本站内容由 AI 生成,可能存在错误。如发现问题,欢迎到 GitHub Issues 反馈。

矩阵加速单元 — Tensor Core 与 XMX

矩阵加速单元 — Tensor Core 与 XMX

更新于 2026-04-03

GPU Architecture 文章中,我们了解了 SM 内部的 Processing Block 结构 — 每个 Block 有 Warp Scheduler、FP32 CUDA Core、INT32 Core 和一个 Tensor Core

那篇文章中 Tensor Core 只是一个名字。本文要回答:它内部到底是什么,为什么能让矩阵乘法快一个数量级。同时,我们会深入 Intel 的对应技术 — XMX (Xe Matrix eXtensions),理解两大阵营如何用不同的硬件实现相同的目标。

Section 1: 为什么需要专用矩阵单元

传统 CUDA Core 做矩阵乘法是逐元素标量乘加。以一个 4×4 矩阵乘为例:

  • C(4×4) = A(4×4) × B(4×4)
  • 每个输出元素需要 4 次乘法 + 3 次加法 = 7 次标量操作
  • 16 个输出元素 × 7 = 112 次标量操作

而 Tensor Core 一条 MMA (Matrix Multiply-Accumulate) 指令就完成整块矩阵乘累加 D = A·B + C。

矩阵乘法 C = A × B (4×4)
目标: 计算 C(4×4) = A(4×4) × B(4×4) — 需要多少次操作?A (4×4)1230012330122301×B (4×4)1010010111000011=C (4×4)每个输出元素 C[i][j] = 4 次乘法 + 3 次加法 = 7 次操作16 个输出元素 × 7 = 112 次标量操作(实际为 64 次乘法 + 48 次加法)CUDA Core: 逐个标量操作 → 112 次 | Tensor Core: 一条 MMA 指令 → 1 次

吞吐量差距是数量级的:H100 SXM 上 FP32 CUDA Core 约 67 TFLOPS,FP16 Tensor Core 约 990 TFLOPS — 约 15 倍差距

AI 训练和推理中 90%+ 的计算量是矩阵乘法(QKV 投影、Attention score、FFN 全是 GEMM)。这就是为什么专用矩阵加速单元如此重要 — 它直接决定了 AI 工作负载的性能天花板。

那么 Tensor Core 和 XMX 内部是怎么做到一拍完成矩阵乘的?答案是 Systolic Array(脉动阵列)


Section 2: Systolic Array — 数据在阵列中脉动流动

基本概念

Systolic Array 是一种由大量简单 PE (Processing Element) 组成的计算网格。核心思想:

  • 数据从边缘流入,沿固定方向在 PE 之间传递
  • 每个 PE 执行一次 Multiply-Accumulate (MAC),然后将数据传给邻居
  • 数据被多个 PE 复用 — 一个输入元素经过多个 PE,参与多次计算
  • 不需要每次都从内存读取 — 极高的数据复用率是效率的关键

这个名字来自 “systole”(心脏收缩),形容数据像血液一样有节奏地在阵列中脉动流动。

4×4 脉动阵列动画

下面的动画展示一个 4×4 output-stationary systolic array 的工作过程。A 矩阵的行从左侧流入,B 矩阵的列从顶部流入,每个 PE 累加自己负责的输出元素。

初始状态
Output-Stationary Systolic Array (4×4)A (输入)2101131001211013×B (权重)1021210003121001=C (输出)5143763337254336每个 PE 计算输出矩阵的一个元素。A 从左侧逐行流入,B 从顶部逐列流入输入按行/列索引错开(stagger),保证同一 k 的 A[i][k] 和 B[k][j] 同时到达 PE(i,j)PE(i,j) 在 cycle t = i+j+k 时处理第 k 对输入 → 总共需要 10 个 cycle (0~9)

关键观察:

  • 波前对角线 — 活跃 PE 像波浪一样从左上角扫到右下角
  • PE(i,j) 在 cycle i+j+k 时处理第 k 对输入 — 输入的错开保证数据同时到达
  • 4×4 矩阵乘需要 10 个 cycle(而非 1 个),但硬件可以流水线化多组矩阵

为什么不直接用 CUDA Core 并行?

看到”10 个 cycle”你可能会问:如果开 16 个 CUDA Core,每个算 C 的一个元素(4 次 MAC),不是只要 4 个 cycle 就完成了?延迟更低,为什么还需要 systolic array?

Systolic array 的优势不在延迟,而在内存访问量面积能效

内存访问量对比(4×4 矩阵乘):

方案计算方式内存读取次数
16 个 CUDA Core 并行每个线程读 A 的一行(4 个)+ B 的一列(4 个)16 × 8 = 128 次
4×4 Systolic ArrayA 的每个元素从左侧流入,流过 4 个 PE 被复用;B 同理16 + 16 = 32 次

A 和 B 总共只有 32 个不同的元素,但 16 个 CUDA Core 各自独立读取,产生了 4 倍的冗余访问。

你可能会想到:用 shared memory 缓存 A 和 B 的 tile,让线程从 shared memory 读取而不是从 global memory 重复读。这确实是 GEMM 优化的标准做法,但它引入了额外开销:

// 阶段 1:所有线程协作加载 tile 到 shared memory
shared_A[ty][tx] = A[row][tx];
shared_B[ty][tx] = B[ty][col];
__syncthreads();   // ← barrier 同步

// 阶段 2:从 shared memory 读取计算
sum += shared_A[ty][k] * shared_B[k][tx];

__syncthreads() 是一个 barrier(屏障) — thread block 内的所有线程必须都执行到这一行才能继续。为什么需要它?因为实际 GEMM 中一个 thread block 远不止 16 个线程 — 典型配置是 256 个线程,分布在 8 个 warp 上。同一 warp 内的 32 个线程锁步执行,但不同 warp 之间由 warp scheduler 独立调度,执行进度完全可能不同。Warp 0 的线程可能需要读取 Warp 3 写入 shared memory 的数据,而 Warp 3 可能还没执行到写入那一行。barrier 确保所有 warp 都完成写入后才开始读取。

即使用了 shared memory + barrier,所有线程仍然需要大量 shared memory 读取(只是从 global memory 变成了 shared memory),加上 barrier 等待的同步开销。

Systolic array 完全不需要这些 — 数据按硬件固定的时序在相邻 PE 间传递,复用是物理上天然发生的,没有同步指令、没有 shared memory 访问。

关于 thread block 与 warp 的关系、shared memory 和寄存器的作用域、__syncthreads() 的工作原理等编程模型细节,参见 GPU Architecture — 硬件概念 vs 软件抽象CUDA 编程模型

面积与能效:

CUDA CoreSystolic Array PE
组成取指、译码、寄存器堆、ALU、分支…一个 MAC 单元 + 寄存器
能力通用:可以执行任意指令专用:只做乘累加
单元面积小(同面积可放更多)
数据搬运能耗shared memory 访问 ~5 pJ相邻 PE 寄存器传递 ~0.5 pJ

同样的芯片面积下,systolic array 可以塞进更多 MAC 单元,并用更少的带宽和能耗喂饱它们。这就是为什么 Tensor Core 吞吐量能达到 CUDA Core 的约 15 倍 — 不是因为单次操作更快,而是因为专用硬件在面积和带宽上都更高效。

4×4 看起来差距不大,但放到实际尺寸(如 4096×4096 的 GEMM):naive 并行方案的冗余内存访问量与矩阵维度成正比增长,带宽瓶颈会成为决定性限制。

Dataflow 变体

Systolic array 有多种数据流模式,区别在于哪个矩阵”固定”在 PE 内,哪个”流动”

Output-StationaryC 留在 PE,A 和 B 流动PEC[i][j]PEC[i][j]PEC[i][j]PEC[i][j]PEC[i][j]PEC[i][j]PEC[i][j]PEC[i][j]PEC[i][j]A 行 →B 列 ↓固定: 输出矩阵 C部分和 C 留在 PE 中累加,A/B 流过Weight-StationaryB 预加载到 PE,A 流动,部分和传递PEB[i][j]PEB[i][j]PEB[i][j]PEB[i][j]PEB[i][j]PEB[i][j]PEB[i][j]PEB[i][j]PEB[i][j]A 行 →部分和 ↓固定: 权重矩阵 B权重 B 预加载到 PE,A 流过,部分和向下传递
  • Output-Stationary: 输出矩阵 C 固定在 PE 中累加,A 和 B 都流过。优点是部分和不需要移动
  • Weight-Stationary: 权重矩阵 B 预加载到 PE,输入 A 流过,部分和向下传递。适合推理(权重固定)

Tensor Core 和 XMX 的具体实现是厂商机密,但本质都是 systolic array 的变体。


Section 3: NVIDIA Tensor Core

MMA 操作

Tensor Core 的核心操作是矩阵乘累加:D = A × B + C

  • 原始概念尺寸是 4×4,但从 Volta 到 Blackwell,实际支持的块尺寸越来越大
  • 尺寸标注遵循 BLAS GEMM 约定:D(m×n)=A(m×k)×B(k×n)+C(m×n)D_{(m \times n)} = A_{(m \times k)} \times B_{(k \times n)} + C_{(m \times n)},其中 m 是输出行数,n 是输出列数,k 是内积维度(A 的列数 / B 的行数)。例如 m16n16k16 表示 A(16×16) × B(16×16) → D(16×16),m16n8k16 表示 A(16×16) × B(16×8) → D(16×8)
  • Hopper (4th gen) wmma API 常用尺寸为 m16n16k16;PTX 层面有更小的 m16n8k16
  • 这些是 Tensor Core 单条指令能处理的矩阵块大小。更大的矩阵乘需要软件做 tiling,将大矩阵拆成这些小块反复调用
  • 每个 SM 有 4 个 Tensor Core(每个 Processing Block 一个)

精度支持演进

从 Volta (2017) 到 Blackwell (2024),Tensor Core 支持的精度不断扩展:

Tensor Core 精度支持演进Volta2017 · 1st genFP16NEWTuring2018 · 2nd genINT8INT4INT1NEWAmpere2020 · 3rd genTF32BF16FP64NEWHopper2022 · 4th genFP8NEWBlackwell2024 · 5th genFP4NEWBlackwell (5th gen) 支持全部精度:FP16BF16TF32FP64FP8FP4INT8趋势: 精度越来越低 → 吞吐量越来越高(每降一级精度,吞吐量约翻倍)

趋势非常清晰:精度越来越低,吞吐量越来越高。FP8 的吞吐量是 FP16 的 2 倍,FP4 又翻倍。这也是为什么 FP8 训练(如 DeepSeek V3)和 FP4 量化推理成为趋势。

注意:Turing/Ampere 时代的 INT4 和 INT1 支持在 Hopper 之后被移除 — 实际 AI 工作负载更青睐 FP8/FP4 等浮点低精度格式。

Warp 级操作

Tensor Core 操作不是单个线程发起的 — 它是 warp 级协作操作。一个 warp 的 32 个线程共同持有输入矩阵的片段(fragment),一起发射 MMA 指令。

线程持有 Fragment
Step 1: Warp 内 32 个线程各持有矩阵片段(Fragment)Warp (32 threads)wmma::fragment — 矩阵块分布在 32 个线程的寄存器中Fragment A16×16 FP16 — 每线程 8 元素Fragment B16×16 FP16 — 每线程 8 元素Fragment C (累加器)16×16 FP32 — 每线程 8 元素Thread 0 的寄存器: A 的 8 个 FP16 元素 + B 的 8 个 FP16 元素 + C 的 8 个 FP32 元素32 个线程合起来 = 完整的 16×16 矩阵块(每个线程只看到自己的一小部分)wmma::load_matrix_sync(frag_a, A_ptr, lda);wmma::load_matrix_sync(frag_b, B_ptr, ldb);

关键要点:

  • Fragment 是矩阵块在 32 个线程寄存器中的分布式表示 — 每个线程只持有一部分
  • wmma::load_matrix_sync 从内存加载到 fragment,wmma::mma_sync 执行矩阵乘,wmma::store_matrix_sync 写回内存
  • wmma API 操作 m16n16k16 块;底层 PTX 指令 mma.sync.aligned.m16n8k16 操作更小的块,由编译器拆分

Section 4: Intel XMX

Intel 的矩阵加速单元叫 XMX (Xe Matrix eXtensions),是 Xe2 架构的核心组件。

Intel Xe-Core 内部结构 — XMX 矩阵引擎Xe2 架构 (Lunar Lake / Panther Lake) — 每 Xe-Core 含 8 个 XMX 单元Xe-CoreVector Engine 0FP32/FP16/INT 8-wide SIMDVector Engine 1FP32/FP16/INT 8-wide SIMDXMX 矩阵引擎 (×8)XMX 0A →B ↓XMX 1A →B ↓XMX 2A →B ↓XMX 3A →B ↓XMX 4A →B ↓XMX 5A →B ↓XMX 6A →B ↓XMX 7A →B ↓Thread ControlSIMD 调度SLM (Shared Local Memory) — 64 KBL1 Cache / Instruction Cache每 XMX 单元: 8×8 systolic array, 支持 FP16/BF16/TF32/INT8/INT4 | D(M×N) = A(M×K) × B(K×N) + C(M×N), M/N/K 取决于精度 | 编程: SYCL joint_matrix / ESIMD

关键规格 (Xe2 / Lunar Lake)

  • 每个 Xe-Core 包含 8 个 XMX 单元 + 2 个 Vector Engine
  • XMX 内部是 8×8 systolic array
  • 支持精度:FP16、BF16、TF32、INT8、INT4
  • 编程接口:SYCL joint_matrix (高层) / ESIMD dpas (低层)

dp4a vs dpas — 容易混淆的两条指令:

  • dp4a (Dot Product of 4 elements and Accumulate):计算 4 对 INT8 元素的点积并累加到 INT32(acc += a[0]*b[0] + a[1]*b[1] + a[2]*b[2] + a[3]*b[3])。在 Vector Engine 上执行,不走 XMX。本质是一条向量点积指令,类似 NVIDIA 的 __dp4a intrinsic(Pascal/Turing 开始支持)。dp4a 在 Xe 架构之前就存在
  • dpas (Dot Product Accumulate Systolic):驱动 XMX 脉动阵列的矩阵指令,一条指令完成一个小矩阵块的乘累加,类似 NVIDIA 的 mma.sync(Tensor Core 指令)。吞吐量远高于 dp4a,因为整个 8×8 systolic array 同时工作

简言之:dp4a 是标量点积(Vector Engine),dpas 是矩阵乘累加(XMX systolic array)。

与 NVIDIA 的架构类比

NVIDIAIntel说明
SMXe-Core基本计算单元
CUDA CoreVector Engine标量/向量计算
Tensor CoreXMX矩阵加速
Warp (32 threads)Sub-group (8/16 wide)SIMD 执行单位
Shared MemorySLM (Shared Local Memory)SM/Xe-Core 内共享存储
wmma / mma.syncjoint_matrix / dpas矩阵操作 API

核心思路完全一致 — 都是用 systolic array 做 D = A × B + C。主要区别在规模(数据中心 GPU vs 客户端 iGPU)和编程模型(CUDA 的 warp-level vs SYCL 的 sub-group)。


Section 5: Tensor Core vs XMX 详细对比

对比维度NVIDIA Tensor Core
H100 Hopper
Intel XMX
Xe2 Lunar Lake
厂商 / 架构NVIDIA (Hopper, 4th gen)Intel (Xe2, Lunar Lake)
内部结构Systolic Array 变体Systolic Array (8×8)(相似)
核心操作D = A × B + C (MMA)D = A × B + C (DPAS)(相似)
矩阵块尺寸 (FP16)m16n8k16m8n8k16
每 SM/Xe-Core 数量4 Tensor Core / SM8 XMX / Xe-Core
FP16 / BF16990 TFLOPS (H100 SXM)~48 TOPS (Lunar Lake iGPU)
FP8 支持Hopper 起 (4th gen)Xe2 起 (Lunar Lake)
FP4 支持Blackwell 起 (5th gen)尚未支持
TF32 支持Ampere 起 (3rd gen)Xe2 起
编程接口 (高层)CUDA wmma / mma.syncSYCL joint_matrix
编程接口 (低层)PTX mma 指令ESIMD dpas 指令
Warp/Sub-group 协作32 线程 warp 协作8/16-wide sub-group
目标场景数据中心 AI 训练/推理客户端 AI 推理 (iGPU)

两者核心思路相同: systolic array 做 D=A×B+C。主要区别在规模 (数据中心 vs 客户端)、矩阵块尺寸和编程接口。Hover 高亮行。

两者的相似点比区别更重要:

  • 都是 systolic array 变体,都做 D = A·B + C
  • 都只能做特定尺寸、特定精度的矩阵乘 — 其他操作仍走传统 CUDA Core / Vector Engine
  • 都需要软件配合(tiling、数据对齐)才能发挥峰值性能 — 这是 GEMM 优化文章的主题

关键区别主要在规模和目标场景:

  • H100 Tensor Core 峰值 ~990 TFLOPS (FP16),面向数据中心 AI 训练
  • Lunar Lake XMX 峰值 ~48 TOPS (INT8),面向客户端 AI 推理
  • NVIDIA 的 32-wide warp vs Intel 的 8/16-wide sub-group 影响编程方式

Section 6: 跨厂商标准 — Subgroup 与 Cooperative Matrix

前面介绍的 wmma(NVIDIA)和 joint_matrix(Intel SYCL)是厂商专有的 API。Vulkan / SPIR-V / OpenCL 等跨平台标准则定义了统一的抽象,使同一套代码可以运行在不同厂商的硬件上。

Subgroup:warp / wave 的跨厂商统一术语

Subgroup 是指一组在硬件上以锁步方式(SIMD/SIMT)执行的线程。这个概念在不同厂商有不同的叫法:

厂商术语典型宽度
NVIDIAWarp32 线程
AMDWave / Wavefront32 或 64 线程
IntelSub-group8 / 16 / 32 线程
Vulkan / SPIR-VSubgroup取决于硬件

Subgroup 内的线程可以直接交换寄存器数据,不需要经过 shared memory,支持的操作包括:

  • Shuffle:线程 A 直接读线程 B 的寄存器值
  • Broadcast:一个线程的值广播给 subgroup 内所有线程
  • Reduction:subgroup 内直接求和 / 求最大值

这比走 shared memory(store → barrier → load)快得多,是高性能 GPU kernel 的基础原语。

Cooperative Matrix:Tensor Core / XMX 的跨厂商编程抽象

前文我们看到,Tensor Core 的 MMA 操作是 warp 级协作 — 32 个线程共同持有矩阵片段(fragment),一起发射矩阵乘指令。Cooperative matrix 就是将这种”subgroup 内多线程合作持有并计算矩阵”的模式标准化

  • 没有任何单个线程持有完整矩阵,矩阵的片段分散在 subgroup 内各线程的寄存器中
  • 硬件以 subgroup 为粒度执行矩阵乘累加 D=A×B+CD = A \times B + C
  • 映射到底层硬件:NVIDIA Tensor Core、Intel XMX、AMD Matrix Core

各层 API 的对应关系:

抽象层级NVIDIAIntel跨厂商标准
高层 APIwmma::mma_syncjoint_matrix_madcooperativeMatrixMulAdd (Vulkan)
加载wmma::load_matrix_syncjoint_matrix_loadcooperativeMatrixLoad
存储wmma::store_matrix_syncjoint_matrix_storecooperativeMatrixStore
规范CUDA wmma APISYCL joint_matrixVK_KHR_cooperative_matrix / SPV_KHR_cooperative_matrix

典型的使用流程(以 Vulkan cooperative matrix 为例):

1. cooperativeMatrixLoad   — 从显存/shared memory 加载矩阵片段到 subgroup 的寄存器中
2. cooperativeMatrixMulAdd — 硬件执行 D = A × B + C(一条指令完成小矩阵乘加)
3. cooperativeMatrixStore  — 将结果写回显存/shared memory

矩阵的可用尺寸取决于硬件和数据类型。程序可以在运行时查询硬件支持的尺寸组合(如 NVIDIA FP16 常见 16×16×16,Intel 可能支持 8×16×32 等)。

为什么需要跨厂商标准

厂商专有 API(wmma、joint_matrix)通常更成熟、优化更深入。但 Vulkan cooperative matrix 的价值在于:

  • 可移植性:同一份 SPIR-V shader 可以在 NVIDIA、AMD、Intel 的 GPU 上运行
  • 生态统一:推理框架(如使用 Vulkan 后端的 llama.cpp / GGML)不需要为每个厂商维护独立的矩阵乘 kernel
  • 覆盖面广:Vulkan 支持桌面、移动、嵌入式等各种平台

实际性能上,跨厂商标准的 overhead 通常很小 — 底层驱动会将 cooperative matrix 操作映射到硬件原生的矩阵指令(Tensor Core HMMA、Intel DPAS 等)。


Section 7: Dual-Pipe — 同时利用两种计算单元

Tensor Core 和 CUDA Core 是 SM 内不同的功能单元,有独立的执行通道。传统做法是串行执行:矩阵乘(Tensor Core)完成后才开始 element-wise 操作(CUDA Core)。

Dual-Pipe 优化打破了这个限制:将输入拆成多个 micro-batch,当 Tensor Core 处理batch B 的 GEMM 时,CUDA Core 同时处理batch A 的 activation/normalization。不同 batch 之间没有数据依赖,因此可以安全重叠。

串行执行 (baseline)
传统串行: GEMM (Tensor Core) → Element-wise (CUDA Core) → GEMM → ...Tensor CoreCUDA CoretimeGEMM Layer 1Tensor Core(空闲)(空闲)Act/NormCUDA CoreGEMM Layer 2Tensor Core(空闲)(空闲)Act/NormCUDA Core总时间 = GEMM + Act/Norm + GEMM + Act/Norm(串行叠加)问题: Tensor Core 和 CUDA Core 交替空闲,SM 利用率低GEMM 期间 CUDA Core 空闲 | Act/Norm 期间 Tensor Core 空闲

Dual-pipe 的条件:

  • 不同 micro-batch 之间没有数据依赖 — Tensor Core 和 CUDA Core 处理的是不同 batch 的数据
  • 需要精心安排 micro-batch 的调度顺序和数据布局
  • 可以显著提升 SM 利用率,特别是在 MoE 等 element-wise 操作较多的架构中

总结

矩阵加速单元的核心设计思想:

  1. 专用硬件做矩阵乘 — Systolic array 用极高的数据复用率实现单条指令完成整块矩阵乘累加
  2. 精度换吞吐 — 从 FP16 到 FP8 到 FP4,每降一级精度吞吐量翻倍,AI 工作负载可以接受较低精度
  3. 协作执行 — 矩阵操作是 warp/sub-group 级别的协作,需要软件配合(fragment 管理、tiling 策略)

下一篇文章将从编程者的视角出发 — CUDA 编程模型,理解 thread/block/grid 的层级、shared memory 的使用、memory coalescing 等关键概念。这些知识是写高性能 GPU 代码(包括利用 Tensor Core)的前提。