
1. 从一块GPU的“车间”说起每次提到AI芯片和GPU大多数人第一反应是“算力强”“跑深度学习专用”。但你要是去问一个真正做算子优化或者搞CUDA性能调优的工程师他最关心的并不是那张显卡的显存有多大、频率有多高而是里面的执行单元Execution UnitEU到底怎么干活。甚至可以说整个GPU架构设计的本质就是围绕执行单元的排布、调度和数据供给做文章。这篇文章我想把GPU执行单元这件事讲透。我会从硬件微观结构讲起再串到AI训练推理中那些绕不开的实际问题——比如为什么深度学习离不开GPU、显存容量到底该按训练还是推理来估算、为什么你装好了驱动但框架还是跑在CPU上、DirectML和CUDA的差距到底在哪。全程用干活的视角不堆砌参数尽量说人话。适合谁看刚入门想搞懂GPU内部原理的学生做AI算子开发、推理部署的工程师还有那些被GPU驱动、显存、调度问题折腾过的运维同学和炼丹玩家。2. GPU执行单元的硬件设计藏着整个架构的灵魂2.1 执行单元到底是什么执行单元简单理解就是GPU里面真正干算术活的计算部件。NVIDIA叫它CUDA CoreAMD叫它Stream ProcessorIntel叫它EUExecution Unit虽然名字各不相同但本质上都是执行整数和浮点运算的最小硬件单元。用生活场景打比方CPU像一个全科医生什么病都能看但全医院就几十个这样的医生GPU像一家大型体检中心里面有几千个护士每个人都只抽血或者只量血压但架不住人多几千个一起上体检效率自然碾压。执行单元就是无数个埋头做算术的“护士”它们只负责运算不关心指令从哪里来、数据要存到哪这些交给上游的控制单元和缓存来处理。具体看Intel的EU设计每个EU内部通常包含两个负责FP32/INT32运算的ALU算术逻辑单元、一个负责FP64的ALU消费级芯片一般被阉割了、还有用于数据收发和消息传递的Send单元。每个EU每个时钟周期最多可以执行4个线程的指令也就是一个4路复用的指令发射能力。换算下来Intel Gen11核显里一个Subslice子片有8个EU一轮调度能塞进去32个线程的指令。2.2 SIMTGPU最核心的并行哲学理解执行单元之前必须先搞明白一个概念SIMT单指令多线程。这是GPU和CPU在计算模型上最本质的分歧。CPU是MIMD——多指令多数据每个核心跑自己的指令流各干各的。GPU是SIMT——一条指令喂给几十个线程这些线程在同一时刻对各自的数据执行同样的操作。你可以把它想象成学校食堂打饭3000个学生排队食堂阿姨喊一声“刷卡”所有人同时刷卡而不是阿姨挨个说“你刷卡、你也刷卡”。在NVIDIA的架构里32个线程组成一个Warp这是GPU调度和执行的粒度。一个Warp里的线程必须执行同一条指令如果遇到if-else分支GPU会先把执行if的线程放行再回头执行else的部分这个现象叫线程发散thread divergence是CUDA性能优化最需要避开的坑。AMD的wavefront是64个线程一个组Intel那边则是8个EU组成一个Subslice调度粒度和SIMT语义略有差异但核心思想一致。为什么这种设计适合AI因为神经网络里的张量运算天然是数据并行的一个卷积核要跟成千上万个像素点做相同的乘加操作一条指令广播给几百个执行单元同时算效率比CPU核心一个个轮流算高几个数量级。2.3 一个执行单元的工作流水线别以为执行单元就是个简单的加法器它要跑的流程挺长。一条指令从取指、解码、调度到真正在执行单元里算完中间要经过好几道关卡。以现代GPU中的典型流程来看指令从指令缓存I-Cache取出交给前端解码器解码。经过调度器Scheduler按照依赖关系把就绪的指令发射给执行单元所在的执行端口。执行单元从寄存器堆Register File读入操作数完成算术运算FMA、INT加乘等再把结果写回寄存器。如果要读显存还得经过加载/存储单元LSU发出访存请求数据从L2缓存或者显存搬回来这个过程往往比计算本身慢很多所以现代GPU用大量线程和并发来掩盖访存延迟。每个执行单元最怕什么怕饥饿也就是没有数据可算、或者算完的指令被堵住了。为了解决这个难题GPU的设计思路是把计算和访存彻底解耦用更高度的线程并行来“藏延迟”。前面那个Warp在等显存数据返回时调度器立刻切换去执行另一个Warp的指令保证执行单元永远不会闲着。顺便说一句很多初学者容易把“执行单元数量”等同于“性能”这是个误区。执行单元多只能说明理论算力上限高实际能不能打满取决于调度效率、数据带宽、缓存命中率和算子本身的并行度。这就像公司招了一万个员工但管理混乱、材料供给不上照样干不过五百人的精干团队。3. 执行单元怎么撑起整个AI计算生态3.1 为什么AI芯片必须用GPU来做训练和推理基础概念讲完我们把执行单元放回AI芯片这个大背景里看。训练神经网络最核心的运算是矩阵乘法和卷积。以全连接层为例输出 输入 × 权重矩阵。假设输入维度是512输出维度是512一次前向传播就是512 × 512 × 512 约等于1.34亿次乘加运算。训练时反向传播还要算两遍梯度更新再算一遍一个批次几十上百个样本一轮迭代就是几十亿次浮点运算。CPU的执行单元数量和单核性能决定了它根本扛不住这个量级。一颗主流服务器CPU比如AMD EPYC 965464核128线程FP32峰值算力大约在3~6 TFLOPS左右而一张RTX 4090的FP32算力差不多80 TFLOPSA100能达到接近160 TFLOPSFP32H100的FP16 Tensor Core算力更是高达990 TFLOPS。差距是几十倍起跳。这个差距的来源就是GPU执行单元的数量密度——一块GPU芯片上能塞下上万个执行单元CPU一颗芯片上最多就是几百个核心。为什么GPU能塞这么多因为它把芯片面积几乎都让给了执行单元。CPU芯片里有大块面积是控制逻辑分支预测、乱序执行、缓存一致性协议GPU把这些全砍了缓存做得小而精省下来的空间全拿去堆执行单元。对于“一个操作重复千万次”的AI计算来说这种偏科设计恰恰是最优解。3.2 从算子到硬件矩阵乘法究竟怎么跑在执行单元上我知道很多人看CUDA代码时总有个疑惑明明写的是一行像普通数学公式的代码比如 C A * B它怎么就跑到执行单元上去算了中间发生了什么用PyTorch举个例子当你调用torch.matmul(A, B)时流程大致是这样的PyTorch根据张量的数据精度FP32还是FP16、维度信息选择对应的CUDA内核函数kernel。这个kernel会被编译成PTXNVIDIA的中间指令集再经过驱动编译成机器码。GPU的线程块Block被分发到不同的SMStreaming Multiprocessor流式多处理器上。每个Block里的线程按Warp为单位由SM的调度器分发到执行单元上执行。执行单元执行的是FMA指令——一条指令同时完成乘法和加法就是这种指令撑起了所有神经网络计算。关于FMA指令多说一句它一条指令干两件事不仅节省指令带宽还能减少一次舍入误差。现代GPU的“TOPS算力”或者“TFLOPS算力”计算方式就是用执行单元数量 × 时钟频率 × 2一次乘加算两次操作。比如一个SM有128个FP32执行单元频率1.5GHz那么一个SM的FP32峰值算力就是 128 × 1.5G × 2 384 GFLOPS。A100有108个SM所以FP32总算力约等于 108 × 384G 41.5 TFLOPS——但官方标称是19.5 TFLOPS有些差异是因为实际架构中每个SM的调度宽度和数据供给限制了每个周期发射的指令数所以理论峰值和实际规格之间存在差距这里不着重展开了你记住算力不是简单乘一下就行的。3.3 Tensor Core专为AI而生的特殊执行单元2017年NVIDIA在Volta架构上推出了Tensor Core这可能是AI芯片历史上最重要的一次设计转向。传统的执行单元一个周期只能做一次FP16乘加而Tensor Core可以在一个周期内完成一个4×4矩阵的乘加也就是64次FMA操作算力直接翻了16倍对比FP16执行单元。Tensor Core的工作方式跟普通的执行单元完全不同。它不再按照Warp里的线程去对应单个元素而是整个Warp把数据加载到Tensor Core的寄存器阵列中由Tensor Core以硬件加速的方式一次性完成整个矩阵块的乘加。程序员层面只需要调用wmmawarp-level matrix multiply-accumulate或者mma指令即可。到了Hopper和Ada Lovelace架构Tensor Core甚至开始支持FP8精度。推理场景下FP8的H100算力能到约4000 TOPS比FP16翻了一倍多。这也是为什么做AI芯片的公司都知道光有普通执行单元是不够的必须有针对矩阵运算定制的硬件加速引擎才能扛住大模型的训练和推理负载。现在市面上几乎每一款AI芯片——无论是NVIDIA的Ampere/Hopper、AMD的CDNA系列还是各类国产AI芯片比如一些基于RISC-V或自研指令集的加速卡设计思路殊途同归大量普通执行单元负责通用并行计算专门的矩阵加速单元负责AI核心算子。你可以认为AI芯片的架构就是“通用执行单元打底 专用加速单元提效”。3.4 显存容量到底该按训练还是推理来算热搜词里有一条我特别想回答“GPU显存容量是测算推理还是训练用的”很多刚入行的人被这个问题困扰其实两者都有关系但参考的指标完全不同。先说训练一个Transformer模型训练时的显存占用大头来自四块模型参数、优化器状态Adam要存一阶动量和二阶动量、梯度、激活值前向传播中间结果反向传播要用。拿70B参数模型用Adam优化器在FP16精度下训练来算参数占 70B × 2 140GB梯度占140GB优化器状态占420GB用FP32存储动量激活值另加。这就是为什么训练大规模模型需要8卡、甚至几十卡A100/H100集群单卡显存根本装不下必须做张量并行、流水线并行和ZeRO优化来把状态分布到多张卡上。再说推理显存占用主要是模型权重和KV Cache。推理不需要优化器状态也不需要梯度激活值只要算一层丢一层所以显存占用量大致是“权重 KV Cache 中间临时缓冲区”。以LLaMA-2 70B为例FP16权重占140GB如果要一次性装进显存至少需要8张H100 80GB或者用GPTQ/AWQ量化到INT4权重降到约35GB这样单卡80GB也能装下。所以结论很直接训练看的是算力峰值和显存容量上限通常越大越好推理看的是单次请求的显存要求模型大小 并发数 × 每请求KV Cache你可以根据模型权重精度和并发度来精确推导。那些动不动标榜“单卡跑70B模型”的方案几乎都是做了量化或者用CPU Offload实则是拿速度换显存“能跑”和“跑得动”完全是两回事。4. 从执行单元到实际生产线驱动、框架、推理引擎的联动4.1 为什么驱动那么重要执行单元再好没有驱动它也不会自己工作。驱动的作用是把上层软件CUDA、PyTorch等发出的指令翻译成GPU硬件能理解的机器码同时负责显存管理、任务调度、中断处理等一揽子事务。这个角色跟操作系统内核类似但因为GPU硬件架构高度并行、指令集复杂GPU驱动的工程量远高于普通外设驱动。热搜词里有一条“GPU failed with error code 0x887a0005”这是DirectX运行时的DXGI_ERROR_DEVICE_REMOVED错误意思是GPU设备从系统中被移除或者驱动崩溃。实际排查中最常见的原因是驱动超频导致GPU不稳定、电源供电不足、显存过热、或者驱动版本和应用程序不兼容。我见过有人连续打游戏弹出这个错误最后发现是显卡供电线没插紧换个线就好了。如果是训练跑着跑着报这个问题优先检查电源和温度再考虑回退驱动版本。安装驱动也是门学问。很多人不知道装驱动前最好用DDUDisplay Driver Uninstaller彻底卸载干净旧驱动尤其是N卡换A卡、或者升级大版本驱动时残留文件很容易导致新驱动异常。CentOS 7.9这类老系统上装NVIDIA驱动还要处理内核头文件kernel-devel和GCC版本兼容问题装完驱动再装CUDA Toolkit顺序错了多半会踩坑。碰到devel包跟内核版本不匹配导致NVIDIA驱动编译失败的几乎是每个Linux新手必经的坎。4.2 为什么框架没走GPUCUDA、PyTorch和驱动的三角关系“我已经装好GPU驱动了为什么PyTorch还是只用CPU算”这个问题可能是我见过次数最多的求助帖。背后的逻辑其实不复杂PyTorch要用GPU需要三层全都匹配才行——GPU驱动支持CUDA、PyTorch安装的是带CUDA的版本、GPU的算力足够新。具体来说PyTorch官方把版本分成了CPU版本和CUDA版本。如果你用pip install torch默认安装装到的通常是CPU版本——因为官方PyPI包默认不带CUDA依赖体积太大。必须用特定命令安装CUDA版本比如pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121装完之后用torch.cuda.is_available()检查如果返回True才说明CUDA环境正常。如果在服务器上还要确认cuDNN版本匹配。另外Windows上经常遇到torch.cuda.is_available()返回True但实际运行特别慢的情况——很可能是Win64版的nvcc和运行时库不一致建议统一通过Anaconda安装cudatoolkit而不是单独装独立版本。这里要补充一点驱动版本决定CUDA Runtime能用的最高版本但反过来并不是说你必须安装跟驱动完全一致的CUDA版本。驱动是向下兼容的CUDA 12.x的运行时可以在支持CUDA 12.2以上的驱动上运行。很多人被“CUDA版本必须和驱动一样”的说法误导其实只要驱动支持的CUDA版本高于或等于运行时需求就能跑。4.3 大模型推理引擎为什么默认吃满GPU以llama.cpp和Ollama为例再说说推理侧llama.cpp和Ollama是现在跑本地大模型最火的工具。llama.cpp最早是纯CPU推理方案但它早就支持了GPU加速而且是把计算卸载到GPU上而不是整个模型都驻留显存。它采用的策略是分层卸载layer offload把一部分Transformer层放在GPU上计算剩下的留在CPU内存里。这种策略使得显存小的用户也能跑大模型只是层数卸载得越多速度越快。Ollama也内置了自己的CUDA和Metal后端Apple Silicon的GPU加速。Windows上如果用Ollama发现没走GPU最常见的原因是环境变量没设置好。Ollama通过OLLAMA_GPU_LAYERS控制GPU算多少层如果设成0那就是纯CPU运行。另一个坑是Ollama需要识别GPU型号和显存如果显卡太老不被支持它会自动回退到CPU模式。需要注意的是directml和CUDA哪个好这类问题本质上是不同生态的选择。DirectML是微软推的跨厂商加速API在Windows上可以跑AMD、Intel、NVIDIA三家显卡但性能一般比不过厂商原生的CUDA或者ROCm因为抽象层多了就会消耗调度开销。我实测过同一台电脑上跑同一个Stable Diffusion模型CUDA比DirectML快20%到30%。如果你用的是N卡跑深度学习无脑选CUDA生态就对了。4.4 GPU实例化到底“减少”了什么热搜词里有个非常专业的问题“GPU实例化到底减少的是什么具体原理是什么”这里他们问的其实是CUDA图形/计算引擎的实例化机制也就是常说的CUDA Graph Instantiation。先解释背景GPU执行任务有一个固定开销——kernel launch内核启动。一次kernel启动CPU要把指令、参数、内存地址等内容通过PCIe/驱动层提交给GPU这个过程一次大约5到10微秒。如果你的神经网络里有几千个小kernel算子单纯launch开销就可能占掉总时间的10%以上尤其在小算子密集的网络比如带大量LayerNorm、残差连接的小模型里特别明显。CUDA Graph的思路是把一串kernel的依赖关系图一次性捕获capture下来实例化instantiate成一个图对象之后每次执行只需要提交一次图GPU内部的调度器按照依赖关系自动依次执行。这样就把原来的“N次launch开销”摊成了“1次launch 内部硬件调度”小算子场景能快30%以上。这里“实例化”减少的就是CPU-GPU之间的通信和启动开销同时允许GPU驱动在实例化时做快速的布局优化。Torch的torch.cuda.graphs模块就是干这个用的大模型推理框架比如TensorRT-LLM、vLLM也在用类似技术来优化多batch场景下的调度比如减少GPU内核启动的开销。用大白话说以前是项目经理一项项给工人派单累死累活还拖延现在是提前把整个工序表排好开工时一次性发下去工人们按照流水线自己衔接效率自然上去了。5. CPU和GPU的分工边界以及调度器的那些事5.1 CPU是怎么把任务交给GPU的GPU执行单元再多整个系统还是CPU主导。一般流程是CPU上的驱动把任务描述成Command Buffer写入显存里一个叫“环形缓冲”Ring Buffer的结构然后通过一个专门的硬件机制比如NVIDIA的doorbell寄存器通知GPU“有新任务了”。GPU的前端Front End从Ring Buffer里读取指令按优先级调度到各个计算引擎Compute、Copy、Copy Engine等去执行。由于CPU和GPU是异步工作的CPU提交完任务后不会傻等GPU算完它会继续运行别的事情最终通过查询事件Event或回调Callback来确认GPU是否完成。这就是GPU异步编程的模型也是很多性能问题爆发的温床——你提交了一个kernel但没做同步后面当你尝试读取GPU计算结果时数据可能还没算完于是驱动会阻塞等待这期间GPU和CPU都在空转。有一个经典思路是CPU预取和GPU计算的流水线重叠CPU把下一批次的数据先拷到显存用独立的内存拷贝队列GPU同时算当前批次两者不冲突训练吞吐能明显提升。很多框架PyTorch DataLoader带pin_memory、TensorFlow的PrefetchDataset就是在帮你做这件事。5.2 GPU调度器的设计哲学GPU调度器和CPU调度器本质上的区别是CPU调度器主要任务是在多核之间均衡负载、减少上下文切换开销GPU调度器则更像一个“任务分发员”它的核心目标是让所有执行单元永不空闲保持busy。为了达到这个目标GPU调度器内建了一大堆硬件队列分别是计算队列、拷贝队列、同步队列等还支持创建多个计算流Stream来并发执行互不依赖的任务。AI训练中最常见的调度指标是GPU利用率和占用率occupancy。占用率的意思是一个SM上活跃的Warp数量除以它最多能承载的Warp数量。占用率不高执行单元就可能饿肚子表现为GPU利用率低。调高占用率的方法一般有三种限制每个线程的寄存器和共享内存用量腾出更多Warp配额、调整Block大小、优化数据加载方式让访存和计算重叠。不过占用率也不是越高越好。占用率拉满会导致每个Warp可用的寄存器减少某些算子可能被强制溢出到本地内存局部数组被放到显存反而适得其反。所以做算子调优时一般会测试两三个不同的占用率档位取实际吞吐最高的那个而不是盲目追求100%占用。5.3 GPU虚拟内存和实例划分的实用意义“GPU虚拟内存”这个热词对于跑大模型训练的人特别重要。GPU有自己的虚拟地址空间页表和CPU虚拟内存类似但由GPU的MMU管理。CUDA的unified memory统一内存就是利用了虚拟内存机制让CPU和GPU共享一个逻辑地址空间数据按需在内存和显存之间迁移开发者的生活方便了但性能可能因此出现不可预测的抖动。GPU实例划分MIG是NVIDIA推出的一块GPU切多块用的技术。A100和H100支持把一块物理GPU切成最多7个独立的GPU实例每个实例拥有独立的显存、执行单元、L2缓存切片和带宽。这个技术最大的价值是让多个任务共享一张卡时依然有稳定的性能隔离——比如一个2C的实例和另一个4C的实例互不干扰一个任务跑崩了不会拖垮另一个。生产环境里特别适合“让一个实例专跑推理服务另一个实例跑开发测试”资源隔离和内存在多租户场景下更干净。AMD那边对应的技术叫MxGPUIntel是SRIOV思路类似。不过要提个醒MIG不是所有卡都有消费级显卡没这功能开启MIG后单卡算力会被物理切分不太适合必须用整卡跑超大模型矩阵乘法的场景。6. 常见问题速查与排查记录6.1 为什么我的程序似乎没有调用GPU这个问题在网络里的出现频次极高把自己做测试时总结的排查顺序分享一下第一层检查驱动。执行nvidia-smi针对N卡如果驱动正常能看到GPU型号、显存使用情况和驱动版本。如果看不到装驱动。第二层检查CUDA运行时。执行nvcc --version确认CUDA版本和驱动支持的CUDA版本匹配驱动版本对应的最高CUDA版本可以查NVIDIA官方兼容表。第三层检查框架。PyTorch运行torch.cuda.is_available()如果输出False说明你装的是CPU版PyTorch按4.2节的方式重装CUDA版。第四层检查代码。确认模型和输入张量都在GPU上用model.to(cuda)和tensor.to(cuda)。第五层检查环境变量。比如有没有设置CUDA_VISIBLE_DEVICES才发现没设导致代码看不到卡。有时候这些全查完依然有问题那就是驱动崩溃或者硬件问题了。Windows下看一下Windows事件查看器里的NVLDDMKM报错Linux下查dmesg | grep -i nvidia。硬件问题优先查电源和温度GPU长时间满载温度超过90度时降频和驱动重置会频繁发生。6.2 显存不足CUDA out of memory到底怎么解如果你碰到PyTorch报CUDA out of memory别急着加卡先看是不是炸在激活值上了。用torch.cuda.memory_summary()看一下显存分配明细如果发现的是大块激活值占用优先考虑减少batch size如果中间结果是PyTorch的自动混合精度AMP没开导致FP32激活翻倍那就开AMP如果是训练多个实验时显存一直没释放检查代码里是不是每次循环创建了新的计算图。还有一个新手很容易踩的坑验证集推理时也开了梯度计算没加torch.no_grad()导致推理时同样保留了中间激活值。这个本可以被省掉的占用有时候能占整个显存的30%以上。6.3 GPU利用率高但速度还是很慢GPU利用率高只能说明执行单元忙但可能是忙在低效的地方。最常见的问题有两个一个是数据加载瓶颈GPU在30秒里算完一批但CPU加载下一批数据花了180秒。减少batch size只会更糟正确的做法是用DataLoader的num_workers并行加载数据、开prefetch_factor并用pin_memory减少CPU到GPU拷贝时间。另一个是kernel launch开销如果你的网络由大量小算子组成GPU利用率看起来高但每个算子只占用GPU一小会儿实际吞吐低。可以用PyTorch的torch.profiler看每个kernel的耗时和空档针对小算子做算子融合比如把LayerNorm 残差 Dropout并成一个Fused Kernel或者直接用torch.compile把计算图整体编译优化能省掉大量调度间隙。我遇到过一个真实案例一个Bert微调任务数据加载没优化时GPU利用率只有40%训练一个epoch要20分钟优化DataLoader并发后利用率到85%时间缩短到8分钟。而同样的时间只上了CUDA Graph优化又压缩了12%。这种几行代码级别的改动比换一张更贵的显卡划算得多。7. 最后分享一点实际体会我在调试算子性能和排查GPU问题这条路上踩过的坑绕起来能写一本书。但总结下来最核心的一个教训是所有GPU问题从驱动崩溃到显存溢出最终都要回到“执行单元到底在干什么”这个源头去思考。当你盯着GPU利用率、显存占用、kernel耗时这些指标时其实你是在揣摩那几万个执行单元的忙碌程度和等待状态。理解了这个底层很多看似玄学的问题都会有清晰的排查路径。做算子优化时我习惯先看Sass/PTX指令分布确认跑到执行单元上的到底是不是高效的FMA指令而不是被编译器生成了多余的load/store遇到性能不符合预期先查访存模式是不是导致L2缓存命中率过低碰到驱动诡异报错先检查电源和散热。这些方法论远比背一堆硬件参数有用。最后送大家一个小技巧Windows下想确认推理框架到底有没有用GPU别只看任务管理器那个百分比那个数值经常是假的。最靠谱的方式还是用nvidia-smi看进程列表能看到对应的进程占着显存和GPU利用率才算真的用上了GPU。祝大家的执行单元们永远满载。