SGLang 里的 Unified Radix Cache:一棵树支持混合模型的前缀缓存

Radix Cache 是 SGLang 用于跨请求缓存复用的核心组件之一。如今的模型采用混合注意力机制,而传统 Radix Cache 只为 full attention KV 设计。因此我们为混合注意力机制设计了 SGLang 的 Unified Radix Cache。多轮对话命中率在 Mooncake L3 下保持 98%;会话感知配置把 SWE-bench TTFT 最多降 16.6%;Rust 树核把滑窗负载 TTFT 最多降 42%。

本文改编自 LMSYS 的 Unified Radix Cache 文章。

背景

Prefix caching 在请求共享同一段 token 前缀时复用 KV。在 full attention 下,后来的请求可以复用已缓存的前缀,只计算新增的 token。

混合模型中的三类数据各有复用规则:FULL(全注意力)需要匹配前缀上的 KV,SWA(滑动窗口注意力)需要复用边界之前的连续窗口,MAMBA 需要递归状态在该边界的快照(checkpoint)。

三种复用语义图示候选边界 t8 · SWA 窗口 W = 4 · 灰格表示边界外的数据
匹配到的 token 前缀t1t2t3t4t5t6t7t8t9t10t11t12
FULL · 整条路径
SWA · 所需窗口
MAMBA · 单点 checkpoint

DeepSeek-V4 组合 FULL 和 SWA,Kimi-K3 为 KDA 递归状态组合 FULL 和 MAMBA,Inkling 三种全有。SGLang 用一棵共享树处理这些组合,各组件负责自己的复用规则。

类矩阵 → 组件化图示
以前:每种「组合 × 能力」一个专门缓存类 · 共 2 个
FULLmatchinsertlockevict
FULL + SWAmatchinsertlockevict
现在:一棵树 + 可插拔组件 · 组件 ×2
UnifiedTreeCore:匹配 · 分裂 · 插入 · 加锁 · 驱逐的公共机制UnifiedRadixCache:池操作编排
组件:FULLSWAsidecar ×N

Unified Radix Cache

设计与机制

每个 radix 节点存一段 token,并为各组件留一个槽位。FULL 和 SWA 的槽存 KV 页索引,MAMBA 的槽存该前缀末端的 checkpoint。窗口之外的 SWA 数据可以保留,也可以独立驱逐,留下称为 tombstone 的空槽。

组件负责各自数据的分裂、插入、加锁和驱逐。回收节点内的部分 SWA 数据时,先在保留边界拆开节点。MAMBA checkpoint 留在原来的前缀末端,复用它的请求通过 copy-on-write 获得私有副本,再继续更新状态。

统一 radix 树重放图示窗口 W = 4 · 每格一个 token · 请求 2 后保留较早的 CSFA
请求 1 · token 序列ABCSFA
root复用边界: 0
t = 0/8

安全复用边界

Matching 沿匹配路径走查,各组件在每个候选边界检查自己的数据。每个候选独立判断,最终选出所有组件都接受的最深边界,从那里继续计算。

复用边界投票图示场景设定:n3 的 SWA 窗口槽位有 tombstone,n4 处没有 MAMBA checkpoint
n1
n2
n3
n4
FULL
SWA
MAMBA
▲ 最深安全边界▲ 最深安全边界▲ 最深安全边界▲ 最深安全边界
直接复用从这里重算
t = 0/5

HiCache 跨内存层级

HiCache 将 Unified Radix Cache 扩展到 GPU L1、主机内存 L2 和外部存储 L3,分层设计与数据搬运见 SGLang HiCache 一文。

组件与 sidecar

DeepSeek-V4 的 FULL 和 SWA 使用独立的页索引,由分配器在两者之间映射。压缩 KV、indexer buffer、compressor state 等辅助池作为 sidecar,沿用所跟随组件的索引并一起传输。其中三个跟随 FULL,两个跟随 SWA。

索引复用图示原文归一化的六页示意 · 本例仅保留尾部两页 SWA
FULL 页(组件)
sidecar ×3 跟随 FULL
↓↓分配器翻译
SWA 窗口槽(组件)
sidecar ×2 跟随 SWA
选中页号 4:FULL 的 F4 和跟随它的三个 sidecar 用同一个页号 4。分配器把 F4 翻译成 SWA 的 S0;跟随 SWA 的两个 sidecar 再抄页号 0。

多轮对话基准

多轮对话不断延长可复用的前缀。在仅 L1、L1+L2、再加入 500 GiB Mooncake L3 的对照中,DeepSeek-V4-Flash 使用 L3 后的有效输入吞吐从仅 L1 时的 9.4K 提升到 145.5K tokens/s,平均 TTFT 低于 9 秒。

多轮对话基准结果图示有效输入吞吐 = 完整 prompt 长度之和 ÷ 墙钟时间,命中的前缀 token 也计入
DeepSeek-V4-FlashFULL+SWA · 4×H200 TP4 · 48 客户端 · 60 轮 · 每轮 4096 入 + 16 出有效输入吞吐(tokens/s)
L19.4K
L1+L214.3K
L1+L2+L3145.5K
Cache hit rate 随对话轮数的变化
命中率 = 每轮命中的前缀 token 总数 ÷ 完整 prompt token 总数。曲线按原图采样点重绘,数值为读图近似值。0%25%50%75%100%0102030405055对话轮次(从 0 开始)L1+L2+L3L1+L2L1对话轮次(从 0 开始) 0 L1: ≈0% L1+L2: ≈0% L1+L2+L3: ≈0%对话轮次(从 0 开始) 5 L1: ≈83% L1+L2: ≈83% L1+L2+L3: ≈83%对话轮次(从 0 开始) 10 L1: ≈91% L1+L2: ≈91% L1+L2+L3: ≈91%对话轮次(从 0 开始) 15 L1: ≈94% L1+L2: ≈94% L1+L2+L3: ≈94%对话轮次(从 0 开始) 20 L1: ≈0% L1+L2: ≈95% L1+L2+L3: ≈95%对话轮次(从 0 开始) 25 L1: ≈0% L1+L2: ≈96% L1+L2+L3: ≈96%对话轮次(从 0 开始) 30 L1: ≈0% L1+L2: ≈97% L1+L2+L3: ≈97%对话轮次(从 0 开始) 35 L1: ≈0% L1+L2: ≈97% L1+L2+L3: ≈97%对话轮次(从 0 开始) 40 L1: ≈0% L1+L2: ≈0% L1+L2+L3: ≈98%对话轮次(从 0 开始) 45 L1: ≈0% L1+L2: ≈0% L1+L2+L3: ≈98%对话轮次(从 0 开始) 50 L1: ≈0% L1+L2: ≈0% L1+L2+L3: ≈98%对话轮次(从 0 开始) 55 L1: ≈0% L1+L2: ≈0% L1+L2+L3: ≈98%
L3 配置收尾指标命中率~98%平均 TTFT< 9 s
Inkling-SmallFULL+SWA+MAMBA · 8×H200 TP8 · 64 客户端 · 30 轮 · 每轮 1216 入 + 64 出有效输入吞吐(tokens/s)
L115.5K
L1+L221.1K
L1+L2+L367.1K
Cache hit rate 随对话轮数的变化
命中率 = 每轮命中的前缀 token 总数 ÷ 完整 prompt token 总数。曲线按原图采样点重绘,数值为读图近似值。0%25%50%75%100%051015202529对话轮次(从 0 开始)L1+L2+L3L1+L2L1对话轮次(从 0 开始) 0 L1: ≈0% L1+L2: ≈0% L1+L2+L3: ≈0%对话轮次(从 0 开始) 1 L1: ≈6% L1+L2: ≈51% L1+L2+L3: ≈51%对话轮次(从 0 开始) 2 L1: ≈0% L1+L2: ≈68% L1+L2+L3: ≈68%对话轮次(从 0 开始) 3 L1: ≈0% L1+L2: ≈76% L1+L2+L3: ≈76%对话轮次(从 0 开始) 4 L1: ≈0% L1+L2: ≈81% L1+L2+L3: ≈81%对话轮次(从 0 开始) 5 L1: ≈0% L1+L2: ≈84% L1+L2+L3: ≈84%对话轮次(从 0 开始) 6 L1: ≈0% L1+L2: ≈86% L1+L2+L3: ≈86%对话轮次(从 0 开始) 7 L1: ≈0% L1+L2: ≈88% L1+L2+L3: ≈88%对话轮次(从 0 开始) 8 L1: ≈0% L1+L2: ≈89% L1+L2+L3: ≈89%对话轮次(从 0 开始) 9 L1: ≈0% L1+L2: ≈90% L1+L2+L3: ≈90%对话轮次(从 0 开始) 10 L1: ≈0% L1+L2: ≈91% L1+L2+L3: ≈91%对话轮次(从 0 开始) 11 L1: ≈0% L1+L2: ≈92% L1+L2+L3: ≈92%对话轮次(从 0 开始) 12 L1: ≈0% L1+L2: ≈93% L1+L2+L3: ≈93%对话轮次(从 0 开始) 13 L1: ≈0% L1+L2: ≈93% L1+L2+L3: ≈93%对话轮次(从 0 开始) 14 L1: ≈0% L1+L2: ≈94% L1+L2+L3: ≈94%对话轮次(从 0 开始) 15 L1: ≈0% L1+L2: ≈94% L1+L2+L3: ≈94%对话轮次(从 0 开始) 16 L1: ≈0% L1+L2: ≈94% L1+L2+L3: ≈94%对话轮次(从 0 开始) 17 L1: ≈0% L1+L2: ≈95% L1+L2+L3: ≈95%对话轮次(从 0 开始) 18 L1: ≈0% L1+L2: ≈62% L1+L2+L3: ≈95%对话轮次(从 0 开始) 19 L1: ≈0% L1+L2: ≈0% L1+L2+L3: ≈95%对话轮次(从 0 开始) 20 L1: ≈0% L1+L2: ≈0% L1+L2+L3: ≈95%对话轮次(从 0 开始) 21 L1: ≈0% L1+L2: ≈0% L1+L2+L3: ≈96%对话轮次(从 0 开始) 22 L1: ≈0% L1+L2: ≈0% L1+L2+L3: ≈96%对话轮次(从 0 开始) 23 L1: ≈0% L1+L2: ≈0% L1+L2+L3: ≈96%对话轮次(从 0 开始) 24 L1: ≈0% L1+L2: ≈0% L1+L2+L3: ≈96%对话轮次(从 0 开始) 25 L1: ≈0% L1+L2: ≈0% L1+L2+L3: ≈96%对话轮次(从 0 开始) 26 L1: ≈0% L1+L2: ≈0% L1+L2+L3: ≈96%对话轮次(从 0 开始) 27 L1: ≈0% L1+L2: ≈0% L1+L2+L3: ≈97%对话轮次(从 0 开始) 28 L1: ≈0% L1+L2: ≈0% L1+L2+L3: ≈97%对话轮次(从 0 开始) 29 L1: ≈0% L1+L2: ≈0% L1+L2+L3: ≈97%
L3 配置收尾指标命中率96.8%平均 TTFT1.23 s

会话感知驱逐

应用通过 session_id 标记同一会话的请求,SGLang 据此保留会话对缓存条目的引用。调用 /close_session 撤销该会话的引用,共享前缀继续保留其他会话的引用。

驱逐时,FULL 在未加锁且符合条件的条目中,依次按有无引用、引用数和基础策略排序,通常先回收无引用的数据。这套会话保留机制用于 L1/L2,L3 由存储后端管理驱逐。

会话感知驱逐图示FULL 条目 · 均未加锁且符合驱逐条件 · AB 由 A、B 共享
普通 LRU
A1ABB1C1C2最近访问
会话感知驱逐
A1A · 活跃ABA + B · 2B1B · 活跃C1无引用C2无引用
A、B 仍有会话引用,AB 由两者共享,C1、C2 已无引用。
t = 0/5

SWE-bench 结果

在 SWE-bench 测试中,同时启用 Unified Radix Cache 和会话感知驱逐,相比 HiRadixCache 加 LRU,将 DeepSeek-V4-Pro 的 TTFT 最多降低 11.0%,Qwen3.5-397B-A17B 最多降低 16.6%。

SWE-bench 会话感知结果图示
DeepSeek-V4-Pro · TP8TTFT 相对基线的降幅
batch 128−11%
batch 256−2.9%
命中率变化batch 128 · device 命中 42% → 51%
Qwen3.5-397B-A17B · TP8TTFT 相对基线的降幅
batch 32−13.5%
batch 64−16.6%
命中率变化batch 32 · device 命中 5% → 34%batch 64 · device+host 命中 58% → 67%

Rust 树核

SGLang 用 Rust 执行树走查、锁记账和 LRU 更新,减少 scheduler 在这些操作上的 CPU 开销。Python 负责物理 KV 分配和缓存编排,并将 Rust 返回的操作应用到各个池。

Rust / Python 所有权图示树结构与池操作的所有权分工
Python 拥有
  • request ↔ token 映射
  • 物理 KV 分配
  • 池操作与编排
match / insert / evict 调用→←deferred actions
Rust 树核拥有
  • radix 拓扑
  • 每组件的锁记账
  • 侵入式 LRU 链表
  • 驱逐走查

仅使用 L1 的 Rust 实验原型 #29074 在 200 轮对话测试中,相比 Python 树将 SWA、full attention 和 hybrid SSM 的 TTFT 分别降低了 38%、10% 和 5%。

Rust 原型基准结果图示原型 #29074 · 仅 L1 · 200 轮 · 每轮 100 入 + 100 出 · 6 次试验 · 与 Python 同机顺序对跑
SWA gpt-oss-20b · TP2
全程 200 轮−38%
第 176–200 轮−42%
Full attention Qwen3-32B · TP2
全程 200 轮−10%
最后 25 轮−18%
Hybrid SSM Qwen3-Next-80B-A3B · TP4
全程 200 轮−5%
最后 25 轮−7%

后续合并的 Rust TreeCore #32710 支持 FULL、SWA、MAMBA 的组合及 HiCache。会话感知缓存继续使用 Python 树核。

上手试试 · Try on SGLang 打开 SGLang 仓库,在真实推理引擎里跑一跑这套机制

延伸阅读

可视化教学由 Yichi Zhang 写作。