查看原文
其他

展望LLM的流式输入增量计算能力 V2【2024Q1】

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

本文讨论的是流式增量【输入】思路,而不是常见的流式输出方式。

TLDR

  • 介绍了让LLM进行流式增量计算的思路和使用场景。


在1.31日曾发过本文的V1速览版本,本次V2重写了文章行文,并增加了具体实现方案的讨论。

1、应用中的低延迟需求

1.1、人性如此

在不少应用场景中,用户对于低延迟需求是很普遍的。虽然说这个要求客观来说总有些不合理,因为在跟人聊天的时候,一般人是可以接受对方反应没有那么快的。但对于一个文字对话交互系统或者语音电话时,用户的耐心就显著的下降了。

虽然从我的偏好来说,只要给出一个合适的进度展示(例如当前的思考步骤),就可以接受以较长的响应时间以换取更高质量的结果。但我也确实同意:对低延迟的偏好是人性的一部分,有被满足的价值(用户为之付费的价值)。目前的应用已经在低延迟上展开了竞争,整个应用层生态对于低延迟的追求趋势难以阻挡。

1.2、现有解决思路

即使不考虑LLM的特性,传统软件开发中也有不少改善响应延迟的思路,例如:1、该并行的部分要并行,缩短整个流程中关键路径的长度;2、设置合适的缓存点和缓存方案,以空间换时间。

考虑到LLM的特性的优化方案就不太多了:

  • 期待硬件性能提升和推理基础设施的改善,不过这方面的进展没有那么快。

  • 使用更小的模型,加速token的输出。不过这导致能使用的模型能力有限,且可能会需要额外的微调工作来保证效果不下降太多。

  • 减少整体流程中关键链路上的LLM调用环节数,能在一次LLM调用中直接解决并以流式的方式直接输出结果是最好的。但这对模型能力的要求变高,且整个流程中很难增加提升效果和可控性的干预方式。

  • 将复杂的单次LLM调用拆分成多个小的不相互依赖的LLM调用并行执行。

但无论怎样,目前对于依赖大参数量模型、长流程的workflow来说,响应延迟的问题是很大的。

2、LLM的增量输入模式

2.1、流式输入增量处理的案例

在人与人之间对话的时候,听者的思考是可以在他听说者说话的时候同时进行的,特别是在说话者的发言较长的时候。相对而言,目前基于LLM的应用则是在上级输入完成后才开始处理。

在信息系统中,流式处理的场景也比较常见,与文本讨论比较接近的是实时语音识别和实时翻译。(对此不熟悉的读者可以去看讯飞过去的发布会,一般会在底部有一个实时语音识别结果和实时翻译结果。https://www.bilibili.com/video/BV1Gx4y1Z7kf/ )

实时语音识别的增量输入总是在原有输入的末尾的,由于算法能力的限制,最末端语音的识别结果需要参考后续信息才能完全确定。所以实时语音识别的输出有两个层面:

  • 算法已经完全确定的结果,一般会相对于输入流有一个明显的延迟。对应于上图的白色文字

  • 基于目前的输入的最佳猜测结果,识别结果的延迟比上一个要好,但识别结果的末尾可能会在后续信息到来后还有修改。对应于上图的白色+灰色文字

实时翻译面对的问题也是类似的,不过由于不同语言语序的差异,末端需要修改的范围更大,准确识别结果的延迟也更大。

无论是实时语音识别还是实时翻译,都是很好的流式增量处理算法的案例。目前LLM原生的方案上还看不到这种方式的应用,这种方式并非无法实现,只是需要新的输入模式协议、LLM模型方案设计以及更多的算力成本,而这就是本文的讨论的目标。

2.2、LLM流式增量输入需求的场景

LLM流式增量输入有几类场景:

  • 用户语音的实时语音识别结果,增量改变主要在输入的末尾

  • 用户文本输入框不完全输入结果的快照流,用户的编辑操作可能发生在输入的任何位置

  • 上级LLM或者其他模块的流式增量输出流,增量可能发生在任意位置。由于上级处理单元的影响,虽然原始增量改变可能很小,但经过上级处理可能导致变化很大,这取决于上级处理模块是否有特意优化增量变化时输出的变化量

由于这里增量变化可能发生在任意位置,不再是传统的仅在输入末尾增加,所以这里明确一下本文所指的流式增量输入:是指在完整的输入交付前和交付时,对于包括不完全/尚未确认、和已经确认的输入的快照流。一般是由上游变更发生时间触发,或者是周期性快照。以下是一个具体的输入流例子:

* 无论* 无论是语音识别* 无论是实时语音识别 # 中间插入* 无论是实时语音识别还是* 无论是实时语音识别还是实时翻译 # 最终确认的完整输入

由于在LLM调用时一般都会使用prompt模板,这会导致即使仅在输入末尾变化的情况在讨论prompt模板之后也可能变成在prompt中间部分改变。甚至可能有在prompt中的多个位置同时变化的情况。

在输出方面,最好也可以像是实时翻译那样的中间结果输出能力,这样也会形成一个输出结果的快照流。可以再输入给后续环节或者展示给用户等。

让LLM支持这种流式增量输入的主要目的在于:在完整的输入到达前,把已经输入的部分提前交付计算,以此缩短后续完整请求到达时的处理时间。同时能够对于后续环节也输出流式的尚未确认结果,给后续环节的加速也提供可能性。这样即使最终输出只要确认输入的结算结果,流式增量输入的处理能力也可以缩短整个系统的处理速度,代价是会对中间尚未确定的结果进行计算,会耗费更多的计算成本。

3、实现思路讨论

本节主要目标是讨论在输入token序列中间位置的变化的场景,可以附带一些约束,例如事先知道prompt模板中的固定部分是不会变的。

我相信最终能落地的方案还会涉及一些我目前未曾设想的细节,所以本节的讨论更多是抛砖引玉。

3.1、transformer层的输入影响扩散及抑制

目前的LLM模型主流是多层transformer架构。如果不考虑attention mask,输入序列经过单层transformer后,输出中的任意token位置都可能由于输入中少量token位置改变而发生改变。虽然softmax会强烈地压低attention score值较小的项对后续的影响,但使用的浮点精度不低的情况下,经过单层transformer后,输入的少量元素改变可能导致所有输出token位置的激活值都有改变,即使大多数变化可能都很小。

考虑到实践中DL和LLM都不需要很高的精度,所以在使用较高的激活值量化方案可以比较好的抑制这种对于最终结果影响不大的中间层表示微扰。例如使用FP8、INT8、INT4等的激活值量化规格,特别是FP8在实践中性能优异,且其浮点尾数仅有2-3bit,能够很大程度上抑制transformer层对于输入中变化token位置对其他token位置的微小影响。不过这个分析仅对每个token位置的单个激活值维度有效,对于单个token位置的整个激活向量是否都能大概率的保持不变还有待实验验证。

抑制单层transformer对于输入中局部修改的影响拓展效应可以提升后续层输入的缓存复用率。

3.2、Attention Mask

目前主流LLM架构的attention mask都使用decoder mask,即每个token位置只与它【之前】的位置计算attention。就是这种mask方式让简单的KV cache成为可能。

但在中间位置修改的场景则会影响后续所有token位置的attention计算,要么对于后续的位置走近似复用缓存结果,要么就需要设计新的mask方式来为该场景优化。本节主要讨论后者。

考虑到该场景下我们知道输入prompt中有哪些token是不会变得,包括:历史message、prompt模板中的固定部分。一个直接的思路是:修改mask,让固定部分的attention计算不包括未来的变动部分,而变动部分的计算则包括所有已知的固定部分。这相当于:把prompt模板中可能会变的注入token片段都移到整个prompt的最尾部,并按正常的方式来设置attention mask,但保持那些注入token片段的位置编码跟它们原来的相一致。(目前的位置编码方式也有影响,本节先忽略这个问题,在下一节讨论。)

这是一种相对简单的方式,但这种attention mask方式的修改应该会影响网络后续层的信息流方式。我怀疑这可能需要从0预训练的LLM才能支持,不确定单靠微调或少量继续预训练是否就能实现。

3.3、位置编码

当输入的修改涉及到改变token总数的时候,想要精确的缓存就需要考虑位置编码的问题。如果不限制位置编码的具体形式,那么理论上“任何改变token总数且在序列中间有改变的变更”都可能会通过位置编码影响到修改处后面大部分token位置的后续计算。

解决这个问题的简单思路有两种:

  • 在prompt中可能会增删的位置设置固定长度的“空白token”buffer,这些“空白token”可以是训练中故意加入的无语义token,也可以是在attention计算中直接忽略的特殊token。在这些位置出现新增时,可以直接覆盖buffer中的这些空白token,出现删除时,空出的位置也可以直接使用这些空白token不足。

  • 重新设计位置编码和训练数据的准备过程,让模型能够适应位置编码的间断性跳跃,甚至做层次性的位置编码设计。目标是让中间位置的变更出现时,不影响后续固定token的位置编码的值。

同样,我目前也怀疑这些方案可能需要从0预训练的LLM才能支持,不确定单靠微调或少量继续预训练是否就能实现。

3.4、考虑重新设计的LLM模型架构

如果都需要重新设计attention mask和位置编码并重新训练了,那么这跟直接新设计一个针对次场景的LLM也没有太多区别了。

我们可以打开一点思路,直接全新的考虑一个特化的LLM架构,主体视角见 展望LLM与半结构化I/O【2023Q4】 。

一种简单的思路是,半结构化的输入是chunk array结构。某些chunk是固定的,对应于历史message和prompt模板中的固定信息;另外的一个或某些chunk会在流式增量的输入中出现修改。chunk的划分由API调用方根据prompt模板进行指定。输出仍然是传统的单token序列。

在此种设计上:位置编码是分2层的,chunk内的位置编码和chunk之间的位置编码,后者需要新的设计。chunk之间的attention mask也需要新的设计。设计目标都是为了实现前面2小节的目标,尽量在出现变更的情况下可以直接精确地复用之前的计算结果缓存。

对于这种结构的训练语料/微调数据确实需要基于新的结构来准备,有一些成本,但并非无法实现。

3.5、历史输出结果的复用

上述方案主要针对的都是输出token序列(的前缀)与之前的中间结果计算一致时,如何尽量直接复用原有计算的缓存。

但输出一般总会在某些位置开始出现与之前结果的不同,虽然从整个输出来看与上次结果的diff量未必很大,但输出过程中一旦出现不同,则可能会显著的影响后续的生成内容。最差的情况是自从某个位置开始后续的整个输出都与前面不同,此时之前的计算结果就是很难复用的。本节更多关注输出结果只是部分改变,仍然在变更出现后存在某些token片段可以复用的情况。

要复用输出结果,首先要考虑的就是LLM decode过程中的随机性问题,除了贪心解码场景之外,随机解码都可能会有多种输出结果。想要在随机解码的情况下复用之前的结果,必然涉及“对于每个输出token,如果历史缓存在本次计算的token分布中的概率可被接受,就可以被接受”这样的方式,并与此伴随一个接受标准的设置。这个方式可以无缝迁移到输入的prompt不同的情况下。

在本节讨论的场景下,一种比较自然的思路是使用类似lookahead decoding(https://lmsys.org/blog/2023-11-21-lookahead-decoding/)的方式。它是虽然是针对于单轮解码过程的加速,但也适用于本文的增量修改场景。它的主要思路是对于历史输出的token序列使用n-gram的方式进行存储,并在后续推理是尝试进行匹配,如果匹配成功则可以直接在一次推理中并行验证多个位置,如果验证成功则可同时确认多个输出token。具体细节可以参看上面的链接。它对于相似请求之间的重复的输出片段、以及对于输出片段的位置平移性比较好。

4、使用方式

4.1、API调用的设计

很明显流式增量输入需要新的API接口设计。

对于每次LLM API调用,要改成创建一个session,创建session时指定prompt中的固定信息、chunk切分信息等等。应用侧不断的发送最新的prompt(或可变更chunk的)快照,LLM服务端根据新变化到来的情况和自身计算速度对于新内容进行计算。服务端异步的流式返回最新的输出序列快照,返回结果触发的方式可以根据场景配置。当最终输入确定时,应用侧向API服务端发送最终输入,并等待结果返回。

当然这种方式需要更多的计算量,更多的服务端低延迟状态缓存,所以也就需要更贵的API收费标准。这是为了追求大参数LLM模型的快速返回所必须支付的成本。

目前看起来,单次LLM请求如何进行流式增量输入和流式增量输出的能力,很大程度上依赖于LLM API供应商来提供。

4.2、在workflow上的流式增量计算

现在的很多应用其实都涉及多个LLM调用环节,以及LLM的流式增量计算也可以增量的输出当前结果快照,就可以把前一轮LLM的流式结果直接输入给后续环节,无论是LLM还是其他处理流程。这样后续流程就可以更早的收到潜在的输入信息,并更早的开始计算。

列举一些在实际workflow中可受益的情况:

  • 后续的LLM流程也可以更早的开始预计算,降低最终请求到来时候的处理时间。

  • 对于分类性任务环节,甚至可以在用户完整输入完之前更早地把输出流稳定在最终分类结果,让分叉后的执行流可以更早的启动、或在激进的预计算策略下提早剪枝掉不会进入的路径。

  • 对于function calling等,也可能在用户完整输入完之前更早地把输出流稳定在最终选择的function及其参数上,让提前触发function calling结果的执行更加可行。

5、总结

我不觉得所有问题都靠小一些参数量的LLM模型是唯一正确的方式,总有些问题还是需要大参数量模型的。而本文就是为此准备的方案。

希望在未来的一年内能在商用LLM API和开源LLM推理库上看到类似的方案。

觉得这个思路不错,想认真在这方面研发的团队,欢迎联系我沟通交流。

交流与合作

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

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

本文于2024.2.4首发于微信公众号与知乎,V1版首发于2023.1.31.

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

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

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

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