大模型高并发推理引擎vLLM:原理、部署与性能调优

发布时间:2026/10/11 4:46:53
大模型高并发推理引擎vLLM:原理、部署与性能调优 大模型跑起来不难让它扛住高并发才是见功夫的地方。我用本地显卡跑开源模型时最头疼的一个问题就是单次推理挺稳但并发一上来请求就开始排队、超时甚至直接把显存打爆。折腾了不少方案之后我最终把 vLLM 作为本地高并发推理服务的底座这套组合在吞吐和响应稳定性上确实给了我很直观的改善。这篇文章就把我从原理到部署、从压测到调优的完整过程写出来给正在为本地多路并发推理发愁的朋友做个参考。这套方案适合谁呢如果你需要在单机或多卡机上向内部系统、个人应用、自动化脚本提供大模型推理接口而且同时会有多个请求打过来那 vLLM 是目前我实测下来最省心的引擎之一。它解决的核心问题不是把模型跑起来而是在显存有限的情况下让尽可能多的请求同时被处理并且不要互相拖垮。下面我会结合自己的实操经历把架构思路、关键参数、部署命令和踩坑记录都过一遍。1. 为什么本地高并发推理服务这么难做1.1 大模型生成是一步一卡的串行过程先说一个很多人容易忽略的事实LLM 的推理不是一次前向计算就出结果的它本质上是自回归生成——每次只生成一个 token这个新 token 会拼接到输入序列末尾然后再次送入模型再生成下一个 token。拿一个典型的聊天场景来说用户问你一段话模型要吐一段几百字的回复这就意味着模型在内部走了几百次完整的前向计算一次都省不了。这个机制带来的直接后果是显存占用和延迟都跟序列长度强相关。序列越长中间要保存的中间状态就越多。而这个中间状态有一个专门的名字——KV Cache。它是 Transformer 架构中为了加速解码而缓存的历史 Key 和 Value 向量。没有它每次生成新 token 都得把前面所有 token 重新算一遍那是灾难性的浪费有了它每次只需把新 token 的 Key、Value 追加进去。但 KV Cache 是需要显存来装的。我在本地部署一个 7B 模型FP16 精度下权重本身大约 14GB看着显卡还有不少余量可真把并发跑起来显存很快就见底——因为每个请求都要独占一份 KV Cache请求越多、上下文越长KV Cache 吃掉的空间就越吓人。1.2 并发上不去根子在显存碎片和调度策略如果你用过朴素的方式部署模型服务很可能遇到过这类现象显存一看还够但并发就是上不去。原因并不复杂——传统的推理框架在做批处理时通常会把一批请求的 KV Cache 一次性预分配到最大长度。比如每条请求最多可能生成 1024 个 token框架就按 1024 的长度给你预留空间哪怕实际只生成了 50 个 token这 900 多个 token 的空间也白白占了。多个请求同时预留显存很快耗尽而且这些预留区域大小不一、生命周期不同用着用着就产生大量碎片。碎片多了之后明明总剩余空间还够但没法把连续的大块空间再分给新请求于是新的请求只能排队等别人释放。这就是很多自研服务并发一高就开始排队的根因之一。另一个问题是批处理粒度。传统框架往往是静态批处理攒够一批请求一起算等整批全算完才接收下一批。如果一批里有快有慢快的请求必须等慢的请求结束才能被返回既浪费算力又拉长了平均延迟。1.3 vLLM 的思路恰好卡中了要害vLLM 最让我服气的地方是它没有在增加显存这件事上做文章而是从算法层面下手提高显存利用率和调度效率。它引入了两个关键设计一个是模仿操作系统虚拟内存分页机制的 PagedAttention把 KV Cache 切分成固定大小的页按需分配、用多少分多少另一个是连续批处理在生成过程中动态加入新请求、动态移除已完成请求不用等整批跑完。这两个设计我把原理和细节放在下一节展开这里先给个结论它们让显存利用率大幅提升也让调度从批次级细化到了token 级。实测下来同一块显卡同样一个模型并发能力和吞吐量跟我之前用静态批处理方案对比提升幅度非常明显。这也就是为什么我觉得 vLLM 特别适合作为本地高并发推理服务的地基。2. 核心原理vLLM 的高吞吐从何而来2.1 PagedAttention给 KV Cache 做分页管理PagedAttention 是 vLLM 最早提出并且至今仍是核心的技术点。它的灵感来自操作系统里的虚拟内存分页物理内存按固定大小的页划分每个进程不需要整块连续空间而是通过页表把虚拟地址映射到不连续的物理页面。在 vLLM 里KV Cache 也被切成固定大小的 block常见是每个 block 存 16 个 token 的 Key 和 Value。每个请求只申请自己实际需要的 block 数量而且这些 block 在显存里不需要连续。这样有两个好处显存按需分配不存在预分配一大块用不完的浪费。显存中的 gap 可以被其他请求的 block 填上碎片化问题被天然缓解。比如一个请求生成到第 18 个 token它需要 2 个 block一个满 16一个只占 2第二个 block 可能被分配到显存里任意一个空闲位置通过 block table 记录映射关系。后续这个请求继续生成到第 30 个 token再动态申请第 3 个 block 即可。这个设计在并行推理时还会带来额外收益因为多个请求可以被分散到不连续显存页中调度器可以更自由地把不同请求组合进同一个 batch而不必担心显存连续性。我在多卡甚至单卡环境下都实测过PagedAttention 开启后KV Cache 的显存利用率比我手动预留的方案高出一大截。2.2 Continuous Batching把整批等齐改成流水线滚动先解释一个词iteration-level scheduling也叫 continuous batching。传统批处理是 request-level 的一批请求集齐了才跑整批跑完才释放。vLLM 的 continuous batching 则是 iteration-level 的每一步前向计算结束后立刻把已经完成生成请求的 slot 空出来让排队中的新请求补进去还在生成中的请求继续留在 batch 里。这就很像一家流水线餐厅有人吃完走了位置立刻让排队的客人坐下而不是非得凑满一个餐厅才开门。效果是GPU 在生成阶段的每个时刻都在处理尽可能多的活跃 token。短请求不会被长请求拖住长请求也不影响新请求尽快入场。排队时间显著下降整体吞吐自然就上去了。但 continuous batching 会引入一个新问题如果新请求进入时显存不够怎么办vLLM 的解决办法是抢占。它会优先把正在进行的请求暂停释放部分显存给新请求或者直接把旧请求的 KV Cache 丢弃等显存缓解后从最近一个 checkpoint 重新恢复计算。抢占是自动的但对延迟敏感的服务来说最好通过参数控制并发上限避免频繁触发抢占。这个我在第 3 节部署时会展开说。2.3 多卡扩展和投机解码本地服务提速的辅助手段vLLM 也支持多卡并行。单机多卡时你可以用 tensor parallelism 把模型切分到多张 GPU 上每一层算一部分所有卡同时算同一个 batch。这是显存不够或者想上更大规模模型时最直接的扩展方式。另外一个值得关注的是投机解码speculative decoding支持。它的大体思路是先用一个小模型快速生成多个候选 token再由大模型一次验证这些 token 是否可接受。如果接受率高大模型一次前向计算能押中2~3 个甚至更多 token生成速度自然翻倍。vLLM 对投机解码有内置的参数支持但选择草模型要谨慎如果草模型和大模型能力差距太大、接受率过低反而会拖慢速度甚至会因为草模型推理开销导致收益变负。这些机制叠加起来vLLM 的吞吐表现才那么亮眼。但原理归原理真正把它跑起来服务还是得看参数配置的功夫。3. 本地高并发推理服务的实战部署过程3.1 环境准备显存规划、驱动版本与安装我本地的试验机器是一张 24GB 显存的 GPU模型以 7B 和 13B 为主。部署前建议先做三件事确认驱动支持 CUDA 12 及以上确认 PyTorch 版本和 vLLM 要求的版本匹配确认磁盘有足够空间下载模型权重7B 模型 FP16 大约 14GBBF16 类似量化后可以更小。安装方式我推荐用 pip 直接装官方 wheel 包项目依赖会自动拉齐pip install vllm不过要注意vLLM 版本迭代很快有时候模型仓库里某个模型需要特定版本才支持。我的习惯是先在本地用一个最小模型快速验证安装是否正常python -c from vllm import LLM; llm LLM(modelQwen/Qwen2.5-1.5B-Instruct-GPTQ-Int4); print(ok)能跑通再上大模型免得进度卡在环境问题上一整天。还有一点vLLM 在 Linux 上表现最稳如果是在 Windows 上开发强烈建议用 Docker 来跑避免一堆底层编译和环境变量问题。Docker 启动时用--gpus all把显卡透传进去即可。3.2 启动服务一行命令拉起 OpenAI 兼容接口vLLM 自带一个开箱即用的 API 服务兼容业界通用的协议格式这对我来说非常方便——因为我的客户端代码完全不需要改动只需要把 base_url 指到 vLLM 的地址即可。最基础的启动命令长这样vllm serve Qwen/Qwen2.5-7B-Instruct \ --host 0.0.0.0 \ --port 8000 \ --gpu-memory-utilization 0.85 \ --max-model-len 8192 \ --max-num-seqs 16这里的参数含义我逐个解释一下--host/--port服务监听地址。0.0.0.0表示允许局域网内其他机器访问如果你的服务只给自己本机用写127.0.0.1更安全。--gpu-memory-utilization 0.85允许 vLLM 使用 85% 的显存。它不是一次性占满而是把这个比例作为上限用于权重、KV Cache 和 CUDA 图等开销。不要设得太高否则会给显存碎片预留的波动留不够余量容易 OOM。--max-model-len 8192模型支持的最大序列长度。这个值要结合模型本身的训练上下文长度来设设得过大虽然能处理长文本但 KV Cache 占用会随长度显著上升间接压缩并发容量。--max-num-seqs 16同一时间能并发处理的序列数量上限。这个值直接决定了并发能力的上限。启动之后接口地址输出到日志里。调用方式和你在普通 API 服务中的习惯完全一样curl http://127.0.0.1:8000/v1/chat/completions \ -H Content-Type: application/json \ -d { model: Qwen/Qwen2.5-7B-Instruct, messages: [{role: user, content: 你好介绍一下你自己}] }能返回内容说明服务已经跑起来了。注意这里的/v1/chat/completions是 chat 类型接口vLLM 同时也兼容补全接口/v1/completions两者都在同一个服务里。3.3 并发压测把服务真实上限测出来启动服务只是第一步真正重要的是摸清它的脾性。我自己写了一个简单的 Python 并发脚本用线程池同时发请求统计总耗时、平均首 token 延迟、平均吞吐。脚本不复杂但很实用import json import time import threading import urllib.request def send_one(index): data json.dumps({ model: Qwen/Qwen2.5-7B-Instruct, messages: [{role: user, content: 请用三句话解释什么是缓存}], max_tokens: 256, temperature: 0.7 }).encode(utf-8) req urllib.request.Request( http://127.0.0.1:8000/v1/chat/completions, datadata, headers{Content-Type: application/json} ) start time.time() with urllib.request.urlopen(req, timeout120) as resp: result json.loads(resp.read().decode(utf-8)) return time.time() - start, result concurrency 8 threads [] results [] start_all time.time() for i in range(concurrency): t threading.Thread(targetlambda: results.append(send_one(i))) threads.append(t) t.start() for t in threads: t.join() total time.time() - start_all print(f并发数: {concurrency}, 总耗时: {total:.2f}s) print(f平均响应: {sum(r[0] for r in results)/len(results):.2f}s)这个脚本简单但能说明问题你可以把concurrency从 1、2、4、8、16 逐步往上加观察平均响应时间是否线性上涨。如果发现响应时间在某档并发后出现断崖式上升说明已经逼近服务瓶颈然后你们再回到参数上做调整。更严谨的压测可以用专门的压测工具来做但本地服务用这个脚本快速摸底已经够了。实测数据我记得很清楚一个 7B 模型在--max-num-seqs 16、--max-model-len 8192、输出 256 token 的情况下8 并发平均响应大约 20 秒出头从 1 并发到 8 并发总吞吐提升非常明显而响应时间只增加了一些。这说明 continuous batching 确实在干活GPU 每个 token 步都在同时处理多个请求。3.4 关键参数调优让吞吐和延迟达到平衡压测之后调整最多的三个参数是--max-num-seqs、--gpu-memory-utilization和--max-model-len。这三者的关系很像一个杠杆调端这头那一头必然变化。--max-num-seqs设得越大同一时刻 GPU 上并行处理的请求越多理论上吞吐越高但每个请求分到的显存和算力也越少单请求的 token 生成速度会变慢。我实际测试中从 8 调到 32总吞吐确实涨了但平均响应时间也涨得比较明显。如果你的业务对每个响应速度有硬性要求宁可限制并发数也不要盲目拉高。--gpu-memory-utilization是显存分配的上限。我出现过一次比较惊险的情况设为 0.95压测到一半直接报 CUDA OOM最后发现是 PyTorch 的碎片预留和 CUDA context 还要占一部分显存留 10%~15% 的余量才安全。我现在的习惯是 0.85 起步模型大或者序列长就降到 0.80。--max-model-len要尽量贴着业务实际需要来设。我的知识库问答场景大多在 2000 token 以内所以把 max-model-len 设成 4096 就够了这样同样显存下 KV Cache 能容纳更多并发请求。如果你设成 32768 这种大上下文但业务根本用不到长文本那就是白白牺牲并发能力。另外还有一组容易被忽略的参数--quantization和精度选项。如果你使用量化模型权重可能需要显式指定量化类型比如vllm serve Qwen/Qwen2.5-7B-Instruct-GPTQ-Int4 \ --quantization gptq \ --gpu-memory-utilization 0.85量化模型能显著降低显存占用从而把更多显存留给 KV Cache也是一种提高并发容量、降低显存门槛的手段。不过量化在极少数情况下会让输出质量略降我一般线上用 AWQ 或 GPTQ 的 4bit 版本原版 FP16 留给精度敏感场景。4. 避坑实录高并发推理服务里的典型问题与排查思路4.1 CUDA Out of Memory 并不总是显存真不够这是我最常遇到的报错。vLLM 日志里出现 CUDA OOM 时有两种情况第一种是权重加 KV Cache 的总占用确实超过了显存上限。这时候调低--gpu-memory-utilization或者换量化模型或者用 tensor-parallel 切到多卡。第二种是显存碎片导致的申请失败。PagedAttention 已经大幅缓解了碎片问题但偶尔长序列请求依然会在生成中途需要额外申请 block如果碎片化严重同样会失败。这种情况可以先检查一下是否有的请求 max_tokens 设置过大把异常长序列限制住。我的排查顺序是先看日志里每个阶段的显存分配情况再确认nvidia-smi看显存实际占用最后看max-model-len和并发上限是否匹配实际负载。大多数 OOM 其实都能靠调低max-model-len或max-num-seqs解决而不是真的换一块更大的卡。4.2 高并发下首 token 延迟飙升、排队超时如果你发现服务响应时间随着并发升高而迅速恶化第一反应先看是不是触发了 vLLM 的排队等待。当活跃请求数达到--max-num-seqs上限后新请求会排队排队时间直接加到响应延迟里。解决办法有两个方向一是对延迟敏感的业务调低max-num-seqs让活跃请求更少、算得更快二是配合外部任务队列做流量整形比如把请求先放到消息队列里由消费方按 vLLM 的吞吐能力平滑处理。我在实际项目里就是二者结合面向内部工具的服务允许高并发排队面向实时交互的服务则严格限制并发保证响应速度优先。还有一个隐性因素max_tokens设置过长会导致长序列占着 slot 不释放后面的短请求也会被堵住。这时候可以把客户端请求里的max_tokens从实际需要出发收短比如聊天场景给 512 就够了而不是给 4096。4.3 量化模型的选择精度和吞吐的权衡量化并不是免费的。GPTQ、AWQ 在 4bit 下能显著减少显存占用但我在实测某些推理模型时发现量化后生成内容的风格和稳定性有细微变化尤其在需要精确复述、数学计算等场景下更敏感。我的建议是如果显存紧张优先考虑 AWQ 或 GPTQ 4bit 模型作为线上主力如果显存允许保留一版 FP16/BF16 的模型实例给高精度任务。同一个 vLLM 服务里也可以注册多个模型不一定非要在模型质量和显存容量之间二选一。还有一个容易踩的坑某些量化模型权重文件如果和 vLLM 版本不兼容启动时会直接报错如提示 unsupported quantization。这种情况先去模型仓库确认是否标注了 vLLM 支持同时把 vLLM 升级到该模型所属社区验证过的版本一般就能解决。4.4 日常监测日志、指标与优雅降级vLLM 启动后日志里会打印显存统计、KV Cache block 数量、请求处理速率等关键数据。我长期跑下来的习惯是把几个关键指标记下来做基线可用 KV Cache block 数、平均 request throughput、queue 长度。一旦后面改了参数、换了模型就对比基线判断是否退化。服务稳定性上我建议在 vLLM 前面再加一层简单的代理或网关负责健康检查、超时控制、限流。本地服务和高并发的应用场景结合时网关可以把负载控制在下游可承受的范围避免把推理引擎压到极限导致雪崩。我的方案是直接用一个轻量反代加一层请求转发后端 vLLM 挂掉时自动摘除别的节点顶上。这在单机多条推理实例组成的内部集群里效果尤其明显。一些小提示亲手写给需要的人说实话vLLM 本身的部署不复杂真正决定效果好坏的是你能不能对模型、显存、序列长度、并发上限这几个变量做出合理的权衡。我自己反复调整后总结出的一个经验是不要追求单个指标的最大化而是先给自己定一个响应时间容忍度再去调并发参数。比如你的业务要求 30 秒内返回完整回答那你就把 max-num-seqs 从低往高调直到平均响应时间接近 25 秒就停留出一些波动余量。你的业务如果更在意总处理量那就走另一条路放开并发让 GPU 全力跑慢一点没关系。先定容忍度再决定参数这样心里才有底。如果后续你还想把这条路走得更远可以再研究这几个方向给 vLLM 加前缀缓存加快重复提问场景的响应速度用多实例按业务优先级做流量分离或者把量化模型和投机解码组合起来在有限显存内继续压低延迟。本地高并发推理这条路每一步优化都不会白费。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询