资讯动态

Nsight Systems实战:GPU利用率从50%拉到90%

发布时间:2026/10/14 0:44:30 来源:尧图企业网站定制
先给你描述一个场景你在跑一个开源大模型的微调nvidia-smi -l 1一刷GPU利用率只在40%到50%之间来回打转。你第一反应是显卡不行或者代码写得稀烂可等真想去定位却发现连个切入点都没有。这个现象我太熟了——GPU利用率低往往不是GPU自己偷懒而是整个训练链路里某个看不见的环节在拖后腿。想把它揪出来靠一张会跳数字的仪表盘远远不够必须用专业的AI Profiling工具而且要零侵入的那种不改一行训练代码、不重新编译模型就能把整个训练过程从头到尾解剖一遍。这篇文章会围绕一次真实排障展开讲讲我怎么利用这类工具把GPU利用率从不到50%拉到90%以上。涉及的工具、命令、判读思路我都会写得尽量具体方便你直接复现。1. 先搞清楚nvidia-smi里的“GPU利用率”到底代表什么1.1 那个百分比真的代表“GPU在忙”吗先说结论nvidia-smi里的利用率指的是在采样周期内GPU上有kernel正在执行的SM时间占比。它衡量的是“有没有活干”而不是“算力单元被用了多少”。打个比方一条自动化食品流水线机器每小时能处理1000个汉堡但上游供货商每5分钟才送来一批半成品机器大部分时间其实在空转。这时候你去看设备状态看到的就是“机器利用率很低”但问题压根不在机器本身。这个区别在深度学习场景里尤其坑。训练一个模型时GPU要干的活包括读取数据、做数据增强、搬数据进显存、执行前向反向、做梯度同步、更新参数。这些环节里只有“执行前向反向”真正用到了GPU算力其他环节很多是在CPU上完成的。CPU一旦慢了GPU自然就像流水线上的机器一样等米下锅利用率掉下去非常正常。1.2 我见过最典型的“假低利用率”案例之前我在调一个视觉模型的微调脚本训练日志里每步输出时间是1.6秒GPU利用率只有34%。当时我的第一反应是batch size太小于是把batch从32调到64结果显存直接爆了。后来细查才发现数据增强在CPU端做得太重——每次喂给GPU之前都要在Python里做随机裁剪、旋转、归一化这串操作走的是纯CPU逻辑每步要花1.1秒。GPU真正干活只需要0.5秒剩下的时间全在干等。这种情况你用任何“调大batch”的直觉去优化都没用因为瓶颈不在显存和GPU算力而在数据管线的吞吐。想验证这个判断最直接的方式就是用Profiling工具把CPU和GPU的时间线放在一起看看看GPU的空档是不是正好对应着CPU的高耗时操作。1.3 不看复杂Profiler之前先做这四个快速检查当然不是所有问题都值得立刻上重型工具。在打开Profiler之前我一般会先做一轮快速检查把嫌疑范围缩到最小用watch -n 0.5 nvidia-smi观察利用率波动周期看是否和训练step对齐。如果利用率呈现“高一下、低一下”的锯齿状大概率是每步之间有周期性等待。看CPU总占用率。如果训练时CPU占用已经拉到很满多半数据预处理或者Python逻辑是瓶颈。在训练循环里打印每次step的耗时以及DataLoader取一批数据的时间。两步一对比数据加载是否成为瓶颈一目了然。粗略数一下每步触发的kernel数量。如果你的模型很简单但每步有几千个kernel调用那很可能大量时间浪费在kernel启动开销上。这些检查花不了十分钟但能帮你决定下一步该用哪一类Profiling工具以及重点看什么指标。2. 零侵入Profiling工具怎么选我为什么盯上Nsight Systems2.1 “零侵入”的真正价值是保住现场市面上能看GPU状态的工具不少但绝大多数要么只看表面指标要么要求你改代码。零侵入工具的意义在于你在训练跑了一半、问题正在发生的时候就能介入不打断任务不改变程序行为拿到的是真实现场而不是“为了分析而分析”的表演数据。我自己主力用的是NVIDIA Nsight Systems命令名是nsys。它对运行中的进程做系统级采样能看到CPU函数调用栈、CUDA API调用、GPU kernel执行、内存拷贝、NCCL通信等几乎全部关键事件而且不需要在代码里插入任何探针。nsys可以直接启动一个训练脚本也可以对一个已经跑起来的进程做后台采样这就是它“零侵入”的含义。与之相对的是PyTorch自带的torch.profiler它需要在训练循环里包一个with语句严格来说算轻侵入。但它也有不可替代的价值能直接输出“哪一行PyTorch算子最耗时”的汇总表对定位框架层面的问题非常高效。from torch.profiler import profile, ProfilerActivity with profile(activities[ProfilerActivity.CPU, ProfilerActivity.CUDA]) as prof: for step in range(3): train_step(batch) print(prof.key_averages().table(sort_bycuda_time_total, row_limit20))2.2 我的标准组合nsys看全局ncu查内核PyTorch Profiler做汇总很多人拿到Nsight Systems之后会犯一个错误试图用它分析每一个kernel。实际上nsys的优势在宏观时间线它能告诉你瓶颈在哪个阶段、GPU在等谁但要深挖某个kernel为什么慢还得用Nsight Compute命令名是ncu后者能给出SM占用率、显存带宽、指令混合之类的微架构指标。我常用的搭配是nsys先跑一轮完整的训练哪怕几百步也行得到整个时间线。从时间线里找出最可疑的段落比如GPU利用率长期为0的空档或者某个耗时极长的kernel。针对性的用ncu去分析那个kernel看是访存受限、计算受限还是占用率太低。如果问题看起来和具体算子相关再用torch.profiler跑几步拿到按算子聚合的耗时表。这套流程看起来多了一步但实际排查效率比直接猜高很多。因为绝大多数GPU利用率问题根本不是某个kernel太慢而是整个训练流程里CPU、GPU、数据管线之间的配合出了问题。这类问题只有nsys这种全局时间线工具才能一眼看穿。2.3 使用前提和容易忽略的版本坑这些工具对运行环境有一些硬性要求使用前最好先确认项目要求NVIDIA驱动建议驱动版本较新至少支持CUDA 11.8以上CUDA Toolkit与驱动版本配套nsys和ncu会随CUDA Toolkit一起安装容器环境如果训练跑在Docker里需要给容器加--cap-add SYS_ADMIN --cap-add SYS_PTRACE或直接加--privileged权限云平台上的共享GPU实例如果没开放性能计数器权限ncu可能采不到部分指标但nsys一般还能用另外注意一点不同版本的nsys生成的文件后缀不一样早期版本是.qdstrm新版是.nsys-rep。用UI打开旧版报告有时候会提示不兼容建议统一用最新工具链。这个坑我踩过别等分析到一半才去折腾格式转换。3. 一次真实排障从“GPU只有50%”到拿到根因的完整链路3.1 现象与最初的三条怀疑有次我在微调一个7B参数的大模型单机8卡DDP跑的是分布式训练。nvidia-smi一刷每张卡利用率大致在45%到55%之间而且波动很厉害有时瞬间掉到20%以下。训练速度勉强能接受但直觉告诉我远没发挥出硬件潜力。当时我列了三条怀疑数据加载跟不上。模型大了每步要处理的token数量也多DataLoader如果没调好很容易成为瓶颈。并行策略有问题。虽然用的是DDP但某些加在模型外面的逻辑可能破坏了计算和通信的重叠。CPU端隐藏同步太多。训练脚本里如果频繁调用item()、.cpu()这类同步操作GPU会被迫停下来等CPU。这三条都有可能但光靠猜没法推进。我决定在下一个训练run里直接挂nsys。3.2 用nsys抓时间线把猜测变成证据执行的分析命令很简单nsys profile --tracecuda,nvtx,osrt --outputfinetune_7b python train.py--tracecuda,nvtx,osrt分别表示采样CUDA活动、NVTX标记模型自己打的阶段标记和操作系统运行时函数。--output指定报告文件名。跑完训练后nsys会生成一个报告文件我习惯直接在Nsight Systems的图形界面里打开。如果你在无图形界面的服务器上工作也可以先导出成文本摘要nsys stats finetune_7b.nsys-rep这个命令会输出一份每个CUDA函数和kernel的调用次数、总耗时、平均耗时的统计表虽然不如时间线直观但定位问题也够用了。3.3 从报告里读出的三个关键信号打开时间线后我看到了三条非常清晰的信号。第一条GPU Stream上每个step的开头都有一段大约0.8秒的空档。空档期间GPU完全没有kernel在执行但CPU线程此时正高度活跃。点开这份时间线里对应的CPU线程能看到大量时间花在DataLoader的__iter__和collate_fn上。这说明在每步训练前GPU都在等CPU把数据准备好两者根本没有重叠。第二条CPU侧出现了大量cudaMemcpyAsync调用而且每次执行时间都不短。翻到代码里一看训练脚本在每步结尾都有一次指标验证把计算好的logits从GPU复制回CPU再做一个torch.max之类的操作。这一步虽然“看起来只是一行代码”但它涉及一次完整的GPU到CPU数据搬运每步搬的数据量还不小。数据在总线上来回折腾时间就被吃掉了。第三条kernel的平均启动间隔偏高。进一步看nsys stats的输出我注意到CPU提交kernel的时间开销在整个step时间里占了不小的比例。这不是说某个算子慢而是脚本里散落着大量细碎的Python操作比如对loss.item()的情况没有批量处理每个item()都会触发一次GPU同步。太多零碎同步点堆在一起GPU利用率的“锯齿”就是这么来的。3.4 修复方案与实测效果针对这三条信号我做了三处改动把每步都做的指标验证改成每50步做一次并把logits从GPU搬回CPU时改用更小的抽样Tensor不做全量搬运。这一步直接削掉了每步末尾的大段传输时间。调大DataLoader的num_workers打开pin_memoryTrue同时把prefetch_factor从默认的2提高到4。这样数据加载和GPU计算能重叠起来GPU不再空等。清理训练循环里的零碎同步操作把多次item()调用合并成一次.cpu()批量取值尽量减少CPU和GPU之间的同步点。改完之后同一步的训练耗时从1.6秒降到0.9秒左右nvidia-smi显示的利用率稳定在88%到93%之间。整个训练任务的时间差不多缩短了40%。但要注意这里改动的每一处都不是我凭空猜出来的而是nsys时间线上白纸黑字写着的证据。4. 定位之后数据搬运、小算子、通信等待三类瓶颈的对症下药4.1 数据与内存搬运占主导的画像这类问题在Profiler里最典型的信号是GPU利用率呈周期性锯齿状空闲时段恰好和CPU侧的数据处理高度重合而且cudaMemcpy系列操作的耗时占比很高。处理方法优先级从高到低让数据加载和GPU计算重叠。DataLoader的num_workers、prefetch_factor、pin_memory这三个参数先调起来。减少CPU到GPU的碎片化拷贝。多个小Tensor最好拼成大块一次拷贝尤其避免在训练循环里频繁调用tensor.cpu()这类反向搬运。如果数据增强实在太重考虑把增强逻辑挪到GPU上用张量操作实现或者直接用DALI这类GPU端数据管线。判断数据瓶颈够不够严重可以做一个快速估算。假设每步要训练128条样本每条样本在CPU端预处理要花2毫秒那么每步的纯数据准备时间就是256毫秒。如果GPU计算本身只需要150毫秒那剩下的106毫秒就是纯等待。重叠起来之后每步时间理论上可以变成max(计算时间, 数据准备时间) 256毫秒而不是两者相加的406毫秒。很多“GPU利用率低”的案例一算这个账就清楚了。4.2 小算子与同步点过多的画像这类问题的特征也很明显kernel调用数量多到上万但单个kernel执行时间非常短经常只有几十微秒。这种碎片化的kernel执行会让GPU频繁在启动和退出的状态之间切换算力根本没有机会满载。PyTorch在不同版本里的执行方式差异很大。频繁的item()、tolist()、用Python的for循环逐元素操作都会制造同步点。优化这类问题优先做三件事给训练循环里的热路径“排毒”。统计所有同步操作的次数把非必要的item()调用删除或者延迟到累积到一批再取。用torch.compile或torch.jit.script把细碎的Python算子融合成更高效的CUDA kernel减少启动次数。批量操作替代逐元素操作。比如计算准确率时不要用Python循环遍历batch而是直接用torch.eq之类的向量化操作。4.3 多卡通信在等待的画像多卡训练里还有个独特现象GPU利用率看着不低但吞吐也提不上去。此时去nsys时间线里看NCCL通信段会发现每步结束时有一个长长的通信尾迹GPU在等待其他卡发来的梯度。处理思路一般是检查DDP的bucket_cap_mb设置适当调小bucket可以让通信和反向计算更早重叠。尝试梯度累积降低同步频率但注意这会增加显存占用要配合梯度剪裁。如果框架支持打开通信计算重叠选项比如PyTorch DDP里的static_graphTrue在某些场景下能显著减少通信等待。更在意单机训练效率的话可以考虑用zero_grad(set_to_noneTrue)减少不必要的显存清零操作。这类问题在不同框架、不同网络结构下差异很大没有万能参数。但排查路径是一致的先在时间线上确认通信占用了多少时间再决定要不要动通信策略。不要一上来就去调NCCL的环境变量那样很容易把本来正常的行为改出问题。5. 零侵入分析时容易踩的坑和我留下的习惯5.1 Profiling过程本身会污染性能数据nsys采样时的开销虽然不大但依然会给训练过程带来额外负担。我实测过开--tracecuda,nvtx,osrt跑训练测出来的step time会比正常训练高出10%到20%。所以千万不能把Profiling过程中测得的绝对耗时当成最终性能指标那只能用来横向比较不同阶段和不同kernel之间的相对关系。如果要和优化后的数据做对比我习惯在Profiling结束之后正常启动一次训练重新测一遍真正的step time。5.2 容器和云环境里权限比工具本身更麻烦在Docker容器里用nsys和ncu最常遇到的就是权限不足表现为采样到的数据不完整或者ncu直接报错。Docker启动时最好提前加上docker run --gpus all --cap-add SYS_ADMIN --cap-add SYS_PTRACE --ipchost ...如果你是租用的云GPU实例管理不了宿主机的驱动与权限那ncu的硬件计数器大概率用不了。这种情况就退回到PyTorch Profiler虽然侵入性大一点但拿到的算子级汇总数据足够解决大部分问题了。5.3 别把“GPU利用率”当成唯一指标最后说一个观念上的坑。GPU利用率高不代表系统性能好。一个训练系统如果利用率很高但同步点频繁、kernel调度稀碎它依然有可能是低效的。反过来利用率不高也不一定代表GPU没吃饱。我判断一个训练系统健不健康通常会同时看四个数GPU利用率、step time、CPU端等待时间、每步kernel数量。这四个数结合起来才能还原全貌。以后如果你的GPU利用率又莫名其妙掉下去先别急着怀疑显卡打开nsys看一眼时间线看看GPU在等谁。多数情况下它等的是CPU等的是数据等的是通信而不是显卡自己跑不动。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑