西西软件园多重安全检测下载网站、值得信赖的软件下载站!
软件
软件
文章
搜索

首页 → 业内动态 → 业内资讯 → 并发从8涨到32,推理慢了6倍:KV Cache把显存吃光了

并发从8涨到32,推理慢了6倍:KV Cache把显存吃光了

相关软件相关文章发表评论 来源:本站整理时间:2026/11/3 19:45:09字体大小:A-A+

作者:xiix点击:0次评论:0次标签: 大模型推理 KVCache GPU服务器 vLLM 推理并发调优

玄星ai助手v1.1.3 安卓AI智能助手
  • 类型:系统工具大小:44.2M语言:中文 评分:10.0
  • 标签:
立即下载

两个月前我接手了一个自建的大模型 API 服务:7B 模型跑在一台单卡 GPU 服务器上,vLLM 起的服务,前面挂一层 Nginx 做入口,平时业务量不大,跑得挺稳。后来运营把小程序的活动推上去了,QPS 从 2 涨到 15。

我的第一反应很朴素:加并发。于是把 --max-num-seqs 从 8 调到 32,Nginx 那边也把 upstream keepalive 和连接数一起放开。结果是吞吐几乎没动,平均首字延迟从 1.2 秒飙到 7 秒多,P95 到了 11 秒,客户端开始大面积超时。服务日志里反复刷着一行 GPU KV cache is full,后面跟着一串 preemption 警告。

最让人想不通的是,nvidia-smi 一看,显存还剩着 30 多 G 空闲,显卡利用率也到了 95%。明明没爆显存,为什么请求全在排队?这篇就把这个坑从头捋一遍。

如果你也在自建大模型推理服务、需要一台显存给得足的 GPU 机器先把环境跑起来,这类 AI 基础设施可以先看看:aaitr(AI云基础设施),选卡的时候显存是第一位的,别光看价格。

GPU 显卡上显存被 KV Cache 占满的示意图:模型权重之外,KV Cache 随并发线性膨胀

一、为什么加并发反而更慢:KV Cache 到底是什么

先把这笔账算清楚。GPU 显存里装的东西大致分四块:模型权重(7B 模型 fp16 大约 14G,永远占着)、KV Cache、激活值和临时缓冲、以及碎片。前面两次排查我盯的全是权重,因为它是静态的;实际上把服务打慢的是 KV Cache。

KV Cache 是什么?模型自回归生成时,每生成一个 token,都要把已经生成的那段内容的 Key 和 Value 缓存下来,下次接着算就不用从头再算一遍。这个缓存是按请求、按 token 数线性增长的:请求越长、并发越高,占的显存就越多。请求进来到生成结束,缓存一直挂着不释放,别人就用不了这块显存。

关键的一句话:并发数 × 单请求序列长度 = KV Cache 的总占用。7B 模型 fp16 下,每个 token 的 KV 大约 512KB(2 份 K/V × 32 层 × 32 头 × 128 维 × 2 字节)。算一笔账:

512KB × 2048 token ≈ 1GB / 请求
1GB × 32 并发 ≈ 32GB 显存

所以我把并发开到 32 的时候,光 KV Cache 就要吃掉 30G 以上的显存,加上权重和激活,留给引擎的余量被压到很薄。引擎发现可用块不够,就会把正在跑的请求踢掉重算(preemption + recompute),被踢掉的请求从头再来一遍这就是延迟翻 6 倍的直接原因:不是算力不够,是显存不够导致算力在反复做无用功。

二、怎么区分"显存不够"和"算力不够"

我这台机器当时就误判过一次,分享三个我实际在用的检查点。

第一,看 vLLM 启动日志。服务起来的时候日志里会打印一行 GPU KV cache size: N tokens,这个 N 就是引擎算出来的 KV 缓存容量(以 token 计)。N 除以你的平均请求长度,就大致等于能同时跑多少个请求。日志里还有 Maximum concurrency 这个提示,它把上限直接告诉你了。

第二,看 /metrics 暴露的指标。重点盯三个:num_requests_running(正在跑)、num_requests_waiting(在排队)、gpu_cache_usage_perc(KV 缓存占用率)。经验判断是这样的:

cache 使用率长期 > 95% + waiting 队列持续 > 0 → 显存/KV Cache 瓶颈
cache 使用率只有 40% 但 running 数上不去 → 算力或调度瓶颈
preemption 计数持续上涨 → 显存已经在赶人了

我那次的现场就是:cache 使用率 98%,waiting 队列常年挂着十几个,preemption 计数每小时涨几千。结论很清楚,不是 CPU 不够,也不是 Nginx 没调好。

第三,别只看 nvidia-smi 的空闲显存。空闲显存不等于可用显存引擎有预留水位,碎片也会占着地方。真正该看的是引擎自己算出来的 KV cache size 和 cache 使用率。

工程师在机房用笔记本查看 GPU 推理服务的显存占用与排队指标

三、真正管用的几个参数,按顺序来

1)--gpu-memory-utilization:默认 0.9,可以推到 0.92~0.95。这个值告诉引擎"允许你用多少比例的显存"。卡上没别的负载时,调高它能把更多显存让给 KV Cache。但别贪到 0.98,留一点给 CUDA context 和显存碎片,不然容易在长跑之后 OOM。

2)--max-model-len:这是最容易漏的一个。很多教程直接照抄模型上限,比如 8192 或 32768。可你的业务真的需要这么长的上下文吗?KV Cache 的占用跟这个值直接相关,设成 4096 而不是 32768,等于给并发腾出了 8 倍的空间。我把 max-model-len 从 8192 降到 4096,再把 max-num-seqs 从 32 调回 16,延迟立刻回到 1.3 秒,吞吐反而比之前高。

3)--max-num-seqs:并发上限要压测着定,不要拍脑袋。我现在的做法是从 4 开始阶梯往上加,8、16、32 各跑一轮 10 分钟,记录吞吐和 P95 延迟,找到那个"吞吐还在涨、延迟已经开始抬头"的拐点,就停在拐点前一个档位。

4)量化:显存不够时最有效的一招。7B 模型从 fp16 换到 AWQ 或 GPTQ 的 4bit 量化,权重能从 14G 压到 5G 上下,省出来的七八 G 全部变成 KV Cache 空间,并发能力直接翻倍。代价是精度损失一点点,对聊天、摘要、分类这类任务基本无感,对严格的代码生成要自己评测一下。

5)--swap-space:只用来救急性子。它把放不下的 KV Cache 溢写到内存(CPU swap),服务不会崩,但请求要跨 PCIe 搬数据,延迟会非常难看。它是保险丝,不是性能手段。

大模型推理请求排队等待的队列示意图:并发请求在引擎前排队

四、并发数到底该设多少:先算一遍 KV Cache 容量

与其撞墙再调,不如先算。单请求的 KV 占用公式:

2(K 和 V)× 层数 × KV 头数 × head_dim × 精度字节 × token 数

拿 7B 模型举例:2 × 32 层 × 32 头 × 128 维 × 2 字节 = 512KB/token。假设你把 max-model-len 设为 4096,一个满长请求的 KV 大约 2GB。若卡上扣掉 14G 权重、2G 激活、1G 碎片之后还剩 24G 给 KV Cache,那理论上最多同时放 12 个满长请求。实际还要留 20% 余量,8~10 个并发是比较稳的数字。

我一开始就是跳过了这一步,直接把并发调到 32,等于让 32 个满长请求(64G)去挤 24G 的空间,必然抢占。这一步算清楚了,后面的参数怎么调都有底。

五、被抢占的请求会重算,延迟就是这么翻的

preemption 的机制值得单独说,因为它是"加并发变慢"最容易被误判成别的问题的一环。引擎发现 KV 块不够时,会挑一个正在生成的请求把它踢出去,回收它的缓存块给新请求用;被踢的请求恢复时,缓存没了,要把已经生成的那一段全部重新算一遍。

这意味着 GPU 有一大段时间在做重复计算,算力利用率看着很高,产出却很低。表现出来就是:QPS 上不去、平均延迟飙升、GPU 利用率 95% 以上但业务毫无进展。日志里会有 Sequence group ... is preempted by PreemptionMode.RECOMPUTE 之类的警告行。

排查时我会直接盯这个计数:只要 preemption 计数在涨,不管当前延迟如何,都说明并发设置已经越界了,先降下来再说。

六、压测别只盯 QPS

我做压测时会记三个数:吞吐(输出 token/s)、首字延迟 TTFT、每个输出 token 的时间 TPOT。只看 QPS 很容易骗自己QPS 是并发除以延迟,并发和延迟一起涨,QPS 可能纹丝不动。

正确的做法是固定并发阶梯,画吞吐-延迟曲线,找拐点。还可以做一次"输入长度扫描":把请求的输入 token 数从 500 扫到 4000,看看吞吐在哪一段开始塌。这条曲线基本就是你的 KV Cache 容量边界。

机房内 GPU 服务器与网络交换设备:大模型推理服务对显存与带宽的双重要求

七、写在最后

这次排查最大的教训是:大模型推理服务的瓶颈几乎总是显存,而不是算力。判断一台 GPU 服务器能不能扛住你的业务,先看三件事显存多大、是什么卡(显存带宽决定单请求速度)、要不要多卡(多卡 tensor parallel 会带来额外的通信开销,单机能解决就别上多机)。

参数上我的最终配置是:7B 模型 AWQ 量化、max-model-len 4096、max-num-seqs 12、gpu-memory-utilization 0.92,压测下来 P95 稳定在 1.9 秒,比之前"并发 32 延迟 11 秒"的状态好了一个数量级。

最后提醒一句:大模型服务上线前一定留一个能立刻回滚的版本。改参数看着是小事,改错了服务就起不来。配置进版本管理,模型权重和量化文件放快照里,出问题重置比现场调参快得多。

如果你也在折腾自建推理,记得先把"每请求 KV 占用"这一笔账算出来,再决定并发数。这一步能省你后面大半的加班时间。

    相关评论

    阅读本文后您有什么感想? 已有人给出评价!

    • 8 喜欢喜欢
    • 3 顶顶
    • 1 难过难过
    • 5 囧囧
    • 3 围观围观
    • 2 无聊无聊

    发表评论

    昵称:
    表情: 高兴 可 汗 我不要 害羞 好 下下下 送花 屎 亲亲