↓跳到主要内容
  1. 技术笔记/

SGLang RadixAttention 原理是什么?前缀缓存怎么提升命中率

·1583 字·8 分钟

English summary

系列导航: 注意力与计算内核精读笔记总览(共 8 篇)|上一篇: 注意力优化上线怎么验收

对应:Baseten Inference Engineering Ch5(Techniques)的 Caching 与 Disaggregation 部分;Zheng et al., SGLang: Efficient Execution of Structured Language Model Programs(arXiv:2312.07104);衔接 05 章(PagedAttention/vLLM)与量化系列 08 章(KV 量化)。 前置:01 章(KV cache 账本、prefill/decode 形态)、05 章(分页与共享)。学完本章你应该能:① 说出前缀缓存的三个杀手场景与它消除的浪费;② 推导"命中率 $h$ → prefill 计算省 $h$ 比例"的收益模型;③ 复述 RadixAttention 的 radix tree、LRU leaves-first 驱逐与引用计数机制;④ 排列 KV 存储层级并给出各自适用场景;⑤ 解释 cache-aware routing 与 disaggregation 中 KV 的角色;⑥ 手算命中率对 TTFT、KV 传输对延迟的影响。


目录(本章) #

  1. 本章目标
  2. 问题:共享前缀,但 KV 用一次就扔
  3. 收益模型:命中率与 prefill 节省
  4. RadixAttention:把 KV 缓存当一棵树来管
  5. Cache-Aware 调度:让缓存命中最大化
  6. KV 存储层级:GPU → CPU → 分布式 → 磁盘
  7. Cache-Aware Routing:把请求路由到"有缓存"的副本
  8. Disaggregation:prefill 与 decode 分离
  9. 组合:分页 + 前缀缓存 + 量化 + 批处理
  10. 数值算例
  11. 实验数据
  12. 本章小结
  13. 习题与解答
  14. 延伸阅读

2. 问题:共享前缀,但 KV 用一次就扔 #

01 章讲过:prefill 阶段把整个 prompt 从第一层算到最后一层,产出 KV cache;decode 阶段逐 token 读 KV、续写输出。大多数 serving 系统里,一个请求算完、输出结束,KV cache 就被丢弃。

SGLang 论文(arXiv:2312.07104)把这件事列为 LLM serving 最大的系统性浪费之一:

KV cache 的计算只依赖前缀 token。 因此,凡是共享同一前缀的请求,本可以复用同一份 KV,却各自重新 prefill 了一遍。

现实负载里共享前缀无处不在:

多轮对话:每轮的 prompt = 系统提示 + 历史轮次 + 新问题
          → 历史轮次的 KV 完全可复用,只有新问题需要 prefill
代码补全:文件上下文 + 游标前的代码是公共前缀,多次补全共享
Agent(ReAct 等):系统提示 + 工具定义 + 已执行轨迹,每一步都要"重发"整个轨迹
少样本(few-shot):MMLU 每道题都带同样的 5 个示例
          → 1000 道题把示例 prefill 了 1000 遍;HellaSwag 甚至是两级共享
          (few-shot 示例 + 公共问题前缀)
自洽采样 / Tree-of-Thought:同一前缀分叉出多个候选分支,分支共享前缀 KV

浪费有两笔账:

① 计算账:无缓存时每个请求 prefill FLOPs ≈ 2LN(L 为 prompt 长度,N 为参数量)
           共享前缀越长、请求越多,重复计算越离谱
② 显存账:每份 KV 都重新占一块显存;相同前缀的 KV 在显存里重复存放

前缀缓存(prefix caching)的做法一句话:把请求结束后的 KV 留下来,下一个共享前缀的请求直接复用,跳过重复的 prefill。


3. 收益模型:命中率与 prefill 节省 #

3.1 定义 #

设请求的 prompt 长度为 $L$,其中 $L_p$ 个 token 能在缓存中命中,新 token 数为 $L_s = L - L_p$。定义命中率(论文定义):

$$ h = \frac{L_p}{L} = \frac{\text{缓存命中的 prompt token 数}}{\text{总 prompt token 数}} $$

3.2 逐层账本推导:省下的比例恰好是 $h$ #

先写单层的前向成本(沿用 01 章的记法:注意力 $4L^2d$、MLP $8d^2L$,这里 $d$ 为隐藏维度):

无缓存——$L$ 个 token 全部前向:

$$ F_{\text{no}} = 4L^2d + 8d^2L $$

有缓存——前缀 token 的前向整体跳过(QKV 投影、注意力、MLP 都不用重算);后缀的 $L_s$ 个 token 仍然要 attend 全部 $L$ 个位置(它们必须看到前缀),所以注意力项是 $4 L_s L d$:

$$ F_{\text{cached}} = 4L_s L d + 8d^2 L_s $$

两式相减:

$$ F_{\text{no}} - F_{\text{cached}} = 4Ld(L - L_s) + 8d^2(L - L_s) = (L - L_s)\big(4Ld + 8d^2\big) $$$$ = \frac{L_p}{L}\left(4L^2d + 8d^2L\right) = h\, F_{\text{no}} $$

结论:命中率 $h$ 的请求,prefill 计算正好省下 $h$ 的比例——注意力与 MLP 两项同时按 $h$ 节省,因为两者都随序列长度线性扩展,前缀跳过后就只剩后缀部分。任何 per-token 项(嵌入、QKV 投影、MLP)都有同样的性质,所以这个结论对任意模型结构都成立(只要命中段的前向确实被整体跳过)。

3.3 对 TTFT 的含义 #

prefill 是计算受限的(06 章),所以 prefill 时间随 FLOPs 近似线性:

$$ t_{\text{prefill}}(h) \approx (1 - h)\, t_{\text{prefill}}(0) $$

TTFT 不能直接按 $(1-h)$ 缩,因为还有不随命中率缩水的固定开销:

$$ t_{\text{TTFT}} \approx \underbrace{t_{\text{fixed}}}_{\text{路由/调度/缓存查找/kernel 启动}} + (1-h)\, t_{\text{prefill}} $$

3.4 三个容易算错的地方 #

① 命中省的是 prefill 计算,decode 每步成本不变
   (但显存被释放 → batch 可更大 → 吞吐上升,这是间接收益)
② 后缀 token 仍要"读"前缀的 KV:省的是 FLOPs,不是带宽
③ 命中率按 token 算;若命中段很短而固定开销很大,TTFT 收益被摊薄

4. RadixAttention:把 KV 缓存当一棵树来管 #

SGLang 论文把 RadixAttention 定位为第一套自动、系统化的 KV 复用机制:它把 KV 放进一棵 radix tree 里长期缓存(请求结束后不丢弃),用 LRU 策略驱逐,并配合缓存感知的调度器。

4.1 数据结构:radix tree(压缩前缀树) #

radix tree 是 trie 的空间高效变体:边可以标注一串 token,而不是单个字符。节点对应一个前缀,节点上挂着这个前缀的 KV cache 张量(分页存储,SGLang 里一页一个 token——天然兼容 05 章的 PagedAttention)。

看一个多轮对话的例子:

root
 └─ "What is the capital of "        ← 公共前缀(节点分裂产生)
     ├─ "France?"                     ← 请求 1 的 prompt(缓存)
     │    └─ "Paris. And its population?"  ← 请求 2 追加(复用请求 1 全部 KV)
     └─ "Germany?"                    ← 请求 3 的 prompt(缓存)

插入"Germany?“这条路径时,原树只有 “What is the capital of France?",两者最长公共前缀是 “What is the capital of “——于是节点分裂,公共前缀上提,两个国家各自成为叶子。这就是 radix tree 的多级共享:系统提示、问题模板、历史轮次,每一级都能被复用。

4.2 三个核心操作 #

① Match(最长前缀匹配):新请求沿树查找最长匹配节点
   → 命中即复用该前缀的 KV,这些 token 的 prefill 直接跳过
② Insert(插入):未匹配的后缀作为新节点挂上去;
   请求的输出 token 也插入树(下一轮对话就能复用"上一轮的答案")
③ Evict(驱逐):显存不足时按 LRU 驱逐"最久未使用的叶子"

驱逐策略的两个关键设计:

Leaves-first(先叶子后祖先):
  叶子是"私有尾巴",驱逐它不影响其他请求复用公共祖先;
  祖先只有等所有子节点都被驱逐、自己也变成叶子后,才可驱逐
  → 热的公共前缀活得最久

引用计数(reference count):
  正在运行的请求会"用"某些节点,refcount > 0 的节点不可驱逐
  → 连续批处理下,运行中请求的 KV 不会被缓存策略误杀

还有一个容易被忽略但很重要的设计——共享内存池:

SGLang 不预分配一块"缓存专用"的固定显存(比如 20% 给缓存、80% 给请求),
而是让"缓存中的 KV"和"运行中请求的 KV"住在同一个池子里:
缓存 = 当前没人用的 KV。
等待请求多时,系统自动驱逐缓存 token、把显存让给更大的 batch。

这样做的好处:显存永远不会"留给缓存却用不上”,也不会"缓存把请求挤爆”。代价是命中率随负载动态浮动——这正是第 5 节调度器要优化的对象。

4.3 伪代码 #

HandleRequest(prompt, rid):
  node, matched = radix_tree.match_longest_prefix(prompt)   # 最长前缀匹配,O(树高)
  if matched < len(prompt):
      node = radix_tree.insert(prompt[matched:], parent=node)  # 新后缀入树
  prefill_and_decode(node)       # 只对未命中 token 算 KV
  # 请求结束后 KV 保留在树中(不再丢弃)

EvictIfNeeded(required):
  while free_memory < required:
      leaf = LRU_least_recently_used_leaf()
      if leaf.refcount == 0:
          evict(leaf)            # 只驱逐没有运行请求在用的叶子

多轮对话/分叉场景还有一个配合技巧:前端解释器先发送"前缀提示”(frontend hint),让运行时知道这个分支会被继续复用,避免误驱逐——这体现了前端语言与运行时协同设计的价值。

4.4 开销 #

在没有复用机会的负载上(ShareGPT 100 个请求),RadixAttention 的数据结构管理只花 0.2 秒、占总时间 74.3 秒的 不到 0.3%——匹配和插入都只沿一条前缀路径走,因此论文的结论是:默认开启,无需配置。


5. Cache-Aware 调度:让缓存命中最大化 #

有了缓存,请求的执行顺序就直接影响命中率。

先到先服务(FCFS)的问题是缓存抖动(thrashing):调度器在不同前缀的请求之间反复横跳,缓存里刚热起来的 KV 很快被驱逐,下个请求又 miss。

RadixAttention 的调度策略是 longest-shared-prefix-first(最长共享前缀优先):把等待队列按"已匹配前缀长度"降序排列,优先执行共享前缀最长的请求。可以证明这个顺序等价于对请求的 radix tree 做深度优先遍历(DFS):

Schedule(batch_size, queue):
  ordered = sort_by_matched_prefix_len_desc(queue)   # 最长共享前缀优先 ≈ DFS 序
  return contiguous_batch(ordered[:batch_size])      # 与连续批处理结合

论文给出了一个离线情形下的最优性定理:

定理 3.1:对一批请求,若缓存容量 ≥ 最大请求长度,按 DFS 序访问请求的 radix tree 可达到最优命中率;最长共享前缀优先序即 DFS 序。

直觉:DFS 沿着一条前缀链一口气处理完所有共享它的请求,公共 KV 只计算一次、一直热着;BFS/轮询式调度会让每个前缀刚建立就被换走。

权衡与边界:

贪婪提升吞吐,但可能饿死共享前缀很短的请求(论文留作未来工作:
与公平调度结合)
延迟敏感场景:可以容忍"有限重排"来换缓存命中

6. KV 存储层级:GPU → CPU → 分布式 → 磁盘 #

书里给了一条 KV 的"存储食物链”:

默认:GPU VRAM(热 KV,decode 每步都要读)
溢出①:CPU 内存(通过 Grace NVLink C2C 快速访问)
溢出②:分布式 KV store(跨副本共享)
溢出③:磁盘(最后手段)
层级典型容量带宽定位
GPU VRAM80–192 GB(H100/H200/B200)3.35–8 TB/s默认;decode 的每步读取都发生在这里
CPU 内存(Grace C2C)数百 GB–1 TB+900 GB/s(双向合计,约 PCIe 5.0 x16 的 7 倍)溢出层,可当"大号 GPU 内存"用
分布式 KV storeTB–PB(多副本)网络带宽跨 replica 共享,cache-aware routing 的基础
磁盘最大很低最后手段:冷数据、重启恢复

为什么要分层?01 章的账本:7B MHA 的 KV 是 512 KB/token——128K 上下文一个请求就要 64 GB,一张 H100 只够装一两个长请求。如果不允许溢出,长上下文服务在高并发下立刻被显存卡死;允许分层后,KV 预算从"单卡显存"扩展到"整机内存 + 集群"。

层级之间的核心权衡是热冷:

热前缀(系统提示、高频 few-shot、活跃会话历史)→ 留在 GPU
冷前缀(不活跃会话、低频共享段)→ 降级到 CPU / 分布式 store
层级越高,容量越大、带宽越低;工程上要做"按访问频率迁移"
(FlexGen 的 offload 思想,也是 RadixAttention 论文点名的未来方向)

书中配套的 KV Cache Sizing Calculator 就是用来先算"多少 KV、放哪层"的。


7. Cache-Aware Routing:把请求路由到"有缓存"的副本 #

生产部署通常有多个模型副本(replica)。路由策略直接决定命中率:

朴素路由(轮询 / 最少连接):
  只看负载,不看缓存 → 同一会话的连续请求被分散到不同副本
  → 每个副本都没有上一轮的 KV → 每轮都 miss、每轮都重新 prefill

Cache-Aware Routing:
  把请求路由到"已持有最长匹配前缀 KV"的副本
  → 命中率 ↑,TTFT ↓(书里的原话)

难点:缓存局部性与负载均衡天然冲突。如果把请求全塞给"有缓存"的副本,那个副本会过载;分散到所有副本,缓存又全 miss。工程上常用的折中:

① Session affinity / 一致性哈希:同一会话/用户固定到同一副本
   → 多轮聊天的前缀自然在固定副本上累积(最简单、最常用)
② 分布式 KV store:公共前缀(系统提示、few-shot)放共享存储,
   任何副本都能取 → 局部性不再绑定单副本
③ 动态路由:NVIDIA Dynamo 按实时负载自动路由(第 8 节),
   并感知 KV 局部性,在"命中"与"负载均衡"之间动态权衡

这个折中的数值影响见第 10 节算例 10.3。


8. Disaggregation:prefill 与 decode 分离 #

书里的最后一个相关主题是 disaggregation(分离式部署):把 prefill 和 decode 放到独立扩展的硬件上:

Prefill workers:面向计算优化(高 FLOPS)——prefill 是计算密集的
Decode workers:面向带宽优化——decode 是带宽密集的
两者独立扩缩:流量 prefill-heavy 时加 prefill worker,decode-heavy 时加 decode worker

分离带来的新瓶颈是传输:prefill worker 算出的 KV cache 必须转移给 decode worker。KV 很大:

$$ 7\text{B MHA}:\quad 512\ \text{KB/token} \times 2048\ \text{token} = 1\ \text{GB} $$

所以书里明确说:KV cache 量化(KV8/KV4,量化系列 08 章)是缓解这个传输瓶颈的手段。

与前缀缓存协同的视角:

系统提示 / 历史轮次的 KV 是"跨请求共享"的 → 可以留在 decode worker
或分布式 KV store 里,不必反复传输;
只有新后缀的 KV 需要从 prefill worker 传过来
→ 命中率越高,跨阶段传输的 KV 越少(本章与量化系列 08 章在此汇合)

NVIDIA Dynamo 提供动态 disaggregation:按实时负载自动在 prefill/decode worker 之间路由,不需要静态划分。


9. 组合:分页 + 前缀缓存 + 量化 + 批处理 #

RadixAttention 论文明确声明与三项技术兼容:连续批处理(continuous batching)、PagedAttention、张量并行。把本系列的工具拼起来:

连续批处理(05 章):每步装最多请求,与缓存感知调度直接结合
PagedAttention(05 章):KV 以物理块存放 → radix 树节点指向一组物理块,
                        共享祖先用引用计数/COW 管理,驱逐即释放块
前缀缓存(本章):跨请求共享前缀的 KV 不重算
KV 量化(量化系列 08 章):KV8/KV4 让同块显存放更多 token,
                          也缓解 disaggregation 的传输
Cache-aware 调度(本章):批内顺序让命中率最大化

组合时容易混淆的收益边界:

前缀缓存命中 h → 省 h 比例的 prefill 计算(TTFT 的直接收益)
显存释放 → batch 变大 → 吞吐上升(间接收益)
KV 量化 → 显存和传输再缩(与缓存正交,叠加)
推测解码 → 省的是 decode 步数(另一个维度,不重复计算)

10. 数值算例 #

10.1 命中率 → prefill 节省(公式验证) #

7B 模型、$L = 4096$、$h = 0.8$($L_p = 3277$,$L_s = 819$):

$$ F_{\text{no}} \approx 2LN = 2 \times 4096 \times 7\times10^9 \approx 5.7\times10^{13}\ \text{FLOPs} $$$$ F_{\text{cached}} = (1-h)F_{\text{no}} = 0.2 \times 5.7\times10^{13} \approx 1.1\times10^{13}\ \text{FLOPs} $$

节省 $4.6\times10^{13}$ FLOPs,恰好 80%。

10.2 TTFT #

H100 FP16 理论 989 TFLOPS,按 50% 效率假设折算约 500 TFLOPS:

$$ t_{\text{prefill}}(0) = \frac{5.7\times10^{13}}{5\times10^{14}} \approx 115\ \text{ms} $$$$ t_{\text{prefill}}(0.8) = 0.2 \times 115 \approx 23\ \text{ms} $$

加上固定开销 10 ms(路由、调度、缓存查找):TTFT 从约 125 ms 降到约 33 ms——prefill 部分快 5 倍,端到端约 3.8 倍。

10.3 Cache-Aware Routing #

假设随机路由命中率 40%、会话亲和命中率 85%:

$$ \frac{1-0.85}{1-0.40} = 0.25 $$

prefill 时间缩到原来的 1/4。命中率从 40% 到 85%,看似只涨了一倍多,但 miss 的 token 从 60% 降到 15%——收益要看"没命中的部分"而不是"命中的部分"。

10.4 多轮对话 / few-shot 显存账 #

1000 道 MMLU 题,5-shot 示例共 1000 个公共 token,每题 100 个 token,7B MHA(512 KB/token):

无缓存:1000 × (1000 + 100) × 512 KB ≈ 577 GB
有缓存:示例只算一次(1000 × 512 KB ≈ 0.5 GB)
        + 每题新 token(1000 × 100 × 512 KB ≈ 51 GB)
        ≈ 52 GB
省 ≈ 524 GB(≈ 91%)→ 同一张卡能服务的 batch 大一个量级

10.5 Disaggregation 传输 #

7B MHA、$L = 2048$:KV = 512 KB × 2048 = 1 GB。不同网络与量化下的传输时间:

配置带宽传输时间对比 prefill(约 58 ms @500 TFLOPS)
100 Gbps12.5 GB/s≈ 85 ms超过 prefill,成瓶颈
400 Gbps50 GB/s≈ 21 ms次要但不可忽略
400 Gbps + KV450 GB/s(传 256 MB)5 ms可忽略

结论与书一致:KV 量化在 disaggregation 里不只是省显存,更是把传输从瓶颈降为噪声。


11. 实验数据 #

论文(SGLang vs vLLM / Guidance / LMQL,A10G / A100):

指标数值
吞吐提升最高 6.4×
延迟降低最高 3.7×
命中率(各 benchmark)50%–99%
调度器相对最优命中率平均 ~96%
RadixAttention 管理开销<0.3%(74.3 s 中 0.2 s)

覆盖场景:agent control(ReAct)、逻辑推理、few-shot(MMLU/HellaSwag 两级共享)、JSON 解码、RAG 流水线(DSPy)、多轮 chat、自洽采样(self-consistency)。

生产观察(Chatbot Arena 部署一个月):

Vicuna-33B:命中率 74.1%,平均 TTFT 降低 1.7×
LLaVA-NeXT-34B:命中率 52.4%(同一图片的多次提问共享图像 token 的 KV)
命中来源:公共系统消息、高频复用的示例图片、多轮对话历史

一个重要的边界:多轮对话的提速在短输出时最明显(前缀 prefill 占总延迟比例大);输出很长时 decode 主导、不同会话间共享又少,提速趋于零。前缀缓存不是万能药——它只对"共享前缀占比高"的负载生效。


12. 本章小结 #

  1. 问题:KV 只依赖前缀,但请求结束就被丢弃 → 共享前缀被反复 prefill,既费计算又费显存;杀手场景:多轮对话、代码补全、Agent。
  2. 收益模型:命中率 $h$ 的请求,prefill 计算恰好省 $h$ 比例(第 3 节推导对注意力与 MLP 同时成立)。
  3. RadixAttention:radix tree 存 KV + LRU leaves-first 驱逐 + 引用计数 + 共享内存池——自动、多级共享、无需手动配置。
  4. 调度:最长共享前缀优先 ≈ DFS,接近最优命中率;FCFS 会缓存抖动。
  5. 存储层级:GPU VRAM → CPU(Grace C2C)→ 分布式 KV store → 磁盘,按热冷迁移。
  6. 路由:把请求送到持有匹配前缀的副本(session affinity / 一致性哈希 / 动态路由),命中率与负载均衡需要折中。
  7. Disaggregation:prefill/decode 分离后 KV 传输成为瓶颈,KV 量化(KV8/KV4)直接缓解。
  8. 组合:分页 + 前缀缓存 + 量化 + cache-aware 调度 + 连续批处理,正交叠加。

13. 习题与解答 #

题 1(推导):证明"命中率 = 节省比例" #

用单层账本(注意力 $4L^2d$ + MLP $8d^2L$)证明:前缀命中比例 $h = L_p/L$ 的请求,prefill FLOPs 恰好省下 $h$ 比例。指出该结论对哪些成本项成立、对哪一项不成立。

题 1 解答

无缓存 $F_{\text{no}} = 4L^2d + 8d^2L$;有缓存时前缀前向整体跳过,后缀仍 attend 全部 $L$ 个位置:$F_{\text{cached}} = 4L_sLd + 8d^2L_s$。相减得 $(L-L_s)(4Ld+8d^2) = hF_{\text{no}}$。所有 per-token 项(嵌入、投影、MLP)都随 $L$ 线性扩展,同样按 $h$ 节省;唯一不省的是"后缀对前缀 KV 的注意力开销"——它被计入 $F_{\text{cached}}$ 的 $4L_sLd$ 项,因此若后缀很长,实际 TTFT 改善会略低于 $h$(加上固定开销后更明显)。

题 2(计算):TTFT 收益 #

7B 模型、$L = 8192$、$h = 0.9$,H100 实测效率约 500 TFLOPS,固定开销 8 ms。求无缓存与有缓存的 TTFT(prefill 部分),以及端到端提升倍数。

题 2 解答

$F_{\text{no}} = 2 \times 8192 \times 7\times10^9 \approx 1.15\times10^{14}$ FLOPs → $t_{\text{prefill}} = 1.15\times10^{14}/5\times10^{14} \approx 230$ ms。有缓存:$0.1 \times 230 = 23$ ms。TTFT:$238 \to 31$ ms,约 7.7 倍。

题 3(概念):为什么驱逐要"先叶子后祖先"、还要引用计数? #

题 3 解答要点

叶子是"私有尾巴",驱逐它不影响其他请求复用公共祖先;祖先被驱逐会一次性杀掉所有后代的共享机会。因此 LRU 只从叶子开始驱逐,祖先只有变成叶子后才能被淘汰——热的公共前缀因此活得最久。引用计数保护运行中的请求:连续批处理下,正在被 batch 使用的节点 refcount > 0,不可驱逐,否则会产生错误结果。共享内存池(缓存与运行请求同一池)则避免"固定缓存分区"造成的显存浪费或不足。

题 4(设计):cache-aware routing 与负载均衡冲突 #

两个副本、50% 流量是共享同一系统提示的多轮聊天。给出路由方案,使命中率尽量高又不把某个副本打爆。

题 4 解答要点

方案组合:① 会话亲和(一致性哈希按 session 路由)让同一会话固定在单副本,前缀自然累积;② 公共系统提示/少样本 KV 放分布式 KV store,任何副本可取(局部性不再绑定单副本);③ 动态路由(如 NVIDIA Dynamo)在"命中收益"与"副本负载"之间实时权衡;④ 热点副本扩容或把热前缀复制到多个副本(多副本各自持有同一份公共 KV)。核心原则:把"跨请求共享的热前缀"与"单会话私有历史"分开处理。

题 5(计算):disaggregation 的传输瓶颈 #

70B GQA-8 模型(KV 每 token 320 KB,见 06 章题 2)、$L = 8192$ 的 prefill。KV 总量多少?在 200 Gbps(25 GB/s)网络上传输要多久?KV4 量化后呢?什么时候传输会成为主要瓶颈?

题 5 解答

KV = 320 KB × 8192 = 2.7 GB;200 Gbps 下 2.7 GB / 25 GB/s ≈ 107 ms。KV4 后 0.67 GB → ≈ 27 ms。对比 prefill:$2 \times 8192 \times 70\times10^9 \approx 1.15\times10^{15}$ FLOPs,H100 上约 2.3 s——此时传输(约 100 ms)只占约 4%,不是瓶颈;但如果 prefill 分散到多卡(TP 并行把 prefill 时间除以卡数)或网络更慢,传输占比就会快速上升。传输是否成为瓶颈取决于"KV 大小 × 请求频率"与"prefill 计算时间"之比,KV 量化在两者间加了一个 4 倍的保险。

题 6(辨析):RadixAttention 与 vLLM 前缀共享的区别 #

05 章讲过 vLLM 的 COW 与简单前缀共享。RadixAttention 多了什么?为什么"自动"很重要?

题 6 解答要点

① 多级树状共享:vLLM 等早期系统只处理"单一系统提示"这类简单共享;radix tree 支持任意多级共享(系统 → 模板 → 历史轮次 → 分支),节点分裂/合并自动完成;② LRU 缓存语义:KV 被当作缓存管理(命中/驱逐/替换),而不仅是引用计数;③ cache-aware 调度:执行顺序反过来优化命中率;④ 前端提示:解释器把"分支会继续"的意图传给运行时,减少误驱逐。自动的意义:论文指出此前的方法需要手动配置(如显式标记共享段),无法覆盖动态树状结构(agent 轨迹、自洽采样分支);RadixAttention 对普通 serving 请求无需任何配置即可生效,且无命中时开销 <0.3%,所以可以默认开启。


14. 延伸阅读 #

  1. SGLang: Efficient Execution of Structured Language Model Programs(arXiv:2312.07104):RadixAttention、cache-aware scheduling、定理 3.1 与全部实验数据。
  2. PagedAttention / vLLM(arXiv:2309.06180)(05 章):分页布局与 COW,是 RadixAttention 的物理层基础。
  3. Inference Engineering Ch5(Baseten):前缀缓存、KV 存储层级、cache-aware routing 与 disaggregation 的教材正文。
  4. HydraGen(arXiv:2402.05099):从 kernel 侧加速共享前缀的批量注意力。
  5. Prompt Cache(arXiv:2311.04934):模块化注意力复用,但可能造成精度下降——注意与"精确复用前缀"的区别。
  6. FlexGen(arXiv:2303.06865):KV/权重跨存储层级 offload,本章第 6 节的理论背景。
  7. 量化系列 08 章(KIVI):KV8/KV4 量化,disaggregation 传输瓶颈的解法。
  8. NVIDIA Dynamo 文档:动态 disaggregation 与 KV-cache-aware 路由的工业实现。
  9. 上一篇: 07 系统集成与生产验收——本系列 00–08 的组合框架与验收协议。

常见问题

SGLang RadixAttention 原理是什么?前缀缓存怎么提升命中率

多数服务在请求结束后丢掉 KV cache,共享同一前缀的请求只好各自重新 prefill。RadixAttention 用基数树把这些 KV 留下来,下一个请求按最长公共前缀直接复用。命中的 token 不再重算,重复的 prefill 被跳过,计算量就按命中率省下来。这也是前缀缓存能省计算的原因。

作者
Meng Yifan (zzszmyf)
独立 AI 顾问,承接企业 AI 落地项目。专注 LLM 推理优化、AI Agent 基础设施与多模态,SGLang 上游贡献者,前百度/BIGO 算法工程师。

💬 本文是 zzszmyf 知识库的一部分。 欢迎在 GitHub 上 讨论、 提 issue,或 编辑此笔记。