《Prompting Is Programming: A Query Language for Large Language Models》论文学习

发布时间 2023-07-19 20:44:15作者: 郑瀚Andrew

一、前言

大型语言模型在诸如对话问答、代码生成等广泛任务上表现出了出色的性能。

在较高的层次上,给定一段输入,大语言模型可用于按照概率统计方式自动补全序列。在此基础上,用户用指令(instructions)或示例(examples)去提示(prompt)大语言模型,以实施各种下游任务。

本质上,提示(prompt)方法是语言模型、用户和外部工具(例如计算器)之间的交互规范。

然而,要获得最先进的性能,或针对特定任务适配的语言模型,需要进行大量复杂的针对特定任务/特定模型的临时交互调整,这非常消耗时间,也不利于持续迭代LLM程序的整体表现效果。

基于这个背景,论文提出了语言模型编程(Language Model Programming,LMP)的思想。 LMP主要包括以下几个方面,

  • 纯文本提示(text prompting)
  • 文本提示(text prompting)和编程脚本(scripting)组合
  • 语言模型输出约束(output constraints),这个特性使得LMP能够轻松适应许多任务,同时抽象语言模型内部结构并提供高级语义。

为了启用 LMP,论文实现了 LMQL(Language Model Query Language),它利用来自 LMP prompt的约束和控制流,以生成有效的推理过程,最大限度地减少对底层语言模型的昂贵调用的数量。

论文通过实验证明 LMQL 可以以直观的方式追上各种最先进的提示方法,特别是促进使用现有高级 API 难以实现的交互流程。

论文实验的评估表明我们保持或提高了几个下游任务的准确性,同时也显着减少使用付费 API 所需的计算量或成本(节省 26-85% 的成本)。

参考资料:

https://arxiv.org/pdf/2212.06094.pdf

 

二、INTRODUCTION

大型语言模型(large LMM)已被证明在各种基于语言的任务上取得了成功,例如机器翻译、文本摘要、问答、推理、从文本生成代码等等。基于这些令人惊艳的效果,LMM 已经超过机器学习社区,变得越来越流行,并正在慢慢集成到许多应用程序中。

大语言模型的本质是一种概率预测模型,它在内部最小操作单元是token,这种token预测方式不同于人类感知语言的方式。本质上,LLM的训练过程是在极大似然拟合一个“pre-token -> next-token”的条件概率函数。

虽然大语言模型可以用直观概念的指令(instructions)或者示例(examples)进行提示(prompt),但使用大语言模型依然存在很多挑战,例如,

  • 首先,因为LMM是在token级别进行操作,因此很难将解码过程限制在合法单词或短语中
  • 此外,许多prompt提示技术可能需要用户和LLM之间的来回交互(例如像 ChatGPT 这样的聊天机器人)或特定于任务的接口(例如执行与外部控制逻辑的算术计算)。为了实现这些prompt提示,需要大量的手动操作(与模型的解码过程进行交互),这限制了completions生成的通用性
  • 最后,由于 LLM 一次仅生成一个(sub-word)token,完成一个序列可能需要多次调用。 此外,随着前缀、提示和迄今为止生成的响应的增长,解码变得越来越昂贵

同时由于语言模型通常是非常大的神经网络,因此实际推理中对计算成本存在较高的要求,同时也存在明显地延迟。对于付费使用的 API(例如 OpenAI 的 GPT 模型),这会导致每个回答的查询的使用成本很高。

在这项工作中,我们提出了通过LMQL进行语言模型编程的想法,通过在自然语言提示的基础上扩展了两大核心特性

  • lightweight scripting(轻量级脚本)
  • constraining of outputs(输出约束)

这种设计有利于在进行LLM prompting中实现前端/后端分离,即允许用户指定复杂的交互(interactions)、控制流(control flow)和约束(constraints),而无需了解 LLM 的内部结构,例如向量化(tokenization)、实现(implementation)和模型架构(architecture)。

此外,LMQL构建的程序屏蔽了底层LLM的细节,这大大提高了LLM迁移性(底层LLM可插拔)。

总体而言,语言模型编程(LMP)保留了简单的自然语言驱动的 LM 接口,还可以实现精确的约束、脚本编写、以及高效的解码。

为了启用 LMP,我们提出了一种称为语言模型查询的新颖语言和运行时语言 (LMQL)。 LMQL 是一种高级语言,具有类似 SQL 的声明性元素和脚本的命令式语法。 底层运行时与现有的LM语言模型兼容。

LMQL 可被用于表达种类丰富地prompt方法(prompt交互范式),而仅仅使用简单、简洁且与供应商无关的代码。

此外,专门设计的评估语法(evaluation semantics),支持部分评估(partial evaluation )和前瞻(lookahead),使我们能够对查询进行端到端的优化:LMQL 利用输出约束和脚本化提示,修剪了 LM 的搜索空间,带来了最高80%的推理成本降低。

我们在图 1 中展示了两个简单 LMQL 程序的示例。

Fig1. Two LMQL programs that demonstrate core features like scripted prompting, eager output constraining and validation, and prompting with control flow.

总结来说,论文的核心贡献是:

  • 引入了语言模型编程的新范式,制定并解决了最近的语言模型提示技术带来的几个挑战。
  • 提出了 LMQL,一种高效、高级的 LM 查询语言,支持脚本化提示和输出约束。  
  • 展示了如何用简单、简洁的 LMQL 程序,表达广泛和先进的prompt提示技术。不仅如此,LMQL 将推理成本和延迟降低了 26-80%,同时保持或提升了任务准确性。

笔者认为,LMQL的主要价值在于,提供了一套可编程的prompt开发框架,使得开发者可以更有效、更精确地构建和LLM的信息交互通道,LMQL为广大开发者开启LLM编程时代提供了有力的工具,类似在software1.0时代IDE的作用。

 

三、OVERVIEW: LANGUAGE MODEL PROGRAMMING

0x1:Background: (Large) Language Models

1、Few-Shot Prompting

Few-shot prompt 指的是语言模型不需要针对下游任务(例如分类、问题回答等)进行定制化地训练。 相反,使用广泛的文本序列预测数据集进行预训练,并在调用它们时以示例的形式提供上下文即可达到不错的效果。

我们在下图中展示了一个例子,

Fig. 3. Example of few-shot promptin 

其中,我们的目标是将“奶酪”从英语翻译成法语。为此,我们提供了几个示例,然后要求语言模型以相同的语法完成“cheese”的翻译,

这样,翻译和其他任务就可以被重新定义为简单的序列补全任务,这使得 LM 成为强大的多任务推理机,甚至是一个通用计算机。

2、Multi-Part Prompting

由于其强大的推理能力,LM 不再只是被用于简单的prompt-completions,也可作为“组合推理引擎”被集成到更大的程序中。

 

LM 编程方案有很多不同的编程范式,例如,

  • 迭代分解(Iterated Decompositions),典型地项目如 langchain,它更关注按顺序使用的多个提示的组成。 
  • 元提示(meta prompting)
  • 工具使用( tool use)
  • 思维链(Chain-Of-Thought)
  • 键值存储(Key-Value Memory)
  • LM 级联框架(LM cascades frame):在概率编程环境中使用。

0x2:Key Challenges

在本节中,我们先支出 LM 使用中的三个关键挑战,之后会讨论如何用语言模型编程和 LMQL 来克服它们。

挑战一:复杂任务需要和LLM多次交互才能得到最终结果

解码过程中的 LM 交互仍然是一个挑战。

考虑下面图4的一个例子,该方法讨论了元提示(meta prompt)的想法,为了获得特定问题的答案,首先要求语言模型扩展prompt提示,然后再次将扩展后的prompt输入到同一模型以获得答案。 在图4的例子中可以看到,我们的目标是找到“什么是地球的周长?”。在元提示(meta prompt)中,我们首先向语言模型询问能否回答这个问题的专家名字,然后询问该专家如何回答这个问题。

如果使用传统的人工 LM 界面,需要人工输入prompt提示的第一部分,手动调用 LM 获得专家名称,得到第一个completions序列,然后从该completions序列中提取专家名称,并手动将其输入到模板的其余部分,然后再次将其提供给LM以获得最终的回答。 这种方法需要通过 API 进行大量的手动交互,甚至人必须参与到整个循环(HITL)中。

这里需要注意的是,一旦某个值固定(例如专家姓名),解码算法将假定它是提示的固定部分,并且不会与提示的其余部分一起,优化最终的答案。在 HITL 循环流程中,这使用户能够手动尝试多个专家名称,并选择他们最喜欢的名称,完成completions的生成。 然而,它排除了自动联合优化所有模板参数,以此最大化总体似然的可能性,也排除了可能会产生更好的结果的可能。

挑战二:Constraints & Token Representation

下图示例中展示的另一个挑战问题是输出约束。

有时,LM 会在生成过程中偏离主题并产生太长的连续文本序列。虽然有些答案可以很好地被用于下一阶段的prompt提示,但大多数产生的结果是错误且无用的。如果这些生成的结果被用于下游另一个信息处理系统,这个问题就尤其严重,因为下游系统可能只能处理固定格式的输入数据。

为了规避这个问题,开发者需要对生成的文本进行限制,因为 LM 本质上是一个概率预测模型,它并不天然地遵守这些限制。

理想情况下,这些约束应该可以用人类可理解的概念和逻辑来表达,因为用户只能基于单词、句子和实体进行推理,而不是像 LM 那样在token级别上进行推理。

挑战三:Efficiency and Cost

最后,效率和性能仍然是巨大的挑战。 虽然人们已经投入了很大精力致力于使 LM 中的推理步骤更加高效,但它们仍然需要昂贵的、高端 GPU 以合理的性能运行。

正因为如此,许多实际用户求助于到在云上运行的托管模型,但即使托管到云上,依然需要使用付费 API 进行调用,LM 查询在计算和财务方面都可能变得非常昂贵。

然而,当引入语言模型编程和约束时,新的优化机会得以出现,因为“预定义行为”和“搜索空间限制”可以减少 LM 被调用的次数。 在这种设置下,验证、解析的成本与单个 LM 调用的巨大成本相比可以大幅减少。

Fig.4 Example of a meta prompt for the circumference of the earth and its scripted prompting counterpart

0x3:Language Model Programming in LMQL

这章我们讨论 LMQL 如何帮助克服传统LLM面临的挑战。

如下图(c)所示,我们编写与之前的人工promot-completions交互相同的查询LMQL 语法。

Fig.4 Example of a meta prompt for the circumference of the earth and its scripted prompting counterpart

在LMQL query中,一切变量被输入到 LM 之前,以及通过LM预测得到的答案,都使用 [VAR] 进行了占位。大括号中的变量名 [VAR] 只是调用先前定义的变量。这大大简化了prompt提示并消除手动交互的需要。

此外在本例中,它还允许联合考虑专家姓名(LLM生成的临时中间变量结果)和答案的解码过程(LLM生成的最终结果)。

此外在本例中,为了解决运行时间较长的句子的问题,LMQL 允许对专家姓名(LLM生成的临时中间变量结果)施加约束。在上图中可以看到,约束强制执行 EXPERT 的解码标记最多为三个单词,并且序列需要以“.”结尾。 虽然可以使用当前的查询 API 指定最大长度,但它们通常作用在token级别上,因此无法精确控制最终序列的词/句生成结果。相比之下,LMQL 支持声明性高级约束,这些约束需要在解码期间强制执行。

总体而言,语言模型编程概括并自动化了许多 multi-part prompting方法,与用户必须手动尝试 EXPERT 的多个值然后选择最好的一个传统手工方式相比,LMQL 允许用户提前施加一组经过考虑的专家限制到prompt-completions生成过程中,并使该选择过程完全自动化。 一旦完成开发和测试,LMQL 查询(和约束)可以应用于无监督的许多不同输入。

LMQL 约束强制输出符合提示模板,并避免生成结果过长等故障情况。这可以提高的下游任务的准确性。

最后,LMQL 也比手动交互更加高效,不需要多次 LM 调用。

 

四、THE LMQL LANGUAGE

在接下来讨论运行时和语法语义之前,我们在这里提供一份 LMQL 语法的宏观解释。对于具体示例,请考虑图 1 中给出的 LMQL 程序。

Fig1. Two LMQL programs that demonstrate core features like scripted prompting, eager output constraining and validation, and prompting with control flow.
LMQL的语法如图5所示。

Fig. 5. Syntax of LMQL. Brackets denote optional elements. Syntax is generally python based. 

LMQL程序有5个部分:
  1. 解码器(decoder):⟨decoder⟩ 表示 LMQL 运行时在求解问题时采用的解码过程。LMQL 支持 argmax、sample 和 beam。
  2. 实际查询(actual query):它符合和model进行交互,可以通俗将<query>理解为python函数体,但区别是:i)不允许声明内部函数(但是可以导入);ii) 每个top-level字符串都被视为对 LM 的直接查询。 这些查询字符串允许两个特殊转义的子字段,类似于 python f-strings1: 1) "{varname}" 调用 a 的值当前范围内的变量。 2.),“[varname]”代表将由LM生成的短语,也成为占位词。
  3. 指定查询模型的 from 子句(from):⟨model⟩ 表示要使用哪个 LM,标识来自 Hugging Face Model 存储库的文本生成模型,或通过 OpenAI API 提供的模型(例如 GPT 系列)。然而,这也可以扩展到其他本地模型或 API 后端。
  4. 指定约束的 where 子句(where):当LM为占位词(hole)生成值时,它们将受查询的 where 子句中定义的约束条件加以约束。其中 ⟨condition⟩ 对 [varname] 占位词变量施加约束,从而约束语言模型的生成结果。约束可以是任意的合取或析取 ⟨cond_expr⟩ 或成员资格(in)检查表达式。 在这些限制条件下,将使用⟨decoder⟩指定的解码过程。解码完成后,相应的变量将在查询程序的范围内创建并赋值为解码结果。如果同名变量已经存在,它将被覆盖。
  5. 分配指令(distribution instruction):在 ⟨python_expression⟩ 中,distribute ⟨var⟩ 是一个可选指令,可以被用来增加返回结果。 这里,⟨var⟩必须引用查询和python表达式集合(概率分布集合)中的最后一个变量。

解码器(decoder)和模型(model)两者都由字符串指定,而查询(query)和约束(where)则以 python 语法给出。

LMQL程序运行完成后,查询程序的结果由许多内容组成,包括:
  • 交互过程(interaction trace):即 LMQL 查询的整个文本记录,以及被 LM 的答案替换的占位词。
  • 占位词(hole)词典:所有占位词变量的集合都是可访问的,允许客户直接访问 LM 响应的特定部分。

对于sample和beam,参数 ? 指定了sample或beam的次数,在这种情况下,将返回与各个变量的 n 个交互轨迹。

为了更好的说明查询(query)和解码(decoder),请考虑上图 1a,它利用纯粹字符串,而上图 1b,则利用了字符串和控制流的组合。

 

注意在图 1b 的程序中,THING 在循环的每次迭代中都被重复赋值,这符合python的语义。

相应的交互跟踪如下图 6 所示。

Fig. 6. The interaction trace for the query from Fig. 1b for different decoding methods. 

对于带有distribution子句的查询,交互跟踪只会评估到待解码的倒数第二个词,最后一个变量不会被解码,而是一个概率分布。LMQL会对概率分布中的每个值评估该输出的可能性。

下图 7 显示了图 1b 中的示例。

 

 

Fig. 7. Continuation of the example from Fig. 1b and Fig. 6a when appending distribute ITEM over things to the query 

这对于编码分类任务(例如情感分析)特别有用,因为下游应用可能对概率分布感兴趣,例如 {正/负}。

0x1:Built-in Functions 

在where子句中,除了标准python代码之外,还额外支持了一组内置函数。例如,

  • word
  • sentence:给定字符串或token表示形式的句子,将其转换为所需的表示形式。
  • stop_at t(⟨var⟩, ⟨str⟩):用户能够明确定义停止标准,表示当变量 ⟨var⟩ 已解码到指定的 <str> 短语时,应停止变量的解码。
  • len:它会重载其默认的 python 函数,它返回字符串(或可迭代)的长度。

为了实现这些指定的内置函数,我们实现了一套高效的附加语义,用于输出验证和解码掩码的生成。

 

五、THE LMQL RUNTIME: QUERY EXECUTION & DECODING 

我们现在讨论 LMQL 运行时如何执行查询。

我们将 ⟨query⟩ 的执行视为一个Python程序,在执行过程中,我们作出如下假设,

  1. 函数是纯净的,且不会引起副作用
  2. 函数是确定性的

⟨query⟩ 是逐行执行的,就像常规 python 函数一样,唯一有一个区别:在执行开始时,交互跟踪 ? ← ? 被初始化为空字符串?。

每当程序执行中遇到top-level字符串?时,下图中的 algorithm 1 就会被触发执行:

我们在下图 9 中说明了这个执行模型,其中列出了评估图 1b 中前 7 行的步骤。

Fig. 9. Example execution of the first 7 lines in Fig. 1b. Text generated by the LM ? in blue. 

当调用??????时,在顶部声明的解码过程,LMQL 程序用于生成占位符的值。

解码通常会在以下条件触发时停止

  1. 当产生序列结束token时
  2. 当因为给定的约束,而无法产生更多token时 

LMQL和LM语言模型进行了深度集成,但正如下图展示的解码算法

除了能够访问token vocabulary的结果分布表之外,我们不对语言模型f施加任何限制。因为,从根本上来说,这是大多数语言模型的核心接口,我们可以轻松地将它们集成在一起,无需进一步更改。

事实上,我们基于 HuggingFace 转换器包中的generate() 函数实现了上图所示的算法。因此,LMQL 已经支持 HuggingFace 中可用的大量 LM模型。

从性能方面进行考虑,对于在大量样本上执行很大的 ? query查询可能会很昂贵,特别是如果在之上调用计算密集型函数用于LM 输出。 然而,由于我们假设函数是纯粹的和确定性的,因此可以基于函数参数缓存结果,这样可以大大减少了所需函数的调用总数。

LMQL 还可以在 LM 预测其下一个 token 分布前,并行地评估约束、控制流和计算token mask。只有符合约束,token mask才会被用于继续文本生成。 这意味着 LMQL 运行时可以与 LM 同步进行,不会产生额外的延迟。

在上图的解码算法中,对于每个新令牌,我们计算一个掩码?词汇表,它只允许产生符合where条件的序列token,直到我们获得了一个序列结束的 eos token 才停止继续生成,我们停止了。

笔者认为,LMQL探索了一种新的LLM编程范式,既认为:

基于英文或者纯中文自然语言prompt并不是最佳的和LLM进行”通信“的协议语言,而在LMQL的帮助下,开发者可以和LLM在token级别进行更细粒度和更复杂的逻辑编程和格式控制,LMQL封装了开发者和LLM操作系统交互通信的复杂接口,提供了一种简洁的编程开发范式。

 

六、VALIDATION AND CONSTRAINT DECODING 

 

七、EVALUATION 

在这章,我们评估 LMQL 作为一种编程语言,以及赋能prompt engineers的工具的有效性。

我们在三个不同的案例研究中评估 LMQL,涵盖广泛的prompt场景。

我们的评估重点关注三个核心问题:

  1. 表现力(Expressiveness):我们能否用简单、简洁的query逻辑,轻松实现常见和高级的提示技术(prompting techniques),特别是在interactive prompting的情况下?
  2. 性能(Performance):LMQL 能否用于有效降低所需的模型查询数量,从而降低使用 LM 的隐含计算或 API 相关成本?
  3. 准确性(Accuracy): LMQL 的受限解码(constrained decoding)是否会影响 LM 完成任务的准确性?

 


LMQL提供了比较高级的接口,接近自然语言提示。 因此,我们评估 LMQL 主要是依据其他现有高级、基于文本的、基于Python接口和LM进行交互的替代方案。

0x1:Case Study 1: Chain-of-Thought Prompting

Fig. 10. LMQL query implementing chain-of-thought prompting for the Odd One Out classification task. 

Table 3. Average performance statistics (over queries) for constrained LMQL chain-of-thought decoding compared with standard chunk-wise decoding for the Odd One Out and Date Understanding datasets. 

0x2:Case Study 2: Interactive Prompting

 

Fig. 11. LMQL code for interactive ReAct prompting scheme for question answering. 

Fig. 12. Comparing different chunk sizes used for the baseline implementation as compared to LMQL, which does not require chunk-wise decoding. All results were measured for interactive ReAct prompting 
 

笔者思考

本质上,像chain-of-thought这种few-shot prompting技术,是在通过修改input,进而影响decoding过程中token mask,进而影响最终result的生成。而LMQL的思想也很简单,它通过重写了LLM decode函数,直接将约束条件施加到了token mask上,进而达到影响最终生成result的目的,这种方式比通过修改input来的更直接,也更有效。

但缺点也是相对的,就是LMQL框架库的需要和LLM的内部细节进行深度耦合和绑定,LMQL的性能和逻辑完善度直接影响到最终的生成结果。