@akshay_pachaar: https://x.com/akshay_pachaar/status/2094490705024676272
摘要
这篇文章解释了用于在GPU上服务大型语言模型的静态、动态和连续批处理策略,详细说明了它们的权衡以及如何优化吞吐量和减少空闲时间。
查看缓存全文
缓存时间: 2026/09/01 23:50
大语言模型中的静态批处理 vs. 动态批处理 vs. 连续批处理:清晰解析
你需要了解的一切,明白为何你的GPU在负载下闲置,以及哪种批处理策略能解决这个问题。从第一性原理出发解析三种策略,每种策略留下的缺陷,分块预填充技术,以及如何根据你的工作负载进行选择。
现代GPU每秒可执行数万亿次浮点运算。在单卡上运行大语言模型时,你常常会观察到它仅利用了算力的一小部分。
原因在于,生成单个token意味着需要从内存中读取模型的所有权重。读取操作占据了绝大部分时间,而计算单元大部分时间都在等待。
批处理技术弥合了这一差距。只需读取一次权重,让多个序列共享同一次计算过程,内存成本就被所有请求分摊了。
所有推理引擎都采用了批处理。它们之间的区别在于何时决定批次组成:
- 静态批处理:在批处理开始前一次性决定
- 动态批处理:通过计时器定时决定
- 连续批处理:在每次前向传播时重新决定
最后一种策略带来了现代推理系统几乎全部的吞吐量提升。前两种策略值得先了解,因为它们各自存在缺陷,而后续策略正是为了解决这些缺陷而设计的。
今天我们将逐一剖析并理解这些策略。
开始吧!🚀
批处理存在的原因
A100 GPU每秒可执行约312万亿次BF16精度运算,内存带宽约为每秒2TB。解码过程完全依赖于内存带宽。
读取权重构成了全部成本,而计算单元则在等待中闲置。这种闲置算力本是免费容量,因此处理60个请求的批次与单个请求共享相同的权重读取过程,吞吐量随之提升,而内存读取时间保持不变。
请注意,吞吐量随批大小陡峭上升后趋于平缓:
- 低于拐点时,你处于内存带宽瓶颈,批处理几乎无成本
- 高于拐点时,你处于计算瓶颈,每个新增序列都会增加时间成本
拐点位置随模型、GPU和序列长度变化,需在自身硬件上实测。
现在你可能会想:既然批处理如此有效,为何不尽可能增大批次?
对于大语言模型而言,这比想象中困难。让我们理解原因。
大语言模型批处理的挑战
批处理技术比大语言模型更早出现。对于分类器或嵌入模型,这本质上是个打包问题:
将输入填充至相同长度,堆叠成单个张量,执行一次前向传播即可。
这种方法有效基于三个前提:单次传播生成完整答案、行与行之间无依赖、每行的计算成本在开始前已知。
但对于自回归大语言模型,这三个前提全部失效:
- 单次传播只生成一个token,而非完整答案:400 token的回复需要400次前向传播
- 行依赖于自身历史:每次传播都会写入该请求的KV缓存,下次传播需要读取
- 计算成本未知:模型通过生成终止token来结束,可能出现在12 token后,也可能在4000 token后
填充技术无法解决这个问题。它只能对齐输入宽度,而无法缩短请求占用GPU的时间。
因此,固定起点的批次只能以最慢请求的速度运行。三种策略正是针对这一问题的渐进式解决方案。
静态批处理
最简单的方案不做任何处理:收集固定数量的请求,共同运行,等所有完成后一起返回。
最终状态如下图所示:
R3在t=9时刻停止生成,但由于R2仍在运行,它必须在GPU上等待至t=15。这造成了双重浪费:
- 已完成响应的用户额外等待6个时间单位
- 被占用的槽位本可服务于排队请求
浪费程度随输出长度方差增大而加剧。Anyscale在40GB A100上对OPT-13B的测试显示:输出长度分布变宽时,静态批处理的吞吐量降至约81 token/秒,而连续批处理仍保持一个数量级的性能优势。
有时仍是正确选择:当输出长度固定时(如分类、嵌入和评分任务),静态批处理不会产生等待问题,且实现更简单。
vLLM中的静态批处理配置示例:
from vllm import LLM, SamplingParams
llm = LLM(model="meta-llama/Llama-3.1-8B-Instruct")
outputs = llm.generate(prompts, SamplingParams(max_tokens=64))
通过限制max_tokens并保持提示长度相近,所有序列几乎同时完成。
👉 Hugging Face pipeline API的batch_size参数也属于静态批处理,适用于评估脚本,但不适合生产环境。
动态批处理
静态批处理还有第二个成本:请求可能在进入批次前等待很长时间。
如果批大小为8但只有5个请求到达,这5个请求必须空闲等待剩余3个。
动态批处理增加了计时器:当达到批大小限制或超时时间到期时触发批次(以先到者为准)。
最终状态如下图所示:
批次1在t=4时刻(固定间隔)触发,此时仅有R1和R2,因为第三个请求尚未到达。两者比静态批处理提前4个时间单位启动。
但它缩短了错误的等待时间:R1在t=7完成,仍需等待至t=12(因R2未完成)。
动态批处理为每个批次做一次决定,之后控制权交给引擎直至所有成员完成。对于固定输出模型,这已是完整解决方案(Triton推理服务器采用此模式)。
dynamic_batching {
preferred_batch_size: [ 4, 8 ]
max_queue_delay_microseconds: 100
}
👉 preferred_batch_size定义调度器尝试形成的批大小,max_queue_delay_microseconds限制请求最大等待时间。
连续批处理
前述两种策略都将批次视为整体工作单元,一旦开始就持续到所有请求完成。这个假设仍在造成资源浪费,而连续批处理彻底抛弃了它。
调度器执行一次迭代后收回控制权并重新决策。当某个序列生成终止token时立即离开批次,下一个迭代中等待的请求即可占用该槽位。
批次组成在每次迭代时变化,因此称为迭代级调度。没有任何槽位需要等待最慢序列,因此即使输出长度差异巨大,GPU也能保持饱和状态。
最终状态如下图所示:
瓶颈在于内存而非计算:每个活跃序列都持有随token增长的KV缓存,正是缓存(而非计算)决定了能并行处理多少序列。
在驻留13B模型的40GB A100上,一次只能处理少量长序列。当缓存池耗尽时,调度器会驱逐运行中的请求并稍后重新计算。
这看似GPU算力不足,实则是重复计算相同的预填充过程。
👉 各引擎用不同名称实现连续批处理:vLLM、SGLang和TGI称之为“continuous batching“,TensorRT-LLM称为“in-flight batching“,LMDeploy称为“persistent batching“。
分块预填充
每次迭代重建批次解决了慢成员问题,但引入新问题——用户感知为卡顿。
新加入的请求需要在单次迭代中完成预填充。32K token的提示会产生一次巨大的计算密集型传播,所有活跃解码进程都必须等待,导致其他请求的输出中途暂停。
下图对比分块预填充前后的差异:
整体预填充:
分块预填充:
(请求C的提示被分解为多个2K token的小块)
跨迭代分割提示:分块预填充将提示分解为固定大小的token范围,通过多次传播逐步扩展请求的KV缓存。
注意力计算逻辑不变,因为后续块会关注先前块处理的内容。第一个token在最后一块处理完成后才到达。
预填充受计算限制,解码受内存限制,因此同时包含两者的批次能充分利用芯片的两个部分。
选择分块大小时需注意:
- 较小分块为调度器提供更多解码机会,减少活跃请求的ITL(首token间延迟)尖峰
- 较大分块能更高效处理新提示,通常改善TTFT(首token延迟),但活跃解码进程可能在token间等待更久
- 过小分块会降低GPU利用率并增加注意力开销,因为后续块需要重新读取前序块创建的KV缓存条目
vLLM V1默认启用分块预填充,通过–max-num-batched-tokens限制每迭代最大token数;SGLang使用–chunked-prefill-size,设为-1则禁用。
vLLM示例:
vllm serve meta-llama/Llama-3.1-8B-Instruct \
--max-num-batched-tokens 8192
SGLang示例:
sglang serve --model-path meta-llama/Llama-3.1-8B-Instruct \
--chunked-prefill-size 8192
👉 没有通用的最佳分块大小。它取决于模型、GPU、提示长度分布以及你的SLO(服务等级协议)衡量的延迟指标。
结论
三种策略仅在决定批次的时机上有所不同,各自适应不同的工作负载形态。
对于输出长度可变的生产流量,连续批处理是唯一可持续的方案,所有主流引擎都默认启用此功能。
本文基于一个GPU事实:前向传播的瓶颈在于从内存读取权重,而非数学计算。
我曾撰文详细解析GPU工作原理:
Akshay @akshay_pachaar·2023年8月13日 文章《GPU如何真正工作》 大语言模型工程师需要的直觉理解,无需硬件手册背景。 读完后,量化、推测解码、连续批处理等技术将不再只是技巧列表… 12146802581K
文章从第一性原理解释内存与计算竞争的原因、硬件中这种差距的根源,以及什么使工作负载受限于内存带宽。无需任何背景知识,它是理解本文所有内容的基础。
敬请期待更多内容!
感谢阅读。
祝好!:)
相似文章
@akshay_pachaar: LLM推理中的批处理策略,清晰解释!(收藏它)- 静态 - 动态 - 和连续批处理 I w…
一篇解释LLM推理中静态、动态和连续批处理策略的文章,以及为什么提供LLM服务与传统ML推理不同。
@akshay_pachaar: https://x.com/akshay_pachaar/status/2087928032904523980
一条科普帖,讲解GPU的工作原理,重点在于主导LLM服务性能的内存-计算不对称性,并说明量化、投机解码和连续批处理等技术如何从这一根本约束出发。
@pallavishekhar_: LLM中的连续批处理 阅读:https://outcomeschool.com/blog/continuous-batching-in-llms…
一篇介绍连续批处理的博客文章,该技术通过动态地将新请求添加到已完成请求的批次中,持续保持GPU忙碌并减少空闲时间,从而提高LLM服务吞吐量。
@akshay_pachaar: https://x.com/akshay_pachaar/status/2084992645966016757
一份技术指南,演示如何使用开源工具在单个 GPU 上部署五个专用的小型模型(SLM、OCR、NER、重排序器、目标检测器),涵盖内存管理、批处理以及 Superlinked Inference Engine。
在连续批处理中实现异步性
本文解释了如何为LLM推理实现异步连续批处理,将CPU批处理准备与GPU计算重叠,以最大化利用率并减少空闲时间。