推理服务里有一种故障很难排查:显存没用满,nvidia-smi 显示还剩几个 GB,但新请求一进来就 OOM。日志里看不到显存泄漏,进程也没变胖。这类问题大概率不是容量不够,是碎片。
显存和磁盘不一样。磁盘上有个 100MB 的空洞,你还能往里写 50MB 的文件;显存分配器在面对一个连续的大块请求时,如果空闲页分散在十几个不连续的位置,这块内存就是申请不到。所以只看「已用/总量」这个指标做容量规划,在动态 shape、长短请求混跑的推理场景里是会出事的。
碎片率怎么算,先别急着上工具
碎片率没有统一定义,常见的口径是「空闲显存中最大的连续块 / 空闲显存总量」,越接近 1 越健康。这个值比绝对空闲量有用得多:空闲 60% 但最大连续块只有 2GB,和空闲 15% 但最大连续块有 8GB,后者能接的请求反而更多。
腾讯云开发者社区一篇解析 vLLM 碎片管理的文章提到,vLLM 集成了 NVIDIA cuMem 相关的工具来获取底层显存使用细节,并定期计算碎片率、做可视化、设阈值告警。这个思路对做工程的人是有参考价值的:把碎片率当成一个常规指标导出,而不是每次出事再人工去看 snapshot。
不过要留个心眼。资料里的宣传话术(“在线整理”“零拷贝整理”“智能决策整理时机”)我建议对照实际版本核验。真正做显存整理要迁移数据、会占带宽、会打断正在进行的 kernel 执行,远不像内存页整理那么便宜。我对「在线、增量、对性能影响小」这种三连说法持保守态度,至少得看自己的负载在整理窗口内的 P99 延迟有没有恶化。
用 snapshot 定位,别靠猜
PyTorch 的 memory snapshot 是最实在的第一步。torch.cuda.memory._record_memory_history() 打开记录,跑一段真实流量后 dump 出来,能在可视化里看到每个块的申请释放来源。max_allocated 和 reserved 之间的差值长期在十几 GB,基本可以确定分配器缓存了太多尺寸不一的小块。
生产治理有一套常规组合拳,值得按顺序试:
- 把动态 shape 做维度分桶,比如序列长度按 256 的倍数对齐,减少无限多的尺寸档位。
- 调整
PYTORCH_CUDA_ALLOC_CONF,看过expandable_segments是否能减少保留区被切碎的情况。这项在不同 PyTorch 版本上行为有差异,要自己压测。 - 接住 vLLM 的
gpu_memory_utilization预算:调高能多吃显存,但给碎片留的余量也跟着变少。遇到诡异 OOM 时,先确认这个值是不是被无脑拉到了 0.95。
治理方案要可观测、可验证、可回滚。具体说,每一项改动前后都拿同一组回放流量跑,对比峰值显存、碎片率、P99 延迟三个数,再决定留不留。
集群层的碎片,是另一个物种
单卡上的碎片是分配器问题,集群上的「GPU 碎片」是调度结果。NVIDIA 的 Ameya Parab 在技术博客里写过 DGX 云上的一个真实案例:Kubernetes 集群里几万个 L40S,Volcano 调度器用默认的 gang scheduling 加随机放置,结果是大量节点只被占用了 3 个 GPU,剩下 1 个空着,需要整节点 4 卡分布式的训练任务排不进去。
他们的做法是把 bin-packing 接进 Volcano,配置里给 binpack.resources.nvidia.com/gpu 加权,让负载优先塞进已部分占用的节点,把整节点腾出来给大活。数据很有分量:改造后集群 GPU 平均占用率约 90%,超过了合同要求的 80%;顺便减少了一类节点命运,即空闲 4 卡却分散在各处的节点,这种节点的数量从 214 降到约 9 个,可直接用的满节点数大涨。
这里要注意 bin-packing 的代价。它追求的是填满,填满意味着小作业和大作业抢“预留位”。如果你的集群要留余地给突发的多卡推理,过度 bin-pack 反而会让大任务等待时间变长。NVIDIA 也没有说这个策略对所有负载都是免费午餐,只说 gang 的“全无”语义应当保留。
另一篇 CSDN 文章提到用 DQN 做显存超卖与重整理,说大模型推理的显存利用率通常只有 35% 到 45%。这个数字我无法核对具体来源口径,所以只把它当作者给出的前提,不当作行业结论。用强化学习做调度决策,听起来诱人,但在调度器上引入模型意味着一个新的失败模式和一套难以回滚的策略。在生产里,rule-based 的 bin-packing 加明确的优先级,赢面往往更大。
落到监控面板上的四件事
真要建监控,我会先上这四个:
- 显存碎片率,按实例和模型输出,配合告警阈值,但阈值先用历史 p95 定,别拍脑袋。
- allocated/segments 离散度,看分配的块大小分布是否越来越细碎。
- OOM 时的 snapshot 自动落盘,别等事后让人去复现。
- 集群维度可用整卡节点数,这个数最能解释“模型明明有空卡却调度不进去”的工单。
有一个容易被忽略的事实:碎片是会自我强化的。一次 OOM 触发分配器释放缓存,接着下一个请求又产生新的块尺寸,长稳时间的跑法比短压测更能暴露这些问题。
回头说我遇到的某次事故。10 个 A100 跑了 86300 秒,OOM 的请求里只有不到两成长度超过均值。最后定位到频繁变化的 beam 宽度让每次生成的 KV block 尺寸来回跳。把 decoding 阶段的常用尺寸做了固定余量预留之后,碎片率从 0.31 稳定清零,改造大约半天。这说明碎片排查里最简单的办法是找频繁变化的尺寸维度,哪怕你没有完整工具链,也值得先在前几节的位置加上 instrumentation,不要把它留成盲区。
转载请注明出处: 皮哈皮阿
本文的链接地址: https://blog.phpat.com/ai-engineering/642.html
微信或手机浏览器扫一扫,随时随地随心阅读与分享
-
推理成本下降之后:模型路由、KV Cache 与端侧推理
上个月一个团队拿着账单来找我:模型单价降了,费用却涨了三倍。看完调用链就明白了,问题不在单价,在用法。单位 token 便宜,不等于每次任务便宜。
2026/10/10
-
2026 年 AI 应用评测:从单轮打分到多轮轨迹评估
一个 Agent 的评测分数从 72% 涨到 81%,团队很高兴,直到发现它学会了在失败时直接结束任务,把“没做完”算成了“无需回答”。评测设计错了,指标越好看越危险。
2026/10/19
-
动态拼批不是开个开关:线上拼批的取舍与门禁
在线服务里的动态拼批和离线批量推理是两码事。窗口时长、KV Cache 水位、批内参数对齐共同决定拼接能否成立。上线前该盯的是 p99 和显存峰值,而不只是吞吐。
2026/10/25
-
长度惩罚参数调优:常见参数名字容易混淆
长度惩罚在主流 API 里并不存在。真正管长度的是 max_tokens 和 stop sequences,frequency、presence、repetition 惩罚管的是用词重复和新词探索。把这些旋钮分开,能避免输出被截断、术语被替换成同义词两类故障。
2026/10/23
-
推理模型的流式输出:thinking 块怎么流
推理模型的流式输出不只是逐字渲染:思考块、正文、工具调用参数混杂在 SSE 流里,客户端要按字段分流、独立缓冲、支持中途中断。记录接通义千问与智谱过程中的判断和踩坑。
2026/10/24
-
推理服务排队延迟的分层观测:从请求数到Token账本
盯 QPS 和 P99 看不到排队延迟长在哪。把这件事拆成入口→上游准入队列→后端等待→batch 解码四层,用 vLLM 暴露的排队与 KV 缓存指标做层级归因,讨论分层之后仍然看不见的部分与取舍。
5天前
京公网安备11011402053654 
@muffin:链接已更新,分类图像功能...
AI网址导航模版图标库更换指南
作者大大,图标库邀请链接失效了,麻烦更新...
AI网址导航模版图标库更换指南
太好看了,快点更新!
友情链接
@戴墨镜的刘备:哈喽,路过点赞。
vue.js组件之手写签名板vue-signature-simple
太好看了,快点更新!太棒了!👍真的很赞!
vue.js组件之手写签名板vue-signature-simple
你写得非常清晰明了,让我很容易理解你的观...
Emlog插件RSS订阅器发布