从 "externally-managed-environment" 到 vLLM 跑通大模型:一份 WSL2 实战记录
如果你最近在 WSL2 的 Ubuntu 里用 pip 装东西,大概率撞上过这个报错:
code复制error: externally-managed-environment
× This environment is externally managed
╰─> To install Python packages system-wide, try apt install
python3-xyz, first. Make sure you have python3-full installed.
我第一次在 Ubuntu 22.04 的 WSL2 里装 PyTorch 时,看到这串字整个人都愣了——我之前在别的机器上 pip install 从来没遇到过这个拦截。后来才搞清楚,这不是环境坏了,也不是权限不够,而是 Python 包管理的一次规则升级。这篇文章就把我从这个报错开始,到最终在 WSL2 里跑通 PyTorch + vLLM 的完整过程记录下来,包括每一步的踩坑和排查思路,希望能帮后来的人少走几趟弯路。
先说结论:这个问题不解决,PyTorch、vLLM 这些重量级 Python 包根本装不进去;解决方式有很多,但选错方案后面会踩更大的坑。适合刚接触 Ubuntu/WSL2、或者被这个报错拦住的开发者参考。
1. 报错背后是谁在管你的 Python 环境
1.1 错误的完整样貌
除了上面那段,完整的报错还会带一段提示:
code复制error: externally-managed-environment
× This environment is externally managed
╰─> To install Python packages system-wide, try apt install
python3-xyz, first. Make sure you have python3-full installed.
If you wish to install a non-Debian-packaged Python package,
create a virtual environment using python3 -m venv path/to/venv.
Then use path/to/venv/bin/python and path/to/venv/bin/pip.
If you wish to install a non-Debian packaged Python application,
it may be easiest to use pipx install xyz, which will manage a
virtual environment for you. Make sure you have pipx installed.
Note: In this environment, this is not a problem!
注意最后那行 "Note: In this environment, this is not a problem!",这不是系统在说废话,而是在明确告诉你:这不是错误,是策略。系统故意不让你直接往全局环境里装包。
1.2 系统如何判断"外部管理":EXTERNALLY-MANAGED 标记
这个机制来自 Python 的 PEP 668,全称是 "Marking Python base environments as externally managed"。它的做法很简单:在 Python 安装目录下放一个标记文件,通常路径是 /usr/lib/python3.11/EXTERNALLY-MANAGED,文件内容类似于:
code复制[externally-managed]
Error=To install Python packages system-wide, try apt install python3-xyz, first...
pip 在安装时会检测这个文件,如果存在就直接拒绝在全局环境安装,除非你显式加了 --break-system-packages 参数。
这套机制的初衷非常好理解:操作系统自带的 Python 是给 apt 和系统工具用的。如果你用 pip 往里面塞了一堆新版本包,很可能把系统依赖搞崩——比如某个包的新版本和 apt 里某个工具要求的旧版本冲突,可能导致系统级的功能直接挂掉。这就像宿舍的公共厨房,你用完了不收拾,还把自己买的冰箱推进去占了公共空间,后来的人就没法做饭了。
1.3 为什么 Ubuntu 22.04 也可能撞上
严格来说,Ubuntu 22.04 自带的 Python 3.10 默认没有启用 PEP 668 拦截,这个机制是从 Ubuntu 23.04 和 Debian 12 开始默认开启的。但实际使用中,我见过很多在 Ubuntu 22.04 上遇到这个报错的情况,主要分两种:
第一种,你自己装了更高版本的 Python。比如用源码编译安装了 Python 3.11/3.12,或者用了 deadsnakes PPA。这些新版本 Python 默认会带 EXTERNALLY-MANAGED 标记。
第二种,你用的不是原版 Ubuntu 22.04,而是基于它的发行版,或者你在 Docker 里用了更新的 Debian 基础镜像。
另外,不少教程会引导你升级 Python 版本或者用 pyenv 管理多版本,这些情况下撞上 PEP 668 几乎是必然的。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 三条出路:选错方案会付出额外代价
2.1 venv:官方推荐,也是我的默认选择
PEP 668 报错信息里其实已经把解决方案写得很清楚了——用虚拟环境。这也是我最终采用的方案。
创建一个虚拟环境非常简单:
bash复制# 先确认 python3 版本
python3 --version
# 创建虚拟环境,名字随意,我用 venv
python3 -m venv venv
# 激活
source venv/bin/activate
激活后,命令行提示符会带上 (venv) 前缀,这时候再用 pip 安装任何包都不会触发 externally-managed-environment 报错,因为 pip 装的是虚拟环境,不是系统全局环境。
注意:如果
python3 -m venv报错说缺 venv 模块,需要先安装python3-venv:sudo apt install python3-venv。
2.2 --break-system-packages:能用,但要想清楚后果
有些教程会让你直接用:
bash复制pip install torch --break-system-packages
这确实能绕过报错,但我不推荐,尤其是 PyTorch 和 vLLM 这种依赖极其复杂的包。原因有三个:
- 系统 Python 是 apt 的"自留地",你用 pip 覆盖了里面的包,下次 apt 升级时可能直接覆盖回去,或者反过来把系统搞挂。
- PyTorch 会拉一堆依赖(numpy、typing-extensions 等),这些依赖版本一旦和系统包冲突,排查起来非常痛苦。
- vLLM 甚至会主动修改 torch 版本,在全局环境里这么折腾,等于在系统底盘上做实验。
如果只是临时装个小工具,--break-system-packages 还能忍;但装 PyTorch + vLLM 这种重量级组合,还是在虚拟环境里更安全。
2.3 conda/miniconda:适合需要特定 CUDA 版本的场景
如果你之前用惯了 conda,或者需要非常细致地控制 CUDA 相关依赖,可以考虑用 miniconda 创建独立环境:
bash复制wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh
bash Miniconda3-latest-Linux-x86_64.sh
# 创建环境
conda create -n llm python=3.11
conda activate llm
conda 环境天然不归系统 Python 管,所以也不会触发 PEP 668。而且 conda 在管理 CUDA 工具链方面有优势,很多老教程会用 conda install pytorch cudatoolkit。不过它的缺点是环境体积大、包源有时候比较慢。我的观点是:如果你不是已经熟悉 conda,直接用 venv 就行。
2.4 我的选型建议
对于 PyTorch + vLLM 这个组合,我的推荐顺序是:venv > conda > --break-system-packages。
尤其是 WSL2 环境,本身就是一个独立的 Linux 子系统,没必要把 Python 环境搞得太复杂。一个干净的系统 Python 加上一个专用的虚拟环境,足够应对绝大多数深度学习场景。
3. WSL2 前置准备:CUDA 和内存是绕不开的两座山
3.1 Windows 侧装好 NVIDIA 驱动,WSL2 里什么都别装
这是 WSL2 和原生 Ubuntu 最大的区别之一。在 WSL2 里做深度学习,NVIDIA 驱动只需要装在 Windows 侧,WSL2 内部不需要、也不应该装 NVIDIA 驱动。
首次遇到这个问题的朋友往往会在 WSL2 里执行 sudo apt install nvidia-driver-xxx,结果装完重启反而出问题。WSL2 的 GPU 直通机制是:Windows 的 NVIDIA 驱动通过 WSL2 的内核模块映射给 Linux 用户空间使用,你在 WSL2 里只需要安装 CUDA Toolkit(或者直接用 PyTorch 自带的 CUDA 运行时)。
我用的验证方式很简单,在 WSL2 终端里执行:
bash复制nvidia-smi
如果能看到类似下面的输出,说明 GPU 直通已经正常:
code复制+---------------------------------------------------------------------------------------+
| NVIDIA-SMI 545.23.08 Driver Version: 545.23.08 CUDA Version: 12.3 |
+---------------------------------------------------------------------------------------+
注意这里显示的 "Driver Version" 是 Windows 侧驱动的版本,CUDA Version 是驱动支持的最高 CUDA 版本,不是 WSL2 里已经装了 CUDA。
3.2 CUDA Toolkit 到底要不要单独装
如果你只是用 PyTorch 做推理和训练,不需要单独装 CUDA Toolkit。因为 PyTorch 的 pip 包是自带 CUDA 运行时(cudart)的,安装时通过 --index-url 选择对应 CUDA 版本的包就行。
但 vLLM 的情况稍微复杂一些。vLLM 在较新的版本中,安装 wheel 包时通常会拉取适当版本的 torch,而 torch 自带的 CUDA 运行时一般够用。不过 vLLM 的部分功能(比如某些 kernel 编译)可能依赖 nvcc。如果你后续遇到编译相关的报错,可以再补装 CUDA Toolkit:
bash复制# 以 CUDA 12.1 为例
wget https://developer.download.nvidia.com/compute/cuda/12.1.1/local_installers/cuda_12.1.1_530.30.02_linux.run
sudo sh cuda_12.1.1_530.30.02_linux.run --toolkit --silent --override
不过一般情况下,先把 vLLM 装起来跑通再说,遇到编译错误再补装也不迟。
3.3 .wslconfig:给 WSL2 分够内存和 CPU
WSL2 默认只分配宿主机物理内存的 50% 或 8GB(取较小值),这对于跑大模型来说往往不够。我跑 Qwen2.5-7B-Instruct 时,加载模型就需要 16GB 左右内存,因此调整 .wslconfig 文件非常必要。
文件位置在 Windows 用户目录下,比如 C:\Users\你的用户名\.wslconfig,内容可以这样写:
ini复制[wsl2]
memory=32GB
processors=8
swap=16GB
localhostForwarding=true
写完后在 PowerShell 里执行 wsl --shutdown,再重新进入 WSL2 让配置生效。
这里有个小技巧:swap 别省,模型加载时峰值内存可能冲得很高,swap 可以当作兜底。用 nvme 固态的话,swap 的性能损失远小于直接 OOM。
3.4 "虚拟化未启用"这个经典问题
很多人在第一次装 WSL2 时就卡在这里:启动 WSL2 提示"无法启动,因为此计算机上未启用虚拟化"。
这个问题的本质是 Windows 的虚拟化平台没有开启。排查步骤我整理成一套:
- 打开任务管理器 -> 性能 -> CPU,查看右下角"虚拟化"是否显示"已启用"。
- 如果显示禁用,需要进 BIOS 开启 Intel VT-x 或 AMD-V。不同主板路径不同,一般在 Advanced -> CPU Configuration 里。
- 如果你的机器是 Windows 10/11 家庭版,还需要确认"虚拟机平台"和"适用于 Linux 的 Windows 子系统"两个 Windows 功能都勾选上了。在 PowerShell(管理员)里执行:
powershell复制dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart - 有些电脑开了 Windows 沙盒或 Hyper-V 之后,和第三方虚拟机软件(比如 VMware、VirtualBox)冲突,也可能导致 WSL2 无法启动。
这里有个容易踩的坑:如果开启了 Hyper-V,VMware Workstation 15.5 及以下版本会无法运行虚拟机。反过来,某些第三方虚拟化软件会抢占 VT-x,导致 WSL2 启动失败。解决办法是尽量保持系统自带的"虚拟机平台"启用,第三方虚拟机软件切换到响应新版 Hyper-V 的版本(VMware 16+)。
4. PyTorch 安装实操:版本对不上,白忙一整天
4.1 先确认 CUDA 环境:nvidia-smi 和 torch 的版本对应关系
在动手装 PyTorch 之前,先看清楚你的驱动支持到哪个 CUDA 版本。用 nvidia-smi 看到的 "CUDA Version" 是驱动支持的最高版本,只要 PyTorch 要求的 CUDA 版本低于或等于这个值,就能正常工作。
比如驱动显示 CUDA 12.3,那么 PyTorch 的 cu118、cu121、cu124 这些版本都可以跑,因为它们的运行时要求都低于 12.3。但如果驱动只支持到 CUDA 11.8,你装了 cu121 的 PyTorch,就会在运行时报告找不到 CUDA 驱动。
我的建议是:装 PyTorch 时选择 cu121 或 cu124 这类版本,这样驱动兼容性最广、性能也稳定。在较新的 PyTorch 版本里,官方已经把默认版本切到了 CUDA 12.x,pip install torch 默认装的实际上就是带 CUDA 支持的版本。
4.2 创建虚拟环境并激活
在 WSL2 的 Ubuntu 里,我先创建一个干净的 venv:
bash复制# 进到你想要放项目的目录
mkdir ~/dev/llm && cd ~/dev/llm
python3 -m venv venv
source venv/bin/activate
激活后检查一下 Python 版本和 pip 版本:
bash复制python --version
pip --version
4.3 安装命令与国内网络优化
从 PyTorch 官网获取安装命令时,建议选 Stable 版本,然后根据你的 CUDA 版本选择对应配置。以 CUDA 12.1 为例:
bash复制pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121
这里有个很关键的小坑:如果你直接 pip install torch,大概率也能装上,但装的是默认的 CUDA 版本(现在通常是 cu124 或更高)。如果你的驱动版本比较旧,可能会遇到运行时报错。所以稳妥起见,还是显式指定 --index-url。
国内网络环境下载 PyTorch 的 wheel 包可能比较慢,尤其是 torch 的包体积有好几个 GB。可以配置 pip 使用清华或阿里云的镜像源,但注意 --index-url 会覆盖镜像源配置。我建议采用下面的方式,既能走国内镜像,又选对 CUDA 版本:
首先把默认源换成清华 PyPI:
bash复制pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
然后安装时,PyTorch 官方源和 PyPI 源的包版本不一定同步,所以更好的方式是先从官方源下载 torch 的 wheel,再切换回普通源装其他依赖。不过实际操作中,很多人直接在国内镜像装 torch 速度反而更快。如果你用国内镜像,可以直接装:
bash复制pip install torch torchvision torchaudio
这样装的是 PyPI 源里默认的版本,多数情况下是带 CUDA 12.x 支持的版本,前提是你的驱动够新。
4.4 验证 CUDA 是否被 PyTorch 识别
安装完成后,先验证一下:
bash复制python -c "import torch; print(torch.__version__); print(torch.cuda.is_available()); print(torch.cuda.get_device_name(0))"
如果输出类似:
code复制2.5.1+cu121
True
NVIDIA GeForce RTX 4090 Laptop GPU
说明 CUDA 直通正常。如果 torch.cuda.is_available() 返回 False,多半是 CUDA 版本选高了、驱动太旧,或者 WSL2 里 nvidia-smi 本身就不正常。
我见过一个比较隐蔽的情况:WSL2 里 nvidia-smi 正常,但 PyTorch 就是检测不到 CUDA。排查了一圈发现是 Windows 侧驱动版本太老,和 WSL2 内核的兼容性有问题。解决方式是升级 Windows 侧 NVIDIA 驱动到最新版本,然后重启电脑。
5. vLLM 安装:它比 PyTorch 更"挑剔"
5.1 vLLM 对 Python 和 CUDA 的硬性要求
vLLM 是目前非常流行的大模型推理加速框架,靠 PagedAttention 和 continuous batching 这些机制大幅提升吞吐。但它的安装条件比 PyTorch 更严格。
不同版本的 vLLM 对 Python 版本有明确要求。以 vLLM 0.6.x 和 0.7.x 为例,官方支持 Python 3.9-3.12,但实际上在 0.6.3 之后官方推荐的版本是 Python 3.10+。我建议直接用 Python 3.11,这个版本兼容性最好。
CUDA 方面,vLLM 依赖 PyTorch 的 CUDA 运行时,所以只要你已经装好了能正常工作的 PyTorch,vLLM 的 CUDA 要求基本都能满足。
5.2 安装 vLLM 及依赖处理
在虚拟环境已经激活的前提下,安装 vLLM 其实只有一行命令:
bash复制pip install vllm
但要注意:vLLM 安装时会自动拉取它依赖的特定版本 torch,如果你的 torch 版本不匹配,pip 会自动升级或降级 torch。这既是好事也是坏消息——好事是 pip 会自动处理依赖,坏消息是它可能把你精心装好的 torch 版本给换了。
我实测下来,vLLM 0.6.x 依赖的 torch 版本是 2.4.x/2.5.x,vLLM 0.7.x 需要 torch 2.5.x。如果你之前装的是 torch 2.1 或更早版本,pip 会强制升级,这个过程可能会在 WSL2 里下载好几个 GB 的依赖包,需要耐心等待。
如果你想避开版本自动调整,可以先把 vLLM 装到一个独立的虚拟环境里,这样和 PyTorch 环境互不干扰:
bash复制python3 -m venv vllm-env
source vllm-env/bin/activate
pip install vllm
5.3 常见安装冲突与修复
在安装 vLLM 时,有几种常见的报错我在这里列一下,方便你对照排查:
| 报错现象 | 原因 | 解决方案 |
|---|---|---|
ModuleNotFoundError: No module named 'torch._inductor' |
torch 版本和 vLLM 不匹配,或者 torch 装的是 CPU 版 | 升级 torch:pip install --upgrade torch --index-url https://download.pytorch.org/whl/cu121 |
CUDA error: no kernel image is available for execution on the device |
CUDA 版本与 GPU 算力不匹配 | 选用与驱动匹配的 CUDA 版本重新安装 torch |
AttributeError: _ARRAY_API not found |
numpy 版本冲突 | pip install --upgrade numpy |
FAILED: Building wheel for vllm |
未预编译的源码构建失败 | 优先装预编译 wheel:pip install vllm,不要加 --no-binary |
其中最常见的是 CUDA 版本不匹配。vLLM 对 CUDA 运行时非常敏感,如果你在 WSL2 里用 cu121 的 torch 没问题,但 vLLM 报 CUDA kernel 相关的错,建议换成 cu121 或 cu124 的 torch 重新安装,保持 vLLM 和 torch 在同一个 CUDA 版本下。
5.4 实测:用 Qwen2.5-0.5B-Instruct 做冒烟测试
安装完成后,用一个小模型做冒烟测试是成本最低的验证方式。我通常用 Qwen2.5-0.5B-Instruct,模型很小,几百 MB,下载快,跑完不会占太多内存。
新建一个 Python 脚本 smoke_test.py:
python复制from vllm import LLM, SamplingParams
llm = LLM(model="Qwen/Qwen2.5-0.5B-Instruct", dtype="float16")
prompt = "什么是深度学习?"
sampling_params = SamplingParams(max_tokens=128, temperature=0.7)
outputs = llm.generate([prompt], sampling_params)
for output in outputs:
print(output.outputs[0].text)
运行:
bash复制python smoke_test.py
如果能看到正常的文本输出,说明 vLLM 已经能正常加载模型并推理。这一步跑通之后,就可以把模型换成更大的量化版本(比如 AWQ/GPTQ 量化后的 7B/14B 模型),或者直接接 OpenAI 兼容的 server 接口。
另外,vLLM 在 WSL2 里跑大模型时,如果遇到 OOM,先检查 /dev/shm 大小。WSL2 默认的共享内存可能只有系统内存的一半,可以用 df -h /dev/shm 查看。如果太小,可以在 .wslconfig 里加一行:
ini复制[wsl2]
sharedMemorySize=8GB
6. 跑通后的验证与高频报错速查表
6.1 验证矩阵:一张表确认环境健康
环境装好之后,我建议按下面的顺序做一遍完整验证,确认每个环节都是通的。
| 验证项目 | 命令 | 预期结果 |
|---|---|---|
| GPU 直通 | nvidia-smi |
显示 GPU 型号和驱动版本 |
| PyTorch CUDA | python -c "import torch; print(torch.cuda.is_available())" |
True |
| vLLM 版本 | python -c "import vllm; print(vllm.__version__)" |
正常输出版本号 |
| vLLM 推理 | 运行上面的 smoke_test.py | 正常输出文本 |
这套验证流程看着简单,但能帮你定位问题出在哪个环节:GPU 直通坏了、PyTorch 没识别 CUDA、还是 vLLM 依赖不兼容。
6.2 高频报错速查表
我在折腾过程中积累了一些高频报错,整理成表格,方便你直接对照:
| 报错 | 定位 | 处理方式 |
|---|---|---|
error: externally-managed-environment |
PEP 668 拦截 | 创建 venv 虚拟环境,或在命令后加 --break-system-packages(不推荐) |
torch.cuda.is_available() 为 False |
驱动装错 / 版本不匹配 | 确认 Windows 侧 NVIDIA 驱动已装,且 CUDA 版本 <= 驱动支持版本 |
ImportError: libcudart.so.12: cannot open shared object file |
没有对应版本的 CUDA 运行时 | 重装对应 CUDA 版本的 torch,或安装 CUDA Toolkit |
ValueError: model not found in vLLM |
模型路径或名称错误 | 确认模型 ID 正确,或使用本地目录路径 |
RuntimeError: NCCL error |
多卡通信初始化失败 | 单卡环境忽略;多卡时确认网卡配置或设置 NCCL_P2P_DISABLE=1 |
6.3 日常维护建议
环境跑通之后,有几个日常维护的小建议值得记住。
第一,每次进入 WSL2 后都要先 source venv/bin/activate,否则 pip 又会把包装到系统环境里。如果你嫌麻烦,可以在 ~/.bashrc 末尾加一行自动激活,但建议还是手动激活,避免多个项目环境混淆。
第二,定期清理 pip 缓存。torch 和 vLLM 的 wheel 动辄几 GB,pip 会缓存这些文件,时间长了很占磁盘空间。清理命令:
bash复制pip cache purge
第三,WSL2 的虚拟磁盘文件(ext4.vhdx)会随着使用不断膨胀,即使删除了文件也不会自动缩小。如果发现 C:\Users\用户名\AppData\Local\Packages\CanonicalGroupLimited... 目录特别大,可以用下面命令压缩:
powershell复制wsl --shutdown
diskpart
# 在 diskpart 里
select vdisk file="C:\Users\用户名\AppData\Local\Packages\CanonicalGroupLimited.Ubuntu22.04LTS_*\LocalState\ext4.vhdx"
attach vdisk readonly
compact vdisk
detach vdisk
exit
这是我实际用下来非常管用的办法,能释放几十 GB 的磁盘空间。
最后再分享一个小技巧:如果你打算长期在 WSL2 里搞大模型推理,建议把模型文件放在 WSL2 的原生文件系统(比如 ~/models)里,不要放在 /mnt/c 或 /mnt/d 下。跨文件系统访问在 WSL2 里性能损耗严重,同一份模型放原生文件系统加载速度快很多。这个坑我一开始没注意到,后来把模型从 D 盘挪回 WSL2 里之后,加载时间几乎少了一半。
