推理 Infra:从计算特征推导系统架构

推理系统不应从组件名开始,而应先问三个问题:一次请求怎样消耗算力和显存,哪些状态能复用,用户能等多久。架构是这些约束推出来的分工,不是产品清单。
本文讨论常见 decoder-only、自回归 Transformer,不把结论直接套到所有多模态或非自回归模型。
一、先看 prefill 和 decode
Prefill 处理输入 token。已知输入可以并行计算,长输入通常更容易利用 GPU 的矩阵计算能力,并建立生成所需的历史 KV。
Decode 每次只生成一个或少量 token,下一步依赖上一步结果。它仍要读取权重、访问历史状态并追加 KV;小批量时常受显存带宽限制,上下文变长后,KV 读取也会变贵。因此,不能简单说 prefill 只吃算力、decode 只吃带宽,实际瓶颈取决于模型、批大小、并行方式和硬件。
flowchart LR
A["输入 token"] --> B["Prefill:建立历史 KV"]
B --> C["采样首 token"]
C --> D["Decode:生成下一 token"]
D --> E["追加 KV"]
E --> F{"停止?"}
F -->|否| D
F -->|是| G["释放请求状态"]
这里有两个常被混淆的缓存概念:
- KV Cache 是单请求内的复用。 生成第 10 个 token 时,前面 token 的 key、value 已经算过,不必为每一步重算整段历史;新的 query 仍要与可见历史交互。
- Prefix Cache 是跨请求的复用。 只有新请求与旧请求拥有兼容的 token 前缀,且模型版本、模板、tokenizer、适配器等条件一致,才能减少对应的 prefill。语义相似或文字相同都不够。
二、引擎把计算变成执行计划
vLLM、SGLang、TensorRT-LLM 等推理引擎,负责算子执行、显存管理、批处理和请求生命周期。它们解决的是“拿到 GPU 后怎样跑得更好”,不是资源分配或业务路由。
FlashAttention 通过分块减少注意力中间结果的读写,但不会消除注意力计算;收益也随序列长度和硬件变化。PagedAttention 按块管理 KV,减少连续分配造成的碎片,便于动态扩展,但不会自动把 KV 变成跨节点缓存。
Continuous batching 允许完成的请求退出、新请求在调度边界加入,不必等整批最长请求结束。它通常提高 GPU 填充率,也可能让长 prefill 干扰 decode,所以引擎还需要分块 prefill、每轮 token 预算、活动序列上限和必要的抢占。
参数没有脱离负载的最优值。扩大批次可能提升吞吐,却拉长等待;抢占能缓解显存压力,却可能带来状态搬运或重算。量化、蒸馏和稀疏化也应先验证质量,再比较延迟、吞吐和成本;微调本身不会缩小模型参数规模。
三、Kubernetes 提供资源,不自动调度模型
引擎解决执行,Kubernetes 解决 Pod 生命周期和资源放置。GPU 数量只是起点,还要匹配显存、型号、驱动、运行时、并行配置和卡间拓扑;跨节点并行还会增加网络依赖。
Kubernetes 不会自动完成模型级调度。 它能依据资源声明放置 Pod,却不知道某个实例的队列、缓存命中率和真实 token 容量。多集群容灾、模型版本选择和按服务能力分流,需要控制器、路由层或专门调度逻辑补上。
弹性也不等于修改副本数。新 Pod 还要等待 GPU、拉镜像、读取权重、加载模型并预热;Readiness 应表示模型已经能服务,缩容则先停止接收新请求,再排空在途生成。
扩缩容不能只盯 GPU 利用率。高利用率可能是有效批处理,也可能是队列失控;排队时长、待处理 token、KV 压力和 SLO 更接近用户体验,并应配合冷启动余量和滞回防止反复震荡。
四、网关要感知计算代价
轮询适合作为基线,但请求数量不等于负载。一个短问答和一个长文生成各算一次请求,消耗的计算、显存和连接时间可能完全不同。
网关至少应使用模型版本、租户配额、输入长度、输出预算、截止时间,以及实例队列和缓存提示。它负责认证、限流、路由和流式协议,不应理解业务提示词来决定业务优先级;应用层可以声明已鉴权的优先级与预算。
流式响应必须传递取消、超时和背压。客户端已经收到部分 token 后,不应像普通幂等查询一样透明重试;取消若没有传到引擎,连接虽已断开,GPU 仍可能继续做无用计算。
五、缓存命中与排队的权衡
Prefix Cache 的键应包含模型权重版本、tokenizer、模板、适配器及相关注意力配置。缓存通常按块共享,命中块数不等于整段请求命中;网关索引也可能过期,最终必须由引擎验证,不兼容时安全回退。
多租户要隔离缓存命名空间、权限和淘汰预算。日志不要直接记录敏感提示词;即使使用哈希,也要考虑存在性和时延侧信道,公共前缀应通过明确策略共享。
看一个具体场景:缓存实例 A 很繁忙,但已有请求需要的共享文档前缀;实例 B 空闲,却没有这段缓存。路由可以:
- 留在 A,省掉重算,但承担排队。
- 把兼容 KV 搬到 B,减少等待,却支付定位、传输、布局转换和显存分配成本。
- 让 B 重算前缀,计算多一些,但可能更快开始 decode。
选择目标应比较预计端到端时延和资源成本,而不是孤立的命中率。 热点前缀还可能形成“越命中,越拥堵”的反馈;可以在多个实例复制热点、限制亲和权重,或排队超阈值后退回负载优先。缓存下沉到主机内存或 SSD 也只是容量换恢复延迟。
flowchart TD
A["请求与候选实例"] --> B["检查版本、权限和容量"]
B --> C["估计队列、命中和迁移代价"]
C --> D{"满足 SLO 的低代价路径"}
D --> E["在缓存实例执行"]
D --> F["迁移兼容 KV"]
D --> G["在空闲实例重算"]
F --> H{"导入成功?"}
H -->|是| I["继续生成"]
H -->|否| G
六、PD 分离不是默认最优
Prefill 和 decode 的干扰较大时,可以把它们放进不同资源池,分别配置批处理和容量。这样可能改善 decode 的稳定性,但 decode 必须接收 prefill 建立的 KV,网络、格式、并行布局和目标显存都会进入关键路径。
sequenceDiagram
participant R as 路由协调层
participant P as Prefill 池
participant D as Decode 池
R->>P: 提交输入和请求标识
P->>P: 建立 KV
R->>D: 申请接收容量
P->>D: 传输兼容 KV
D->>D: 执行后续生成
D-->>R: 流式返回输出
两类容量必须联动。只扩 prefill 会造成中间状态堆积,只扩 decode 又会让输入处理排队;低并发、短上下文、网络较弱或资源池较小时,共置部署往往更简单。PD 分离只有在隔离收益覆盖搬运、碎片化和运维成本时才值得采用。
七、最小实验与指标
先固定模型、引擎、精度和生成配置,测单实例基线,再加入两个副本和一个薄 Go 网关。第一版只做流式转发、取消、超时、有限队列和轮询;之后再加入队列信息与前缀亲和,状态缺失时回退基线。
测试覆盖冷缓存、公共前缀、热点集中、长短输入输出混合和跨租户隔离。可用模拟后端验证发现、流式协议和故障处理,但模拟不能证明 GPU 加速或生产容量;本文不提供未经测量的性能数字。
实验前固定指标口径:
- TTFT:请求发出到首个有效输出 token,包含网络、排队和执行时间。
- TPOT:首 token 后到最后 token 的耗时除以后续 token 数,并另记 token 间隔。
- P95:在同一窗口计算请求级 TTFT、TPOT、总时延,并按请求长度分组。
- Goodput:单位时间内同时满足 TTFT、TPOT 等约定 SLO 的成功请求数;同时记录吞吐、拒绝、失败和超时。
还要记录输入输出 token、排队时间、显存、KV 命中 token、传输字节和故障回退。一次只改变一个主要因素:先比较轮询与负载感知,再比较缓存亲和,最后在相同预算下比较共置与 PD 分离;任何结论都只适用于对应硬件、负载和测量边界。
结语
推理 Infra 的因果链可以压缩为:计算特征决定引擎策略,资源层提供可用执行环境,网关决定请求放置,缓存和 PD 分离把部分计算换成状态与网络成本。最终应优化满足服务承诺的有效产出,而不是单独追求 GPU 利用率或缓存命中率。
推理 Infra:从计算特征推导系统架构
Agent 走向生产:从任务完成到证据闭环