vLLM高吞吐推理实战:PagedAttention与连续批处理优化GPU瓶颈 📅 发布时间:2026/9/2 10:27:16 👁 浏览次数: 如果你的本地大模型服务经常出现请求排队、首字卡顿、并发一高就显存溢出先别急着换显卡——大多数时候是推理框架的调度方式没选对。vLLM 是目前社区里使用最广的高吞吐推理引擎之一。它把大模型推理时最占显存却最容易被浪费的 KV Cache 管理方式彻底改掉靠 PagedAttention 和连续批处理把 GPU 算力压干。同时提供 OpenAI 兼容 API意味着你之前写的openai客户端代码只需要改一个base_url就能切到本地模型。本文会从 vLLM 的原理拆起讲清 PagedAttention 和连续批处理到底解决了什么然后给出一套可直接复用的 Python 部署、接口调用、并发测试和故障排查流程。适合想把开源模型部署成高并发服务的后端开发者、算法工程师也适合已经用 Ollama/Transformers 跑过模型、但觉得吞吐不够的同学。1. 核心能力速览能力项说明项目类型大模型推理引擎 / 高吞吐推理服务框架核心机制PagedAttention分页注意力、连续批处理Continuous Batching、OpenAI 兼容 API支持模型常见开源 LLM 文本模型以及部分多模态/嵌入模型需结合 vLLM 官方文档确认适用模型规模从 7B/8B 到几十 B 或更大规模取决于单卡显存或多卡张量并行推荐硬件NVIDIA GPU 优先显存规格由“模型权重 KV Cache 激活值”共同决定需实测是否支持 CPUvLLM 也提供后端用于 CPU 推理但高吞吐场景优先 GPU是否支持多卡支持通过张量并行tensor-parallel-size在单机多卡上切分模型启动方式命令行vllm serve也可用 Docker 镜像是否支持 API支持 OpenAI 兼容接口/v1/models、/v1/chat/completions、/v1/completions等是否支持批量任务支持配合并发客户端可提升吞吐服务端按请求级调度监控能力可通过--enable-metrics暴露 Prometheus 指标默认日志会输出吞吐数据一键启动社区有集成项目但官方标准方式是命令启动适合场景统一模型网关、Agent 工具链后端、私有知识库问答、批量内容生成2. 为什么传统推理框架会“越用越慢”传统的 Transformers 库推理常见两种状态单请求逐个推理或者静态 batch 推理。前者浪费 GPU 算力GPU 利用率很低后者虽然吃满显存但 batch 是固定大小的只要 batch 里还有一个请求没跑完整个 batch 都占着显存和算力新请求必须等下一批。LLM 推理是一个自回归过程每个 token 都要依赖之前生成的 token。生成过程中需要把历史上所有 token 的键值向量缓存下来这就是 KV Cache。KV Cache 的大小和序列长度成正比长上下文时非常吃显存。传统框架为了省事通常会根据 max_length 或某个估算值给每个请求预分配一大块连续显存但实际生成长度往往用不满这部分预留显存就浪费了。更麻烦的是不同请求的长度不一致预分配的碎片会让显存出现大量空洞GPU 显存明明没满却塞不进新的请求。vLLM 的两个核心机制就是冲着这两个问题去的PagedAttention 解决 KV Cache 的分配与碎片问题连续批处理解决静态 batch 的调度浪费问题。这两点叠加在长上下文、高并发场景下吞吐提升非常明显。部分公开 benchmark 中vLLM 相对朴素实现的吞吐提升可以达到数倍到 8 倍以上但具体数字受模型、显卡、批参数、输入输出长度影响很大不要拿标题中的“8 倍”当万能结论。3. PagedAttention用操作系统的思路管理显存写操作系统的同学对“分页”应该很熟。vLLM 的 PagedAttention 借鉴了操作系统的虚拟内存和页表思想把连续显存切成固定大小的 block页每个 block 默认存若干个 token 的 KV Cache。请求的 KV Cache 不再要求一整块连续显存而是一页一页按需分配然后用一个“页表”把它们串起来。这样做有几个直接收益显存按需分配请求生成多少 token 就分配多少页不再因为“预留一整块连续显存”而浪费空间。KV Cache 可以存放在非连续显存中碎片能被重新利用显存利用率大幅提高。同一条序列生成长度不断增加时只需要申请新页旧的页可以复用。注意力计算时通过页表索引找到对应的 key、value计算逻辑统一性能开销可控。简单说PagedAttention 让“显存里还剩很多碎片空间但新请求进不来”这种情况基本消失。这也是 vLLM 能同时容纳更多并发请求的关键前提。实际部署时常见做法是设置--gpu-memory-utilization例如 0.85 到 0.95让 vLLM 把大部分显存拿来做 KV Cache。4. 连续批处理吃掉闲散算力传统静态批处理请求先堆积凑够 batch size 才推理batch 内所有请求必须一起到结束。长请求会拖慢短请求短请求跑完了也要等长请求GPU 每次都跟着最慢的那个请求一起结束算力白白浪费。连续批处理Continuous Batching把调度粒度从“请求”降到了“解码步”。每个 step 结束后vLLM 会检查哪些请求已经生成完毕立即释放它们的显存和算力。有没有新请求排队可以立刻插入下一个 step。这样短请求快速完成退出长请求继续生成新请求随时插队GPU 每个 step 都在尽最大可能满负荷运行。整个过程由 vLLM 的调度器完成不需要调用方关心。这也是 vLLM 高并发吞吐的核心来源。需要补充一点连续批处理对单个请求的延迟影响不大它提升的是整机吞吐。如果你只有一个串行请求vLLM 的“快”更多体现在显存复用和长上下文管理上而不是单请求推理时间。想让 vLLM 发挥优势一定要用并发去打。5. 本地部署环境准备与前置条件vLLM 是 Python 生态安装和部署都比较直给。部署前先确认几件事5.1 硬件GPUNVIDIA GPU驱动版本要能支持 CUDA 11.8 或更高。显存建议根据模型规模来8B 模型建议 16G 以上量化版可以更低一些更大的模型建议 24G 或使用多卡张量并行。注意具体显存占用必须实测模型权重、max-model-len、并发数都会影响 KV Cache 占用。内存建议 32G 以上模型加载和 tokenizer 预处理都需要内存。硬盘模型文件按参数量算8B FP16 权重约 16G 左右量化版更小。需要预留模型文件 运行日志的空间。5.2 操作系统与软件Linux 优先Ubuntu 18.04 / 20.04 / 22.04 都是常见选择。Windows 上 vLLM 的官方支持较弱更稳妥的做法是 Linux WSL2 CUDA 环境。Python 3.9 到 3.12具体以 vLLM 官方文档为准。CUDA Toolkit 对应版本的 PyTorch。如果使用 Docker需要 NVIDIA Container Toolkit。5.3 检查清单# 检查显卡驱动 nvidia-smi # 检查 Python 版本 python --version # 检查 CUDA 是否可用PyTorch 装好后 python -c import torch; print(torch.cuda.is_available())如果nvidia-smi都跑不出来先处理驱动再继续 vLLM 安装。5.4 模型来源本文用 Qwen3 系列作为示例你也可以换成 Llama、DeepSeek 或其他支持的模型。模型名需要按 Hugging Face 上的仓库标识填写例如Qwen/Qwen3-8B这类形式需要以你下载的模型名为准。首次启动时 vLLM 会自动下载权重如果不想走公网下载可以先把模型下载到本地目录然后通过--model /path/to/model指定本地路径。下载模型建议用 huggingface-cli 或 modelscope 的工具这部分工具与推理框架解耦按自己的网络环境选择。6. 安装 vLLM 与启动 Qwen3 服务6.1 安装 vLLM建议创建独立虚拟环境避免依赖冲突python -m venv .venv source .venv/bin/activate pip install --upgrade pip pip install vllm安装后检查版本确认 vllm 已进入当前环境vllm --version如果是 Docker 部署官方镜像可以按文档拉取。注意镜像 tag 与你的 CUDA 驱动保持兼容具体以官方 Docker Hub 为准。6.2 启动一个 OpenAI 兼容的服务下面以 Qwen3-8B 为例启动一个服务vllm serve Qwen/Qwen3-8B \ --served-model-name qwen3-8b \ --host 0.0.0.0 \ --port 8000 \ --gpu-memory-utilization 0.9 \ --max-model-len 8192参数说明--served-model-name对外暴露的模型名之后调用 API 时用这个名字。--host监听地址。只在本地测试可以填127.0.0.1要开放给局域网或容器内使用再考虑0.0.0.0同时要做好访问限制。--port端口默认 8000。如果被占用换一个。--gpu-memory-utilization允许 vLLM 使用的显存比例上限。如果显卡上还跑着别的任务建议调低。--max-model-len最大上下文长度。显存不够时优先调低这个值。--tensor-parallel-size多卡场景例如2表示用 2 张卡跑张量并行。启动日志中出现类似Starting vLLM server、Uvicorn running on http://0.0.0.0:8000的信息说明服务已经起来。此时打开浏览器访问http://127.0.0.1:8000/docs可以看到 Swagger 风格的接口文档这是验证服务是否正常的简单方法。6.3 Docker Compose 部署模板如果你更习惯容器化部署这里给一个 docker-compose 模板。注意镜像名、版本号和挂载路径需要按实际项目调整services: vllm: image: vllm/vllm-openai:latest runtime: nvidia environment: - HUGGING_FACE_HUB_TOKEN${HF_TOKEN} command: - --model - Qwen/Qwen3-8B - --served-model-name - qwen3-8b - --port - 8000 ports: - 8000:8000 volumes: - ./models:/root/.cache/huggingface启动docker compose up -d这个模板只演示基础结构生产环境还需要根据显卡、权限、数据卷和日志收集做细化。6.4 验证服务是否正常先看模型列表curl http://127.0.0.1:8000/v1/models返回 JSON 里包含 model id说明 API 服务已经正常暴露。7. OpenAI 兼容 API 与 Python 调用实战7.1 为什么是 OpenAI 兼容 API接口兼容最大的价值是“迁移成本几乎为零”。你之前用openai库写的代码只需要把base_url改成 vLLM 服务的地址把api_key随便填一个占位值就能切到本地模型。LangChain、Dify、FastGPT、CodeBuddy 这类的工具链也大多支持这种接法。7.2 Python 调用示例先安装 OpenAI SDKpip install openai非流式调用from openai import OpenAI client OpenAI( base_urlhttp://127.0.0.1:8000/v1, api_keyEMPTY ) response client.chat.completions.create( modelqwen3-8b, messages[ {role: system, content: 你是一个简洁的技术助手。}, {role: user, content: 请用两句话解释 vLLM 的 PagedAttention。} ], temperature0.7, max_tokens512 ) print(response.choices[0].message.content)流式调用from openai import OpenAI client OpenAI( base_urlhttp://127.0.0.1:8000/v1, api_keyEMPTY ) stream client.chat.completions.create( modelqwen3-8b, messages[{role: user, content: 写一篇 200 字左右的 vLLM 介绍。}], streamTrue ) for chunk in stream: delta chunk.choices[0].delta.content if delta: print(delta, end)如果运行后能正常输出内容说明 vLLM 的 OpenAI 兼容 API 已经打通。接下来可以继续测并发和批量任务。7.3 curl 调用示例不带 SDK 的环境直接用 curl 验证curl http://127.0.0.1:8000/v1/chat/completions \ -H Content-Type: application/json \ -d { model: qwen3-8b, messages: [ {role: user, content: 什么是连续批处理} ], max_tokens: 256 }返回的 JSON 中包含choices[0].message.content就是模型生成的结果。8. 批量任务与并发测试vLLM 服务端是会自己批处理的但前提是客户端要“同时”发多个请求。如果客户端还是一个一个串行调用那连续批处理也帮不上忙。所以批量任务设计的关键是用并发客户端去打接口。8.1 并发测试脚本import time from concurrent.futures import ThreadPoolExecutor from openai import OpenAI BASE_URL http://127.0.0.1:8000/v1 MODEL_NAME qwen3-8b def send_request(idx): client OpenAI(base_urlBASE_URL, api_keyEMPTY) start time.time() resp client.chat.completions.create( modelMODEL_NAME, messages[ {role: user, content: f请只回复收到第 {idx} 个请求。} ], max_tokens32, temperature0 ) cost time.time() - start content resp.choices[0].message.content return idx, content, round(cost, 2) def main(): with ThreadPoolExecutor(max_workers16) as pool: results list(pool.map(send_request, range(16))) for idx, content, cost in results: print(frequest {idx}: {content} | cost {cost}s) if __name__ __main__: main()重点观察两块16 个请求全部完成的总耗时以及单请求耗时。如果单请求耗时会随着并发数上升而变长说明 GPU 在排队这是吞吐换延迟的正常现象如果并发一高就报错再去看服务端日志是不是显存不足或连接超时。8.2 从文件批量生成批量任务的通用模板import json import time from concurrent.futures import ThreadPoolExecutor from openai import OpenAI BASE_URL http://127.0.0.1:8000/v1 MODEL_NAME qwen3-8b def process_item(item): client OpenAI(base_urlBASE_URL, api_keyEMPTY) try: resp client.chat.completions.create( modelMODEL_NAME, messages[ {role: user, content: item[prompt]} ], max_tokensitem.get(max_tokens, 512) ) return {id: item[id], output: resp.choices[0].message.content, status: ok} except Exception as e: return {id: item[id], error: str(e), status: failed} if __name__ __main__: with open(tasks.jsonl, r, encodingutf-8) as f: tasks [json.loads(line) for line in f] with ThreadPoolExecutor(max_workers8) as pool: results list(pool.map(process_item, tasks)) with open(results.jsonl, w, encodingutf-8) as f: for r in results: f.write(json.dumps(r, ensure_asciiFalse) \n) failed [r for r in results if r[status] failed] print(ftotal{len(results)}, failed{len(failed)})批量任务建议每个 item 记录 id 和原始 prompt失败时能快速定位。使用 JSONL 做输入和输出方便断点续跑。并发数从 4、8、16 逐级往上调不要一上来就打满。对失败任务做重试但要加间隔和重试上限避免把服务打挂。8.3 关于“本地模型能不能联网”的说明vLLM 只负责推理不提供“联网能力”。模型本身是否联网取决于调用方如果你的 Agent 工具链里接了搜索 API、数据库、知识库那模型可以通过工具调用获得外部信息。vLLM 这一层只是模型服务不决定联网与否。9. 资源占用与性能观察9.1 怎么看显存占用启动 vLLM 后另开一个终端执行nvidia-smi -l 2这里能看到进程级的显存占用。如果显存不够优先做三件事调低--max-model-len减少 KV Cache 预留量。调低--gpu-memory-utilization给其他任务留空间。换量化版本模型或在部署时选择更小的模型。更精确的观察方式是用--enable-metrics开启 Prometheus 指标然后配合 Grafana 看请求级监控。这个配置适合生产环境。9.2 关注哪些指标重点关注两个延迟指标TTFTTime To First Token从发出请求到收到第一个 token 的时间。首字慢通常是模型太大、max-model-len 过长或显存不足导致排队。TPOTTime Per Output Token后续每生成一个 token 的平均耗时。这个值越低越好反映单 token 的生成速度。vLLM 日志中会输出吞吐相关统计比如 average throughput。批量任务时可以把这些日志保存下来对比不同并发数下的整体吞吐。9.3 降低显存占用的常见手段使用量化模型例如 FP8 或者 GPTQ/AWQ 量化版。调低--max-model-len。减小--gpu-memory-utilization但要注意这也会减少 KV Cache 可用空间降低并发容量。多卡跑--tensor-parallel-size 2让模型权重和 KV Cache 分摊到多张卡上。实际数字必须以你的模型版本和显卡实测为准不同硬件、不同量化方式结果差异很大。9.4 端口冲突与进程残留如果启动时报端口被占用lsof -i :8000确认是残留进程还是其他服务占用再决定杀进程或换端口。vLLM 如果是被 CtrlC 强制终止可能出现进程残留建议统一用进程管理器管理比如 systemd 或 Docker。10. 常见问题与排查方法问题现象可能原因排查方式解决方案服务启动不了报 CUDA 相关错误显卡驱动、CUDA、PyTorch 版本不匹配检查nvidia-smi和python -c import torch; print(torch.cuda.is_available())按 vLLM 官方要求对齐 CUDA 与 PyTorch 版本启动后页面打不开服务未启动或端口被占用查看终端日志检查lsof -i更换端口或重启服务显存不足 / OOM模型权重 KV Cache 超过显存观察nvidia-smi看是启动阶段还是推理阶段 OOM调低--max-model-len、--gpu-memory-utilization换量化版模型并发一高就报连接超时客户端并发数过大或服务端显存不足查看服务端日志确认是否出现显存相关报错降低客户端并发数调大超时时间优化模型显存配置首字慢TTFT 高模型大、上下文长、显存不足导致排队结合--enable-metrics观察各阶段耗时调低--max-model-len使用量化模型检查是否有多余任务占显存接口返回 non-JSON 或解析失败请求参数格式不对或模型名填错先调用/v1/models确认模型 ID按实际served-model-name传参批量任务运行到一半卡住客户端异常退出或服务端 OOM检查 JSONL 输出和日志任务记录 id支持断点续跑降低并发数模型回答质量不稳定采样参数、系统提示词或模型版本问题对比不同 temperature、top_p 和提示词固定随机种子使用工程化的提示词模板Docker 里访问不到 GPU未安装 NVIDIA Container Toolkit 或不支持 runtime检查容器内nvidia-smi安装 NVIDIA Container Toolkit配置 Docker runtime11. 最佳实践与使用建议先给结论再展开第一次启动用小参数、小模型先把链路跑通。保留一套最小可运行配置改参数前先备份。模型、输入、输出分目录管理。批量任务必须加日志和失败重试。接口服务要限制访问范围不要裸奔到公网。涉及人脸、声音、版权素材时必须先完成授权确认。发布或商用前要做效果复核和合规检查。实际操作中我建议你按下面这套顺序来迭代。第一先跑通最小配置。模型用一个小尺寸版本--max-model-len设置低一些--gpu-memory-utilization也保守一点能拿到第一个生成结果就行。第二逐步加压。用并发脚本从 4 并发开始测试观察显存和耗时。如果单请求延迟明显上升说明显卡已经满载这时候再提高并发收益不大。第三处理好目录结构。模型权重、任务输入、结果输出、日志建议分目录存放避免模型和任务文件混在一起。批量任务脚本尽量可重复执行使用 JSONL 记录每个任务的状态方便断点续跑。第四接口服务安全。vLLM 的/v1接口本身没有内置鉴权默认也不限制来源。如果是内网工具使用监听127.0.0.1最稳妥如果需要对外开放一定要在前面加一层网关或鉴权不要直接把 8000 端口暴露到公网。第五合规边界。本地部署模型、批量生成内容、接入 Agent 工具链时要注意数据隐私。涉及人脸照片、声音样本、版权文本时必须确认授权尤其是做内容生产或商用投放之前要复核模型输出是否涉及侵权或违规内容。第六生产环境考虑。如果服务需要长期运行建议用 systemd 或 Docker Compose 管理进程配置自动重启。监控方面打开--enable-metrics配合 Prometheus 采集指标可以在吞吐下降时快速定位是显存瓶颈还是请求排队问题。如果你在局域网里有多台机器需要访问模型服务可以把--host设为0.0.0.0但一定要确认网络隔离和权限控制。vLLM 本身不限制调用方这是方便也是风险控制权在部署者手里。12. 总结与下一步vLLM 最值得尝试的点不是它写起来多花哨而是它把“显存管理”和“请求调度”这两件最影响吞吐的事做对了。PagedAttention 减少显存碎片连续批处理压满 GPU 算力OpenAI 兼容 API 又把接入成本降到最低。建议你拿到环境后先做三件事部署一个小尺寸模型用/v1/models验证服务正常。写一个并发脚本从 4 并发测到 16 并发观察显存和首字延迟。把日志和 Prometheus 指标配起来确认服务长期运行的稳定性。最容易踩的坑集中在三处CUDA 版本和 PyTorch 不匹配导致启动失败显存不足导致的 OOM以及客户端串行调用导致连续批处理发挥不出来。后续可以继续验证的方向把 vLLM 接入 LangChain、Dify 或 CodeBuddy 这类工具链通过 OpenAI 兼容接口组合成完整的 Agent 服务测试 Qwen3 的函数调用能力看 tool-call-parser 怎么配对比 vLLM 和 sglang 在同一批模型上的吞吐表现或者在生产环境用 Docker Compose 加指标监控做一套完整的模型服务。先把这篇的流程跑通再往这些方向迭代效率会高很多。