线上推理服务本来跑得好好的,某天开始报显存不足、请求排队甚至进程被杀,重启一下又好一阵——这种反复最让人抓不住重点。显存被占满通常不是单一原因,可能是并发堆太高、KV 缓存和中间张量没及时释放,也可能是碎片把可用空间切碎了。这篇给运维一条从观测到调参的排查顺序,每一步都写清看什么指标、动哪个参数,以及什么情况下这套办法不适用。
线上推理服务本来跑得好好的,某天开始报显存不足、请求排队,甚至进程直接被系统杀掉,重启一下又能撑一阵——这种反复最折磨人。它不像单纯容量不够那样一眼能看出来,往往几个原因叠在一起:并发堆太高、缓存和中间张量没及时释放、碎片把本来够用的空间切碎。只盯着一个方向调,改完指标没动,很容易误判。
下面给一条从观测到动手的顺序,每一步都写清看什么、动什么、什么时候该停手。核心原则是:先分清现象再动手,一次只改一项,每项都留观测和回滚。别一上来就重启或者降并发,那样你永远不知道真正的原因是什么。
先分清是「一直满」还是「涨上去不回落」
显存告警出现时,第一件事不是调参数,而是把现象归类。同样是「满了」,下面这三种走向代表完全不同的根因,处理方式也差得很远。归类错了,后面所有动作都是白费。
- 启动即满
- 服务刚起来没接几个请求就贴顶,基本是模型加载尺寸、并行配置或预留缓冲设置超出当前卡容量
- 随请求量缓慢爬升
- 曲线随流量上行、不再回落,通常是并发或缓存随请求累积,属于容量与策略问题
- 跑一段时间突然爆掉
- 长时间平稳后骤增,多与长序列请求、缓存持续增长或碎片累积有关
要看的观测项至少有四类:单卡已用显存随时间曲线、活跃请求数、实际批大小、单请求序列长度分布。只盯显存一个数字会漏掉关键线索,比如显存平稳但请求数也在涨,说明单请求占用在悄悄变大。
- 把一个完整业务周期(高峰到低谷)的曲线拉出来,而不是只看告警那一刻的快照
- 对照活跃请求数和批大小,看显存增长是跟着流量走还是自己往上跑
- 统计请求的序列长度分布,找出长尾那一小撮是不是占了大头
- 先在 接口测速 量出稳定状态下的延迟与吞吐基线,异常时段拿来对比
并发与批大小:先把预留空间留出来
很多人习惯把并发开到显存用满为止,觉得闲置就是浪费。问题是显存不是静态的,请求长度、解码步数都会有尖峰,压到极限后第一个稍长的请求就能把服务打爆。正确做法是从目标吞吐反推并发,而不是拿卡容量去倒推。
| 现象 | 优先怀疑 | 先动的参数 |
|---|---|---|
| 并发一提高就 OOM | 并发上限过高,没留尖峰余量 | 并发上限、队列长度上限 |
| 长短请求混批后整批占用飙高 | 批内长度差异过大 | 按长度分桶、限制最大输出长度 |
| 队列越积越长最后全崩 | 队列被当成显存缓冲池 | 设队列上限,满了就拒绝或降级 |
| 单请求正常但整体顶不住 | 模型/精度配置与卡不匹配 | 去部署测算页重算配置 |
还有一种常见误判:把并发降下来后发现吞吐掉得比预期多。这说明瓶颈可能不在并发,而在单请求的序列长度。这时更该做的是限制最大输出长度或按长度分桶,而不是继续压并发。
- 从目标 QPS 和单请求平均耗时反推需要多少并发,再留出尖峰余量,不要用满
- 长序列和短序列混在一批会拉高整批占用,按长度分桶或给输出长度设硬上限
- 队列长度必须封顶,超出就拒绝或降级,别让队列无限膨胀把显存拖垮
- 这套算法不适用于批内长度差异极大的场景,那种情况更适合把长短请求拆成独立服务
KV 缓存、中间张量与对象释放
如果曲线是缓升不回落,重点就转到缓存和对象释放上。KV 缓存随上下文长度线性增长,是长对话场景最容易失控的一块。按「并发数 × 平均序列长度」粗略估算一下它该占多大,再决定上限,而不是让它自由增长。
另一类问题出在自己维护的会话状态上:只增不减的缓存表、没设过期时间的对话历史,跑得越久占得越多。这类问题在压测里往往看不出来,因为压测请求短、生命周期也短,一上真实流量就暴露。
- 流式输出中途断连、客户端超时放弃时,确认对应请求的中间张量和缓存真的被回收了
- 检查所有自己维护的缓存表有没有过期或容量上限,只增不减的一定有问题
- 显存回收有延迟是正常的,关键是它最终会不会降下来,不会降就是泄漏
- 调参后别只看显存数字,错误率和延迟变差说明你可能是靠降质换空间
显存碎片:为什么总量够用却申请不到
碎片是最容易被忽略的一类。典型症状是监控里空闲显存还有一大块,但新请求就是申请失败,重启之后又恢复正常,过一阵再来一遍。总量明明够,卡在「找不到一块连续的」。成因多与不同尺寸张量反复分配释放有关,长序列请求频繁进出会明显加剧。
| 症状 | 是否偏向碎片 | 怎么验证 |
|---|---|---|
| 空闲总量够但申请连续空间失败 | 是 | 看失败时的空闲块分布,而非只看总量 |
| 重启后立刻恢复,跑一阵复发 | 是 | 对比重启前后同一负载下的表现 |
| 请求一多就失败,空闲也少 | 不一定 | 先按并发和缓存两条线排查 |
| 固定长度请求也随机失败 | 是 | 说明分配模式本身在制造碎片 |
处理方向上,靠调参只能缓解:限制最大序列长度、把批大小收敛到少数几个固定档位、减少频繁重建模型或加载卸载权重,都能降低不同尺寸张量交错分配的概率。如果几轮下来还是反复复发,说明分配策略和当前负载形态不匹配,长期得换更合适的部署形态,比如固定分档的批处理服务或带显存池管理的运行时。
调参顺序与回滚
前面三条线理清后,动手的顺序很重要。最忌讳一次改好几项,改完好了不知道是哪项起了作用,坏了也说不清是哪项引入的。推荐按下面这个顺序走,从代价最小、影响最可控的开始。
- 限制输入输出长度给最大序列长度和最大输出长度设硬上限,验证方式是看长尾请求占比是否下降、显存峰值是否跟着降
- 降并发并封队列上限按目标吞吐反推并发,给队列设上限,超出即拒绝或降级;验证看错误率和延迟有没有可接受的劣化
- 调批处理策略按长度分桶或收敛批大小档位;验证看单批显存占用方差是否变小
- 查缓存释放检查会话状态、KV 缓存表和断连请求的回收路径;验证看显存曲线能否回落到基线
- 处理碎片统一批大小档位、限制长序列、减少模型重建;验证看申请失败是否随空闲总量充足而消失
每改一项,记录改动前后的显存峰值、延迟和错误率三个数,方便判断是不是真的有效。线上先小流量验证再全量,改动要能一键回滚。如果几轮调整都压不住,说明容量或架构本身不匹配,该考虑量化、分片或换部署方式了,这时去 部署测算 按模型重新算一遍资源和配置更靠谱。
显存被占满很少是单一原因,先归类现象,再按代价从低到高逐项动,一次只改一项。
常见问题
显存占满时第一步该看什么?
先看已用显存的时间曲线,判断是一直贴顶还是涨上去不回落。前者多半是容量或并发配置不够,后者通常是有对象或缓存没释放。定下方向再动手,比直接调参数快得多。
显存碎片和显存不够怎么区分?
看重启后的表现。重启就恢复正常、跑一段时间又不行,且空闲总量看着还有余量,基本是碎片;如果重启后依然很快占满,那是容量本身不够,得从并发、序列长度或量化上限入手。
调小并发或者截断输入会不会影响体验?
会,但通常比服务直接不可用要好。可以按请求类型分档处理,比如长文档任务单独排队、限制最大输出长度,短请求照常走。具体阈值要结合你自己的延迟基线来定,别照搬别人的数。