Skip to content

显存优化与性能调优深入阅读

假设你接手的是同一个模型系统:训练阶段在中后段开始 OOM;进入推理阶段后,长上下文和多轮对话又把 KV Cache 顶高,最终不是 batch 或并发上不去,就是延迟和吞吐一起变差。

这条线最重要的是按暴露顺序判断:训练先在哪一侧失控,推理又在哪一侧顶住预算,最后哪些方案只是止血,哪些方案真的值得保留。

显存优化从对象账本到证据决策

图片先把显存对象、生命周期、候选策略和证据出口放在同一张图中;下面按训练、推理和端到端验证三个阶段展开。

主项目线先处理训练侧证据,再进入按需扩展:73 建立训练基线,76 比较 checkpoint / offload / hybrid,75 形成训练侧预算决策,74 用 profiling 对候选方案做端到端验证。Task4–5 的推理缓存与量化内容解决另一类显存问题,不是训练项目的硬性前置。

完整阅读顺序可以概括为:先理解显存对象和生命周期,再建立账本;确认训练侧压力后进入 73 → 76 → 75 → 74,如果问题转向推理,则分别进入 KV Cache 和量化分支;单卡无法承载时,再进入分布式扩展。每条分支都沿“机制 → 策略 → 证据 → 决策”展开,不要求把所有分支串成一条线性前置。

起点:先建立显存问题的共同语言

这条路线不是从“选哪个省显存技巧”开始,而是先确认系统中有哪些显存对象,以及它们在什么时候存在。

Task0 先从训练计算图开始:forward 产生的部分中间结果可能要留到 backward 使用,因此 activation 会在训练期间形成动态显存压力。Part00 07 Autograd and Backward 负责建立通用的计算图、梯度和生命周期语言,18 Activation and Loss Backward 先观察局部梯度和激活门控,再由 17 Autograd Basics 把这些规则扩展到 Attention backward、saved tensors 和状态代价;这里得到的是机制判断,不是某个 GPU 上的显存节省比例。

Task1 再把对象放回硬件和运行时环境:dtype 决定对象的字节数,模型规模决定对象数量,GPU 内存层级决定容量和带宽,06 VRAM / ZeRO 再把参数、梯度和 optimizer state 放入训练状态账本。主线是 01 → 02 → 03 → 0604 Attention12 Tensor Core / Mixed Precision14 FlashAttention Memory Model 是共享支撑,13 Profiling 是后续测量出口,Part00 20 Profiling and Memory Ledger 用来补充对象和指标的统一视角。

到这里,学习者应该能够提出一个可测量的问题:当前压力来自哪个对象、发生在生命周期的哪个阶段、属于容量不足还是带宽 / 搬运问题。只有完成这一步,后面的 checkpoint、offload、KV Cache 或量化比较才有明确的假设。

因此 Task1 的位置是一个桥梁,而不是额外的学习负担:Task0 解释“为什么会保存状态”,Task1 统一“状态如何进入资源账本和测量指标”,Task2 才比较“如何用重算、累积或搬运换显存”。如果还不能指出主要对象,就不应直接根据技巧名称选择策略。

第一段:训练中后段开始 OOM

故事可以从一个常见症状开始:某个 batch、序列长度或训练阶段触发 OOM。第一反应往往是缩 batch,但这通常只是止血动作,不是判断结论。更稳的做法是先沿训练侧显存链路排一遍:

这一段真正要拆开的,是 effective batchactivationcheckpointingoffload。核心路径先完成 73 -> 76 checkpoint -> 75offload / hybrid 和更高压力 workload 属于训练侧扩展。很多时候 batch 只是把 activation、重算和搬运成本一并放大了,而不是唯一矛盾本身。

这里可以回看 Part00 的共享入口,确认 dtype、device、输入形状、布局和错误类型。它们提供排查语言,但不替代 73 / 76 的真实测量。

训练侧显存压力定位

图中先改变 workload,再判断压力对象,最后选择 checkpoint、offload 或预算策略;它不直接给出某张 GPU 的节省比例。

如果压力来自 block 内部,再补看激活、归一化和 Attention 的共享机制:先判断哪些中间结果会参与 backward,再区分训练 Attention 的临时张量与自回归 decode 中保留的 KV Cache。前者主要进入训练侧 73 / 76,后者转入推理侧 22 / 34 / 66。

第二段:另一条分支——推理侧显存还是装不下

推理侧不要求先完成训练项目;只要问题对象从 activation / optimizer state 转为权重、KV Cache 或推理临时空间,就可以从 Task1 直接进入这条分支。典型现象是模型能加载,但只要上下文拉长、并发上去,显存就被 KV Cache 顶满。这时要切到推理侧显存链路:

核心路径先看 22 -> 34,再用 66 完成单 backend 最小验证;24 RadixAttention37 KV Cache Scheduling 属于推理侧运行时扩展,41 KV Cache Quantization67 的真实量化 backend 部署转入量化分支。请求路由、扩缩容和服务治理回到推理优化路线,不在这里继续展开。

这里的核心不是“为什么慢”,而是“为什么装不下”。要先分清 cache 增长是不是请求形态的自然结果,prefix reuse 和 paging 是否足够,KV cache quantization 是否值得引入。

KV Cache 的容量与复用边界

图中把请求增长、Cache 状态、复用策略和容量证据串起来;真实容量仍需回到固定模型和 workload 测量。

量化是另一条显存扩展分支:核心先看 21 量化理论25 W8A1640 GPTQ / AWQ41 FP8 / KV Cache Quantization,最后进入 67 量化推理与部署 的真实 backend 验证。这里要判断的是:量化省下来的显存是否换来了更长上下文、更大 batch 或更高并发,而不是只看权重文件变小。

量化作为显存工具

图中先区分量化对象和处理时机,再进入 backend、质量和性能验证;文件变小不等于完整服务成本按比例下降。

第三段:账本和实测开始打架

走到这一步,团队通常会碰到第三类问题:理论上算出来应该够,实测却还是很紧;或者峰值显存降下来了,但 benchmark 没好多少。这时就不能只看局部收益,而要把账本和实测证据对齐:

这一段真正要回答的是:理论账本里有没有漏掉临时 buffer、碎片或流程开销;训练侧峰值下降是不是只是把时间转移到了别处;推理侧 cache 压缩是不是只是把显存问题换成了延迟问题。74 不替代 75 的训练侧预算决策,而是负责最后的 profiling 和端到端验证。

显存优化的证据与决策闭环

这张图把固定条件、对照实验、预算敏感性和 profiling 收束到同一份结论中。

Part00 的 09 Module10 State Dict 也要放回这里理解:前者帮助确认哪些状态属于模型对象,后者帮助区分“保存以便恢复”和“运行时减少驻留”。如果这两个边界没有先分清,显存账本很容易把 checkpoint 文件、参数状态和 activation 混成一类。

第四段:把候选方案放回同一张对比表

到了真正做决策的时候,不能只写“试过 checkpointing、offload、KV quant”,而要把它们放回同一张 baseline / candidate 对比表里。训练侧先由 75 输出 accept / tune / reject,再由 74 检查候选方案在端到端 workload 下是否仍然成立;推理扩展则单独记录 cache policy、并发、质量和 backend 证据。真正的判断不该停在“省了显存”。

第五段:单卡之外的显存分摊

当单卡账本已经说明模型或训练状态无法在当前预算容纳,下一步不是继续叠加单卡技巧,而是判断哪些状态可以切分到多张卡,以及通信代价是否可接受。此时进入分布式分支:

这条分支要同时记录单卡显存、通信时间、扩展效率和稳定性。具体机制见07 分布式显存与系统扩展13 Profiling74 可以复用来解释通信、搬运、重算和 kernel 代价,但 Profiling 是证据方法,不是分布式切分策略本身。

最终结论长什么样

把这条故事走完以后,一个更像真实交付的结论通常不是“我们用了某个省显存技巧”,而是:训练中后段 OOM 的主要矛盾在 activation 与 effective batch,训练侧方案经过 73 -> 76 -> 75 -> 74 做出预算判断和代价解释;如果继续进入推理侧,再说明 KV Cache 或量化是否改变了当前 workload 的容量边界,并把 backend 结果交给推理项目解释。

Released under the MIT License.