进阶 lmql.ai 2026-10-11 18:10:44 · 31 阅读
第2章 LMQL 0.7 发布:引入过程式提示编程与嵌套查询功能
LMQL 开发者调研 LMQL Developer Survey
2024年2月14日
我们启动了一项名为“LMQL 开发者调研”的新计划。通过这个简短的问卷,我们希望能从 LMQL 及更广泛的 LLM 社区中获取更多见解。我们正在收集更广泛的反馈信号,以了解人们目前如何使用 LMQL,或希望用它来做什么。
这份调研的结果将帮助我们规划 LMQL 下一个主要版本的工作方向。
你可以在此处访问调研:https://forms.gle/pGvAicNpUhS1rAkK9。
LMQL 0.7 带来过程式提示编程 LMQL 0.7 brings Procedural Prompt Programming
2023年10月10日
今天,我们发布了 LMQL 0.7。这是自首次发布以来最大的一次更新,包含许多来自社区的贡献。除了嵌套查询、Generations API 和 Chat API 等多项核心新功能外,它还包含几个实验性预览功能,让你能在功能正式发布前先行试用。
LMQL 0.7 也已转向语义化版本控制(Semantic Versioning),其直接前一个版本为 0.0.6.6。这意味着下一个功能版本将是 0.8,而下一个修复版本将是 0.7.1。
用于过程式提示编程的嵌套查询
在 0.7 版本中,你可以通过嵌套查询(Nested Queries)在另一个查询的上下文中,将一个 LMQL 查询作为嵌套函数调用。为此,LMQL 实现了面向提示的过程式编程。举例来说,请考虑以下示例:
lmql# 思维链提示策略
@lmql.query
def chain_of_thought():
'''lmql
"A: Let's think step by step.\n [REASONING]"
"Therefore the answer is[ANSWER]" where STOPS_AT(ANSWER, ".")
return ANSWER.strip()
'''
# 顶级查询
"Q: It is August 12th, 2020. What date was it \
100 days ago? [ANSWER: chain_of_thought]"
ANSWER # May 4th, 2020
我们首先定义一个简单的 LMQL 函数 chain_of_thought,用于执行思维链(chain-of-thought)提示。在顶级查询中,我们可以调用此函数,使用 [ANSWER: chain_of_thought] 语法来解码答案。执行期间,LMQL 会将 chain_of_thought 中的指令和约束插入顶级查询,为 ANSWER 生成一个值,然后再次移除这些指令和约束,仅返回最终结果。
嵌套查询本质上是提示函数调用(Prompt Function Calls)。这种嵌套查询的设计灵感来源于传统编程中的函数或过程调用概念。移除中间指令和约束的做法也与栈展开(stack unwinding)思想有着异曲同工之妙,这是一种在低级语言中实现函数调用的技术。
LMQL 将这些理念迁移到了提示工程中,从而继承了过程式编程的普遍优势:
封装与模型聚焦:嵌套查询封装并隐藏了生成 ANSWER 所使用的提示逻辑,这意味着我们的顶级查询更加整洁、简洁。此外,通过在顶级查询上下文中向模型隐藏中间指令,我们可以减少整体提示中的噪声,让模型专注于当前相关信息,而不会被之前的中间步骤分散注意力。
嵌套与复用:LMQL 查询可以任意深度嵌套,允许你模块化和复用查询。例如,你可以定义一个 get_year 查询来从响应文本中提取年份,然后在 chain_of_thought 中使用该查询从问题中提取日期。通过为子提示实现模块化,嵌套查询还允许你在不同的查询程序中复用提示。
要了解关于嵌套查询的更多信息,请参阅文档的相关章节。
Generations API
LMQL 0.7 增加了 Generations API,这是一个基于 LMQL 的轻量级高层文本生成与评分库。该 API 旨在易于使用,用户无需自己编写任何 LMQL 代码:
python# 获取模型实例
m: lmql.LLM = lmql.model("openai/gpt-3.5-turbo-instruct")
# 简单生成
m.generate_sync("Hello", max_tokens=10)
# -> Hello, I am a 23 year old female.
LLM.generate 和 LLM.score 等函数允许你使用任何支持 LMQL 的推理后端来生成或评估文本。Generations API 与标准 LMQL 完美兼容,你可以按需自由切换或组合使用这两者。 如需更多信息,请参阅文档。 聊天功能 LMQL 0.7 新增了一个聊天 API,只需几行 LMQL 代码即可轻松部署聊天机器人。 LMQL Chat 配备了自定义输出写入器,支持通过 WebSocket、HTTP 和 SSE 等多种渠道轻松流式传输聊天机器人的输入和输出。我们还添加了一个简单的 lmql chat CLI 工具,你可以用它将 LMQL 查询即时启动为完全交互式的聊天机器人。 我们还提供了关于如何使用 LMQL 开发聊天机器人的文档资源,包括关于聊天机器人服务、内部推理以及防御提示词注入等章节。如需更多信息,请参阅文档。 后端支持 LMQL 0.7 带来了三个用于推理和分词的新后端: LMQL 0.7 支持 OpenAI 新发布的 gpt-3.5-turbo-instruct 模型。与其他 3.5 系列模型不同,该变体支持 Completions API,这意味着 LMQL 约束与其兼容。 LMQL 现在支持将模型托管在 replicate.com 基础设施上,允许你在云端运行 LMQL 模型。了解更多,请参阅文档。感谢社区成员 @charles-dyfis-net 的贡献! LMQL 添加了 sentencepiece 作为额外的分词后端,专门针对 llama.cpp 模型。这意味着 llama.cpp 模型现在可以在不依赖 transformers 进行分词的情况下使用。感谢社区成员 @khushChopra 的贡献。 推理证书 为了让 LLM 推理更加透明且可复现,LMQL 0.7 还添加了推理证书。推理证书是一种简单的数据结构,记录复现推理结果所需的关键信息。任何在 LMQL 上下文中发生的 LLM 调用都可以生成证书。 要生成推理证书,请在查询或 generate 调用中传入 certificate=True 或 certificate=:
truncated# 调用并保存证书
say_hello(certificate="my-certificate.json")
生成的证书文件提供了一种记录、追踪和复现 LLM 推理结果的方法,它记录了确切的(分词后的)提示词以及环境和生成参数信息。
这有助于更好地理解推理过程中的动态,调试问题,并复现结果。它还提供了一种记录 LLM 失败案例的方式,有助于更好地引导关于 LLM 具体能力和局限性的讨论。
装饰器
变量装饰器提供了一种简单的新方法,可以在 LMQL 的核心生成循环中调用自定义 Python 函数:
lmqldef screaming(value):
"""将字符串转换为大写的装饰器"""
return value.upper()
"Say 'this is a test':[@screaming TEST]"
promptdownSay 'this is a test': TEST THIS IS A TEST
与 Python 装饰器类似,LMQL 装饰器是接收变量作为输入并可以包装和修改其值的函数。
在上面的例子中,我们使用 @screaming 装饰器将 TEST 的值转换为大写。装饰器可用于实现广泛的自定义功能,包括字符串规范化、数据类型转换等。LMQL 还提供了允许在生成期间流式传输或预处理数据的装饰器。如需更多信息,请参阅文档。
文档更新
网站和 LMQL 文档中的许多章节也已更新和扩展,现在包含更多的示例和解释。我们更新了视觉设计,使其更易于阅读和导航。
文档现在还包括一份正在编写中的语言参考,旨在集中提供 LMQL 语法和语义更全面、更正式的描述。
预览功能
除了许多新的核心功能外,LMQL 0.7 还附带了几个实验性的预览功能,允许你在这些功能完全稳定并作为主线功能发布之前进行试用。
这些功能被标记为实验性,尚未得到完全支持。我们发布它们是为了收集反馈,并允许用户提前进行测试。请注意,这些功能可能会发生变化,并可能在未来的版本中删除或修改。
LMQL Actions 预览
LMQL Actions 是 LMQL 函数调用层的第一版。它允许你将任意 Python 函数暴露给 LLM 推理循环,并让模型在生成过程中调用它们。函数演示和调用协议都可以由 LMQL 运行时自动处理,从而允许像下面这样简单的使用:
def wiki(q): ...
def calc(expr): ...
"Q: What is the population of the US and Germany combined?"
"A: [REASONING]" where inline_use(REASONING, [wiki, calc])
未来的版本将带来更多关于 Actions 的文档和细节,包括如何根据你的用例使用和定制它。在那之前,我们欢迎大家尝试并挖掘当前实现的所有可能性,该实现完全包含在 actions.py 中。
正则表达式约束预览
LMQL 现在支持正则表达式约束,允许你使用正则表达式来约束变量的输出。例如,以下查询将始终生成 DD/MM 格式的有效日期:
"It's the last day of June so today is [RESPONSE]" where REGEX(RESPONSE, r"[0-9]{2}/[0-9]{2}")
类型 / 数据类型约束预览
LMQL 正朝着完全类型化的 LLM 生成迈进。在此过程中,我们开始支持 dataclass 约束,允许你将变量输出约束为特定的结构化输出模式:
lmqlimport lmql
from dataclasses import dataclass
@dataclass
class Person:
name: str
age: int
job: str
"Alice is a 21 years old and works as an engineer at LMQL Inc in Zurich, Switzerland.\n"
"Structured: [PERSON_DATA]\n" where type(PERSON_DATA) is Person
PERSON_DATA
# Person(name='Alice', age=21, job='engineer')
为了实现这一点,LMQL 利用受限生成,确保 LLM 始终生成填充有效 Person 对象所需的所有信息。生成的 PERSON_DATA 对象随后可以直接像常规 Python 对象一样使用。类型功能仍处于早期阶段,我们正在努力添加更多功能和特性。
其他变更
LMQL playground 现在可以从 Windows cmd.exe 中使用。感谢社区成员 @mosheduminer。
LMQL/LMTP 模型后端现在可以作为 Langchain LLM 对象访问,以便在你的 Langchain 管道中使用。感谢 @4onon 的贡献。
LMQL 现在可以作为 NixOS 包安装。感谢 @charles-dyfis-net 的贡献。
🎬 就此完结!
LMQL 0.7 是一个大型版本更新,我们很期待看到你用它能构建什么。一如既往地,如果你有任何问题、建议或 bug 报告,请通过 GitHub、Discord、Twitter 或邮件 hello@lmql.ai 告诉我们。
LMQL v0.0.6.6
2023年7月25日
我们刚刚发布了 LMQL 0.0.6.6。这是一个次要更新,包含几个较小的修复和改进。
lmql.F 现在支持位置参数:
pythongreet = lmql.F("Greet {a} and {b}: [GREETING]")
# 使用位置参数调用
greet("Alice", "Bob") # Greet Alice and Bob: Hello!
# 使用关键字参数调用
greet(a="Alice", b="Bob") # Greet Alice and Bob: Hello!
我们改进了 llama.cpp 后端的错误处理,并修复了模型标识符解析的 bug。
我们还修复了 LMTP 调度器的一个 bug,之前即使没有任务存在,CPU 负载也很高。感谢社区成员 @4onen 报告并修复此问题!
添加了后端对 auto_gptq 量化模型的支持,由社区成员 @meditans 贡献。
我们修复了一个问题,之前对于 Azure OpenAI 模型需要配置一个虚拟的 api.env。详见我们的文档。感谢社区成员 Missing 和 @hooman-bayer 提供的反馈和贡献。
版本说明:0.0.6.6 是最后一个带有两个前导零的版本。从下一个版本开始,LMQL 将采用语义化版本控制,并使用单个前导零,例如 0.6.7。
LMQL 简化并新增 llama.cpp 支持
2023年7月13日
今天我们要发布 LMQL 0.0.6.5。此更新对 LMQL 语法进行了重大简化,使其更接近标准 Python。它还包含基于 llama.cpp 的推理后端、多个 bug 修复和其他小改进。
你可以在浏览器中的 lmql.ai/playground 试用最新版本的 LMQL,或通过 pip install lmql 安装它。
一行即可搞定
最显著的是,0.0.6.5 带来了 LMQL 核心语法的多项简化。当然,所有更改都是向后兼容的,你可以继续使用现有的查询代码,无需任何更改即可迁移到新版本。
通过这种方式,我们旨在最小化语法开销,采用合理的默认值以启用更简洁的程序,如下所示:
"One line is all it takes [CONTINUATION]"
promptdownOne line is all it takes CONTINUATIONFallin' in love with me.
合理默认值之所以可能,是因为 LMQL 现在自动假设 argmax 和 openai/text-davinci-003 为(可配置的)默认模型。如果你更喜欢使用不同的模型或自定义解码器设置,仍可以显式指定它们,例如在后文演示的 @lmql.query 装饰器函数中。
无需任何额外配置,上述简单的查询代码将翻译为完整的 LMQL 程序,如下所示:
argmax "One line is all it takes [CONTINUATION]" from "openai/text-davinci-003"
行内约束 LMQL 现在支持指定多个行内 where 约束。这使得约束可以引用局部程序变量,从而允许当前约束依赖于模型之前的输出。 "A list of awesome Dua Lipa songs:\n" songs = [] "- New Rules\n" for i in range(4): "-[SONG]\n" where STOPS_BEFORE(SONG, "\n") songs.append(SONG) "Out of these, my favorite is[FAVORITE]" where FAVORITE in songs promptdownDua Lipa 的热门歌曲列表: - New Rules - SONGDon't Start Now - SONGIDGAF - SONGBe the One - SONGBlow Your Mind (Mwah) 在这些歌曲中,我最喜欢的是FAVORITEDon't Start Now 注意,在此示例中,LMQL 代码现在读起来更像标准的 Python 代码,且没有额外的缩进层级。
@lmql.query 函数 重构后的语法也大大提高了 LMQL 的可读性,尤其是在 Python 中配合 @lmql.query 函数装饰器使用时: pythonimport lmql import json @lmql.query(model="openai/text-curie-001", temperature=0.9) def summarize(): '''lmql """ 提供流行音乐偶像 Dua Lipa 的简介: {{ "name": "[STRING_VALUE]", "chart_position": [INT_VALUE], "top_songs": [[ "[STRING_VALUE]", "[STRING_VALUE]" ]] }} """ where STOPS_BEFORE(STRING_VALUE, '"') and INT(INT_VALUE) and len(TOKENS(INT_VALUE)) < 3 return json.loads(context.prompt.split("pop icon:",1)[1]) ''' print(summarize()) # {'name': 'Dua Lipa', 'chart_position': 3415, 'top_songs': ['New Rules', 'Havana']}
lmql.F Lambda 函数 基于 LMQL 新的极简语法,我们引入了编写基于 LLM 的 lambda 函数的一种新颖且简洁的方式。这为在代码中集成小型 LLM 实用工具提供了轻量级的入口,无需编写完整的 LMQL 程序。 pythonimport lmql summarize = lmql.F("Summarize the following in a few words: {data}: [SUMMARY]") main_subject = lmql.F("What is the main subject (noun) of the following text? {data}: [SUBJECT]", "len(TOKENS(SUBJECT)) < 20") text = "In LMQL, users can specify high-level, logical constraints ..." summarize(data=text) # LMQL enables high-level constraints to be enforced during text # generation, simplifying multi-part prompting and integration. main_subject(data=text) # Language Model Query Language (LMQL)
llama.cpp 推理后端 LMQL 现已完全集成 llama.cpp——一个优秀的 C++ 实现,支持多种基于 Transformer 的语言模型。 在 LMQL 中使用 llama.cpp 非常简单,只需在查询的 from 子句中指定即可: argmax "Say 'this is a test':[RESPONSE]" from "llama.cpp:.bin"
我们支持在进程内直接加载 llama.cpp,也支持通过 lmql serve-model 进行远程推理。想了解更多关于 llama.cpp 及其在 LMQL 中的用法,请查阅 LMQL 文档中的相应章节。
其他更新 - LMQL 新增了 random 模型后端,可以从 GPT-2 词表中随机采样 token。这对调试和测试很有用,也可用于高度约束查询程序中的数据生成。 - 修复了两个缓存问题,避免了可能导致模型输出重复的缓存冲突。 - 更稳健的查询字符串解析,支持对特殊字符 [、]、{ 和 } 的可靠转义。 - 新增对基于 transformers 的 Llama 模型及其 HF tokenizer(快速版)的支持。 - 简化了 Azure OpenAI 的支持,详见文档中的相关章节。 感谢社区成员 @minosvasilias 和 @CircArgs 对本次发布的贡献。 发布 LMQL v0.0.6.4:LMTP、Azure、同步 API 等 2023 年 6 月 8 日 本次更新包含多项 bug 修复和改进,其中最重要的变化有: - Azure OpenAI 支持 LMQL 现已支持通过 Azure 托管的 OpenAI 模型。关于如何使用 Azure 模型,请参阅文档中的相应章节。感谢 @veqtor 贡献此功能。 - 基于 LMTP 的本地模型支持 LMQL 0.0.6.4 实现了一种全新的协议,用于流式传输本地模型的 token 输出,性能大幅提升。在首批基准测试中,本地模型推理速度提升了 5-6 倍。关于如何使用本地模型,请参阅文档中的相应章节。 想深入了解新流式协议的内部原理,即语言模型传输协议(LMTP),可查阅这个 README 文件。未来我们计划基于 LMTP 实现更多模型后端,进一步简化 LMQL 与模型之间的通信。 LMQL 的全新流式协议(LMTP)支持更快的本地模型推理。 同步 Python API 除了基于 async/await 的 API 之外,LMQL 现在也提供了同步 API。这意味着你在 Python 中调用 LMQL 时不再需要依赖 asyncio。 要使用同步 API,只需声明不带 async 关键字的 @lmql.query 函数,例如: pythonimport lmql @lmql.query def hello(s: str): '''lmql argmax "Hello {s} [RESPONSE]" return RESPONSE from "chatgpt" ''' print(hello("world")) # ['Hello! How can I assist you today?'] 如果希望在同步上下文中使用 lmql.run,现在可以改用 lmql.run_sync。欲了解更多在 Python 中使用 LMQL 的方法,请查阅我们的文档。 优化的 Tokenizer 后端 LMQL 现在可以使用出色的 tiktoken tokenizer 作为分词后端(适用于 OpenAI 模型)。此外,所有分词后端都已移植为在字节层面操作,这提升了对多字节字符和表情符号的支持。这对于非英语语言和特殊字符尤为重要。 Docker 镜像 LMQL 现在提供了一个 Docker 镜像,可用于在容器化环境中运行 LMQL playground。详情请参阅文档。非常感谢 @SilacciA 为该功能的贡献。 更快的启动时间 我们优化了 LMQL 的导入层级,从而加快了模块加载速度。 LMQL Release v0.0.6.3LMQL v0.0.6.3 2023 年 5 月 11 日 今天,我们发布了 LMQL v0.0.6.3。此更新包含多项 bug 修复和改进。主要变更如下: 更轻量的运行时 作为持续优化工作的一部分,我们让 LMQL 变得更加轻量(不再强制依赖 transformers)。默认情况下,LMQL 不再需要 transformers 或 PyTorch。如果你依赖本地模型,只需通过 pip install lmql[hf] 安装 LMQL,即可获得完整的 Transformers 集成。 Token 约束 在 LMQL 约束语言中新增了一个函数 TOKENS(...),允许你为给定变量指定生成 token 的下限、上限或确切数量。 argmax "A 10 token response[WHO]" from "openai/text-ada-001" where len(TOKENS(WHO)) == 10 条件停止 STOPS_AT 现在可以与额外的侧边条件结合使用。这允许你指定仅在满足其他条件时才强制执行的停止短语。 例如,在下面,当生成的文本遇到换行符时停止,但前提是整体变量输出长度至少为 10 个 tokens。 argmax "Hello[WHO]" from "openai/text-ada-001" where len(TOKENS(WHO)) > 10 and STOPS_AT(WHO, "\n") lmql.run: 由 @lfegray 贡献,提升了 lmql.run 的输入验证。具体来说,当客户端逻辑未为所有必需的查询参数指定输入值时,lmql.run 现在会提供更有用的错误消息。 自动缓存失效:LMQL 位于 ~/.cache/lmql 的 tokenizer 缓存现在会在升级到新版本时自动失效。这应该能防止因过期缓存文件导致的问题。 注意:版本 0.0.6.2 已被跳过并从 pypi.org 撤回,因为意外推送了一个无效发布。 LMQL Release v0.0.6.1LMQL v0.0.6.1 2023 年 5 月 3 日 我们发布了 LMQL v0.0.6.1,该版本包含若干 bug 修复和改进。主要变更如下: 缓存层 Bug 修复 此版本包含若干针对新近引入的缓存层的修复和改进。 停止短语 通过 STOPS_BEFORE 指定的停止短语现在会作为 "stop" 参数传递给 OpenAI API,从而减少请求所用的 token 数量。如果你想禁用此功能(例如允许推测执行),可以指定新的解码器参数 openai_nonstop=True。 异步输出写入器 所有输出写入器都已重构为使用异步 I/O。这应该能简化与其他异步框架的集成,例如 HTTP 或 Websocket API。我们还在文档中新增了关于输出流式传输的一章。 用于 HTTP 端点、WebSockets 和 Server-Sent Events 的输出写入器 基于更新后的输出写入器接口,我们添加了三种新的输出写入器,分别用于将 LMQL 查询作为 HTTP 端点、WebSockets 和 Server-Sent Events (SSE) 提供服务。欲了解更多信息,可查看新 lmql.output 模块中相对简单的实现。未来我们还会提供更多关于如何使用它们的文档,例如结合 aiohttp。 发布 LMQL 缓存层 (v0.0.6)Releasing the LMQL Caching Layer (v0.0.6) 2023 年 5 月 1 日 今天,我们发布了 LMQL 0.0.6,这是首个集成 LMQL 缓存层的版本。缓存层可以大幅降低 LLM 交互的 token 使用量,从而降低运行查询的成本和延迟。在这篇博文中,我们简要概述了缓存层,并演示它如何将运行查询所需的 token 使用量、延迟和请求次数最多减少 80%。我们在多种不同场景中观察到改进,包括基于模板的查询、长格式约束和工具增强。 你可以在基于浏览器的 Playground IDE 中实验 LMQL,或通过 pip install lmql 在本地安装最新版本。 缓存层 缓存层实现为一种基于树的数据结构,它缓存所有模型输出(包括 logits、tokens 和元数据),使运行时能够更有效地探索 LLM 的 token 空间,即使存在多个变量、约束和工具增强的情况。缓存可以被视为一个仅追加的树,在查询执行期间被探索,并根据查询代码、约束和推测执行扩展分支。 为了说明缓存层的效果,我们考虑了以下几个示例场景,这些场景现在都以传统查询方法所需时间和 token 数量的一小部分运行完成。 基于模板的查询 当指定带有多个变量以填充的提示模板时,LLM 通常每个变量需要调用一次。例如,考虑以下引导 LLM 生成列表的模板: argmax "A list of things not to forget when going to the sea (not travelling): \n" "- Sunglasses \n" "-[THING]" "-[THING]" "-[THING]" "-[THING]" from 'openai/text-ada-001' where STOPS_AT(THING, "\n") 无缓存:Tokens: 390, 请求:4 | 带缓存层:Tokens: 89 (-77%), 请求:1 (-75%) 这里,LLM 通常需要调用 4 次,每个 [THING] 变量调用一次。每次调用都会产生 token 和延迟成本(无论是 OpenAI 还是本地模型)。之所以需要单独调用,是因为我们的模板规定在每个 [THING] 之前插入 - token。 使用缓存层后,LMQL 现在只需调用一次 LLM,并用结果 tokens 填充所有变量,只要 LLM 输出自然与你的模板对齐。如果 LLM 在初始调用的结果中某处不再与模板对齐,LMQL 将从该点自动重新调用 LLM,保证整体结果一致且已解析为单独的 [THING] 变量。 短路长约束 当你指定像 A in ["ABCDE", "FGHIJK"] 这样的长约束时,LMQL 运行时会引导 LLM 选择其中一个选项,然后继续强制序列,直到选定的值完全解码完成。为了说明,考虑以下查询: argmax "If we have the choice we choose[OPTION]" from "openai/text-ada-001" where OPTION in ["Option A with a whole lot of extra context", "Option B with context", "Another Option, also with a lot of additional text" ] promptdownIf we have the choice we choose OPTIONOption A with a whole lot of extra context 无缓存:Tokens: 123, 请求:9 | 带缓存层:Tokens: 25 (-80%), 请求:2 (-78%) 这里,在 LLM 产生 "Option" 然后是 " A" 之后,LMQL 短路进一步的模型调用,并自动完成结果为 "Option A with a whole lot of extra context"。之所以可能,是因为一旦预测出 Option A,剩余的 tokens 完全由约束确定。 工具增强的查询 最后,我们考虑工具增强的查询。LLM 代理和工具增强是非常强大的范式,允许 LLM 将外部知识和推理纳入其预测。然而,这有代价:每次工具调用时,LLM 都需要重新调用以在插入工具输出后继续解码。这影响了运行查询的 token 成本和延迟,因为 LLM 和工具之间必须发送和接收许多请求。 作为一个例子,考虑以下增强 LLM 使用键值存储能力的查询,也可以在基于浏览器的 LMQL Playground 中运行。 无缓存:Tokens: 5,162, 请求:12 | 带缓存层:Tokens: 3,481 (-33%), 请求:8 (-33%) 这里,每当 LLM 产生与我们键值存储相关的动作时,我们就调用一个处理存储并返回结果(用于分配和获取存储值)的工具。每次工具调用的结果随后插入 LLM 输出,并重新调用 LLM 继续解码。 我们计算了 10 次工具交互,如果不使用缓存,结果是 12 个请求。然而,使用新的缓存层,我们可以将其减少到 8 个请求,甚至低于工具交互次数。之所以可能,是因为如果 LLM 已经正确预测了工具输出,缓存层不会中止 LLM 生成。 此场景表明,LLM 完成序列的自然能力可以通过依赖推测执行来利用,从而减少工具交互次数。 持久化缓存 当然,LMQL 运行时的内存缓存也可以持久化到磁盘,允许你在多个查询之间重用缓存树,从而自动降低 token 成本和延迟。在某些情况下,这甚至可以将 LLM 的请求次数减少到 0,例如如果缓存已经包含所需结果。 为此,你只需在查询代码中指定 cache="file.tokens" 参数: argmax(cache="joke.tokens") """A good dad joke. A indicates the punchline Q:[JOKE] A:[PUNCHLINE]""" from "openai/text-davinci-003" where len(JOKE) < 120 and STOPS_AT(JOKE, "?") and STOPS_AT(PUNCHLINE, "\n") and len(PUNCHLINE) > 1 此查询的首次成功运行会将缓存持久化到 joke.tokens。后续运行会自动从磁盘加载缓存,仅在缓存不包含匹配项时才调用 LLM。这也适用于底层 LLM 请求仅部分重叠的查询,因为基于树的缓存数据结构会自动识别匹配的 subtree。 查询开发期间的缓存:持久化缓存在查询开发期间特别有用,因为它允许你在同一查询的多次运行之间重用缓存。即使你在运行之间稍微更改查询,持久化缓存也会降低查询的 token 成本和延迟。 注意事项和禁用缓存 你可以在查询代码中指定 cache=False 来禁用缓存层。这将导致 LMQL 运行时始终调用 LLM,从不使用缓存。这对于调试目的或确保 LLM 始终被调用的情况很有用。 此外,由于缓存当前实现为仅追加的数据结构,它会无限增长。这对长期运行的应用程序可能有问题,因为缓存最终会增长到相对较大的尺寸。未来,我们计划实现限制缓存大小的简单策略,例如最近最少使用 (LRU) 驱逐策略。 结论 在这篇文章中,我们介绍了 LMQL 运行时的新缓存层,它允许你通过重用以前生成的 LLM 输出来降低查询的 token 成本和延迟。我们演示了缓存层如何在多种场景中减少 LLM 调用次数,包括长约束、短路和工具增强的查询。我们还展示了如何将缓存持久化到磁盘,从而在多个查询之间重用缓存。 欲了解更多关于 LMQL 的信息,也请查阅我们的文档,或加入我们的 Discord 直接与我们交流。期待你的反馈! LMQL Release 0.0.5LMQL Release 0.0.5 2023 年 4 月 17 日 今天,我们发布了 LMQL 版本 0.0.5。此版本专注于稳定性和性能改进。详细变更列表见下文。我们特别高兴的是,首批社区贡献已作为此版本的一部分合并,还有许多其他贡献正在进行中。 lmql==0.0.5 已发布在 PyPI 上,基于 GitHub 仓库当前的 main 分支。更新版本也已部署到基于浏览器的 lmql.ai/playground。 Changelog 解码器性能 argmax 和 sample 解码器进行了一些优化,使其运行更快。这在常见查询工作负载上带来 20-30% 的速度提升。#24. 后处理语义 内部而言,LMQL 现在允许约束实现后处理语义。这用于在变量值完成后,将其转换为提示中更规范化的形式,以及在查询代码上下文中语义有意义的数据类型。#24. 例如,当对生成的数字使用 INT() 约束时,模型将仅限于生成有效整数,现在,结果 NUM 值还将转换为 int 值: argmax "My favorite number is: [NUM]\n" print(type(NUM), NUM * 2) # 4
"Number times two is {NUM * 2}"
from
'openai/text-ada-001'
where
INT(NUM)
核心解释器 LMQL 核心解释器的完整重新实现已完成。这修复了一些小问题,并总体上提高了处理分支解码算法时的可靠性和性能。#24.
Playground 本地使用和浏览器内使用时,LMQL Playground 现在会增量流式传输来自 LMQL 解释器的调试器信息。这在 Playground 中运行时带来加速,尤其是对于较长的输出。#27f9a8ad.
其他修复:
从 Python 内部使用时(作为装饰函数),LMQL 代码不再需要双重转义,例如,现在可以写 STOPS_AT(VAR, "\n") 而不是 STOPS_AT(VAR, "\\n")
LMQL 推理 API 会缓冲启动期间到达的请求,以避免服务器尚未就绪时的错误。#15,感谢 @chrispan。
OpenAI 请求并行化在 Linux 系统上不再导致关于 worker 进程的错误 #6。
预览
除上述变更外,我们正在开发许多其他功能,包括:
llama.cpp 支持 始于这个 PR,感谢 @CircArgs。
类型约束支持 例如 type(VAR) is DataClass,自动强制模型生成在结构上符合给定类型的值。更多细节见这个 Twitter thread。
支持在查询执行期间使用 Antlr 解析器,以强制模型生成符合给定语法的值。
将 Logit Masking 扩展到 OpenAI Chat Models。这将实现 LMQL 约束的完整支持,例如用于 chatgpt 和 gpt-4 模型。见 #25,感谢 @kharvd。
LLM.generate 和 LLM.score 等函数允许你使用任何支持 LMQL 的推理后端来生成或评估文本。Generations API 与标准 LMQL 完美兼容,你可以按需自由切换或组合使用这两者。 如需更多信息,请参阅文档。 聊天功能 LMQL 0.7 新增了一个聊天 API,只需几行 LMQL 代码即可轻松部署聊天机器人。 LMQL Chat 配备了自定义输出写入器,支持通过 WebSocket、HTTP 和 SSE 等多种渠道轻松流式传输聊天机器人的输入和输出。我们还添加了一个简单的 lmql chat CLI 工具,你可以用它将 LMQL 查询即时启动为完全交互式的聊天机器人。 我们还提供了关于如何使用 LMQL 开发聊天机器人的文档资源,包括关于聊天机器人服务、内部推理以及防御提示词注入等章节。如需更多信息,请参阅文档。 后端支持 LMQL 0.7 带来了三个用于推理和分词的新后端: LMQL 0.7 支持 OpenAI 新发布的 gpt-3.5-turbo-instruct 模型。与其他 3.5 系列模型不同,该变体支持 Completions API,这意味着 LMQL 约束与其兼容。 LMQL 现在支持将模型托管在 replicate.com 基础设施上,允许你在云端运行 LMQL 模型。了解更多,请参阅文档。感谢社区成员 @charles-dyfis-net 的贡献! LMQL 添加了 sentencepiece 作为额外的分词后端,专门针对 llama.cpp 模型。这意味着 llama.cpp 模型现在可以在不依赖 transformers 进行分词的情况下使用。感谢社区成员 @khushChopra 的贡献。 推理证书 为了让 LLM 推理更加透明且可复现,LMQL 0.7 还添加了推理证书。推理证书是一种简单的数据结构,记录复现推理结果所需的关键信息。任何在 LMQL 上下文中发生的 LLM 调用都可以生成证书。 要生成推理证书,请在查询或 generate 调用中传入 certificate=True 或 certificate=
行内约束 LMQL 现在支持指定多个行内 where 约束。这使得约束可以引用局部程序变量,从而允许当前约束依赖于模型之前的输出。 "A list of awesome Dua Lipa songs:\n" songs = [] "- New Rules\n" for i in range(4): "-[SONG]\n" where STOPS_BEFORE(SONG, "\n") songs.append(SONG) "Out of these, my favorite is[FAVORITE]" where FAVORITE in songs promptdownDua Lipa 的热门歌曲列表: - New Rules - SONGDon't Start Now - SONGIDGAF - SONGBe the One - SONGBlow Your Mind (Mwah) 在这些歌曲中,我最喜欢的是FAVORITEDon't Start Now 注意,在此示例中,LMQL 代码现在读起来更像标准的 Python 代码,且没有额外的缩进层级。
@lmql.query 函数 重构后的语法也大大提高了 LMQL 的可读性,尤其是在 Python 中配合 @lmql.query 函数装饰器使用时: pythonimport lmql import json @lmql.query(model="openai/text-curie-001", temperature=0.9) def summarize(): '''lmql """ 提供流行音乐偶像 Dua Lipa 的简介: {{ "name": "[STRING_VALUE]", "chart_position": [INT_VALUE], "top_songs": [[ "[STRING_VALUE]", "[STRING_VALUE]" ]] }} """ where STOPS_BEFORE(STRING_VALUE, '"') and INT(INT_VALUE) and len(TOKENS(INT_VALUE)) < 3 return json.loads(context.prompt.split("pop icon:",1)[1]) ''' print(summarize()) # {'name': 'Dua Lipa', 'chart_position': 3415, 'top_songs': ['New Rules', 'Havana']}
lmql.F Lambda 函数 基于 LMQL 新的极简语法,我们引入了编写基于 LLM 的 lambda 函数的一种新颖且简洁的方式。这为在代码中集成小型 LLM 实用工具提供了轻量级的入口,无需编写完整的 LMQL 程序。 pythonimport lmql summarize = lmql.F("Summarize the following in a few words: {data}: [SUMMARY]") main_subject = lmql.F("What is the main subject (noun) of the following text? {data}: [SUBJECT]", "len(TOKENS(SUBJECT)) < 20") text = "In LMQL, users can specify high-level, logical constraints ..." summarize(data=text) # LMQL enables high-level constraints to be enforced during text # generation, simplifying multi-part prompting and integration. main_subject(data=text) # Language Model Query Language (LMQL)
llama.cpp 推理后端 LMQL 现已完全集成 llama.cpp——一个优秀的 C++ 实现,支持多种基于 Transformer 的语言模型。 在 LMQL 中使用 llama.cpp 非常简单,只需在查询的 from 子句中指定即可: argmax "Say 'this is a test':[RESPONSE]" from "llama.cpp:
其他更新 - LMQL 新增了 random 模型后端,可以从 GPT-2 词表中随机采样 token。这对调试和测试很有用,也可用于高度约束查询程序中的数据生成。 - 修复了两个缓存问题,避免了可能导致模型输出重复的缓存冲突。 - 更稳健的查询字符串解析,支持对特殊字符 [、]、{ 和 } 的可靠转义。 - 新增对基于 transformers 的 Llama 模型及其 HF tokenizer(快速版)的支持。 - 简化了 Azure OpenAI 的支持,详见文档中的相关章节。 感谢社区成员 @minosvasilias 和 @CircArgs 对本次发布的贡献。 发布 LMQL v0.0.6.4:LMTP、Azure、同步 API 等 2023 年 6 月 8 日 本次更新包含多项 bug 修复和改进,其中最重要的变化有: - Azure OpenAI 支持 LMQL 现已支持通过 Azure 托管的 OpenAI 模型。关于如何使用 Azure 模型,请参阅文档中的相应章节。感谢 @veqtor 贡献此功能。 - 基于 LMTP 的本地模型支持 LMQL 0.0.6.4 实现了一种全新的协议,用于流式传输本地模型的 token 输出,性能大幅提升。在首批基准测试中,本地模型推理速度提升了 5-6 倍。关于如何使用本地模型,请参阅文档中的相应章节。 想深入了解新流式协议的内部原理,即语言模型传输协议(LMTP),可查阅这个 README 文件。未来我们计划基于 LMTP 实现更多模型后端,进一步简化 LMQL 与模型之间的通信。 LMQL 的全新流式协议(LMTP)支持更快的本地模型推理。 同步 Python API 除了基于 async/await 的 API 之外,LMQL 现在也提供了同步 API。这意味着你在 Python 中调用 LMQL 时不再需要依赖 asyncio。 要使用同步 API,只需声明不带 async 关键字的 @lmql.query 函数,例如: pythonimport lmql @lmql.query def hello(s: str): '''lmql argmax "Hello {s} [RESPONSE]" return RESPONSE from "chatgpt" ''' print(hello("world")) # ['Hello! How can I assist you today?'] 如果希望在同步上下文中使用 lmql.run,现在可以改用 lmql.run_sync。欲了解更多在 Python 中使用 LMQL 的方法,请查阅我们的文档。 优化的 Tokenizer 后端 LMQL 现在可以使用出色的 tiktoken tokenizer 作为分词后端(适用于 OpenAI 模型)。此外,所有分词后端都已移植为在字节层面操作,这提升了对多字节字符和表情符号的支持。这对于非英语语言和特殊字符尤为重要。 Docker 镜像 LMQL 现在提供了一个 Docker 镜像,可用于在容器化环境中运行 LMQL playground。详情请参阅文档。非常感谢 @SilacciA 为该功能的贡献。 更快的启动时间 我们优化了 LMQL 的导入层级,从而加快了模块加载速度。 LMQL Release v0.0.6.3LMQL v0.0.6.3 2023 年 5 月 11 日 今天,我们发布了 LMQL v0.0.6.3。此更新包含多项 bug 修复和改进。主要变更如下: 更轻量的运行时 作为持续优化工作的一部分,我们让 LMQL 变得更加轻量(不再强制依赖 transformers)。默认情况下,LMQL 不再需要 transformers 或 PyTorch。如果你依赖本地模型,只需通过 pip install lmql[hf] 安装 LMQL,即可获得完整的 Transformers 集成。 Token 约束 在 LMQL 约束语言中新增了一个函数 TOKENS(...),允许你为给定变量指定生成 token 的下限、上限或确切数量。 argmax "A 10 token response[WHO]" from "openai/text-ada-001" where len(TOKENS(WHO)) == 10 条件停止 STOPS_AT 现在可以与额外的侧边条件结合使用。这允许你指定仅在满足其他条件时才强制执行的停止短语。 例如,在下面,当生成的文本遇到换行符时停止,但前提是整体变量输出长度至少为 10 个 tokens。 argmax "Hello[WHO]" from "openai/text-ada-001" where len(TOKENS(WHO)) > 10 and STOPS_AT(WHO, "\n") lmql.run: 由 @lfegray 贡献,提升了 lmql.run 的输入验证。具体来说,当客户端逻辑未为所有必需的查询参数指定输入值时,lmql.run 现在会提供更有用的错误消息。 自动缓存失效:LMQL 位于 ~/.cache/lmql 的 tokenizer 缓存现在会在升级到新版本时自动失效。这应该能防止因过期缓存文件导致的问题。 注意:版本 0.0.6.2 已被跳过并从 pypi.org 撤回,因为意外推送了一个无效发布。 LMQL Release v0.0.6.1LMQL v0.0.6.1 2023 年 5 月 3 日 我们发布了 LMQL v0.0.6.1,该版本包含若干 bug 修复和改进。主要变更如下: 缓存层 Bug 修复 此版本包含若干针对新近引入的缓存层的修复和改进。 停止短语 通过 STOPS_BEFORE 指定的停止短语现在会作为 "stop" 参数传递给 OpenAI API,从而减少请求所用的 token 数量。如果你想禁用此功能(例如允许推测执行),可以指定新的解码器参数 openai_nonstop=True。 异步输出写入器 所有输出写入器都已重构为使用异步 I/O。这应该能简化与其他异步框架的集成,例如 HTTP 或 Websocket API。我们还在文档中新增了关于输出流式传输的一章。 用于 HTTP 端点、WebSockets 和 Server-Sent Events 的输出写入器 基于更新后的输出写入器接口,我们添加了三种新的输出写入器,分别用于将 LMQL 查询作为 HTTP 端点、WebSockets 和 Server-Sent Events (SSE) 提供服务。欲了解更多信息,可查看新 lmql.output 模块中相对简单的实现。未来我们还会提供更多关于如何使用它们的文档,例如结合 aiohttp。 发布 LMQL 缓存层 (v0.0.6)Releasing the LMQL Caching Layer (v0.0.6) 2023 年 5 月 1 日 今天,我们发布了 LMQL 0.0.6,这是首个集成 LMQL 缓存层的版本。缓存层可以大幅降低 LLM 交互的 token 使用量,从而降低运行查询的成本和延迟。在这篇博文中,我们简要概述了缓存层,并演示它如何将运行查询所需的 token 使用量、延迟和请求次数最多减少 80%。我们在多种不同场景中观察到改进,包括基于模板的查询、长格式约束和工具增强。 你可以在基于浏览器的 Playground IDE 中实验 LMQL,或通过 pip install lmql 在本地安装最新版本。 缓存层 缓存层实现为一种基于树的数据结构,它缓存所有模型输出(包括 logits、tokens 和元数据),使运行时能够更有效地探索 LLM 的 token 空间,即使存在多个变量、约束和工具增强的情况。缓存可以被视为一个仅追加的树,在查询执行期间被探索,并根据查询代码、约束和推测执行扩展分支。 为了说明缓存层的效果,我们考虑了以下几个示例场景,这些场景现在都以传统查询方法所需时间和 token 数量的一小部分运行完成。 基于模板的查询 当指定带有多个变量以填充的提示模板时,LLM 通常每个变量需要调用一次。例如,考虑以下引导 LLM 生成列表的模板: argmax "A list of things not to forget when going to the sea (not travelling): \n" "- Sunglasses \n" "-[THING]" "-[THING]" "-[THING]" "-[THING]" from 'openai/text-ada-001' where STOPS_AT(THING, "\n") 无缓存:Tokens: 390, 请求:4 | 带缓存层:Tokens: 89 (-77%), 请求:1 (-75%) 这里,LLM 通常需要调用 4 次,每个 [THING] 变量调用一次。每次调用都会产生 token 和延迟成本(无论是 OpenAI 还是本地模型)。之所以需要单独调用,是因为我们的模板规定在每个 [THING] 之前插入 - token。 使用缓存层后,LMQL 现在只需调用一次 LLM,并用结果 tokens 填充所有变量,只要 LLM 输出自然与你的模板对齐。如果 LLM 在初始调用的结果中某处不再与模板对齐,LMQL 将从该点自动重新调用 LLM,保证整体结果一致且已解析为单独的 [THING] 变量。 短路长约束 当你指定像 A in ["ABCDE", "FGHIJK"] 这样的长约束时,LMQL 运行时会引导 LLM 选择其中一个选项,然后继续强制序列,直到选定的值完全解码完成。为了说明,考虑以下查询: argmax "If we have the choice we choose[OPTION]" from "openai/text-ada-001" where OPTION in ["Option A with a whole lot of extra context", "Option B with context", "Another Option, also with a lot of additional text" ] promptdownIf we have the choice we choose OPTIONOption A with a whole lot of extra context 无缓存:Tokens: 123, 请求:9 | 带缓存层:Tokens: 25 (-80%), 请求:2 (-78%) 这里,在 LLM 产生 "Option" 然后是 " A" 之后,LMQL 短路进一步的模型调用,并自动完成结果为 "Option A with a whole lot of extra context"。之所以可能,是因为一旦预测出 Option A,剩余的 tokens 完全由约束确定。 工具增强的查询 最后,我们考虑工具增强的查询。LLM 代理和工具增强是非常强大的范式,允许 LLM 将外部知识和推理纳入其预测。然而,这有代价:每次工具调用时,LLM 都需要重新调用以在插入工具输出后继续解码。这影响了运行查询的 token 成本和延迟,因为 LLM 和工具之间必须发送和接收许多请求。 作为一个例子,考虑以下增强 LLM 使用键值存储能力的查询,也可以在基于浏览器的 LMQL Playground 中运行。 无缓存:Tokens: 5,162, 请求:12 | 带缓存层:Tokens: 3,481 (-33%), 请求:8 (-33%) 这里,每当 LLM 产生与我们键值存储相关的动作时,我们就调用一个处理存储并返回结果(用于分配和获取存储值)的工具。每次工具调用的结果随后插入 LLM 输出,并重新调用 LLM 继续解码。 我们计算了 10 次工具交互,如果不使用缓存,结果是 12 个请求。然而,使用新的缓存层,我们可以将其减少到 8 个请求,甚至低于工具交互次数。之所以可能,是因为如果 LLM 已经正确预测了工具输出,缓存层不会中止 LLM 生成。 此场景表明,LLM 完成序列的自然能力可以通过依赖推测执行来利用,从而减少工具交互次数。 持久化缓存 当然,LMQL 运行时的内存缓存也可以持久化到磁盘,允许你在多个查询之间重用缓存树,从而自动降低 token 成本和延迟。在某些情况下,这甚至可以将 LLM 的请求次数减少到 0,例如如果缓存已经包含所需结果。 为此,你只需在查询代码中指定 cache="file.tokens" 参数: argmax(cache="joke.tokens") """A good dad joke. A indicates the punchline Q:[JOKE] A:[PUNCHLINE]""" from "openai/text-davinci-003" where len(JOKE) < 120 and STOPS_AT(JOKE, "?") and STOPS_AT(PUNCHLINE, "\n") and len(PUNCHLINE) > 1 此查询的首次成功运行会将缓存持久化到 joke.tokens。后续运行会自动从磁盘加载缓存,仅在缓存不包含匹配项时才调用 LLM。这也适用于底层 LLM 请求仅部分重叠的查询,因为基于树的缓存数据结构会自动识别匹配的 subtree。 查询开发期间的缓存:持久化缓存在查询开发期间特别有用,因为它允许你在同一查询的多次运行之间重用缓存。即使你在运行之间稍微更改查询,持久化缓存也会降低查询的 token 成本和延迟。 注意事项和禁用缓存 你可以在查询代码中指定 cache=False 来禁用缓存层。这将导致 LMQL 运行时始终调用 LLM,从不使用缓存。这对于调试目的或确保 LLM 始终被调用的情况很有用。 此外,由于缓存当前实现为仅追加的数据结构,它会无限增长。这对长期运行的应用程序可能有问题,因为缓存最终会增长到相对较大的尺寸。未来,我们计划实现限制缓存大小的简单策略,例如最近最少使用 (LRU) 驱逐策略。 结论 在这篇文章中,我们介绍了 LMQL 运行时的新缓存层,它允许你通过重用以前生成的 LLM 输出来降低查询的 token 成本和延迟。我们演示了缓存层如何在多种场景中减少 LLM 调用次数,包括长约束、短路和工具增强的查询。我们还展示了如何将缓存持久化到磁盘,从而在多个查询之间重用缓存。 欲了解更多关于 LMQL 的信息,也请查阅我们的文档,或加入我们的 Discord 直接与我们交流。期待你的反馈! LMQL Release 0.0.5LMQL Release 0.0.5 2023 年 4 月 17 日 今天,我们发布了 LMQL 版本 0.0.5。此版本专注于稳定性和性能改进。详细变更列表见下文。我们特别高兴的是,首批社区贡献已作为此版本的一部分合并,还有许多其他贡献正在进行中。 lmql==0.0.5 已发布在 PyPI 上,基于 GitHub 仓库当前的 main 分支。更新版本也已部署到基于浏览器的 lmql.ai/playground。 Changelog 解码器性能 argmax 和 sample 解码器进行了一些优化,使其运行更快。这在常见查询工作负载上带来 20-30% 的速度提升。#24. 后处理语义 内部而言,LMQL 现在允许约束实现后处理语义。这用于在变量值完成后,将其转换为提示中更规范化的形式,以及在查询代码上下文中语义有意义的数据类型。#24. 例如,当对生成的数字使用 INT() 约束时,模型将仅限于生成有效整数,现在,结果 NUM 值还将转换为 int 值: argmax "My favorite number is: [NUM]\n" print(type(NUM), NUM * 2) #