vLLM 快速上手教程:PagedAttention 与吞吐优化实战
2026/9/10 19:02:50 网站建设 项目流程

我一直觉得,搞大模型部署的人,早晚都得跟 vLLM 打交道。不管你是跑开源模型做验证,还是要把模型落地成线上服务,吞吐上不去、显存爆掉、首字延迟高,这些坑我基本都踩过一遍。vLLM 厉害的地方在于,它把 PagedAttention、continuous batching、量化推理这些优化全揉进了一个框架里,你不用自己从底层去改,装好、配好参数就能把 GPU 的潜力榨出来。

这篇算是我的 vLLM 教程系列第四篇,专门讲怎么快速上手。我不会跟你从分布式推理讲起,也不扯太深的源码分析,就是给你一条我自己实测下来最顺的路径:环境准备、装库、跑一个模型、起一个 OpenAI 兼容的服务、把性能调一调,最后列一些新手最容易踩的坑。这部分内容适合刚接触 vLLM 的人,也适合已经能跑通但想搞清楚参数背后逻辑的兄弟。读完之后,你应该能自己从零部署一个可以对外提供服务的模型接口,而不只是照抄命令行。

1. 动手之前,先把这三件事搞清楚

1.1 vLLM 到底帮你解决了什么问题

先说个很实在的背景。Transformer 模型推理其实是个“内存墙”问题。模型参数量大,KV cache 也大,传统的推理框架在处理请求时,预先给每一条请求分配一整块连续的显存空间,但请求的实际长度是不知道的,往往导致预留空间要么不够用、要么大量浪费。并发一高,显存直接爆掉。

vLLM 的核心思路是 PagedAttention,借鉴了操作系统的虚拟内存分页管理。KV cache 不再需要连续存储,而是拆成多个 block,按需分配。这个设计带来两个直接收益:显存利用率大幅提高,请求之间还能共享前缀缓存。再加上 continuous batching,也就是一个请求跑完生成就立刻“插队”下一个 token 的任务进来,GPU 几乎不会闲着。

所以,如果你要部署大模型并且考虑性能,vLLM 基本是绕不过去的基础设施。

1.2 硬件和软件环境到底要满足什么

我自己做测试常用的配置是:一张 24GB 显存的 RTX 3090 或 A10,跑 7B 到 14B 的模型;如果只是做 API 开发调试,8GB 显存也能跑小尺寸模型,但生成速度和并发上限就别抱太大期望。vLLM 当前版本对 CUDA 有要求,一般 NVIDIA 驱动建议 535 以上,CUDA toolkit 可以不用单独装,因为 vLLM 的 wheel 包通常会绑定好对应的 CUDA runtime,但 PyTorch 版本必须匹配。

操作系统上,Ubuntu 20.04 / 22.04 最省心,Windows 原生支持还是差点意思,虽然有 Windows 版本,但我更推荐你在 WSL2 里跑,或者直接用 Docker。很多人在 Windows 上装 vLLM 遇到 dll 缺失、NCCL 初始化失败之类的问题,十有八九是环境隔离没做好。

提示:如果你用的是 Jetson 这类 ARM 设备,安装方式完全不同,要去找官方为 JetPack 编译的 wheel,直接 pip install vllm 大概率会失败。

1.3 快速看看自己的 GPU 能不能跑

不需要跑任何脚本,直接看两个数就行。一个是显卡显存,一个是模型参数量。以 FP16 精度为例,一个 7B 模型光权重就要占 14GB 显存,再加上 KV cache 和激活值,24GB 勉强够用;如果你要跑 70B 模型,单卡基本无望,要么多卡张量并行,要么做 AWQ/GPTQ 量化把权重大幅压缩。

显存估算有个粗略公式:

模型权重显存 ≈ 参数量(以B为单位) × 2(如果是FP16),也就是 1B 参数约等于 2GB。 额外预留 4GB 到 8GB 给 KV cache 和推理开销,根据你的并发数和序列长度动态调整。

把这步算清楚,你就知道自己该选什么部署方案了。

2. 环境准备与 vLLM 安装全记录

2.1 用 Docker 还是裸机环境

我的习惯是,本地做实验直接建 Python 虚拟环境,项目上线直接上 Docker。两种方式我都列一下。

如果你在干净的 Linux 服务器上,裸机安装其实不麻烦:

python -m venv vllm-env source vllm-env/bin/activate pip install --upgrade pip pip install vllm

第一次安装会拉不少依赖,比如 torch、transformers、xformers 这些,时间取决于网络,通常 5 到 10 分钟。装完验证一下:

python -c "import vllm; print(vllm.__version__)"

能打印出版本号,说明核心库装好了。

如果你是生产环境或者是想省去 CUDA、驱动各种乱七八糟的环境问题,Docker 是最稳的路:

docker pull vllm/vllm-openai:latest docker run --runtime nvidia --gpus all \ -v ~/.cache/huggingface:/root/.cache/huggingface \ -p 8000:8000 \ --ipc=host \ vllm/vllm-openai:latest \ --model Qwen/Qwen2.5-7B-Instruct

这个镜像把依赖全都打包好了,你只要把模型目录挂载进去就行。注意--ipc=host这个参数,很多人漏掉它,结果多进程数据共享的时候出现莫名其妙的内存错误。

2.2 从 Modelscope 下载模型的坑

国内网络环境拉 Hugging Face 模型经常超时,我的建议是直接用 Modelscope。ModelScope 上有大量开源模型的镜像,下载速度快得多。vLLM 本身没有直接把 Modelscope 集成进去,所以你需要在启动脚本里先手动下载模型,然后指定本地路径。这个过程不复杂:

from modelscope import snapshot_download model_dir = snapshot_download('Qwen/Qwen2.5-7B-Instruct') print(model_dir)

更彻底的办法是设置环境变量,让 vLLM 启动时默认走 Modelscope:

export VLLM_USE_MODELSCOPE=True

有了这个环境变量,你甚至可以直接把模型的 Modelscope 路径传给--model参数。vLLM 启动时如果发现本地没有缓存,就会自动从 Modelscope 拉取。这个方式对国内用户特别友好,能省去一堆代理配置的麻烦。

注意:VLLM_USE_MODELSCOPE 这个环境变量依赖 modelscope 库存在,pip install modelscope记得先装上。如果你用的镜像里没内置,启动会直接报找不到模型的错。

2.3 常见安装报错,能避开就避开

我遇到过很多次安装阶段的坑,列几个高频的:

  • Torch 版本不匹配:vLLM 对 PyTorch 版本有严格的要求,装新不用旧。如果你环境里已经有旧版 torch,pip 在解析依赖时可能会保留旧版导致冲突。建议在干净的虚拟环境里重装。
  • CUDA 版本检查不过:vLLM 的 wheel 是按 CUDA 12.1 / 12.4 等版本编译的,你需要确认 nvidia-smi 显示的驱动支持对应版本。驱动太老的话,会提示 libcuda.so 找不到。
  • 显存不足不是安装问题:首次运行 vLLM 示例时如果 OOM,不要怀疑代码,去查你的并发数和 max-model-len,减下来基本就好了。

3. 两条路跑通第一个模型

3.1 用 Python API 快速验证推理

在写任何服务之前,先用 Python API 把模型加载起来,确认模型本身没问题。这是排错最快的方式。我通常会在项目根目录放一个quick_test.py,内容很精简:

from vllm import LLM, SamplingParams llm = LLM(model="Qwen/Qwen2.5-7B-Instruct", gpu_memory_utilization=0.9) sampling_params = SamplingParams( temperature=0.7, top_p=0.8, max_tokens=256, ) prompt = "用一句话解释什么是大语言模型" outputs = llm.generate([prompt], sampling_params) for output in outputs: print(output.outputs[0].text)

这里有个参数我要单独拿出来说:gpu_memory_utilization。它表示 vLLM 最多占用多少比例的显存。默认值是 0.9,如果你的卡上还要跑别的进程,就得调低一点,比如 0.6 或者 0.7。设得太高会直接 OOM,设得太低则 KV cache 空间不够,并发一上来就报错。建议按自己的实际负载来调,没有万能值。

跑完这段脚本,如果终端里能正常打印出模型回复,说明推理流程通了。这时候你还可以顺手看下打印的日志,里面会有显存使用、加载时间、吞吐量这些指标,可以作为后续调优的基线。

3.2 用命令行动态测试推理效果

vLLM 也提供了一个 CLI 方式,不用写 Python 代码就能直接测试模型。这种方式对快速换不同模型做对比非常方便:

vllm serve Qwen/Qwen2.5-7B-Instruct --port 8000

启动后它会监听 8000 端口。然后你在另一个终端用 curl 发请求:

curl http://localhost:8000/v1/completions \ -H "Content-Type: application/json" \ -d '{ "model": "Qwen/Qwen2.5-7B-Instruct", "prompt": "中国大模型的优势是什么?", "max_tokens": 200 }'

正常返回的 JSON 里会包含 choices 字段,里面就是模型生成的文本。如果你想测试对话模型,就用/v1/chat/completions,请求体稍微不一样:

curl http://localhost:8000/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "Qwen/Qwen2.5-7B-Instruct", "messages": [ {"role": "user", "content": "你好,介绍一下你自己"} ], "max_tokens": 200 }'

这个接口最大的价值是它完全兼容 OpenAI 的 API 格式。也就是说,你原来代码里用的是openai这个 Python 库,只要把base_url改成http://localhost:8000/v1,其他代码几乎不用动。这意味着你之前封装的业务逻辑、prompt 模板、流式请求,全部可以直接复用,迁移成本非常低。

3.3 模型格式选型:原生权重、AWQ、GPTQ 还是 FP8

跑通基础推理之后,你很快就会面对一个新的选择:用哪种格式的模型权重。

原生 HF 权重(FP16/BF16)是最通用的,直接--model指定路径即可,兼容性最好。缺点是占显存高,7B 模型至少 14GB,卡一紧张就跑不动。

AWQ 和 GPTQ 是量化格式,4bit 量化后 7B 模型权重只有 4GB 左右,vLLM 原生支持加载。启动时加参数:

vllm serve TheBloke/Qwen2.5-7B-Instruct-AWQ --quantization awq

如果 GPTQ 也是一样:

vllm serve TheBloke/Qwen2.5-7B-Instruct-GPTQ --quantization gptq

FP8 格式适合 H100、L40S 这些支持 FP8 计算的卡,显存占用和速度和 FP16 相比优势明显,但注意部分老卡不支持,要提前确认。

我的个人建议是:线上服务优先考虑量化格式,本地调试用原生权重最省心。量化后的质量损失通常很小,但显存省出来的空间可以让并发翻倍,幅度非常大。

4. 把服务跑起来,参数这样调才靠谱

4.1 启动参数的优先级与选择逻辑

vLLM 的参数非常多,新手最容易犯的错就是把参数挨个全调一遍,结果每个参数都是默认值,真正影响性能的没改。我建议你按这个优先级来:

第一梯队:--model--tensor-parallel-size--gpu-memory-utilization。这三个直接决定了能不能跑起来。模型不指定肯定不行;多卡并行不设就用单卡,大模型直接 OOM;显存利用率不调就会默认 0.9,小显存卡容易满。

第二梯队:--max-model-len--max-num-seqs--enforce-eager。这三个决定了并发能力和显存开销。max_model_len默认值往往很大,比如 32768,如果你的应用只需要 4096,建议手动手动设小,因为 KV cache 会按这个长度预分配。这是很多人遇到“明明模型不大但显存一直很紧”的核心原因。

第三梯队:--quantization--dtype--trust-remote-code。这些是跟模型格式、精度相关的开关,需要根据你下载的权重来定,不能随意乱设。

我贴一个实际生产环境常用的启动命令,供你参考:

vllm serve Qwen/Qwen2.5-7B-Instruct \ --tensor-parallel-size 1 \ --gpu-memory-utilization 0.92 \ --max-model-len 8192 \ --max-num-seqs 64 \ --port 8000 \ --host 0.0.0.0

这个配置在单张 24GB 显存的卡上能跑到一个不错的吞吐水平。max-num-seqs表示最多同时处理的序列数,设太大不会自动压垮显存,因为 vLLM 会按显存动态调度;但它会影响排队策略,设得太小可能导致 GPU 空闲。

4.2 并发数、batch size 与显存的关系

很多人不理解 vLLM 为什么吞吐高,就是因为它会把并发请求动态拼成一个大 batch 一起算。你每次请求进来,vLLM 不会像传统方案那样排着队一个个处理,而是同时处理很多请求,在 GPU 上并行计算,单位时间的 token 产出自然就上去了。

但这里有个平衡:batch 越大,每个 sequence 的 KV cache 占用总和越大。当显存接近上限时,vLLM 会主动拒绝新的请求,返回 429。所以你在压测的时候如果看到一堆 429,不要去调超时时间,而是去看显存余量,把max-num-seqs调小,或者减少max-model-len

另一个经验:如果追求并发能力,优先调gpu-memory-utilization,给 KV cache 留越多空间,能容纳的并发序列就越多。但千万别设成 1.0,因为 PyTorch 和 CUDA context 本身也要占显存,设成 0.95 都可能直接 CUDA OOM。我用 0.92 是比较稳的。

4.3 借助缓存机制提升命中率,响应速度翻倍

vLLM 的另一个隐藏优势是 prefix caching,也就是自动前缀缓存。它会把已计算过的 token 的 KV cache 保存下来,当新的请求和之前请求有相同前缀时,直接复用,跳过重复计算。

这个机制对多轮对话特别有用。因为多轮对话里每次你都要把历史消息拼到前面,前缀基本不变,vLLM 会自动命中缓存,首 token 延迟会大幅下降。

想开启这个能力,启动时加上:

--enable-prefix-caching

有了这个开关,你可以在日志里看到Prefix cache hit rate这个指标,命中率越高,说明越多的重复计算被跳过了。

如果你在做 RAG 类的应用,把固定的 system prompt 做得足够长且不频繁变动,这个缓存命中率会非常漂亮。相反,如果你每个请求都把历史聊天记录全部换掉,那缓存基本没意义。

注意:prefix caching 会占用额外显存来存储历史 block,所以要把 KV cache 空间留足。开启了之后如果出现 OOM,优先把 max-model-len 降下来。

4.4 不做推理性能测压等于白部署

部署完了别急着上线,先做一轮简单的压测,确认瓶颈在哪里。vLLM 官方带了一个 benchmark 脚本,非常好用:

python benchmarks/benchmark_serving.py \ --backend vllm \ --model Qwen/Qwen2.5-7B-Instruct \ --endpoint /v1/completions \ --num-prompts 100 \ --max-concurrency 20 \ --request-rate 10

这个脚本会统计吞吐量、延迟分布、TTFT(time to first token,首 token 延迟)、TPOT(time per output token,每输出一个 token 的时间)这些核心指标。

我一般只看三个数:

  • Throughput (output tokens/s):代表总产出速度,越高越好。
  • TTFT 中位数:低于 500ms 属于正常,超过 1s 就要看看是不是前缀缓存没命中。
  • TPOT:每个 token 的生成速度,约等于你感知到的“打字机”速度,越低越流畅。

如果你测出来吞吐远低于预期,常见原因是max-num-seqs太小导致并发批不起来,或者max-model-len太大导致 KV cache 碎片化、可用块变少。把这两个参数摆到一起观察,基本能找到问题。

5. 常见坑与排查经验

5.1 常见报错速查表

我把自己实际遇到的问题整理成了一个速查表,你碰到类似情况可以直接对照处理。

报错信息原因处理方式
CUDA out of memory显存不足,可能是模型太大或 max-model-len 太大降低 gpu-memory-utilization,缩短 max-model-len,或换量化模型
Error in model execution: RuntimeError: NCCL error多卡通信异常检查显卡之间的 NVLink 或 PCIe 连接,设置NCCL_P2P_DISABLE=1试试
AssertionError: tensors are on cuda and host多进程/多线程数据拷贝异常启动容器时加上--ipc=host,或者减少 num-workers
The model's max seq len is larger than the maximum number of tokens输入长度超过模型限制裁剪文本或增加 max-model-len
ValueError: Unknown quantization method: gptq量化参数不匹配确认模型仓库里的量化方式,传对应的--quantization
ModuleNotFoundError: No module named 'vllm._C'vLLM 安装不完整重装对应版本的 wheel,确认和 torch 版本匹配
bitsandbytes 相关报错某些量化方式依赖此库加载失败vLLM 官方对 bitsandbytes 支持有限,建议直接用 AWQ 或 GPTQ
openai error: model not found请求中的 model 名称和启动参数中的不一致请求体里的 model 字段必须和--model保持一致

5.2 显存规划与 OOM 的排查思路

遇到 OOM,第一件事不是改代码,而是搞清楚显存去了哪里。我提供一个很实用的排查链条。

先看模型权重占了多少。如果是 FP16 的 7B,那大概 14GB;如果是 13B,那接近 26GB,单卡 24GB 基本没戏。再看 KV cache。vLLM 启动时会打印 KV cache 的 block 数量和每个 block 的大小,这些信息在日志里都能找到。最后看 CUDA context 和运行时占用,这部分通常 1 到 2GB。

如果已经 OOM,优先调整顺序是:降低max-num-seqs→ 降低max-model-len→ 降低gpu-memory-utilization→ 换量化模型。不要一上来就换小模型,那样影响精度。

我见过最多的情况是:模型只有 7B,显存也够,但max-model-len默认 32768,导致 KV cache 预分配了十几 GB,其他请求全部被拒。把max-model-len调成 4096 或 8192,问题直接消失。

5.3 关于 Windows 和 Modelscope 的现实建议

很多读者会私信问 Windows 能不能跑。我的答复是:能跑,但不推荐。vLLM 在 Windows 上依赖 CUDA 的必要库和编译工具链,即使能用,性能也不如 Linux。如果你只有 Windows 环境,优先考虑 WSL2,或者用云 GPU 实例跑,比自己折腾省太多时间。

关于 Modelscope 我再强调一句:虽然VLLM_USE_MODELSCOPE=True很方便,但同一时间只建议配一个模型源。如果你本地已经下载过 Hugging Face 的缓存,又打开了 Modelscope,vLLM 会优先去 Modelscope 的缓存目录找,反而会触发重复下载。建议二选一,路径清晰才不会乱。

5.4 序列长度和 padding 的一些细节

有些模型需要 padding,比如 embedding 模型。如果你用 vLLM 跑完 BGE-M3 这类 embedding 模型的 API,记得在请求里传truncate_prompt_tokens参数。这个参数的作用是超长时自动截断,否则会报输入长度溢出的错。跟 vLLM 0.28 相关的版本里,BGE-M3 的加载参数有过一些调整,遇到报错时多看一眼官方 release note 比乱猜更高效。

另外,如果你为了优化显存,自己把输入统一 padding 到固定长度,这个操作在 vLLM 里其实是多余的。vLLM 内部有自己的 batching 策略,外部 padding 只会浪费算力和带宽。直接把原始长度发过去就好。

结语(个人体会)

vLLM 这个框架,说实话,我已经离不开它了。每次拿到一张新显卡,第一件事永远是装 vLLM,跑一遍 benchmark,看看这张卡在当前模型下能压出多少吞吐。它能火起来不是没有原因的——你不需要是内核开发者,也不需要精通 CUDA,只要把几个关键参数理解透彻,就能把一个开源模型调教成有实用价值的高并发服务。

在这篇文章里,我尽量把最容易挡住新手的东西都讲清楚了。从环境准备、安装、跑通推理,到最后调参和压测,你照着走一遍,每个环节中出现的问题都能从上面的速查表里找到答案。当然,vLLM 的迭代速度非常快,版本之间参数和功能差异不小,你安装新版本时最好瞄一眼官方的 changelog。我的建议是:不要盲目追新,选一个自己验证过稳定的版本,把它的参数吃透,比什么都强。

最后分享一个小技巧:如果你是多卡环境,先从单卡跑通再上多卡,不要一开始就上 tensor-parallel。多卡的通信开销和显存分配策略跟单卡完全不一样,先走通单卡链路,多卡只是加一个参数的事。踩过几次坑之后你会发现,vLLM 上手其实就这么简单。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询