vLLM高吞吐推理实战:PagedAttention与连续批处理优化GPU瓶颈
2026/9/2 10:27:09 网站建设 项目流程

如果你的本地大模型服务经常出现请求排队、首字卡顿、并发一高就显存溢出,先别急着换显卡——大多数时候是推理框架的调度方式没选对。

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 硬件

  • GPU:NVIDIA 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 serverUvicorn 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 SDK:

pip install openai

非流式调用:

from openai import OpenAI client = OpenAI( base_url="http://127.0.0.1:8000/v1", api_key="EMPTY" ) response = client.chat.completions.create( model="qwen3-8b", messages=[ {"role": "system", "content": "你是一个简洁的技术助手。"}, {"role": "user", "content": "请用两句话解释 vLLM 的 PagedAttention。"} ], temperature=0.7, max_tokens=512 ) print(response.choices[0].message.content)

流式调用:

from openai import OpenAI client = OpenAI( base_url="http://127.0.0.1:8000/v1", api_key="EMPTY" ) stream = client.chat.completions.create( model="qwen3-8b", messages=[{"role": "user", "content": "写一篇 200 字左右的 vLLM 介绍。"}], stream=True ) 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_url=BASE_URL, api_key="EMPTY") start = time.time() resp = client.chat.completions.create( model=MODEL_NAME, messages=[ {"role": "user", "content": f"请只回复:收到第 {idx} 个请求。"} ], max_tokens=32, temperature=0 ) cost = time.time() - start content = resp.choices[0].message.content return idx, content, round(cost, 2) def main(): with ThreadPoolExecutor(max_workers=16) as pool: results = list(pool.map(send_request, range(16))) for idx, content, cost in results: print(f"request {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_url=BASE_URL, api_key="EMPTY") try: resp = client.chat.completions.create( model=MODEL_NAME, messages=[ {"role": "user", "content": item["prompt"]} ], max_tokens=item.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", encoding="utf-8") as f: tasks = [json.loads(line) for line in f] with ThreadPoolExecutor(max_workers=8) as pool: results = list(pool.map(process_item, tasks)) with open("results.jsonl", "w", encoding="utf-8") as f: for r in results: f.write(json.dumps(r, ensure_ascii=False) + "\n") failed = [r for r in results if r["status"] == "failed"] print(f"total={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 关注哪些指标

重点关注两个延迟指标:

  • TTFT(Time To First Token):从发出请求到收到第一个 token 的时间。首字慢通常是模型太大、max-model-len 过长或显存不足导致排队。
  • TPOT(Time 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 如果是被 Ctrl+C 强制终止,可能出现进程残留,建议统一用进程管理器管理,比如 systemd 或 Docker。

10. 常见问题与排查方法

问题现象可能原因排查方式解决方案
服务启动不了,报 CUDA 相关错误显卡驱动、CUDA、PyTorch 版本不匹配检查nvidia-smipython -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 runtime

11. 最佳实践与使用建议

先给结论,再展开:

  • 第一次启动用小参数、小模型,先把链路跑通。
  • 保留一套最小可运行配置,改参数前先备份。
  • 模型、输入、输出分目录管理。
  • 批量任务必须加日志和失败重试。
  • 接口服务要限制访问范围,不要裸奔到公网。
  • 涉及人脸、声音、版权素材时,必须先完成授权确认。
  • 发布或商用前,要做效果复核和合规检查。

实际操作中,我建议你按下面这套顺序来迭代。

第一,先跑通最小配置。模型用一个小尺寸版本,--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 又把接入成本降到最低。

建议你拿到环境后,先做三件事:

  1. 部署一个小尺寸模型,用/v1/models验证服务正常。
  2. 写一个并发脚本,从 4 并发测到 16 并发,观察显存和首字延迟。
  3. 把日志和 Prometheus 指标配起来,确认服务长期运行的稳定性。

最容易踩的坑,集中在三处:CUDA 版本和 PyTorch 不匹配导致启动失败;显存不足导致的 OOM;以及客户端串行调用导致连续批处理发挥不出来。

后续可以继续验证的方向:把 vLLM 接入 LangChain、Dify 或 CodeBuddy 这类工具链,通过 OpenAI 兼容接口组合成完整的 Agent 服务;测试 Qwen3 的函数调用能力,看 tool-call-parser 怎么配;对比 vLLM 和 sglang 在同一批模型上的吞吐表现;或者在生产环境用 Docker Compose 加指标监控做一套完整的模型服务。先把这篇的流程跑通,再往这些方向迭代效率会高很多。

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

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

立即咨询