前缀缓存服务: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%。
本节摘要:把 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)。
阅读完本节,你应当能够:
经典服务把每个请求的提示当作不透明整体。哪怕 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 正相反——谁先到服务谁,可能在下一个长前缀请求到来前就把热点分支驱逐了。
基数树(紧凑 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 倍节省。
基数树支撑的复用,在缓存抖动时毫无意义。两条关键策略:
FCFS 两条都违反。一个共享 2000 token 的请求排在一个共享 50 token 的请求后面,那 2000 token 分支就被驱逐以接纳 50 token 的那个。
6.4 倍那个数字依赖一致的提示模板顺序。如果你的客户端有时把提示拼成 [system, tools, context, history, question],有时拼成 [system, context, tools, history, question],树就找不到共享前缀。对人来说是共享前缀,对基数树是两条不同序列。
工程师的杠杆:你的提示模板就是缓存键。固定顺序。把所有不可变内容(系统、工具、schema)放最前,检索上下文次之,用户问题最后。别把动态内容夹进前缀。
研究里的真实案例:把动态内容移出可缓存前缀这一个改动,让某个部署的缓存命中率从 7% 飙到 74%。
赢:
输(回落到 vLLM 水平):
KV 复用可以做成内核小把戏。SGLang 的洞见是:复用只有在调度器让热点分支常驻时才划算。朴素的「有就复用」策略在混合负载下会把缓存搅乱。基数树索引的调度器,才是把内核把戏变成 29% 生产优势的关键。
两个系统不是严格对立。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 | 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(原课程目录)。给定工作负载描述(提示模板形状、检索模式、并发租户数),它产出:
跑通对比:运行 code/main.py。同一工作负载对比 FCFS 与缓存感知。差距来自哪——prefill 节省、decode 节省,还是队列延迟?
乱序实验:把工作负载改成随机排列 [system, tools, context]。重跑,命中率变成多少?为什么?
算 HBM 成本:算 Llama 3.1 8B 上把 2000 token 系统提示作为一条基数分支常驻的 HBM 成本,对比 16 序列批次无前缀复用的成本。
读论文:读 SGLang RadixAttention 论文,用三句话解释为什么树形 LRU 驱逐在前缀密集负载下胜过 block 形 LRU。
诊断低命中:某客户报命中率只有 8%。说出三个可能原因及各自要跑的诊断。
--enable-prefix-caching + Router):差距收窄但 SGLang 整栈基数优先,前缀主导场景仍默认选它。下一节,我们钻进 Blackwell 硬件——看 FP8 与 NVFP4 这类硬件专用低精度格式如何在张量核上跑,以及把它们编译进推理引擎要付什么代价。