首页 / 资讯中心 / 文章详情

vLLM Serve 调参实战:从显存分配到高并发吞吐的完整指南

vLLM Serve 调参实战:从显存分配到高并发吞吐的完整指南 ★ FEATURED ARTICLE
第一次跑 vLLM Serve很多人会被一长串参数劝退。拿一个开源 7B 对话模型做推理服务不加任何参数也能跑起来但跑完一看显存还剩一大截并发一上去请求全在排队日志里一堆警告又不知道从哪里下手。这篇文章不打算把每个参数都念一遍而是从一次真实部署的调参链路出发把 vLLM Serve 里常用但容易用错的参数按“显存分配—批处理调度—计算精度—多卡分布—前缀缓存—问题排查”这条线拆开讲清楚。内容适合三类人第一次用 vLLM 部署推理服务的新手需要把服务压到更高吞吐的开发者以及刚接手多卡推理服务、被各种显存问题折腾过的人。读完之后你能明白每个参数为什么存在、改它会影响什么以及遇到常见报错时应该先动哪个旋钮。1. 先把服务拉起来模型、接口与基础参数1.1 模型路径与模型名参数看似简单却容易翻车--model是第一个要决定的参数它告诉 vLLM 加载哪个模型。可以指向本地目录也可以直接填一个远端模型仓库的标识符。本地目录里必须有模型权重和完整的配置文件vLLM 启动时会根据 config 推断模型的层数、注意力头数、词汇表大小这些结构信息。如果在生产环境使用我建议用脚本提前把模型同步到本地磁盘不要每次启动都从远端拉权重否则冷启动时间会非常感人。--served-model-name是服务对调用方暴露的模型名。很多人忽略它直接用默认值等后面同时部署多个模型时才发现调用方根本区分不了。它跟--model不是一回事--model是“服务器要加载哪个权重”--served-model-name是“调用方应该写哪个名字来访问这个服务”。例如模型本地目录叫./models/my-7b你可以对外叫my-7b-prod这样后面灰度切流时不需要改调用方代码。还有一个容易被忽略的--tokenizer参数。默认情况下 vLLM 会从模型目录的 config 推断 tokenizer但有些自定义模型的 tokenizer 文件跟权重不在同一个目录或者预处理器经过了特殊改造这时候不显式指定就会遇到乱码、句子截断异常等问题。排查思路很简单模型输出中文突然变乱、特别短就停优先检查 tokenizer 是否匹配而不是急着怀疑显存不够。1.2 服务监听与认证参数生产环境必须处理--host默认绑定127.0.0.1只允许本机访问。部署在服务器上给其他服务调用时记得改成0.0.0.0这个坑几乎每个新手都会踩一次容器起来了、日志也正常但外部就是连不上最后发现是 host 没改。--port默认 8000遇到端口冲突可以直接换。真正重要的是--api-key。vLLM Serve 暴露的是标准 HTTP 接口如果不设置认证任何拿到服务器端口的人都能直接调用你的模型算力全被别人白嫖。生产环境务必设置并且用环境变量或密钥管理服务注入不要写死在启动脚本里。--disable-log-requests是我每次部署都会带的参数默认情况下 vLLM 会把每个请求的详细内容打到日志这在隐私敏感的场景下非常危险。关闭请求日志之后服务运行状态、错误栈这些日志仍然保留只是不再刷用户输入。1.3 一个最简可用启动命令先给一个最小启动命令能跑通再谈优化vllm serve ./models/my-7b \ --served-model-name my-7b \ --host 0.0.0.0 \ --port 8000 \ --api-key your-secret-key \ --disable-log-requests不加任何显存和并发参数时vLLM 会用默认配置启动。第一次调试时先用这条命令把服务拉起来然后访问健康检查接口和补全接口确认模型能正常回答再进入下一步调参。很多同学一上来就堆上一堆参数出了问题反而不知道是哪个参数导致的这是调优的大忌。2. 显存分配逻辑KV Cache 与 gpu-memory-utilization2.1 KV Cache 是什么为什么只看首 token 看不出问题KV Cache 是 vLLM 性能的核心也是显存占用的主要去向。打个比方模型每生成一个 token都要反复“回看”之前已经生成的所有 token 的 Key 向量和 Value 向量。与其每次重新算一遍不如把这些向量缓存下来这就是 KV Cache。它决定了你同时能处理多少请求、能支持多长的上下文。只看首 token 延迟完全看不出 KV Cache 的影响因为单个短请求根本不会把缓存打满。真正的考验发生在长文和并发场景一个请求带 8K 上文另一个请求带 16K 上文KV Cache 会成倍增长显存很快见底。所以调优的第一步是先搞清楚 KV Cache 在你的显存里占了多大空间。2.2 gpu-memory-utilization 的经验取值--gpu-memory-utilization默认是 0.9含义是 vLLM 最多能占用多少比例的显存。剩下那部分给模型权重、CUDA 内核、中间激活和推理框架本身的运行时代码用。0.9 适合专属推理卡比如整张卡只跑 vLLM 一个服务。但如果是跟其他进程共用显卡或者显存本来就紧张调到 0.6 到 0.7 更稳妥。这个参数并不是越高越好调到 0.98 甚至 0.99 时模型加载阶段可能不报错一接请求就直接 OOM。原因在于运行时需要临时分配的激活内存不在预留预算里极端利用率没有给动态内存留后路。实操时我一般这样取值单卡跑 7B 级别模型0.85 起步多卡张量并行0.88 到 0.9加了量化之后反而要降一点因为量化权重和反量化过程同样会占额外显存。一个有效的观察窗口是 vLLM 启动日志里打印的 KV Cache 容量数值例如KV cache size 4.20 GB这个数字能直接告诉你缓存预算还剩多少。2.3 max-model-len 设多大先算账再决定--max-model-len决定模型允许的最大上下文长度它不只是模型结构参数更是一个显存预算参数。KV Cache 的显存占用大致可以这样估算KV Cache 字节数 ≈ 2K 和 V 两份× 层数 × KV 头数 × 头维度 × 序列长度 × 并发序列数 × 每个元素字节数以某个 32 层、KV 头数为 8、头维度 128 的 7B 开源模型为例半精度下每个元素占 2 字节单条序列每 1K token 的 KV Cache 大约是 2 × 32 × 8 × 128 × 1024 × 2 134 MB。如果模型用的是全量注意力头而不是分组查询注意力KV 头数变成 32单条序列每 1K token 就要 512 MB。看到差距了吗上下文长度从 2K 提到 16K同样的显存能支撑的并发数直接掉一个数量级。所以我的建议是先统计线上真实请求的上下文长度分布取 P99 再上浮一点不要按模型的理论最大长度去配。之前帮一个团队调服务业务平均上下文只有 3K、最长不超过 8K他们却把 max-model-len 按模型理论值 32K 设置结果 KV Cache 预占了大量显存并发一直被压得很低。把它改成 12K 之后同样的硬件吞吐翻倍都不止。2.4 用启动日志反推显存分配调这个参数不要靠猜每次改完参数重启看两个地方第一是 vLLM 启动日志里 KV Cache size 的数值第二是监控工具里实际的显存水位。如果 KV Cache 很小但显存占用已经很高说明权重或运行时占了大头如果 KV Cache 大得惊人说明上下文或者并发预算开过了。明确了这步后面调 max-num-seqs 才有依据。3. 吞吐与并发batch 与 token 调度参数3.1 max-num-seqs 与 max-num-batched-tokens 的关系--max-num-seqs是服务端允许同时计算的请求序列数上限--max-num-batched-tokens是每一步迭代最多处理的 token 总数。这两个参数的关系可以这么理解前者是餐厅能同时接纳的客人桌数后者是后厨一次最多能同时下锅的菜量。桌数开得再大后厨一次只能炒几道菜客人还是得等着。实操中常见的问题是只调其中一个。比如把 max-num-seqs 拉到 256但 max-num-batched-tokens 还是默认值并发请求确实进来了可每个迭代能处理的 token 只有那么多GPU 算力没有被喂满表现就是排队时间依然很高、吞吐没有明显变化。3.2 连续批处理的工作方式vLLM 的优势在于连续批处理也就是 continuous batching。传统推理框架要等一个 batch 全部生成结束才能接收下一批请求这会让 GPU 在 batch 尾声阶段大量空闲。vLLM 的做法是每步迭代结束后已经完成的请求立刻移出 batch新请求可以在迭代间隙插入当前计算。这样 GPU 几乎所有时间都在算 token而不是等请求。--max-num-batched-tokens就是这一步能塞进 GPU 的上限。设得太小算力喂不满设得太大每步计算量波动会很剧烈显存峰值也随之上升。实际调参时我习惯从 4096 起步压测观察 GPU 利用率没到 90% 就往上加一次加 2048。3.3 max-num-seqs 调优实践max-num-seqs 直接受 KV Cache 预算约束。可用 KV Cache 总量除以单条序列在业务平均长度下的缓存占用可以得到一个参考并发上限。但实际不用算得那么精确更快的方法是按配置表去压测模型规模显卡显存max-num-seqs 参考起点7B 级别24GB12813B 级别40GB6470B 级别多卡80GB 级别整机32这些只是起点不是终点。压测时如果吞吐还在涨、排队时间也不高就继续翻倍如果出现显存不足或排队时间明显上升就退回一档。值得留意的是max-num-seqs 增大后每个请求的 KV Cache 都会占一块固定显存所以测试时要用接近业务真实长度的 prompt而不是用几行字的短请求去测否则结果完全是误导。3.4 enable-chunked-prefill 解决长 prompt 堵车问题长 prompt 的 prefill 阶段会一次性算完一个大请求往往独占整块算力后面所有短请求都得等。开--enable-chunked-prefill之后vLLM 会把长 prompt 拆成小块跟 decode 阶段的 token 交替计算避免“一个长文档问题卡住所有人”的场景。这在 RAG 类服务里效果特别明显业务方经常上传几千 token 的长文档其他用户的短问题被堵好几秒。开启后短请求的首 token 延迟显著下降长请求的总完成时间也不会差太多。代价是调度器多了一层切块和合并逻辑极端情况下吞吐会有一点损失但绝大多数业务场景下收益远大于损失。4. 计算图与数据精度eager、dtype 与量化参数4.1 enforce-eager 要不要打开默认情况下 vLLM 会用 GPU 静态计算图技术把模型的计算提前捕获成固定图结构从而减少每一步调度的内核启动开销。这对高频小 token 的 decode 阶段非常有利是吞吐提升的重要来源。代价是每次输入形状变化时需要重放计算图而且会为每条序列预留额外显存。--enforce-eager的作用就是禁用这个静态图机制改为普通的即时执行模式。什么场景需要开排查模型计算的诡异 bug 时禁用后错误信息通常更直接或者在新的实验性结构上运行时静态图捕获阶段报错可以先用 eager 模式验证模型本身是否正常。但生产环境不建议常开吞吐下降非常明显尤其短文本场景下差距可能到 30% 以上。4.2 dtype 选 float16 还是 bfloat16--dtype默认是 auto也就是从模型 config 里读取保存精度。大多数开源模型的权重保存为 float16用float16即可。bfloat16 的指数范围比 float16 大得多对权重溢出不敏感训练后的模型直接用 bfloat16 加载通常数值更稳但前提是你的显卡硬件支持而且加载时也必须按 bfloat16 处理。实操建议就一条权重是什么精度--dtype就填什么精度不要混。出现过一些情况模型 config 标记为 float16但实际权重是 bfloat16 导出的启动不报错输出质量却明显下降。这时把 dtype 改成 bfloat16 重新加载问题立刻消失。判断方法很简单用一段固定 prompt 分别用两种 dtype 加载对比输出是否稳定。4.3 量化参数和 KV Cache 数据精度--quantization支持 awq、gptq、fp8 这类量化方式。量化可以把权重从 16 位压到 4 位或 8 位显著降低权重占用的显存让模型放进更小的卡。但要注意量化参数必须跟模型权重本身的格式匹配模型是 awq 量化格式就用--quantization awq模型是普通 fp16 格式你强行指定 awq要么加载失败要么生成垃圾内容。--kv-cache-dtype fp8是另一条独立的路线它把 KV Cache 从 16 位压成 8 位缓存显存直接砍半。代价是精度下降在长上下文下误差可能累积生成质量是否可接受一定要用业务评测集验证。我的选型表大致是这样的场景推荐做法原因权重显存紧张--quantization awq/gptq降低权重占用模型更快装进单卡长文高并发瓶颈在缓存--kv-cache-dtype fp8KV Cache 减半并发容量直接翻倍显存极致吃紧两者组合权重和缓存都压缩但需要验证质量生产质量敏感不量化 KV Cache留足显存给缓存优先保证输出质量调试时先把两项分开测不要一上来就组合。量化带来的收益通常不是线性的有些场景下权重从 16 位压到 8 位吞吐提升只有几个百分点但要承担的精度风险却大得多。5. 多卡与分布式部署参数5.1 tensor-parallel-size 怎么选--tensor-parallel-size是张量并行参数含义是把模型的计算切成几份放到几张卡上同时算。原理是把注意力矩阵、全连接矩阵按行或列切分每步计算之后做一次全量聚合通信。选这个参数的核心原则是只有当单卡显存放不下模型权重加常用 KV Cache 时才开张量并行。如果一张卡能放下不要为了“用满所有卡”而强行开 TP2吞吐不一定涨还可能因为通信开销反而下降。张量并行每层计算都要做聚合通信卡之间走的是 PCIe 或高性能卡间互联一旦跨机器走网络通信延迟会直接把性能拖垮。所以多卡场景尽量选同一台机器上的卡。有一个容易被忽略的点开张量并行之后KV Cache 上的预算要按多卡总和来算。日志里显示的 KV Cache 是切分后每卡的量实际服务能支持的并发需要把多卡的缓存加起来评估。5.2 max-parallel-loading-workers 与权重加载加速--max-parallel-loading-workers控制模型加载阶段并行读盘的进程数。大模型权重动辄几十 GB默认并行度不高时加载阶段 CPU 和磁盘 IO 都打不满冷启动时间很长。对这个参数我的经验是分模型规模处理。7B 级别模型保持默认即可并行度太高反而让磁盘小文件 IO 变成瓶颈70B 级别模型可以调到 8启动时间能缩短不少。但要注意加载权重时并行进程多了机器内存占用也会跟着涨因为每个进程都会有一份权重副本。机器内存不足时不是显存 OOM而是主存先被吃满这个报错经常被误判。5.3 多卡启动示例vllm serve ./models/my-70b \ --served-model-name my-70b \ --tensor-parallel-size 8 \ --gpu-memory-utilization 0.88 \ --max-model-len 8192 \ --max-num-seqs 64 \ --api-key your-secret-key启动之后用监控工具逐卡看显存水位确认负载是否均匀。如果发现某张卡明显偏高、其他卡闲置优先检查 pending 的请求是不是触发了某种不均衡的调度再考虑是否要限流或调整并发参数。6. 前缀缓存与长文场景prefix caching 与聊天模板6.1 enable-prefix-caching 对 RAG 和多轮会话很有效--enable-prefix-caching会开启前缀缓存。vLLM 会把之前算过的 KV Cache 按块缓存下来新请求如果前缀哈希命中直接复用之前的计算结果不需要重新跑 prefill。这个参数最适合两类业务RAG 服务中系统提示词和固定的检索上下文会反复出现命中率极高多轮对话服务中历史消息作为前缀被反复携带。我实测过一个几千字长 prompt 的场景命中缓存后首 token 延迟从几秒降到了几十毫秒吞吐提升非常明显而且不需要改动任何上层代码。判断一个服务适不适合开看缓存命中率。vLLM 的监控指标里会暴露命中率数据如果持续低于 20%说明请求前缀随机性太强缓存基本没有复用价值这时开启只会增加哈希表维护的开销关掉反而更稳。6.2 前缀随机性强时不要盲目开启日志分析类、临时字符串处理类服务prompt 几乎每次都不一样命中率极低开启前缀缓存是纯负担。还有一种情况也要注意虽然很多请求共享系统提示词但提示词后面紧跟的是用户输入如果前缀缓存按整块不跨请求复用命中率也不会太高。这类服务建议先用压测确认命中率再决定是否开启。6.3 chat-template 是隐性性能参数模型在对话补全接口下工作时需要把原始消息列表转换成带特殊标记符的 prompt。vLLM 默认读取模型目录里的 tokenizer 配置中的聊天模板字段如果模板缺失或者格式不对会出现输出截断、不断重复同一句话等诡异问题。--chat-template可以指定一个自定义模板文件。很多团队换模型之后回答质量变差问题不在模型权重而是模板里的提示词格式跟新模型的训练格式不匹配。另外还要注意模板本身会占用上下文长度模板写得越长实际留给用户内容的 token 越少KV Cache 的预算也被吃掉一部分。7. 踩坑实录常见报错与排查方法7.1 显存不足CUDA OOM的排查顺序显存不足是 vLLM 部署里最常见的报错但报错位置五花八门有的加载阶段就炸有的一接请求才崩。我的排查顺序非常固定先看启动日志里 KV Cache size 的数值确认预留了多少显存。检查--gpu-memory-utilization是否设得过高优先降到 0.85 再试。检查--max-model-len是否远大于业务真实诉求下调一个档位。最后再压--max-num-seqs把并发上限降下来。这三个参数构成了显存三角利用率、上下文长度、并发上限。调整顺序有讲究优先动 max-model-len 影响最直接因为每多一档上下文长度KV Cache 都是线性增长利用率次之并发上限放最后因为调低并发只影响吞吐不影响模型能力。7.2 请求排队时间长的优化方向如果监控里排队时间持续偏高而 GPU 利用率没有打满大概率是并发入口太小。请求进不来GPU 还闲着。先调大--max-num-seqs再调大--max-num-batched-tokens。但如果是 GPU 利用率已经接近 100%排队是因为计算能力不够这时候加并发没有意义只能通过量化、减上下文、多卡并行或者换更大显存来解决。诊断时一定要同时看两边数据排队指标和 GPU 利用率。只看其一很容易做出相反的操作。7.3 性能上不去的根因定位压测服务性能先看两个指标吞吐 token/s 和首 token 延迟。如果吞吐上不去第一件事看 GPU 利用率。利用率低说明请求喂不满算力方向是提高并发和 batch利用率高说明算力饱和方向是减负或扩容。简单验证接口是否正常可以直接用 curl 手测curl http://127.0.0.1:8000/v1/chat/completions \ -H Authorization: Bearer your-secret-key \ -H Content-Type: application/json \ -d { model: my-7b, messages: [{role: user, content: 写一段200字的自我介绍}], max_tokens: 200 }手测只能验证通不通压并发还是要用专门的压测工具。注意压测时要把请求长度和输出长度都设为接近业务真实值用短输入短输出压出来的结果对长文业务几乎没有参考价值。7.4 版本升级后的参数变化vLLM 迭代速度很快参数默认值甚至参数名都会在版本之间变化。比如某些老版本里的批处理参数到新版本被重构启动时会直接给出警告。我的做法是升级前先看 release notes升级后先小流量灰度不要拿着旧命令直接全量替换。遇到参数找不到先跑一下vllm serve --help以当前版本实际支持的参数为准。旧配置文件里的参数如果已经不生效尽早从配置里清理掉否则会误导后续排查。8. 从“最小可用配置”到“高并发生产配置”的调参路线8.1 推荐的调整顺序调参要按顺序来一次只动一个参数改一个记录一次压测数据这样出了问题才能定位到人。先用最简参数把服务跑通确认接口正常。查看显存分布调整--gpu-memory-utilization和--max-model-len让 KV Cache 预算基本匹配业务需求。压测逐步提高--max-num-seqs和--max-num-batched-tokens目标是打满 GPU 利用率。显存仍有瓶颈时再考虑量化、KV Cache 数据精度压缩。最后开启前缀缓存和分块预填充针对长文场景收尾。每一步的改动都记在表格里参数、压测吞吐、首 token 延迟、显存水位四个数据一起记。没有记录就谈不上调优完全是凭运气改参数。8.2 两套参考配置低并发内网小规模场景单卡 24GB 显存跑 7B 级别模型vllm serve ./models/my-7b \ --served-model-name my-7b \ --gpu-memory-utilization 0.85 \ --max-model-len 8192 \ --max-num-seqs 128 \ --max-num-batched-tokens 4096 \ --enable-prefix-caching \ --api-key your-secret-key这套配置的特点是保守、稳定单卡上有充足余量不会因为突发的长请求把显存打爆。高并发生产场景多张 80GB 级别显卡跑 70B 级别模型vllm serve ./models/my-70b \ --served-model-name my-70b \ --tensor-parallel-size 8 \ --gpu-memory-utilization 0.88 \ --max-model-len 8192 \ --max-num-seqs 128 \ --max-num-batched-tokens 8192 \ --enable-chunked-prefill \ --enable-prefix-caching \ --disable-log-requests \ --api-key your-secret-key这套配置适合长文 RAG 服务和并发调用较多的场景。8 卡并行能把 70B 模型完整装下chunked prefill 防止大请求堵车prefix caching 降低长文首 token 延迟。8.3 复盘别把参数当性能银弹调参解决的只是“资源分配不合理”的问题解决不了“模型本身计算量太大”的问题。如果业务大并发需求已经超过单机上限加参数没有意义该扩容就扩容该换推理架构就换推理架构。真正的调优闭环是看指标、定位瓶颈、调参数、再看指标循环迭代。短 token 测试通过不代表长文并发就稳每个版本上线前都要用真实业务流量回放验收。测试数据里混入了几条理论值的最大长度请求观察显存水位是否安全再决定要不要限流。这套参数调到这里我个人最大的体会是vLLM Serve 调参本质上是在有限的显存里做资源再分配而不是凭空变出算力。刚开始我也喜欢把并发调到很大、上下文开到理论最大值结果每次都撞 OOM。后来养成一个习惯先看日志里 KV Cache 的实际分配再动手改参数改完一定记录一次压测数据。如果你也是刚接触 vLLM建议从最小配置跑通开始一次只动一个参数把每一步的变化记下来。遇到任何奇怪的显存报错先想想三个数利用率、上下文长度、并发上限九成的问题都能倒推出来。
阅读完成 · 觉得有帮助?
咨询建站