想象你是一座繁忙集装箱码头的调度员。货船载满了标准集装箱——这些箱子就是矩阵中的数据。客户的需求很简单:把两堆货(矩阵 A 和矩阵 B)按规则相乘,再叠到已有的货堆(矩阵 C)上,得到新货堆(矩阵 D)。码头上有一台威力惊人的龙门吊——它就是 Tensor Core。普通吊车一次只能吊一个箱子(普通 CUDA 核心一次做一个乘加),而龙门吊一次能吊起一整片集装箱,在空中完成“翻转、对位、叠放”的组合动作,这就是 mma 指令。 但要让这台龙门吊干活,你不能只说“把货搬过去”,必须写一份精确到每一步的吊装作业单:用几号吊具、吊哪一片、放在哪个作业区、集装箱朝哪个方向码放。这份作业单,就是 PTX 指令。CUDA C++ 像物流系统只关心“从哪到哪”,而 PTX 必须关心“吊机的每个具体动作”——它是连接高级语言与 GPU 硬件的中间指令层。本文将带你以码头作业为线索,从 PTX 指令语法、矩阵片段寄存器、内存布局到性能调优,完整掌握 Tensor Core 的底层编程。
掌握 PTX 与 mma 指令编程,意味着你能在最底层控制 GPU 的算力引擎,写出比高级库更快、更省显存的算子,这是高性能计算与 AI 推理引擎工程师的核心竞争力。
可视化演示
双模式展示PTX编程:MMA指令执行流程(Shm→ldm加载→frag寄存器→mma.sync→输出)和代际对比(V100 FP16/A100 BF16/H100 FP8算力演进)。
本节配有交互动效(进入视口后自动加载,约 3KB)。
MMA指令执行流程 Shm:共享内存中的矩阵分块(tile) ldm:ldmatrix指令加载到寄存器fragment mma:mma.sync执行矩阵乘加(D=A×B+C) flow: ldmatrix A,B → mma.sync al,m,n,k → store D warp内线程协同加载fragment并执行MMA运算。
PTX指令概览
码头调度室里有两套文档:一套是物流公司给的“运输总单”(CUDA C++ 源码),只写从哪个仓库取货、送到哪个泊位;另一套是龙门吊司机实际执行的“吊装作业单”(PTX 指令),精确到吊具型号、起吊角度、放箱位置。总单经过一层翻译,变成作业单,再经过一层翻译,变成司机手上的操作按钮——这就是 GPU 的两级编译:CUDA C++ 先编译成 PTX,再由驱动编译成 SASS 机器码。
PTX 的全称是 Parallel Thread Execution(并行线程执行),它是 NVIDIA 定义的虚拟指令集。它的巧妙之处在于:作业单不绑定具体某台吊机型号,老吊机和新吊机都能读懂同一份作业单,只是实际动作快慢不同。这保证了 PTX 编写的代码能跨越多代 GPU 运行,同时也让程序员能够触及硬件能力的“最后一公里”。
在 GPU 港口里,最小的“作业班组”是 warp,一个 warp 恰好有 32 名工人(线程)。龙门吊的每一次起吊,都需要这 32 名工人同时握住吊具、齐声喊号子一起发力。因此 mma 指令是 warp 级指令——它要求一个 warp 的 32 个线程全部参与、协同完成。在 CUDA 内核中,你可以用 asm volatile 嵌入 PTX 指令,这就是“内联 PTX”,相当于在总单中直接粘贴一份吊装作业单。
回到码头:如果你想让龙门吊高效工作,就先要学会读懂作业单的格式。PTX 是一个庞大的指令集,而我们最关心的那一页,写着 mma——矩阵乘加指令。它就是龙门吊最核心的“组合动作”手册。
- warp_size表示一个 warp 包含的线程数量。你可以把它理解为龙门吊班组的人数,这个人数由硬件固定,程序员无法改变。
- 2^5为什么恰好是 32?因为 GPU 硬件按二进制设计,用 5 个比特就能给 32 名工人分别编号(0~31),这是硬件寻址的自然结果。
- = 32结论值。所有 warp 级指令(包括 mma)都隐含“32 个线程同时行动”这一前提,忘记这一点,后续的寄存器分配与内存布局都无从谈起。
mma指令语法
龙门吊最厉害的一招叫“组合吊装”:吊具同时抓起一整片集装箱(矩阵 A 的某个分块),在空中与另一片集装箱(矩阵 B 的分块)对位交错,落到已有的货堆(矩阵 C)上,瞬间完成“乘加叠放”。整个过程一条指令搞定,这就是 mma(matrix multiply-accumulate)。普通 CUDA 核心要一条条指令地做“乘一次、加一次”,而 Tensor Core 把整个分块的乘加打包成了一个硬件动作。
在 PTX 中,一次组合吊装写成这样的作业单:
逐个词拆开看:mma 是操作码,sync 表示班组内需要同步,aligned 表示所有地址与数据类型必须对齐;m16n8k8 是本次吊装的货物规格——M 维度 16、N 维度 8、K 维度 8,意味着 A 是 16×8 的货片,B 是 8×8 的货片,C 和 D 都是 16×8 的货堆。随后的 .row、.col 分别是 A、B 矩阵的“码放朝向”(行主序或列主序),最后的 .f32.f16.f16.f32 依次声明 D、A、B、C 四种货物的材质(数据类型)。
看到这里你可能会问:作业单上的 A、B、C、D 究竟存放在哪里?龙门吊的吊具上挂着四个作业区,每个区由 32 名工人分别掌管一部分货物——这就是矩阵片段寄存器,下一节我们走进这四个作业区。
- D = A×B+C这是整个指令的灵魂。Tensor Core 不是简单地算乘法,而是在一次硬件操作里完成“乘起”和“叠加”两件事。龙门吊一次落箱,既完成了新货的搬运,也完成了与旧货堆的合并。
- m16n8k8 形状指令名中的 m/n/k 分别对应 M、N、K 三个维度。M 是 A 的行数,N 是 B 的列数,K 是 A 的列数(同时也是 B 的行数)。这些形状是硬件固定的,不能随意指定。
- A 与 B 的维度约束A 必须能左乘 B,即 A 的列数等于 B 的行数。这里 A 是 16×8,B 是 8×8,中间共享的 K=8 像两片货物互相咬合的卡槽。
- C 与 D 同维度累加矩阵 C 与输出矩阵 D 的形状完全相同,都是 M×N。这保证了“叠放”操作天然合法,无需额外形状转换。
mma.sync.aligned.m16n8k8.row.col.f32.f16.f16.f32 {d}, {a}, {b}, {c};矩阵片段寄存器
龙门吊的吊具不是一个大铁板,而是由 32 名工人手持的托盘拼成的。每名工人手里只攥着几件货物(几个数),这些货物就存放在工人胸前的工具口袋(寄存器)里。整个吊具的 A 区、B 区、C 区、D 区,分别对应 mma 指令的四个矩阵片段(fragment)。没有集中的大仓库,所有数据都分散在每名工人的口袋中。
以 m16n8k8 的 A 片段为例:A 矩阵共有 16×8=128 个元素,分给 32 名工人,每人恰好拿 4 个元素。如果元素是 fp16 半精度(16 位),那么每 2 个元素可以塞进一个 32 位寄存器,所以每名工人口袋里是 2 个寄存器。同理,B 片段 8×8=64 个元素分给 32 人,每人 2 个元素(1 个寄存器);C 和 D 片段是 fp32 累加结果,16×8=128 个 32 位数分下去,每人 4 个寄存器。
这些片段不是随意分配的,而是严格按 lane ID(工人在班组中的编号)映射。第 0 号工人拿 A 矩阵第一行的前几个元素,第 1 号工人拿紧接着的几个……硬件工程师在文档里画好了完整的“分货表”。正因如此,同一个寄存器在不同 mma 指令中可以被不同片段复用——上一轮还是 A 片段的寄存器,下一轮经过 ldmatrix 重新装载,就变成了新的 B 片段。这就是寄存器复用:工具口袋不变,装的东西可以换。理解了四个作业区,下一步要搞清楚货物从大仓库(全局内存)运到作业区途中的码放规则。
- M×K这是 A 矩阵的总元素个数,对应龙门吊作业区 A 里的全部货物数量。m16n8k8 时就是 16×8=128 件货。
- ÷32把总货物平均分给 32 名工人。这是“warp 级协作”的数学体现——没有谁单独拿一整片,而是人人有份。
- R_thread每名工人最终手中的货物件数。m16n8k8 时结果是 4,即每线程持有 4 个 A 元素(打包成 2 个寄存器)。
内存布局约束
集装箱在货场上码放是有规章的:箱子必须全部朝一个方向,吊具才能一次抓牢;如果朝向混乱,龙门吊一抓就散。同样,Tensor Core 要求矩阵以特定的“主序”排列——行主序(row-major)或列主序(col-major)。mma 指令中的 .row 和 .col 修饰符,就是告诉硬件“这批货是横着码还是竖着码”。行主序的相邻元素在内存中横向挨着,列主序则纵向挨着。
货物从港口外的大仓库(全局内存)运到龙门吊旁边的中转堆场(共享内存)之后,还需要一辆专用的“集装箱转运车”把它们整齐装到工人的口袋里,这辆车就是 ldmatrix。一条 ldmatrix.sync.aligned.m8n8.x4 指令,可以同时从共享内存抓取 4 个 8×8 的矩阵块,精准分配到 32 名工人的寄存器中——这个动作比普通 ld.shared 逐元素装载快得多,因为它完全摸清了龙门吊的分货表。
但转运车对货物摆放有苛刻要求:每个 8×8 矩阵块的起始地址必须 16 字节对齐。除此之外,32 名工人同时去共享内存取货时,还可能撞上同一个“装卸通道”(bank),导致取货排队。共享内存有 32 个 bank,每个 bank 每次服务一个 4 字节单元;如果多名工人同时访问同一个 bank,就会被串行处理,这就是 bank 冲突。解决方式是使用 swizzle 模式:码放货物时故意按异或规则打乱位置,让工人的访问均匀分散到不同 bank。
回到码头:货物码放规则解决了,接下来要考虑选什么材质的集装箱。不同的货物需要不同的箱子,而 Tensor Core 对不同“材质”的运算速度与精度也大不相同。
- addr ÷ 4addr 是共享内存的字节地址,硬件按 4 字节为一个基本单元分配 access。先把字节地址换算成单元编号。
- mod 32对 32 取模。因为共享内存恰好有 32 个 bank,单元编号对 32 求余,就得到它所属的通道号。两名工人若算出相同 bank,就会冲突。
- 映射结果把 bank 视作“装卸通道”。swizzle 的本质,就是通过地址变换让原本映射到同一通道的访问散开,使 32 条通道被均匀使用。
数据类型精度
码头上备有四种集装箱:标准钢箱(FP16)、重型加固箱(TF32)、轻量铝箱(BF16)和速装箱(INT8)。选用哪种箱子,直接影响龙门吊的单次吊装重量(吞吐量)和货物安全(精度)。FP16 半精度是 Tensor Core 最经典的搭档:只有 16 位,尾数 10 位,数值范围有限,但两个 FP16 能拼进一个 32 位寄存器,计算吞吐翻倍,是深度学习训练和推理的主力材质。
TF32 是专门为“既要精度又要速度”准备的折中方案:它保留了 FP32 的 8 位指数(数值范围与 FP32 相同),但把尾数砍到 10 位。相当于重型箱——外面尺寸和 FP32 一样大,但内壁加厚,装贵重货物不会溢出。BF16 则反过来,指数 8 位保范围、尾数仅 7 位,适合对数值范围敏感但对尾数精度不敏感的场合。INT8 则是把货物压缩打包,配合 FP32 累加器,常用于推理阶段的量化加速。
如何从数学上理解 TF32 的截断?FP32 有 23 位尾数,TF32 只保留高 10 位,等价于把尾数的小数部分“四舍五入”到 10 位精度——或者说,把原数放大 2 的 13 次方倍、截掉小数部分、再缩小回来。
选好了集装箱材质,最后一步是优化整个码头的作业节奏。再快的龙门吊,如果工人和转运车总是互相等待,吞吐量也上不去——这就是性能调优的用武之地。
- x × 2^13把 x 放大 8192 倍。这样原本尾数的低 13 位就被“顶”到了整数部分,为下一步截断做准备。放大倍数的选择由尾数位数差决定:23-10=13。
- trunc截断操作,一刀砍掉小数部分(即低 13 位尾数)。这相当于把货物倒进一个只有 10 位精度的模具,多余的边角料直接削掉。
- × 2^-13再把结果缩小 8192 倍,还原到原来的数量级。缩放和截断合在一起,就是“只保留 10 位有效尾数”的数学表达。
性能调优技巧
码头运营的核心目标是“吊机不停”——龙门吊每秒钟都应该在干活,而不是等货。最有效的办法是双缓冲:准备两块中转堆场,龙门吊正在计算堆场 A 里的货物时,转运车已经往堆场 B 卸下了下一批货;等 A 算完,立刻切换到 B,转运车再回头给 A 补货。计算与搬运像两条传送带一样重叠进行,这就是软件流水线。
在 PTX 层面,这需要精细控制每条 ldmatrix 和 mma 的发射顺序。更深一层的约束来自寄存器压力:每名工人的工具口袋数量是有限的(每个线程最多 255 个寄存器)。mma 的四个片段本就要占用不少寄存器,如果为了流水线多缓存几批货,寄存器就会超支,导致内核无法启动或占用率暴跌。占用率(occupancy)决定了一个 SM 上能同时驻留多少个 warp——驻留越多,当一个 warp 等待内存时,另一个 warp 就能顶上干活,延迟就被“藏”了起来。
至此,我们从读懂作业单、掌握组合动作、分配四区货物、规范码放规则、选择集装箱材质,到最终优化码头调度,完整走了一遍 Tensor Core 的 PTX 编程流程。龙门吊依然在轰鸣,但现在的你,已经能亲手写出那份让它发挥极限效率的吊装作业单了。
- active_warps当前同时驻留在 SM 上的 warp 数量。它受寄存器、共享内存等资源限制,不是想开多少就开多少。
- max_warps硬件允许的 warp 数上限,由 SM 设计决定。这是码头工位的总数,满载时能容纳的班组数。
- 比值含义占用率越高,意味着越多的班组同时待命,即使某组在等货,其他组也能继续吊装,从而隐藏访存延迟。但占用率并非越高越好——过高的并行可能挤占寄存器,反而限制每个 warp 的流水线深度,需要在实践中权衡。
AI 应用场景
AI推理引擎中的PTX算子优化
阶段:模型推理部署与性能优化
解决的问题:深度学习框架默认的卷积与矩阵乘法算子,往往为了通用性牺牲了硬件利用率。在生产环境中,同一个模型要在不同型号的 GPU 上达到极致吞吐,必须针对 Tensor Core 的 mma 指令手工编写或调优 kernel。PTX 层编程让工程师能精确控制矩阵分块形状、内存布局与数据类型,把算子效率从框架默认的 60% 提升到 90% 以上。
具体案例:某视频理解模型的推理服务延迟超标。工程师将核心 GEMM 替换为基于 mma.sync.aligned.m16n8k16 的手写 PTX kernel,并用 ldmatrix.x4 批量装载共享内存分块,配合双缓冲流水线,最终在 A100 上实现了 1.8 倍加速,且精度无损。这就像把龙门吊的每一次起吊都重新设计了吊具与路径,让每秒钟的吊装次数大幅上升。
大模型混合精度训练策略
阶段:模型训练与精度管理
解决的问题:大模型训练动辄需要数百张 GPU,FP32 全精度训练显存开销大、速度慢;无脑使用 FP16 又可能出现梯度下溢或溢出。理解 TF32 与 FP16 的位宽结构后,工程师可以为不同层选择不同的 Tensor Core 精度——例如用 TF32 代替 FP32 做前向传播,用 FP16 配合 FP32 累加器做反向传播,在保持收敛质量的同时显著提升训练吞吐。
具体案例:某 LLM 微调任务中,基线用 FP32 训练一个 epoch 需要 12 小时。团队开启 torch.backends.cuda.matmul.allow_tf32 = True 后,矩阵乘法自动切换到 TF32 Tensor Core 路径,训练时间缩短到 8 小时,验证集困惑度几乎不变。这是理解“8 位指数保范围、10 位尾数够用”精度权衡后做出的合理选择。
科学计算中的自定义矩阵内核
阶段:HPC 数值计算与 kernel 开发
解决的问题:许多科学计算(如气象模拟、分子动力学)需要自定义的数据类型和矩阵形状,标准库的 GEMM 无法直接覆盖。通过 PTX 内联汇编,研究人员可以直接组合 mma 指令与自定义的 ldmatrix 装载流程,为特定物理模型打造专属的矩阵引擎。
具体案例:某量子化学团队需要频繁计算 16×8 尺寸的小矩阵相乘,标准 cuBLAS 因分块开销过大而效率低下。他们用内联 PTX 编写了专用的 m16n8k8 kernel,将预热开销与寄存器占用降到最低,最终在 GPU 集群上获得了 2.3 倍的端到端加速。
Python 演示
import numpy as np def to_fp16(x): """模拟FP16在Tensor Core中的输入:转半精度后转回fp32参与累加。""" return x.astype(np.float16).astype(np.float32) def to_tf32(x): """模拟TF32截断:保留fp32的8位指数,尾数从23位截到10位。""" bits = np.ascontiguousarray(x, dtype=np.float32).view(np.uint32) bits = bits & np.uint32(0xFFFFE000) return bits.view(np.float32) def to_fp32(x): """FP32基准:不做任何精度截断。""" return x.astype(np.float32) def mma_accumulate(A, B, C, cast_fn): """模拟mma指令语义 D = A x B + C,输入先按指定精度舍入。""" Aq = cast_fn(A) Bq = cast_fn(B) return Aq @ Bq + C def run_demo(): np.random.seed(42) M, K, N = 128, 64, 64 A = np.random.randn(M, K).astype(np.float32) B = np.random.randn(K, N).astype(np.float32) C = np.random.randn(M, N).astype(np.float32) ref = A @ B + C schemes = { "fp16 (Tensor Core)": to_fp16, "tf32 (Tensor Core)": to_tf32, "fp32 (CUDA Core)": to_fp32, } for name, cast in schemes.items(): out = mma_accumulate(A, B, C, cast) err = np.abs(out - ref).mean() print(f"{name:22s} MAE = {err:.6e}") if __name__ == "__main__": run_demo()
def to_fp16(x)把 FP32 张量先降为 FP16 再升回 FP32,模拟 Tensor Core 以 FP16 读取输入、以 FP32 累加的过程。def to_tf32(x)通过位运算把 FP32 的尾数低 13 位清零,等价于 TF32 的 10 位尾数截断。bits & np.uint32(0xFFFFE000)0xFFFFE000 的二进制低 13 位全为 0,按位与即丢弃低 13 位尾数。def mma_accumulate(A, B, C, cast_fn)用Aq @ Bq + C一句话复现mma指令的数学语义 D = A×B+C。schemes = { ... }用字典把三种精度策略映射到对应的舍入函数,便于统一循环对比。err = np.abs(out - ref).mean()计算平均绝对误差(MAE),量化每种精度相对 FP32 基准的偏差。
常见误区
误区我以为 PTX 就是 GPU 最终执行的机器码,写完 PTX 就能直接控制硬件电路,所以调优 PTX 等同于调优 SASS。
正解PTX 是虚拟指令集,属于中间表示层。它还要经过驱动中的编译器(ptxas)翻译成 SASS,才能真正被 GPU 执行。就像吊装作业单是写给人看的规范文件,司机还得把它转化为手上的具体操作。同一条 PTX 在不同代 GPU 上会生成不同的 SASS,这正是 PTX 跨代兼容的原因。
为何常见:CUDA 编译报错信息里常同时出现 PTX 和 SASS 相关文件,加上 nvcc 默认输出 .ptx 中间文件,容易让人误以为 PTX 就是最终可执行代码。
误区我以为写好一条 `mma.sync.aligned.m16n8k8...` 指令后,当前线程就会自己完成整个 16×8 的矩阵乘加。
正解mma 是 warp 级指令,必须由整个 warp 的 32 个线程协同执行。每个线程只持有矩阵片段的一部分(例如 A 片段每线程仅 4 个元素),所有线程同时发出一条相同的 mma 指令,硬件才把碎片拼成完整的矩阵运算。好比龙门吊的 32 名工人必须同时发力,缺一个人吊具就会倾斜。
为何常见:普通 CUDA 指令如 fma.rn.f32 是单线程语义,先入为主地以为所有指令都如此,忽略了 mma 属于 warp 级矩阵指令这个特殊类别。
误区既然 mma 能做矩阵乘加,那我是不是可以用它算任意大小(比如 100×100)的矩阵乘法,只要写好 M、N、K 就行。
正解Tensor Core 只支持硬件预设的少数形状组合,如 m16n8k8、m16n8k16、m16n8k32 等。更大的矩阵必须拆成多个这种形状的分块(tile),循环调用 mma 指令。就像龙门吊的吊具规格是固定的,一片只能吊 16×8 的货,大货堆必须分批吊装。
为何常见:矩阵乘法在数学上对形状没有限制,把数学直觉直接搬到了硬件指令上,忽略了 Tensor Core 是定制化电路而非通用计算单元。
误区我以为 A、B、C、D 四个片段是放在共享内存里的大数组,mma 指令直接读写共享内存。
正解fragment 是寄存器片段的缩写,四个矩阵片段最终都分配在 warp 各线程的寄存器文件中。共享内存只是数据从全局内存到寄存器之间的“中转堆场”,真正的搬运动作由 ldmatrix 完成并写入寄存器。理解这一点对控制寄存器压力、提升占用率至关重要。
为何常见:共享内存名字里有“共享”二字,且编程中确实要大量操作共享内存,容易让人误以为它就是矩阵数据的最终存放地。
误区FP16 不过是比 FP32 数字精度差一些,反正神经网络对误差不敏感,所有层都可以无脑用 FP16 跑 Tensor Core。
正解FP16 的指数只有 5 位,数值范围远小于 FP32。梯度、中间激活很容易超出 ±65504 的范围导致溢出,而小数值又会下溢为 0。TF32 正是为了在保留 FP32 范围的同时获得 Tensor Core 加速而设计的。选择数据类型要看数值范围与尾数精度的双重约束,而不是笼统地“低精度换速度”。
为何常见:深度学习中“混合精度训练”的概念很流行,很多人只记住了“FP16 更快”,却忽略了混合精度的核心是“为不同数据分配合适的精度”。
面试题
Q1. PTX 在 GPU 编程中的角色是什么?
- A.最终执行的硬件机器码
- B.介于 CUDA 与 SASS 间的中间指令层← 正确答案
- C.负责显存分配的运行时库
- D.一种用于调试 CUDA 的图形界面工具
答案:B。PTX 是虚拟指令集,位于 CUDA C++ 与 SASS 之间,由驱动编译为最终机器码。它像“吊装作业单”——规范而跨代通用,但还不是司机手上的具体操作。A 是 SASS 的角色,C 是 CUDA 运行时 API 的职责,D 与 PTX 无关。
Q2. 一个 warp 包含多少个线程?
- A.8 个
- B.16 个
- C.32 个← 正确答案
- D.64 个
答案:C。GPU 硬件规定一个 warp 有 32 个线程,这是调度与 warp 级指令(如 mma)的基本单位。32 来自二进制的 5 比特寻址(2^5=32)。
Q3. mma.sync.aligned.m16n8k8.row.col.f32.f16.f16.f32 中 .row 和 .col 分别描述哪个矩阵的布局?
- A.分别是 C 和 D 的布局
- B.分别是 A 和 B 的布局← 正确答案
- C.分别是 D 和 C 的布局
- D.分别是 B 和 A 的布局
答案:B。mma 指令中布局修饰符依次作用于 A 和 B:.row 表示 A 行主序,.col 表示 B 列主序。C 和 D 默认行主序。就像码放货物时先规定 A 区横放、B 区竖放。
Q4. m16n8k8 指令中,A 矩阵的维度是多少?
- A.16×8← 正确答案
- B.8×8
- C.16×16
- D.8×16
答案:A。m16n8k8 表示 M=16、N=8、K=8。A 矩阵形状是 M×K = 16×8,B 是 K×N = 8×8,C/D 是 M×N = 16×8。
Q5. 关于矩阵片段(fragment)的说法,正确的有哪些?
- A.A/B/C/D 四个片段都存储在共享内存中
- B.每个线程持有片段的一部分元素← 正确答案
- C.mma 由整个 warp 协作完成← 正确答案
- D.片段数量与每线程寄存器数无关
答案:BC。fragment 存储在寄存器而非共享内存,A 错;warp 的 32 个线程平分矩阵元素,B 对;mma 是 warp 级指令,C 对;每线程元素数=总元素÷32,直接决定寄存器占用,D 错。这就像 32 名工人每人攥几件货,而不是货全堆在公共堆场。
Q6. ldmatrix 指令的主要作用是什么?
- A.从全局内存直接加载标量
- B.从共享内存高效装载 8×8 矩阵块到寄存器← 正确答案
- C.释放共享内存空间
- D.把寄存器数据写回全局内存
答案:B。ldmatrix 是 Tensor Core 编程专用的“转运车”,一次可装载多个 8×8 矩阵块到寄存器片段,避免逐元素加载的低效。A 是普通 ld 指令的职责,C/D 是 stmatrix 等指令的职责。
Q7. TF32 相比 FP16 的核心优势是什么?
- A.计算速度更快
- B.保留与 FP32 相同的指数范围← 正确答案
- C.尾数精度更高到 23 位
- D.不需要 Tensor Core 支持
答案:B。TF32 有 8 位指数,与 FP32 相同,数值范围大;FP16 只有 5 位指数,易溢出。TF32 尾数仍是 10 位,速度通常不高于 FP16。它就像“尺寸和 FP32 一样大、内壁特殊加固”的重型箱。
Q8. 共享内存地址映射中,若地址 addr=68(字节),则对应的 bank 编号是?
- A.bank 0
- B.bank 1← 正确答案
- C.bank 16
- D.bank 17
答案:B。bank = (addr/4) mod 32 = (68/4) mod 32 = 17 mod 32 = 17。因此地址 68 落在 bank 17。若多个线程同时访问同一 bank,将产生冲突而串行化。
Q9. 为什么 swizzle 模式能减少共享内存 bank 冲突?
- A.它增加了共享内存容量
- B.它改变数据存储地址使同一行的访问散落到不同 bank← 正确答案
- C.它把矩阵缩小到单个 bank 内
- D.它禁止多个线程同时访问共享内存
答案:B。swizzle 通过 XOR 地址变换,让原本连续映射到同一 bank 的数据分散到不同 bank。swizzle 不改变容量、不缩小矩阵,也不禁止并发访问,只是重新排布了“货位”,使 32 条装卸通道被均匀使用。
Q10. 关于双缓冲与软件流水线,下列说法正确的有哪些?
- A.双缓冲需要两份共享内存空间← 正确答案
- B.软件流水线让数据拷贝与矩阵计算重叠← 正确答案
- C.双缓冲可以提高共享内存总容量
- D.流水线设计可能增加寄存器压力← 正确答案
答案:ABD。双缓冲用两块缓冲区交替装载,A 对;核心思想是搬运与计算重叠,B 对;共享内存容量由硬件决定,双缓冲只是分区使用而非扩容,C 错;为了提前缓存数据,往往需要更多寄存器,可能降低占用率,D 对。这就像准备两个中转堆场让吊机不停歇,但需要更多工人手袋装货。