大规模深度学习性能调优:自顶向下的五件套
GPU 利用率上不去显存莫名 OOM分布式训练卡死不动这些问题不能靠再加一张卡解决必须靠 Profiling 把瓶颈找出来。性能调优的正确顺序是自顶向下先在框架层看哪个算子慢再下到系统层看 CPU/GPU 时间线最后下到 GPU 芯片内部看微架构。每一层都有专属工具越往下越精细但开销也越大。错用工具——比如用 Nsight Compute 跑整个训练循环——会让程序慢上百倍得不偿失。下面这五件套覆盖了从框架到硬件的全栈是我日常排查的标准武器库。一、PyTorch Profiler第一手排查工具什么时候用90% 的性能问题用 PyTorch Profiler 就能定位。典型场景不知道哪个算子最耗时怀疑 DataLoader 读图太慢导致 GPU 在空等GPU Starvation显存峰值排查OOM 原因它的优势是几乎不用改代码套个 Context Manager 就行。用法一导出给 TensorBoard 看图形化import torch prof torch.profiler.profile( activities[ torch.profiler.ProfilerActivity.CPU, torch.profiler.ProfilerActivity.CUDA, ], # 预热1步,记录2步,避免Profiler本身带来过大开销 scheduletorch.profiler.schedule(wait1, warmup1, active2, repeat1), on_trace_readytorch.profiler.tensorboard_trace_handler(./log/resnet_profile), record_shapesTrue, # 记录Tensor形状 profile_memoryTrue, # 记录显存分配 ) prof.start() for step, data in enumerate(dataloader): train_step(data) prof.step() prof.stop()然后tensorboard --logdir./log/resnet_profile。重点看三个视图Trace 视图找DataLoader Wait时间块。如果 GPU 时间线上有大段空白多半是数据加载拖了后腿Operator 视图看Self CUDA Time排行榜谁排第一谁就是嫌疑犯Memory 视图显存随时间的曲线OOM 之前一定有异常的尖峰用法二直接在终端打印表格轻量、快速懒得开 TensorBoard 时,直接 print 一张表更快:import torch import torchvision.models as models model models.resnet18().cuda() inputs torch.randn(16, 3, 224, 224).cuda() # 预热,让GPU初始化完成 for _ in range(3): model(inputs) torch.cuda.synchronize() with torch.profiler.profile( activities[ torch.profiler.ProfilerActivity.CPU, torch.profiler.ProfilerActivity.CUDA, ], record_shapesTrue, ) as prof: with torch.profiler.record_function(my_resnet_forward): outputs model(inputs) # 打印表格,sort_by可以是cuda_time_total / self_cuda_time_total / cpu_time_total print(prof.key_averages().table(sort_bycuda_time_total, row_limit10))终端输出长这样----------------------- ---------- ---------- ---------- ---------- ---------- Name Self CPU % Self CPU Self CUDA % Self CUDA # of Calls ----------------------- ---------- ---------- ---------- ---------- ---------- my_resnet_forward 2.50% 1.200ms 0.00% 0.000us 1 aten::conv2d 0.15% 72.000us 0.00% 0.000us 20 aten::cudnn_convolution 1.50% 720.000us 75.00% 11.400ms 20 aten::batch_norm 0.10% 48.000us 10.00% 1.520ms 20 ... ----------------------- ---------- ---------- ---------- ---------- ---------- Self CPU time total: 48.000ms Self CUDA time total: 15.200ms怎么读懂这张表四个维度必须分清Nameaten::xxx是 PyTorch 底层 C 算子aten::conv2d是卷积aten::add是加法自己用record_function(xxx)打的标签也会出现在这里Self vs Total这是最容易搞混的一对Total Time算子从开始到结束的总耗时包含其内部调用的所有子算子时间。比如aten::conv2d内部会调aten::convolution → aten::cudnn_convolution它的 Total 把底层全包进去了Self Time剔除子调用后该算子自己消耗的时间排查瓶颈时盯紧Self Time。Total 高没意义——它可能只是个壳Self 高才说明这一行真的慢。CPU vs CUDACPU列CPU 下发指令Launch kernel或纯 CPU 算子的耗时CUDA列GPU 真正在硅片上算的时间深度学习场景主要看Self CUDA。# of Calls调用次数。轻量级算子调用上万次,累加起来也很恐怖。比如某个 element-wise 算子单次只有 5us但被调用 50000 次就是 250ms。实战技巧sort_byself_cuda_time_total排序前两三行通常就是罪魁祸首。最常见的惊喜包括自定义的 Attention 实现、低效的 LayerNorm、忘了 fuse 的 element-wise 操作。二、Nsight Systems (nsys)系统级时间线什么时候用PyTorch Profiler 告诉你哪个算子慢nsys 告诉你整个系统在干什么。当你怀疑CPU 准备数据和 GPU 计算没重叠H2DHost to Device数据拷贝阻塞了计算分布式训练里 NCCL 通信和计算没并行就该上 nsys 了。怎么用不用改代码但建议在关键代码段加torch.cuda.nvtx.range(MyOp)打标签nsys 会显示出来。# 标准用法:生成报告文件,本地用GUI打开 nsys profile -t cuda,nvtx,osrt -o my_profile python train.py # 终端汇总用法:加 --statstrue 直接打印文本表格 nsys profile -t cuda,nvtx --statstrue python train.py终端输出--statstrue跑完后 nsys 在终端打印两类核心表格CUDA API 统计CPU 端发出的指令耗时Time (%) Total Time (ns) Num Calls Avg (ns) Name -------- --------------- --------- ---------- -------------------- 45.2% 1,250,000,000 100 12,500,000 cudaMemcpy (H2D) 30.5% 850,000,000 5000 170,000 cudaLaunchKernel ...这个例子里 H2D 拷贝占了 45%瓶颈很清楚——数据搬运太重要么 pin memory、要么改 DataLoader、要么用 prefetch。CUDA Kernel 统计GPU 端真正执行的耗时Time (%) Total Time (ns) Instances Avg (ns) Name -------- --------------- --------- -------- ---------------------- 60.1% 500,000,000 1000 500,000 volta_sgemm_128x64_nn 15.2% 126,000,000 2000 63,000 layer_norm_kernel ...矩阵乘法占 60% 是健康的计算密集型任务理应如此如果某个不起眼的 element-wise kernel 占到 30%那就有问题。GUI 界面看什么终端表格只有汇总真正的价值在 GUI 的时间线。把.nsys-rep下载到本地用 Nsight Systems GUI 打开会看到一张多行时间线CPU 行每个核心在做什么数据预处理、Python 解释器、Kernel LaunchCUDA HW 行GPU 实际执行的 Kernel按 Stream 分行PCIe / Memcpy 行H2D / D2H 数据拷贝NVTX 行你自己打的标签NCCL 行分布式通信AllReduce / AllGather / ReduceScatter排障时盯三件事GPU 时间线有没有空白。空白 GPU 在发呆多半是 CPU 算太慢或 H2D 拷贝阻塞NCCL 色块和计算色块上下重不重叠。不重叠 通信被串行化了扩展性会很差AllGather / ReduceScatter 占多大比例。FSDP / ZeRO-3 训练里AllGather 是 GPU 在计算前临时把分片参数借齐ReduceScatter 是计算后把梯度规约并打散回各 GPU。这两个色块如果占据时间线一大半说明通信成为瓶颈要考虑梯度累积、offload、或更高带宽的互连三、Nsight Compute (ncu)Kernel 级显微镜什么时候用前两步定位到某个 Kernel 慢——比如自己写的 FlashAttention 跑得不如预期——但你不知道它为什么慢是被显存带宽卡住了算力没喂饱还是寄存器溢出ncu 就是干这个的。它会告诉你这个 Kernel 在 GPU 芯片内部的真实状态。⚠️ 重要警告绝对不要用 ncu 跑整个训练循环。它会对每个 Kernel 做极细粒度的硬件计数器采样开销是 50~200 倍。我见过有人ncu python train.py然后程序跑了三天没动——以为是死锁其实是 ncu 在尽职工作。正确姿势精准狙击# 只profile名字包含layer_norm的kernel,只采集1次,输出完整指标 ncu --kernel-regex layer_norm -c 1 --set full -o report python test_kernel.py通常的做法是把要测的算子单独剥离出来写个 mini 脚本不要带训练循环。终端输出解读ncu 的输出是一块块 Section最关键的有三个1. Speed of Light光速模型— 一眼看懂瓶颈类型Section: GPU Speed Of Light Throughput ----------------------------- ---------- --------------- Memory Throughput % 85.20 ← 访存接近打满 Compute (SM) Throughput % 22.10 ← 算力只跑到22% ----------------------------- ---------- --------------- WRN This kernel exhibits low compute performance and is memory bound.这种情况叫Memory Bound算子在等内存读取算力单元闲着。优化方向是 fuse 算子、用更大 block 提高数据复用、或者改成 FP16 减少访存量。反过来如果 Compute % 高、Memory % 低就是Compute Bound瓶颈在算力本身能做的不多除非换更高效的算法。2. Memory Workload — 看缓存命中率Section: Memory Workload Analysis ----------------------------- ---------- ------- L1/TEX Hit Rate % 45.50 L2 Hit Rate % 88.20 ----------------------------- ---------- -------L1 命中率低意味着同一块数据被反复从 L2 / 显存读取——典型的访存模式问题。Tile 大小、shared memory 的使用方式都会影响这个。3. Occupancy — 线程块配置合不合理Section: Occupancy ----------------------------- ---------- ------- Theoretical Active Warps/SM warp 32.00 Achieved Active Warps/SM warp 12.50 Occupancy Ratio % 39.06 ← 只跑到理论值的39% ----------------------------- ---------- -------Occupancy 低有几种原因寄存器用太多每线程占资源多能起的 Warp 就少、Block 太大或太小、shared memory 用太多。GUI 里有交互式工具能告诉你减到什么程度能提升。Roofline ModelGUI 里最直观的视图GUI 会画一张 Roofline 图横轴是算术强度FLOP / Byte纵轴是吞吐。每个 Kernel 是图上一个点。点落在斜线带宽屋顶下方 → Memory Bound点落在水平线算力屋顶下方 → Compute Bound点离屋顶有多远就是优化空间有多大四、NCCL_DEBUGINFO分布式通信排障什么时候用DDP / FSDP / ZeRO 训练突然 hang 住、超时、或者多机扩展效率差到不能看。这些问题大概率出在通信层但报错信息往往一脸懵——这时候打开 NCCL 的调试日志就对了。怎么用export NCCL_DEBUGINFO export NCCL_DEBUG_SUBSYSINIT,GRAPH # 可选:只看初始化和图构建 torchrun --nproc_per_node8 train.py重点看三件事1. 网卡选择日志会有这种行NCCL INFO NET/IB : Using [0]mlx5_0:1/IB如果看到mlx5_xxx是 InfiniBand速度正确。如果看到eth0或更糟的docker0那就是走错网卡了——多机训练性能直接腰斩。常见的修法是显式指定NCCL_SOCKET_IFNAMEeth1或NCCL_IB_HCAmlx5。2. 连接方式单机内 GPU 之间textNCCL INFO Channel 00 : 0[3000] - 1[4000] via P2P/IPC NCCL INFO Channel 00 : 0[3000] - 4[7000] via SYSNVL/P2P/IPC走 NVLink 或 NVSwitch最快PHB走 PCIe Host Bridge差一档SYS跨 NUMA 走 CPU 内存最慢A100 / H100 服务器理论上 GPU 之间应该全是NVL如果出现SYS说明拓扑或绑核出了问题。3. 拓扑构建textNCCL INFO Trees [0] 1/-1/-1-0--1 NCCL INFO Channel 00/02 : Ring : 0 - 1 - 2 - 3 - 0NCCL 会构建 Ring 或 Tree 拓扑做 AllReduce。这块日志卡住或反复重试多半是 GPU 之间不通要去查 IB 网络或 GPU Fabric Manager。五、nvidia-smi topo / dmon硬件层监控最后这套是基本功训练前先做体检训练中做轻量监控。nvidia-smi topo -m查看 GPU 拓扑nvidia-smi topo -m输出一张矩阵GPU0 GPU1 GPU2 GPU3 GPU4 GPU5 GPU6 GPU7 GPU0 X NV12 NV12 NV12 NV12 NV12 NV12 NV12 GPU1 NV12 X NV12 NV12 NV12 NV12 NV12 NV12 GPU2 NV12 NV12 X NV12 NV12 NV12 NV12 NV12 ...字符含义NV#NVLink 直连数字越大带宽越高A100 是 NV12H100 是 NV18PIX/PXB/PHB走 PCIe 不同层级依次变慢NODE/SYS跨 NUMA 节点最慢部署多卡训练前先看一遍。如果两张卡之间是SYS那把它们放进同一个 process group 就是性能灾难。nvidia-smi dmon实时监控nvidia-smi dmon -s ucm -d 1-s ucm Utilization Compute Memory-d 1 每秒采样。输出像这样# gpu sm mem enc dec mclk pclk # Idx % % % % MHz MHz 0 30 90 0 0 1593 1410 1 95 45 0 0 1593 1410GPU0 的sm才 30%但mem跑到 90%——典型的访存密集特征算力被显存带宽拖住。这种 Kernel 拿去 ncu 分析多半会确认 Memory Bound。GPU1 的sm95% /mem45% 是计算密集的健康状态。完整排查工作流把这五件套串起来遇到性能问题的标准流程是nvidia-smi dmon先看一眼— sm 利用率多少是不是有卡在睡觉PyTorch Profiler 跑一轮— 找 Self CUDA Time 排行榜的 Top 3怀疑数据加载或通信上 nsys— 看 GPU 时间线有没有空白、NCCL 有没有重叠某个 Kernel 死活优化不动上 ncu— Roofline 看是 Memory Bound 还是 Compute Bound分布式训练 hang 住开 NCCL_DEBUGINFO— 检查网卡、连接方式、拓扑工具的开销是金字塔上面便宜下面贵。能在第 2 步解决的问题不要拖到第 4 步——绝大多数训练性能问题PyTorch Profiler nsys 这一组合就够用了。真正需要下到 ncu 的场景是写自定义 CUDA Kernel 或榨取理论峰值那是另一个层级的工作。至于 NCCL 和拓扑相关的——这些问题往往不是性能问题而是能不能跑起来的问题等到训练 hang 了再开始查就晚了所以首次部署时先topo -m看一眼、单机多卡和多机各跑一次 NCCL_DEBUG是值得的预防性投入。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2601699.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!