长提示如何阻塞其他请求 - 优化LLM性能

在我们关于 LLM 性能系列的上一部分中,我们探讨了 token 生成过程中预填充和解码阶段之间的差异。简而言之:对于第一个输出 token (预填充步骤),需要处理整个提示,这可以高效并行化并充分利用 GPU。对于所有后续输出 token (解码步骤),只需处理一个额外的 token,这计算量较小但必须按顺序进行。当许多请求同时处理时,任何旨在降低延迟的策略都需要在先前调度的请求的解码步骤仍在进行时运行新到达请求的预填充步骤。因此,同时处理新请求和正在运行的请求需要在预填充和解码阶段之间进行仔细平衡,这带来了两个主要挑战,我们将在下文中讨论。其中一个是可以轻松解决的问题,而另一个则构成一个更根本的缺陷。
更简单的挑战:长提示阻塞队列
由于单个解码步骤的计算强度不高,可以通过批处理多个请求的解码来提高吞吐量。然而,对于预填充,这种方法行不通。由于所有提示 token 的并行处理,单个预填充步骤就足以使 GPU 利用率饱和。因此,在 vLLM 的默认分块预填充策略中,每个预填充块仅包含单个请求的提示 token。队列中的下一个请求必须等到前一个预填充阶段完成才能开始自己的预填充阶段。
这种针对不同请求的预填充块的顺序调度带来了挑战:每当调度一个带有非常长提示的请求进行预填充时,任何后续请求都必须等待长预填充完成才能开始处理;一个长提示会阻塞预填充队列。(请注意,预填充的顺序处理是分块预填充的默认特性,并且仅在已存在并发请求处于其解码阶段时才会出现;因此得名“部分预填充”。)

不幸的是,这个挑战既不能通过 vLLM 侧的优先级调度(参见本系列的第一篇文章)也不能通过更复杂的上游调度器来解决。原因是长提示可以在任何后续请求存在之前就被调度,所以调度器没有什么可等待的。
请求并行预填充
一个直接的解决方案是并行处理不同请求的预填充块。这可能不是资源优化的,因为单个请求的预填充块可能已经使计算能力饱和。任何额外并行执行的预填充都可能会稍微延长预填充持续时间,并进一步减慢任何并发的解码请求。如果这能减少短请求的延迟并使系统看起来更具响应性,那么这是可以接受的。然而,当队列中的下一个请求也具有长提示时,这种方法就会失败。在这种情况下,两个计算密集型预填充将一起进行批处理,并导致严重的减速。
在最近的 vLLM 更新中,实现了一种改进的策略:它允许不同请求的并行预填充,但限制了同时处理的长提示请求的数量。例如,一个配置可以启用批处理四个请求的预填充,但其中只有一个请求的提示 token 长度可以超过 10,000。在这种配置下,长请求的行为仍然和以前一样:长提示按顺序处理。然而,短请求不再需要等待前一个请求的长预填充完成;短提示可以走快车道。这些请求不再遭受长时间的等待,并显示出更低的首个 token 生成时间指标。
当然,并行预填充只能缩短等待时间;但在并发长预填充操作期间,每输出 token 时间仍然会增加。在这方面,请求并行预填充显示出与标准分块预填充相同的行为和性能,只是首个 token 生成时间更短。

根本性缺陷:并行预填充导致 token 生成速度变慢
每当不同请求的预填充和解码在同一个 GPU 操作中执行时,所需时间都会比单独的解码步骤更长。用户会体验到 token 生成被后续请求中断或减慢。特别是,一个带有长提示的请求足以减慢所有先前已调度并处于解码阶段的请求。
这是在相同 GPU 上并发处理预填充和解码的一个根本性缺陷,因为您几乎无能为力:
- (a) 您可以惩罚长提示并让它们等待(例如,直到所有短的、高优先级的请求都已完成)。这会增加这些请求的延迟,并且无法解决根本原因:特别是当启用请求并行预填充时,减速也会影响长提示请求之后调度的短提示请求。此外,在高负载时期,长提示请求在合理时间内被调度的机会会非常小。在 TNG,我们为批处理请求的 API 实施了类似的策略,这些请求以非常低的优先级调度。
- (b) 您可以为长提示请求设置单独的推理服务器,并设置一个路由器,根据负载和提示长度转发请求。这种方法需要更多的 GPU 资源,但短上下文请求的推理服务器对 GPU 内存的要求较低(例如,Llama-3.3-70B 对于 130k token 的上下文长度需要四个 H100,但第二个部署只需两个 H100 就可以处理上下文长度小于 10k token 的请求)。然而,为了优化资源利用率,需要一个复杂的路由器设计。例如,当没有长提示请求时,应该仍然利用较大的推理服务器。
- (c) 您可以拥有独立的预填充和解码推理引擎。这种解耦预填充的架构结合了多个 vLLM 部署,每个部署只运行预填充或只运行解码。完成预填充阶段后,KV 缓存会传输到解码工作器,这会产生少量通信开销。但由于预填充和解码在不同的 GPU 上隔离运行,因此不再存在由并发预填充引起的解码直接中断。
以下测量结果显示了理想并发处理(与隔离请求无异)、实际并发处理和解耦预填充策略之间的差异:

解耦预填充 - 针对延迟优化
将预填充和解码分离在很大程度上消除了存在其他请求时 token 生成速度变慢的问题,这使其成为一种非常有吸引力的策略。其代价是需要第二个全尺寸 vLLM 部署(例如,对于 Llama-3.3-70B,如果您想支持 130k token 的最大上下文长度,则需要四个 H100 GPU 用于预填充工作器,另外四个 H100 GPU 用于解码工作器)。另一个缺点是 GPU 利用率不均匀:因为预填充是计算密集型的,而解码则不是,所以预填充工作器可能会在解码工作器之前使 GPU 利用率饱和。另一方面,大型集群可以由不同数量的预填充和解码工作器组成(取决于负载模式),以优化资源利用率。
解耦预填充的目的不是为了提高总吞吐量,而是为了提高总“有效吞吐量”(即满足延迟目标的请求速率)。因此,如果您的应用程序对单个请求的延迟不敏感,那么它并不是 GPU 资源的最佳利用方式。
另一个需要注意的地方:vLLM 中的解耦预填充功能仍处于实验阶段,一些优化和功能尚无法使用。例如,目前上下文长度有较低限制,并且解码工作器没有持续使用 CUDA 图,导致上图中长提示请求的解码速度变慢。幸运的是,这些都不是根本性障碍,很可能在未来的 vLLM 版本中得到解决。