公司动态
DeepSeek-V4-Flash × 昇腾 PD 分离 × Mooncake 外部 KV 池:external 命中乱码排障全记录
一句话结论在 DeepSeek-V4-Flashhybrid 架构 昇腾 PD 分离场景下AscendStoreConnector(backendmooncake) 外部共享 KV 池的 “external 命中” 路径未经过端到端验证实测表现为命中即乱码静默输出损坏而 PD 分离本身的 P→D KV 传输路径是可用且相对稳定的。重要声明文中标注[推断]的部分是基于现象与公开资料issue / release note / 社区报告的合理推测尚未被官方 issue 直接确认需要你用本文第 6 节的闸门自行验证。请勿将推断直接当结论上生产。1. 环境与背景1.1 部署架构项目配置硬件昇腾 910B3单卡 64GB HBM单机 8 卡模型DeepSeek-V4-Flash-0731hybridCompress-4/128 HCA / Mamba 混合框架vLLM 0.26.0 vLLM-Ascend实际 commit 未锁定强烈建议核查架构PD 分离Prefill / DecodeP 节点并行DP2 × TP4两机各 4 卡共 2 个 DP 组投机解码DSparknum_speculative_tokens7量化W8A8--quantization ascendKV 量化曾开int8已确认移除现走默认 fp16/bf161.2 业务痛点长上下文16K~200K tokens业务为主并发上来后本地 KV Cache 淘汰极快前缀命中率崩塌缓存丢失后需重新 prefill 长上下文耗时数秒到数十秒核心诉求启用跨节点共享 KV 池Mooncake 外部池复用别人的前缀 KV避免重算2. 问题现象2.1 核心现象external 命中即乱码启用AscendStoreConnector(backendmooncake) 后只要external_prefix_cache_hit_rate prefix_cache_hit_rata长上下文输出必乱码此时使用了外部缓存导致乱码但是好像一旦缓存命中后会放入gpu的显存中我重新对话prefix_cache_hit_rata高了又正常了关掉外部池纯本地 prefix cache完全正常、不乱码2.2 现象特征决定了排查方向现象说明短上下文正常长上下文乱短的没跨 chunk / group 边界本地命中正常external 命中乱问题在从池 load 回来这条路径external2% 也乱不是命中比例问题是只要命中过问题跑一会才乱池子先被填满、命中才开始发生纯 DRAM 与 SSD 均复现与存储介质无关2.3 关键认知为什么 2% 命中也会全乱KV Cache 是累加复用的——前缀里错一个 block后面所有 token 的 attention 都基于错误的 KV 计算。乱码不需要全部命中外部。类比一本 100 页的书只有第 3 页2%是错的但后续剧情都依赖第 3 页整本书就废了。这个认知直接**排除了把 external 命中率压低就能规避**的想法——只要 0就可能中招。3. 排障过程逐一排除嫌疑3.1 嫌疑一int8 KV 量化 →已排除假设kv_cache_dtypeint8的 KV 序列化进 Mooncake 池跨节点 load 回来后反量化出错scale / zero_point 错位、block 边界不对齐产生微小数值误差注意力累乘累加上万 token 后完全乱码。验证确认已移除 int8 配置走默认 fp16/bf16长上下文 external 命中依然乱码。结论int8 不是唯一根因但仍是高危组合外部池场景建议先不用 int8。3.2 嫌疑二MTP / DSpark 投机解码 →已排除逻辑上假设DSpark 的 draft 状态与 pool load 的 KV 不同步导致乱码。排除理由MTP/DSPark 作用在decode 阶段投机采样生成 output token而乱码发生在prefill / context 侧external 命中的是输入前缀 KV。这两者是独立的两条链路。重要推论不要为了救乱码而关掉 MTP/DSpark——既救不了又白丢一份加速没有测试。3.3 嫌疑三use_layerwise→确认不支持官方约束use_layerwise仅backendmemcache支持backendmooncake明确不支持hybrid 模型 TP 不匹配时源码会直接raise NotImplementedErrorhybrid 场景必须保持use_layerwise: false3.4 嫌疑四kv_load_failure_policyrecompute→确认不支持官方文档明确hybrid attention 模型DeepSeekV4、Qwen3.5不支持recompute源码层面 hybrid 会直接断言失败必须用默认fail策略3.5 嫌疑五存储介质 / 容量 / 参数 →已排除纯 DRAM vs SSD均复现 → 与介质无关global_segment_size调大调小均复现 → 与容量无关各种超时参数均复现 → 与超时无关这组排除实验价值极高——它把问题从配置/容量/介质层锁死到了语义层KV 状态恢复。3.6 嫌疑六DSA-CP 开关 →引发崩溃非乱码根因尝试开enable_dsa_cptrueenable_flashcomm1true后服务直接崩溃SDMA EI00100 / SDMA memory copy task exception error code: 507035 / The vector core execution is abnormal MTE error info / aivec error exception DDR address of the MTE instruction is out of range崩溃原因DSpark(num7) DSA-CPFlashComm1三者冲突底层环通信ring-ring-ring内存越界结论DSA-CP 是非零前缀命中的前提社区报告佐证但与 DSpark 不兼容。二者只能二选一或把 DSpark 降级为 MTP 1-token4. 根因收敛4.1 hybrid 模型 KV 的特殊性为什么这么难DeepSeek-V4-Flash 不是标准 GQA/MLA它的 KV 包含三类难以统一处理的状态压缩注意力状态Compress-4 / Compress-128多 token 压缩成一个 state前缀长度在压缩维度上不再简单对齐Mamba / SSM 递归状态必须严格按顺序累积不能像普通 KV 那样随便截取一段复用多个 KV Cache Group不同 group 的 block size、对齐方式不同这意味着外部池 KV 复用在 hybrid 上变成了普通模型存 KV → 命中 → 原样拷回 → 直接复用 V4(hybrid)存 KV 压缩state SSM state 专家路由 命中时要验证压缩边界对齐SSM状态连续hash一致 跨节点还要block对齐TP切分一致4.2 静默损坏的机理 [推断]最可能的机制content hash 命中与物理 KV 实际可安全复用的长度不一致。跨 Compress-4/128 的长 prompt 在写入池 / 读出池时压缩边界与 SSM 递归状态的连续性无法保证导致 load 回来的 KV 与现场重算的 KV 不一致——不报错只产生微小数值误差注意力累乘后完全乱码。4.3 官方已知关联修复佐证此路径确有静默损坏风险vLLM-Ascend v0.23.0rc1 release notes 明确修复“Fixed silent prefix-cache output corruption and block-table overflow for Qwen3-Next, Qwen3.5, and other hybrid Mamba models using MTP/EAGLE”#11353这说明hybrid KV 的前缀命中在这条代码线上确实曾出现静默输出损坏——你看到的命中即乱码是一个真实存在的风险类别。4.4 确定性支持矩阵本文最有价值的部分之一能力对 DeepSeek-V4-Flash(hybrid)依据PD 分离P→D KV 传输MooncakeHybridConnector✅支持实验性官方教程 落地案例外部共享 KV 池AscendStoreConnector mooncake⚠️未验证实测乱码本文实测use_layerwise❌mooncake 不支持仅 memcache官方文档kv_load_failure_policyrecompute❌hybrid 不支持官方文档 源码断言DSA-CP DSpark(7) 同时开❌冲突崩溃SDMA 越界本文实测DSA-CP MTP(1)⚠️ 待验证社区报告可行社区报告KV Cache CPU Offload⚠️ 架构支持V4-Flash 端到端待验证官方文档未点名 V45. 可行方案5.1 方案 A稳定优先推荐今天就能用保留 PD 分离只放弃外部共享池靠本地 prefix cache 路由亲和性提吞吐。P 节点standalone不带 Store--kv-transfer-config{ kv_connector: MooncakeHybridConnector, kv_role: kv_producer, kv_ip: P节点IP, kv_port: 30000, engine_id: 0, kv_connector_extra_config: { prefill: {dp_size: 2, tp_size: 4}, decode: {dp_size: 2, tp_size: 4} } }D 节点standalone consumer--kv-transfer-config{ kv_connector: MooncakeHybridConnector, kv_role: kv_consumer, kv_port: 30100, engine_id: 1, kv_connector_extra_config: { prefill: {dp_size: 2, tp_size: 4}, decode: {dp_size: 2, tp_size: 4} } }配套已验证不乱码的组合exportPYTHONHASHSEED0exportHCCL_INTRA_ROCE_ENABLE1exportVLLM_PREFIX_CACHE_RETENTION_INTERVAL16384# block-size128 下# P 开启 prefix caching不加 --no-enable-prefix-caching# D 保持 --no-enable-prefix-caching --no-disable-hybrid-kv-cache-manager --async-scheduling# 不用 AscendStoreConnector、不用 memcache、不用 use_layerwise、不用 recompute关键把跨节点共享 KV的收益换成把本地 prefix cache 命中率做高——请求按前缀亲和性路由到固定 P 实例 调大 HBM 给本地 pool 必要时加大批处理。5.2 方案 B外部池 正确性闸门给非要用共享缓存的人如果你一定要试外部池在方案 A 基础上只改 P 节点用MultiConnector把传输层和池化层包起来D 节点保持 standalone不要加AscendStoreConnector--kv-transfer-config{ kv_connector: MultiConnector, kv_role: kv_producer, engine_id: 0, kv_connector_extra_config: { connectors: [ { kv_connector: MooncakeHybridConnector, kv_role: kv_producer, kv_port: 30000, kv_connector_extra_config: { prefill: {dp_size: 2, tp_size: 4}, decode: {dp_size: 2, tp_size: 4} } }, { kv_connector: AscendStoreConnector, kv_role: kv_producer, kv_connector_extra_config: { lookup_rpc_port: 0, backend: mooncake, use_layerwise: false, load_async: false, consumer_is_to_put: false } } ] } }必读风险这个组合PD 分离 外部池没有公开成功案例。必须先过下面第 6 节的 token 级闸门。参数要点use_layerwise: falsemooncake 后端不支持load_async: false规避异步加载竞态不要kv_load_failure_policyrecomputehybrid 不支持想提高成功率可把 P/D 并行度改成DP2 × TP8 EP16对齐社区验证的 KV 切分5.3 方案 CCPU Offload 兜底外部池不靠谱时用 CPU 内存做 HBM 之外的第二层详见姊妹篇《调优篇》P 节点OffloadingConnector—— 被淘汰的前缀 KV 换页到 CPU下次直接从 CPU 载回避免重算D 节点RecomputeCPUOffloadConnectorrecompute_scheduler_enable:true—— 被抢占请求的 KV 暂存 CPU避免回流 P 重算6. token 级正确性闸门方法论可复现6.1 为什么不能看起来像人话乱码是静默错误输出语法通顺、格式正确但内容完全错误。肉眼无法判断。必须做token 级不是文本级比对。6.2 闸门设计temperature0 固定seed构造≥ 16K tokens的长 prompt要跨过 16384 的 retention checkpoint发两次相同请求第二次应命中外部池对比两次的output token ID 序列不是文本扫描 4K / 8K / 16K / 32K / 65K 多档长度构造跨 Compress-4/128 checkpoint的长 prompt vs 纯 Full Attention 短前缀6.3 判定标准# 伪代码response_1query(prompt,temperature0,seed42)# 第一次应 missresponse_2query(prompt,temperature0,seed42)# 第二次应 external hitassertresponse_1.token_idsresponse_2.token_ids# 逐 token 比对完全一致→ 通过✅ 该配置可用任何一档出现 diff→判为不可用回退方案 A6.4 分档加回 DSpark闸门通过后再逐步把投机解码加回来每档重跑闸门先--speculative-config {method:mtp,num_speculative_tokens:1,enforce_eager:true}通过后试3再试7哪档开始乱码就停在上一档——这是保外部池与保 DSpark 加速的权衡点7. 待验证清单与后续7.1 版本基线核查最重要先做这个pip show vllm vllm-ascendgitrev-parse HEAD官方 vLLM-Ascend 最新 tag 只到v0.23.0对齐 vLLM v0.23.0没有对应 vLLM 0.26.0 的官方 tag你现在这套vLLM 0.26.0 vLLM-Ascend很可能是内部分支/开发分支/版本显示被覆盖不锁定真实 commit升级就是盲赌公开成功案例含 0 error / 0 mismatch多在vLLM-Ascend nightly-mainvLLM 0.23.07.2 别被启动时的11GB吓到那是理论预留不是真实占用排查过程中你可能见过 vLLM 的提示“一个完整请求需要 11GB 显存显存不够请调低 max-model-len”这大概率只是 vLLM 按max-model-len算出来的理论预留最大值而非运行时真实占用别拿它当故障证据vLLM 启动时做一次profile_run再按max-model-len × 每 token KV 大小预留单请求最坏情况的 KV 空间该估算偏保守可能未充分计入 V4-Flash 的 MLA 压缩 / 混合注意力带来的 KV 缩减且默认按请求一定跑满max-model-len留预算V4-Flash 采用压缩注意力每 token KV 远小于传统 GQA 模型社区实测约 6~8 bytes/token 量级真实数字只能靠启动日志反算不挂任何 KV 连接器裸跑 baseline# 启动日志里找这两行# GPU KV cache size: YYYYY tokens# Available KV cache memory: Z GiBbytes_per_token(Z *1024^3)/ Y与社区参考值V4-Flash 压缩后约6~8 bytes/token量级量级一致→ hybrid 压缩 KV 工作正常显存不够只是max-model-len设太大导致的预留误报显著偏大数倍甚至一个数量级→ 排查是否误加了--disable-hybrid-kv-cache-manager禁掉它会让压缩失效、KV 变大或版本 / 连接器存在异常把max-model-len降到业务真实 P99这类误报会自然消失。一切以启动日志反算为准不要拿启动提示当真实占用。7.3 上游反馈建议你的诊断链是高质量 bug report建议提 issue包含精确复现external_prefix_cache_hit_rate 0即乱码本地正常、短正常、纯 DRAM 复现完整环境V4-Flash-0731 vLLM 0.26.0 Ascend Mooncake已排除项int8 / MTP / SSD / 容量 / 超时 / 介质 全部排除定位到语义层复用对比实验local vs external 的 token 级 diff7.4 结语这次排障最大的价值不是调参数调好了而是把一条未验证的技术路径用实验钉死成了确定的边界PD 分离本身可用外部共享 KV 池对 hybrid V4-Flash未验证且实测乱码int8 / MTP / 介质 / 容量 / 超时都不是根因真正该做的是锁版本 → 过闸门 → 分级加回特性最后提醒本文所有 [推断] 部分均未经官方确认。技术判断的价值在于可证伪——带上闸门用你自己的数据说话。