这篇记录一次很典型的 vLLM 启动慢排查:模型文件放在 NAS 上,GPU 服务器只负责跑推理容器。docker pull 能完成,容器也能 running,但 OpenAI-compatible API 一直没有 ready,网关侧看到的只是模型服务不可用。

现在很多团队在做内部知识库、代码助手、RAG 网关或 Agent 服务时,都会把 Qwen、DeepSeek、Llama 一类模型集中放到 NAS 或共享存储里。这样方便统一管理模型版本,也能避免每台 GPU 服务器重复存一份大模型。问题是,启动链路被拆成了几段:Docker 镜像、NAS 挂载、目录权限、GPU Runtime、vLLM 健康检查。任何一段慢或不一致,最后都可能表现为“vLLM 启动卡住”。

下面按可复制的排查顺序整理。

1. 环境说明

示例环境如下,实际版本按自己的机器替换:

组件 示例
GPU 服务器 Ubuntu 22.04 / Debian 12 / Rocky Linux 9
容器运行时 Docker Engine + NVIDIA Container Toolkit
GPU A10 / A100 / L20 / L40S / 4090 等
模型目录 NAS 挂载到宿主机 /mnt/models
vLLM 镜像 vllm/vllm-openai:latest 或固定版本标签
服务端口 8000

先把基础信息记录下来,后面排查才不会混:

uname -a
cat /etc/os-release

docker version
docker info | sed -n '1,80p'

nvidia-smi
nvidia-smi --query-gpu=name,driver_version,memory.total --format=csv

findmnt -T /mnt/models
df -hT /mnt/models

如果有多台 GPU 服务器,建议每台都跑一遍,把输出贴到同一个排查记录里。很多“某台能起、某台起不来”的问题,最后都是镜像 digest、NAS 挂载参数或 NVIDIA runtime 配置不一致。

2. 常见错误现象

vLLM 启动慢不一定有明确报错。常见表现有几类:

容器状态为 running,但 /health 长时间不通
日志停在 Loading safetensors checkpoint shards
日志停在 tokenizer / config / model path 相关位置
容器里报 Permission denied 或 No such file or directory
宿主机 nvidia-smi 正常,容器内报 No CUDA GPUs are available
网关反复重试,外部只看到 502 / upstream timeout

先不要把这些都归因到 vLLM 参数。更稳的顺序是把启动拆成五层:

排查层 要确认什么
Docker 镜像 镜像能拉取、版本一致、基础 CUDA/Python 环境可用
NAS 挂载 宿主机挂载正常,容器内能看到同一个模型目录
权限和读速 容器用户可读,模型文件完整,NAS 读延迟可接受
GPU Runtime 容器内能看到 GPU,驱动和 CUDA runtime 不冲突
vLLM ready /health/v1/models、一次最小请求都能返回

3. Docker 镜像先单独验证

不要一上来就 docker compose up -d。先把镜像拉取和服务启动拆开。

docker pull vllm/vllm-openai:latest
docker image inspect vllm/vllm-openai:latest --format '{{.Id}} {{.Size}}'
docker image inspect vllm/vllm-openai:latest --format '{{json .RepoDigests}}'

确认镜像里 Python、PyTorch 和 vLLM 能正常导入:

docker run --rm --entrypoint python3 vllm/vllm-openai:latest -V

docker run --rm --entrypoint python3 vllm/vllm-openai:latest -c \
'import torch, vllm; print("torch", torch.__version__); print("cuda", torch.version.cuda); print("vllm", vllm.__version__)'

如果 GPU 服务器访问 Docker Hub 不稳定,可以在这一层用毫秒镜像(1ms.run)的同名入口做拉取验证:

docker pull docker.1ms.run/vllm/vllm-openai:latest
docker pull docker.1ms.run/nvidia/cuda:12.4.1-runtime-ubuntu22.04

这一步只解决镜像获取和环境一致性问题。镜像能拉下来,不代表 NAS 目录、GPU runtime 和 vLLM ready 都没问题。

多节点环境建议固定版本或 digest,不要长期依赖 latest

docker image inspect vllm/vllm-openai:latest --format '{{index .RepoDigests 0}}'

把输出写进发布记录,避免下一台机器拉到不同内容。

4. 检查 NAS 挂载是否真的可用

宿主机能 ls /mnt/models,不代表容器内 vLLM 能读到模型。先在宿主机检查挂载:

findmnt -T /mnt/models
mount | grep /mnt/models
df -hT /mnt/models

ls -lah /mnt/models
ls -lah /mnt/models/Qwen3-32B
find /mnt/models/Qwen3-32B -maxdepth 2 -type f | head -50

重点看模型目录是否完整。至少要能看到类似文件:

config.json
tokenizer.json
tokenizer_config.json
generation_config.json
model-00001-of-000xx.safetensors
model.safetensors.index.json

再用 vLLM 镜像进容器确认路径:

docker run --rm \
  -v /mnt/models:/models:ro \
  --entrypoint bash \
  vllm/vllm-openai:latest \
  -lc 'id; ls -lah /models; ls -lah /models/Qwen3-32B | head'

检查关键文件是否可读:

docker run --rm \
  -v /mnt/models:/models:ro \
  --entrypoint bash \
  vllm/vllm-openai:latest \
  -lc 'test -r /models/Qwen3-32B/config.json && echo config-ok'

docker run --rm \
  -v /mnt/models:/models:ro \
  --entrypoint bash \
  vllm/vllm-openai:latest \
  -lc 'find /models/Qwen3-32B -maxdepth 1 -name "*.safetensors" | head'

如果容器内看不到目录,先修 Docker volume、NAS 挂载点或路径映射。不要让 vLLM 带着错误路径启动,它只会在日志里表现成模型加载失败或长时间等待。

5. 检查权限、UID 和只读挂载

NAS 权限问题很常见,尤其是模型从另一台机器同步过来,文件属主可能是旧 UID。

宿主机上看属主和权限:

stat /mnt/models
stat /mnt/models/Qwen3-32B
ls -ln /mnt/models/Qwen3-32B | head

容器内确认运行身份:

docker run --rm \
  -v /mnt/models:/models:ro \
  --entrypoint bash \
  vllm/vllm-openai:latest \
  -lc 'id; test -r /models/Qwen3-32B/config.json && echo readable'

如果有 Permission denied,先不要粗暴改成全局可写。更稳的方式是:

sudo chgrp -R inference /mnt/models/Qwen3-32B
sudo chmod -R g+rX /mnt/models/Qwen3-32B

生产环境里,模型目录通常建议只读挂载:

-v /mnt/models:/models:ro

vLLM、Hugging Face cache、日志、临时文件可以放到本机 SSD:

mkdir -p /data/vllm-cache/hf /data/vllm-cache/torch

docker run --rm \
  -v /mnt/models:/models:ro \
  -v /data/vllm-cache:/cache \
  -e HF_HOME=/cache/hf \
  -e TORCH_HOME=/cache/torch \
  --entrypoint bash \
  vllm/vllm-openai:latest \
  -lc 'echo $HF_HOME; echo $TORCH_HOME'

这样能避免推理服务把缓存写回 NAS 模型目录,后续排查也更清楚。

6. 检查 NAS 读速和小文件延迟

大模型目录里不只有大权重文件,还有 tokenizer、配置、index、分片元信息。NAS 读速慢时,日志看起来像卡在加载模型。

先粗略看大文件顺序读:

ls -lh /mnt/models/Qwen3-32B/*.safetensors | head

time dd if=/mnt/models/Qwen3-32B/model-00001-of-000xx.safetensors \
  of=/dev/null bs=64M count=16 status=progress

把文件名替换成真实存在的分片。再看目录遍历和小文件读取:

time find /mnt/models/Qwen3-32B -type f | wc -l

time bash -lc 'for f in /mnt/models/Qwen3-32B/*.json; do head -c 1024 "$f" >/dev/null; done'

如果安装了 fio,可以做一轮只读测试:

fio --name=model-read \
  --directory=/mnt/models/Qwen3-32B \
  --rw=read \
  --bs=4m \
  --size=4g \
  --numjobs=1 \
  --runtime=60 \
  --time_based \
  --group_reporting

NFS 环境可以查看挂载参数:

nfsstat -m

SMB/CIFS 环境看挂载命令和日志:

mount | grep cifs
dmesg -T | grep -i cifs | tail -50

如果 NAS 读速明显不稳定,常见处理是:模型权重仍放 NAS 做统一管理,启动前同步到本机 NVMe;或者把高频服务使用的模型放在本机 SSD,只把归档模型留在 NAS。

7. 检查 NVIDIA Runtime

宿主机能看到 GPU,不代表容器能看到 GPU。先看宿主机:

nvidia-smi
lsmod | grep nvidia

再看容器内:

docker run --rm --gpus all nvidia/cuda:12.4.1-runtime-ubuntu22.04 nvidia-smi

如果上面这个命令失败,再看 Docker runtime:

docker info | grep -i runtime
nvidia-container-cli info

常见修复思路:

sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker
docker info | grep -i runtime

也可以检查 /etc/docker/daemon.json

{
  "runtimes": {
    "nvidia": {
      "path": "nvidia-container-runtime",
      "runtimeArgs": []
    }
  }
}

再次验证:

docker run --rm --gpus all \
  --entrypoint python3 \
  vllm/vllm-openai:latest \
  -c 'import torch; print(torch.cuda.is_available()); print(torch.cuda.device_count())'

如果容器内 GPU 可见,再去调 vLLM 的 --tensor-parallel-size--gpu-memory-utilization--max-model-len。如果容器内 GPU 不可见,调模型参数没有意义。

8. 启动 vLLM 并检查 health

先用最小命令启动,不要一开始就接网关、鉴权和复杂编排。

docker rm -f vllm-qwen3 2>/dev/null || true

docker run -d \
  --name vllm-qwen3 \
  --gpus all \
  --ipc=host \
  -p 8000:8000 \
  -v /mnt/models:/models:ro \
  -v /data/vllm-cache:/cache \
  -e HF_HOME=/cache/hf \
  -e TORCH_HOME=/cache/torch \
  vllm/vllm-openai:latest \
  --model /models/Qwen3-32B \
  --served-model-name qwen3 \
  --host 0.0.0.0 \
  --port 8000 \
  --gpu-memory-utilization 0.90

看日志:

docker logs -f --tail=200 vllm-qwen3

另开一个终端做健康检查:

curl -fsS http://127.0.0.1:8000/health
curl -s http://127.0.0.1:8000/v1/models | jq .

再做一次最小请求:

curl -s http://127.0.0.1:8000/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "qwen3",
    "messages": [{"role": "user", "content": "ping"}],
    "max_tokens": 8
  }' | jq .

如果 /health 不通,继续看日志停在哪一步:

日志位置 常见方向
模型路径相关 NAS 路径、volume 映射、目录结构
tokenizer / config 模型文件不完整或权限不可读
CUDA 初始化 NVIDIA runtime、驱动、CUDA 兼容性
显存分配 模型规模、并行参数、上下文长度
API 已启动但网关失败 端口、反代、readiness、网关超时

9. Docker Compose 示例

排查稳定后,再放进 Compose。示例:

services:
  vllm:
    image: vllm/vllm-openai:latest
    container_name: vllm-qwen3
    restart: unless-stopped
    ipc: host
    ports:
      - "8000:8000"
    volumes:
      - /mnt/models:/models:ro
      - /data/vllm-cache:/cache
    environment:
      HF_HOME: /cache/hf
      TORCH_HOME: /cache/torch
      NVIDIA_VISIBLE_DEVICES: all
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: all
              capabilities: [gpu]
    command:
      - --model
      - /models/Qwen3-32B
      - --served-model-name
      - qwen3
      - --host
      - 0.0.0.0
      - --port
      - "8000"
      - --gpu-memory-utilization
      - "0.90"
    healthcheck:
      test: ["CMD-SHELL", "curl -fsS http://127.0.0.1:8000/health || exit 1"]
      interval: 30s
      timeout: 5s
      retries: 10
      start_period: 10m

启动和验证:

docker compose config
docker compose pull
docker compose up -d
docker compose logs -f --tail=200 vllm
docker compose ps
curl -fsS http://127.0.0.1:8000/health

start_period 不要设得太短。大模型冷启动、NAS 读取、显存初始化都需要时间,5 秒失败就重启会把正常加载变成反复重启。

10. FAQ

容器是 running,为什么服务还不可用?

running 只说明进程还活着,不等于模型已加载完成。对调用方来说,要以 /health/v1/models 和一次最小请求为准。

NAS 上能看到模型,为什么 vLLM 还是说找不到?

宿主机路径和容器路径不是一回事。要用 docker run -v /mnt/models:/models:ro ... ls /models 在容器内确认。

镜像拉取成功后,还要查 NAS 吗?

要查。镜像只决定运行环境,模型目录是否完整、权限是否可读、NAS 读速是否稳定,都在镜像层之外。

宿主机 nvidia-smi 正常,为什么容器里没有 GPU?

通常是 NVIDIA Container Toolkit、Docker runtime、--gpus all 或设备可见性配置问题。先用 docker run --rm --gpus all nvidia/cuda:... nvidia-smi 验证容器层。

vLLM 健康检查应该怎么设?

冷启动场景下,start_period 要给足模型加载窗口。可以先用 5 到 10 分钟做基线,再根据模型大小、NAS 读速和 GPU 初始化耗时调整。

模型应该放 NAS 还是本机 SSD?

模型版本多、节点多时,NAS 适合做统一管理;高频在线服务对冷启动敏感时,本机 NVMe 更稳。常见做法是 NAS 做源目录,发布时同步到本机 SSD,vLLM 从本机路径加载。

总结

NAS 放模型、GPU 服务器跑 vLLM 时,服务启动慢通常不是单点问题。按下面顺序排更清楚:

  1. 先验证 Docker 镜像能稳定拉取,版本和 digest 一致。
  2. 再确认 NAS 挂载在宿主机和容器内路径一致。
  3. 检查模型目录权限、文件完整性和 NAS 读速。
  4. 用最小 CUDA 容器验证 NVIDIA runtime。
  5. 最后用 /health/v1/models 和一次最小请求判断 vLLM 是否 ready。

这几层拆开以后,镜像问题、存储问题、GPU runtime 问题和 vLLM 参数问题就不会混在一起,后续扩节点、换模型、接网关也更容易复用。

Logo

免费领 100 小时云算力,进群参与显卡、AI PC 幸运抽奖

更多推荐