Tair 与 SGLang 为 DeepSeek V4 构建分层 KV Cache:Prefill 复用与 Decode 显存优化

Tair 与 SGLang 为 DeepSeek V4 设计分层 KV Cache,优化长上下文 Prefill 与 Decode。

原文标题:Tair 联手 SGLang 共建 DeepSeekV4 分层缓存架构

原文作者:阿里云开发者

冷月清谈:

文章介绍了阿里云 Tair KVCache 团队与 SGLang 社区围绕 DeepSeek V4 长上下文推理构建的分层缓存架构。DeepSeek V4 采用 CSA、HCA、SWA 混合注意力结构,同一 token 在不同 attention 路径下会对应 raw KV、C4、C128、indexer、compress state 等多种物理形态,传统连续 KV Cache 管理方式难以适配。方案通过 Shadow Radix 使用 full logical index 统一逻辑地址,再由后端映射到不同物理 pool。Prefill 阶段,V4 HiCache 基于 UnifiedRadixTree,将前缀 KV 从 GPU 扩展到 Host 和 Storage,并通过 sidecar pool 管理 DeepSeek V4 的多类缓存数据,提升长前缀复用能力;Decode 阶段,V4 HiSparse 针对 C4 sparse attention,将完整 C4 KV mirror 放在 CPU,GPU 仅保留 hot buffer,按 top-k miss 增量加载,减少冷 KV 常驻显存。实测多轮对话 Prefill 吞吐接近 3 倍提升,Decode 侧可释放显存并支持更大 BatchSize。

怜星夜思:

1、长上下文推理里,KV Cache 分层是不是会成为标配?还是只有 DeepSeek V4 这类复杂 attention 才需要?
2、HiCache 和 HiSparse 都在做 Host/GPU 分层,它们最核心的区别到底是什么?
3、Shadow Radix 用 full logical index 统一地址,这种设计会不会增加系统复杂度和调试难度?
4、HiSparse 依赖 CPU 到 GPU 的按需加载,实际部署时最容易踩的坑可能是什么?

原文内容

阿里妹导读


文章内容基于作者个人技术实践与独立思考,旨在分享经验,仅代表个人观点。

DeepSeek V4 采用 CSA + HCA 的混合注意力结构:CSA 通过低倍率压缩与 sparse top-k 检索保留长程历史,HCA 通过高倍率压缩提供全局上下文补充,再由 SWA 负责最近窗口内的精细建模。

这种设计降低了长上下文推理的计算和显存压力,但也对推理系统提出了一个根本性的挑战:同一个 token 在不同 attention 路径里会对应完全不同的物理形态——最近窗口需要 raw KV,长历史被压缩成 C4 或 C128,C4 还要配合 indexer 做稀疏检索。如果继续把 KV Cache 当作一块连续 buffer 来管理,系统根本接不住这样的复杂度。

阿里云 Tair KVCache 团队联合SGLang 社区,为 DeepSeek V4 构建了一套面向 Prefill 和 Decode 的分层缓存架构。实测在多轮对话场景下,Prefill 结合 HiCache 吞吐提升接近 3 倍;Decode 侧通过 HiSparse 分层 SparseAttention 机制,成功释放显存提升 5~10 倍 BatchSize。

本文将沿着这条架构展开,并梳理 Shadow Radix、UnifiedRadixTree、HiCache 与 HiSparse 在其中的职责边界。

Tair KVCache 与 SGLang社区的这套方案,对 DeepSeek V4 的处理可以概括为如下链路:

  • SGLang 提出了 Shadow Radix 提供统一的逻辑地址,让 prefix cache 和 scheduler 不直接感知 SWA、C4、C128 等物理细节。

  • HiCache 和 HiSparse 分别服务 prefill 与 decode:

    • HiCache 在存储层用GPU → Host → Storage 三级架构扩展前缀复用能力;

    • HiSparse 在计算层用降低 C4 sparse attention 在 decode 阶段的 GPU 常驻 KV。

下文将按这条边界展开:先聊聊 Shadow Radix,再讲述 P/D 分离下 HiCache 与 HiSparse 的功能,并且分别进入 V4 HiCache 和 V4 HiSparse 的实现讲解。

1. Shadow Radix:

一棵逻辑树对应五种物理形态

DeepSeek V4 的 attention kvcache 由几类不同的数据路径组成。最近 128 个 token 走 sliding window attention,远处历史以 C4 或 C128 的压缩 KV 参与计算;C4 layer 还会通过 indexer 选出本步需要访问的 sparse kvcache

图 1:DeepSeek V4 的每层 attention 同时面对最近窗口和压缩历史。图来自 SGLang DeepSeek-V4 Day-0 blog。

Shadow Radix 的目标是让不同物理 pool 共用一套逻辑坐标。在 SGLang 里,radix tree 保存的是 full logical index。这个 index 会出现在 req.prefix_indicesreq_to_token_pool.req_to_token、 ForwardBatch.out_cache_loc 等位置。

full logical index 不等同于某个物理 KV 地址。它更像一条稳定的 token 地址线:scheduler 用它描述 prefix 命中,request table 用它描述请求上下文,backend 再根据 layer 类型把它翻译到具体 pool。

full logical index
  -> SWA index                最近窗口里的 raw KV
  -> C4 compressed index      4:1 压缩后的历史 KV
  -> C128 compressed index    128:1 压缩后的历史 KV
  -> C4 indexer index         稀疏检索用的量化 key
  -> compress state ring      继续压缩所需的 rolling state

图 2:Shadow Radix 使用一棵逻辑前缀树维护 full logical index,再由 backend 把同一段逻辑前缀投影到 SWA、C4、C128、indexer 和 state pools。

几个投影规则需要分开看。SWA 依赖 full_to_swa_index_mapping, 通过 full logical indices 直接映射至 swa indices。C4 和 C128 的压缩位置可直接由 full logical indices 通过除法推算,例如 raw_out_loc // 4 或 raw_out_loc // 128。compress state 先从 full index 翻译到 SWA index,再进入每个 SWA page 所属的 ring index:

swa_page  = swa_loc // swa_page_size
state_loc = swa_page * ring_size + (swa_loc % ring_size)

图 3:官方 Shadow Radix storage layout。它强调的是同一份 logical slot 在多个物理 pool 中有不同投影,而不是让 radix tree 直接管理每个物理页。

2. 分层缓存架构应对 P/D 不同的显存压力

在 SGLang 的 P/D 分离部署中,Prefill Worker 和 Decode Worker 承担的压力不同。Prefill 侧的成本集中在长前缀计算上;Decode 侧的问题则更多来自长期 KV 常驻显存,显存被冷 KV 占住后,decode batch size 很难继续拉大。HiCache 和 HiSparse 都会引入 Host 分层,但它们分别对应这两个阶段的瓶颈。

图 4:SGLang P/D 分离下的缓存边界。

Prefill Worker 开启 HiCache,用 L2 Host 和 L3 Storage 扩展 prefix KV cache;Decode Worker 开启 HiSparse,只在 C4 sparse attention 的 miss token 上做增量 Host→GPU 加载。

两者的差异可以直接放在一张表里:

机制

所在阶段

管理对象

主要数据路径

目标

V4 HiCache

Prefill

prefix KV cache

GPU L1 ↔ Host L2 ↔ Storage L3

扩大前缀 KV 的可复用空间,提高长前缀命中率

V4 HiSparse

Decode

C4 sparse attention KV

CPU C4 mirror ↔ GPU hot buffer

减少 C4 compressed KV 在 GPU 上的常驻占用

HiCache 依赖 UnifiedRadixTree 的 prefix match 语义。请求进入 prefill 时,scheduler 先匹配 full logical prefix;如果命中已经 offload 到 Host 或 Storage 的前缀,HiCache 会在 prefill 计算前把必要的 KV load back 到 GPU。

HiSparse 不走这条 prefix match 路径。Decode 每一步,C4 indexer 会选出本步需要访问的 top-k compressed tokens。HiSparse 检查这些 token 是否已经在 GPU hot buffer 中,miss 的部分再从 CPU mirror 增量拷回 GPU。

3. V4 HiCache:让 Prefill 的前缀缓存
从 GPU 一路延伸到 Storage

V4 HiCache 是 DeepSeek V4 在 prefill 阶段使用的分层 prefix cache。它把原本只能留在 GPU 的 prefix KV 扩展到 L2 Host 和 L3 Storage,并且要保证 Full、SWA 以及 DeepSeek V4 的 sidecar pools 一起移动。

这部分实现直接基于 python/sglang/srt/mem_cache/unified_radix_cache.py

3.1 UnifiedRadixTree——
一套接口,多种 KV 形态

UnifiedRadixTree 的定位是统一 SGLang 的 PrefixCache 实现。它对 scheduler 暴露的仍然是 BasePrefixCache 风格接口:

match_prefix()
cache_finished_req()
cache_unfinished_req()
evict()
init_load_back()
变化发生在 tree 内部。传统 radix cache 的 node 主要保存一段 key 和一段 value;UnifiedRadixTree 的 node 额外保存 component_data。每个 component 可以独立维护自己的 device value、host value、lock ref、host lock ref 和元数据。

当前主要 component 包括:

  • FullComponent:管理基础 full logical value,也是大多数 prefix hit 的主坐标。

  • SWAComponent:管理 sliding window KV,处理 SWA tombstone、full-to-SWA 翻译和窗口内恢复。

  • MambaComponent:管理 Mamba state,处理状态类 cache 的 copy-on-write 语义。

图 5:UnifiedRadixTree 的可插拔缓存架构。

Scheduler 只调用统一的 PrefixCache API;模型差异通过 Full、SWA、Mamba 等 component 注入。DeepSeek V4 使用 FULL + SWA 作为 tree component,C4、C128、indexer 和 compress state 作为 sidecar pools 跟随主 anchor offload/load-back。

LRU 也按 component 拆开。Full、SWA、Mamba 可以有不同的驱逐优先级和锁范围;HiCache 打开后,还会有对应的 host LRU。这样主树只负责 radix 匹配、节点 split、insert、lock/refcount 和组件编排,具体的释放、恢复、备份逻辑交给 component hook。

对 DeepSeek V4 来说,UnifiedRadixTree 只需要 FULL + SWA 两个 tree component。C4、C128、C4 indexer 和各类 compress state 不直接参与 prefix tree 匹配,它们作为 sidecar pools 挂在 Full 或 SWA 下面,在 offload/load-back 时跟随主 pool 一起搬运。

3.2 DeepSeek V4 如何基于 
UnifiedRadixTree 实现多 Pool 的 Offload 管理

图 6:DeepSeek V4 HiCache Sider pools 跟随 anchor pool offload

V4 HiCache 的初始化路径如下:

UnifiedRadixCache.init_hicache()
  -> attach_hybrid_pool_to_unified_cache()
  -> build_deepseek_v4_hicache_stack()
  -> HostPoolGroup + HybridCacheController
build_deepseek_v4_hicache_stack() 会为 DeepSeek V4 构造一组 host pools。基本结构是用 Full 和 SWA 作为 anchor,再把其他物理 pool 作为 sidecar 挂上去。
FULL anchor:
  KV
  C4 compressed KV
  C4 indexer KV
  C128 compressed KV
SWA anchor:
  SWA KV
  C4 compress state
  C4 indexer compress state
  C128 compress state
UnifiedRadixTree 不需要理解每个 sidecar pool 的物理 layout。它只构造 PoolTransfer:主 pool 要搬哪些 indices,sidecar pool 应该从哪个 anchor 推导 indices。数据移动由 HybridCacheController 负责,包括 host buffer 分配、device buffer 分配、D2H backup、H2D load-back,以及 L3 Storage 的 batch get/set。

回到 prefill 请求路径,V4 HiCache 的流程是:

prefill request
  -> match_prefix(full logical tokens)
  -> device hit: 直接复用 GPU prefix
  -> host hit: init_load_back(best_match_node)
  -> Full/SWA + sidecar pools H2D load-back
  -> backend 根据 full indices 翻译 SWA/C4/C128/state
  -> prefill 计算剩余未命中 token
L3 Storage 的位置也在这条链路里。Host L2 是更近的层;当前缀被写入 storage backend 后,后续请求可以通过 prefetch 把可能命中的 prefix 先拉回 Host,再由 load-back 恢复到 GPU。Mooncake 和 HF3FS 这类 backend 负责 Storage I/O,UnifiedRadixTree 仍然只维护 prefix match 和 node residency。

3.3 HiCache 实践——多轮对话吞吐提升3倍

HiCache 为 DeepSeek V4 提供了更大的 KVCache 存储空间,尤其适合多轮对话中大量复用历史前缀的场景。以下图中的 Multiturn 数据集为例,开启 L2 HiCache 后,相比 L1 Only,系统在保持较高前缀命中率和较低 TTFT 的同时,将 Input Throughput 提升到接近 3 倍。

图 7:Multi-turn Benchmark结果

4. V4 HiSparse:

Decode 阶段不再为冷 KV 买单

HiSparse 是 decode 阶段的 sparse attention 分层方案。它和 HiCache 使用的词汇有重叠,比如 Host、GPU、load,但它们处理的问题不同。HiSparse 不依赖 UnifiedRadixTree 的 prefix hit,也不会通过 best_match_node 做 prefix load-back。

4.1 HiSparse——
GPU 只留 hot buffer,miss 了再去拿

Sparse attention 的前提是:每一步只读取少量 KV。如果完整 KV 仍然常驻 GPU,那么计算变稀疏了,显存占用却没有同步下降。HiSparse 的设计就是把完整 KV mirror 放到 CPU pinned memory,在 GPU 上只保留一个较小的 hot buffer。

Decode 每步执行时,indexer 先给出本步需要访问的 top-k token。HiSparse kernel 随后检查这些 token 是否已经在 GPU hot buffer 中:

  • hit:直接读取 resident slot;

  • miss:从 CPU host pool 拷到可驱逐的 GPU slot,并更新请求自己的 page table 或 slot table。

图 8: GPU 只保留小 hot buffer(4k/8k),CPU host pool 保存完整 KV mirror;miss 时再 swap-in。这是 decode-time sparse loading,不是 prefill prefix cache。

4.2 DeepSeekV4 + HiSparse 设计

DeepSeek V4 中,HiSparse 主要作用在 C4 compressed KV 上。SWA 只覆盖最近窗口,常驻 GPU 的成本相对可控;C128 仍按 dense history 读取;真正会随长上下文持续膨胀的是 C4 compressed history。因此,V4 HiSparse 的缓存布局可以概括为:

SWA: 留在 GPU,小而热
C128: 留在 GPU,dense 读取
C4: CPU full mirror + GPU hot buffer
C4 indexer / state: 按各自语义管理
在 P/D 分离部署中,Prefill 侧仍按 DeepSeek V4 的正常路径生成各类 KV。传到 Decode 侧时,C4 compressed KV 进入 Decode Host mirror,作为完整历史的 CPU 备份;SWA、C128、indexer 和 state 则进入 Decode GPU。这样 Decode Worker 不再为全部 C4 历史预留显存,只保留一个可替换的 C4 hot buffer。

图 9:DeepSeek V4 + HiSparse 的 P/D 传输与 decode 流程。

P 端将 C4 compressed KV 直接传到 Decode Host mirror,其他 V4 KV 进入 Decode GPU;D 端每步执行 C4 top-k、HiSparse swap-in 和 C4 sparse attention forward,并在生成新的 C4 token 后按需 D2H backup。

进入 decode 后,流程收敛成一个很短的循环。第一步,C4 indexer 根据当前 query 选出本步需要访问的 top-k compressed token position。第二步,HiSparseCoordinator.swap_in_selected_pages 检查这些 token 是否已经在 C4 hot buffer 中:命中时直接返回 GPU slot,未命中时从 Host mirror 拷入可驱逐 slot,并更新 LRU 与 token-to-slot 映射。第三步,DeepSeek V4 backend 用新的 device slot 覆盖 c4_sparse_page_indices,C4 sparse attention 只读取已经 resident 的 GPU KV。生成新 token 后,如果落在 C4 压缩边界上,再把对应 KV backup 到 Host mirror,保证 CPU 侧始终保有完整 C4 历史。

4.3 HiSparse 实践——
显存节省转化为吞吐收益

HiSparse 的收益随并发和上下文长度变化明显。并发升高后,GPU 侧常驻 KV 的节省开始转化为大 Batch 高吞吐收益。

图 10:peak throughput for DeepSeek-V4-Flash on 2xB200, 200K-input / 20K-output, swa_full_tokens_ratio=0.001

另一个需要关注的指标是 miss count。hot buffer 太小或 LRU 策略不合适时,每步 top-k 会产生更多 host miss,导致 H2D 传输成为瓶颈。 我们为每个 Req 在 Device 侧维护了 4K LRU Buffer,可显著降低每步 topk 的 miss tokens

图 11:hot buffer 大小和 LRU 策略会直接影响 miss count。miss 不是系统噪声,而是 HiSparse 性能模型里必须单独管理的成本。

5. 总结

总的来说,DeepSeek V4 的分层缓存可以从三个层次理解:

第一层是 Shadow Radix,解决"怎么描述"的问题。它把 prefix cache 统一在 full logical index 上,让 scheduler 和 radix tree 不需要直接管理 SWA、C4、C128、indexer 和 state pools。

第二层是 V4 HiCache,解决"Prefill 放不下"的问题。它基于 UnifiedRadixTree,把 Full/SWA 作为 prefix tree component,再通过 SidecarPool 把 DeepSeek V4 的多类物理 pool 一起 offload、load-back 和 prefetch。它服务 prefill,目标是提高长前缀 KV 的复用能力。

第三层是 V4 HiSparse,解决"Decode 占太满"的问题。它服务 decode,只针对 C4 sparse attention 做分层加载。CPU 保存完整 C4 mirror,GPU 只维护 per-request hot buffer,本步 top-k miss 才增量加载到 GPU。

这套架构在Prefill 侧为多轮对话的 Input Throughput 提升接近 3 倍,长前缀命中率和 TTFT 均保持在健康水位;在Decode 侧,HiSparse 在 2×B200、200K 输入的场景下,通过释放 C4 冷 KV 的显存占用,将 peak throughput 显著拉高——并发越大,收益越明显。

如果你正在部署 DeepSeek V4 的长上下文推理服务,尤其是面临多轮对话前缀复用或大并发 decode 的场景,欢迎尝试 SGLang + Tair KVCache 的组合。

参考资料

  • SGLang DeepSeek-V4 Day-0 blog: https://www.lmsys.org/blog/2026-04-25-deepseek-v4/

  • SGLang HiSparse blog: https://www.lmsys.org/blog/2026-04-10-sglang-hisparse/


从系统角度看,分层会不会成为标配,取决于 H2D 传输、命中率、调度复杂度能否被收益覆盖。文章里 Prefill 有前缀复用,Decode 有 sparse top-k,这两类访问模式都比较适合分层。如果访问完全随机,分层未必一定划算。

3 个赞

会增加复杂度,但可能是必要复杂度。针对“Shadow Radix 会不会更难调试”,答案肯定是会:逻辑地址和物理地址分离后,排查问题要同时看 full index、SWA index、C4/C128 index、state ring。可如果不这么做,scheduler 直接理解所有物理形态,复杂度会扩散到更多模块。

1 个赞

关于“HiCache 和 HiSparse 的区别”,最简单的说法是:HiCache 管前缀复用,HiSparse 管 decode 时的冷 KV。HiCache 是请求进来前先看有没有历史 prefix 命中,把能复用的 KV 拉回来;HiSparse 是生成每个 token 时,只把这一步 sparse attention 真要用的 C4 KV 放到 GPU。

2 个赞

针对“是不是只有 DeepSeek V4 才需要”,我看不是。DeepSeek V4 的复杂点在于 SWA、C4、C128、indexer 这些形态把问题放大了,但本质矛盾是长上下文推理里的 KV 太大。只要模型上下文越来越长、并发越来越高,缓存分层就是绕不开的工程题。

2 个赞

如果拿饭店打比方,HiCache 是提前把老顾客常点的菜备好,Prefill 一来就能少做很多;HiSparse 是服务员每次只把当前要上的菜端到桌上,后厨还有完整库存,不让桌面被盘子占满。

3 个赞