vllm单推理测试

官方匹配关系

截至 2026 年 6 月 8 日,推荐先使用稳定版:

组件 版本
镜像 quay.io/ascend/vllm-ascend:v0.18.0-a3
vLLM 0.18.0
vLLM-Ascend 0.18.0
PyTorch 2.9.0
torch-npu 2.9.0.post2
CANN/NNAL 9.0.0
硬件目标 Atlas A3,ascend910_9391

官方表格在:​编辑vLLM-Ascend v0.18.0 安装文档

镜像仓库在:Quay vllm-ascend Tags

原理是:

宿主机:A3 固件 + NPU 驱动 ↓ 设备和驱动库挂进容器 容器:CANN 9.0.0 + torch-npu 2.9.0.post2 + torch 2.9.0 + vLLM 0.18.0 + vLLM-Ascend 0.18.0

1. 检查宿主机

cat /etc/os-release uname -m docker --version npu-smi info ls -1 /dev/davinci[0-9]* 2>/dev/null | sort -V cat /usr/local/Ascend/driver/version.info 2>/dev/null

重点确认:

  • npu-smi info 能正常显示 A3 卡。
  • 至少存在 /dev/davinci0 到 /dev/davinci3。
  • Docker 正常。
  • 如果系统是 Ubuntu,使用 v0.18.0-a3。
  • 如果系统是 openEuler,使用 v0.18.0-a3-openeuler。

2. 拉取官方镜像

Ubuntu:

export IMAGE=quay.io/ascend/vllm-ascend:v0.18.0-a3 docker manifest inspect "$IMAGE" >/dev/null docker pull "$IMAGE"

openEuler:

export IMAGE=quay.io/ascend/vllm-ascend:v0.18.0-a3-openeuler docker pull "$IMAGE"

国内网络较慢时,可以使用官方 FAQ 推荐的镜像代理:

docker pull m.daocloud.io/quay.io/ascend/vllm-ascend:v0.18.0-a3 docker tag m.daocloud.io/quay.io/ascend/vllm-ascend:v0.18.0-a3 \ quay.io/ascend/vllm-ascend:v0.18.0-a3

查看是否拉取成功:

docker images | grep vllm-ascend docker image inspect "$IMAGE" --format '{``{index .RepoDigests 0}}'

最后一条显示的是镜像不可变的 digest,可以保存下来做复现实验。

3. 准备模型目录

假设官方 BF16 权重在:

export MODEL_HOST=/data/models/Qwen3.5-35B-A3B test -f "$MODEL_HOST/config.json" ls -lh "$MODEL_HOST" | head du -sh "$MODEL_HOST"

第一轮不要使用训练导出的权重,也不要使用 W8A8。先用官方 BF16 权重排除权重转换和量化问题。

4. 启动 TP4 容器

mkdir -p /data/vllm-logs docker run --rm -it \ --name qwen35-vllm-a3 \ --net=host \ --shm-size=32g \ --device /dev/davinci0 \ --device /dev/davinci1 \ --device /dev/davinci2 \ --device /dev/davinci3 \ --device /dev/davinci_manager \ --device /dev/devmm_svm \ --device /dev/hisi_hdc \ -v /usr/local/dcmi:/usr/local/dcmi \ -v /usr/local/bin/npu-smi:/usr/local/bin/npu-smi \ -v /usr/local/Ascend/driver/lib64:/usr/local/Ascend/driver/lib64 \ -v /usr/local/Ascend/driver/version.info:/usr/local/Ascend/driver/version.info \ -v /etc/ascend_install.info:/etc/ascend_install.info \ -v "$MODEL_HOST:/models/Qwen3.5-35B-A3B:ro" \ -v /data/vllm-logs:/logs \ "$IMAGE" bash

官方示例映射 A3 的 16 个设备;你现在做 TP4 单机实验,先映射 0,1,2,3 即可。

5. 在容器里检查版本

进入容器后执行:

npu-smi info python - <<'PY' import importlib.metadata as md import torch import torch_npu for pkg in ["vllm", "vllm-ascend", "torch", "torch-npu"]: print(f"{pkg:14s} = {md.version(pkg)}") print("NPU available =", torch.npu.is_available()) print("NPU count =", torch.npu.device_count()) print("NPU 0 =", torch.npu.get_device_name(0)) x = torch.ones(4, device="npu:0") print("NPU calculation =", x + 1) PY

期望看到:

vllm = 0.18.0 vllm-ascend = 0.18.0 torch = 2.9.0 torch-npu = 2.9.0.post2 NPU available = True NPU count = 4 NPU calculation = tensor([2., 2., 2., 2.], device='npu:0')

如果这里失败,先不要启动 Qwen。此时问题属于容器、驱动或设备映射,不属于 MTP。

6. 检查模型是否真的带 MTP

python - <<'PY' import json from pathlib import Path model = Path("/models/Qwen3.5-35B-A3B") config = json.loads((model / "config.json").read_text()) print("mtp_num_hidden_layers =", config.get("mtp_num_hidden_layers")) index = json.loads( next(model.glob("*.safetensors.index.json")).read_text() ) names = [x for x in index["weight_map"] if ".mtp." in x] print("MTP tensor count =", len(names)) for name in names[:10]: print(name) PY

应当看到:

mtp_num_hidden_layers = 1 MTP tensor count = 非零 model.language_model.mtp.layers.0....

7. 启动 MTP 推理

仍在容器内:

export ASCEND_RT_VISIBLE_DEVICES=0,1,2,3 export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True export HCCL_BUFFSIZE=512 export OMP_PROC_BIND=false export OMP_NUM_THREADS=1 export TASK_QUEUE_ENABLE=1 vllm serve /models/Qwen3.5-35B-A3B \ --host 0.0.0.0 \ --port 8000 \ --served-model-name qwen35-mtp \ --tensor-parallel-size 4 \ --distributed-executor-backend mp \ --max-model-len 4096 \ --max-num-seqs 16 \ --max-num-batched-tokens 4096 \ --gpu-memory-utilization 0.90 \ --mm-processor-cache-gb 0 \ --no-enable-prefix-caching \ --trust-remote-code \ --speculative_config \ '{"method":"qwen3_5_mtp","num_speculative_tokens":3,"enforce_eager":true}' \ --compilation-config \ '{"cudagraph_mode":"FULL_DECODE_ONLY"}' \ 2>&1 | tee /logs/qwen35_mtp.log

不要在这个官方容器里重新 pip install vllm、torch 或 torch-npu,否则"匹配容器"的意义就没了。

启动成功后,用持续请求触发统计,然后检查:

grep "SpecDecoding metrics" /data/vllm-logs/qwen35_mtp.log | tail -20

判定成功的关键不是服务能生成文字,而是:

Drafted > 0 Accepted > 0 Mean acceptance length > 1.00 Avg Draft acceptance rate > 0%

如果这个官方容器加官方 BF16 权重能接受,而 VERL 中仍为零,就能基本锁定为 VERL 动态更新主模型,却没有正确更新 MTP drafter 权重

参考:​编辑官方安装文档​编辑镜像下载 FAQCANN 9.0.0 版本说明

复制代码
export ASCEND_RT_VISIBLE_DEVICES=0,1,2,3
export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True
export HCCL_BUFFSIZE=512
export OMP_PROC_BIND=false
export OMP_NUM_THREADS=1
export TASK_QUEUE_ENABLE=1

vllm serve /models/Qwen3.5-35B-A3B \
  --host 0.0.0.0 \
  --port 8000 \
  --served-model-name qwen35-mtp \
  --tensor-parallel-size 4 \
  --distributed-executor-backend mp \
  --max-model-len 4096 \
  --max-num-seqs 16 \
  --max-num-batched-tokens 4096 \
  --gpu-memory-utilization 0.90 \
  --mm-processor-cache-gb 0 \
  --no-enable-prefix-caching \
  --trust-remote-code \
  --speculative_config \
  '{"method":"qwen3_5_mtp","num_speculative_tokens":3,"enforce_eager":true}' \
  --compilation-config \
  '{"cudagraph_mode":"FULL_DECODE_ONLY"}' \
  2>&1 | tee /logs/qwen35_mtp.log

不用依赖 pgrep 找模型。你这个脚本和日志已经把路径确认出来了:

/efs_rl/weights/Qwen3.5-35B-A3B

脚本中:

HF_MODEL_PATH=${HF_MODEL_PATH:-"/efs_rl/weights/Qwen3.5-35B-A3B"} actor_rollout_ref.model.path=${HF_MODEL_PATH}

含义是:

如果外部设置过 HF_MODEL_PATH → 使用外部值 否则 → 使用 /efs_rl/weights/Qwen3.5-35B-A3B

日志也明确记录:

'path': '/efs_rl/weights/Qwen3.5-35B-A3B' Loading from /efs_rl/weights/Qwen3.5-35B-A3B

为什么 pgrep 是空的

VERL 启动的 vLLM 通常是 Ray 管理的内嵌 Worker:

VERL TaskRunner └── Ray Worker └── vLLMHttpServer └── vLLM Engine

它不一定以 vllm serve 或 api_server 出现在命令行里。训练停止以后,这些临时 Worker 也会退出。

因此:

pgrep -af "vllm|api_server"

为空只说明现在没有相关服务运行,不代表模型不存在。

第一步:确认当前容器能看到模型

在准备启动 vLLM 的容器中执行:

export MODEL=/efs_rl/weights/Qwen3.5-35B-A3B ls -ld "$MODEL" ls -lh "$MODEL/config.json" ls -lh "$MODEL" | head -30 du -sh "$MODEL"

正常应该能看到:

config.json tokenizer.json 或 tokenizer_config.json model-00001-of-xxxxx.safetensors model.safetensors.index.json

如果提示:

No such file or directory

说明当前容器没有挂载 /efs_rl。需要退出容器,在宿主机检查:

ls -ld /efs_rl/weights/Qwen3.5-35B-A3B docker inspect 容器名 --format '{``{json .Mounts}}' | python -m json.tool

重新启动容器时增加:

-v /efs_rl:/efs_rl

第二步:检查是不是带 MTP 的原始权重

python - <<'PY' import glob import json import os root = os.environ["MODEL"] with open(root + "/config.json") as f: config = json.load(f) text_config = config.get("text_config", {}) mtp_layers = config.get( "mtp_num_hidden_layers", text_config.get("mtp_num_hidden_layers") ) print("模型目录:", root) print("model_type:", config.get("model_type")) print("mtp_num_hidden_layers:", mtp_layers) indexes = glob.glob(root + "/*.safetensors.index.json") if not indexes: raise RuntimeError("没有找到 *.safetensors.index.json") with open(indexes[0]) as f: weight_map = json.load(f)["weight_map"] mtp_names = [name for name in weight_map if ".mtp." in name] print("总 tensor 数:", len(weight_map)) print("MTP tensor 数:", len(mtp_names)) print("前 10 个 MTP tensor:") for name in mtp_names[:10]: print(" ", name) PY

期望结果:

mtp_num_hidden_layers: 1 MTP tensor 数: 大于 0 model.language_model.mtp.layers.0....

如果 MTP tensor 数为零,这份权重只能跑普通推理,不能用它验证 MTP。

第三步:确认当前 vLLM 支持专用 MTP

复制代码
python - <<'PY'
from importlib.metadata import version

print("vllm:", version("vllm"))
print("vllm-ascend:", version("vllm-ascend"))

from vllm.model_executor.models.qwen3_5_mtp import Qwen3_5MoeMTP
print("Qwen3_5MoeMTP:", Qwen3_5MoeMTP)
print("检查通过")
PY

如果这里 ImportError,就不要继续启动,因为当前容器缺少 Qwen3.5 MTP 实现。

终端 A:启动独立 vLLM

先确认 VERL 已经停掉,并检查卡空闲:

npu-smi info

然后启动:

复制代码
(
export ASCEND_RT_VISIBLE_DEVICES=0,1,2,3
export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True
export HCCL_BUFFSIZE=512
export OMP_PROC_BIND=false
export OMP_NUM_THREADS=1
export TASK_QUEUE_ENABLE=1

vllm serve /efs_rl/weights/Qwen3.5-35B-A3B \
  --host 0.0.0.0 \
  --port 8000 \
  --served-model-name qwen35-mtp \
  --tensor-parallel-size 4 \
  --distributed-executor-backend mp \
  --max-model-len 4096 \
  --max-num-seqs 16 \
  --max-num-batched-tokens 4096 \
  --gpu-memory-utilization 0.90 \
  --mm-processor-cache-gb 0 \
  --no-enable-prefix-caching \
  --trust-remote-code \
  --speculative-config \
  '{"method":"qwen3_5_mtp","num_speculative_tokens":3,"enforce_eager":true}' \
  --compilation-config \
  '{"cudagraph_mode":"FULL_DECODE_ONLY"}' \
  2>&1 | tee /tmp/qwen35_mtp.log
)

等待出现:

Application startup complete Uvicorn running on http://0.0.0.0:8000

这个终端不能关。

终端 B:发请求

重新进入容器,先检查服务:

curl -s http://127.0.0.1:8000/health curl -s http://127.0.0.1:8000/v1/models

持续发送 16 个并发请求:

复制代码
python - <<'PY'
import json
import urllib.request
from concurrent.futures import ThreadPoolExecutor, as_completed

URL = "http://127.0.0.1:8000/v1/chat/completions"

prompts = [
    "详细解释大语言模型的训练和推理过程。",
    "写一篇关于人工智能未来发展的文章。",
    "用通俗语言解释量子计算的基本原理。",
    "介绍新能源技术未来十年的发展方向。",
]

def send(i):
    body = {
        "model": "qwen35-mtp",
        "messages": [{
            "role": "user",
            "content": prompts[i % len(prompts)]
        }],
        "temperature": 0,
        "max_tokens": 256,
        "ignore_eos": True,
        "stream": False
    }

    request = urllib.request.Request(
        URL,
        data=json.dumps(body).encode(),
        headers={"Content-Type": "application/json"}
    )

    with urllib.request.urlopen(request, timeout=600) as response:
        result = json.load(response)
        return i, result.get("usage", {}).get("completion_tokens")

with ThreadPoolExecutor(max_workers=4) as pool:
    jobs = [pool.submit(send, i) for i in range(16)]
    for job in as_completed(jobs):
        i, tokens = job.result()
        print(f"请求 {i:02d} 完成,生成 {tokens} tokens")
PY

终端 C:看接受率

复制代码
tail -f /tmp/qwen35_mtp.log |
grep --line-buffered "SpecDecoding metrics"

或者请求结束后:

复制代码
grep "SpecDecoding metrics" /tmp/qwen35_mtp.log | tail -20

最终判断:

复制代码
Drafted > 0 且 Accepted > 0:单独 vLLM MTP 跑通
Drafted > 0 但 Accepted = 0:权重加载或 MTP 实现仍有问题
没有 SpecDecoding metrics:MTP 没启用或统计没打开
服务起不来:先看 /tmp/qwen35_mtp.log 的第一个 ERROR

所以你现在最先执行的是前面的模型目录检查、MTP tensor 检查、Qwen3_5MoeMTP import 检查。这三项通过后,才启动服务。

相关推荐
大模型推理18 小时前
《Nano-vLLM 源码解读》第 16 篇 · Linear 投影
vllm
嘉陵妹妹1 天前
VLLM auto DL环境配置
vllm
an86950011 天前
【无标题】
vllm
蔡不菜和他的uU们2 天前
vLLM实践之个人AI基建——云端vLLM+SSH Tunnel+本地Cherry Studio
人工智能·ssh·vllm
likerhood3 天前
服务器使用 vLLM 部署 Qwen2.5-Coder-7B-CL 笔记
服务器·笔记·vllm
一只努力的微服务3 天前
vLLM vs SGLang 深度技术对比
vllm·sglang
做个文艺程序员4 天前
第08篇:K8s 部署 AI 大模型推理服务:GPU 调度 × vLLM × Java 客户端集成——从 0 到生产的完整方案
人工智能·kubernetes·vllm
reset20215 天前
vllm性能优化
性能优化·vllm
我叫张土豆5 天前
V100 显卡部署 Qwen3-ASR-1.7B 语音识别模型(vLLM + Docker 完整教程)
docker·语音识别·vllm