vLLM 服务启动慢排查:NAS 模型目录、Docker 镜像和 GPU Runtime

这篇记录一次很典型的 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 时,服务启动慢通常不是单点问题。按下面顺序排更清楚:

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

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

相关推荐
古怪今人5 小时前
WSL和Hyper-V Ubuntu安装docker Docker安装Reids、MySQL、PostgreSQL和RabbitMQ
运维·docker·容器
周易宅5 小时前
Docker MySQL 8.0.45 性能优化配置文档
mysql·docker·性能优化
ん贤5 小时前
Kubernetes 核心资源对象与应用编排基础
云原生·容器·kubernetes
LZZ and MYY5 小时前
将Virtual PLCnext 部署在PVE的LXC容器
云原生·容器·kubernetes
仙柒41518 小时前
Docker存储原理
运维·docker·容器
快乐的哈士奇21 小时前
LangFuse 自托管实战:选型理由、Docker 部署与常用配置全解析
运维·人工智能·docker·容器
weixin_449290011 天前
Docker + MySQL 在 Windows 11 上的本地安装部署文档
mysql·docker·容器
Ysn07191 天前
中文乱码:在 Docker 容器中设置中文语言环境
运维·python·docker·容器