去年做AI推理服务迁移的时候,最大的痛点不是模型本身,而是环境。CUDA版本、cuDNN版本、Python版本、PyTorch版本,四个东西排列组合出来的环境问题能把人逼疯。“在我机器上能跑"这句话在AI服务部署面前毫无意义;

Docker容器化是解决这个问题的标准方案。把所有依赖打包进一个镜像,任何地方docker run一下就能起来。今天把整个流程走一遍,从Dockerfile编写到生产环境优化;

推理框架选型

先说结论:高并发场景选vLLM,简单场景选Ollama,需要和HuggingFace生态深度集成选TGI;

vLLM的核心优势是PagedAttention——借鉴操作系统的虚拟内存分页思想管理KV Cache,显存利用率比传统方案高很多。实际测试中,在相同硬件上vLLM的吞吐量通常是HuggingFace Transformers的3-5倍,在高并发场景下差距更大。

编写Dockerfile

FROM nvidia/cuda:12.4.1-devel-ubuntu22.04

ENV DEBIAN_FRONTEND=noninteractive
ENV PYTHONUNBUFFERED=1

# 系统依赖
RUN apt-get update && apt-get install -y \
    python3.11 python3.11-venv python3-pip \
    git curl && \
    rm -rf /var/lib/apt/lists/*

RUN ln -sf /usr/bin/python3.11 /usr/bin/python && \
    ln -sf /usr/bin/pip3 /usr/bin/pip

# Python依赖——注意版本锁定
RUN pip install --no-cache-dir \
    vllm==0.8.5 \
    transformers==4.51.0 \
    accelerate==1.6.0 \
    huggingface-hub==0.30.0

WORKDIR /app
COPY entrypoint.sh /app/
RUN chmod +x /app/entrypoint.sh

EXPOSE 8000
ENTRYPOINT ["/app/entrypoint.sh"]

几个注意点。基础镜像用nvidia/cuda而不是pytorch/pytorch,因为后者体积太大而且包含很多用不到的东西。Python依赖要锁定版本号,不然下次构建可能因为某个包升级了导致整个服务挂掉。ENTRYPOINT用脚本而不是直接写命令,方便通过环境变量控制启动参数。

启动脚本

#!/bin/bash
set -e

MODEL_NAME=${MODEL_NAME:-"Qwen/Qwen2.5-7B-Instruct"}
TENSOR_PARALLEL=${TENSOR_PARALLEL:-1}
MAX_MODEL_LEN=${MAX_MODEL_LEN:-4096}
GPU_MEMORY=${GPU_MEMORY_UTILIZATION:-0.9}

echo "Starting vLLM: model=$MODEL_NAME tp=$TENSOR_PARALLEL max_len=$MAX_MODEL_LEN"

python -m vllm.entrypoints.openai.api_server \
    --model "$MODEL_NAME" \
    --tensor-parallel-size $TENSOR_PARALLEL \
    --max-model-len $MAX_MODEL_LEN \
    --gpu-memory-utilization $GPU_MEMORY \
    --host 0.0.0.0 \
    --port 8000 \
    --trust-remote-code

docker-compose.yml

version: "3.8"
services:
  vllm:
    build: .
    container_name: vllm-inference
    restart: unless-stopped
    environment:
      - MODEL_NAME=Qwen/Qwen2.5-7B-Instruct
      - TENSOR_PARALLEL=1
      - MAX_MODEL_LEN=8192
      - GPU_MEMORY_UTILIZATION=0.85
      - HF_TOKEN=${HF_TOKEN}
    ports:
      - "8000:8000"
    volumes:
      - model-cache:/root/.cache/huggingface
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: 1
              capabilities: [gpu]
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 10s
      retries: 5
      start_period: 300s
volumes:
  model-cache:

model-cache用named volume挂载模型缓存目录,这样重建容器不用重新下载模型。start_period设300秒是因为7B模型加载大约需要2-3分钟,healthcheck在这段时间内不计入失败次数。

构建和运行

docker compose build
docker compose up -d
docker compose logs -f vllm  # 观察模型加载进度

看到"Uvicorn running on http://0.0.0.0:8000"就表示服务启动成功了。

测试

curl http://localhost:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "Qwen/Qwen2.5-7B-Instruct",
    "messages": [{"role": "user", "content": "用Python写一个快速排序"}],
    "temperature": 0.7,
    "max_tokens": 1024
  }'

生产优化

多实例部署用Nginx做负载均衡,每个实例绑定一张GPU。Nginx配置里proxy_read_timeout要设长一些(300秒),大模型推理的响应时间可能很长;

监控用Prometheus采集vLLM内置的metrics端点。关键指标:请求延迟P50/P95/P99、GPU显存利用率、请求队列长度、Token生成速度;

镜像拉取加速在国内是个大问题。建议用阿里云或腾讯云的容器镜像仓库做缓存代理,或者把构建好的镜像推到私有仓库,生产环境从私有仓库拉取。

写在最后

Docker化部署AI服务,本质上就是把"运维复杂度"封装到镜像里,让部署变成一条docker compose up命令。虽然构建镜像的过程需要花一些时间,但一旦搞定了,后续的部署、迁移、扩缩容都会变得非常轻松。