下面是一份"照着读就能写"的清单,全部基于仓库实际行号(未改动任何文件)。
kernels/common/pack.cuh(30 行,整个仓库的绑定"语法糖"只有一行)pack.cuh:15 → #define torch_pybinding_func(f) m.def(#f, &f, #f)f 注册进 pybind 模块 m,名字 = C++ 函数名本身(#f 是字符串化),Python 侧就出现 lib.<函数名>。PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) 的 {} 里调用,因为宏展开后用到变量名 m。pack.cuh:13 LDST128BITS、pack.cuh:17 WARP_SIZE:写新 kernel 时直接复用,别重造。kernels/hgemm/hgemm.cu:884-934(这是唯一需要抄的"顶层实验接口"C++ 侧)884 #define CHECK_T(x) TORCH_CHECK(x.is_cuda() && x.is_contiguous(), ...)
886 #define binding_tiled_func_gen(name) ... # 生成 host 侧 launcher
919-922 binding_tiled_func_gen(hgemm_naive / bcf / bcf_dbf / bcf_dbf_rw);
924 extern void hgemm_cublas(torch::Tensor, torch::Tensor, torch::Tensor);
928 PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) { torch_pybinding_func(...); }
binding_tiled_func_gen 宏体(886-917 行)就是你的新 kernel 与 Python 之间唯一的契约,逐行看它做了什么:
CHECK_T(a/b/c) → 要求 cuda + contiguous(你的 A2/A2L 传 b_t 也要 contiguous,host 侧 .t().contiguous() 即可)。M=a.size(0), K=a.size(1), N=b.size(1) → 注意 B 的形状约定是 [K, N];A2/A2L 的 B 是 [N, K],所以 launcher 里 N/K 的取法要变(N=b.size(0), K=b.size(1)),别照抄。BM=BN=128, threads=256, dim3 blocks_per_grid((N+BN-1)/BN, (M+BM-1)/BM) → grid 顺序是 x=N 方向、y=M 方向。stream = at::cuda::getCurrentCUDAStream() → 必须在当前 stream 上跑。if (a.dtype()==torch::kHalf) ... else ... → fp16/bf16 分派。name##_kernel<128,128,32><<<grid, 256, 0, stream>>>(...) → 模板实参在这里写死。⚠️ 你要做
STAGES=3(48KB dynamic smem)时,这段宏不够用:还要在 launch 前加cudaFuncSetAttribute(kernel_ptr, cudaFuncAttributeMaxDynamicSharedMemorySize, bytes)并把 smem 大小作为<<<...>>>第 3 个参数传入。 现成写法参考(虽然那两个文件是 TMA、SM80 不可用,但只抄这段 opt-in 写法即可):
kernels/flash_attn/flash_attn.cu:595(cudaFuncSetAttribute+<<<..., smem_size, stream>>>)kernels/flash_decode/flash_decode.cu:637kernels/softmax/softmax.cu:498- dynamic smem 声明范例:
kernels/flash_attn/flash_attn.cu:282(extern __shared__ __align__(128) uint8_t smem_buf[];)
kernels/hgemm/cublas.cu("多 .cu 编成一个模块"的写法)void hgemm_cublas(torch::Tensor a, torch::Tensor b, torch::Tensor c)(36-70 行),故意不含 PYBIND11_MODULE。hgemm.cu:924 的 extern void hgemm_cublas(...); 声明 + torch_pybinding_func(hgemm_cublas) 注册。ablation.cu 想复用 cublas.cu,只需在 test.py 的 sources 里同时列上 ../hgemm/cublas.cu,并在 ablation.cu 里写同样的 extern 声明,两个 .cu 由 ninja 一起编译成一个 .so。kernels/sgemm/sgemm.cu:526-541(另一个 PYBIND 例子,确认"一函数一名字"的对应关系)sgemm.cublas / sgemm_naive / sgemm_tiling / sgemm_at_tiling / sgemm_at_bcf_swizzling / sgemm_at_bcf_swizzling_rw / sgemm_at_bcf_swizzling_dbf_rw / sgemm_tf32_*abl_A1_ld_shared、abl_A2_ld_shared_bt、abl_A2L_ldmatrix_bt、abl_A3_ldmatrix),Python 侧 lib.abl_A1_ld_shared(...) 直接对应。.py 顶层实验接口怎么写kernels/hgemm/test.py(这就是你要模仿的模板,逐块看)| 行号 | 内容 | 你要拿走什么 |
|---|---|---|
| 7-9 | from torch.utils.cpp_extension import load / torch.set_grad_enabled(False) |
编译入口、禁梯度 |
| 11-30 | load(name="hgemm_lib", sources=[...], extra_cuda_cflags=[...], extra_cflags=common_flags, verbose=True) |
唯一的编译+加载点:sources 里放你新写的 .cu;extra_cuda_cflags 里的 -U__CUDA_NO_BFLOAT16_CONVERSIONS__、--expt-relaxed-constexpr、--use_fast_math、-Xptxas -v 必须保留(最后一个会打印每个模板实例的 smem/寄存器用量,是文档 §7.4 的验证手段) |
| 33-65 | benchmark(op, a, b, c=None, warmup=10, rep=200, prefix=...) |
计时函数:warmup → torch.cuda.synchronize() → perf_counter() → rep 次 → sync;TFLOPS = M*N*K*2*rep/1e12/duration;prefix=="torch" 时把结果存为 baseline,其余算 speedup。A1/A2/A2L/A3 的表格就靠这个函数 + 不同 prefix 输出 |
| 67-73 | diff_check(a, b, prefix, eps=0.016) |
bf16 容差 0.016;A1~A3 之间应 torch.equal 级别为 0 |
| 76-104 | test_all()(多尺寸扫描,被注释掉) |
想扫 256/512/1024/4096/8192 时取消注释 |
| 107-155 | test_4096() |
你的主实验入口:造 a/b/c → torch.matmul baseline → lib.hgemm_cublas → lib.hgemm_naive/bcf/bcf_dbf/bcf_dbf_rw 各 benchmark + diff_check。你只需把 lib.hgemm_bcf_dbf 换成 lib.abl_A1/abl_A2/abl_A2L/abl_A3(并且 A2/A2L 要额外准备 b_t = b.t().contiguous(),在计时之外) |
| 157-160 | if __name__ == "__main__": test_4096() |
入口 |
kernels/hgemm/test_for_ncu.py(ncu 版)test.py 几乎相同,差别:warmup=0, rep=1,且不计时不校验,只为了让 ncu 采样一次。sources 写成相对路径 "hgemm.cu"(因为 ncu 脚本通常在同目录跑)。kernels/hgemm/run_ncu.sh(1 行):ncu -k regex:"gemm|Kernel" --set full -o profile_res -f python test_for_ncu.py
⇒ 你的 ablation/ 里照抄成 ncu -k regex:"ablation" --set full -o prof -f python test_for_ncu.py。kernels/sgemm/test.py + kernels/sgemm/readme.md(表格与命名范式)sgemm/test.py 的 load(name="gemm_lib", sources=[sgemm.cu, cublas.cu, sgemm_tf32.cu]) → 一个模块里放多个 .cu 的实例。sgemm/readme.md → 结果表格式(每档一行 ms + speedup + tflops 的 yaml 块)→ 你的 kernels/ablation/readme.md 就照这个格式填"表 2"。这条链要读的是 kernels/hgemm/hgemm.cu 的 kernel 主体(它是你的实验基座,A3 = 现有 bcf_dbf 的读路径):
| 位置 | 内容 | 原理要点 |
|---|---|---|
hgemm.cu:22-40 |
CP_ASYNC_CG / CP_ASYNC_COMMIT_GROUP / CP_ASYNC_WAIT_GROUP_0 / LDMATRIX_X4 / LDMATRIX_X2 / LDMATRIX_X2_TRANS |
全部是 asm volatile 内联 PTX。注意:SM80 多级流水需要 cp.async.wait_group N (N>0),本文件只有 WAIT_GROUP_0(宏名里的 0 就是 group 数) → 你要 STAGES=3 必须自己写 wait_group<1>,或从 kernels/hgemm_sm120/hgemm_sm120.cu 抄那段 cp_async_wait_group<N> 模板(那个文件整体 TMA 不可用,只抄这一个函数) |
hgemm.cu:43-56 |
M16N8K16_F16 / M16N8K16_BF16 |
mma 指令封装,A/B fragment 寄存器布局 |
hgemm.cu:15-19 |
SWIZZLE_A/B/C |
三个 swizzle 宏;A2/A2L 的预转置 B 需要新的 SWIZZLE_BT |
hgemm.cu:58-201 |
hgemm_naive_kernel |
最干净的读路径模板:grid swizzle(linear_id→bx,by)、cp.async 装载、ldmatrix 取 fragment、16 次 mma、写回 C。看这一份就懂"fragment 怎么来、mma 怎么吃" |
hgemm.cu:203-353 |
hgemm_bcf_kernel |
+ BCF swizzle |
hgemm.cu:354-602 |
hgemm_bcf_dbf_kernel |
+ 双缓冲(写死 2 buffer,你要改成 STAGES 模板参数就从这里改) |
hgemm.cu:603-882 |
hgemm_bcf_dbf_rw_kernel |
+ coalesced 写回(smem 暂存 C) |
hgemm.cu:119-147(naive 内) |
LDMATRIX_X4 / LDMATRIX_X2_TRANS 的实际寻址 a_row/a_col/b_row/b_col |
这几行就是 A3 的实现,你 A1/A2/A2L 要写的是同位置换寻址,其余(mma body、写回)照抄不动 |
hgemm.cu:884-917 |
CHECK_T + binding_tiled_func_gen |
host 侧 launcher(见 §1.2) |
调用链全景(文档 §6 的代码依据):
test.py: lib = load(sources=[...]) # ninja 现场编译 → ablation_lib.so
↓ import 即执行 PYBIND11_MODULE → m.def("abl_A1", ...) (pack.cuh:15)
↓ lib.abl_A1(a,b,c)
C++ launcher (hgemm.cu:886 宏) → 检查 contiguous → 取 M/N/K → grid/block → stream
↓ kernel<<<grid,256,smem,stream>>>(...)
device: grid swizzle → cp.async gmem→smem → wait_group + __syncthreads
→ ldmatrix/ld.shared smem→reg → mma → 写回 C
↓
benchmark(): warmup10 → sync → perf_counter → rep200 → sync → TFLOPS
建议新建目录 kernels/ablation/,完全不动 kernels/hgemm/*(保持历史数据可复现),需要写 6 个文件:
kernels/ablation/ablation.cu —— 唯一的 C++ 主体template <int LOAD_MODE, int READ_MODE, int B_LAYOUT, int STAGES, int SWIZZLE, typename T> __global__ void hgemm_ablation_kernel(...)extern __shared__ __align__(128) char smem_raw[];)+ cudaFuncSetAttribute(抄 flash_attn.cu:595 的写法),因为 STAGES=3 → 48KB、STAGES=4 → 64KB 静态 smem 放不下。hgemm.cu:58-201(naive)的骨架,只把 ①cp.async 装载段、②ldmatrix 段换成 if constexpr (READ_MODE==...) 三套寻址(文档 §3.4 已给出具体地址公式)。LOAD_MODE(cp.async vs ldg+st.shared)保留参数位,为阶梯 B 预留。ABL_BIND(NAME, LM, RM, BL, ST, SW) 宏(仿 hgemm.cu:886 的 binding_tiled_func_gen),但不能照抄它的宏体——你要加 cudaFuncSetAttribute、要按 B_LAYOUT 决定 B 的形状取法(N=b.size(0),K=b.size(1) vs N=b.size(1),K=b.size(0))、要传 smem 大小。extern void hgemm_cublas(torch::Tensor, torch::Tensor, torch::Tensor); + torch_pybinding_func(hgemm_cublas);(复用 baseline,注意 cublas.cu 期望 B 是 [K,N])。PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) { torch_pybinding_func(abl_A1); ... }。abl_A1_ld_shared / abl_A2_ld_shared_bt / abl_A2L_ldmatrix_bt / abl_A3_ldmatrix。kernels/ablation/test.py —— 顶层实验接口(PyBIND 的消费端)kernels/hgemm/test.py 全文,改:load(name="ablation_lib", sources=[ablation.cu, ../hgemm/cublas.cu], ...)。test_4096() 里:torch.matmul baseline → lib.hgemm_cublas → 四个 lib.abl_* 各 benchmark + diff_check。b_t = b.t().contiguous()(计时之外生成,且要连续分配以免引入额外 D2D 抖动),并 diff_check 时用 b 而非 b_t 算参考值。kernels/ablation/test_for_ncu.py + 4) kernels/ablation/run_ncu.shhgemm/test_for_ncu.py(warmup=0, rep=1)与 hgemm/run_ncu.sh(ncu -k regex:"ablation" --set full -o prof -f python test_for_ncu.py)。kernels/ablation/micro.cu + micro_test.py(文档 §4.3 强烈建议)DO_MMA 开关:DO_MMA=0 只做 fragment 载入并 acc ^= reg 防 DCE(纯读路径吞吐),DO_MMA=1 加 16 次 mma。micro_test.py 抄 test.py 的 benchmark,sources=[micro.cu]。kernels/ablation/readme.mdkernels/sgemm/readme.md 的格式:勾选清单(每档一行说明)+ yaml 结果块(ms / speedup / tflops)+ 复现命令(export TORCH_CUDA_ARCH_LIST=...)。name="ablation_lib" 与 hgemm_lib 不同,否则 torch 扩展缓存会串(缓存在 ~/.cache/torch_extensions)。sources 必须列全:.cu 增删后要改 sources(hgemm 目录 vs sgemm 目录的 name 分别是 hgemm_lib / gemm_lib,同一 .so 内多文件靠这个 list 合并)。extern __shared__ + cudaFuncSetAttribute 必须成对,否则 >48KB 直接 launch 失败。B_LAYOUT 变:A1/A3 是 [K,N],A2/A2L 是 [N,K],launcher 与 Python 侧要同时改,且 CHECK_T 的 contiguous 要求不能破。TORCH_CUDA_ARCH_LIST 必须 export,且测性能前 nvidia-smi -lgc/-lmc 锁频、测完 -rgc/-rmc 解锁,否则噪声大于 A1↔A2 的 1~5% 效应量。需要的话,下一步我可以按上面这份清单,把 kernels/ablation/ 的 6 个文件骨架写出来(仍然不改动 kernels/hgemm/*)。