想象你经营着一家大型食堂,每天要为一万名顾客配餐。普通厨师(CUDA Core)会一份一份地炒菜:放油、下料、翻炒、出锅,虽然灵活,但效率有限。而食堂采购了一台“配餐流水线”(Tensor Core)——它不擅长单份小炒,却能在同一时间批量完成几十份标准套餐的装配。你的任务,就是学会如何把食材(矩阵)切好、按固定模具(wmma API)摆放、再交给流水线批量生产。这篇文章,就是你的“后厨操作手册”。我们将从硬件原理讲到编程实战,再到性能调优,带你把 GPU 的每一分算力都榨干。

岗位关联

学完本文,你将掌握 Tensor Core 的硬件原理与 wmma 编程范式,能独立编写高性能矩阵乘加算子,为 LLM 推理加速、GEMM 库优化等一线 AI 工程任务打下扎实基础。

目录
  1. 可视化演示
  2. 硬件架构原理
  3. wmma编程模型
  4. 矩阵分块策略
  5. 混合精度实战
  6. 性能优化途径
  7. 调试性能分析
  8. 工程应用实践
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

本节配有交互动效(进入视口后自动加载,约 3KB)。

硬件架构原理

食堂后厨里,最忙的永远是那位“配餐大师”。普通厨师炒一份菜要起锅烧油、颠勺调味,一套动作十几步;而配餐大师面前有十台标准模具,只要把食材按格子摆好,一压一推,十份套餐同时出炉。Tensor Core 就是 GPU 里的这位配餐大师——它不处理灵活的单条指令,而是在一个周期内完成一次完整的矩阵乘加运算,把 $D = A \times B + C$ 这个深度学习中最常见的公式变成了“肌肉记忆”。

在 Volta 架构出现之前,GPU 只能靠成百上千个 CUDA Core 逐个计算乘法和加法,就像让十位厨师一人炒一道菜再拼盘,速度慢且耗能。Tensor Core 的出现改变了局面:每个 Tensor Core 内部有专门的高速乘法器和加法树,输入两个小矩阵,直接输出乘加结果。以 A100 为例,每个 SM 中有四个 Tensor Core,FP16 峰值算力可达 312 TFLOPS,是同卡 FP32 CUDA Core 算力的数倍。

这种“以空间换时间”的设计,本质上是把硬件单元从“通用”推向“专用”。通用 CUDA Core 负责灵活的控制流和标量运算,Tensor Core 则专注于矩阵乘加这条主干道。两者各司其职,共同支撑起深度学习训练与推理的巨大算力需求。

D=A×B+CD = A \times B + C

1. 符号说明:$D$ 是输出矩阵,$A$ 和 $B$ 是两个输入矩阵,$C$ 是累加矩阵(通常初始为 0 或上一次的偏置结果)。

D=A×B+CD = A \times B + C

2. 乘法优先:先计算 $A$ 与 $B$ 的矩阵乘法,这要求 $A$ 的列数等于 $B$ 的行数,结果矩阵的尺寸由 $A$ 的行数和 $B$ 的列数决定。

A(M×K)×B(K×N)=R(M×N)A_{(M \times K)} \times B_{(K \times N)} = R_{(M \times N)}

3. 累加融合:将乘法结果 $R$ 与矩阵 $C$ 逐元素相加,这一步在 Tensor Core 内部的加法树中与乘法同步完成,没有额外的访存开销。

Dij=Rij+CijD_{ij} = R_{ij} + C_{ij}

食堂里配餐大师的模具尺寸是固定的,Tensor Core 也有固定形状偏好(如 16×16×16)。接下来我们看看如何通过 wmma API 与这位大师沟通。

wmma编程模型

配餐大师不会说普通话,他只听“套餐模板”——一种印好格子的标准订单。你不需要告诉他每一份菜怎么炒,只需要把食材按格子放好,在订单上打勾。CUDA 提供的 wmma API(Warp Matrix Multiply-Accumulate)正是这张标准订单:它把矩阵运算包装成三个固定步骤,让程序员无需触碰底层硬件细节。

wmma 的核心是 fragment 这个“分片托盘”。矩阵太大搬不动,fragment 负责把一块 16×16 的小矩阵从全局内存或共享内存“端”到每个线程的寄存器里。load_matrix_sync 函数同步加载数据到 fragment,mma_sync 执行乘加,store_matrix_sync 把结果写回内存。这三个函数调用,就对应了配餐流程中的“备菜 → 压模 → 出餐”。

之所以叫 sync(同步),是因为一个 warp(32 个线程)需要集体协作完成这些操作。每个线程只持有 fragment 中的一部分元素,用“大家同时伸手拿自己那份”来理解最贴切。所有线程步调一致,缺一不可——这正是 wmma 与普通 CUDA 编程最大的思维差异:你不是在写“我的线程做什么”,而是在写“整个 warp 共同完成什么”。

C_frag+=A_frag×B_frag\text{C\_frag} += \text{A\_frag} \times \text{B\_frag}

1. fragment 初始化:声明 fragment 时指定矩阵类型(matrix_a / matrix_b / accumulator)、精度(half / float)和形状(如 16×16×16),相当于告诉食堂“我要三人份标准套餐”。

fragment<matrix_a,half,16,16,16> a_frag\text{fragment}<\text{matrix\_a}, \text{half}, 16, 16, 16> \ \text{a\_frag}

2. 同步加载:load_matrix_sync 把共享内存或全局内存中的一块数据按固定布局填入 fragment,每个线程拿到属于自己的那几份“食材”。

load_matrix_sync(a_frag,ptr,ldm)\text{load\_matrix\_sync}(\text{a\_frag}, \text{ptr}, \text{ldm})

3. 乘加执行:mma_sync 让整个 warp 的 fragment 进行一次矩阵乘加,硬件内部完成所有乘法和加法,结果累积到累加器 fragment 中。

mma_sync(c_frag,a_frag,b_frag,c_frag)\text{mma\_sync}(\text{c\_frag}, \text{a\_frag}, \text{b\_frag}, \text{c\_frag})

现在你有了“标准订单”,但整张菜单可能有几千个菜。下一节我们看看如何把大矩阵“切碎”,分批喂给这位配餐大师。

矩阵分块策略

食材太大,模具放不下怎么办?答案很简单:切。一整头牛不能直接塞进模具,但切成 16×16 的小肉块就刚刚好。矩阵分块(Tiling)就是干这个的——传统的矩阵乘法需要 $M \times N \times K$ 次乘加,如果直接把整个矩阵交给 Tensor Core,硬件模具根本装不下。聪明的做法是把大矩阵切成若干个 16×16(或 16×8×8)的小 tile,逐个送入 Tensor Core 计算。

在 CUDA 程序的视角里,分块策略是一场“三层嵌套的配餐循环”:外层循环遍历输出分块的行和列,内层循环遍历 K 维度上的累加步数。每一轮内层循环,我们都从全局内存取一块 A 的 tile 和一块 B 的 tile,放进共享内存这个“备菜台”,再让 warp 们从备菜台取食材送入 Tensor Core。

共享内存在这里扮演了“冰箱”的角色——全局内存太慢,直接把每个 tile 送到 Tensor Core 会饿着它。先把当前循环需要的 A 和 B 分块搬进共享内存,warp 再从共享内存快速取用,访存速度能提升一个数量级。而 tile 尺寸的选择也很有讲究:16×16 能完美贴合 Tensor Core 的模具,但更大的 tile(如 32×32)能减少循环次数、提高数据复用率,代价是共享内存占用更大,可能降低 SM 上同时驻留的线程块数量。

Ctile=∑k=0K/16−1Atile(k)×Btile(k)\text{C}_{\text{tile}} = \sum_{k=0}^{K/16-1} \text{A}_{\text{tile}}^{(k)} \times \text{B}_{\text{tile}}^{(k)}

1. 外层行块循环:遍历输出矩阵的行方向,每次取 $M_{\text{tile}}$ 行,对应 $\text{C}_{\text{tile}}$ 的第一维。

for i in range(0,M,16)\text{for } i \text{ in range}(0, M, 16)

2. 外层列块循环:遍历输出矩阵的列方向,与行块组合确定一个输出分块。

for j in range(0,N,16)\text{for } j \text{ in range}(0, N, 16)

3. 内层 K 循环:沿 K 维逐步累加,每次取 A 的 $16 \times 16$ 块和 B 的 $16 \times 16$ 块做乘加。

Cij+=Aik×Bkj,k=0,16,32,…\text{C}_{ij} += \text{A}_{ik} \times \text{B}_{kj}, \quad k = 0, 16, 32, \dots

分块是空间上的切配艺术,而混合精度则是“食材用量”上的巧妙计算。接下来我们看看如何用“半份食材”做出同样美味的菜。

混合精度实战

食堂要控制成本,食材贵怎么办?聪明的做法是:排骨切成薄片(FP16)来配菜,但记账时仍然用精确到分的计算器(FP32)。混合精度训练的哲学与此如出一辙——用 FP16 存储和搬运矩阵数据,节省一半内存带宽,但在 Tensor Core 内部用 FP32 做累加,保证计算精度不“缩水”。

为什么要这样做?FP16 只用 16 位存储,数值范围小、精度低,但它的存储体积是 FP32 的一半,搬运速度翻倍。Tensor Core 的硬件设计恰好迎合了这种“不对称”:输入两个 FP16 矩阵,内部用高精度乘法器和 FP32 累加器,最终输出 FP32 结果。就像配餐时食材是半份,但每份菜的分量都由电子秤精确称量。

实际使用中还有一个经典坑:FP16 的动态范围只有约 5.96e-8 到 65504,梯度稍微小一点就会“下溢”变成零,导致训练不收敛。损失缩放(Loss Scaling)技术应运而生——训练时把损失值放大若干倍(如 1024),等梯度计算完毕再缩小回来,相当于把整道菜先放大再摆盘,确保微小食材也能被看见。

Dfp32=fp32(Afp16)×fp32(Bfp16)+Cfp32D_{\text{fp32}} = \text{fp32}(A_{\text{fp16}}) \times \text{fp32}(B_{\text{fp16}}) + C_{\text{fp32}}

1. 存储格式:$A$ 和 $B$ 以 FP16 格式存放在内存中,内存占用减半、带宽需求减半。

Afp16, Bfp16A_{\text{fp16}}, \ B_{\text{fp16}}

2. 内部提升:进入 Tensor Core 后,FP16 值被转换为 FP32 参与乘法,避免精度损失。

fp32(Afp16)×fp32(Bfp16)\text{fp32}(A_{\text{fp16}}) \times \text{fp32}(B_{\text{fp16}})

3. 高精度累加:乘法结果与 FP32 累加矩阵 $C$ 相加,全程保持单精度累积,只在最终写回时选择是否转换为低精度。

Dij←(乘积)ij+CijD_{ij} \leftarrow (\text{乘积})_{ij} + C_{ij}

食材备好了,模板也有了,但如何让流水线持续高速运转不空转?这就进入了性能优化的深水区。

性能优化途径

食堂开业后你发现:配餐大师经常“等菜”。食材没送到,模具空转;模具压完了,下一个分块还没切好。性能优化的本质,就是消除这些“等待”——让食材供应链、切配台和压模机三者之间永远没有空闲。

第一大优化是内存对齐。Tensor Core 要求矩阵的行主序步长(ldm)是 16 字节的倍数,相当于托盘上的每道菜必须按固定间距摆放,否则取餐机器人会拿错。如果你用行主序存储一个 17 列的矩阵,每行末尾要补上 padding 字节,确保每行首地址对齐。

第二大优化是规避共享内存的 bank 冲突。共享内存被划分成 32 个 bank,同一时钟周期内多个线程访问同一个 bank 就会排队。当多个线程同时读取同一行相邻地址时,如果地址恰好映射到同一个 bank,性能骤降。解决办法是在共享内存数组的每行末尾加一个元素的 padding,让访问“错开”。

第三大优化是双缓冲流水线。原理和食堂的“双灶台”一样:一个灶台在做菜,另一个灶台已经在预热。用两个共享内存缓冲区交替加载和计算,cp.async 异步拷贝让数据搬运与矩阵乘加重叠执行,把访存延迟完全隐藏在计算背后。这样流水线的吞吐公式就变成了两个阶段中较慢者的速度,而不是两者之和。

T总≈max⁡(T加载,T计算)T_{\text{总}} \approx \max(T_{\text{加载}}, T_{\text{计算}})

1. 加载阶段:共享内存缓冲区 $B_0$ 正在从全局内存装载下一块数据。

T加载=全局内存传输时间T_{\text{加载}} = \text{全局内存传输时间}

2. 计算阶段:共享内存缓冲区 $B_1$ 中的数据正被 Tensor Core 消耗计算。

T计算=wmma 乘加执行时间T_{\text{计算}} = \text{wmma 乘加执行时间}

3. 重叠效果:双缓冲让两个阶段并行,总时间由慢者决定。

T总=max⁡(T加载,T计算)T_{\text{总}} = \max(T_{\text{加载}}, T_{\text{计算}})

优化之后,如何确认食堂真的变快了?这就轮到质检员登场了。

调试性能分析

食堂老板想知道“出餐速度到底卡在哪个环节”,光靠感觉不行,得用数据说话。GPU 性能分析工具 Nsight Compute(ncu)就是这位质检员:它会详细记录你的 kernel 在每个硬件单元上的耗时、占用率和吞吐量,告诉你 Tensor Core 的利用率是 90% 还是只有 30%。

使用 ncu 的基本流程是“三问”:第一问,计算吞吐够不够?看 tensor 指令的占用率,如果 Tensor Core 只有 20% 活跃,说明数据供给跟不上,要优化访存或分块策略。第二问,访存效率高不高?看 Memory Throughput 和 L2 Cache Hit Rate,如果全局内存访问频繁且命中率低,说明共享内存复用不够。第三问,SM 占用率合理吗?看 Achieved Occupancy,如果线程块太大或寄存器用太多,SM 上驻留的 warp 数量会减少,延迟隐藏能力变弱。

瓶颈定位有一个经典的“剥洋葱”思路:先用 ncu 的全量报告看大方向(计算密集还是访存密集?),再逐项深入——如果是访存瓶颈,检查是否有未合并访问、bank 冲突和低缓存命中;如果是计算瓶颈,检查 Tensor Core 是否有空转周期。每一步都用数据佐证,而不是凭直觉改代码。

利用率=实际活跃周期理论最大周期×100%\text{利用率} = \frac{\text{实际活跃周期}}{\text{理论最大周期}} \times 100\%

1. 实际活跃周期:ncu 统计出的 Tensor Core 真正执行乘加指令的时钟数。

TactiveT_{\text{active}}

2. 理论最大周期:kernel 运行总时长内 Tensor Core 能执行指令的最大时钟数。

TtotalT_{\text{total}}

3. 利用率计算:两者相除得百分数,低于 50% 说明流水线存在明显“饿肚子”。

Utilization=TactiveTtotal\text{Utilization} = \frac{T_{\text{active}}}{T_{\text{total}}}

质检合格后,终于到了出正式菜单的时候。最后我们把所有技巧打包,看看真实工程中如何应用。

工程应用实践

从单道菜的烹饪技巧到一家餐厅的完整菜单,中间还差一步:标准化。CUTLASS 是 NVIDIA 开源的 GEMM 算子库,它把分块、流水线、混合精度这些技巧固化成模板代码,你只需要声明“我要 FP16 输入、FP32 累加、16×16 tile”,就能自动生成一个高性能矩阵乘算子。就像食堂把配餐流程写成标准作业手册,新员工照着做就能出菜。

在 Transformer 架构中,Tensor Core 的身影无处不在:QKV 投影矩阵乘、注意力分数计算、FFN 两层线性变换,每一步都是 GEMM。以 GPT-3 为例,一次前向推理要执行上千次矩阵乘法,这些运算约占整体计算量的 90% 以上。用 Tensor Core 加速这些 GEMM,推理速度能提升数倍,这也是为什么大模型推理框架(如 TensorRT-LLM、vLLM)深度依赖 Tensor Core。

工程中还有两个实战心得:第一,算子融合——把“矩阵乘 + 偏置 + 激活函数”打包成一个 kernel,减少中间结果的显存读写,相当于在食堂里把“配餐+热菜+装盘”合并成一个工位;第二,版本兼容——不同 GPU 架构(Volta/Turing/Ampere/Hopper)的 Tensor Core 指令集和 wmma API 有差异,编写算子时要用 __CUDA_ARCH__ 宏做分支,确保老卡也能运行。

GEMM:C(M×N)=∑k=1KA(M×K)⋅B(K×N)\text{GEMM}: \quad C_{(M \times N)} = \sum_{k=1}^{K} A_{(M \times K)} \cdot B_{(K \times N)}

1. 输出矩阵:$C$ 的每个元素是 $A$ 的第 $i$ 行与 $B$ 的第 $j$ 列的内积。

Cij=∑k=1KAikBkjC_{ij} = \sum_{k=1}^{K} A_{ik} B_{kj}

2. 计算规模:总乘加次数为 $M \times N \times K$,当 $M=N=K=4096$ 时超 680 亿次运算。

FLOPs=2×M×N×K\text{FLOPs} = 2 \times M \times N \times K

3. 并行分解:所有 $C_{ij}$ 互不依赖,天然可并行——这正是分块交给不同 SM 执行的硬件基础。

C=⋃tilesCtileC = \bigcup_{\text{tiles}} C_{\text{tile}}

从硬件原理到工程实践,Tensor Core 编程的完整链路已经打通。就像一家食堂从采购、切配、压模、出餐到质检的全流程管理,每一步都有章可循。

AI 应用场景

LLM推理引擎中的GEMM加速

阶段:模型部署与推理优化

解决的问题:大模型推理时,QKV 投影和 FFN 层的大量矩阵乘法在普通 CUDA Core 上耗时巨大,导致 token 生成延迟高、吞吐量低,传统优化手写 kernel 难以兼顾效率与通用性。

具体案例:vLLM 和 TensorRT-LLM 在服务 Llama-3-70B 时,利用 Tensor Core 的 FP16/BF16 wmma 算子加速 GEMM。通过将 batch 内多个请求的 KV 计算合并为一个大矩阵乘,配合 Tensor Core 的分块执行,推理吞吐量提升约 3.5 倍,单 token 延迟从 45ms 降至 13ms。

推荐系统Embedding层的矩阵化改造

阶段:特征工程与训练加速

解决的问题:大规模推荐模型的 Embedding 查表后需要与权重矩阵相乘,在 CPU 或普通 GPU 算子下吞吐不足,成为训练与上线服务的性能瓶颈。

具体案例:某广告推荐系统的 M×K 交互矩阵(M=用户数,K=特征维度)通过自定义 CUDA kernel 调用 wmma API,将向量与矩阵乘法组织为 16×16 tile 的批量乘加,配合混合精度(FP16 存储、FP32 累加),整体训练吞吐提升 2.8 倍,GPU 显存占用降低 40%。

科学计算中的GEMM算子库开发

阶段:算子研发与基础库建设

解决的问题:金融风险模拟和流体力学仿真中的稠密线性代数运算在 AI 芯片上的性能表现依赖高效的 GEMM 算子,而通用库(如 cuBLAS)无法覆盖所有定制化的矩阵尺寸和精度需求。

具体案例:某量化团队基于 CUTLASS 和 wmma API 自研了一批支持混合精度(FP16/BF16 输入、FP32 累加)的 GEMM 算子,针对 128×128 到 4096×4096 的矩阵尺寸做模板特化,实测比 cuBLAS 同配置快 12%–18%。

Python 演示

python36 行
import numpy as np  def wmma_simulate(A, B, C, tile=16):    """用分块三重循环模拟 Tensor Core 的 wmma 乘加流程."""    M, K = A.shape    K2, N = B.shape    assert K == K2, "A 的列数必须等于 B 的行数"    for i in range(0, M, tile):        for j in range(0, N, tile):            acc = C[i:i+tile, j:j+tile].astype(np.float32)            for k in range(0, K, tile):                a_tile = A[i:i+tile, k:k+tile].astype(np.float16)                b_tile = B[k:k+tile, j:j+tile].astype(np.float16)                acc += a_tile.astype(np.float32) @ b_tile.astype(np.float32)            C[i:i+tile, j:j+tile] = acc    return C  def main():    M, N, K = 64, 64, 64    rng = np.random.default_rng(42)    A = rng.standard_normal((M, K)).astype(np.float32)    B = rng.standard_normal((K, N)).astype(np.float32)    C = np.zeros((M, N), dtype=np.float32)     C_out = wmma_simulate(A, B, C, tile=16)     C_ref = A.astype(np.float32) @ B.astype(np.float32)    err = np.max(np.abs(C_out.astype(np.float32) - C_ref))    print("最大绝对误差:", err)    print("分块结果与参考一致:", err < 0.5)  if __name__ == "__main__":    main()
目的:用 NumPy 模拟 Tensor Core wmma 的三重分块乘加流程,帮助理解 CUDA 中 fragment 加载、mma_sync 乘加和结果写回的核心逻辑。
  1. for i in range(0, M, tile):外层循环按 tile 步长遍历输出矩阵行方向,对应 CUDA 中线程块在网格中的行划分。
  2. acc = C[i:i+tile, j:j+tile].astype(np.float32)取出输出分块并转为 FP32,模拟 Tensor Core 中累加器 fragment 的精度。
  3. a_tile = A[i:i+tile, k:k+tile].astype(np.float16)将 A 的分块转为 FP16,模拟 wmma 中以半精度加载输入矩阵。
  4. acc += a_tile.astype(np.float32) @ b_tile.astype(np.float32)核心乘加语句:FP16 转 FP32 后相乘累加,模拟 mma_sync 的行为。
  5. C[i:i+tile, j:j+tile] = acc将累加结果写回输出矩阵,对应 store_matrix_sync 函数。

常见误区

误区我之前一直以为 Tensor Core 是更新一代的 CUDA Core,程序里随便写个矩阵乘法它就会自动用上,速度肯定快很多。

正解两者是分工不同的硬件单元:CUDA Core 是通用标量计算核心,擅长控制流和逐元素操作;Tensor Core 是专用矩阵乘加单元,只做 $D=A\times B+C$ 这类稠密矩阵运算。想让 Tensor Core 工作,必须显式调用 wmma API 或依赖 cuBLAS 等库,普通 CUDA 代码默认走的是 CUDA Core。

为何常见:GPU 架构不断迭代,Tensor Core 是较新的特性,容易让人误以为它是 CUDA Core 的替代品,实际上它们是并存的异构单元。

误区我以为只要把矩阵乘法改成 load_matrix_sync 加 mma_sync,性能就能翻倍,结果跑出来反而比普通 CUDA 核函数还慢。

正解wmma 只是提供了使用 Tensor Core 的接口,性能收益取决于分块策略、共享内存复用、访存对齐和流水线隐藏等综合优化。如果数据布局不对齐、分块太小、没有双缓冲,Tensor Core 会大量空转,甚至因为额外的数据搬移开销导致性能回退。

为何常见:看到官方文档的峰值算力数字很震撼,容易忽略“峰值需要特定数据流模式才能达到”这一前提。

误区我一直担心用 FP16 训练模型会把梯度算歪,所以宁可用 FP32,速度慢也认了。

正解Tensor Core 的混合精度方案是“FP16 存储 + FP32 累加”,乘法和累加都在 FP32 下完成,精度损失远小于“全程 FP16”。配合损失缩放(Loss Scaling)技术,可以把梯度下溢问题也解决掉。实际上,大部分现代大模型(GPT、Llama、BERT)都使用混合精度训练,效果与 FP32 基本一致。

为何常见:人们看到“半精度”就联想到“半吊子”,忽略了硬件内部其实做了高精度累加的事实。

误区我写好的 wmma kernel 在 NVIDIA 的卡上都能跑,同学用老显卡跑我的代码应该也没问题。

正解Tensor Core 从 Volta 架构(V100,2017 年)才开始引入,Turing、Ampere、Hopper 等后续架构才有。更早的 Pascal 及之前的 GPU 完全不支持 wmma API。此外,不同架构的 Tensor Core 在形状和精度上也有差异(如 Ampere 支持 TF32、Hopper 支持 FP8),代码中需要用 __CUDA_ARCH__ 做版本判断。

为何常见:多数开发者的本地环境已经是较新的 RTX 系列,容易把“我的卡有”当成“所有卡都有”。

面试题

Q1. Tensor Core 最核心的硬件加速计算是什么?

单选题 · easy

  • A.融合乘加:D=A×B+C← 正确答案
  • B.逐点加法:D=A+B
  • C.纯矩阵乘:D=A×B
  • D.逐点减法:D=A-B

答案:A。Tensor Core 的核心是融合乘加(FMA),在一个时钟周期内完成矩阵乘法和累加。选 B 只做了加法、选 C 只有乘法,都没体现“融合”优势;选 D 与硬件功能完全无关。正如配餐流水线同时完成“配菜+装盘”,融合才能省时间。

Q2. wmma API 中用于描述矩阵分片数据存储的类是什么?

单选题 · mid

  • A.fragment 分片管理类← 正确答案
  • B.matrix 矩阵容器类
  • C.tensor 张量对象类
  • D.thread_block 线程块类

答案:A。wmma 用 fragment 类管理矩阵分片,它负责把一块 tile 的数据分布到 warp 各线程的寄存器中。matrix 和 tensor 是其他库(如 Eigen、ATen)中的概念;thread_block 是线程协作 API,与数据分片无关。fragment 像是配餐时每人托盘里分到的那几份菜。

Q3. Tensor Core 使用 FP16 输入时,累加器默认采用什么精度?

单选题 · easy

  • A.FP32 单精度累加器← 正确答案
  • B.FP16 半精度累加器
  • C.INT8 整型累加器
  • D.BF16 宽半精度累加器

答案:A。Tensor Core 的设计是“FP16 输入、FP32 累加”,保证大矩阵乘加的高精度累积。如果累加也用 FP16,误差会随 K 维度累积而快速放大。INT8 累加用于量化推理场景,BF16 则是另一类低精度格式,但 wmma 默认针对 FP16×FP16+FP32。

Q4. GPU 中矩阵分块(Tiling)的主要目的是什么?

单选题 · mid

  • A.适配固定计算形状← 正确答案
  • B.减少程序代码行数
  • C.让循环嵌套层次更多
  • D.让线程数自动变少

答案:A。分块的核心目的是把大矩阵切成 Tensor Core 硬件模具能处理的固定形状(如 16×16×16),同时提高数据在共享内存中的复用率,减少全局内存访问。减少代码行数和增加循环嵌套是附属结果而非目的;线程数由线程块配置决定,不受分块影响。

Q5. 以下哪些是 wmma API 提供的核心函数?

多选题 · mid

  • A.load_matrix_sync 加载← 正确答案
  • B.mma_sync 乘加同步← 正确答案
  • C.store_matrix_sync 写回← 正确答案
  • D.printf_matrix 打印矩阵

答案:ABC。wmma 三件套包括 load_matrix_sync(加载数据到 fragment)、mma_sync(执行矩阵乘加)、store_matrix_sync(写回结果)。这三个函数对应配餐的“备菜、压模、出餐”流程。printf_matrix 是虚构的,wmma 中不存在此函数。

Q6. wmma 中 “fragment 形状为 16×16×16” 描述的是什么?

单选题 · easy

  • A.每线程片段尺寸形状← 正确答案
  • B.全局内存总容量
  • C.共享内存总大小
  • D.线程束中线程数

答案:A。16×16×16 表示一次 mma_sync 中 A 分片为 16×16、B 分片为 16×16、累加器为 16×16,K 维步长为 16。它描述的是 fragment 的矩阵形状,与全局内存容量和共享内存大小无关;线程束固定 32 线程,不由 fragment 决定。

Q7. 规避共享内存 bank 冲突最常用的办法是什么?

单选题 · mid

  • A.补零填充避免冲突← 正确答案
  • B.增大线程束规模
  • C.改用全局内存存储
  • D.降低 GPU 核心频率

答案:A。在共享内存数组的每行末尾加一个元素的 padding(补零),可以让相邻线程访问的地址错开映射到不同 bank,从而消除冲突。增大线程束规模无法改变 bank 映射关系;改用全局内存只会更慢;降低频率是开倒车。

Q8. 下列哪些属于 Tensor Core 算子的有效性能优化手段?

多选题 · hard

  • A.双缓冲重叠隐藏延迟← 正确答案
  • B.共享内存按行长对齐← 正确答案
  • C.增加不必要的循环层数
  • D.降低累加器计算精度

答案:AB。双缓冲让数据加载与计算重叠,隐藏访存延迟;按行长对齐(16B 对齐)保证 wmma 访存指令不越界、不拆分。增加循环层数只会增加开销;降低累加器精度会引入误差,且 Tensor Core 不提供“低精度累加”选项。

Q9. 相比 CUDA Core,Tensor Core 最显著的优势是什么?

单选题 · hard

  • A.矩阵乘加吞吐更高← 正确答案
  • B.标量加法速度更快
  • C.分支预测能力更强
  • D.寄存器文件容量更大

答案:A。Tensor Core 的硬件设计目标就是让矩阵乘加吞吐量达到 CUDA Core 的数倍乃至一个数量级,代价是失去通用性。标量加法和分支预测是 CUDA Core 的强项;寄存器文件容量由架构统一设计,与 Tensor Core 无关。

Q10. 混合精度训练中,下列哪些做法是正确的?

多选题 · hard

  • A.损失缩放保尾数精度← 正确答案
  • B.主权重保持FP32格式← 正确答案
  • C.全流程都只使用FP16
  • D.梯度累积用FP16变量

答案:AB。损失缩放(Loss Scaling)把小的梯度值放大到 FP16 可表示范围,避免下溢;主权重保持 FP32 是为了在参数更新时保留高精度累积。“全流程 FP16”和“梯度累积用 FP16”都会导致精度崩溃,是混合精度训练中最常见的错误做法。