深夜的城市公交调度中心,值班员老张发现一个怪象:最后一班定时班车已经空跑了三趟,可站台上还站着十几个等车的乘客——因为下一班要 30 分钟后才发。把班车改成“实时拼车”后,小巴在环路上循环行驶,每空出一个座位,调度系统就立刻把候车乘客补上去。车几乎永远满载,乘客也不再干等。 大模型推理服务正在经历同样的变革。GPU 就像那辆班车,每个推理请求就像乘客。传统的静态批处理像定时班车,固定凑满一批才统一出发,GPU 常常空转、请求互相拖累;而 Continuous Batching(连续批处理)像实时拼车,一个请求生成完,新请求立刻补进空位,让 GPU 始终满载。这套机制把推理吞吐量提升数倍,是 vLLM 等主流推理框架的核心秘密。本文将带你从静态批处理的痛点出发,一步步看懂动态插入、KV Cache 共享、框架实现与性能指标。
对 AI 推理工程师来说,Continuous Batching 是决定推理服务成本与吞吐的核心技术——掌握它,能让同一块 GPU 服务数倍的用户。
目录
可视化演示
动态演示Continuous Bat的核心机制,通过开始/执行/监控/完成四种模式展示关键概念与交互流程。
本节配有交互动效(进入视口后自动加载,约 3KB)。
Continuous Bat:静态批处 静态批处理的吞吐瓶颈与连续批处 迭代级调度与动态请求插入机制 核心机制可视化 Continuous Bat整体架构与关键组件
静态批处理:定时班车的空驶之痛
调度员老张管理的线路曾经是定时班车制:每隔 30 分钟发一班,凑够一车人才走。乘客少时车辆空驶,乘客多时后面的人只能眼巴巴等下一班。更麻烦的是,只要有一位乘客动作慢,整车人都得陪着他等。
大模型推理最早采用的就是这种“定时班车”——静态批处理(Static Batching)。多个请求凑成一个 batch 后统一进入 GPU,整个批次同步推进。它的三个先天缺陷和定时班车一模一样:一是请求要排队等批;二是同一批里每个请求生成的 token 数量差异巨大,最长的请求拖住所有人;三是 GPU 在等待和收尾阶段大量空转。
假设一个 batch 里有 B 个请求,第 i 个请求需要生成 L_i 个 token,GPU 的峰值算力为 C,总耗时 T。静态批处理的 GPU 利用率可以写成:
这就是定时班车的空驶之痛:车在跑,座位却空着;人在等,车却发不了。要解决它,必须让“发车”不再受固定批次约束。
- 分子所有请求的任务总量 — 把批内每个请求生成的 token 数逐一相加,得到这批请求实际需要完成的工作量。
- 分母理论算力预算 — 用 GPU 峰值算力 C 乘以总耗时 T,表示这段时间理论上最多能处理多少 token。
动态插队:实时拼车的补位智慧
老张引入实时拼车小巴后,规则彻底变了:车辆在环路上循环行驶,每到一站只要有空座就立刻让候车乘客上车,人满也不等,直接出发。每个乘客的目标站数各不相同,到站就下车,空位马上被下一位乘客补上。
Continuous Batching 正是这种“迭代级调度”。它不再把一个批次当作铁板一块,而是让调度器在每个解码步骤(decode step)结束后检查一次:哪些请求已经生成完毕?完成的立即移出批次,并立刻从等待队列插入新请求。插入操作发生在每一个迭代步之后,而不是等整批结束。
为了衡量这种调度的“满载程度”,定义占用率:设批次最大容量为 B_max,第 t 个迭代步的实际请求数为 B_t,共运行 N 步,则平均占用率为:
拼车小巴永远在补位,连续批处理也永远在补位。被取消的“整批等待”,正是吞吐量飞跃的起点。
GPU 利用率:让每块算力都坐满
公交公司算了一笔账:满载的小巴每公里的收益是空载车的五倍。调度中心的核心目标因此变成——让车辆在任何时刻都有尽可能多的乘客。
对 GPU 来说,让算力“坐满”同样关键。连续批处理通过两件事提升利用率:一是消除了批次之间的等待空档,GPU 不再发呆;二是长短请求互补,短请求快速下车、长请求继续占座,队列里始终有新请求补入,batch 保持高位。
推理服务的核心指标是吞吐量(Throughput)。设一次运行中 B 个请求各自生成了 output_len_i 个 token,引擎总耗时为 t_engine,则:
满载的车辆带来翻倍的营收。同样,当 GPU 的每条计算单元都在产出 token 时,成本被摊薄,用户等待也显著缩短——这就是连续批处理的商业魔力。
- 分子输出 token 总数 — 把所有请求生成的 token 数量相加,这是服务真正“卖给用户”的内容量。
- 分母引擎总耗时 — 从第一个请求进入引擎到最后一个请求离开的墙钟时间。
KV 缓存:乘客的行李牌与共享路段
拼车小巴上每位乘客都有一块行李牌,记录自己已经走过的站点。如果两位乘客要去同一片区,他们可以共享前一程的记录,不必重新认路。
大模型的自回归解码有个特点:生成下一个 token 时,需要重新计算前面所有 token 的注意力分数。为了避免重复劳动,推理引擎把每个已生成 token 的 Key(K)和 Value(V)向量缓存下来,这就是 KV Cache。它相当于每个请求的“行李牌”——生成得越长,行李越多,占用的显存也越大。
以一个 L 层、H 个注意力头、每个头维度为 D 的模型为例,序列长度为 S 的请求,它的 KV Cache 显存占用为:
行李牌多了,车厢就挤了。连续批处理下不同请求的行李牌交错摆放,如何高效安放,成了下一节的核心问题。
vLLM 框架:分页停车坪与调度器实战
拼车公司的停车场曾因碎片化浪费大量空间:每位乘客固定占一块区域,下车后区域零散无法合并使用。后来停车场改成“分块停车坪”,车辆按标准车位灵活停放,碎片空间瞬间被激活。
vLLM 正是把这个思路带进大模型推理的开源框架。它的核心是 PagedAttention:把 KV Cache 切分成固定大小的块(block),按需分配、不连续存放,像操作系统分页一样管理显存。配合迭代级调度器,vLLM 实现了真正的 Continuous Batching,并支持抢占(preemption):当显存不足时,先把低优先级请求的 KV Cache 换出到 CPU,腾出空间后再换回。
碎片率是衡量管理效果的关键指标。设已分配显存可容纳的 token 数为 Capacity,其中真正有数据的有效 token 数为 V_valid,则碎片率为:
分块停车坪让每一寸空间都用起来。vLLM 因此能把更多请求塞进同一块 GPU,这也是它成为当下部署大模型首选框架的原因。
性能指标:调度中心的 KPI 报表
调度中心每月出一份 KPI 报表:总运客量(吞吐)、最久乘客等待时间(长尾延迟)、每公里成本(成本效率)。三张表放在一起,才能评判拼车改革值不值得。
衡量连续批处理也一样,不能只看单一指标。吞吐量(tokens/s 或 requests/s)反映整体产能;TTFT(首 token 延迟)和 p99 解码延迟反映用户体验;显存占用与 GPU 利用率反映成本。连续批处理追求的是在满足延迟约束(SLA/SLO)的前提下最大化吞吐。
假设同一份请求流分别在静态和连续批处理下运行,得到吞吐量 TPT_static 与 TPT_continuous,加速比为:
KPI 报表最终告诉老张:满载的智慧不只在运量,更在让每一个等待都有价值。对推理工程师来说,理解这些指标,才能在成本和体验之间找到最优解。
AI 应用场景
大模型 API 服务的并发调度
阶段:模型推理部署(在线服务)
解决的问题:传统静态批处理下,高并发用户请求要么挤在等待队列里,要么让 GPU 反复空转,延迟和成本双高。连续批处理在每次解码后动态补位,让 GPU 满载,API 吞吐提升数倍,排队时间大幅缩短。
具体案例:某 LLM 提供商用 vLLM 部署 7B 模型,将服务端批处理从静态改为连续,在保证 p99 延迟小于 2 秒的情况下,吞吐量从 800 tokens/s 提升到 3500 tokens/s,GPU 数量不变。
离线批量数据分析中的长短互补
阶段:批量离线推理(数据处理流水线)
解决的问题:离线任务常混合短输出(摘要、分类)与长输出(报告生成),静态批处理被最长的任务拖慢整体周期。连续批处理让长短任务互补运行,缩短整条流水线的耗时。
具体案例:数据团队每天处理 10 万条新闻,其中 60% 生成 50-token 摘要、40% 生成 1000-token 报告。采用连续批处理后,流水线总体完成时间缩短约 60%。
多租户共享推理集群的 SLO 保障
阶段:推理资源池化管理
解决的问题:多个业务线共享 GPU 集群,请求长度差异大,容易互相干扰。连续批处理配合优先级抢占与大任务、小任务之间的动态平衡,可以保障核心业务延迟。
具体案例:某企业中台在共享集群上运行对话机器人与文档分析两个服务,通过按优先级调度和 KV 缓存抢占,核心对话服务的 p99 延迟稳定在 800ms 内,同时集群整体利用率提升 40%。
Python 演示
import random class FakeRequest: """模拟一个 LLM 推理请求, 需要生成 output_len 个 token""" def __init__(self, req_id, output_len): self.req_id = req_id self.output_len = output_len self.generated = 0 def is_done(self): return self.generated >= self.output_len def decode_one_step(self): self.generated += 1 return self.is_done() def simulate(mode, requests, max_batch): pending = list(requests) running = [] total_tokens = 0 steps = 0 if mode == "static": while pending: batch = pending[:max_batch] pending = pending[max_batch:] while batch: for req in batch: req.decode_one_step() total_tokens += len(batch) batch = [req for req in batch if not req.is_done()] steps += 1 else: while pending or running: while len(running) < max_batch and pending: running.append(pending.pop(0)) if not running: break for req in running: req.decode_one_step() total_tokens += len(running) running = [req for req in running if not req.is_done()] steps += 1 return total_tokens / max(steps, 1) def main(): random.seed(2024) reqs = [FakeRequest(i, random.randint(4, 12)) for i in range(60)] static_tpt = simulate("static", reqs, max_batch=8) random.seed(2024) reqs = [FakeRequest(i, random.randint(4, 12)) for i in range(60)] continuous_tpt = simulate("continuous", reqs, max_batch=8) print("静态批处理平均每步吞吐: %.2f tokens" % static_tpt) print("连续批处理平均每步吞吐: %.2f tokens" % continuous_tpt) print("连续批处理加速比: %.2fx" % (continuous_tpt / max(static_tpt, 1e-9))) if __name__ == "__main__": main()
def decode_one_step(self):每调用一次代表 GPU 解码一步,生成 1 个 token。batch = [req for req in batch if not req.is_done()]静态模式必须整批一起等,直到所有请求完成才换下一批。total_tokens += len(batch)这一步产出的 token 数等于当前活跃请求数。while len(running) < max_batch and pending:连续模式一有空位就立刻从等待队列补入新请求。running = [req for req in running if not req.is_done()]连续模式只移除本步完成的请求,不影响其他请求继续生成。return total_tokens / max(steps, 1)平均每步吞吐量,用于公平对比两种调度模式。
常见误区
误区我理解的连续批处理就是把一个等待队列里的请求尽量多塞进一次 batch,batch 越大效果越好,跟动态调度关系不大。
正解连续批处理的关键在“动态”而非“大小”。它让调度器在每一步解码后把完成的请求移出、把新请求插入,消除的是批次之间的等待空档。增大 batch size 只是静态批处理时代的参数调优,两者改善的瓶颈完全不同。
为何常见:因为连续批处理确实会让平均 batch 更大,初学者容易把因果倒置,以为“大”才是原因。
误区KV Cache 占用大量显存,是拖慢推理的元凶,干脆不用缓存,每次都全量重算更省心。
正解KV Cache 是用显存换时间,避免每次解码都重复计算历史 token 的注意力。连续批处理的显存管理(如 PagedAttention)恰恰是为了让缓存放得下、放得好。禁用缓存会让计算量随序列长度爆炸式增长,延迟反而剧增。
为何常见:KV Cache 在长上下文下确实显存占用可观,看到它变大就以为是浪费。
误区只要用了连续批处理,推理服务就一定更快,所以所有场景都应该无脑切换到 vLLM。
正解当并发很低、请求长度很均匀时,动态调度的开销可能吃掉收益。连续批处理的优势与负载特征强相关,高并发、长短混合、长上下文场景收益最大。选型时要先评估自己的请求分布。
为何常见:宣传材料常展示理想负载下的加速比,忽略了低并发场景。
误区PagedAttention 能做前缀共享,相当于把 KV Cache 压缩了,所以显存占用能降到原来的十分之一甚至更少。
正解前缀共享是让相同前缀的请求复用同一段缓存,减少的是重复存储,不是压缩数据本身。压缩靠的是量化或剪枝,前缀共享靠的是引用复用。两者机理完全不同,前缀共享的收益取决于请求前缀的重复程度。
为何常见:看到显存占用大幅下降,就容易和模型压缩技术混为一谈。
面试题
Q1. 连续批处理(Continuous Batching)的核心思想是什么?
- A.让所有请求长度保持一致
- B.增大每个批次的请求数量
- C.请求完成后立即补入新请求← 正确答案
- D.减小模型参数规模
答案:C。连续批处理强调“动态补位”——一个请求生成完立刻插入新请求,而不是等整批结束。A 是理想化假设,B 是静态批处理的调参思路,D 是模型压缩,都与核心思想无关。
Q2. 静态批处理最典型的痛点是?
- A.最慢请求拖住整批等待← 正确答案
- B.训练数据不足导致过拟合
- C.模型参数量过大难以部署
- D.梯度消失导致训练不收敛
答案:A。静态批处理像定时班车,整批同步推进,生成最长的请求决定了整批的完成时间,这就是“队头阻塞”。B、D 是训练阶段问题,C 是部署容量问题,均非批处理的核心痛点。
Q3. 连续批处理的调度决策发生在什么时机?
- A.用户提交请求的时刻
- B.模型权重更新的时刻
- C.一次完整推理结束之后
- D.每个解码步骤完成后← 正确答案
答案:D。调度器在每个解码步骤(迭代)结束后检查完成情况并补入新请求,因此是“迭代级”调度。A 只是请求入队时机,B 是训练时机,C 是静态批处理的收尾时机。
Q4. KV Cache 中保存的内容是?
- A.模型的全部权重参数
- B.训练时的梯度累积量
- C.已生成 token 的 K 和 V← 正确答案
- D.优化器的动量参数
答案:C。自回归解码时,已生成 token 的 Key 和 Value 向量被缓存下来供后续注意力计算复用,这就是 KV Cache。A 是模型权重,B、D 是训练过程中才有的状态,均与推理缓存无关。
Q5. vLLM 的 PagedAttention 主要解决什么问题?
- A.KV 缓存碎片化与按需分配← 正确答案
- B.提高单次矩阵乘法速度
- C.降低模型量化误差
- D.增加批次的并发数量
答案:A。PagedAttention 借鉴操作系统的分页思想,把 KV Cache 切成固定大小的块按需分配,大幅降低显存碎片。B 是算子优化,C 是量化技术,D 只是连续批处理的结果而非它解决的问题。
Q6. 什么情况下 KV Cache 前缀共享收益最大?
- A.两个请求使用完全不同的提示词
- B.请求长度都很短的场景
- C.模型参数量远大于数据量
- D.多个请求拥有相同的长前缀← 正确答案
答案:D。共享的是一段相同的 K/V 缓存,只有多个请求拥有相同的长前缀时才能大量复用。A 没有可共享内容,B 长度短则共享收益小,C 与缓存共享无直接关系。
Q7. 采用连续批处理后 GPU 利用率显著提升的主因是?
- A.请求长度被强制压缩到更短
- B.用更小精度的模型进行推理
- C.消除了批次间的等待与收尾空档← 正确答案
- D.让 GPU 时钟频率动态升压
答案:C。连续批处理在迭代级动态补位,GPU 在批次之间不再空转,同时长短请求互补填满算力。A 会损伤生成质量,B 是量化手段,D 是硬件层面的频率调节,都非调度机制的主因。
Q8. 以下哪些负载特征最适合采用连续批处理?
- A.高并发且请求有长有短← 正确答案
- B.同一模型服务多路用户← 正确答案
- C.单请求独占 GPU 的场景
- D.请求长且间隔非常稀疏
答案:AB。连续批处理的价值来自并发下的动态补位与长短互补,A、B 正是这种场景。C 没有批可拼,D 请求稀疏导致 GPU 长期空闲,动态调度也难有收益。
Q9. 关于 KV Cache 的正确描述有哪些?
- A.大小随序列长度线性增长← 正确答案
- B.请求结束后相应缓存可释放← 正确答案
- C.只保存每层的 Query 向量
- D.缓存与模型的层数无关
答案:AB。KV Cache 存的是 K、V 而非 Query,且每层都要存,所以大小与层数正相关,C、D 错误。它随序列长度线性增长,请求结束后即可释放,A、B 正确。
Q10. 衡量连续批处理收益时应关注哪些指标?
- A.吞吐量 tokens 每秒← 正确答案
- B.p99 用户感知延迟← 正确答案
- C.显存碎片率变化← 正确答案
- D.模型参数量大小
答案:ABC。吞吐量反映产能提升,p99 延迟反映用户体验是否被牺牲,显存碎片率反映 PagedAttention 等机制的管理效果。模型参数量是静态属性,不随调度策略变化,不能作为收益指标。