想象你经营着一家中央厨房,晚高峰要出三千份同样的套餐。你的后厨有一群全能厨师(也就是普通GPU计算核心),每个人都很勤快:接单、取食材、配菜、下锅、装盒,一步不落。可问题来了——每份套餐都要从货架上取“同一排”食材,三千份套餐重复取料,厨师的手都断了,饭还没出完。 这时候工业工程师提出一个点子:与其让全能厨师逐份做,不如在厨房中间架一条预制菜流水线:把食材按批倒进去,几个工位同时配合,一次端出十六份、六十四份套餐。这条流水线,就是GPU里的Tensor Core。 Tensor Core是GPU中专门执行“矩阵乘加”运算的硬件单元,一条指令就能算完一小块矩阵乘法。不过,再好的流水线也要有人指挥:什么时候备料、什么时候开机器、什么时候出货——这套指挥口令,就是WMMA编程接口。本文就带你走进这家“矩阵速算中央厨房”,从硬件原理到完整实现,把Tensor Core矩阵乘加速的门道一次讲透。

岗位关联

学会Tensor Core矩阵乘加速,你就掌握了现代GPU性能调优的“硬通货”——大模型训练、推理加速、科学计算的性能瓶颈,都从这里打开缺口。对AI从业者而言,这是把“懂模型”升级为“懂算力”的关键一跃。

目录
  1. 可视化演示
  2. GPU算力瓶颈
  3. Tensor Core原理
  4. WMMA编程模型
  5. 片段加载存储
  6. MMA矩阵乘加
  7. 混合精度策略
  8. 性能对比分析
  9. 实战注意事项
  10. AI 应用场景
  11. Python 演示
  12. 常见误区
  13. 面试题

可视化演示

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

GPU算力瓶颈

晚高峰的中央厨房里,全能厨师们忙得脚不沾地。每份套餐都要从食材架上取“第 i 排食材”和“第 j 列调料”,一一配对、下锅、装盒。厨师确实什么都会,但问题是:三千份套餐的食材高度重复,他们却只能一份一份地做,货架前人挤人,效率上不去。

在GPU世界里,这种“大量重复配对再累加”的活儿,就是通用矩阵乘法(GEMM,General Matrix Multiply)。设输入矩阵 A 是 M×K,B 是 K×N,输出 C 是 M×N。C 的每一个元素,都对应 A 的一整行与 B 的一整列逐对相乘再累加:

看一眼公式就会发现:当计算相邻的 c_ij 时,它们共用同一行 A 的数据。这种“数据重用”在矩阵乘法里到处都是,可普通CUDA核心每次只算一个标量乘加,搬运量远大于计算量。整个后厨忙得冒烟,货架通道却堵成瓶颈——这正是专用硬件登场的理由。

工程师的思路是:既然订单高度重复,何不设计一条“批量配料”的专用流水线?于是,GPU里不再只有全能厨师,还多了专门处理“一锅出多份”的Tensor Core。通用核心负责灵活复杂的杂活,重活累活交给流水线。矩阵乘法这条主菜,从此有了专门的产线。

cij=∑k=0K−1aikbkj\displaystyle c_{ij}=\sum_{k=0}^{K-1} a_{ik} b_{kj}
  1. 输出元素 c_ij结果矩阵 C 第 i 行第 j 列的那个元素,相当于一份“套餐订单”。
    cij\displaystyle c_{ij}
  2. 配对相乘 a_ik × b_kjA 的第 i 行第 k 个元素,与 B 的第 k 行第 j 个元素相乘,就像取一份食材配一份调料。
    aikbkj\displaystyle a_{ik} b_{kj}
  3. K 项累加求和把 k 从 0 到 K-1 的所有配对乘积累加起来,才得到这一份套餐的完整味道。
    ∑k=0K−1\displaystyle \sum_{k=0}^{K-1}

Tensor Core原理

这条预制菜流水线长什么样?它不像全能厨师那样什么菜都会做,它只做一件事:拿一批半成品原料 A 和 B,倒进已经铺好底料 C 的餐盒里,一次拌出成品 D。用数学语言说,它一条指令完成的就是“矩阵乘加”:

硬件结构上,Tensor Core是挂在GPU流式多处理器(SM)内部的一组专用矩阵单元。从Volta架构开始引入,每个时钟周期能完成一个4×4×4的矩阵乘加运算。它不抢全能厨师的活,而是和CUDA核心并排工作:遇到GEMM这种大批量订单,调度器就把活儿派给Tensor Core。

Tensor Core支持的数据类型很有讲究:输入A、B常用FP16(半精度),而累加器C和输出D用FP32(单精度)。这就像流水线用“浓缩高汤”快速提味,但锅底始终是精炖的老汤兜底——既快又不至于让味道垮掉。理解了这条流水线,下一步就是学会怎么给它下指令。

D=A×B+C\displaystyle D = A \times B + C
  1. A 和 B两个输入小矩阵块,通常用FP16半精度存储,相当于流水线入口的两筐半成品食材。
    A,B\displaystyle A, B
  2. × 批量乘加不是普通标量乘法,而是把 A 和 B 的多个元素成批配对、相乘并累加,一个周期完成一小块矩阵乘法。
    A×B\displaystyle A \times B
  3. + C 累加器C 是之前已经算好的结果块,新结果直接叠加在其上,方便做大规模分块累加。
    +C\displaystyle +C
  4. D 输出矩阵块流水线出口端出的成品,一块完整的输出小矩阵。
    D\displaystyle D

WMMA编程模型

流水线再好,也要有人下派工单。在CUDA C++里,这套派工单就是WMMA(Warp Matrix Multiply-Accumulate)API。它把“指挥Tensor Core干活”包装成了三个标准动作:把原料搬上操作台、启动流水线、把成品搬到出货口。

一个WMMA操作永远由一个warp(32个线程的编队)协同完成。你可以把这32个线程想成流水线旁的32名工人,每人手里端着一部分托盘(寄存器),大家拼在一起才是一整块16×16的输出片段。每个工人端多少呢?有固定公式:

WMMA的编程流程清晰固定:第一步用 wmma::fragment 声明A、B、累加器三种片段;第二步用 load_matrix_sync 把内存数据加载进fragment;第三步调用 mma_sync 执行矩阵乘加;第四步用 store_matrix_sync 把结果写回内存;最后别忘了给累加器清零。整个过程就像填一张标准派工单:物料在哪、做什么动作、成品放哪,写得明明白白。

fragment是理解WMMA的关键——它不是什么神秘缓存,而是“每个线程手里那几格托盘”。A片段、B片段、累加器片段各归各的,由编译器自动分配到寄存器里。派工单下对了,流水线就按部就班地转起来。

e=16×1632=8\displaystyle e = \frac{16 \times 16}{32} = 8
  1. 16×16 输出块WMMA标准操作一次处理一个16行16列的输出矩阵块。
    16×16\displaystyle 16 \times 16
  2. 除以 32 个线程这个输出块由 warp 的 32 个线程共同分担。
    /32\displaystyle /32
  3. 每线程 8 个元素算下来每个线程的寄存器片段里保存8个输出元素,这就是fragment片段的最小单元。
    e=8\displaystyle e = 8

片段加载存储

派工单写好了,备料组就要动起来。load_matrix_sync 负责把食材从大仓库搬到操作台——也就是把矩阵数据从全局内存或共享内存,搬运到线程的寄存器片段里。这一步是流水线的“进料口”,搬得快不快直接影响整条产线的节奏。

加载函数长这样:wmma::load_matrix_sync(frag, ptr, ldm)。第一个参数是目标片段,第二个是内存指针,第三个 ldm 是leading dimension(矩阵主维度的跨度)。它告诉备料员:食材在货架上怎么排列,每排之间隔多远。内存布局还有讲究:A矩阵常用 row_major(行优先),也可用 col_major(列优先),选错了整个配方都会乱套。

存储方向相反,store_matrix_sync 把操作台上做好的成品片段搬回出货区(内存)。这里同样要指定布局和跨度。数据对齐是隐藏的加速关键——片段加载要求指针按16字节对齐,相当于要求货架上的每一排食材必须整整齐齐码好,叉车才能一次叉走一整排。对齐不好,备料员就得绕路,流水线跟着干等。

备料和出货是流水线的两条腿。前期数据搬得好,后面的计算才能全速运转;搬得乱七八糟,Tensor Core再快也只能饿着肚子等料。所以实战中经常用共享内存做中转站:先从显存批量拉一大块数据进共享内存,再让Tensor Core从共享内存快速取材,减少重复走仓库的远路。

MMA矩阵乘加

流水线最核心的一下,就是 mma_sync 这条指令。在CUDA C++里写作 wmma::mma_sync(acc_frag, a_frag, b_frag, acc_frag),它把A片段和B片段做矩阵乘,结果叠加到累加器片段上。这一条指令,就是Tensor Core真正干活的动作。

底层对应PTX指令 mma.sync.aligned.m16n8k16.row.col.f32.f16.f16.f32。名字里的数字就是流水线的加工规格:m=16行输出,n=8列输出,k=16的内维累加长度。一次加工包含多少次运算呢?

寄存器管理是这一节的技术深水区。每个线程的fragment元素数量是编译期确定的,编译器会把它们分别安排到具体寄存器编号上。计算时数据就在寄存器里,不需要反复进出内存——这正是Tensor Core快的一个原因:物料始终在手边。

实战中的计算流水线技巧是:加载第 i+1 块数据的同时,计算第 i 块。就像流水线工位不停机,备料员在前一站备下一批,加工位同时处理当前这批。这种重叠(overlap)能有效隐藏访存延迟,让Tensor Core的吞吐一直饱满。

FLOPs=2×16×8×16=4096\displaystyle FLOPs = 2 \times 16 \times 8 \times 16 = 4096
  1. 16×8×16 乘加次数输出块16×8,每个元素累加16次,一共2048次乘加。
    16×8×16\displaystyle 16 \times 8 \times 16
  2. 乘以 2一次乘加包含一次乘法加一次加法,按浮点运算数FLOPs计要算2次。
    ×2\displaystyle \times 2
  3. 4096 FLOPs一条指令完成4096次浮点操作,而普通CUDA核心一条指令只做1次乘加。
    =4096\displaystyle =4096

混合精度策略

流水线要提速,最快的办法是“少炖一会儿”。FP16半精度就是这种思路:用浓缩高汤快速提味,锅底仍然用FP32老汤兜底。但浓缩高汤有个致命弱点——它的动态范围小,一旦遇到味道很淡的原料(数值很小的梯度),直接就“尝不出来”了,这在深度学习训练里意味着梯度下溢成零。

Tensor Core的混合精度计算标准姿势是:A、B用FP16输入,累加器C和输出D用FP32。这样既享受了半精度的高速运算,又保留了累加过程的数值稳定性。NVIDIA从Ampere架构起还提供了TF32模式:用19位近似精度模拟FP32输入,不用改代码就能让老模型吃到Tensor Core的红利,精度损失却比FP16小得多。

对于训练中最怕的梯度下溢问题,业界有个标准解法叫“损失缩放”(loss scaling)。做法很简单:像厨师给高汤加盐提味一样,把梯度整体乘一个大因子,让它落入FP16能表示的范围:

混合精度不是“偷工减料”,而是“精准地省”。输入用半成品高汤加速,累加用老汤保底,梯度用盐放大防丢味。这套组合拳让Tensor Core既能跑得快,又不会让模型精度崩盘。实际工程中,PyTorch的AMP(自动混合精度)模块已经把这套策略封装好,一句 torch.cuda.amp 就能让训练速度接近翻倍。

y~=s×y\displaystyle \tilde{y} = s \times y
  1. y 原始梯度反向传播算出来的梯度值,可能小到1e-7甚至更小。
    y\displaystyle y
  2. s 缩放因子一个较大的常数,常用65536,把数值整体放大。
    s\displaystyle s
  3. ÿ 缩放后梯度放大后的梯度在FP16范围内可正常表示,参与矩阵乘加。
    y~\displaystyle \tilde{y}
  4. 训练后还原更新权重前把梯度除以 s,恢复真实尺度再更新参数。
    y~s\displaystyle \frac{\tilde{y}}{s}

性能对比分析

流水线建好了,到底快了多少?不能靠感觉,得掐表比赛。同一份GEMM订单,让CUDA核心产线和Tensor Core产线各做一遍,记录各自耗时。为了避免偶然性,比赛规则是:同规模矩阵、同数据分布、跑多次取中位数。

衡量加速效果的标准公式:

除了加速比,工业界更常用“吞吐量”来评估产线能力,也就是每秒能完成多少次浮点运算。GEMM的浮点运算总数是 2×M×N×K,除以耗时,就得到TFLOPS:

实测效果如何?在V100上,FP16 Tensor Core相比FP32 CUDA核心的GEMM,通常能拿到3到5倍的加速;到了A100,TF32模式也能稳定翻倍以上。但要注意:加速比不是固定的,矩阵规模越大、分块越合理,优势越明显。小矩阵反而可能因为搬运开销占比大,加速不明显。所以做性能对比时,必须同规模、同条件、取中位数,这才是一份可信的“体检报告”。

Speedup=TbaselineTtensor\displaystyle Speedup = \frac{T_{baseline}}{T_{tensor}}
  1. 2MNK 总运算量M×N个输出元素,每个累加K次,每次乘加算2次浮点操作。
    2MNK\displaystyle 2 M N K
  2. t 耗时秒数用计时器测得的实际运行时间。
    t\displaystyle t
  3. 除以 10^12把每秒浮点操作数换算成“万亿次每秒”(TFLOPS)。
    ×1012\displaystyle \times 10^{12}

实战注意事项

流水线运营久了,各种坑就出来了。第一个坑是“食材堆不下”:Tensor Core每次只处理16×16的小块,但大矩阵动辄上千维,必须把大矩阵切成一个个小块(Tiling),让数据在共享内存里循环往复地复用。块开太小,复用不足;块开太大,共享内存和寄存器塞不下,反而拖慢速度。

第二个坑是共享内存的“货架冲突”(Bank Conflict)。共享内存被硬件分成32个bank,32个线程同时取料时,如果两个线程撞到同一个bank的不同格子,就只能排队等待。解决办法通常是“错峰”:在分配共享内存数组时多垫一行数据,让相邻线程访问不同bank,冲突自然消散。

第三个坑是“机器空转”:Tensor Core计算很快,如果数据还没搬到位,流水线就要干等。解决办法是软件流水线——提前把下一块数据预取到寄存器,算当前块的同时加载下一块,让访存和计算重叠起来。跟前文说的“备料下一批、同时加工当前批”是一个道理。

最后一个建议是调试技巧:先跑一个小矩阵(比如64×64),用CPU结果做对照,验证每个fragment的加载、计算、存储都正确,再放大规模。很多初学者一上来就调大矩阵,结果错了都不知道错在哪一步。流水线运营的智慧,从来都是先小批试产,确认配方无误,再全速开工。

AI 应用场景

大模型训练中的自动混合精度

阶段:模型训练加速

解决的问题:训练GPT等大模型时,线性层和注意力矩阵乘占据绝大部分计算量。传统FP32训练让CUDA核心逐个计算,GPU利用率低、训练周期长。借助Tensor Core + AMP,模型自动把能安全降为FP16的算子交给Tensor Core,反向传播时用损失缩放保护小梯度。

具体案例:某团队微调70亿参数大模型,原先一个训练step需1.8秒。开启AMP后,线性层GEMM落到Tensor Core上,step时间降到0.6秒,训练速度提升约3倍,而验证集精度几乎没有下降。

推理服务中的批量解码加速

阶段:推理部署与优化

解决的问题:LLM生成token时,每个step都要做大矩阵乘。传统逐token计算无法充分利用Tensor Core的批量计算能力。通过连续批处理(continuous batching)把多个请求的GEMM拼成大块矩阵,一次性交给Tensor Core处理。

具体案例:某AI应用每天处理百万次对话请求。使用Tensor Core优化的推理引擎后,单卡吞吐从每秒120个token提升到480个token,延迟反而降低,GPU资源占用直接减半。

科学计算中的cuBLAS高性能GEMM

阶段:高性能计算库调用

解决的问题:材料模拟、流体力学、金融风控等场景大量依赖稠密线性代数计算。手写的矩阵乘在GPU上很难跑出峰值性能,而NVIDIA cuBLAS库底层已针对Tensor Core做了深度优化。

具体案例:某气候模拟项目把核心的矩阵乘从自研CUDA代码切换到cuBLAS的TF32模式,在A100上获得2.3倍加速,同时保持模拟结果在可接受误差范围内。

Python 演示

python55 行
import numpy as npimport time  def naive_matmul(A, B):    """逐元素三重循环,模拟普通CUDA核心逐个乘加。"""    M, K = A.shape    K2, N = B.shape    assert K == K2    C = np.zeros((M, N), dtype=np.float32)    for i in range(M):        for j in range(N):            total = 0.0            for k in range(K):                total += A[i, k] * B[k, j]            C[i, j] = total    return C  def tensor_core_sim(A, B, block=16):    """按 block 分块,每块用矩阵乘法一次算完,模拟 WMMA 片段流程。"""    M, K = A.shape    K2, N = B.shape    assert K == K2    C = np.zeros((M, N), dtype=np.float32)    for i in range(0, M, block):        for j in range(0, N, block):            acc = np.zeros((block, block), dtype=np.float32)            for k in range(0, K, block):                a_frag = A[i:i + block, k:k + block]                b_frag = B[k:k + block, j:j + block]                # 模拟一条 MMA 指令: D = A * B + C                acc += a_frag @ b_frag            C[i:i + block, j:j + block] = acc    return C  def benchmark(M, N, K):    rng = np.random.default_rng(42)    A = rng.random((M, K)).astype(np.float32)    B = rng.random((K, N)).astype(np.float32)    t0 = time.perf_counter()    C_ref = naive_matmul(A, B)    t1 = time.perf_counter()    C_tc = tensor_core_sim(A, B)    t2 = time.perf_counter()    err = np.max(np.abs(C_ref - C_tc))    print(f"M={M} N={N} K={K}  最大误差={err:.2e}")    print(f"逐元素耗时={t1 - t0:.4f}s  "          f"分块模拟TC耗时={t2 - t1:.4f}s")    print(f"加速比={(t1 - t0) / (t2 - t1):.2f}x")  if __name__ == "__main__":    benchmark(128, 128, 128)
目的:演示“批量小块矩阵乘”相比“逐元素三重循环”的加速原理——这正是Tensor Core提升GEMM性能的核心思想:把大量标量运算打包成少量矩阵块指令。
  1. assert K == K2检查矩阵内维匹配,确保A的列数等于B的行数,否则无法相乘。
  2. C = np.zeros((M, N), dtype=np.float32)初始化输出矩阵,float32作为累加器,模拟Tensor Core的FP32累加策略。
  3. for i in range(0, M, block)按块遍历M维,相当于在输出矩阵上做Tiling分块。
  4. acc = np.zeros((block, block), dtype=np.float32)每个输出块的累加器清零,对应WMMA的accumulator fragment。
  5. acc += a_frag @ b_frag块矩阵乘法并累加,模拟一条MMA指令完成 D = A×B + C。
  6. C[i:i + block, j:j + block] = acc把完成的小块写回输出矩阵,对应 store_matrix_sync 存储片段。

常见误区

误区我一直以为只要代码跑在Volta以后的GPU上,Tensor Core就会自动帮我加速,旧CUDA代码什么都不用改就能变快。

正解Tensor Core必须通过WMMA API或PTX指令显式调用,并且要把数据组织成16×16×16的分块、指定内存布局,普通标量CUDA代码根本不会触发它。就好比厨房里装了预制菜流水线,但厨师还是按老方法一份份做,流水线全程闲置。

为何常见:厂商宣传总说“GPU算力翻倍”,很容易让人误以为硬件能力会自动普惠所有代码,忽略了专用硬件需要专用编程模型来驱动。

误区FP16只有10位尾数,算出来的矩阵乘结果肯定全是噪声,模型精度一定会崩。

正解Tensor Core虽然输入是FP16,但累加器是FP32,中间累加过程并没有降精度。配合损失缩放、TF32混合精度方案,绝大多数模型训练和推理的精度损失都可以忽略,速度却能提升数倍。

为何常见:大家习惯把“输入精度”和“计算精度”混为一谈,以为全程都是FP16,忽略了硬件实际上用FP32承接了累加这条“精度底线”。

误区WMMA里的fragment片段应该是一块共享内存,所有线程都能直接看到和访问。

正解fragment是寄存器中的私有切片,由warp内32个线程按规则各持一部分,warp协同才能拼成完整片段。共享内存只是数据搬运的中转站,不是片段的存放地。

为何常见:CUDA编程里共享内存是讲得最多的协作数据空间,看到“片段”这种集体协作概念,自然容易往共享内存上靠。

误区Tile开得越大,数据复用越多,Tensor Core肯定跑得越快,所以32×32不够,直接上128×128。

正解块大会撑爆共享内存和寄存器。寄存器溢出会让数据被挤到本地内存,速度反而暴跌。分块尺寸需要在“数据复用”和“硬件资源占用”之间找平衡,通常结合矩阵规模和硬件规格专门调优。

为何常见:直觉上“复用多=更快”没错,但忽略了硬件资源有上限,一旦越过资源红线就会引发隐性降速。

误区既然是NVIDIA的“标配技术”,那我的旧显卡、入门显卡应该都有Tensor Core吧?

正解Tensor Core从Volta架构才引入,Pascal及更早的架构完全没有;消费级、专业级、服务器级GPU的Tensor Core数量也差异巨大。开发前必须查目标硬件的算力版本和规格表。

为何常见:教程和新闻里反复出现Tensor Core,给人一种“新显卡都有”的错觉,忽略了架构分代和产品线差异。

面试题

Q1. Tensor Core 首次出现在哪代 GPU 架构中?

单选题 · easy

  • A.Volta 架构首次引入← 正确答案
  • B.Turing 架构首次引入
  • C.Ampere 架构首次引入
  • D.Pascal 架构首次引入

答案:A。NVIDIA在2017年的Volta架构(如V100)上首次引入Tensor Core。Turing和Ampere在它之后,Pascal在它之前。好比预制菜流水线是Volta这家“厨房”第一次装的,后续厨房只是升级换代。

Q2. 矩阵乘法 C=A×B 中,单个元素 c_ij 需要对 A 第 i 行与 B 第 j 列的多少对元素做乘加?

单选题 · mid

  • A.恰好 M 对元素乘加
  • B.恰好 N 对元素乘加
  • C.恰好 K 对元素乘加← 正确答案
  • D.恰好 M×N 对元素乘加

答案:C。c_ij 的公式是对 k 从 0 到 K-1 累加 a_ik × b_kj,K 是 A 的列数也是 B 的行数(内维)。内维长度决定累加次数,M、N 只是矩阵外形尺寸,不参与单个元素的累加配对。

Q3. WMMA 标准 16×16×16 操作由多少个线程协作完成?

单选题 · mid

  • A.16 个线程协同完成
  • B.32 个线程协同完成← 正确答案
  • C.64 个线程协同完成
  • D.128 个线程协同完成

答案:B。WMMA 以一个 warp 为单位执行,而一个 warp 固定包含 32 个线程。这32人各自端着寄存器片段,拼成一整块16×16输出。好比流水线一条工位的32名工人集体协作,少一个人托盘就拼不齐。

Q4. 关于 Tensor Core 混合精度计算,以下说法正确的是?

多选题 · mid

  • A.FP16 输入且用 FP16 累加
  • B.FP16 输入且用 FP32 累加← 正确答案
  • C.TF32 可近似替代 FP32 输入← 正确答案
  • D.Tensor Core 累加精度低于任何情况

答案:BC。Tensor Core 标准姿势是 FP16 输入、FP32 累加(B 对);TF32 是 Ampere 起用19位近似精度模拟 FP32 输入(C 对)。A 错在累加器实际是 FP32;D 错得绝对,FP32 累加保证了中间精度底线。

Q5. PTX 指令 mma.sync.aligned.m16n8k16 一条指令包含多少次乘加运算?

单选题 · hard

  • A.16×8×16 共 2048 次← 正确答案
  • B.16×8×16×2 共 4096 次
  • C.16×16×8 共 2048 次
  • D.16×16×16 共 4096 次

答案:A。mma 指令的命名代表 m16n8k16:输出 16×8,内维 K=16,因此乘加(MAC)次数是16×8×16=2048。选项 B 是浮点运算数 FLOPs(乘加计2次),题干问的是乘加次数本身,两者不要混淆。

Q6. WMMA 的 load_matrix_sync 负责把数据从哪搬到哪?

单选题 · easy

  • A.从显存搬入共享内存
  • B.从内存搬入寄存器片段← 正确答案
  • C.从寄存器搬出到显存
  • D.从共享内存搬回显存

答案:B。load_matrix_sync 的语义是“加载”,把全局/共享内存中的矩阵数据装入 warp 线程各自持有的 fragment(寄存器)中。它是流水线的进料口,方向永远是外部内存→寄存器片段,反向操作由 store_matrix_sync 负责。

Q7. 关于 WMMA 中的 fragment 片段,正确的描述是?

多选题 · mid

  • A.片段数据存放在共享内存中
  • B.片段由 warp 内各线程分别持有← 正确答案
  • C.三种片段对应 A/B/累加器矩阵← 正确答案
  • D.加载片段时需要指定内存序← 正确答案

答案:BCD。fragment 是寄存器上的私有切片,由 warp 内线程按规则各持数份(B 对);WMMA 用三种类型分别承载 A、B、累加器(C 对);load_matrix_sync 必须指定 row_major 或 col_major(D 对)。A 错,片段不在共享内存,共享内存只是中转站。

Q8. Tensor Core 矩阵乘加速中,分块 Tiling 的主要目的是什么?

单选题 · easy

  • A.减少寄存器总数占用
  • B.提升数据复用和访存效率← 正确答案
  • C.降低乘法的算法复杂度
  • D.让每个线程独立计算一整块

答案:B。Tiling 把大矩阵切成小块,让数据块在共享内存中被反复重用,减少从显存重复搬运,提升访存效率。它不改变算法复杂度(仍是 O(MNK)),也不会减少寄存器占用,而是用空间换数据复用。

Q9. FP16 训练中的损失缩放(loss scaling)主要解决什么问题?

单选题 · hard

  • A.避免矩阵乘法中指数溢出
  • B.防止梯度值在 FP16 下溢为零← 正确答案
  • C.将模型参数整体扩大两倍
  • D.提升激活函数的输出范围

答案:B。FP16 动态范围小,反向传播产生的梯度经常小到 FP16 无法表示,直接下溢成0。损失缩放把梯度整体乘一个大因子送入可表示范围,更新前再除回去。它防的是“小到下溢”,不是“大到溢出”。

Q10. 关于 Tensor Core 实战调优,以下做法正确的是?

多选题 · hard

  • A.保证 K 维是 16 的倍数← 正确答案
  • B.共享内存布局避开 Bank 冲突← 正确答案
  • C.用软件流水线隐藏访存延迟← 正确答案
  • D.让每个线程独占整个矩阵块

答案:ABC。K 维与 MMA 形状对齐才能避免边界浪费(A 对);共享内存错峰访问防止线程排队(B 对);预先加载下一块数据、让访存与计算重叠(C 对)。D 错,WMMA 强调 warp 协作,单个线程无法独立完成整块矩阵乘。