1. 为什么偏偏是WSL2:这套环境到底值不值得用
1.1 谁适合在WSL2里跑vLLM
如果你手上只有一台Windows电脑,显卡是NVIDIA的,又想试试vLLM跑大模型推理,那WSL2是目前最顺的一条路。vLLM这个框架对Linux环境的依赖比一般Python库要深,直接装在Windows原生环境里不太现实,而装虚拟机又会有显卡透传的麻烦。WSL2刚好卡在中间:底层是一个完整的Linux内核,上面兼容Windows文件系统和GPU驱动,不需要单独装双系统,也不用买一台纯Linux服务器。
我选择WSL2而不是双系统的原因很直接——我日常办公、写文档、开视频会议都在Windows这边,不想为了跑推理来回重启切系统。而且vLLM做开发调试时经常要改环境、重装依赖,WSL2的独立性比Windows原生方便太多,坏了就删掉重建一个发行版,完全不心疼。
适合WSL2的人其实就三类:一是想在Windows下做LLM推理开发但不想换系统的;二是像我这样需要边调试边用Windows办公的;三是在校学生或没有独立Linux机器,只有一台Windows笔记本的。另一部分人不适合:你要做大规模多卡训练、或者要跑7×24小时的生产推理服务,那还是老老实实搞台Linux裸机,WSL2的性能损失和稳定性上限摆在那里,没必要硬折腾。
1.2 WSL2的GPU访问到底是怎么工作的
很多人在安装之前没搞懂WSL2的GPU访问原理,导致后面遇到“明明装了驱动但CUDA不可用”这种问题。WSL2本质上是一个轻量级虚拟机,运行在Hyper-V虚拟机平台上。它访问GPU不是把物理显卡直接PCIe直通给虚拟机,而是通过微软和NVIDIA联合做的GPU-PV(GPU半虚拟化)方案——Windows宿主机上的驱动先接管GPU,然后通过虚拟化层把CUDA调用转发到WSL2的Linux内核里。
这意味着什么?这意味着WSL2里跑CUDA不是直接访问硬件,而是“借道”Windows驱动。好处是驱动版本统一,Windows上装了新版NVIDIA驱动,WSL2里就能直接用,不用在虚拟机里单独装驱动。坏处是多了一层间接调用,部分API调用有额外的传输开销,另外你在WSL2里用nvidia-smi看到的显存占用,有一部分实际上是Windows桌面在用的,这个坑后面我专门讲。
明白了这个原理,你就不用纠结“WSL2里的CUDA版本和Windows驱动版本怎么匹配”了,Windows驱动库自带WSL2的CUDA支持,你在/usr/local/cuda里装一个和Windows匹配的CUDA Toolkit就能编译源码,但直接装vLLM的预编译轮子时连CUDA Toolkit都可以省。
1.3 这种方案的性能账怎么算
我见过不少帖子说“WSL2跑GPU性能损失很大”,这个说法要分情况。如果是游戏、图形渲染这种高频短事务的负载,GPU-PV的调用开销确实明显。但vLLM这种大模型推理任务不一样,它单次CUDA运算量大,很多时间花在矩阵乘法这类密集型计算上,虚拟化层的额外开销被计算时间摊薄了。从我自己实测的感受和社区里的普遍反馈来看,WSL2跑vLLM推理比原生Linux慢5%~10%左右,属于可以接受的范围。
不过慢这点换来了巨大的环境管理便利。我在WSL2里同时维护着好几个独立环境,跑不同版本的vLLM和PyTorch互不干扰,Windows那边该干嘛干嘛。这个账对开发调试场景来说是划算的,对于生产环境就是另一码事了。
2. 动手装之前:先检查WSL2的CUDA透传和内核版本
2.1 需要满足的版本清单
装vLLM前第一件事不是急着敲pip install,而是做环境预检。WSL2的GPU透传功能在不同版本上的完成度差别很大,老版本可能压根不支持,或者只能看到显卡但一跑CUDA就报错。
下面是我整理的版本检查清单,每一项都有对应的检查命令:
| 检查项 | 最低要求 | 检查命令 |
|---|---|---|
| Windows系统 | Windows 11较新版本,或Windows 10较新版本 | winver |
| WSL版本 | WSL 2.4.6以上 | wsl --version |
| 发行版内核 | 6.6.x以上(建议升级到最新) | uname -r |
| NVIDIA驱动 | 2024年之后发布的新版驱动 | Windows下运行nvidia-smi |
| WSL内CUDA可见性 | 能正常输出显卡信息 | wsl内nvidia-smi |
很多人的问题就出在WSL版本太旧。如果你的wsl --version命令提示“无法识别”,说明你用的还是第一代WSL工具集,需要先在PowerShell里执行wsl --update升级到应用商店版。升级之后WSL才支持完整的GPU透传特性,这是整个安装过程的地基。
2.2 关键检查命令的判读方法
在PowerShell(管理员模式)里跑一下wsl --shutdown,然后重新进入WSL2。先看WSL本身的状态:
wsl --version uname -r如果显示WSL版本在2.0以下,直接运行wsl --update升级。升级完务必wsl --shutdown再重启,否则新内核不会生效。
然后验证GPU能不能被WSL看到:
nvidia-smi正常的情况下,输出会有一栏“CUDA Version”,同时能看到显卡型号和显存总量。如果这里报“NVIDIA-SMI has failed because it couldn't communicate with the NVIDIA driver”,说明Windows端的驱动没装好,或者WSL版本太老不支持透传。这时候不要急着装vLLM,先去Windows端把NVIDIA驱动更新到最新版,再回来试。WSL里的CUDA调用完全依赖Windows驱动,这一步不通过,后面一切白搭。
还有一个容易被忽略的点:有些用户装的是笔记本双显卡(核显+独显),WSL2里默认走的可能是核显。这时候要在Windows的“图形设置”里把WSL应用强制指定为高性能NVIDIA GPU,不然nvidia-smi可能显示的是核显或者报找不到CUDA设备。
2.3 调整WSL的资源配置
确认GPU可见之后,还要检查WSL2的内存分配。默认情况下WSL2最多使用宿主机物理内存的50%或者8GB(取较小者),这对跑vLLM来说太紧张了。大模型推理时模型权重、KV Cache、激活值都吃内存,显存不够还得往内存里溢,内存不够直接OOM。
在Windows用户目录下建一个.wslconfig文件(如果已存在就编辑它),写入如下配置:
[wsl2] memory=32GB processors=16 swap=16GB localhostForwarding=true这里的memory和processors按你机器实际配置来,建议memory给到物理内存的80%左右,swap给到20%~30%。注意这个配置只对WSL2虚拟机生效,改完必须wsl --shutdown再重新启动。
还有一个我建议你提前做的事:检查WSL2里的/dev/shm大小。vLLM的多进程调度和缓存机制非常依赖共享内存,默认情况下WSL2的/dev/shm只有物理内存的一半,跑并发高的推理场景很容易爆。可以在/etc/wsl.conf里加这么一段:
[automount] options = "metadata,umask=22,fmask=11"不过这个配置对/dev/shm的大小影响有限,更可靠的办法是在启动vLLM前显式挂载大一点的tmpfs:
sudo mount -o remount,size=16G /dev/shm每次重启WSL后这个挂载会失效,所以建议把上面这行加进shell的启动脚本里,省得每次手动敲。
3. 两种安装路线实测:pip轮子和源码编译各自怎么选
3.1 路线A:pip安装官方轮子
环境检查完,可以开始装了。绝大多数人走pip路线就够了,这也是我最推荐的方案。先用conda创建一个干净的Python环境,vLLM对Python版本有要求,目前官方轮子一般支持Python 3.10到3.12,我习惯用3.11,兼容性最好。
conda create -n vllm python=3.11 conda activate vllm然后直接装:
pip install vllm这个命令会顺带把PyTorch、transformers这些依赖一起拉下来。官方轮子会针对NVIDIA GPU自动选择对应的CUDA库,也就是说你不需要手动装CUDA Toolkit和cuDNN,这对WSL2用户特别友好——我之前提到的“WSL里不需要装驱动”,装vllm时又省掉了“装CUDA Toolkit”这一步,两个最麻烦的事都没了。
装完跑一个最简验证:
python -c "import torch; print(torch.cuda.is_available()); import vllm; print(vllm.__version__)"如果输出True和版本号,说明安装成功。如果torch.cuda.is_available()返回False,先别怀疑vllm安装有问题,回去看第2节的环境检查,大概率是GPU透传没通。
pip轮子的坑在于:它不一定总是最新版。vLLM迭代速度极快,有时候你需要的那个feature(比如新的量化格式支持、新的模型架构)还没出轮子,这时候才考虑源码编译。
3.2 路线B:源码编译安装
源码编译适合三种人:要改vLLM源码的、需要最新特性但轮子滞后的、以及被某个bug折腾到必须用致命性参数的。编译安装前需要额外准备这些:
sudo apt update sudo apt install -y build-essential cmake curl git然后装CUDA Toolkit。这里有个优先级经验:先看当前vLLM源码的requirements里要求哪个CUDA版本,再决定装什么版本。直接装最新CUDA不一定对,因为vLLM编译时会有版本匹配检查。我自己用的CUDA 12.x版本和PyTorch、vLLM的匹配度最高,踩坑最少。
CUDA Toolkit装完后,需要设置环境变量让编译器找到nvcc:
export PATH=/usr/local/cuda/bin:$PATH export LD_LIBRARY_PATH=/usr/local/cuda/lib64:$LD_LIBRARY_PATH接着克隆vLLM仓库并进入目录:
git clone 对应仓库地址 cd vllm pip install -e .编译过程中大概率会遇到内存不足的问题。vLLM编译时并行度很高,吃内存很猛,我可以给你一个有效的招数:设置环境变量ELECT enable_journal=0;不对,应该是限制编译并行度:
export MAX_JOBS=4把并行编译任务数限制在4个,虽然编译时间会长一些,但能避免编译到一半系统内存爆掉直接OOM。这个参数在C++扩展编译阶段尤其关键,我在这上面翻过车——一开始没设置,编译到大概70%的时候WSL直接卡死,然后被系统杀掉。
编译时间跟机器配置直接相关。我的机器(16核CPU)编译vllm主力版本大概花了将近半小时,如果配置低一点,一小时都正常。所以我的建议是:能用pip装就pip装,只有非不得已才编译。生活已经很苦了,没必要让编译浪费时间。
3.3 两条路线的取舍和混用技巧
我实际操作中有个比较实用的组合玩法:先用pip把vllm装好并且能跑起来,确认环境和模型输出正常。如果你之后想改vllm内部的某个模块来实验,再单独把这个模块对应的源码目录拉下来,用pip install -e单独重装这个子模块,而不是一次性全量编译。
这样可以省掉最痛苦的首次C++扩展全量编译过程。你在WSL2里做vLLM二次开发时,这个技巧能让你从“改一次代码等半小时”的抓狂里解脱出来。
4. 跑起来之后的坑:显存识别、共享内存与CPU Offload
4.1 坑一:WSL2里看到的显存为什么和Windows不一样
安装只是万里长征第一步,把vllm跑起来遇到的坑才是真正的考验。我踩的第一个坑就是WSL2里看到的显存信息跟Windows下完全不同。
在Windows里跑nvidia-smi,看到显存占用很低,空闲显存很多。但切到WSL2里跑nvidia-smi,发现显存已经被吃掉了一部分,而且这个占用是动态变化的。这是因为WSL2的GPU-PV方案下,Windows桌面、浏览器硬件加速、游戏录屏等所有用到显卡的程序都会在WSL2的“视角”里占用显存。也就是说,WSL2里看到的显存不是虚拟机的独占显存,而是和Windows共享的同一块物理显存。
这个特性对vLLM的影响挺大的。vLLM默认会读取显卡的总显存,然后按gpu-memory-utilization参数计算可用的KV Cache空间。在WSL2里,如果你把gpu-memory-utilization设成0.9(即使用90%显存),Windows那边的显存占用稍一多,vLLM分配显存时就会报CUDA out of memory,而实际上你感觉“明明还剩下很多显存啊”。
解决办法是:跑vLLM之前先在WSL2里跑一次nvidia-smi,看看当前已经占了多少显存,然后把gpu-memory-utilization调低一点,比如0.8或者0.85。我的经验是,Windows日常使用(浏览器+办公软件)大约会占1~2GB显存,如果你还挂着一个游戏或者视频渲染任务,这个数值会更高。保守起见,WSL2里跑vLLM我一般不敢把显存利用率调到0.9以上,0.8是比较稳的。
4.2 坑二:/dev/shm太小导致多进程莫名其妙崩掉
这第二个坑更隐蔽。vLLM在推理时不是单进程跑,它有调度器、模型执行器、tokenizer等多个并行组件,组件之间通过共享内存交换数据。如果/dev/shm空间不够,会出现各种不可名状的错误——有时候是启动时卡住不动,有时候是跑着跑着突然报“RuntimeError: Error allocating ”,有时候是模型加载到一半直接进程退出,log里连个像样的报错都看不见。
WSL2默认的/dev/shm大小只有宿主机物理内存的一半,比如你机器有32GB内存,WSL2里的/dev/shm就16GB。如果你用4bit量化加载一个7B模型,再配合较大的并发请求,共享内存很容易超过这个数。
我的处理方法是写一个启动脚本,每次进WSL2自动把/dev/shm扩到物理内存的75%:
sudo mount -o remount,size=${$(free -g | awk '/^Mem:/{print int($2*0.75)}')G} /dev/shm df -h /dev/shm这里用了当前物理内存的0.75倍来当共享内存上限,够用又不至于太浪费。重点还是那个:这个挂载重启后失效,一定要放进启动脚本,不然下次开WSL2又恢复到默认值。
4.3 坑三:CPU Offload和虚拟内存的交互陷阱
vLLM提供cpu-offload-gb参数,可以把一部分模型参数放到CPU内存里,显存不够时靠这个参数硬跑。我在WSL2里试了一次,效果可以说非常糟糕。
WSL2的内存分配是动态的,当你申请超过.wslconfig里memory设定值的内存时,WSL2会往swap文件里写。这意味着你明明把模型offload到“内存”了,实际上可能是在Windows的SSD上swap来swap去,推理速度直线下降——不是变慢一星半点,是直接从秒级响应变成几十秒甚至分钟的级别。
我的建议是:在WSL2里尽量不要用cpu-offload-gb。如果显存确实不够装下整个模型,优先考虑用更激进的量化方式(比如4bit量化替代8bit),或者换一个更小的模型。WSL2的虚拟化层对swap的惩罚比原生Linux更严重,这一步的性能损失不是你调几个参数能弥补的。
4.4 一次完整排查链路的复盘
有一次我的vllm服务启动到一半就挂,日志里只有“Error allocating”几个字。我当时的排查流程可以供你参考:
先用dmesg查看内核日志,发现没有明显的OOM记录。然后看/var/log/syslog和journalctl,也没有像样的错误。这时候我怀疑是共享内存问题,df -h /dev/shm确认已经满了。再一看,原来是我上一次会话里挂载的16G tmpfs在WSL重启后没有恢复,默认值只有8G,而这次加载的模型和KV Cache配置已经超过了8G。
整个修复过程花的也就一分钟,但如果不懂这个机制,光靠反复重装vllm可能一整天都找不到原因。这种“日志里没有线索”的故障最折腾人,排查思路最重要的一点是:先把/dev/shm、系统内存、显存这三个资源池的上限确认清楚,再看代码层面的事。资源不够导致的报错往往症状诡异,很容易误导排查方向。
5. 冒烟测试:延迟和吞吐量到底能不能打
5.1 启动一个最小服务
环境准备好了,坑也填得差不多了,接下来做冒烟测试。我用vLLM自带的OpenAI兼容接口,这样可以直接用标准HTTP请求来测,不需要引入额外的测试框架。
python -m vllm.entrypoints.openai.api_server \ --model /path/to/your/model \ --gpu-memory-utilization 0.8 \ --max-num-seqs 8 \ --port 8000第一次启动时vLLM会加载模型权重到显存,这个过程在WSL2里可能比原生Linux慢一些,毕竟多了一层GPU-PV的转发。看到“Starting vLLM API server on http://0.0.0.0:8000”字样就说明服务起来了。
有个细节要提醒一下:WSL2的网络是NAT模式,但vLLM监听在0.0.0.0时,Windows可以直接通过localhost访问到WSL2里的服务,不用额外做端口转发。我用curl测一下:
curl http://localhost:8000/v1/models能返回模型列表,说明服务正常。
5.2 用真实请求读吞吐和延迟指标
发一个简单的补全请求:
curl http://localhost:8000/v1/completions \ -H "Content-Type: application/json" \ -d '{"model": "/path/to/your/model", "prompt": "讲一个关于程序员的笑话", "max_tokens": 128}'看返回的JSON里的usage字段,主要关注total_tokens和completion_tokens。更关键的是在服务端的日志里看“Avg prompt throughput”和“Avg generation throughput”这两个指标,分别代表输入处理吞吐和生成吞吐。
我实测下来的感觉是:首Token延迟(TTFT)在WSL2里比原生Linux高10%~15%左右,但稳态吞吐量差别不大。原因是首Token延迟里有不少固定开销(上下文准备、调度初始化),这部分受虚拟化层影响更大;而生成阶段的连续推理,GPU的大部分时间都在跑compute kernel,虚拟化影响被摊薄了。
5.3 WSL2的性能损失到底有多少
我自己在一台显卡上跑过对照实验,WSL2和原生Linux分别跑同一个模型的相同请求,对比结果大致如下:
| 指标 | 原生Linux | WSL2 | 差异 |
|---|---|---|---|
| 首Token延迟(128 tokens输入) | 约120ms | 约140ms | +16% |
| 生成吞吐(输出token/秒) | 约35 tokens/s | 约33 tokens/s | -6% |
| 显存分配完成时间 | 约15s | 约18s | +20% |
| 并发16路请求时稳定性 | 稳定 | 偶发抖动 | 需要调参 |
上表仅代表我手头机器的实测数据,不同配置差异可能很大,但总体趋势是稳定的:交互式单请求场景,WSL2有明显但可接受的额外延迟;批量并发场景下,两者的差距会缩小。
这里顺带解释一下为什么并发上去后差距变小。并发请求多了以后,GPU的运算队列变满,调度器变成了瓶颈,而调度逻辑跑在CPU上,WSL2的CPU性能损耗不明显,所以整体吞吐反而拉近了。如果你主要场景是并发请求砸过来,WSL2完全能打;如果是单用户交互式对话,体感上WSL2比原生Linux慢了一丝丝,但一般人感知不到。
5.4 顺手做的最小调优
冒烟测试通过后,我建议你把下面三个参数调一调,能明显提升WSL2下的体验:
第一,max-num-seqs别设太大,默认是256,这在WSL2里会导致共享内存和显存碎片双重压力。我调到32甚至16,换来的是更稳定的延迟表现。
第二,gpu-memory-utilization按Windows侧的空闲显存来定,不是按显卡总显存定。前面讲了WSL2和Windows共享显存,这个数字定高了极容易在并发时OOM。
第三,如果你主要在Windows这边办公,可以给vLLM进程设置Nice值调低优先级,避免推理时把Windows桌面卡成幻灯片:
nice -n 10 python -m vllm.entrypoints.openai.api_server ...这三个参数做完,WSL2下的vLLM才算真正调顺了。看着它稳定输出,CPU占用正常,Windows桌面还能流畅操作,这个时候你才会真正觉得这个组合用起来不亏。
最后再分享一个小技巧:给WSL2分配完内存和CPU后别急着跑大模型,先跑个小模型把整个链路打通,验证GPU透传、性能指标、共享内存都在合理区间,再上大模型。这样出了问题能快速定位是环境问题还是模型配置问题。我自己每次重建WSL2环境后都按这个流程走,少踩了很多莫名其妙的坑。