Qwen3.5-397B-A17B-w8a8 双机部署排障复盘:从报错到定位损坏分片

背景

本文记录一次在两台 Atlas A2 节点上部署 Qwen3.5-397B-A17B-w8a8(vLLM Ascend)时的完整排障过程:

  • 先按双节点 torchrun + tensor-parallel-size=16 方式拉起服务;
  • 再从日志中抽取关键异常;
  • 基于调用栈回溯到具体加载逻辑;
  • 最终用校验脚本定位到损坏的模型分片文件。

目标不是“猜测”,而是通过可复现日志与代码路径,得到可验证结论。


1. 复现环境与启动命令

1.1 两节点启动前准备

mkdir -p /tmp/qwen_logs

1.2 节点0(节点0)

docker run -it --rm \
  --name qwen35-tp16-node0 \
  --net=host --ipc=host --privileged --userns=host \
  --security-opt label=disable --shm-size=64g \
  --device=/dev/davinci0 --device=/dev/davinci1 --device=/dev/davinci2 --device=/dev/davinci3 \
  --device=/dev/davinci4 --device=/dev/davinci5 --device=/dev/davinci6 --device=/dev/davinci7 \
  --device=/dev/davinci_manager --device=/dev/devmm_svm --device=/dev/hisi_hdc \
  -v /usr/local/Ascend/driver:/usr/local/Ascend/driver \
  -v /usr/local/Ascend/firmware:/usr/local/Ascend/firmware \
  -v /usr/local/sbin/npu-smi:/usr/local/sbin/npu-smi \
  -v /home/qwen35w8a8:/models/qwen35w8a8:ro \
  -w /vllm-workspace/vllm \
  vllm-ascend:qwen3_5-v0-a2 /bin/bash -lc '
set -euo pipefail
export PYTHONPATH=/vllm-workspace/vllm:/usr/local/Ascend/ascend-toolkit/latest/python/site-packages
source /usr/local/Ascend/ascend-toolkit/set_env.sh || true
export HCCL_IF_IP=节点0
export HCCL_SOCKET_IFNAME=bond0
export GLOO_SOCKET_IFNAME=bond0
export TP_SOCKET_IFNAME=bond0
export OMP_NUM_THREADS=1
export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True
export ASCEND_RT_VISIBLE_DEVICES=0,1,2,3,4,5,6,7

torchrun \
  --nnodes=2 \
  --nproc_per_node=8 \
  --node_rank=0 \
  --master_addr=节点0 \
  --master_port=29500 \
  -m vllm.entrypoints.openai.api_server \
  --model /models/qwen35w8a8 \
  --served-model-name qwen3.5 \
  --host 0.0.0.0 --port 8010 \
  --distributed-executor-backend external_launcher \
  --tensor-parallel-size 16 \
  --max-model-len 1024 \
  --max-num-batched-tokens 2048 \
  --max-num-seqs 4 \
  --gpu-memory-utilization 0.90 \
  --quantization ascend
' 2>&1 | tee /tmp/qwen_logs/node0.log

1.3 节点1(节点1)

docker run -it --rm \
  --name qwen35-tp16-node1 \
  --net=host --ipc=host --privileged --userns=host \
  --security-opt label=disable --shm-size=64g \
  --device=/dev/davinci0 --device=/dev/davinci1 --device=/dev/davinci2 --device=/dev/davinci3 \
  --device=/dev/davinci4 --device=/dev/davinci5 --device=/dev/davinci6 --device=/dev/davinci7 \
  --device=/dev/davinci_manager --device=/dev/devmm_svm --device=/dev/hisi_hdc \
  -v /usr/local/Ascend/driver:/usr/local/Ascend/driver \
  -v /usr/local/Ascend/firmware:/usr/local/Ascend/firmware \
  -v /usr/local/sbin/npu-smi:/usr/local/sbin/npu-smi \
  -v /home/qwen35w8a8:/models/qwen35w8a8:ro \
  -w /vllm-workspace/vllm \
  vllm-ascend:qwen3_5-v0-a2 /bin/bash -lc '
set -euo pipefail
export PYTHONPATH=/vllm-workspace/vllm:/usr/local/Ascend/ascend-toolkit/latest/python/site-packages
source /usr/local/Ascend/ascend-toolkit/set_env.sh || true
export HCCL_IF_IP=节点1
export HCCL_SOCKET_IFNAME=bond0
export GLOO_SOCKET_IFNAME=bond0
export TP_SOCKET_IFNAME=bond0
export OMP_NUM_THREADS=1
export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True
export ASCEND_RT_VISIBLE_DEVICES=0,1,2,3,4,5,6,7

torchrun \
  --nnodes=2 \
  --nproc_per_node=8 \
  --node_rank=1 \
  --master_addr=节点0 \
  --master_port=29500 \
  -m vllm.entrypoints.openai.api_server \
  --model /models/qwen35w8a8 \
  --served-model-name qwen3.5 \
  --host 0.0.0.0 --port 8010 \
  --distributed-executor-backend external_launcher \
  --tensor-parallel-size 16 \
  --max-model-len 1024 \
  --max-num-batched-tokens 2048 \
  --max-num-seqs 4 \
  --gpu-memory-utilization 0.90 \
  --quantization ascend
' 2>&1 | tee /tmp/qwen_logs/node1.log

2. 第一轮日志筛查:快速锁定失败阶段

先用统一关键字抓取异常:

grep -n "ERROR\|Traceback\|RuntimeError\|EngineCore failed to start" /tmp/qwen_logs/node0.log | head -n 20
grep -n "ERROR\|Traceback\|RuntimeError\|EngineCore failed to start" /tmp/qwen_logs/node1.log | head -n 20

观察到两个现象:

  1. 两侧都出现 EngineCore failed to start
  2. 但 node1 的调用栈更早进入 load_model 相关路径并抛异常。

这说明问题不像纯网络/HCCL 首因(因为通信已基本建立),更像是模型加载阶段出错。


3. 第二轮定位:提取 node1 首个完整异常上下文

为了避免被截断日志误导,直接抓取首个异常附近的大段上下文:

line=$(grep -n "EngineCore failed to start" /tmp/qwen_logs/node1.log | head -n1 | cut -d: -f1)
sed -n "$((line-160)),$((line+220))p" /tmp/qwen_logs/node1.log

关键调用栈末端如下(核心片段):

.../weight_utils.py", line 724, in safetensors_weights_iterator
  with safe_open(st_file, framework="pt") as f:
safetensors_rust.SafetensorError: Error while deserializing header: incomplete metadata, file not fully covered

这个错误信息非常明确:

  • safe_open 在读取某个 .safetensors 文件头时失败;
  • incomplete metadata, file not fully covered 指向文件内容不完整/截断/损坏,而不是参数配置错误。

4. 排除干扰项:通信并非首要根因

从日志还能看到:

  • 多个 rank 出现 Rank x is connected to 15 peer ranks,说明 16 卡组网/进程组建立过;
  • 也有 Rank 0 is connected to 0 peer ranks 的瞬态输出,但并未直接对应最终抛错点;
  • 真正致命栈落在 safetensors 反序列化。

结论:本次失败不是先由 HCCL/NCCL 通信错误触发,而是模型权重文件读取失败触发。


5. 最终验证:用代码逐个校验模型分片

为了把“怀疑”变成“证据”,在容器内逐个检查所有 safetensors 分片:

docker run --rm -it \
  -v /home/qwen35w8a8:/models:ro \
  vllm-ascend:qwen3_5-v0-a2 /bin/bash -lc '
python3 - << "PY"
import glob
from safetensors import safe_open

bad = []
for p in sorted(glob.glob("/models/*.safetensors")):
    try:
        with safe_open(p, framework="pt") as f:
            _ = list(f.keys())[:1]
    except Exception as e:
        bad.append((p, str(e)))

print("bad_count =", len(bad))
for p, e in bad:
    print("BAD:", p)
    print("ERR:", e)
PY
'

输出:

bad_count = 1
BAD: /models/quant_model_weights-00084-of-00099.safetensors
ERR: Error while deserializing header: incomplete metadata, file not fully covered

至此,问题定位闭环完成。


6. 根因结论

本次双机启动失败的直接根因是:

  • 模型分片文件损坏/不完整,具体为:
    • /home/qwen35w8a8/quant_model_weights-00084-of-00099.safetensors

因此 vllm 在模型加载阶段触发 safetensors_rust.SafetensorError,并导致 EngineCore failed to start


7. 可复用排障方法论(模板)

以后遇到类似“大模型分布式启动失败”,建议按这个顺序走:

  1. 先抓首个致命异常EngineCore failed to start 附近 200~400 行;
  2. 看调用栈最底层库抛错点(不要只看上层 RuntimeError);
  3. 区分阶段:通信初始化、权重加载、KV cache 初始化分别对应不同根因;
  4. 写最小验证脚本(本例是逐个 safe_open)把根因“可执行化”;
  5. 输出可证据化结论:具体文件、具体错误、可复现命令。

8. 一句话复盘

这次不是“参数调优问题”,也不是“网络首因问题”,而是单个 safetensors 分片损坏导致的模型加载失败。通过“日志调用栈 + 逐文件代码验证”,最终把问题定位到 00084-of-00099 这个具体文件。

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐