SGLang团队提出用单一Radix树统一管理前缀缓存,兼顾会话内和跨会话的KV复用,显著提升LLM推理吞吐量。
前缀缓存复用 KV,当请求共享相同的 token 前缀时。在全注意力下,一旦共享前缀的 KV 计算完成,随着更多 token 的追加,它仍然有效。后续请求如果携带相同前缀,可以复用这些 KV 条目,而不必在 prefill 阶段重新计算。SGLang 用一棵以 token 序列为键的基数树来追踪这个映射关系。在 prefill 之前,树会找到最长可复用的前缀,并将其 KV 位置返回给调度器。
混合模型打破了这个单一复用规则。一个请求可能同时包含全注意力 KV、滑窗注意力 KV 和循环状态,每一种都有不同的复用语义。全注意力 KV 在整个匹配前缀范围内都保持可复用。滑窗注意力 KV 仅覆盖尾部窗口。循环状态仅在精确的前缀检查点处有效。它们共享相同的 token 前缀,但复用的边界并不相同。强制在所有类型上使用单一边界,要么丢弃有效的复用机会,要么允许无效的复用。
这些复用规则在不同的模型家族中以不同的组合出现。将每种组合编码为专门的缓存类会产生组合类矩阵,一旦再加上 HiCache 等正交能力,情况就更加复杂。早期的实现遵循这种模式,在各个缓存变体间复制匹配、插入、锁定和淘汰逻辑。
统一基数缓存通过将共享前缀标识与组件特定的复用有效性分离开来解决了这个组合设计问题。单一的 token 键控基数拓扑为每个前缀提供规范坐标,而全注意力 KV、滑窗注意力 KV 和 Mamba 检查点则作为组件附加。HiCache 原生嵌入到相同的组件生命周期中,跨 GPU L1、Host L2 和外部 L3 层扩展组件标识。辅助池可以作为副车跟随组件,而无需定义新的复用边界。新的模型家族可以组合这些能力,而无需引入另一棵缓存树。
图 1. 单一基数拓扑提供规范前缀标识。FULL、SWA 和 MAMBA 组件分别强制执行路径复用、尾部窗口复用和检查点复用语义,而 HiCache 控制其负载驻留在 GPU L1、Host L2 还是外部 L3 层。副车遵循声明的源池,而不改变树的行为。
一棵树替代了缓存类矩阵。全注意力 KV、滑窗注意力 KV 和 Mamba 检查点共享一个基数拓扑,而组件强制执行不同的复用语义。
钩子保持树核心的通用性。组件控制匹配、分割、插入、锁定和淘汰,因此新的混合组合不需要新的树实现。
HiCache 原生嵌入到组件生命周期中。组件和副车在跨 GPU L1、Host L2 和外部 L3 层迁移时保持相同的前缀标识。在多轮基准测试中,L3 在后续轮次中保持命中率接近 DeepSeek-V4-Flash 的 98% 和 Inkling-Small 的 96.8%。
会话活动引导淘汰。会话感知淘汰优先考虑活动会话的缓存条目,而不固定它们。在 SWE-bench 运行中,采用会话感知统一基数缓存配置比普通 HiRadixCache 与 LRU 的组合低了 2.9% 到 16.6% 的 TTFT。
实验性 Rust 树核心降低了长前缀开销。在滑窗基准测试中,原型在第 176 到 200 轮比 Python 树低了 42% 的 TTFT。
混合模型组合了共享相同 token 前缀但遵循不同复用规则的可缓存值。统一基数缓存将共享前缀映射到单一基数拓扑,将每条复用规则映射到一个 TreeComponent。UnifiedTreeCore 运行通用的匹配、分割、插入、锁定和淘汰机制。UnifiedRadixCache 协调池操作,而每个组件仅定义变化的语义。
FULL 组件始终存在。SGLang 添加 SWA 组件用于混合滑窗注意力,以及 MAMBA 组件用于混合循环层。例如,DeepSeek-V4 组合了 FULL 和 SWA,Kimi-K3 组合了 FULL 和 MAMBA 用于其 KDA 循环状态,Inkling 在同一棵树上组合了所有三个组件。新的模型家族可以复用已有的组件组合。如果它引入了一个当前集合无法表达的复用规则,SGLang 可以添加一个新的 TreeComponent 而无需创建另一棵树的实现。
FULL 提供路径复用。它为匹配前缀中的每个 token 保留 KV,并保护相应的祖先路径。SWA 提供窗口复用。它需要一个连续的尾部窗口,而较旧的 SWA 槽位可能是空的墓碑,其基数节点仍保留在共享拓扑中。MAMBA 提供检查点复用。它需要在可复用边界处设置一个循环检查点,并在变更前将共享状态复制到私有的请求槽位中。这些组件对相同的候选边界应用不同的规则。
在前缀匹配期间,UnifiedTreeCore 遵循规范的 FULL 路径,并将每个访问的节点视为候选边界。仅 FULL 匹配是不够的。每个活动组件都会创建一个验证器,只有当所有验证器都接受候选边界时,可复用边界才会向前推进。拒绝不会停止遍历,因为某个组件可能接受更远的节点。在图 2 中,n1 和 n2 通过了每个验证器,而 n3 和 n4 至少有一个组件检查失败。遍历到达 n4,但保留 n2 作为最深的安全结果。
图 2. 组件投票将遍历深度与可复用前缀深度分离。遍历到达 n4,而 n2 是 FULL、SWA 和 MAMBA 都接受的最深边界。
遍历之后,核心构建一个 MatchResult。组件终结器然后准备选定的值以供复用,包括当共享的 MAMBA 检查点变为一个请求的私有状态时所需的复制。
相同的组件契约覆盖了树生命周期的其余部分:
该契约保持树核心的通用性,同时允许组件保留不同的正确性规则。移除一个组件负载并不总是移除基数节点。剩余的拓扑仍可以锚定其他组件,空的组件槽位可以作为墓碑保留,直到该组件恢复或节点变得不再必要。由于组件语义保留在单一前缀标识上,HiCache 可以跨内存层扩展组件负载,而无需引入另一棵树。
组件决定什么可以被复用。HiCache 决定可复用负载驻留在哪里。统一基数缓存在 GPU L1、Host L2 和外部 L3 层之间携带相同的组件标识,因此在层之间移动数据不会改变其前缀标识或复用规则。组件描述所需的传输,HybridCacheController 执行物理 I/O。
并非每个物理池都需要自己的组件。锚点决定复用语义或提供其他池跟随的页索引。副车存储单独的负载,但复用其声明源池的索引。它跟随该源移动,而无需对可复用边界投票,也无需向基数拓扑添加另一个槽位。
DeepSeek-V4 使这个区别具体化。FULL 覆盖逻辑前缀,而 SWA 仅覆盖其尾部窗口,因此两者都是组件。它们还使用独立的设备索引空间。在图 3 中规范化的六页示例中,分配器在运行时将 FULL 尾部槽位 F4、F5 映射到 SWA 槽位 S0、S1。C4 和 C128 压缩 KV 池、索引器缓冲区和压缩器状态不定义新的复用边界。它们注册为副车,三个池跟随 FULL,两个跟随 SWA。
图 3. 组件可以在独立索引空间之间转换,而每个副车复用其声明源池的精确索引。两种关系都保留跨 HiCache 层的单一共享前缀标识。
多轮工作负载在每一轮对话中都会增长一个可复用的会话前缀。如果底层缓存在 GPU 容量耗尽后仍保留该前缀,缓存命中率应保持较高,TTFT(首 token 时间)增长也应更慢。
我们在两个混合模型上比较了三种缓存配置:仅 GPU L1、GPU L1 加主机 L2、以及 GPU L1 加主机 L2 再加 500 GiB Mooncake Store 分布式内存层作为 L3。DeepSeek-V4-Flash 在四块 H200 GPU(TP4)、48 客户端、60 轮、每轮 4096 输入 token 加 16 输出 token 条件下使用 FULL 和 SWA。Inkling-Small 在八块 H200 GPU(TP8)、64 客户端、30 轮、每轮 1216 输入 token 加 64 输出 token 条件下使用 FULL、SWA 和 MAMBA。
以下命令包含模型路径和 Mooncake 客户端配置的占位符。它们记录了此处使用的运行时和工作负载标志,但不构成完整的可复现环境。
分别运行每种缓存配置,并在启动下一个之前停止其服务器。
export MODEL=/path/to/DeepSeek-V4-Flash-FP8
export SGLANG_ENABLE_UNIFIED_RADIX_TREE=1
COMMON="--trust-remote-code --model-path $MODEL --tp 4 --mem-fraction-static 0.9 \
--context-length 262144 --page-size 64 --max-running-requests 16 \
--host 0.0.0.0 --enable-cache-report --enable-metrics \
--enable-metrics-for-all-schedulers"
HICACHE="--enable-hierarchical-cache --hicache-ratio 2 --hicache-size 0 \
--hicache-mem-layout page_first --hicache-io-backend kernel \
--hicache-write-policy write_through \
--hicache-storage-prefetch-policy wait_complete"
# L1-only
sglang serve $COMMON --port 30001
# L2 HiCache
sglang serve $COMMON $HICACHE --port 30000
# Start the 500 GiB external Mooncake Store tier first, then add:
sglang serve $COMMON $HICACHE --port 30000 \
--hicache-storage-backend mooncake \
--hicache-storage-backend-extra-config "$MOONCAKE_CLIENT_JSON"
# Benchmark: use port 30001 for L1, or port 30000 for L2/L3.
PORT=30000
python3 benchmark/hicache/bench_multiturn.py \
--host 127.0.0.1 --port "$PORT" --model-path "$MODEL" \
--num-clients 48 --num-rounds 60 --request-length 4096 --output-length 16 \
--max-parallel 16 --request-rate 64 --disable-auto-run \
--disable-random-sample --enable-round-barrier --ready-queue-policy fifo \
--seed 20260626
export MODEL=/path/to/inkling
export SGLANG_ENABLE_UNIFIED_RADIX_TREE=1
export CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7
COMMON="--trust-remote-code --model-path $MODEL --tp 8 \
--mem-fraction-static 0.85 --context-length 262144 --page-size 64 \
--max-total-tokens 750080 --max-running-requests 16 --host 0.0.0.0 \
--enable-cache-report --enable-metrics --enable-metrics-for-all-schedulers \
--mamba-radix-cache-strategy extra_buffer --swa-full-tokens-ratio 0.1 \
--mamba-full-memory-ratio 0.1 --disable-prefill-cuda-graph"
HICACHE="--enable-hierarchical-cache --hicache-ratio 2 --hicache-size 0 \
--hicache-mem-layout page_first --hicache-io-backend kernel \
--hicache-write-policy write_through \
--hicache-storage-prefetch-policy wait_complete"
# L1-only
sglang serve $COMMON --port 30001
# L2 HiCache
sglang serve $COMMON $HICACHE --port 30000
# Start the 500 GiB external Mooncake Store tier first, then add:
sglang serve $COMMON $HICACHE --port 30000 \
--hicache-storage-backend mooncake \
--hicache-storage-backend-extra-config "$MOONCAKE_CLIENT_JSON"
# Benchmark: use port 30001 for L1, or port 30000 for L2/L3.
PORT=30000
python3 benchmark/hicache/bench_multiturn.py \
--host 127.0.0.1 --port "$PORT" --model-path "$MODEL" \
--num-clients 64 --num-rounds 30 --request-length 1216 --output-length 64 \
--max-parallel 16 --request-rate 64 --disable-auto-run \
--disable-random-sample --enable-round-barrier --ready-queue-policy fifo \
--seed 20260626 --log-file "inkling-${PORT}.jsonl" --tag inkling
图 4. L1 和 L1 加 L2 在会话历史增长时会达到容量限制,之后缓存命中率下降,TTFT 上升。外部 L3 层为更多轮次保留了可复用前缀,并产生了最高的有效输入 token 吞吐量。两行使用了不同的模型、GPU 数量、请求形状和规模,因此只能在每行内部比较各层。

图 4 报告了每轮的平均 TTFT 和 prompt token 缓存命中率。对于每一轮,命中率是所有请求的缓存前缀 token 总和除以它们完整 prompt 长度的总和。在两种工作负载中,L1 最先失去可复用前缀,L2 延缓了容量限制,L3 在预热后保持高位,最终超过 96%。
在 DeepSeek-V4-Flash 上,L3 将命中率保持在近 98%,将平均 TTFT 控制在 9 秒以下,达到 145.5K 有效输入 tokens/s,相比之下 L1 为 9.4K,L1 加 L2 为 14.3K。在 Inkling-Small 上,L3 最终达到 96.8% 的命中率和 1.23 秒的 TTFT,同时达到 67.1K 有效输入 tokens/s,相比之下 L1 为 15.5K,L1 加 L2 为 21.1K。
有效输入 token 吞吐量遵循 bench_multiturn.py:完整 prompt 长度总和除以墙上时钟时间。它将缓存命中的前缀 token 计入成绩,因此衡量的是前缀复用下的服务进度,而非原始 prefill 计算吞吐量。在这些运行中,L3 的收益主要来自于在较小层达到容量后保持可复用前缀的可用性。
共享树上的会话感知驱逐
会话感知驱逐直接在 UnifiedRadixCache 中实现。该机制提供了一个普通 LRU 所不具备的复用信号。LRU 记录了哪些缓存条目最近被访问,但不记录哪些前缀属于活跃会话,以及它们在下一轮很可能被复用。在内存压力下,它可能驱逐一个活跃会话的 GPU KV,同时保留无关的条目。
应用程序为每个请求附加一个稳定的 session_id。在请求成功完成后,Unified Radix Cache 为该会话注册可复用区域。FULL 跟踪其前缀路径,SWA 跟踪其尾部窗口,MAMBA 跟踪其可复用前沿。所有会话仍共享一个 radix 拓扑结构,每一轮仍提供其完整 prompt。
这些引用改变驱逐顺序,而非固定内存。FULL 按条目是否被引用、它们的会话引用计数以及配置的基础驱逐优先级对候选进行排序。SWA 和 MAMBA 首先扫描其各自可复用区域中的未引用条目,然后在需要更多空间时回退到已引用条目作为备选。当前策略覆盖 GPU L1 和主机 L2,不包括外部 L3 层。
当应用程序调用 /close_session 时,Unified Radix Cache 移除该会话的引用,但不立即删除其缓存条目。会话代际和有界的已关闭会话墓碑机制防止在关闭或重新打开后完成的过时请求恢复已释放的引用。
图 5. 三个活跃会话共享一个 radix 拓扑结构。FULL 跟踪已引用的前缀路径,SWA 跟踪尾部窗口,MAMBA 跟踪可复用前沿。GPU 和主机驱逐优先考虑未引用条目,而已引用条目作为备选仍可被驱逐。关闭会话会移除其保留信号,但不立即删除可复用缓存条目。
SWE-bench 工作负载上的会话感知 HiCache
我们用 TP8 和 HiCache 在 DeepSeek-V4-Pro 和 Qwen3.5-397B-A17B 上评估 SWE-bench 智能体轨迹。基线使用普通的 HiRadixCache 配合 LRU。比较时启用 Unified Radix Cache 和 --enable-session-radix-cache。由于这同时改变了缓存实现和驱逐策略,观察到的差异不应被解释为会话感知的孤立消融。基准记录提供了服务器标志和沙箱配置。
图 6 的顶部一行堆叠了设备和主机缓存命中率。在批大小 128 时,DeepSeek-V4-Pro 的设备命中率从约 42% 增加到 51%。在批大小 32 时,Qwen3.5-397B-A17B 从约 5% 增加到 34%。在批大小 64 时,Qwen 的总设备加主机命中率从约 58% 增加到 67%。
底部一行报告了相应的 TTFT。相对于普通 HiRadixCache 基线,会话感知的 Unified Radix Cache 配置在批大小 128 和 256 时分别观察到 DeepSeek-V4-Pro 的 TTFT 降低 11.0% 和 2.9%。Qwen3.5-397B-A17B 在批大小 32 和 64 时分别观察到 TTFT 降低 13.5% 和 16.6%。
图 6. 顶部面板堆叠了缓存驻留情况:柔和的橙色和蓝色底部分别显示基线和 Unified 配置的设备缓存命中,而灰色顶部显示主机缓存命中。底部面板报告了测量的 TTFT,每对标签上方显示了相对于基线的降低幅度。两个模型使用独立的 TTFT 比例尺。
迈向 Rust 树核心
随着共享前缀不断增长,树遍历、锁簿记、LRU 更新和淘汰扫描都会给调度器的关键路径增加额外工作。UnifiedRadixCache 将这个树状态机与缓存编排分离,使得树核心成为一个原生实现的自然目标。
实验性 Rust Unified Radix Cache 是一个可选的、仅 L1 的原型。Rust 拥有基数拓扑结构、每个组件的锁计数、侵入式 LRU 链表和淘汰遍历。Python 仍然是请求到 token 映射和物理 KV 分配的唯一所有者。在修改树之后,Rust 返回延迟操作供 Python 应用到池上。该原型支持 FULL、SWA 和 MAMBA,但不支持 HiCache。
我们在一个 200 轮合成对话上比较了这个原型与 Python UnifiedRadixCache。每一轮添加 100 个输入 token 并生成 100 个输出 token。两个后端使用相同的模型和服务器标志,在相同的 GPU 上顺序运行,并执行六次试验。工作负载涵盖:TP2 下 Qwen3-32B 的全注意力、TP2 下 gpt-oss-20b 的 SWA,以及 TP4 下 Qwen3-Next-80B-A3B 的混合 SSM。复现脚本需要 Rust 扩展的发布版本构建。
图 7. 一个实验性仅 L1 的 Rust 原型与 Python 树的对比。顶行显示总 TTFT 和 CUDA 事件计时的 GPU 预填充间隔。底行显示它们的差异,这不是 CPU 时间的直接测量。三个模型使用独立的 y 轴刻度。

Rust 原型在 SWA 工作负载上记录了最大的减少。整个 200 轮 TTFT 降低 38%,在第 176 到 200 轮期间降低 42%。全注意力总体 TTFT 降低 10%,最后 25 轮降低 18%。混合 SSM 工作负载总体 TTFT 降低 5%,最后 25 轮降低 7%。
图 7 的底行从总 TTFT 中减去 CUDA 事件计时的 GPU 预填充间隔。这个残差包括树簿记、调度、同步、采样、反token化、传输和其他未插装的工作。它不是直接的 CPU 计时器,Rust 和 Python 之间的完整差异不能仅归因于基数树操作。混合 SSM 的结果说明了这一边界。其残差大幅下降,但更大的 GPU 前向传播限制了总 TTFT 中可见的变化。
这些测量仅适用于上述实验性原型。后续的 Rust UnifiedTreeCoreInterface RFC #32710 定义了目标所有权边界。编排和池管理保留在 Python 中,位于可替换树核心的后面。RFC 目前仅支持 FULL,不发布性能结果。
Unified Radix Cache 建立了共享前缀标识,但系统的三个部分仍需围绕它收敛:
完成可替换的 Rust 树核心。路线图 #20415 跟踪此迁移。剩余工作是将 Rust 核心扩展到 SWA、MAMBA 和 HiCache,同时将池分配和编排保留在 Python 中。
将 GPU L1 直接连接到外部 L3 层。直接 L3 模式将使 Host L2 成为可选的暂存层,并将分布式内存作为更大的共享缓存暴露。这需要协调的准入、预取、传输和淘汰,而不仅仅是另一个存储连接器。
在整个服务堆栈中协调 agentic KV 缓存。Agentic 工作负载已经受益于每个引擎内的前缀缓存。路线图 #21846 将相同的缓存标识扩展到路由器、预填充和解码 worker 以及 HiCache,以便这些层可以协调会话、子代理和工具调用的预取、降级和保留。
混合模型没有一个通用的可复用边界,但它们不需要为注意力和循环状态的每种组合都维护一个独立的基数树。Unified Radix Cache 保持一个规范的 token 拓扑结构,而 FULL、SWA 和 MAMBA 组件则各自强制执行自己的复用、锁和淘汰语义。
这个共享标识的用途超出了前缀匹配。HiCache 在内存层之间保留它,会话引用将其转化为保留信号,而实验性 Rust 核心展示了树簿记如何在不将池所有权移出 Python 的情况下演进。更长期的方向是使可复用缓存状态对整个服务系统可见,而不仅仅是对本地分配器可见。
感谢阿里巴巴云 TairKVCache 团队共同主导 Unified Radix Cache 与 HiCache 在混合模型上的集成,并验证大规模生产部署。感谢 Thinking Machines Lab 团队在重负载下验证 Unified Radix Cache 并修复组件组合的正确性问题。还要感谢 Clank.world 团队在高并发生产部署中验证 Gemma 4 SWA HiCache。
我们还要感谢 Mingjun Zhang 贡献的会话感知淘汰工作及其验证。感谢蚂蚁集团 SCT Inference 团队的 Tingwei Huang 帮助将混合 HiCache 与 Mooncake 集成。感谢 Lianmin Zheng、Ishan Dhanani、Zhiqiang Xie、Chao Shi、Yanbo Yang、Shangming Cai、Hongjia Zhang 以及 SGLang 社区的架构评审、系统集成、基准测试和反馈。还要感谢路线图 #20415 和 #21846 的所有贡献者。