去年做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命令。虽然构建镜像的过程需要花一些时间,但一旦搞定了,后续的部署、迁移、扩缩容都会变得非常轻松。