ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

推理引擎不是四代升级线:调度、内存、复用与部署应分开看

推理引擎不是四代升级线:调度、内存、复用与部署应分开看

把静态批处理、PagedAttention、前缀缓存、PD 分离画成四代产品,很容易得出一个错误结论:只要升级到最后一代,前面的问题就一起消失了。实际选型时,这几项技术经常同时存在,解决的约束也不同。容量放得下,不代表请求排得好;省掉了重复 Prefill,也不代表 Decode 会更快。

10 月 20 日,Google 发布 vLLM、SGLang 的原生 PyTorch TPU 后端,说明调度器与硬件实现可以分层演进。对做性能工程的人,更有用的问题是:这次改动改变了哪一段工作,代价转移到了哪里。Google 官方发布

四条轴,各有自己的账

观察轴主要改变需要继续测什么
批处理与调度哪些请求、多少 Token 一起执行排队时间、首字等待、输出间隔
KV 内存组织逻辑 Token 怎样映射到物理块块浪费、共享写入、抢占次数
前缀复用已计算的前缀能否再次使用命中长度、身份隔离、失效与重算
Prefill/Decode 部署计算阶段放在哪些节点KV 传输、路由、负载不均与故障

PagedAttention 的原论文讨论的是动态 KV 内存管理;不要把它等同于完整的请求调度策略,也不要把论文中的特定对比结果写成任何模型都能达到的常数。PagedAttention 论文,2023

例如,多轮对话在下一轮发送完整历史时,会产生重复的输入计算。前缀缓存直接针对这部分工作。但如果每次请求的系统提示都不同、输出又很长,前缀复用能省下的工作就少得多。换成更大的显存池,也不会让不存在的公共前缀自动出现。

一个不用 GPU 的工作量检查

下面只计算相邻请求的公共前缀长度,不估计毫秒或吞吐。Token ID 是手写的教学输入,不经过真实分词器。执行python3 example.py即可运行。

from itertools import takewhile def common_prefix(a, b): return sum(1 for _ in takewhile(lambda pair: pair[0] == pair[1], zip(a, b))) requests = [[1, 2, 3, 4], [1, 2, 3, 8], [9, 2, 3, 4]] reusable = [0] + [common_prefix(a, b) for a, b in zip(requests, requests[1:])] original_work = sum(map(len, requests)) remaining_work = sum(len(row) - hit for row, hit in zip(requests, reusable)) assert reusable == [0, 3, 0] assert original_work == 12 and remaining_work == 9 print({"input_tokens": original_work, "reusable_tokens": sum(reusable), "remaining_prefill_tokens": remaining_work})

这里的 12→9 只是这组三个序列的 Token 工作量变化。真实缓存可能同时查找更早的请求,可能只在完整块边界命中,也可能因模型、租户或容量限制不能复用。这个小程序没有证明“快了 25%”:算子效率、队列以及数据搬运仍然会影响延迟。

决策从瓶颈开始

若日志显示大量 KV 抢占,先检查容量预算、块组织和并发,而不是直接拆成两套集群。若 GPU 有空档但请求排队,检查 CPU 下发、调度轮次与批次形状。若一段公共提示反复计算,再检查缓存键与路由。若长 Prefill 挤压流式输出,才值得比较分块调度和 PD 分离的总成本。

部署拆分还会增加网络传输、状态定位和失败回退。它可以解决干扰,也会制造新的运维边界。一个完整方案应同时列出“省掉的工作”和“新增的工作”,让同一条请求链路能够核对,而不是把不同机器上的最好数字拼成演进史。

本文没有运行真实模型、TPU 后端或多节点部署。离线例子仅验证公共前缀计算;架构收益需要固定模型、硬件、输入输出分布、缓存热度和成功率,再做同条件比较。选型的目标是处理手头的负载,不是凑齐功能清单。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进