flowchart LR
subgraph TileSwap["2D Tile 坐标与指针轴交换"]
InTile["输入分块 (BLOCK_M, BLOCK_N)"] --> Trans["转置操作 tl.trans(x) ➔ (BLOCK_N, BLOCK_M)"]
Trans --> OutTile["按列转置写回目标地址"]
end
第 8 课:二维 Tile 与转置
参考答案版:包含完整代码实现与问答题深度参考解析。建议先独立完成练习题后再展开核对。
本课目标与完成标准
学完后你应能:构造二维 pointer tensor,正确处理非方阵与双向 mask。
通过必须同时满足:
- 独立补齐本课唯一的代码填空题,并通过给定检查;
- 三个问答题均说明因果链,而不是只报术语;
- 能指出至少一个正确性边界和一个性能取舍;
- 总分不低于 8/10,且没有一票否决级概念错误。
前置关系
- 课程前置:Python、PyTorch 张量、CUDA 基本线程/内存概念
- 本课在路线中的作用:二维 tile 用广播后的行/列 offset 构造 pointer matrix;
tl.trans交换编译期维度。
核心心智模型
1. 它是什么,解决什么问题
矩阵转置(Matrix Transpose: )是最基础但对硬件内存层次极具杀伤力的内存重排算子。 在行优先(Row-Major)物理存储下,如果直接按标量或朴素方式转置,面临非此即彼的内存性能断崖:
- 若以行连续方式合并读取(Coalesced Read),则写回目标矩阵时将跨行跨列产生巨大的跨步写入(Strided Write),导致 L2 缓存行污染与极低的写带宽;
- 反之,若写回保持合并,则读取必定离散跨步。 在传统 CUDA 编程中,解决此问题的标准范式是使用带
+1Padding 的共享内存(Shared Memory)充当片上数据交换中转站,极度繁琐且容易引发 Bank Conflict。 本课解决的核心工程问题是:在 Triton 中如何使用二维广播语法构建高维 Pointer Tensor 与二维谓词掩码,并借助原生编译原语tl.trans将复杂的片上寄存器/共享内存转置重排交给编译器自动优化,实现非方阵与任意步长下的安全合并读写。
2. 它如何工作
2D 分块转置与内存步长重塑架构如图 8-1 与图 8-2 所示:
图 8-1:二维分块转置与内存步长重塑架构
flowchart TD
Program2D["2D Program 划分 (pid_m, pid_n)"] --> Load["合并加载源矩阵分块"]
Load --> SMemTrans["Triton 编译器自动在片上执行无冲突转置"]
SMemTrans --> Store["合并写回目标转置矩阵"]
图 8-2:2D 分块转置全局合并读写流程
二维网格分块映射: 发射网格为
grid = (cdiv(M, BM), cdiv(N, BN))。每个 Program 处理一个形状为(BM, BN)的二维 Tile。二维广播指针与双向掩码构造:
- 行偏移向量
rm = pid_m * BM + tl.arange(0, BM),形状为(BM,); - 列偏移向量
rn = pid_n * BN + tl.arange(0, BN),形状为(BN,); - 通过广播语法
rm[:, None](形状(BM, 1))与rn[None, :](形状(1, BN))合成形状为(BM, BN)的二维指针矩阵: - 二维输入掩码:
mask = (rm[:, None] < M) & (rn[None, :] < N),形状同样为(BM, BN)。
- 行偏移向量
合并加载与片上向量转置(
tl.trans):- 执行
tile = tl.load(ptrs, mask=mask)。此时tile处于片上寄存器,形状为(BM, BN)。 - 调用
tl.trans(tile),在编译期将寄存器块的逻辑维度从(BM, BN)交换为(BN, BM)。底层 Triton 编译器(Triton-GPU Dialect)会自动在片上寄存器或利用 Bank 优化的共享内存中调度高效的数据重排,对开发者完全透明。
- 执行
交换指针与输出掩码写回:
- 输出矩阵的目标形状为 ,目标地址指针通过将坐标轴反转构造: 其形状为
(BN, BM)。 - 输出掩码维度同步反转:
out_mask = (rn[:, None] < N) & (rm[None, :] < M),形状为(BN, BM)。 - 执行
tl.store(out_ptrs, tl.trans(tile), mask=out_mask),实现读写双方均处于行连续的最佳合并访存状态!
- 输出矩阵的目标形状为 ,目标地址指针通过将坐标轴反转构造: 其形状为
3. 正确性条件与常见误区
输入掩码与输出掩码的形状失配(Shape Mismatch): 输入 Tile 的形状是
(BM, BN),其掩码mask形状也是(BM, BN)。 输出 Tile 由于被转置,形状变成了(BN, BM)。如果直接将输入的mask传给tl.store,当 时,Triton 编译器会直接抛出张量形状不匹配编译错误;即使 ,若处理非方阵边界块(如 ),输入掩码中 轴与 轴的布尔值被错误复用,会导致边界处数据被严重切碎截断或写入越界垃圾。只测方阵(Square Matrix)的虚假正确性: 若仅测试 的方阵,输入步长与输出步长、行块与列块完全对称,会掩盖大量关于 与 、
som与son混淆的严重逻辑 Bug。必须使用长宽极度不对称的矩形矩阵(如 )进行边界压力验证。非连续输入与输出连续契约: 虽然代码中输入地址使用了通用步长
x.stride(0)和x.stride(1),但输出out必须分配连续内存,或者显式指定符合转置期望的目标步长。
4. 性能与工程取舍
Tile 尺寸选择(
32x32vs64x64): (1024 元素)是一个通用且均衡的选择。对于 FP32,单 Tile 占用 4KB 显存数据; 若选择更大 Tile(如 元素),能提升内存突发传输长度,但单个 Program 占用的寄存器翻倍,可能导致 SM 上并发活动 Warp 数量骤降(Occupancy 降低)。非方阵 Tile()与对齐优化: 若已知某一维度的物理步长极大,选择不对称的 Tile(如 )有时能更好地匹配内存控制器的 128-byte 突发访问,但增加了代码复杂性。
具体演示
以输入形状 、分块大小 为例:
- 网格大小: 个 M 块, 个 N 块,总共发射 个 2D Program。
- Program (0, 2) 处理输入尾块 :
- 行范围 ,前 31 行有效,最后 1 行越界( 为 False);
- 列范围 ,仅第 64 列有效,后 31 列越界( 仅 1 个 True);
- 输入掩码
mask形状为 ,仅左上角 的区域为 True; - 转置后
tl.trans(tile)形状变为 ; - 输出掩码
out_mask形状为 ,仅左上角 的区域为 True; - 精准写回到输出矩阵的 行列区域,位级无缝拼合。
请在阅读后先合上这一节,用自己的语言复述“输入状态 → 中间状态 → 输出状态”,再做练习。
实践任务:唯一代码填空题
补齐转置值。
规则:只能修改 TODO/______ 所在位置;不要删除断言或放宽误差。代码注释说明了每个边界条件。
Code
import torch
import triton
import triton.language as tl
@triton.jit
def transpose_kernel(x, out, M: tl.constexpr, N: tl.constexpr,
sxm: tl.constexpr, sxn: tl.constexpr,
som: tl.constexpr, son: tl.constexpr,
BM: tl.constexpr, BN: tl.constexpr):
rm = tl.program_id(0) * BM + tl.arange(0, BM)
rn = tl.program_id(1) * BN + tl.arange(0, BN)
ptrs = x + rm[:, None] * sxm + rn[None, :] * sxn
mask = (rm[:, None] < M) & (rn[None, :] < N)
tile = tl.load(ptrs, mask=mask)
out_ptrs = out + rn[:, None] * som + rm[None, :] * son
out_mask = (rn[:, None] < N) & (rm[None, :] < M)
tl.store(out_ptrs, ______, mask=out_mask) # TODO: 交换 tile 两维
def transpose(x):
assert x.ndim == 2
M, N = x.shape
out = torch.empty((N, M), device=x.device, dtype=x.dtype)
grid = (triton.cdiv(M, 32), triton.cdiv(N, 32))
transpose_kernel[grid](x, out, M, N, x.stride(0), x.stride(1),
out.stride(0), out.stride(1), BM=32, BN=32)
return out
for shape in ((2, 3), (31, 65), (128, 96)):
x = torch.randn(shape, device="cuda")
torch.testing.assert_close(transpose(x), x.T)检查方法
在 CUDA/Triton 环境运行本单元格;断言覆盖规则尺寸和非规则尾块。首次 JIT 不计入性能。
提交时请给出:补齐后的代码、实际运行输出(环境不可用时注明“仅静态审查”)以及对失败用例的解释。
Q1
不要背定义:请从输入、状态变化和输出三个阶段解释“二维 Tile 与转置”的工作机制。
你的答案:
Q2
输出 mask 为何不能直接复用输入 mask?
你的答案:
Q3
对非连续输入,这个 stride-aware kernel 还需哪些 wrapper 检查?
你的答案:
评分与通过规则
- 代码 4 分:正常输入 2 分,边界输入 1 分,解释实现 1 分;
- Q1~Q3 各 2 分;
- 一票否决:结果碰巧正确但核心因果链错误、删除边界检查、把未运行结果说成实测。
需要提示时按四级机制请求:概念区域 → 具体方向 → 关键局部 → 完整答案。