Ubuntu 18.04 上装 CUDA 11.8,再往上叠一个 vLLM,这个组合我第一次接到需求时的反应是:机器是别人给的,动不了。老服务器跑着别的训练任务,驱动版本被卡死在一段不能随便升的区间里,而业务侧又希望同一台机器上多开一个推理服务。于是“Ubuntu 18.04 + CUDA 11.8 + vLLM 安装”这件事,就从一个简单的 pip 命令,变成了一个需要先把版本账算清楚、再决定走哪条路的工程问题。
这篇内容写给两类人:一类是手上握着不能重装系统的老机器、必须把 vLLM 跑起来的一线工程师;另一类是刚接触大模型推理部署、想搞清楚“为什么装个包会这么费劲”的同学。我会把三条安装路线(预编译包、源码编译、容器)的取舍讲透,把版本矩阵、编译参数、踩坑排查都摊开写,最后给一份能直接抄的清单。看完你应该能在自己那台 18.04 上,判断出“我这台机器到底该走哪条路”,而不是盲目复制一条 pip install 然后在报错里泡两天。
1. 先把版本账算清楚,别急着敲命令
1.1 三个组件的真实约束关系
很多人在这一步栽跟头,是因为把 CUDA Toolkit 和显卡驱动混为一谈。真正决定你能不能跑某个版本的,不是/usr/local/cuda-*这个目录,而是驱动版本。驱动是向上兼容的:装了 535 的驱动,底下的 11.8 和 12.1 运行时都能跑;但反过来,470 的驱动绝对跑不了 12.x 的运行时。所以“我要用 CUDA 11.8”这句话,本质上翻译过来是“我的驱动版本被限制在了 470 甚至更老,升不上去”。
那 vLLM 这边的约束又是什么?它不像 CUDA Toolkit 那样挑驱动,它挑的是 torch。vLLM 的每个发行版都死死绑住一个 torch 版本,torch 又绑住一个 CUDA 运行时版本。你在 PyPI 上装到的 vLLM 预编译包,从 0.5 版本之后基本是按 CUDA 12.1 编译的,内部链接的是libcudart.so.12。这意味着如果你的环境是纯 CUDA 11.8 且驱动卡在 470,直接pip install vllm之后大概率会看到ImportError: libcudart.so.12: cannot open shared object file,或者更隐蔽的 kernel 加载失败。
而 Ubuntu 18.04 这一层,加的是第三类麻烦:默认 Python 3.6、默认 gcc 7.5、默认 cmake 3.10,glibc 停在 2.27。vLLM 从 0.6 起要求 Python >= 3.9,编译链要求 cmake >= 3.26,这就注定了你不可能“什么都不改直接装”。
下面这张表是我整理的实际约束对照,建议动手前先对照自己机器填一遍:
| 组件 | Ubuntu 18.04 默认值 | vLLM 实际要求 | 需要做的动作 |
|---|---|---|---|
| Python | 3.6.9 | >= 3.9(0.6.x 起) | 源码编译 3.10 或用第三方 PPA |
| gcc / g++ | 7.5 | 建议 >= 8,且 <= 11 | 装 gcc-9 并切换 alternatives |
| cmake | 3.10 | >= 3.26(编译期) | pip 装 cmake wheel |
| glibc | 2.27 | 大部分 wheel 要求 >= 2.17 | 通常够用,个别包要求 2.28 |
| 驱动 | 视机器而定 | CUDA 11.8 需 >= 450.80.02 | 470 系列可用,525+ 更宽裕 |
| 内存 | 视机器而定 | 源码编译建议 >= 16G | 加 swap 或限 MAX_JOBS |
1.2 驱动版本决定了你走哪条路
这是全文最重要的一句话:先看驱动,再谈方案。跑一条nvidia-smi看右上角的Driver Version,结论立刻分叉。
如果驱动是 525 及以上,你其实没必要死磕 11.8。直接装 CUDA 12.1 运行时对应的 torch,然后pip install vllm拿官方预编译包,十几分钟就能收工。11.8 只是这台机器历史上的选择,不是必须继承的负担——前提是这台机器上没有别的框架被钉在 11.8 上。
如果驱动是 470 到 520 之间,那 CUDA 11.8 是你能用的上限,这条路必须走,而且 vLLM 基本只能源码编译。因为官方预编译包是按 12.1 构建的,你硬装下去,pip 会把nvidia-cuda-runtime-cu12这一串包一起拽进来,运行时可能出现“torch 是 cu118、vLLM 的 kernel 是 cu12 编译”的混合状态,症状是能 import、能建模、一跑推理就崩,排查起来极其痛苦。
如果驱动更老(比如 450 刚够线),那 11.8 是硬约束,同时建议直接放弃在这台机器上跑大模型推理,先评估换卡或换机的成本——很多新模型的算子在这些卡上根本没有可用的 kernel。
1.3 三条路线怎么选
我把三条路线的适用条件列成表,你可以直接对号入座:
| 路线 | 适用场景 | 耗时 | 风险点 |
|---|---|---|---|
| 预编译 wheel | 驱动 >= 525,或不介意装 cu121 运行时 | 10-20 分钟 | 与现有 cu118 环境冲突 |
| 源码编译 | 驱动锁死 11.8、必须纯 cu118 | 40-90 分钟 | 编译期 OOM、cmake 版本、toolkit 路径 |
| 容器镜像 | 机器能装 container runtime、允许隔离 | 20-30 分钟 | 驱动兼容性、共享内存、模型挂载 |
我个人在受控环境里最推荐容器,理由很简单:它把 Python、gcc、cmake、CUDA 运行时全部打包,宿主机的 18.04 只需要提供一个足够新的驱动。你不需要为一个推理服务去污染宿主机上跑着的训练环境。但如果机器上连 container runtime 都没装、或者运维不允许,那就老老实实走源码编译,这也是本文的重点。
2. 动手前的地基工程:Python、编译器和驱动
2.1 五条命令摸清家底
别急着装东西。老机器最大的风险是你以为它是干净的,其实早就被前任装过三套 CUDA、两套 conda。先跑这几条:
nvidia-smi # 驱动版本、显卡型号、显存 nvcc --version 2>/dev/null # 有没有 toolkit,什么版本 ls -l /usr/local/ | grep cuda # 有几套 CUDA 并存 python3 --version && which python3 # 默认 Python gcc --version && cmake --version # 编译链版本 free -h && df -h / # 内存和磁盘重点看三件事。第一,nvidia-smi里的显卡型号,它决定了后面TORCH_CUDA_ARCH_LIST怎么填,填错了编译出来的 kernel 在你卡上跑不了。第二,/usr/local下如果有多个 cuda 目录,后面的环境变量必须以你指定的那套为准,不能让 PATH 里的顺序随机决定。第三,free -h里的可用内存,编译 vLLM 的 CUDA 扩展时峰值很容易吃到 12G 以上,内存不够就得靠 swap 或者把并发降下来。
还有一条容易被忽略的检查:/dev/shm的大小。跑张量并行或者大 batch 推理时,进程间通信会用到共享内存,很多容器默认只给 64M,会直接报错。用df -h /dev/shm看一眼,小于 1G 的都要调整。
2.2 Python 3.10 为什么是这里的最优解
vLLM 0.6.x 要求 Python >= 3.9。理论上 3.9、3.10、3.11 都行,但在这台 18.04 上,3.10 是最稳的那个点。
原因在 OpenSSL。Ubuntu 18.04 仓库里的libssl-dev是 1.1.1 系列,你用它编 Python 3.10 完全没问题;但从 3.12 开始,CPython 对 OpenSSL 版本的要求抬高了,configure 阶段就容易报错,你得先自己编一套新版 OpenSSL 再来编 Python,工作量翻倍。而 3.9 又偏老,有些新版本的依赖包已经不再提供 3.9 的 wheel,会逼你去本地编译。3.10 正好在“依赖包都还支持”和“系统库够用”的交叉点上。
编译安装的完整步骤:
sudo apt update sudo apt install -y build-essential zlib1g-dev libncurses5-dev \ libgdbm-dev libnss3-dev libssl-dev libreadline-dev libffi-dev \ libsqlite3-dev libbz2-dev liblzma-dev uuid-dev tk-dev cd /tmp curl -O https://www.python.org/ftp/python/3.10.13/Python-3.10.13.tgz tar -xf Python-3.10.13.tgz && cd Python-3.10.13 ./configure --prefix=/usr/local --with-ensurepip=install make -j$(nproc) sudo make altinstall几个细节值得说。--enable-optimizations会启用 PGO,编译时间从 3 分钟变成 15 分钟以上,小内存机器还可能中途 OOM,我一般不开——为了那 5% 的启动性能提升,不值。用altinstall而不是install,是为了不覆盖系统的python3,18.04 上有一堆系统工具依赖 3.6,覆盖了会出问题。--enable-shared我一般也不加,加了之后每次运行都要配LD_LIBRARY_PATH,纯属给自己找麻烦。
装完验证python3.10 -V,然后建虚拟环境时统一用绝对路径,避免 PATH 混乱:
/usr/local/bin/python3.10 -m venv /opt/venvs/vllm source /opt/venvs/vllm/bin/activate pip install --upgrade pip setuptools wheel2.3 驱动和 CUDA 11.8 的正确装法
如果驱动已经在新版本区间,别动它。装驱动这件事的风险远大于收益,尤其是在有别的服务在跑的机器上。你只需要装 CUDA Toolkit。
用 runfile 方式装,安装过程中务必取消勾选 Driver 那一项。这一步是关键,很多人手一抖把驱动一起装了,结果装进去一个比现有版本更老的驱动,重启后nvidia-smi直接报 mismatch,整台机器起不来。
sudo sh cuda_11.8.0_520.61.05_linux.run --toolkit --silent --override--toolkit只装工具链,--silent免交互,--override跳过编译器版本检查(18.04 的 gcc 7.5 会触发警告,但实际能编)。
装完在/etc/profile.d/下加一个脚本,把路径固定住:
export CUDA_HOME=/usr/local/cuda-11.8 export PATH=$CUDA_HOME/bin:$PATH export LD_LIBRARY_PATH=$CUDA_HOME/lib64:$LD_LIBRARY_PATH这里有个容易踩的坑:/usr/local/cuda这个软链接可能指向别的版本。永远用带版本号的绝对路径,不要图省事用cuda。我见过一台机器上软链接指到了 10.2,编译时 nvcc 版本和 torch 版本对不上,报错信息完全误导人。
至于驱动本身,如果确实需要升级,流程是:先apt purge掉所有旧的驱动相关包,把 nouveau 加入黑名单,重启进文本模式,再装新驱动,重启验证。这套操作在老机器上一次成功率大概七成,剩下的三成里有一半是内核头文件缺失导致的 DKMS 编译失败。所以我的建议是:能不升就不升,先确认你现在的驱动到底能不能撑住你的目标方案。
2.4 编译工具链:gcc、cmake、ninja 一个都不能少
CUDA 11.8 支持的 gcc 上限是 11,Ubuntu 18.04 默认的 7.5 虽然勉强能编,但 vLLM 里大量使用 C++17 特性,gcc 7 对其中一部分支持不完整,容易在编译中后期报一堆模板错误。装 gcc-9,然后用 alternatives 切过去:
sudo apt install -y gcc-9 g++-9 sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 90 sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-9 90cmake 是另一个硬门槛。18.04 自带 3.10,而 vLLM 的构建脚本要求 3.26 以上。最省事的办法是用 pip 装一个,它自带预编译二进制,不依赖系统 glibc 太新的版本:
pip install "cmake>=3.26" ninja装完之后which cmake应该指向虚拟环境里的那个。如果你的虚拟环境外面还有别的构建流程要用 cmake,注意不要让这个 pip 版本的 cmake 污染全局环境。
最后是内存。编译 vLLM 的 CUDA kernel 时,nvcc 对每个源文件都要吃掉 2-4G 内存,并发数一高就 OOM。如果机器内存只有 16G,加上 swap:
sudo fallocate -l 16G /swapfile sudo chmod 600 /swapfile sudo mkswap /swapfile && sudo swapon /swapfileswap 会让编译变慢,但总比编译到 80% 被杀掉强。用MAX_JOBS=4控制并发,效果比堆 swap 更好。
3. 路线 A:源码编译 vLLM 的完整实操
3.1 版本组合怎么定,先让 pip 告诉你
选版本这一步,不要凭记忆。我见过太多人照着半年前的博客装vllm==0.4.2,结果发现那个版本不支持他现在要跑的模型。
最可靠的方法是让 pip 自己算。用--dry-run让 pip 只解析依赖不实际安装:
pip install "vllm==0.6.1" --dry-run 2>&1 | head -40输出里会明确列出它打算装的 torch 版本。你拿这个 torch 版本去 PyTorch 的 wheel 索引页面确认有没有 cu118 的构建,如果有,说明这个 vLLM 版本在你的环境里是有解的;如果没有,就往下降一个 vLLM 版本再试。
我实测下来,在 CUDA 11.8 环境下比较稳的组合是 vLLM 0.6.x 配 torch 2.4/2.5 的 cu118 构建。但这不是金科玉律,因为你还要考虑模型支持度——太老的 vLLM 不认识新模型的架构,加载时会报architecture is not supported。
版本定下来之后,先把 torch 单独装好,验证它能识别到显卡,再装 vLLM:
pip install torch==2.4.0 torchvision==0.19.0 \ --index-url https://download.pytorch.org/whl/cu118 python -c "import torch; print(torch.__version__, torch.version.cuda, torch.cuda.is_available())"期望输出是2.4.0 11.8 True。如果cuda.is_available()是 False,别往下走了,先解决这个,通常是驱动版本不够或者 CUDA_VISIBLE_DEVICES 被设置成了空值。
3.2 拉源码、装依赖、控环境变量
源码编译的核心是“关掉构建隔离”。pip 默认会给每个包创建独立的构建环境,这意味着它会在一个干净的沙箱里重新下载 torch,然后编译 vLLM 的扩展。这个 torch 很可能不是你刚装的那个版本,甚至可能是 CPU 版本,编译出来的东西当然没法用。
git clone https://github.com/vllm-project/vllm.git cd vllm git checkout v0.6.1 git submodule update --init --recursive pip install -r requirements-build.txt pip install -r requirements-cuda.txtrequirements-cuda.txt里包含了编译所需的一切,包括 xformers、flash-attn 之类的加速库。这一步如果在国内网络环境下很慢,可以给 pip 配上你所在网络里响应最快的镜像源。
然后是环境变量,这几行决定了编译能不能成功:
export CUDA_HOME=/usr/local/cuda-11.8 export PATH=$CUDA_HOME/bin:$PATH export LD_LIBRARY_PATH=$CUDA_HOME/lib64:$LD_LIBRARY_PATH export TORCH_CUDA_ARCH_LIST="7.0;7.5;8.0;8.6" export MAX_JOBS=4 export NVCC_THREADS=2TORCH_CUDA_ARCH_LIST需要重点解释。它告诉 nvcc 只针对这些计算能力编译 kernel。填得越多,编译越慢、产物体积越大;填得越少,编译快,但换一张卡就不能用了。常见对应关系如下:
| 显卡系列 | 计算能力 | 备注 |
|---|---|---|
| V100 | 7.0 | 不支持 bf16,只能用 fp16 |
| T4 | 7.5 | 显存小,适合小模型 |
| A100 | 8.0 | 支持 bf16 |
| A10 / A6000 / 3090 | 8.6 | 主流推理卡 |
| 4090 / L40 | 8.9 | 支持 fp8 的新架构 |
| H100 | 9.0 | 需要更新的 toolkit |
只填你机器上实际有的那张卡的能力值。如果你只在一台 T4 机器上跑,就填"7.5",编译时间能从 50 分钟降到 20 分钟。
3.3 开始编译,以及中途出错怎么办
pip install -e . --no-build-isolation -v 2>&1 | tee build.log--no-build-isolation是灵魂参数,它让 pip 用当前环境里的 torch 去编译,而不是另起炉灶。-v输出详细日志,配合tee把日志留下来,出错时方便定位。
编译过程分三段:先编 C++ 部分(大约 3-5 分钟),然后_C这个 CUDA 扩展(最慢,占 80% 的时间),最后编各种自定义 kernel。中间会看到大量的warning,这些基本都可以忽略,真正致命的是error。
如果编译在 CUDA 那一段被杀掉,日志里出现Killed或者cc1plus: out of memory,那是内存不够,把MAX_JOBS降到 2,确保 swap 已经挂上,重来。
如果报fatal error: cuda_runtime.h: No such file or directory,说明CUDA_HOME没生效,检查echo $CUDA_HOME和ls $CUDA_HOME/include/cuda_runtime.h。
如果报 gcc 版本相关的错误,比如error: unrecognized command line option '-std=c++17',说明 alternatives 没生效,gcc --version确认一下是不是 9。
3.4 编译产物校验:别等上线才发现白编了
编译成功不等于能用。装完之后立刻做三层验证,一层比一层深。
第一层,检查扩展能不能加载:
python -c "import vllm; print(vllm.__version__)" python -c "from vllm import _C; print('ext ok')"第二层,检查 kernel 是否匹配你的卡。用一个小模型跑一次真实推理,别用 mock:
python - <<'EOF' from vllm import LLM, SamplingParams llm = LLM(model="/data/models/Qwen2.5-0.5B-Instruct", dtype="half", max_model_len=2048) out = llm.generate(["你好,介绍一下你自己"], SamplingParams(max_tokens=64)) print(out[0].outputs[0].text) EOF如果这一步报no kernel image is available for execution on the device,就是TORCH_CUDA_ARCH_LIST填错了,或者编译时用的 torch 是 CPU 版本。回去查build.log里 nvcc 的-gencode参数,能看到实际编了哪些架构。
第三层,测吞吐。用vllm bench或者简单写个循环发请求,看每秒能出多少 token。这一步的意义是发现“能跑但极慢”的情况,通常原因是没编进来 flash-attn,退化到了朴素实现。
3.5 起服务时的几个关键参数
推理服务化用 OpenAI 兼容接口最省事:
python -m vllm.entrypoints.openai.api_server \ --model /data/models/Qwen2.5-7B-Instruct \ --served-model-name qwen-7b \ --host 0.0.0.0 --port 8000 \ --dtype half \ --max-model-len 8192 \ --gpu-memory-utilization 0.90 \ --swap-space 8 \ --disable-log-requests--dtype half在 V100 和 T4 上是必须的,这两代卡不支持 bf16,用auto会让 vLLM 尝试 bf16 然后报错或者退化成 fp32 撑爆显存。--gpu-memory-utilization 0.90是预分配的显存比例,vLLM 会一口气吃掉这部分做 KV cache,所以要留 10% 给 CUDA context 和临时张量。--swap-space是 CPU 侧的交换空间,遇到长请求排队时有用,但要保证/dev/shm足够大。
还有一个参数--enforce-eager,它会关掉 CUDA Graph 捕获。代价是吞吐降低 10%-20%,好处是启动快、显存占用低、不容易因为图捕获失败而卡死。在调试阶段我一般开着它,等一切稳定了再去掉。
4. 路线 B:预编译包的快速通道
4.1 什么情况下可以偷懒
如果你的驱动够新(525+),其实有个更省事的做法:别管机器上装的是 CUDA 11.8,直接装 cu121 的 torch 加官方 vLLM wheel。驱动向上兼容,11.8 的 toolkit 装着不影响你跑 12.1 的运行时,因为 pip 的nvidia-cuda-runtime-cu12这些包自带运行时库。
pip install torch==2.5.1 --index-url https://download.pytorch.org/whl/cu121 pip install vllm==0.6.3整个过程十分钟内搞定,不用编译,不用管 cmake 版本,不用管 gcc。这是我最推荐大多数人的路径。
但有个前提必须确认:nvidia-smi能正常输出,且驱动版本 >= 525。低于这个数,cu121 的运行时初始化会失败。
4.2 混合环境的清理
老机器上最大的隐患是“装过一版又装一版”。cu118 和 cu121 的运行时库同时存在于 site-packages 里,谁先被加载取决于文件系统顺序,结果就是玄学崩溃。
清理的办法很简单:重建虚拟环境。别在旧环境上修修补补,pip uninstall的残留文件会一直在那儿。规程是:删掉/opt/venvs/vllm,重新python3.10 -m venv,重新装。养成这个习惯能省掉大量莫名其妙的排查时间。
验证环境是否干净,用这条:
pip list | grep -iE "torch|nvidia|vllm"看输出里是不是只有一套 cu12 相关的包,有没有nvidia-cuda-runtime-cu11和cu12同时存在的现象。
5. 踩坑速查表:报错、原因、处置
5.1 动态库和符号类问题
这类问题占了我在老系统上遇到的一半以上,症状是“装完了、import 就炸”。
| 报错关键字 | 真实原因 | 处置方式 |
|---|---|---|
libcudart.so.12: cannot open | torch 是 cu118,vLLM 要 cu12 | 统一版本,或源码编译 |
libcudart.so.11.0: cannot open | 没装 cu118 运行时或路径没配 | 装 torch cu118 或设 LD_LIBRARY_PATH |
undefined symbol: _ZN2at... | torch 与 vLLM 版本不匹配 | 按 dry-run 结果重装 |
version GLIBC_2.28 not found | 某个 wheel 编译时用了更新的 glibc | objdump -T x.so | grep GLIBC_定位后降版本 |
libgomp.so.1: cannot open | gcc 运行时缺失 | apt install libgomp1 |
排查GLIBC这类问题有个好用的命令:ldd看依赖树,objdump -T看某个 so 要求的符号版本。找到具体是哪个文件要求的 2.28,再决定是降那个包的版本,还是放弃。
5.2 显存和性能类问题
torch.cuda.OutOfMemoryError不一定真的是显存不够。可能是--max-model-len设得太大,vLLM 按最坏情况预留 KV cache,直接把显存吃满。也可能是--gpu-memory-utilization设成了 0.95,连 CUDA context 的空间都没留。
诊断顺序是:先降--gpu-memory-utilization到 0.85,再降--max-model-len,最后考虑降--max-num-seqs。如果这些都不行,那就是模型本身在你卡上放不下,得换量化版本或者换卡。
另一个常见现象是“能跑但很慢”。先看启动日志里有没有Using FlashAttention backend这类信息,如果退化到了xformers或者朴素实现,吞吐会差一大截。老卡上 flash-attn 的预编译包往往没有对应版本,这里要么接受性能损失,要么自己编 flash-attn(很折腾)。
5.3 环境和启动类问题
服务起不来但没报错,通常是端口被占或者 host 绑定错了。--host 0.0.0.0才能被外部访问,默认的127.0.0.1只能本机连。
进程被 OOM Killer 干掉,看一下dmesg | tail -20有没有Out of memory: Killed process。这种情况一般是模型加载阶段内存峰值太高,解法是加 swap 或者用--load-format分片加载。
还有一个隐蔽的坑:/dev/shm太小。报错信息通常是Bus error或者 NCCL 初始化失败。用df -h /dev/shm查,容器里需要启动时加--shm-size=8g。
6. 我踩过之后总结的几条经验
版本一旦跑通,第一件事是把pip freeze > requirements.lock存下来。这台机器上的组合是花了几个小时试出来的,下次重装或者扩容,照着锁文件走,别再重新试一遍。锁文件里要包含 torch、vllm、xformers、transformers 这几个关键包的确切版本,缺一个都可能复现不了。
第二,别在生产机器上编译。编译过程会吃掉大量 CPU 和内存,如果这台机器同时还跑着训练任务,很容易互相影响。我的做法是在一台配置相同的测试机上编好,把pip freeze的结果和一个完整的虚拟环境目录打包过去,用--no-index离线安装。
第三,对于这种“系统版本老、组件要求新”的场景,长期看容器是唯一可持续的方案。宿主机的 18.04 只要提供一个足够新的驱动,剩下的全部隔离在镜像里。你不需要为了一个推理服务去动一台跑着关键任务的机器,也不用担心某次apt upgrade把 gcc 换掉导致环境失效。
最后一个实战心得:如果编译反复失败且时间成本已经超过半天,果断换容器方案。我见过太多人在源码编译上耗掉两天,最后发现容器镜像里已经把一切调好了。判断标准很简单——编译失败超过三次,且每次都是不同原因,说明这套环境的坑比你预想的多,继续投入的边际收益已经很低了。