Skip to content

HGEMM SMEM Swizzle 代码学习 ​

本人学习笔记,AI总结

09 篇讲 bank conflict 时给了两把刀:padding(行尾塞废位错开 bank)和 CuTe swizzle(位交错)。当时 swizzle 只在 10 篇的 CuTe 转置里露过脸,被标记为"不深入"。本篇把它拆透:17 篇的 MMA kernel 去掉 A_PAD/B_PAD,换上smem swizzle——同样消 bank conflict,但零浪费、零对齐副作用,是 Tensor Core kernel 的工业标配(CUTLASS 内部就这么干)。

实测对照(RTX 4090,方阵):

形状无 swizzle(PAD=0)+ smem swizzle提升
2048³122.7130.8+6.6%
4096³145.8148.5+1.8%
8192³153.1151.9-0.8%
12288³148.6147.3-0.9%
16384³155.9154.3-1.0%

有意思:只有小形状赚,大形状反而微亏。这个反直觉的结果本身就是本篇最好的教材——先把 swizzle 机理讲透,再回来解释为什么。

问题:128bit 访存的 bank conflict 有多隐蔽 ​

复习 bank 布局(09 篇):smem 32 个 bank,每 bank 4 字节宽。128bit(16 字节)访存是本 kernel 的常态——cp.async 每次搬 16B,ldmatrix 每次读 16B。一条 16B 访存横跨 4 个 bank。

17 篇的 smem 布局(A 矩阵,s_a[BM][BK] = 128 行 × 16 列 half,half 2 字节):

行 i 的起始字节 = i * 16 * 2 = i * 32 字节 → 行起点 = bank (i*32/4) % 32 = (i*8) % 32

行号:  0    1    2    3    4 ...
起点bank: 0    8   16   24    0(32%32) ...
每行的 16B(4 bank): 行0 占 bank 0-3, 行1 占 bank 8-11, 行2 占 16-19, 行3 占 24-27
                    行4 又回到 bank 0-3!

周期是 4 行:行 0/4/8/12 起点全是 bank 0。17 篇 kernel 的装载映射 load_a_smem_m = tid / 2——warp 内相邻 tid 的 m 差 1,但 16B 写落在 4 行一循环的 bank 上,warp 32 线程 → 8 路冲突(32 线程的写请求挤 4 组重复 bank)。ldmatrix 读端同理。

09 篇的 padding 刀法在这里失灵一半:给 BK=16 加 8 个 half 的 PAD,行距变 48 字节 = 12 bank,周期变 8——冲突降但不消,而且 128×24×2B×stage 数的 smem 膨胀会挤掉一级流水线(smem 总量 96KB 上限,stage2 就要 16KB×2)。大 tile 的 Tensor Core kernel 付不起这个浪费。

swizzle 核心:一次异或,行内块重排 ​

swizzle 的思路和 padding 完全不同:不动布局尺寸,改元素位置。存储时对列坐标做位异或重排,让"物理相邻的行"在 16B 粒度上互相错开。

核心函数(hgemm_mma_stage_tn_swizzle.cu:124,一行就是全部魔法):

c++
// i: 行号; j: 列号; 返回: 重排后的列号
// kColStride=16 (BK), kStep=8 (128bit = 8 half)
swizzle_permuted_j = (((j >> 3) ^ (i >> 2)) % (kColStride >> 3)) << 3;
//               块号 j/8  XOR  行组 i/4        块总数 2          回到字节粒度

拆开读:每行 16 个 half 分成 2 个 8-half 块(每块 16B=128bit);重排 = 块号 XOR (行号/4)。

具体算(i=行, 块号 0/1,i>>2 的值每 4 行变一次):

行 0~3  (i>>2=0): 块号 XOR 0 → 不变     [块0|块1]
行 4~7  (i>>2=1): 块号 XOR 1 → 交换     [块1|块0]
行 8~11 (i>>2=2): 块号 XOR 0 → 不变     [块0|块1]   ← 2  xor 回 0
行 12~15(i>>2=3): 块号 XOR 1 → 交换     [块1|块0]

物理 smem 里相邻 4 行一组交替"正常/交换"。效果:行 0 的 16B 从 bank 0-3 出发,行 4 的 16B 现在取的是原块 1 的数据——bank 8-11。原来 4 行一循环的 bank 重复模式被搅成 8 行才循环,装载/读取时相邻行不再撞 bank。

为什么是 i>>2(4 行一组)而不是 i>>1?必须和访存形态匹配:本 kernel 装载时 2 个 tid 合作搬一行(tid/2=m),warp 内 16 行同时访问——组距 4 保证同 warp 的 32 个 16B 请求均匀落在 8×4=32 个 bank 上。kStep=8(128bit)对齐 cp.async 的 16B 粒度,kColStride=16 对应 BK——参数全部由 kernel 的访存形态决定,不是玄学调参。

写端与读端:一处重排,处处配合 ​

swizzle 是侵入式的:存储布局变了,所有访问 smem 的地方都要用同一个函数换算。

写端(cp.async 装载,源码 263 行):

c++
// 存的时候: 列坐标经过 swizzle 换算
int permuted_a_k = swizzle_permuted_A_j<MMA_K>(load_smem_a_m, load_smem_a_k);
uint32_t load_smem_a_ptr = smem_a_base_ptr +
    (smem_sel * s_a_stage_offset + load_smem_a_m * (BK) + permuted_a_k) * sizeof(half);
CP_ASYNC_CG(load_smem_a_ptr, &A[load_gmem_a_addr], 16);

读端(ldmatrix,297 行):同一个 swizzle_permuted_A_j 换算——写进去用重排列号,读出来也用同一个重排列号,逻辑上数据没动(s_a[i][swizzle(j)] 两端一致),物理上 bank 分布被打散。

这引出 swizzle 的本质:它是"纯物理层"的优化——逻辑布局(tile 语义)不变,计算代码一行不用改,只有 smem 地址换算插入。对照 padding 的"物理塞废位 + 逻辑索引偏移",swizzle 没有任何存储浪费(BK 保持 16 不膨胀),stage 数不用降。

对照 09/10 篇:三把刀的完整对比 ​

到本篇,bank conflict 的武器库集齐了,横向对比(这是贯穿 09→10→18 的总结账):

paddingCuTe Swizzle(10 篇)手写 swizzle(本篇)
原理行尾塞废位错开行距位异或交错位异或交错
存储有浪费(PAD 列)零浪费零浪费
对齐破坏 16B 对齐(04 篇的坑)保持保持
表达s[BK][BN+PAD] 直白composition(Swizzle, layout) 声明式一个 inline 函数
灵活性固定任意 shape/stride 组合的代数推导按访存形态定制

10 篇的 CuTe swizzle (c^r) 位交错和本篇的 ((j>>3)^(i>>2)) 是同一个东西的不同记法——CuTe 把它抽象成可组合的代数对象(Swizzle<B,M,S>:B=位数、M=异或掩码起点、S=位移),手写版直接针对 m16n8k16 + 128bit 访存形态定制。10 篇当时"不深入"的部分,现在从手写侧完全打通了——这也正是 19 篇(CuTe 版 HGEMM)要兑现的闭环:用 CuTe 把 17/18 篇的所有手排布局重新表达一遍。

回到数据:为什么小形状赚、大形状亏 ​

现在能解释开头那个反直觉了。冲突消除的收益 = 冲突造成的延迟占总耗时的比例:

  • 小形状(2048³):K/BK = 128 轮迭代,每轮 ldmatrix/cp.async 的 bank 排队是循环体的主要开销之一——消掉它,+6.6%
  • 大形状(8192³+):计算量是 3 次方涨(8192³ vs 2048³ = 64 倍),mma 指令流占满 SM,smem 延迟被计算密度掩盖(15 篇"瓶颈翻转"的又一实例)——冲突收益趋于 0,反而 swizzle 的 XOR 换算指令是每循环白白多几条整数运算,-1%

这验证了 12 篇立的铁律:优化手段必须打在瓶颈上。swizzle 打的是"访存冲突",但大形状 HGEMM 的瓶颈根本不在那。工业库(CUTLASS)仍然默认带 swizzle——因为它们要服务所有形状,小形状的 +6.6% 也是 +,且 XOR 两条指令在编译器调度好的大 kernel 里经常能藏进流水线空隙(我们 -O2 的手写版藏不住,cuBLAS 级的 SASS 调度可以)。

另一个实验证据:x4 版(A&B 都 swizzle + ldmatrix.x4,README 里 4090 的最优配置之一)在 4096³ 打到 149.7 vs swizzle 版 148.5——组合优化在大中形状又开始赚,说明 x4 把读端指令数砍半的收益盖过了 XOR 开销。swizzle 不是孤立开关,和 ldmatrix 宽度、tile 形状联动调优才有完整收益。

完整代码 ​

hgemm_mma_stage_tn_swizzle.cu ​

hgemm_mma_stage_tn_swizzle.cu
c++
#include <algorithm>
#include <cuda_bf16.h>
#include <cuda_fp16.h>
#include <cuda_fp8.h>
#include <cuda_runtime.h>
#include <float.h>
#include <mma.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <vector>
using namespace nvcuda;

#define WARP_SIZE 32
#define DEVICE_INLINE __device__ inline
#define HOST_DEVICE_INLINE __device__ __host__ inline
#define INT4(value) (reinterpret_cast<int4 *>(&(value))[0])
#define FLOAT4(value) (reinterpret_cast<float4 *>(&(value))[0])
#define HALF2(value) (reinterpret_cast<half2 *>(&(value))[0])
#define BFLOAT2(value) (reinterpret_cast<__nv_bfloat162 *>(&(value))[0])
#define LDST32BITS(value) (reinterpret_cast<half2 *>(&(value))[0])
#define LDST64BITS(value) (reinterpret_cast<float2 *>(&(value))[0])
#define LDST128BITS(value) (reinterpret_cast<float4 *>(&(value))[0])
// gmem -> smem
#define CP_ASYNC_COMMIT_GROUP() asm volatile("cp.async.commit_group;\n" ::)
#define CP_ASYNC_WAIT_ALL() asm volatile("cp.async.wait_all;\n" ::)
#define CP_ASYNC_WAIT_GROUP(n)                                                 \
  asm volatile("cp.async.wait_group %0;\n" ::"n"(n))
// ca(cache all, L1 + L2): support 4, 8, 16 bytes, cg(cache global, L2): only
// support 16 bytes.
#define CP_ASYNC_CA(dst, src, bytes)                                           \
  asm volatile(                                                                \
      "cp.async.ca.shared.global.L2::128B [%0], [%1], %2;\n" ::"r"(dst),       \
      "l"(src), "n"(bytes))
#define CP_ASYNC_CG(dst, src, bytes)                                           \
  asm volatile(                                                                \
      "cp.async.cg.shared.global.L2::128B [%0], [%1], %2;\n" ::"r"(dst),       \
      "l"(src), "n"(bytes))
// smem -> gmem: requires sm_90 or higher.
#define CP_ASYNC_BULK_COMMIT_GROUP()                                           \
  asm volatile("cp.async.bulk.commit_group;\n" ::)
#define CP_ASYNC_BULK_WAIT_ALL() asm volatile("cp.async.bulk.wait_all;\n" ::)
#define CP_ASYNC_BULK_WAIT_GROUP(n)                                            \
  asm volatile("cp.async.bulk.wait_group %0;\n" ::"n"(n))
#define CP_ASYNC_BULK(dst, src, bytes)                                         \
  asm volatile(                                                                \
      "cp.async.bulk.global.shared::cta.bulk_group.L2::128B [%0], [%1], "      \
      "%2;\n" ::"r"(dst),                                                      \
      "l"(src), "n"(bytes))
// ldmatrix
#define LDMATRIX_X1(R, addr)                                                   \
  asm volatile("ldmatrix.sync.aligned.x1.m8n8.shared.b16 {%0}, [%1];\n"        \
               : "=r"(R)                                                       \
               : "r"(addr))
#define LDMATRIX_X2(R0, R1, addr)                                              \
  asm volatile("ldmatrix.sync.aligned.x2.m8n8.shared.b16 {%0, %1}, [%2];\n"    \
               : "=r"(R0), "=r"(R1)                                            \
               : "r"(addr))
#define LDMATRIX_X4(R0, R1, R2, R3, addr)                                      \
  asm volatile(                                                                \
      "ldmatrix.sync.aligned.x4.m8n8.shared.b16 {%0, %1, %2, %3}, [%4];\n"     \
      : "=r"(R0), "=r"(R1), "=r"(R2), "=r"(R3)                                 \
      : "r"(addr))
#define LDMATRIX_X1_T(R, addr)                                                 \
  asm volatile("ldmatrix.sync.aligned.x1.trans.m8n8.shared.b16 {%0}, [%1];\n"  \
               : "=r"(R)                                                       \
               : "r"(addr))
#define LDMATRIX_X2_T(R0, R1, addr)                                            \
  asm volatile(                                                                \
      "ldmatrix.sync.aligned.x2.trans.m8n8.shared.b16 {%0, %1}, [%2];\n"       \
      : "=r"(R0), "=r"(R1)                                                     \
      : "r"(addr))
#define LDMATRIX_X4_T(R0, R1, R2, R3, addr)                                    \
  asm volatile(                                                                \
      "ldmatrix.sync.aligned.x4.trans.m8n8.shared.b16 {%0, %1, %2, %3}, "      \
      "[%4];\n"                                                                \
      : "=r"(R0), "=r"(R1), "=r"(R2), "=r"(R3)                                 \
      : "r"(addr))
// stmatrix: requires sm_90 or higher.
#define STMATRIX_X1(addr, R)                                                   \
  asm volatile(                                                                \
      "stmatrix.sync.aligned.x1.m8n8.shared.b16 [%0], {%1};\n" ::"r"(addr),    \
      "r"(R))
#define STMATRIX_X2(addr, R0, R1)                                              \
  asm volatile(                                                                \
      "stmatrix.sync.aligned.x2.m8n8.shared.b16 [%0], {%1, %2};\n" ::"r"(      \
          addr),                                                               \
      "r"(R0), "r"(R1))
#define STMATRIX_X4(addr, R0, R1, R2, R3)                                      \
  asm volatile(                                                                \
      "stmatrix.sync.aligned.x4.m8n8.shared.b16 [%0], {%1, %2, %3, %4};\n" ::  \
          "r"(addr),                                                           \
      "r"(R0), "r"(R1), "r"(R2), "r"(R3))
#define STMATRIX_X1_T(addr, R)                                                 \
  asm volatile(                                                                \
      "stmatrix.sync.aligned.x1.trans.m8n8.shared.b16 [%0], {%1};\n" ::"r"(    \
          addr),                                                               \
      "r"(R))
#define STMATRIX_X2_T(addr, R0, R1)                                            \
  asm volatile(                                                                \
      "stmatrix.sync.aligned.x2.trans.m8n8.shared.b16 [%0], {%1, %2};\n" ::    \
          "r"(addr),                                                           \
      "r"(R0), "r"(R1))
#define STMATRIX_X4_T(addr, R0, R1, R2, R3)                                    \
  asm volatile(                                                                \
      "stmatrix.sync.aligned.x4.trans.m8n8.shared.b16 [%0], {%1, %2, %3, "     \
      "%4};\n" ::"r"(addr),                                                    \
      "r"(R0), "r"(R1), "r"(R2), "r"(R3))
// mma m16n8k16
#define HMMA16816(RD0, RD1, RA0, RA1, RA2, RA3, RB0, RB1, RC0, RC1)            \
  asm volatile(                                                                \
      "mma.sync.aligned.m16n8k16.row.col.f16.f16.f16.f16 {%0, %1}, {%2, %3, "  \
      "%4, %5}, {%6, %7}, {%8, %9};\n"                                         \
      : "=r"(RD0), "=r"(RD1)                                                   \
      : "r"(RA0), "r"(RA1), "r"(RA2), "r"(RA3), "r"(RB0), "r"(RB1), "r"(RC0),  \
        "r"(RC1))

HOST_DEVICE_INLINE
int div_ceil(int a, int b) { return (a % b != 0) ? (a / b + 1) : (a / b); }

// i: row index; j: col index.
// e.g kColStride = 16, kStep = 8 -> load 8 half as 128 bits memory issue.
template <const int kColStride = 16, const int kStep = 8>
static __device__ __forceinline__ int swizzle_permuted_j(int i, int j) {
  // for col_stride > 16, we have to permute it using col major ZigZag order.
  // e.g, A smem logical layout [Br,d]=[Br,64] -> store layout [4][Br][16].
  static_assert(kColStride <= 16, "kColStride must <= 16");
  // swizzle: ((int(j / kStep) ^ int(i / 4)) % int(kColStride / kStep)) * kStep;
  static_assert(kStep == 4 || kStep == 8, "kStep must be 8 or 4.");
  static_assert(kColStride % kStep == 0,
                "kColStride must be multiple of kStep.");
  if constexpr (kStep == 8) {
    return (((j >> 3) ^ (i >> 2)) % (kColStride >> 3)) << 3;
  } else {
    static_assert(kStep == 4);
    return (((j >> 2) ^ (i >> 2)) % (kColStride >> 2)) << 2;
  }
}

// i: row index; j: col index
template <const int kMmaAtomK = 16>
static __device__ __forceinline__ int swizzle_permuted_A_j(int i, int j) {
  // -------------------
  // -col 0~16, step 8--
  // -------------------
  // | row 0  | (0, 8) |
  // | row 1  | (0, 8) |
  // | row 2  | (0, 8) |
  // | row 3  | (0, 8) |
  // -------------------
  // | row 4  | (8, 0) |
  // | row 5  | (8, 0) |
  // | row 6  | (8, 0) |
  // | row 7  | (8, 0) |
  // -------------------
  // | row 8  | (0, 8) |
  // | row 9  | (0, 8) |
  // | row 10 | (0, 8) |
  // | row 11 | (0, 8) |
  // -------------------
  // | row 12 | (8, 0) |
  // | row 13 | (8, 0) |
  // | row 14 | (8, 0) |
  // | row 15 | (8, 0) |
  // -------------------
  return swizzle_permuted_j<kMmaAtomK, 8>(i, j);
}

// i: row index; j: col index
template <const int kMmaAtomK = 16>
static __device__ __forceinline__ int swizzle_permuted_B_j(int i, int j) {
  // -------------------
  // -col 0~16, step 8--
  // -------------------
  // | row 0  | (0, 8) |
  // | row 1  | (0, 8) |
  // | row 2  | (0, 8) |
  // | row 3  | (0, 8) |
  // -------------------
  // | row 4  | (8, 0) |
  // | row 5  | (8, 0) |
  // | row 6  | (8, 0) |
  // | row 7  | (8, 0) |
  // -------------------
  // | row 8  | (0, 8) |
  // | row 9  | (0, 8) |
  // | row 10 | (0, 8) |
  // | row 11 | (0, 8) |
  // -------------------
  // | row 12 | (8, 0) |
  // | row 13 | (8, 0) |
  // | row 14 | (8, 0) |
  // | row 15 | (8, 0) |
  // -------------------
  return swizzle_permuted_j<kMmaAtomK, 8>(i, j);
}

// NN: A/B/C All row major
// TN: A row major MxK, B col major NxK, C row major MxN
// 128x128, mma2x4, warp4x4(64,32,16), stages, block swizzle, dsmem
template <const int MMA_M = 16, const int MMA_N = 8, const int MMA_K = 16,
          const int MMA_TILE_M = 2, const int MMA_TILE_N = 4,
          const int WARP_TILE_M = 4, const int WARP_TILE_N = 4,
          const int A_PAD = 0, const int B_PAD = 0, const int K_STAGE = 2,
          const bool BLOCK_SWIZZLE = false>
__global__ void __launch_bounds__(256)
    hgemm_mma_m16n8k16_mma2x4_warp4x4_stages_dsmem_tn_swizzle_kernel(
        half *A, half *B, half *C, int M, int N, int K) {
  // BLOCK_SWIZZLE 0/1 control use block swizzle or not.
  const int bx = ((int)BLOCK_SWIZZLE) * blockIdx.z * gridDim.x + blockIdx.x;
  const int by = blockIdx.y;
  const int NUM_K_TILES = div_ceil(K, MMA_K);
  constexpr int BM = MMA_M * MMA_TILE_M * WARP_TILE_M; // 16*2*4=128
  constexpr int BN = MMA_N * MMA_TILE_N * WARP_TILE_N; // 8*4*4=128
  constexpr int BK = MMA_K;                            // 16

  extern __shared__ half smem[];
  half *s_a = smem;
  half *s_b = smem + K_STAGE * BM * (BK + A_PAD);
  constexpr int s_a_stage_offset = BM * (BK + A_PAD); // BMxBK 128*16
  constexpr int s_b_stage_offset = BN * (BK + B_PAD); // BNxBK 128*16

  const int tid = threadIdx.y * blockDim.x + threadIdx.x; // within block
  const int warp_id = tid / WARP_SIZE; // 0~7 warp_id within block
  const int lane_id = tid % WARP_SIZE; // 0~31
  const int warp_m = warp_id % 2;      // 0,1
  const int warp_n = warp_id / 2;      // 0,1,2,3

  int load_smem_a_m = tid / 2;                 // row 0~127
  int load_smem_a_k = (tid % 2 == 0) ? 0 : 8;  // col 0,8
  int load_smem_b_n = tid / 2;                 // row 0~127
  int load_smem_b_k = (tid % 2 == 0) ? 0 : 8;  // col 0,8
  int load_gmem_a_m = by * BM + load_smem_a_m; // global row of c
  int load_gmem_b_n = bx * BN + load_smem_b_n; // global col of c
  if (load_gmem_a_m >= M || load_gmem_b_n >= N)
    return;

  uint32_t RA[WARP_TILE_M][4];
  uint32_t RB[WARP_TILE_N][2];
  uint32_t RC[WARP_TILE_M][WARP_TILE_N][2];
#pragma unroll
  for (int i = 0; i < WARP_TILE_M; ++i) {
#pragma unroll
    for (int j = 0; j < WARP_TILE_N; ++j) {
      RC[i][j][0] = 0;
      RC[i][j][1] = 0;
    }
  }

  uint32_t smem_a_base_ptr = __cvta_generic_to_shared(s_a);
  uint32_t smem_b_base_ptr = __cvta_generic_to_shared(s_b);

#pragma unroll
  for (int k = 0; k < (K_STAGE - 1); ++k) {     // 0, 1
    int load_gmem_a_k = k * BK + load_smem_a_k; // global col of a
    int load_gmem_a_addr = load_gmem_a_m * K + load_gmem_a_k;
    int load_gmem_b_k = k * BK + load_smem_b_k; // global col of b
    int load_gmem_b_addr = load_gmem_b_n * K + load_gmem_b_k;

    uint32_t load_smem_a_ptr =
        (smem_a_base_ptr +
         (k * s_a_stage_offset + load_smem_a_m * (BK + A_PAD) +
          swizzle_permuted_A_j<MMA_K>(load_smem_a_m, load_smem_a_k)) *
             sizeof(half));
    CP_ASYNC_CG(load_smem_a_ptr, &A[load_gmem_a_addr], 16);

    uint32_t load_smem_b_ptr =
        (smem_b_base_ptr +
         (k * s_b_stage_offset + load_smem_b_n * (BK + B_PAD) +
          swizzle_permuted_B_j<MMA_K>(load_smem_b_n, load_smem_b_k)) *
             sizeof(half));
    CP_ASYNC_CG(load_smem_b_ptr, &B[load_gmem_b_addr], 16);

    CP_ASYNC_COMMIT_GROUP();
  }

  CP_ASYNC_WAIT_GROUP(K_STAGE - 2); // s2->0, s3->1, s4->2
  __syncthreads();

#pragma unroll
  for (int k = (K_STAGE - 1); k < NUM_K_TILES; ++k) {
    // gmem -> smem
    // s2/4 can use bitwise ops but s3 can not, so, we use mod
    // ops for all stages kernel. s2: (k + 1)&1, s4: (k + 1)&3
    // s3: (k + 1) % 3
    int smem_sel = (k + 1) % K_STAGE; // s3 k 2->0, k 3->1, k 4->2...
    int smem_sel_next = k % K_STAGE;  // s3 k 2->2, k 3->0, k 4->1...

    int load_gmem_a_k = k * BK + load_smem_a_k; // global col of a
    int load_gmem_a_addr = load_gmem_a_m * K + load_gmem_a_k;
    int load_gmem_b_k = k * BK + load_smem_b_k; // global col of b
    int load_gmem_b_addr = load_gmem_b_n * K + load_gmem_b_k;

    uint32_t load_smem_a_ptr =
        (smem_a_base_ptr +
         (smem_sel_next * s_a_stage_offset + load_smem_a_m * (BK + A_PAD) +
          swizzle_permuted_A_j<MMA_K>(load_smem_a_m, load_smem_a_k)) *
             sizeof(half));
    CP_ASYNC_CG(load_smem_a_ptr, &A[load_gmem_a_addr], 16);

    uint32_t load_smem_b_ptr =
        (smem_b_base_ptr +
         (smem_sel_next * s_b_stage_offset + load_smem_b_n * (BK + B_PAD) +
          swizzle_permuted_B_j<MMA_K>(load_smem_b_n, load_smem_b_k)) *
             sizeof(half));
    CP_ASYNC_CG(load_smem_b_ptr, &B[load_gmem_b_addr], 16);

    CP_ASYNC_COMMIT_GROUP();

// smem -> reg
#pragma unroll
    for (int i = 0; i < WARP_TILE_M; ++i) {
      int warp_smem_a_m = warp_m * (MMA_M * WARP_TILE_M) + i * MMA_M;
      int lane_smem_a_m = warp_smem_a_m + lane_id % 16; // 0~15
      int lane_smem_a_k = (lane_id / 16) * 8;           // 0,8
      uint32_t lane_smem_a_ptr =
          (smem_a_base_ptr +
           (smem_sel * s_a_stage_offset + lane_smem_a_m * (BK + A_PAD) +
            swizzle_permuted_A_j<MMA_K>(lane_smem_a_m, lane_smem_a_k)) *
               sizeof(half));
      LDMATRIX_X4(RA[i][0], RA[i][1], RA[i][2], RA[i][3], lane_smem_a_ptr);
    }

#pragma unroll
    for (int j = 0; j < WARP_TILE_N; ++j) {
      int warp_smem_b_n = warp_n * (MMA_N * WARP_TILE_N) + j * MMA_N;
      int lane_smem_b_n = warp_smem_b_n + lane_id % 8; // 0~7, MMA_N=8
      int lane_smem_b_k = ((lane_id / 8) % 2) * 8;     // 0,8
      uint32_t lane_smem_b_ptr =
          (smem_b_base_ptr +
           (smem_sel * s_b_stage_offset + lane_smem_b_n * (BK + B_PAD) +
            swizzle_permuted_B_j<MMA_K>(lane_smem_b_n, lane_smem_b_k)) *
               sizeof(half));
      LDMATRIX_X2(RB[j][0], RB[j][1], lane_smem_b_ptr);
    }

// MMA compute
#pragma unroll
    for (int i = 0; i < WARP_TILE_M; ++i) {
#pragma unroll
      for (int j = 0; j < WARP_TILE_N; ++j) {
        HMMA16816(RC[i][j][0], RC[i][j][1], RA[i][0], RA[i][1], RA[i][2],
                  RA[i][3], RB[j][0], RB[j][1], RC[i][j][0], RC[i][j][1]);
      }
    }

    CP_ASYNC_WAIT_GROUP(K_STAGE - 2);
    __syncthreads();
  }

  // make sure all memory issues ready.
  if ((K_STAGE - 2) > 0) {
    CP_ASYNC_WAIT_GROUP(0);
    __syncthreads();
  }

  // processing last (K_STAGE-1) k iters.
  {
#pragma unroll
    for (int k = 0; k < (K_STAGE - 1); k++) {
      int stage_sel = ((NUM_K_TILES - (K_STAGE - 1) + k) % K_STAGE);
// ldmatrix for s_a, ldmatrix.trans for s_b.
// smem -> reg
#pragma unroll
      for (int i = 0; i < WARP_TILE_M; ++i) {
        int warp_smem_a_m = warp_m * (MMA_M * WARP_TILE_M) + i * MMA_M;
        int lane_smem_a_m = warp_smem_a_m + lane_id % 16; // 0~15
        int lane_smem_a_k = (lane_id / 16) * 8;           // 0,8
        uint32_t lane_smem_a_ptr =
            (smem_a_base_ptr +
             (stage_sel * s_a_stage_offset + lane_smem_a_m * (BK + A_PAD) +
              swizzle_permuted_A_j<MMA_K>(lane_smem_a_m, lane_smem_a_k)) *
                 sizeof(half));
        LDMATRIX_X4(RA[i][0], RA[i][1], RA[i][2], RA[i][3], lane_smem_a_ptr);
      }

#pragma unroll
      for (int j = 0; j < WARP_TILE_N; ++j) {
        int warp_smem_b_n = warp_n * (MMA_N * WARP_TILE_N) + j * MMA_N;
        int lane_smem_b_n = warp_smem_b_n + lane_id % 8; // 0~7, MMA_N=8
        int lane_smem_b_k = ((lane_id / 8) % 2) * 8;     // 0,8
        uint32_t lane_smem_b_ptr =
            (smem_b_base_ptr +
             (stage_sel * s_b_stage_offset + lane_smem_b_n * (BK + B_PAD) +
              swizzle_permuted_B_j<MMA_K>(lane_smem_b_n, lane_smem_b_k)) *
                 sizeof(half));
        LDMATRIX_X2(RB[j][0], RB[j][1], lane_smem_b_ptr);
      }

// MMA compute
#pragma unroll
      for (int i = 0; i < WARP_TILE_M; ++i) {
#pragma unroll
        for (int j = 0; j < WARP_TILE_N; ++j) {
          HMMA16816(RC[i][j][0], RC[i][j][1], RA[i][0], RA[i][1], RA[i][2],
                    RA[i][3], RB[j][0], RB[j][1], RC[i][j][0], RC[i][j][1]);
        }
      }
    }
  }

  {
    for (int i = 0; i < WARP_TILE_M; ++i) {
// How to use LDST128BITS here? __shfl_sync -> lane 0 -> store 8 half.
// thus, we only need 8 memory issues with 128 bits after shfl_sync.
#pragma unroll
      for (int j = 0; j < WARP_TILE_N; ++j) {
        // reuse RA[4][4] for collective store, reduce register pressure.
        RA[0][0] = RC[i][j][0];
        RA[1][0] = RC[i][j][1];
        RA[0][1] = __shfl_sync((0xffffffff), RC[i][j][0], lane_id + 1);
        RA[0][2] = __shfl_sync((0xffffffff), RC[i][j][0], lane_id + 2);
        RA[0][3] = __shfl_sync((0xffffffff), RC[i][j][0], lane_id + 3);
        RA[1][1] = __shfl_sync((0xffffffff), RC[i][j][1], lane_id + 1);
        RA[1][2] = __shfl_sync((0xffffffff), RC[i][j][1], lane_id + 2);
        RA[1][3] = __shfl_sync((0xffffffff), RC[i][j][1], lane_id + 3);

        if (lane_id % 4 == 0) {
          int store_warp_smem_c_m = warp_m * (MMA_M * WARP_TILE_M) + i * MMA_M;
          int store_lane_gmem_c_m = by * BM + store_warp_smem_c_m + lane_id / 4;
          int store_warp_smem_c_n = warp_n * (MMA_N * WARP_TILE_N) + j * MMA_N;
          int store_lane_gmem_c_n = bx * BN + store_warp_smem_c_n;
          int store_gmem_c_addr_0 =
              store_lane_gmem_c_m * N + store_lane_gmem_c_n;
          int store_gmem_c_addr_1 =
              (store_lane_gmem_c_m + 8) * N + store_lane_gmem_c_n;
          LDST128BITS(C[store_gmem_c_addr_0]) = LDST128BITS(RA[0][0]);
          LDST128BITS(C[store_gmem_c_addr_1]) = LDST128BITS(RA[1][0]);
        }
      } // end for WARP_TILE_N
    } // end for WARP_TILE_M
  }
}

// build cpp binary
#ifndef NO_MMA_HGEMM_BIN

#include "utils.h"

// 128x128, mma2x4, warp4x4(64,32,16), stages, block&smem swizzle, dsmem, TN
#define LAUNCH_16816_STAGE_SWIZZLE_MMA2x4_WARP4x4_DSMEM_TN_X2_KERNEL(stages,   \
                                                                     stride)   \
  {                                                                            \
    const int smem_max_size = ((stages) * BM * (BK + A_PAD) * sizeof(half) +   \
                               (stages) * BN * (BK + B_PAD) * sizeof(half));   \
    cudaFuncSetAttribute(                                                      \
        hgemm_mma_m16n8k16_mma2x4_warp4x4_stages_dsmem_tn_swizzle_kernel<      \
            MMA_M, MMA_N, MMA_K, MMA_TILE_M, MMA_TILE_N, WARP_TILE_M,          \
            WARP_TILE_N, A_PAD, B_PAD, (stages), true>,                        \
        cudaFuncAttributeMaxDynamicSharedMemorySize, 98304);                   \
    const int N_SWIZZLE = (N + (stride) - 1) / (stride);                       \
    dim3 block(NUM_THREADS);                                                   \
    dim3 grid((div_ceil(N, BN) + N_SWIZZLE - 1) / N_SWIZZLE, div_ceil(M, BM),  \
              N_SWIZZLE);                                                      \
    hgemm_mma_m16n8k16_mma2x4_warp4x4_stages_dsmem_tn_swizzle_kernel<          \
        MMA_M, MMA_N, MMA_K, MMA_TILE_M, MMA_TILE_N, WARP_TILE_M, WARP_TILE_N, \
        A_PAD, B_PAD, (stages), true>                                          \
        <<<grid, block, smem_max_size>>>(a, b, c, M, N, K);                    \
  }

template <const int K_STAGE = 2, const int BLOCK_SWIZZLE_STRIDE = 2048>
void lanunch_hgemm_mma_m16n8k16_swizzle_tn(half *a, half *b, half *c, int M,
                                           int N, int K) {
  constexpr int MMA_M = 16;
  constexpr int MMA_N = 8;
  constexpr int MMA_K = 16;
  constexpr int MMA_TILE_M = 2;
  constexpr int MMA_TILE_N = 4;
  constexpr int WARP_TILE_M = 4;
  constexpr int WARP_TILE_N = 4;
  constexpr int A_PAD = 0; // apply smem swizzle
  constexpr int B_PAD = 0; // apply smem swizzle
  constexpr int NUM_THREADS =
      (MMA_TILE_M * MMA_TILE_N * WARP_SIZE); // 2 * 4 * 32 = 256
  constexpr int BM = MMA_M * MMA_TILE_M * WARP_TILE_M;
  constexpr int BN = MMA_N * MMA_TILE_N * WARP_TILE_N;
  constexpr int BK = MMA_K;
  // s2: 2*128*(32)*2=16KB, 2*32*(128+16)*2=18KB, ~35KB
  // s3: 3*128*(32)*2=24KB, 3*32*(128+16)*2=27KB, ~51KB
  // s4: 4*128*(32)*2=32KB, 4*32*(128+16)*2=36KB, ~68KB
  // s5: 5*128*(32)*2=40KB, 5*32*(128+16)*2=45KB, ~85KB
  LAUNCH_16816_STAGE_SWIZZLE_MMA2x4_WARP4x4_DSMEM_TN_X2_KERNEL(
      K_STAGE, BLOCK_SWIZZLE_STRIDE);
}

#ifdef HGEMM_MMA_DEBUG
#include <iostream>
#endif

int main(int argc, char *argv[]) {
// ncu --metrics
// sm__sass_l1tex_data_bank_conflicts_pipe_lsu_mem_shared_op_ldsm
// ./hgemm_mma_stage_tn_swizzle.80.debug.bin 1024 1024 1024 0 1
#ifdef HGEMM_MMA_DEBUG
  const int test_num = 1;
#else
  const int test_num = 64;
#endif
  int M_list[test_num];
  int N_list[test_num];
  int K_list[test_num];

  for (int i = 0; i < test_num; i++) {
    M_list[i] = (i + 1) * 256;
    N_list[i] = (i + 1) * 256;
    K_list[i] = (i + 1) * 256;
  }

#ifdef HGEMM_MMA_DEBUG
  if (argc > 1)
    M_list[0] = std::stoi(argv[1]);
  if (argc > 2)
    N_list[0] = std::stoi(argv[2]);
  if (argc > 3)
    K_list[0] = std::stoi(argv[3]);
#endif

#ifdef HGEMM_MMA_DEBUG
  int outer_repeat = 1, inner_repeat = 1, warmup = 1;
  if (argc > 4)
    warmup = std::stoi(argv[4]);
  if (argc > 5)
    inner_repeat = std::stoi(argv[5]);
#else
  int outer_repeat = 10, inner_repeat = 1, warmup = 1;
#endif

  printf(
      "ALGO = MMA16816 HGEMM TN MMA=2x4 WARP=4x4 STAGES=2 BLOCK SWIZZLE=2048, "
      "SMEM SWIZZLE, B LDMATRIX.X2\n");
#ifndef HGEMM_MMA_DEBUG
  for (int j = 0; j < 5; j++) {
    int M = M_list[j], N = N_list[j], K = K_list[j];
    float max_error = gemm_error_check_tn<half>(
        lanunch_hgemm_mma_m16n8k16_swizzle_tn<2, 2048>, M, N, K);
    printf("M N K = %6d %6d %6d, ", M, N, K);
    printf("Max Error = %f\n", max_error);
  }
#endif

  for (int j = 0; j < test_num; j++) {
    int M = M_list[j], N = N_list[j], K = K_list[j];

    double max_sec = 0.0;
    double min_sec = DBL_MAX;
    double total_sec = 0.0;

    for (int k = 0; k < outer_repeat; k++) {
      double this_sec =
          perf_gemm<half>(lanunch_hgemm_mma_m16n8k16_swizzle_tn<2, 2048>, M, N,
                          K, inner_repeat, warmup);
      max_sec = max(max_sec, this_sec);
      min_sec = min(min_sec, this_sec);
      total_sec += this_sec;
    }

    // 1 TFLOPS = 10^12 FLOPS
    // ref: https://imgtec.eetrend.com/blog/2021/100062210.html.
    double avg_sec = total_sec / outer_repeat;
    double avg_Tflops = ((double)M) * N * K * 2 * 1e-12 / avg_sec;

    printf("M N K = %6d %6d %6d, W = %1d, R = %2d ", M, N, K, warmup,
           inner_repeat);
    printf("Time = %12.8lf %12.8lf %12.8lf s, ", min_sec, avg_sec, max_sec);
    printf("AVG Performance = %10.4lf Tflops\n", avg_Tflops);
  }

  return 0;
}

#else

#include <torch/extension.h>
#include <torch/types.h>
#define STRINGFY(str) #str
#define TORCH_BINDING_COMMON_EXTENSION(func)                                   \
  m.def(STRINGFY(func), &func, STRINGFY(func));

#define CHECK_TORCH_TENSOR_DTYPE(T, th_type)                                   \
  if (((T).options().dtype() != (th_type))) {                                  \
    std::cout << "Tensor Info:" << (T).options() << std::endl;                 \
    throw std::runtime_error("values must be " #th_type);                      \
  }

#define CHECK_TORCH_TENSOR_SHAPE(T, S0, S1)                                    \
  if (((T).size(0) != (S0)) || ((T).size(1) != (S1))) {                        \
    throw std::runtime_error("Tensor size mismatch!");                         \
  }

// 128x128, mma2x4, warp4x4(64,32,16), stages, block&smem swizzle, dsmem, TN
#define LAUNCH_16816_STAGE_SWIZZLE_MMA2x4_WARP4x4_DSMEM_TN_X2_KERNEL(stages,   \
                                                                     stride)   \
  {                                                                            \
    const int smem_max_size = ((stages) * BM * (BK + A_PAD) * sizeof(half) +   \
                               (stages) * BN * (BK + B_PAD) * sizeof(half));   \
    cudaFuncSetAttribute(                                                      \
        hgemm_mma_m16n8k16_mma2x4_warp4x4_stages_dsmem_tn_swizzle_kernel<      \
            MMA_M, MMA_N, MMA_K, MMA_TILE_M, MMA_TILE_N, WARP_TILE_M,          \
            WARP_TILE_N, A_PAD, B_PAD, (stages), true>,                        \
        cudaFuncAttributeMaxDynamicSharedMemorySize, 98304);                   \
    const int N_SWIZZLE = (N + (stride) - 1) / (stride);                       \
    dim3 block(NUM_THREADS);                                                   \
    dim3 grid((div_ceil(N, BN) + N_SWIZZLE - 1) / N_SWIZZLE, div_ceil(M, BM),  \
              N_SWIZZLE);                                                      \
    hgemm_mma_m16n8k16_mma2x4_warp4x4_stages_dsmem_tn_swizzle_kernel<          \
        MMA_M, MMA_N, MMA_K, MMA_TILE_M, MMA_TILE_N, WARP_TILE_M, WARP_TILE_N, \
        A_PAD, B_PAD, (stages), true><<<grid, block, smem_max_size>>>(         \
        reinterpret_cast<half *>(a.data_ptr()),                                \
        reinterpret_cast<half *>(b.data_ptr()),                                \
        reinterpret_cast<half *>(c.data_ptr()), M, N, K);                      \
  }

#define LAUNCH_16816_STAGE_NO_SWIZZLE_MMA2x4_WARP4x4_DSMEM_TN_X2_KERNEL(       \
    stages)                                                                    \
  {                                                                            \
    const int smem_max_size = ((stages) * BM * (BK + A_PAD) * sizeof(half) +   \
                               (stages) * BN * (BK + B_PAD) * sizeof(half));   \
    cudaFuncSetAttribute(                                                      \
        hgemm_mma_m16n8k16_mma2x4_warp4x4_stages_dsmem_tn_swizzle_kernel<      \
            MMA_M, MMA_N, MMA_K, MMA_TILE_M, MMA_TILE_N, WARP_TILE_M,          \
            WARP_TILE_N, A_PAD, B_PAD, (stages), false>,                       \
        cudaFuncAttributeMaxDynamicSharedMemorySize, 98304);                   \
    dim3 block(NUM_THREADS);                                                   \
    dim3 grid(div_ceil(N, BN), div_ceil(M, BM));                               \
    hgemm_mma_m16n8k16_mma2x4_warp4x4_stages_dsmem_tn_swizzle_kernel<          \
        MMA_M, MMA_N, MMA_K, MMA_TILE_M, MMA_TILE_N, WARP_TILE_M, WARP_TILE_N, \
        A_PAD, B_PAD, (stages), false><<<grid, block, smem_max_size>>>(        \
        reinterpret_cast<half *>(a.data_ptr()),                                \
        reinterpret_cast<half *>(b.data_ptr()),                                \
        reinterpret_cast<half *>(c.data_ptr()), M, N, K);                      \
  }

// 128x128, mma2x4, warp4x4(64,32,16), stages, block&smem swizzle, dsmem
void hgemm_mma_m16n8k16_mma2x4_warp4x4_stages_dsmem_tn_swizzle(
    torch::Tensor a, torch::Tensor b, torch::Tensor c, int stages, bool swizzle,
    int swizzle_stride) {
  CHECK_TORCH_TENSOR_DTYPE(a, torch::kHalf)
  CHECK_TORCH_TENSOR_DTYPE(b, torch::kHalf)
  CHECK_TORCH_TENSOR_DTYPE(c, torch::kHalf)
  const int M = a.size(0);
  const int K = a.size(1);
  const int N = b.size(1);
  CHECK_TORCH_TENSOR_SHAPE(a, M, K)
  CHECK_TORCH_TENSOR_SHAPE(b, K, N)
  CHECK_TORCH_TENSOR_SHAPE(c, M, N)
  constexpr int MMA_M = 16;
  constexpr int MMA_N = 8;
  constexpr int MMA_K = 16;
  constexpr int MMA_TILE_M = 2;
  constexpr int MMA_TILE_N = 4;
  constexpr int WARP_TILE_M = 4;
  constexpr int WARP_TILE_N = 4;
  constexpr int A_PAD = 0; // 0,8,16
  constexpr int B_PAD = 0; // 0,8,16
  constexpr int NUM_THREADS =
      (MMA_TILE_M * MMA_TILE_N * WARP_SIZE); // 2 * 4 * 32 = 256
  constexpr int BM = MMA_M * MMA_TILE_M * WARP_TILE_M;
  constexpr int BN = MMA_N * MMA_TILE_N * WARP_TILE_N;
  constexpr int BK = MMA_K;

  if (swizzle) {
    // assert(swizzle_stride % 256 == 0);
    switch (stages) {
    case 2:
      LAUNCH_16816_STAGE_SWIZZLE_MMA2x4_WARP4x4_DSMEM_TN_X2_KERNEL(
          2, swizzle_stride);
      break;
    case 3:
      LAUNCH_16816_STAGE_SWIZZLE_MMA2x4_WARP4x4_DSMEM_TN_X2_KERNEL(
          3, swizzle_stride);
      break;
    case 4:
      LAUNCH_16816_STAGE_SWIZZLE_MMA2x4_WARP4x4_DSMEM_TN_X2_KERNEL(
          4, swizzle_stride);
      break;
    case 5:
      LAUNCH_16816_STAGE_SWIZZLE_MMA2x4_WARP4x4_DSMEM_TN_X2_KERNEL(
          5, swizzle_stride);
      break;
    default:
      LAUNCH_16816_STAGE_SWIZZLE_MMA2x4_WARP4x4_DSMEM_TN_X2_KERNEL(
          2, swizzle_stride);
      break;
    }
  } else {
    switch (stages) {
    case 2:
      LAUNCH_16816_STAGE_NO_SWIZZLE_MMA2x4_WARP4x4_DSMEM_TN_X2_KERNEL(2);
      break;
    case 3:
      LAUNCH_16816_STAGE_NO_SWIZZLE_MMA2x4_WARP4x4_DSMEM_TN_X2_KERNEL(3);
      break;
    case 4:
      LAUNCH_16816_STAGE_NO_SWIZZLE_MMA2x4_WARP4x4_DSMEM_TN_X2_KERNEL(4);
      break;
    case 5:
      LAUNCH_16816_STAGE_NO_SWIZZLE_MMA2x4_WARP4x4_DSMEM_TN_X2_KERNEL(5);
      break;
    default:
      LAUNCH_16816_STAGE_NO_SWIZZLE_MMA2x4_WARP4x4_DSMEM_TN_X2_KERNEL(2);
      break;
    }
  }
}

#endif

本篇小结 ​

  1. 128bit 访存的 bank 冲突:16B 横跨 4 bank,s_a[128][16] 的行距 32B 造成 4 行一循环的 bank 重复,8 路冲突——大 tile + 128bit 访存下 padding 的浪费付不起(smem 预算挤掉流水级数)
  2. swizzle 核心:块号 XOR (行号/组距),一行位运算让相邻行组在 16B 块粒度互相错开——零存储浪费、零对齐破坏、参数(kStep=8/kColStride=16/组距4)全部由访存形态决定
  3. 侵入式但局部:写端 cp.async 和读端 ldmatrix 插同一个换算函数,逻辑布局不变——"纯物理层优化"
  4. 与 10 篇打通:CuTe 的 Swizzle<B,M,S> 和手写 ((j>>3)^(i>>2)) 同源异记——10 篇欠的"不深入"在这里还清,19 篇 CuTe 版是完整闭环
  5. 瓶颈原则第四次出场:小形状(循环体访存主导)+6.6%,大形状(计算主导,冲突被掩盖)-1%——swizzle 是对的工具,但要打在对的形状上