Prhub

#25477 [BugFix]: Fix DeepSeek V4 HiCache layer count logic

原始 PR 作者 hzh0425 合并时间 2026-05-16 23:50 文件变更 3 提交数 4 评论 14 代码增减 +161 / -144

执行摘要

修复 DeepSeek V4 HiCache 层计数逻辑并拆分测试 CI

PP 支持提交改变了 layer_mapping 结构,使其包含 None 条目,原有的 len(compression_ratios) 不再代表真实的活跃层数,导致 HiCache 在多层映射、sidecar pool 注册时访问到不存在的层,引发崩溃或数据错误。PR body 明确说明:“A previous PP support commit changed DeepSeekV4TokenToKVPool.layer_mapping to be stage-aware, leaving entries outside the active layer range as None.”

此 PR 修复了关键的 PP + HiCache 兼容性问题,核心逻辑改动集中在层映射计算,值得精读以理解 PP 对缓存层的影响。同时应关注两个遗留风险:state pools 索引偏移和测试空覆盖,建议在后续 PR 中跟进修复。

讨论亮点

gemini-code-assist[bot] 在 review 中指出了两个高优问题:

  • state pools 索引风险:在 hybrid_pool_assembler.py 中,切片后 layer_id 从 0 开始计数,但 c4_state_global_layers 仍用此局部索引访问全局 compress_state_pools,在 PP 模式下会访问错误的 state pools。该评论标记为高优先级,但未在后续提交中修复。
  • 测试空覆盖:新增的测试类中存在重复的 test_gsm8k 定义,且都使用 pass 实现,导致继承自 UnifiedRadixTreeTestMixin 的实际测试被静默禁用,测试套件会误报成功。建议改用 @unittest.skipIf 装饰器调用 super()

ShangmingCai 最后批准了 PR,评论“LGTM, let's wait for the CI.”

实现拆解

  1. 核心逻辑修复(hybrid_pool_assembler.py):将 build_deepseek_v4_hicache_stackattach_hybrid_pool_to_unified_cache 中的 transfer_layer_numlen(kvcache.compression_ratios) 改为 kvcache.end_layer - kvcache.start_layer,并添加 TODO 备注后续支持 PP。
  2. 层映射切片修正:在枚举 kvcache.layer_mapping 构建 C4/C128 映射时,增加切片 [kvcache.start_layer : kvcache.end_layer],确保只处理当前 PP stage 拥有的层,避免访问 None 条目。
  3. 测试拆分与 CI 重排
    • 新增 test_unified_radix_cache_kl_hicache.py,将原本仅在 nightly 中运行的 Mamba 和 DeepSeek V4 HiCache 测试移至 base CI,注册为 base-c stage。
    • test_unified_radix_hicache_kl.py 重命名为 test_unified_radix_cache_kl_hicache_nightly.py,移除 Mamba 和 DeepSeek V4 测试,仅保留 GLM5 模型测试,并新增 GSM8KTwoPassMixin 用于验证精度稳定性。
文件 模块 状态 重要度
python/sglang/srt/mem_cache/hybrid_cache/hybrid_pool_assembler.py 缓存层 modified 6.0
test/registered/radix_cache/test_unified_radix_cache_kl_hicache.py 测试 added 7.31
test/registered/radix_cache/test_unified_radix_cache_kl_hicache_nightly.py 测试 renamed 7.25

关键符号

build_deepseek_v4_hicache_stack attach_hybrid_pool_to_unified_cache

关键源码片段

python/sglang/srt/mem_cache/hybrid_cache/hybrid_pool_assembler.py core-logic

核心修复文件:修改了 DeepSeek V4 HiCache 的 transfer_layer_num 计算和层映射枚举逻辑,确保 PP 场景下只处理当前 stage 的活跃层。

# python/sglang/srt/mem_cache/hybrid_cache/hybrid_pool_assembler.pydef build_deepseek_v4_hicache_stack(
    *,
    params: CacheInitParams,
    server_args: ServerArgs,
    kvcache: Any,
    page_size: int,
    tp_group,
    load_cache_event,
    attn_cp_group=None,
    attn_tp_group=None,
    storage_backend: Optional[str],
    host_swa_evict_fn=None,
    device_swa_evict_fn=None,
    prefetch_threshold: int = 256,
    model_name: Optional[str] = None,
    storage_backend_extra_config: Optional[dict] = None,
    pp_rank: int = 0,
    pp_size: int = 1,
    enable_storage_metrics: bool = False,
) -> tuple[HostPoolGroup, HybridCacheController]:
    # 使用 end_layer - start_layer 替代 len(compression_ratios)
    # 确保在多 PP stage 下只计算当前 stage 拥有的层数
    transfer_layer_num = kvcache.end_layer - kvcache.start_layer
​
    full_layer_mapping = {layer_id: layer_id for layer_id in range(transfer_layer_num)}
    swa_layer_mapping = {
        layer_id: layer_id for layer_id in range(len(kvcache.swa_kv_pool.kv_buffer))
    }
​
    c4_layer_mapping = {}
    c128_layer_mapping = {}
    c4_state_global_layers = []
    c128_state_global_layers = []
    # 对 layer_mapping 切片,只处理当前 stage 的层(避免访问 None 条目)
    for layer_id, layer_item in enumerate(
        kvcache.layer_mapping[kvcache.start_layer : kvcache.end_layer]
    ):
        if layer_item.compress_ratio == 4:
            c4_layer_mapping[layer_id] = layer_item.compress_layer_id
            c4_state_global_layers.append(layer_id)
        elif layer_item.compress_ratio == 128:
            c128_layer_mapping[layer_id] = layer_item.compress_layer_id
            c128_state_global_layers.append(layer_id)
    # ... 后续使用 c4_state_global_layers 索引 kvcache.compress_state_pools
    # 注意:review 指出此处 layer_id 为局部索引,可能引起 PP 下 state pools 访问错误

评论区精华

State pools 索引风险 正确性

gemini-code-assist[bot] 指出切片后 layer_id 为局部索引,但 c4_state_global_layers 仍用此索引访问全局 compress_state_pools,在 PP 模式下会访问错误的 state pools。

结论:未在代码中修复,PR 仍被批准合并。 · unresolved

测试类中重复定义 test_gsm8k 并使用 pass 测试

gemini-code-assist[bot] 发现 TestUnifiedMambaHiCache 中存在重复的 test_gsm8k 定义且均为 pass,导致继承自 UnifiedRadixTreeTestMixin 的实际测试被禁用。

结论:未修复,PR 被批准合并。 · unresolved

TestUnifiedDeepSeekV4FlashHiCache 同样问题 测试

类似的重复定义和 pass 覆盖也出现在 TestUnifiedDeepSeekV4FlashHiCache 类中,并且 test_multiturn_logprobs_match 也使用了 pass。

结论:未修复,PR 被批准合并。 · unresolved

风险与影响

  1. 未完全解决的 state pools 索引风险:review 指出的使用局部索引访问全局 state pools 的问题在 PP 模式下可能仍会导致错误,虽然当前 kvcache.compress_state_pools 的结构可能恰好使偏移正确,但需要警惕。
  2. 测试中 pass 覆盖风险TestUnifiedMambaHiCacheTestUnifiedDeepSeekV4FlashHiCache 存在重复的 test_gsm8k 定义(均为 pass),导致 GSM8K 精度测试在新增的 base CI 中不会实际执行,可能隐藏回归。
  3. CI 执行时间:将原本 nightly 的 Mamba/DSV4 测试移至 base CI 增加了 base-c stage 的执行时间(估计 768 秒),但通过拆分文件避免全部 nightly 测试一起运行,整体 CI 时间可控。

用户影响:修复了 DeepSeek V4 使用 HiCache + PP 时的启动崩溃或推理错误,用户升级后可正常启用 hierarchical cache。
系统影响:CI 结构变化,base CI 新增两个大型测试(Mamba 和 DSV4),nightly 测试减少,有助于更快发现回归。
团队影响:需维护两个测试文件,且注意修复 review 中遗留的问题。

state pools 索引风险未修复 测试空覆盖可能隐藏回归

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论