如何控制LLM的输出格式 ?

大模型向量数据库云通信
  1. 介绍 =====

在与LLM进行交互时,我们往往期望获得精确且结构化的输出,尤其是在模型的输出将直接作为代码或其他程序输入的场景(如Agent智能体中的工具调用)中。然而,有时无论指令多么明确,生成式语言模型都可能表现出过度的随意性,输出内容偏离目标,甚至完全"失控",生成无用的信息。

不过,我们有一些技术手段可以约束语言模型的行为,引导其生成符合特定规范的输出。本文将作为一份实践指南,详细介绍其中一种非常强大的技术:约束解码。我们将探讨结构化生成和约束解码的概念、它们的工作原理、最佳实践、有用的模式以及应避免的常见问题。

更多AI相关,欢迎关注微信公众号:"小窗幽记机器学习"

  1. 什么是结构化输出 ===========

所谓结构化输出(Structured Outputs)指的是让 LLM 按照预先定义的 Schema 输出符合要求的结果。这种 Schema 不仅限于 JSON Schema,还可以是正则表达式(例如电子邮件、电话号码,甚至简单的 Y/N),甚至是代码。

结构化生成可以通过多种方式实现,包括:

  1. Prompt Engineering(提示词工程):

在提示中包含所需输出结构的描述。这也可以采用few-shot(小样本)提示法,即在提示中包含输入和输出的示例。 2. 微调(SFT&LoRA):

使用特定输出结构的输入输出对,对模型进行监督微调、LoRA微调以完成特定任务。这将使模型在推理中倾向于生成更加符合预期的回复。 3. 多阶段提示(多轮对话、信息抽取):

该方式倾向于多轮提问、多轮对话,模型不需要在第一时间完成结构化输出的任务。通过生成阶段的文本自行组装生成结构化输出。 4. 接口定义:

Openai及Qwen的官方接口都有提供可选的Json的返回格式,可以确保api以Json格式返回,尽管它不能对Json内容提供完全准确的保证。

那么,根据上述的一些技术实现的难点及实际场景的痛点。有一种解决方案,即使在处理复杂任务的相对较弱模型时,也能保证精确的输出:Constrained Decoding(约束解码)

  1. 什么是约束解码?他是如何工作的? ===================

Constrained Decoding(约束解码)是一种通过操控 LLM 的 token 生成过程,将模型的下一个 token 预测限制为仅生成那些不违反所需输出结构的 token。可以简单理解为:在 LLM 生成每个 token 的过程中,根据预定义的规则对候选 token 进行过滤,只保留符合规则的 token。

picture.image

Constrained Decoding的一般步骤如下:

  1. 定义约束规则:首先,定义约束规则。比如正则表达式、上下文无关法(CFG)等形式化的语法规则,或者是一些自定义的逻辑规则。
  2. LLM 生成候选 Token:基于当前上下文(即已经生成的 Token 序列),LLM 会生成一个候选 Token 列表,并为每个 Token 赋予一个概率值。
  3. 约束检查(Mask Gen):根据预定义的约束规则,检查候选 Token 列表中的每个 Token,确保它们符合设定的规则。
  4. 过滤(Apply Mask):将不符合规则的 Token 的概率值设置为 0(或极小值),从而排除这些 Token。
  5. 采样:根据过滤后的概率分布,从剩余的候选 Token 中随机采样一个 Token,作为下一个生成的 Token。
  6. 重复步骤 2-5,直到生成完整的文本序列。

举个简单具体的例子:

假设在LLM生成已经到了这个步骤,

  
{  
  "name": "卖肠粉的小男孩",  
  "age":  
  

此时,约束解码机制会检查下一个可能的 token。假设候选 token 列表包含:

  • "42" (数字)
  • "hello" (字符串)
  • true (布尔值)
  • "," (逗号)
  • "{" (左花括号)

约束检查会:

  1. 识别当前位置需要一个数字,即限定格式。
  2. 评估每个候选token,最后将不符合的token概率设定为0,保留数字token的原始概率。
  3. 最后从符合规则的token中选择答案。

通过以上操作则能始终保持语法正确性,最终得到正确的Json结构。那么,假设我们的格式化输出是建立在一大段连续的长文本量级,我们该如何编写一个合理的规则使模型能稳定输出呢?

  1. 什么是上下文无关语法(CFG)?CFG如何实现约束解码? ===============================

4.1 CFG介绍

picture.image

根据维基百科定义,上下文无关文法(CFG),是一种形式文法(formal grammar)。形式文法是形式语言(formal language)的文法,由一组产生规则(production rules)组成,描述该形式语言中所有可能的字符串形式。相信看到百科的介绍及网上一系列的博客推文,早已让各位读者晕头转向,故小编借鉴了一个知乎大神的思路,给大家转化为通俗易懂的讲解方式。

4.2 如何理解上下文无关?

什么是上下文,上下文在哪里?为什么说这个文法上下文无关?

在应用一个产生式进行推导时,前后已经推导出的部分结果就是上下文。上下文无关的意思的,只要文法的定义里有某个产生式,不管一个非终结符前后的串是什么,就可以应用相应的产生式进行推导。(从形式上来看,就是产生式的左边都是单独一个非终结符,即形如 S-> ...,而不是非终结符左右还有别的东西,例如 aSb -> ...)

我们举一个相对具体的例子来说明:

  
产生式:  
Sent -> S V O  
S -> 我|你|小狗  
V -> 吃|跑|走  
O -> 步|饭|路|肉  

其中英文字母指代的是非终结符(SVO表示主谓宾),汉字都是终结符。

这个文法可以组成 334=36种组合,比如{我吃饭,小狗跑步,小狗吃肉,你跑肉,你跑路,....}

可以看到,其中有一些的组合在语义上是错误的。比如“你跑肉”,是根据公式推导得到:

Sent -> SVO -> 你VO -> 你跑O -> 你跑肉

但是上下文无关文法里,因为有“O -> 步|饭|路|肉”这样一条产生式,O 就永远都可以推出“肉”这个词,它并不在乎应用“步|饭|路|肉”这个产生式进行推导时 O 所在的上下文(在这个例子里,就是”你跑O“中 O 左的字符串”你“和”O“)。事实上,在 O 推出“肉”这一步,它的左边是“跑”这个词,而”跑“和”肉“不搭配,导致最后的句子读起来很奇怪。

上下文有关文法 呢?产生式可以定义为(其中前两条产生式仍是上下文无关的,后四条则是上下文有关的):

  
Sent -> S V O  
S -> 我|你|小狗  
我V -> 我吃  
你V -> 你吃  
吃O -> 吃饭 | 吃肉  
跑O -> 跑步|跑路  
...  

可以看到,这里对 O 的推导过程施加了约束:虽然 V 还是能推出”吃“和”跑“两个词,但是仅仅当 O 左边是”我吃|你吃“时,才允许它推导出”肉|饭“;而当 O左边是”跑“时,允许它推导出”步|路“。这样通过上下文的约束,就保证了谓宾搭配的一致性。类似地,包含 O 的产生式也约束了动宾搭配的一致性。

那么,解释完上下文相关及无关文法后,为什么我们要选用上下文无关语法来实现约束呢?

答:为了解决连续长文本的约束解码问题,我们引入CFG(上下文无关语法)来帮助我们实现约束。该方法的大致脉络则是通过一组规则来定义结构(如下图3)。其中每条规则都包含一个字符序列或其他规则,并允许递归组合来表示复杂的结构。相比于正则表达式等其它格式,CFG 由于支持递归结构,因而能提供更大的灵活性,使其适合描述 JSON、SQL 和领域特定语言(DSL)等常见语言。

picture.image

  1. 约束解码是完美的解决方案吗? =================

当小编抛出这样的话题,答案很明显不是。在 LLM 的约束解码过程中,模型需要逐个生成 token。每生成一个新 token 之前,系统都需要根据预定义的规则对候选 token 进行验证。这个验证过程面临着计算效率的挑战:特别像Qwen-72B现在这样拥有152,000个 token 的大型词表来说,如果需要在每个生成步骤都对整个词表进行完整的语法规则验证,计算开销会非常大。此外,CFG 解释需要一个堆栈状态来跟踪之前匹配的递归规则,因此无法提前计算和缓存堆栈模式的所有组合。最后,LLM 生成结果中的每个 token 都包含多个字符,这些字符可能会跨越语法元素的边界,并在运行时执行期间导致进一步的递归或堆栈弹出。这种未对齐的边界问题很棘手,需要在语法执行期间小心处理它们。

  1. XGrammer:兼顾效率及稳定的开源方案 ========================

论文地址: https://arxiv.org/pdf/2411.15100

开源代码仓库: https://github.com/mlc-ai/xgrammar

为了解决约束解码出现低效、开销大等的问题,陈天奇团队便开源了其结构化输出的高效替代方案:XGrammer 。相比于之前的 SOTA 方法,XGrammar 可以将上下文无关语法的每 token 延迟减少多达 100 倍!此外,他们还基于 Llama3.1 模型实验了集成了 XGrammar 的 LLM serving 引擎;在 H100 GPU 上,这能将通过结构化输出实现端到端 LLM serving 的速度提升 80 倍!

picture.image

XGrammer 采用了一种字节级下推自动机(byte-level pushdown automaton) 来解析上下文无关语法。这种字节级的设计允许每个字符边界由一个或多个字节组成,从而能够处理不规则的 token 边界,并支持包含非完整 UTF-8 字符(sub-UTF8 characters)的 token。自动机的结构经过专门优化,以提升匹配效率。

在预处理阶段,系统会生成一个自适应 token 掩码缓存 。通过预先计算与上下文无关的 token 掩码,该缓存显著加快了运行时的掩码生成过程。同时,上下文扩展(context extension)机制 进一步提高了缓存的命中率和整体性能。

在推理过程中,大部分 token 掩码可通过缓存快速生成,而针对剩余的上下文相关 token,则由一个持续性的执行堆栈(persistent execution stack) 进行高效处理。

此外,掩码生成过程与 LLM 的推理过程是并行进行的 ,以最大限度地降低约束解码所带来的性能开销。每当 LLM 在掩码约束下生成一个新 token,系统便会立即利用该 token 更新下推自动机的堆栈状态,为下一轮掩码生成做好准备。

  1. 总结与思考 ========

本文围绕大语言模型(LLM)的结构化输出展开,从最基本的结构化生成技术讲起,深入探讨了约束解码的原理、应用与局限,并进一步引入上下文无关语法(CFG)作为构建结构规则的核心形式语言。最后,我们介绍了一个在实际性能和稳定性之间取得良好平衡的开源方案——XGrammer,它为大规模结构化生成带来了新的可能。

回顾整篇内容,我们可以看到,LLM 本质上是一个强大的语言生成器,而不是规则引擎。如何用规则驯服“自由”的模型 ,不仅是提示工程的问题,更是生成机制层面上的挑战。结构化输出与约束解码正是在这条路上的关键一步:它们让我们得以在生成式模型和严格格式之间找到一条平衡路径。

从个人的实践经验来看,约束解码是目前少有的能让 LLM 输出真正"可控"结构的技术路径之一 。但它并不是"银弹":性能瓶颈、规则设计复杂度以及与模型 tokenization 机制的对齐,都是实际部署时不可忽视的问题。因此,探索如 XGrammer 这样的新框架,不仅是技术演进的趋势,更是实际场景落地的刚需。

#LLM #大模型 #AI

0
0
0
0
关于作者

文章

0

获赞

0

收藏

0

相关资源
CV 技术在视频创作中的应用
本次演讲将介绍在拍摄、编辑等场景,我们如何利用 AI 技术赋能创作者;以及基于这些场景,字节跳动积累的领先技术能力。
相关产品
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论