前缀缓存服务:RadixAttention 与 KV 复用


文档摘要

前缀缓存服务:RadixAttention 与 KV 复用 本节摘要:把 KV 缓存当作一等公民、可复用的资源,存进一棵基数树(radix tree),调度方式也随之改变——不再像 vLLM 那样先来先服务(FCFS),而是缓存感知的调度器优先处理共享前缀更长的请求,本质是对基数树做深度优先遍历,让热点分支常驻 HBM。SGLang 就是围绕这个想法构建服务的引擎。在 Llama 3.1 8B、ShareGPT 风格 1K 提示上,SGLang 跑出约 16200 tok/s,而 vLLM 约 12500——领先约 29%。在前缀密集的 RAG 工作负载上,优势可达 6.4 倍;语音克隆形态的工作负载缓存命中率冲到 86%。

前缀缓存服务:RadixAttention 与 KV 复用

本节摘要:把 KV 缓存当作一等公民、可复用的资源,存进一棵基数树(radix tree),调度方式也随之改变——不再像 vLLM 那样先来先服务(FCFS),而是缓存感知的调度器优先处理共享前缀更长的请求,本质是对基数树做深度优先遍历,让热点分支常驻 HBM。SGLang 就是围绕这个想法构建服务的引擎。在 Llama 3.1 8B、ShareGPT 风格 1K 提示上,SGLang 跑出约 16200 tok/s,而 vLLM 约 12500——领先约 29%。在前缀密集的 RAG 工作负载上,优势可达 6.4 倍;语音克隆形态的工作负载缓存命中率冲到 86%。2026 年已部署在 xAI、LinkedIn、Cursor、Oracle、GCP、Azure、AWS 等 40 万+ GPU 上。陷阱在于:那个 6.4 倍会在前缀顺序不一致时蒸发——顺序,是工程师手里的杠杆

对应原课程:Phase 17 · Lesson 06 · 06-sglang-radixattention(原英文 phases/17-infrastructure-and-production/06-sglang-radixattention/docs/en.md)。

学习目标

阅读完本节,你应当能够:

  1. 画出 RadixAttention:前缀如何存进基数树,以及同根分支的序列如何共享 KV block。
  2. 解释缓存感知调度,以及为什么 FCFS 对前缀密集流量是错的。
  3. 给定前缀缓存命中率与提示长度分布,算出工作负载的预期加速。
  4. 说出让 6.4 倍成为现实(而非白白丢掉)的提示排序纪律。

一、问题与直觉

经典服务把每个请求的提示当作不透明整体。哪怕 5000 个 RAG 请求都以同样的 2000 token 系统提示加同样的检索前导开头,vLLM 也会把那 2000 token 前缀 prefill 5000 次。GPU 在一遍遍重复同样的活。

观察是:Agentic 与 RAG 工作负载里的提示几乎总是共享长前缀。系统提示、工具 schema、few-shot 示例、检索头、对话历史——这些都跨请求重复。如果你把这个前缀的 KV 缓存存一次复用,就不必再 prefill。

RadixAttention 干的就是这件事。token 序列被索引在基数树里;每个节点拥有从根到它的路径上那段 token 序列的 KV block。新请求走入这棵树:任何 token 匹配的节点就复用它的 KV block。Prefill 成本正比于「新」后缀,而不是整个提示。

挑战在调度。如果两个请求共享 2000 token 前缀,第三个只共享同前缀的 200 token,你希望把两个长共享请求一起服务,让长前缀留在 HBM。FCFS 正相反——谁先到服务谁,可能在下一个长前缀请求到来前就把热点分支驱逐了。

二、核心概念

基数树作为 KV 索引

基数树(紧凑 trie)存 token 序列。每个节点拥有一段 token 范围及其 KV block;子节点把序列再延伸一个或多个 token。

root |- "You are a helpful assistant..." (2000 token, 124 KV block) |- "Context: <doc A>..." (500 token, 31 block) |- "Question: Alice..." (80 token, 5 block) |- "Question: Bob..." (95 token, 6 block) |- "Context: <doc B>..." (520 token, 33 block)

一个新请求带「系统提示 + Context: 文档A + 问: Carol」进来。调度器走树:系统前缀匹配(复用 124 block),文档 A 分支匹配(复用 31 block),然后只为「问: Carol」分配新 block(4 个)。Prefill 成本:4 block 新 token。没有树的话:160 block。prefill 上约 40 倍节省。

缓存感知调度

基数树支撑的复用,在缓存抖动时毫无意义。两条关键策略:

  1. 深度优先派发:从队列挑下一个请求时,优先选与当前运行集同根分支的请求。这把热点分支钉在 HBM。
  2. 分支级 LRU,不是 block 级:驱逐整条分支(从最少使用的叶开始),而不是单个 block,使缓存形状匹配基数形状。

FCFS 两条都违反。一个共享 2000 token 的请求排在一个共享 50 token 的请求后面,那 2000 token 分支就被驱逐以接纳 50 token 的那个。

你该背下来的基准数字

  • Llama 3.1 8B、H100、ShareGPT 1K 提示:SGLang 约 16200 tok/s vs vLLM 约 12500(领先约 29%)。
  • 前缀密集 RAG(同系统 + 同文档,问题变化):SGLang 上最高 6.4 倍。
  • 语音克隆工作负载:86.4% 前缀缓存命中率。
  • SGLang 客户的生产命中率:50%~99%,取决于提示纪律。
  • 2026 年部署在 40 万+ GPU 上。

排序陷阱

6.4 倍那个数字依赖一致的提示模板顺序。如果你的客户端有时把提示拼成 [system, tools, context, history, question],有时拼成 [system, context, tools, history, question],树就找不到共享前缀。对人来说是共享前缀,对基数树是两条不同序列。

工程师的杠杆:你的提示模板就是缓存键。固定顺序。把所有不可变内容(系统、工具、schema)放最前,检索上下文次之,用户问题最后。别把动态内容夹进前缀

研究里的真实案例:把动态内容移出可缓存前缀这一个改动,让某个部署的缓存命中率从 7% 飙到 74%。

RadixAttention 赢在哪、输在哪

:

  • RAG(同检索前导,问题变化)。
  • Agent(同工具 schema,查询变化)。
  • 长系统提示的聊天。
  • 重复前导的语音/视觉工作负载。

(回落到 vLLM 水平):

  • 提示独一无二的独立生成(代码补全、无系统提示的开放聊天)。
  • 每个请求都把独特内容夹进前缀的动态提示。

为什么这是调度器问题,不只是内核问题

KV 复用可以做成内核小把戏。SGLang 的洞见是:复用只有在调度器让热点分支常驻时才划算。朴素的「有就复用」策略在混合负载下会把缓存搅乱。基数树索引的调度器,才是把内核把戏变成 29% 生产优势的关键。

与 vLLM 的互动

两个系统不是严格对立。2026 年 vLLM 加了前缀缓存(--enable-prefix-caching)和缓存感知路由器(Rust 写的 vLLM Router)。差距收窄但没消失——SGLang 整套栈是基数优先,vLLM 是嫁接上去的。前缀复用主导的工作负载,SGLang 仍是默认;无明显前缀模式的通用服务,vLLM 仍持平或更好。

三、从零实现:基数树缓存 + 缓存感知调度器

原课程 code/main.py 实现一个玩具基数树 KV 缓存 + 双策略(FCFS / 缓存感知)调度器,跑同一工作负载对比命中率与吞吐差,再跑「乱序」工作负载展示 6.4 倍的崩塌。下面给一个最小可读的命中率估算骨架。

def prefix_cache_speedup(prompt_len, shared_prefix_len, base_throughput, reuse_overhead=0.0): """估算前缀缓存带来的 prefill 加速。 参数: prompt_len: 完整提示 token 数 shared_prefix_len: 命中复用的前缀 token 数 base_throughput: 无缓存时的 prefill tok/s reuse_overhead: 复用查树的小开销(秒,默认 0) 返回: (有效 prefill 加速比, 命中率) """ hit_rate = shared_prefix_len / prompt_len if prompt_len else 0 # 复用部分几乎免费,只需 prefill 新后缀 new_work_ratio = 1 - hit_rate effective_speedup = round(1 / new_work_ratio, 2) if new_work_ratio else float("inf") return effective_speedup, round(hit_rate, 3) # 案例:提示 2000 token,命中前缀 1950 sp, hr = prefix_cache_speedup(2000, 1950) print(f"加速 {sp}x, 命中率 {hr*100:.1f}%") # 案例:同一前缀被乱序拼接,命中只剩 200 sp2, hr2 = prefix_cache_speedup(2000, 200) print(f"乱序后: 加速 {sp2}x, 命中率 {hr2*100:.1f}%") # 这正是『6.4 倍会蒸发』的数学

💡 命中率是工程师能直接撬动的杠杆:固定提示模板顺序、把动态内容后置、为多租户固定各自系统提示——命中率每升 10 个百分点,prefill 成本就降一档。

四、框架对比:vLLM vs SGLang(TensorRT-LLM)

维度 vLLM SGLang TensorRT-LLM
KV 索引 PagedAttention(block),可选前缀缓存 基数树原生,默认前缀复用 KV cache reuse(自研,需配置)
调度 FCFS + 可选缓存感知 Router 缓存感知(深度优先)原生 in-flight batching,前缀复用次要
前缀密集 RAG --enable-prefix-caching 后接近 6.4 倍,默认领先 中等
通用服务 持平或更好 略低(基数开销) 高(需编译)
适合 通用、无明显前缀模式 RAG/Agent/长系统提示 极致吞吐、可接受编译成本

选型心法:前缀复用主导 → SGLang;通用混合 → vLLM;极致吞吐 + 可接受编译 → TensorRT-LLM。多数生产 RAG/Agent 场景,SGLang 是 2026 默认。

五、可复用产物

本节产出 outputs/skill-radix-scheduler-advisor.md(原课程目录)。给定工作负载描述(提示模板形状、检索模式、并发租户数),它产出:

  1. 提示排序处方:不可变部分前置、动态部分后置、多租户各自钉系统提示。
  2. SGLang 采纳与否:基于前缀命中率预估给出 go/no-go。
  3. 命中率诊断清单:命中率低于预期时的三大排查方向(顺序、驱逐、多租户隔离)。

六、练习

  1. 跑通对比:运行 code/main.py。同一工作负载对比 FCFS 与缓存感知。差距来自哪——prefill 节省、decode 节省,还是队列延迟?

  2. 乱序实验:把工作负载改成随机排列 [system, tools, context]。重跑,命中率变成多少?为什么?

  3. 算 HBM 成本:算 Llama 3.1 8B 上把 2000 token 系统提示作为一条基数分支常驻的 HBM 成本,对比 16 序列批次无前缀复用的成本。

  4. 读论文:读 SGLang RadixAttention 论文,用三句话解释为什么树形 LRU 驱逐在前缀密集负载下胜过 block 形 LRU。

  5. 诊断低命中:某客户报命中率只有 8%。说出三个可能原因及各自要跑的诊断。

本节要点回顾

  1. KV 缓存是一等可复用资源:存进基数树,前缀只算一次,后续请求复用 block。
  2. 缓存感知调度是关键:深度优先派发 + 分支级 LRU,FCFS 会把热点分支驱逐掉。
  3. 基准:SGLang 在 ShareGPT 上约 16200 vs vLLM 约 12500 tok/s(29% 领先);前缀密集 RAG 可达 6.4 倍。
  4. 顺序是工程师的杠杆:提示模板就是缓存键,固定排序、动态内容后置,命中率可从 7% 升到 74%。
  5. 赢在 RAG/Agent/长系统提示:输在提示独一无二的独立生成。
  6. 这是调度器问题不止内核:朴素「有就复用」会搅乱缓存,基数索引调度器才是生产优势来源。
  7. vLLM 已嫁接前缀缓存(--enable-prefix-caching + Router):差距收窄但 SGLang 整栈基数优先,前缀主导场景仍默认选它。

下一节,我们钻进 Blackwell 硬件——看 FP8 与 NVFP4 这类硬件专用低精度格式如何在张量核上跑,以及把它们编译进推理引擎要付什么代价。


发布者: 作者: Rohit Gupta 转发
评论区 (0)
U