这篇记录一次很典型的 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 |
先把基础信息记录下来,后面排查才不会混:
bash
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 启动慢不一定有明确报错。常见表现有几类:
text
容器状态为 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。先把镜像拉取和服务启动拆开。
bash
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 能正常导入:
bash
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)的同名入口做拉取验证:
bash
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:
bash
docker image inspect vllm/vllm-openai:latest --format '{{index .RepoDigests 0}}'
把输出写进发布记录,避免下一台机器拉到不同内容。
4. 检查 NAS 挂载是否真的可用
宿主机能 ls /mnt/models,不代表容器内 vLLM 能读到模型。先在宿主机检查挂载:
bash
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
重点看模型目录是否完整。至少要能看到类似文件:
text
config.json
tokenizer.json
tokenizer_config.json
generation_config.json
model-00001-of-000xx.safetensors
model.safetensors.index.json
再用 vLLM 镜像进容器确认路径:
bash
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'
检查关键文件是否可读:
bash
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。
宿主机上看属主和权限:
bash
stat /mnt/models
stat /mnt/models/Qwen3-32B
ls -ln /mnt/models/Qwen3-32B | head
容器内确认运行身份:
bash
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,先不要粗暴改成全局可写。更稳的方式是:
bash
sudo chgrp -R inference /mnt/models/Qwen3-32B
sudo chmod -R g+rX /mnt/models/Qwen3-32B
生产环境里,模型目录通常建议只读挂载:
bash
-v /mnt/models:/models:ro
vLLM、Hugging Face cache、日志、临时文件可以放到本机 SSD:
bash
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 读速慢时,日志看起来像卡在加载模型。
先粗略看大文件顺序读:
bash
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
把文件名替换成真实存在的分片。再看目录遍历和小文件读取:
bash
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,可以做一轮只读测试:
bash
fio --name=model-read \
--directory=/mnt/models/Qwen3-32B \
--rw=read \
--bs=4m \
--size=4g \
--numjobs=1 \
--runtime=60 \
--time_based \
--group_reporting
NFS 环境可以查看挂载参数:
bash
nfsstat -m
SMB/CIFS 环境看挂载命令和日志:
bash
mount | grep cifs
dmesg -T | grep -i cifs | tail -50
如果 NAS 读速明显不稳定,常见处理是:模型权重仍放 NAS 做统一管理,启动前同步到本机 NVMe;或者把高频服务使用的模型放在本机 SSD,只把归档模型留在 NAS。
7. 检查 NVIDIA Runtime
宿主机能看到 GPU,不代表容器能看到 GPU。先看宿主机:
bash
nvidia-smi
lsmod | grep nvidia
再看容器内:
bash
docker run --rm --gpus all nvidia/cuda:12.4.1-runtime-ubuntu22.04 nvidia-smi
如果上面这个命令失败,再看 Docker runtime:
bash
docker info | grep -i runtime
nvidia-container-cli info
常见修复思路:
bash
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker
docker info | grep -i runtime
也可以检查 /etc/docker/daemon.json:
json
{
"runtimes": {
"nvidia": {
"path": "nvidia-container-runtime",
"runtimeArgs": []
}
}
}
再次验证:
bash
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
先用最小命令启动,不要一开始就接网关、鉴权和复杂编排。
bash
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
看日志:
bash
docker logs -f --tail=200 vllm-qwen3
另开一个终端做健康检查:
bash
curl -fsS http://127.0.0.1:8000/health
curl -s http://127.0.0.1:8000/v1/models | jq .
再做一次最小请求:
bash
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。示例:
yaml
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
启动和验证:
bash
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 时,服务启动慢通常不是单点问题。按下面顺序排更清楚:
- 先验证 Docker 镜像能稳定拉取,版本和 digest 一致。
- 再确认 NAS 挂载在宿主机和容器内路径一致。
- 检查模型目录权限、文件完整性和 NAS 读速。
- 用最小 CUDA 容器验证 NVIDIA runtime。
- 最后用
/health、/v1/models和一次最小请求判断 vLLM 是否 ready。
这几层拆开以后,镜像问题、存储问题、GPU runtime 问题和 vLLM 参数问题就不会混在一起,后续扩节点、换模型、接网关也更容易复用。