跳转至

瓶颈定位

🐌 如何判断训练慢是因为显存不足导致的频繁换页,还是计算瓶颈?

💡 结论:看 GPU 利用率、显存占用和 CPU 内存活动。频繁换页会表现为“显存满但 GPU 使用率低且波动大,同时 CPU 内存占用高或出现大量数据搬运”;计算瓶颈则是“GPU 利用率持续很高,显存不一定满”。

🔍 具体判断方法:

  1. 检查显存换页(offload 或 swapping)
  2. 使用 nvidia-smi 观察显存是否长时间接近物理上限(>95%),同时 GPU 利用率(GPU-Util)很低(例如 0-30% 波动)。
  3. 如果启用了 CPU offload(如 DeepSpeed ZeRO-Offload 或手动 swap),可以观察 nvidia-smi dmon 中的 pci rx/tx(PCIe 传输速率)。若持续有大量数据在 CPU 与 GPU 间传输(例如 >5 GB/s),且训练速度极慢,很可能在换页。
  4. Linux 下可用 sudo cat /proc/meminfonvtop 查看 CPU 内存占用,若系统内存被大量占用且不断变化,说明参数或优化器状态被交换到 CPU。

  5. 检查计算瓶颈

  6. GPU 利用率持续 >80-90%,显存占用相对合理,但训练速度依然慢。这通常是计算力(TFLOPS)达到上限。
  7. 使用 Nsight Systems 或 PyTorch Profiler 分析内核时间:如果大部分时间消耗在 GPU 内核执行(如矩阵乘法),而内存拷贝时间很少,就是计算瓶颈。
  8. 如果 batch size 很大,GPU SM(流处理器)全部忙碌,则是计算瓶颈。可以通过尝试减小 batch size 观察 GPU 利用率是否下降不明显,若利用率仍高,则是计算密集。

  9. 利用 profiling 工具直接观察

  10. nvidia-smi --query-gpu=utilization.gpu,utilization.memory,memory.used,memory.total --format=csv 持续监控。
  11. 使用 torch.cuda.memory_summary() 在 Python 中查看 PyTorch 分配器状态,如果有大量 cudaMalloc 重试,说明碎片化或缺乏连续显存,可能导致 fallback 到 CPU。

📌 典型现象对比:

查看内嵌表格

✅ 因此,判断是换页还是计算瓶颈,核心是看 GPU 空闲的原因:如果是因为在等数据,大概率是显存不足导致换页;如果是忙于计算却仍然慢,则需升级 GPU 或优化算法。


📊 训练时 GPU 利用率低但显存占满,是什么原因?可能发生在什么阶段?

💡 原因:显存虽然被占满,但其中大部分是静态分配(如模型权重、优化器状态),真正用于计算的活跃数据少,导致 GPU 空闲。

🔎 详细原因和阶段:

  • 原因一:小 batch + 大模型,激活太少 显存被模型权重、优化器状态撑满,但每次迭代的 batch 很小,GPU 计算单元“吃不饱”,大部分时间在空闲等待数据搬运或下一批次。常见于显存受限的微调场景。

  • 原因二:梯度累积或小 batch 推理 训练中使用梯度累积时,每个 micro-batch 的计算量不足,两次计算间有延迟。推理时如果并发请求少,显存虽占满(KV Cache 池预留),但 GPU 执行稀疏,利用率低。

  • 原因三:数据加载或 CPU 预处理成为瓶颈 显存满了,但数据还没有准备好,GPU 在等待。这可能是 I/O 瓶颈,发生在训练开始的数据预取阶段,也可能持续发生如果 DataLoader 太慢。

  • 原因四:频繁的梯度检查点重计算 为了节省显存而开启梯度检查点(gradient checkpointing),会导致额外的重计算,但这些重计算本身是计算密集的,通常会提高利用率。但若模型过大,重计算引起内核开销和同步,偶尔也可能利用率低(较少见)。

🕒 可能发生阶段:

  • 训练启动初期:模型加载后立即占满权重+优化器,但刚开始取数据,GPU 空闲。

  • 小 batch 训练全过程:如果受限于显存只能用很小的 batch,GPU 利用率持续低迷。

  • 推理服务空闲期:虽然模型和 KV Cache 池长期驻留显存,但请求少,GPU 闲。

✅ 解决方案:

  • 若可能,增大 batch size(或使用梯度累积等效)。

  • 优化数据管道(使用更多 worker、预处理缓存、pin_memory)。

  • 使用模型并行,减少单卡权重/优化器占用,从而增大可用的 batch 空间。


⚡ 推理时,小 batch 通常是带宽瓶颈,大 batch 是计算瓶颈,为什么?

💡 这个规律源于 GPU 的并行特性:小批量无法充分利用计算单元,大量时间消耗在数据搬运(内存带宽);大批量则有足够工作填满计算单元,限制因素变为芯片算力。

🧠 技术原理:

  • 小 batch 带宽受限 推理需要将权重从显存(HBM)搬运到片上缓存(SRAM)才能计算。当 batch 很小时,计算时间很短,反而权重搬运占据主导。因为权重矩阵巨大,每个 token 都要搬运一次完整权重,小 batch 的算术强度(计算量/访存量)很低,GPU 的算力被带宽“饿死”。表现为 GPU 利用率低,大多数时间在等待内存访问。此处的“带宽瓶颈”指显存带宽(HBM BW)。

  • 大 batch 计算受限 当 batch 增大,多个 token 可以共享一次权重搬运,算术强度显著提升。此时计算时间变长,显存带宽不再是瓶颈,反而芯片的峰值算力(TFLOPS)成为上限。GPU 利用率接近 100%,但受限于 SM 数量和时钟频率,无法更快。此时是计算瓶颈。

📈 举例: 在矩阵乘法中,对于权重 W [H, H],输入 X [B, S, H],小 B 导致乘法的算数强度 ≈ BSH / (H^2) 很小,每次搬运权重的成本高于计算。大 B 则强度增加,最终达到硬件 TFLOPS 上限。

🎯 因此,优化推理时:小 batch 需关注内存带宽利用率(如使用 FP8、Kernel Fusion),大 batch 则需要更强劲的 GPU 或降低计算精度(如 INT8)。


🚀 在长文本推理中,瓶颈是计算还是显存?为什么?

💡 长文本推理的瓶颈通常是显存(尤其是 KV Cache),而非计算,因为显存限制了最大序列长度和 batch size,但解码速度本身可能仍受限于内存带宽。

🧩 详细分析:

  • 显存瓶颈主导 随着序列长度增加,KV Cache 线性增长。前面计算可知,100K token 的 KV Cache 可达数十 GB,很快超过单卡显存,导致无法推理或只能小 batch。因此显存容量直接决定了能不能跑,是硬瓶颈。

  • 计算方面:带宽瓶颈居多 在长文本生成阶段(解码),每次生成一个 token,需要访问所有历史的 K、V,计算注意力。当 batch=1 时,算术强度仍然较低,内存访问量巨大(需读取整个 KV Cache 和权重),因此实际瓶颈往往是显存带宽,而非计算峰值。这也是为什么长文本推理速度随着序列变长而线性下降的原因——带宽被不断增长的 KV Cache 拖累。

  • 因此,长文本推理面临的矛盾是:显存容量限制序列长度和批大小;显存带宽限制生成速度。计算反而是次要的。

✅ 优化的重点:采用 GQA/MQA 减少 KV Cache 大小和带宽压力;量化 KV Cache;使用 FlashDecoding 加速长序列注意力;增大显存容量以支持更长上下文。


🛠️ 如何通过 nvidia-smi 和 profiling 工具定位显存瓶颈?

💡 使用 nvidia-smi 实时监控显存使用率和总占用,结合 Nsight Systems/PyTorch Profiler 分析显存分配、碎片和访问模式。

📋 具体步骤:

  1. nvidia-smi 快速检查
  2. nvidia-smi 查看 Memory-Usage,如果接近 Total,基本可判定显存紧张。
  3. nvidia-smi dmon -s pucm 可以持续显示 GPU 利用率 (sm)、显存利用率 (mem)、编解码器等。若 mem 高、sm 低 → 显存瓶颈。
  4. 观察 pci rx/tx 如果持续很高,可能是换页或频繁 CPU-GPU 传输,说明显存不足在借用系统内存。

  5. PyTorch Profiler 内存分析

  6. 使用 torch.profiler.profile 配合 profile_memory=True,可以记录算子级别的显存分配和释放历史,生成 memory_trace
  7. 通过 TensorBoard 查看 Memory Timeline,找出哪些操作申请了大块显存,以及是否存在“锯齿”状的分配模式,这表明显存碎片或频繁分配/释放,可能导致实际可用显存减少。

  8. Nsight Systems

  9. 可捕获 CUDA API 调用、内存分配和拷贝事件。通过 Memory View,可以看到每一时刻的显存使用量、分配高峰、以及碎片情况。特别适合定位训练中突然 OOM 的根本原因(是某个算子分配过多,还是碎片积累)。

  10. 显存带宽瓶颈定位

  11. 使用 nvidia-smi nvlink 查看 NVLink 带宽利用率(多卡),或通过 ncu (NVIDIA Nsight Compute) 分析内核的 mem_l1_wavefrontsmem_traffic 等指标,判断是否受限于显存带宽(高 memory_throughput 但低计算吞吐)。

📌 结论:快速判断用 nvidia-smi,深度分析用 PyTorch Profiler 内存视图 + Nsight Systems,定位具体显存占用和带宽瓶颈。


📈 训练时,激活值占比突然升高,可能是什么配置改变了?

💡 激活值占比突然升高,通常是序列长度、batch size 变大,或者关闭了梯度检查点,或者模型架构变化导致的中间张量增加。

🔍 常见原因:

  1. 序列长度或 batch size 增大 激活显存近似与 batch_size × seq_len 成正比。如果训练配置中不小心将 max_seq_len 增大(比如数据处理时未截断),或者动态 padding 导致某个 batch 特别长,激活会瞬间飙升。

  2. 关闭梯度检查点(gradient checkpointing) 开启 checkpoint 会丢弃部分中间激活,反向时重计算,以时间换空间。如果误将其关闭(例如从 FP16 O2 切换到 O1 或者更改了训练参数),所有中间激活都会常驻,导致激活占比激增。

  3. 注意力实现变化 如果从 FlashAttention 切换到传统注意力,会显式构建注意力分数矩阵 [B, heads, S, S],该张量随 S 平方增长。这会大幅增加激活内存。反之,启用 FlashAttention 可降低激活。

  4. 使用了更大的 micro-batch 进行前向 在梯度累积场景,如果 micro_batch_size 增大,激活峰值随之增高。

  5. 模型结构改变 若在现有模型中插入新的模块(如适配器、更深层)、或特征维度临时增加,也会导致激活内存上升。

🔧 检查方法:对比配置文件中 seq_length, per_device_train_batch_size, gradient_checkpointing, use_flash_attn 等参数。使用 Profiler 查看激活张量分布,定位是哪类算子导致增长。

因此,要快速锁定原因:先对比当前与之前的训练超参,再检查是否无意中改动了模型前向实现。


💨 什么是“显存带宽瓶颈”的典型表现?

💡 典型表现:GPU 计算利用率不高,但显存控制器的利用率接近峰值,增加 batch size 并不能明显提高吞吐,性能随序列长度线性恶化。

🎭 具体症状:

  • GPU 利用率(SM 占用)低,通常在 30%~60%,而 memory 子系统的利用率(如内存控制器、DRAM 读写带宽)接近 100%(通过 nvidia-sminvtop 的高级视图可查看)。

  • 增加 batch size 或序列长度,延迟几乎等比例增长,吞吐(token/s)提升很小,因为每多一个 token 都要更多地从 HBM 读数据,带宽成为墙。

  • 算术强度极低:profile 显示计算时间/内存访问时间的比值很小,例如大多数时间消耗在 memcpy 或简单的逐元素操作上,矩阵乘法等计算单元闲置。

  • 性能对内存频率敏感:降低显存频率(如使用低功耗模式)会导致性能严重下降,而提升核心频率几乎无影响。

  • ncu (Nsight Compute) 分析:每个 SM 的 L1/TEX 命中率低,dram__throughput 非常高且已触达理论带宽,而 sm__throughput 未饱和。

📌 常见场景:

  • 推理时 batch=1 的自回归生成,每次 token 都要完整读取权重和 KV Cache,属于典型的带宽瓶颈。

  • 长文本推理时,随着 KV Cache 变大,注意力操作不断从 HBM 读取大量数据,带宽成为主要约束。

因此,当发现 GPU 算力闲置而内存通道满载,就是典型的显存带宽瓶颈,需要采用 Kernel Fusion、量化、GQA 等减少数据移动的优化。


🔗 在分布式训练中,通信开销与显存的间接关系是怎样的?

💡 通信开销与显存存在“此消彼长”的间接博弈:为减少显存占用而采用模型切分,会增加通信量;而为降低通信优化显存使用,则可能造成显存膨胀。两者的平衡是分布式训练优化的核心。

📊 间接关系主要体现在:

  1. 模型分片减少显存,但引入通信 使用 ZeRO(阶段 2/3)或张量并行(TP)将权重、梯度、优化器状态切分到多卡,降低了单卡显存需求。但前向/反向时需要额外的 All-Reduce、All-Gather 或 Reduce-Scatter 来收集完整参数或梯度。越细的切分(更省显存),通信量越大、通信频率越高。 这就是典型的“以通信换显存”。

  2. 梯度累积与批大小的影响 为了在有限显存下模拟大 batch,需使用梯度累积(micro-batching)并减少单次激活显存。但梯度累积本身不增加通信量。然而,若为了进一步降低激活显存而开启梯度检查点(用计算换显存),则不会增加通信。

  3. 流水线并行(PP)中的显存与通信 PP 将模型按层切到不同设备,单卡显存压力减小,但需要将中间激活(hidden states)在设备间传输。激活大小与 batch 和序列长度成正比。通过 1F1B 调度可以限制驻留激活数,减少显存峰值,但会增加流水线气泡。这里通信开销(点对点传输)与显存占用(激活)直接挂钩:减少显存意味着更小的 micro-batch,传输数据量不变但频率可能增加。

  4. 数据并行中的显存与通信 标准 DDP 每卡拥有完整模型,显存占用高,但反向梯度只需一次 All-Reduce,通信量固定。若显存不够必须用 ZeRO-3 分片,则梯度通信被替换为参数收集等更频繁的通信操作。显存越紧张,所需并行度越高,通信代价往往越大。

  5. 通信缓冲区本身占用显存 通信库(NCCL)往往需要预留缓冲区(用于集合通信),这些缓冲区常驻显存。大规模集群或大张量通信时,缓冲区可能达到数百 MB,间接占用本已紧张的显存。

✅ 总结:显存与通信是分布式训练中的“阴阳”两面。优化目标是:在满足显存约束的前提下,尽量减少不必要的通信(如优先使用梯度累积而非增加模型并行度),并通过通信计算重叠(overlap)隐藏延迟。理解两者关系,才能在有限的硬件上找到最佳并行配置。


💥 为什么训练后期突然 OOM,之前训练正常?

💡 训练后期突然 OOM 往往不是因为模型权重变化,而是由于训练过程中动态变化的资源占用——比如激活增加、显存碎片累积、数据处理逻辑变化或框架缓存膨胀。

🔎 常见原因:

  1. 动态序列长度或 Padding 策略变化 如果数据预处理时未严格截断,训练后期可能遇到了更长的样本,导致序列长度增加,激活与 KV Cache 随之暴涨,撑破显存。或者动态 padding 导致某个 batch 的序列长度远超平均。

  2. 显存碎片不断积累 训练过程中不断分配/释放临时张量,PyTorch 的 CUDA 缓存分配器可能产生大量碎片。起初碎片少,剩余连续显存足够;运行数小时后,虽然总空闲显存看似不少,但无足够连续大块满足新的分配请求(如一次大的激活张量),从而触发 OOM。这在开启梯度检查点或频繁生成临时张量的模型中尤为常见。

  3. 缓存或日志占用递增 训练日志、eval 时的缓存、或者某些监控 Hook 可能无意中保存了历史张量(如记录 grad norm),导致显存缓慢泄露。使用 torch.cuda.memory_summary() 可以查看缓存分配器状态,观测 reservedallocated 的差异是否持续扩大。

  4. 优化器状态膨胀 某些优化器(如带 weight decay 的实现)可能在内部分配额外缓冲区;或者混合精度训练的 loss scaler 相关开销。多数是固定的,但部分实现存在随时间增长的风险。

  5. checkpoint 保存与加载 保存模型检查点时会申请额外显存放序列化的中间对象。如果主进程在保存时 OOM,可能误导。

  6. 学习率调度或动量累积 不会直接导致 OOM,但可能改变 loss 形态,间接影响部分框架的异常检测。

🔧 排查步骤:

  • 在 OOM 前立即使用 torch.cuda.empty_cache() 观察是否恢复,若恢复则可能是碎片。

  • 使用 PyTorch Profiler 记录内存分配历史,定位 OOM 时刻哪个算子申请了大块显存。

  • 检查数据集中最大序列长度分布,在训练后期是否出现了极端长样本。

  • 检查代码中是否有列表或缓存不断增长(如存储验证集预测结果)。

✅ 因此,训练后期 OOM 通常不是模型本身变大了,而是动态行为或碎片导致。监控分配记录并加入定期缓存清理是有效预防手段。


⚡ 推理服务的延迟抖动,是否可能和显存碎片有关?如何排查?

💡 极有可能!显存碎片会导致 KV Cache 分配失败或被迫进行开销高昂的压缩/交换,直接造成请求延迟波动甚至超时。

🧩 关联机制:

  • PagedAttention 块分配:vLLM 等框架将 KV Cache 划分为固定大小的 block。若显存碎片化,尽管总空闲 block 足够,但物理上不连续时,分配器可能需要更长时间搜索可用块,或触发 block 压缩/复制(如 vLLM 的 defrag),这些操作都会增加延迟,引发抖动。

  • PyTorch 缓存分配器的碎片:即使 KV 块分配逻辑正常,底层 CUDA 分配器的碎片也可能导致 cudaMalloc 耗时增加,影响所有涉及动态显存分配的操作。

  • Fragmentation 引起的 OOM 重试:更严重时,分配失败触发重试或 fallback,导致个别请求延迟飙升。

🛠️ 排查方法:

  1. 监控 KV Cache 利用率与碎片率 vLLM 等框架通常提供 Metrics 接口,暴露 gpu_cache_usagenum_free_blocks 等。若 num_free_blocks 较大但分配延迟高,则可能是碎片。

  2. 使用 PyTorch 内存分析 在服务启动初期和运行一段时间后分别调用 torch.cuda.memory_summary(),对比 Segment 数量和 allocated 碎片模式。如果长时间后出现大量小的空闲区块,即可确认碎片。

  3. Profile 分配延迟 用 torch.profiler.profile 记录 cudaMalloc 调用时间,观察抖动是否与分配耗时正相关。

  4. 启用显存池监控 一些框架允许设置环境变量 PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True 来减少碎片。可对比开启前后抖动情况。

  5. 观察 PagedAttention 内部事件 使用 vLLM 的 --enable-chunked-prefill--max-num-seqs 控制并行度,结合日志查看是否存在 block_table 溢出或重映射。

✅ 结论:显存碎片会导致 KV Cache 分配效率下降,是推理服务延迟抖动的重要潜在原因。通过框架指标和内存 profiler 可确认并采取碎片整理(手动或定期重启)缓解。


🗺️ 使用不同的并行策略,显存瓶颈会如何转移?

💡 并行策略本质上是将“显存压力”在不同维度(设备、层、张量)间重新分配,因此瓶颈会从单卡转移到通信带宽或某类特定层上。

🔀 转移路径:

  1. 数据并行(DDP/ZeRO-1)
  2. 显存瓶颈:每卡需容纳完整模型权重、优化器状态和激活。
  3. 转移后:显存瓶颈仍在单卡,并未消失,只是通过增加卡数扩展了总吞吐。当单卡装不下模型时,必须转向模型并行。

  4. ZeRO-2 / ZeRO-3

  5. 将优化器状态(ZeRO-2)甚至权重(ZeRO-3)分片到多卡,单卡显存瓶颈大大缓解。
  6. 新瓶颈转移至:通信带宽(参数收集的 All-Gather 开销)和激活显存。因为激活未被分片,仍占据大量显存。此时优化重点变为降低激活内存(梯度检查点、FlashAttention)。

  7. 张量并行(TP)

  8. 将层内权重和激活均切分,进一步降低单卡激活显存。
  9. 瓶颈转移到:卡间高带宽通信(每次前向都要 All-Reduce)。受限于 NVLink 带宽,通常只在单机内使用 TP。显存问题变成了“内部总线速度”问题。

  10. 流水线并行(PP)

  11. 将不同层放不同卡,单卡权重和激活都减少(但激活减少程度取决于 micro-batch 数)。
  12. 瓶颈转移到:流水线气泡和设备间的激活传输延迟。显存不再是主要矛盾,但负载不均衡会导致某些卡显存依然紧张(不均衡转移)。

  13. 混合并行(3D 并行)

  14. 综合使用 TP+PP+DP/ZeRO,全局显存压力被多维分解,单卡瓶颈基本解决。
  15. 系统瓶颈转为:网络通信拓扑、同步开销以及软件调度效率。

📊 总结表:

查看内嵌表格

因此,选择并行策略就是决定“把显存压力转嫁到哪里”,需要结合硬件特性(带宽、拓扑)进行权衡。


🔄 什么情况下,显存优化已经足够,但计算成为新瓶颈?

💡 当通过量化、GQA、FlashAttention 等手段将权重和 KV Cache 压缩至极,显存占用远低于物理容量,可以轻松放入大 batch 和长序列,此时 GPU 核心算力(TFLOPS)成为限制吞吐的唯一因素。

🎯 典型场景:

  1. 高吞吐推理服务 使用 INT4 量化、MQA、FlashDecoding 等技术后,KV Cache 和权重变得极小,单卡可同时服务几十个长序列请求。此时批量变大,计算密度升高,GPU SM 被完全占用,若需更高吞吐,只能增加算力(更多/更强的 GPU)或降低计算精度(FP8)。

  2. 训练中的大 batch 场景 在分布式训练中,通过 ZeRO-3 和梯度检查点将单卡显存占用压得很低,可以塞进极大的 micro-batch。此时前向/反向计算完全受限于 GPU 计算速度,带宽不再是瓶颈。此时升级更快的 GPU(如 V100→A100)能线性提升训练速度。

  3. 模型压缩后 蒸馏或剪枝后的小模型已经非常轻量,显存不再是问题,但推理延迟要求极高(如实时对话),此时计算效率决定用户体验。需用 TensorRT 等深度优化推理引擎,对计算图进行极致融合。

  4. MoE 模型推理 虽然总权重大,但激活参数小。通过专家并行分散权重,单卡仅需处理少量专家,显存压力小,但路由和专家计算需消耗大量算力,吞吐受限于 GPU 计算能力。

✅ 判别指标:

  • GPU 利用率持续 >90%,显存占用 <70%,增加 batch size 不再提高吞吐(已触达算力上限)→ 计算瓶颈。

  • ncu profile 显示 sm__throughput 接近峰值,而 dram__throughput 尚有富余。

因此,当显存不再成为约束后,优化的重心就转移到算力利用率和模型计算效率上。


🔍 如何用 PyTorch Profiler 找出显存占用最大的操作?

💡 使用 torch.profiler.profile 结合 record_shapesprofile_memory 参数,再通过 TensorBoard 的 Memory Viewer 可以清晰看到每个算子分配和释放的显存,轻松定位“大胃王”。

📋 操作步骤:

  1. 基本 Profiler 配置

  2. python

  3. with torch.profiler.profile( activities=[ torch.profiler.ProfilerActivity.CPU, torch.profiler.ProfilerActivity.CUDA, ], record_shapes=True, profile_memory=True, # 关键:记录内存分配 with_stack=True, # 记录调用栈,方便定位代码) as prof: model(input)prof.export_chrome_trace("trace.json")

  4. 使用 TensorBoard 查看 运行 tensorboard --logdir ./log,打开 Memory 视图。可以看到:

  5. 分配时间线:每个 CUDA 算子的显存分配/释放矩形块。
  6. 按算子统计:列出每个算子类型(如 aten::linear)的峰值内存占用、分配次数等。
  7. 点击具体块可查看 Python 调用栈,直接追溯代码行。

  8. 找出最大占用 在 Memory 视图的左侧面板,按 Allocation Size 降序排列,排在最前面的就是占用最大的算子。常见的有:

  9. aten::linear 的权重和激活
  10. aten::scaled_dot_product_attention 的中间注意力矩阵
  11. aten::embedding 的输入输出
  12. 框架自动分配的缓冲区

  13. 现场调试 也可在 Python 中直接打印内存统计:

  14. python

  15. print(torch.cuda.memory_summary())

  16. 这会输出当前显存的分配器状态,包括各分配段的信息,但不细粒度到算子。

✅ 因此,PyTorch Profiler 的 Memory 分析工具是定位显存占用热点的利器,几乎任何训练/推理的显存问题都应先用它看一眼。


💥 训练时 loss 正常下降但突然 OOM,可能的原因是什么?

💡 与之前“训练后期 OOM”类似,但特别强调 loss 正常下降,意味着训练过程在正常进行,突然 OOM 往往是由于数据处理中的异常样本、框架调度变化或显存泄漏累积到临界点。

🔎 特别可能的原因:

  1. 异常长样本 Loss 正常下降说明模型能处理当前序列,但突然遇到一个远超平均长度的文本(未做截断),激活显存瞬间飙升致 OOM。这是最常见原因。

  2. 梯度累积或 micro-batch 调度变化 某些动态调整策略(如根据样本长度动态改变 micro-batch 数量)可能在某些组合下导致显存峰值意外超标。

  3. evaluation 阶段占用 训练过程中每隔若干步会进行验证(eval)。Eval 时模型进入无梯度模式,但可能使用更大的 batch 或更多的缓存来加速指标计算(如存储预测),这部分额外显存没有及时释放,叠加训练显存后溢出。

  4. 框架自动混合精度调整 某些框架(如 DeepSpeed)在训练过程中可能会根据统计动态调整 precision 或 offload 策略,若其改变了显存使用模式,也可能诱发 OOM。

  5. 显存泄漏 每个 step 有很小一部分显存未被释放(如 Hook 记录张量),经过数千 step 累积后突然超过阈值,即使 loss 正常也会 OOM。这属于缓慢增长型泄漏,在某个分配大张量时触发。

🛡️ 排查方法:

  • 设置 torch.cuda.set_per_process_memory_fractionmax_split_size_mb 避免碎片。

  • 记录 OOM 前的 batch 输入,检查是否存在超长样本。

  • 在 eval 前后打印显存占用,对比差异。

  • 使用 PyTorch Profiler 记录分配,分析 OOM 步的分配峰值。

✅ 因此,训练中突然 OOM 大多是外部因素(数据、eval、碎片)引发,模型本身训练正常不代表内存管理安全。


🏁 总结:显存、计算和通信,三者在不同规模任务中的角色如何转换?

💡 随着模型规模、数据量、序列长度的增长,瓶颈会在显存、通信、计算之间转移,形成“木桶效应”。理解其转换规律是系统优化的基础。

📈 规模演化主线:

📌 小规模(<1B 参数,短序列,单机)

  • 显存:通常充裕,不是瓶颈。

  • 计算:成为主要瓶颈,GPU 算力决定训练/推理速度。

  • 通信:几乎无影响,单卡或单机内数据并行通信量极小。

  • 优化重点:提高计算效率(混合精度、Kernel 融合)。

📌 中等规模(1B-13B,中等序列,单机多卡)

  • 显存:开始成为约束,尤其全量微调时无法单卡容纳优化器状态。

  • 通信:多卡数据并行仍无碍,但若用模型并行(TP),卡间 NVLink 带宽成为性能因素。

  • 计算:仍重要,但可通过显存优化(梯度检查点)换空间,牺牲部分计算速度。

  • 优化重点:显存与计算的平衡——梯度检查点、FlashAttention、ZeRO-2。

📌 大规模(13B-70B,长序列,多机多卡)

  • 显存:非常紧张,KV Cache 和激活成为瓶颈,迫使采用 GQA/MQA、KV Cache 量化、PagedAttention。

  • 通信:跨机通信(RDMA)成为瓶颈,必须精心设计 TP/PP/DP 混合策略,减少跨机流量。

  • 计算:受限于单卡算力,但并行后总计算量巨大,需要提高 MFU(模型浮点利用率)。

  • 优化重点:三者的联合优化——设计最优并行拓扑,overlap 通信与计算,显存换通信(recomputation)等。

📌 超大规模(100B+,超长序列,大规模集群)

  • 显存:通过多维并行切分后,单卡压力相对可控,但集群总显存必须满足至少一份完整模型加最大优化器状态。

  • 通信:成为最核心瓶颈。通信延迟和带宽限制系统扩展性,必须使用定制网络(如 InfiniBand)和通信计算重叠。

  • 计算:虽绝对需求高,但通过大规模并行可得解决,往往不是首要限制。

  • 优化重点:通信拓扑、流水线调度、容错机制。

🔄 转换核心逻辑:

每当显存被优化缓解,更大的 batch 或更长的序列成为可能,此时计算或通信就会暴露为新瓶颈。反之,当计算变快(新硬件),我们倾向于增大模型或序列,显存和通信瓶颈又会出现。三者的动态平衡推动着大模型系统的持续进化。

因此,设计系统时需识别当前规模和硬件所处的“主要矛盾”,集中资源突破,然后预判下一个瓶颈,提前规划。