目标设备:SM80(Ampere,如 A100)
目标:设计一套逐步叠加优化的消融实验(ablation),量化每一项优化(
ldmatrixvsld.shared、cp.asyncvs 同步 load)到底带来了多少性能提升。本文档是
初步规划.md的 SM80 裁剪版:已移除全部 TMA 相关内容(原 TMA 章节改为「SM80 能力边界」),并补充了 Ampere 特有的实验注意事项。 与原版的差异见文末 §6 与完整版的差异。
| 特性 | SM80 (A100) | 说明 |
|---|---|---|
cp.async.cg.shared.global (16B) |
✅ 可用 | Ampere 引入,.cg 绕过 L1 直达 L2 |
cp.async.ca.shared.global (4/8/16B) |
✅ 可用 | 会经过 L1 |
cp.async.commit_group / cp.async.wait_group N |
✅ 可用 | SM80 上唯一的异步完成追踪手段 |
cp.async.mbarrier.arrive |
❌ | 要求 sm_90 |
cp.async.bulk / cp.async.bulk.tensor(TMA) |
❌ | 要求 sm_90 |
st.async |
❌ | 要求 sm_90 |
cp.reduce.async.bulk |
❌ | 要求 sm_90 |
mbarrier.init/arrive |
⚠️ 基础版可用 | 但 expect_tx / try_wait.parity 需 sm_90;SM80 上无法用它精确追踪 cp.async 完成 |
ldmatrix.sync.aligned.m8n8.x1/x2/x4[.trans].shared.b16 |
✅ 可用 | 自 Turing (sm_75) 引入,SM80 完整支持 |
mma.sync.aligned.m16n8k16.row.col.f32.f16/f16.f32 / .bf16 |
✅ 可用 | 非 Hopper wgmma |
wgmma(warpgroup MMA) |
❌ | 要求 sm_90 |
cp.async + 多级流水(STAGES≥3) |
✅ 可用 | Ampere 上的标准做法 |
cuda::memcpy_async / cuda::barrier (libcu++) |
✅ 可用 | 内部就是 cp.async + mbarrier 的封装 |
结论:TMA 及一切依赖 TMA 的代码在 SM80 上不可用。
| 文件 | 是否可用 | 原因 |
|---|---|---|
kernels/hgemm/hgemm.cu |
✅ 可用,推荐作为实验基座 | 纯 cp.async + ldmatrix + mma.sync,无 TMA |
kernels/sgemm/sgemm_tf32.cu |
✅ 可用 | 纯 cp.async + ldmatrix + mma.sync(TF32) |
kernels/sgemm/sgemm.cu |
✅ 可用 | SIMT 版本,可作对照组 |
kernels/hgemm_sm120/hgemm_sm120.cu |
❌ 不可用 | 依赖 TMA(cp_async_bulk_tensor_2d)与 mbarrier.expect_tx |
kernels/flash_attn/flash_attn.cu |
❌ 不可用 | TMA-only(flash_attn.cu:21 的 CP_ASYNC_CG 宏定义了但从未调用) |
kernels/flash_decode/flash_decode.cu |
❌ 不可用 | TMA-only(cp_async_bulk_tensor_3d) |
kernels_dsl/** |
— | 无相关内容(仅 triton/tilelang 的 vector_add、softmax toy 示例) |
⚠️ 注意:
kernels/hgemm_sm120/readme.md里的性能数字(cp.async 34.00 TF / TMA 34.31 TF 等)在 SM80 上无法复现,不能直接当作 baseline。所有 baseline 必须在你自己的 SM80 设备上重测。
来自 kernels/hgemm/hgemm.cu:22-40、kernels/sgemm/sgemm_tf32.cu:22-35:
#define CP_ASYNC_CG(dst, src) asm volatile("cp.async.cg.shared.global.L2::128B [%0], [%1], 16;\n" ...)
#define CP_ASYNC_COMMIT_GROUP() // cp.async.commit_group
#define CP_ASYNC_WAIT_GROUP_0() // cp.async.wait_group 0
#define LDMATRIX_X4(R0,R1,R2,R3,PTR) // ldmatrix.sync.aligned.m8n8.x4.shared.b16
#define LDMATRIX_X2(R0,R1,PTR) // ldmatrix.sync.aligned.m8n8.x2.shared.b16
#define LDMATRIX_X2_TRANS(R0,R1,PTR) // ldmatrix.sync.aligned.m8n8.x2.trans.shared.b16
多级流水版本需要的 wait_group<N>(N>0)在 hgemm_sm120 里有现成写法,但那个文件依赖 TMA 不可用 —— 需要把这段 PTX 抄到新文件里:
template <int N>
__device__ __forceinline__ void cp_async_wait_group() {
if constexpr (N == 0) asm volatile("cp.async.wait_group 0;\n" ::);
else if constexpr (N == 1) asm volatile("cp.async.wait_group 1;\n" ::);
else if constexpr (N == 2) asm volatile("cp.async.wait_group 2;\n" ::);
else asm volatile("cp.async.wait_group 3;\n" ::);
}
kernels/hgemm/readme.md(4096³ bf16,来源机型未知):
hgemm_cublas 4.210 ms 32.64 TF
hgemm_naive 5.191 ms 26.47 TF ← ldmatrix + mma(无 swizzle / 无双缓冲)
hgemm_bcf 4.337 ms 31.69 TF ← + BCF swizzle
hgemm_bcf_dbf 4.096 ms 33.55 TF ← + double buffer
hgemm_bcf_dbf_rw 4.076 ms 33.72 TF ← + coalesced gmem r/w
kernels/sgemm/readme.md(4096³ TF32):
sgemm_tf32_bt_swizzle_dbf 8.723 ms 15.76 TF (非 cp.async:普通 load + st.shared)
sgemm_tf32_swizzle_bcf_dbf 8.276 ms 16.61 TF (cp.async)
⚠️ 这组差异里 swizzle / dbf / grid-swizzle 三个因素是混在一起的,不能直接归因到 cp.async —— 这正是需要做受控消融的原因。
核心思路:把优化拆成两条独立的阶梯,每条阶梯逐档叠加,每档只改变一个变量,从而把增量收益归因清楚。
固定基线:GEMM 形状 M=N=K=4096、bf16、BM=BN=128、BK=32、256 线程 / 8 warp、warp tile 64×32、mma.m16n8k16。
(建议全部在 kernels/hgemm/hgemm.cu 的结构上做,保持其它变量一致。)
对应 ldmatrix 这一项。需要三档,且必须给 ld.shared 变体最有利布局,否则测到的是布局差异而非指令效率。
| 档 | A fragment(16×16) | B fragment(8×8) | LSU 指令数 / lane |
|---|---|---|---|
| A0 | 直接从 gmem 读(无 smem) | 同 | 无 smem,最慢 |
| A1 | ld.shared.b32 ×4 |
ld.shared.b16 ×4(Bs[BK][BN] 布局,k 方向不连续) |
8 |
| A2 | ld.shared.b32 ×4 |
ld.shared.b32 ×2(B 预转置为 Bs[BN][BK],ld.shared 最优情况) |
6 |
| A3 | ldmatrix.x4 ×1 |
ldmatrix.x2.trans ×1 |
2 |
A3 即现有 kernels/hgemm/hgemm.cu:131/142 的写法。
关键:A3 相比 A1/A2 省的是 LSU 指令数和地址计算指令,不省带宽(数据搬运量相同)。 另外注意
ldmatrix.x4是整个 warp 协作指令(32 个 lane 各提供一个 8×8 tile 的行地址,硬件自动 gather+转置+广播),而ld.shared是 per-lane 独立访存 —— 这正是 A3 的核心优势来源。
| 档 | 搬运用什么 | 完成同步 | 能否与 mma 重叠 |
|---|---|---|---|
| B0 | ld.global.v4 + st.shared.v4 |
__syncthreads() |
❌ 完全串行 |
| B1 | cp.async.cg 16B |
commit_group + wait_group<0> + __syncthreads() |
❌ 单 buffer 下无收益 |
| B2 | cp.async.cg + 双缓冲(STAGES=2) |
wait_group<0>(读旧 buffer 前 wait 住新 buffer) |
✅ |
| B3 | cp.async.cg + 多级流水(STAGES=3) |
wait_group<STAGES-2> |
✅ 带宽利用更充分 |
SM80 上没有 TMA 作为「第三档」,B3 就是搬运路径的终点。若还想再往上走,下一档是 B4:cp.async + 寄存器级双缓冲(ldmatrix 结果在寄存器里预取下一 k_step,见
hgemm/hgemm.cu:355的bcf_dbf思路)。
坑 1:B1 单 buffer 下 cp.async 几乎无收益,甚至更慢。 若按 B0→B1→B2 顺序测,会得出"cp.async 没用"的错误结论。cp.async 的价值在于释放 LSU 和 issue slot,只有当有第二级 buffer 承接、让搬运算与 mma 重叠时才能兑现。
✅ 正确阶梯顺序:
同步 load + 双缓冲→cp.async + 双缓冲→cp.async + 多级流水。 也就是说,"双缓冲"和"cp.async"必须拆成两步分别加,否则无法区分收益来自哪一项。实现上:
同步 load + 双缓冲= 普通的ld.global+st.shared到 buffer[write],__syncthreads()后读 buffer[read],再__syncthreads()换索引;cp.async + 双缓冲= 把搬运算换成cp.async.cg+commit_group/wait_group。
坑 2:在最终 kernel 上测 ldmatrix vs ld.shared,差异会被淹没。 若 kernel 被 mma 吞吐或 DRAM 带宽卡住,smem→reg 段的差异在 wall-clock 上看不见。 在 A100 上这个风险尤其高:A100 的 FP16 Tensor Core 峰值约 312 TFLOPS,而实测 GEMM 常在 150~250 TFLOPS,两者差距不像消费级卡那么大,LSU 段更容易被掩盖。
✅ 建议额外做一个 smem→reg 微基准(只 load 不 mma,循环 N 次),直接测 LSU 吞吐差异; 同时用 ncu 采集:
smsp__inst_executed_pipe_lsu_mem_shared_op_ld.sum l1tex__data_bank_conflicts_pipe_lsu_mem_shared_op_ld.sum sm__inst_executed_pipe_lsu.avg.pct_of_peak_sustained_active
坑 3:只测一种 shape 会被误导。
✅ 建议扫
K ∈ {256, 512, 1024, 4096, 8192}×M/N ∈ {512, 4096},画热力图。
另外两条硬性要求:
nvidia-smi -lgc <MHz> 锁核心频率、nvidia-smi -lmc <MHz> 锁显存频率(hgemm_sm120/readme.md 里有 A100 的做法),测完用 -rgc / -rmc 复位。否则重测噪声大于效应。ldmatrix.trans 仅做转置重排),因此正确性可直接 diff 对比,误差应为 0。smem 容量:A100 每 SM 164 KB,单 block 最多 163 KB(需 cudaFuncSetAttribute(cudaFuncAttributeMaxDynamicSharedMemorySize, ...) 显式开启 >48KB)。
本实验 BM=BN=128, BK=32, bf16 → A tile = 128×32×2 = 8 KB,B tile = 32×128×2 = 8 KB,每 stage 16 KB。
cp.async.cg 的 16B 对齐:cp.async.cg 只支持 16B 粒度,且 gmem 地址需 16B 对齐。若 K 或 N 不是 8 的倍数(bf16 下 16B = 8 个元素),边界 tile 需要 fallback 路径。
实验时务必保证 M/N/K 都是 8 的倍数,把 fallback 从性能数据里排除掉。
cp.async 与 L2 行为:.cg 绕过 L1,命中 L2。A100 L2 为 40 MB(大),所以 K 较小的 shape 会大量命中 L2 —— 这也是为什么坑 3 里 K 的扫描很重要。
cp.async.wait_group 的语义是"按组计数",而不是按字节。STAGES=3 时用 wait_group<1> 允许 1 组仍在飞行中,不能用 wait_group<0>(那会把流水退化成单 buffer)。这一点在写 B3 时最容易写错。
A100 上没有 wgmma,所有 mma 都是 warp 级 mma.sync,因此 ldmatrix 的收益不会被 Hopper 的 wgmma 掩盖 —— 这其实是 SM80 做这个实验的优势:在 SM90 上 wgmma 直接从 smem 取数,ldmatrix 的收益会大幅缩水。SM80 是观察 ldmatrix 价值更干净的平台。
TF32 的注意:如果同时在 sgemm_tf32.cu 上做对照,A100 上 TF32 Tensor Core 吞吐约为 FP16 的一半,别把两者的 TFLOPS 直接横向比较。
这一节回答一个容易踩坑的问题:SIMT 和 cp.async / ldmatrix 是同一层级的东西吗?
答案:不是。 把 SIMT 误当作 ldmatrix / cp.async 的"对照组"会直接导致实验结论作废。
SIMT = Single Instruction, Multiple Threads,是 NVIDIA 的硬件执行模型:一条指令由一个 warp 的 32 个线程同时执行。
它与 SIMD 的区别:SIMD 是"一条指令操作 N 个数据槽,程序员手动向量化";SIMT 是"一条指令操作 1 个数据,硬件在 warp 内自动 SIMD 化",程序员写标量代码。
关键:所有 CUDA kernel 都是 SIMT 的,包括 Tensor Core kernel —— 只是 mma.sync 指令本身的执行方式(一条指令被 warp 内多个线程分组协作完成)比普通 FMA 更"特殊"。
| 层级 | 东西 | 说明 |
|---|---|---|
| 执行模型 / 计算单元 | SIMT(标量 FMA pipe) vs Tensor Core(MMA pipe) | SM 内的硬件单元,决定峰值算力,差 8~16 倍 |
| 访存指令 | ld.global / st.shared / ld.shared / cp.async / ldmatrix |
单条指令,属于"怎么把数据喂给计算单元",差 5~30% |
ldmatrix 服务的对象是 Tensor Core 的 fragment 布局(m16n8k16 的 lane→(row,col) 映射),在 SIMT kernel 里根本用不了;cp.async 虽然技术上也能用于 SIMT kernel,但 SIMT kernel 通常不需要 smem staging(每个线程直接标量读 gmem 算),强行加上只会引入额外搬运成本。如果把两者混在同一张消融表里,会犯一个经典错误:把 8 倍的架构收益算到 ldmatrix 头上。
用途 1:性能上下界锚点(不是消融实验的一环)
// SIMT 版(kernels/sgemm/sgemm.cu):每个线程算若干个输出元素
for (int k = 0; k < BK; ++k)
sum[i][j] += As[i][k] * Bs[k][j]; // 标量 FMA,走 FP32 pipe
// Tensor Core 版(kernels/sgemm/sgemm_tf32.cu)
MMA_M16N8K8(...); // mma.sync,走 Tensor Core
A100 峰值参考:
| 路径 | A100 峰值 |
|---|---|
| SIMT FP32 FMA | ~19.5 TFLOPS |
| Tensor Core TF32 | ~156 TFLOPS |
| Tensor Core FP16/BF16 | ~312 TFLOPS |
差 816 倍的量级会完全淹没 ldmatrix / cp.async 带来的 530% 提升 —— 这正是 §1.3「坑 2」的成因。
用途 2:架构级对照,回答"这套优化路线值不值得"
真正要回答的问题分两层:
| 问题 | 对照方式 |
|---|---|
| Q1: "上 Tensor Core 值得吗?" | SIMT vs Tensor Core |
| Q2: "在 Tensor Core 框架内,ldmatrix / cp.async 各值多少?" | 阶梯 A / 阶梯 B 内部互比 |
kernels/sgemm/ 里是一对标准对照:
kernels/sgemm/sgemm.cu ← SIMT 版(标量 FMA),已做到 tiling + swizzle + double buffer + coalesced r/w
kernels/sgemm/sgemm_tf32.cu ← Tensor Core 版(mma.sync + ldmatrix + cp.async)
而且 SIMT 版本身也有一套完整的优化阶梯(见 sgemm/readme.md):
sgemm_tiling ← 基础 tiling
sgemm_at_tiling ← + A 转置入 smem
sgemm_at_bcf_swizzling ← + swizzle 消 bank conflict
sgemm_at_bcf_swizzling_rw ← + coalesced 写回
sgemm_at_bcf_swizzling_dbf_rw ← + double buffer(反超 cuBLAS)
这是教科书级的消融阶梯写法 —— 每步只加一个东西,readme 里就有逐档 TFLOPS。 本实验的 cp.async / ldmatrix 阶梯应当照抄这个范式。
📌 公平性提醒:SIMT 版也可以做 shared memory tiling 和向量化解码(仓库里已经做了), 所以 SIMT vs Tensor Core 的对比必须是"都优化到位"的两套实现之比,而不是"裸 SIMT" vs "花活 Tensor Core"。
| 是什么 | 在实验中的角色 | |
|---|---|---|
| SIMT | 执行模型 / 计算单元选择(标量 FMA vs Tensor Core),差 8~16 倍 | 架构级对照,单独一张表 |
| cp.async / ldmatrix | 喂数据给 Tensor Core 的访存指令,差 5~30% | 实验变量,阶梯 A / 阶梯 B |
现在 kernels/hgemm/hgemm.cu 里有 4 个 kernel 几乎完全重复(naive / bcf / bcf_dbf / bcf_dbf_rw),继续加变体会爆炸。
建议新建 kernels/ablation/(基于 kernels/hgemm/hgemm.cu 改造,SM80 安全),用一个模板参数化的 kernel 覆盖全组合:
template <int LOAD_MODE, // 0=ldg+st.shared, 1=cp.async
int READ_MODE, // 0=ld.shared, 1=ldmatrix
int B_LAYOUT, // 0=Bs[BK][BN], 1=Bs[BN][BK]
int STAGES, // 1,2,3,4
int SWIZZLE> // 0/1
__global__ void hgemm_ablation_kernel(...);
用 if constexpr 分支(编译器消掉未走分支,零运行时开销),变体命名 abl_L{lm}_R{rm}_bl{bl}_s{s}_sw{sw}。
实现要点:
LOAD_MODE == 0 时用 float4/uint4 做 ld.global + st.shared,并保留 __syncthreads() 语义LOAD_MODE == 1 时用 CP_ASYNC_CG + CP_ASYNC_COMMIT_GROUP() + cp_async_wait_group<STAGES-2>()READ_MODE == 0 时按 mma fragment 的 lane→(row,col) 映射手工写 ld.shared(注意 A fragment 需 4 次 b32,B fragment 视 B_LAYOUT 决定是 4 次 b16 还是 2 次 b32)STAGES >= 2 时 smem 用 As[STAGES][BM][BK] / Bs[STAGES][BK][BN] 的 stage-major 布局,配合 read_idx / write_idx 索引最终输出 3 张独立的表,不要把 SIMT 与 ldmatrix / cp.async 混排在同一张表里(理由见 §1.5)。
| 实现 | 路径 | TFLOPS | 相对 SIMT |
|---|---|---|---|
sgemm_at_bcf_swizzling_dbf_rw |
SIMT FP32 FMA | - | 1x |
sgemm_tf32_swizzle_bcf_dbf |
TC / TF32 / mma.sync |
- | ~8x |
hgemm_bcf_dbf_rw |
TC / BF16 / mma.sync |
- | ~16x |
cublas (bf16) |
厂商库 | - | - |
这一表回答"上 Tensor Core 值不值"。注意 SIMT 与 TC 两边必须都是优化到位的版本。
| 变体 | 描述 | LSU 指令/lane | TFLOPS | vs 上一档 |
|---|---|---|---|---|
abl_L1_R0_bl0_s3_sw1 |
ld.shared,B 自然布局 Bs[BK][BN] |
8 | - | - |
abl_L1_R0_bl1_s3_sw1 |
ld.shared,B 预转置 Bs[BN][BK](最有利对手) |
6 | - | - |
abl_L1_R1_bl1_s3_sw1 |
ldmatrix.x4 + ldmatrix.x2.trans |
2 | - | - |
| 变体 | 描述 | TFLOPS | vs 上一档 | 累计 |
|---|---|---|---|---|
abl_L0_R1_s1_sw1 |
同步 ld.global+st.shared,单 buffer |
- | - | 基准 |
abl_L0_R1_s2_sw1 |
同步 load + 双缓冲 | - | - | |
abl_L1_R1_s1_sw1 |
cp.async,单 buffer | - | - | |
abl_L1_R1_s2_sw1 |
cp.async + 双缓冲 | - | - | |
abl_L1_R1_s3_sw1 |
cp.async + 多级流水 (STAGES=3) | - | - | |
abl_L1_R1_s4_sw1 |
cp.async + STAGES=4 | - | - |
关键读法:"双缓冲"与"cp.async"必须拆成两步分别加,增量才可归因。 单 buffer 的 cp.async(B3 那一档)预期与同步 load 持平甚至略慢 —— 这是正常的,不是实验失败。
| 变体 \ shape | M=N=4096,K=256 | K=1024 | K=4096 | K=8192 | M=N=512,K=4096 |
|---|---|---|---|---|---|
| (同上各变体) | - | - | - | - | - |
K 小 → latency-bound,cp.async / 多级流水收益最大;K 大 → compute-bound,收益被 mma 吞吐盖住。
kernels/ablation/,实现模板化 kernel(LOAD_MODE / READ_MODE / B_LAYOUT / STAGES / SWIZZLE)test.py:扫多个 shape(K ∈ {256,512,1024,4096,8192})、锁频、输出 Markdown 表格(每档 TFLOPS + 相对上一档的增量百分比)readme.md 记录结论表格kernels/hgemm/cublas.cu 已存在,直接用)sgemm.cu 的 SIMT 优化阶梯作为架构级对照(表 1),并与 sgemm_tf32.cu 配对比较| 关注点 | 指标 |
|---|---|
| ldmatrix 增量 | smsp__inst_executed_pipe_lsu_mem_shared_op_ld.sum、l1tex__data_bank_conflicts_pipe_lsu_mem_shared_op_ld.sum、sm__inst_executed_pipe_lsu.avg.pct_of_peak_sustained_active |
| cp.async 增量 | smsp__inst_executed_op_global_ld.sum、gpu__time_duration.sum、l1tex__throughput、dram__throughput |
| 同步 load 的延迟暴露 | smsp__warp_issue_stalled_long_scoreboard_per_warp_active.pct(同步 load 会明显偏高) |
| 流水是否真的重叠 | smsp__warp_issue_stalled_mio_throttle_per_warp_active.pct / smsp__warp_issue_stalled_lg_throttle_per_warp_active.pct |
已有 profiling 脚手架:kernels/hgemm/run_ncu.sh、kernels/hgemm/test_for_ncu.py
(用法:ncu -k regex:"gemm|Kernel" --set full -o profile_res -f python test_for_ncu.py)。
| 章节 | 完整版 | SM80 版 |
|---|---|---|
| TMA 介绍 | 完整章节(描述符 / 发起 / mbarrier / 约束) | 删除,替换为「§0.1 SM80 能力边界」对照表 |
| 阶梯 B | B0 同步 / B1 cp.async / B2 cp.async 多级 / B3 TMA | 删除 B3(TMA),改为 B0 同步 / B1 cp.async / B2 双缓冲 / B3 多级 / B4 寄存器双缓冲 |
| 异步完成机制 | mbarrier + expect_tx(按字节) |
commit_group + wait_group<N>(按组计数) |
| swizzle | 软件 swizzle vs TMA 硬件 swizzle | 只有软件 swizzle(SWIZZLE_A/B),无需考虑二者冲突 |
| 基座文件 | hgemm_sm120 |
kernels/hgemm/hgemm.cu |
| 不可用文件 | — | 明确标注 hgemm_sm120 / flash_attn / flash_decode 不可用 |
| 新增 | — | A100 smem 容量与 opt-in、16B 对齐、L2 行为、wait_group 语义、wgmma 缺失(SM80 做 ldmatrix 实验反而更干净) |
TMA = Tensor Memory Accelerator,Hopper (SM90) 引入的硬件 DMA 引擎,Blackwell 保留并增强。负责"把一整块多维 tile 从 global 搬到 shared"。
| cp.async | TMA | |
|---|---|---|
| 指令粒度 | 每线程一条指令搬 16B | 一个线程一条指令搬整个 tile |
| 地址计算 | 每线程自己算 gmem/smem 地址 + swizzle XOR | 硬件按描述符算,零地址计算指令 |
| 搬运宽度 | 4B/8B/16B | 一行最长 256B,tile 可任意大 |
| 落 smem 排布 | 软件 XOR swizzle | 硬件自动 swizzle(CU_TENSOR_MAP_SWIZZLE_128B/64B) |
| 完成通知 | commit_group / wait_group N(按组,粗) |
mbarrier + expect_tx(bytes)(按字节,精确) |
| 访问模式 | 任意(可 gather) | 只能整 tile copy |
| 支持 | Ampere+ | Hopper+ (SM90+) |
cuTensorMapEncodeTiled,见 hgemm_sm120.cu:731)cp.async.bulk.tensor.2d.shared::cta.global.mbarrier::complete_tx::bytes(见 hgemm_sm120.cu:114)expect_tx约束:global 地址 16B 对齐;box 最内维字节数匹配 swizzle 宽度;rank ≤ 5;descriptor 需在 device 生命周期内有效;不支持任意 gather。
| 日期 | 变更 |
|---|---|
| v1 | 初版:从 初步规划.md 裁剪出 SM80 版本,移除 TMA |
| v2 | 追加 §1.5「SIMT / Tensor Core 与访存指令的层级关系」;§3 改为 4 张分表(架构级对照 / 阶梯 A / 阶梯 B / 形状敏感性);§4 增加「复现 SIMT 优化阶梯」待办 |