考察点
面试官想确认你是否理解"批处理"在 LLM 推理里的特殊性:每个请求的生成长度事先不可知且差异巨大,传统 CV/搜索推荐那种"凑齐一批、算完再放行"的静态批处理在这里会严重空转。答题主线是:静态批处理的浪费机制 → continuous batching 的迭代级调度怎么解决 → 实现上要处理哪些麻烦(KV 动态伸缩、抢占、prefill 插入)。追问常往 chunked prefill、调度公平性、和 PagedAttention 的配合走。
参考答案
静态批处理为什么浪费
静态批处理(static/dynamic batching 都是指请求粒度)的流程是:凑够 N 个请求组成一批,一起 prefill、一起 decode,整批全部生成结束才释放资源、放下一批进来。问题在于生成是变长的:一批里有的请求 50 个 token 就结束了,有的要生成 2000 个。已完成的请求不能提前退出,它的槽位和 KV Cache 被占着,GPU 继续为整条 batch 跑前向——只为那几个还没结束的请求服务。生成长度方差越大,空转越狠。实测中长尾部请求会让整批 GPU 利用率掉到很低的水平,吞吐被最长的那个请求拖死。
Continuous batching:按 token 粒度调度
Continuous batching(连续批处理,Orca 系统 2022 年提出,vLLM、TGI、TRT-LLM 都采用)把调度粒度从"请求"降到"迭代(iteration)":
- 每生成一个 token(一次 decode 迭代)后,调度器重新检查 batch 状态。
- 已生成结束(遇到 EOS 或达到 max_tokens)的请求立刻退出,释放槽位和显存。
- 队列里等待的请求立刻补位进来,先跑 prefill,下一轮开始跟老请求一起 decode。
效果是 GPU 每一步迭代都在为"真实需要计算"的请求工作,没有陪跑。配合 PagedAttention 解决显存碎片后,吞吐相对朴素静态批处理常见数倍提升——这也是为什么现在所有主流推理框架都把它当标配。
实现上要解决的麻烦
- KV Cache 的动态伸缩:请求随时进出,显存分配必须按需增长、即退即还,连续预分配根本玩不转——这正是 PagedAttention 的用武之地,block 粒度申请释放,调度器和显存管理是配套设计的。
- Prefill 插入的队头阻塞:新请求补位要先做 prefill,长 prompt 的 prefill 耗时长,会拖慢正在 decode 请求的那几步 TPOT。解法是 chunked prefill:把长 prompt 切块,每次迭代混着 decode 请求算一小块,decode 的流畅度不受影响(vLLM 已默认开启)。代价是 prefill 完成稍慢、chunk 边界 KV 管理略复杂。
- 显存打满时的抢占:运行中 KV 需求超过容量,调度器要选中低优先级请求,把它的 KV 换出到 CPU 内存(swapping)或直接丢弃、之后重算(recomputation),腾出空间给高优先级请求。
- 公平性与 SLO:纯 FCFS 简单但大请求会饿死小请求;生产上通常配合优先级队列和 max batch size、max tokens 两个调度预算来平衡。
一句话总结给面试官
静态批处理是"班车",坐满发车、全员到终点才开门;continuous batching 是"流水线",每个 token 生成完都有人可以上下车。LLM 生成长度的高方差决定了班车模式必然空转,迭代级调度 + 分页显存管理是现代推理引擎吞吐的两大支柱。
可能的追问
- Continuous batching 对延迟有影响吗? 平均延迟改善(完成即释放,排队时间缩短),但大批量 decode 会轻微拉长单步 TPOT;整体是吞吐优先的技术,配合 SLO 限制 batch 上限使用。
- Chunked prefill 的 chunk 大小怎么定? 权衡 decode 干扰和 prefill 速度:太小 prefill 拖长、kernel 效率低;太大 TPOT 抖动明显。常见按 token 预算调度(如每次迭代总共算 2048 个 token,decode 和 prefill 共享这个预算)。
- 批内请求长度差异大,decode 怎么对齐? 每个请求 decode 步数天然一致(每步都只生成一个 token),不需要 padding 到最大长度;长度差异体现在 KV Cache 长短,由 PagedAttention 的 block 结构吸收。