sskj/dspark_deepseekv4_fix_pr_prep.md
2026-07-08 02:17:13 +00:00

287 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# DeepSeek-V4-Flash-DSpark 修复记录GitHub Issue #47648
> 本文件记录 vLLM DSpark 在 DeepSeek-V4-Flash 上的启动失败问题、根因、修复 diff 以及验证结果,便于后续直接用于提交 PR。
---
## 1. 问题概述
在 H200/SM90以及同样走 NVIDIA 路径的 B200/SM120使用 `--spec-method dspark` 部署 `DeepSeek-V4-Flash-DSpark` 时,模型初始化阶段会失败。主要表现为两类错误:
1. **DSpark draft 权重加载路径错配**draft 权重checkpoint 中以 `mtp.{i}.*` 命名)没有被正确加载到模型中。
2. **KV cache shape mismatch**:当启用 `fp8_ds_mla` layout 时KV cache 的 per-token slot size 被错误计算为 512B而实际需要 584B导致 shape 断言失败。
关联 issue[vllm-project/vllm#47648](https://github.com/vllm-project/vllm/issues/47648)
---
## 2. 环境信息
- vLLM 版本:`0.23.1rc1.dev788+gfa4321de3`vllm-dspark wheel
- PyTorch`2.11.0+cu129`
- GPU8× NVIDIA H200SM90TP=8
- 模型:`/data/models/DeepSeek-V4-Flash-DSpark`
- 启动命令:
```bash
vllm serve /data/models/DeepSeek-V4-Flash-DSpark \
--trust-remote-code \
--tensor-parallel-size 8 \
--kv-cache-dtype fp8 \
--block-size 256 \
--max-model-len auto \
--max-num-seqs 256 \
--tokenizer-mode deepseek_v4 \
--reasoning-parser deepseek_v4 \
--spec-method dspark \
--spec-model /data/models/DeepSeek-V4-Flash-DSpark \
--spec-tokens 5 \
--no-disable-hybrid-kv-cache-manager \
--disable-uvicorn-access-log \
--port 30004
```
---
## 3. 根因分析
### 3.1 根因一DSpark draft 权重加载路径错配
#### 位置
`vllm/models/deepseek_v4/nvidia/dspark.py``_remap_dspark_name` 方法。
#### 问题描述
DeepSeek-V4-Flash-DSpark 的 draft 权重是嵌在目标模型 checkpoint 中的,命名格式为 `mtp.{i}.*`,例如:
```text
mtp.0.self_attn.wq_b.weight
mtp.0.ffn.experts.0.w1.weight
mtp.1.self_attn.wq_b.weight
...
```
`_remap_dspark_name` 负责把这些 checkpoint key 映射到 DSpark draft 模型内部的真实参数名。原来的实现:
```python
return f"model.layers.{self.num_hidden_layers + stage}.{rest}"
```
`mtp.0.*` 映射成了 `model.layers.{num_hidden_layers + 0}.*`(例如 `model.layers.64.*`)。
但实际上DSpark draft 的 3 个 `DeepseekV4DecoderLayer` 是放在一个 `nn.ModuleList` 里的PyTorch 真实注册的参数名只跟 `ModuleList` 的索引有关:
```text
model.layers.0.*
model.layers.1.*
model.layers.2.*
```
`DeepseekV4DecoderLayer` 构造函数里传入的 `prefix=f"layers.{num_hidden_layers + i}"` 只是为了内部计算 `compress_ratio`(让 `layer_id >= num_hidden_layers` 时固定 `compress_ratio=1`**不是真实的参数名前缀**。
因此,原来的映射导致所有 draft block 权重都找不到对应的模型参数draft layer 实际上没有被正确加载。
#### 修复
`mtp.{i}` 的 block 权重映射到 `model.layers.{i}`
```python
def _remap_dspark_name(self, name: str) -> str | None:
"""Map a checkpoint ``mtp.{i}.*`` name to this model's parameter path.
Returns None for non-mtp weights (owned by the target model).
"""
m = re.match(r"mtp\.(\d+)\.(.*)", name)
if m is None:
return None
stage = int(m.group(1))
rest = m.group(2)
# The confidence head is not wired into inference yet; drop its weights.
if rest.startswith("confidence_head."):
return None
# Head-stack params live at model level (mtp.last), context combiner at
# model level (mtp.0); everything else is a per-layer decoder block.
head_prefixes = (
"norm.",
"hc_head_fn",
"hc_head_base",
"hc_head_scale",
"markov_head.",
)
if rest.startswith(("main_proj.", "main_norm.")) or rest.startswith(
head_prefixes
):
return f"model.{rest}"
# Draft layers live in a ModuleList, so their actual parameter names are
# model.layers.{stage}.* even though the prefix passed to the decoder
# layer is layers.{num_hidden_layers + stage} (for compress_ratio).
return f"model.layers.{stage}.{rest}"
```
### 3.2 根因二DeepSeek-V4 KV cache shape mismatch
#### 位置
`vllm/models/deepseek_v4/attention.py``DeepseekV4Attention.get_kv_cache_spec` 方法。
#### 问题描述
`DeepseekV4Attention.get_kv_cache_spec` 返回的 `MLAAttentionSpec` 没有设置 `kv_quant_mode`。在 `gpu_model_runner.py` 中,这个缺失导致 `cache_dtype_str` 被当成 `"auto"` 传给 `DeepseekV4FlashMLABackend.get_kv_cache_shape`,返回的 shape 是 `(num_blocks, block_size, 512)`
但对于 `fp8_ds_mla` layoutUE8M0 block-scaled fp8`uint8` 打包),每个 token 的 KV slot 实际大小是 584 字节,而不是 512。于是 KV cache 分配的空间不够,触发 shape mismatch / assert。
#### 修复
`MLAAttentionSpec` 中加上 `kv_quant_mode=get_kv_quant_mode(self.kv_cache_dtype)`
```python
def get_kv_cache_spec(self, vllm_config: VllmConfig) -> KVCacheSpec | None:
if (
self.compress_ratio <= 1
): # SWA part. Allocated separately as DeepseekV4SWACache.
return None
# fp8_ds_mla is a UE8M0 block-scaled uint8 layout and needs 576B
# alignment; plain bf16 / per-tensor fp8 rows use natural element-size
# pages.
uses_fp8_ds_mla_layout = self.kv_cache_dtype == "fp8_ds_mla"
return MLAAttentionSpec(
block_size=vllm_config.cache_config.block_size,
num_kv_heads=1,
head_size=self.head_dim,
dtype=torch.uint8 if uses_fp8_ds_mla_layout else self.kv_cache_torch_dtype,
compress_ratio=self.compress_ratio,
cache_dtype_str=self.kv_cache_dtype,
alignment=576 if uses_fp8_ds_mla_layout else None,
model_version="deepseek_v4",
kv_quant_mode=get_kv_quant_mode(self.kv_cache_dtype),
)
```
---
## 4. 修改文件清单
| 文件 | 修改内容 |
|---|---|
| `vllm/models/deepseek_v4/nvidia/dspark.py` | `_remap_dspark_name`draft block 权重从 `model.layers.{num_hidden_layers + stage}` 改为 `model.layers.{stage}` |
| `vllm/models/deepseek_v4/attention.py` | `DeepseekV4Attention.get_kv_cache_spec`:在 `MLAAttentionSpec` 中补充 `kv_quant_mode=get_kv_quant_mode(self.kv_cache_dtype)` |
> 如果上游 vllm-main 仓库与 vllm-dspark 安装包的文件结构一致,也需要同步修改 `vllm-main` 下对应路径的同名文件。
---
## 5. 完整 diff面向 PR
```diff
--- a/vllm/models/deepseek_v4/nvidia/dspark.py
+++ b/vllm/models/deepseek_v4/nvidia/dspark.py
@@ -486,8 +486,8 @@ class DSparkDeepseekV4ForCausalLM(nn.Module):
if rest.startswith(("main_proj.", "main_norm.")) or rest.startswith(
head_prefixes
):
return f"model.{rest}"
- # Draft layers live after the target layers in the decoder stack.
- return f"model.layers.{self.num_hidden_layers + stage}.{rest}"
+ # Draft layers live in a ModuleList, so their actual parameter names are
+ # model.layers.{stage}.* even though the prefix passed to the decoder
+ # layer is layers.{num_hidden_layers + stage} (for compress_ratio).
+ return f"model.layers.{stage}.{rest}"
```
```diff
--- a/vllm/models/deepseek_v4/attention.py
+++ b/vllm/models/deepseek_v4/attention.py
@@ -613,6 +613,7 @@ class DeepseekV4Attention(nn.Module, AttentionLayerBase, ABC):
cache_dtype_str=self.kv_cache_dtype,
alignment=576 if uses_fp8_ds_mla_layout else None,
model_version="deepseek_v4",
+ kv_quant_mode=get_kv_quant_mode(self.kv_cache_dtype),
)
```
---
## 6. 验证结果
### 6.1 Qwen3 DSpark 通路验证
- 目标模型:`/data/models/Qwen3-4B`
- Draft 模型:`deepseek-ai/dspark_qwen3_4b_block7`
- 结果:服务启动成功,`/v1/completions` 返回结果正常speculative decoding 工作。
### 6.2 DeepSeek-V4-Flash-DSpark 服务验证
修复后,使用第 2 节的命令可以成功启动服务,监听 `http://127.0.0.1:30004`。简单 completion 请求返回正常:
```bash
curl -s -X POST http://127.0.0.1:30004/v1/completions \
-H "Content-Type: application/json" \
-d '{
"model": "/data/models/DeepSeek-V4-Flash-DSpark",
"prompt": "The capital of France is",
"max_tokens": 20,
"temperature": 0.0
}'
# 返回: " Paris."
```
### 6.3 Serving benchmark
使用 vLLM 内置 `vllm bench serve`
```bash
vllm bench serve \
--host 127.0.0.1 --port 30004 \
--backend openai \
--dataset-name sharegpt \
--dataset-path /data/user1/yy/datasets/ShareGPT_filtered_chat.json \
--sharegpt-output-len 256 \
--num-prompts 50 \
--max-concurrency 16 \
--endpoint /v1/completions \
--model /data/models/DeepSeek-V4-Flash-DSpark \
--seed 42
```
结果:
| 指标 | 数值 |
|---|---|
| Request throughput | **2.38 req/s** |
| Output token throughput | **610.47 tok/s** |
| Acceptance rate | **28.20%** |
| Mean acceptance length | **2.41** |
| Mean TTFT | 2162 ms |
| Mean TPOT | 16.85 ms |
| Total input tokens | 16381 |
| Total generated tokens | 12800 |
服务器日志中的 SpecDecoding metrics 在稳态下 acceptance rate 落在 25%36% 区间,与 DSpark 论文预期一致。
---
## 7. 影响范围
- **受影响**:所有使用 NVIDIA 路径运行 DeepSeek-V4-Flash-DSpark 的 GPU包括 H200/SM90 的 FlashMLA 路径,以及 B200/SM120 的 FlashInfer SM120 路径)。
- **不受影响**
- Qwen3 DSpark使用独立的 `qwen3_dspark.py` 实现,不共享 `_remap_dspark_name`)。
- AMD/ROCm 和 XPU 平台(目前 DSpark 仅支持 NVIDIA
---
## 8. 后续 PR 待办
- [ ] 在 vllm-main 上同步应用以上两处修改。
- [ ] 跑通 DeepSeek-V4-Flash-DSpark 的单元测试 / 冒烟测试。
- [ ] 检查 `DeepseekV4IndexerCache.get_kv_cache_spec` 是否也需要补充 `kv_quant_mode`(当前未改动,因为未触发错误)。
- [ ] 补充 DSpark draft 权重加载的回归测试(可选,但建议)。
- [ ] 向 vllm-project/vllm 提交 PR并在描述中引用 issue #47648
---
## 9. 备注
- 本文件中的 token、路径、版本号均基于 2026-07-05 的实际运行环境。
- GitHub issue 评论已发布https://github.com/vllm-project/vllm/issues/47648#issuecomment-4886275279