第 7 章 — 进阶 Batching 策略

发布于: 2026-04-29 最后更新于: 2026-07-08 版本: 1
第 7 章 — 进阶 Batching 策略

第 7 章 — 进阶 Batching 策略

LLM Primer VI: Scaling AI Systems》章节走读的第七篇。这一章要说的是:batching 不是一项优化,是让带宽受限 decoding 变得经济可行的那个决定性动作 —— 而且 batch 是动词,不是名词。


这一章为什么存在

一次 decode 步从 HBM 里读几十 GB 的权重,产出一个 token。既然 GPU 反正在流那些权重,那就可以在同一步里给很多请求各产出一个 token,边际成本几乎为零。所以 batching 在通常意义上不是一项优化 —— 它是让 decoding 在内存带宽受限的硬件上变得经济可行的唯一办法。但最直接的 batch 方式一碰到真实世界就崩,因为请求长短不齐、结束时间不齐。第 7 章从那个"差点就能用"的朴素方案开始走,一路走到生产 engine 真正在跑的迭代级调度器,再命名那个调度器留给第 8 章去还的债务。

一句话:Continuous batching 把 batch 从名词变成动词 —— 序列每一次迭代都在进出 —— 而这一变,让 KV cache 浮成新的绑定约束。

7.1 静态 batching 被 fastest-finisher 问题打败

静态 batching 是所有人第一次写的那种:收请求直到 batch 达到 B 或者超时,在整个 batch 上按最大长度 S_max 跑 prefill,decode 循环跑到 batch 里每一条序列都吐出 EOS 为止。两个成本合起来是灾难。Padding —— 一段 50 token 的 prompt 和 4,000 token 的 prompt 在同一 batch 里,前者要为后者付 80 倍的 prefill。fastest-finisher —— decode 循环要跑到每一条序列结束,一个 batch 里一条 20 token 请求和一条 2,000 token 请求,99% 的时间是 31 个槽在产没人要的 token。平均吞吐看起来还行,尾延迟被 batch 里最长的那条请求主导。动态 batching(可变超时、允许晚到者进 pending batch)缓解了等待,但没改变这个方案的本质;decode 一开始跑,同样的 fastest-finisher 问题就继承下来了。

7.2 Continuous batching 按迭代调度,不按请求调度

Continuous batching —— Orca 的迭代级调度、NVIDIA 的 in-flight batching,vLLM 和 TGI 那个"核心把戏" —— 是那个换元。不再把 batch 承诺在生成的整个生命周期里,调度器在每一个 decode 步都重新审视 batch 的组成。每一步之后:已完成的序列腾出槽位、释放各自的 KV;队列里的新序列填进腾出来的槽;联合 decode 步在新的活跃集合上跑。让这变得可行的是 LLM decoding 的两个性质。每一个 decode 步在结构上是同一个操作 —— kernel 不关心第 7 号槽和第 11 号槽里是哪一条序列。每一条序列各自持有自己的 KV cache,序列离开时它的内存就直接可用。公平性的单位变成了迭代,不是请求:一条 20 token 请求大约 20 个迭代就走完,无论 batch 里还有谁;一条 2,000 token 请求占一个槽多久算多久,不绑架别人。真实长尾流量 —— 短聊天回合和长 RAG 补全的混合 —— 上,70B 模型的 GPU 利用率从静态 batching 那种 10–20% 涨到 60–80%,p99 延迟同时明显收紧。

7.3 Chunked prefill 把 prefill 和 decode 揉在同一块芯片上

Continuous batching 仍然留了个张力。新到来的请求需要 prefill,那是 compute-bound、偏好长序列的一次前向传播。运行中的序列需要 decode,那是 memory-bandwidth-bound、偏好宽 batch 的。一段全新 4,000 token prompt 的完整 prefill,和 30 条正在跑的序列的 decode 步放到同一次前向传播里,要么拖慢正在跑的 decode(现有用户的 TTFT 变差),要么拖慢新请求的 prefill(新用户的 TTFT 变差)。Chunked prefill 把 prefill 切成小块 —— 比如一次 512 token —— 交错到活跃序列的 decode 步之间。单次前向传播里同时载着一部分新 prompt 的 prefill 工作和一部分正在跑序列的 decode 工作,两个阶段共用同一份权重流。Prefill 的 compute-bound 特性吸收算术;decode 的带宽需求被摊到每字节更多的有用工作上。两个阶段在单卡上不再对立。剩下那个"流量大到还是打起来"的情形,是第 12 章拆分部署的引子。

值得记住:Continuous batching 的吞吐胜利是真的,但它同时把内存问题重新提了一遍。序列每一步都在进出,engine 不能按槽预留一大板 KV。任何替代那块板的方案,都要撑得住持续的准入和淘汰 —— 所以要分页。

第 7 章为下面铺了什么

Continuous batching 做了自己的分内事,同时把 KV cache 暴露成新的并发绑定约束。每一条活跃序列都带着按当前长度算的 KV,序列每一步都在进出,engine 事先不知道任何一条会走多长。朴素的"每槽一板"布局会把 batching 的胜利大半还回去。第 8 章把操作系统那套解决方案搬过来:把 cache 切成小的物理 block,用一张 page table 把它们和逻辑 token 位置解耦,再用一份淘汰策略把 block 回收或跨序列共享。PagedAttention 就是那个让 continuous batching 的 KV 问题变得可解的动作。


下一章 — 第 8 章:新一代 KV Cache 管理PagedAttention、H2O 淘汰、InfiniGen,以及前缀缓存的经济学。

想看完整的全貌?书里这一章包括可跑的静态 batching 实现、按 vLLM/TGI 精神写的完整 continuous batching 调度器、六个请求穿过四槽 engine 的一步步走过案例,以及本文没地方展开的抢占/淘汰/准入控制讨论。在 Amazon 上查看 LLM Primer VI →

下田 昌平
下田 昌平
作为株式会社Receipt Roller的CEO兼CTO,目前负责开发电子收据服务以及自动将对话分类并生成行动任务的系统「ACTIONBRIDGE」。从小便接触编程,1996年参与开发测量仪器的相关程序,始终保持着对技术的深刻探索与热情。 在此前的职业生涯中,曾担任日本最大呼叫中心行业企业的子公司——一家研究开发公司的CEO/CTO,领导了多个技术开发项目。目前,我依然活跃在编程的最前沿,持续书写代码。