4、vLLM镜像构建:编写Dockerfile、基于PyTorch基础镜像、集成vLLM库、多阶段构建优化
镜像构建这件事,说白了就是给模型推理服务搭一个「家」。这个家要干净、要够大、还要跑得快。我刚开始做推理服务时,镜像动不动就几个G,拉取一次等半天,后来才意识到——多阶段构建才是正解。
今天咱们就手把手把vLLM的镜像搭起来。我会把踩过的坑、试过的方案都摊开来讲。
4.1 为什么选PyTorch基础镜像?
vLLM底层依赖PyTorch的CUDA算子。你想想看,如果从Ubuntu裸镜像开始装,光装CUDA驱动、cuDNN、PyTorch就得折腾半天。而且版本还容易打架。
我个人习惯直接用官方的 pytorch/pytorch:2.1.0-cuda12.1-cudnn8-runtime 作为基础镜像。原因很简单:
- CUDA和cuDNN已经配好了,不用自己折腾
- PyTorch版本和vLLM的兼容性经过验证
- runtime版本比devel版本小很多,只保留运行时需要的库
4.2 编写Dockerfile:从零到一
先看一个最基础的版本。嗯,这个版本能跑,但不够好。咱们一步步优化。
# 第一阶段:构建阶段
FROM pytorch/pytorch:2.1.0-cuda12.1-cudnn8-runtime AS builder
WORKDIR /app
# 安装编译依赖
RUN apt-get update && apt-get install -y \
git \
build-essential \
&& rm -rf /var/lib/apt/lists/*
# 安装vLLM
RUN pip install --no-cache-dir vllm==0.4.0
# 第二阶段:运行阶段
FROM pytorch/pytorch:2.1.0-cuda12.1-cudnn8-runtime
WORKDIR /app
# 从构建阶段复制vLLM
COPY --from=builder /usr/local/lib/python3.10/dist-packages /usr/local/lib/python3.10/dist-packages
# 暴露端口
EXPOSE 8000
# 默认启动命令
CMD ["python", "-m", "vllm.entrypoints.openai.api_server", "--model", "/model", "--port", "8000"]
这个Dockerfile能工作,但有几个问题。我在项目中遇到过:
- 构建阶段和运行阶段用了同一个基础镜像,体积没降下来多少
- 没有指定Python版本,万一基础镜像升级了Python版本,复制过去的包可能不兼容
- 没有做缓存优化,每次构建都要重新下载vLLM
4.3 多阶段构建优化
多阶段构建的核心思想是:编译和运行分离。编译阶段可以装各种工具,运行阶段只保留最小依赖。
我优化后的版本长这样:
# ========== 第一阶段:编译构建 ==========
FROM pytorch/pytorch:2.1.0-cuda12.1-cudnn8-devel AS builder
WORKDIR /build
# 安装编译工具
RUN apt-get update && apt-get install -y \
git \
cmake \
build-essential \
&& rm -rf /var/lib/apt/lists/*
# 先安装依赖,利用Docker缓存层
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 安装vLLM(从源码编译,可以针对当前GPU架构优化)
RUN pip install --no-cache-dir vllm==0.4.0
# ========== 第二阶段:运行镜像 ==========
FROM pytorch/pytorch:2.1.0-cuda12.1-cudnn8-runtime
WORKDIR /app
# 设置环境变量
ENV PYTHONUNBUFFERED=1 \
CUDA_VISIBLE_DEVICES=all \
VLLM_USE_PRECOMPILED=1
# 只复制需要的Python包
COPY --from=builder /usr/local/lib/python3.10/site-packages /usr/local/lib/python3.10/site-packages
# 复制启动脚本
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
EXPOSE 8000
ENTRYPOINT ["/entrypoint.sh"]
CMD ["--model", "/model", "--port", "8000"]
- 构建阶段用devel镜像(有编译工具),运行阶段用runtime镜像(体积小)
- 先复制requirements.txt再安装,利用Docker缓存层加速
- 使用entrypoint.sh作为入口,方便扩展启动参数
- 设置环境变量,避免运行时出现意外
4.4 集成vLLM库的注意事项
vLLM的安装其实有坑。我踩过最深的坑是——CUDA版本不匹配。
vLLM 0.4.0要求CUDA 12.1以上。如果你用CUDA 11.8的基础镜像,编译时会报错。所以一定要确认基础镜像的CUDA版本。
另一个常见问题是Flash Attention的兼容性。vLLM依赖Flash Attention来加速推理,但Flash Attention对GPU架构有要求:
| GPU架构 | 计算能力 | 是否支持Flash Attention |
|---|---|---|
| V100 (Volta) | 7.0 | 不支持 |
| A100 (Ampere) | 8.0 | 支持 |
| H100 (Hopper) | 9.0 | 支持 |
| RTX 4090 | 8.9 | 支持 |
VLLM_USE_FLASH_ATTN=0 来禁用Flash Attention,vLLM会自动回退到xformers实现。
4.5 构建与验证
镜像写好了,怎么验证它能不能用?我一般会做三步检查:
- 构建检查:
docker build -t vllm-server:latest .确保能成功构建 - 启动检查:
docker run --gpus all vllm-server:latest --model facebook/opt-125m看能不能正常加载模型 - 推理检查:用curl发送一个请求,看能不能正常返回结果
我曾经遇到过一个问题:镜像构建成功了,但启动时一直报 CUDA error: out of memory。排查了半天,发现是基础镜像的CUDA版本和宿主机的驱动不匹配。后来我统一用CUDA 12.1的镜像,配合驱动版本 >= 535,问题就解决了。
4.6 知识体系总览
下面这张图把镜像构建的核心逻辑串起来了。你可以看到,整个流程就是「选基础镜像 → 编译vLLM → 多阶段裁剪 → 验证上线」。
- 单阶段构建(devel镜像):约 8.5 GB
- 多阶段构建(runtime镜像):约 3.2 GB
- 优化后体积减少:62%
镜像构建这块,说白了就是「空间换时间」和「时间换空间」的平衡。多阶段构建牺牲了一点构建时的复杂度,换来了运行时镜像的精简。我个人觉得,这笔买卖很划算。
好了,镜像已经就绪。接下来就是把它跑起来,让模型真正开始推理。