把 vLLM 跑在 Windows 上,这件事本身就像在驯服一只不怎么听话的猛兽。vLLM 的设计目标从来就不是给 Windows 当原生公民的,它的内存管理、PagedAttention 调度、GPU 显存分配逻辑,全部是围绕 Linux 内核的 CUDA 生态来写的。但现实是,很多人的日常开发机就是 Windows,显卡是 RTX 4090 或 4080,内存 64GB,想本地跑 Qwen3-8B-FP8,又不想为了一个推理服务单独装一台 Linux 机器。这篇文章就是干这个用的,我会带你从零开始,把 Windows 上的 vLLM 部署跑通,加载 Qwen3-8B-FP8 模型,完成推理验证,并把手写梳理清楚,为什么用 WSL2、为什么选 FP8、Docker 和裸装哪个更省心,以及实测跑批处理时的显存和延迟表现。
如果你属于这几类人,这篇文章正好对你有用:想在本地做 LLM 应用开发、需要频繁调用开源模型做实验的算法工程师;没有 Linux 服务器但有高性能游戏显卡的独立开发者;以及那些看到别人在 Linux 上跑 vLLM 跑得很爽,自己却在 Windows 上折腾一周没起来的人。读完之后,你应该能自己复现一遍完整流程,并在你的机器上跑起来。
1. windows 上跑 vLLM 的真实处境:先明确难点在哪
vLLM 发布至今,官方压根没把 Windows 列为支持平台。这不是歧视,是技术上的一堆墙。我最早在 Windows 上试图直接 pip install vLLM 的时候,踩的坑一个接一个,从编译报错到 CUDA 工具链不兼容,最后连 Python 虚拟环境都快被我装废了。
1.1 为什么不建议原生 Windows 安装 vLLM
先说一个基本结论:不要在 Windows 原生环境下硬装 vLLM,尤其是带 GPU 算力的模型推理场景。vLLM 重度依赖三个东西:
- CUDA 12.x 及对应的 PyTorch 预编译包:PyTorch 在 Windows 下对 CUDA 的支持还算可以,但 vLLM 内部很多扩展算子(比如融合 Attention Kernel)是通过自定义 CUDA 扩展编译的,这个过程在 Windows 上非常脆弱。
- Linux 特有的内存映射机制:vLLM 的 PagedAttention 需要把显存和内存之间的页表映射做到极致,Windows 的虚拟内存语义和 Linux 的 mmap 语义有差异,导致性能达不到预期。
- NCCL 和通信库:如果未来你要跑多卡或多节点,Windows 下 NCCL 的支持根本不完整。
我第一次试的时候用的 Python 3.10 + CUDA 12.1,在 Windows 下 pip install vLLM,编译好了一个多小时之后,跑起来的瞬间直接报非法指令错误。那一刻我就明白,这条路走不通,别浪费时间。
1.2 WSL2 到底解决了什么问题
WSL2 的本质是一个轻量级虚拟机,它跑的是真正的 Linux 内核。对 vLLM 来说,关键点在于:
- 完整的 Linux 系统调用支持,包括 mmap、fork、NUMA 调度等。
- CUDA 通过 GPU Paravirtualization 直接透传到 WSL2 内部,性能损耗很低。
- 不需要单独装双系统,不需要额外准备一台 Linux 机器,日常还能继续用 Windows。
这个方案最舒服的地方是文件系统、网络都和 Windows 打通了。你可以在C:\workspace下写代码,然后在 WSL2 里直接访问/mnt/c/workspace。从模型路径到日志输出,两边共享,调试体验比纯远程 Linux 服务器还要好。
1.3 需要提前准备的硬件和软件清单
先说硬件底线。Qwen3-8B-FP8 模型权重大约 8-9GB,加上运行时激活值、KV Cache、CUDA context 等开销,显存建议 12GB 起步,16GB 更稳妥。我测试用的是 RTX 4080 16GB,运行 8B FP8 模型,上下文长度 4096,batch size 为 1 时完全没问题。如果你是 RTX 3070 8GB 或更低,运行这个模型会比较吃力,建议考虑 Qwen3-4B 或量化到 INT4 的版本。
软件清单如下:
| 组件 | 版本要求 | 理由 |
|---|---|---|
| Windows 10/11 | 21H2 以上,建议 Windows 11 | WSL2 支持更完善,GPU 透传更稳定 |
| WSL2 | 内核 5.10 以上,建议更新到最新 | 太老的内核可能存在 CUDA 兼容问题 |
| NVIDIA 驱动 | 550 或以上,建议 560+ | WSL2 下的 CUDA 透传依赖新版驱动 |
| CUDA(WSL2 内) | 12.1 或 12.4 | vLLM 预编译 wheel 对应的 CUDA 版本 |
| Python | 3.10 或 3.12 | vLLM 官方测试的主要版本 |
| vLLM | 0.6.x 或 0.7.x | 本文实测版本 0.6.6 稳定 |
2. 环境准备实战:从 Windows 到 WSL2 的完整链路
环境准备环节是最容易出问题的地方。我见过不少人在这一步反复折腾,不是因为操作复杂,而是因为每一步都有几个容易忽略的"坑"。下面按顺序来,每一步我都会告诉你为什么这样做。
2.1 启用 WSL2 并安装 Ubuntu 22.04
打开 PowerShell(管理员模式),执行:
wsl --install这条命令默认会安装 WSL2 和 Ubuntu。安装完成后重启电脑。如果之前你已经装了 WSL1,需要手动升级:
wsl --set-version Ubuntu-22.04 2确认版本:
wsl --status这里有个很容易被忽略的点:WSL2 的内核版本需要足够新,否则后面跑 CUDA 程序会出现奇怪的问题。建议执行一次:
wsl --update把内核更新到最新版本。
2.2 在 WSL2 里安装 NVIDIA CUDA 支持
WSL2 本身不直接装 NVIDIA 驱动,它用的是 Windows 宿主机上的驱动。但你需要在 WSL2 内部安装 CUDA Toolkit 来获得nvcc编译器和 CUDA 运行库。
进入 WSL2 终端:
wget https://developer.download.nvidia.com/compute/cuda/repos/wsl-ubuntu/x86_64/cuda-keyring_1.1-1_all.deb sudo dpkg -i cuda-keyring_1.1-1_all.deb sudo apt-get update sudo apt-get -y install cuda-toolkit-12-4装完验证:
nvidia-smi如果能看到和 Windows 下一样的 GPU 信息,说明 CUDA 透传已经正常工作。这一步非常关键,它决定了后面的所有流程能否跑通。
要注意的是,nvidia-smi显示的 CUDA 版本是驱动支持的版本,并不代表nvcc的版本。你需要单独确认:
nvcc --version如果你没装 CUDA Toolkit,nvcc大概率不存在,但 PyTorch 和 vLLM 依然可以用,因为它们自带了 CUDA 运行库。不过后续如果要编译自定义算子,没有nvcc会比较麻烦。我建议还是装上,一劳永逸。
2.3 配置 Python 虚拟环境
在 WSL2 里,我强烈建议使用venv或conda,不要让 vLLM 直接装到系统 Python 里。因为 vLLM 的依赖链很长,PyTorch、transformers、tokenizers 等版本都有微妙的要求,隔离环境可以避免污染系统环境。
我选择的是conda,主要理由是后续做实验时切换不同 Python 版本很方便:
wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh bash Miniconda3-latest-Linux-x86_64.sh conda create -n vllm python=3.10 -y conda activate vllm2.4 安装 PyTorch 与 vLLM
这里有一个版本对齐的问题。pip install vllm会自动把匹配的 PyTorch 拉进来,但如果你想手动控制版本,建议先装 PyTorch 再装 vLLM:
pip install torch==2.4.0 torchvision==0.19.0 --index-url https://download.pytorch.org/whl/cu124 pip install vllm==0.6.6我实测过 vLLM 0.6.6 和 PyTorch 2.4.0 + CUDA 12.4 的组合,稳定性是最好的。装完以后,快速验证一下:
python -c "import vllm; print(vllm.__version__)"如果能正常输出0.6.6,说明 vLLM 已经装好了。
3. 下载并加载 Qwen3-8B-FP8:模型文件管理的那些坑
Qwen3-8B-FP8 是通义千问团队发布的一个 FP8 量化版本,它和原版 Qwen3-8B 的区别在于,权重用 FP8 格式存储和计算,可以显著降低显存占用和推理开销。FP8 是 NVIDIA Hopper 架构(H100)引入的,但 40 系显卡(Ada Lovelace 架构)也支持 FP8 计算,只不过速度会比原生 FP16 慢一些,显存节省是实实在在的。
3.1 从 ModelScope 拉模型:国内网络环境下的实践
考虑到国内下载 HuggingFace 模型的不确定性,我建议直接用 ModelScope(魔搭社区)。ModelScope 是国内团队维护的模型社区,大量中文模型都有镜像,下载速度非常快。
安装 ModelScope 库:
pip install modelscope然后写一个简单的下载脚本:
from modelscope import snapshot_download model_dir = snapshot_download('Qwen/Qwen3-8B-FP8', cache_dir='/mnt/c/workspace/models') print(f'模型已下载到 {model_dir}')这里我把模型放在/mnt/c/workspace/models,对应的就是 Windows 的C:\workspace\models,这样 Windows 端的代码也能直接访问模型文件,两边不冲突。
下载完成后,检查一下目录结构,确认包含以下关键文件:
config.json:模型配置,包括层数、头数、量化方式等。model-00001-of-0000X.safetensors:分片后的权重文件。tokenizer.json和tokenizer_config.json:分词器文件。
3.2 用 Transformers 加载模型时的数据格式问题
Qwen3-8B-FP8 是一个量化模型,加载方式会被自动识别。但你需要注意config.json中的quantization_config字段,看一下quant_method是什么。如果写的是fp8或者fbgemm_fp8,说明模型用的是一种基于 PyTorch 的 FP8 量化方案,这可能需要较新版本的 transformers 才能正确解析。
在加载之前,一个比较稳妥的方式是直接看模型目录里的 README 或者 config 文件。如果 transformers 版本太旧,可能会把 FP8 权重错误地当作 FP16 加载,导致模型输出完全乱掉。这种情况我遇到过不止一次。
3.3 vLLM 对 FP8 模型的加载差异
vLLM 在加载 FP8 模型时有自己的逻辑。它的内部实现并不完全依赖 transformers 的from_pretrained,而是有自己的weight_loader来处理量化权重。
在 vLLM 中,加载 Qwen3-8B-FP8 的方式有两种:
方式一:使用LLM类直接加载
from vllm import LLM, SamplingParams llm = LLM( model="/mnt/c/workspace/models/Qwen/Qwen3-8B-FP8", tensor_parallel_size=1, gpu_memory_utilization=0.85, max_model_len=4096, trust_remote_code=False, )方式二:使用OfflineBatchInferencer或者 API Server
如果你是想部署成服务,直接启动 OpenAI 兼容的 API:
python -m vllm.entrypoints.openai.api_server \ --model /mnt/c/workspace/models/Qwen/Qwen3-8B-FP8 \ --served-model-name qwen3-8b-fp8 \ --port 8000 \ --gpu-memory-utilization 0.85 \ --max-model-len 4096启动成功后,你会看到终端打印出类似INFO: Started server process ...的日志,然后就可以用它了。
3.4 模型路径的三种写法与推荐选择
- 本地路径:
/mnt/c/workspace/models/Qwen/Qwen3-8B-FP8,推荐。加载速度快,依赖最少。 - ModelScope ID:
Qwen/Qwen3-8B-FP8,vLLM 0.6.6 本身支持从 ModelScope 拉取模型,但需要先设置环境变量。 - HuggingFace ID:
Qwen/Qwen3-8B-FP8,如果网络条件允许也能用,但国内速度不稳定。
我推荐你用本地路径,这是最省心的方式,还避免了很多网络相关的坑。
4. 性能实测与参数调节:显存占用、吞吐、延迟一次讲透
环境搭好、模型能加载,这只是第一步。真正决定你的部署体验的是参数调优。下面我用一组实测数据,帮你理解 vLLM 的几个关键参数。
4.1gpu_memory_utilization设多少合适
这个参数控制 vLLM 最多能占用多少比例的显存。它直接影响你能分配多少 KV Cache,而 KV Cache 大小决定了上下文长度和并发能力。
我的实测情况(RTX 4080 16GB,Qwen3-8B-FP8):
| 系数 | KV Cache 可用显存 | 最大上下文长度(约) | 并发请求数(128 tok/req) |
|---|---|---|---|
| 0.70 | 约 3.5GB | 4096 | 8 |
| 0.85 | 约 5.2GB | 8192 | 12 |
| 0.95 | 约 6.5GB | 8192+ | 16 |
注意,显存里不只是模型权重,还有 CUDA context、激活值、临时计算缓冲。如果gpu_memory_utilization设置太高,可能会导致 CUDA OOM。16GB 显存实测下来,0.85是一个比较稳的值。
4.2max_model_len和实际输出长度的关系
这个参数很多人会忽略。vLLM 的max_model_len决定的是 KV Cache 的预留上限,不是每次请求的实际长度。如果你设置了 8192,即使每次实际请求只有 512 token,显存也已经为 8192 预留了空间,这样就浪费了。
更好的做法是按实际需求设置。如果你的应用场景是短文本问答,平均输入 512 token、输出 128 token,那max_model_len=2048就绰绰有余了,你还能省下大量显存给更高的并发。
4.3 batched inference 与吞吐量
vLLM 的核心优势是 Continuous Batching,它能同时处理多个请求,并通过调度策略最大化 GPU 利用率。实测中,我用 50 个不同长度的请求做压力测试,结果很有参考价值:
- 单请求串行:每个请求延迟约 85ms(输出 128 token),但 GPU 利用率只有 30% 左右,吞吐量约 1500 token/s。
- 并发 10 个请求:每个请求延迟上升到 120ms,但吞吐量飙升到 4500 token/s,GPU 利用率达到 85% 以上。
- 并发 30 个请求:每个请求延迟约 200ms,吞吐量约 5200 token/s,接近显存和算力的综合上限。
如果你是在做服务部署,一定不要一次只发一个请求,要把请求并发起来,才能充分发挥 vLLM 的价值。
4.4 FP8 与 FP16 的性能差异:牺牲了多少速度
很多人关心 FP8 是不是比 FP16 快。答案是:不一定。在 RTX 40 系显卡上,FP8 的 Tensor Core 吞吐量理论上翻倍,但要真正吃到这个红利,需要硬件的 FP8 支持被软件充分调用。实测下来,Qwen3-8B-FP8 与 FP16 版的区别是:
- 显存占用:FP8 比 FP16 节省约 45%(因为权重减半)。
- 生成速度:FP8 大约比 FP16 慢 5%-10%,原因是 vLLM 在 FP8 下的算子融合优化不如 FP16 成熟。
- 输出质量:在常规测试集上,肉眼几乎看不出差异。
所以,选择 FP8 的唯一理由应该是显存不够,而不是追求速度。如果显存充足,FP16 反而更省心。
5. 常见踩坑记录:我在 WSL2 上跑 Qwen3-8B-FP8 时遇到的一堆问题
以下是我在被这个问题折磨几天之后总结的踩坑记录,每个都配了根因分析和解决办法。
5.1 CUDA error: out of memory / 显存明明够却报 OOM
这个报错很迷惑。明明已经设了 16GB 显存,但加载 8GB 权重的模型后,直接 OOM。根因是 vLLM 会预先为 KV Cache 分配指定比例的显存,如果你的gpu_memory_utilization设得过高,比如 0.95,同时max_model_len又设成 8192,在两块里算就会爆掉。
解决办法:调低gpu_memory_utilization到 0.80,或者调低max_model_len到 4096。试一次就知道,正常启动后日志里会明确输出 KV Cache 的大小。
5.2 tokenizer 加载慢 / 频繁联网
vLLM 加载 tokenizer 时,如果本地没有缓存文件,会自动去 HuggingFace 下载。如果网络不通,就会一直卡着。解决办法是提前配好环境变量:
export HF_ENDPOINT=https://hf-mirror.com或者在启动 API Server 时用--tokenizer /mnt/c/workspace/models/Qwen/Qwen3-8B-FP8/tokenizer.json直接指定本地路径。
5.3 WSL2 下 WebUI 或 Client 连接不到 API
常见的症状是:vLLM 启动正常,日志也显示监听在0.0.0.0:8000,但 Windows 浏览器访问http://localhost:8000/v1/models始终超时。
这个问题我查了很久,发现是 WSL2 的 localhost 转发在特定 Windows 版本或防火墙配置下失效了。解决办法有三个,按优先级排序:
- 在 WSL2 内确认服务监听地址:
ss -tlnp | grep 8000 - 如果监听的是
127.0.0.1,改成0.0.0.0重新启动。 - 如果是防火墙拦截,在 Windows 安全中心里放行 WSL 的流量。
5.4 vLLM 日志中文乱码
这个其实不影响功能,但影响排查。vLLM 在 WSL2 终端里打印日志时,会有一些 Unicode 字符显示成乱码。解决办法是强制 UTF-8 编码:
export PYTHONIOENCODING=utf-8 export LANG=C.UTF-86. 进阶折腾:为 Windows 场景定制更顺手的 vLLM 使用姿势
如果你已经把最基本的情况跑通了,可以继续看这个部分。这些都是我在实际使用中逐渐摸索出来的,能让你的日常体验舒服很多。
6.1 用 Docker Desktop 还是裸装
我在文章前面推荐的是裸装(直接在 WSL2 里 pip install)。但如果你的机器上已经装了 Docker Desktop,也可以直接用 vLLM 官方镜像:
docker run --runtime nvidia --gpus all \ -v /mnt/c/workspace/models:/models \ -p 8000:8000 \ vllm/vllm-openai:latest \ --model /models/Qwen/Qwen3-8B-FP8 \ --served-model-name qwen3-8b-fp8裸装的优势是调试方便,可以直接改代码、加日志;Docker 的优势是环境一致性好,换机器部署不用重新配环境。我的建议是:开发阶段用裸装,部署阶段用 Docker。
6.2 对接 OpenAI SDK 与 FastAPI 客户端
部署 vLLM 的 API Server 不只是为了手动测试,更关键的是要和你的应用代码对接。因为 vLLM 是 OpenAI 兼容协议,所以用 OpenAI SDK 就能直接访问:
from openai import OpenAI client = OpenAI( api_key="EMPTY", base_url="http://localhost:8000/v1" ) response = client.chat.completions.create( model="qwen3-8b-fp8", messages=[ {"role": "user", "content": "请用一句话介绍你自己。"} ], temperature=0.7, max_tokens=128 ) print(response.choices[0].message.content)如果你担心未来要切换回别人的 API,这个对接方式可以大大减少后续迁移成本。它意味着,你本地调试用的代码,可以无缝切换到 OpenAI 或其他兼容 vLLM 的服务去。
6.3 缓存优化:如何提高 KV Cache 的命中率
vLLM 有一个日志参数会统计 KV Cache 命中率。这个命中率越高,说明重复的请求前段被缓存得越好,生成延迟也就越低。我看热搜里有人专门关注"vLLM 如何优化大模型的缓存命中率",我分享三个实用技巧:
- 设置 prefix cache 功能:vLLM 0.6+ 默认支持
--enable-prefix-caching,能自动缓存公共前缀(比如长文档、固定的 Prompt 前缀),对多轮对话场景帮助很大。 - 固化 system prompt:在同一个服务实例中,如果所有请求共用同一个长系统 Prompt,把它固定在消息列表开头,vLLM 就能更高效地复用其对应的 KV Cache。
- 控制请求并发粒度:过多细碎的并发会把缓存打散,实测下,同一时刻并发 20 个相似请求,命中率比并发 100 个完全不同的请求高得多。
6.4 多轮对话场景下的显存释放策略
在配置自动释放策略时需要注意,虽然 vLLM 默认会在请求结束后自动释放 KV Cache,但如果你在做多轮对话,最佳实践是让 WebSocket 关联的对话上下文保持连续。vLLM 的 API Server 不主动保持对话状态,是否续传上下文完全由你的应用层控制,这一点和开发本地 demo 时感觉到的"默认有上下文"其实不同。
7. 写在最后的几条心得
用 Windows + WSL2 跑 vLLM 这件事,确实要比直接在 Linux 上装多绕几层弯,但总比装双系统、带两台电脑方便多了。整个流程走通之后,稳定性其实和纯 Linux 差距不大,至少我连续跑了一个月的 API Server,没有崩过一次。
最后分享几个我当时花了不少时间才确认的细节,你照着做可以少走弯路:
- 尽量保持 WSL2 和 Windows 的 NVIDIA 驱动都更新到最新,两个系统下的驱动版本一致性直接影响 CUDA 兼容性。
- 模型文件放 Windows 盘(
/mnt/c/)虽然方便,但首次加载速度会明显慢于放 WSL2 内部文件系统(~/models)。如果文件不经常移动,建议放 WSL2 内部。 - 不要禁用 WSL2 的自动内存回收,否则长时间跑服务后,WSL2 的内存占用会攀升到 Windows 整体卡顿。
- 定期清理
~/.cache/huggingface和~/.cache/modelscope下的缓存,这些文件动不动就十几个 GB。
我没有用 Docker 方式跑生产服务之前,以为裸装就是最优解;后来发现,把镜像打包好之后,换机器部署是真的省心。两种方式各有适用场景,可以都试一下再选。
用 vLLM 跑 Qwen3-8B-FP8 只是入了个门,后面你可以尝试做函数调用、做长文本 embedding、甚至接入自己的 RAG 链路。希望这篇文章能帮你在 Windows 上少踩几个坑,有别的部署问题,欢迎评论区交流。