一、cache
Cache(高速缓存)的重要性在于缓解处理器与主存之间的速度差距。它利用程序访问的时间局部性和空间局部性,将可能重复使用的指令和数据保存在更快的存储中,减少处理器等待数据的时间,提高程序运行效率。在 AI 等需要大量数据运算的任务中,合理利用 Cache、提高数据复用率,可以减少数据搬运开销,让处理器的计算能力得到更充分的发挥。
1. 什么是cache
用来实现对RAM数据的缓存,让CPU更快地访问它们
存储层次上,位于CPU和RAM之间
容量比RAM小但交换速度快
cache对CPU的性能影响很大,CPU 运算速度快,而访问主存较慢,
Cache 利用程序访问的局部性,减少 CPU 等待主存的时间
2.cache命中和缺失
Cpu要访问的数据在cache中有缓存,称为命中hit,没有就是缺失miss
1.不要期望编译器对你做任何优化,程序的数据布局和访问方式很重要
2.Cache系统代表性的包括三种级别:
|--------|----------------------------|---------------------------|
| 层级 | 常见位置 | 相对特点 |
| L1 | CPU 芯片内部,通常每个核心独有 | 容量最小、延迟最低,常分为指令缓存和数据缓存 |
| L2 | CPU 芯片内部,通常每个核心独有,也可能共享 | 容量和延迟通常介于 L1、L3 之间 |
| L3 | 通常也在 CPU 芯片内部,常由多个核心共享 | 通常是三级 Cache 中容量最大、延迟最高的一层 |
当运算器需要从存储器中提取数据时,它首先在最高级的cache中寻找然后在次高级的cache中寻找。如果在cache中找到,则称为命中hit;反之,则称为不命中miss。
可简化理解为先查 L1,未命中则查 L2,再未命中则查 L3;如果各级 Cache 都未命中,则访问主存。命中或未命中是针对某一级 Cache 而言的。
cache misses的种类:
(1)cold misses:第一次访问某个内存块时,由于它尚未进入 Cache 而产生的缺失。不可避免。若K级cache空,则必发生cache miss,空的cache称为cold cache,这种cache misses称为compulsory misses或者cold misses。当cache已被warmed up则一般不会再发生cold misses。
(2)conflict misses:多个内存块映射到同一个 Cache 组,因该组容纳不下而相互替换,导致后续访问未命中 。例如:一个组只能容纳 2 个块,程序却反复访问映射到该组的 3 个块,就可能不断发生替换,即使 Cache 的其他组还有空位。这一点关系到对于程序员而言能否写出cache友好代码。
(3)Capacity misses:容量缺失。程序常会分阶段执行(例如循环:内层、外层),每个阶段会取cache blocks的固定几个块,这几个块所构成的集合称为working set**。**当working set超过cache大小时,当 Cache 总容量不足以保留需要重复使用的块,导致它们被替换、再次访问时未命中,就产生容量缺失capacity misses。
(1)从cache指令上做优化:简化调用关系,减少冗余代码(即不是必须存在的的代码),减小代码量,减少不必要的调用;
减少热点执行路径的代码体积、改善代码布局,通常有助于指令缓存。不过:
- 函数调用少,不一定意味着指令缓存表现好。
- 内联可以减少调用开销,却可能增大代码体积。
- 循环展开也可能提高执行效率,同时增加指令缓存压力。
因此应关注频繁执行的代码是否紧凑,而不只是程序总代码量。
(2)从数据cache上做优化:即减少cache miss的次数,方法有不少,http://blog.chinaunix.net/uid-7319742-id-2059720.html 这篇文章有介绍
常用方法包括:
- 连续访问:尽量按数组的内存排列顺序遍历,提高空间局部性。
- 及时复用:让相同数据的多次访问尽量靠近,提高时间局部性。
- 分块处理:把大任务拆成工作集能放入 Cache 的小块。
- 调整数据布局:把经常一起访问的数据放在一起,减少无用数据加载和映射冲突。
3.cpu访问cache的速度
介于内存和寄存器之间
4.L3 cache
L1 cache;L2 cache;L3 cache,读写延迟依次增加,实现成本逐渐降低。
Register->L1 cache->L2 cache->L3 cache->Memory->Mass Storage
是解决性能与价格矛盾所采取的折中设计


CPU接收到指令后,会最先向CPU中的一级缓存区寻找相关数据,一级缓存和CPU是同频运行的,容量较小,不可能每次都命中。CPU继续向二级缓存寻找,二级缓存中没有的话,继续转向L3缓存、主存和硬盘
程序运行时可以使用perf工具观察cache-miss的rate.
5.局部性原理
时间局部性: cpu 访问过的数据,近期还要访问;
空间局部性:被 C pu 访问过的数据附近的数据,近期还要被访问
* 如果刚刚访问过的数据缓存在 cache 中,下次访问的时候,可以直接从 cache 中取
6.cache line
是 Cache 存储和管理数据的基本单位。
RAM和高速缓存之间、高速缓存之间的数据移动不是以单个字节甚至 word 完成的
相反,移动数据的最小单位是缓存行(CPU 可以只读取几个字节,但 Cache 通常按整行填充。)
主存地址 对应的内存块
0~63 第 0 块
64~127 第 1 块
128~191 第 2 块
例如,CPU 要读取地址 100 的一个字节。如果 Cache 未命中,通常会把 地址 64~127 的整块数据加载到一条缓存行中。接着访问地址 101,就可能直接命中这条缓存行。这就是空间局部性发挥作用的方式。
目前主流CPU的Cache Line:64Byte
假设缓存行大小为 64 B,某一级 Cache 的数据容量为 512 B,则该 Cache 能容纳 8 条缓存行。
查看cache line大小
cat /sys/devices/system/cpu/cpu1/cache/index0/coherency_line_size
cache linie的影响:
for (int i = 0; i < N; i+=k)
arri *= 3;
步长 k 会影响一条缓存行中有多少数据被实际使用
一条缓存行:arr0 ~ arr15
k = 1: ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
k = 2: ● · ● · ● · ● · ● · ● · ● · ● ·
k = 4: ● · · · ● · · · ● · · · ● · · ·
k = 8: ● · · · · · · · ● · · · · · · ·
k =16: ● · · · · · · · · · · · · · · ·
●:被访问的元素 ·:未使用的元素
假设每条缓存行装 16 个 int,数组起始地址按缓存行对齐:
缓存行: 第0行 第1行 第2行 第3行
下标: 0~15 16~31 32~47 48~63
不同步长的访问情况是:
k=1 :访问 0、1、2......63 → 四条行都访问
k=8 :访问 0、8、16、24......56 → 四条行都访问
k=16:访问 0、16、32、48 → 四条行都访问
k=32:访问 0、32 → 只访问第0、2行
k=64:访问 0 → 只访问第0行
因此:
- 步长 1~16:虽然每行使用的元素越来越少,但没有跳过整行,需要加载的缓存行数基本相同,所以耗时可能差不多。
- 步长 32:"每两个缓存行接触一次"指的是访问一行、跳过一行,加载量约减半。
- 步长 64:"每四个缓存行接触一次"指的是访问一行、跳过三行,加载量又减半。
这里的"接触一条缓存行",就是访问它里面至少一个元素。在未缓存的情况下,即使只访问一个元素,通常也需要加载整条行。

注意当步长在1到16范围内,循环运行时间几乎不变。但从16开始,每次步长加倍,运行时间减半。
**由于16个整型数占用64字节(一个缓存行),for循环步长在1到16之间必定接触到相同数目的缓存行:**即数组中所有的缓存行。当步长为32,我们只有大约每两个缓存行接触一次,当步长为64,只有每四个接触一次。
7.cache写机制
Cache写机制分为write through和write back两种。
Write-through(直写模式)在数据更新时,同时写入缓存Cache和后端存储(下一级缓存或主存 RAM)。此模式的优点是操作简单;缺点是因为数据修改需要同时写入主存,数据写入速度较慢。
Write-back(回写模式)在数据更新时只写入缓存Cache。只在数据被替换出缓存时,被修改的缓存数据才会被写到后端存储。此模式的优点是数据写入速度快,因为不需要写存储;缺点是一旦更新后的数据未被写入存储时出现系统掉电的情况,数据将无法找回。
直写:修改当前层,同时向下一级传递写入。
回写:先修改当前层并标脏,需要时再把脏行写回下一级。
8.cache一致性
Cache 一致性(Cache coherence)是指:当多个 CPU 核心缓存了同一个内存地址的数据时,需要协调这些副本,避免某个核心一直使用已经过时的数据。
例如,最初 x = 0:
核心 A 的 Cache:x = 0
核心 B 的 Cache:x = 0
如果核心 A 把 x 改为 1,核心 B 缓存的 0 就过时了。因此,硬件需要通过缓存一致性协议协调。
常见方式是写入前使其他副本失效:
- 核心 A 获得该缓存行的写权限,其他核心中的对应副本失效。
- 核心 A 修改数据,将缓存行标记为已修改。
- 核心 B 再次读取时,需要获取最新数据;数据可以由核心 A 的缓存提供,不一定先写回 RAM。
因此,缓存一致性不要求所有 Cache 和 RAM 在每一时刻都保存相同的值 ,而是保证对同一地址的读写遵守一致的规则。常见协议有 MESI。
一致性通常以缓存行 为单位管理。这会产生"伪共享":两个核心修改同一缓存行中的不同变量,虽然变量互不相关,仍可能触发反复的缓存行所有权转移,降低性能。
还要注意:缓存一致性不等于线程安全。它不会让 x++ 自动成为原子操作,多线程共享数据仍可能需要原子操作或锁。
9.cache替换策略
cache工作原理要求他尽量保存最新数据,当从主存向cache传送一个新块,而cache中可用位置已被占满时,就会产生cache替换的问题。
常用的替换算法有下面三种:
1)LFU(Least Frequently Used,最不经常使用)算法
将一段时间内被访问次数最少的那个块替换出去。每块设置一个计数器,从0开始计数,每访问一次,被访块的计数器就增1。当需要替换时,将计数值最小的块换出,同时将所有块的计数器都清零。这种算法将计数周期限定在对这些特定块两次替换之间的间隔时间内,不能严格反映近期访问情况,新调入的块很容易被替换出去。
2)LRU(Least Recently Used,近期最少使用)算法
是把CPU近期最少使用的块替换出去。这种替换方法需要随时记录Cache中各块的使用情况,以便确定哪个块是近期最少使用的块。每块也设置一个计数器,Cache每命中一次,命中块计数器清零,其他各块计数器增1。当需要替换时,将计数值最大的块换出。
LRU算法相对合理,但实现起来比较复杂,系统开销较大。这种算法保护了刚调入Cache的新数据块,具有较高的命中率。LRU算法不能肯定调出去的块近期不会再被使用,所以这种替换算法不能算作最合理、最优秀的算法。但是研究表明,采用这种算法可使Cache的命中率达到90%左右。
3) 随机替换
最简单的替换算法是随机替换。随机替换算法完全不管Cache的情况,简单地根据一个随机数选择一块替换出去。随机替换算法在硬件上容易实现,且速度也比前两种算法快。缺点则是降低了命中率和Cache工作效率。
10.cache的映射
主存与cache的地址映射方式有全相联方式、直接方式和组相联方式三种。
直接映射
将一个主存块存储到唯一的一个Cache行。
- 多对一的映射关系,但一个主存块只能拷贝到cache的一个特定行位置上去。
cache的行号i和主存的块号j有如下函数关系:i=j mod m(m为cache中的总行数)

优点:硬件简单,容易实现
缺点:命中率低, Cache的存储空间利用率低
- 全相联映射
可以将一个主存块存储到任意一个Cache行。
主存的一个块直接拷贝到cache中的任意一行上

优点:命中率较高,Cache的存储空间利用率高
缺点:线路复杂,成本高,速度低
3)组相联映射
可以将一个主存块存储到唯一的一个Cache组中任意一个行。
将cache分成u组,每组v行,主存块存放到哪个组是固定的,至于存到该组哪一行是灵活的,即有如下函数关系:cache总行数m=u×v 组号q=j mod u

组间采用直接映射,组内为全相联
硬件较简单,速度较快,命中率较高
二、超标量和超流水线
1.超标量架构(superscalar architecture)
经典RISC处理器的流水线
| 阶段 | 名称 | 核心动作 | 关键硬件 |
|---|---|---|---|
| IF | Fetch 取址 | PC 给出地址,I‑Cache取出指令,送入指令寄存器 IR | PC、I‑Cache、IR |
| ID | Decode 译码 | 指令译码,读寄存器堆获取源操作数 | 译码器、寄存器堆 |
| EX | Execute 执行 | ALU 完成运算、有效地址计算 | ALU |
| MEM | Memory 访存 | D‑Cache访问;非访存指令该级空跑 | D‑Cache |
| WB | Write Back 回填 | 结果写回目的寄存器;无目的寄存器则跳过写回 | 寄存器堆 |
(1)指令流水线技术:
将每条指令的处理过程分解为多步,并让不同指令的各步操作重叠,从而实现几条指令并行处理。程序中的指令仍是一条条顺序执行,但可以预先取若干条指令,并在当前指令尚未执行完时,提前启动后续指令的另一些操作步骤。这样显然可加速一段程序的运行过程。
(2)标量流水线技术
一般而言,CPU执行一条指令需要经过以下阶段:取指->译码->地址生成->取操作数->执行->写回,每个阶段都要消耗一个时钟周期,同时每个阶段的计算结果在周期结束以前都要发送到阶段之间的锁存器上,以供下一个阶段使用。
将一条指令(instruction)分成若干个周期处理,以达到多条指令在不同阶段重叠处理,从而提高cpu部件利用率的技术叫做标量流水技术。(Scalar)

**(3)超标量技术:**是指cpu内一般有多套执行硬件(具有多指令发射能力和多个执行单元),能在一个时钟周期内发射多条指令,有多条流水线,这些流水线能够并行处理,每时钟周期内可以完成一条以上的指令。
在单流水线结构中,指令虽然能够重叠执行,但仍然是顺序的,每个周期只能发射(issue)或退休(retire)一条指令。超级标量结构的cpu支持指令级并行,每个周期可以发射多条指令(2-4条居多)。可以顺序执行,也可以乱序执行。这样,可以使得cpu平均每周期完成的指令书(Instruction Per Clock) > 1, 从而提高cpu处理速度
(4)超流水线:**以增加流水线级数(超过5-6步)的方法来缩短机器周期,减少每一级需要完成的工作,相同的时间内超级流水线执行了更多的机器指令,提高CPU主频。**采用简单指令以加快执行速度是所有流水线的共同特点,但超级流水线配置了多个功能部件和指令译码电路,采用多条流水线并行处理,还有多个寄存器端口和总线,可以同时执行多个操作,因此比普通流水线执行的更快,在一个机器周期内可以流出多条指令。(通过细化、提高主频,使得在一个机器周期内完成一个甚至多个操作)
超标量侧重于将流水线"加宽",超流水线侧重于将流水线"加深";两种技术可以同时采用。

(硬件上复制多套 Integer、FPU 执行单元,Instruction Unit 可以同时分派多条指令给不同单元,实现多指令并行,也就是前面图的超标量 "加宽流水线"。)
指令间的相关性
(1)先写后读(Read After Write, RAW)

(2)先读后写(Write After Read, WAR)

(3)先写后写(Write After Write, WAW)

超标量处理器中,WAW、WAR和RAW这三种相关性都会阻碍指令的乱序执行。
超标量流水线
超标量处理器有两种方式可以执行指令,顺序执行(in-order)和乱序执行(out-of-order)。

顺序执行的超标量处理器中,指令的执行必须遵循程序中指定的顺序。

乱序执行,指令在流水线中不在遵循程序中指定的顺序来执行,一旦某条指令的操作数准备好了,就可以将其送到FU中执行。

为了解决乱序执行时解决WAW和WAR这两种相关性,需要对寄存器进行重命名(register renaming)。
2.超标量处理器的流水线阶段
超标量处理器中的各个阶段介绍如下:核心特点是:顺序分发、乱序执行、顺序提交。
(1)Fetch(取指令):这部分负责从I-Cache中取指令,I-Cache负责存储最近常用的指令;分支预测器用来决定下一条指令的PC值。
(2)Decode(解码):识别指令的类型
(3)Register Renaming(寄存器重命名):解决WAW和WAR这两种"伪相关性",需要使用寄存器重命名的方法,将指令集中定义的逻辑寄存器重命名为处理器内部使用的物理寄存器。物理寄存器的个数更多余逻辑寄存器,处理器可以调度更多可以并行执行的指令。将存在RAW的寄存器进行标记,后续通过旁路网络(bypassing network)解决存在的"真相关性"。
(4)Dispatch(分发):被重命名之后的指令会按照程序中的规定的顺序,写到发射队列(Issue Queue)、重排序缓存(ROB)和Store Buffer等部件中
(5)Issue(发射):经过流水线的分发(Dispatch)阶段之后,指令被写到了发射队列(Issue Queue)中,仲裁(select)电路回从这个部件中挑选出合适的指令送到FU中执行。
(6)Register File Read(读取寄存器):被仲裁电路选中的指令需要从物理寄存器堆(Physical Register File, PRF)中读取操作数
(7)Execute(执行):各种FU单元执行
(8)Write back(写回):将FU计算的结果写到物理寄存器(PRF)中,通过旁路网络将计算结果送到需要的地方。
(9)Commit(提交):这个阶段起主要作用的部件是重排序缓存(ROB),它将乱序执行的指令拉回到程序中规定的顺序。
3.问题
超标量处理器为什么不能简单地通过增加发射宽度,让性能成比例提高
| 限制因素 | 问题 | 常见处理方法及代价 |
|---|---|---|
| 数据依赖 | 后续指令必须等待前面指令的结果,可同时执行的指令有限 | 乱序执行寻找其他就绪指令;旁路转发减少等待,但无法消除 RAW 真依赖 |
| 资源竞争 | 多条指令同时需要有限的执行单元、端口或带宽 | 增加资源或调度等待;增加资源会提高面积和功耗 |
| 取指与分支限制 | 取指速度不足,或分支路径尚未确定,执行单元得不到足够指令 | 提高取指带宽、分支预测与推测执行;预测错误需要恢复 |
| 寄存器堆与旁路网络复杂 | 并行指令越多,操作数读写和结果转发需求越大 | 增加端口或采用分簇等设计,但可能增加延迟、功耗及控制复杂度 |
| 发射宽度与主频的权衡 | 更宽的处理器需要更复杂的依赖检查、唤醒、选择和转发电路 | 必须权衡 IPC、主频、面积和功耗,不能只追求更宽 |
参考:
Superscalar CPUs: Multiple, Parallel, Execution Units
Superscalar Challenges And Limitations - Consensus Academic Search Engine
三、GPU
1.GPU硬件
在整个GPU架构中存在着大量的流式多处理器( Streaming Multiprocessor,SM ),它占据着GPU的大部分空间。


①每个SM都有自己的寄存器文件、共享内存和缓存等资源;并且拥有很多core资源,可以同时执行多个线程,SM是GPU中的可独立计算单元
②划分SM的主要目的是提高GPU的并行计算能力和资源利用率;
划分SM后,GPU可以通过将计算任务分解成多个小部分的工作,**分配给不同的SM并行执行,**从而加快计算速度
划分SM还可以避免不同计算任务之间的资源竞争,提高GPU并行性能
③SM由多个CUDA core组成,每个SM根据GPU架构不同有不同数量的CUDA core'
虽然说SM中还配有register和shared memory等存储资源,但他们属于稀缺资源。
在这种情况下,由哪些线程资源来占有这些稀缺资源执行任务,就离不开Warp Scheduler调度器。
2.CUDA编程模型


3.CUDA编程模型和 GPU硬件 的关系
在CUDA编程模型中,计算任务是以thread和thread block和grid的形式进行组织的
①将计算任务分成多个可以并行的子块,交给多个thread block计算。在thread block内部,再将任务进一步划分成多块,由每个thread计算。
②cuda硬件也是采用了分层次的组织方式,被划分为多个SM,每个SM的内部又有多个CUDA core,CUDA thread 和 thread block最终是运行在了CUDA core和SM上面
GPU将计算任务分成多个可以并行的子块,交给多个thread block计算。在thread block内部,再将任务进一步划分成多块,由每个thread计算。

warp(线程束)是最基本的执行单元 ,是 GPU 硬件调度和执行线程的基本分组 。是一个 block 内部,一般由 32 个线程组成的执行分组。 GPU 会把 block 中的thread按顺序划分成多个 warp。每个thread有一个threadIdx,这些thread只能执行相同的指令。

一个 SM 同时驻留的线程数量有限,但可以分批执行更多线程。 例如,假如一个SM最大只能存储1024个线程的信息,但一个SM可以拥有超过1024个线程。超过容量的线程可等待资源释放后,以block为单位分批次进入。
这时在SM内存的warp线程的调度单元就是Warp Scheduler。Warp Scheduler 负责从该 SM 已驻留的 warp 中选择就绪的 warp,并发射指令。
尽管warp中的线程执行同一个kernel,但可能具有不同的行为,比如分支结构。
GPU 线程束(warp)的执行特性:
- 无分支时,32 线程同步执行,效率最高。
- 有分支时,线程束会拆分,不同分支串行执行,导致性能下降。
一个SM同时并发的warp是有限的,因为资源限制,SM要为每个线程块分配共享内存,而也要为每个线程束中的线程分配独立的寄存器,所以SM的配置会影响其所支持的线程块和warp并发数量。
软件:Grid → 包含多个 Block → 每个 Block 包含多个 Thread
↓ 调度到
硬件:GPU → 包含多个 SM
- CUDA 编程通过启动 kernel,在 GPU 上创建一个Grid。Grid 是一次 kernel 启动产生的所有 block 的集合,每个 Block 包含多个Thread。;由 GPU 的硬件调度机制把 block 分配到 SM。
- Warp 是线程的执行分组;由 SM 内的 Warp Scheduler 选择就绪的 warp,发射其指令。
- 普通执行模型下,一个 block 被分配到某个 SM 后,其所有 warp 都在该 SM 上执行直到完成,可以这样理解和记忆。
- 如果一个warp其操作数之一或者两者都还没有准备好,就会发生一种叫上下文切换的过程,将控制权转移到另一个线程束上
与CPU不同的是,在切换离开特定的线程束时,该线程束的所有数据仍然保留在寄存器文件中,以便在其操作数准备好时能够快速恢复执行
4.为什么gpu占用率不满?
一个SM中的共享资源等是有限的,共享资源是以block为单位分配的,导致一个SM中可执行的线程块数量是有限的。
5.为什么一个SM能够同时执行多个线程块?
每个SM能同时执行的warp数上限取决于硬件限制、kernel参数设置和线程与线程块资源使用所导致实际能够执行的数量限制。
- 驻留多少个 warp:受线程、block、寄存器和共享内存等容量限制。
- 每周期能发射多少条 warp 指令:受调度器、发射能力、执行资源及指令是否就绪等限制。
①当资源充足,大于线程和线程块使用的资源时,这时每个SM执行的warp数量受限于kernel设置的参数:
SM的同时执行的线程数量有限,如果每个线程的线程块数量太少,这会导致SM同时执行的线程数不够
一般一个线程块的线程数要达到128、256才能充分用满SM,这个参数可以进行调节从而找到一个最优值。
②其次,线程和线程块资源使用导致实际能够执行的数量限制。如果线程块的shared mem使用太多,比如一个线程块就用完了所有的shared mem的一半以上,这样一个SM最多只能执行1个线程块。
为了保证一个SM能同时执行多个线程块,显然每个线程块只能用每个SM总的shared mem的几分之一。
寄存器使用也是一样,寄存器使用合理时一个warp能够同时执行32个线程,同时一个子块的资源能满足同时驻留多个warp。而寄存器使用太多一个子块无法驻留多个warp,甚至极端情况一个warp的资源所有连32个线程都不够用。
例如,在安培(Ampere)架构的NVIDIA GPU中,每个SM可以同时执行最多64个线程块。
图灵(Turing)架构的NVIDIA GPU中,每个SM可以同时执行最多4个线程块。
帕斯卡(Pascal)架构的NVIDIA GPU中,每个SM可以同时执行最多2个线程块。
现在,让我们看一个例子,以了解线程块的资源分配如何影响SM的占用率。
RTX 3060 的计算能力为 8.6,每个 SM 最多同时驻留 16 个 block、48 个 warp,即 1536 个线程;每个 block 最多 1024 个线程。
如果在申请资源时使用32作为block的大小 ,并总共需要申请使用2048个线程,那么需要2048/32=64个这样的线程块。
cpp
myKernel<<<64, 32>>>(...); // <<<线程块数量, 每个线程块的线程数量>>>
RTX 3060每个 SM 最多驻留 16 个 block、48 个 warp**(1536 个线程)。**由于每个 block 只有一个 warp,首先达到 block 数量上限:
每个 SM 最多驻留:16 × 32 = 512 个线程
占用率:512 ÷ 1536 ≈ 33.3%
因此,若要这 64 个 block 全部同时驻留,至少需要 4 个 SM 的容量;也可以用更少的 SM 分批执行。
每个线程使用 64 个寄存器, RTX 3060 每个 SM 有 65536 个 32 位寄存器。仅考虑寄存器容量:
最多容纳:65536 ÷ 64 = 1024 个线程
占用率上限:1024 ÷ 1536 ≈ 66.7%
这是容量估算,实际还受 block 大小、寄存器分配粒度和共享内存等限制。NVIDIA Ampere 架构指南
如果两个条件同时成立------每 block 32 个线程、每线程 64 个寄存器------则先碰到 16 个 block 的上限,最终占用率上限仍为 33.3%。
这里的占用率是驻留 warp 数与最大驻留 warp 数的比值,不等于 GPU 性能利用率。
四、CUDA 执行与 GPU 资源管理机制
1.异步 launch
异步 launch:CPU 提交计算后继续工作
通常,CPU 启动 CUDA kernel 后,不必等待 GPU 执行完成就能继续:
kernel<<<grid, block>>>(d_data);
// CPU 可以继续执行自己的工作
doCpuWork();
// 到这里才等待 GPU 完成
cudaDeviceSynchronize();
所以,kernel 启动语句返回,表示工作已经提交 ,不表示计算结果已经准备好。
(99+ 封私信 / 11 条消息) cuda基础之异步启动 - 知乎
2.异步 DMA
异步 DMA让数据搬运与其他工作重叠
DMA 是直接内存访问。CPU 发起传输后,由硬件搬运数据,不需要 CPU 逐字节复制。
CUDA 中常通过以下接口提交异步传输:
cudaMemcpyAsync(d_data, h_data, bytes,
cudaMemcpyHostToDevice, stream);
为了可靠地实现 CPU 内存与 GPU 显存之间的异步传输和重叠,主机缓冲区应使用锁页内存(pinned memory),例如通过 cudaMallocHost()分配。实际能否与计算重叠,还取决于硬件能力和依赖关系。
注意:这和 kernel 内部的"显存到共享内存异步拷贝"是不同的使用场景。
3.多 stream
多 stream建立多条任务队列
Stream 可以理解为有顺序的 GPU 工作队列:
Stream A:上传数据 A → 计算 A → 下载结果 A
Stream B:上传数据 B → 计算 B → 下载结果 B
- 同一 stream 内:这些操作按顺序执行。
- 不同 stream 之间:没有额外依赖时,有机会重叠执行。
例如,可以在 GPU 计算 A 时上传 B,减少空闲。但 stream 不绑定某个 SM,也不会独占一组 CUDA core。资源不足时,不同 stream 的工作仍可能无法并行。
4.Event
Event记录进度、建立依赖、计时
Event 是一个插入 stream 的完成标记。它完成,意味着该 stream 中排在它前面的工作已经完成。
例如,B 必须等 A 的结果,但 CPU 不需要停下来等:
kernelA<<<grid, block, 0, streamA>>>(d_data);
cudaEventRecord(done, streamA);
cudaStreamWaitEvent(streamB, done, 0);
kernelB<<<grid, block, 0, streamB>>>(d_data);
这里是 stream B 等待事件 ,不是 CPU 等待。若要让 CPU 等待,可用 cudaEventSynchronize(done);事件也可用于 GPU 计时。以上四类机制见 CUDA 异步执行文档。
5. 进程隔离与调度
多个程序怎么共享 GPU
这不是单一 CUDA API,而是两个问题:
- 隔离:不同进程通常有各自的 CUDA context、地址空间和资源,不能随意访问对方的分配。
- 调度:多个进程都提交任务时,系统决定如何让它们使用 GPU。
普通多进程共享通常涉及时间片和上下文切换;内存地址空间隔离不等于计算资源独占,也不保证性能互不影响。
相关技术 : MPS(Multi-Process Service)允许多个进程的工作更有效地并发利用 GPU,减少上下文切换开销。它与多 stream 不同:多 stream 通常是在同一进程内安排工作,MPS 面向多进程共享。NVIDIA MPS 文档
6. MIG
MIG把支持的 GPU 划分为多个硬件实例
MIG 全称 Multi-Instance GPU。它把一张支持的 GPU 划分成多个实例,每个实例拥有专属的计算和显存等资源:
一张物理 GPU
├─ 实例 A → 分配给任务 A
├─ 实例 B → 分配给任务 B
└─ 实例 C → 分配给任务 C
它适合多个用户或服务共享 GPU、又希望减少相互干扰的场景。这是硬件资源划分,不是简单建立几个 stream。 NVIDIA MIG 介绍
| 概念 | 一句话记忆 |
|---|---|
| 异步 launch | CPU 提交计算后不用等 |
| 异步 DMA | 硬件搬数据,有机会与计算重叠 |
| 多 stream | 多条有序队列,有机会并行推进 |
| Event | 标记进度、协调依赖、测量时间 |
| 进程隔离与调度 | 多个程序如何安全地共享 GPU |
| MIG | 将 GPU 划分成拥有专属资源的实例 |
MIG 主要由部分数据中心 GPU 和新款 RTX PRO Blackwell 专业卡支持。
根据 NVIDIA 当前官方列表,常见支持型号如下:
| 架构 | GPU 型号 | 最多 MIG 实例数 |
|---|---|---|
| Ampere | A100(40GB/80GB) | 7 |
| Ampere | A30 | 4 |
| Hopper | H100、H200、H20 | 7 |
| Blackwell | B200、GB200 中的 GPU | 7 |
| Blackwell | RTX PRO 6000 Blackwell:服务器版、工作站版、Max-Q 工作站版 | 4 |
| Blackwell | RTX PRO 5000 Blackwell | 2 |
| Blackwell | RTX PRO 4500 Blackwell | 2 |