跳到正文

前缀缓存与分布式推理 ​

一句话:前缀缓存用“相同前缀只算一次”换显存和首字延迟,分布式推理用“把权重和 KV 切开”换单卡装不下的模型;前者决定共享效率,后者决定模型能不能上线,两者的共同约束都是通信与显存。

为什么需要 ​

两个问题在单机调优到顶后同时出现:

  • 固定 system prompt、多轮对话和 few-shot 示例让大量请求共享同一段前缀,却各自算一遍 prefill,既慢又占 KV。
  • 70B 级模型权重加 KV 远超单卡容量,必须多卡;切法选错,通信开销会把并行收益吃掉。

这两件事都围绕“同一份数据能不能复用、放在哪”。前缀缓存复用计算和显存,分布式并行切分权重和 KV;前者优化成本,后者突破容量上限。

核心机制 ​

前缀缓存:按块哈希复用 ​

引擎把 prompt 按块切开,每块 token 序列做哈希,索引到物理块。命中从第一块开始,连续命中到第一个未命中为止;后面的块即使内容相同也不能复用,因为前缀链断了。命中部分只增加引用计数,不重新计算;未命中部分正常 prefill。

这个机制和分页共享是同一套底层:块表、引用计数、写时复制。差别在于触发方式——前缀缓存是“相同前缀自动复用”,写时复制是“共享后要写时再分离”。

命中率由什么决定 ​

因素影响
前缀长度越长,命中一次省得越多
前缀稳定性system prompt 频繁改,命中率骤降
请求分布多轮对话、固定模板命中高;每请求独立命中低
路由方式同一前缀打到不同副本,缓存不共享
驱逐策略缓存容量不足时,旧块被回收,命中率下降

前缀缓存不是“开了就有效”。如果网关把同一会话的请求随机打到不同副本,每个副本都要重新算;这时需要会话粘性或前缀感知路由,让相同前缀落到同一副本。

多轮对话怎么复用 ​

客户端把完整历史放进 messages,引擎对 token 序列做哈希。第二轮请求的前缀包含第一轮的 system + user + assistant,只要这些 token 完全一致,就能命中第一轮已经算好的块。不需要传会话 ID,也不需要引擎保存对话状态。

注意“完全一致”:如果在历史里插入了时间戳、随机 ID 或动态排序的工具结果,前缀就会变,命中率下降。工程上要把稳定内容放前面,动态内容放后面,避免污染前缀。

缓存容量与驱逐 ​

缓存块也是 KV 块,不能无限增长。容量不足时按策略驱逐:

  • LRU:淘汰最久未使用的块,适合对话场景。
  • LFU:淘汰最少使用的块,适合固定模板。
  • 按成本:优先保留长前缀、复用次数高的块。

驱逐会降低后续命中率,所以缓存容量要和并发、前缀长度一起规划。把缓存开得很大但命中率很低,只是把显存从 KV 挪到了缓存。

分布式推理:TP 与 PP ​

张量并行把每层的权重矩阵按列或行切开,每张卡算一部分,层内通过 All-Reduce 合并。优点是每层都能并行,GPU 利用率高;缺点是需要高频通信,通常要求卡间有高速互连(NVLink 或高速 RDMA)。TP 度数一般不超过单机 GPU 数,跨机 TP 的通信成本往往不划算。

流水线并行按层切分,每张卡负责一段连续的层,激活在卡间顺序传递。优点是通信量小,适合跨机;缺点是有流水线气泡,推理的逐 token 生成让气泡更难填满。PP 在推理里通常用于“TP 之后仍然装不下”的情况。

专家并行用于 MoE 模型:不同专家放在不同卡上,token 路由到对应专家。它降低单卡权重压力,但引入路由和负载不均问题。

怎么选并行 ​

约束首选原因
单卡装得下,只求吞吐多副本无通信开销,扩展最简单
单卡装不下,同机多卡TP层内并行,延迟低
TP 后仍装不下,跨机TP + PP先层内再层间
MoE 且专家多EP + TP按专家切分
卡间互连差PP 或量化减少通信

选择顺序是:先量化压缩权重,再看单卡是否装得下;装不下优先同机 TP;仍不够再加 PP;MoE 单独考虑 EP。不要为了“用满卡”而上并行,通信开销可能让总吞吐低于少卡多副本。

通信与尾延迟 ​

TP 每层至少一次 All-Reduce,层数越多通信次数越多;PP 有阶段性空泡,首 token 延迟会变长。两者都会让 P99 比单卡更差,尤其在小 batch 下。分布式推理的调优重点不是峰值吞吐,而是让通信和计算重叠、让 batch 足够大以摊薄通信。

实战场景 ​

场景:客服 Agent 前缀命中率 0.6%——时间戳写进 system prompt ​

现象:8 卡 13B 副本组上的多轮客服 Agent,固定前缀 2,400 token(角色、工具说明、12 条 few-shot)加每轮对话,QPS 稳定 20,缓存一直开着,gpu_prefix_cache_hit_rate 却常年在 0.6% 上下(经验值,需校准),P99 TTFT 1.9 s。2,400 token 一次 prefill 约 0.32 s(经验值,需校准),20 QPS 即每秒 6.4 个 GPU·秒,8 张卡里约 6 张在重复算同一段前缀;副本加到 12 张,TTFT 只降到 1.7 s,账单涨五成。

排查路径:

排除对象判定证据结论
缓存未开启动参数含 enable_prefix_caching,/metrics 的 hit 计数在涨而非恒零排除:生效但打不中
路由打散绕过网关直连单副本,同一条 prompt 连打 100 次,命中率仍 0.6%排除:不是跨副本不共享
前缀内容漂移抓 20 条请求落盘做 token diff,第 2 块(token 17–32)起出现 2026-10-26T14:07:31 与 req_id=8f3c…;few-shot 由 dict 遍历生成,顺序每次不同确认:首块之后即分叉

对症做法:把易变内容后移到最后一个 user 消息,few-shot 改成有序列表。

python
SYSTEM = load("prompts/cs_system_v7.txt")             # 字节级冻结
FEWSHOT = json.load(open("prompts/fewshot_v7.json"))  # list,不用 dict 遍历
def build(req):
    msgs = [{"role": "system", "content": SYSTEM}] + FEWSHOT + req.history
    msgs.append({"role": "user", "content": f"{req.question}\n[req_id={req.trace_id}]"})
    return msgs

按块哈希是逐块连续命中的,第一处不同之后的块即使内容相同也一律判死:2,400 token 折 150 块,分叉点前置等于作废后面 148 块。时间戳挪到最后一条 user 消息后,命中块从 1–2 块升到约 118 块(78%),TTFT 回到 0.62 s。

复盘要点:判据是“前缀里凡随请求变化的字节,必须排在最后一个 user 消息之内”,上线前拿两条请求的 token 序列 diff,要求第一处差异落在固定前缀之后。0.6% 持续 9 天无人告警,阈值应设在命中率低于 50% 即报——2,400 token 的固定前缀在真实客服负载下不该低于这个数。

场景:32B 服务 TP 从 2 加到 4,跨机 All-Reduce 让吞吐反降 38% ​

现象:32B 模型(64 层、hidden 5120、FP16),同机 TP=2 单副本 760 tok/s,TPOT P99 21 ms。为省副本改成跨机 TP=4:TPOT 只从 21 ms 降到 17 ms,4 卡总吞吐 941 tok/s,比两个 TP=2 副本的 1,520 tok/s 低 38%(经验值,需校准),GPU 利用率 71% 掉到 54%,每张卡都在等通信。

排查路径:

排除对象判定证据结论
batch 变小两侧 max_num_seqs 都是 16,/metrics 的 running 稳定 16排除
单卡降频或 MIGnvidia-smi -q -d CLOCK 两侧 SM clock 一致,MIG 未开排除
KV 抢占num_preemptions_total 为 0,空闲块占 18%排除:不是显存
通信占比nsys 里 NCCL 占 5.5 ms / 17 ms;按 64 层 × 2 次集合通信、单次 240 KiB、100 Gbps RDMA 折算得 5.7 ms,与 profile 吻合确认:All-Reduce 是瓶颈

对症做法:先算通信账,再决定加卡还是抬 batch。

python
L, H, B, N = 64, 5120, 16, 4                 # 层数 / hidden / batch / TP 度
payload = 2 * (N - 1) / N * B * H * 2        # 单次 All-Reduce 字节(FP16)
comm = 2 * L * (25e-6 + payload / 12.5e9)    # 128 次 ×(延迟 + 传输),100 Gbps
print(f"{comm*1e3:.1f} ms/step, {comm/B*1e6:.0f} us/token")   # 5.7 ms, 357 us/token

每步只产出 B 个 token,集合通信次数却固定 128 次,每 token 通信成本正比于 1/batch:batch 16 时 357 µs,batch 64 时 207 µs。把 max_num_seqs 抬到 64 并开分块 prefill,同一跨机副本回到 2,600 tok/s(经验值,需校准);延迟优先则回滚成同机 TP=2 双副本,NVLink 下单次集合通信约 10 µs,128 次合计 1.3 ms,不再构成瓶颈。

复盘要点:判据是“每 token 通信成本 = 集合通信次数 ×(延迟 + 单次字节 ÷ 带宽)÷ batch”,算出来超过单 token 计算时间的 20% 就不要加 TP 度。加卡前先看单次 payload 落在哪个域:同机 NVLink 的 240 KiB 是噪声,跨机 100 Gbps 的 240 KiB 是 19 µs,差两个数量级。

场景:72B 服务 PP=4 只切 4 个微批,气泡空转 43% 的卡 ​

现象:72B Int4(权重约 40 GB)跨 2 机分 4 段跑 PP=4,max_num_seqs=32、单微批 8 条,等分 m=4 个微批。单副本 1,280 tok/s,同样 4 张卡各起 18B 副本合计 2,480 tok/s;GPU 利用率在 55% 上下周期性起伏,P99 TTFT 从 180 ms 涨到 470 ms(经验值,需校准)。

排查路径:

排除对象判定证据结论
段间切分不均4 段单步耗时 80/79/81/80 ms,偏差小于 2%排除
KV 不足触发抢占num_preemptions_total 为 0,空闲块 18%排除
段间带宽段间只传激活,单次 8 MB,100 Gbps 下 0.6 ms,占比小于 3%排除
气泡比例按 (p−1)/(m+p−1) 得 3/7 = 42.9%,与 nsys 统计的 GPU 空闲 43% 一致确认:微批数不足

对症做法:把微批数抬到 4p,气泡按公式收敛。

yaml
pipeline_parallel_size: 4
max_num_seqs: 64              # 64 ÷ 4 = 16 个微批,m = 4p
max_num_batched_tokens: 8192
enable_chunked_prefill: true  # prefill 也切块,减少首段空等

气泡占比只由 p 和 m 决定:m=4 时 43%,m=8 时 27%,m=16 时 16%,m=32 时 8.6%,与卡数无关。抬到 m=16 后单微批只剩 4 条,GEMM 效率下降约 15%(经验值,需校准),净收益仍是 1,280 → 2,050 tok/s。TTFT 不随 m 改善:首 token 要串过 4 段 prefill,这是 PP 的固有代价,延迟敏感场景应改用同机 TP=4 把 40 GB 摊到 4 张卡(每卡 10 GB),TTFT 压回 190 ms 量级。

复盘要点:判据是“PP 的微批数必须 ≥ 4p,且 PP 只用于 TP 之后仍装不下”,p>2 时还要守 TTFT < 300 ms 这条红线,两条不满足就该换并行方式而不是继续调参。GPU 空闲率的周期性方波是气泡的一级信号,本次 43% 的空闲持续两周无人当成异常——利用率曲线出现周期就先查 PP,而不是查负载。

最小上手 ​

先验证前缀缓存,再考虑多卡:

text
1. 同一 system prompt + 不同问题,连续请求 100 次
2. 记录首请求与后续请求的 prefill 时间、KV 占用
3. 改变 system prompt 一个字符,再测一次
4. 把同一前缀请求打到两个副本,观察命中率

判据:相同前缀的后续请求 prefill 明显下降、KV 占用下降;改一个字符后命中率归零;跨副本请求无法命中。三步能区分“缓存没开”“前缀不稳定”“路由不对”。

生产动作 ​

  • 稳定前缀:system prompt、工具描述、few-shot 放前面且尽量不变;动态内容后置。
  • 前缀感知路由:同一会话或同一模板的请求尽量落到同一副本;没有粘性时,缓存收益会打折。
  • 缓存容量规划:按并发、前缀长度和命中率估容量,监控命中率与驱逐量。
  • 并行选型验证:用真实 batch 和序列长度比较 TP / PP / 多副本的 P99 与吞吐,不只看单步延迟。
  • 拓扑匹配:TP 放同机高速互连,PP 跨机;不要把 TP 跨到低速网络。
  • 版本升级重测:缓存哈希、块大小、并行实现都会随版本变化。

边界 ​

  • 前缀必须完全一致:语义相同但措辞不同不命中。
  • 缓存不是越多越好:低命中率的缓存只是占显存,挤占 KV 并发。
  • 共享有安全边界:跨租户共享前缀缓存要确认不会泄露彼此内容;隔离场景按租户分实例。
  • TP 不是线性加速:通信、同步和负载不均会让扩展效率随卡数下降。
  • PP 不适合低延迟小 batch:流水线气泡在逐 token 生成时尤其明显。
  • 分布式不解决容量规划:多卡只是把显存拼大,KV 总量仍然有限。

讲清楚 ​

前缀缓存按块哈希复用相同前缀的 KV,命中后只算未命中后缀;收益取决于前缀长度、稳定性和路由,前缀一变或请求打散到不同副本,命中率就会掉。分布式推理解决单卡装不下的问题:TP 切层内权重、通信频繁但延迟低,适合同机高速互连;PP 切层间、通信少但有流水线气泡,适合跨机;MoE 用 EP。选型顺序是先量化、再单卡、再同机 TP、最后 PP,多副本永远是无通信开销的首选。两者共同的约束是显存和通信,调优要用真实负载看 P99,而不是只看峰值吞吐。

适用:vLLM 等支持前缀缓存与张量 / 流水线并行的推理引擎 | 初稿 2026-10

把 AI 工程讲到既懂也会用