SGLang 用一棵 radix tree 装下 FULL、SWA、Mamba 三种复用规则,命中靠组件投票,迁移靠 HiCache 同名迁移,淘汰靠 session 感知。树只留一棵,规则拆给组件。
类比到此为止,实际差别是:树只负责给每段前缀发坐标,FULL、SWA、MAMBA 各自作为组件决定自己的复用边界,投票定最深且全过的节点。
三种复用规则,一棵树怎么装得下?
三种复用边界互不相同,套同一个前缀是浪费甚至越界。SGLang 的解法是:树只留一棵,规则从树里拆出来。
混合模型把全注意 KV、滑动窗口 KV、循环状态塞进同一请求,共享 token 前缀(模型读入的最小文本单位序列)却不能共享复用边界。全注意的 KV 在整条前缀上都有效;滑动窗口(Sliding Window Attention, SWA)的 KV 只在尾部一段窗口里有效;Mamba(一种用固定大小状态代替完整 KV 的循环层架构,MAMBA)那种循环状态只在精确 checkpoint 点才可用。早先的实现把每种组合各自写成 cache 类,再叠上 HiCache 这类正交能力——cache 类按排列组合膨胀,组合爆炸,匹配、插入、锁、淘汰逻辑也跟着重复。
Unified Radix Cache 把这两件事拆开。一棵按 token 前缀组织的 radix tree(基数树,radix tree)给每段前缀分配唯一坐标,FULL、SWA、MAMBA 各自作为一个 TreeComponent 挂上去。
怎么定复用边界?这是组件投票,不是一条规则。FULL 走整条路径,SWA 走尾部窗口,MAMBA 只取精确 checkpoint。匹配时 UnifiedTreeCore 沿 FULL 路径走,每个访问到的节点都是候选边界;每个激活的组件生成一个 validator 投票,最深且全部通过的节点才算安全。DeepSeek-V4 用 FULL+SWA,Kimi-K3 的 KDA(线性注意力变体,用固定大小状态做循环)走 FULL+MAMBA,Inkling 把三种组件装在同一棵树上。
新模型家族可以直接复用现有组件组合;要新增复用规则时,再加一个 TreeComponent,不用再造一棵树。
树只管走路,规则交给组件投票
边界由谁定?不是树,是挂在节点上的三个组件。FULL、SWA、MAMBA 各自定义自己的"可复用边界",意见不一致时,投票决定。一个新模型只要挑组件组合,不必再造一棵树。
混合模型难在同一个 token 前缀下,全注意力、滑动窗口、循环状态各有各的"可复用边界"。每出现一种组合就写一个缓存类,叠加 HiCache 之后变成组合爆炸——前面说过的那一天。
SGLang 的 Unified Radix Cache 让 radix 树只承担前缀坐标的职能,FULL、SWA、MAMBA 各自决定自己的边界,整棵树保持一棵。
那么边界怎么找?投票制。UnifiedTreeCore 沿着 FULL 路径走,每个访问到的节点都是候选边界。FULL 通过不算数,还要把节点丢给所有激活的组件校验器:滑动窗口检查尾部窗口是否连续,循环状态检查有没有精确的检查点。
任意一个否决,就换一个再走。最深那个所有组件都同意的节点,才是可复用边界。走完一圈,MatchResult 交给各组件的 finalizer 收尾,比如 MAMBA 检查点要从共享拷贝一份进私有槽再写。
投票的代价是节点可能"半空"。SWA 组件在老槽位被淘汰后只留 tombstone(墓碑标记,表示该位置已空),radix 节点本身不动;MAMBA 私有副本走完,节点上同样可能留空槽。要等组件重新激活或节点失效,empty 槽才被清掉。
同一前缀下不同组件的数据可能错位落座,但树的拓扑仍然稳定,组件回到自己那一份就能继续工作。
其余生命周期全靠钩子串起来。匹配时 create_match_validator 决定节点能不能用;拆分时 redistribute_on_node_split 把 KV 重新落位;插入时 handle_node_insertion 处理重叠;淘汰时 dispose_node 决定跨设备的释放顺序。树核心只负责发球和走位,规则细节回到组件。原文把这套抽象总结为"new model families can compose these capabilities without introducing another cache tree",前提是复用规则还落在已有组件的射程内。
新模型接入被压成两步:挑组件组合,或新增 TreeComponent。先在已有 FULL / SWA / MAMBA 组合里挑一个,跑同一种前缀坐标;只有当出现新规则(比如另一种 循环状态(模型用来压缩长上文的小型记忆单元)变体)才动 TreeComponent 接口,不会再长出新树。后续 HiCache 的层级迁移、淘汰策略都沿着同一棵树的同一组组件钩子挂上去,不用重新拼装一整套缓存逻辑。
三种注意力合用一棵树,谁说了算?
三种复用规则各管一段:FULL 沿整条前缀有效,SWA(滑动窗口注意力,只覆盖尾部一段 token) 只能复用尾部窗口,MAMBA(循环状态层,只在精确检查点处有效) 只在精确检查点才生效。SGLang 把三种规则挂到同一棵 radix tree(基数树,按 token 序列索引前缀的树形结构) 上,候选复用点逐个过组件校验,谁否决谁回退,最后留下所有组件都同意的最近节点。
Figure 2 画的就是这个流程。UnifiedTreeCore 沿 FULL 路径走到 n4,但 n3、n4 至少被一个组件拒掉,n2 成为三个组件同时接受的复用边界。复用深度是投票结果,不是遍历深度。
Inkling 这类同时混三种注意力机制的全复合模型,不用额外的树实现。
匹配后还会发生一步。匹配阶段每个组件生成 validator 校验候选边界,结束后再走 finalizer 准备结果。状态可能在 finalizer 阶段被复制——共享的 MAMBA 检查点被某个请求认领后,得拷进该请求的私有槽位再改。这个"认领即私有"机制让共享状态不会因并发写而错乱。
淘汰开始认得人:活会话优先留下
把不同复用规则塞进同一棵树只是第一步。树还得分得清谁还在线、谁已经走了。下一道考题是淘汰。
树的总容量是有限的。
一个还在跟模型来回对话的 session 跟一个十分钟没动静的 session,缓存价值差得很远——前者的下一轮请求极可能接着用这段前缀,后者随时可能整个丢弃。
SGLang 的做法是 session 感知淘汰:把缓存条目跟产生它的 session 绑在一起,淘汰时倾向保留活跃 session 的条目,但不做硬钉死。原文说得很直白:
在 SWE-bench 工作负载上,session 感知版相对普通 HiRadixCache + LRU 的 TTFT(首 token 延迟)低 2.9% 到 16.6%。两个数字代表什么?
TTFT 是首 token 延迟,越低代表用户发完请求后等第一个字返回越快;LRU 是最经典的"最近最少使用"淘汰算法,先进来的先被踢走。session 感知版在两种极端负载下都跑出了更低的延迟,说明它不只在某一类请求模式下占优。
效果不只来自淘汰。SGLang 还在把树的核心迁向 Rust,原型在滑动窗口基准的第 176 到 200 轮把 TTFT 又压低了最多 42%。这段测试区间对应极长对话的后段,树结构本身的开销开始成为瓶颈。
Python 实现每访问一个 radix 节点(树里的分支点)都要付解释器成本,前缀越长、节点越深,开销越大。Rust 版本把这部分压下去了。
把这三件事放在一起:组件决定能不能复用,HiCache 决定缓存放哪一层,session 感知淘汰决定谁先被踢,Rust 核心决定大树的遍历成本。没有一个能单独背下 TTFT 改善的功劳。
下一阶段值得追的信号:session 感知淘汰在生产长上下文工作负载下能不能保住 16.6% 的领先,以及 Rust 树核心在 FULL+SWA+MAMBA 三组件全开时是否还稳得住那 42%。两边只要有一边数据回落,就说明当前的"软淘汰 + 跨语言迁移"组合还没到能在生产环境全面铺开的程度。
看一棵树怎么投票,再决定要不要用
动手的前提是先看懂:FULL、SWA、MAMBA 这三个组件各自管什么,session-aware 淘汰和 HiCache 三层迁移又是怎么挂在同一棵树上。下面 5 步按原文给出的信息逐项核对。
第一步是核对原文描述的组件组合是否与模型对应。原文把 Unified Radix Cache 列为同一棵 token-keyed 树,FULL 永远在,模型若带 sliding window attention(一种只看最近若干 token 的注意力机制)就多一个 SWA 组件,带 Mamba 类循环层(一种把历史压成固定大小状态的层)就多一个 MAMBA 组件。先拿你手头的模型对照:它带 SWA 还是带 Mamba 层,决定树上挂几个组件。组件组合对不上,session-aware 淘汰和 L3 外部层都用不起来。
第二步是看懂 KV cache 命中与组件投票的区别。SGLang 端到端复用的是 KV cache(推理时把已算过的中间结果存下来复用,省去重复计算),不是树本身。两条共用同一 system prompt 的请求,第二条的 TTFT(首个 token 返回时间)会明显下降——这只能证明 KV 命中,不能证明三种组件都在投票。前者是缓存生效,后者是组件校验通过,两回事。
第三步才能看懂组件投票。原文 Figure 2 描述:FULL 路径走到 n4,但 n3、n4 至少有一个组件校验不过,复用边界停在 n2。树干走到底,复用边界不一定走到底。先看懂这张图的投票流程,再谈要不要用。
第四步验证 HiCache 跨层迁移。原文称 L3 外部层在多轮基准里把 DeepSeek-V4-Flash 后段命中率维持在 98% 左右、Inkling-Small 维持在 96.8% 左右。
HiCache 控制组件载体落在 GPU L1、Host L2、还是外部 L3,组件名不变意味着身份可迁移,跨层后仍认得同一前缀。原文未给出读者可复现的多轮基准脚本,所以这一项只能盯官方后续是否放脚本和权重,目前没有可上手的复测路径。
第五步观察 session-aware 淘汰。原文称在 SWE-bench(一个衡量模型解决真实软件工程任务能力的测试集)负载下,session-aware 配置比普通 HiRadixCache + LRU(最近最少使用淘汰策略) 的 TTFT 低 2.9% 到 16.6%。复现门槛是拿到同款 SWE-bench 任务流并打日志:对比开启与关闭 session-aware 时的 TTFT 分布,看活跃 session 的前缀是不是优先留下。这一项原文也未给现成复测脚本,官方一旦发布,按上面方法跑一次即可对照。
两个尚未发生的事。原文提到 Rust 树核心原型在 sliding window 基准上,第 176 至 200 轮 TTFT 最多降 42%。这是工程进度,不是现在能跑的东西。
本文基于LMSYS Org Blog原文撰写(2026-08-11)。厂商公布的数字(跑分、降幅等)均为官方口径,除注明外尚未经第三方独立复测。