尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

AI系统性能优化:从单点突破到系统级协同的工程实践

发布时间:2026/9/28 16:44:08

资讯中心
01
ARTICLE

AI系统性能优化:从单点突破到系统级协同的工程实践

AI系统性能优化:从单点突破到系统级协同的工程实践
1. 从单点优化到系统级性能工程为什么第三篇才是真正的分水岭做AI系统性能优化的人前两年往往沉迷于单点突破——把某个算子写得更快、把某个kernel的occupancy拉满、把某个通信原语换成更高效的实现。这些工作当然有价值但做到一定阶段就会发现一个尴尬的事实单点优化带来的收益正在被系统其他部分的瓶颈迅速吃掉。你花两周把attention的计算时间压下去30%结果端到端吞吐只涨了4%因为瓶颈早就转移到了显存带宽或者调度开销上。这就是“AI系统性能工程”这个系列走到第三篇时必须面对的问题当单点优化触及边际收益递减的拐点性能工程的重心必须从“局部最优”转向“全局协同”。前两篇我们聊了算子级别的优化和单卡层面的显存管理那些是基本功。第三篇要解决的是更高维度的矛盾——计算、通信、存储、调度这四个子系统之间的相互制约关系以及如何用系统化的方法找到真正的全局瓶颈。这篇文章适合谁看如果你已经能独立完成单算子优化、理解roofline模型、知道什么是arithmetic intensity但面对一个真实的训练或推理系统时仍然不知道从哪里下手那这篇就是写给你的。如果你还在纠结怎么把某个matmul写得更快建议先回头补前两篇的基础。这里不会重复讲基础概念我们直接进入系统级的性能分析方法论和实操细节。全文会围绕四个核心问题展开怎么建立系统级的性能模型、怎么定位跨子系统的瓶颈、怎么设计协同优化方案、怎么验证优化效果并防止回退。每个部分都会给出可复现的操作步骤和参数选择的计算过程最后附上我在实际项目中踩过的坑和排查技巧。2. 系统级性能建模别再靠直觉猜瓶颈了2.1 为什么单卡roofline模型在系统层面会失效单卡roofline模型的核心假设是计算和访存可以重叠性能上限由两者中更慢的那个决定。这个模型在单算子层面非常好用但放到系统层面就有一个致命缺陷——它假设数据是“免费”到达计算单元的。真实系统里数据要经过HBM、L2、SMEM、寄存器四级存储层次跨卡还要走NVLink或PCIe跨节点还要走网络。每一级都有带宽上限和延迟而且这些资源是共享的。我见过太多团队拿着单卡roofline算出来的理论峰值去对标实际吞吐然后困惑为什么只达到了30%。原因很简单系统级性能不是单卡性能的线性叠加而是多个资源约束下的帕累托最优问题。你需要一个能同时表达计算约束、带宽约束、延迟约束和同步约束的模型。一个实用的做法是建立“资源-时间”矩阵。把系统中所有关键资源列出来——SM计算单元、HBM带宽、L2带宽、NVLink带宽、网络带宽、SMEM容量、寄存器文件——然后对每个算子标注它主要消耗哪些资源、消耗量是多少。这个矩阵不需要精确到cycle级别但必须能回答一个核心问题当前算子的瓶颈资源是什么下一个算子的瓶颈资源又是什么两者是否冲突。2.2 用“关键路径法”找到真正的端到端瓶颈资源-时间矩阵建好之后下一步是找关键路径。关键路径的概念来自项目管理但在性能工程里同样适用端到端延迟等于关键路径上所有环节的延迟之和非关键路径上的优化对总延迟没有贡献。具体操作上我习惯用分层计时的方式。以训练一个Transformer为例把一次迭代拆成数据加载、前向计算、反向计算、梯度同步、参数更新五个阶段。每个阶段再往下拆一层比如前向计算拆成embedding、attention、FFN、layer norm。然后对每个子阶段记录三个时间戳开始时间、结束时间、以及该阶段内计算单元的实际忙碌时间。这里有个关键细节不要只看wall-clock时间要看计算单元的忙碌率。一个阶段花了10ms但计算单元只忙碌了3ms那7ms就是等待时间——可能在等数据、等同步、等调度。等待时间才是系统级优化的主战场。我通常会画一张甘特图用Python的matplotlib就能画横轴是时间纵轴是各个资源单元每个算子用矩形表示矩形的高度表示该资源的使用率。这张图能直观地暴露资源冲突比如两个计算密集型算子被安排在了同一时间段导致计算单元争抢或者一个通信算子阻塞了后续所有计算算子。2.3 一个可落地的系统性能模型示例假设你在训练一个13B参数的模型用8卡A100TP4PP2DP2。我们来建一个简化但够用的性能模型。首先算理论计算量。13B参数每个token的前向计算量约等于2×参数量26 GFLOPs反向约等于4×参数量52 GFLOPs合计78 GFLOPs/token。假设序列长度2048batch size 8那么一次迭代的计算量是78×2048×8≈1.28 PFLOPs。A100的FP16峰值算力是312 TFLOPS8卡合计2496 TFLOPS。理论最短计算时间是1.28/2496≈0.51ms。但实际中MFUModel FLOPs Utilization能到50%就算不错了所以实际计算时间约1ms。然后算通信量。TP4意味着每层attention和FFN后都要做all-reduce每次all-reduce的数据量是batch×seq×hidden×2字节。以hidden5120为例一次all-reduce是8×2048×5120×2≈168MB。每层两次all-reduce40层就是80次合计13.4GB。NVLink带宽是600GB/sA100双向但实际有效带宽约400GB/s所以通信时间约33ms。等等33ms的通信时间远大于1ms的计算时间这显然不对。问题出在哪里因为all-reduce可以和计算重叠而且TP的all-reduce是在层内进行的不需要等所有层算完。实际中通信和计算的重叠程度决定了端到端性能。如果重叠得好通信时间可以被计算时间掩盖如果重叠得差通信就会成为瓶颈。这个简单的计算说明了一个重要事实在系统层面通信往往是比计算更大的瓶颈而重叠是解决通信瓶颈的核心手段。但重叠不是免费的它需要额外的显存来缓存通信数据需要精细的调度来避免死锁还需要考虑通信和计算对SM资源的争抢。3. 跨子系统瓶颈定位从“哪里慢”到“为什么慢”3.1 计算、通信、存储的三角制约关系系统级性能问题的根源几乎都可以归结为计算、通信、存储三者之间的制约关系。这三者共享硬件资源计算用SM通信用NVLink/网络和SM用于打包解包存储用HBM和L2。当三者中的任何一个成为瓶颈时另外两个的资源利用率就会下降。我习惯用一个“三角诊断法”来定位问题。具体做法是在系统运行过程中同时采集三组指标——计算单元的活跃周期占比、通信链路的带宽利用率、存储层次的实际带宽与理论带宽之比。然后看哪个指标先达到饱和。如果计算活跃度高但吞吐上不去说明计算本身是瓶颈需要优化算子效率。如果通信带宽利用率高但计算活跃度低说明通信是瓶颈需要优化重叠或减少通信量。如果存储带宽接近理论峰值但计算和通信都不饱和说明访存是瓶颈需要优化数据复用或减少访存。但真实情况往往更复杂三者可能交替成为瓶颈。比如在一个pipeline并行系统中micro-batch 1在做计算时micro-batch 2在做通信micro-batch 3在等存储。这时候需要的是时间维度上的资源调度而不是单点的资源优化。3.2 用Nsight Systems做全栈时间线分析Nsight Systems是我最常用的系统级分析工具。它的核心价值是能把CPU、GPU、通信、存储的时间线对齐到同一时间轴上让你一眼看出谁在等谁。使用步骤上我通常这样操作nsys profile -t cuda,nvtx,osrt,cudnn,cublas -o profile_report --force-overwrite true python train.py关键参数说明-t指定要采集的trace类型cuda采集CUDA API和kernelnvtx采集自定义的range标记osrt采集操作系统运行时事件。--force-overwrite避免文件已存在时报错。采集完成后用nsys stats生成统计报告或者用GUI打开.nsys-rep文件做可视化分析。在GUI里我重点关注三个视图CUDA HW视图看kernel执行和memcpyNVTX视图看自定义的代码段OSRT视图看CPU线程状态。一个典型的发现是GPU在等CPU下发kernel。这在PyTorch里很常见因为Python的GIL和动态图机制会导致CPU成为瓶颈。解决办法是用CUDA Graph或者TorchScript把下发开销摊薄。另一个常见发现是kernel之间有空隙原因是显存分配或同步操作。这时候需要检查是否有不必要的.item()调用或者显存碎片。3.3 通信瓶颈的精细定位方法通信瓶颈的定位比计算瓶颈更麻烦因为通信涉及多个rank需要跨进程分析。我的做法是分三步走。第一步确认通信是否是瓶颈。用torch.distributed的monitored_barrier或者NCCL的调试日志记录每次通信操作的开始和结束时间。如果通信时间占端到端时间的比例超过20%就值得优化。第二步确认是哪种通信瓶颈。NCCL的通信瓶颈通常有三类带宽瓶颈数据量太大、延迟瓶颈小消息太多、同步瓶颈rank之间负载不均。带宽瓶颈的表现是通信时间与数据量成正比延迟瓶颈的表现是通信时间与消息数量成正比同步瓶颈的表现是某些rank的通信时间远大于其他rank。第三步针对性优化。带宽瓶颈需要减少通信量比如用ZeRO或者梯度压缩延迟瓶颈需要合并小消息比如把多个all-reduce合并成一个同步瓶颈需要做负载均衡比如调整数据分配或者用动态batching。这里有个实操细节NCCL的环境变量对性能影响很大。NCCL_ALGO可以强制指定ring或tree算法NCCL_PROTO可以指定LL、LL128或Simple协议。对于大消息ringSimple通常最好对于小消息treeLL延迟更低。但具体选哪个需要实测。我一般会写一个简单的benchmark脚本遍历不同的组合找到当前硬件拓扑下的最优配置。4. 协同优化方案设计让1124.1 计算与通信的重叠策略选择计算通信重叠是系统级优化的核心手段但重叠策略的选择需要根据具体场景来定。常见的重叠策略有三种粗粒度重叠、细粒度重叠、以及完全融合。粗粒度重叠是指把计算和通信分成大的阶段比如前向计算全部做完再做梯度同步。这种策略实现简单但重叠效果差因为通信期间计算单元完全空闲。细粒度重叠是指把计算和通信拆成小的chunk交替执行。比如把梯度分成多个bucket每个bucket算完就立即开始all-reduce同时继续算下一个bucket的梯度。PyTorch的DDP默认就是这个策略通过bucket_cap_mb参数控制bucket大小。bucket太小会导致通信次数多、延迟高bucket太大会导致重叠不充分。经验值是25MB左右但需要根据网络带宽和计算量调整。完全融合是指把通信操作嵌入到计算kernel内部比如在Flash Attention里把跨卡的数据交换和attention计算融合在一起。这种策略重叠效果最好但实现难度极高需要手写CUDA kernel和通信原语。我的建议是先从细粒度重叠开始用DDP的bucket机制或者Megatron的梯度累积来重叠。如果还不够再考虑完全融合。不要一上来就追求完全融合因为调试成本太高而且容易引入正确性问题。4.2 显存与并行的协同设计显存是系统级性能的隐形约束。很多时候计算和通信都不是瓶颈但显存不够导致batch size上不去或者导致频繁的offload最终拖累了整体性能。显存优化的核心思路是“用时间换空间”或者“用通信换空间”。用时间换空间的典型做法是activation checkpointing把中间激活值丢掉反向时重新计算。用通信换空间的典型做法是ZeRO把优化器状态和梯度分片到不同卡上。但这两者都有代价。Activation checkpointing会增加约30%的计算量ZeRO会增加通信量。所以选择哪种策略取决于当前系统的瓶颈是什么。如果计算是瓶颈就不要用checkpointing如果通信是瓶颈就不要用ZeRO。更精细的做法是混合策略对计算密集的层用checkpointing对通信密集的层用ZeRO。或者根据层的特性动态选择。比如attention层的激活值大但计算量相对小适合checkpointingFFN层的激活值小但计算量大适合保留激活值。4.3 调度策略与负载均衡在分布式系统里调度策略对性能的影响经常被低估。一个不合理的调度会导致某些rank过载而另一些rank空闲最终端到端性能由最慢的rank决定。负载不均的来源有很多数据长度不一致、计算图动态变化、硬件异构等。对于数据长度不一致的问题可以用动态batching或者token-level的负载均衡。对于计算图动态变化的问题可以用静态图或者CUDA Graph来固定执行顺序。对于硬件异构的问题需要根据每张卡的实际算力来分配工作量。我遇到过一个典型案例在一个8卡系统里7张卡的利用率都在90%以上但第8张卡只有60%。排查后发现是数据分配的问题——第8张卡分到的样本序列长度普遍偏长。解决办法是用一个简单的贪心算法做负载均衡把所有样本按长度排序然后依次分配给当前负载最小的卡。这个改动让端到端吞吐提升了15%。5. 实操验证与防回退优化不是一次性的5.1 建立可复现的基准测试性能优化最怕的是“这次快了下次又慢了”。要避免这个问题必须建立可复现的基准测试。基准测试的核心要求是输入固定、环境固定、测量方法固定。输入固定意味着用固定的随机种子和固定的数据。环境固定意味着记录CUDA版本、驱动版本、NCCL版本、PyTorch版本以及所有相关的环境变量。测量方法固定意味着用相同的warmup次数、相同的测量轮数、相同的统计方法通常取中位数或95分位数。我通常会写一个benchmark.py脚本把所有这些固定下来然后每次优化后跑一遍记录结果。脚本里会包含模型初始化、数据生成、warmup、正式测量、结果输出。输出格式用JSON方便后续对比。import torch import json import time def benchmark(model, input_data, warmup10, iters50): # warmup for _ in range(warmup): model(input_data) torch.cuda.synchronize() # measure times [] for _ in range(iters): start time.perf_counter() model(input_data) torch.cuda.synchronize() end time.perf_counter() times.append(end - start) times.sort() return { median: times[len(times)//2], p95: times[int(len(times)*0.95)], min: times[0], max: times[-1] }这个脚本看起来简单但有几个关键细节torch.cuda.synchronize()必须在计时前后都调用否则测到的是下发时间而不是执行时间warmup次数要足够让GPU频率和缓存都进入稳定状态测量轮数要足够多避免偶然波动。5.2 性能回退的常见原因与预防性能回退的原因通常有几类代码变更引入了额外的同步、显存分配策略变化导致碎片、通信拓扑变化导致带宽下降、以及编译器优化策略变化。预防回退的最好方法是持续集成性能测试。每次代码合并前自动跑一遍基准测试如果性能下降超过阈值比如3%就阻止合并。这个阈值不能设得太小因为GPU性能本身有波动也不能设得太大否则会漏掉真正的回退。另一个实用技巧是记录“性能指纹”。除了端到端时间还记录各个子阶段的时间和资源利用率。这样当性能回退时能快速定位是哪个子阶段出了问题。比如端到端时间涨了5%但前向时间没变反向时间涨了10%那问题很可能在反向的某个算子上。5.3 从优化到可观测建立性能监控体系优化做完不是终点建立持续的性能监控体系才是。在生产环境里性能问题往往不是突然出现的而是逐渐劣化的。比如显存碎片逐渐增多、通信队列逐渐变长、kernel执行时间逐渐变长。我建议至少监控四个维度的指标吞吐量tokens/s或samples/s、延迟P50/P95/P99、资源利用率GPU利用率、显存占用、网络带宽、以及错误率通信超时、OOM等。监控数据要能下钻。比如吞吐量下降了能下钻到是哪个rank下降了、是哪个阶段下降了、是哪个算子变慢了。这需要在前向和反向的关键节点埋点记录时间戳和资源状态。一个轻量级的做法是用NVTX标记关键代码段然后用Nsight Systems定期采集。虽然Nsight Systems有性能开销但可以只在排查问题时开启平时用轻量级的CUDA Event计时。6. 常见问题与排查技巧实录6.1 通信hang住的排查思路通信hang住是分布式训练里最让人头疼的问题之一。表现是程序卡住不动GPU利用率降到0但进程没有退出。排查的第一步是确认hang在哪个通信操作上。用py-spy dump或者gdbattach到进程上看调用栈。如果是NCCL的调用看是all-reduce、all-gather还是其他操作。第二步是确认是哪个rank的问题。通常hang住是因为某个rank没有到达集合通信点。可以在每个rank上打印通信前的标记看哪个rank没有打印。第三步是确认原因。常见原因有某个rank的显存OOM导致进程退出、某个rank的计算时间远大于其他rank、网络抖动导致某个rank的通信超时。解决办法分别是调整显存分配、做负载均衡、增加通信超时时间。这里有个实用技巧设置NCCL_ASYNC_ERROR_HANDLING1和TORCH_NCCL_ASYNC_ERROR_HANDLING1让NCCL在检测到错误时主动abort而不是无限等待。另外设置NCCL_TIMEOUT为一个合理的值比如30分钟避免永久hang住。6.2 显存碎片导致OOM的解决显存碎片是另一个常见问题。表现是显存总量够但分配失败或者batch size稍微大一点就OOM。解决显存碎片的方法有几个。一是用PYTORCH_CUDA_ALLOC_CONF设置expandable_segments:True让PyTorch的缓存分配器支持可扩展段减少碎片。二是避免频繁的显存分配和释放尽量复用buffer。三是用torch.cuda.memory_summary()查看碎片情况如果碎片率超过20%就需要考虑重构显存使用模式。我遇到过一个典型案例模型训练过程中显存逐渐增长最终OOM。排查后发现是某个自定义算子每次调用都会分配新的临时显存但没有释放。解决办法是在算子内部用静态buffer或者用PyTorch的out参数复用输出tensor。6.3 性能优化速查表问题现象可能原因排查方法解决方案GPU利用率低但吞吐上不去CPU瓶颈或通信瓶颈Nsight Systems看CPU线程和通信时间线用CUDA Graph减少下发开销或优化通信重叠通信时间占比高通信量大或重叠差NCCL调试日志看通信时间和数据量减少通信量ZeRO/压缩或改进重叠策略显存OOM但总量够显存碎片torch.cuda.memory_summary()设置expandable_segments复用buffer某些rank特别慢负载不均各rank计时对比负载均衡或动态batching性能逐渐劣化显存碎片或队列积压长期监控资源利用率定期重启或重构显存管理kernel之间有空隙同步操作或显存分配Nsight Systems看kernel间隙移除不必要的同步预分配显存6.4 几个反直觉的实操心得第一个心得有时候降低计算效率反而能提升端到端性能。比如用更慢但显存占用更小的算子可以让batch size翻倍最终吞吐反而更高。这在显存受限的场景下很常见。第二个心得通信优化不一定非要减少通信量。有时候增加通信量但改善重叠效果端到端性能反而更好。比如把all-reduce拆成多个小all-reduce虽然总通信量不变但重叠更充分。第三个心得不要迷信理论峰值。理论峰值是在理想条件下测出来的实际系统中能达到70%就算优秀。优化目标是让实际性能逼近实际可达的上限而不是逼近理论峰值。第四个心得性能优化需要跨团队协作。算法团队、框架团队、硬件团队的目标往往不一致。算法团队想要更大的模型框架团队想要更稳定的性能硬件团队想要更高的利用率。系统级性能工程需要在这些目标之间找到平衡点。7. 一个完整的优化案例从定位到验证7.1 案例背景与初始性能去年我参与了一个7B模型在8卡A100上的训练优化项目。初始配置是TP2PP2DP2序列长度2048batch size 4FP16混合精度。初始吞吐是每秒1200个tokenMFU约35%。目标是提升到每秒2000个token以上MFU达到50%以上。这个目标不算激进但也不容易因为35%到50%需要解决多个系统级瓶颈。7.2 瓶颈定位过程第一步用Nsight Systems采集时间线。发现三个问题一是kernel之间有大量空隙平均每个kernel间隙约50微秒二是all-reduce通信时间占比约25%三是显存占用接近上限batch size无法提升。第二步深入分析。kernel间隙的原因是PyTorch的动态图下发开销每个kernel下发需要约30微秒加上Python的GIL竞争导致GPU经常等CPU。all-reduce通信时间长的原因是TP2的all-reduce数据量大且没有和计算充分重叠。显存占用高的原因是activation没有做checkpointing中间激活值占用了大量显存。第三步确认优化优先级。kernel间隙是最容易解决的用CUDA Graph就能消除大部分。通信重叠需要调整梯度bucket策略。显存问题需要引入activation checkpointing但会增加计算量需要权衡。7.3 优化实施与效果验证优化一用CUDA Graph捕获整个训练迭代。PyTorch从1.10开始支持torch.cuda.graphs但需要模型是静态的。我们把模型用TorchScript导出然后用CUDA Graph捕获。这个改动消除了约80%的kernel间隙吞吐从1200提升到1500。优化二调整DDP的bucket大小。默认是25MB我们改成50MB减少通信次数。同时设置gradient_as_bucket_viewTrue减少显存拷贝。这个改动让通信时间占比从25%降到18%吞吐提升到1650。优化三引入activation checkpointing。对attention层做checkpointingFFN层不做。这样计算量增加约15%但显存占用降低约40%batch size从4提升到8。吞吐提升到2100MFU达到52%。优化四微调NCCL配置。设置NCCL_ALGORing和NCCL_PROTOSimple在8卡A100的拓扑下ringsimple的带宽利用率最高。这个改动让通信时间再降5%吞吐稳定在2200左右。最终结果吞吐从1200提升到2200MFU从35%提升到55%超过了最初的目标。7.4 优化后的防回退措施优化完成后我们建立了性能监控体系。每次代码合并前自动跑基准测试记录吞吐、延迟、显存占用、通信时间四个指标。如果任一指标下降超过3%就阻止合并。同时我们把优化配置写进了启动脚本包括CUDA Graph的捕获逻辑、DDP的bucket配置、NCCL的环境变量。这样新加入的团队成员不需要重新摸索直接复用经过验证的配置。这个案例给我的最大体会是系统级优化不是单点突破而是多个优化的叠加效应。每个优化单独看可能只有10%到20%的提升但叠加起来就是80%以上的提升。而且优化的顺序很重要——先解决最容易的kernel间隙再解决中等难度的通信重叠最后解决最难的显存约束。如果顺序反了可能会在显存优化上花大量时间但收益被kernel间隙吃掉。最后再分享一个小技巧在做系统级优化时准备一个“优化日志”记录每次改动的配置、预期效果、实际效果、以及观察到的副作用。这个日志在后续排查问题时非常有用因为你能快速回溯到某个性能变化是由哪次改动引起的。我现在的优化日志已经积累了上百条记录每次遇到新问题先翻日志往往能找到类似的案例和解决方案。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。