引言
前缀缓存(Prefix caching)在请求共享相同 token 前缀时复用 KV。在全注意力(full attention)下,一旦共享前缀的 KV 被计算出来,随着更多 token 追加进来,它仍然保持有效。后续具有相同前缀的请求可以直接复用这些 KV 条目,而不必在预填充(prefill)阶段重新计算。SGLang 通过以 token 序列为键的基数树(radix tree)来跟踪这一映射关系。在预填充之前,该树会找到最长可复用前缀,并将其 KV 位置返回给调度器。
混合模型打破了这种单一复用规则。一个请求可能同时包含全注意力 KV、滑动窗口注意力(sliding window attention)KV 和循环状态(recurrent states),每种都有不同的复用概念。全注意力 KV 在整个匹配前缀范围内均可复用。滑动窗口注意力 KV 仅覆盖尾部窗口。循环状态只在精确的前缀检查点处有效。它们共享相同的 token 前缀,但可复用边界并不相同。如果强行对所有类型施加单一边界,要么丢弃了有效的复用,要么允许了无效的复用。
这些复用规则在不同模型家族中以不同组合出现。将每种组合编码为专门的缓存类会形成组合爆炸式的类矩阵,尤其是再加入 HiCache 等正交能力之后。早期的实现正是遵循这种模式,在各种缓存变体中重复了匹配、插入、锁定和驱逐逻辑。
统一基数缓存(Unified Radix Cache)通过将共享前缀身份与组件特定的复用有效性分离开来,解决了这种组合式设计问题。一个以 token 为键的单一基数拓扑结构为每个前缀提供了规范坐标,而全注意力 KV、滑动窗口注意力 KV 和 Mamba 检查点则作为组件挂接其上。HiCache 原生融入同一组件生命周期,将组件身份扩展到 GPU L1、Host L2 和外部 L3 层级。辅助池可以作为伴生组件跟随主组件,而无需定义新的复用边界。新的模型家族可以在不引入另一棵缓存树的情况下组合这些能力。
核心亮点
- 一棵树取代了缓存类别矩阵。全注意力 KV、滑动窗口注意力 KV 和 Mamba 检查点共享同一个基数树拓扑结构,而各组件则强制执行不同的复用语义。
- 钩子机制保持树核心的通用性。组件控制匹配、拆分、插入、锁定和驱逐,因此新的混合组合无需实现新的树结构。
- HiCache 天然融入组件生命周期。组件和旁挂组件在跨 GPU L1、主机 L2 和外部 L3 层级迁移时,保持相同的共享前缀标识。在多轮对话基准测试中,L3 层级在后续轮次中使 DeepSeek-V4-Flash 的命中率保持在近 98%,Inkling-Small 则为 96.8%。
- 会话活动引导驱逐策略。感知会话的驱逐机制优先保留活跃会话的缓存条目,而无需将其固定。在 SWE-bench 运行中,感知会话的 Unified Radix Cache 配置相比使用 LRU 的普通 HiRadixCache,TTFT 降低了 2.9% 至 16.6%。
- 实验性的 Rust 树核心降低了长前缀开销。在滑动窗口基准测试中,该原型在第 176 至 200 轮之间的 TTFT 比 Python 树版本低 42%。
一棵树,可组合组件
混合模型组合了共享相同 token 前缀但遵循不同复用规则的可缓存值。Unified Radix Cache 将共享前缀映射到一个基数树拓扑结构,并将每种复用规则映射到一个 TreeComponent。UnifiedTreeCore 运行通用的匹配、拆分、插入、锁定和驱逐机制。UnifiedRadixCache 协调池操作,而每个组件仅定义其差异化的语义。
FULL 组件始终存在。SGLang 为混合滑动窗口注意力添加了 SWA 组件,并为混合循环层添加了 MAMBA 组件。例如,DeepSeek-V4 组合了 FULL 和 SWA,Kimi-K3 为其 KDA 循环状态组合了 FULL 和 MAMBA,而 Inkling 在同一棵树上组合了全部三个组件。新的模型家族可以复用现有的组件组合。如果它引入了一条当前集合无法表达的复用规则,SGLang 可以添加一个新的 TreeComponent,而无需再创建一种树的实现。
FULL 提供路径复用。它保留匹配前缀中每个 token 的 KV,并保护对应的祖先路径。SWA 提供窗口复用。它要求一个连续的尾部窗口,而较旧的 SWA 槽位可能是空的墓碑节点,其基数树节点仍保留在共享拓扑中。MAMBA 提供检查点复用。它要求在可复用边界处有一个循环检查点,并在变更前将共享状态复制到私有请求槽位中。这些组件对同一个候选边界应用不同的规则。
寻找安全复用边界
在前缀匹配过程中,UnifiedTreeCore 沿规范 FULL 路径行进,并将每个访问到的节点视为候选边界。仅 FULL 匹配是不够的。每个活动组件都会创建一个验证器,只有当所有验证器都接受该候选边界时,可复用边界才会向前推进。拒绝并不会停止遍历,因为某个组件可能会接受更靠后的节点。在图 2 中,n1 和 n2 通过了所有验证器,而 n3 和 n4 至少有一个组件检查未通过。遍历到达了 n4,但保留了 n2 作为最深的安全结果。
遍历结束后,核心构建一个 MatchResult。随后组件终结器准备所选值以供复用,包括当共享的 MAMBA 检查点变为某个请求私有时所需要执行的复制。
树生命周期中的组件钩子
相同的组件契约覆盖了树生命周期的其余部分:
| 生命周期 | 组件决定的内容 |
|---|---|
| 匹配 | create_match_validator 用于判断某个候选结果是否可复用。finalize_match_result_in_tree_core 和 finalize_match_result_in_cache 则负责准备最终选定的结果。 |
| 拆分 | redistribute_on_node_split 决定当基数树节点被拆分时,组件数据如何迁移。 |
| 插入 | update_component_on_insert_overlap 和 commit_insert_component_data 决定组件拥有哪些池索引,以及新数据挂载在何处。 |
| 锁 | acquire_component_lock 和 release_component_lock 用于保护一条路径、一个尾部窗口或一个检查点。 |
| 驱逐 | evict_device_start、evict_device_next_node 和 evict_device_end 负责选择设备候选对象。evict_component 移除组件数据,drive_host_eviction 回收主机资源。 |
这一契约让树核心保持通用性,同时允许各组件保留各自不同的正确性规则。移除某个组件的负载并不总是会移除对应的基数树节点。剩余的拓扑结构仍可锚定其他组件,而空的组件槽位可以保留为墓碑标记,直到该组件被恢复或该节点不再需要为止。由于组件语义始终附着在同一个前缀标识上,HiCache 可以在不引入另一棵树的情况下,将组件负载扩展到多个内存层级。
跨内存层级的原生 HiCache
组件决定哪些内容可以复用。HiCache 决定可复用负载存放在哪里。统一基数缓存(Unified Radix Cache)在 GPU L1、主机 L2 和外部 L3 层级之间携带相同的组件标识,因此在层级间移动数据不会改变其前缀标识或复用规则。组件描述所需的传输操作,HybridCacheController 负责执行实际的物理 I/O。
组件、锚点与边车
并非每个物理池都需要自己的组件。锚点(anchor)决定复用语义,或提供其他池所遵循的页索引。边车(sidecar)存储独立的负载,但复用其声明源池的索引。它随源池一起移动,不参与可复用边界的投票,也不会在基数树拓扑中增加新的槽位。
DeepSeek-V4 将这一区别具体化。FULL 覆盖逻辑前缀,而 SWA 仅覆盖其尾部窗口,因此两者都是组件。它们还使用独立的设备索引空间。在图 3 中规范化的六页示例里,分配器在运行时将 FULL 尾部槽位 F4、F5 映射到 SWA 槽位 S0、S1。C4 和 C128 压缩 KV 池、索引器缓冲区以及压缩器状态并未定义新的复用边界。它们注册为 sidecar,其中三个池跟随 FULL,两个跟随 SWA。
HiCache 多轮基准测试结果
多轮工作负载在每一轮都会增长一个可复用的对话前缀。如果较低层级在 GPU 容量耗尽后仍保留该前缀,缓存命中率应保持较高,TTFT 的增长也应更缓慢。
我们在两个混合模型上比较了三种缓存配置:仅 GPU L1、GPU L1 加 Host L2,以及 GPU L1 加 Host L2 再加一个 500 GiB 的 Mooncake Store 分布式内存层作为 L3。DeepSeek-V4-Flash 在四块 H200 GPU 上使用 FULL 和 SWA,采用 TP4,48 个客户端,60 轮,每轮 4,096 个输入 token 加 16 个输出 token。Inkling-Small 在八块 H200 GPU 上使用 FULL、SWA 和 MAMBA,采用 TP8,64 个客户端,30 轮,每轮 1,216 个输入 token 加 64 个输出 token。
下面的命令大纲包含模型路径和 Mooncake 客户端配置的占位符。它们记录了此处使用的运行时和工作负载标志,但并非完整的可复现环境。
DeepSeek-V4 和 Inkling 的配置大纲
独立运行每种缓存配置,并在启动下一种配置之前停止其服务器。
DeepSeek-V4
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"
sglang serve $COMMON --port 30001
sglang serve $COMMON $HICACHE --port 30000
sglang serve $COMMON $HICACHE --port 30000 \
--hicache-storage-backend mooncake \
--hicache-storage-backend-extra-config "$MOONCAKE_CLIENT_JSON"
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
Inkling
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"
sglang serve $COMMON --port 30001
sglang serve $COMMON $HICACHE --port 30000
sglang serve $COMMON $HICACHE --port 30000 \
--hicache-storage-backend mooncake \
--hicache-storage-backend-extra-config "$MOONCAKE_CLIENT_JSON"
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按轮次报告了平均TTFT和提示词token缓存命中率。对于每一轮,命中率是所有请求的缓存前缀token之和除以它们的完整提示词长度之和。在这两种工作负载中,L1首先丢失可复用的前缀,L2延迟了容量上限的到来,而L3在预热后保持高位,最终命中率超过96%。
在DeepSeek-V4-Flash上,L3将命中率保持在接近98%,平均TTFT保持在9秒以下,并达到145.5K有效输入token/秒,而L1为9.4K,L1加L2为14.3K。在Inkling-Small上,L3以96.8%的命中率和1.23秒的TTFT收尾,同时达到67.1K有效输入token/秒,而L1为15.5K,L1加L2为21.1K。
有效输入token吞吐量遵循bench_multiturn.py的定义:完整提示词长度之和除以墙钟时间。它计入缓存命中的前缀token,因此衡量的是前缀复用下的服务进度,而非原始预填充计算吞吐量。在这些运行中,L3的增益主要来自于在较小层级达到容量上限后,仍能保持可复用前缀的可用性。
共享树上的会话感知驱逐
会话感知驱逐直接实现在UnifiedRadixCache中。该机制提供了普通LRU所不具备的复用信号。LRU记录哪些缓存条目最近被访问过,但不记录哪些前缀属于活跃会话并可能在其下一轮中被复用。在内存压力下,它可能驱逐活跃会话的GPU KV,同时保留无关条目。
应用程序为每个请求附加一个稳定的session_id。请求成功完成后,Unified Radix Cache为该会话注册可复用区域。FULL跟踪其前缀路径,SWA跟踪其尾部窗口,MAMBA跟踪其可复用前沿。所有会话仍然共享一个基数树拓扑结构,每一轮仍然提供其完整提示词。
这些引用会改变驱逐顺序,而不是固定内存。FULL 策略根据条目是否被引用、其会话引用计数以及配置的基础驱逐优先级来对候选条目排序。SWA 和 MAMBA 首先扫描各自可复用区域中未被引用的条目,当需要更多空间时再回退到被引用的条目。当前策略覆盖 GPU L1 和 Host L2,不涉及外部 L3 层级。
当应用调用 /close_session 时,Unified Radix Cache 会移除该会话的引用,但不会立即删除其缓存条目。会话代次(session generations)和有界关闭会话墓碑标记(tombstones)可防止在关闭或重新打开之后才完成的过期请求恢复已释放的引用。
在 SWE-bench 工作负载上的会话感知 HiCache
我们使用 TP8 和 HiCache 在 SWE-bench 智能体轨迹上评估 DeepSeek-V4-Pro 和 Qwen3.5-397B-A17B。基线使用带有 LRU 的普通 HiRadixCache。对比组启用 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(首 token 延迟)。与普通的 HiRadixCache 基线相比,感知会话的统一 Radix Cache 配置在批大小为 128 和 256 时,DeepSeek-V4-Pro 的 TTFT 分别降低了 11.0% 和 2.9%。Qwen3.5-397B-A17B 在批大小为 32 和 64 时,TTFT 分别降低了 13.5% 和 16.6%。
迈向 Rust 树核心
随着共享前缀的增长,树遍历、锁记账、LRU 更新和驱逐扫描会给调度器的关键路径增加工作量。UnifiedRadixCache 将此树状态机与缓存编排分离,这使得树核心成为原生实现的自然目标。
实验性的 Rust Unified Radix Cache 是一个可选启用、仅限 L1 的原型。Rust 负责 radix 拓扑结构、各组件锁记账、侵入式 LRU 列表和驱逐遍历。Python 仍然是请求到 token 映射和物理 KV 分配的唯一所有者。在修改树之后,Rust 返回延迟操作供 Python 应用于池。该原型支持 FULL、SWA 和 MAMBA,但不支持 HiCache。
我们通过一个 200 轮合成对话将该原型与 Python UnifiedRadixCache 进行比较。每一轮增加 100 个输入 token 并生成 100 个输出 token。两个后端使用相同的模型和服务器标志,在同一批 GPU 上顺序运行,并执行六次试验。工作负载涵盖使用 Qwen3-32B 在 TP2 下的全注意力、使用 gpt-oss-20b 在 TP2 下的 SWA,以及使用 Qwen3-Next-80B-A3B 在 TP4 下的混合 SSM。复现脚本需要 Rust 扩展的发布版本构建。
Rust 原型在 SWA 工作负载上实现了最大幅度的降低。全部 200 轮对话的首 token 延迟(TTFT)降低了 38%,第 176 至 200 轮则降低了 42%。全注意力机制的 TTFT 整体降低了 10%,最后 25 轮降低了 18%。混合 SSM 工作负载的 TTFT 整体降低了 5%,最后 25 轮降低了 7%。
图 7 最下面一行从总 TTFT 中减去了由 CUDA 事件计时的 GPU 预填充间隔。该残差包括树簿记、调度、同步、采样、反分词化、传输以及其他未插桩的工作。它不是直接的 CPU 计时器,完整的 Rust 与 Python 差异不能仅归因于基数树操作。混合 SSM 的结果说明了这一边界。其残差大幅下降,但更大的 GPU 前向传播限制了总 TTFT 中可见的变化。
这些测量仅适用于上述实验原型。后续的 Rust UnifiedTreeCoreInterface RFC #32710 定义了目标所有权边界。编排和池管理仍保留在 Python 中,位于可替换的树核心之后。该 RFC 目前仅支持 FULL 模式,且未公布性能结果。
未来工作
统一基数缓存建立了共享前缀标识,但系统的三个部分仍需围绕它进行整合:
- 完成可替换的 Rust 树核心。路线图 #20415 跟踪此次迁移。剩余工作是将 Rust 核心扩展到 SWA、MAMBA 和 HiCache,同时将池分配和编排保留在 Python 中。
- 将 GPU L1 直接连接到外部 L3 层级。直接 L3 模式将使 Host L2 成为可选的暂存层级,并将分布式内存暴露为更大的共享缓存。这需要协调的准入、预取、传输和驱逐机制,而不仅仅是另一个存储连接器。
- 在整个服务栈中协调智能体 KV 缓存。智能体工作负载已经从每个引擎内的前缀缓存中受益。路线图 #21846 将相同的缓存标识扩展到路由器、预填充和解码工作节点以及 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 推理团队的 Tingwei Huang 协助将 Hybrid HiCache 与 Mooncake 集成。我们感谢 Lianmin Zheng、Ishan Dhanani、Zhiqiang Xie、Chao Shi、Yanbo Yang、Shangming Cai、Hongjia Zhang 以及 SGLang 社区在架构评审、系统集成、基准测试和反馈方面提供的帮助。我们也感谢所有为相关路线图 #20415 和 #21846 做出贡献的人。