尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

vLLM核心架构与实践:PagedAttention与Continuous Batching如何提升大模型推理吞吐

vLLM核心架构与实践:PagedAttention与Continuous Batching如何提升大模型推理吞吐 当一批用户同时请求对话、摘要、代码生成时GPU 显存很快被 KV Cache 占满推理吞吐骤降这是很多做过 LLM 服务化部署的人都经历过的场景。单靠 PyTorch 手写推理循环往往无法满足高并发下的性能要求这时就需要一个专门为大模型推理设计的高吞吐服务框架。vLLM 是目前社区最流行的选择之一它通过 PagedAttention、Continuous Batching 等机制把显存利用率和请求吞吐提升了一个量级。进入 2025 年vLLM 已经成为 AI Infra 层无法绕开的核心组件。本文将围绕 vLLM 的系统设计与实践落地先拆解它的核心架构再逐步演示环境准备、模型服务启动、客户端调用最后整理高频问题与生产环境建议。无论你是在做 RAG 应用、Agent 编排还是在给团队搭建模型推理服务都可以从本文找到可直接落地的内容。1. 为什么大模型推理需要专门引擎1.1 大模型推理与普通推理的本质区别传统深度学习推理模式非常简单输入一张图片或一段文本前向计算一次输出分类结果或向量整个过程毫秒级完成内存占用也基本固定。大语言模型LLM则完全不同。它以自回归方式生成文本也就是一个 token 一个 token 地产生结果。每生成一个新 token都要把之前的 token 重新读入计算。为了加速这个过程推理框架会缓存历史 token 的注意力中间结果也就是 KV Cache它随着生成的推进不断增长。这个特性让“简单用 PyTorch 跑一次 forward”的思路失效了。一个完整的 LLM 推理服务需要考虑显存如何分配、KV Cache 如何管理、多个请求如何组织成 batch、不同长度的生成序列如何调度、服务接口如何暴露。1.2 高吞吐推理的三个核心瓶颈结合工程实际LLM 推理服务通常受三方面制约第一是显存墙。模型权重本身占据大量显存而 KV Cache 在并发请求下增长极快。如果 KV Cache 管理粗糙可能几百个并发就把 A100 80G 的显存打满实际计算单元却大量空闲。第二是访存墙。自回归生成是典型的访存密集型任务每一个 token 的生成都需要把完整的权重从显存搬运到计算单元。此时 GPU 的计算单元可能只用了 10%而显存带宽已经跑满。这是高吞吐推理设计中最难优化的部分。第三是调度墙。不同请求的 prompt 长度不同期望输出长度也不同。一个 batch 里既有马上结束的短请求也有持续生成的超长请求。如何动态调度决定了 GPU 资源能不能被榨干。1.3 vLLM 的定位推理服务框架不是深度学习框架很多同学会把 vLLM 和 PyTorch 混淆或者拿 vLLM 与 LangChain 比较这里先澄清一个概念。PyTorch 是深度学习训练与推理的基础框架负责自动微分、算子执行、张量计算。LangChain 是应用层编排框架负责把大模型包装成链式调用、接入 RAG 和 Agent。vLLM 则位于两者之间它是专门为 LLM 推理而生的服务框架基于 PyTorch 构建但解决的问题是“如何更高效地把模型跑成服务”。vLLM 与当前比较火的 SGLang 也常被放在一起对比。SGLang 主打 RadixAttention 和更高效的前后端协同调度在部分长 prompt 场景下性能表现亮眼vLLM 则胜在生态成熟、接口兼容性好、社区案例多。两者都在快速迭代选型时建议用真实业务流量做 benchmark而不是只看宣传数据。2. vLLM 核心架构拆解2.1 PagedAttention把虚拟内存思想搬进显存PagedAttention 是 vLLM 论文中最重要的设计也是它早期脱颖而出的关键。要理解 PagedAttention先要看传统 LLM 推理框架是怎么管理 KV Cache 的。传统方案在请求开始时根据预估值申请一整块连续显存存放该请求的 KV Cache。这个预估值通常取最大序列长度比如 2048 或 4096。问题在于大部分请求实际生成的 token 数远少于预设值那块显存申请了但用不满造成内部碎片同时不同请求的 KV 块大小不一致显存中出现大量无法合并的小空洞造成外部碎片。PagedAttention 的思路非常像操作系统中的分页机制。它把 KV Cache 切分成固定大小的 block块每个 block 可以存储若干 token 的 KV 值。请求的 KV Cache 不要求物理连续而是通过 block table 映射到任意可用的物理块。新增 token 时动态申请新块用完的块立刻释放。这样就避免了大量预分配浪费显存利用率显著提升。用一句话概括PagedAttention 用“显存分页”的方式把 KV Cache 的碎片与浪费降到了极低水平。这个机制直接提升了批处理容量也就是同一时刻能并发处理的请求数量从而带来更高的吞吐。2.2 Continuous Batching请求级动态调度传统批处理通常采用静态 batching一批请求同时进入计算必须等最慢的一个生成完成整批结果才能一起返回。如果某个请求生成特别长其他请求只能干等GPU 的算力白费。vLLM 实现了 Continuous Batching也叫迭代级调度或请求级调度。它的核心逻辑是在每个 decoder step 之前调度器都会重新决定“这一轮计算哪些 sequence”而不是一开始就把批次锁死。当一个请求提前生成结束系统立刻把它从当前批次中移除并释放它占用的显存 block同时等待队列里的新请求可以立即插入下一轮计算。这个动态调度机制让 GPU 始终在处理有意义的 token 生成而不是浪费在等待和空转上。与 Continuous Batching 配套的还有 Preemption抢占机制。当显存不足时调度器会暂停某些序列的生成先保证高优先级请求继续等显存释放后再恢复被抢占序列。这种设计牺牲了少量单请求延迟换取整体吞吐稳定非常贴合在线服务场景。2.3 vLLM 的整体服务流水线从代码层面拆解vLLM 的核心组件大致包括API Server对外暴露 OpenAI 兼容的 HTTP 接口LLM Engine管理模型加载、推理循环、采样参数Scheduler负责 continuous batching、抢占、block 管理Distributed Executor处理张量并行/数据并行下的模型执行Model Worker真正执行 Transformer 前向计算的进程。一个完整请求的流程可以简化为以下步骤客户端通过/v1/chat/completions发送请求。API Server 解析请求参数放入待调度队列。Scheduler 在下一轮 step 把符合条件的序列加入 batch。模型执行器对 batch 做前向计算产出新的 token。KV Cache 按需写入新的 block同时更新 block table。如果序列到达结束条件调度器将其移出并返回结果否则继续下一轮。这套设计把请求管理、显存管理、模型执行解耦每个组件可以独立演进也让多卡部署、量化支持、LoRA 微调插入等功能变得更容易扩展。3. 环境准备与安装部署3.1 操作系统与硬件要求vLLM 目前在 Linux 环境下的支持最稳定这也是绝大多数生产环境的选择。NVIDIA GPU 配合 CUDA 的路径资料最多问题也最容易排查。如果你想在 Windows 10 或 Windows 11 上使用 vLLM需要明确一点官方对原生 Windows 的支持并不优先很多底层算子依赖 Linux 生态。社区中常见的做法是使用 WSL2或者直接在 Docker Desktop 中跑 Linux 容器。如果只是本地调试单个模型WSL2 的体验已经比较接近 Linux 原生环境。在安装前建议确认下面几个信息GPU 型号与显存大小驱动版本是否支持目标 CUDA 版本Python 版本建议 3.10 以上是否已配置 NVIDIA Container ToolkitDocker 部署时需要。如果使用非 NVIDIA 硬件比如昇腾、寒武纪等国产芯片需要额外适配插件。例如昇腾平台有对应版本的 vLLM-ASCEND 适配项目但支持矩阵通常滞后于 NVIDIA 主线。特别是 Embedding 模型和 Reranker 模型因为任务类型与生成模型差异较大在昇腾 910B 系列上经常出现启动失败或算子不支持的问题这一点后续在常见问题部分会展开说明。3.2 pip 与 Docker 安装方式最直接的方式是通过 pip 安装pip install vllm安装后会拉取大量依赖包括 torch、transformers、flashinfer 等。需要注意版本匹配问题不建议手动升级或降级 torch 版本否则可能导致算子编译失败。生产环境更推荐使用 Docker。vLLM 官方镜像会提前编译好符合特定 CUDA 版本的依赖避免本地编译时间和环境冲突。docker pull vllm/vllm-openai:latest启动容器时需要把模型目录挂载进去并映射端口docker run --runtime nvidia --gpus all \ -v ~/models:/models \ -p 8000:8000 \ --ipchost \ vllm/vllm-openai:latest \ --model /models/qwen/Qwen2.5-7B-Instruct \ --served-model-name qwen2.5-7b这里的--ipchost很重要vLLM 在并行解码时依赖共享内存如果容器 IPC 限制过小可能触发无法预料的启动失败。4. 核心启动参数深度解析4.1 dtype 精度选择fp16、fp32 与 bf16大模型的精度参数直接决定显存占用和推理质量。vLLM 默认按模型保存的权重精度加载但很多情况下我们需要手动指定。fp16 的优点是计算快、显存占用减半缺点是动态范围较窄容易出现数值溢出尤其在注意力分数较大的场景。bf16 是近年来大模型训练和推理的主流选择它有和 fp32 相同的大范围指数位数值稳定性好几乎不会溢出也不需要额外的缩放逻辑。代价是低精度尾数可能带来轻微的精度损失但在绝大多数 NLP 任务中感知不到。fp32 适合需要精确数值验证的场景比如排查推理结果异常、对照参考实现或者模型本身过于敏感。但显存占用直接翻倍生产环境极少全 fp32 推理。在实际操作中启动服务时常用--dtype bfloat16如果你的 GPU 是 Turing 架构及更老的卡不支持 bf16可以改用 fp16--dtype float16选择精度还需要关注量化方案。AWQ、GPTQ 等量化权重可以显著降低显存需求但启动服务时不能直接指定--dtype int4而是直接把量化后的模型目录传给--modelvLLM 会自动读取量化配置。建议先了解自己 GPU 的架构和算子支持情况再决定精度策略。4.2 --enforce-eager 到底有什么影响很多人在部署时看到网上教程用--enforce-eager却不知道这个参数真正做了什么。vLLM 默认会使用 CUDA Graph 来提高推理性能。CUDA Graph 会把一系列 GPU kernel 启动捕获并重放大幅降低 kernel launch 带来的 CPU 开销在短请求、高并发场景下收益非常明显。代价是 CUDA Graph 需要额外的显存空间且在每次请求形状变化时可能需要重新捕获。--enforce-eager的作用是禁用 CUDA Graph强制使用 eager 模式执行。启用后显存占用会明显降低模型加载速度也更快但吞吐量通常会下降尤其在短 prompt、高并发场景下。适合使用--enforce-eager的场景包括显存非常紧张无法容纳 CUDA Graph 预留空间在机器学习开发阶段只想快速验证服务能否启动调试算子问题时需要绕过 CUDA Graph 的重放逻辑。生产环境追求吞吐时不建议随意开启这个参数。如果你看到“显存不足”报错优先排查的是并发参数和模型量化而不是直接关掉 CUDA Graph。4.3 max-num-seqs 与并发控制--max-num-seqs是控制并发序列数的关键参数默认值通常是 256。它表示 Scheduler 在每一轮迭代中最多同时处理的序列数量。这个参数与显存的关系很直接。并发序列越多KV Cache 占用越大所以当显存不足时降低max-num-seqs往往最有效。反过来如果 GPU 利用率偏低但显存还有不少余量适当提高这个值可以让更多请求进入 batch提升吞吐。需要强调的是max-num-seqs并不等于客户端的最大并发请求数。当请求数量超过该值时超出部分会在队列中等待而不会报错。因此它更像是推理引擎内部的批处理能力上限而不是服务入口的并发限制。另外一个相关参数是--max-model-len它决定了模型能接受的最长序列长度。如果设置过长会预分配更多显存如果过短长文本请求会被截断或报错。部署时建议根据业务实际输入场景来配置而不是无脑设置成模型的最大长度。4.4 多卡部署与张量并行当单卡显存放不下模型时vLLM 支持通过张量并行Tensor Parallelism将模型切分到多张卡上。--tensor-parallel-size 2这个参数表示把模型切分到 2 张 GPU 上运行。常用场景是 70B 级别模型或 32B 模型在双卡 A100/L20 上的部署。多卡部署最容易踩的坑有两个。第一个坑是显存不均。张量并行要求每张卡预留的空间保持一致如果某张卡已经被其他进程占用了部分显存vLLM 可能启动失败。排查时可用nvidia-smi先检查剩余显存。第二个坑是算力共享与通信瓶颈。张量并行涉及每层 transformer 的 allreduce 通信PCIe 或 NVLink 带宽会成为瓶颈。在 L20 这类双卡不过 NVLink 直连的服务器上大模型吞吐可能远低于预期。此时先确认nvidia-smi topo -m的拓扑结构再决定是否使用多卡。如果通信拓扑不理想优先考虑单卡小模型或者数据并行每个副本独立服务一个请求。4.5 如何启动 Embedding 与 Reranker 模型vLLM 的核心定位是生成式大模型的高吞吐推理但社区也一直在扩展 Embedding 模型和 Reranker 模型的支持。如果你使用主流的 bge、gte 系列 embedding 模型可以尝试vllm serve /models/bge-m3 \ --task embedding \ --served-model-name bge-m3Reranker 模型则通过--task reranker指定。启动后同样可以通过/v1/embeddings或/v1/rerank接口调用。但这里必须说明vLLM 对 Embedding/Reranker 的支持远不如生成模型成熟。部分模型结构可能缺少适配算子不同版本的兼容性差异也很大。在昇腾 910B 这类非 NVIDIA 平台上由于适配项目优先对齐生成式模型Embedding 和 Reranker 模型启动失败的情况非常常见。如果你的业务是纯 RAG 场景并且把向量检索和段落重排当作核心链路建议优先考虑专门面向 Embedding 的推理服务比如 Hugging Face 的 TEIText Embeddings Inference或者使用支持更多任务类型的框架。把 vLLM 用在生成任务上把专业工具用在检索任务上整体稳定性会高很多。5. 完整实战部署一个开源大模型服务5.1 准备模型与项目目录本文以 Qwen2.5-7B-Instruct 为例进行演示。先创建模型目录下载模型权重。如果你已经有离线模型文件直接复用即可。mkdir -p ~/models cd ~/models # 通过 modelscope 或 huggingface 下载按实际网络情况选择 pip install modelscope modelscope download --model Qwen/Qwen2.5-7B-Instruct --local_dir ./Qwen2.5-7B-Instruct这里建议使用 modelscope 在国内网络环境中加速下载。下载完成后检查目录中是否包含config.json、model.safetensors等关键文件。5.2 启动 vLLM 服务在服务器终端执行下面的命令启动一个 OpenAI 兼容的推理服务vllm serve ~/models/Qwen2.5-7B-Instruct \ --served-model-name qwen2.5-7b \ --dtype bfloat16 \ --max-model-len 8192 \ --max-num-seqs 64 \ --port 8000参数说明--served-model-name对外暴露的模型名可自定义方便后续切换。--dtype bfloat16按 bf16 精度加载权重。--max-model-len 8192限制最大序列长度预分配显存。--max-num-seqs 64限制并发序列数量避免显存被打爆。--port 8000服务监听端口。启动成功后日志中会显示模型加载耗时、GPU 显存分布和当前通过 OpenAI 兼容 API 对外提供服务。此时可以打开另一个终端验证服务健康状态curl http://localhost:8000/v1/models如果返回了模型列表说明服务已经正常运行。5.3 使用 OpenAI SDK 调用由于 vLLM 提供了 OpenAI 兼容接口你可以直接使用openai客户端库调用无需任何改造。from openai import OpenAI client OpenAI( base_urlhttp://localhost:8000/v1, api_keyEMPTY ) resp client.chat.completions.create( modelqwen2.5-7b, messages[ {role: system, content: 你是一个乐于助人的中文助手。}, {role: user, content: 请用一句话解释什么是 KV Cache。} ], temperature0.7, max_tokens512 ) print(resp.choices[0].message.content)运行这个脚本前需要安装依赖pip install openai正常输出会是一段关于 KV Cache 的解释。这个示例说明你的业务代码完全可以在本地开发环境使用 OpenAI 接口在生产环境切换到 vLLM 服务只需改变base_url代码层面无需改动。5.4 流式输出与多轮对话在对话类应用中流式输出几乎是标配因为用户不希望等整个结果生成完才看到内容。vLLM 同样支持 OpenAI 的流式协议只需要把参数stream设为True。from openai import OpenAI client OpenAI( base_urlhttp://localhost:8000/v1, api_keyEMPTY ) stream client.chat.completions.create( modelqwen2.5-7b, messages[ {role: user, content: 写一个 Python 快速排序函数。} ], streamTrue ) for chunk in stream: delta chunk.choices[0].delta if delta and delta.content: print(delta.content, end, flushTrue)流式输出的内部机制是 vLLM 在生成每个 token 后立即通过 SSEServer-Sent Events推送给客户端而不是等完整序列结束。这在长文生成场景中极大改善了用户体验。多轮对话的实现也不复杂只需要把历史消息都放入messages数组vLLM 会自动对整段上下文做 prefill并结合 KV Cache 复用历史计算结果。5.5 观察服务日志与性能指标部署上线后观察服务日志是每日运维的基础工作。vLLM 的默认日志会输出每次请求的排队时间、prefill token 数、decode token 数、总耗时、吞吐等指标。典型的日志片段如下INFO 06-01 12:00:00 engine.py:123] Avg prompt throughput: 150.2 tokens/s INFO 06-01 12:00:00 engine.py:123] Avg generation throughput: 850.6 tokens/s INFO 06-01 12:00:00 engine.py:123] Running: 24 reqs, queued: 8 reqs如果看到 queued 数量持续增长说明服务吞吐已经达到瓶颈需要考虑调大max-num-seqs、加卡或者横向扩容。如果 Running 数量明显低于配置上限但 GPU 利用率已经很高则说明序列生成本身已经接近硬件上限继续加并发未必有效。6. 高频问题与排查思路6.1 加载 Qwen 9B 等中型模型爆显存很多用户在部署 Qwen 7B/9B 级别模型时遇到显存不足报错信息通常类似CUDA out of memory. Tried to allocate 512.00 MiB这个问题的原因不只是模型权重KV Cache 和 CUDA Graph 预留空间同样占据显存。排查流程如下先用nvidia-smi确认显卡总显存和当前占用再计算权重所需显存。7B 模型在 bf16 下约需 14GB9B 约需 18GB基础权重已经占去一半以上显存。剩下的显存需要同时容纳 KV Cache 和 CUDA Graph。解决方向包括将--max-num-seqs调低比如 16 或 32将--max-model-len调低比如 4096使用--enforce-eager关闭 CUDA Graph 的额外显存占用对权重做量化比如 AWQ 4bit。建议按“先减并发、再减长度、最后加量化”的顺序调试避免一次性修改多个参数导致无法定位原因。6.2 L20 等双卡服务器无法多卡运行模型L20 显卡在 AI 推理服务器中非常常见但很多用户反馈双卡跑大模型失败或吞吐异常低。首先检查驱动和 CUDA 是否识别到两张卡nvidia-smi如果只显示一张卡先检查物理插槽、PCIe 通道或 BIOS 设置。如果两张卡都正常再用张量并行启动并把--tensor-parallel-size设置为 2。启动失败时重点看日志中是否出现 NCCL 相关错误这往往意味着多卡通信初始化失败。还有一个容易忽略的问题L20 双卡之间如果没有 NVLink而是通过 PCIe 通信张量并行的吞吐提升会非常有限。此时建议改用数据并行也就是用两个独立的 vLLM 进程分别加载模型副本前面由负载均衡组件分发请求效果可能更好。6.3 昇腾等非 NVIDIA 平台无法启动 Embedding/Reranker在昇腾 910B 服务器上使用 vLLM 启动 Embedding 和 Reranker 模型失败通常是算子适配问题。昇腾的适配项目 vLLM-ASCEND 主要面向生成式 LLMEmbedding 池化和 Reranker 交叉编码相关的算子往往滞后甚至缺失。如果必须在这类硬件上提供向量检索能力建议两条路线改用原生的 MindIE 或昇腾相关推理方案使用专门的 Embedding 服务框架并通过标准化 API 对外提供服务。如果你只是生成式模型推理需要昇腾适配则建议严格对照适配项目的支持矩阵选择模型和版本不要盲目升级 vLLM 主线版本否则可能破坏兼容性。6.4 启动失败速查清单问题现象常见原因解决思路启动即报 CUDA 版本错误驱动与 PyTorch 的 CUDA 版本不匹配检查nvcc --version和python -c import torch;print(torch.version.cuda)容器内找不到 GPU未安装 NVIDIA Container Toolkit安装后重启 Docker并添加--runtime nvidia模型加载耗时极长磁盘 I/O 慢或首次编译缓存预热模型目录、升级 NVMe、使用镜像缓存请求长时间排队无返回max-num-seqs 过低或模型生成过长调高并发上限或对 max_tokens 做限制输出乱码或重复循环精度设置不当或量化效果差尝试 bf16、调整 temperature或换用未量化权重显存足够但无法启动CUDA Graph 预留空间冲突临时用--enforce-eager验证再排查其他占用7. 生产环境最佳实践7.1 模型预热与平滑上线vLLM 服务刚启动时模型权重已经加载到显存但第一次请求仍然会触发 CUDA kernel 的初始化和显存分配响应延迟明显偏高。生产环境上线前建议主动发送一批预热请求覆盖典型的输入输出长度让 CUDA Graph 缓存和显存分配提前稳定下来。预热可以用简单的 Python 脚本完成循环调用接口几十次。预热后再把服务切到生产流量可以避免“上线即超时”的尴尬。7.2 并发评估与容量规划容量规划不能用“最大并发用户数”直接换算。更准确的做法是基于 token 吞吐量来评估。假设单实例的 generation throughput 是 800 tokens/s业务平均每个请求输出 300 token那么这个实例每秒大约可以完成 2.7 个完整请求换算成每分钟约 160 个请求。这样的估算方式比单纯看并发数更接近真实情况。在压测时建议使用真实业务 prompt 的分布而不是统一的短文本。不同长度 prompt 的 prefill 时间和显存占用差异很大会直接影响吞吐结果。7.3 版本管理与灰度发布vLLM 迭代速度极快新版本可能带来性能提升也可能引入 regression。生产环境不建议直接跟随 latest 版本。推荐做法是固定版本并在测试环境用同一批压测脚本验证后再升级。对于模型更新同样需要灰度。可以利用--served-model-name把新旧模型同时部署在两个独立服务上通过网关按比例分发流量。确认新模型质量稳定后再逐步切换。7.4 vLLM 与 SGLang 的选型建议如果你正在搭建新的推理服务可以在 vLLM 和 SGLang 之间做一次对比测试。vLLM 的优势是社区影响力大、OpenAI 兼容接口稳定、Embedding/多模态扩展方向清晰。SGLang 的优势是 RadixAttention 在长 prompt 和高并发复用场景下表现出色多模态和结构化输出也做得很好。我的建议比较务实如果团队刚起步优先选 vLLM因为它遇到问题时更容易搜到答案。如果业务主要是复杂提示词、多轮对话、代码生成等重度前缀复用场景并且团队有算法工程能力做针对性调优再考虑 SGLang。7.5 监控、告警与安全边界生产环境的推理服务必须纳入监控。至少要采集以下指标GPU 利用率、显存使用率、显存温度平均 prefill tokens/s 和 generation tokens/s请求排队数、P99 延迟错误率与超时数。安全方面vLLM 默认提供的 API 接口没有鉴权机制。如果服务暴露在非可信网络必须在网关层增加 API Key 校验、IP 白名单和请求频率限制避免被刷爆显存。权限管理遵循最小权限原则推理服务账号不应具备模型文件所在目录的写权限模型更新应该通过独立流程执行运维人员与开发人员的权限需分开管理。涉及模型替换或配置变更时先在测试环境验证再走生产变更流程必要时提前备份原始权重。
返回列表