2026-10-21 15:52:23 +08:00
# DeepSeek V4 Pro / PPU-ZW810E vLLM 补丁
这是 2026-10-20 至 2026-10-21 在 `PPU-ZW810E` 上验证过的 DeepSeek V4 Pro 推理补丁。它以原始 vLLM 镜像为基础,修改四个 vLLM Python 源文件;**不修改 `/model` 的 checkpoint 或模型 `config.json` **。当前验证环境是 vLLM 0.26.0、PyTorch 2.11.0、Python 3.12、16 张 PPU, TP=8、DP=2、EP 开启。
已验证的基础镜像:
```text
swr.cn-north-4.myhuaweicloud.com/inference-engines/llm:v2.1.1-pytorch2.11.0-ubuntu24.04-cuda13.0-vllm0.26.0-py312
```
## 修复了什么
1. **FP8 block linear 无法通过 PPU CUTLASS sm80 fallback。**原服务加载完 64 个权重 shard 后,在 `cutlass_scaled_mm_sm80` / `scaled_mm_c2x.cu:110` 报错。运行时设置 `VLLM_DISABLED_KERNELS=PPUCutlassFp8BlockScaledMMKernel` ,从候选列表中移除这个出错的 kernel; 本次模型日志确认随后选择了 Marlin 的 FP8 weight-only 路径。PPU DeepGEMM 仍保留在候选列表中,其他形状或模型不保证总是选择 Marlin。Marlin 可能比原生 FP8 计算慢。
2. **输出投影的 FP8 融合算子不支持该架构。** `_fused_inv_rope_fp8_quant_per_head` 使用 `fp8e4nv` ,而 PPU 编译器只支持 `fp8e4b15` 、`fp8e5` 。`VLLM_PPU_BF16_O_PROJ=1` 时,在 Marlin 打包前将 `wo_a` 的原始 block FP8 权重与 scale 解码成 BF16; 输出投影使用 FP32 中间值完成 inverse RoPE、BF16 分组 `bmm` ,然后调用现有 `wo_b` 。这是 FP8 **解码到 BF16** ,不是重新编码成 uint8 FP8。FP8 channelwise 路径也会遇到 `fp8e4nv` ,因此没有采用。
3. **compressor GEMM 触发设备参数错误。**原 BF16 输入、FP32 输出的 GEMM 在 PPU 上报 `CUBLAS_STATUS_INVALID_VALUE` ;失败后再在同一流上转换 FP32 会继续报 `invalid argument` 。补丁直接把两侧输入转换为 FP32, 再执行普通 `torch.mm` ,避免先调用已知失败的 GEMM。
4. **profile / 稀疏 MLA 预热仍有异步设备错误。**曾在 MoE 加法以及 indexer 的 `torch.empty` 处看到 `invalid argument`;这些位置不一定是实际出错算子。仅把 `VLLM_MULTI_STREAM_GEMM_TOKEN_THRESHOLD` 设为 `0` 不会关闭注意力的辅助流。新增 `VLLM_PPU_SERIAL_ATTENTION=1`, 使注意力、indexer、compressor 使用单流。该配置完成了启动和实际推理,但**尚未单独证明多流是唯一根因** 。
代码位置:
| 文件 | 改动 |
| --- | --- |
| `src/vllm/model_executor/layers/quantization/fp8.py` | 在权重处理阶段为 `wo_a` 启用 BF16 解码 |
| `src/vllm/models/deepseek_v4/ppu/ops/o_proj.py` | BF16 输出投影与 FP32 compressor GEMM |
| `src/vllm/models/deepseek_v4/ppu/flashmla.py` | 将 PPU 输出投影切换到 BF16 路径 |
| `src/vllm/models/deepseek_v4/attention.py` | 两处 compressor 调用与注意力单流开关 |
`upstream/` 是从原始文件备份取得的精确版本,`src/` 是成功运行的精确版本,`patches/vllm.patch` 是两者的统一 diff, `manifest.json` 记录两组 SHA-256。`scripts/apply.py` 在写入前核对全部四个目标文件和包内源码;遇到不同的 vLLM 版本会拒绝安装。首次安装会在目标文件旁生成 `.ppu-dsv4.orig` 备份。
该版本也接受 `vllm serve ... --linear-backend marlin` ,它会要求所有适用的量化 linear 使用 Marlin; 如果某种层没有可用的 Marlin kernel, 启动会直接报错。它**不是** `VLLM_DISABLED_KERNELS=PPUCutlassFp8BlockScaledMMKernel` 的等价替代,且本项目的完整模型成功验证没有使用该选项。`--quantization marlin` 是另一类量化配置,不能把当前 FP8 checkpoint 当作 Marlin 格式 checkpoint 来指定。
## 构建包含补丁的新镜像(推荐)
在 PPU 主机上执行:
```sh
cd /tmp/ppu_deepseek_v4_pro_patch
nerdctl build \
--build-arg BASE_IMAGE=swr.cn-north-4.myhuaweicloud.com/inference-engines/llm:v2.1.1-pytorch2.11.0-ubuntu24.04-cuda13.0-vllm0.26.0-py312 \
-t deepseek-v4-pro:ppu-bf16-serial .
```
若主机提示 BuildKit daemon 未运行,可启动仅用于本次构建的 BuildKit containerd worker, 再指定其 socket; 这样会复用 containerd 的镜像层缓存:
```sh
nohup buildkitd --oci-worker=false --containerd-worker=true \
--containerd-worker-namespace default \
--addr unix:///tmp/ppu-buildkit.sock --root /tmp/ppu-buildkit \
> /tmp/ppu-buildkit.log 2>&1 < /dev/null &
nerdctl build --buildkit-host unix:///tmp/ppu-buildkit.sock \
--build-arg BASE_IMAGE=swr.cn-north-4.myhuaweicloud.com/inference-engines/llm:v2.1.1-pytorch2.11.0-ubuntu24.04-cuda13.0-vllm0.26.0-py312 \
-t deepseek-v4-pro:ppu-bf16-serial .
```
构建时的 `RUN python .../scripts/apply.py --apply` 会先验证原始镜像内四个文件的 SHA-256, 再替换源码。`nerdctl build` 成功即表示补丁已写入**新镜像**,原镜像标签不会被覆盖。构建失败若提示 `Installed source does not match this patch` ,说明基础镜像中的 vLLM 源码与本项目的原始版本不同;应先比较源码,不要强行跳过校验。
2026-10-21 16:02:59 +08:00
创建服务容器时,沿用原容器的 PPU 设备映射、权限、共享内存和网络配置,改用 `deepseek-v4-pro:ppu-bf16-serial` 镜像,并将模型挂载到 `/model` 。这台机器原容器的模型挂载源是 `/bmcp_lvm_fs/data/model/DeepSeek-V4-Pro` ,容器目标是 `/model` ,共享内存配置为 64 GiB。PPU 设备如何注入由机器的容器运行环境决定,应复用原容器的创建参数。新镜像已经默认设置四个环境变量:`VLLM_DISABLED_KERNELS` 、`VLLM_PPU_BF16_O_PROJ` 、`VLLM_PPU_SERIAL_ATTENTION` 、`VLLM_MULTI_STREAM_GEMM_TOKEN_THRESHOLD` 。
2026-10-21 15:52:23 +08:00
在新容器里启动已验证的配置(把 `deepseek_v4_patched` 换成实际容器名):
```sh
nerdctl exec -d \
-e CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 \
deepseek_v4_patched \
python /opt/ppu-deepseek-v4-pro-patch/examples/launch.py serve.log
```
2026-10-21 16:02:59 +08:00
启动器读取 `examples/command-serial.json` ,将日志写到 `/opt/ppu-deepseek-v4-pro-patch/examples/serve.log` 。它运行的核心命令如下;如果按原有编排系统直接运行 `vllm serve` ,需同时传入镜像默认的四个环境变量,并明确指定全部 16 张卡:
2026-10-21 15:52:23 +08:00
```sh
vllm serve /model \
--trust-remote-code --kv-cache-dtype fp8 --block-size 256 \
--tensor-parallel-size 8 --data-parallel-size 2 --enable-expert-parallel \
--no-enable-flashinfer-autotune \
--tokenizer-mode deepseek_v4 --tool-call-parser deepseek_v4 \
--enable-auto-tool-choice --reasoning-parser deepseek_v4 \
--max-model-len 4096 --max-num-batched-tokens 1024 \
--enforce-eager --port 18000
```
该启动器将 `RLIMIT_MEMLOCK` 设为 unlimited; 如果容器运行时不允许此设置, 需在创建容器时提供相应 `memlock` 限额。`CUDA_VISIBLE_DEVICES` 不在镜像中固定,因为实际可见的 PPU 取决于部署环境。
## 检查、直接安装与回滚
新镜像内可检查四个文件的状态:
```sh
nerdctl exec deepseek_v4_patched \
python /opt/ppu-deepseek-v4-pro-patch/scripts/apply.py --check
```
如果要对一个**尚未修改**的原始容器做临时验证,可以将本目录复制进去,再检查、安装;这只修改该容器的可写层,不能替代上面的镜像构建:
```sh
cd /workspace/pytorch
nerdctl cp ./ppu_deepseek_v4_pro_patch deepseek_v4_original:/opt/ppu-deepseek-v4-pro-patch
nerdctl exec deepseek_v4_original python /opt/ppu-deepseek-v4-pro-patch/scripts/apply.py --check
nerdctl exec deepseek_v4_original python /opt/ppu-deepseek-v4-pro-patch/scripts/apply.py --apply
```
`--apply` 可重复运行;已经是补丁版本时不再替换。容器内回滚四处源码:
```sh
nerdctl exec deepseek_v4_original python /opt/ppu-deepseek-v4-pro-patch/scripts/apply.py --rollback
```
回滚后应停止服务并用原始环境变量重新启动;对于基于新镜像创建的容器,更直接的完整回滚方式是重新使用原始镜像。不要在正在执行推理的进程内热替换 Python 源码。
## 验证范围与限制
- 四个源码文件均通过语法检查;`patches/vllm.patch` 可对 `upstream/` 通过 `git apply --check` 。安装脚本的幂等安装、回滚和错误版本拒绝有离线测试。
- 合成尺寸的 BF16 `wo_a` 解码与输出投影相对参考计算的 L2 误差约 0.0016; `(1024,7168) @ (7168,1024)` 的 FP32 compressor GEMM 单卡测试通过。
- 实际模型加载约 55 GiB/卡; TP8/DP2/EP 的服务通过 profile、KV cache 分配、稀疏 MLA 预热。一次短请求和四次并发短请求成功;两个 API 进程均处理了请求。
- 长上下文测试中, 1037、3085、3853、4045 输入 token 的请求各生成 32 token, 均成功。一个 3619 输入 token 的聊天请求正确返回文中预埋的“蓝色”。测试后日志没有 `ERROR` 。
- 已验证配置使用 `--enforce-eager` 和 `--max-model-len 4096` 。超过 4096 token、较高并发、CUDA graph 模式以及生产性能尚未验证。原始未禁用辅助流的失败不能仅凭当前结果断定就是多流造成的。