想象你走进一家开在繁华商圈的大型连锁餐厅。总部的设计图纸上,每一层楼都被规划成几十个一模一样的小后厨:同样的灶台数量、同样的橱柜、同样的排风扇,配置完全一致。为什么要这样设计?因为与其费尽心思造一个能同时炒一百道菜的巨型灶台,不如复制一大堆普通厨房让它们同时开工——分工协作,总吞吐量反而更高。GPU芯片的设计者正是这么想的:芯片里没有“一个超级处理器”,而是集成了一群结构相同、各自独立运转的流多处理器(Streaming Multiprocessor,简称SM)。 每个后厨里还有更精妙的安排:几位全能厨师什么菜都能炒,一台自动包饺子机专攻饺子,各位厨师的围裙口袋里别着常用的小工具,正中央的大理石备菜台供全组共用,冷藏仓库则在地下室。主厨把订单按32桌一组合并成团,哪组食材先送到就先做哪组,某组还在等电梯运菜时,主厨早已转身去炒另一组菜——绝不让灶台闲着。这套“分组下单、轮换开火”的机制,正是GPU能同时服务上万桌“客人”(线程)的秘密。 这篇文章会带你逛遍SM这个“神奇后厨”:认识灶台(CUDA核心)、包饺子机(张量核心)、口袋与备菜台(寄存器与共享内存),看懂主厨的排班表(Warp调度),再聊聊如何让后厨火力全开(性能优化)。
理解SM架构是GPU编程和深度学习性能调优的“内功心法”:无论是手写CUDA Kernel、编写Triton算子,还是为训练集群挑选显卡,都需要知道算力从哪里来、瓶颈在哪里。
可视化演示
展示GPU SM内部的CUDA Core、Tensor Core协同与Warp调度机制
本节配有交互动效(进入视口后自动加载,约 3KB)。
SM内部计算资源布局 每个SM包含64个CUDA Core和4个Tensor Core 寄存器文件与共享内存构成片上存储层次 SM = CUDA Cores + Tensor Cores + 存储 SM是GPU并行计算的基本执行单元
SM基础架构
想象一家开在繁华商圈的大型连锁餐厅。总部的设计图纸上,每一层楼都被规划成几十个一模一样的小后厨:同样的灶台数量、同样的橱柜、同样的排风扇,配置完全一致。为什么不像单户餐厅那样只做一个气派的大厨房?因为与其费尽心思造一个能同时炒一百道菜的巨型灶台,不如复制一大堆普通厨房让它们同时开工——分工协作,总吞吐量反而更高、也更可靠。
GPU芯片的设计者正是这么想的。GPU内部并没有“一个超级处理器”,而是集成了几十乃至上百个结构相同、相互独立的计算单元块,每个单元块都能独立取指令、独立做调度、独立执行计算。这些单元块就是流多处理器SM。打开一张Ampere架构的芯片白皮书,你会看到占据芯片绝大部分面积的不是某一个庞然大物,而是一排排整齐的SM方块,它们通过片上的互联网络与L2缓存、显存控制器相连。
一个典型的SM内部包含四类关键部件:第一,CUDA核心阵列——几十到上百个能执行整数、浮点运算的通用计算单元;第二,张量核心——专门加速矩阵乘法的硬件单元;第三,存储体系——寄存器文件、共享内存以及各级缓存;第四,控制逻辑——warp调度器和指令分发单元。从Fermi到Ampere再到Hopper,SM内部的布局不断调整,但“多模块协同、各司其职”的设计思想始终没变。GPU的并行度,本质上是SM数量的线性放大:
就像餐厅的繁荣靠的不只是单个厨师的厨艺,更是后厨的复制与排布,GPU的算力也深深扎根于SM的架构设计。理解了这个“后厨单元”,接下来我们就推门进去,看看里面的灶台和师傅们。
- SM数量一块GPU芯片上流多处理器的个数,直接决定并行规模。A100拥有108个SM,是总核心数的“乘数”。
- 每SM的CUDA核心数单个SM内标量运算单元的个数,A100每个SM内嵌128个。
- 相乘求总把两者乘起来,得到整块GPU的CUDA核心总数,A100即为 13824。
计算核心单元
每个后厨里都配有两种“人力”:全能厨师什么菜都能炒,从切丝到爆炒样样在行;自动包饺子机则只干一件事——包饺子,但速度是厨师的几十倍。SM里恰好也有这样两类核心,它们各司其职,配合起来威力惊人。
第一类是我们熟知的CUDA核心,它的本质是一个通用的标量运算单元,负责执行整数和浮点数的加减乘除、逻辑判断、比较以及融合乘加(FMA)等指令。所谓FMA,就是“乘法和加法一步完成”:计算 a = b × c + d 时,普通流程先乘后加两步,FMA用一个单元一口气算完。CUDA核心的优势在于“全能”——任何指令都能处理,哪怕遇到分支跳转也能灵活应对,因此适合控制流复杂、计算形态不规则的代码。
第二类是张量核心(Tensor Core),它是NVIDIA从Volta架构起为深度学习量身定制的“包饺子机”。一个张量核心每个时钟周期就能完成一个4×4×4矩阵的乘加运算,相当于4096次乘加。为什么专门造这种单元?因为神经网络里最耗时的操作——卷积、全连接、注意力——归根结底都是巨型矩阵乘法。把矩阵乘扔给张量核心,把元素级的辅助运算留给CUDA核心,这座后厨的产出效率就能成倍提升:
训练大模型时,绝大部分矩阵乘法都会被调度到“包饺子机”上完成,全能厨师则负责激活函数、归一化、数据搬移等杂活。两类核心的无间配合,才是SM能撑起千亿参数训练的关键。
- SM数与每SM张量核心数刻画张量核心的硬件规模,A100每个SM有4个张量核心,全卡共432个。
- 频率核心的工作时钟频率,单位是Hz,决定每秒钟能执行多少个周期。
- 单核每周期乘加数一个张量核心每个周期能完成的乘加操作对数,Ampere为1024次。
- 乘2换算FLOPs一次乘加包含一次乘法和一次加法,按浮点运算次数计为2次。
- 除以10¹²得TFLOPS把每秒万亿次浮点运算作为单位,即最终宣传的峰值算力。
存储层次
厨师的工作效率不仅取决于手艺,更取决于“取料”的速度。最贴身的是围裙口袋里随时能摸到的盐瓶,取用只需一眨眼;稍远一点是后厨正中央的大理石备菜台,大家手边的半成品都码在上面;最远的则是地下室的冷藏仓库,容量大得惊人,但跑一个来回要好几分钟。SM的存储设计,完全复刻了这套“贴着算、就近放、远处存”的哲学。
最贴身的是寄存器文件(Register File)。它紧贴着CUDA核心,每个线程都有自己私有的几个寄存器,访问延迟只有约1个时钟周期,是所有存储中速度最快的。但寄存器是稀缺资源——A100每个SM的寄存器文件只有256KB,平均下来每个线程大约只能分到几十个寄存器。接下来是共享内存(Shared Memory),它同样位于SM内部,但属于同一个线程块的所有线程共用,大家可以通过它交换数据、协作计算,访问延迟约20~30个周期。共享内存需要程序员在代码里显式声明和搬运数据,相当于“备菜台要自己摆盘”。最外围则是全局内存(显存),容量可达数十GB,但延迟高达400~800个时钟周期——去仓库取一趟菜的时间,够灶台炒几十道菜了。SM内共享内存的带宽估算公式如下:
在CUDA优化中,数据放对位置比什么都重要。把反复用到的数据从仓库搬上备菜台(共享内存),把最热的数据塞进口袋(寄存器),让访问全局内存的次数降到最低——这套“就近取料”的思路,正是FlashAttention等经典算子的核心设计逻辑。
- 时钟频率SM的工作频率,决定共享内存每秒钟能完成多少次读写事务。
- 数据位宽每个时钟周期能从共享内存读出(或写入)的字节数,A100每个SM为128字节。
- SM数量每个SM的共享内存独立并行工作,总带宽随SM数线性叠加。
Warp调度机制
主厨不会一盘菜一盘菜地炒,而是把订单按32桌合成一组,整组一起下锅。哪一组食材先到就先做哪一组;某组还在等配菜,主厨就转身去炒别的组。后厨里最忌讳的事是“人等灶”——订单可以等,灶台绝不能闲着。GPU里的调度,玩的就是同一套把戏。
Warp是GPU硬件的最小调度单位:32个线程被捆绑为一组,始终执行同一条指令,这就是SIMT(单指令多线程)模型。当你在CUDA里启动一个包含1024个线程的线程块时,硬件会把它自动切成32个warp,逐个派发执行。每个SM内部有4个独立的warp调度器,每个调度器每个时钟周期可以从“就绪”的warp中挑选一个,向计算核心发射指令。这里的关键在于延迟隐藏:全局内存访问动辄几百个周期的延迟,但这期间调度器并不等待,而是立刻切换到另一个不依赖该数据的warp继续执行——“人歇灶不歇”。SM能同时容纳的warp数量有限(如A100为64个),实际驻留的warp数与上限的比值,就是GPU圈子里常说的占用率:
只要排队等菜的人足够多,主厨就永远有活干;同理,只要驻留的warp足够多,计算单元就不会饿肚子。这也是为什么CUDA优化中,经常用“提高占用率”来对抗访存延迟——不是为了多算,而是为了“总有活可干”。
- 活跃Warp数当前已驻留在SM上、能够被调度器挑选执行的warp数量。
- 最大Warp数硬件规定的上限,由线程容量、寄存器用量、共享内存占用三者共同约束。
- 乘以100%把比值转换成百分数,便于对比不同配置下的调度潜力。
任务组织模型
餐厅总部接到一个两千人的婚宴订单,当然不会把活儿全压给某一个后厨。销售部会把宴席拆成每桌8~10人的小桌,每张桌子的菜单装进一个信封,再按后厨的忙闲程度分发给各楼层的厨房。CUDA把任务派发给SM的方式,和这套流程如出一辙。
程序员在CUDA中启动内核时,会定义一个“网格”(Grid),网格是“线程块”(Block)的集合;每个线程块包含若干线程,比如128、256或512个。硬件拿到这个网格后,会把一个个线程块按顺序分配给各个SM——每个线程块一旦被分配到某个SM,就会“定居”下来,直到所有线程执行完毕才会腾出位置。线程块内部,线程之间可以通过共享内存交换数据,并借助 __syncthreads() 设置“集合点”:块内所有线程必须都到达这个点,才能继续往下走,就像一桌客人必须全部到齐才能开席。而线程块之间完全独立,互不感知。每个SM能同时驻留多少个线程块,由硬件容量和块大小共同决定:
把订单切成合适大小的“桌”,才能让每个后厨都恰好满负荷,又不至于让一桌客人跨两个厨房。实际工程里,block大小选128还是512,直接影响warp的划分、共享内存的占用和最终的负载均衡——这正是每一位CUDA开发者反复调参的核心原因。
- SM线程容量SM能同时容纳的最大线程总数,A100为2048个线程。
- 线程块大小每个block包含的线程数量,由程序员在启动内核时设定。
- 向下取整线程块不能跨越SM,多出来的碎片容量无法被利用,因此向下取整。
深度学习优化
想让后厨的产出翻倍,聪明的老板不会只给厨师发更多调料,而是重新设计流程:包饺子不用手工改走机器,配菜提前切好码上备菜台,采购一趟就把整周的食材拉回来。深度学习在GPU上的性能优化,本质上就是这三板斧。
第一板斧:把矩阵乘法交给张量核心,并用“分块”(Tiling)策略让数据在共享内存里反复复用。以经典的FlashAttention为例,它不再把巨大的注意力矩阵整体算完再写回显存,而是把Q、K、V切成一块块小片,轮流搬进共享内存,在SM内部完成分块计算后再写回——访存量从O(N²)级别降到了O(N)级别,训练速度成倍提升。第二板斧:让访存“合并”。一个warp里的32个线程如果读写连续的地址,硬件可以一次事务把所有数据取完;如果各自访问分散的地址,就要拆成几十次事务,效率暴跌。第三板斧:用“算术强度”判断该优化什么。这个指标衡量“每读一个字节的数据,能做多少次浮点运算”:
TensorRT、FlashAttention、cuBLAS这些名字之所以如雷贯耳,正是因为它们在帮你的“后厨”重新设计流程:让包饺子机满转、备菜台高效周转、仓库访问次数压到最低。理解了SM的内部结构,你就能看懂这些优化工具的每一招,甚至自己写出下一个FlashAttention。
- 浮点运算量完成算法所需的全部浮点运算次数,一次乘加计2次FLOPs。
- 访存量从全局内存读取和写回的字节总数,单位是Bytes。
- 求比值两者相除得到算术强度,单位为 FLOPs/Byte,用于判断kernel属于访存受限型还是计算受限型。
AI 应用场景
大模型训练中的矩阵乘法加速
阶段:模型训练(算力核心)
解决的问题:大模型训练中线性层、注意力层反复执行巨型矩阵乘法,若用通用单元手工计算,速度慢几个数量级,训练周期从天级拖到月级。AI如何解决:将矩阵乘运算调度到SM内的张量核心,配合混合精度(FP16/BF16),让“包饺子机”全速运转。
具体案例:训练LLaMA-2 70B时,前向和反向传播中超过90%的浮点运算集中在矩阵乘法。通过cuBLAS库将GEMM算子映射到张量核心,并按SM的共享内存容量做分块(如128×128的tile),在A100上实现约312 TFLOPS的FP16峰值算力,训练吞吐相比纯CUDA核心方案提升5~10倍。
FlashAttention的共享内存分块
阶段:算子开发与推理优化
解决的问题:标准注意力机制需要计算N×N的注意力矩阵并写回显存,当序列长度达到数千甚至数万时,访存开销呈平方级爆炸,GPU利用率极低。AI如何解决:把Q/K/V切成小块,利用SM的共享内存做“备菜台”,在SM内完成分块注意力计算,避免大矩阵落盘。
具体案例:在长文本推理场景中,BLOOM-176B的生成速度受限于注意力层的访存瓶颈。引入FlashAttention-2后,每个线程块把查询块加载到共享内存,循环读取键值块并在寄存器中累积结果,显存访问量从O(N²)降至O(N),单卡推理吞吐提升约2~3倍。
推理卡选型与SM利用率评估
阶段:模型部署与硬件选型
解决的问题:部署团队常遇到“买的卡不便宜,但GPU利用率只有20%”的困惑——显存占用高不代表算力被用起来。AI如何解决:通过NVIDIA Nsight Compute等工具从SM视角做性能剖析,查看sm busy率、warp占用率、warp stall原因,定位瓶颈是访存受限还是计算受限。
具体案例:某团队部署Stable Diffusion时发现图像生成慢,profile结果显示SM的活跃warp数远低于上限,根因是每个线程块占用的共享内存过大。把block size从512调到256、精简共享内存申请后,占用率从35%升到70%,端到端推理延迟下降45%。
Python 演示
"""SM集群调度模拟:演示Warp轮转调度与延迟隐藏"""from collections import deque MAX_WARPS = 6 # SM 最多容纳的活跃 Warp 数 class Warp: """一个 Warp 代表 32 个线程构成的指令流""" def __init__(self, wid, inst_count=6): self.wid = wid self.inst_left = inst_count self.ready_at = 0 # 本次访存返回的全局周期 def issue(self, cycle): """执行一条指令,返回 True 表示整个 Warp 已完成""" self.inst_left -= 1 if self.inst_left % 2 == 1: # 每 2 条指令模拟一次访存等待 self.ready_at = cycle + 3 return self.inst_left <= 0 class SM: """流多处理器:单个调度器对活跃 Warp 做轮转调度""" def __init__(self): self.active = deque() self.done = [] def add(self, warp): if len(self.active) < MAX_WARPS: self.active.append(warp) return True return False def tick(self, cycle): if not self.active: return warp = self.active.popleft() if warp.ready_at > cycle: self.active.append(warp) # 访存未返回,先切到别的 Warp return if warp.issue(cycle): self.done.append(warp.wid) else: self.active.append(warp) def main(): sm = SM() total = 12 index = 0 cycle = 0 while index < total or sm.active: while len(sm.active) < MAX_WARPS and index < total: sm.add(Warp(index)) index += 1 sm.tick(cycle) if len(sm.done) == total: break cycle += 1 print(f"共调度 {total} 个 Warp(每个含 32 线程),耗时 {cycle} 周期") print("完成顺序:", sm.done) if __name__ == "__main__": main()
self.active = deque()活跃Warp队列,对应SM调度器手里的“待排班表”。if warp.ready_at > cycle:判断访存是否已返回,未返回就放回队尾,先执行别的Warp。while len(sm.active) < MAX_WARPS and index < total:队列有空位就补充新Warp,对应线程块分批入驻SM的过程。sm.tick(cycle)每个时钟周期调度器执行一次派发,对应硬件每周期发射一条指令。print(f"共调度 {total} 个 Warp...")输出总耗时,直观感受延迟隐藏对吞吐的贡献。
常见误区
误区感觉GPU就是无数个小处理器粗暴地拼在一起,SM只是机构造上的筐,随便堆多少个核心性能就翻多少倍。
正解SM远不止“装核心的筐”。它自带独立的warp调度器、寄存器文件、共享内存和指令分发单元,是一个能够自主取指、调度、执行的完整微处理器。GPU的可扩展性确实来自SM数量的增加,但每个SM的内部组织方式——比如调度带宽、存储带宽、张量核心配比——直接影响单SM的效率和深度学习负载的表现。A100与RTX 3090的SM数量相近时,训练性能也会因张量核心和存储配置差异而天差地别。
为何常见:厂商宣传页上“几千个核心”的数字太显眼,让人误以为并行度只等于核心数量,忽略了“核心是如何被组织起来的”。
误区以为共享内存和L1缓存是同一个东西,数据放进去后硬件会自动帮你加载和置换,程序员什么都不用管。
正解共享内存是程序员显式管理的暂存区(Scratchpad),数据必须由代码用 __shared__ 声明、手动从全局内存搬入搬出;L1缓存则是硬件自动管理的缓存,程序员无法直接控制其内容。两者在物理上都位于SM内、延迟接近,但一个“手动挡”一个“自动挡”。写CUDA Kernel时用错这两者,轻则性能平平,重则数据出错。
为何常见:两者都在SM内部、访问延迟都很低,教科书上又常常把它们画在同一个框图里,容易让人误以为是同一件东西。
误区以为写CUDA时要手动把线程凑成32个一组,像管理进程池一样自己去分组、去调度。
正解Warp是硬件层面的调度单位,程序员在代码里只关心线程和线程块。硬件会把线程块自动切成若干32线程的warp,一切分组、派发、切换都由SM内的调度器完成。程序员能做的只是“间接影响”——比如通过控制线程块大小、避免分支发散,来影响warp的划分和执行效率。
为何常见:CUDA API里存在 __shfl_sync、__syncthreads 这类与warp相关的函数,让人误以为warp是需要手动管理的一等公民。
误区觉得寄存器是“私有的高速缓存”,每个线程分到的寄存器越多,计算就越快,所以写代码时拼命声明变量。
正解寄存器文件总量是固定的(如A100每SM 256KB),每个线程多占一个寄存器,SM能同时驻留的线程就越少,warp数下降,占用率随之降低,延迟隐藏能力反而变差。专业做法是用 __launch_bounds__ 或 -maxrregcount 限制寄存器用量,在“每线程寄存器数”和“驻留warp数”之间寻找最佳平衡点。
为何常见:沿用了CPU优化的直觉——缓存越大越好、私有空间越多越好,没有意识到GPU的并行吞吐比单线程的“舒适度”更值钱。
面试题
Q1. 流多处理器(SM)在GPU中的角色是?
- A.负责GPU与CPU通信的接口
- B.独立完成计算的核心单元← 正确答案
- C.管理显存分配的硬件单元
- D.连接多个GPU的高速总线
答案:B。SM是GPU内部可独立取指、调度和执行线程的计算核心单元,GPU的并行能力正是由众多SM叠加而成。A是NVLink/PCIe的工作,C通常由显存控制器承担,D则是互联总线的职责,都与SM的定义无关。
Q2. 一个Warp包含多少个线程?
- A.8个线程组成一个Warp
- B.16个线程组成一个Warp
- C.32个线程组成一个Warp← 正确答案
- D.64个线程组成一个Warp
答案:C。Warp是GPU硬件调度的最小单位,固定包含32个线程,它们在同一时刻执行同一条指令(SIMT模型)。8、16、64都不是硬件规定的warp宽度,选其他数字说明对SIMT的基本模型记忆有误。
Q3. 张量核心的主要职责是?
- A.加速矩阵乘加运算← 正确答案
- B.执行分支跳转指令
- C.管理共享内存读写
- D.负责线程块负载均衡
答案:A。张量核心是为深度学习定制的矩阵乘加单元,单周期即可完成4×4×4矩阵乘加。分支跳转是CUDA核心的职责,共享内存读写和负载均衡分别由存储控制器、调度器负责,都不属于张量核心的专长。
Q4. SM中寄存器文件的主要特征是?
- A.所有线程共享的大容量缓存
- B.每个线程私有且访问最快← 正确答案
- C.容量与全局内存相当
- D.读写数据需要经过总线仲裁
答案:B。寄存器文件紧贴计算核心,每个线程拥有自己私有的寄存器,访问延迟仅1个周期,是SM中速度最快的存储。它并非线程共享,容量也只有几百KB,远小于显存,更无需总线仲裁——这些特征都指向“私有且最快”。
Q5. 共享内存与全局内存的核心区别是?
- A.共享内存容量远大于全局内存
- B.全局内存位于SM内部
- C.共享内存位于SM内且延迟低← 正确答案
- D.共享内存仅能存储常量数据
答案:C。共享内存在SM内部,延迟20~30周期,由块内线程共用;全局内存在芯片外部(显存),延迟400~800周期。共享内存容量远小于全局内存,而全局内存在芯片外而非SM内,共享内存也并非只能存常量,因此只能选C。
Q6. 延迟隐藏的核心机制是?
- A.提高GPU主频加速所有指令
- B.warp等待数据时切换其他warp← 正确答案
- C.将数据全部预加载到显存
- D.减少每个warp的指令数量
答案:B。延迟隐藏是指当一个warp因访存发生等待时,调度器立刻换入其他就绪warp执行,让计算单元始终忙碌。提高主频治标不治本,预加载到显存并不能消除访问延迟,减少指令数量与隐藏延迟更是无关。
Q7. 某SM最大支持2048个线程,每个线程块设置256个线程,该SM最多驻留几个线程块?
- A.最多驻留2个线程块
- B.最多驻留4个线程块
- C.最多驻留8个线程块← 正确答案
- D.最多驻留16个线程块
答案:C。线程块不能跨越SM,驻留块数 = SM线程容量 ÷ 线程块大小 = 2048 ÷ 256 = 8,因此最多驻留8个线程块。2和4低估了容量,16则超过了SM的线程上限,除非线程块大小是128才行。
Q8. SM内部的组成包括哪些核心部件?
- A.CUDA核心← 正确答案
- B.张量核心← 正确答案
- C.光刻机单元
- D.寄存器文件← 正确答案
答案:ABD。SM由CUDA核心阵列、张量核心、寄存器文件、共享内存以及warp调度器等部件构成。光刻机属于芯片制造设备,与SM的内部组成毫无关系,属于干扰项。
Q9. 影响SM占用率(Occupancy)的因素有哪些?
- A.每个线程的寄存器用量← 正确答案
- B.线程块占用的共享内存← 正确答案
- C.GPU的显存总容量
- D.线程块的大小设置← 正确答案
答案:ABD。占用率取决于SM中能驻留多少个warp,而驻留数量受每线程寄存器用量、每块共享内存占用以及线程块大小共同约束。显存总容量属于全局内存层面,与SM内部能塞下多少warp无关。
Q10. 关于Warp调度,以下说法正确的有?
- A.一个warp内线程执行同一条指令← 正确答案
- B.分支发散会降低warp执行效率← 正确答案
- C.warp数量越多占用率一定越高
- D.调度器可切换warp来隐藏延迟← 正确答案
答案:ABD。SIMT模型下一个warp的32个线程共享同一条指令;如果线程走向不同分支,warp会串行执行各分支,效率明显下降,所以应尽量避免分支发散;调度器在warp访存等待时切换其他warp正是延迟隐藏。C的错误在于warp数量受硬件上限约束,超过上限后增加warp不再提升占用率。