本文介绍一篇 LLM 推理加速技术相关的文章,值得读一读。
LLMs 在现实应用中的计算成本主要由服务成本所主导,但是传统的批处理策略存在低效性。在这篇文章中,我们将告诉你,为什么 Continuous Batching 连续批处理成为了解决这一问题的新方法,而不再把 LLMs 视为“黑匣子”。这个技术如何利用内存,而不是计算能力,来实现 10 倍以上的性能提升,将改变AI领域的游戏规则。文章标题:
How continuous batching enables 23x throughput in LLM inference while reducing p50 latency
文章链接:https://www.anyscale.com/blog/continuous-batching-llm-inference
为了更好地理解这篇文章,让我们先了解一下大型语言模型(LLM)的推断过程以及传统批处理策略中存在的低效性。
当我们谈论大型语言模型(LLM)的推断过程时,我们指的是使用已经训练好的模型来对输入文本进行处理,从而生成相应的输出。推断过程涉及将一个或多个文本片段传递给模型,并从模型中获取相应的预测或生成的文本。 在传统的批处理策略中,文本通常会被分成小批次(batch)进行处理,以便在 GPU 或其他硬件上进行并行计算。然而,由于 LLMs 通常需要大量的内存和计算资源,传统的批处理策略可能会导致一些低效性:
难以处理长文本:传统批处理策略可能会限制模型处理长文本的能力,因为它们可能无法一次性将整个文本载入内存。
传统的处理方法将 LLMs 视为“黑匣子”,主要通过内部更改(如量化和自定义 CUDA 内核)来进行优化。这种方法忽视了 LLMs 在推断过程中生成输出的迭代性质,以及 LLM 推断通常受限于内存而不是计算资源。由于 LLMs 通常需要大量的 GPU 内存和计算成本,这导致在实际应用中,服务成为计算成本的主导因素。 因为 LLMs 在推断过程中需要迭代生成输出,而且通常情况下是内存受限的,所以存在着可以在系统级别进行批处理优化的机会。这意味着可以通过合理的批处理策略来最大程度地利用 GPU 资源,从而显著提高推断吞吐量,降低计算成本,使得服务成本不再是主导因素。
作者之所以持这样的看法,是因为他们认为 LLMs 在生成输出时是迭代进行的,并且 LLM 推断通常受到内存而不是计算资源的限制。这意味着存在一些出人意料的系统级批处理优化方法,可以在实际工作负载中产生显著的性能提升。 相较于将 LLMs 视为不可调优的“黑匣子”,作者认为可以通过采用更灵活的系统级批处理策略来实现性能的大幅度提升,而不仅仅局限于内部更改如量化和自定义 CUDA 内核。这样的优化方法可以使得在实际应用中,LLMs 的性能提升达到 10 倍甚至更多。
当作者提到 LLMs 通常是内存受限而不是计算受限时,他指的是在 LLM 推断过程中,通常更多地受到可用内存的限制,而不是计算能力的限制。 内存受限意味着在处理大型语言模型时,系统的内存资源是一个相对稀缺的资源。这意味着模型在推断时需要将许多数据存储在内存中,例如输入文本、中间计算结果等。如果内存不足以容纳所需的数据,可能会导致内存溢出或性能下降。 相比之下,计算受限指的是在进行模型推断时,计算资源(例如 CPU 或 GPU 的处理能力)是主要的瓶颈。这种情况下,系统的处理能力会成为推断性能的主要限制因素,而内存资源可能并不是主要的瓶颈。 因此,在 LLM 推断中,作者指出通常更关键的是如何有效地利用有限的内存资源,而不是解决计算资源瓶颈。通过优化内存的使用方式,可以使得在实际工作负载中推断性能提升 10 倍甚至更多。这意味着通过合理地调度和利用内存,可以显著地提高 LLM 模型在实际应用中的性能表现。
当进行 LLM 推断时,对于每一个请求,我们会首先提供一个称为“前缀”或“提示”的 token 序列作为输入。这个前缀通常包含了一个或多个起始 token,用于引导模型生成接下来的文本。例如,在文章中的例子中,前缀是句子:“What is the capital of California:”。 这个前缀的目的是为了提供模型一个起点,使其能够理解用户的请求并生成相应的响应。在这个例子中,前缀引导模型去回答加利福尼亚的首府是什么。 一旦提供了前缀,LLM 会开始生成一个完整的响应序列,它会在产生一个终止 token 或达到最大序列长度时停止。这是一个迭代的过程,每一次前向传递模型都会产生一个额外的完成 token,逐步构建出完整的响应序列。
达到了设定的最大序列长度,这时也会停止生成。
当以句子“加利福尼亚的首府是什么:”作为提示时,LLM 会逐步生成完整的响应。这是一个迭代的过程,每次迭代都会产生一个新的完成 token。 示例迭代过程:
第十次迭代:LLM 生成第十个 token "o",此时我们有完整的响应:["S", "a", "c", "r", “a”, "m", "e", "n", "t", "o"]。
当作者提到了 Byte-Pair Encoding(字节对编码)时,实际上指的是一种流行的文本压缩和编码技术。它的主要作用是将文本中的字符或字节序列进行编码,以便更有效地表示和传输文本数据。 具体来说,Byte-Pair Encoding 通过识别和合并在文本中频繁出现的字符对(字节对),来构建一个更紧凑的编码表。这使得一些常用的字符或词组可以用更短的编码表示,从而减小了文本的总体大小。 在大型语言模型(LLM)的上下文中,使用 Byte-Pair Encoding 可以帮助将原始文本转化为模型可以更有效地处理的编码形式。这也是为什么在实际情况中,token 与 ASCII 字符并不是一一映射的原因之一。
在这个玩具示例中,图中的元素代表了 LLM 推断的一些关键组成部分:
批处理大小(Batch Size):这里展示的示例只包含一个输入序列,因此批处理大小为1。
当处理一个请求时,预填充阶段扮演着关键的角色。这个阶段起初可能会花费一些时间,但它在整个生成过程中扮演着非常重要的作用。 在预填充阶段,模型会提前计算一些关于注意力机制的输入信息。这些输入信息是在生成过程中保持不变的,因为它们与前缀(或提示)无关。这样做的好处是,在每次进行后续的生成时,不需要重新计算这些输入信息,从而节省了计算资源和时间。 具体来说,这些提前计算的输入信息可以帮助模型在生成后续 token 时更高效地利用 GPU 的并行计算能力。这是因为这些输入信息可以独立计算,而不受后续生成过程的影响。 总的来说,预填充阶段的作用是优化模型的生成过程,通过提前计算一些与前缀无关的输入信息,从而在后续的生成过程中节省计算资源和时间。 这个阶段的存在是为了使整个生成过程更加高效和快速,尤其是对于需要生成大量 token 的情况下,可以明显地提升性能。
当作者提到 LLM 推断是内存 - IO 受限而不是计算受限时,意味着在 LLM 推断过程中,主要的瓶颈并不在于计算速度,而在于数据的传输速度,特别是从主内存加载数据到 GPU 内存的过程。 这对 LLM 推断的吞吐量有着重要的影响。具体来说,由于数据传输速度相对较慢,如果我们可以减少需要从主内存加载到 GPU 内存的次数,就能提高推断的效率,从而提高吞吐量。 GPU 内存在这里起到了关键的作用。它是临时存储模型参数、输入数据和计算结果的地方。在 LLM 推断过程中,模型参数需要在 GPU 内存中保留,同时输入数据也需要被加载到 GPU 内存中才能进行计算。因此,GPU 内存的大小限制了我们可以处理的数据量以及批次的大小。 总的来说,GPU 内存的充足与否直接影响了 LLM 推断的性能和吞吐量。如果我们能够优化内存的使用,比如通过模型量化策略或其他方法减少内存占用,就能提升推断效率,从而实现更高的吞吐量。
当基本模型大小和 token 序列长度增加时,GPU 内存的消耗量也会相应增加。这是因为更大的模型和更长的序列需要更多的内存来存储它们的参数和生成的中间结果。 具体地说,一般可以使用以下方法来估算 GPU 内存消耗:
基本模型大小(模型参数):随着模型大小的增加,需要更多的内存来存储模型的权重、偏差等参数。一般来说,模型越大,所需内存就越多。
Token 序列长度:每个 token 都需要一定的内存来存储其编码和相关信息。因此,当序列长度增加时,内存消耗也会随之增加。
模型架构:不同的模型架构可能会对内存消耗产生不同的影响。一些模型可能会有特定的内存优化策略或特性,可以影响其在 GPU 上的内存占用。
GPU 类型和内存容量:不同类型和容量的 GPU 具有不同的内存限制。较大内存的 GPU 可以容纳更大的模型和序列。
其他辅助数据和计算:除了模型参数和 token 序列之外,还可能存在其他计算所需的内存,比如中间结果的存储等。
连续批处理:这是一种内存优化技术,不需要对模型进行修改。它可以提高 LLM 生成的内存效率。
合并前缀:对于多个请求,将它们的前缀合并成一个批次。这样做的好处是可以利用 GPU 的并行计算能力,因为可以一次性地计算多个请求的前缀。
提升模型推断效率:通过共享计算资源,连续批处理可以更高效地利用 GPU 的计算能力,从而提升了模型推断的速度。
模型参数的加载对 LLMs 的计算饱和度有很大影响是因为在 GPU 架构中,内存和计算是两个相对独立但又相互关联的方面。
内存带宽的限制:然而,GPU 的内存带宽相对有限。内存带宽是指 GPU 用于在内存和处理器之间传输数据的速度。加载模型参数意味着将模型的权重和其他相关数据从存储介质(如硬盘或内存)传输到 GPU 的内存中。
提高内存带宽的利用率:GPU 的内存带宽是有限的资源,而加载模型参数通常会消耗大量的内存带宽。通过批处理,可以更有效地利用这些内存带宽,使其在计算过程中得到更充分的利用。
提高计算资源的利用率:LLM 推断通常是内存 - IO 受限的,而不是计算受限的,意味着加载数据到 GPU 的计算核心比在计算核心上执行 LLM 计算花费的时间更多。通过批处理,可以更有效地利用计算资源,提高计算速度。
传统的批处理方法被称为静态批处理,是因为在这种方法中,批处理的大小在推断完成之前保持不变。与 LLM 推断的迭代性质相关的是,在 LLM 推断过程中,每个请求是逐步生成的。具体来说: 在静态批处理中,一次性加载了模型参数,并在整个推断过程中重复使用这些参数来处理多个输入序列。这样做更有效地利用了芯片的内存带宽,提高了计算利用率、吞吐量,并降低了 LLM 推断的成本。 然而,LLM 推断是一个迭代的过程。对于每个请求,模型会逐步生成输出序列的各个部分,直到生成停止标记或达到最大序列长度为止。这意味着每次模型前向传递时,都会获得一个额外的输出 token。例如,如果我们以句子“加利福尼亚的首府是什么:”作为提示,它将需要进行十次前向传递才能得到完整的响应,即 ["S", "a", "c", "r", “a”, "m", "e", "n", "t", "o"]。 由于 LLM 推断是一个迭代生成的过程,静态批处理可能导致 GPU 在批处理中的不同序列的生成长度不同时被低效利用。因为不同的序列可能会在批处理中的不同迭代步骤中完成生成,而静态批处理会等待所有序列完成生成后才开始处理新的序列。这导致了在等待最后一个序列完成生成之前,GPU 可能会被低效利用的情况。
Variance in Generation Output:在静态批处理中,如果不同序列的生成长度差异较大,那么某些序列可能会迅速完成,而其他序列则需要更长的时间。这种差异性会导致 GPU 的部分计算资源一直处于闲置状态,因为它们无法立即用于生成更多的序列。
静态批处理在输入和输出序列长度不相等的情况下会低效利用 GPU。举例来说,假设我们有一个 LLM 模型,可以接受最多 512 个 token 的输入序列,但其生成的输出序列可能长度不一。如果我们采用静态批处理,即将一批输入序列一次性加载到 GPU 中进行推断,那么如果批中的不同序列生成长度不同,就会导致以下情况:
假设我们有一个批次,其中包含了以下两个输入序列:
输入序列 2(长度为 512)生成的输出序列长度为 30。
输入序列 2 生成的输出序列长度为 30,但在整个批次处理期间,GPU 只能等待最长的生成过程完成。这导致 GPU 的计算资源被浪费了 10 个 token 的长度。
连续批处理是一种相对于静态批处理更高效的方法,特别适用于 LLM 推断。它的工作原理如下:
提高 GPU 利用率:连续批处理通过更灵活地利用 GPU 的计算资源来提高 GPU 的利用率。在静态批处理中,如果批次中的不同序列的生成长度不同,GPU 会被低效利用,因为它必须等待批次中的所有序列完成生成才能开始下一个批次。而在连续批处理中,一旦一个序列完成生成,就可以立即开始处理下一个序列,从而最大程度地减少了 GPU 的闲置时间。
当使用迭代级别调度时,相较于静态批处理,批次的大小是在每个迭代中动态确定的,而不是在推断过程的开始时就固定下来。这意味着一旦批次中的某个序列完成生成,就可以立即插入一个新的序列以继续利用 GPU 进行计算。 相对于静态批处理,迭代级别调度具有以下优势:
更高的推断吞吐量:由于 GPU 的计算资源得到更充分的利用,相对于静态批处理,迭代级别调度可以实现更高的推断吞吐量,从而加快了整个推断过程的速度。
选择使用连续批处理这个术语是因为它最准确地描述了优化方法的本质。下面是连续批处理与动态批处理以及迭代级别调度之间的区别:
迭代级别调度:迭代级别调度与连续批处理类似,它也是在生成过程中动态调整批处理的大小。但它强调的是调度是以迭代为单位的,也就是说,在每个迭代中决定批处理的大小,而不是随时随地都可以进行调整
参考文献
https://mp.weixin.qq.com/s/bs3puOXFZYg5K-zfyDfpOw
原文标题:Continuous Batching:解锁LLM潜力!让LLM推断速度飙升23倍,降低延迟!
文章出处:【微信公众号:智能感知与物联网技术研究所】欢迎添加关注!文章转载请注明出处。
全部0条评论
快来发表一下你的评论吧 !