多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

AI Infra必知:GPU架构如何决定训练性能与优化方向

AI Infra必知:GPU架构如何决定训练性能与优化方向 1. 先搞清楚为什么搞AI Infra必须啃GPU架构做AI Infra的人或多或少都听过一句话GPU是AI的算力基石。但真到干活的时候你会发现很多人对基石的理解只停留在贵快缺货这三个层面。真正在集群上做训练、调性能、配资源的时候不懂GPU架构的人会频繁碰到下面这类问题一张A100在跑一个小模型时nvidia-smi显示的利用率只有30%但训练就是快不起来瓶颈到底在哪儿两台机器做分布式训练网络开销高得离谱8卡变4卡连一半加速比都跑不出来是代码问题还是拓扑问题为什么同一个模型在消费级显卡上调整batch size后显存够用但换到数据中心卡上反而OOM显存带宽这四个字到底意味着什么这些问题如果没有GPU架构层面的知识打底排查起来就是瞎猫碰死耗子。你会陷入调参—看指标—再调参的循环做了大量改动仍然定位不到根因。这篇文章是我AI Infra入门系列的第二篇上一篇我们聊了AI Infra的宏观视角和整体技术栈这次专门讲GPU架构。我不会从电子工程的角度讲晶体管而是从AI Infra从业者真正需要的角度出发GPU的任务拆解方式、存储层次、多卡互联、软件栈和调度隔离。你会搞清楚那些规格表上的参数到底影响什么以及遇到性能问题时该怎么从架构层面找原因。本文适合三类读者第一类是刚转行做AI Infra、对GPU只有模糊概念的工程师第二类是算法工程师想弄明白自己写的训练代码在GPU上是怎么被执行的第三类是运维和平台开发需要理解GPU资源管理背后的硬件逻辑。2. GPU的核心设计哲学用大量小核换吞吐量用隐藏延迟换效率2.1 从CPU到GPU为什么CPU主频高却跑不过GPUCPU和GPU的根本差异不在于谁快谁慢而在于它们为不同的目标做了完全不同的设计取舍。CPU的核心追求是低延迟——它要在极短的时间内完成一个个复杂的逻辑判断、分支跳转、数据依赖操作。所以CPU核心做得很重有巨大的控制单元、深度的乱序执行逻辑、大容量的缓存所有设计都在为一个目标服务让单个指令尽可能快地执行完。GPU的核心追求是高吞吐量——它不关心单条指令有多快只关心单位时间内能处理多少数据。当你要处理100万个像素、100万条向量、100万个矩阵运算时单条指令的延迟不重要重要的是能不能在同一时间把100万个计算全部推出去。这就是GPU采用大量小核路线的根本原因。一颗A100有6912个CUDA核心而一颗顶级的服务器CPU通常只有几十个核心。但GPU的每个核心小得多没有复杂的乱序执行单元没有超深的分支预测甚至没有传统意义上完整的操作系统进程调度视角——它靠什么保证效率靠并发掩盖延迟。2.2 延迟隐藏与SIMTGPU没有把等数据的时间浪费掉GPU的设计精髓在于延迟隐藏Latency Hiding。当一个线程组在等待显存数据返回时GPU不会让计算单元闲着而是立刻切换到另一个线程组继续计算。这个切换非常廉价因为它只换寄存器组和线程上下文。一个SMStreaming Multiprocessor上有数千个线程的寄存器状态常驻就是为了随时可以无缝切换。很多人不理解GPU为什么不像CPU那样做大缓存、做复杂调度器。答案就是不需要。CPU把宝贵的芯片面积用来做缓存和分支预测是为了让单个线程等数据的时间尽量短。GPU反其道而行之它把芯片面积用来堆计算单元不怕你等数据因为它有足够多的其他线程可以顶上。这种执行模型叫做SIMTSingle Instruction, Multiple Threads本质上是一组线程通常32个线程构成一个Warp同时执行同一条指令只是作用在不同数据上。这是理解GPU编程最核心的概念之一。当你写CUDA内核函数时千万不要用CPU的思路去想这个分支会不会让线程等很久——在GPU上真正需要关注的是是否出现了Warp Divergence线程分支发散也就是一个Warp里的32个线程走入了不同的代码分支。一旦发生这两个分支会被串行执行本来并行的32个线程直接损失一半以上的效率。在实践中我踩过一个特别典型的坑在CUDA内核里写了一个if (data[i] threshold)的过滤逻辑数据是随机分布的话Warp Divergence会频繁触发性能直接掉了40%以上。后来把数据按阈值排序让连续的一批线程走同一分支性能才恢复。这个经验不一定每次都适用但它能帮你培养GPU思维优化GPU代码的核心是考虑数据布局和执行模型而不是只盯着算法复杂度。2.3 SM的完整结构从线程到Warp再到Block流式多处理器SM是GPU最基本的计算单元。以NVIDIA Ampere架构的A100为例每个SM包含4个处理块每个块有32个FP32 CUDA核心、16个FP64核心和16个Tensor Core另外还有4个纹理单元和专用的加载/存储单元。从编程模型看动态关系是这样的你启动一个CUDA Kernel会指定一个Grid网格Grid由多个Block线程块组成。每个Block包含若干线程这些线程被进一步分组为Warp每个Warp固定32个线程。GPU的GigaThread引擎把Block分配给不同的SM。一个Block的所有线程必须运行在同一个SM上但一个SM上可以同时运行多个Block。在每个SM内部Warp由Warp Scheduler调度执行。A100每个SM有4个Scheduler每个时钟周期可以发射一条指令给一个Warp。理解这个层级关系之后很多编程时的直觉就能建立起来。比如为什么Block大小建议设置为32的倍数因为如果Block里是40个线程硬件会不得不分配两个Warp浪费24个线程的槽位同时还可能产生负载不均衡。又比如为什么一个Block内线程数不宜超过1024因为这是GPU硬件层面能够调度的上限。2.4 以资源用量读懂架构Register Files与Occupancy的博弈每个SM上的寄存器文件Register File容量是有限的A100每个SM有65536个32位寄存器。如果每个线程占用32个寄存器那么一个SM同时最多容纳2048个线程如果每个线程占用64个寄存器线程上限就变成1024。对比一下这个关系与Occupancy占用率这个概念你会发现寄存器用量直接决定了SM上能同时驻留多少个线程。Occupancy高意味着有更多Warp可供调度器切换更有利于隐藏访存延迟。很多人在写CUDA时为了减少寄存器溢出刻意调低寄存器和局部内存使用率但如果太低导致Occupancy反而下降性能也上不去。这是一个需要实测权衡的平衡点。我做过一个优化案例某个自定义算子在V100上实测默认寄存器用量96个Occupancy只有37%延迟隐藏不足所以吞吐一直上不去。后来通过限制每个线程的中间变量数量把寄存器压到64个Occupancy提高到50%吞吐提升了30%左右。提示性能优化没有万能药任何参数调整都要用NVIDIA Nsight这类工具做Profile之后再做决定。3. 存储层次AI训练成本最贵的流量包是显存带宽3.1 为什么规格表上显存带宽比显存大小更关键很多人买显卡第一眼看显存大小12G、24G、80G感觉越大越强。但对于AI训练来说显存带宽Memory Bandwidth很多时候比显存容量更能决定整个训练的上限。为什么因为GPU在做矩阵乘法、卷积这类操作时计算单元的工作模式很简单从显存读数据到寄存器计算写回。如果一个操作是读写密集型的那么不管你计算单元多快整个操作都会被访存带宽卡住。这就是为什么A100用了带宽高达2TB/s的HBM2e显存而普通GDDR6显存带宽只有几百GB/s——老实说只要你把同样的模型分别在搭载这两种显存的卡上跑一次差距立刻就会体现出来。你可以把显存带宽理解成流量包的额度SM里的计算单元是高速公路上跑的货车。公路再宽、货车再多如果收费站出口只能一辆一辆放行那整体吞吐量照样上不去。这也是为什么很多AI推理卡宁可把频率拉低、核心数减少也要优先堆带宽——因为很多模型是访存密集型的算力反而不是第一瓶颈。3.2 三层访存结构全局内存、共享内存与寄存器从编程模型的角度GPU访存有清晰的层次结构每一层的延迟差异是数量级的寄存器Register延迟约1个时钟周期容量最小属于每个线程私有。共享内存Shared Memory位于SM内部延迟约20-30个时钟周期属于Block内共享。A100每个SM有192KB共享内存这是一个可以主动管理的高速缓存。L1/L2缓存L1和共享内存在硬件上共用容量L2缓存位于芯片级所有SM共享A100有40MB L2。全局显存Global Memory延迟高达400-800个时钟周期容量最大也就是规格表上的24G、40G、80G。这里隐藏着一个非常核心的优化思路如果你能尽量减少全局显存的访问次数把热点数据搬进共享内存或者寄存器里复用那性能会得到数量级的提升。这比调一堆编译选项都管用。用矩阵乘法举例。朴素做法是每次计算都从全局显存读三个矩阵的对应元素一个分块矩阵乘法的CUDA实现会用__shared__把子块A和子块B先拷进共享内存然后所有线程从共享内存中读数据做乘加。一个128x128的分块如果用共享内存全局访存次数下降数十倍性能差距就在这儿。3.3 显存带宽为什么是AI训练的最大瓶颈当代大模型训练语法上离不开大量数据喂给GPU这个操作。以训练GPT类模型为例每个训练Token都要经过数百层网络的前向和反向传播。中间激活值Activations的保存和读取、权重梯度的更新与同步全都是显存读写操作。粗算一下7B模型用混合精度训练参数本身占14GBFP16优化器状态按Fused AdamW用FP32主权重FP32动量FP32方差大约42GB。只这简单的参数就已经让消费级卡望而却步。即便是在A100 80GB上运行每次迭代把全部模型数据写读一遍就是几十GB的显存流量。如果带宽不够GPU计算单元大部分时间都在等数据表现出来就是nvidia-smi里利用率低但不是因为代码写得不好是因为喂不上数据。在AI Infra的视角下有一门必修的功课就是学会看一个模型到底是Compute Bound计算受限算术强度高还是Memory Bound访存受限算术强度低。这两个不同的场景在GPU选型上意味着完全不同的决策方向。如果是Memory Bound的模型买更多CUDA核心意义不大把钱花在高带宽的卡上反而见效。这一点涉及到下面这张表格里的核心参数。指标好理解的方式选型含义FP32 TFLOPS每秒能做多少次单精度浮点计算通用AI训练/推理的基本速度Tensor Core TFLOPS每秒能做多少次混合精度矩阵运算大模型训练速度的关键显存带宽每秒能从VRAM读出多少数据访存密集模型的最核心指标显存容量能装下多大的模型和批次决定能不能跑不代表跑得快NVLink带宽卡与卡之间数据交换速度多卡训练扩展性的关键4. Tensor Core、NVLink与多卡互联一块卡到一台训练机的关键跨越4.1 Tensor Core为什么AI卡比游戏卡值钱那么多经常有朋友问玩游戏用的RTX 3080也是GPU显存不小为什么跑大模型训练跟A100差距那么大除了显存带宽还有一个关键差别就在Tensor Core。现代AI工作负载大部分可以表示为矩阵乘法。而Tensor Core是专门为矩阵乘加运算设计的硬件单元支持混合精度计算。A100的Tensor Core在稀疏场景下能达到312 TFLOPSFP16稀疏对比之下RTX 3080只有约97 TFLOPS。这个数量级的差距不是靠更多CUDA核心能追回来的。Tensor Core实际工作的原理是它接收两个4x4的FP16矩阵在单周期内完成矩阵乘加运算输出FP32累加结果。CUDA核心要分多步完成的运算Tensor Core一次就做完了。近几年训练大模型的标配是BF16/FP16混合精度正是因为Tensor Core能同时把吞吐量拉满。所以说做AI Infra选型时这卡有几个Tensor Core和Tensor Core算力多少通常比CUDA核心数量更有参考价值。很多面向AI的推理卡直接把Tensor Core当主打卖点就是这个逻辑。4.2 NVLink多卡训练扩展性的第一个瓶颈单卡再强也有物理上限NVIDIA的NVLink技术连接多块GPU。每一代NVLink的带宽都在提升A100时代是600GB/s双向H100时代到了900GB/s。这个带宽有多重要分布式训练中梯度同步依赖于AllReduce通信。以数据并行为例每轮迭代后所有卡要把自己算出的梯度汇总到全局再平均分发给每张卡。这个通信量随模型尺寸线性增长如果互联带宽不够通信时间可能超过计算时间。经常有人遇到这种情况单卡训练benchmark表现不错但8卡分布式训练只比4卡快1.5倍。排查到最后通常发现两个原因一个是数据加载和预处理撑不住另一个就是卡间互联拓扑不合理。比如8卡里如果有两张卡走PCIe而不是NVLink它的通信速度会断崖式下降整机性能被这块短板拽住。NVIDIA目前的NVLink拓扑分两种形态一种是NVLink全互联8卡都直连延迟低带宽高另一种是NVSwitch架构8块GPU通过NVSwitch互连构成一个完全连接的拓扑。在云厂商租用GPU实例时是否支持NVLink直接决定了你的分布式训练上限。4.3 从单机8卡到跨机集群通信占比意味着什么单机8卡仍然是很多中型团队最常用的训练环境。8卡内通信依赖NVLink跨机通信就要走RDMA网络最常见的是InfiniBand或RoCE。这里引出一个概念通信/计算比。假设你的模型是7B规模前向反向一次需要计算约140 GFLOPs。A100单卡混合精度算力约312 TFLOPS理论计算时间约0.45ms。但梯度同步需要的通信量大概在14GB7B参数2字节梯度如果是8卡通信每卡通信量约12.25GB7B2字节*7/8。即便通信带宽达到400Gbps也需要245ms——对比计算时间的0.45ms通信时间差了三个数量级。分布式训练中8卡不如4卡理想的本质原因就在于此计算在等通信GPU一直在空转等梯度。实践中应对方法包括梯度压缩如TopK梯度稀疏化、梯度累积、流水线并行等但这些操作都需要架构层面的理解才能真正用对。比如混合精度训练里的梯度AllReduce通常是FP32的如果你能把梯度压缩成FP16通信量直接减半。这个优化做下来8卡训练的扩展效率能明显改善。4.4 一个真实的8卡训练性能评估案例去年我们评估一批A100服务器跑一个经典的ResNet-50图像分类模型时单卡吞吐约1100 images/s理论上8卡应该到8500-8800 images/s。实测做数据并行8卡只跑了5000左右扩展效率62%这是一个典型的需要从架构层面排查的例子。用nvidia-smi topo -m看了卡间拓扑发现8卡中卡0和卡1之间走的是PCIe而不是NVLink因为服务器主板上PCIe通道数不够。又把数据加载线程数从4调高到16用NVIDIA Data Loading库做了数据管道预取吞吐提升到6200。再把梯度同步从NCCL的环状算法改成树的聚合最后提到7500左右。虽然没能完全拉满但也收获了20%以上的性能提升。注意多卡训练性能不是简单堆卡通信拓扑和数据管道的影响可能比代码本身还大。遇到多卡加速比不理想时先查拓扑和数据管道再考虑模型算法。5. 软件栈全景从驱动、CUDA到框架你的程序是怎么层层到达GPU执行器的5.1 驱动、运行时、CUDA库到底谁是谁很多做AI Infra的工程师每天跟nvidia-smi见面但分不清驱动、CUDA Toolkit、cuDNN这几个层次之间的关系。如果把这个软件栈分一下层会清楚很多GPU驱动Kernel Driver最底层负责操作系统与GPU硬件的通信包括显存管理、中断处理、上下文切换。装机时要先装驱动推荐用NVIDIA官方驱动安装包或系统包管理器装的驱动版本。CUDA驱动API与运行时API驱动API更底层直接管理GPU设备和上下文运行时API封装得更易用比如cudaMalloc开发中通常直接用运行时API。CUDA核心库cuBLASBLAS线性代数、cuDNN深度神经网络、cuSPARSE稀疏矩阵、NCCL多卡集合通信等。PyTorch底层很多操作直接调用这些库。框架层PyTorch、TensorFlow、MindSpore等。框架把Layer、Optimizer高级概念翻译成CUDA库调用和Kernel启动。这个层次关系最直接的价值在于排查问题。比如跑PyTorch时报CUDA out of memory有经验的工程师会先看进程实际占用了多少显存同时用nvidia-smi观察Memroy-Usage。如果一个进程占用的显存远大于PyTorch报告的数字可能是产生了很多CUDA context没有释放如果是碎片化问题可以调PYTORCH_CUDA_ALLOC_CONFexpandable_segments:True。5.2 从创建CUDA Context到Kernel Launch的全链路如果你没有深入理解软件栈遇到进程占用了GPU但看不见任何运算的现象会很懵。我简单梳理一下一次Kernel启动的完整链路调用cudaSetDevice()指定GPU运行时为该设备创建一个CUDA Context包含地址空间和资源句柄调用cudaMalloc分配显存这里设计到CUDA缓存分配器调用cudaMemcpy把数据拷入显存启动Kernel时编译器把CUDA代码编译成PTX中间表示再转成SASS机器码交给驱动加载到GPU上GPU的GigaThread引擎负责把任务分发到具体SM。链路中很多影响性能的地方不在代码层面而在驱动和库的配置层面。比如NCCL环境变量NCCL_P2P_LEVEL控制是否允许GPU直接通信Peer-to-Peer默认情况P2P是开启的。但是有一些云环境因为虚拟化原因P2P不可用强制开启会导致传输崩溃改为NCCL_P2P_LEVELLOC才能稳定。5.3 读懂nvidia-smi的输出不是只看利用率百分比nvidia-smi是最常用的GPU观测工具但很多人的解读方式过于粗暴看到Utilization 100%就认为满载了看到40%就认为没有利用好。实际情况要复杂得多GPU-Util: 表示某段时间内SM上至少有一个Warp处于活跃状态的占比不是平均计算负载。一个Kernel如果极度访存密集SM大部分时间在等数据Utilization一样不高。Volatile GPU-Util: 采样周期内的瞬时值变化范围很大要结合时间维度看比如用nvidia-smi dmon看统计曲线。Memory Usage: 当前显存占用但显存占用高不代表性能好——很可能只是无效缓存。Ecc Mode / Power Usage / Temp: 温度和功耗可以从另一个角度反映负载程度。如果功率墙被触发频率会下降算力就会流失。更深入的工具是nvidia-smi dmon和nvidia-smi dmon -c可以按帧显示每个GPU的SM利用率、显存读写带宽、温度功耗。NVIDIA Nsight Compute可以下钻到Kernel级别显示一个Kernel内部是访存密集还是计算密集是所有AI Infra工程师的必备工具。5.4 常见驱动问题的排查经验system进程占用GPU高背后在发生什么最近热搜词里有一个很有意思的问题——system进程占用GPU高。Windows环境下特别容易出现这个现象表现是任务管理器里GPU显示被System进程占用很高但是实际没有跑任何AI或图形任务。这个问题有一个常见根因GPU硬件调度和驱动版本不匹配导致的异常中断积压。Windows的GPU调度程序WDDM模型负责把显存页面和DMA请求转发给驱动如果驱动在某个操作上卡住System进程就会被拖住。遇到这种情况优先建议升级或更换驱动版本并检查是否有显卡驱动强制开启硬件加速GPU计划Hardware-accelerated GPU scheduling导致与不稳定驱动冲突。Linux环境下如果system或kernel worker占CPU高可以看dmesg里是否有NVRM驱动报错。6. GPU也是要切的从资源切片、MIG到任务调度的实战逻辑6.1 显存和算力的隔离为什么一张卡不能简单切给多人公司里买了一张80GB的A100只有半个小组要用另一个组也有短任务要跑能不能两张卡一起用理论上可以但硬切显存可比切内存麻烦得多。GPU的算力调度由硬件完成不像CPU那样有标准的抢占式时间片策略而且不同进程之间如果不做GPU上下文隔离可能互相污染内存。有一类轻量级方案是进程级别隔离——多个进程通过CUDA MPSMulti-Process Service共享同一个GPU。MPS能把不同进程的Kernel合并进同一上下文减少上下文切换开销同时可以限制每个进程的最大算力占比。但MPS的隔离性较弱一个进程的非法访问可能拖垮整个GPU。真正意义上的硬隔离方案是NVIDIA A100起引入的MIGMulti-Instance GPU。MIG把GPU切成多个硬件实例每个实例有独立的SM、显存带宽和L2缓存切片实例之间物理隔离——一个实例崩溃不影响其他实例。MIG本质上解决了大卡不好分的问题适合多团队共享一张A100/H100的场景。6.2 时间切片、优先级与GPU抢占调度算力共享的另一个维度是把GPU的时间片分给不同任务。默认情况下CUDA上下文按优先级调度高优先级的流可以抢占低优先级的Kernel。Kubernetes生态中常配合NVIDIA Device Plugin使用支持配置nvidia.com/gpu.memory和nvidia.com/gpu.cuda.cores等虚拟资源但底层本质还是用时间切片或CGroup等方式做软隔离。谷歌的time-slicing调度置多个进程轮流运行在同一个物理GPU上适合短任务但对长任务来说吞吐会显著下降。在实践中我建议短任务或调试任务用时间切片长训练任务最好申请独占卡或MIG实例。一个典型案例是有一次我们把一个在线推理服务和一个批训练任务放在同一张卡上训练任务把SM占满推理请求的延迟直接飙到了几秒最后不得不通过MIG把GPU切成两个实例才解决。此后我们定了一条规则任何生产在线服务不许和训练任务共享同一块物理GPU。6.3 显存不够训练怎么办架构层解决手段不止换卡很多时候我们没办法说换卡就换卡尤其是预算有限或GPU紧缺的情况下。架构层面有几个真实可用的显存优化思路BF16/FP16混合精度模型权重的显存占用直接减半。如果GPU支持BF16A100和更新架构都支持尽量用BF16而不是FP16因为BF16的动态范围更大训练稳定性明显更好。梯度检查点Gradient Checkpointing不保存全量中间激活值反向传播时重新计算。典型能节省50%-70%的激活显存代价是大约30%的额外计算开销。特别适合层数极深的大模型。ZeRO / FSDP把优化器状态、梯度、参数切片分到多卡上。ZeRO-2至少能切分梯度和优化器状态3D并行超大规模训练用这套方案。KV Cache优化推理侧大模型推理时KV Cache是显存大头。PagedAttention这类技术把KV Cache分页管理利用率显著提高。vLLM框架就用这个思路。GPU租用和免费GPU训练模型也是热词如果自己没有GPU可以先把模型适配成BF16并加上梯度检查点这样最小租用显存32GB的实例就能微调7B模型如果想靠免费额度跑实验记得把数据管道设成异步加载很多免费实例的CPU很弱数据加载反而是明显瓶颈。6.4 多租户集群如何优雅地共享GPU资源当GPU被多个团队共享时需要一个资源抽象层来完成分卡、分片、分时的事。目前主流方案如下层次技术方案隔离粒度典型场景单卡分片MIGSM/L2/带宽隔离强隔离一张A100切给多个小任务单卡共享MPS 时间切片弱隔离软调度多个短时长推理任务集群调度Kubernetes Device Plugin整卡分配大部分生产训练集群集群超卖Volcano/自定义调度器 时间切片整卡或分片抢占式训练/开发调试多卡聚合Ray NCCL / Horovod跨卡任务调度分布式训练作业这个表格不是让大家照着搭建而是提供选型地图。如果你的团队主要跑短小时级调试任务在共享卡上用时间切片就够如果是多团队长期稳定训练最好水平扩展整卡数量配合MIG分给短任务。还是那句话规则不是越细越好而是要让基础设施简单、可预测。7. 动手前必看GPU选型、启动前检查与一次踩坑实录7.1 先想清楚你的工作负载是计算密集还是访存密集这个判断决定了你选显卡的第一优先级。如果是训练GPT类大模型Tensor Core算力和显存带宽最重要如果你是做大模型推理部署显存带宽和显存容量反而是第一考虑因素如果你跑的是小模型批量试验可能消费级显卡就能满足不必上去买数据中心卡。具体到算力需求可以做一个粗略估算我以训练7B参数模型为例算一次账参数14GBBF16梯度14GBBF16优化器状态按AdamW用FP32主权重FP32动量FP32方差计算总共42GB激活值占比取决于序列长度和批次大小合计稳超70GB再加上代码和框架开销80GB的A100是起步配置。如果你是想在本地微调可用的最小方案可以把优化器换成Adafactor或8-bit优化器显存需求降低到约40GB级别加上梯度检查和数据并行两张24GB显卡也能跑。7.2 跑分布式训练之前我建议你先做这5步检查实际排查经验告诉我80%的分布式训练初期问题在启动就能规避用nvidia-smi topo -m看清卡间拓扑。如果有卡之间走的是PCIe而非NVLink先把它们排除出单机8卡训练组或改用更小的并行规模。用nvidia-smi dmon -c 10跑10秒确认每张卡的Utilization和带宽行为正常。如果任何一张卡Utilization长期为0很可能是环境或驱动问题。用小模型全跑一遍通信benchmark用all_reduce_perf测NCCL的实际带宽确认是否接近硬件理论值。如果差距巨大检查网络协议和NUMA绑定。确认NCCL_P2P_LEVEL和NCCL_SOCKET_IFNAME等环境变量适配当前环境。云上虚拟化环境尤其容易在这块踩坑。做数据管道的预取别让CPU加载数据成为瓶颈。推荐方案是先用tf.data或PyTorch的DataLoader设num_workers8以上并设prefetch_factor同时观察GPU空闲时间。7.3 一次踩坑实录FP32训练导致显存带宽耗尽分享一个具体案例。有次我们在一台A100上跑一个Transformer模型模型不大约1.5B参数单卡能放得下。理论上单卡算力足够但实测每秒处理不到300 token。nvidia-smi显示的Utilization只有45%但显存读写带宽经常撞到90%以上。用Nsight Compute抓了一个Kernel的Profile发现绝大多数时间花在访存等待上。查模型代码后发现代码里有一个比较隐蔽的错误在Embedding层把索引转换成了FP32 Tensor导致后续所有算子在计算时都使用FP32而非FP16矩阵操作全跑在普通CUDA核心上Tensor Core完全没被利用。换成BF16之后同样模型的训练速度从300 tokens/s飙升到1100 tokens/s。这个案例充分说明Tensor Core与数据精度的架构级影响模型代码里一个数据类型的细节在架构层面就是能让性能翻好几倍的变量。这也是AI Infra工程师和算法工程师共同需要掌握的知识点。7.4 要不要上手CUDA编程入门者最有效的学习路径如果是做AI Infra是否需要直接上手写CUDA Kernel我的建议是先会用工具做Profile再会读CUDA代码然后写几个简单的Kernel练手最后回归到框架优化。这个路径比一开始就啃CUDA手册高效得多。第一步学的是nvidia-smi、nsys、nsight-compute这组工具能定位出某个算子瓶颈在计算还是访存。第二步学的是cudaMalloc、cudaMemcpy、cudaLaunchKernel这些API的行为能读懂框架运行时在干什么。第三步自己写一个SGEMM矩阵乘的Kernel慢慢加入共享内存、double buffering、向量化这个过程能深刻理解延迟隐藏和存储层次。第四步回到PyTorch层面用torch.compile或CUDA Graphs做性能优化这时候你对GPU架构的理解就能直接产生实际收益。有一件事要提醒不要和市面上那些只讲解原理的教程硬磕到底。选择一个具体的算子比如LayerNorm或Softmax在Nsight里反复去看它怎么跑然后尝试优化。一次完整的算子级性能调优胜过你读十篇架构文章。8. 最后分享一点我的个人体会我从零开始理解GPU到能独当一面排查训练集群问题大约花了大半年。回头来看最有效的做法不是先背参数而是带着问题去学为什么这个训练任务的GPU利用率低为什么多卡加速比不对为什么不同的卡跑同一个模型差距这么大每解决一个问题对架构的理解就深一层。AI Infra这个领域有个特点它需要的是硬件、软件、算法三者交汇处的知识。GPU架构看起来是硬件知识但它决定了显存怎么管理、任务怎么调度、性能怎么优化最后体现为你在AI平台上的每一个决策。本文里很多内容是在实际项目里反复确认过的经验也有一些是业界公认的最佳实践但不同硬件、不同驱动、不同框架组合还会有不少差异性。所以我的建议是读完之后先拿你手头的一张GPU打开Nsight选择一个正在跑步的模型一步步去看它的Kernel和硬件计数器。纸上得来终觉浅这句话在GPU架构这门学问上尤为真实。
返回列表