查看原文
其他

展望LLM与半结构化I/O【2023Q4】

孔某人 孔某人的低维认知 2024-04-04

本文主要讨论一种非共识的视角,不得不一开始就解释一下本文的标题:

1、半结构化I/O

1.1、何谓半结构化I/O

I/O是Input/Output的缩写,目前一般具体指计算机的输入输出设备。本文则用来指LLM或LLM API的输入输出,虽然有点怪,但暂时我也没找到其他合适的词。目前的一般认知中,LLM的I/O一般都是token序列,也就是非结构化的文本。

半结构化I/O是指输入和输出不再是完全非结构化的文本,而是其中包含了一些“结构”,但也带有非结构化的元素。例如说:整体是一个(数据结构中的)树结构,其中每个叶子节点以及节点的一些属性可以是非结构的文本。实际上可以任意组合数据结构中的各种结构,让其中的某些元素存储非结构的文本(或图像等其他非结构化表示),但整体结构上是“结构化”的。从计算机专业的角度,这个比较像是包含了非结构元素的AST,但本文的半结构化并不限于树结构。

1.2、为什么讨论半结构化I/O

虽然增加结构化的部分让整体看起来似乎提供了更多的(结构)信息,也更容易被处理,但实际上半结构化相对于结构化是增加了更多的限制。非结构化的I/O协议可以兼容未来一切可以被序列化为文本的信息,但半结构化I/O就要视情况而定了。

现在已经在广泛使用非结构化I/O的协议和模型结构了,为什么还要讲半结构化呢?总体来说半结构化相对于非结构化有几点好处:

  • 半结构化是一种“特化”,为了在具体的场景下针对性的进行优化。这点的具体表现会在下文具体场景中分别讨论。

  • 半结构化更容易表达层次和隔离,可以很自然地消除歧义

  • 半结构化相对于结构化提供了一些抽象层次,这带来了灵活性,可以让供应商可以在用户不必修改代码的情况下变更底层实现。


1.3、API协议 与 模型结构 可以分离

虽然现在LLM和LLM的API基本还都是非结构化的,两者是一致的,但这并不代表这两者非要一致。

很多人没意识到的是,现在支持function calling功能的LLM API的I/O其实已经是半结构化I/O的协议了,输入中的函数的信息,输出中的函数调用参数等都是以一种结构化的方式提供给API用户的。其实message list这种形式本身就已经是一个比较简单的半结构化方式了。

半结构化的输入可以由API供应商序列化为非结构化的文本输入给LLM,LLM输出的原始文本可以经过API供应商提供的解析转化为某种半结构化的方式来方便用户调用。这就是目前function calling功能的实现方式,API的I/O是半结构化的,但内部模型是非结构化的。

反过来也是可以的,API的I/O是非结构化的,内部模型是结构化的。

严格来说半结构化I/O的LLM模型结构是否还能叫做LLM是可以讨论的,为方便起见本文以下称这种为半结构化LLM

目前虽然还没有知名的半结构化LLM结构,但这个方式是可以的,本文后面也会进行讨论。

2、半结构化I/O的API协议

本节从API协议的层面进行讨论,半结构化LLM的模型设计等会在下一节进行讨论。

2.1、现在的半结构化I/O协议

目前的API协议中,半结构化I/O主要抽象了两个部分:

  • 多轮对话的边界以及发言人信息,主要用于输入

  • Function calling相关的信息,输入输出都有

前者的作用主要是解耦LLM的使用方式与LLM Chat微调阶段使用的数据格式,做过开源LLM部署的开发者对此都有了解。

后者function calling在输入上的作用也是类似,解耦和屏蔽LLM模型具体使用的序列化格式,并且能提供升级时候的无缝迁移(不考虑效果变化)。输出方面更多则是为了方便用户使用,而不是让用户做麻烦又易错的半结构化信息提取。并且供应商进行输出的半结构化提取或直接输出半结构化信息可以使用一些手段来提升效果,例如:

  • 发现输出格式非法的结果时进行自动重试

  • 直接限制为只能生成合法的输出结果,或直接生成半结构化的结果


2.2、半结构化输入协议

2.2.1、对抗prompt攻击

做过与数据库交互的LLM应用开发者应该会想要prepared statement这样的东西,这是对抗SQL注入的有效方式。但在LLM调用中,却没有任何办法强制隔离一个内容区域,这导致了各种prompt攻击非常容易。

LLM应用层的开发者有什么好的办法来发现和识别prompt攻击么?其实很难,毕竟他们处理语义的最主要工具就是LLM,而正是这个工具的调用过程本身被攻击了。考虑到prompt攻击的广泛适用性,以及基座LLM供应商更强的语义处理能力,这个prompt攻击识别/防护功能其实应该是LLM供应商来做的。

但之前的近乎非结构化的输入协议让LLM供应商也很难从中提取哪些是应用开发者的指令,哪些是攻击者输入的信息。所以长期来看,通过半结构化的方式明确区分指令和指令要处理的内容是一种必然。

半结构化的输入方式未必要替换之前的方式,更多应该是提供了一些新的调用API入口,支持一些常见的调用范式

2.2.2、平台上的自然语言描述函数

LLM调用中很大比例是作为一个语义文本处理函数使用的,函数的实现是通过自然语言描述的。这个自然语言函数在应用开发过程中由开发者调试好,以prompt模板的形式存储。

但这个抽象只在开发者一侧是比较明确的,如果站在LLM供应商的角度上来说,识别调用中所有的这样自然语言函数就是一个比较麻烦的事情,更别说如何关联这些自然语言函数的不同版本之间的信息。这阻碍了LLM供应商参与到这些自然语言函数的优化过程中,并非理论上做不到,只是单纯的第一步的成本问题。

长期来看,更好的协议是在平台侧显式地构建一个自然语言函数对象,或者说LLM处理过程对象。虽然名字上类似数据库中的存储过程(在数据库服务器端运行的函数),但它和存储过程的意义有着明显的不同。

从API供应商的角度,这个抽象有很大价值:

  • 可以明显降低分析API调用请求的成本

  • 通过缓存prompt模板在不同请求间的公共部分来降低计算成本

  • 平台可以使用更好的序列化方式或半结构化LLM模型来优化该类调用的效果

  • 可以识别和发现较差的prompt,向开发者推送优化建议

  • 可以自然地从简单的prompt模板函数迁移到针对性的微调方案,应用侧无需修改。这可能会是平台侧自动微调服务最主要的新付费来源入口。

  • 可以显式地让用户指定few-shot的样例库,对样例进行预计算,并可以向开发者提供样例自适应选择策略。

  • 很多增值功能需要用户将请求历史保存到平台上,这是一个增加用户部分授权数据使用场景的场景。

  • 向开发者提供函数级的资源隔离和预留能力,这也是微调后模型推理自然需要的。

除了上述供应商对开发者提供的能力外,对于开发者的其他价值:

  • 切换到半结构化本身就会有效果上的提升,例如:改善指令遵从效果,不再需要自己调试分隔符,提升对于prompt攻击的对抗能力等

  • 长期来看,大部分要求不是非常高的场景都可以把样例维护和自动微调功能都托管到平台侧,实现云端有自主学习能力的函数。降低应用侧的开发复杂度和成本,并且可以实现代码逻辑发布后的能够随API能力提升而自动提升效果。

展望这种形态的产品设计:

  • 输入是半结构化的,显式的指定各个参数

  • 默认及冷启动阶段是用户配置的prompt模板,用户也可以选择锁死prompt,也可以选择平台在能够优化效果时进行变更。

  • 调用的基础模型本身也可以配置为平台自动选择的,平台推出更适合该场景的基础模型时自动应用。API调用定价也是自动调整的。

  • 函数对象上提供历史变更消息,应用侧可以通过此了解平台自动参数的变更历史、功能版本等变更历史、平台对于该函数优化的建议等。

  • 开发者可以批量提供和追加样例库

  • 在开发者配置允许的情况下,平台可以优化prompt改进效果

  • 在开发者/用户愿意付费的情况下,切换到微调模型。

  • 在开发者/用户能够对函数结果的真值/结果好坏进行反馈的情况下,平台提供效果监控,并可以对于不同版本、微调/非微调等提供效果对比,方便用户进行选择。

  • 可以通过这种方式提供更复杂的例如知识库等的高级功能

  • 平台可以通过这种API提供常见的工具支持,例如python sandbox


2.2.3、有状态的Workspace API

上一节更多讨论的是一个无状态的智能函数,虽然这个函数本身可以有学习和优化的能力,但在单次调用流程里它是无状态的。

同样的思路扩展到有状态服务也是很自然的,只不过除了智能函数外,还会需要保存状态Workspace概念。这方面的例子就是OpenAI在11月推出的Asstistant API。

通过这种方式能够提供的功能就更多了,理论上可以把所有业务功能都包装为这种方式,完全就看基座LLM觉得哪些业务逻辑值得做。

2.3、半结构化输出协议

目前的function calling、json mode等都已经是半结构化输出的实例,它们的作用是:简化用户解析工作,可以在平台侧采用手段提升准确率。目前OpenAI的json mode还比较简单,只能支持结果整体就是一个json的情况,如果是回答中混有一些json则无法使用。

这个方式可以进一步推广到输出时产生半结构化的输出,对于其中的json节点、代码节点、表格节点等应用一些方式来提升准确率。这方面会在后面讨论半结构化LLM模型时继续讨论。

3、半结构化I/O的模型

3.1、模型的解码端

想要输出半结构化的结果,最好的方式之一就是直接输出半结构化的结构,而不是先生成文本再进行结构提取。目前的json mode就是接近于这种思路。

从本文的角度来说,输出端就有更多玩法了,例如:

  • 当解码到一个代码片段节点时,切换到代码生成模型进行推理,直到代码元素完成时再切回默认模型推理。其他类型元素也都可以类似实现。

  • 生成一个数学表达式节点后,直接调用工具计算结果,而不是让LLM产生推理结果。甚至在构造数据时就有目的性的:把计算问题转为先触发生成对应类型节点,填充问题,再在解码时调用工具直接计算。

  • 进一步推广为:在回答中部产生对于平台上tool的调用时,直接触发调用,将结果保存到半结构化的结构中后再继续推理,实现在一次回答内边推理边调用外部工具的能力。目前OpenAI在一次调用只能选择回答或直接发起调用外部tool。

  • 在某些特殊位置触发直接获取LLM预测的不同token的概率分布并以某种形式嵌入到输出中。

而且要注意的是这只是内部模型的输出,返回给API的用户时可以剔除掉一些中间结果信息,甚至还原为非结构化的文本。

我认为这种思路提供了很大的工程效果上的提升空间,即使在模型基础能力没有进步的情况下。

3.2、模型的前中段

既然模型的输入都可能是非线性的了,那么整个模型的结构其实可选择的就比较多样了。本文在这里就不给大家开脑洞了,之前DL时代在各种各样的数据机构上有很多“奇奇怪怪”的结构,特别是在树结构和图结构上的设计适合迁移到现在,觉得无法想象可以做什么设计的读者建议去看相关的论文。

突破线性结构之后,容易被人忽视的变化是:整体模型对于位置编码的要求降低了。传统LLM输入各种信息全靠序列化成一个token序列,所以对于位置编码的要求比较高,超长上下文,相对顺序都需要有一定准确度。但在半结构化输入的场景下就有些不同,因为很少有那种无法被分解又特别长特别耦合的信息,很多难处理的输入都可以在API设计时针对性的进行设计,让输入直接是有一定结构的表示方式,例如把超长的代码库变成分文件的代码AST。

以下举一些具体场景的例子作为抛砖引玉:

【System prompt】

System prompt明显是跟用户对话message不同的,它有些类似于对于对话文本的处理函数的描述。目前是单纯的把它和对话历史混在一起,这也导致了它容易被攻击,效果也不足够强。

我在7月的时候就指出想要充分的隔离System prompt也许应该采用与message history分离成两端输入的模型结构。(见 《基于LLM的程序开发:System Message的崛起》发表于知乎 )

【Long Context】

长上下文,特别是其中内容之间耦合性不高,接近于水平堆砌大量内容的长上下文场景,我认为更适合类似Map-Reduce的整体结构,这方面参考我之前的专题文章 【2023Q4】再谈Long-Context LLM 。

【长结构化文档/代码库】

这里的长结构化文档包括:

  • 带有结构化层次章节信息的文档

  • HTML等本身带富结构的文档

包含较多代码的代码库经过语法解析之后,也能形成类似结构的信息,不过其中一般还要附加一些如类型信息、引用索引等附带信息。

对于这种结构来说,似乎直接能够使用某种针对树形结构的模型是更适合的。

【为节点定制的模型】

一些输入本身会带有一些特殊的元素节点,例如一段json、一段代码、一个表格等等。也许我们应该对于这些特殊元素针对性的做一个模型组件。

【结语】

希望读者的思路能找回之前DL模型结构百花齐放的时代,为客户的常用场景去定制一些模型结构来优化。

3.3、数据集的来源

半结构化LLM模型除了无法自动适应各种场景外,第二大的问题就是训练数据的来源问题,毕竟原始LLM训练的语料当中可没这些结构信息。

在一些场景下,我们可以通过半结构化I/O的API来自然地收集到对应格式的数据。但不少场景下还是需要类似现在function calling数据的方式去专门生产这类的数据。这个过程的成本是很高的,这也是之前会觉得这种思路不好落地的原因之一。

但目前整个基座LLM训练的生态中,大家已经在逐渐转向用很高成本的方案去处理和生产预训练的数据了,这个高的标准是指:每份数据至少被gpt4-turbo级别的模型处理过一遍。为什么要转向这么重的思路本文暂且不提,但如果是在这个前提下,为本文讨论主题的特殊结构模型专门准备数据就成为了可能,甚至在一些极端条件下只是把某个数据转化过程的输出格式指定修改了一下而没有增加成本。

从这个角度来说,更高质量的训练数据生产和更结构化特化的模型结构设计会是很耦合的

4、结语

再次强调,本文并非是提倡把所有的非结构化方式改成半结构化的,而是建议【新增】半结构化的方式。毕竟半结构化的方式无法处理各种奇奇怪怪的小众场景,仍然是需要非结构化的基础方案作为兜底的。

但半结构化I/O的API协议设计和半结构化的LLM模型确实有它的吸引力。

沿着本文的这个视角可以延展到一些更具体场景也更复杂方案的讨论,但这些都没有包括在本文之中。本文主要是作为一个基础思路介绍,用于被后面延续这个思路的文章引用。

交流与合作

如果希望和我交流讨论,或参与相关的讨论群,或者建立合作,请私信联系,见 联系方式

希望留言可以知乎对应文章下留言

本文于2023.12.22首发于微信公众号与知乎。

知乎链接 https://zhuanlan.zhihu.com/p/673647093

继续滑动看下一个
向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存