liangsu9988 commited on
Commit
9022070
·
verified ·
1 Parent(s): e877859

Publish PI0.5 Thor kernels from 503100c (torch213-cxx11-cu130-aarch64-linux)

Browse files
Files changed (42) hide show
  1. CARD.md +28 -0
  2. README.md +135 -0
  3. SYNC.md +80 -0
  4. VALIDATION.md +155 -0
  5. benchmarks/README.md +11 -0
  6. benchmarks/RESULTS.md +144 -0
  7. benchmarks/benchmark.py +22 -2
  8. benchmarks/benchmark_bias.py +241 -0
  9. build.toml +75 -0
  10. build/torch213-cxx11-cu130-aarch64-linux/__init__.py +93 -2
  11. build/torch213-cxx11-cu130-aarch64-linux/{fp8_gemm_source_test.abi3.so → _fp8_gemm_cuda_503100c.abi3.so} +2 -2
  12. build/torch213-cxx11-cu130-aarch64-linux/_ops.py +3 -3
  13. build/torch213-cxx11-cu130-aarch64-linux/metadata.json +5 -9
  14. csrc/README.md +7 -0
  15. csrc/cublaslt_fp8_bias_sm110.cu +179 -0
  16. csrc/cublaslt_fp8_bias_sm110.cuh +21 -0
  17. csrc/cutlass_sm110_fp8_gemm.cu +176 -0
  18. csrc/cutlass_sm110_fp8_gemm.cuh +31 -0
  19. csrc/cutlass_sm120_block128_fp8_gemm.cu +273 -0
  20. csrc/cutlass_sm120_block128_fp8_gemm.cuh +51 -0
  21. csrc/fp8_block128_gemm_mma_sm89.cu +318 -0
  22. csrc/fp8_block128_gemm_mma_sm89.cuh +102 -0
  23. csrc/fp8_bs_gemm_device.cuh +1327 -0
  24. csrc/fp8_gemv_m1_sm120.cu +159 -0
  25. csrc/fp8_gemv_m1_sm120.cuh +31 -0
  26. csrc/fp8_gemv_m1_sm89.cu +188 -0
  27. csrc/fp8_gemv_m1_sm89.cuh +38 -0
  28. csrc/fp8_smallM_handtuned_ldmatrix_sm120.cu +377 -0
  29. csrc/fp8_smallM_handtuned_ldmatrix_sm120.cuh +68 -0
  30. csrc/fp8_smallM_handtuned_sm120.cu +338 -0
  31. csrc/fp8_smallM_handtuned_sm120.cuh +89 -0
  32. csrc/gemm_types_sm110.h +362 -0
  33. examples/README.md +3 -0
  34. flake.lock +118 -0
  35. flake.nix +20 -0
  36. scripts/README.md +3 -0
  37. tests/README.md +9 -0
  38. tests/test_fp8_gemm.py +698 -0
  39. torch-ext/README.md +6 -0
  40. torch-ext/fp8_gemm/__init__.py +345 -0
  41. torch-ext/torch_binding.cpp +577 -0
  42. torch-ext/torch_binding.h +54 -0
CARD.md ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # flashrt/fp8-gemm
2
+
3
+ FlashRT native CUDA FP8 GEMV/GEMM kernels for low-latency transformer and
4
+ diffuser linear layers.
5
+
6
+ The block-128 scaled API supports Ada `sm_89` and Blackwell `sm_120a`.
7
+ The per-tensor API supports Blackwell `sm_110a` (Jetson AGX Thor) and
8
+ `sm_120a`. SM110 uses the production FlashRT Sq/T1/Wide CUTLASS family and has
9
+ been swept across PI0.5, GROOT, Cosmos Edge, and LingBot VLA projection shapes.
10
+
11
+ ## Functions
12
+
13
+ - `fp8_linear_bf16(input, weight, alpha=1.0, out=None, variant=0)`
14
+ - `fp8_linear_residual_bf16(input, weight, residual, alpha=1.0, variant=0)`
15
+ - `fp8_linear_bias_bf16(input, weight, bias, alpha=1.0, out=None)`
16
+ - `fp8_linear_bias_residual_bf16(input, weight, bias, residual, alpha=1.0)`
17
+ - `fp8_linear_bias_gelu_bf16(input, weight, bias, alpha=1.0, out=None)`
18
+ - `fp8_blockwise_linear_bf16(input, weight, input_scale, weight_scale, out=None)`
19
+ - `select_fp8_linear_tile(m, n, k, variant=0)`
20
+
21
+ On SM110, keep `variant=0` for the tuned public dispatcher. Variants `1`, `2`,
22
+ and `3` force Sq, T1, and Wide respectively for diagnostics.
23
+
24
+ SM110 also provides BF16 bias, in-place bias+residual, and tanh-GELU+bias
25
+ epilogues for SigLIP-style projection and MLP shapes. The validated large-M
26
+ plain GEMM band is `M=65..1024`.
27
+
28
+ See the repository README for shape contracts, validation status, and examples.
README.md ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # fp8-gemm
2
+
3
+ FlashRT native CUDA FP8 GEMV/GEMM kernels for low-latency transformer and
4
+ diffuser linear layers on NVIDIA Ada SM89 and Blackwell SM110/SM120 GPUs.
5
+
6
+ This package exposes the hand-tuned FP8 E4M3 decode and small-M kernels as
7
+ Tensor APIs for Hugging Face Kernel Hub. It is intended for model runtimes that
8
+ already hold activations and weights in FP8 and want a low-overhead BF16 output
9
+ linear path.
10
+
11
+ ## Available Functions
12
+
13
+ - `fp8_linear_bf16(input, weight, alpha=1.0, out=None, variant=0)`
14
+ - `fp8_linear_residual_bf16(input, weight, residual, alpha=1.0, variant=0)`
15
+ - `fp8_linear_bias_bf16(input, weight, bias, alpha=1.0, out=None)`
16
+ - `fp8_linear_bias_residual_bf16(input, weight, bias, residual, alpha=1.0)`
17
+ - `fp8_linear_bias_gelu_bf16(input, weight, bias, alpha=1.0, out=None)`
18
+ - `fp8_blockwise_linear_bf16(input, weight, input_scale, weight_scale, out=None)`
19
+ - `fp8_blockwise_swiglu_quantize_fp8(input, gate_up_weight, input_scale, gate_up_weight_scale, output=None, output_scale=None)`
20
+ - `select_fp8_linear_tile(m, n, k, variant=0)`
21
+
22
+ Tensor contract:
23
+
24
+ - `input`: `torch.float8_e4m3fn`, shape `(M, K)`, contiguous CUDA tensor.
25
+ - `weight`: `torch.float8_e4m3fn`, shape `(N, K)`, contiguous CUDA tensor.
26
+ - `out`: `torch.bfloat16`, shape `(M, N)`.
27
+ - `residual`: `torch.bfloat16`, shape `(1, N)` or `(N,)`, only supported for
28
+ the `M=1` decode GEMV path.
29
+ - `K % 16 == 0`; SM120 additionally requires `K % 32 == 0`.
30
+ - On SM120, `M == 1` uses dedicated GEMV and `2 <= M <= 64` uses small-M
31
+ GEMM tiles.
32
+ - On SM110 (Jetson AGX Thor), the per-tensor API uses the production FlashRT
33
+ CUTLASS Sq/T1/Wide family and supports the validated model-shape matrix from
34
+ decode through large vision/backbone rows. The large-M production band is
35
+ validated from `M=65` through `M=1024`, including PI0.5 prefill QKV, O,
36
+ gate/up, and down projections at `M=712..970`. `N` and `K` must be divisible
37
+ by 16.
38
+ - The three BF16 bias APIs are SM110-only. They accept BF16 `(N,)` bias and
39
+ preserve the same row-major FP8 `(M,K)` input and `(N,K)` weight contract.
40
+ The residual API updates a BF16 `(M,N)` tensor in place. The GELU API uses
41
+ the tanh approximation.
42
+ - SM110 `variant=0` is the production auto dispatcher. Diagnostic variants are
43
+ `1=Sq`, `2=T1`, and `3=Wide`; they are correctness-tested but should not be
44
+ pinned by model integrations without a shape-specific benchmark.
45
+ - The per-tensor kernels use Blackwell FP8 MMA instructions and are not valid
46
+ for SM89. SM89 support is provided by the blockwise API below.
47
+ - `alpha` is a host float. For per-tensor FP8 quantization, pass
48
+ `float(input_scale * weight_scale)` from your static calibration metadata.
49
+
50
+ The blockwise API uses a separate contract:
51
+
52
+ - `input`: FP8 E4M3 `(M, K)`.
53
+ - `weight`: FP8 E4M3 `(N, K)`.
54
+ - `input_scale`: FP32 `(M, K / 128)`.
55
+ - `weight_scale`: FP32 `(N / 128, K / 128)`.
56
+ - `N` and `K` must be divisible by 128; `M` is unrestricted.
57
+ - Output is BF16 `(M, N)`.
58
+ - On SM89, the blockwise API dispatches to the production FlashRT native
59
+ `mma.sync.aligned.m16n8k32` GEMM/GEMV implementation.
60
+ - On SM120, it dispatches to the production FlashRT CUTLASS block-scaled
61
+ implementation.
62
+ - SM110 is intentionally not claimed by the blockwise API; use the per-tensor
63
+ static-scale path there. Other architectures are rejected explicitly.
64
+
65
+ The fused SM89 producer accepts FP8 `(M,K)` input, FP8 `(2*N,K)` gate/up
66
+ weight, block-128 FP32 scales, and returns FP8 `(M,N)` plus FP32 `(M,N/128)`
67
+ output scales. Its public range is `1 <= M <= 256` with `N` and `K` divisible
68
+ by 128. It is rejected explicitly on non-SM89 GPUs.
69
+
70
+ ## Minimal Usage
71
+
72
+ ```python
73
+ from kernels import get_kernel
74
+ import torch
75
+
76
+ ops = get_kernel("flashrt/fp8-gemm", version=1, trust_remote_code=True)
77
+
78
+ x = torch.randn((16, 4096), device="cuda", dtype=torch.bfloat16).to(torch.float8_e4m3fn)
79
+ w = torch.randn((8192, 4096), device="cuda", dtype=torch.bfloat16).to(torch.float8_e4m3fn)
80
+
81
+ y = ops.fp8_linear_bf16(x, w, alpha=1.0)
82
+ ```
83
+
84
+ SM110 bias epilogues:
85
+
86
+ ```python
87
+ bias = torch.randn((8192,), device="cuda", dtype=torch.bfloat16)
88
+ residual = torch.randn((16, 8192), device="cuda", dtype=torch.bfloat16)
89
+
90
+ y = ops.fp8_linear_bias_bf16(x, w, bias, alpha=1.0)
91
+ ops.fp8_linear_bias_residual_bf16(x, w, bias, residual, alpha=1.0)
92
+ y_gelu = ops.fp8_linear_bias_gelu_bf16(x, w, bias, alpha=1.0)
93
+ ```
94
+
95
+ Warm each distinct SM110 bias shape once before CUDA Graph capture. The
96
+ cuBLASLt fallback lazily creates and caches its descriptor, algorithm, and
97
+ workspace on the first call; replay itself performs no allocation.
98
+
99
+ Decode residual path:
100
+
101
+ ```python
102
+ x = torch.randn((1, 4096), device="cuda", dtype=torch.bfloat16).to(torch.float8_e4m3fn)
103
+ w = torch.randn((4096, 4096), device="cuda", dtype=torch.bfloat16).to(torch.float8_e4m3fn)
104
+ residual = torch.zeros((1, 4096), device="cuda", dtype=torch.bfloat16)
105
+
106
+ ops.fp8_linear_residual_bf16(x, w, residual, alpha=1.0)
107
+ ```
108
+
109
+ Block-128 scaling:
110
+
111
+ ```python
112
+ m, k, n = 51, 1536, 1536
113
+ x = torch.randn((m, k), device="cuda").to(torch.float8_e4m3fn)
114
+ w = torch.randn((n, k), device="cuda").to(torch.float8_e4m3fn)
115
+ x_scale = torch.ones((m, k // 128), device="cuda", dtype=torch.float32)
116
+ w_scale = torch.ones((n // 128, k // 128), device="cuda", dtype=torch.float32)
117
+
118
+ y = ops.fp8_blockwise_linear_bf16(x, w, x_scale, w_scale)
119
+ ```
120
+
121
+ ## Validation
122
+
123
+ ```bash
124
+ python fp8-gemm/tests/test_fp8_gemm.py --backend source --mode full
125
+ python fp8-gemm/benchmarks/benchmark.py --backend source --mode headline
126
+ python fp8-gemm/benchmarks/benchmark.py --backend source --mode pi05-prefill
127
+ python fp8-gemm/benchmarks/benchmark_bias.py --backend source
128
+ ```
129
+
130
+ The SM110 full sweep covers PI0.5, GROOT N1.6/N1.7, Cosmos Edge, and LingBot
131
+ VLA projection families, plus decode, generic small-M, the `M=65` large-M
132
+ boundary, and the three SigLIP bias epilogues. Public
133
+ benchmark tables are only updated after source correctness, installed artifact
134
+ correctness, shape/tile sweeps, `torch.compile(fullgraph=True)`, CUDA Graph
135
+ replay, and parity against the original FlashRT native pointer entry pass.
SYNC.md ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Source Sync
2
+
3
+ - Upstream FlashRT source: `../official/FlashRT`
4
+ - Initial package date: June 20, 2026
5
+ - SM89 source revision: `70b8eac4b05e9193bd99631cf872c5a971b59f5d`
6
+ - SM110 sync revision: `132049d7c3a3534fb7d35676cd726f39408b1af6`
7
+
8
+ Copied source files:
9
+
10
+ - `csrc/gemm/fp8_gemv_m1_sm120.cu`
11
+ - `csrc/gemm/fp8_gemv_m1_sm120.cuh`
12
+ - `csrc/gemm/fp8_smallM_handtuned_sm120.cu`
13
+ - `csrc/gemm/fp8_smallM_handtuned_sm120.cuh`
14
+ - `csrc/gemm/fp8_smallM_handtuned_ldmatrix_sm120.cu`
15
+ - `csrc/gemm/fp8_smallM_handtuned_ldmatrix_sm120.cuh`
16
+ - `csrc/gemm/cutlass_sm120_block128_fp8_gemm.cu`
17
+ - `csrc/gemm/cutlass_sm120_block128_fp8_gemm.cuh`
18
+ - `csrc/gemm/fp8_block128_gemm_mma_sm89.cu`
19
+ - `csrc/gemm/fp8_block128_gemm_mma_sm89.cuh`
20
+ - `csrc/gemm/fp8_bs_gemm_device.cuh`
21
+ - `csrc/gemm/fp8_gemv_m1_sm89.cu`
22
+ - `csrc/gemm/fp8_gemv_m1_sm89.cuh`
23
+ - `csrc/gemm/gemm_types_sm100.h`
24
+ - `csrc/gemm/cutlass_sm100.cu`
25
+
26
+ The SM110 copies are package-local as `csrc/gemm_types_sm110.h` and
27
+ `csrc/cutlass_sm110_fp8_gemm.cu`. The C declarations in
28
+ `csrc/cutlass_sm110_fp8_gemm.cuh` are packaging glue; the upstream pointer API
29
+ declares them in its aggregate binding instead.
30
+
31
+ Local packaging edits:
32
+
33
+ - Added Tensor-facing PyTorch custom ops in `torch-ext/torch_binding.cpp`.
34
+ - Added Python wrappers and fake registrations in `torch-ext/fp8_gemm`.
35
+ - Kept public APIs model-agnostic; no raw pointer or stream arguments.
36
+ - Bound the upstream measured `32x128-w4-s1` fused SwiGLU producer without
37
+ changing its CUDA tile or arithmetic.
38
+ - Added a Tensor-facing SM110 dispatcher over the upstream BF16-output Sq, T1,
39
+ and Wide tactics. The public dispatcher and diagnostic variants do not alter
40
+ the copied GEMM templates or arithmetic.
41
+ - Renamed SM100 source filenames locally to make their SM110 package role
42
+ explicit; CUTLASS still uses the SM100-family architecture templates when
43
+ compiling for `sm_110a`.
44
+ - The SM110 build uses `-O3`, `--expt-relaxed-constexpr`, and
45
+ `--use_fast_math`, matching the validated native path.
46
+
47
+ Architecture assumptions:
48
+
49
+ - CUDA 12.8+ for SM89/SM120; CUDA 13.0+ for SM110.
50
+ - NVIDIA Ada SM89 for block-128 scaled GEMM/GEMV.
51
+ - NVIDIA Blackwell SM110a for per-tensor Sq/T1/Wide FP8 GEMM with BF16 output.
52
+ - NVIDIA Blackwell SM120a for the original public APIs. The per-tensor FP8 MMA path uses
53
+ `.kind::f8f6f4` instructions and must be compiled for `sm_120a`, not plain
54
+ `sm_120`.
55
+ - The SM110 kernel depends on the builder-provided CUTLASS 4.5 package. The
56
+ package flake is pinned to a builder revision that exports `cutlass_4_5`.
57
+ - The release flake temporarily pins
58
+ `LiangSu8899/kernels@d720fa9`, based on
59
+ `huggingface/kernels@e9152aa`. The fork preserves the upstream builder
60
+ sources and changes only the stale CUTLASS 4.5.2 fixed-output hash from the
61
+ specified value to the value returned by the upstream archive. Return to an
62
+ upstream revision after that hash correction lands.
63
+
64
+ Runtime constraints:
65
+
66
+ - Inputs are FP8 E4M3 tensors with layout `input[M, K]` and `weight[N, K]`.
67
+ - Output is BF16 `out[M, N]`.
68
+ - `K` must be divisible by 32.
69
+ - On SM120, `M` must be `1` or in `2..64`. M=128 remains an internal tuning
70
+ item because the validated correct SM120 tile is not performance-positive
71
+ enough for public release.
72
+ - On SM110, `N` and `K` must be divisible by 16. The full-row Sq/T1/Wide path
73
+ has been validated on `M` from 1 through 1024 across PI0.5, GROOT,
74
+ Cosmos Edge, and LingBot projection families.
75
+ - `alpha` is a host float scale multiplier, normally
76
+ `input_scale * weight_scale`.
77
+ - The blockwise path consumes FP32 scales with layouts `(M, K/128)` and
78
+ `(N/128, K/128)`. It is the same CUTLASS kernel and schedule dispatcher used
79
+ by the upstream FlashRT pointer API.
80
+ - Blockwise scaling is not exposed on SM110 in this increment.
VALIDATION.md ADDED
@@ -0,0 +1,155 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Validation
2
+
3
+ Date: June 20, 2026
4
+
5
+ Local environment:
6
+
7
+ - GPU: NVIDIA GeForce RTX 5090
8
+ - PyTorch: 2.9.1+cu128
9
+ - CUDA runtime reported by PyTorch: 12.8
10
+ - Source build target: `sm_120a`
11
+
12
+ ## Source Correctness
13
+
14
+ Command:
15
+
16
+ ```bash
17
+ python fp8-gemm/tests/test_fp8_gemm.py --backend source --mode full
18
+ ```
19
+
20
+ Result: 14/14 checks passed, plus the blockwise custom op passed
21
+ `torch.compile(fullgraph=True)` with bitwise-equal output to the eager wrapper.
22
+
23
+ Covered public v1 rows:
24
+
25
+ - M=1 decode GEMV: `K in {512,4096}`, `N in {512,2048,8192}`
26
+ - small-M GEMM: `M in {8,16,32,64}` with representative
27
+ transformer/diffuser-adjacent `K,N` rows
28
+ - M=1 residual-add GEMV
29
+ - block-128 scaled FP8 GEMM at:
30
+ - `(M,K,N)=(1,1024,1024)`
31
+ - `(51,1536,1536)`
32
+ - `(277,2048,2048)`
33
+ - `(1024,1152,1152)`
34
+ - `(2520,3072,3072)`
35
+ - `(128,4096,12288)`
36
+
37
+ Metrics:
38
+
39
+ - `max_abs`
40
+ - `mean_abs`
41
+ - `p99_abs`
42
+ - cosine similarity
43
+ - output dtype
44
+ - tolerance
45
+
46
+ The blockwise rows use the stricter gate:
47
+
48
+ - `max_abs <= 0.0625`
49
+ - `mean_abs <= 0.003`
50
+ - `p99_abs <= 0.015625`
51
+ - cosine similarity `>= 0.9999`
52
+
53
+ The release benchmark also compares the Tensor wrapper against an independent
54
+ binding of the original FlashRT pointer API. Matching source code alone is not
55
+ treated as proof of zero wrapper overhead.
56
+
57
+ ## Source Benchmark
58
+
59
+ Command:
60
+
61
+ ```bash
62
+ python fp8-gemm/benchmarks/benchmark.py \
63
+ --backend source --mode headline --warmup 20 --iterations 100 --compile-ref
64
+ ```
65
+
66
+ Result: all public rows passed. Headline rows are recorded in
67
+ `benchmarks/RESULTS.md`.
68
+
69
+ ## Architecture Scope Boundary
70
+
71
+ On SM120, the public per-tensor path supports `M=1` and `2 <= M <= 64`.
72
+ The blockwise path retains its independent unrestricted-M contract.
73
+
74
+ On SM110, the public per-tensor path uses the production CUTLASS Sq/T1/Wide
75
+ family and supports the validated model-shape matrix. The current full sweep
76
+ covers the large-M boundary `65`, exact PI0.5 prefill rows `712/768/970`, and
77
+ representative `K,N` rows from PI0.5, GROOT N1.6/N1.7, Cosmos Edge, and
78
+ LingBot VLA. It also gates BF16 bias, in-place bias+residual, and tanh-GELU
79
+ bias epilogues on SigLIP dimensions `1152/3456/4304`. SM110 blockwise scaling
80
+ is not claimed.
81
+
82
+ The August 8 Thor source gate passed `39/39` with zero failures. Plain large-M
83
+ GEMM was bitwise equal to the reference, and every PI0.5 prefill auto tile was
84
+ within 2% of the fastest validated package/native tile.
85
+
86
+ ## Thor SM110 Increment
87
+
88
+ Validated August 2, 2026:
89
+
90
+ - GPU: NVIDIA Thor, compute capability 11.0;
91
+ - PyTorch: 2.11.0+cu130;
92
+ - CUDA: 13.0;
93
+ - CUTLASS: 4.5.2, matching the current `kernel-builder` `cutlass_4_5`
94
+ dependency;
95
+ - pinned builder: `e9152aa24e0d99eca255ca9f1beb996de32f9ca4`;
96
+ - source correctness: 23/23;
97
+ - locally installed aarch64 artifact correctness: 23/23;
98
+ - `torch.compile(fullgraph=True)`: exact output parity;
99
+ - CUDA Graph capture/replay: exact output parity;
100
+ - original SM120 source regression on RTX 5090: 14/14.
101
+
102
+ The 23 Thor rows include 20 production auto-dispatch checks and three forced
103
+ Sq/T1/Wide diagnostics. Ordinary GEMMs were bitwise equal to the FP32
104
+ accumulation reference after BF16 output conversion. The residual row passed
105
+ with `max_abs=0.0625`, `p99_abs=0.0625`, and cosine `0.9999958` under the
106
+ documented BF16 residual contract.
107
+
108
+ Source-to-installed-artifact performance parity passed over 17 public
109
+ auto-dispatch shapes: median artifact/source `0.9986`, p95 `1.0195`, and max
110
+ `1.0244`.
111
+ Comparisons against the original FlashRT pointer entry are reported separately
112
+ in `benchmarks/RESULTS.md`.
113
+
114
+ The final clean local artifact was built from
115
+ `d31c69b1cb97ecd703aba01e29f423097f11c86a`. All 17 production rows passed the
116
+ dispatcher gate; the worst auto/fastest-valid-tile paired ratio was `1.0028`.
117
+ Sixteen rows matched the original CUTLASS 4.4.2 native entry within about 1.3%
118
+ in the paired graph comparison. The PI0.5 gate/up row is a documented CUTLASS
119
+ 4.5.2 version outlier at `1.128x`; it is not described as native-performance
120
+ parity.
121
+
122
+ Before the SM110 update was published, the existing Thor pipeline dependency
123
+ set was cold-loaded from Hub using both `kernels==0.16.0` and
124
+ `kernels==0.12.3`: 20/20 package imports passed for each client. The Thor host
125
+ required `HF_ENDPOINT=https://hf-mirror.com`; direct access to
126
+ `huggingface.co` timed out, so official-endpoint cold loading remains a
127
+ post-publication check on a host with direct Hub access.
128
+
129
+ ## HF Jobs Publish Status
130
+
131
+ `flashrt/fp8-gemm` v1 was built and uploaded through the repository HF Jobs
132
+ workflow.
133
+
134
+ - Hub revision checked on June 20, 2026: `166f09be`
135
+ - Uploaded variants:
136
+ - `torch211-cxx11-cu128-x86_64-linux`
137
+ - `torch211-cxx11-cu130-x86_64-linux`
138
+ - `torch212-cxx11-cu130-x86_64-linux`
139
+ - `torch212-cxx11-cu132-x86_64-linux`
140
+
141
+ The existing SM120 Hub variants remain published. The new
142
+ `torch211-cxx11-cu130-aarch64-linux` SM110 artifact is not included in the
143
+ older Hub revision above; it must be published only after the clean-commit
144
+ artifact rebuild and cold-cache checks.
145
+
146
+ ## SM89 Increment
147
+
148
+ The source now also exposes block-128 scaled FP8 GEMM/GEMV and
149
+ `fp8_blockwise_swiglu_quantize_fp8` on SM89. The fused producer performs the
150
+ gate/up GEMMs, SiLU product, and block-128 FP8 requantization in one launch.
151
+ It requires `1<=M<=256`, `N%128==0`, and `K%128==0` and uses the upstream
152
+ measured `32x128-w4-s1` tile. SM120 source regression remains 14/14. SM89
153
+ installed correctness, tile parity, and performance claims remain gated on an
154
+ SM89 release artifact run; source presence alone is not recorded as runtime
155
+ validation.
benchmarks/README.md ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Benchmarks
2
+
3
+ ```bash
4
+ python fp8-gemm/benchmarks/benchmark.py --backend source --mode headline
5
+ ```
6
+
7
+ The benchmark sweeps dispatcher rows and explicit M=1 GEMV variants.
8
+
9
+ On NVIDIA Thor, use `--mode thor-full`. The runner records normal launches,
10
+ CUDA Graph replay, all SM110 Sq/T1/Wide diagnostic tiles, and an optional
11
+ original FlashRT pointer-API comparison when `FLASHRT_NATIVE_ROOT` is set.
benchmarks/RESULTS.md ADDED
@@ -0,0 +1,144 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Benchmark Results: fp8-gemm
2
+
3
+ Validated locally on June 20, 2026.
4
+
5
+ Environment:
6
+
7
+ - GPU: NVIDIA GeForce RTX 5090
8
+ - PyTorch: 2.9.1+cu128
9
+ - CUDA runtime reported by PyTorch: 12.8
10
+ - Build target: `sm_120a`
11
+ - Backend: source extension
12
+ - Benchmark command:
13
+
14
+ ```bash
15
+ python fp8-gemm/benchmarks/benchmark.py \
16
+ --backend source --mode headline --warmup 20 --iterations 100 --compile-ref
17
+ ```
18
+
19
+ Correctness gate:
20
+
21
+ ```bash
22
+ python fp8-gemm/tests/test_fp8_gemm.py --backend source --mode full
23
+ ```
24
+
25
+ Result: 8/8 rows passed. Metrics recorded: max absolute error, mean absolute
26
+ error, p99 absolute error, cosine similarity, dtype, and tolerance. Public v1
27
+ SM120 scope is `M=1` decode and `2 <= M <= 64` small-M rows. SM110 uses a
28
+ separate full-row CUTLASS dispatcher described below.
29
+
30
+ ## Headline Rows
31
+
32
+ | Shape | Tile | FlashRT us | Torch eager us | Torch compile us | Speedup vs eager | Speedup vs compile | Max abs | P99 abs | Cosine |
33
+ | --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
34
+ | `M=1,K=4096,N=2048` | `gemv_fp8_m1_w4` | 6.188 | 32.799 | 41.714 | 5.30x | 6.74x | 0.000 | 0.000 | 1.000000 |
35
+ | `M=1,K=4096,N=8192` | `gemv_fp8_m1_w8` | 10.290 | 162.342 | 156.012 | 15.78x | 15.16x | 0.000 | 0.000 | 1.000000 |
36
+ | `M=16,K=4096,N=4096` | `ld_fp8_gemm_16x128x256_w4` | 14.391 | 106.215 | 96.204 | 7.38x | 6.68x | 0.000 | 0.000 | 1.000000 |
37
+ | `M=32,K=4096,N=8192` | `ld_fp8_gemm_32x128x256_w4` | 22.581 | 200.997 | 189.331 | 8.90x | 8.38x | 0.000 | 0.000 | 1.000000 |
38
+ | `M=64,K=512,N=1024` | `ld_fp8_gemm_64x128x256_w4` | 8.259 | 18.085 | 50.002 | 2.19x | 6.05x | 0.000 | 0.000 | 1.000000 |
39
+
40
+ ## M=1 Variant Sweep
41
+
42
+ The dispatcher defaults to `variant=0`. Explicit variants are retained for
43
+ benchmarking and tuning; public callers should use `variant=0` unless they have
44
+ measured their exact shape.
45
+
46
+ | Shape | Variant | Tile | FlashRT us | Speedup vs eager | Status |
47
+ | --- | ---: | --- | ---: | ---: | --- |
48
+ | `M=1,K=4096,N=2048` | 0 | `gemv_fp8_m1_w4` | 6.188 | 5.30x | pass |
49
+ | `M=1,K=4096,N=2048` | 4 | `gemv_fp8_m1_w4` | 6.186 | 5.30x | pass |
50
+ | `M=1,K=4096,N=2048` | 8 | `gemv_fp8_m1_w8` | 6.184 | 5.30x | pass |
51
+ | `M=1,K=4096,N=2048` | 16 | `gemv_fp8_m1_w16` | 6.188 | 5.30x | pass |
52
+ | `M=1,K=4096,N=8192` | 0 | `gemv_fp8_m1_w8` | 10.290 | 15.78x | pass |
53
+ | `M=1,K=4096,N=8192` | 4 | `gemv_fp8_m1_w4` | 10.274 | 15.81x | pass |
54
+ | `M=1,K=4096,N=8192` | 8 | `gemv_fp8_m1_w8` | 10.272 | 15.82x | pass |
55
+ | `M=1,K=4096,N=8192` | 16 | `gemv_fp8_m1_w16` | 10.278 | 15.80x | pass |
56
+
57
+ ## Block-128 Scaled GEMM
58
+
59
+ Measured on RTX 5090 against an independent binding of the original FlashRT
60
+ pointer API. The wrapper and native columns execute the same production
61
+ CUTLASS kernel from separate extension modules. PyTorch eager and compile
62
+ dequantize the block-scaled tensors to FP32, run the GEMM, and cast to BF16.
63
+ CUTLASS is already the native implementation, so there is no additional
64
+ contract-equivalent library row.
65
+
66
+ | Workload `(M,K,N)` | Native us | Wrapper us | Wrapper/native | Eager us | Compile us | Max abs | P99 abs | Cosine |
67
+ | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
68
+ | decode `(1,1024,1024)` | 10.287 | 10.276 | 0.999 | 36.256 | 42.242 | 0.000000 | 0.000000 | 1.0000001 |
69
+ | action `(51,1536,1536)` | 14.364 | 14.360 | 1.000 | 55.595 | 51.500 | 0.000061 | 0.000000 | 1.0000001 |
70
+ | GROOT `(277,2048,2048)` | 28.698 | 28.692 | 1.000 | 104.895 | 73.989 | 0.000122 | 0.000000 | 1.0000001 |
71
+ | vision `(1024,1152,1152)` | 18.456 | 18.466 | 1.001 | 101.103 | 79.565 | 0.000122 | 0.000000 | 1.0000000 |
72
+ | video `(2520,3072,3072)` | 114.188 | 114.696 | 1.004 | 1032.687 | 930.219 | 0.000244 | 0.000000 | 1.0000000 |
73
+ | Qwen MLP `(128,4096,12288)` | 51.258 | 51.253 | 1.000 | 892.744 | 390.698 | 0.000244 | 0.000000 | 1.0000000 |
74
+
75
+ All wrapper outputs were bitwise equal to the original native entry. The
76
+ PyTorch-reference metrics above use production-scale ranges; the wider full
77
+ correctness sweep remains the release gate.
78
+
79
+ ## Release Status
80
+
81
+ - Source correctness: passed.
82
+ - Source benchmark/tile sweep: passed for v1 public scope.
83
+ - Existing SM120 installed artifacts: published.
84
+ - SM110 local installed artifact: correctness, compile, graph, and parity
85
+ passed.
86
+ - SM110 Hub artifact: pending clean-commit rebuild and upload.
87
+
88
+ ## NVIDIA Thor SM110 Results
89
+
90
+ Validated August 2, 2026 on NVIDIA Thor with PyTorch 2.11.0+cu130, CUDA 13.0,
91
+ CUTLASS 4.5.2, and a locally installed
92
+ `torch211-cxx11-cu130-aarch64-linux` artifact. Timings are CUDA Graph replay
93
+ latencies. `Native` is the independently loaded original FlashRT pointer API;
94
+ the ratio is installed artifact / native, so values above 1 are slower.
95
+
96
+ | Workload `(M,K,N)` | Auto tile | Artifact us | Native us | Artifact/native | Correctness |
97
+ | --- | --- | ---: | ---: | ---: | --- |
98
+ | decode `(1,4096,2048)` | T1 | 17.232 | 17.344 | 0.997 | pass |
99
+ | decode-wide `(1,4096,8192)` | T1 | 50.144 | 50.112 | 1.001 | pass |
100
+ | small-M `(16,4096,4096)` | T1 | 23.952 | 24.304 | 0.997 | pass |
101
+ | small-M `(32,4096,8192)` | T1 | 46.256 | 46.000 | 1.013 | pass |
102
+ | small-M `(64,512,1024)` | T1 | 11.296 | 11.264 | 0.997 | pass |
103
+ | PI0.5 QKV `(51,2048,2560)` | T1 | 14.080 | 14.048 | 1.009 | pass |
104
+ | PI0.5 O `(51,2048,2048)` | T1 | 13.440 | 13.504 | 0.993 | pass |
105
+ | PI0.5 gate/up `(51,2048,16384)` | Wide | 92.496 | 81.312 | 1.128 | pass |
106
+ | PI0.5 down `(51,8192,2048)` | T1 | 23.392 | 23.392 | 1.003 | pass |
107
+ | GROOT DiT QKV `(51,1536,4608)` | T1 | 15.232 | 15.104 | 1.004 | pass |
108
+ | GROOT N1.7 O `(277,2048,2048)` | Wide | 18.704 | 18.704 | 1.002 | pass |
109
+ | GROOT N1.7 gate/up `(277,2048,16384)` | Wide | 186.432 | 189.360 | 0.985 | pass |
110
+ | GROOT N1.7 down `(277,8192,2048)` | Sq | 50.080 | 49.984 | 1.003 | pass |
111
+ | GROOT vision O `(1024,1024,1024)` | Sq | 15.360 | 15.424 | 0.997 | pass |
112
+ | Cosmos Edge action `(64,2048,9216)` | T1 | 25.264 | 25.312 | 0.999 | pass |
113
+ | LingBot vision O `(1024,1280,1280)` | Wide | 17.152 | 17.216 | 0.997 | pass |
114
+ | LingBot action gate/up `(105,2048,16384)` | Wide | 140.080 | 139.984 | 1.002 | pass |
115
+
116
+ Each graph ratio is the median of paired, per-launch package/native samples;
117
+ candidate order rotates every round to control Thor DVFS bias. Sixteen rows are
118
+ within about 1.3% of the original FlashRT pointer extension. PI0.5 gate/up is a
119
+ reproducible CUTLASS dependency-version outlier: the Hub-buildable package uses
120
+ CUTLASS 4.5.2 while the original native extension uses 4.4.2, and the paired
121
+ ratio in the final clean artifact run is 1.128. CUTLASS 4.0 was also tested but
122
+ failed at runtime on SM110, so it is not a valid packaging fallback. This row
123
+ is retained explicitly and is not used for a native-parity claim.
124
+
125
+ The installed artifact selected the fastest validated tactic on every row;
126
+ worst auto/fastest-valid-tile was 1.0028. Source-to-artifact packaging parity
127
+ passed with median 0.9986, p95 1.0195, and max 1.0244.
128
+
129
+ ## PI0.5 Thor Prefill and BF16 Bias Update
130
+
131
+ Source gate rerun August 8, 2026 on NVIDIA Thor, PyTorch 2.13.0+cu130:
132
+
133
+ - correctness: `39/39`, `fail_count=0`;
134
+ - plain FP8 GEMM outputs were bitwise equal to the reference across the
135
+ `M=65..1024` band;
136
+ - PI0.5 prefill QKV/O/gate-up/down auto dispatch was within 2% of the fastest
137
+ valid Sq/T1/Wide package tile and the original FlashRT native entry;
138
+ - all 11 BF16 bias, bias+residual, and bias+GELU checks passed;
139
+ - bias-only output was exact; residual p99 was at most one BF16 step; tanh-GELU
140
+ p99 was at most `3.8147e-6`, with cosine at least `0.999995`.
141
+
142
+ The down-projection bias family uses a CUTLASS Wide fused epilogue while other
143
+ SigLIP shapes use the faster cuBLASLt path. The dispatcher preserves the
144
+ public row-major `(N,K)` weight contract.
benchmarks/benchmark.py CHANGED
@@ -6,6 +6,7 @@ from __future__ import annotations
6
  import argparse
7
  import importlib
8
  import json
 
9
  import os
10
  import statistics
11
  import sys
@@ -45,6 +46,10 @@ SHAPES = {
45
  "cosmos_edge_action": (64, 2048, 9216),
46
  "lingbot_vision_o": (1024, 1280, 1280),
47
  "lingbot_action_gate_up": (105, 2048, 16384),
 
 
 
 
48
  }
49
 
50
  MODES = {
@@ -58,6 +63,12 @@ MODES = {
58
  "cosmos_edge_action",
59
  "lingbot_action_gate_up",
60
  ],
 
 
 
 
 
 
61
  "thor-full": list(SHAPES),
62
  }
63
 
@@ -128,7 +139,10 @@ def load_source_ops() -> SourceOps:
128
  if capability == (11, 0):
129
  if not (cutlass_include / "cutlass" / "cutlass.h").is_file():
130
  raise RuntimeError("set CUTLASS_INCLUDE for the SM110 source benchmark")
131
- cuda_sources = [str(PACKAGE / "csrc" / "cutlass_sm110_fp8_gemm.cu")]
 
 
 
132
  source_define = "-DFLASHRT_FP8_GEMM_SOURCE_SM110_ONLY"
133
  extra_includes = [
134
  str(cutlass_include),
@@ -179,6 +193,12 @@ def select_tile(m: int, n: int, k: int, variant: int = 0) -> str:
179
  raise RuntimeError("SM110 variant must be in [0, 3]")
180
  if variant:
181
  return forced[variant]
 
 
 
 
 
 
182
  if n >= 8 * k:
183
  return "sm110_wide_bf16"
184
  if m >= 128 and k >= 4 * n:
@@ -309,7 +329,7 @@ def metrics(got, expected):
309
  return (
310
  float(diff.max().item()),
311
  float(diff.mean().item()),
312
- float(torch.quantile(diff, 0.99).item()),
313
  float(torch.nn.functional.cosine_similarity(got.float().flatten(), expected.float().flatten(), dim=0).item()),
314
  )
315
 
 
6
  import argparse
7
  import importlib
8
  import json
9
+ import math
10
  import os
11
  import statistics
12
  import sys
 
46
  "cosmos_edge_action": (64, 2048, 9216),
47
  "lingbot_vision_o": (1024, 1280, 1280),
48
  "lingbot_action_gate_up": (105, 2048, 16384),
49
+ "pi05_prefill_qkv": (712, 2048, 2560),
50
+ "pi05_prefill_o": (970, 2048, 2048),
51
+ "pi05_prefill_gate_up": (768, 2048, 32768),
52
+ "pi05_prefill_down": (768, 16384, 2048),
53
  }
54
 
55
  MODES = {
 
63
  "cosmos_edge_action",
64
  "lingbot_action_gate_up",
65
  ],
66
+ "pi05-prefill": [
67
+ "pi05_prefill_qkv",
68
+ "pi05_prefill_o",
69
+ "pi05_prefill_gate_up",
70
+ "pi05_prefill_down",
71
+ ],
72
  "thor-full": list(SHAPES),
73
  }
74
 
 
139
  if capability == (11, 0):
140
  if not (cutlass_include / "cutlass" / "cutlass.h").is_file():
141
  raise RuntimeError("set CUTLASS_INCLUDE for the SM110 source benchmark")
142
+ cuda_sources = [
143
+ str(PACKAGE / "csrc" / "cutlass_sm110_fp8_gemm.cu"),
144
+ str(PACKAGE / "csrc" / "cublaslt_fp8_bias_sm110.cu"),
145
+ ]
146
  source_define = "-DFLASHRT_FP8_GEMM_SOURCE_SM110_ONLY"
147
  extra_includes = [
148
  str(cutlass_include),
 
193
  raise RuntimeError("SM110 variant must be in [0, 3]")
194
  if variant:
195
  return forced[variant]
196
+ if m >= 512 and k == 2048 and 2048 <= n <= 2560:
197
+ return "sm110_sq_bf16"
198
+ if m >= 512 and n >= 16 * k:
199
+ return "sm110_t1_bf16"
200
+ if m >= 512 and k >= 4 * n:
201
+ return "sm110_wide_bf16"
202
  if n >= 8 * k:
203
  return "sm110_wide_bf16"
204
  if m >= 128 and k >= 4 * n:
 
329
  return (
330
  float(diff.max().item()),
331
  float(diff.mean().item()),
332
+ float(diff.kthvalue(max(1, math.ceil(0.99 * diff.numel()))).values.item()),
333
  float(torch.nn.functional.cosine_similarity(got.float().flatten(), expected.float().flatten(), dim=0).item()),
334
  )
335
 
benchmarks/benchmark_bias.py ADDED
@@ -0,0 +1,241 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Benchmark SM110 BF16-output FP8 GEMM epilogues."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import importlib
8
+ import json
9
+ import math
10
+ import os
11
+ import statistics
12
+ import sys
13
+ from pathlib import Path
14
+
15
+ import torch
16
+
17
+
18
+ ROOT = Path(__file__).resolve().parents[2]
19
+ PACKAGE = ROOT / "fp8-gemm"
20
+ REGISTRATION_INCLUDE = (
21
+ ROOT.parent
22
+ / "kernels"
23
+ / "kernel-builder"
24
+ / "src"
25
+ / "pyproject"
26
+ / "templates"
27
+ / "torch"
28
+ )
29
+
30
+ SHAPES = {
31
+ "siglip_qkv": (512, 1152, 3456),
32
+ "siglip_mlp_up": (768, 1152, 4304),
33
+ "siglip_mlp_down": (768, 4304, 1152),
34
+ }
35
+
36
+
37
+ class SourceOps:
38
+ def __init__(self, namespace: str) -> None:
39
+ self.ops = getattr(torch.ops, namespace)
40
+
41
+ def fp8_linear_bias_bf16(self, x, w, bias, alpha=1.0, out=None):
42
+ if out is None:
43
+ out = torch.empty(
44
+ (x.shape[0], w.shape[0]), device=x.device, dtype=torch.bfloat16
45
+ )
46
+ self.ops.fp8_linear_bias_bf16(x, w, bias, float(alpha), out)
47
+ return out
48
+
49
+ def fp8_linear_bias_residual_bf16(
50
+ self, x, w, bias, residual, alpha=1.0
51
+ ):
52
+ self.ops.fp8_linear_bias_residual_bf16(
53
+ x, w, bias, float(alpha), residual
54
+ )
55
+ return residual
56
+
57
+ def fp8_linear_bias_gelu_bf16(self, x, w, bias, alpha=1.0, out=None):
58
+ if out is None:
59
+ out = torch.empty(
60
+ (x.shape[0], w.shape[0]), device=x.device, dtype=torch.bfloat16
61
+ )
62
+ self.ops.fp8_linear_bias_gelu_bf16(x, w, bias, float(alpha), out)
63
+ return out
64
+
65
+
66
+ def load_source_ops() -> SourceOps:
67
+ from torch.utils.cpp_extension import load
68
+
69
+ cutlass = Path(os.environ["CUTLASS_INCLUDE"])
70
+ os.environ.setdefault("TORCH_CUDA_ARCH_LIST", "11.0a")
71
+ namespace = "fp8_gemm_bias_source_bench"
72
+ load(
73
+ name=namespace,
74
+ sources=[
75
+ str(PACKAGE / "torch-ext" / "torch_binding.cpp"),
76
+ str(PACKAGE / "csrc" / "cutlass_sm110_fp8_gemm.cu"),
77
+ str(PACKAGE / "csrc" / "cublaslt_fp8_bias_sm110.cu"),
78
+ ],
79
+ extra_include_paths=[
80
+ str(PACKAGE / "csrc"),
81
+ str(REGISTRATION_INCLUDE),
82
+ str(cutlass),
83
+ str(cutlass.parent / "tools" / "util" / "include"),
84
+ ],
85
+ extra_cflags=[
86
+ "-O3", "-DNDEBUG", "-DCUDA_KERNEL",
87
+ "-DFLASHRT_FP8_GEMM_SOURCE_SM110_ONLY",
88
+ ],
89
+ extra_cuda_cflags=[
90
+ "-O3", "-DNDEBUG", "--expt-relaxed-constexpr", "--use_fast_math",
91
+ "-DCUDA_KERNEL", "-DFLASHRT_FP8_GEMM_SOURCE_SM110_ONLY",
92
+ ],
93
+ verbose=False,
94
+ )
95
+ return SourceOps(namespace)
96
+
97
+
98
+ def load_installed_ops(artifact: str | None):
99
+ if artifact:
100
+ sys.path.insert(0, artifact)
101
+ try:
102
+ return importlib.import_module("fp8_gemm")
103
+ finally:
104
+ if artifact:
105
+ sys.path.remove(artifact)
106
+
107
+
108
+ def load_native():
109
+ root = os.environ.get("FLASHRT_NATIVE_ROOT")
110
+ if not root:
111
+ return None
112
+ sys.path.insert(0, root)
113
+ try:
114
+ module = importlib.import_module("flash_rt.flash_rt_kernels")
115
+ return module.GemmRunner()
116
+ finally:
117
+ sys.path.remove(root)
118
+
119
+
120
+ def measure(fn, warmup: int, iterations: int, rounds: int = 7) -> float:
121
+ samples = []
122
+ for _ in range(rounds):
123
+ for _ in range(warmup):
124
+ fn()
125
+ start = torch.cuda.Event(enable_timing=True)
126
+ end = torch.cuda.Event(enable_timing=True)
127
+ start.record()
128
+ for _ in range(iterations):
129
+ fn()
130
+ end.record()
131
+ torch.cuda.synchronize()
132
+ samples.append(start.elapsed_time(end) * 1000.0 / iterations)
133
+ return float(statistics.median(samples))
134
+
135
+
136
+ def metrics(got, expected):
137
+ diff = (got.float() - expected.float()).abs().flatten()
138
+ rank = max(1, math.ceil(0.99 * diff.numel()))
139
+ return {
140
+ "max_abs": float(diff.max().item()),
141
+ "mean_abs": float(diff.mean().item()),
142
+ "p99_abs": float(diff.kthvalue(rank).values.item()),
143
+ "cosine": float(torch.nn.functional.cosine_similarity(
144
+ got.float().flatten(), expected.float().flatten(), dim=0
145
+ ).item()),
146
+ }
147
+
148
+
149
+ def main() -> int:
150
+ parser = argparse.ArgumentParser()
151
+ parser.add_argument("--backend", choices=["source", "installed"], default="source")
152
+ parser.add_argument("--artifact", default=None)
153
+ parser.add_argument("--warmup", type=int, default=20)
154
+ parser.add_argument("--iterations", type=int, default=64)
155
+ parser.add_argument("--json-out", default=None)
156
+ args = parser.parse_args()
157
+ if torch.cuda.get_device_capability() != (11, 0):
158
+ raise SystemExit("SM110 is required")
159
+ ops = load_source_ops() if args.backend == "source" else load_installed_ops(args.artifact)
160
+ native = load_native()
161
+ native_stream = int(torch.cuda.current_stream().cuda_stream)
162
+ rows = []
163
+ for name, (m, k, n) in SHAPES.items():
164
+ generator = torch.Generator(device="cuda").manual_seed(m + k + n)
165
+ x = (torch.randn((m, k), device="cuda", generator=generator) * 0.25).to(
166
+ torch.float8_e4m3fn
167
+ )
168
+ weight = (
169
+ torch.randn((n, k), device="cuda", generator=generator) * 0.25
170
+ ).to(torch.float8_e4m3fn)
171
+ weight_kn = weight.t().contiguous()
172
+ bias = (torch.randn((n,), device="cuda", generator=generator) * 0.1).to(
173
+ torch.bfloat16
174
+ )
175
+ alpha = 0.75
176
+ base = x.float() @ weight.float().t() * alpha
177
+ for epilogue in ("bias", "bias_residual", "bias_gelu"):
178
+ if epilogue == "bias":
179
+ out = torch.empty((m, n), device="cuda", dtype=torch.bfloat16)
180
+ invoke = lambda: ops.fp8_linear_bias_bf16(
181
+ x, weight, bias, alpha=alpha, out=out
182
+ )
183
+ expected = (base + bias.float()).to(torch.bfloat16)
184
+ elif epilogue == "bias_residual":
185
+ initial = (torch.randn((m, n), device="cuda", generator=generator) * 0.1).to(
186
+ torch.bfloat16
187
+ )
188
+ out = initial.clone()
189
+ invoke = lambda: ops.fp8_linear_bias_residual_bf16(
190
+ x, weight, bias, out, alpha=alpha
191
+ )
192
+ expected = (initial.float() + base + bias.float()).to(torch.bfloat16)
193
+ else:
194
+ out = torch.empty((m, n), device="cuda", dtype=torch.bfloat16)
195
+ invoke = lambda: ops.fp8_linear_bias_gelu_bf16(
196
+ x, weight, bias, alpha=alpha, out=out
197
+ )
198
+ expected = torch.nn.functional.gelu(
199
+ base + bias.float(), approximate="tanh"
200
+ ).to(torch.bfloat16)
201
+ invoke()
202
+ torch.cuda.synchronize()
203
+ accuracy = metrics(out, expected)
204
+ hub_us = measure(invoke, args.warmup, args.iterations)
205
+ row = {
206
+ "shape": name,
207
+ "M": m,
208
+ "K": k,
209
+ "N": n,
210
+ "epilogue": epilogue,
211
+ "hub_us": hub_us,
212
+ **accuracy,
213
+ }
214
+ if native is not None and epilogue == "bias":
215
+ native_out = torch.empty_like(out)
216
+ native_invoke = lambda: native.fp8_nn_bias_bf16(
217
+ x.data_ptr(), weight_kn.data_ptr(), native_out.data_ptr(),
218
+ bias.data_ptr(), m, n, k, alpha, native_stream
219
+ )
220
+ native_invoke()
221
+ torch.cuda.synchronize()
222
+ row["native_us"] = measure(
223
+ native_invoke, args.warmup, args.iterations
224
+ )
225
+ row["hub_over_native"] = row["hub_us"] / row["native_us"]
226
+ row["native_metrics"] = metrics(native_out, expected)
227
+ rows.append(row)
228
+ payload = {"device": torch.cuda.get_device_name(), "rows": rows}
229
+ rendered = json.dumps(payload, indent=2, sort_keys=True)
230
+ print(rendered)
231
+ if args.json_out:
232
+ Path(args.json_out).write_text(rendered + "\n")
233
+ failed = [
234
+ row for row in rows
235
+ if row["p99_abs"] > 0.25 or row["mean_abs"] > 0.02 or row["cosine"] < 0.999
236
+ ]
237
+ return 1 if failed else 0
238
+
239
+
240
+ if __name__ == "__main__":
241
+ raise SystemExit(main())
build.toml ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [general]
2
+ name = "fp8-gemm"
3
+ version = 1
4
+ edition = 5
5
+ license = "Apache-2.0"
6
+ backends = ["cuda"]
7
+
8
+ [general.cuda]
9
+ minver = "12.8"
10
+
11
+ [general.hub]
12
+ repo-id = "flashrt/fp8-gemm"
13
+
14
+ [torch]
15
+ include = ["csrc"]
16
+ src = [
17
+ "torch-ext/torch_binding.cpp",
18
+ "torch-ext/torch_binding.h",
19
+ ]
20
+
21
+ [kernel.fp8_gemm_sm110]
22
+ backend = "cuda"
23
+ cuda-capabilities = ["11.0a"]
24
+ cuda-flags = [
25
+ "--expt-relaxed-constexpr",
26
+ "-O3",
27
+ "--use_fast_math",
28
+ ]
29
+ cuda-minver = "13"
30
+ depends = [
31
+ "torch",
32
+ "cutlass_4_5",
33
+ ]
34
+ include = ["csrc"]
35
+ src = [
36
+ "csrc/gemm_types_sm110.h",
37
+ "csrc/cutlass_sm110_fp8_gemm.cu",
38
+ "csrc/cutlass_sm110_fp8_gemm.cuh",
39
+ "csrc/cublaslt_fp8_bias_sm110.cu",
40
+ "csrc/cublaslt_fp8_bias_sm110.cuh",
41
+ ]
42
+
43
+ [kernel.fp8_gemm_sm89]
44
+ backend = "cuda"
45
+ cuda-capabilities = ["8.9"]
46
+ cuda-minver = "12.8"
47
+ depends = ["torch"]
48
+ include = ["csrc"]
49
+ src = [
50
+ "csrc/fp8_block128_gemm_mma_sm89.cu",
51
+ "csrc/fp8_block128_gemm_mma_sm89.cuh",
52
+ "csrc/fp8_bs_gemm_device.cuh",
53
+ "csrc/fp8_gemv_m1_sm89.cu",
54
+ "csrc/fp8_gemv_m1_sm89.cuh",
55
+ ]
56
+
57
+ [kernel.fp8_gemm]
58
+ backend = "cuda"
59
+ cuda-capabilities = ["12.0a"]
60
+ cuda-minver = "12.8"
61
+ depends = [
62
+ "torch",
63
+ "cutlass_4_0",
64
+ ]
65
+ include = ["csrc"]
66
+ src = [
67
+ "csrc/fp8_gemv_m1_sm120.cu",
68
+ "csrc/fp8_gemv_m1_sm120.cuh",
69
+ "csrc/fp8_smallM_handtuned_sm120.cu",
70
+ "csrc/fp8_smallM_handtuned_sm120.cuh",
71
+ "csrc/fp8_smallM_handtuned_ldmatrix_sm120.cu",
72
+ "csrc/fp8_smallM_handtuned_ldmatrix_sm120.cuh",
73
+ "csrc/cutlass_sm120_block128_fp8_gemm.cu",
74
+ "csrc/cutlass_sm120_block128_fp8_gemm.cuh",
75
+ ]
build/torch213-cxx11-cu130-aarch64-linux/__init__.py CHANGED
@@ -37,6 +37,34 @@ def _fp8_linear_residual_bf16_fake(
37
  return None
38
 
39
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  @torch.library.register_fake(add_op_namespace_prefix("fp8_blockwise_linear_bf16"))
41
  def _fp8_blockwise_linear_bf16_fake(
42
  input: torch.Tensor,
@@ -95,8 +123,8 @@ def select_fp8_linear_tile(m: int, n: int, k: int, variant: int = 0) -> str:
95
  variant = int(variant)
96
  if m <= 0 or n <= 0 or k <= 0:
97
  raise RuntimeError("m, n, and k must be positive")
98
- if k % 32 != 0:
99
- raise RuntimeError("k must be divisible by 32")
100
  capability = torch.cuda.get_device_capability() if torch.cuda.is_available() else None
101
  if capability == (11, 0):
102
  forced = {1: "sm110_sq_bf16", 2: "sm110_t1_bf16", 3: "sm110_wide_bf16"}
@@ -106,6 +134,12 @@ def select_fp8_linear_tile(m: int, n: int, k: int, variant: int = 0) -> str:
106
  raise RuntimeError("SM110 requires n and k divisible by 16")
107
  if variant:
108
  return forced[variant]
 
 
 
 
 
 
109
  if n >= 8 * k:
110
  return "sm110_wide_bf16"
111
  if m >= 128 and k >= 4 * n:
@@ -116,6 +150,8 @@ def select_fp8_linear_tile(m: int, n: int, k: int, variant: int = 0) -> str:
116
  return "sm110_wide_bf16"
117
  return "sm110_t1_bf16"
118
  if m == 1:
 
 
119
  if variant == 4:
120
  return "gemv_fp8_m1_w4"
121
  if variant == 8:
@@ -131,6 +167,8 @@ def select_fp8_linear_tile(m: int, n: int, k: int, variant: int = 0) -> str:
131
  return "gemv_fp8_m1_w16"
132
  if variant != 0:
133
  raise RuntimeError("small-M dispatcher currently supports variant=0 only")
 
 
134
  if m <= 16:
135
  if k % 256 == 0:
136
  return "ld_fp8_gemm_16x128x256_w4" if n % 128 == 0 else "ld_fp8_gemm_16x64x256_w4"
@@ -197,6 +235,56 @@ def fp8_linear_residual_bf16(
197
  return residual
198
 
199
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
200
  def fp8_blockwise_linear_bf16(
201
  input: torch.Tensor,
202
  weight: torch.Tensor,
@@ -248,6 +336,9 @@ def fp8_blockwise_swiglu_quantize_fp8(
248
  __all__ = [
249
  "fp8_linear_bf16",
250
  "fp8_linear_residual_bf16",
 
 
 
251
  "fp8_blockwise_linear_bf16",
252
  "fp8_blockwise_swiglu_quantize_fp8",
253
  "select_fp8_linear_tile",
 
37
  return None
38
 
39
 
40
+ def _check_bias_linear_shapes(input, weight, bias, out) -> None:
41
+ if input.dim() != 2 or weight.dim() != 2:
42
+ raise RuntimeError("input and weight must be rank-2 tensors")
43
+ if input.shape[1] != weight.shape[1]:
44
+ raise RuntimeError("input and weight K dimensions must match")
45
+ if bias.shape != (weight.shape[0],):
46
+ raise RuntimeError("bias must have shape (weight.shape[0],)")
47
+ if out.shape != (input.shape[0], weight.shape[0]):
48
+ raise RuntimeError("out must have shape (input.shape[0], weight.shape[0])")
49
+
50
+
51
+ @torch.library.register_fake(add_op_namespace_prefix("fp8_linear_bias_bf16"))
52
+ def _fp8_linear_bias_bf16_fake(input, weight, bias, alpha: float, out) -> None:
53
+ _check_bias_linear_shapes(input, weight, bias, out)
54
+
55
+
56
+ @torch.library.register_fake(add_op_namespace_prefix("fp8_linear_bias_residual_bf16"))
57
+ def _fp8_linear_bias_residual_bf16_fake(
58
+ input, weight, bias, alpha: float, residual
59
+ ) -> None:
60
+ _check_bias_linear_shapes(input, weight, bias, residual)
61
+
62
+
63
+ @torch.library.register_fake(add_op_namespace_prefix("fp8_linear_bias_gelu_bf16"))
64
+ def _fp8_linear_bias_gelu_bf16_fake(input, weight, bias, alpha: float, out) -> None:
65
+ _check_bias_linear_shapes(input, weight, bias, out)
66
+
67
+
68
  @torch.library.register_fake(add_op_namespace_prefix("fp8_blockwise_linear_bf16"))
69
  def _fp8_blockwise_linear_bf16_fake(
70
  input: torch.Tensor,
 
123
  variant = int(variant)
124
  if m <= 0 or n <= 0 or k <= 0:
125
  raise RuntimeError("m, n, and k must be positive")
126
+ if k % 16 != 0:
127
+ raise RuntimeError("k must be divisible by 16")
128
  capability = torch.cuda.get_device_capability() if torch.cuda.is_available() else None
129
  if capability == (11, 0):
130
  forced = {1: "sm110_sq_bf16", 2: "sm110_t1_bf16", 3: "sm110_wide_bf16"}
 
134
  raise RuntimeError("SM110 requires n and k divisible by 16")
135
  if variant:
136
  return forced[variant]
137
+ if m >= 512 and k == 2048 and 2048 <= n <= 2560:
138
+ return "sm110_sq_bf16"
139
+ if m >= 512 and n >= 16 * k:
140
+ return "sm110_t1_bf16"
141
+ if m >= 512 and k >= 4 * n:
142
+ return "sm110_wide_bf16"
143
  if n >= 8 * k:
144
  return "sm110_wide_bf16"
145
  if m >= 128 and k >= 4 * n:
 
150
  return "sm110_wide_bf16"
151
  return "sm110_t1_bf16"
152
  if m == 1:
153
+ if k % 32:
154
+ raise RuntimeError("SM120 requires k divisible by 32")
155
  if variant == 4:
156
  return "gemv_fp8_m1_w4"
157
  if variant == 8:
 
167
  return "gemv_fp8_m1_w16"
168
  if variant != 0:
169
  raise RuntimeError("small-M dispatcher currently supports variant=0 only")
170
+ if k % 32:
171
+ raise RuntimeError("SM120 requires k divisible by 32")
172
  if m <= 16:
173
  if k % 256 == 0:
174
  return "ld_fp8_gemm_16x128x256_w4" if n % 128 == 0 else "ld_fp8_gemm_16x64x256_w4"
 
235
  return residual
236
 
237
 
238
+ def fp8_linear_bias_bf16(
239
+ input: torch.Tensor,
240
+ weight: torch.Tensor,
241
+ bias: torch.Tensor,
242
+ alpha: float = 1.0,
243
+ out: torch.Tensor | None = None,
244
+ ) -> torch.Tensor:
245
+ """SM110 FP8 linear with fused BF16 bias and BF16 output."""
246
+ if out is None:
247
+ out = torch.empty(
248
+ (input.shape[0], weight.shape[0]),
249
+ device=input.device,
250
+ dtype=torch.bfloat16,
251
+ )
252
+ ops.fp8_linear_bias_bf16(input, weight, bias, float(alpha), out)
253
+ return out
254
+
255
+
256
+ def fp8_linear_bias_residual_bf16(
257
+ input: torch.Tensor,
258
+ weight: torch.Tensor,
259
+ bias: torch.Tensor,
260
+ residual: torch.Tensor,
261
+ alpha: float = 1.0,
262
+ ) -> torch.Tensor:
263
+ """SM110 fused ``residual += alpha * input @ weight.T + bias``."""
264
+ ops.fp8_linear_bias_residual_bf16(
265
+ input, weight, bias, float(alpha), residual
266
+ )
267
+ return residual
268
+
269
+
270
+ def fp8_linear_bias_gelu_bf16(
271
+ input: torch.Tensor,
272
+ weight: torch.Tensor,
273
+ bias: torch.Tensor,
274
+ alpha: float = 1.0,
275
+ out: torch.Tensor | None = None,
276
+ ) -> torch.Tensor:
277
+ """SM110 FP8 linear with fused BF16 bias and GELU epilogue."""
278
+ if out is None:
279
+ out = torch.empty(
280
+ (input.shape[0], weight.shape[0]),
281
+ device=input.device,
282
+ dtype=torch.bfloat16,
283
+ )
284
+ ops.fp8_linear_bias_gelu_bf16(input, weight, bias, float(alpha), out)
285
+ return out
286
+
287
+
288
  def fp8_blockwise_linear_bf16(
289
  input: torch.Tensor,
290
  weight: torch.Tensor,
 
336
  __all__ = [
337
  "fp8_linear_bf16",
338
  "fp8_linear_residual_bf16",
339
+ "fp8_linear_bias_bf16",
340
+ "fp8_linear_bias_residual_bf16",
341
+ "fp8_linear_bias_gelu_bf16",
342
  "fp8_blockwise_linear_bf16",
343
  "fp8_blockwise_swiglu_quantize_fp8",
344
  "select_fp8_linear_tile",
build/torch213-cxx11-cu130-aarch64-linux/{fp8_gemm_source_test.abi3.so → _fp8_gemm_cuda_503100c.abi3.so} RENAMED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:6da6757d21dbf032ac9c79d54dceffafed47cbf28b392de481d03e6078f198a8
3
- size 1893488
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:22cab0b528b64c6915faa2735614461a01a77df8803ba448361d1f3c4e275885
3
+ size 2534560
build/torch213-cxx11-cu130-aarch64-linux/_ops.py CHANGED
@@ -1,6 +1,6 @@
1
  import torch
2
- from . import fp8_gemm_source_test
3
- ops = torch.ops.fp8_gemm_source_test
4
 
5
  def add_op_namespace_prefix(op_name: str):
6
- return f"fp8_gemm_source_test::{op_name}"
 
1
  import torch
2
+ from . import _fp8_gemm_cuda_503100c
3
+ ops = torch.ops._fp8_gemm_cuda_503100c
4
 
5
  def add_op_namespace_prefix(op_name: str):
6
+ return f"_fp8_gemm_cuda_503100c::{op_name}"
build/torch213-cxx11-cu130-aarch64-linux/metadata.json CHANGED
@@ -1,6 +1,6 @@
1
  {
2
  "name": "fp8-gemm",
3
- "id": "fp8_gemm_source_test",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "python-depends": [],
@@ -13,20 +13,16 @@
13
  "digest": {
14
  "algorithm": "sha256",
15
  "files": {
16
- "__init__.py": "hKsdFER2ZmYz36/zz9LYE7ddsK/R1dprHJFx+9yBzks=",
17
- "fp8_gemm_source_test.abi3.so": "baZ1fSHb8DKsnHnVTc7/r+1Hy/KLOS3kgdA+YHjxmKg=",
18
- "_ops.py": "zi4cl9u1hlmXMSZ8ZPvvrFIj8y1rvp8y3OTUWOXsp84=",
19
  "fp8_gemm/__init__.py": "v6p5XMfQzddhi1fLSAw4HX9CyS0rQsidvu9VsT01xi4="
20
  }
21
  },
22
  "provenance": {
23
  "kernel": {
24
- "sha": "456d297",
25
  "dirty": false
26
- },
27
- "validation": {
28
- "torch": "2.13.0+cu130",
29
- "cuda": "13.0"
30
  }
31
  }
32
  }
 
1
  {
2
  "name": "fp8-gemm",
3
+ "id": "_fp8_gemm_cuda_503100c",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "python-depends": [],
 
13
  "digest": {
14
  "algorithm": "sha256",
15
  "files": {
16
+ "__init__.py": "HsFfsGY7AK/6Fziv4r2VQ+R2kDjS6hxKKgWqlA+zrKY=",
17
+ "_fp8_gemm_cuda_503100c.abi3.so": "IsqwtSi2TGkV+qJzVhRGGgGnffiAO6RINh0fPE4nWIU=",
18
+ "_ops.py": "RYYdMXXadIz2MzzsJ5E8N2JwNrfVICVpxfJyn0+g770=",
19
  "fp8_gemm/__init__.py": "v6p5XMfQzddhi1fLSAw4HX9CyS0rQsidvu9VsT01xi4="
20
  }
21
  },
22
  "provenance": {
23
  "kernel": {
24
+ "sha": "503100c",
25
  "dirty": false
 
 
 
 
26
  }
27
  }
28
  }
csrc/README.md ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ # csrc
2
+
3
+ Native FlashRT CUDA sources copied from upstream:
4
+
5
+ - `fp8_gemv_m1_sm120.*`
6
+ - `fp8_smallM_handtuned_sm120.*`
7
+ - `fp8_smallM_handtuned_ldmatrix_sm120.*`
csrc/cublaslt_fp8_bias_sm110.cu ADDED
@@ -0,0 +1,179 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #include "cublaslt_fp8_bias_sm110.cuh"
2
+
3
+ #include <cublasLt.h>
4
+
5
+ #include <cstdint>
6
+ #include <mutex>
7
+ #include <unordered_map>
8
+
9
+ namespace {
10
+
11
+ constexpr size_t kWorkspaceBytes = 32 * 1024 * 1024;
12
+
13
+ struct Key {
14
+ int M;
15
+ int N;
16
+ int K;
17
+ int epilogue;
18
+
19
+ bool operator==(const Key& other) const {
20
+ return M == other.M && N == other.N && K == other.K &&
21
+ epilogue == other.epilogue;
22
+ }
23
+ };
24
+
25
+ struct KeyHash {
26
+ size_t operator()(const Key& key) const {
27
+ size_t value = std::hash<int>{}(key.M);
28
+ value ^= std::hash<int>{}(key.N) + 0x9e3779b9 + (value << 6) +
29
+ (value >> 2);
30
+ value ^= std::hash<int>{}(key.K) + 0x9e3779b9 + (value << 6) +
31
+ (value >> 2);
32
+ value ^= std::hash<int>{}(key.epilogue) + 0x9e3779b9 + (value << 6) +
33
+ (value >> 2);
34
+ return value;
35
+ }
36
+ };
37
+
38
+ struct Entry {
39
+ cublasLtMatmulDesc_t operation = nullptr;
40
+ cublasLtMatrixLayout_t weight = nullptr;
41
+ cublasLtMatrixLayout_t input = nullptr;
42
+ cublasLtMatrixLayout_t output = nullptr;
43
+ cublasLtMatmulAlgo_t algorithm{};
44
+ };
45
+
46
+ cublasLtHandle_t handle = nullptr;
47
+ void* workspace = nullptr;
48
+ std::unordered_map<Key, Entry, KeyHash> cache;
49
+ std::mutex cache_mutex;
50
+
51
+ int status_code(cublasStatus_t status) {
52
+ return status == CUBLAS_STATUS_SUCCESS ? 0 : -1000 - static_cast<int>(status);
53
+ }
54
+
55
+ void destroy_entry(Entry& entry) {
56
+ if (entry.operation) cublasLtMatmulDescDestroy(entry.operation);
57
+ if (entry.weight) cublasLtMatrixLayoutDestroy(entry.weight);
58
+ if (entry.input) cublasLtMatrixLayoutDestroy(entry.input);
59
+ if (entry.output) cublasLtMatrixLayoutDestroy(entry.output);
60
+ entry = Entry{};
61
+ }
62
+
63
+ int ensure_runtime() {
64
+ if (handle) return 0;
65
+ cublasStatus_t status = cublasLtCreate(&handle);
66
+ if (status != CUBLAS_STATUS_SUCCESS) return status_code(status);
67
+ const cudaError_t cuda_status = cudaMalloc(&workspace, kWorkspaceBytes);
68
+ return cuda_status == cudaSuccess ? 0 : -2000 - static_cast<int>(cuda_status);
69
+ }
70
+
71
+ int create_entry(const Key& key, Entry* entry) {
72
+ cublasStatus_t status = cublasLtMatmulDescCreate(
73
+ &entry->operation, CUBLAS_COMPUTE_32F, CUDA_R_32F);
74
+ cublasOperation_t transpose = CUBLAS_OP_T;
75
+ cublasOperation_t no_transpose = CUBLAS_OP_N;
76
+ cublasLtEpilogue_t epilogue =
77
+ key.epilogue == static_cast<int>(FlashRtFp8BiasEpilogue::kBiasGelu)
78
+ ? CUBLASLT_EPILOGUE_GELU_BIAS
79
+ : CUBLASLT_EPILOGUE_BIAS;
80
+ cudaDataType_t bias_type = CUDA_R_16BF;
81
+ if (status == CUBLAS_STATUS_SUCCESS) {
82
+ status = cublasLtMatmulDescSetAttribute(
83
+ entry->operation, CUBLASLT_MATMUL_DESC_TRANSA, &transpose,
84
+ sizeof(transpose));
85
+ }
86
+ if (status == CUBLAS_STATUS_SUCCESS) {
87
+ status = cublasLtMatmulDescSetAttribute(
88
+ entry->operation, CUBLASLT_MATMUL_DESC_TRANSB, &no_transpose,
89
+ sizeof(no_transpose));
90
+ }
91
+ if (status == CUBLAS_STATUS_SUCCESS) {
92
+ status = cublasLtMatmulDescSetAttribute(
93
+ entry->operation, CUBLASLT_MATMUL_DESC_EPILOGUE, &epilogue,
94
+ sizeof(epilogue));
95
+ }
96
+ if (status == CUBLAS_STATUS_SUCCESS) {
97
+ status = cublasLtMatmulDescSetAttribute(
98
+ entry->operation, CUBLASLT_MATMUL_DESC_BIAS_DATA_TYPE, &bias_type,
99
+ sizeof(bias_type));
100
+ }
101
+
102
+ // Row-major weight [N,K] is column-major [K,N]. Row-major input [M,K]
103
+ // is column-major [K,M]. The logical result [N,M] has row-major [M,N]
104
+ // storage, so no layout conversion or transpose kernel is required.
105
+ if (status == CUBLAS_STATUS_SUCCESS) {
106
+ status = cublasLtMatrixLayoutCreate(
107
+ &entry->weight, CUDA_R_8F_E4M3, key.K, key.N, key.K);
108
+ }
109
+ if (status == CUBLAS_STATUS_SUCCESS) {
110
+ status = cublasLtMatrixLayoutCreate(
111
+ &entry->input, CUDA_R_8F_E4M3, key.K, key.M, key.K);
112
+ }
113
+ if (status == CUBLAS_STATUS_SUCCESS) {
114
+ status = cublasLtMatrixLayoutCreate(
115
+ &entry->output, CUDA_R_16BF, key.N, key.M, key.N);
116
+ }
117
+
118
+ cublasLtMatmulPreference_t preference = nullptr;
119
+ if (status == CUBLAS_STATUS_SUCCESS) {
120
+ status = cublasLtMatmulPreferenceCreate(&preference);
121
+ }
122
+ if (status == CUBLAS_STATUS_SUCCESS) {
123
+ const size_t workspace_bytes = kWorkspaceBytes;
124
+ status = cublasLtMatmulPreferenceSetAttribute(
125
+ preference, CUBLASLT_MATMUL_PREF_MAX_WORKSPACE_BYTES,
126
+ &workspace_bytes, sizeof(workspace_bytes));
127
+ }
128
+ cublasLtMatmulHeuristicResult_t results[16]{};
129
+ int returned = 0;
130
+ if (status == CUBLAS_STATUS_SUCCESS) {
131
+ status = cublasLtMatmulAlgoGetHeuristic(
132
+ handle, entry->operation, entry->weight, entry->input, entry->output,
133
+ entry->output, preference, 16, results, &returned);
134
+ }
135
+ if (preference) cublasLtMatmulPreferenceDestroy(preference);
136
+ if (status == CUBLAS_STATUS_SUCCESS && returned > 0) {
137
+ entry->algorithm = results[0].algo;
138
+ return 0;
139
+ }
140
+ destroy_entry(*entry);
141
+ return status == CUBLAS_STATUS_SUCCESS ? -1100 : status_code(status);
142
+ }
143
+
144
+ } // namespace
145
+
146
+ int fp8_linear_bias_sm110_bf16(
147
+ const void* input_fp8,
148
+ const void* weight_fp8,
149
+ const void* bias_bf16,
150
+ void* out_bf16,
151
+ int M,
152
+ int N,
153
+ int K,
154
+ float alpha,
155
+ float beta,
156
+ FlashRtFp8BiasEpilogue epilogue,
157
+ cudaStream_t stream) {
158
+ std::lock_guard<std::mutex> lock(cache_mutex);
159
+ int rc = ensure_runtime();
160
+ if (rc != 0) return rc;
161
+ const Key key{M, N, K, static_cast<int>(epilogue)};
162
+ auto iterator = cache.find(key);
163
+ if (iterator == cache.end()) {
164
+ Entry entry;
165
+ rc = create_entry(key, &entry);
166
+ if (rc != 0) return rc;
167
+ iterator = cache.emplace(key, entry).first;
168
+ }
169
+ Entry& entry = iterator->second;
170
+ cublasStatus_t status = cublasLtMatmulDescSetAttribute(
171
+ entry.operation, CUBLASLT_MATMUL_DESC_BIAS_POINTER, &bias_bf16,
172
+ sizeof(bias_bf16));
173
+ if (status != CUBLAS_STATUS_SUCCESS) return status_code(status);
174
+ status = cublasLtMatmul(
175
+ handle, entry.operation, &alpha, weight_fp8, entry.weight, input_fp8,
176
+ entry.input, &beta, out_bf16, entry.output, out_bf16, entry.output,
177
+ &entry.algorithm, workspace, kWorkspaceBytes, stream);
178
+ return status_code(status);
179
+ }
csrc/cublaslt_fp8_bias_sm110.cuh ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #pragma once
2
+
3
+ #include <cuda_runtime.h>
4
+
5
+ enum class FlashRtFp8BiasEpilogue : int {
6
+ kBias = 0,
7
+ kBiasGelu = 1,
8
+ };
9
+
10
+ int fp8_linear_bias_sm110_bf16(
11
+ const void* input_fp8,
12
+ const void* weight_fp8,
13
+ const void* bias_bf16,
14
+ void* out_bf16,
15
+ int M,
16
+ int N,
17
+ int K,
18
+ float alpha,
19
+ float beta,
20
+ FlashRtFp8BiasEpilogue epilogue,
21
+ cudaStream_t stream);
csrc/cutlass_sm110_fp8_gemm.cu ADDED
@@ -0,0 +1,176 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // CUTLASS FP8 GEMM implementations for SM110a. The package dispatcher uses
2
+ // BF16-output Sq/T1/Wide variants. Weight B is column-major in the CUTLASS
3
+ // problem and is supplied by the public API as a contiguous [N, K] tensor.
4
+
5
+ #include "gemm_types_sm110.h"
6
+ #include "cutlass_sm110_fp8_gemm.cuh"
7
+ #include "cutlass/util/device_memory.h"
8
+ #include <cuda_runtime.h>
9
+ #include <cstdio>
10
+
11
+ // Generic runner: initialize and run on the caller's stream.
12
+ template <typename GemmOp>
13
+ static int cutlass_run_impl(void* A, void* B, void* D,
14
+ int M, int N, int K,
15
+ float alpha, float beta,
16
+ cudaStream_t stream) {
17
+ using ElementA = typename GemmOp::ElementA;
18
+ using ElementB = typename GemmOp::ElementB;
19
+ using ElementD = typename GemmOp::ElementD;
20
+
21
+ // CUTLASS stride computation
22
+ auto stride_A = cutlass::make_cute_packed_stride(
23
+ typename GemmOp::GemmKernel::StrideA{}, {M, K, 1});
24
+ auto stride_B = cutlass::make_cute_packed_stride(
25
+ typename GemmOp::GemmKernel::StrideB{}, {N, K, 1});
26
+ auto stride_D = cutlass::make_cute_packed_stride(
27
+ typename GemmOp::GemmKernel::StrideD{}, {M, N, 1});
28
+
29
+ typename GemmOp::Arguments args{
30
+ cutlass::gemm::GemmUniversalMode::kGemm,
31
+ {M, N, K, 1}, // problem size
32
+ {(ElementA*)A, stride_A, (ElementB*)B, stride_B},
33
+ {{alpha, beta}, (ElementD*)D, stride_D, (ElementD*)D, stride_D}
34
+ };
35
+
36
+ GemmOp gemm;
37
+ size_t ws_size = GemmOp::get_workspace_size(args);
38
+ static cutlass::device_memory::allocation<uint8_t> workspace(0);
39
+ if (ws_size > workspace.size()) {
40
+ workspace = cutlass::device_memory::allocation<uint8_t>(ws_size);
41
+ }
42
+
43
+ auto status = gemm.can_implement(args);
44
+ if (status != cutlass::Status::kSuccess) {
45
+ fprintf(stderr, "[CUTLASS] cannot implement: M=%d N=%d K=%d\n", M, N, K);
46
+ return -1;
47
+ }
48
+
49
+ status = gemm.initialize(args, workspace.get(), stream);
50
+ if (status != cutlass::Status::kSuccess) {
51
+ fprintf(stderr, "[CUTLASS] init failed: M=%d N=%d K=%d\n", M, N, K);
52
+ return -2;
53
+ }
54
+
55
+ status = gemm.run(stream);
56
+ if (status != cutlass::Status::kSuccess) {
57
+ fprintf(stderr, "[CUTLASS] run failed: M=%d N=%d K=%d\n", M, N, K);
58
+ return -3;
59
+ }
60
+ return 0;
61
+ }
62
+
63
+ template <typename GemmOp>
64
+ static int cutlass_run_bias_impl(
65
+ void* A, void* B, void* bias, void* D, int M, int N, int K,
66
+ float alpha, float beta, cudaStream_t stream) {
67
+ using ElementA = typename GemmOp::ElementA;
68
+ using ElementB = typename GemmOp::ElementB;
69
+ using ElementC = typename GemmOp::ElementC;
70
+ using ElementD = typename GemmOp::ElementD;
71
+ using ElementBias = cutlass::bfloat16_t;
72
+
73
+ auto stride_A = cutlass::make_cute_packed_stride(
74
+ typename GemmOp::GemmKernel::StrideA{}, {M, K, 1});
75
+ auto stride_B = cutlass::make_cute_packed_stride(
76
+ typename GemmOp::GemmKernel::StrideB{}, {N, K, 1});
77
+ auto stride_C = cutlass::make_cute_packed_stride(
78
+ typename GemmOp::GemmKernel::StrideC{}, {M, N, 1});
79
+ auto stride_D = cutlass::make_cute_packed_stride(
80
+ typename GemmOp::GemmKernel::StrideD{}, {M, N, 1});
81
+
82
+ typename GemmOp::Arguments args{
83
+ cutlass::gemm::GemmUniversalMode::kGemm,
84
+ {M, N, K, 1},
85
+ {reinterpret_cast<ElementA*>(A), stride_A,
86
+ reinterpret_cast<ElementB*>(B), stride_B},
87
+ {{alpha, beta}, reinterpret_cast<ElementC*>(D), stride_C,
88
+ reinterpret_cast<ElementD*>(D), stride_D}
89
+ };
90
+ args.epilogue.thread.bias_ptr =
91
+ reinterpret_cast<ElementBias const*>(bias);
92
+
93
+ GemmOp gemm;
94
+ const size_t ws_size = GemmOp::get_workspace_size(args);
95
+ static cutlass::device_memory::allocation<uint8_t> workspace(0);
96
+ if (ws_size > workspace.size()) {
97
+ workspace = cutlass::device_memory::allocation<uint8_t>(ws_size);
98
+ }
99
+ auto status = gemm.can_implement(args);
100
+ if (status != cutlass::Status::kSuccess) return -11;
101
+ status = gemm.initialize(args, workspace.get(), stream);
102
+ if (status != cutlass::Status::kSuccess) return -12;
103
+ status = gemm.run(stream);
104
+ return status == cutlass::Status::kSuccess ? 0 : -13;
105
+ }
106
+
107
+ // Exported C functions.
108
+ extern "C" {
109
+
110
+ int cutlass_fp8_sq(void* A, void* B, void* D, int M, int N, int K,
111
+ float alpha, float beta, cudaStream_t stream) {
112
+ return cutlass_run_impl<sm100_sq::Gemm>(A, B, D, M, N, K, alpha, beta, stream);
113
+ }
114
+
115
+ int cutlass_fp8_t1(void* A, void* B, void* D, int M, int N, int K,
116
+ float alpha, float beta, cudaStream_t stream) {
117
+ return cutlass_run_impl<sm100_t1::Gemm>(A, B, D, M, N, K, alpha, beta, stream);
118
+ }
119
+
120
+ int cutlass_fp8_wide(void* A, void* B, void* D, int M, int N, int K,
121
+ float alpha, float beta, cudaStream_t stream) {
122
+ return cutlass_run_impl<sm100_wide::Gemm>(A, B, D, M, N, K, alpha, beta, stream);
123
+ }
124
+
125
+ int cutlass_fp8_plain(void* A, void* B, void* D, int M, int N, int K,
126
+ float alpha, float beta, cudaStream_t stream) {
127
+ return cutlass_run_impl<sm100_plain::Gemm>(A, B, D, M, N, K, alpha, beta, stream);
128
+ }
129
+
130
+ int cutlass_fp8_gelu(void* A, void* B, void* D, int M, int N, int K,
131
+ float alpha, float beta, cudaStream_t stream) {
132
+ return cutlass_run_impl<sm100_gelu::Gemm>(A, B, D, M, N, K, alpha, beta, stream);
133
+ }
134
+
135
+ // FP32 output variants for models with activations exceeding FP16 range.
136
+ int cutlass_fp8_sq_f32out(void* A, void* B, void* D, int M, int N, int K,
137
+ float alpha, float beta, cudaStream_t stream) {
138
+ return cutlass_run_impl<sm100_sq_f32out::Gemm>(A, B, D, M, N, K, alpha, beta, stream);
139
+ }
140
+
141
+ int cutlass_fp8_wide_f32out(void* A, void* B, void* D, int M, int N, int K,
142
+ float alpha, float beta, cudaStream_t stream) {
143
+ return cutlass_run_impl<sm100_wide_f32out::Gemm>(A, B, D, M, N, K, alpha, beta, stream);
144
+ }
145
+
146
+ // BF16 output variants
147
+ int cutlass_fp8_sq_bf16out(void* A, void* B, void* D, int M, int N, int K,
148
+ float alpha, float beta, cudaStream_t stream) {
149
+ return cutlass_run_impl<sm100_sq_bf16out::Gemm>(A, B, D, M, N, K, alpha, beta, stream);
150
+ }
151
+
152
+ int cutlass_fp8_wide_bf16out(void* A, void* B, void* D, int M, int N, int K,
153
+ float alpha, float beta, cudaStream_t stream) {
154
+ return cutlass_run_impl<sm100_wide_bf16out::Gemm>(A, B, D, M, N, K, alpha, beta, stream);
155
+ }
156
+
157
+ int cutlass_fp8_t1_bf16out(void* A, void* B, void* D, int M, int N, int K,
158
+ float alpha, float beta, cudaStream_t stream) {
159
+ return cutlass_run_impl<sm100_t1_bf16out::Gemm>(A, B, D, M, N, K, alpha, beta, stream);
160
+ }
161
+
162
+ int cutlass_fp8_wide_bias_bf16out(
163
+ void* A, void* B, void* bias, void* D, int M, int N, int K,
164
+ float alpha, float beta, cudaStream_t stream) {
165
+ return cutlass_run_bias_impl<sm100_wide_bias_bf16out::Gemm>(
166
+ A, B, bias, D, M, N, K, alpha, beta, stream);
167
+ }
168
+
169
+ int cutlass_fp8_wide_bias_gelu_bf16out(
170
+ void* A, void* B, void* bias, void* D, int M, int N, int K,
171
+ float alpha, cudaStream_t stream) {
172
+ return cutlass_run_bias_impl<sm100_wide_bias_gelu_bf16out::Gemm>(
173
+ A, B, bias, D, M, N, K, alpha, 0.0f, stream);
174
+ }
175
+
176
+ } // extern "C"
csrc/cutlass_sm110_fp8_gemm.cuh ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ #pragma once
3
+
4
+ #include <cuda_runtime.h>
5
+
6
+ extern "C" {
7
+
8
+ int cutlass_fp8_sq(void* A, void* B, void* D, int M, int N, int K,
9
+ float alpha, float beta, cudaStream_t stream);
10
+ int cutlass_fp8_t1(void* A, void* B, void* D, int M, int N, int K,
11
+ float alpha, float beta, cudaStream_t stream);
12
+ int cutlass_fp8_wide(void* A, void* B, void* D, int M, int N, int K,
13
+ float alpha, float beta, cudaStream_t stream);
14
+ int cutlass_fp8_plain(void* A, void* B, void* D, int M, int N, int K,
15
+ float alpha, float beta, cudaStream_t stream);
16
+ int cutlass_fp8_gelu(void* A, void* B, void* D, int M, int N, int K,
17
+ float alpha, float beta, cudaStream_t stream);
18
+ int cutlass_fp8_sq_bf16out(void* A, void* B, void* D, int M, int N, int K,
19
+ float alpha, float beta, cudaStream_t stream);
20
+ int cutlass_fp8_wide_bf16out(void* A, void* B, void* D, int M, int N, int K,
21
+ float alpha, float beta, cudaStream_t stream);
22
+ int cutlass_fp8_t1_bf16out(void* A, void* B, void* D, int M, int N, int K,
23
+ float alpha, float beta, cudaStream_t stream);
24
+ int cutlass_fp8_wide_bias_bf16out(
25
+ void* A, void* B, void* bias, void* D, int M, int N, int K,
26
+ float alpha, float beta, cudaStream_t stream);
27
+ int cutlass_fp8_wide_bias_gelu_bf16out(
28
+ void* A, void* B, void* bias, void* D, int M, int N, int K,
29
+ float alpha, cudaStream_t stream);
30
+
31
+ } // extern "C"
csrc/cutlass_sm120_block128_fp8_gemm.cu ADDED
@@ -0,0 +1,273 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ //
3
+ // Path B implementation: CUTLASS block-128 FP8 GEMM for SM120a.
4
+ // Header: cutlass_sm120_block128_fp8_gemm.cuh.
5
+ //
6
+ // Kernel template ported from CUTLASS 4.x example 87b
7
+ // (third_party/cutlass/examples/87_blackwell_geforce_gemm_blockwise/
8
+ // 87b_blackwell_geforce_fp8_bf16_gemm_groupwise.cu).
9
+ //
10
+ // Two GEMM instantiations are kept live and dispatched by M:
11
+ // * Pingpong (TileShape 64 x 128 x 128) — M <= 64
12
+ // * Cooperative (TileShape 128 x 128 x 128) — M > 64
13
+ //
14
+ // Per-shape Arguments + workspace are cached in two thread-safe maps.
15
+ // The kernel itself is fused (no dequant intermediate), removing the
16
+ // 3x memory bandwidth tax of Path D.
17
+
18
+ #include "cutlass_sm120_block128_fp8_gemm.cuh"
19
+
20
+ #include "cute/tensor.hpp"
21
+ #include "cutlass/cutlass.h"
22
+ #include "cutlass/detail/blockwise_scale_layout.hpp"
23
+ #include "cutlass/epilogue/collective/collective_builder.hpp"
24
+ #include "cutlass/epilogue/dispatch_policy.hpp"
25
+ #include "cutlass/epilogue/thread/activation.h"
26
+ #include "cutlass/gemm/collective/collective_builder.hpp"
27
+ #include "cutlass/gemm/device/gemm_universal_adapter.h"
28
+ #include "cutlass/gemm/dispatch_policy.hpp"
29
+ #include "cutlass/gemm/kernel/gemm_universal.hpp"
30
+ #include "cutlass/gemm/kernel/tile_scheduler_params.h"
31
+ #include "cutlass/tensor_ref.h"
32
+ #include "cutlass/util/packed_stride.hpp"
33
+
34
+ #include <cstdio>
35
+ #include <mutex>
36
+ #include <unordered_map>
37
+
38
+ namespace flash_rt {
39
+ namespace gemm {
40
+
41
+ namespace {
42
+
43
+ using namespace cute;
44
+
45
+ // ── Element / layout types (match 87b) ───────────────────────────
46
+ using ElementA = cutlass::float_e4m3_t;
47
+ using LayoutA = cutlass::layout::RowMajor;
48
+ constexpr int AlignmentA = 128 / cutlass::sizeof_bits<ElementA>::value;
49
+
50
+ using ElementB = cutlass::float_e4m3_t;
51
+ using LayoutB = cutlass::layout::ColumnMajor;
52
+ constexpr int AlignmentB = 128 / cutlass::sizeof_bits<ElementB>::value;
53
+
54
+ using ElementC = cutlass::bfloat16_t;
55
+ using LayoutC = cutlass::layout::RowMajor;
56
+ constexpr int AlignmentC = 128 / cutlass::sizeof_bits<ElementC>::value;
57
+
58
+ using ElementD = ElementC;
59
+ using LayoutD = LayoutC;
60
+ constexpr int AlignmentD = AlignmentC;
61
+
62
+ using ElementAccumulator = float;
63
+ using ElementCompute = float;
64
+
65
+ // DeepSeek / Qwen3.6 layout: per-token activation, 128x128 weight.
66
+ //
67
+ // majorSFA = majorSFB = K so the SFA tensor is laid out (M, K/128)
68
+ // row-major (the natural ckpt layout produced by HF dynamic FP8 quant)
69
+ // instead of (M, K/128) col-major (the CUTLASS MN-major default).
70
+ // Same for SFB: (N/128, K/128) row-major matches the safetensors
71
+ // weight_scale_inv on disk.
72
+ constexpr int ScaleGranularityM = 1;
73
+ constexpr int ScaleGranularityN = 128;
74
+ constexpr int ScaleGranularityK = 128;
75
+ using ScaleConfig =
76
+ cutlass::detail::Sm120BlockwiseScaleConfig<ScaleGranularityM,
77
+ ScaleGranularityN,
78
+ ScaleGranularityK,
79
+ cute::UMMA::Major::K,
80
+ cute::UMMA::Major::K>;
81
+ using LayoutSFA = decltype(ScaleConfig::deduce_layoutSFA());
82
+ using LayoutSFB = decltype(ScaleConfig::deduce_layoutSFB());
83
+
84
+ // ── Two kernel variants (Pingpong for small M, Cooperative for larger M) ──
85
+ template <class MmaTileShape_, class Schedule_>
86
+ struct GemmInstance {
87
+ using MmaTileShape = MmaTileShape_;
88
+ using ClusterShape = Shape<_1, _1, _1>;
89
+
90
+ using CollectiveEpilogue = typename cutlass::epilogue::collective::CollectiveBuilder<
91
+ cutlass::arch::Sm120, cutlass::arch::OpClassTensorOp,
92
+ MmaTileShape, ClusterShape,
93
+ cutlass::epilogue::collective::EpilogueTileAuto,
94
+ ElementAccumulator, ElementCompute,
95
+ ElementC, LayoutC, AlignmentC,
96
+ ElementD, LayoutD, AlignmentD,
97
+ cutlass::epilogue::collective::EpilogueScheduleAuto
98
+ >::CollectiveOp;
99
+
100
+ using CollectiveMainloop = typename cutlass::gemm::collective::CollectiveBuilder<
101
+ cutlass::arch::Sm120, cutlass::arch::OpClassTensorOp,
102
+ ElementA, cute::tuple<LayoutA, LayoutSFA>, AlignmentA,
103
+ ElementB, cute::tuple<LayoutB, LayoutSFB>, AlignmentB,
104
+ ElementAccumulator,
105
+ MmaTileShape, ClusterShape,
106
+ cutlass::gemm::collective::StageCountAutoCarveout<
107
+ static_cast<int>(sizeof(typename CollectiveEpilogue::SharedStorage))>,
108
+ Schedule_
109
+ >::CollectiveOp;
110
+
111
+ using GemmKernel = cutlass::gemm::kernel::GemmUniversal<
112
+ Shape<int, int, int, int>,
113
+ CollectiveMainloop,
114
+ CollectiveEpilogue,
115
+ void>;
116
+
117
+ using Gemm = cutlass::gemm::device::GemmUniversalAdapter<GemmKernel>;
118
+ };
119
+
120
+ using PingpongMmaTileShape = Shape<_64, _128, _128>;
121
+ using CooperativeMmaTileShape = Shape<_128, _128, _128>;
122
+
123
+ using PingpongGemm =
124
+ typename GemmInstance<PingpongMmaTileShape,
125
+ cutlass::gemm::KernelTmaWarpSpecializedBlockwisePingpongSm120>::Gemm;
126
+ using CooperativeGemm =
127
+ typename GemmInstance<CooperativeMmaTileShape,
128
+ cutlass::gemm::KernelScheduleSm120Blockwise>::Gemm;
129
+
130
+ // ── Per-shape workspace cache ───────────────────────────────────
131
+ struct ShapeKey {
132
+ int M, N, K;
133
+ bool operator==(const ShapeKey& o) const {
134
+ return M == o.M && N == o.N && K == o.K;
135
+ }
136
+ };
137
+ struct ShapeKeyHash {
138
+ size_t operator()(const ShapeKey& k) const noexcept {
139
+ return (static_cast<size_t>(k.M) * 1315423911u)
140
+ ^ (static_cast<size_t>(k.N) * 2654435761u)
141
+ ^ static_cast<size_t>(k.K);
142
+ }
143
+ };
144
+
145
+ struct CachedWorkspace {
146
+ void* ptr = nullptr;
147
+ size_t size = 0;
148
+ };
149
+
150
+ std::unordered_map<ShapeKey, CachedWorkspace, ShapeKeyHash> g_ws_cache;
151
+ std::mutex g_ws_mu;
152
+
153
+ void* get_workspace(int M, int N, int K, size_t needed) {
154
+ std::lock_guard<std::mutex> lk(g_ws_mu);
155
+ ShapeKey key{M, N, K};
156
+ auto it = g_ws_cache.find(key);
157
+ if (it != g_ws_cache.end() && it->second.size >= needed) {
158
+ return it->second.ptr;
159
+ }
160
+ if (it != g_ws_cache.end()) {
161
+ cudaFree(it->second.ptr);
162
+ g_ws_cache.erase(it);
163
+ }
164
+ CachedWorkspace w;
165
+ w.size = needed;
166
+ if (needed > 0) {
167
+ cudaMalloc(&w.ptr, needed);
168
+ }
169
+ g_ws_cache[key] = w;
170
+ return w.ptr;
171
+ }
172
+
173
+ template <class Gemm>
174
+ cutlass::Status run_gemm(
175
+ const void* A_fp8, const void* B_fp8, void* D_bf16,
176
+ int M, int N, int K,
177
+ const float* act_scale, const float* w_scale,
178
+ cudaStream_t stream)
179
+ {
180
+ using StrideA = typename Gemm::GemmKernel::StrideA;
181
+ using StrideB = typename Gemm::GemmKernel::StrideB;
182
+ using StrideC = typename Gemm::GemmKernel::StrideC;
183
+ using StrideD = typename Gemm::GemmKernel::StrideD;
184
+
185
+ StrideA stride_A = cutlass::make_cute_packed_stride(
186
+ StrideA{}, cute::make_shape(M, K, 1));
187
+ StrideB stride_B = cutlass::make_cute_packed_stride(
188
+ StrideB{}, cute::make_shape(N, K, 1));
189
+ StrideC stride_C = cutlass::make_cute_packed_stride(
190
+ StrideC{}, cute::make_shape(M, N, 1));
191
+ StrideD stride_D = cutlass::make_cute_packed_stride(
192
+ StrideD{}, cute::make_shape(M, N, 1));
193
+
194
+ LayoutSFA layout_SFA = ScaleConfig::tile_atom_to_shape_SFA(
195
+ cute::make_shape(M, N, K, 1));
196
+ LayoutSFB layout_SFB = ScaleConfig::tile_atom_to_shape_SFB(
197
+ cute::make_shape(M, N, K, 1));
198
+
199
+ typename Gemm::Arguments args{
200
+ cutlass::gemm::GemmUniversalMode::kGemm,
201
+ {M, N, K, 1},
202
+ {
203
+ reinterpret_cast<ElementA const*>(A_fp8), stride_A,
204
+ reinterpret_cast<ElementB const*>(B_fp8), stride_B,
205
+ act_scale, layout_SFA,
206
+ w_scale, layout_SFB
207
+ },
208
+ {
209
+ {1.0f, 0.0f}, // epilogue.thread (alpha, beta)
210
+ nullptr, stride_C, // C unused (beta = 0)
211
+ reinterpret_cast<ElementD*>(D_bf16), stride_D
212
+ }
213
+ };
214
+
215
+ Gemm gemm;
216
+ size_t ws_size = Gemm::get_workspace_size(args);
217
+ void* ws_ptr = get_workspace(M, N, K, ws_size);
218
+
219
+ auto status = gemm.can_implement(args);
220
+ if (status != cutlass::Status::kSuccess) {
221
+ std::fprintf(stderr,
222
+ "[fp8_block128_gemm_cutlass_sm120_bf16out] can_implement FAIL "
223
+ "for M=%d N=%d K=%d (status=%d)\n",
224
+ M, N, K, static_cast<int>(status));
225
+ return status;
226
+ }
227
+ status = gemm.initialize(args, ws_ptr, stream);
228
+ if (status != cutlass::Status::kSuccess) {
229
+ std::fprintf(stderr,
230
+ "[fp8_block128_gemm_cutlass_sm120_bf16out] initialize FAIL "
231
+ "for M=%d N=%d K=%d (status=%d)\n",
232
+ M, N, K, static_cast<int>(status));
233
+ return status;
234
+ }
235
+ status = gemm.run(stream);
236
+ return status;
237
+ }
238
+
239
+ } // namespace
240
+
241
+ void fp8_block128_gemm_cutlass_sm120_bf16out(
242
+ const void* A_fp8,
243
+ const void* B_fp8,
244
+ void* D_bf16,
245
+ int M, int N, int K,
246
+ const float* act_block_scale,
247
+ const float* w_block_scale,
248
+ cudaStream_t stream)
249
+ {
250
+ // Schedule selection: small M -> Pingpong (better latency at low
251
+ // arithmetic intensity); large M -> Cooperative (better throughput
252
+ // when there are enough M-tiles to fill the SMs).
253
+ cutlass::Status status;
254
+ if (M <= 64) {
255
+ status = run_gemm<PingpongGemm>(
256
+ A_fp8, B_fp8, D_bf16, M, N, K,
257
+ act_block_scale, w_block_scale, stream);
258
+ } else {
259
+ status = run_gemm<CooperativeGemm>(
260
+ A_fp8, B_fp8, D_bf16, M, N, K,
261
+ act_block_scale, w_block_scale, stream);
262
+ }
263
+
264
+ if (status != cutlass::Status::kSuccess) {
265
+ std::fprintf(stderr,
266
+ "[fp8_block128_gemm_cutlass_sm120_bf16out] run FAIL "
267
+ "for M=%d N=%d K=%d (status=%d); D output undefined\n",
268
+ M, N, K, static_cast<int>(status));
269
+ }
270
+ }
271
+
272
+ } // namespace gemm
273
+ } // namespace flash_rt
csrc/cutlass_sm120_block128_fp8_gemm.cuh ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ //
3
+ // CUTLASS-based block-128 FP8 GEMM for SM120a (RTX 5090 / Blackwell
4
+ // consumer).
5
+ //
6
+ // Native block-scaled FP8 GEMM with DeepSeek-V3 / Qwen3.6 layout:
7
+ // * activation: per-token (1) x per-128 K block scale
8
+ // * weight : per-128 N x per-128 K block scale
9
+ // * output : BF16
10
+ //
11
+ // Replaces the Path D dequantize-then-bf16-GEMM stop-gap with a
12
+ // fused Tensor Core kernel from CUTLASS 4.x example 87b
13
+ // (87b_blackwell_geforce_fp8_bf16_gemm_groupwise.cu). Same Python /
14
+ // pybind signature shape as fp8_block128_gemm_descale_bf16out so
15
+ // callers can swap with one-line change.
16
+
17
+ #pragma once
18
+
19
+ #include <cuda_runtime.h>
20
+
21
+ namespace flash_rt {
22
+ namespace gemm {
23
+
24
+ // Path B SM120a CUTLASS block-128 FP8 GEMM, BF16 output.
25
+ //
26
+ // Layout & shapes match Path D's signature:
27
+ // A_fp8 : (M, K) e4m3 row-major
28
+ // B_fp8 : (N, K) e4m3 row-major
29
+ // D_bf16 : (M, N) bf16 row-major
30
+ // act_scale : (M, K/128) fp32 row-major
31
+ // w_scale : (N/128, K/128) fp32 row-major
32
+ //
33
+ // Constraints: K and N must be multiples of 128. M is unrestricted.
34
+ //
35
+ // Internally selects a Cooperative or Pingpong CUTLASS schedule
36
+ // based on M (Pingpong is faster when M is small, e.g. decode
37
+ // step or short prefill). Caller does not provide scratch buffers
38
+ // (the kernel is fused, no dequant intermediates needed).
39
+ //
40
+ // Stream-safe; per-shape arguments + workspace cached internally.
41
+ void fp8_block128_gemm_cutlass_sm120_bf16out(
42
+ const void* A_fp8,
43
+ const void* B_fp8,
44
+ void* D_bf16,
45
+ int M, int N, int K,
46
+ const float* act_block_scale,
47
+ const float* w_block_scale,
48
+ cudaStream_t stream);
49
+
50
+ } // namespace gemm
51
+ } // namespace flash_rt
csrc/fp8_block128_gemm_mma_sm89.cu ADDED
@@ -0,0 +1,318 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ //
3
+ // Native Ada (sm_89) FP8 e4m3 -> BF16 block-128 scaled GEMM.
4
+ // Header: fp8_block128_gemm_mma_sm89.cuh.
5
+ //
6
+ // Adapted from csrc/gemm/fp8_smallM_handtuned_sm120.cu (same cp.async
7
+ // pipeline + m16n8k32 MMA tiling). Two sm_89-specific changes vs that file:
8
+ // 1. MMA uses the plain Ada FP8 op `mma.sync.aligned.m16n8k32.row.col.
9
+ // f32.e4m3.e4m3.f32` (no `.kind::f8f6f4`, which is sm_120a-only).
10
+ // 2. Per-tensor `alpha` is replaced by DeepSeek-style block-128 scaling:
11
+ // BLOCK_K is pinned to 128 so each K-iteration is exactly one scale
12
+ // block. Each k-iter accumulates into a temp, then folds
13
+ // act_scale[row,kb] * w_scale[n/128,kb] into the running accumulator.
14
+ //
15
+ // This reads the FP8 weight directly (no dequant-to-bf16 scratch), cutting
16
+ // per-linear weight traffic ~5x vs fp8_block128_gemm_descale_bf16out while
17
+ // keeping the per-token activation scale (no precision downgrade).
18
+
19
+ #include "fp8_block128_gemm_mma_sm89.cuh"
20
+ // Device-side kernel body. Shared verbatim with the standalone micro-bench
21
+ // (benchmarks/sm89_fp8_block128_gemm), so the bench's `--mode baseline` runs
22
+ // this exact kernel and cannot drift behind production.
23
+ #include "fp8_bs_gemm_device.cuh"
24
+
25
+ #include <cuda_bf16.h>
26
+ #include <cuda_fp8.h>
27
+ #include <cuda_runtime.h>
28
+ #include <cstdint>
29
+ #include <stdexcept>
30
+
31
+ namespace flash_rt {
32
+ namespace gemm {
33
+ namespace block128_sm89 {
34
+
35
+ namespace {
36
+
37
+ template <int BM, int BN, int W, int STAGES, int MIN_BLK>
38
+ int launch_(const void* A, const void* B, void* D,
39
+ int M, int N, int K, const float* act_scale,
40
+ const float* w_scale, cudaStream_t s)
41
+ {
42
+ constexpr int BK = 128;
43
+ constexpr int SCALE_KTILE = 8;
44
+ int grid_m = (M + BM - 1) / BM;
45
+ int grid_n = (N + BN - 1) / BN;
46
+ dim3 grid(grid_m, grid_n, 1);
47
+ dim3 block(W * 32, 1, 1);
48
+ // Swizzled A/B cp.async stages (no pad) + staged scale tile.
49
+ int smem_bytes = STAGES * (BM + BN) * BK
50
+ + (BM * SCALE_KTILE + SCALE_KTILE) * (int)sizeof(float);
51
+ if (smem_bytes > 48 * 1024) {
52
+ cudaFuncSetAttribute(
53
+ (const void*)&fp8_bs_gemm_kernel<BM, BN, W, STAGES, MIN_BLK, false>,
54
+ cudaFuncAttributeMaxDynamicSharedMemorySize, smem_bytes);
55
+ }
56
+ fp8_bs_gemm_kernel<BM, BN, W, STAGES, MIN_BLK, false><<<grid, block, smem_bytes, s>>>(
57
+ reinterpret_cast<const __nv_fp8_e4m3*>(A),
58
+ reinterpret_cast<const __nv_fp8_e4m3*>(B),
59
+ act_scale, w_scale,
60
+ reinterpret_cast<__nv_bfloat16*>(D),
61
+ M, N, K);
62
+ cudaError_t err = cudaGetLastError();
63
+ return (err == cudaSuccess) ? 0 : 1;
64
+ }
65
+
66
+ // Residual-fold launch: D = bf16(acc) + resid, fusing the residual add into the
67
+ // GEMM epilogue (no separate residual_add launch, no D HBM round-trip). resid
68
+ // is [M, N] BF16 row-major, same layout as D. See fp8_bs_gemm_device.cuh.
69
+ template <int BM, int BN, int W, int STAGES, int MIN_BLK>
70
+ int launch_resid_(const void* A, const void* B, void* D,
71
+ int M, int N, int K, const float* act_scale,
72
+ const float* w_scale, const void* resid, cudaStream_t s)
73
+ {
74
+ constexpr int BK = 128;
75
+ constexpr int SCALE_KTILE = 8;
76
+ int grid_m = (M + BM - 1) / BM;
77
+ int grid_n = (N + BN - 1) / BN;
78
+ dim3 grid(grid_m, grid_n, 1);
79
+ dim3 block(W * 32, 1, 1);
80
+ int smem_bytes = STAGES * (BM + BN) * BK
81
+ + (BM * SCALE_KTILE + SCALE_KTILE) * (int)sizeof(float);
82
+ if (smem_bytes > 48 * 1024) {
83
+ cudaFuncSetAttribute(
84
+ (const void*)&fp8_bs_gemm_kernel<BM, BN, W, STAGES, MIN_BLK, true>,
85
+ cudaFuncAttributeMaxDynamicSharedMemorySize, smem_bytes);
86
+ }
87
+ fp8_bs_gemm_kernel<BM, BN, W, STAGES, MIN_BLK, true><<<grid, block, smem_bytes, s>>>(
88
+ reinterpret_cast<const __nv_fp8_e4m3*>(A),
89
+ reinterpret_cast<const __nv_fp8_e4m3*>(B),
90
+ act_scale, w_scale,
91
+ reinterpret_cast<__nv_bfloat16*>(D),
92
+ M, N, K,
93
+ reinterpret_cast<const __nv_bfloat16*>(resid));
94
+ cudaError_t err = cudaGetLastError();
95
+ return (err == cudaSuccess) ? 0 : 1;
96
+ }
97
+
98
+ } // namespace
99
+
100
+ #define DEFINE(NAME, BM, BN, W, S, MB) \
101
+ int NAME(const void* A, const void* B, void* D, int M, int N, int K, \
102
+ const float* act_scale, const float* w_scale, cudaStream_t s) { \
103
+ return launch_<BM, BN, W, S, MB>(A, B, D, M, N, K, act_scale, w_scale, s);\
104
+ }
105
+
106
+ // Residual-fold variants (suffix _resid). D = bf16(acc) + resid. Only the
107
+ // tiles the prefill down-proj actually selects are defined; additive — the
108
+ // non-resid DEFINE list above is unchanged.
109
+ #define DEFINE_RESID(NAME, BM, BN, W, S, MB) \
110
+ int NAME(const void* A, const void* B, void* D, int M, int N, int K, \
111
+ const float* act_scale, const float* w_scale, const void* resid, \
112
+ cudaStream_t s) { \
113
+ return launch_resid_<BM, BN, W, S, MB>(A, B, D, M, N, K, act_scale, \
114
+ w_scale, resid, s); \
115
+ }
116
+
117
+ // GeGLU silu-fold launch: fuses gate+up GEMM + silu(gate)*up + per-token
118
+ // block-128 FP8 quant into one launch (no [M,2N] BF16 transient). B is
119
+ // gate_up_w [2*N, K] (gate rows [0,N), up rows [N,2N)); w_scale is gate_up_s
120
+ // [2*N/128, K/128]. Outputs FP8 [M,N] + scale [M,N/128]. See device header.
121
+ template <int BM, int BN, int W, int STAGES, int MIN_BLK>
122
+ int launch_geglu_silu_fold_(const void* A, const void* B,
123
+ int M, int N, int K, const float* act_scale,
124
+ const float* w_scale, void* output, float* out_scale,
125
+ cudaStream_t s)
126
+ {
127
+ constexpr int BK = 128;
128
+ constexpr int SCALE_KTILE = 8;
129
+ int grid_m = (M + BM - 1) / BM;
130
+ int grid_n = (N + BN - 1) / BN; // over output N (== inter), NOT 2*N
131
+ dim3 grid(grid_m, grid_n, 1);
132
+ dim3 block(W * 32, 1, 1);
133
+ // A/B cp.async stages + gate_smem (BM*BN bf16) + scales + amax scratch.
134
+ int smem_bytes = STAGES * (BM + BN) * BK
135
+ + (BM * BN) * (int)sizeof(__nv_bfloat16)
136
+ + (BM * SCALE_KTILE + 2 * SCALE_KTILE) * (int)sizeof(float)
137
+ + (W * BM + BM) * (int)sizeof(float);
138
+ if (smem_bytes > 48 * 1024) {
139
+ cudaFuncSetAttribute(
140
+ (const void*)&fp8_bs_geglu_silu_fold_kernel<BM, BN, W, STAGES, MIN_BLK>,
141
+ cudaFuncAttributeMaxDynamicSharedMemorySize, smem_bytes);
142
+ }
143
+ fp8_bs_geglu_silu_fold_kernel<BM, BN, W, STAGES, MIN_BLK><<<grid, block, smem_bytes, s>>>(
144
+ reinterpret_cast<const __nv_fp8_e4m3*>(A),
145
+ reinterpret_cast<const __nv_fp8_e4m3*>(B),
146
+ act_scale, w_scale,
147
+ reinterpret_cast<__nv_fp8_e4m3*>(output),
148
+ out_scale, M, N, K);
149
+ cudaError_t err = cudaGetLastError();
150
+ return (err == cudaSuccess) ? 0 : 1;
151
+ }
152
+
153
+ // A-persistent interleaved variant: stage A once, reuse ONE B smem region for
154
+ // gate then up within each k-iter (both gate+up acc live in regs, true
155
+ // interleaved per K-tile). Single B region -> 3 CTA/SM at s1 (vs interleaved's
156
+ // 2, vs two-pass's 3). See fp8_bs_geglu_silu_fold_apersist_kernel.
157
+ template <int BM, int BN, int W, int STAGES, int MIN_BLK>
158
+ int launch_geglu_silu_fold_apersist_(const void* A, const void* B,
159
+ int M, int N, int K, const float* act_scale,
160
+ const float* w_scale, void* output,
161
+ float* out_scale, cudaStream_t s)
162
+ {
163
+ constexpr int BK = 128;
164
+ constexpr int SCALE_KTILE = 8;
165
+ int grid_m = (M + BM - 1) / BM;
166
+ int grid_n = (N + BN - 1) / BN;
167
+ dim3 grid(grid_m, grid_n, 1);
168
+ dim3 block(W * 32, 1, 1);
169
+ // Same smem layout as the two-pass variant (gate_smem region kept for layout
170
+ // parity though apersist doesn't use it as a handoff — gate stays in regs).
171
+ int smem_bytes = STAGES * (BM + BN) * BK
172
+ + (BM * BN) * (int)sizeof(__nv_bfloat16)
173
+ + (BM * SCALE_KTILE + 2 * SCALE_KTILE) * (int)sizeof(float)
174
+ + (W * BM + BM) * (int)sizeof(float);
175
+ if (smem_bytes > 48 * 1024) {
176
+ cudaFuncSetAttribute(
177
+ (const void*)&fp8_bs_geglu_silu_fold_apersist_kernel<BM, BN, W, STAGES, MIN_BLK>,
178
+ cudaFuncAttributeMaxDynamicSharedMemorySize, smem_bytes);
179
+ }
180
+ fp8_bs_geglu_silu_fold_apersist_kernel<BM, BN, W, STAGES, MIN_BLK><<<grid, block, smem_bytes, s>>>(
181
+ reinterpret_cast<const __nv_fp8_e4m3*>(A),
182
+ reinterpret_cast<const __nv_fp8_e4m3*>(B),
183
+ act_scale, w_scale,
184
+ reinterpret_cast<__nv_fp8_e4m3*>(output),
185
+ out_scale, M, N, K);
186
+ cudaError_t err = cudaGetLastError();
187
+ return (err == cudaSuccess) ? 0 : 1;
188
+ }
189
+
190
+ DEFINE(fp8_block128_gemm_bs_sm89_32x128x128_w4, 32, 128, 4, 2, 4)
191
+ DEFINE(fp8_block128_gemm_bs_sm89_64x128x128_w4, 64, 128, 4, 2, 4)
192
+ DEFINE(fp8_block128_gemm_bs_sm89_64x128x128_w8, 64, 128, 8, 2, 4)
193
+ DEFINE(fp8_block128_gemm_bs_sm89_128x128x128_w4, 128, 128, 4, 2, 2)
194
+ DEFINE(fp8_block128_gemm_bs_sm89_128x128x128_w8, 128, 128, 8, 2, 2)
195
+ DEFINE(fp8_block128_gemm_bs_sm89_32x64x128_w4, 32, 64, 4, 2, 4)
196
+ DEFINE(fp8_block128_gemm_bs_sm89_64x64x128_w4, 64, 64, 4, 2, 4)
197
+ DEFINE(fp8_block128_gemm_bs_sm89_128x64x128_w4, 128, 64, 4, 2, 2)
198
+ DEFINE(fp8_block128_gemm_bs_sm89_16x128x128_w4, 16, 128, 4, 2, 4)
199
+ DEFINE(fp8_block128_gemm_bs_sm89_16x64x128_w4, 16, 64, 4, 2, 4)
200
+ DEFINE(fp8_block128_gemm_bs_sm89_32x128x128_w4_s1, 32, 128, 4, 1, 4)
201
+ DEFINE(fp8_block128_gemm_bs_sm89_64x64x128_w4_s1, 64, 64, 4, 1, 4)
202
+ DEFINE(fp8_block128_gemm_bs_sm89_128x128x128_w8_s1, 128, 128, 8, 1, 2)
203
+
204
+ #undef DEFINE
205
+
206
+ // Residual-fold variants for the down-proj prefill tiles (see dispatcher
207
+ // below): the 2B/8B down-proj selects 64x64_s1 (8B) / 64x64 (2B small-M) /
208
+ // 32x64 (small-M) at the S ranges Phase-0 measured. Defined additively; the
209
+ // baseline kernels above are untouched.
210
+ DEFINE_RESID(fp8_block128_gemm_bs_sm89_32x64x128_w4_resid, 32, 64, 4, 2, 4)
211
+ DEFINE_RESID(fp8_block128_gemm_bs_sm89_64x64x128_w4_resid, 64, 64, 4, 2, 4)
212
+ DEFINE_RESID(fp8_block128_gemm_bs_sm89_64x64x128_w4_s1_resid, 64, 64, 4, 1, 4)
213
+ DEFINE_RESID(fp8_block128_gemm_bs_sm89_128x128x128_w8_s1_resid, 128, 128, 8, 1, 2)
214
+
215
+ #undef DEFINE_RESID
216
+
217
+ // GeGLU silu-fold tile variants (BLOCK_N pinned to 128 = one quant block).
218
+ #define DEFINE_GEGLU(NAME, BM, BN, W, S, MB) \
219
+ int NAME(const void* A, const void* B, int M, int N, int K, \
220
+ const float* act_scale, const float* w_scale, void* output, \
221
+ float* out_scale, cudaStream_t s) { \
222
+ return launch_geglu_silu_fold_<BM, BN, W, S, MB>( \
223
+ A, B, M, N, K, act_scale, w_scale, output, out_scale, s); \
224
+ }
225
+ DEFINE_GEGLU(fp8_bs_geglu_silu_fold_sm89_32x128_w4_s2, 32, 128, 4, 2, 4)
226
+ DEFINE_GEGLU(fp8_bs_geglu_silu_fold_sm89_16x128_w4_s2, 16, 128, 4, 2, 4)
227
+ DEFINE_GEGLU(fp8_bs_geglu_silu_fold_sm89_64x128_w4_s2, 64, 128, 4, 2, 4)
228
+ DEFINE_GEGLU(fp8_bs_geglu_silu_fold_sm89_128x128_w8_s1, 128, 128, 8, 1, 2)
229
+ // Low-smem variants (STAGES=1) to recover occupancy lost to gate_smem on sm89:
230
+ // the s2 dual-buffer + gate_smem pushes dynamic smem >48KB → Block Limit Shared
231
+ // Mem = 1 (8% occupancy, ncu-confirmed). s1 trades cp.async overlap for 3-4x
232
+ // the CTA density. Primary candidates for the prefill M>=128 regime.
233
+ DEFINE_GEGLU(fp8_bs_geglu_silu_fold_sm89_32x128_w4_s1, 32, 128, 4, 1, 4)
234
+ DEFINE_GEGLU(fp8_bs_geglu_silu_fold_sm89_16x128_w4_s1, 16, 128, 4, 1, 4)
235
+ #undef DEFINE_GEGLU
236
+
237
+ // A-persistent interleaved variant (single B smem region, gate+up acc both in
238
+ // regs). launch wrapper shares the smem formula with the two-pass variant.
239
+ #define DEFINE_GEGLU_AP(NAME, BM, BN, W, S, MB) \
240
+ int NAME(const void* A, const void* B, int M, int N, int K, \
241
+ const float* act_scale, const float* w_scale, void* output, \
242
+ float* out_scale, cudaStream_t s) { \
243
+ return launch_geglu_silu_fold_apersist_<BM, BN, W, S, MB>( \
244
+ A, B, M, N, K, act_scale, w_scale, output, out_scale, s); \
245
+ }
246
+ DEFINE_GEGLU_AP(fp8_bs_geglu_silu_fold_apersist_sm89_32x128_w4_s1, 32, 128, 4, 1, 2)
247
+ DEFINE_GEGLU_AP(fp8_bs_geglu_silu_fold_apersist_sm89_16x128_w4_s1, 16, 128, 4, 1, 2)
248
+ DEFINE_GEGLU_AP(fp8_bs_geglu_silu_fold_apersist_sm89_32x128_w4_s2, 32, 128, 4, 2, 2)
249
+ #undef DEFINE_GEGLU_AP
250
+
251
+ int fp8_block128_gemm_blockscaled_sm89_bf16out(
252
+ const void* A, const void* B, void* D, int M, int N, int K,
253
+ const float* act_scale, const float* w_scale, cudaStream_t stream)
254
+ {
255
+ if ((N % 128) != 0)
256
+ throw std::runtime_error(
257
+ "fp8_block128_gemm_blockscaled_sm89_bf16out requires N multiple of 128");
258
+ if ((K % 128) != 0)
259
+ throw std::runtime_error(
260
+ "fp8_block128_gemm_blockscaled_sm89_bf16out requires K multiple of 128");
261
+ // Tuned on 4090 over Qwen3-VL-8B-FP8 layer shapes (qkv 6144, o 4096,
262
+ // gate/up 12288, down 4096x12288) at S=79..256. BLOCK_M=32 keeps grid
263
+ // occupancy high at small M; BLOCK_N=64 wins until M crosses ~128, then
264
+ // the wider BLOCK_N=128 amortizes better. Tiny-N (<2048) prefers BLOCK_N=64.
265
+ //
266
+ // ViT prefill is a different regime: full-res FlashRT.png runs M=6256.
267
+ // On these large-M shapes the language-prefill heuristic is wrong for
268
+ // the small-N linears:
269
+ // - patch_embed / proj (N=1152, K≈1152..1536) prefer 32x128
270
+ // - fc2 / merger-fc2 (N=1152, K>=4096) prefer 64x64
271
+ // Keep the original small-M path intact and only branch once the grid is
272
+ // already abundant (M>=2048), so text prefill / decode remain unchanged.
273
+ if (N < 2048)
274
+ {
275
+ if (M >= 2048) {
276
+ if (K >= 4096)
277
+ return fp8_block128_gemm_bs_sm89_64x64x128_w4(
278
+ A, B, D, M, N, K, act_scale, w_scale, stream);
279
+ return fp8_block128_gemm_bs_sm89_32x128x128_w4(
280
+ A, B, D, M, N, K, act_scale, w_scale, stream);
281
+ }
282
+ return fp8_block128_gemm_bs_sm89_16x64x128_w4(
283
+ A, B, D, M, N, K, act_scale, w_scale, stream);
284
+ }
285
+ if (M < 128)
286
+ return fp8_block128_gemm_bs_sm89_32x64x128_w4(
287
+ A, B, D, M, N, K, act_scale, w_scale, stream);
288
+ // Language prefill (M>=128, N>=2048) is limited by low eligible warps on
289
+ // Ada. A single cp.async stage reduces shared-memory pressure and wins on
290
+ // Qwen3-VL 2B/8B prefill shapes. Keep a short-prefill exception for the
291
+ // wide 8B MLP, where the 8-warp tile remains slightly faster.
292
+ if (N >= 8192 && K == 4096 && M < 1024)
293
+ return fp8_block128_gemm_bs_sm89_128x128x128_w8_s1(
294
+ A, B, D, M, N, K, act_scale, w_scale, stream);
295
+ // Small-M regime (M<256) for N<8192 linears (qkv/o/down): at M=128 the
296
+ // 64x64/s1 grid underfills the SMs (8B qkv 64x64_s1 = 192 blocks = 1.5/SM;
297
+ // 2B qkv = 128 blocks = 1/SM), so achieved occupancy is grid-limited well
298
+ // below the theoretical cap. The smaller 32x64 tile doubles grid_m (8B qkv
299
+ // -> 384 blocks = 3/SM; 2B qkv -> 256 = 2/SM) and wins despite a lower
300
+ // per-block warp cap — ncu shows 8B qkv M=128: 32x64 51.6us vs 64x64_s1
301
+ // 61.9us (-17%). Graph-captured e2e confirms: 2B S=128 -13.5%, 8B S=128
302
+ // -12.0%, 2B S=192 -5.5%, 8B S=192 -2.0% (gain shrinks as M approaches the
303
+ // 256 crossover, beyond which 64x64/s1 wins — see layer-regime micro-bench).
304
+ // Wide-MLP gate_up keeps its existing s1 tile (8B via 128x128_w8_s1 above;
305
+ // 2B via the default 64x64_s1 below) — it is best at all M.
306
+ if (M < 256 && N < 8192)
307
+ return fp8_block128_gemm_bs_sm89_32x64x128_w4(
308
+ A, B, D, M, N, K, act_scale, w_scale, stream);
309
+ if (N == 2048 && M < 1024)
310
+ return fp8_block128_gemm_bs_sm89_64x64x128_w4(
311
+ A, B, D, M, N, K, act_scale, w_scale, stream);
312
+ return fp8_block128_gemm_bs_sm89_64x64x128_w4_s1(
313
+ A, B, D, M, N, K, act_scale, w_scale, stream);
314
+ }
315
+
316
+ } // namespace block128_sm89
317
+ } // namespace gemm
318
+ } // namespace flash_rt
csrc/fp8_block128_gemm_mma_sm89.cuh ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ #pragma once
3
+
4
+ #include <cuda_runtime.h>
5
+
6
+ namespace flash_rt {
7
+ namespace gemm {
8
+ namespace block128_sm89 {
9
+
10
+ // Native Ada (sm_89) FP8 e4m3 -> BF16 block-128 scaled GEMM.
11
+ //
12
+ // Computes D_rm[M,N] = (act_fp8 @ w_fp8^T) with DeepSeek-style block-128
13
+ // scaling applied in the mainloop:
14
+ // D[m,n] = sum_{kb} act_scale[m, kb] * w_scale[n/128, kb]
15
+ // * sum_{k in kb} A[m,k] * B[n,k]
16
+ //
17
+ // Inputs (all device pointers):
18
+ // A : [M, K] FP8 e4m3 row-major (per-token quantized act)
19
+ // B : [N, K] FP8 e4m3 row-major (= W, ckpt weight)
20
+ // act_scale : [M, K/128] fp32 row-major (per-token block scale)
21
+ // w_scale : [N/128, K/128] fp32 row-major (weight_scale_inv)
22
+ // D : [M, N] BF16 row-major
23
+ //
24
+ // Drop-in replacement for fp8_block128_gemm_descale_bf16out but reads the
25
+ // FP8 weight directly (no dequant scratch). K and N must be multiples of 128.
26
+ // Returns 0 on success.
27
+
28
+ #define DECL(NAME) \
29
+ int NAME(const void* A, const void* B, void* D, int M, int N, int K, \
30
+ const float* act_scale, const float* w_scale, cudaStream_t stream)
31
+
32
+ DECL(fp8_block128_gemm_bs_sm89_32x128x128_w4);
33
+ DECL(fp8_block128_gemm_bs_sm89_64x128x128_w4);
34
+ DECL(fp8_block128_gemm_bs_sm89_64x128x128_w8);
35
+ DECL(fp8_block128_gemm_bs_sm89_128x128x128_w4);
36
+ DECL(fp8_block128_gemm_bs_sm89_128x128x128_w8);
37
+ DECL(fp8_block128_gemm_bs_sm89_32x64x128_w4);
38
+ DECL(fp8_block128_gemm_bs_sm89_64x64x128_w4);
39
+ DECL(fp8_block128_gemm_bs_sm89_128x64x128_w4);
40
+ DECL(fp8_block128_gemm_bs_sm89_16x128x128_w4);
41
+ DECL(fp8_block128_gemm_bs_sm89_16x64x128_w4);
42
+ DECL(fp8_block128_gemm_bs_sm89_32x128x128_w4_s1);
43
+ DECL(fp8_block128_gemm_bs_sm89_64x64x128_w4_s1);
44
+ DECL(fp8_block128_gemm_bs_sm89_128x128x128_w8_s1);
45
+
46
+ #undef DECL
47
+
48
+ // Residual-fold tile variants (epilogue adds `resid`): D = bf16(acc) + resid.
49
+ // resid is [M, N] BF16 row-major, same layout as D. Fuses the residual add
50
+ // into the GEMM epilogue (no separate residual_add launch, no D HBM
51
+ // round-trip). Additive — the non-resid kernels above are unchanged.
52
+ #define DECL_RESID(NAME) \
53
+ int NAME(const void* A, const void* B, void* D, int M, int N, int K, \
54
+ const float* act_scale, const float* w_scale, const void* resid, \
55
+ cudaStream_t stream)
56
+
57
+ DECL_RESID(fp8_block128_gemm_bs_sm89_32x64x128_w4_resid);
58
+ DECL_RESID(fp8_block128_gemm_bs_sm89_64x64x128_w4_resid);
59
+ DECL_RESID(fp8_block128_gemm_bs_sm89_64x64x128_w4_s1_resid);
60
+ DECL_RESID(fp8_block128_gemm_bs_sm89_128x128x128_w8_s1_resid);
61
+
62
+ #undef DECL_RESID
63
+
64
+ // GeGLU silu-fold tile variants: fuse gate+up GEMM + silu(gate)*up + per-token
65
+ // block-128 FP8 quant into one launch. B = gate_up_w [2*N, K] (gate rows
66
+ // [0,N), up rows [N,2N)); w_scale = gate_up_s [2*N/128, K/128]. Output FP8
67
+ // [M,N] + scale [M,N/128]. BLOCK_N pinned to 128 (one quant block per CTA).
68
+ #define DECL_GEGLU(NAME) \
69
+ int NAME(const void* A, const void* B, int M, int N, int K, \
70
+ const float* act_scale, const float* w_scale, void* output, \
71
+ float* out_scale, cudaStream_t stream)
72
+
73
+ DECL_GEGLU(fp8_bs_geglu_silu_fold_sm89_32x128_w4_s2);
74
+ DECL_GEGLU(fp8_bs_geglu_silu_fold_sm89_16x128_w4_s2);
75
+ DECL_GEGLU(fp8_bs_geglu_silu_fold_sm89_64x128_w4_s2);
76
+ DECL_GEGLU(fp8_bs_geglu_silu_fold_sm89_128x128_w8_s1);
77
+ DECL_GEGLU(fp8_bs_geglu_silu_fold_sm89_32x128_w4_s1);
78
+ DECL_GEGLU(fp8_bs_geglu_silu_fold_sm89_16x128_w4_s1);
79
+
80
+ #undef DECL_GEGLU
81
+
82
+ // A-persistent interleaved variant (single B smem region, both gate+up acc in
83
+ // registers). Same I/O contract as DECL_GEGLU.
84
+ #define DECL_GEGLU_AP(NAME) \
85
+ int NAME(const void* A, const void* B, int M, int N, int K, \
86
+ const float* act_scale, const float* w_scale, void* output, \
87
+ float* out_scale, cudaStream_t stream)
88
+
89
+ DECL_GEGLU_AP(fp8_bs_geglu_silu_fold_apersist_sm89_32x128_w4_s1);
90
+ DECL_GEGLU_AP(fp8_bs_geglu_silu_fold_apersist_sm89_16x128_w4_s1);
91
+ DECL_GEGLU_AP(fp8_bs_geglu_silu_fold_apersist_sm89_32x128_w4_s2);
92
+
93
+ #undef DECL_GEGLU_AP
94
+
95
+ // Auto-dispatch over the tuned tile set above based on (M, N, K).
96
+ int fp8_block128_gemm_blockscaled_sm89_bf16out(
97
+ const void* A, const void* B, void* D, int M, int N, int K,
98
+ const float* act_scale, const float* w_scale, cudaStream_t stream);
99
+
100
+ } // namespace block128_sm89
101
+ } // namespace gemm
102
+ } // namespace flash_rt
csrc/fp8_bs_gemm_device.cuh ADDED
@@ -0,0 +1,1327 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ #pragma once
3
+
4
+ // Shared device-side implementation of the SM89 FP8 block-128 scaled GEMM
5
+ // kernel. This header is the single source of truth for the kernel body: both
6
+ // the production launcher (fp8_block128_gemm_mma_sm89.cu) and the standalone
7
+ // micro-benchmark (benchmarks/sm89_fp8_block128_gemm) include it, so the
8
+ // bench's `--mode baseline` runs the *exact* production kernel and cannot
9
+ // drift behind it. When experimenting, copy this kernel into the bench's
10
+ // candidate slot and edit there; once an experiment is accepted and folded
11
+ // back here, the bench baseline tracks it automatically.
12
+
13
+ #include <cuda_bf16.h>
14
+ #include <cuda_fp8.h>
15
+ #include <cuda_runtime.h>
16
+ #include <cstdint>
17
+
18
+ namespace flash_rt {
19
+ namespace gemm {
20
+ namespace block128_sm89 {
21
+
22
+ __device__ __forceinline__ void mma_m16n8k32_e4m3(
23
+ float &d0, float &d1, float &d2, float &d3,
24
+ uint32_t a0, uint32_t a1, uint32_t a2, uint32_t a3,
25
+ uint32_t b0, uint32_t b1)
26
+ {
27
+ // Ada (sm_89) FP8 tensor-core op — NO .kind::f8f6f4 qualifier.
28
+ asm volatile(
29
+ "mma.sync.aligned.m16n8k32.row.col.f32.e4m3.e4m3.f32 "
30
+ "{%0, %1, %2, %3}, {%4, %5, %6, %7}, {%8, %9}, {%0, %1, %2, %3};\n"
31
+ : "+f"(d0), "+f"(d1), "+f"(d2), "+f"(d3)
32
+ : "r"(a0), "r"(a1), "r"(a2), "r"(a3), "r"(b0), "r"(b1));
33
+ }
34
+
35
+ __device__ __forceinline__ void cp_async_16(uint32_t smem, const uint8_t* src) {
36
+ int b = (src == nullptr) ? 0 : 16;
37
+ asm volatile("cp.async.ca.shared.global [%0], [%1], 16, %2;\n"
38
+ :: "r"(smem), "l"(src), "r"(b));
39
+ }
40
+
41
+ __device__ __forceinline__ uint32_t to_smem(const void* p) {
42
+ return static_cast<uint32_t>(__cvta_generic_to_shared(p));
43
+ }
44
+
45
+ // True when the adjacent output column pair {c, c+1} is fully in bounds, so a
46
+ // 32-bit bfloat162 store is valid. n_pair_base is even (=...+2*l) and N is a
47
+ // multiple of 128, so &D[row*N + c] is 4-byte aligned for the vector store.
48
+ __device__ __forceinline__ bool col_pair_ok(int c, int N) {
49
+ return c + 1 < N;
50
+ }
51
+
52
+ // ldmatrix.x4: load four 8x8 b16 fragments from smem into 4 registers/lane in
53
+ // one instruction, replacing 4 scalar 32-bit LDS to offload the LSU pipe
54
+ // (NCU on the scalar path: LSU 67.7%, 54.7M shared loads = 27% of all insts).
55
+ __device__ __forceinline__ void ldmatrix_x4_b16(
56
+ uint32_t &d0, uint32_t &d1, uint32_t &d2, uint32_t &d3, uint32_t smem_addr)
57
+ {
58
+ asm volatile(
59
+ "ldmatrix.sync.aligned.x4.m8n8.shared.b16 {%0, %1, %2, %3}, [%4];\n"
60
+ : "=r"(d0), "=r"(d1), "=r"(d2), "=r"(d3)
61
+ : "r"(smem_addr));
62
+ }
63
+
64
+ // SiLU in fp32. Matches quantize::silu_f32 (fp8_per_token_block_quant.cu:416)
65
+ // so the GeGLU silu-fold epilogue reproduces silu_mul_merged's math exactly.
66
+ __device__ __forceinline__ float silu_f32(float x) {
67
+ return x / (1.0f + expf(-x));
68
+ }
69
+
70
+ // BLOCK_K is pinned to 128 (one DeepSeek scale block per K-iteration).
71
+ // - A: [M, K] row-major FP8 e4m3, act_scale [M, K/128] fp32
72
+ // - B: [N, K] row-major FP8 e4m3, w_scale [N/128, K/128] fp32
73
+ // - D: [M, N] row-major BF16
74
+ // - BLOCK_N must keep each warp's 8-wide N-atoms inside one 128 scale block.
75
+ //
76
+ // RESID (opt-in epilogue fold): when true, the BF16 store adds a per-element
77
+ // residual `resid[M, N]` (same BF16 layout as D): D = bf16(acc + resid).
78
+ // This folds what would otherwise be a separate residual_add launch + an
79
+ // extra D round-trip through HBM, mirroring #134's residual-fold epilogue.
80
+ // When RESID=false, `resid` is unused and the `if constexpr (RESID)` branch
81
+ // is dead-stripped at compile time, so the baseline kernel is byte-identical.
82
+ template <int BLOCK_M, int BLOCK_N, int NUM_WARPS, int STAGES,
83
+ int MIN_BLOCKS_PER_SM, bool RESID = false>
84
+ __global__ __launch_bounds__(NUM_WARPS * 32, MIN_BLOCKS_PER_SM)
85
+ void fp8_bs_gemm_kernel(
86
+ const __nv_fp8_e4m3* __restrict__ A,
87
+ const __nv_fp8_e4m3* __restrict__ B,
88
+ const float* __restrict__ act_scale, // [M, K/128]
89
+ const float* __restrict__ w_scale, // [N/128, K/128]
90
+ __nv_bfloat16* __restrict__ D,
91
+ int M, int N, int K,
92
+ const __nv_bfloat16* __restrict__ resid = nullptr) // [M, N] BF16, used iff RESID
93
+ {
94
+ constexpr int BLOCK_K = 128;
95
+ constexpr int THREADS = NUM_WARPS * 32;
96
+ constexpr int M_ATOMS = BLOCK_M / 16;
97
+ constexpr int N_ATOMS = BLOCK_N / 8;
98
+ constexpr int N_ATOMS_PW = N_ATOMS / NUM_WARPS;
99
+ constexpr int N_PAIRS_PW = N_ATOMS_PW / 2; // ldmatrix pairs 2 N-atoms
100
+ constexpr int K_ATOMS = BLOCK_K / 32; // = 4
101
+ constexpr int NUM_CHUNKS_PER_ROW = BLOCK_K / 16; // 8 chunks of 16 bytes
102
+ // 128B swizzle: chunk_sw = chunk ^ (row & SWIZZLE_MASK). Removes the old
103
+ // SMEM_K_PAD and the bank conflicts; applied identically on cp.async store
104
+ // and ldmatrix load so the round-trip is bit-exact.
105
+ constexpr int SWIZZLE_MASK = NUM_CHUNKS_PER_ROW - 1; // = 7
106
+
107
+ static_assert(BLOCK_M % 16 == 0, "BLOCK_M multiple of 16");
108
+ static_assert(BLOCK_N % 8 == 0, "BLOCK_N multiple of 8");
109
+ static_assert(BLOCK_N <= 128, "one CTA must fit one N scale block");
110
+ static_assert((BLOCK_N / 8) % NUM_WARPS == 0, "N-atoms split across warps");
111
+ static_assert(N_ATOMS_PW >= 2 && N_ATOMS_PW % 2 == 0,
112
+ "ldmatrix pairs 2 N-atoms: N_ATOMS_PW must be even >= 2");
113
+
114
+ // Stage the per-CTA activation/weight scales in shared memory with a
115
+ // coalesced load, so the per-k_iter scale fold reads smem instead of
116
+ // row-strided scalar global loads (NCU's top global-load bottleneck).
117
+ // Only SCALE_KTILE scale-block columns are staged at a time, re-staged on
118
+ // each k-tile boundary, so the smem footprint is K-independent (~2 KB) and
119
+ // occupancy does not regress on large-K shapes (e.g. down, K128=96).
120
+ constexpr int SCALE_KTILE = 8;
121
+ constexpr int A_TILE = BLOCK_M * BLOCK_K; // swizzled, no pad
122
+ constexpr int B_TILE = BLOCK_N * BLOCK_K;
123
+
124
+ extern __shared__ uint8_t smem_raw[];
125
+ uint8_t* A_smem = smem_raw;
126
+ uint8_t* B_smem = A_smem + STAGES * A_TILE;
127
+ float* as_smem = reinterpret_cast<float*>(B_smem + STAGES * B_TILE);
128
+ float* ws_smem = as_smem + BLOCK_M * SCALE_KTILE;
129
+
130
+ const int cta_m = blockIdx.x;
131
+ const int cta_n = blockIdx.y;
132
+ const int m_base = cta_m * BLOCK_M;
133
+ const int n_base = cta_n * BLOCK_N;
134
+
135
+ const int t = threadIdx.x;
136
+ const int warp_id = t / 32;
137
+ const int lane = t % 32;
138
+ const int l = lane % 4;
139
+ const int h = lane / 4;
140
+ // ldmatrix.x4 lane -> fragment partition.
141
+ const int frag_group = lane / 8; // 0..3 (TL,TR,BL,BR)
142
+ const int row_in_frag = lane % 8; // row within an 8x8 fragment
143
+ const int row_block = frag_group / 2; // top(0)/bottom(1) 8 rows
144
+ const int col_block = frag_group % 2; // left(0)/right(1) 16-byte chunk
145
+
146
+ const int K128 = K >> 7; // # scale blocks along K
147
+
148
+ // Coalesced staging of one SCALE_KTILE-wide scale block into smem.
149
+ auto stage_scales = [&](int kb0) {
150
+ const int as_total = BLOCK_M * SCALE_KTILE;
151
+ for (int idx = t; idx < as_total; idx += THREADS) {
152
+ int r = idx / SCALE_KTILE;
153
+ int kc = idx - r * SCALE_KTILE;
154
+ int row = m_base + r;
155
+ int kb = kb0 + kc;
156
+ as_smem[idx] = (row < M && kb < K128)
157
+ ? act_scale[(size_t)row * K128 + kb] : 0.0f;
158
+ }
159
+ for (int kc = t; kc < SCALE_KTILE; kc += THREADS) {
160
+ int kb = kb0 + kc;
161
+ ws_smem[kc] = (kb < K128)
162
+ ? w_scale[(size_t)(n_base >> 7) * K128 + kb] : 0.0f;
163
+ }
164
+ __syncthreads();
165
+ };
166
+
167
+ auto issue_load = [&](int stage, int k_base) {
168
+ constexpr int A_CHUNKS = BLOCK_M * NUM_CHUNKS_PER_ROW;
169
+ constexpr int A_ITERS = (A_CHUNKS + THREADS - 1) / THREADS;
170
+ #pragma unroll
171
+ for (int it = 0; it < A_ITERS; ++it) {
172
+ int idx = it * THREADS + t;
173
+ if (idx >= A_CHUNKS) break;
174
+ int row_a = idx / NUM_CHUNKS_PER_ROW;
175
+ int chunk_a = idx % NUM_CHUNKS_PER_ROW;
176
+ int m_glob = m_base + row_a;
177
+ int k_glob = k_base + chunk_a * 16;
178
+ const uint8_t* a_src = nullptr;
179
+ if (m_glob < M && k_glob < K) {
180
+ a_src = reinterpret_cast<const uint8_t*>(&A[(size_t)m_glob * K + k_glob]);
181
+ }
182
+ int csw = chunk_a ^ (row_a & SWIZZLE_MASK);
183
+ cp_async_16(
184
+ to_smem(&A_smem[stage * A_TILE + row_a * BLOCK_K + csw * 16]),
185
+ a_src);
186
+ }
187
+ constexpr int B_CHUNKS = BLOCK_N * NUM_CHUNKS_PER_ROW;
188
+ constexpr int B_ITERS = (B_CHUNKS + THREADS - 1) / THREADS;
189
+ #pragma unroll
190
+ for (int it = 0; it < B_ITERS; ++it) {
191
+ int idx = it * THREADS + t;
192
+ if (idx >= B_CHUNKS) break;
193
+ int row_b = idx / NUM_CHUNKS_PER_ROW;
194
+ int chunk_b = idx % NUM_CHUNKS_PER_ROW;
195
+ int n_glob = n_base + row_b;
196
+ int k_glob = k_base + chunk_b * 16;
197
+ const uint8_t* b_src = nullptr;
198
+ if (n_glob < N && k_glob < K) {
199
+ b_src = reinterpret_cast<const uint8_t*>(&B[(size_t)n_glob * K + k_glob]);
200
+ }
201
+ int csw = chunk_b ^ (row_b & SWIZZLE_MASK);
202
+ cp_async_16(
203
+ to_smem(&B_smem[stage * B_TILE + row_b * BLOCK_K + csw * 16]),
204
+ b_src);
205
+ }
206
+ };
207
+
208
+ // Running (scaled) accumulators across all K-blocks.
209
+ float acc[M_ATOMS][N_ATOMS_PW][4];
210
+ #pragma unroll
211
+ for (int mi = 0; mi < M_ATOMS; ++mi)
212
+ #pragma unroll
213
+ for (int ni = 0; ni < N_ATOMS_PW; ++ni)
214
+ #pragma unroll
215
+ for (int j = 0; j < 4; ++j) acc[mi][ni][j] = 0.0f;
216
+
217
+ const int K_ITERS = (K + BLOCK_K - 1) / BLOCK_K;
218
+ #pragma unroll
219
+ for (int s = 0; s < STAGES - 1; ++s) {
220
+ int kb = s * BLOCK_K;
221
+ if (kb < K) issue_load(s, kb);
222
+ asm volatile("cp.async.commit_group;\n" ::);
223
+ }
224
+
225
+ int compute_stage = 0;
226
+ for (int k_iter = 0; k_iter < K_ITERS; ++k_iter) {
227
+ int issue_iter = k_iter + (STAGES - 1);
228
+ int issue_stage = issue_iter % STAGES;
229
+ if (issue_iter < K_ITERS) issue_load(issue_stage, issue_iter * BLOCK_K);
230
+ asm volatile("cp.async.commit_group;\n" ::);
231
+ asm volatile("cp.async.wait_group %0;\n" :: "n"(STAGES - 1));
232
+ __syncthreads();
233
+
234
+ // This k_iter is exactly one scale block (kb = k_iter).
235
+ const int kb = k_iter;
236
+ // Re-stage the next SCALE_KTILE-wide scale block on each tile boundary.
237
+ if ((kb % SCALE_KTILE) == 0) stage_scales(kb);
238
+ // w_scale is constant across this CTA's BLOCK_N if it fits one
239
+ // 128 block; index per warp's N base to stay correct for BLOCK_N>128.
240
+ float tacc[M_ATOMS][N_ATOMS_PW][4];
241
+ #pragma unroll
242
+ for (int mi = 0; mi < M_ATOMS; ++mi)
243
+ #pragma unroll
244
+ for (int ni = 0; ni < N_ATOMS_PW; ++ni)
245
+ #pragma unroll
246
+ for (int j = 0; j < 4; ++j) tacc[mi][ni][j] = 0.0f;
247
+
248
+ uint8_t* A_stage = A_smem + compute_stage * A_TILE;
249
+ uint8_t* B_stage = B_smem + compute_stage * B_TILE;
250
+ #pragma unroll
251
+ for (int ka = 0; ka < K_ATOMS; ++ka) {
252
+ // ldmatrix.x4 loads the m16xk32 A fragment (4 regs/lane) per m-atom.
253
+ uint32_t A_regs[M_ATOMS][4];
254
+ #pragma unroll
255
+ for (int mi = 0; mi < M_ATOMS; ++mi) {
256
+ int row = mi * 16 + row_block * 8 + row_in_frag;
257
+ int chunk = 2 * ka + col_block;
258
+ int csw = chunk ^ (row & SWIZZLE_MASK);
259
+ ldmatrix_x4_b16(A_regs[mi][0], A_regs[mi][1], A_regs[mi][2], A_regs[mi][3],
260
+ to_smem(&A_stage[row * BLOCK_K + csw * 16]));
261
+ }
262
+ // ldmatrix.x4 loads two N-atoms (n16xk32) per pair.
263
+ uint32_t B_regs[N_PAIRS_PW][4];
264
+ #pragma unroll
265
+ for (int np = 0; np < N_PAIRS_PW; ++np) {
266
+ int nrow = warp_id * N_ATOMS_PW * 8 + np * 16 + row_block * 8 + row_in_frag;
267
+ int chunk = 2 * ka + col_block;
268
+ int csw = chunk ^ (nrow & SWIZZLE_MASK);
269
+ ldmatrix_x4_b16(B_regs[np][0], B_regs[np][1], B_regs[np][2], B_regs[np][3],
270
+ to_smem(&B_stage[nrow * BLOCK_K + csw * 16]));
271
+ }
272
+ #pragma unroll
273
+ for (int mi = 0; mi < M_ATOMS; ++mi) {
274
+ #pragma unroll
275
+ for (int np = 0; np < N_PAIRS_PW; ++np) {
276
+ int ni0 = np * 2, ni1 = np * 2 + 1;
277
+ // ldm fragment -> mma A operand: a0=d0,a1=d2,a2=d1,a3=d3.
278
+ mma_m16n8k32_e4m3(
279
+ tacc[mi][ni0][0], tacc[mi][ni0][1], tacc[mi][ni0][2], tacc[mi][ni0][3],
280
+ A_regs[mi][0], A_regs[mi][2], A_regs[mi][1], A_regs[mi][3],
281
+ B_regs[np][0], B_regs[np][1]);
282
+ mma_m16n8k32_e4m3(
283
+ tacc[mi][ni1][0], tacc[mi][ni1][1], tacc[mi][ni1][2], tacc[mi][ni1][3],
284
+ A_regs[mi][0], A_regs[mi][2], A_regs[mi][1], A_regs[mi][3],
285
+ B_regs[np][2], B_regs[np][3]);
286
+ }
287
+ }
288
+ }
289
+
290
+ // Fold block scales: D += act_scale[row,kb] * w_scale[ncol/128,kb] * tacc
291
+ // Scales come from the smem stage (coalesced load above), indexed by
292
+ // the column within the current SCALE_KTILE tile. BLOCK_N <= 128 keeps
293
+ // the CTA inside one 128-column weight-scale block.
294
+ int kbt = kb % SCALE_KTILE;
295
+ float ws_cta = ws_smem[kbt];
296
+ #pragma unroll
297
+ for (int mi = 0; mi < M_ATOMS; ++mi) {
298
+ int row0 = m_base + mi * 16 + h;
299
+ int row1 = row0 + 8;
300
+ float as0 = as_smem[(mi * 16 + h) * SCALE_KTILE + kbt];
301
+ float as1 = as_smem[(mi * 16 + h + 8) * SCALE_KTILE + kbt];
302
+ #pragma unroll
303
+ for (int ni = 0; ni < N_ATOMS_PW; ++ni) {
304
+ acc[mi][ni][0] += tacc[mi][ni][0] * (as0 * ws_cta);
305
+ acc[mi][ni][1] += tacc[mi][ni][1] * (as0 * ws_cta);
306
+ acc[mi][ni][2] += tacc[mi][ni][2] * (as1 * ws_cta);
307
+ acc[mi][ni][3] += tacc[mi][ni][3] * (as1 * ws_cta);
308
+ }
309
+ }
310
+ // Do not let the next cp.async overwrite this shared-memory stage
311
+ // before all warps finish reading it.
312
+ __syncthreads();
313
+ compute_stage = (compute_stage + 1) % STAGES;
314
+ }
315
+ asm volatile("cp.async.wait_all;\n" ::);
316
+
317
+ // Epilogue: write BF16. m16n8 layout: thread (h,l) -> rows {h,h+8},
318
+ // cols {2*l, 2*l+1}.
319
+ #pragma unroll
320
+ for (int mi = 0; mi < M_ATOMS; ++mi) {
321
+ int row0 = m_base + mi * 16 + h;
322
+ int row1 = row0 + 8;
323
+ #pragma unroll
324
+ for (int ni = 0; ni < N_ATOMS_PW; ++ni) {
325
+ int n_pair_base = n_base + warp_id * N_ATOMS_PW * 8 + ni * 8 + 2 * l;
326
+ // acc[0,1] = row0 cols {2l,2l+1}; acc[2,3] = row1 cols {2l,2l+1}.
327
+ // RESID epilogue fold: add the BF16 residual in-register before the
328
+ // bf16 store, so the residual read is fused into the GEMM epilogue
329
+ // and never lands as a separate D HBM round-trip + launch.
330
+ if constexpr (RESID) {
331
+ if (row0 < M && col_pair_ok(n_pair_base, N)) {
332
+ __nv_bfloat162 r = *reinterpret_cast<const __nv_bfloat162*>(
333
+ &resid[(size_t)row0 * N + n_pair_base]);
334
+ *reinterpret_cast<__nv_bfloat162*>(&D[(size_t)row0 * N + n_pair_base]) =
335
+ __floats2bfloat162_rn(acc[mi][ni][0] + __low2float(r),
336
+ acc[mi][ni][1] + __high2float(r));
337
+ } else if (row0 < M) {
338
+ if (n_pair_base < N)
339
+ D[(size_t)row0 * N + n_pair_base] = __float2bfloat16(
340
+ acc[mi][ni][0] + __bfloat162float(resid[(size_t)row0 * N + n_pair_base]));
341
+ if (n_pair_base + 1 < N)
342
+ D[(size_t)row0 * N + n_pair_base + 1] = __float2bfloat16(
343
+ acc[mi][ni][1] + __bfloat162float(resid[(size_t)row0 * N + n_pair_base + 1]));
344
+ }
345
+ if (row1 < M && col_pair_ok(n_pair_base, N)) {
346
+ __nv_bfloat162 r = *reinterpret_cast<const __nv_bfloat162*>(
347
+ &resid[(size_t)row1 * N + n_pair_base]);
348
+ *reinterpret_cast<__nv_bfloat162*>(&D[(size_t)row1 * N + n_pair_base]) =
349
+ __floats2bfloat162_rn(acc[mi][ni][2] + __low2float(r),
350
+ acc[mi][ni][3] + __high2float(r));
351
+ } else if (row1 < M) {
352
+ if (n_pair_base < N)
353
+ D[(size_t)row1 * N + n_pair_base] = __float2bfloat16(
354
+ acc[mi][ni][2] + __bfloat162float(resid[(size_t)row1 * N + n_pair_base]));
355
+ if (n_pair_base + 1 < N)
356
+ D[(size_t)row1 * N + n_pair_base + 1] = __float2bfloat16(
357
+ acc[mi][ni][3] + __bfloat162float(resid[(size_t)row1 * N + n_pair_base + 1]));
358
+ }
359
+ } else {
360
+ // Emit one 32-bit bfloat162 store per row instead of two scalar
361
+ // 16-bit stores (NCU's top store-pattern bottleneck after C1).
362
+ // Tail (odd last column) falls back to scalar stores.
363
+ if (row0 < M && col_pair_ok(n_pair_base, N)) {
364
+ *reinterpret_cast<__nv_bfloat162*>(&D[(size_t)row0 * N + n_pair_base]) =
365
+ __floats2bfloat162_rn(acc[mi][ni][0], acc[mi][ni][1]);
366
+ } else if (row0 < M) {
367
+ if (n_pair_base < N) D[(size_t)row0 * N + n_pair_base] = __float2bfloat16(acc[mi][ni][0]);
368
+ if (n_pair_base + 1 < N) D[(size_t)row0 * N + n_pair_base+1] = __float2bfloat16(acc[mi][ni][1]);
369
+ }
370
+ if (row1 < M && col_pair_ok(n_pair_base, N)) {
371
+ *reinterpret_cast<__nv_bfloat162*>(&D[(size_t)row1 * N + n_pair_base]) =
372
+ __floats2bfloat162_rn(acc[mi][ni][2], acc[mi][ni][3]);
373
+ } else if (row1 < M) {
374
+ if (n_pair_base < N) D[(size_t)row1 * N + n_pair_base] = __float2bfloat16(acc[mi][ni][2]);
375
+ if (n_pair_base + 1 < N) D[(size_t)row1 * N + n_pair_base+1] = __float2bfloat16(acc[mi][ni][3]);
376
+ }
377
+ }
378
+ }
379
+ }
380
+ }
381
+
382
+ // ============================================================================
383
+ // GeGLU silu-fold megakernel (Phase 2): fuses gate GEMM + up GEMM +
384
+ // silu(gate)*up + per-token block-128 FP8 quant into ONE launch, writing FP8
385
+ // output + scale directly — eliminating the [M, 2*N] BF16 transient that the
386
+ // baseline gate_up GEMM would write and silu_mul_merged_to_fp8 would read back.
387
+ //
388
+ // gate_up_w : [2*N, K] FP8 row-major (gate rows [0,N); up rows [N,2N))
389
+ // gate_up_s : [2*N/128, K/128] fp32 (up row = gate row + N/128)
390
+ // A : [M, K] FP8 (per-token quantized), act_scale [M, K/128]
391
+ // output : [M, N] FP8, scale [M, N/128]
392
+ //
393
+ // Two-pass per CTA (mirrors sm100 flashrt_megakernel_geglu's "gate stays in
394
+ // smem"): pass 1 accumulates gate over full K and stores silu(gate) as BF16
395
+ // into a smem gate buffer; pass 2 reuses the same A/B smem staging, accumulates
396
+ // up, then the epilogue reads gate from smem, forms v = bf16(bf16(silu(gate))*up)
397
+ // (matching silu_mul_merged's two bf16 roundings), reduces |v| over the 128-col
398
+ // quant block per row, and quantizes to FP8. No grid_barrier (single CTA owns
399
+ // its full quant block: BLOCK_N == 128 == one scale block). GEMM body reuses
400
+ // the same cp.async + ldmatrix.x4 + mma.m16n8k32 tiles as fp8_bs_gemm_kernel.
401
+ // ============================================================================
402
+ template <int BLOCK_M, int BLOCK_N, int NUM_WARPS, int STAGES,
403
+ int MIN_BLOCKS_PER_SM>
404
+ __global__ __launch_bounds__(NUM_WARPS * 32, MIN_BLOCKS_PER_SM)
405
+ void fp8_bs_geglu_silu_fold_kernel(
406
+ const __nv_fp8_e4m3* __restrict__ A,
407
+ const __nv_fp8_e4m3* __restrict__ B, // gate_up_w [2*N, K]
408
+ const float* __restrict__ act_scale, // [M, K/128]
409
+ const float* __restrict__ w_scale, // gate_up_s [2*N/128, K/128]
410
+ __nv_fp8_e4m3* __restrict__ output, // [M, N]
411
+ float* __restrict__ out_scale, // [M, N/128]
412
+ int M, int N, int K)
413
+ {
414
+ static_assert(BLOCK_N == 128,
415
+ "GeGLU silu-fold requires BLOCK_N==128 (one quant block per CTA)");
416
+ constexpr int BLOCK_K = 128;
417
+ constexpr int THREADS = NUM_WARPS * 32;
418
+ constexpr int M_ATOMS = BLOCK_M / 16;
419
+ constexpr int N_ATOMS = BLOCK_N / 8; // 16
420
+ constexpr int N_ATOMS_PW = N_ATOMS / NUM_WARPS;
421
+ constexpr int N_PAIRS_PW = N_ATOMS_PW / 2;
422
+ constexpr int K_ATOMS = BLOCK_K / 32; // 4
423
+ constexpr int NUM_CHUNKS_PER_ROW = BLOCK_K / 16;
424
+ constexpr int SWIZZLE_MASK = NUM_CHUNKS_PER_ROW - 1;
425
+ constexpr int SCALE_KTILE = 8;
426
+ constexpr int A_TILE = BLOCK_M * BLOCK_K;
427
+ constexpr int B_TILE = BLOCK_N * BLOCK_K;
428
+
429
+ static_assert(BLOCK_M % 16 == 0, "BLOCK_M multiple of 16");
430
+ static_assert(N_ATOMS_PW >= 2 && N_ATOMS_PW % 2 == 0,
431
+ "ldmatrix pairs 2 N-atoms: N_ATOMS_PW must be even >= 2");
432
+
433
+ extern __shared__ uint8_t smem_raw[];
434
+ uint8_t* A_smem = smem_raw;
435
+ uint8_t* B_smem = A_smem + STAGES * A_TILE;
436
+ // gate_smem: silu(gate) as BF16, [BLOCK_M, BLOCK_N]. One CTA-tile, written
437
+ // by pass 1 epilogue, read by pass 2 epilogue. The sm100 geglu's "gate
438
+ // stays in smem" handoff, without tcgen05/EVT.
439
+ __nv_bfloat16* gate_smem = reinterpret_cast<__nv_bfloat16*>(
440
+ B_smem + STAGES * B_TILE);
441
+ float* as_smem = reinterpret_cast<float*>(gate_smem + BLOCK_M * BLOCK_N);
442
+ float* wsg_smem = as_smem + BLOCK_M * SCALE_KTILE; // gate w_scale row
443
+ float* wsu_smem = wsg_smem + SCALE_KTILE; // up w_scale row
444
+ // amax partials: 4 warps × BLOCK_M rows. Cross-warp reduce per row.
445
+ float* amax_smem = wsu_smem + SCALE_KTILE;
446
+
447
+ const int cta_m = blockIdx.x;
448
+ const int cta_n = blockIdx.y;
449
+ const int m_base = cta_m * BLOCK_M;
450
+ const int n_base = cta_n * BLOCK_N; // n0, < N (output col block)
451
+
452
+ const int t = threadIdx.x;
453
+ const int warp_id = t / 32;
454
+ const int lane = t % 32;
455
+ const int l = lane % 4;
456
+ const int h = lane / 4;
457
+ const int frag_group = lane / 8;
458
+ const int row_in_frag = lane % 8;
459
+ const int row_block = frag_group / 2;
460
+ const int col_block = frag_group % 2;
461
+
462
+ const int K128 = K >> 7;
463
+ const int N128 = N >> 7; // gate w_scale blocks
464
+ // gate B-rows [n_base, n_base+BLOCK_N); up B-rows [n_base+N, n_base+N+BLOCK_N)
465
+ const int gate_b_row0 = n_base;
466
+ const int up_b_row0 = n_base + N;
467
+ const int gate_ws_row = (n_base >> 7); // gate w_scale block row
468
+ const int up_ws_row = gate_ws_row + N128; // up w_scale block row
469
+
470
+ // ---- scale staging (shared by both passes; re-staged per SCALE_KTILE) ----
471
+ auto stage_scales = [&](int kb0) {
472
+ const int as_total = BLOCK_M * SCALE_KTILE;
473
+ for (int idx = t; idx < as_total; idx += THREADS) {
474
+ int r = idx / SCALE_KTILE;
475
+ int kc = idx - r * SCALE_KTILE;
476
+ int row = m_base + r;
477
+ int kb = kb0 + kc;
478
+ as_smem[idx] = (row < M && kb < K128)
479
+ ? act_scale[(size_t)row * K128 + kb] : 0.0f;
480
+ }
481
+ for (int kc = t; kc < SCALE_KTILE; kc += THREADS) {
482
+ int kb = kb0 + kc;
483
+ wsg_smem[kc] = (kb < K128)
484
+ ? w_scale[(size_t)gate_ws_row * K128 + kb] : 0.0f;
485
+ wsu_smem[kc] = (kb < K128)
486
+ ? w_scale[(size_t)up_ws_row * K128 + kb] : 0.0f;
487
+ }
488
+ __syncthreads();
489
+ };
490
+
491
+ // ---- cp.async A + (gate or up) B tile staging ----
492
+ // b_row0 selects which 128-row band of B [2*N, K] to stage.
493
+ auto issue_load = [&](int stage, int k_base, int b_row0) {
494
+ constexpr int A_CHUNKS = BLOCK_M * NUM_CHUNKS_PER_ROW;
495
+ constexpr int A_ITERS = (A_CHUNKS + THREADS - 1) / THREADS;
496
+ #pragma unroll
497
+ for (int it = 0; it < A_ITERS; ++it) {
498
+ int idx = it * THREADS + t;
499
+ if (idx >= A_CHUNKS) break;
500
+ int row_a = idx / NUM_CHUNKS_PER_ROW;
501
+ int chunk_a = idx % NUM_CHUNKS_PER_ROW;
502
+ int m_glob = m_base + row_a;
503
+ int k_glob = k_base + chunk_a * 16;
504
+ const uint8_t* a_src = nullptr;
505
+ if (m_glob < M && k_glob < K) {
506
+ a_src = reinterpret_cast<const uint8_t*>(&A[(size_t)m_glob * K + k_glob]);
507
+ }
508
+ int csw = chunk_a ^ (row_a & SWIZZLE_MASK);
509
+ cp_async_16(
510
+ to_smem(&A_smem[stage * A_TILE + row_a * BLOCK_K + csw * 16]),
511
+ a_src);
512
+ }
513
+ constexpr int B_CHUNKS = BLOCK_N * NUM_CHUNKS_PER_ROW;
514
+ constexpr int B_ITERS = (B_CHUNKS + THREADS - 1) / THREADS;
515
+ #pragma unroll
516
+ for (int it = 0; it < B_ITERS; ++it) {
517
+ int idx = it * THREADS + t;
518
+ if (idx >= B_CHUNKS) break;
519
+ int row_b = idx / NUM_CHUNKS_PER_ROW;
520
+ int chunk_b = idx % NUM_CHUNKS_PER_ROW;
521
+ int n_glob = b_row0 + row_b;
522
+ int k_glob = k_base + chunk_b * 16;
523
+ const uint8_t* b_src = nullptr;
524
+ if (n_glob < 2 * N && k_glob < K) {
525
+ b_src = reinterpret_cast<const uint8_t*>(&B[(size_t)n_glob * K + k_glob]);
526
+ }
527
+ int csw = chunk_b ^ (row_b & SWIZZLE_MASK);
528
+ cp_async_16(
529
+ to_smem(&B_smem[stage * B_TILE + row_b * BLOCK_K + csw * 16]),
530
+ b_src);
531
+ }
532
+ };
533
+
534
+ // ---- one GEMM pass over full K, accumulating into `acc` with the given
535
+ // w_scale smem row (gate or up). b_row0 selects the B band. ----
536
+ auto run_pass = [&](float (*acc)[N_ATOMS_PW][4], int b_row0,
537
+ const float* ws_smem_pass) {
538
+ const int K_ITERS = (K + BLOCK_K - 1) / BLOCK_K;
539
+ #pragma unroll
540
+ for (int s = 0; s < STAGES - 1; ++s) {
541
+ int kb = s * BLOCK_K;
542
+ if (kb < K) issue_load(s, kb, b_row0);
543
+ asm volatile("cp.async.commit_group;\n" ::);
544
+ }
545
+ int compute_stage = 0;
546
+ for (int k_iter = 0; k_iter < K_ITERS; ++k_iter) {
547
+ int issue_iter = k_iter + (STAGES - 1);
548
+ int issue_stage = issue_iter % STAGES;
549
+ if (issue_iter < K_ITERS) issue_load(issue_stage, issue_iter * BLOCK_K, b_row0);
550
+ asm volatile("cp.async.commit_group;\n" ::);
551
+ asm volatile("cp.async.wait_group %0;\n" :: "n"(STAGES - 1));
552
+ __syncthreads();
553
+
554
+ const int kb = k_iter;
555
+ if ((kb % SCALE_KTILE) == 0) stage_scales(kb);
556
+
557
+ float tacc[M_ATOMS][N_ATOMS_PW][4];
558
+ #pragma unroll
559
+ for (int mi = 0; mi < M_ATOMS; ++mi)
560
+ #pragma unroll
561
+ for (int ni = 0; ni < N_ATOMS_PW; ++ni)
562
+ #pragma unroll
563
+ for (int j = 0; j < 4; ++j) tacc[mi][ni][j] = 0.0f;
564
+
565
+ uint8_t* A_stage = A_smem + compute_stage * A_TILE;
566
+ uint8_t* B_stage = B_smem + compute_stage * B_TILE;
567
+ #pragma unroll
568
+ for (int ka = 0; ka < K_ATOMS; ++ka) {
569
+ uint32_t A_regs[M_ATOMS][4];
570
+ #pragma unroll
571
+ for (int mi = 0; mi < M_ATOMS; ++mi) {
572
+ int row = mi * 16 + row_block * 8 + row_in_frag;
573
+ int chunk = 2 * ka + col_block;
574
+ int csw = chunk ^ (row & SWIZZLE_MASK);
575
+ ldmatrix_x4_b16(A_regs[mi][0], A_regs[mi][1], A_regs[mi][2], A_regs[mi][3],
576
+ to_smem(&A_stage[row * BLOCK_K + csw * 16]));
577
+ }
578
+ uint32_t B_regs[N_PAIRS_PW][4];
579
+ #pragma unroll
580
+ for (int np = 0; np < N_PAIRS_PW; ++np) {
581
+ int nrow = warp_id * N_ATOMS_PW * 8 + np * 16 + row_block * 8 + row_in_frag;
582
+ int chunk = 2 * ka + col_block;
583
+ int csw = chunk ^ (nrow & SWIZZLE_MASK);
584
+ ldmatrix_x4_b16(B_regs[np][0], B_regs[np][1], B_regs[np][2], B_regs[np][3],
585
+ to_smem(&B_stage[nrow * BLOCK_K + csw * 16]));
586
+ }
587
+ #pragma unroll
588
+ for (int mi = 0; mi < M_ATOMS; ++mi) {
589
+ #pragma unroll
590
+ for (int np = 0; np < N_PAIRS_PW; ++np) {
591
+ int ni0 = np * 2, ni1 = np * 2 + 1;
592
+ mma_m16n8k32_e4m3(
593
+ tacc[mi][ni0][0], tacc[mi][ni0][1], tacc[mi][ni0][2], tacc[mi][ni0][3],
594
+ A_regs[mi][0], A_regs[mi][2], A_regs[mi][1], A_regs[mi][3],
595
+ B_regs[np][0], B_regs[np][1]);
596
+ mma_m16n8k32_e4m3(
597
+ tacc[mi][ni1][0], tacc[mi][ni1][1], tacc[mi][ni1][2], tacc[mi][ni1][3],
598
+ A_regs[mi][0], A_regs[mi][2], A_regs[mi][1], A_regs[mi][3],
599
+ B_regs[np][2], B_regs[np][3]);
600
+ }
601
+ }
602
+ }
603
+
604
+ int kbt = kb % SCALE_KTILE;
605
+ float ws_cta = ws_smem_pass[kbt];
606
+ #pragma unroll
607
+ for (int mi = 0; mi < M_ATOMS; ++mi) {
608
+ int row0 = m_base + mi * 16 + h;
609
+ int row1 = row0 + 8;
610
+ float as0 = as_smem[(mi * 16 + h) * SCALE_KTILE + kbt];
611
+ float as1 = as_smem[(mi * 16 + h + 8) * SCALE_KTILE + kbt];
612
+ #pragma unroll
613
+ for (int ni = 0; ni < N_ATOMS_PW; ++ni) {
614
+ acc[mi][ni][0] += tacc[mi][ni][0] * (as0 * ws_cta);
615
+ acc[mi][ni][1] += tacc[mi][ni][1] * (as0 * ws_cta);
616
+ acc[mi][ni][2] += tacc[mi][ni][2] * (as1 * ws_cta);
617
+ acc[mi][ni][3] += tacc[mi][ni][3] * (as1 * ws_cta);
618
+ }
619
+ }
620
+ __syncthreads();
621
+ compute_stage = (compute_stage + 1) % STAGES;
622
+ }
623
+ asm volatile("cp.async.wait_all;\n" ::);
624
+ };
625
+
626
+ // =================== Pass 1: gate GEMM → silu(gate) in smem ===================
627
+ float gate_acc[M_ATOMS][N_ATOMS_PW][4];
628
+ #pragma unroll
629
+ for (int mi = 0; mi < M_ATOMS; ++mi)
630
+ #pragma unroll
631
+ for (int ni = 0; ni < N_ATOMS_PW; ++ni)
632
+ #pragma unroll
633
+ for (int j = 0; j < 4; ++j) gate_acc[mi][ni][j] = 0.0f;
634
+
635
+ run_pass(gate_acc, gate_b_row0, wsg_smem);
636
+
637
+ // Pass 1 epilogue: store silu(gate_acc) as BF16 into gate_smem[BM, BN].
638
+ // Thread (h,l) owns rows {mi*16+h, mi*16+h+8}, cols {ni*8+2l, ni*8+2l+1}
639
+ // within its warp's N band. Replicate silu_mul_merged's first bf16 rounding
640
+ // (bf16(silu(g))) so the fused path matches the split kernel's precision.
641
+ #pragma unroll
642
+ for (int mi = 0; mi < M_ATOMS; ++mi) {
643
+ int row0 = m_base + mi * 16 + h;
644
+ int row1 = row0 + 8;
645
+ #pragma unroll
646
+ for (int ni = 0; ni < N_ATOMS_PW; ++ni) {
647
+ int n_pair_base = warp_id * N_ATOMS_PW * 8 + ni * 8 + 2 * l;
648
+ // gate_smem is [BLOCK_M, BLOCK_N]; local col = n_pair_base.
649
+ if (row0 < M) {
650
+ __nv_bfloat162 gs = __floats2bfloat162_rn(
651
+ silu_f32(gate_acc[mi][ni][0]), silu_f32(gate_acc[mi][ni][1]));
652
+ *reinterpret_cast<__nv_bfloat162*>(
653
+ &gate_smem[(row0 - m_base) * BLOCK_N + n_pair_base]) = gs;
654
+ __nv_bfloat162 gs2 = __floats2bfloat162_rn(
655
+ silu_f32(gate_acc[mi][ni][2]), silu_f32(gate_acc[mi][ni][3]));
656
+ *reinterpret_cast<__nv_bfloat162*>(
657
+ &gate_smem[(row1 - m_base) * BLOCK_N + n_pair_base]) = gs2;
658
+ }
659
+ }
660
+ }
661
+ __syncthreads(); // gate_smem visible to pass 2 epilogue in all warps
662
+ // gate_acc registers now free; reused for up_acc.
663
+
664
+ // =================== Pass 2: up GEMM → up_acc ===================
665
+ float up_acc[M_ATOMS][N_ATOMS_PW][4];
666
+ #pragma unroll
667
+ for (int mi = 0; mi < M_ATOMS; ++mi)
668
+ #pragma unroll
669
+ for (int ni = 0; ni < N_ATOMS_PW; ++ni)
670
+ #pragma unroll
671
+ for (int j = 0; j < 4; ++j) up_acc[mi][ni][j] = 0.0f;
672
+
673
+ run_pass(up_acc, up_b_row0, wsu_smem);
674
+
675
+ // =================== Pass 2 epilogue: silu(gate)*up + quant → FP8 ===================
676
+ // v = bf16(bf16(silu(gate)) * up), matching silu_mul_merged's two bf16
677
+ // roundings (silu(gate) was already bf16-rounded into gate_smem in pass 1;
678
+ // here we bf16-round the product). Then per-row amax over the 128-col block
679
+ // and quantize.
680
+ constexpr float kFp8Max = 448.0f;
681
+ // Each thread owns 8 cols (4 n-atoms × 2) for 2 rows per m-atom. Compute |v|
682
+ // and a per-warp partial amax per row (the warp owns 32 of the row's 128 cols).
683
+ // amax_smem[warp_id][row_in_cta] holds the warp's row-amax partial.
684
+ float v[M_ATOMS][N_ATOMS_PW][4];
685
+ #pragma unroll
686
+ for (int mi = 0; mi < M_ATOMS; ++mi) {
687
+ int row0 = m_base + mi * 16 + h;
688
+ int row1 = row0 + 8;
689
+ int rloc0 = mi * 16 + h; // local row in [0, BLOCK_M)
690
+ int rloc1 = rloc0 + 8;
691
+ float amax0 = 0.0f, amax1 = 0.0f;
692
+ #pragma unroll
693
+ for (int ni = 0; ni < N_ATOMS_PW; ++ni) {
694
+ int n_pair_base = warp_id * N_ATOMS_PW * 8 + ni * 8 + 2 * l;
695
+ // gate value (already bf16(silu(gate))) from smem; up from registers.
696
+ if (row0 < M) {
697
+ __nv_bfloat162 g = *reinterpret_cast<const __nv_bfloat162*>(
698
+ &gate_smem[rloc0 * BLOCK_N + n_pair_base]);
699
+ float gf0 = __low2float(g), gf1 = __high2float(g);
700
+ v[mi][ni][0] = __bfloat162float(__float2bfloat16(gf0 * up_acc[mi][ni][0]));
701
+ v[mi][ni][1] = __bfloat162float(__float2bfloat16(gf1 * up_acc[mi][ni][1]));
702
+ amax0 = fmaxf(amax0, fmaxf(fabsf(v[mi][ni][0]), fabsf(v[mi][ni][1])));
703
+ } else {
704
+ v[mi][ni][0] = 0.0f; v[mi][ni][1] = 0.0f;
705
+ }
706
+ if (row1 < M) {
707
+ __nv_bfloat162 g = *reinterpret_cast<const __nv_bfloat162*>(
708
+ &gate_smem[rloc1 * BLOCK_N + n_pair_base]);
709
+ float gf0 = __low2float(g), gf1 = __high2float(g);
710
+ v[mi][ni][2] = __bfloat162float(__float2bfloat16(gf0 * up_acc[mi][ni][2]));
711
+ v[mi][ni][3] = __bfloat162float(__float2bfloat16(gf1 * up_acc[mi][ni][3]));
712
+ amax1 = fmaxf(amax1, fmaxf(fabsf(v[mi][ni][2]), fabsf(v[mi][ni][3])));
713
+ } else {
714
+ v[mi][ni][2] = 0.0f; v[mi][ni][3] = 0.0f;
715
+ }
716
+ }
717
+ // Warp-shuffle reduce the 4 lanes (l=0..3) that share row0 / row1.
718
+ for (int off = 2; off > 0; off >>= 1) {
719
+ amax0 = fmaxf(amax0, __shfl_xor_sync(0xffffffff, amax0, off));
720
+ amax1 = fmaxf(amax1, __shfl_xor_sync(0xffffffff, amax1, off));
721
+ }
722
+ if (l == 0) {
723
+ amax_smem[warp_id * BLOCK_M + rloc0] = amax0;
724
+ amax_smem[warp_id * BLOCK_M + rloc1] = amax1;
725
+ }
726
+ }
727
+ __syncthreads();
728
+
729
+ // Cross-warp reduce: each warp wrote its row-amax partial. Final reduce per
730
+ // row done by warp 0 lanes, broadcast via smem.
731
+ #pragma unroll
732
+ for (int rloc = t; rloc < BLOCK_M; rloc += THREADS) {
733
+ int row = m_base + rloc;
734
+ if (row >= M) continue;
735
+ float amax = 0.0f;
736
+ #pragma unroll
737
+ for (int w = 0; w < NUM_WARPS; ++w)
738
+ amax = fmaxf(amax, amax_smem[w * BLOCK_M + rloc]);
739
+ float sc = fmaxf(amax / kFp8Max, 1.0e-12f);
740
+ amax_smem[rloc] = sc; // reuse slot to broadcast final scale
741
+ // Each active thread owns a distinct rloc in this strided loop, so each
742
+ // writes its own row's scale — no race. (The earlier `warp_id==0 &&
743
+ // lane==0` guard let only thread 0 write, leaving rows 1..BLOCK_M-1
744
+ // unwritten → garbage out_scale, correct-but-unscaled fp8 output.)
745
+ out_scale[(size_t)row * (N >> 7) + (n_base >> 7)] = sc;
746
+ }
747
+ __syncthreads();
748
+
749
+ // Quantize + store FP8. Thread re-reads its v[] and the row's scale.
750
+ #pragma unroll
751
+ for (int mi = 0; mi < M_ATOMS; ++mi) {
752
+ int row0 = m_base + mi * 16 + h;
753
+ int row1 = row0 + 8;
754
+ int rloc0 = mi * 16 + h;
755
+ int rloc1 = rloc0 + 8;
756
+ float sc0 = (row0 < M) ? amax_smem[rloc0] : 1.0f;
757
+ float sc1 = (row1 < M) ? amax_smem[rloc1] : 1.0f;
758
+ float inv0 = 1.0f / sc0, inv1 = 1.0f / sc1;
759
+ #pragma unroll
760
+ for (int ni = 0; ni < N_ATOMS_PW; ++ni) {
761
+ int n_pair_base = n_base + warp_id * N_ATOMS_PW * 8 + ni * 8 + 2 * l;
762
+ if (row0 < M && col_pair_ok(n_pair_base, N)) {
763
+ float q0 = fminf(fmaxf(v[mi][ni][0] * inv0, -kFp8Max), kFp8Max);
764
+ float q1 = fminf(fmaxf(v[mi][ni][1] * inv0, -kFp8Max), kFp8Max);
765
+ // pack two fp8 e4m3 into a 16-bit store
766
+ __nv_fp8_e4m3 p0(q0), p1(q1);
767
+ uint16_t pack = (uint16_t)(*reinterpret_cast<const uint8_t*>(&p1)) << 8
768
+ | (uint16_t)(*reinterpret_cast<const uint8_t*>(&p0));
769
+ *reinterpret_cast<uint16_t*>(&output[(size_t)row0 * N + n_pair_base]) = pack;
770
+ } else if (row0 < M) {
771
+ if (n_pair_base < N) {
772
+ float q = fminf(fmaxf(v[mi][ni][0] * inv0, -kFp8Max), kFp8Max);
773
+ output[(size_t)row0 * N + n_pair_base] = __nv_fp8_e4m3(q);
774
+ }
775
+ if (n_pair_base + 1 < N) {
776
+ float q = fminf(fmaxf(v[mi][ni][1] * inv0, -kFp8Max), kFp8Max);
777
+ output[(size_t)row0 * N + n_pair_base + 1] = __nv_fp8_e4m3(q);
778
+ }
779
+ }
780
+ if (row1 < M && col_pair_ok(n_pair_base, N)) {
781
+ float q2 = fminf(fmaxf(v[mi][ni][2] * inv1, -kFp8Max), kFp8Max);
782
+ float q3 = fminf(fmaxf(v[mi][ni][3] * inv1, -kFp8Max), kFp8Max);
783
+ __nv_fp8_e4m3 p2(q2), p3(q3);
784
+ uint16_t pack = (uint16_t)(*reinterpret_cast<const uint8_t*>(&p3)) << 8
785
+ | (uint16_t)(*reinterpret_cast<const uint8_t*>(&p2));
786
+ *reinterpret_cast<uint16_t*>(&output[(size_t)row1 * N + n_pair_base]) = pack;
787
+ } else if (row1 < M) {
788
+ if (n_pair_base < N) {
789
+ float q = fminf(fmaxf(v[mi][ni][2] * inv1, -kFp8Max), kFp8Max);
790
+ output[(size_t)row1 * N + n_pair_base] = __nv_fp8_e4m3(q);
791
+ }
792
+ if (n_pair_base + 1 < N) {
793
+ float q = fminf(fmaxf(v[mi][ni][3] * inv1, -kFp8Max), kFp8Max);
794
+ output[(size_t)row1 * N + n_pair_base + 1] = __nv_fp8_e4m3(q);
795
+ }
796
+ }
797
+ }
798
+ }
799
+ }
800
+
801
+ // ============================================================================
802
+ // GeGLU silu-fold, A-persistent two-pass variant.
803
+ //
804
+ // Same fusion as fp8_bs_geglu_silu_fold_kernel (gate+up GEMM + silu(gate)*up +
805
+ // per-token block-128 FP8 quant, one launch, no [M,2N] BF16 transient), but a
806
+ // different smem/register strategy that fixes the two-pass weaknesses the ncu
807
+ // diagnosis isolated:
808
+ //
809
+ // two-pass loss = (a) A re-loaded twice (2*M*K HBM) + (b) 2x pipeline drain.
810
+ // interleaved loss = 2x B smem (both gate+up staged) -> 1 CTA/SM occupancy.
811
+ //
812
+ // A-persistent: stage A into smem ONCE (reused by both the gate pass and the up
813
+ // pass), but keep only ONE B smem region that is filled with B_gate for the
814
+ // gate pass and then RE-FILLED with B_up for the up pass (sequential, not
815
+ // simultaneous). So:
816
+ // - A loaded once from HBM (the interleaved HBM win), held in smem across
817
+ // both passes -> no A re-load. act_scale also staged once.
818
+ // - B smem = a single STAGES*BN*BK region (NOT doubled) -> fits 4 CTA/SM.
819
+ // - only ONE accumulator live at a time (gate_acc -> store silu(gate) to a
820
+ // small smem gate buffer -> reuse regs for up_acc) -> ~70 regs (two-pass
821
+ // register profile), not the ~140 of true interleaved.
822
+ //
823
+ // The catch: A must fit in smem for the whole K-walk (A is [BM, K], not
824
+ // [BM, BK]), so this only works when K is small enough that BM*K fp8 + the rest
825
+ // stays under the smem budget — i.e. the Qwen3-VL gate_up shapes where K=hidden
826
+ // (2B K=2048, 8B K=4096). For BM=32: A_persist = 32*K = 64KB (8B) / 32KB (2B).
827
+ // 8B 64KB alone already exceeds a CTA's smem, so A-persistent is only viable
828
+ // for the 2B shape (K=2048) at BM<=32, OR by staging A in K-chunks and walking
829
+ // gate+up together within each K-chunk (chunked-interleaved). The chunked form
830
+ // is implemented here: A is staged per BLOCK_K tile like the baseline, but BOTH
831
+ // the gate MMA and the up MMA for that K-tile run before the tile is evicted —
832
+ // i.e. gate and up advance K-tile-by-K-tile together (true interleaved per
833
+ // K-tile, the sm100 geglu pattern), yet B is staged in ONE region reused for
834
+ // gate-then-up WITHIN the k-iter (load B_gate, gate-MMA, load B_up into the
835
+ // SAME region, up-MMA). That keeps B smem single (no 2x) AND loads A once AND
836
+ // holds both gate_acc+up_acc in regs (interleaved) — but pays by serializing
837
+ // the two B loads within a k-iter (no overlap between B_gate and B_up loads).
838
+ //
839
+ // Net vs two-pass: A loaded once (saves M*K HBM), one pipeline drain (K_ITERS
840
+ // stalls not 2*K_ITERS), but B_gate/B_up loads are serial within each k-iter.
841
+ // Net vs interleaved: B smem halved (2 CTA/SM recoverable to 3-4), but loses
842
+ // B_gate||B_up load overlap. On sm89 (HBM-bound, no TMA) the smem/occupancy
843
+ // recovery usually dominates, so this is the predicted winner.
844
+ // ============================================================================
845
+ template <int BLOCK_M, int BLOCK_N, int NUM_WARPS, int STAGES,
846
+ int MIN_BLOCKS_PER_SM>
847
+ __global__ __launch_bounds__(NUM_WARPS * 32, MIN_BLOCKS_PER_SM)
848
+ void fp8_bs_geglu_silu_fold_apersist_kernel(
849
+ const __nv_fp8_e4m3* __restrict__ A,
850
+ const __nv_fp8_e4m3* __restrict__ B, // gate_up_w [2*N, K]
851
+ const float* __restrict__ act_scale, // [M, K/128]
852
+ const float* __restrict__ w_scale, // gate_up_s [2*N/128, K/128]
853
+ __nv_fp8_e4m3* __restrict__ output, // [M, N]
854
+ float* __restrict__ out_scale, // [M, N/128]
855
+ int M, int N, int K)
856
+ {
857
+ static_assert(BLOCK_N == 128,
858
+ "GeGLU silu-fold requires BLOCK_N==128 (one quant block per CTA)");
859
+ constexpr int BLOCK_K = 128;
860
+ constexpr int THREADS = NUM_WARPS * 32;
861
+ constexpr int M_ATOMS = BLOCK_M / 16;
862
+ constexpr int N_ATOMS = BLOCK_N / 8; // 16
863
+ constexpr int N_ATOMS_PW = N_ATOMS / NUM_WARPS;
864
+ constexpr int N_PAIRS_PW = N_ATOMS_PW / 2;
865
+ constexpr int K_ATOMS = BLOCK_K / 32; // 4
866
+ constexpr int NUM_CHUNKS_PER_ROW = BLOCK_K / 16;
867
+ constexpr int SWIZZLE_MASK = NUM_CHUNKS_PER_ROW - 1;
868
+ constexpr int SCALE_KTILE = 8;
869
+ constexpr int A_TILE = BLOCK_M * BLOCK_K;
870
+ constexpr int B_TILE = BLOCK_N * BLOCK_K;
871
+
872
+ static_assert(BLOCK_M % 16 == 0, "BLOCK_M multiple of 16");
873
+ static_assert(N_ATOMS_PW >= 2 && N_ATOMS_PW % 2 == 0,
874
+ "ldmatrix pairs 2 N-atoms: N_ATOMS_PW must be even >= 2");
875
+
876
+ extern __shared__ uint8_t smem_raw[];
877
+ uint8_t* A_smem = smem_raw; // STAGES * A_TILE
878
+ uint8_t* B_smem = A_smem + STAGES * A_TILE; // STAGES * B_TILE (reused gate/up)
879
+ // gate_smem: silu(gate) BF16, [BM, BN]. Written by gate epilogue, read by
880
+ // the final silu(gate)*up epilogue (NOT per k-iter — only once at the end).
881
+ __nv_bfloat16* gate_smem = reinterpret_cast<__nv_bfloat16*>(
882
+ B_smem + STAGES * B_TILE);
883
+ float* as_smem = reinterpret_cast<float*>(gate_smem + BLOCK_M * BLOCK_N);
884
+ float* wsg_smem = as_smem + BLOCK_M * SCALE_KTILE; // gate w_scale row
885
+ float* wsu_smem = wsg_smem + SCALE_KTILE; // up w_scale row
886
+ float* amax_smem = wsu_smem + SCALE_KTILE;
887
+
888
+ const int cta_m = blockIdx.x;
889
+ const int cta_n = blockIdx.y;
890
+ const int m_base = cta_m * BLOCK_M;
891
+ const int n_base = cta_n * BLOCK_N;
892
+ const int gate_b_row0 = n_base;
893
+ const int up_b_row0 = n_base + N;
894
+
895
+ const int t = threadIdx.x;
896
+ const int warp_id = t / 32;
897
+ const int lane = t % 32;
898
+ const int l = lane % 4;
899
+ const int h = lane / 4;
900
+ const int frag_group = lane / 8;
901
+ const int row_in_frag = lane % 8;
902
+ const int row_block = frag_group / 2;
903
+ const int col_block = frag_group % 2;
904
+
905
+ const int K128 = K >> 7;
906
+ const int N128 = N >> 7;
907
+ const int gate_ws_row = (n_base >> 7);
908
+ const int up_ws_row = gate_ws_row + N128;
909
+
910
+ auto stage_scales = [&](int kb0) {
911
+ const int as_total = BLOCK_M * SCALE_KTILE;
912
+ for (int idx = t; idx < as_total; idx += THREADS) {
913
+ int r = idx / SCALE_KTILE;
914
+ int kc = idx - r * SCALE_KTILE;
915
+ int row = m_base + r;
916
+ int kb = kb0 + kc;
917
+ as_smem[idx] = (row < M && kb < K128)
918
+ ? act_scale[(size_t)row * K128 + kb] : 0.0f;
919
+ }
920
+ for (int kc = t; kc < SCALE_KTILE; kc += THREADS) {
921
+ int kb = kb0 + kc;
922
+ wsg_smem[kc] = (kb < K128)
923
+ ? w_scale[(size_t)gate_ws_row * K128 + kb] : 0.0f;
924
+ wsu_smem[kc] = (kb < K128)
925
+ ? w_scale[(size_t)up_ws_row * K128 + kb] : 0.0f;
926
+ }
927
+ __syncthreads();
928
+ };
929
+
930
+ // Stage A + one B band (gate or up) into smem. b_row0 picks the band.
931
+ auto issue_load = [&](int stage, int k_base, int b_row0) {
932
+ constexpr int A_CHUNKS = BLOCK_M * NUM_CHUNKS_PER_ROW;
933
+ constexpr int A_ITERS = (A_CHUNKS + THREADS - 1) / THREADS;
934
+ #pragma unroll
935
+ for (int it = 0; it < A_ITERS; ++it) {
936
+ int idx = it * THREADS + t;
937
+ if (idx >= A_CHUNKS) break;
938
+ int row_a = idx / NUM_CHUNKS_PER_ROW;
939
+ int chunk_a = idx % NUM_CHUNKS_PER_ROW;
940
+ int m_glob = m_base + row_a;
941
+ int k_glob = k_base + chunk_a * 16;
942
+ const uint8_t* a_src = nullptr;
943
+ if (m_glob < M && k_glob < K) {
944
+ a_src = reinterpret_cast<const uint8_t*>(&A[(size_t)m_glob * K + k_glob]);
945
+ }
946
+ int csw = chunk_a ^ (row_a & SWIZZLE_MASK);
947
+ cp_async_16(
948
+ to_smem(&A_smem[stage * A_TILE + row_a * BLOCK_K + csw * 16]),
949
+ a_src);
950
+ }
951
+ constexpr int B_CHUNKS = BLOCK_N * NUM_CHUNKS_PER_ROW;
952
+ constexpr int B_ITERS = (B_CHUNKS + THREADS - 1) / THREADS;
953
+ #pragma unroll
954
+ for (int it = 0; it < B_ITERS; ++it) {
955
+ int idx = it * THREADS + t;
956
+ if (idx >= B_CHUNKS) break;
957
+ int row_b = idx / NUM_CHUNKS_PER_ROW;
958
+ int chunk_b = idx % NUM_CHUNKS_PER_ROW;
959
+ int n_glob = b_row0 + row_b;
960
+ int k_glob = k_base + chunk_b * 16;
961
+ const uint8_t* b_src = nullptr;
962
+ if (n_glob < 2 * N && k_glob < K) {
963
+ b_src = reinterpret_cast<const uint8_t*>(&B[(size_t)n_glob * K + k_glob]);
964
+ }
965
+ int csw = chunk_b ^ (row_b & SWIZZLE_MASK);
966
+ cp_async_16(
967
+ to_smem(&B_smem[stage * B_TILE + row_b * BLOCK_K + csw * 16]),
968
+ b_src);
969
+ }
970
+ };
971
+
972
+ // MMA pass over the staged A/B tiles for one k-iter, accumulating into `acc`
973
+ // with the given w_scale smem row.
974
+ auto mma_tile = [&](float (*acc)[N_ATOMS_PW][4], int compute_stage,
975
+ const float* ws_smem_pass) {
976
+ const int kb = (compute_stage); // caller passes k_iter; recompute below
977
+ (void)kb;
978
+ float tacc[M_ATOMS][N_ATOMS_PW][4];
979
+ #pragma unroll
980
+ for (int mi = 0; mi < M_ATOMS; ++mi)
981
+ #pragma unroll
982
+ for (int ni = 0; ni < N_ATOMS_PW; ++ni)
983
+ #pragma unroll
984
+ for (int j = 0; j < 4; ++j) tacc[mi][ni][j] = 0.0f;
985
+
986
+ uint8_t* A_stage = A_smem + compute_stage * A_TILE;
987
+ uint8_t* B_stage = B_smem + compute_stage * B_TILE;
988
+ #pragma unroll
989
+ for (int ka = 0; ka < K_ATOMS; ++ka) {
990
+ uint32_t A_regs[M_ATOMS][4];
991
+ #pragma unroll
992
+ for (int mi = 0; mi < M_ATOMS; ++mi) {
993
+ int row = mi * 16 + row_block * 8 + row_in_frag;
994
+ int chunk = 2 * ka + col_block;
995
+ int csw = chunk ^ (row & SWIZZLE_MASK);
996
+ ldmatrix_x4_b16(A_regs[mi][0], A_regs[mi][1], A_regs[mi][2], A_regs[mi][3],
997
+ to_smem(&A_stage[row * BLOCK_K + csw * 16]));
998
+ }
999
+ uint32_t B_regs[N_PAIRS_PW][4];
1000
+ #pragma unroll
1001
+ for (int np = 0; np < N_PAIRS_PW; ++np) {
1002
+ int nrow = warp_id * N_ATOMS_PW * 8 + np * 16 + row_block * 8 + row_in_frag;
1003
+ int chunk = 2 * ka + col_block;
1004
+ int csw = chunk ^ (nrow & SWIZZLE_MASK);
1005
+ ldmatrix_x4_b16(B_regs[np][0], B_regs[np][1], B_regs[np][2], B_regs[np][3],
1006
+ to_smem(&B_stage[nrow * BLOCK_K + csw * 16]));
1007
+ }
1008
+ #pragma unroll
1009
+ for (int mi = 0; mi < M_ATOMS; ++mi) {
1010
+ #pragma unroll
1011
+ for (int np = 0; np < N_PAIRS_PW; ++np) {
1012
+ int ni0 = np * 2, ni1 = np * 2 + 1;
1013
+ mma_m16n8k32_e4m3(
1014
+ tacc[mi][ni0][0], tacc[mi][ni0][1], tacc[mi][ni0][2], tacc[mi][ni0][3],
1015
+ A_regs[mi][0], A_regs[mi][2], A_regs[mi][1], A_regs[mi][3],
1016
+ B_regs[np][0], B_regs[np][1]);
1017
+ mma_m16n8k32_e4m3(
1018
+ tacc[mi][ni1][0], tacc[mi][ni1][1], tacc[mi][ni1][2], tacc[mi][ni1][3],
1019
+ A_regs[mi][0], A_regs[mi][2], A_regs[mi][1], A_regs[mi][3],
1020
+ B_regs[np][2], B_regs[np][3]);
1021
+ }
1022
+ }
1023
+ }
1024
+ return tacc; // caller folds scales into acc
1025
+ };
1026
+
1027
+ // Running accumulators for gate and up, both live across the whole K-loop
1028
+ // (true interleaved: both gate and up advance K-tile-by-K-tile together).
1029
+ float gate_acc[M_ATOMS][N_ATOMS_PW][4];
1030
+ float up_acc[M_ATOMS][N_ATOMS_PW][4];
1031
+ #pragma unroll
1032
+ for (int mi = 0; mi < M_ATOMS; ++mi)
1033
+ #pragma unroll
1034
+ for (int ni = 0; ni < N_ATOMS_PW; ++ni)
1035
+ #pragma unroll
1036
+ for (int j = 0; j < 4; ++j) {
1037
+ gate_acc[mi][ni][j] = 0.0f;
1038
+ up_acc[mi][ni][j] = 0.0f;
1039
+ }
1040
+
1041
+ const int K_ITERS = (K + BLOCK_K - 1) / BLOCK_K;
1042
+ // Prefetch STAGES-1 A tiles (A is shared by both passes — issued once).
1043
+ // B is NOT prefetched here; within each k-iter we issue B_gate then B_up
1044
+ // into the SAME smem region after the previous iter's B is consumed.
1045
+ #pragma unroll
1046
+ for (int s = 0; s < STAGES - 1; ++s) {
1047
+ int kb = s * BLOCK_K;
1048
+ if (kb < K) issue_load(s, kb, gate_b_row0); // first prefetch = gate B
1049
+ asm volatile("cp.async.commit_group;\n" ::);
1050
+ }
1051
+
1052
+ int compute_stage = 0;
1053
+ for (int k_iter = 0; k_iter < K_ITERS; ++k_iter) {
1054
+ int issue_iter = k_iter + (STAGES - 1);
1055
+ int issue_stage = issue_iter % STAGES;
1056
+ // Issue the NEXT A tile + the NEXT gate-B tile (the up-B for this k_iter
1057
+ // is loaded inside the gate-MMA sync below, reusing B_smem after gate
1058
+ // MMA reads finish).
1059
+ if (issue_iter < K_ITERS) issue_load(issue_stage, issue_iter * BLOCK_K, gate_b_row0);
1060
+ asm volatile("cp.async.commit_group;\n" ::);
1061
+ asm volatile("cp.async.wait_group %0;\n" :: "n"(STAGES - 1));
1062
+ __syncthreads();
1063
+
1064
+ const int kb = k_iter;
1065
+ if ((kb % SCALE_KTILE) == 0) stage_scales(kb);
1066
+
1067
+ // ---- gate MMA on the staged A + B_gate ----
1068
+ {
1069
+ float tacc[M_ATOMS][N_ATOMS_PW][4];
1070
+ #pragma unroll
1071
+ for (int mi = 0; mi < M_ATOMS; ++mi)
1072
+ #pragma unroll
1073
+ for (int ni = 0; ni < N_ATOMS_PW; ++ni)
1074
+ #pragma unroll
1075
+ for (int j = 0; j < 4; ++j) tacc[mi][ni][j] = 0.0f;
1076
+ uint8_t* A_stage = A_smem + compute_stage * A_TILE;
1077
+ uint8_t* B_stage = B_smem + compute_stage * B_TILE;
1078
+ #pragma unroll
1079
+ for (int ka = 0; ka < K_ATOMS; ++ka) {
1080
+ uint32_t A_regs[M_ATOMS][4];
1081
+ #pragma unroll
1082
+ for (int mi = 0; mi < M_ATOMS; ++mi) {
1083
+ int row = mi * 16 + row_block * 8 + row_in_frag;
1084
+ int chunk = 2 * ka + col_block;
1085
+ int csw = chunk ^ (row & SWIZZLE_MASK);
1086
+ ldmatrix_x4_b16(A_regs[mi][0], A_regs[mi][1], A_regs[mi][2], A_regs[mi][3],
1087
+ to_smem(&A_stage[row * BLOCK_K + csw * 16]));
1088
+ }
1089
+ uint32_t B_regs[N_PAIRS_PW][4];
1090
+ #pragma unroll
1091
+ for (int np = 0; np < N_PAIRS_PW; ++np) {
1092
+ int nrow = warp_id * N_ATOMS_PW * 8 + np * 16 + row_block * 8 + row_in_frag;
1093
+ int chunk = 2 * ka + col_block;
1094
+ int csw = chunk ^ (nrow & SWIZZLE_MASK);
1095
+ ldmatrix_x4_b16(B_regs[np][0], B_regs[np][1], B_regs[np][2], B_regs[np][3],
1096
+ to_smem(&B_stage[nrow * BLOCK_K + csw * 16]));
1097
+ }
1098
+ #pragma unroll
1099
+ for (int mi = 0; mi < M_ATOMS; ++mi) {
1100
+ #pragma unroll
1101
+ for (int np = 0; np < N_PAIRS_PW; ++np) {
1102
+ int ni0 = np * 2, ni1 = np * 2 + 1;
1103
+ mma_m16n8k32_e4m3(
1104
+ tacc[mi][ni0][0], tacc[mi][ni0][1], tacc[mi][ni0][2], tacc[mi][ni0][3],
1105
+ A_regs[mi][0], A_regs[mi][2], A_regs[mi][1], A_regs[mi][3],
1106
+ B_regs[np][0], B_regs[np][1]);
1107
+ mma_m16n8k32_e4m3(
1108
+ tacc[mi][ni1][0], tacc[mi][ni1][1], tacc[mi][ni1][2], tacc[mi][ni1][3],
1109
+ A_regs[mi][0], A_regs[mi][2], A_regs[mi][1], A_regs[mi][3],
1110
+ B_regs[np][2], B_regs[np][3]);
1111
+ }
1112
+ }
1113
+ }
1114
+ int kbt = kb % SCALE_KTILE;
1115
+ float ws_cta = wsg_smem[kbt];
1116
+ #pragma unroll
1117
+ for (int mi = 0; mi < M_ATOMS; ++mi) {
1118
+ int row0 = m_base + mi * 16 + h;
1119
+ int row1 = row0 + 8;
1120
+ float as0 = as_smem[(mi * 16 + h) * SCALE_KTILE + kbt];
1121
+ float as1 = as_smem[(mi * 16 + h + 8) * SCALE_KTILE + kbt];
1122
+ #pragma unroll
1123
+ for (int ni = 0; ni < N_ATOMS_PW; ++ni) {
1124
+ gate_acc[mi][ni][0] += tacc[mi][ni][0] * (as0 * ws_cta);
1125
+ gate_acc[mi][ni][1] += tacc[mi][ni][1] * (as0 * ws_cta);
1126
+ gate_acc[mi][ni][2] += tacc[mi][ni][2] * (as1 * ws_cta);
1127
+ gate_acc[mi][ni][3] += tacc[mi][ni][3] * (as1 * ws_cta);
1128
+ }
1129
+ }
1130
+ }
1131
+ __syncthreads(); // B_smem safe to overwrite with B_up
1132
+
1133
+ // ---- load B_up into the SAME B_smem region, then up MMA ----
1134
+ // (A is still resident in A_smem[compute_stage]; not reloaded from HBM.)
1135
+ {
1136
+ // issue B_up into B_smem[compute_stage] (A_smem left untouched)
1137
+ constexpr int B_CHUNKS = BLOCK_N * NUM_CHUNKS_PER_ROW;
1138
+ constexpr int B_ITERS = (B_CHUNKS + THREADS - 1) / THREADS;
1139
+ int k_base = k_iter * BLOCK_K;
1140
+ #pragma unroll
1141
+ for (int it = 0; it < B_ITERS; ++it) {
1142
+ int idx = it * THREADS + t;
1143
+ if (idx >= B_CHUNKS) break;
1144
+ int row_b = idx / NUM_CHUNKS_PER_ROW;
1145
+ int chunk_b = idx % NUM_CHUNKS_PER_ROW;
1146
+ int n_glob = up_b_row0 + row_b;
1147
+ int k_glob = k_base + chunk_b * 16;
1148
+ const uint8_t* b_src = nullptr;
1149
+ if (n_glob < 2 * N && k_glob < K) {
1150
+ b_src = reinterpret_cast<const uint8_t*>(&B[(size_t)n_glob * K + k_glob]);
1151
+ }
1152
+ int csw = chunk_b ^ (row_b & SWIZZLE_MASK);
1153
+ cp_async_16(
1154
+ to_smem(&B_smem[compute_stage * B_TILE + row_b * BLOCK_K + csw * 16]),
1155
+ b_src);
1156
+ }
1157
+ asm volatile("cp.async.commit_group;\n" ::);
1158
+ asm volatile("cp.async.wait_group %0;\n" :: "n"(0));
1159
+ __syncthreads();
1160
+
1161
+ float tacc[M_ATOMS][N_ATOMS_PW][4];
1162
+ #pragma unroll
1163
+ for (int mi = 0; mi < M_ATOMS; ++mi)
1164
+ #pragma unroll
1165
+ for (int ni = 0; ni < N_ATOMS_PW; ++ni)
1166
+ #pragma unroll
1167
+ for (int j = 0; j < 4; ++j) tacc[mi][ni][j] = 0.0f;
1168
+ uint8_t* A_stage = A_smem + compute_stage * A_TILE;
1169
+ uint8_t* B_stage = B_smem + compute_stage * B_TILE;
1170
+ #pragma unroll
1171
+ for (int ka = 0; ka < K_ATOMS; ++ka) {
1172
+ uint32_t A_regs[M_ATOMS][4];
1173
+ #pragma unroll
1174
+ for (int mi = 0; mi < M_ATOMS; ++mi) {
1175
+ int row = mi * 16 + row_block * 8 + row_in_frag;
1176
+ int chunk = 2 * ka + col_block;
1177
+ int csw = chunk ^ (row & SWIZZLE_MASK);
1178
+ ldmatrix_x4_b16(A_regs[mi][0], A_regs[mi][1], A_regs[mi][2], A_regs[mi][3],
1179
+ to_smem(&A_stage[row * BLOCK_K + csw * 16]));
1180
+ }
1181
+ uint32_t B_regs[N_PAIRS_PW][4];
1182
+ #pragma unroll
1183
+ for (int np = 0; np < N_PAIRS_PW; ++np) {
1184
+ int nrow = warp_id * N_ATOMS_PW * 8 + np * 16 + row_block * 8 + row_in_frag;
1185
+ int chunk = 2 * ka + col_block;
1186
+ int csw = chunk ^ (nrow & SWIZZLE_MASK);
1187
+ ldmatrix_x4_b16(B_regs[np][0], B_regs[np][1], B_regs[np][2], B_regs[np][3],
1188
+ to_smem(&B_stage[nrow * BLOCK_K + csw * 16]));
1189
+ }
1190
+ #pragma unroll
1191
+ for (int mi = 0; mi < M_ATOMS; ++mi) {
1192
+ #pragma unroll
1193
+ for (int np = 0; np < N_PAIRS_PW; ++np) {
1194
+ int ni0 = np * 2, ni1 = np * 2 + 1;
1195
+ mma_m16n8k32_e4m3(
1196
+ tacc[mi][ni0][0], tacc[mi][ni0][1], tacc[mi][ni0][2], tacc[mi][ni0][3],
1197
+ A_regs[mi][0], A_regs[mi][2], A_regs[mi][1], A_regs[mi][3],
1198
+ B_regs[np][0], B_regs[np][1]);
1199
+ mma_m16n8k32_e4m3(
1200
+ tacc[mi][ni1][0], tacc[mi][ni1][1], tacc[mi][ni1][2], tacc[mi][ni1][3],
1201
+ A_regs[mi][0], A_regs[mi][2], A_regs[mi][1], A_regs[mi][3],
1202
+ B_regs[np][2], B_regs[np][3]);
1203
+ }
1204
+ }
1205
+ }
1206
+ int kbt = kb % SCALE_KTILE;
1207
+ float ws_cta = wsu_smem[kbt];
1208
+ #pragma unroll
1209
+ for (int mi = 0; mi < M_ATOMS; ++mi) {
1210
+ int row0 = m_base + mi * 16 + h;
1211
+ int row1 = row0 + 8;
1212
+ float as0 = as_smem[(mi * 16 + h) * SCALE_KTILE + kbt];
1213
+ float as1 = as_smem[(mi * 16 + h + 8) * SCALE_KTILE + kbt];
1214
+ #pragma unroll
1215
+ for (int ni = 0; ni < N_ATOMS_PW; ++ni) {
1216
+ up_acc[mi][ni][0] += tacc[mi][ni][0] * (as0 * ws_cta);
1217
+ up_acc[mi][ni][1] += tacc[mi][ni][1] * (as0 * ws_cta);
1218
+ up_acc[mi][ni][2] += tacc[mi][ni][2] * (as1 * ws_cta);
1219
+ up_acc[mi][ni][3] += tacc[mi][ni][3] * (as1 * ws_cta);
1220
+ }
1221
+ }
1222
+ }
1223
+ __syncthreads();
1224
+ compute_stage = (compute_stage + 1) % STAGES;
1225
+ }
1226
+ asm volatile("cp.async.wait_all;\n" ::);
1227
+
1228
+ // ============ Epilogue: silu(gate)*up + per-row amax + quant ============
1229
+ // gate_acc and up_acc both live in registers. Replicate silu_mul_merged's
1230
+ // two bf16 roundings: bf16(silu(gate)) then bf16(silu_bf * up).
1231
+ constexpr float kFp8Max = 448.0f;
1232
+ float v[M_ATOMS][N_ATOMS_PW][4];
1233
+ #pragma unroll
1234
+ for (int mi = 0; mi < M_ATOMS; ++mi) {
1235
+ int row0 = m_base + mi * 16 + h;
1236
+ int row1 = row0 + 8;
1237
+ int rloc0 = mi * 16 + h;
1238
+ int rloc1 = rloc0 + 8;
1239
+ float amax0 = 0.0f, amax1 = 0.0f;
1240
+ #pragma unroll
1241
+ for (int ni = 0; ni < N_ATOMS_PW; ++ni) {
1242
+ if (row0 < M) {
1243
+ float gf0 = __bfloat162float(__float2bfloat16(silu_f32(gate_acc[mi][ni][0])));
1244
+ float gf1 = __bfloat162float(__float2bfloat16(silu_f32(gate_acc[mi][ni][1])));
1245
+ v[mi][ni][0] = __bfloat162float(__float2bfloat16(gf0 * up_acc[mi][ni][0]));
1246
+ v[mi][ni][1] = __bfloat162float(__float2bfloat16(gf1 * up_acc[mi][ni][1]));
1247
+ amax0 = fmaxf(amax0, fmaxf(fabsf(v[mi][ni][0]), fabsf(v[mi][ni][1])));
1248
+ } else { v[mi][ni][0] = 0.0f; v[mi][ni][1] = 0.0f; }
1249
+ if (row1 < M) {
1250
+ float gf0 = __bfloat162float(__float2bfloat16(silu_f32(gate_acc[mi][ni][2])));
1251
+ float gf1 = __bfloat162float(__float2bfloat16(silu_f32(gate_acc[mi][ni][3])));
1252
+ v[mi][ni][2] = __bfloat162float(__float2bfloat16(gf0 * up_acc[mi][ni][2]));
1253
+ v[mi][ni][3] = __bfloat162float(__float2bfloat16(gf1 * up_acc[mi][ni][3]));
1254
+ amax1 = fmaxf(amax1, fmaxf(fabsf(v[mi][ni][2]), fabsf(v[mi][ni][3])));
1255
+ } else { v[mi][ni][2] = 0.0f; v[mi][ni][3] = 0.0f; }
1256
+ }
1257
+ for (int off = 2; off > 0; off >>= 1) {
1258
+ amax0 = fmaxf(amax0, __shfl_xor_sync(0xffffffff, amax0, off));
1259
+ amax1 = fmaxf(amax1, __shfl_xor_sync(0xffffffff, amax1, off));
1260
+ }
1261
+ if (l == 0) {
1262
+ amax_smem[warp_id * BLOCK_M + rloc0] = amax0;
1263
+ amax_smem[warp_id * BLOCK_M + rloc1] = amax1;
1264
+ }
1265
+ }
1266
+ __syncthreads();
1267
+
1268
+ #pragma unroll
1269
+ for (int rloc = t; rloc < BLOCK_M; rloc += THREADS) {
1270
+ int row = m_base + rloc;
1271
+ if (row >= M) continue;
1272
+ float amax = 0.0f;
1273
+ #pragma unroll
1274
+ for (int w = 0; w < NUM_WARPS; ++w)
1275
+ amax = fmaxf(amax, amax_smem[w * BLOCK_M + rloc]);
1276
+ float sc = fmaxf(amax / kFp8Max, 1.0e-12f);
1277
+ amax_smem[rloc] = sc;
1278
+ // Each active thread owns a distinct rloc — write its row's scale
1279
+ // directly (no single-thread guard; see two-pass variant for the bug
1280
+ // this fixes).
1281
+ out_scale[(size_t)row * (N >> 7) + (n_base >> 7)] = sc;
1282
+ }
1283
+ __syncthreads();
1284
+
1285
+ #pragma unroll
1286
+ for (int mi = 0; mi < M_ATOMS; ++mi) {
1287
+ int row0 = m_base + mi * 16 + h;
1288
+ int row1 = row0 + 8;
1289
+ int rloc0 = mi * 16 + h;
1290
+ int rloc1 = rloc0 + 8;
1291
+ float sc0 = (row0 < M) ? amax_smem[rloc0] : 1.0f;
1292
+ float sc1 = (row1 < M) ? amax_smem[rloc1] : 1.0f;
1293
+ float inv0 = 1.0f / sc0, inv1 = 1.0f / sc1;
1294
+ #pragma unroll
1295
+ for (int ni = 0; ni < N_ATOMS_PW; ++ni) {
1296
+ int n_pair_base = n_base + warp_id * N_ATOMS_PW * 8 + ni * 8 + 2 * l;
1297
+ if (row0 < M && col_pair_ok(n_pair_base, N)) {
1298
+ float q0 = fminf(fmaxf(v[mi][ni][0] * inv0, -kFp8Max), kFp8Max);
1299
+ float q1 = fminf(fmaxf(v[mi][ni][1] * inv0, -kFp8Max), kFp8Max);
1300
+ __nv_fp8_e4m3 p0(q0), p1(q1);
1301
+ uint16_t pack = (uint16_t)(*reinterpret_cast<const uint8_t*>(&p1)) << 8
1302
+ | (uint16_t)(*reinterpret_cast<const uint8_t*>(&p0));
1303
+ *reinterpret_cast<uint16_t*>(&output[(size_t)row0 * N + n_pair_base]) = pack;
1304
+ } else if (row0 < M) {
1305
+ if (n_pair_base < N) output[(size_t)row0 * N + n_pair_base] = __nv_fp8_e4m3(fminf(fmaxf(v[mi][ni][0] * inv0, -kFp8Max), kFp8Max));
1306
+ if (n_pair_base + 1 < N) output[(size_t)row0 * N + n_pair_base + 1] = __nv_fp8_e4m3(fminf(fmaxf(v[mi][ni][1] * inv0, -kFp8Max), kFp8Max));
1307
+ }
1308
+ if (row1 < M && col_pair_ok(n_pair_base, N)) {
1309
+ float q2 = fminf(fmaxf(v[mi][ni][2] * inv1, -kFp8Max), kFp8Max);
1310
+ float q3 = fminf(fmaxf(v[mi][ni][3] * inv1, -kFp8Max), kFp8Max);
1311
+ __nv_fp8_e4m3 p2(q2), p3(q3);
1312
+ uint16_t pack = (uint16_t)(*reinterpret_cast<const uint8_t*>(&p3)) << 8
1313
+ | (uint16_t)(*reinterpret_cast<const uint8_t*>(&p2));
1314
+ *reinterpret_cast<uint16_t*>(&output[(size_t)row1 * N + n_pair_base]) = pack;
1315
+ } else if (row1 < M) {
1316
+ if (n_pair_base < N) output[(size_t)row1 * N + n_pair_base] = __nv_fp8_e4m3(fminf(fmaxf(v[mi][ni][2] * inv1, -kFp8Max), kFp8Max));
1317
+ if (n_pair_base + 1 < N) output[(size_t)row1 * N + n_pair_base + 1] = __nv_fp8_e4m3(fminf(fmaxf(v[mi][ni][3] * inv1, -kFp8Max), kFp8Max));
1318
+ }
1319
+ }
1320
+ }
1321
+ (void)gate_smem; // apersist keeps gate in regs; gate_smem unused (kept for layout parity)
1322
+ (void)mma_tile; // helper retained for future chunked variant; unused in this path
1323
+ }
1324
+
1325
+ } // namespace block128_sm89
1326
+ } // namespace gemm
1327
+ } // namespace flash_rt
csrc/fp8_gemv_m1_sm120.cu ADDED
@@ -0,0 +1,159 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ //
3
+ // Dedicated M=1 FP8 e4m3 -> BF16 GEMV for sm_120a decode (batch=1 token).
4
+ //
5
+ // The hand-tuned MMA GEMMs pad M=1 to BLOCK_M=16 (m16n8k32), computing 16
6
+ // rows to use 1 — fine for compute (memory-bound) but the N=2560 shapes only
7
+ // spawn N/BLOCK_N blocks and starve the SMs. This GEMV assigns one warp per
8
+ // output row: A[1,K] is staged once into smem (hot in L2 across blocks), each
9
+ // warp streams its B row in 16-byte coalesced chunks and warp-reduces the dot
10
+ // product. BLOCK_N effectively 1-per-warp => N/WARPS_PER_BLOCK blocks (e.g.
11
+ // N=2560, W=8 -> 320 blocks) saturates occupancy without a split-K reduction.
12
+
13
+ #include "fp8_gemv_m1_sm120.cuh"
14
+
15
+ #include <cuda_bf16.h>
16
+ #include <cuda_fp8.h>
17
+ #include <cuda_runtime.h>
18
+ #include <cstdint>
19
+
20
+ namespace flash_rt {
21
+ namespace gemm {
22
+ namespace gemv_m1 {
23
+
24
+ namespace {
25
+
26
+ // One warp per output row n. A staged in smem as raw fp8 (K bytes). B row read
27
+ // in uint4 (16 fp8) coalesced chunks, stride 32 across the warp. K assumed a
28
+ // multiple of 16 (all Higgs/Qwen3 GEMM K: 2560/4096/9728).
29
+ template <int WARPS_PER_BLOCK>
30
+ __global__ __launch_bounds__(WARPS_PER_BLOCK * 32, 8)
31
+ void gemv_fp8_m1_kernel(
32
+ const __nv_fp8_e4m3* __restrict__ A, // [K]
33
+ const __nv_fp8_e4m3* __restrict__ B, // [N, K]
34
+ __nv_bfloat16* __restrict__ D, // [N]
35
+ int N, int K, float alpha)
36
+ {
37
+ extern __shared__ __nv_fp8_e4m3 sA[]; // [K]
38
+ const int tid = threadIdx.x;
39
+ const int lane = tid & 31;
40
+ const int warp = tid >> 5;
41
+ const int threads = WARPS_PER_BLOCK * 32;
42
+ const int K16 = K >> 4; // # of 16-byte (uint4) groups
43
+
44
+ // Cooperatively stage A into smem, 16 bytes per thread.
45
+ uint4* sA16 = reinterpret_cast<uint4*>(sA);
46
+ const uint4* A16 = reinterpret_cast<const uint4*>(A);
47
+ for (int i = tid; i < K16; i += threads) sA16[i] = A16[i];
48
+ __syncthreads();
49
+
50
+ const int n = blockIdx.x * WARPS_PER_BLOCK + warp;
51
+ if (n >= N) return;
52
+
53
+ const uint4* Brow = reinterpret_cast<const uint4*>(B) + (size_t)n * K16;
54
+ const __nv_fp8_e4m3* sAf = sA;
55
+ float acc = 0.0f;
56
+ for (int i = lane; i < K16; i += 32) {
57
+ uint4 bpack = Brow[i];
58
+ const __nv_fp8_e4m3* bp = reinterpret_cast<const __nv_fp8_e4m3*>(&bpack);
59
+ const __nv_fp8_e4m3* ap = sAf + (i << 4);
60
+ #pragma unroll
61
+ for (int j = 0; j < 16; ++j) {
62
+ acc += float(ap[j]) * float(bp[j]);
63
+ }
64
+ }
65
+ #pragma unroll
66
+ for (int off = 16; off > 0; off >>= 1) {
67
+ acc += __shfl_down_sync(0xffffffffu, acc, off);
68
+ }
69
+ if (lane == 0) D[n] = __float2bfloat16(acc * alpha);
70
+ }
71
+
72
+ // GEMV with fused residual accumulate: D[n] += acc * alpha (in-place into the
73
+ // residual stream). The residual is per-element local — no cross-block
74
+ // dependency like the norm — so it folds into the epilogue for free, removing
75
+ // the separate residual_add launch. Each output n is written by one warp lane.
76
+ template <int WARPS_PER_BLOCK>
77
+ __global__ __launch_bounds__(WARPS_PER_BLOCK * 32, 8)
78
+ void gemv_fp8_m1_resadd_kernel(
79
+ const __nv_fp8_e4m3* __restrict__ A,
80
+ const __nv_fp8_e4m3* __restrict__ B,
81
+ __nv_bfloat16* __restrict__ D, // residual stream, accumulated in place
82
+ int N, int K, float alpha)
83
+ {
84
+ extern __shared__ __nv_fp8_e4m3 sA[];
85
+ const int tid = threadIdx.x, lane = tid & 31, warp = tid >> 5;
86
+ const int threads = WARPS_PER_BLOCK * 32, K16 = K >> 4;
87
+ uint4* sA16 = reinterpret_cast<uint4*>(sA);
88
+ const uint4* A16 = reinterpret_cast<const uint4*>(A);
89
+ for (int i = tid; i < K16; i += threads) sA16[i] = A16[i];
90
+ __syncthreads();
91
+ const int n = blockIdx.x * WARPS_PER_BLOCK + warp;
92
+ if (n >= N) return;
93
+ const uint4* Brow = reinterpret_cast<const uint4*>(B) + (size_t)n * K16;
94
+ float acc = 0.0f;
95
+ for (int i = lane; i < K16; i += 32) {
96
+ uint4 bpack = Brow[i];
97
+ const __nv_fp8_e4m3* bp = reinterpret_cast<const __nv_fp8_e4m3*>(&bpack);
98
+ const __nv_fp8_e4m3* ap = sA + (i << 4);
99
+ #pragma unroll
100
+ for (int j = 0; j < 16; ++j) acc += float(ap[j]) * float(bp[j]);
101
+ }
102
+ #pragma unroll
103
+ for (int off = 16; off > 0; off >>= 1) acc += __shfl_down_sync(0xffffffffu, acc, off);
104
+ if (lane == 0) D[n] = __float2bfloat16(__bfloat162float(D[n]) + acc * alpha);
105
+ }
106
+
107
+ template <int W>
108
+ int launch_resadd_(const void* A, const void* B, void* D,
109
+ int N, int K, float alpha, cudaStream_t stream) {
110
+ dim3 grid((N + W - 1) / W);
111
+ size_t smem = (size_t)K * sizeof(__nv_fp8_e4m3);
112
+ gemv_fp8_m1_resadd_kernel<W><<<grid, W * 32, smem, stream>>>(
113
+ reinterpret_cast<const __nv_fp8_e4m3*>(A),
114
+ reinterpret_cast<const __nv_fp8_e4m3*>(B),
115
+ reinterpret_cast<__nv_bfloat16*>(D), N, K, alpha);
116
+ return 0;
117
+ }
118
+
119
+ template <int W>
120
+ int launch_(const void* A, const void* B, void* D,
121
+ int /*M*/, int N, int K, float alpha, cudaStream_t stream) {
122
+ dim3 grid((N + W - 1) / W);
123
+ dim3 block(W * 32);
124
+ size_t smem = (size_t)K * sizeof(__nv_fp8_e4m3);
125
+ gemv_fp8_m1_kernel<W><<<grid, block, smem, stream>>>(
126
+ reinterpret_cast<const __nv_fp8_e4m3*>(A),
127
+ reinterpret_cast<const __nv_fp8_e4m3*>(B),
128
+ reinterpret_cast<__nv_bfloat16*>(D),
129
+ N, K, alpha);
130
+ return 0;
131
+ }
132
+
133
+ } // namespace
134
+
135
+ #define DEFINE(NAME, W) \
136
+ int NAME(const void* A, const void* B, void* D, int M, int N, int K, \
137
+ float alpha, cudaStream_t stream) { \
138
+ return launch_<W>(A, B, D, M, N, K, alpha, stream); \
139
+ }
140
+
141
+ DEFINE(gemv_fp8_m1_w4, 4)
142
+ DEFINE(gemv_fp8_m1_w8, 8)
143
+ DEFINE(gemv_fp8_m1_w16, 16)
144
+
145
+ #define DEFINE_RA(NAME, W) \
146
+ int NAME(const void* A, const void* B, void* D, int M, int N, int K, \
147
+ float alpha, cudaStream_t stream) { \
148
+ return launch_resadd_<W>(A, B, D, N, K, alpha, stream); \
149
+ }
150
+
151
+ DEFINE_RA(gemv_fp8_m1_resadd_w4, 4)
152
+ DEFINE_RA(gemv_fp8_m1_resadd_w8, 8)
153
+
154
+ #undef DEFINE
155
+ #undef DEFINE_RA
156
+
157
+ } // namespace gemv_m1
158
+ } // namespace gemm
159
+ } // namespace flash_rt
csrc/fp8_gemv_m1_sm120.cuh ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ #pragma once
3
+
4
+ #include <cuda_runtime.h>
5
+
6
+ namespace flash_rt {
7
+ namespace gemm {
8
+ namespace gemv_m1 {
9
+
10
+ // Dedicated M=1 FP8 e4m3 -> BF16 GEMV for sm_120a decode shapes.
11
+ // Inputs: FP8 A [1,K] row-major, FP8 B [N,K] row-major (= W.T), BF16 D [1,N].
12
+ // alpha = a_scale * w_scale (per-tensor). M is ignored (M=1 assumed).
13
+ // Warp-per-output-row: each warp reduces one B row against A (held in smem),
14
+ // 16-byte vectorized coalesced B loads. No MMA / no BLOCK_M padding tax.
15
+ // Returns 0 on success.
16
+
17
+ #define DECL(NAME) \
18
+ int NAME(const void* A, const void* B, void* D, \
19
+ int M, int N, int K, float alpha, cudaStream_t stream)
20
+
21
+ DECL(gemv_fp8_m1_w4);
22
+ DECL(gemv_fp8_m1_w8);
23
+ DECL(gemv_fp8_m1_w16);
24
+ DECL(gemv_fp8_m1_resadd_w4); // D[n] += acc*alpha (fused residual)
25
+ DECL(gemv_fp8_m1_resadd_w8);
26
+
27
+ #undef DECL
28
+
29
+ } // namespace gemv_m1
30
+ } // namespace gemm
31
+ } // namespace flash_rt
csrc/fp8_gemv_m1_sm89.cu ADDED
@@ -0,0 +1,188 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ //
3
+ // M=1 FP8 e4m3 -> BF16 block-128 scaled GEMV for SM89 Qwen3-VL decode.
4
+ // Header: fp8_gemv_m1_sm89.cuh.
5
+ //
6
+ // Split out of the SM120 per-tensor GEMV (fp8_gemv_m1_sm120) so the SM89
7
+ // block-128 decode path owns its own file: per-token activation scale
8
+ // [K/128] and DeepSeek-style weight block scale [N/128, K/128], applied in
9
+ // the warp reduction. Warp-per-output-row, A held in smem, 16-byte coalesced
10
+ // B loads. No MMA / no padding tax (M=1).
11
+
12
+ #include "fp8_gemv_m1_sm89.cuh"
13
+
14
+ #include <cuda_bf16.h>
15
+ #include <cuda_fp8.h>
16
+ #include <cuda_runtime.h>
17
+ #include <cstdint>
18
+
19
+ namespace flash_rt {
20
+ namespace gemm {
21
+ namespace gemv_m1_sm89 {
22
+
23
+ namespace {
24
+
25
+ template <int WARPS_PER_BLOCK>
26
+ __global__ __launch_bounds__(WARPS_PER_BLOCK * 32, 8)
27
+ void gemv_fp8_block128_m1_kernel(
28
+ const __nv_fp8_e4m3* __restrict__ A, // [K]
29
+ const __nv_fp8_e4m3* __restrict__ B, // [N, K]
30
+ __nv_bfloat16* __restrict__ D, // [N]
31
+ int N, int K,
32
+ const float* __restrict__ act_scale, // [K/128]
33
+ const float* __restrict__ w_scale, // [N/128, K/128]
34
+ float alpha)
35
+ {
36
+ extern __shared__ __nv_fp8_e4m3 sA[];
37
+ const int tid = threadIdx.x;
38
+ const int lane = tid & 31;
39
+ const int warp = tid >> 5;
40
+ const int threads = WARPS_PER_BLOCK * 32;
41
+ const int K16 = K >> 4;
42
+ const int K128 = K >> 7;
43
+
44
+ uint4* sA16 = reinterpret_cast<uint4*>(sA);
45
+ const uint4* A16 = reinterpret_cast<const uint4*>(A);
46
+ for (int i = tid; i < K16; i += threads) sA16[i] = A16[i];
47
+ __syncthreads();
48
+
49
+ const int n = blockIdx.x * WARPS_PER_BLOCK + warp;
50
+ if (n >= N) return;
51
+
52
+ const uint4* Brow = reinterpret_cast<const uint4*>(B) + (size_t)n * K16;
53
+ const __nv_fp8_e4m3* sAf = sA;
54
+ const float* w_scale_row = w_scale + (size_t)(n >> 7) * K128;
55
+ float acc = 0.0f;
56
+ for (int i = lane; i < K16; i += 32) {
57
+ const int kb = i >> 3;
58
+ const float s = act_scale[kb] * w_scale_row[kb] * alpha;
59
+ uint4 bpack = Brow[i];
60
+ const __nv_fp8_e4m3* bp =
61
+ reinterpret_cast<const __nv_fp8_e4m3*>(&bpack);
62
+ const __nv_fp8_e4m3* ap = sAf + (i << 4);
63
+ #pragma unroll
64
+ for (int j = 0; j < 16; ++j) {
65
+ acc += float(ap[j]) * float(bp[j]) * s;
66
+ }
67
+ }
68
+ #pragma unroll
69
+ for (int off = 16; off > 0; off >>= 1) {
70
+ acc += __shfl_down_sync(0xffffffffu, acc, off);
71
+ }
72
+ if (lane == 0) D[n] = __float2bfloat16(acc);
73
+ }
74
+
75
+ template <int W>
76
+ int launch_block128_(const void* A, const void* B, void* D,
77
+ int /*M*/, int N, int K,
78
+ const float* act_scale, const float* w_scale,
79
+ float alpha, cudaStream_t stream) {
80
+ dim3 grid((N + W - 1) / W);
81
+ dim3 block(W * 32);
82
+ size_t smem = (size_t)K * sizeof(__nv_fp8_e4m3);
83
+ gemv_fp8_block128_m1_kernel<W><<<grid, block, smem, stream>>>(
84
+ reinterpret_cast<const __nv_fp8_e4m3*>(A),
85
+ reinterpret_cast<const __nv_fp8_e4m3*>(B),
86
+ reinterpret_cast<__nv_bfloat16*>(D),
87
+ N, K, act_scale, w_scale, alpha);
88
+ return 0;
89
+ }
90
+
91
+ // BF16-input variant: skips activation FP8 quantization.
92
+ // A is BF16, B is FP8 with block-128 weight scale. No act_scale needed.
93
+ template <int WARPS_PER_BLOCK>
94
+ __global__ __launch_bounds__(WARPS_PER_BLOCK * 32, 8)
95
+ void gemv_fp8_block128_m1_bf16in_kernel(
96
+ const __nv_bfloat16* __restrict__ A, // [K] BF16
97
+ const __nv_fp8_e4m3* __restrict__ B, // [N, K] FP8
98
+ __nv_bfloat16* __restrict__ D, // [N]
99
+ int N, int K,
100
+ const float* __restrict__ w_scale) // [N/128, K/128]
101
+ {
102
+ extern __shared__ __nv_bfloat16 sA_bf16[];
103
+ const int tid = threadIdx.x;
104
+ const int lane = tid & 31;
105
+ const int warp = tid >> 5;
106
+ const int threads = WARPS_PER_BLOCK * 32;
107
+ const int K16 = K >> 4;
108
+ const int K128 = K >> 7;
109
+
110
+ // Load BF16 activation (2 bytes each) via uint32 pairs.
111
+ uint* sU = reinterpret_cast<uint*>(sA_bf16);
112
+ const uint* AU = reinterpret_cast<const uint*>(A);
113
+ const int Khalf = K >> 1;
114
+ for (int i = tid; i < Khalf; i += threads) sU[i] = AU[i];
115
+ __syncthreads();
116
+
117
+ const int n = blockIdx.x * WARPS_PER_BLOCK + warp;
118
+ if (n >= N) return;
119
+
120
+ const uint4* Brow = reinterpret_cast<const uint4*>(B) + (size_t)n * K16;
121
+ const float* w_scale_row = w_scale + (size_t)(n >> 7) * K128;
122
+ float acc = 0.0f;
123
+ for (int i = lane; i < K16; i += 32) {
124
+ const int kb = i >> 3;
125
+ const float ws = w_scale_row[kb];
126
+ uint4 bpack = Brow[i];
127
+ const __nv_fp8_e4m3* bp =
128
+ reinterpret_cast<const __nv_fp8_e4m3*>(&bpack);
129
+ const __nv_bfloat16* ap = sA_bf16 + (i << 4);
130
+ float dot = 0.0f;
131
+ #pragma unroll
132
+ for (int j = 0; j < 16; ++j) {
133
+ dot += __bfloat162float(ap[j]) * float(bp[j]);
134
+ }
135
+ acc += dot * ws;
136
+ }
137
+ #pragma unroll
138
+ for (int off = 16; off > 0; off >>= 1) {
139
+ acc += __shfl_down_sync(0xffffffffu, acc, off);
140
+ }
141
+ if (lane == 0) D[n] = __float2bfloat16(acc);
142
+ }
143
+
144
+ template <int W>
145
+ int launch_block128_bf16in_(const void* A, const void* B, void* D,
146
+ int /*M*/, int N, int K,
147
+ const float* w_scale, cudaStream_t stream) {
148
+ dim3 grid((N + W - 1) / W);
149
+ dim3 block(W * 32);
150
+ size_t smem = (size_t)K * sizeof(__nv_bfloat16);
151
+ gemv_fp8_block128_m1_bf16in_kernel<W><<<grid, block, smem, stream>>>(
152
+ reinterpret_cast<const __nv_bfloat16*>(A),
153
+ reinterpret_cast<const __nv_fp8_e4m3*>(B),
154
+ reinterpret_cast<__nv_bfloat16*>(D),
155
+ N, K, w_scale);
156
+ return 0;
157
+ }
158
+
159
+ } // namespace
160
+
161
+ #define DEFINE_BLOCK128(NAME, W) \
162
+ int NAME(const void* A, const void* B, void* D, int M, int N, int K, \
163
+ const float* act_scale, const float* w_scale, float alpha, \
164
+ cudaStream_t stream) { \
165
+ return launch_block128_<W>(A, B, D, M, N, K, act_scale, w_scale, alpha, \
166
+ stream); \
167
+ }
168
+
169
+ DEFINE_BLOCK128(gemv_fp8_block128_m1_w4, 4)
170
+ DEFINE_BLOCK128(gemv_fp8_block128_m1_w8, 8)
171
+ DEFINE_BLOCK128(gemv_fp8_block128_m1_w16, 16)
172
+
173
+ #undef DEFINE_BLOCK128
174
+
175
+ #define DEFINE_BLOCK128_BF16IN(NAME, W) \
176
+ int NAME(const void* A, const void* B, void* D, int M, int N, int K, \
177
+ const float* w_scale, cudaStream_t stream) { \
178
+ return launch_block128_bf16in_<W>(A, B, D, M, N, K, w_scale, stream); \
179
+ }
180
+
181
+ DEFINE_BLOCK128_BF16IN(gemv_fp8_block128_m1_bf16in_w8, 8)
182
+ DEFINE_BLOCK128_BF16IN(gemv_fp8_block128_m1_bf16in_w16, 16)
183
+
184
+ #undef DEFINE_BLOCK128_BF16IN
185
+
186
+ } // namespace gemv_m1_sm89
187
+ } // namespace gemm
188
+ } // namespace flash_rt
csrc/fp8_gemv_m1_sm89.cuh ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ #pragma once
3
+
4
+ #include <cuda_runtime.h>
5
+
6
+ namespace flash_rt {
7
+ namespace gemm {
8
+ namespace gemv_m1_sm89 {
9
+
10
+ // M=1 FP8 e4m3 -> BF16 GEMV with per-token activation block scale [K/128] and
11
+ // per-weight 128x128 block scale [N/128, K/128]. Matches official Qwen3-VL FP8
12
+ // checkpoints that store `.weight` + `.weight_scale_inv`. Decode-shape sibling
13
+ // of the M>1 fp8_block128_gemm_mma_sm89 kernel. Warp-per-output-row, A staged
14
+ // in smem, 16-byte coalesced B loads. Returns 0 on success.
15
+ #define DECL_BLOCK128(NAME) \
16
+ int NAME(const void* A, const void* B, void* D, \
17
+ int M, int N, int K, const float* act_scale, \
18
+ const float* w_scale, float alpha, cudaStream_t stream)
19
+
20
+ DECL_BLOCK128(gemv_fp8_block128_m1_w4);
21
+ DECL_BLOCK128(gemv_fp8_block128_m1_w8);
22
+ DECL_BLOCK128(gemv_fp8_block128_m1_w16);
23
+
24
+ #undef DECL_BLOCK128
25
+
26
+ // BF16-input variants: A is BF16, B is FP8. No act_scale, only w_scale.
27
+ #define DECL_BLOCK128_BF16IN(NAME) \
28
+ int NAME(const void* A, const void* B, void* D, \
29
+ int M, int N, int K, const float* w_scale, cudaStream_t stream)
30
+
31
+ DECL_BLOCK128_BF16IN(gemv_fp8_block128_m1_bf16in_w8);
32
+ DECL_BLOCK128_BF16IN(gemv_fp8_block128_m1_bf16in_w16);
33
+
34
+ #undef DECL_BLOCK128_BF16IN
35
+
36
+ } // namespace gemv_m1_sm89
37
+ } // namespace gemm
38
+ } // namespace flash_rt
csrc/fp8_smallM_handtuned_ldmatrix_sm120.cu ADDED
@@ -0,0 +1,377 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ //
3
+ // Hand-tuned FP8 e4m3 -> BF16 GEMM v2 for sm_120a small-M motus shapes.
4
+ // Adds 128B swizzle smem layout + ldmatrix.x4.m8n8.b16 reads to clear the
5
+ // 4-way smem bank conflict that bottlenecks v1 (`fp8_smallM_handtuned`).
6
+ //
7
+ // Restrictions for this version:
8
+ // - BLOCK_K = 128 (natural 128B swizzle row stride)
9
+ // - N_ATOMS_PER_WARP must be even (paired into one ldmatrix.x4 each)
10
+ //
11
+ // MMA path identical to v1 (inline PTX m16n8k32 e4m3 e4m3 f32).
12
+
13
+ #include "fp8_smallM_handtuned_ldmatrix_sm120.cuh"
14
+
15
+ #include <cuda_bf16.h>
16
+ #include <cuda_fp8.h>
17
+ #include <cuda_runtime.h>
18
+ #include <cstdint>
19
+
20
+ namespace flash_rt {
21
+ namespace gemm {
22
+ namespace smallM_ld {
23
+
24
+ namespace {
25
+
26
+ __device__ __forceinline__ void mma_m16n8k32_e4m3(
27
+ float &d0, float &d1, float &d2, float &d3,
28
+ uint32_t a0, uint32_t a1, uint32_t a2, uint32_t a3,
29
+ uint32_t b0, uint32_t b1)
30
+ {
31
+ asm volatile(
32
+ "mma.sync.aligned.kind::f8f6f4.m16n8k32.row.col.f32.e4m3.e4m3.f32 "
33
+ "{%0, %1, %2, %3}, {%4, %5, %6, %7}, {%8, %9}, {%0, %1, %2, %3};\n"
34
+ : "+f"(d0), "+f"(d1), "+f"(d2), "+f"(d3)
35
+ : "r"(a0), "r"(a1), "r"(a2), "r"(a3), "r"(b0), "r"(b1));
36
+ }
37
+
38
+ __device__ __forceinline__ void cp_async_16(uint32_t smem, const uint8_t* src) {
39
+ int b = (src == nullptr) ? 0 : 16;
40
+ asm volatile("cp.async.ca.shared.global [%0], [%1], 16, %2;\n"
41
+ :: "r"(smem), "l"(src), "r"(b));
42
+ }
43
+
44
+ __device__ __forceinline__ uint32_t to_smem(const void* p) {
45
+ return static_cast<uint32_t>(__cvta_generic_to_shared(p));
46
+ }
47
+
48
+ __device__ __forceinline__ void ldmatrix_x4_b16(
49
+ uint32_t &d0, uint32_t &d1, uint32_t &d2, uint32_t &d3,
50
+ uint32_t smem_addr)
51
+ {
52
+ asm volatile(
53
+ "ldmatrix.sync.aligned.x4.m8n8.shared.b16 {%0, %1, %2, %3}, [%4];\n"
54
+ : "=r"(d0), "=r"(d1), "=r"(d2), "=r"(d3)
55
+ : "r"(smem_addr));
56
+ }
57
+
58
+ // 128B swizzle: byte_addr_swizzled = row*128 + (chunk16 XOR (row & 7))*16
59
+ // where chunk16 = byte_col / 16 in [0, 7] (one chunk = 16 bytes).
60
+ // Applied identically on cp.async store and ldmatrix load to round-trip cleanly.
61
+
62
+ template <int BLOCK_M, int BLOCK_N, int BLOCK_K, int NUM_WARPS,
63
+ int STAGES = 2, int MIN_BLOCKS_PER_SM = 4>
64
+ __global__ __launch_bounds__(NUM_WARPS * 32, MIN_BLOCKS_PER_SM)
65
+ void fp8_gemm_ld_kernel(
66
+ const __nv_fp8_e4m3* __restrict__ A,
67
+ const __nv_fp8_e4m3* __restrict__ B,
68
+ __nv_bfloat16* __restrict__ D,
69
+ int M, int N, int K, float alpha)
70
+ {
71
+ static_assert(BLOCK_K == 64 || BLOCK_K == 128 || BLOCK_K == 256
72
+ || BLOCK_K == 512,
73
+ "BLOCK_K must be 64/128/256/512 (BK=512 uses repeated 128B swizzle period)");
74
+ constexpr int NUM_CHUNKS_PER_ROW = BLOCK_K / 16; // 4, 8, 16, or 32
75
+ // Swizzle mask: 128B period (8 chunks). For BK=256/512 the pattern
76
+ // repeats every 8 chunks; ldmatrix.x4 reads within one period at a
77
+ // time so bank-conflict-free remains.
78
+ constexpr int SWIZZLE_MASK = (NUM_CHUNKS_PER_ROW <= 8)
79
+ ? (NUM_CHUNKS_PER_ROW - 1) : 7;
80
+ constexpr int THREADS = NUM_WARPS * 32;
81
+ constexpr int M_ATOMS = BLOCK_M / 16;
82
+ constexpr int N_ATOMS = BLOCK_N / 8;
83
+ constexpr int N_ATOMS_PW = N_ATOMS / NUM_WARPS;
84
+ static_assert(BLOCK_M % 16 == 0, "BLOCK_M must be multiple of 16");
85
+ static_assert(BLOCK_N % 8 == 0, "BLOCK_N must be multiple of 8");
86
+ static_assert(N_ATOMS_PW >= 2 && N_ATOMS_PW % 2 == 0,
87
+ "N atoms per warp must be even >=2 (paired for ldmatrix.x4)");
88
+ constexpr int N_PAIRS_PW = N_ATOMS_PW / 2;
89
+ constexpr int K_ATOMS = BLOCK_K / 32; // = 4 for BLOCK_K=128
90
+ constexpr int A_TILE_BYTES = BLOCK_M * BLOCK_K;
91
+ constexpr int B_TILE_BYTES = BLOCK_N * BLOCK_K;
92
+
93
+ extern __shared__ __align__(128) uint8_t smem_raw[];
94
+ uint8_t* A_smem = smem_raw;
95
+ uint8_t* B_smem = A_smem + STAGES * A_TILE_BYTES;
96
+
97
+ const int cta_m = blockIdx.x;
98
+ const int cta_n = blockIdx.y;
99
+ const int m_base = cta_m * BLOCK_M;
100
+ const int n_base = cta_n * BLOCK_N;
101
+
102
+ const int t = threadIdx.x;
103
+ const int warp_id = t / 32;
104
+ const int lane = t % 32;
105
+
106
+ // Lane partition for ldmatrix.x4 addressing (lane -> fragment).
107
+ const int frag_group = lane / 8; // 0..3 (TL,TR,BL,BR per ldmatrix)
108
+ const int row_in_frag = lane % 8; // row within fragment 0..7
109
+ const int row_block = frag_group / 2; // top(0) / bot(1)
110
+ const int col_block = frag_group % 2; // left(0) / right(1)
111
+
112
+ // Lane partition for mma epilogue write.
113
+ const int h = lane / 4; // 0..7
114
+ const int l = lane % 4; // 0..3
115
+
116
+ auto issue_load = [&](int stage, int k_base) {
117
+ // A tile: BLOCK_M rows x BLOCK_K bytes, each thread issues 16-byte chunks.
118
+ constexpr int A_CHUNKS = BLOCK_M * (BLOCK_K / 16);
119
+ constexpr int A_ITERS = (A_CHUNKS + THREADS - 1) / THREADS;
120
+ #pragma unroll
121
+ for (int it = 0; it < A_ITERS; ++it) {
122
+ int idx = it * THREADS + t;
123
+ if (idx >= A_CHUNKS) break;
124
+ int row_a = idx / (BLOCK_K / 16);
125
+ int chunk_a = idx % (BLOCK_K / 16);
126
+ int m_g = m_base + row_a;
127
+ int k_g = k_base + chunk_a * 16;
128
+ const uint8_t* src = nullptr;
129
+ if (m_g < M && k_g < K) {
130
+ src = reinterpret_cast<const uint8_t*>(&A[m_g * K + k_g]);
131
+ }
132
+ int chunk_sw = chunk_a ^ (row_a & SWIZZLE_MASK);
133
+ uint32_t dst = to_smem(
134
+ &A_smem[stage * A_TILE_BYTES + row_a * BLOCK_K + chunk_sw * 16]);
135
+ cp_async_16(dst, src);
136
+ }
137
+ // B tile: BLOCK_N rows x BLOCK_K bytes.
138
+ constexpr int B_CHUNKS = BLOCK_N * (BLOCK_K / 16);
139
+ constexpr int B_ITERS = (B_CHUNKS + THREADS - 1) / THREADS;
140
+ #pragma unroll
141
+ for (int it = 0; it < B_ITERS; ++it) {
142
+ int idx = it * THREADS + t;
143
+ if (idx >= B_CHUNKS) break;
144
+ int row_b = idx / (BLOCK_K / 16);
145
+ int chunk_b = idx % (BLOCK_K / 16);
146
+ int n_g = n_base + row_b;
147
+ int k_g = k_base + chunk_b * 16;
148
+ const uint8_t* src = nullptr;
149
+ if (n_g < N && k_g < K) {
150
+ src = reinterpret_cast<const uint8_t*>(&B[n_g * K + k_g]);
151
+ }
152
+ int chunk_sw = chunk_b ^ (row_b & SWIZZLE_MASK);
153
+ uint32_t dst = to_smem(
154
+ &B_smem[stage * B_TILE_BYTES + row_b * BLOCK_K + chunk_sw * 16]);
155
+ cp_async_16(dst, src);
156
+ }
157
+ };
158
+
159
+ // Per-warp accumulators: M_ATOMS rows of mma x N_ATOMS_PW cols x 4 fp32.
160
+ float acc[M_ATOMS][N_ATOMS_PW][4];
161
+ #pragma unroll
162
+ for (int mi = 0; mi < M_ATOMS; ++mi)
163
+ #pragma unroll
164
+ for (int ni = 0; ni < N_ATOMS_PW; ++ni)
165
+ #pragma unroll
166
+ for (int j = 0; j < 4; ++j) acc[mi][ni][j] = 0.0f;
167
+
168
+ // Prefetch STAGES-1 chunks.
169
+ const int K_ITERS = (K + BLOCK_K - 1) / BLOCK_K;
170
+ #pragma unroll
171
+ for (int s = 0; s < STAGES - 1; ++s) {
172
+ if (s * BLOCK_K < K) issue_load(s, s * BLOCK_K);
173
+ asm volatile("cp.async.commit_group;\n" ::);
174
+ }
175
+
176
+ int compute_stage = 0;
177
+ for (int k_iter = 0; k_iter < K_ITERS; ++k_iter) {
178
+ int issue_iter = k_iter + (STAGES - 1);
179
+ int issue_stage = issue_iter % STAGES;
180
+ if (issue_iter < K_ITERS) issue_load(issue_stage, issue_iter * BLOCK_K);
181
+ asm volatile("cp.async.commit_group;\n" ::);
182
+ asm volatile("cp.async.wait_group %0;\n" :: "n"(STAGES - 1));
183
+ __syncthreads();
184
+
185
+ uint8_t* A_stage = A_smem + compute_stage * A_TILE_BYTES;
186
+ uint8_t* B_stage = B_smem + compute_stage * B_TILE_BYTES;
187
+
188
+ // K-atom inner loop. Per k_a: ldmatrix A (per m-atom) and B (per N-pair).
189
+ #pragma unroll
190
+ for (int k_a = 0; k_a < K_ATOMS; ++k_a) {
191
+ uint32_t A_regs[M_ATOMS][4];
192
+ #pragma unroll
193
+ for (int mi = 0; mi < M_ATOMS; ++mi) {
194
+ int row_in_tile = mi * 16 + row_block * 8 + row_in_frag;
195
+ int chunk = 2 * k_a + col_block;
196
+ int chunk_sw = chunk ^ (row_in_tile & SWIZZLE_MASK);
197
+ uint32_t addr = to_smem(
198
+ &A_stage[row_in_tile * BLOCK_K + chunk_sw * 16]);
199
+ ldmatrix_x4_b16(
200
+ A_regs[mi][0], A_regs[mi][1], A_regs[mi][2], A_regs[mi][3],
201
+ addr);
202
+ // ldmatrix output mapping vs mma m16n8k32 A operand:
203
+ // ldm d0=TL → mma a0
204
+ // ldm d1=TR → mma a2
205
+ // ldm d2=BL → mma a1
206
+ // ldm d3=BR → mma a3
207
+ }
208
+
209
+ uint32_t B_regs[N_PAIRS_PW][4];
210
+ #pragma unroll
211
+ for (int np = 0; np < N_PAIRS_PW; ++np) {
212
+ int n_base_pair = warp_id * N_ATOMS_PW * 8 + np * 16;
213
+ int n_row_in_tile = n_base_pair + row_block * 8 + row_in_frag;
214
+ int chunk = 2 * k_a + col_block;
215
+ int chunk_sw = chunk ^ (n_row_in_tile & SWIZZLE_MASK);
216
+ uint32_t addr = to_smem(
217
+ &B_stage[n_row_in_tile * BLOCK_K + chunk_sw * 16]);
218
+ ldmatrix_x4_b16(
219
+ B_regs[np][0], B_regs[np][1], B_regs[np][2], B_regs[np][3],
220
+ addr);
221
+ // ldm output mapping for paired N-atoms:
222
+ // d0 = TL = N-atom0's b0 (rows 0-7, K-cols 0-15)
223
+ // d1 = TR = N-atom0's b1 (rows 0-7, K-cols 16-31)
224
+ // d2 = BL = N-atom1's b0 (rows 8-15, K-cols 0-15)
225
+ // d3 = BR = N-atom1's b1 (rows 8-15, K-cols 16-31)
226
+ }
227
+
228
+ #pragma unroll
229
+ for (int mi = 0; mi < M_ATOMS; ++mi) {
230
+ #pragma unroll
231
+ for (int np = 0; np < N_PAIRS_PW; ++np) {
232
+ int ni0 = np * 2;
233
+ int ni1 = np * 2 + 1;
234
+ // N-atom 0: B = (b0=B_regs[np][0], b1=B_regs[np][1])
235
+ mma_m16n8k32_e4m3(
236
+ acc[mi][ni0][0], acc[mi][ni0][1],
237
+ acc[mi][ni0][2], acc[mi][ni0][3],
238
+ A_regs[mi][0], A_regs[mi][2],
239
+ A_regs[mi][1], A_regs[mi][3],
240
+ B_regs[np][0], B_regs[np][1]);
241
+ // N-atom 1: B = (b0=B_regs[np][2], b1=B_regs[np][3])
242
+ mma_m16n8k32_e4m3(
243
+ acc[mi][ni1][0], acc[mi][ni1][1],
244
+ acc[mi][ni1][2], acc[mi][ni1][3],
245
+ A_regs[mi][0], A_regs[mi][2],
246
+ A_regs[mi][1], A_regs[mi][3],
247
+ B_regs[np][2], B_regs[np][3]);
248
+ }
249
+ }
250
+ }
251
+ compute_stage = (compute_stage + 1) % STAGES;
252
+ }
253
+ asm volatile("cp.async.wait_all;\n" ::);
254
+
255
+ // Epilogue: write 4 fp32 acc per lane to D[BF16].
256
+ // mma m16n8 output per lane (h=lane/4, l=lane%4):
257
+ // d0,d1 -> row h, cols 2l, 2l+1
258
+ // d2,d3 -> row h+8, cols 2l, 2l+1
259
+ #pragma unroll
260
+ for (int mi = 0; mi < M_ATOMS; ++mi) {
261
+ int row0 = m_base + mi * 16 + h;
262
+ int row1 = row0 + 8;
263
+ #pragma unroll
264
+ for (int ni = 0; ni < N_ATOMS_PW; ++ni) {
265
+ int col_base = n_base + warp_id * N_ATOMS_PW * 8 + ni * 8 + 2 * l;
266
+ if (row0 < M) {
267
+ if (col_base < N)
268
+ D[row0 * N + col_base ] = __float2bfloat16(acc[mi][ni][0] * alpha);
269
+ if (col_base + 1 < N)
270
+ D[row0 * N + col_base + 1] = __float2bfloat16(acc[mi][ni][1] * alpha);
271
+ }
272
+ if (row1 < M) {
273
+ if (col_base < N)
274
+ D[row1 * N + col_base ] = __float2bfloat16(acc[mi][ni][2] * alpha);
275
+ if (col_base + 1 < N)
276
+ D[row1 * N + col_base + 1] = __float2bfloat16(acc[mi][ni][3] * alpha);
277
+ }
278
+ }
279
+ }
280
+ }
281
+
282
+ template <int BM, int BN, int BK, int W, int STAGES = 2, int MIN_BLK = 4>
283
+ int launch_(const void* A, const void* B, void* D,
284
+ int M, int N, int K, float alpha, cudaStream_t s)
285
+ {
286
+ if (K % BK != 0) return 2;
287
+ int grid_m = (M + BM - 1) / BM;
288
+ int grid_n = (N + BN - 1) / BN;
289
+ dim3 grid(grid_m, grid_n, 1);
290
+ dim3 block(W * 32, 1, 1);
291
+ int smem_bytes = STAGES * (BM + BN) * BK;
292
+ if (smem_bytes > 48 * 1024) {
293
+ cudaFuncSetAttribute(
294
+ (const void*)&fp8_gemm_ld_kernel<BM, BN, BK, W, STAGES, MIN_BLK>,
295
+ cudaFuncAttributeMaxDynamicSharedMemorySize, smem_bytes);
296
+ }
297
+ fp8_gemm_ld_kernel<BM, BN, BK, W, STAGES, MIN_BLK><<<grid, block, smem_bytes, s>>>(
298
+ reinterpret_cast<const __nv_fp8_e4m3*>(A),
299
+ reinterpret_cast<const __nv_fp8_e4m3*>(B),
300
+ reinterpret_cast<__nv_bfloat16*>(D),
301
+ M, N, K, alpha);
302
+ cudaError_t err = cudaGetLastError();
303
+ return (err == cudaSuccess) ? 0 : 1;
304
+ }
305
+
306
+ } // namespace
307
+
308
+ #define DEFINE(NAME, BM, BN, BK, W, S) \
309
+ int NAME(const void* A, const void* B, void* D, int M, int N, int K, \
310
+ float alpha, cudaStream_t stream) { \
311
+ return launch_<BM, BN, BK, W, S, 4>(A, B, D, M, N, K, alpha, stream); \
312
+ }
313
+ #define DEFINE_BIG(NAME, BM, BN, BK, W, S) \
314
+ int NAME(const void* A, const void* B, void* D, int M, int N, int K, \
315
+ float alpha, cudaStream_t stream) { \
316
+ return launch_<BM, BN, BK, W, S, 1>(A, B, D, M, N, K, alpha, stream); \
317
+ }
318
+
319
+ DEFINE(ld_fp8_gemm_16x64x128_w4, 16, 64, 128, 4, 2)
320
+ DEFINE(ld_fp8_gemm_16x128x128_w4, 16, 128, 128, 4, 2)
321
+ DEFINE(ld_fp8_gemm_16x256x128_w8, 16, 256, 128, 8, 2)
322
+ DEFINE(ld_fp8_gemm_32x64x128_w4, 32, 64, 128, 4, 2)
323
+ DEFINE(ld_fp8_gemm_32x128x128_w4, 32, 128, 128, 4, 2)
324
+ DEFINE(ld_fp8_gemm_32x128x128_w8, 32, 128, 128, 8, 2)
325
+
326
+ DEFINE(ld_fp8_gemm_16x64x128_w4_s3, 16, 64, 128, 4, 3)
327
+ DEFINE(ld_fp8_gemm_16x128x128_w4_s3,16, 128, 128, 4, 3)
328
+ DEFINE(ld_fp8_gemm_32x64x128_w4_s3, 32, 64, 128, 4, 3)
329
+ DEFINE(ld_fp8_gemm_32x128x128_w4_s3,32, 128, 128, 4, 3)
330
+
331
+ DEFINE(ld_fp8_gemm_16x192x128_w4, 16, 192, 128, 4, 2)
332
+ DEFINE(ld_fp8_gemm_32x192x128_w4, 32, 192, 128, 4, 2)
333
+
334
+ DEFINE(ld_fp8_gemm_16x64x128_w4_s4, 16, 64, 128, 4, 4)
335
+ DEFINE(ld_fp8_gemm_16x64x128_w4_s5, 16, 64, 128, 4, 5)
336
+ DEFINE(ld_fp8_gemm_32x64x128_w4_s4, 32, 64, 128, 4, 4)
337
+ DEFINE(ld_fp8_gemm_32x64x128_w4_s5, 32, 64, 128, 4, 5)
338
+ DEFINE(ld_fp8_gemm_16x128x128_w4_s4,16, 128, 128, 4, 4)
339
+ DEFINE(ld_fp8_gemm_32x128x128_w4_s4,32, 128, 128, 4, 4)
340
+
341
+ // BK=256 variants — large K-tile, fewer K-iters, more compute per CTA.
342
+ DEFINE(ld_fp8_gemm_16x64x256_w4, 16, 64, 256, 4, 2)
343
+ DEFINE(ld_fp8_gemm_16x128x256_w4, 16, 128, 256, 4, 2)
344
+ DEFINE(ld_fp8_gemm_32x64x256_w4, 32, 64, 256, 4, 2)
345
+ DEFINE(ld_fp8_gemm_32x128x256_w4, 32, 128, 256, 4, 2)
346
+ DEFINE(ld_fp8_gemm_16x64x256_w4_s3, 16, 64, 256, 4, 3)
347
+
348
+ // BK=64 variants — small K-tile, more K-iters, finer pipeline grain.
349
+ DEFINE(ld_fp8_gemm_16x64x64_w4, 16, 64, 64, 4, 2)
350
+ DEFINE(ld_fp8_gemm_16x128x64_w4, 16, 128, 64, 4, 2)
351
+ DEFINE(ld_fp8_gemm_32x64x64_w4, 32, 64, 64, 4, 2)
352
+ DEFINE(ld_fp8_gemm_16x64x64_w4_s3, 16, 64, 64, 4, 3)
353
+ DEFINE(ld_fp8_gemm_16x64x64_w4_s4, 16, 64, 64, 4, 4)
354
+
355
+ // und_qkv (M=188, N=9216, K=512) untried variants: bigger BM reduces
356
+ // CTA count (188/64=3 m_tiles vs 188/32=6); bigger BK reduces K_iter
357
+ // overhead; BK=512 single-iter eliminates pipeline overhead at K=512.
358
+ DEFINE(ld_fp8_gemm_64x64x128_w4, 64, 64, 128, 4, 2)
359
+ DEFINE(ld_fp8_gemm_64x128x128_w4, 64, 128, 128, 4, 2)
360
+ DEFINE(ld_fp8_gemm_64x64x256_w4, 64, 64, 256, 4, 2)
361
+ DEFINE(ld_fp8_gemm_64x128x256_w4, 64, 128, 256, 4, 2)
362
+ DEFINE(ld_fp8_gemm_64x64x256_w4_s3, 64, 64, 256, 4, 3)
363
+ DEFINE(ld_fp8_gemm_32x64x256_w4_s3, 32, 64, 256, 4, 3)
364
+ DEFINE(ld_fp8_gemm_32x128x256_w4_s3,32, 128, 256, 4, 3)
365
+ DEFINE(ld_fp8_gemm_128x64x128_w4, 128, 64, 128, 4, 2)
366
+ DEFINE(ld_fp8_gemm_128x128x128_w4, 128, 128, 128, 4, 2)
367
+
368
+ // BK=512 single-iter benched 14.35us, worse than BK=256 pipelined 12.35us.
369
+ // Kept the relaxed static_assert (BK=512 now allowed) but no variants
370
+ // instantiated — they lose to existing BK=256 cp.async pipeline.
371
+
372
+ #undef DEFINE
373
+ #undef DEFINE_BIG
374
+
375
+ } // namespace smallM_ld
376
+ } // namespace gemm
377
+ } // namespace flash_rt
csrc/fp8_smallM_handtuned_ldmatrix_sm120.cuh ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ //
3
+ // Hand-tuned FP8 e4m3 GEMM v2 — adds 128B swizzle smem layout + ldmatrix.x4
4
+ // loads to clear bank conflicts that bottleneck v1 (`fp8_smallM_handtuned`).
5
+ //
6
+ // All variants restricted to BLOCK_K = 128 (natural 128B swizzle stride).
7
+
8
+ #pragma once
9
+ #include <cuda_runtime.h>
10
+
11
+ namespace flash_rt {
12
+ namespace gemm {
13
+ namespace smallM_ld {
14
+
15
+ #define DECL(NAME) \
16
+ int NAME(const void* A, const void* B, void* D, int M, int N, int K, \
17
+ float alpha, cudaStream_t stream)
18
+
19
+ DECL(ld_fp8_gemm_16x64x128_w4);
20
+ DECL(ld_fp8_gemm_16x128x128_w4);
21
+ DECL(ld_fp8_gemm_16x256x128_w8);
22
+ DECL(ld_fp8_gemm_32x64x128_w4);
23
+ DECL(ld_fp8_gemm_32x128x128_w4);
24
+ DECL(ld_fp8_gemm_32x128x128_w8);
25
+
26
+ DECL(ld_fp8_gemm_16x64x128_w4_s3);
27
+ DECL(ld_fp8_gemm_16x128x128_w4_s3);
28
+ DECL(ld_fp8_gemm_32x64x128_w4_s3);
29
+ DECL(ld_fp8_gemm_32x128x128_w4_s3);
30
+
31
+ DECL(ld_fp8_gemm_16x192x128_w4);
32
+ DECL(ld_fp8_gemm_32x192x128_w4);
33
+
34
+ DECL(ld_fp8_gemm_16x64x128_w4_s4);
35
+ DECL(ld_fp8_gemm_16x64x128_w4_s5);
36
+ DECL(ld_fp8_gemm_32x64x128_w4_s4);
37
+ DECL(ld_fp8_gemm_32x64x128_w4_s5);
38
+ DECL(ld_fp8_gemm_16x128x128_w4_s4);
39
+ DECL(ld_fp8_gemm_32x128x128_w4_s4);
40
+
41
+ DECL(ld_fp8_gemm_16x64x256_w4);
42
+ DECL(ld_fp8_gemm_16x128x256_w4);
43
+ DECL(ld_fp8_gemm_32x64x256_w4);
44
+ DECL(ld_fp8_gemm_32x128x256_w4);
45
+ DECL(ld_fp8_gemm_16x64x256_w4_s3);
46
+
47
+ DECL(ld_fp8_gemm_16x64x64_w4);
48
+ DECL(ld_fp8_gemm_16x128x64_w4);
49
+ DECL(ld_fp8_gemm_32x64x64_w4);
50
+ DECL(ld_fp8_gemm_16x64x64_w4_s3);
51
+ DECL(ld_fp8_gemm_16x64x64_w4_s4);
52
+
53
+ // und_qkv attack variants (M=188, K=512)
54
+ DECL(ld_fp8_gemm_64x64x128_w4);
55
+ DECL(ld_fp8_gemm_64x128x128_w4);
56
+ DECL(ld_fp8_gemm_64x64x256_w4);
57
+ DECL(ld_fp8_gemm_64x128x256_w4);
58
+ DECL(ld_fp8_gemm_64x64x256_w4_s3);
59
+ DECL(ld_fp8_gemm_32x64x256_w4_s3);
60
+ DECL(ld_fp8_gemm_32x128x256_w4_s3);
61
+ DECL(ld_fp8_gemm_128x64x128_w4);
62
+ DECL(ld_fp8_gemm_128x128x128_w4);
63
+
64
+ #undef DECL
65
+
66
+ } // namespace smallM_ld
67
+ } // namespace gemm
68
+ } // namespace flash_rt
csrc/fp8_smallM_handtuned_sm120.cu ADDED
@@ -0,0 +1,338 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ //
3
+ // Hand-tuned FP8 e4m3 -> BF16 GEMM for sm_120a small-M motus shapes.
4
+ // Inline-PTX m16n8k32 mma + 2-stage cp.async pipeline, no cutlass collective
5
+ // builder overhead. Modeled after V5split kernel_A pattern; epilogue is just
6
+ // alpha * acc -> BF16 (no bias / GELU / quant).
7
+ //
8
+ // Motivation: cutlass scaffold has ~8 us launch/setup floor on sm_120 for
9
+ // small-M kernels, even with smallest tiles. cuBLASLt nvjet ~5 us. To break
10
+ // below cuBLASLt, must avoid the scaffold entirely.
11
+ //
12
+ // Per-tensor scale (A_scale, W_scale as float scalars folded into alpha).
13
+ // Returns 0 on success.
14
+
15
+ #include "fp8_smallM_handtuned_sm120.cuh"
16
+
17
+ #include <cuda_bf16.h>
18
+ #include <cuda_fp8.h>
19
+ #include <cuda_runtime.h>
20
+ #include <cstdint>
21
+
22
+ namespace flash_rt {
23
+ namespace gemm {
24
+ namespace smallM_hand {
25
+
26
+ namespace {
27
+
28
+ __device__ __forceinline__ void mma_m16n8k32_e4m3(
29
+ float &d0, float &d1, float &d2, float &d3,
30
+ uint32_t a0, uint32_t a1, uint32_t a2, uint32_t a3,
31
+ uint32_t b0, uint32_t b1)
32
+ {
33
+ asm volatile(
34
+ "mma.sync.aligned.kind::f8f6f4.m16n8k32.row.col.f32.e4m3.e4m3.f32 "
35
+ "{%0, %1, %2, %3}, {%4, %5, %6, %7}, {%8, %9}, {%0, %1, %2, %3};\n"
36
+ : "+f"(d0), "+f"(d1), "+f"(d2), "+f"(d3)
37
+ : "r"(a0), "r"(a1), "r"(a2), "r"(a3), "r"(b0), "r"(b1));
38
+ }
39
+
40
+ __device__ __forceinline__ void cp_async_16(uint32_t smem, const uint8_t* src) {
41
+ int b = (src == nullptr) ? 0 : 16;
42
+ asm volatile("cp.async.ca.shared.global [%0], [%1], 16, %2;\n"
43
+ :: "r"(smem), "l"(src), "r"(b));
44
+ }
45
+
46
+ __device__ __forceinline__ uint32_t to_smem(const void* p) {
47
+ return static_cast<uint32_t>(__cvta_generic_to_shared(p));
48
+ }
49
+
50
+ // Generic FP8 GEMM kernel parameterized on tile shape + pipeline stages.
51
+ // - A: [M, K] row-major FP8 e4m3
52
+ // - B: [N, K] row-major FP8 e4m3 (= W.T col-major layout)
53
+ // - D: [M, N] row-major BF16
54
+ // - alpha = a_scale * w_scale (per-tensor)
55
+ // - STAGES = pipeline depth (2 or 3)
56
+ // - MIN_BLOCKS_PER_SM = launch_bounds hint (1 for big-smem variants)
57
+ template <int BLOCK_M, int BLOCK_N, int BLOCK_K, int NUM_WARPS,
58
+ int STAGES = 2, int MIN_BLOCKS_PER_SM = 4>
59
+ __global__ __launch_bounds__(NUM_WARPS * 32, MIN_BLOCKS_PER_SM)
60
+ void fp8_gemm_kernel(
61
+ const __nv_fp8_e4m3* __restrict__ A,
62
+ const __nv_fp8_e4m3* __restrict__ B,
63
+ __nv_bfloat16* __restrict__ D,
64
+ int M, int N, int K,
65
+ float alpha)
66
+ {
67
+ static_assert(BLOCK_K % 32 == 0, "BLOCK_K must be multiple of 32");
68
+ static_assert(BLOCK_N % 8 == 0, "BLOCK_N must be multiple of 8");
69
+ static_assert(BLOCK_M % 16 == 0, "BLOCK_M must be multiple of 16 (mma m=16)");
70
+ static_assert((BLOCK_N / 8) % NUM_WARPS == 0, "N-atoms must split evenly across warps");
71
+
72
+ constexpr int THREADS = NUM_WARPS * 32;
73
+ constexpr int M_ATOMS = BLOCK_M / 16; // m-atom rows per CTA
74
+ constexpr int N_ATOMS = BLOCK_N / 8;
75
+ constexpr int N_ATOMS_PW = N_ATOMS / NUM_WARPS;
76
+ constexpr int K_ATOMS = BLOCK_K / 32;
77
+ constexpr int SMEM_K_PAD = BLOCK_K + 16; // +16 byte padding to avoid bank conflict
78
+
79
+ // smem layout: [stage][row][col_padded]; STAGES stages.
80
+ extern __shared__ uint8_t smem_raw[];
81
+ uint8_t* A_smem = smem_raw;
82
+ uint8_t* B_smem = A_smem + STAGES * BLOCK_M * SMEM_K_PAD;
83
+
84
+ const int cta_m = blockIdx.x;
85
+ const int cta_n = blockIdx.y;
86
+ const int m_base = cta_m * BLOCK_M;
87
+ const int n_base = cta_n * BLOCK_N;
88
+
89
+ const int t = threadIdx.x;
90
+ const int warp_id = t / 32;
91
+ const int lane = t % 32;
92
+ const int l = lane % 4;
93
+ const int h = lane / 4;
94
+
95
+ auto issue_load = [&](int stage, int k_base) {
96
+ // Load A [BLOCK_M, BLOCK_K] FP8 = BLOCK_M * BLOCK_K bytes.
97
+ constexpr int A_TOTAL_16B = BLOCK_M * BLOCK_K / 16;
98
+ constexpr int A_ITERS = (A_TOTAL_16B + THREADS - 1) / THREADS;
99
+ #pragma unroll
100
+ for (int it = 0; it < A_ITERS; ++it) {
101
+ int idx = it * THREADS + t;
102
+ if (idx >= A_TOTAL_16B) break;
103
+ int row_a = idx / (BLOCK_K / 16);
104
+ int koff_a = (idx % (BLOCK_K / 16)) * 16;
105
+ int m_glob = m_base + row_a;
106
+ int k_glob = k_base + koff_a;
107
+ const uint8_t* a_src = nullptr;
108
+ if (m_glob < M && k_glob < K) {
109
+ a_src = reinterpret_cast<const uint8_t*>(&A[m_glob * K + k_glob]);
110
+ }
111
+ cp_async_16(
112
+ to_smem(&A_smem[stage * BLOCK_M * SMEM_K_PAD
113
+ + row_a * SMEM_K_PAD + koff_a]),
114
+ a_src);
115
+ }
116
+ // Load B [BLOCK_N, BLOCK_K] FP8 (B is [N, K] row-major).
117
+ constexpr int B_TOTAL_16B = BLOCK_N * BLOCK_K / 16;
118
+ constexpr int B_ITERS = (B_TOTAL_16B + THREADS - 1) / THREADS;
119
+ #pragma unroll
120
+ for (int it = 0; it < B_ITERS; ++it) {
121
+ int idx = it * THREADS + t;
122
+ if (idx >= B_TOTAL_16B) break;
123
+ int row_b = idx / (BLOCK_K / 16);
124
+ int koff_b = (idx % (BLOCK_K / 16)) * 16;
125
+ int n_glob = n_base + row_b;
126
+ int k_glob = k_base + koff_b;
127
+ const uint8_t* b_src = nullptr;
128
+ if (n_glob < N && k_glob < K) {
129
+ b_src = reinterpret_cast<const uint8_t*>(&B[n_glob * K + k_glob]);
130
+ }
131
+ cp_async_16(
132
+ to_smem(&B_smem[stage * BLOCK_N * SMEM_K_PAD
133
+ + row_b * SMEM_K_PAD + koff_b]),
134
+ b_src);
135
+ }
136
+ };
137
+
138
+ // Per-warp accumulators: M_ATOMS * N_ATOMS_PW * 4 fp32
139
+ float acc[M_ATOMS][N_ATOMS_PW][4];
140
+ #pragma unroll
141
+ for (int mi = 0; mi < M_ATOMS; ++mi) {
142
+ #pragma unroll
143
+ for (int ni = 0; ni < N_ATOMS_PW; ++ni) {
144
+ #pragma unroll
145
+ for (int j = 0; j < 4; ++j) acc[mi][ni][j] = 0.0f;
146
+ }
147
+ }
148
+
149
+ // Prefetch STAGES-1 chunks before main loop (deep pipeline).
150
+ const int K_ITERS = (K + BLOCK_K - 1) / BLOCK_K;
151
+ #pragma unroll
152
+ for (int s = 0; s < STAGES - 1; ++s) {
153
+ int kb = s * BLOCK_K;
154
+ if (kb < K) issue_load(s, kb);
155
+ asm volatile("cp.async.commit_group;\n" ::);
156
+ }
157
+
158
+ int compute_stage = 0;
159
+ for (int k_iter = 0; k_iter < K_ITERS; ++k_iter) {
160
+ int k_base = k_iter * BLOCK_K;
161
+ // Issue next load STAGES-1 ahead.
162
+ int issue_iter = k_iter + (STAGES - 1);
163
+ int issue_stage = issue_iter % STAGES;
164
+ if (issue_iter < K_ITERS) issue_load(issue_stage, issue_iter * BLOCK_K);
165
+ asm volatile("cp.async.commit_group;\n" ::);
166
+ // Wait until STAGES-1 prior loads are still in flight, current ready.
167
+ asm volatile("cp.async.wait_group %0;\n" :: "n"(STAGES - 1));
168
+ __syncthreads();
169
+
170
+ #pragma unroll
171
+ for (int k_iter = 0; k_iter < K_ATOMS; ++k_iter) {
172
+ int kA0 = k_iter * 32 + 4 * l;
173
+ int kA2 = k_iter * 32 + 4 * l + 16;
174
+ #pragma unroll
175
+ for (int mi = 0; mi < M_ATOMS; ++mi) {
176
+ int rA0 = mi * 16 + h;
177
+ int rA1 = mi * 16 + h + 8;
178
+ uint32_t A0 = *reinterpret_cast<const uint32_t*>(
179
+ &A_smem[compute_stage * BLOCK_M * SMEM_K_PAD + rA0 * SMEM_K_PAD + kA0]);
180
+ uint32_t A1 = *reinterpret_cast<const uint32_t*>(
181
+ &A_smem[compute_stage * BLOCK_M * SMEM_K_PAD + rA1 * SMEM_K_PAD + kA0]);
182
+ uint32_t A2 = *reinterpret_cast<const uint32_t*>(
183
+ &A_smem[compute_stage * BLOCK_M * SMEM_K_PAD + rA0 * SMEM_K_PAD + kA2]);
184
+ uint32_t A3 = *reinterpret_cast<const uint32_t*>(
185
+ &A_smem[compute_stage * BLOCK_M * SMEM_K_PAD + rA1 * SMEM_K_PAD + kA2]);
186
+ #pragma unroll
187
+ for (int ni = 0; ni < N_ATOMS_PW; ++ni) {
188
+ int co_n = warp_id * N_ATOMS_PW * 8 + ni * 8 + h;
189
+ uint32_t B0 = *reinterpret_cast<const uint32_t*>(
190
+ &B_smem[compute_stage * BLOCK_N * SMEM_K_PAD + co_n * SMEM_K_PAD + kA0]);
191
+ uint32_t B1 = *reinterpret_cast<const uint32_t*>(
192
+ &B_smem[compute_stage * BLOCK_N * SMEM_K_PAD + co_n * SMEM_K_PAD + kA2]);
193
+ mma_m16n8k32_e4m3(
194
+ acc[mi][ni][0], acc[mi][ni][1], acc[mi][ni][2], acc[mi][ni][3],
195
+ A0, A1, A2, A3, B0, B1);
196
+ }
197
+ }
198
+ }
199
+ compute_stage = (compute_stage + 1) % STAGES;
200
+ }
201
+ asm volatile("cp.async.wait_all;\n" ::);
202
+
203
+ // Epilogue: alpha * acc -> BF16 -> HBM.
204
+ // m16n8 layout: thread (h, l): rows {h, h+8}, cols {2*l, 2*l+1}.
205
+ #pragma unroll
206
+ for (int mi = 0; mi < M_ATOMS; ++mi) {
207
+ int row0 = m_base + mi * 16 + h;
208
+ int row1 = row0 + 8;
209
+ #pragma unroll
210
+ for (int ni = 0; ni < N_ATOMS_PW; ++ni) {
211
+ int n_pair_base = n_base + warp_id * N_ATOMS_PW * 8 + ni * 8 + 2 * l;
212
+ #pragma unroll
213
+ for (int j = 0; j < 4; ++j) {
214
+ int row = (j < 2) ? row0 : row1;
215
+ int col = n_pair_base + (j & 1);
216
+ if (row < M && col < N) {
217
+ float v = acc[mi][ni][j] * alpha;
218
+ D[row * N + col] = __float2bfloat16(v);
219
+ }
220
+ }
221
+ }
222
+ }
223
+ }
224
+
225
+ template <int BM, int BN, int BK, int W, int STAGES = 2, int MIN_BLK = 4>
226
+ int launch_(const void* A, const void* B, void* D,
227
+ int M, int N, int K, float alpha, cudaStream_t s)
228
+ {
229
+ int grid_m = (M + BM - 1) / BM;
230
+ int grid_n = (N + BN - 1) / BN;
231
+ dim3 grid(grid_m, grid_n, 1);
232
+ dim3 block(W * 32, 1, 1);
233
+ int smem_bytes = STAGES * (BM + BN) * (BK + 16);
234
+ // sm_120 default dynamic smem is 48 KB; opt-in to higher (up to ~228 KB).
235
+ if (smem_bytes > 48 * 1024) {
236
+ cudaFuncSetAttribute(
237
+ (const void*)&fp8_gemm_kernel<BM, BN, BK, W, STAGES, MIN_BLK>,
238
+ cudaFuncAttributeMaxDynamicSharedMemorySize, smem_bytes);
239
+ }
240
+ fp8_gemm_kernel<BM, BN, BK, W, STAGES, MIN_BLK><<<grid, block, smem_bytes, s>>>(
241
+ reinterpret_cast<const __nv_fp8_e4m3*>(A),
242
+ reinterpret_cast<const __nv_fp8_e4m3*>(B),
243
+ reinterpret_cast<__nv_bfloat16*>(D),
244
+ M, N, K, alpha);
245
+ cudaError_t err = cudaGetLastError();
246
+ return (err == cudaSuccess) ? 0 : 1;
247
+ }
248
+
249
+ } // namespace
250
+
251
+ // Variant instantiations.
252
+ #define DEFINE(NAME, BM, BN, BK, W, S) \
253
+ int NAME(const void* A, const void* B, void* D, int M, int N, int K, \
254
+ float alpha, cudaStream_t stream) { \
255
+ return launch_<BM, BN, BK, W, S, 4>(A, B, D, M, N, K, alpha, stream); \
256
+ }
257
+ // Big-smem variant — uses MIN_BLOCKS_PER_SM=1 to relax register pressure.
258
+ #define DEFINE_BIG(NAME, BM, BN, BK, W, S) \
259
+ int NAME(const void* A, const void* B, void* D, int M, int N, int K, \
260
+ float alpha, cudaStream_t stream) { \
261
+ return launch_<BM, BN, BK, W, S, 1>(A, B, D, M, N, K, alpha, stream); \
262
+ }
263
+
264
+ // 2-stage pipeline baseline.
265
+ DEFINE(fp8_gemm_16x64x128_w4, 16, 64, 128, 4, 2)
266
+ DEFINE(fp8_gemm_16x128x128_w4, 16, 128, 128, 4, 2)
267
+ DEFINE(fp8_gemm_16x256x128_w8, 16, 256, 128, 8, 2)
268
+ DEFINE(fp8_gemm_32x64x128_w4, 32, 64, 128, 4, 2)
269
+ DEFINE(fp8_gemm_32x128x128_w4, 32, 128, 128, 4, 2)
270
+ DEFINE(fp8_gemm_32x128x128_w8, 32, 128, 128, 8, 2)
271
+
272
+ // 3-stage pipeline (better cp.async overlap).
273
+ DEFINE(fp8_gemm_16x64x128_w4_s3, 16, 64, 128, 4, 3)
274
+ DEFINE(fp8_gemm_16x128x128_w4_s3, 16, 128, 128, 4, 3)
275
+ DEFINE(fp8_gemm_32x64x128_w4_s3, 32, 64, 128, 4, 3)
276
+ DEFINE(fp8_gemm_32x128x128_w4_s3, 32, 128, 128, 4, 3)
277
+
278
+ // BLOCK_K=256 (fewer K-iters, bigger cp.async chunks per iter).
279
+ DEFINE(fp8_gemm_16x64x256_w4, 16, 64, 256, 4, 2)
280
+ DEFINE(fp8_gemm_16x128x256_w4, 16, 128, 256, 4, 2)
281
+ DEFINE(fp8_gemm_32x64x256_w4, 32, 64, 256, 4, 2)
282
+ DEFINE(fp8_gemm_32x128x256_w4, 32, 128, 256, 4, 2)
283
+
284
+ // Wider BLOCK_N for big-N shapes (action_qkv, und_qkv: N=9216).
285
+ DEFINE(fp8_gemm_16x192x128_w4, 16, 192, 128, 4, 2)
286
+ DEFINE(fp8_gemm_16x192x128_w8, 16, 192, 128, 8, 2)
287
+ DEFINE(fp8_gemm_32x192x128_w4, 32, 192, 128, 4, 2)
288
+
289
+ // 4-stage pipeline.
290
+ DEFINE(fp8_gemm_16x64x128_w4_s4, 16, 64, 128, 4, 4)
291
+ DEFINE(fp8_gemm_32x64x128_w4_s4, 32, 64, 128, 4, 4)
292
+
293
+ // Wider BLOCK_N=384 (needs N % 384, 8-warp config).
294
+ DEFINE(fp8_gemm_16x384x128_w8, 16, 384, 128, 8, 2)
295
+ DEFINE(fp8_gemm_32x384x128_w8, 32, 384, 128, 8, 2)
296
+
297
+ // More warps, smaller BLOCK_N (more N-tiles parallelism per CTA).
298
+ DEFINE(fp8_gemm_16x64x128_w8, 16, 64, 128, 8, 2)
299
+ DEFINE(fp8_gemm_32x64x128_w8, 32, 64, 128, 8, 2)
300
+
301
+ // 32x64x128 with 8-stage pipeline (deep cp.async overlap for K-bound shapes).
302
+ DEFINE(fp8_gemm_32x64x128_w4_s5, 32, 64, 128, 4, 5)
303
+
304
+ // BLOCK_K=64 variants — better pipeline overlap for K-small shapes (K=512).
305
+ DEFINE(fp8_gemm_16x64x64_w4, 16, 64, 64, 4, 2)
306
+ DEFINE(fp8_gemm_16x128x64_w4, 16, 128, 64, 4, 2)
307
+ DEFINE(fp8_gemm_32x64x64_w4, 32, 64, 64, 4, 2)
308
+ DEFINE(fp8_gemm_32x128x64_w4, 32, 128, 64, 4, 2)
309
+ DEFINE(fp8_gemm_16x64x64_w4_s3, 16, 64, 64, 4, 3)
310
+ DEFINE(fp8_gemm_16x64x64_w4_s4, 16, 64, 64, 4, 4)
311
+
312
+ // Big-smem (BLOCK_N=384/512) — MIN_BLOCKS_PER_SM=1 relaxes register pressure.
313
+ // Targets multi-wave shapes (und_qkv: 9216 N) to reduce wave count.
314
+ DEFINE_BIG(fp8_gemm_16x384x128_w4_big, 16, 384, 128, 4, 2)
315
+ DEFINE_BIG(fp8_gemm_32x384x128_w4_big, 32, 384, 128, 4, 2)
316
+ DEFINE_BIG(fp8_gemm_16x512x128_w8_big, 16, 512, 128, 8, 2)
317
+ DEFINE_BIG(fp8_gemm_16x256x128_w4_big, 16, 256, 128, 4, 2)
318
+ DEFINE_BIG(fp8_gemm_32x256x128_w4_big, 32, 256, 128, 4, 2)
319
+
320
+ // BLOCK_M=64 / 128 variants — for M=138 shapes to drop wave count to 1.
321
+ // und_qkv (M=138 N=9216): BLOCK_M=64 -> 3 M-tiles, BLOCK_M=128 -> 2 M-tiles.
322
+ // Combined with BLOCK_N=128: 216 / 144 total CTAs => 1-1.3 waves on 170 SMs.
323
+ DEFINE(fp8_gemm_64x64x128_w4, 64, 64, 128, 4, 2)
324
+ DEFINE(fp8_gemm_64x128x128_w4, 64, 128, 128, 4, 2)
325
+ DEFINE(fp8_gemm_64x128x128_w8, 64, 128, 128, 8, 2)
326
+ DEFINE(fp8_gemm_128x64x128_w4, 128, 64, 128, 4, 2)
327
+ DEFINE(fp8_gemm_128x128x128_w4, 128, 128, 128, 4, 2)
328
+ DEFINE(fp8_gemm_128x128x128_w8, 128, 128, 128, 8, 2)
329
+ DEFINE_BIG(fp8_gemm_64x256x128_w4_big, 64, 256, 128, 4, 2)
330
+ DEFINE_BIG(fp8_gemm_64x256x128_w8_big, 64, 256, 128, 8, 2)
331
+ DEFINE_BIG(fp8_gemm_128x256x128_w8_big, 128, 256, 128, 8, 2)
332
+
333
+ #undef DEFINE
334
+ #undef DEFINE_BIG
335
+
336
+ } // namespace smallM_hand
337
+ } // namespace gemm
338
+ } // namespace flash_rt
csrc/fp8_smallM_handtuned_sm120.cuh ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ #pragma once
3
+
4
+ #include <cuda_runtime.h>
5
+
6
+ namespace flash_rt {
7
+ namespace gemm {
8
+ namespace smallM_hand {
9
+
10
+ // Hand-tuned FP8 e4m3 -> BF16 GEMM for sm_120a small-M motus shapes.
11
+ // Inputs: FP8 A [M,K] row-major, FP8 B [N,K] row-major (= W.T), BF16 D [M,N].
12
+ // alpha = a_scale * w_scale (per-tensor).
13
+ // Returns 0 on success.
14
+
15
+ #define DECL(NAME) \
16
+ int NAME(const void* A, const void* B, void* D, \
17
+ int M, int N, int K, float alpha, cudaStream_t stream)
18
+
19
+ // 2-stage pipeline.
20
+ DECL(fp8_gemm_16x64x128_w4);
21
+ DECL(fp8_gemm_16x128x128_w4);
22
+ DECL(fp8_gemm_16x256x128_w8);
23
+ DECL(fp8_gemm_32x64x128_w4);
24
+ DECL(fp8_gemm_32x128x128_w4);
25
+ DECL(fp8_gemm_32x128x128_w8);
26
+
27
+ // 3-stage pipeline.
28
+ DECL(fp8_gemm_16x64x128_w4_s3);
29
+ DECL(fp8_gemm_16x128x128_w4_s3);
30
+ DECL(fp8_gemm_32x64x128_w4_s3);
31
+ DECL(fp8_gemm_32x128x128_w4_s3);
32
+
33
+ // BLOCK_K=256.
34
+ DECL(fp8_gemm_16x64x256_w4);
35
+ DECL(fp8_gemm_16x128x256_w4);
36
+ DECL(fp8_gemm_32x64x256_w4);
37
+ DECL(fp8_gemm_32x128x256_w4);
38
+
39
+ // BLOCK_N=192 (for N=9216 shapes).
40
+ DECL(fp8_gemm_16x192x128_w4);
41
+ DECL(fp8_gemm_16x192x128_w8);
42
+ DECL(fp8_gemm_32x192x128_w4);
43
+
44
+ // 4-stage pipeline.
45
+ DECL(fp8_gemm_16x64x128_w4_s4);
46
+ DECL(fp8_gemm_32x64x128_w4_s4);
47
+
48
+ // Wider BLOCK_N=384 (needs N % 384).
49
+ DECL(fp8_gemm_16x384x128_w8);
50
+ DECL(fp8_gemm_32x384x128_w8);
51
+
52
+ // 8-warp variants of 16x64 / 32x64.
53
+ DECL(fp8_gemm_16x64x128_w8);
54
+ DECL(fp8_gemm_32x64x128_w8);
55
+
56
+ // 5-stage pipeline.
57
+ DECL(fp8_gemm_32x64x128_w4_s5);
58
+
59
+ // BLOCK_K=64.
60
+ DECL(fp8_gemm_16x64x64_w4);
61
+ DECL(fp8_gemm_16x128x64_w4);
62
+ DECL(fp8_gemm_32x64x64_w4);
63
+ DECL(fp8_gemm_32x128x64_w4);
64
+ DECL(fp8_gemm_16x64x64_w4_s3);
65
+ DECL(fp8_gemm_16x64x64_w4_s4);
66
+
67
+ // Big-smem BLOCK_N variants.
68
+ DECL(fp8_gemm_16x384x128_w4_big);
69
+ DECL(fp8_gemm_32x384x128_w4_big);
70
+ DECL(fp8_gemm_16x512x128_w8_big);
71
+ DECL(fp8_gemm_16x256x128_w4_big);
72
+ DECL(fp8_gemm_32x256x128_w4_big);
73
+
74
+ // BLOCK_M=64 / 128 — wave reduction for M=138 shapes (und_qkv main target).
75
+ DECL(fp8_gemm_64x64x128_w4);
76
+ DECL(fp8_gemm_64x128x128_w4);
77
+ DECL(fp8_gemm_64x128x128_w8);
78
+ DECL(fp8_gemm_128x64x128_w4);
79
+ DECL(fp8_gemm_128x128x128_w4);
80
+ DECL(fp8_gemm_128x128x128_w8);
81
+ DECL(fp8_gemm_64x256x128_w4_big);
82
+ DECL(fp8_gemm_64x256x128_w8_big);
83
+ DECL(fp8_gemm_128x256x128_w8_big);
84
+
85
+ #undef DECL
86
+
87
+ } // namespace smallM_hand
88
+ } // namespace gemm
89
+ } // namespace flash_rt
csrc/gemm_types_sm110.h ADDED
@@ -0,0 +1,362 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ //
3
+ // CUTLASS SM100-family FP8 GEMM templates used by the SM110a Thor build.
4
+ // The public package path consumes the BF16-output Sq/T1/Wide variants below;
5
+ // the FP16 and FP32 variants remain available to the native implementation.
6
+ // T1/T2 use explicit TmaWarpSpecialized2Sm scheduling for tactic control.
7
+ #pragma once
8
+
9
+ #include "cutlass/cutlass.h"
10
+ #include "cute/tensor.hpp"
11
+ #include "cutlass/gemm/dispatch_policy.hpp"
12
+ #include "cutlass/gemm/collective/collective_builder.hpp"
13
+ #include "cutlass/epilogue/dispatch_policy.hpp"
14
+ #include "cutlass/epilogue/collective/collective_builder.hpp"
15
+ #include "cutlass/epilogue/fusion/operations.hpp"
16
+ #include "cutlass/gemm/device/gemm_universal_adapter.h"
17
+ #include "cutlass/gemm/kernel/gemm_universal.hpp"
18
+ #include "cutlass/epilogue/thread/activation.h"
19
+ #include "cutlass/util/packed_stride.hpp"
20
+
21
+ using namespace cute;
22
+
23
+ // Type aliases
24
+ using cutlass_fp8 = cutlass::float_e4m3_t;
25
+ using cutlass_fp16 = cutlass::half_t;
26
+
27
+ // ============================================================
28
+ // PlainGemm: 256×128×64, Cluster 2×2×1
29
+ // Standard FP8→FP16 GEMM (Identity epilogue)
30
+ // ============================================================
31
+ namespace sm100_plain {
32
+ using Tile = Shape<_256, _128, _64>;
33
+ using Cluster = Shape<_2, _2, _1>;
34
+ using Fusion = cutlass::epilogue::fusion::LinCombEltAct<
35
+ cutlass::epilogue::thread::Identity, cutlass_fp16, float>;
36
+ using Epi = typename cutlass::epilogue::collective::CollectiveBuilder<
37
+ cutlass::arch::Sm100, cutlass::arch::OpClassTensorOp,
38
+ Tile, Cluster, cutlass::epilogue::collective::EpilogueTileAuto,
39
+ float, float, cutlass_fp16, cutlass::layout::RowMajor, 8,
40
+ cutlass_fp16, cutlass::layout::RowMajor, 8,
41
+ cutlass::epilogue::collective::EpilogueScheduleAuto, Fusion>::CollectiveOp;
42
+ using Main = typename cutlass::gemm::collective::CollectiveBuilder<
43
+ cutlass::arch::Sm100, cutlass::arch::OpClassTensorOp,
44
+ cutlass_fp8, cutlass::layout::RowMajor, 16,
45
+ cutlass_fp8, cutlass::layout::ColumnMajor, 16,
46
+ float, Tile, Cluster,
47
+ cutlass::gemm::collective::StageCountAutoCarveout<
48
+ static_cast<int>(sizeof(typename Epi::SharedStorage))>,
49
+ cutlass::gemm::collective::KernelScheduleAuto>::CollectiveOp;
50
+ using Gemm = cutlass::gemm::device::GemmUniversalAdapter<
51
+ cutlass::gemm::kernel::GemmUniversal<Shape<int,int,int,int>, Main, Epi>>;
52
+ } // namespace sm100_plain
53
+
54
+ // ============================================================
55
+ // GeluGemm: 256×128×64 + GELU epilogue
56
+ // ============================================================
57
+ namespace sm100_gelu {
58
+ using Tile = Shape<_256, _128, _64>;
59
+ using Cluster = Shape<_2, _2, _1>;
60
+ using Fusion = cutlass::epilogue::fusion::LinCombEltAct<
61
+ cutlass::epilogue::thread::GELU, cutlass_fp16, float>;
62
+ using Epi = typename cutlass::epilogue::collective::CollectiveBuilder<
63
+ cutlass::arch::Sm100, cutlass::arch::OpClassTensorOp,
64
+ Tile, Cluster, cutlass::epilogue::collective::EpilogueTileAuto,
65
+ float, float, cutlass_fp16, cutlass::layout::RowMajor, 8,
66
+ cutlass_fp16, cutlass::layout::RowMajor, 8,
67
+ cutlass::epilogue::collective::EpilogueScheduleAuto, Fusion>::CollectiveOp;
68
+ using Main = typename cutlass::gemm::collective::CollectiveBuilder<
69
+ cutlass::arch::Sm100, cutlass::arch::OpClassTensorOp,
70
+ cutlass_fp8, cutlass::layout::RowMajor, 16,
71
+ cutlass_fp8, cutlass::layout::ColumnMajor, 16,
72
+ float, Tile, Cluster,
73
+ cutlass::gemm::collective::StageCountAutoCarveout<
74
+ static_cast<int>(sizeof(typename Epi::SharedStorage))>,
75
+ cutlass::gemm::collective::KernelScheduleAuto>::CollectiveOp;
76
+ using Gemm = cutlass::gemm::device::GemmUniversalAdapter<
77
+ cutlass::gemm::kernel::GemmUniversal<Shape<int,int,int,int>, Main, Epi>>;
78
+ } // namespace sm100_gelu
79
+
80
+ // ============================================================
81
+ // SqGemm: 256×256×128 — deeper K pipeline for large GEMMs
82
+ // ============================================================
83
+ namespace sm100_sq {
84
+ using Tile = Shape<_256, _256, _128>;
85
+ using Cluster = Shape<_2, _2, _1>;
86
+ using Fusion = cutlass::epilogue::fusion::LinCombEltAct<
87
+ cutlass::epilogue::thread::Identity, cutlass_fp16, float>;
88
+ using Epi = typename cutlass::epilogue::collective::CollectiveBuilder<
89
+ cutlass::arch::Sm100, cutlass::arch::OpClassTensorOp,
90
+ Tile, Cluster, cutlass::epilogue::collective::EpilogueTileAuto,
91
+ float, float, cutlass_fp16, cutlass::layout::RowMajor, 8,
92
+ cutlass_fp16, cutlass::layout::RowMajor, 8,
93
+ cutlass::epilogue::collective::EpilogueScheduleAuto, Fusion>::CollectiveOp;
94
+ using Main = typename cutlass::gemm::collective::CollectiveBuilder<
95
+ cutlass::arch::Sm100, cutlass::arch::OpClassTensorOp,
96
+ cutlass_fp8, cutlass::layout::RowMajor, 16,
97
+ cutlass_fp8, cutlass::layout::ColumnMajor, 16,
98
+ float, Tile, Cluster,
99
+ cutlass::gemm::collective::StageCountAutoCarveout<
100
+ static_cast<int>(sizeof(typename Epi::SharedStorage))>,
101
+ cutlass::gemm::collective::KernelScheduleAuto>::CollectiveOp;
102
+ using Gemm = cutlass::gemm::device::GemmUniversalAdapter<
103
+ cutlass::gemm::kernel::GemmUniversal<Shape<int,int,int,int>, Main, Epi>>;
104
+ } // namespace sm100_sq
105
+
106
+ // ============================================================
107
+ // WideGemm: 256×128×128 — deeper K for FFN down projection
108
+ // ============================================================
109
+ namespace sm100_wide {
110
+ using Tile = Shape<_256, _128, _128>;
111
+ using Cluster = Shape<_2, _2, _1>;
112
+ using Fusion = cutlass::epilogue::fusion::LinCombEltAct<
113
+ cutlass::epilogue::thread::Identity, cutlass_fp16, float>;
114
+ using Epi = typename cutlass::epilogue::collective::CollectiveBuilder<
115
+ cutlass::arch::Sm100, cutlass::arch::OpClassTensorOp,
116
+ Tile, Cluster, cutlass::epilogue::collective::EpilogueTileAuto,
117
+ float, float, cutlass_fp16, cutlass::layout::RowMajor, 8,
118
+ cutlass_fp16, cutlass::layout::RowMajor, 8,
119
+ cutlass::epilogue::collective::EpilogueScheduleAuto, Fusion>::CollectiveOp;
120
+ using Main = typename cutlass::gemm::collective::CollectiveBuilder<
121
+ cutlass::arch::Sm100, cutlass::arch::OpClassTensorOp,
122
+ cutlass_fp8, cutlass::layout::RowMajor, 16,
123
+ cutlass_fp8, cutlass::layout::ColumnMajor, 16,
124
+ float, Tile, Cluster,
125
+ cutlass::gemm::collective::StageCountAutoCarveout<
126
+ static_cast<int>(sizeof(typename Epi::SharedStorage))>,
127
+ cutlass::gemm::collective::KernelScheduleAuto>::CollectiveOp;
128
+ using Gemm = cutlass::gemm::device::GemmUniversalAdapter<
129
+ cutlass::gemm::kernel::GemmUniversal<Shape<int,int,int,int>, Main, Epi>>;
130
+ } // namespace sm100_wide
131
+
132
+ // ============================================================
133
+ // T1Gemm: 128×256×128, Cluster 2×1×1, TmaWarpSpecialized2Sm
134
+ // EXACT match for Myelin's s128x256 best tactic (2SM)
135
+ // ============================================================
136
+ namespace sm100_t1 {
137
+ using Tile = Shape<_128, _256, _128>;
138
+ using Cluster = Shape<_2, _1, _1>;
139
+ using Fusion = cutlass::epilogue::fusion::LinCombEltAct<
140
+ cutlass::epilogue::thread::Identity, cutlass_fp16, float>;
141
+ using Epi = typename cutlass::epilogue::collective::CollectiveBuilder<
142
+ cutlass::arch::Sm100, cutlass::arch::OpClassTensorOp,
143
+ Tile, Cluster, cutlass::epilogue::collective::EpilogueTileAuto,
144
+ float, float, cutlass_fp16, cutlass::layout::RowMajor, 8,
145
+ cutlass_fp16, cutlass::layout::RowMajor, 8,
146
+ cutlass::epilogue::TmaWarpSpecialized2Sm, Fusion>::CollectiveOp;
147
+ using Main = typename cutlass::gemm::collective::CollectiveBuilder<
148
+ cutlass::arch::Sm100, cutlass::arch::OpClassTensorOp,
149
+ cutlass_fp8, cutlass::layout::RowMajor, 16,
150
+ cutlass_fp8, cutlass::layout::ColumnMajor, 16,
151
+ float, Tile, Cluster,
152
+ cutlass::gemm::collective::StageCountAutoCarveout<
153
+ static_cast<int>(sizeof(typename Epi::SharedStorage))>,
154
+ cutlass::gemm::collective::KernelScheduleAuto>::CollectiveOp;
155
+ using Gemm = cutlass::gemm::device::GemmUniversalAdapter<
156
+ cutlass::gemm::kernel::GemmUniversal<Shape<int,int,int,int>, Main, Epi>>;
157
+ } // namespace sm100_t1
158
+
159
+ // ============================================================
160
+ // T2Gemm: 256×256×128, Cluster 2×1×1, TmaWarpSpecialized2Sm
161
+ // ============================================================
162
+ namespace sm100_t2 {
163
+ using Tile = Shape<_256, _256, _128>;
164
+ using Cluster = Shape<_2, _1, _1>;
165
+ using Fusion = cutlass::epilogue::fusion::LinCombEltAct<
166
+ cutlass::epilogue::thread::Identity, cutlass_fp16, float>;
167
+ using Epi = typename cutlass::epilogue::collective::CollectiveBuilder<
168
+ cutlass::arch::Sm100, cutlass::arch::OpClassTensorOp,
169
+ Tile, Cluster, cutlass::epilogue::collective::EpilogueTileAuto,
170
+ float, float, cutlass_fp16, cutlass::layout::RowMajor, 8,
171
+ cutlass_fp16, cutlass::layout::RowMajor, 8,
172
+ cutlass::epilogue::TmaWarpSpecialized2Sm, Fusion>::CollectiveOp;
173
+ using Main = typename cutlass::gemm::collective::CollectiveBuilder<
174
+ cutlass::arch::Sm100, cutlass::arch::OpClassTensorOp,
175
+ cutlass_fp8, cutlass::layout::RowMajor, 16,
176
+ cutlass_fp8, cutlass::layout::ColumnMajor, 16,
177
+ float, Tile, Cluster,
178
+ cutlass::gemm::collective::StageCountAutoCarveout<
179
+ static_cast<int>(sizeof(typename Epi::SharedStorage))>,
180
+ cutlass::gemm::collective::KernelScheduleAuto>::CollectiveOp;
181
+ using Gemm = cutlass::gemm::device::GemmUniversalAdapter<
182
+ cutlass::gemm::kernel::GemmUniversal<Shape<int,int,int,int>, Main, Epi>>;
183
+ } // namespace sm100_t2
184
+
185
+ // ============================================================
186
+ // FP32 Output Variants — for models with activations > FP16 range
187
+ // (e.g., Pi0-FAST Gemma 2B deep layers where residual > 65504)
188
+ // Same tile configs, only output dtype changed: cutlass_fp16 → float
189
+ // ============================================================
190
+
191
+ using cutlass_fp32 = float;
192
+ using cutlass_bf16 = cutlass::bfloat16_t;
193
+
194
+ // ============================================================
195
+ // BF16 Output Variants — for models trained in BF16 with large activations
196
+ // Same FP8 inputs/accumulation, BF16 output (range ±3.4e38)
197
+ // ============================================================
198
+
199
+ namespace sm100_sq_bf16out {
200
+ using Tile = Shape<_256, _256, _128>;
201
+ using Cluster = Shape<_2, _2, _1>;
202
+ using Fusion = cutlass::epilogue::fusion::LinCombEltAct<
203
+ cutlass::epilogue::thread::Identity, cutlass_bf16, float>;
204
+ using Epi = typename cutlass::epilogue::collective::CollectiveBuilder<
205
+ cutlass::arch::Sm100, cutlass::arch::OpClassTensorOp,
206
+ Tile, Cluster, cutlass::epilogue::collective::EpilogueTileAuto,
207
+ float, float, cutlass_bf16, cutlass::layout::RowMajor, 8,
208
+ cutlass_bf16, cutlass::layout::RowMajor, 8,
209
+ cutlass::epilogue::collective::EpilogueScheduleAuto, Fusion>::CollectiveOp;
210
+ using Main = typename cutlass::gemm::collective::CollectiveBuilder<
211
+ cutlass::arch::Sm100, cutlass::arch::OpClassTensorOp,
212
+ cutlass_fp8, cutlass::layout::RowMajor, 16,
213
+ cutlass_fp8, cutlass::layout::ColumnMajor, 16,
214
+ float, Tile, Cluster,
215
+ cutlass::gemm::collective::StageCountAutoCarveout<
216
+ static_cast<int>(sizeof(typename Epi::SharedStorage))>,
217
+ cutlass::gemm::collective::KernelScheduleAuto>::CollectiveOp;
218
+ using Gemm = cutlass::gemm::device::GemmUniversalAdapter<
219
+ cutlass::gemm::kernel::GemmUniversal<Shape<int,int,int,int>, Main, Epi>>;
220
+ } // namespace sm100_sq_bf16out
221
+
222
+ namespace sm100_wide_bf16out {
223
+ using Tile = Shape<_256, _128, _128>;
224
+ using Cluster = Shape<_2, _2, _1>;
225
+ using Fusion = cutlass::epilogue::fusion::LinCombEltAct<
226
+ cutlass::epilogue::thread::Identity, cutlass_bf16, float>;
227
+ using Epi = typename cutlass::epilogue::collective::CollectiveBuilder<
228
+ cutlass::arch::Sm100, cutlass::arch::OpClassTensorOp,
229
+ Tile, Cluster, cutlass::epilogue::collective::EpilogueTileAuto,
230
+ float, float, cutlass_bf16, cutlass::layout::RowMajor, 8,
231
+ cutlass_bf16, cutlass::layout::RowMajor, 8,
232
+ cutlass::epilogue::collective::EpilogueScheduleAuto, Fusion>::CollectiveOp;
233
+ using Main = typename cutlass::gemm::collective::CollectiveBuilder<
234
+ cutlass::arch::Sm100, cutlass::arch::OpClassTensorOp,
235
+ cutlass_fp8, cutlass::layout::RowMajor, 16,
236
+ cutlass_fp8, cutlass::layout::ColumnMajor, 16,
237
+ float, Tile, Cluster,
238
+ cutlass::gemm::collective::StageCountAutoCarveout<
239
+ static_cast<int>(sizeof(typename Epi::SharedStorage))>,
240
+ cutlass::gemm::collective::KernelScheduleAuto>::CollectiveOp;
241
+ using Gemm = cutlass::gemm::device::GemmUniversalAdapter<
242
+ cutlass::gemm::kernel::GemmUniversal<Shape<int,int,int,int>, Main, Epi>>;
243
+ } // namespace sm100_wide_bf16out
244
+
245
+ // Wide projection with a per-column BF16 bias. This keeps the public
246
+ // row-major [N,K] weight contract and removes the layout-dependent cuBLASLt
247
+ // penalty on PI0.5/SigLIP down projections.
248
+ namespace sm100_wide_bias_bf16out {
249
+ using Tile = Shape<_256, _128, _128>;
250
+ using Cluster = Shape<_2, _2, _1>;
251
+ using Fusion = cutlass::epilogue::fusion::LinCombPerColBias<
252
+ cutlass_bf16, float, cutlass_bf16, cutlass_bf16>;
253
+ using Epi = typename cutlass::epilogue::collective::CollectiveBuilder<
254
+ cutlass::arch::Sm100, cutlass::arch::OpClassTensorOp,
255
+ Tile, Cluster, cutlass::epilogue::collective::EpilogueTileAuto,
256
+ float, float, cutlass_bf16, cutlass::layout::RowMajor, 8,
257
+ cutlass_bf16, cutlass::layout::RowMajor, 8,
258
+ cutlass::epilogue::collective::EpilogueScheduleAuto, Fusion>::CollectiveOp;
259
+ using Main = typename cutlass::gemm::collective::CollectiveBuilder<
260
+ cutlass::arch::Sm100, cutlass::arch::OpClassTensorOp,
261
+ cutlass_fp8, cutlass::layout::RowMajor, 16,
262
+ cutlass_fp8, cutlass::layout::ColumnMajor, 16,
263
+ float, Tile, Cluster,
264
+ cutlass::gemm::collective::StageCountAutoCarveout<
265
+ static_cast<int>(sizeof(typename Epi::SharedStorage))>,
266
+ cutlass::gemm::collective::KernelScheduleAuto>::CollectiveOp;
267
+ using Gemm = cutlass::gemm::device::GemmUniversalAdapter<
268
+ cutlass::gemm::kernel::GemmUniversal<Shape<int,int,int,int>, Main, Epi>>;
269
+ } // namespace sm100_wide_bias_bf16out
270
+
271
+ namespace sm100_wide_bias_gelu_bf16out {
272
+ using Tile = Shape<_256, _128, _128>;
273
+ using Cluster = Shape<_2, _2, _1>;
274
+ using Fusion = cutlass::epilogue::fusion::LinCombPerColBiasEltAct<
275
+ cutlass::epilogue::thread::GELU_taylor,
276
+ cutlass_bf16, float, cutlass_bf16, cutlass_bf16>;
277
+ using Epi = typename cutlass::epilogue::collective::CollectiveBuilder<
278
+ cutlass::arch::Sm100, cutlass::arch::OpClassTensorOp,
279
+ Tile, Cluster, cutlass::epilogue::collective::EpilogueTileAuto,
280
+ float, float, cutlass_bf16, cutlass::layout::RowMajor, 8,
281
+ cutlass_bf16, cutlass::layout::RowMajor, 8,
282
+ cutlass::epilogue::collective::EpilogueScheduleAuto, Fusion>::CollectiveOp;
283
+ using Main = typename cutlass::gemm::collective::CollectiveBuilder<
284
+ cutlass::arch::Sm100, cutlass::arch::OpClassTensorOp,
285
+ cutlass_fp8, cutlass::layout::RowMajor, 16,
286
+ cutlass_fp8, cutlass::layout::ColumnMajor, 16,
287
+ float, Tile, Cluster,
288
+ cutlass::gemm::collective::StageCountAutoCarveout<
289
+ static_cast<int>(sizeof(typename Epi::SharedStorage))>,
290
+ cutlass::gemm::collective::KernelScheduleAuto>::CollectiveOp;
291
+ using Gemm = cutlass::gemm::device::GemmUniversalAdapter<
292
+ cutlass::gemm::kernel::GemmUniversal<Shape<int,int,int,int>, Main, Epi>>;
293
+ } // namespace sm100_wide_bias_gelu_bf16out
294
+
295
+ namespace sm100_t1_bf16out {
296
+ using Tile = Shape<_128, _256, _128>;
297
+ using Cluster = Shape<_2, _1, _1>;
298
+ using Fusion = cutlass::epilogue::fusion::LinCombEltAct<
299
+ cutlass::epilogue::thread::Identity, cutlass_bf16, float>;
300
+ using Epi = typename cutlass::epilogue::collective::CollectiveBuilder<
301
+ cutlass::arch::Sm100, cutlass::arch::OpClassTensorOp,
302
+ Tile, Cluster, cutlass::epilogue::collective::EpilogueTileAuto,
303
+ float, float, cutlass_bf16, cutlass::layout::RowMajor, 8,
304
+ cutlass_bf16, cutlass::layout::RowMajor, 8,
305
+ cutlass::epilogue::TmaWarpSpecialized2Sm, Fusion>::CollectiveOp;
306
+ using Main = typename cutlass::gemm::collective::CollectiveBuilder<
307
+ cutlass::arch::Sm100, cutlass::arch::OpClassTensorOp,
308
+ cutlass_fp8, cutlass::layout::RowMajor, 16,
309
+ cutlass_fp8, cutlass::layout::ColumnMajor, 16,
310
+ float, Tile, Cluster,
311
+ cutlass::gemm::collective::StageCountAutoCarveout<
312
+ static_cast<int>(sizeof(typename Epi::SharedStorage))>,
313
+ cutlass::gemm::collective::KernelScheduleAuto>::CollectiveOp;
314
+ using Gemm = cutlass::gemm::device::GemmUniversalAdapter<
315
+ cutlass::gemm::kernel::GemmUniversal<Shape<int,int,int,int>, Main, Epi>>;
316
+ } // namespace sm100_t1_bf16out
317
+
318
+ namespace sm100_sq_f32out {
319
+ using Tile = Shape<_256, _256, _128>;
320
+ using Cluster = Shape<_2, _2, _1>;
321
+ using Fusion = cutlass::epilogue::fusion::LinCombEltAct<
322
+ cutlass::epilogue::thread::Identity, cutlass_fp32, float>;
323
+ using Epi = typename cutlass::epilogue::collective::CollectiveBuilder<
324
+ cutlass::arch::Sm100, cutlass::arch::OpClassTensorOp,
325
+ Tile, Cluster, cutlass::epilogue::collective::EpilogueTileAuto,
326
+ float, float, cutlass_fp32, cutlass::layout::RowMajor, 4,
327
+ cutlass_fp32, cutlass::layout::RowMajor, 4,
328
+ cutlass::epilogue::collective::EpilogueScheduleAuto, Fusion>::CollectiveOp;
329
+ using Main = typename cutlass::gemm::collective::CollectiveBuilder<
330
+ cutlass::arch::Sm100, cutlass::arch::OpClassTensorOp,
331
+ cutlass_fp8, cutlass::layout::RowMajor, 16,
332
+ cutlass_fp8, cutlass::layout::ColumnMajor, 16,
333
+ float, Tile, Cluster,
334
+ cutlass::gemm::collective::StageCountAutoCarveout<
335
+ static_cast<int>(sizeof(typename Epi::SharedStorage))>,
336
+ cutlass::gemm::collective::KernelScheduleAuto>::CollectiveOp;
337
+ using Gemm = cutlass::gemm::device::GemmUniversalAdapter<
338
+ cutlass::gemm::kernel::GemmUniversal<Shape<int,int,int,int>, Main, Epi>>;
339
+ } // namespace sm100_sq_f32out
340
+
341
+ namespace sm100_wide_f32out {
342
+ using Tile = Shape<_256, _128, _128>;
343
+ using Cluster = Shape<_2, _2, _1>;
344
+ using Fusion = cutlass::epilogue::fusion::LinCombEltAct<
345
+ cutlass::epilogue::thread::Identity, cutlass_fp32, float>;
346
+ using Epi = typename cutlass::epilogue::collective::CollectiveBuilder<
347
+ cutlass::arch::Sm100, cutlass::arch::OpClassTensorOp,
348
+ Tile, Cluster, cutlass::epilogue::collective::EpilogueTileAuto,
349
+ float, float, cutlass_fp32, cutlass::layout::RowMajor, 4,
350
+ cutlass_fp32, cutlass::layout::RowMajor, 4,
351
+ cutlass::epilogue::collective::EpilogueScheduleAuto, Fusion>::CollectiveOp;
352
+ using Main = typename cutlass::gemm::collective::CollectiveBuilder<
353
+ cutlass::arch::Sm100, cutlass::arch::OpClassTensorOp,
354
+ cutlass_fp8, cutlass::layout::RowMajor, 16,
355
+ cutlass_fp8, cutlass::layout::ColumnMajor, 16,
356
+ float, Tile, Cluster,
357
+ cutlass::gemm::collective::StageCountAutoCarveout<
358
+ static_cast<int>(sizeof(typename Epi::SharedStorage))>,
359
+ cutlass::gemm::collective::KernelScheduleAuto>::CollectiveOp;
360
+ using Gemm = cutlass::gemm::device::GemmUniversalAdapter<
361
+ cutlass::gemm::kernel::GemmUniversal<Shape<int,int,int,int>, Main, Epi>>;
362
+ } // namespace sm100_wide_f32out
examples/README.md ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ # Examples
2
+
3
+ See the package README for minimal Hub usage.
flake.lock ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "nodes": {
3
+ "flake-compat": {
4
+ "locked": {
5
+ "lastModified": 1767039857,
6
+ "narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
7
+ "owner": "edolstra",
8
+ "repo": "flake-compat",
9
+ "rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
10
+ "type": "github"
11
+ },
12
+ "original": {
13
+ "owner": "edolstra",
14
+ "repo": "flake-compat",
15
+ "type": "github"
16
+ }
17
+ },
18
+ "flake-utils": {
19
+ "inputs": {
20
+ "systems": "systems"
21
+ },
22
+ "locked": {
23
+ "lastModified": 1731533236,
24
+ "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
25
+ "owner": "numtide",
26
+ "repo": "flake-utils",
27
+ "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
28
+ "type": "github"
29
+ },
30
+ "original": {
31
+ "owner": "numtide",
32
+ "repo": "flake-utils",
33
+ "type": "github"
34
+ }
35
+ },
36
+ "kernel-builder": {
37
+ "inputs": {
38
+ "flake-compat": "flake-compat",
39
+ "flake-utils": "flake-utils",
40
+ "nixpkgs": "nixpkgs",
41
+ "rust-overlay": "rust-overlay"
42
+ },
43
+ "locked": {
44
+ "lastModified": 1785676244,
45
+ "narHash": "sha256-os1+/tdJLsC/iYG6t0F49Q3FOCaMB5fFp5cyDVF1d2o=",
46
+ "owner": "LiangSu8899",
47
+ "repo": "kernels",
48
+ "rev": "d720fa90fb9cd92d1bc60a9dc5c55bef2aafabb8",
49
+ "type": "github"
50
+ },
51
+ "original": {
52
+ "owner": "LiangSu8899",
53
+ "repo": "kernels",
54
+ "rev": "d720fa90fb9cd92d1bc60a9dc5c55bef2aafabb8",
55
+ "type": "github"
56
+ }
57
+ },
58
+ "nixpkgs": {
59
+ "locked": {
60
+ "lastModified": 1783284758,
61
+ "narHash": "sha256-tiQ8/qi8I45OOaBBYlVbXoAVkeQzvvTQOv5I45rMw5o=",
62
+ "owner": "NixOS",
63
+ "repo": "nixpkgs",
64
+ "rev": "ec1a11210589d294f0ac99d3290a27e6c73dfa1d",
65
+ "type": "github"
66
+ },
67
+ "original": {
68
+ "owner": "NixOS",
69
+ "repo": "nixpkgs",
70
+ "rev": "ec1a11210589d294f0ac99d3290a27e6c73dfa1d",
71
+ "type": "github"
72
+ }
73
+ },
74
+ "root": {
75
+ "inputs": {
76
+ "kernel-builder": "kernel-builder"
77
+ }
78
+ },
79
+ "rust-overlay": {
80
+ "inputs": {
81
+ "nixpkgs": [
82
+ "kernel-builder",
83
+ "nixpkgs"
84
+ ]
85
+ },
86
+ "locked": {
87
+ "lastModified": 1783320166,
88
+ "narHash": "sha256-l7C/OsjcnWDOk2K3ssj+SBduwL67LashjBqis9+t468=",
89
+ "owner": "oxalica",
90
+ "repo": "rust-overlay",
91
+ "rev": "20ee15370c9256669d66968b89ee20a4b0a4e673",
92
+ "type": "github"
93
+ },
94
+ "original": {
95
+ "owner": "oxalica",
96
+ "repo": "rust-overlay",
97
+ "type": "github"
98
+ }
99
+ },
100
+ "systems": {
101
+ "locked": {
102
+ "lastModified": 1681028828,
103
+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
104
+ "owner": "nix-systems",
105
+ "repo": "default",
106
+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
107
+ "type": "github"
108
+ },
109
+ "original": {
110
+ "owner": "nix-systems",
111
+ "repo": "default",
112
+ "type": "github"
113
+ }
114
+ }
115
+ },
116
+ "root": "root",
117
+ "version": 7
118
+ }
flake.nix ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ description = "Flake for FlashRT FP8 GEMM kernels";
3
+
4
+ inputs = {
5
+ # Based on huggingface/kernels@e9152aa with only the stale CUTLASS 4.5.2
6
+ # fixed-output hash updated. Return to upstream after the hash fix lands.
7
+ kernel-builder.url =
8
+ "github:LiangSu8899/kernels/d720fa90fb9cd92d1bc60a9dc5c55bef2aafabb8";
9
+ };
10
+
11
+ outputs =
12
+ {
13
+ self,
14
+ kernel-builder,
15
+ }:
16
+ kernel-builder.lib.genKernelFlakeOutputs {
17
+ inherit self;
18
+ path = ./.;
19
+ };
20
+ }
scripts/README.md ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ # Scripts
2
+
3
+ Package-specific helper scripts are not required yet.
tests/README.md ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ # Tests
2
+
3
+ ```bash
4
+ python fp8-gemm/tests/test_fp8_gemm.py --backend source --mode full
5
+ ```
6
+
7
+ SM110 full mode adds PI0.5, GROOT, Cosmos Edge, and LingBot VLA projection
8
+ shapes plus forced Sq/T1/Wide correctness rows. Use `--backend installed` with
9
+ the exact artifact directory for the release gate.
tests/test_fp8_gemm.py ADDED
@@ -0,0 +1,698 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Correctness tests for fp8-gemm."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import importlib
8
+ import json
9
+ import math
10
+ import os
11
+ import sys
12
+ from dataclasses import asdict, dataclass
13
+ from pathlib import Path
14
+
15
+ import torch
16
+
17
+
18
+ ROOT = Path(__file__).resolve().parents[2]
19
+ PACKAGE = ROOT / "fp8-gemm"
20
+ REGISTRATION_INCLUDE = (
21
+ ROOT.parent
22
+ / "kernels"
23
+ / "kernel-builder"
24
+ / "src"
25
+ / "pyproject"
26
+ / "templates"
27
+ / "torch"
28
+ )
29
+
30
+
31
+ SHAPES = {
32
+ "decode_m1_k512_n512": (1, 512, 512),
33
+ "decode_m1_k4096_n2048": (1, 4096, 2048),
34
+ "decode_m1_k4096_n8192": (1, 4096, 8192),
35
+ "small_m8_k1024_n2048": (8, 1024, 2048),
36
+ "small_m16_k4096_n4096": (16, 4096, 4096),
37
+ "small_m32_k4096_n8192": (32, 4096, 8192),
38
+ "small_m64_k512_n1024": (64, 512, 1024),
39
+ }
40
+
41
+ SM110_SHAPES = {
42
+ "large_m_boundary_65": (65, 2048, 2048),
43
+ # PI0.5 / PI0 decoder and encoder projection families.
44
+ "pi05_action_qkv": (51, 2048, 2560),
45
+ "pi05_action_o": (51, 2048, 2048),
46
+ "pi05_action_gate_up": (51, 2048, 16384),
47
+ "pi05_action_down": (51, 8192, 2048),
48
+ # GROOT N1.6/N1.7 DiT, backbone, and vision rows.
49
+ "groot_dit_qkv": (51, 1536, 4608),
50
+ "groot_n17_llm_o": (277, 2048, 2048),
51
+ "groot_n17_llm_gate_up": (277, 2048, 16384),
52
+ "groot_n17_llm_down": (277, 8192, 2048),
53
+ "groot_n17_vit_o": (1024, 1024, 1024),
54
+ # Cosmos Edge and LingBot projection families.
55
+ "cosmos_edge_action": (64, 2048, 9216),
56
+ "lingbot_vision_o": (1024, 1280, 1280),
57
+ "lingbot_action_gate_up": (105, 2048, 16384),
58
+ # PI0.5 Thor prefill tower, full real row envelope.
59
+ "pi05_prefill_qkv": (712, 2048, 2560),
60
+ "pi05_prefill_o": (970, 2048, 2048),
61
+ "pi05_prefill_gate_up": (768, 2048, 32768),
62
+ "pi05_prefill_down": (768, 16384, 2048),
63
+ }
64
+
65
+ MODES = {
66
+ "smoke": ["decode_m1_k512_n512", "small_m8_k1024_n2048"],
67
+ "headline": [
68
+ "decode_m1_k4096_n2048",
69
+ "decode_m1_k4096_n8192",
70
+ "small_m16_k4096_n4096",
71
+ "small_m32_k4096_n8192",
72
+ ],
73
+ "full": list(SHAPES.keys()),
74
+ }
75
+
76
+
77
+ @dataclass
78
+ class Metrics:
79
+ shape: str
80
+ M: int
81
+ K: int
82
+ N: int
83
+ variant: int
84
+ tile: str
85
+ max_abs: float
86
+ mean_abs: float
87
+ p99_abs: float
88
+ cosine: float
89
+ dtype: str
90
+ tolerance: str
91
+ passed: bool
92
+
93
+
94
+ class SourceOps:
95
+ def __init__(self, namespace: str) -> None:
96
+ self._ops = getattr(torch.ops, namespace)
97
+
98
+ @staticmethod
99
+ def select_fp8_linear_tile(m: int, n: int, k: int, variant: int = 0) -> str:
100
+ return select_tile(m, n, k, variant)
101
+
102
+ def fp8_linear_bf16(self, x, w, alpha=1.0, out=None, variant=0):
103
+ if out is None:
104
+ out = torch.empty((x.shape[0], w.shape[0]), device=x.device, dtype=torch.bfloat16)
105
+ self._ops.fp8_linear_bf16(x, w, float(alpha), int(variant), out)
106
+ return out
107
+
108
+ def fp8_linear_residual_bf16(self, x, w, residual, alpha=1.0, variant=0):
109
+ self._ops.fp8_linear_residual_bf16(x, w, float(alpha), int(variant), residual)
110
+ return residual
111
+
112
+ def fp8_linear_bias_bf16(self, x, w, bias, alpha=1.0, out=None):
113
+ if out is None:
114
+ out = torch.empty(
115
+ (x.shape[0], w.shape[0]), device=x.device, dtype=torch.bfloat16
116
+ )
117
+ self._ops.fp8_linear_bias_bf16(x, w, bias, float(alpha), out)
118
+ return out
119
+
120
+ def fp8_linear_bias_residual_bf16(
121
+ self, x, w, bias, residual, alpha=1.0
122
+ ):
123
+ self._ops.fp8_linear_bias_residual_bf16(
124
+ x, w, bias, float(alpha), residual
125
+ )
126
+ return residual
127
+
128
+ def fp8_linear_bias_gelu_bf16(self, x, w, bias, alpha=1.0, out=None):
129
+ if out is None:
130
+ out = torch.empty(
131
+ (x.shape[0], w.shape[0]), device=x.device, dtype=torch.bfloat16
132
+ )
133
+ self._ops.fp8_linear_bias_gelu_bf16(x, w, bias, float(alpha), out)
134
+ return out
135
+
136
+ def fp8_blockwise_linear_bf16(
137
+ self, x, w, input_scale, weight_scale, out=None
138
+ ):
139
+ if out is None:
140
+ out = torch.empty(
141
+ (x.shape[0], w.shape[0]),
142
+ device=x.device,
143
+ dtype=torch.bfloat16,
144
+ )
145
+ self._ops.fp8_blockwise_linear_bf16(
146
+ x, w, input_scale, weight_scale, out
147
+ )
148
+ return out
149
+
150
+ def fp8_blockwise_swiglu_quantize_fp8(
151
+ self, x, gate_up_weight, input_scale, gate_up_weight_scale,
152
+ output=None, output_scale=None,
153
+ ):
154
+ n = gate_up_weight.shape[0] // 2
155
+ if output is None:
156
+ output = torch.empty(
157
+ (x.shape[0], n), device=x.device, dtype=torch.float8_e4m3fn
158
+ )
159
+ if output_scale is None:
160
+ output_scale = torch.empty(
161
+ (x.shape[0], n // 128), device=x.device, dtype=torch.float32
162
+ )
163
+ self._ops.fp8_blockwise_swiglu_quantize_fp8(
164
+ x, gate_up_weight, input_scale, gate_up_weight_scale,
165
+ output, output_scale,
166
+ )
167
+ return output, output_scale
168
+
169
+
170
+ def _current_arch_list() -> str:
171
+ major, minor = torch.cuda.get_device_capability(0)
172
+ if (major, minor) == (11, 0):
173
+ return "11.0a"
174
+ return "12.0a" if (major, minor) == (12, 0) else f"{major}.{minor}"
175
+
176
+
177
+ def load_source_ops() -> SourceOps:
178
+ from torch.utils.cpp_extension import load
179
+
180
+ if not REGISTRATION_INCLUDE.is_dir():
181
+ raise RuntimeError(f"missing kernel-builder registration include: {REGISTRATION_INCLUDE}")
182
+ os.environ.setdefault("TORCH_CUDA_ARCH_LIST", _current_arch_list())
183
+ namespace = "fp8_gemm_source_test"
184
+ cutlass_include = Path(
185
+ os.environ.get(
186
+ "CUTLASS_INCLUDE",
187
+ str(
188
+ ROOT.parent
189
+ / "flashrt_pr31_review"
190
+ / "third_party"
191
+ / "cutlass"
192
+ / "include"
193
+ ),
194
+ )
195
+ )
196
+ if not (cutlass_include / "cutlass" / "cutlass.h").is_file():
197
+ raise RuntimeError(
198
+ "CUTLASS 4 include path is required; set CUTLASS_INCLUDE"
199
+ )
200
+ capability = torch.cuda.get_device_capability(0)
201
+ if capability == (8, 9):
202
+ cuda_sources = [
203
+ str(PACKAGE / "csrc" / "fp8_block128_gemm_mma_sm89.cu"),
204
+ str(PACKAGE / "csrc" / "fp8_gemv_m1_sm89.cu"),
205
+ ]
206
+ source_define = "-DFLASHRT_FP8_GEMM_SOURCE_SM89_ONLY"
207
+ elif capability == (11, 0):
208
+ cuda_sources = [
209
+ str(PACKAGE / "csrc" / "cutlass_sm110_fp8_gemm.cu"),
210
+ str(PACKAGE / "csrc" / "cublaslt_fp8_bias_sm110.cu"),
211
+ ]
212
+ source_define = "-DFLASHRT_FP8_GEMM_SOURCE_SM110_ONLY"
213
+ else:
214
+ cuda_sources = [
215
+ str(PACKAGE / "csrc" / "fp8_gemv_m1_sm120.cu"),
216
+ str(PACKAGE / "csrc" / "fp8_smallM_handtuned_sm120.cu"),
217
+ str(PACKAGE / "csrc" / "fp8_smallM_handtuned_ldmatrix_sm120.cu"),
218
+ str(PACKAGE / "csrc" / "cutlass_sm120_block128_fp8_gemm.cu"),
219
+ ]
220
+ source_define = "-DFLASHRT_FP8_GEMM_SOURCE_SM120_ONLY"
221
+ load(
222
+ name=namespace,
223
+ sources=[str(PACKAGE / "torch-ext" / "torch_binding.cpp"), *cuda_sources],
224
+ extra_include_paths=[
225
+ str(PACKAGE / "csrc"),
226
+ str(REGISTRATION_INCLUDE),
227
+ str(cutlass_include),
228
+ str(cutlass_include.parent / "tools" / "util" / "include"),
229
+ ],
230
+ extra_cflags=["-O3", "-DNDEBUG", "-DCUDA_KERNEL", source_define],
231
+ extra_cuda_cflags=[
232
+ "-O3", "-DNDEBUG", "--expt-relaxed-constexpr", "--use_fast_math",
233
+ "-U__CUDA_NO_HALF_OPERATORS__",
234
+ "-U__CUDA_NO_HALF_CONVERSIONS__",
235
+ "-U__CUDA_NO_BFLOAT16_CONVERSIONS__",
236
+ "-U__CUDA_NO_HALF2_OPERATORS__",
237
+ "-DCUDA_KERNEL", source_define
238
+ ],
239
+ verbose=False,
240
+ )
241
+ return SourceOps(namespace)
242
+
243
+
244
+ def load_installed_ops(artifact: str | None):
245
+ if artifact:
246
+ sys.path.insert(0, artifact)
247
+ try:
248
+ return importlib.import_module("fp8_gemm")
249
+ finally:
250
+ if artifact:
251
+ sys.path.remove(artifact)
252
+
253
+
254
+ def select_tile(m: int, n: int, k: int, variant: int = 0) -> str:
255
+ if torch.cuda.get_device_capability(0) == (11, 0):
256
+ forced = {1: "sm110_sq_bf16", 2: "sm110_t1_bf16", 3: "sm110_wide_bf16"}
257
+ if variant not in {0, *forced}:
258
+ raise RuntimeError("SM110 variant must be in [0, 3]")
259
+ if variant:
260
+ return forced[variant]
261
+ if m >= 512 and k == 2048 and 2048 <= n <= 2560:
262
+ return "sm110_sq_bf16"
263
+ if m >= 512 and n >= 16 * k:
264
+ return "sm110_t1_bf16"
265
+ if m >= 512 and k >= 4 * n:
266
+ return "sm110_wide_bf16"
267
+ if n >= 8 * k:
268
+ return "sm110_wide_bf16"
269
+ if m >= 128 and k >= 4 * n:
270
+ return "sm110_sq_bf16"
271
+ if n == k and m >= 512:
272
+ return "sm110_sq_bf16" if k <= 1024 else "sm110_wide_bf16"
273
+ if n == k and m >= 128:
274
+ return "sm110_wide_bf16"
275
+ return "sm110_t1_bf16"
276
+ if m == 1:
277
+ if variant == 4:
278
+ return "gemv_fp8_m1_w4"
279
+ if variant == 8:
280
+ return "gemv_fp8_m1_w8"
281
+ if variant == 16:
282
+ return "gemv_fp8_m1_w16"
283
+ if n <= 2048:
284
+ return "gemv_fp8_m1_w4"
285
+ if n <= 8192:
286
+ return "gemv_fp8_m1_w8"
287
+ return "gemv_fp8_m1_w16"
288
+ if m <= 16:
289
+ if k % 256 == 0:
290
+ return "ld_fp8_gemm_16x128x256_w4" if n % 128 == 0 else "ld_fp8_gemm_16x64x256_w4"
291
+ if n % 256 == 0:
292
+ return "ld_fp8_gemm_16x256x128_w8"
293
+ if n % 192 == 0:
294
+ return "ld_fp8_gemm_16x192x128_w4"
295
+ if n % 128 == 0:
296
+ return "ld_fp8_gemm_16x128x128_w4"
297
+ return "ld_fp8_gemm_16x64x128_w4"
298
+ if m <= 32:
299
+ if k % 256 == 0:
300
+ return "ld_fp8_gemm_32x128x256_w4" if n % 128 == 0 else "ld_fp8_gemm_32x64x256_w4"
301
+ if n % 192 == 0:
302
+ return "ld_fp8_gemm_32x192x128_w4"
303
+ if n % 128 == 0:
304
+ return "ld_fp8_gemm_32x128x128_w4"
305
+ return "ld_fp8_gemm_32x64x128_w4"
306
+ if m <= 64:
307
+ if k % 256 == 0:
308
+ return "ld_fp8_gemm_64x128x256_w4" if n % 128 == 0 else "ld_fp8_gemm_64x64x256_w4"
309
+ if n % 128 == 0:
310
+ return "ld_fp8_gemm_64x128x128_w4"
311
+ return "ld_fp8_gemm_64x64x128_w4"
312
+ if m <= 64:
313
+ if k % 256 == 0:
314
+ return "ld_fp8_gemm_64x128x256_w4" if n % 128 == 0 else "ld_fp8_gemm_64x64x256_w4"
315
+ if n % 128 == 0:
316
+ return "ld_fp8_gemm_64x128x128_w4"
317
+ return "ld_fp8_gemm_64x64x128_w4"
318
+ raise RuntimeError("unsupported M")
319
+
320
+
321
+ def make_inputs(m: int, k: int, n: int, seed: int):
322
+ gen = torch.Generator(device="cuda")
323
+ gen.manual_seed(seed)
324
+ x_bf16 = (torch.randn((m, k), device="cuda", generator=gen) * 0.25).to(torch.bfloat16)
325
+ w_bf16 = (torch.randn((n, k), device="cuda", generator=gen) * 0.25).to(torch.bfloat16)
326
+ x = x_bf16.to(torch.float8_e4m3fn)
327
+ w = w_bf16.to(torch.float8_e4m3fn)
328
+ return x, w
329
+
330
+
331
+ def reference(x: torch.Tensor, w: torch.Tensor, alpha: float) -> torch.Tensor:
332
+ return ((x.float() @ w.float().T) * float(alpha)).to(torch.bfloat16)
333
+
334
+
335
+ def compare(got: torch.Tensor, expected: torch.Tensor) -> tuple[float, float, float, float]:
336
+ diff = (got.float() - expected.float()).abs().flatten()
337
+ max_abs = float(diff.max().item())
338
+ mean_abs = float(diff.mean().item())
339
+ p99_rank = max(1, min(diff.numel(), math.ceil(0.99 * diff.numel())))
340
+ p99_abs = float(diff.kthvalue(p99_rank).values.item())
341
+ cos = float(torch.nn.functional.cosine_similarity(got.float().flatten(), expected.float().flatten(), dim=0).item())
342
+ return max_abs, mean_abs, p99_abs, cos
343
+
344
+
345
+ def check_threshold(max_abs: float, mean_abs: float, p99_abs: float, cos: float) -> bool:
346
+ return max_abs <= 0.5 and mean_abs <= 0.02 and p99_abs <= 0.25 and cos >= 0.999
347
+
348
+
349
+ def run_case(ops, name: str, shape: tuple[int, int, int], variant: int = 0) -> Metrics:
350
+ m, k, n = shape
351
+ x, w = make_inputs(m, k, n, seed=1000 + m + k + n + variant)
352
+ alpha = 1.0
353
+ expected = reference(x, w, alpha)
354
+ got = ops.fp8_linear_bf16(x, w, alpha=alpha, variant=variant)
355
+ torch.cuda.synchronize()
356
+ max_abs, mean_abs, p99_abs, cos = compare(got, expected)
357
+ tile = ops.select_fp8_linear_tile(m, n, k, variant)
358
+ passed = check_threshold(max_abs, mean_abs, p99_abs, cos)
359
+ return Metrics(
360
+ shape=name,
361
+ M=m,
362
+ K=k,
363
+ N=n,
364
+ variant=variant,
365
+ tile=tile,
366
+ max_abs=max_abs,
367
+ mean_abs=mean_abs,
368
+ p99_abs=p99_abs,
369
+ cosine=cos,
370
+ dtype=str(got.dtype),
371
+ tolerance="max_abs<=0.5 mean_abs<=0.02 p99_abs<=0.25 cosine>=0.999",
372
+ passed=passed,
373
+ )
374
+
375
+
376
+ def run_residual_case(ops) -> Metrics:
377
+ m, k, n = (1, 4096, 2048)
378
+ x, w = make_inputs(m, k, n, seed=2026)
379
+ residual = torch.randn((1, n), device="cuda", dtype=torch.bfloat16) * 0.1
380
+ expected = (residual.float() + reference(x, w, 1.0).float()).to(torch.bfloat16)
381
+ got = residual.clone()
382
+ variant = 0 if torch.cuda.get_device_capability(0) == (11, 0) else 8
383
+ ops.fp8_linear_residual_bf16(x, w, got, alpha=1.0, variant=variant)
384
+ torch.cuda.synchronize()
385
+ max_abs, mean_abs, p99_abs, cos = compare(got, expected)
386
+ passed = check_threshold(max_abs, mean_abs, p99_abs, cos)
387
+ return Metrics(
388
+ shape="decode_residual_m1_k4096_n2048",
389
+ M=m,
390
+ K=k,
391
+ N=n,
392
+ variant=variant,
393
+ tile=(
394
+ "sm110_t1_bf16_residual"
395
+ if torch.cuda.get_device_capability(0) == (11, 0)
396
+ else "gemv_fp8_m1_resadd_w8"
397
+ ),
398
+ max_abs=max_abs,
399
+ mean_abs=mean_abs,
400
+ p99_abs=p99_abs,
401
+ cosine=cos,
402
+ dtype=str(got.dtype),
403
+ tolerance="max_abs<=0.5 mean_abs<=0.02 p99_abs<=0.25 cosine>=0.999",
404
+ passed=passed,
405
+ )
406
+
407
+
408
+ def run_bias_cases(ops) -> int:
409
+ count = 0
410
+ shapes = [
411
+ (512, 1152, 4304),
412
+ (768, 4304, 1152),
413
+ (768, 1152, 3456),
414
+ ]
415
+ for m, k, n in shapes:
416
+ x, w = make_inputs(m, k, n, seed=7000 + m + k + n)
417
+ bias = (torch.randn((n,), device="cuda") * 0.1).to(torch.bfloat16)
418
+ alpha = 0.75
419
+ base = (x.float() @ w.float().T) * alpha
420
+
421
+ got = ops.fp8_linear_bias_bf16(x, w, bias, alpha=alpha)
422
+ expected = (base + bias.float()).to(torch.bfloat16)
423
+ maximum, mean, p99, cosine = compare(got, expected)
424
+ assert maximum <= 0.5 and mean <= 0.02 and p99 <= 0.25 and cosine >= 0.999, (
425
+ "bias", m, k, n, maximum, mean, p99, cosine
426
+ )
427
+
428
+ residual = (torch.randn((m, n), device="cuda") * 0.1).to(
429
+ torch.bfloat16
430
+ )
431
+ residual_before = residual.clone()
432
+ got_residual = ops.fp8_linear_bias_residual_bf16(
433
+ x, w, bias, residual, alpha=alpha
434
+ )
435
+ expected_residual = (
436
+ residual_before.float() + base + bias.float()
437
+ ).to(torch.bfloat16)
438
+ maximum, mean, p99, cosine = compare(got_residual, expected_residual)
439
+ assert maximum <= 0.5 and mean <= 0.02 and p99 <= 0.25 and cosine >= 0.999, (
440
+ "bias_residual", m, k, n, maximum, mean, p99, cosine
441
+ )
442
+
443
+ got_gelu = ops.fp8_linear_bias_gelu_bf16(x, w, bias, alpha=alpha)
444
+ expected_gelu = torch.nn.functional.gelu(
445
+ base + bias.float(), approximate="tanh"
446
+ ).to(torch.bfloat16)
447
+ maximum, mean, p99, cosine = compare(got_gelu, expected_gelu)
448
+ assert maximum <= 0.5 and mean <= 0.02 and p99 <= 0.25 and cosine >= 0.999, (
449
+ "bias_gelu", m, k, n, maximum, mean, p99, cosine
450
+ )
451
+ count += 3
452
+
453
+ m, k, n = (512, 1152, 4304)
454
+ x, w = make_inputs(m, k, n, seed=8801)
455
+ bias = torch.randn((n,), device="cuda", dtype=torch.bfloat16)
456
+
457
+ def invoke(input, weight, bias):
458
+ return ops.fp8_linear_bias_bf16(input, weight, bias)
459
+
460
+ eager = invoke(x, w, bias)
461
+ compiled = torch.compile(invoke, fullgraph=True)(x, w, bias)
462
+ torch.testing.assert_close(compiled, eager, rtol=0.0, atol=0.0)
463
+
464
+ graph_out = torch.empty_like(eager)
465
+ ops.fp8_linear_bias_bf16(x, w, bias, out=graph_out)
466
+ graph = torch.cuda.CUDAGraph()
467
+ with torch.cuda.graph(graph):
468
+ ops.fp8_linear_bias_bf16(x, w, bias, out=graph_out)
469
+ graph.replay()
470
+ torch.testing.assert_close(graph_out, eager, rtol=0.0, atol=0.0)
471
+ return count + 2
472
+
473
+
474
+ def run_blockwise_case(
475
+ ops, name: str, shape: tuple[int, int, int]
476
+ ) -> Metrics:
477
+ m, k, n = shape
478
+ gen = torch.Generator(device="cuda").manual_seed(5000 + m + k + n)
479
+ x = (torch.randn((m, k), device="cuda", generator=gen) * 0.4).to(
480
+ torch.float8_e4m3fn
481
+ )
482
+ w = (torch.randn((n, k), device="cuda", generator=gen) * 0.4).to(
483
+ torch.float8_e4m3fn
484
+ )
485
+ input_scale = (
486
+ 0.005
487
+ + 0.02
488
+ * torch.rand((m, k // 128), device="cuda", generator=gen)
489
+ ).float().contiguous()
490
+ weight_scale = (
491
+ 0.005
492
+ + 0.02
493
+ * torch.rand((n // 128, k // 128), device="cuda", generator=gen)
494
+ ).float().contiguous()
495
+ expanded_input_scale = input_scale.repeat_interleave(128, dim=1)
496
+ expanded_weight_scale = weight_scale.repeat_interleave(
497
+ 128, dim=0
498
+ ).repeat_interleave(128, dim=1)
499
+ expected = (
500
+ (x.float() * expanded_input_scale)
501
+ @ (w.float() * expanded_weight_scale).T
502
+ ).to(torch.bfloat16)
503
+ got = ops.fp8_blockwise_linear_bf16(
504
+ x, w, input_scale, weight_scale
505
+ )
506
+ torch.cuda.synchronize()
507
+ max_abs, mean_abs, p99_abs, cos = compare(got, expected)
508
+ passed = (
509
+ max_abs <= 0.0625
510
+ and mean_abs <= 0.003
511
+ and p99_abs <= 0.015625
512
+ and cos >= 0.9999
513
+ )
514
+ return Metrics(
515
+ shape=name,
516
+ M=m,
517
+ K=k,
518
+ N=n,
519
+ variant=0,
520
+ tile=(
521
+ "mma_sm89_block128"
522
+ if torch.cuda.get_device_capability(0) == (8, 9)
523
+ else "cutlass_sm120_block128"
524
+ ),
525
+ max_abs=max_abs,
526
+ mean_abs=mean_abs,
527
+ p99_abs=p99_abs,
528
+ cosine=cos,
529
+ dtype=str(got.dtype),
530
+ tolerance=(
531
+ "max_abs<=0.0625 mean_abs<=0.003 "
532
+ "p99_abs<=0.015625 cosine>=0.9999"
533
+ ),
534
+ passed=passed,
535
+ )
536
+
537
+
538
+ def run_blockwise_compile_case(ops) -> None:
539
+ m, k, n = (51, 1536, 1536)
540
+ gen = torch.Generator(device="cuda").manual_seed(9153)
541
+ x = (torch.randn((m, k), device="cuda", generator=gen) * 0.4).to(
542
+ torch.float8_e4m3fn
543
+ )
544
+ w = (torch.randn((n, k), device="cuda", generator=gen) * 0.4).to(
545
+ torch.float8_e4m3fn
546
+ )
547
+ input_scale = torch.rand(
548
+ (m, k // 128), device="cuda", generator=gen, dtype=torch.float32
549
+ ).mul_(0.02).add_(0.005)
550
+ weight_scale = torch.rand(
551
+ (n // 128, k // 128),
552
+ device="cuda",
553
+ generator=gen,
554
+ dtype=torch.float32,
555
+ ).mul_(0.02).add_(0.005)
556
+
557
+ def invoke(input, weight, input_scale, weight_scale):
558
+ return ops.fp8_blockwise_linear_bf16(
559
+ input, weight, input_scale, weight_scale
560
+ )
561
+
562
+ eager = invoke(x, w, input_scale, weight_scale)
563
+ compiled = torch.compile(invoke, fullgraph=True)(
564
+ x, w, input_scale, weight_scale
565
+ )
566
+ torch.testing.assert_close(compiled, eager, rtol=0.0, atol=0.0)
567
+
568
+
569
+ def run_sm89_swiglu_case(ops, m: int, n: int, k: int) -> None:
570
+ gen = torch.Generator(device="cuda").manual_seed(8900 + m + n + k)
571
+ x = (torch.randn((m, k), device="cuda", generator=gen) * 0.3).to(
572
+ torch.float8_e4m3fn
573
+ )
574
+ weight = (
575
+ torch.randn((2 * n, k), device="cuda", generator=gen) * 0.3
576
+ ).to(torch.float8_e4m3fn)
577
+ input_scale = torch.rand(
578
+ (m, k // 128), device="cuda", generator=gen
579
+ ).mul_(0.02).add_(0.005)
580
+ weight_scale = torch.rand(
581
+ (2 * n // 128, k // 128), device="cuda", generator=gen
582
+ ).mul_(0.02).add_(0.005)
583
+ output, output_scale = ops.fp8_blockwise_swiglu_quantize_fp8(
584
+ x, weight, input_scale, weight_scale
585
+ )
586
+ expanded_x_scale = input_scale.repeat_interleave(128, dim=1)
587
+ expanded_w_scale = weight_scale.repeat_interleave(128, dim=0).repeat_interleave(128, dim=1)
588
+ x_f32 = x.float() * expanded_x_scale
589
+ weight_f32 = weight.float() * expanded_w_scale
590
+ gate, up = (x_f32 @ weight_f32.t()).split(n, dim=1)
591
+ expected = (
592
+ torch.nn.functional.silu(gate).bfloat16() * up.bfloat16()
593
+ ).bfloat16()
594
+ actual = (
595
+ output.float() * output_scale.repeat_interleave(128, dim=1)
596
+ ).bfloat16()
597
+ maximum, mean, p99, cosine = compare(actual, expected)
598
+ assert output.dtype == torch.float8_e4m3fn
599
+ assert output_scale.dtype == torch.float32
600
+ assert torch.isfinite(output_scale).all() and (output_scale > 0).all()
601
+ assert cosine >= 0.999 and mean <= 0.01 and p99 <= 0.05, (
602
+ m, n, k, maximum, mean, p99, cosine
603
+ )
604
+
605
+
606
+ def main() -> None:
607
+ parser = argparse.ArgumentParser()
608
+ parser.add_argument("--backend", choices=["source", "installed"], default="source")
609
+ parser.add_argument("--artifact", default=None)
610
+ parser.add_argument("--mode", choices=sorted(MODES), default="smoke")
611
+ parser.add_argument("--json-out", default=None)
612
+ args = parser.parse_args()
613
+
614
+ if not torch.cuda.is_available():
615
+ raise SystemExit("CUDA is required")
616
+ capability = torch.cuda.get_device_capability(0)
617
+ if capability not in {(8, 9), (11, 0), (12, 0)}:
618
+ raise SystemExit(
619
+ "fp8-gemm source tests require SM89, SM110, or SM120; "
620
+ f"got SM{capability[0]}{capability[1]}"
621
+ )
622
+
623
+ ops = load_source_ops() if args.backend == "source" else load_installed_ops(args.artifact)
624
+ rows = []
625
+ if capability in {(11, 0), (12, 0)}:
626
+ rows.extend(run_case(ops, name, SHAPES[name]) for name in MODES[args.mode])
627
+ rows.append(run_residual_case(ops))
628
+ if capability == (11, 0) and args.mode == "full":
629
+ rows.extend(
630
+ run_case(ops, name, shape) for name, shape in SM110_SHAPES.items()
631
+ )
632
+ rows.extend(
633
+ run_case(
634
+ ops,
635
+ f"sm110_forced_variant_{variant}",
636
+ SM110_SHAPES["pi05_action_gate_up"],
637
+ variant,
638
+ )
639
+ for variant in (1, 2, 3)
640
+ )
641
+ bias_count = run_bias_cases(ops)
642
+ else:
643
+ bias_count = 0
644
+ if capability in {(8, 9), (12, 0)}:
645
+ blockwise_shapes = [
646
+ ("blockwise_decode", (1, 1024, 1024)),
647
+ ("blockwise_action", (51, 1536, 1536)),
648
+ ]
649
+ if args.mode == "full":
650
+ blockwise_shapes += [
651
+ ("blockwise_groot", (277, 2048, 2048)),
652
+ ("blockwise_vision", (1024, 1152, 1152)),
653
+ ("blockwise_video", (2520, 3072, 3072)),
654
+ ("blockwise_qwen_mlp", (128, 4096, 12288)),
655
+ ]
656
+ rows.extend(
657
+ run_blockwise_case(ops, name, shape)
658
+ for name, shape in blockwise_shapes
659
+ )
660
+ run_blockwise_compile_case(ops)
661
+ if capability == (8, 9):
662
+ for m, n, k in [
663
+ (1, 128, 128), (16, 512, 1024), (31, 1536, 1536),
664
+ (32, 2048, 4096), (51, 4096, 4096), (128, 4096, 4096),
665
+ (256, 4096, 4096),
666
+ ]:
667
+ run_sm89_swiglu_case(ops, m, n, k)
668
+ try:
669
+ x = torch.zeros((257, 128), device="cuda", dtype=torch.float8_e4m3fn)
670
+ w = torch.zeros((256, 128), device="cuda", dtype=torch.float8_e4m3fn)
671
+ xs = torch.ones((257, 1), device="cuda", dtype=torch.float32)
672
+ ws = torch.ones((2, 1), device="cuda", dtype=torch.float32)
673
+ ops.fp8_blockwise_swiglu_quantize_fp8(x, w, xs, ws)
674
+ except RuntimeError as error:
675
+ assert "M <= 256" in str(error)
676
+ else:
677
+ raise AssertionError("M=257 must be rejected")
678
+
679
+ failed = [row for row in rows if not row.passed]
680
+ payload = {
681
+ "passed": len(rows) - len(failed) + bias_count,
682
+ "failed": len(failed),
683
+ "rows": [asdict(row) for row in rows],
684
+ "bias_checks": bias_count,
685
+ }
686
+ print(json.dumps(payload, indent=2, sort_keys=True))
687
+ if args.json_out:
688
+ output_path = Path(args.json_out)
689
+ output_path.parent.mkdir(parents=True, exist_ok=True)
690
+ output_path.write_text(
691
+ json.dumps(payload, indent=2, sort_keys=True) + "\n"
692
+ )
693
+ if failed:
694
+ raise SystemExit(1)
695
+
696
+
697
+ if __name__ == "__main__":
698
+ main()
torch-ext/README.md ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ # torch-ext
2
+
3
+ Python package: `fp8_gemm`
4
+
5
+ The package registers Torch custom ops and fake implementations for
6
+ `torch.compile` tracing.
torch-ext/fp8_gemm/__init__.py ADDED
@@ -0,0 +1,345 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """FlashRT FP8 GEMM kernels."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import torch
6
+
7
+ from ._ops import add_op_namespace_prefix, ops
8
+
9
+
10
+ @torch.library.register_fake(add_op_namespace_prefix("fp8_linear_bf16"))
11
+ def _fp8_linear_bf16_fake(
12
+ input: torch.Tensor,
13
+ weight: torch.Tensor,
14
+ alpha: float,
15
+ variant: int,
16
+ out: torch.Tensor,
17
+ ) -> None:
18
+ if input.dim() != 2 or weight.dim() != 2:
19
+ raise RuntimeError("input and weight must be rank-2 tensors")
20
+ if out.shape != (input.shape[0], weight.shape[0]):
21
+ raise RuntimeError("out must have shape (input.shape[0], weight.shape[0])")
22
+ return None
23
+
24
+
25
+ @torch.library.register_fake(add_op_namespace_prefix("fp8_linear_residual_bf16"))
26
+ def _fp8_linear_residual_bf16_fake(
27
+ input: torch.Tensor,
28
+ weight: torch.Tensor,
29
+ alpha: float,
30
+ variant: int,
31
+ residual: torch.Tensor,
32
+ ) -> None:
33
+ if input.shape[0] != 1:
34
+ raise RuntimeError("residual path supports only M=1")
35
+ if residual.shape != (1, weight.shape[0]):
36
+ raise RuntimeError("residual must have shape (1, weight.shape[0])")
37
+ return None
38
+
39
+
40
+ def _check_bias_linear_shapes(input, weight, bias, out) -> None:
41
+ if input.dim() != 2 or weight.dim() != 2:
42
+ raise RuntimeError("input and weight must be rank-2 tensors")
43
+ if input.shape[1] != weight.shape[1]:
44
+ raise RuntimeError("input and weight K dimensions must match")
45
+ if bias.shape != (weight.shape[0],):
46
+ raise RuntimeError("bias must have shape (weight.shape[0],)")
47
+ if out.shape != (input.shape[0], weight.shape[0]):
48
+ raise RuntimeError("out must have shape (input.shape[0], weight.shape[0])")
49
+
50
+
51
+ @torch.library.register_fake(add_op_namespace_prefix("fp8_linear_bias_bf16"))
52
+ def _fp8_linear_bias_bf16_fake(input, weight, bias, alpha: float, out) -> None:
53
+ _check_bias_linear_shapes(input, weight, bias, out)
54
+
55
+
56
+ @torch.library.register_fake(add_op_namespace_prefix("fp8_linear_bias_residual_bf16"))
57
+ def _fp8_linear_bias_residual_bf16_fake(
58
+ input, weight, bias, alpha: float, residual
59
+ ) -> None:
60
+ _check_bias_linear_shapes(input, weight, bias, residual)
61
+
62
+
63
+ @torch.library.register_fake(add_op_namespace_prefix("fp8_linear_bias_gelu_bf16"))
64
+ def _fp8_linear_bias_gelu_bf16_fake(input, weight, bias, alpha: float, out) -> None:
65
+ _check_bias_linear_shapes(input, weight, bias, out)
66
+
67
+
68
+ @torch.library.register_fake(add_op_namespace_prefix("fp8_blockwise_linear_bf16"))
69
+ def _fp8_blockwise_linear_bf16_fake(
70
+ input: torch.Tensor,
71
+ weight: torch.Tensor,
72
+ input_scale: torch.Tensor,
73
+ weight_scale: torch.Tensor,
74
+ out: torch.Tensor,
75
+ ) -> None:
76
+ if input.dim() != 2 or weight.dim() != 2:
77
+ raise RuntimeError("input and weight must be rank-2 tensors")
78
+ m, k = input.shape
79
+ n = weight.shape[0]
80
+ if weight.shape[1] != k or n % 128 or k % 128:
81
+ raise RuntimeError("weight shape is invalid or N/K are not divisible by 128")
82
+ if input_scale.shape != (m, k // 128):
83
+ raise RuntimeError("input_scale must have shape (M, K / 128)")
84
+ if weight_scale.shape != (n // 128, k // 128):
85
+ raise RuntimeError("weight_scale must have shape (N / 128, K / 128)")
86
+ if out.shape != (m, n):
87
+ raise RuntimeError("out must have shape (M, N)")
88
+ return None
89
+
90
+
91
+ @torch.library.register_fake(
92
+ add_op_namespace_prefix("fp8_blockwise_swiglu_quantize_fp8")
93
+ )
94
+ def _fp8_blockwise_swiglu_quantize_fp8_fake(
95
+ input: torch.Tensor,
96
+ gate_up_weight: torch.Tensor,
97
+ input_scale: torch.Tensor,
98
+ gate_up_weight_scale: torch.Tensor,
99
+ output: torch.Tensor,
100
+ output_scale: torch.Tensor,
101
+ ) -> None:
102
+ m, k = input.shape
103
+ if gate_up_weight.dim() != 2 or gate_up_weight.shape[0] % 2:
104
+ raise RuntimeError("gate_up_weight must have shape (2*N, K)")
105
+ n = gate_up_weight.shape[0] // 2
106
+ if gate_up_weight.shape[1] != k or n % 128 or k % 128:
107
+ raise RuntimeError("gate_up_weight shape is invalid or N/K are not divisible by 128")
108
+ if input_scale.shape != (m, k // 128):
109
+ raise RuntimeError("input_scale must have shape (M, K/128)")
110
+ if gate_up_weight_scale.shape != (2 * n // 128, k // 128):
111
+ raise RuntimeError("gate_up_weight_scale must have shape (2*N/128, K/128)")
112
+ if output.shape != (m, n) or output_scale.shape != (m, n // 128):
113
+ raise RuntimeError("output buffers have invalid shapes")
114
+ return None
115
+
116
+
117
+ def select_fp8_linear_tile(m: int, n: int, k: int, variant: int = 0) -> str:
118
+ """Return the FlashRT tile selected by the public dispatcher."""
119
+
120
+ m = int(m)
121
+ n = int(n)
122
+ k = int(k)
123
+ variant = int(variant)
124
+ if m <= 0 or n <= 0 or k <= 0:
125
+ raise RuntimeError("m, n, and k must be positive")
126
+ if k % 16 != 0:
127
+ raise RuntimeError("k must be divisible by 16")
128
+ capability = torch.cuda.get_device_capability() if torch.cuda.is_available() else None
129
+ if capability == (11, 0):
130
+ forced = {1: "sm110_sq_bf16", 2: "sm110_t1_bf16", 3: "sm110_wide_bf16"}
131
+ if variant not in {0, *forced}:
132
+ raise RuntimeError("SM110 variant must be 0 (auto), 1 (Sq), 2 (T1), or 3 (Wide)")
133
+ if n % 16 or k % 16:
134
+ raise RuntimeError("SM110 requires n and k divisible by 16")
135
+ if variant:
136
+ return forced[variant]
137
+ if m >= 512 and k == 2048 and 2048 <= n <= 2560:
138
+ return "sm110_sq_bf16"
139
+ if m >= 512 and n >= 16 * k:
140
+ return "sm110_t1_bf16"
141
+ if m >= 512 and k >= 4 * n:
142
+ return "sm110_wide_bf16"
143
+ if n >= 8 * k:
144
+ return "sm110_wide_bf16"
145
+ if m >= 128 and k >= 4 * n:
146
+ return "sm110_sq_bf16"
147
+ if n == k and m >= 512:
148
+ return "sm110_sq_bf16" if k <= 1024 else "sm110_wide_bf16"
149
+ if n == k and m >= 128:
150
+ return "sm110_wide_bf16"
151
+ return "sm110_t1_bf16"
152
+ if m == 1:
153
+ if k % 32:
154
+ raise RuntimeError("SM120 requires k divisible by 32")
155
+ if variant == 4:
156
+ return "gemv_fp8_m1_w4"
157
+ if variant == 8:
158
+ return "gemv_fp8_m1_w8"
159
+ if variant == 16:
160
+ return "gemv_fp8_m1_w16"
161
+ if variant != 0:
162
+ raise RuntimeError("M=1 variant must be 0, 4, 8, or 16")
163
+ if n <= 2048:
164
+ return "gemv_fp8_m1_w4"
165
+ if n <= 8192:
166
+ return "gemv_fp8_m1_w8"
167
+ return "gemv_fp8_m1_w16"
168
+ if variant != 0:
169
+ raise RuntimeError("small-M dispatcher currently supports variant=0 only")
170
+ if k % 32:
171
+ raise RuntimeError("SM120 requires k divisible by 32")
172
+ if m <= 16:
173
+ if k % 256 == 0:
174
+ return "ld_fp8_gemm_16x128x256_w4" if n % 128 == 0 else "ld_fp8_gemm_16x64x256_w4"
175
+ if n % 256 == 0:
176
+ return "ld_fp8_gemm_16x256x128_w8"
177
+ if n % 192 == 0:
178
+ return "ld_fp8_gemm_16x192x128_w4"
179
+ if n % 128 == 0:
180
+ return "ld_fp8_gemm_16x128x128_w4"
181
+ return "ld_fp8_gemm_16x64x128_w4"
182
+ if m <= 32:
183
+ if k % 256 == 0:
184
+ return "ld_fp8_gemm_32x128x256_w4" if n % 128 == 0 else "ld_fp8_gemm_32x64x256_w4"
185
+ if n % 192 == 0:
186
+ return "ld_fp8_gemm_32x192x128_w4"
187
+ if n % 128 == 0:
188
+ return "ld_fp8_gemm_32x128x128_w4"
189
+ return "ld_fp8_gemm_32x64x128_w4"
190
+ if m <= 64:
191
+ if k % 256 == 0:
192
+ return "ld_fp8_gemm_64x128x256_w4" if n % 128 == 0 else "ld_fp8_gemm_64x64x256_w4"
193
+ if n % 128 == 0:
194
+ return "ld_fp8_gemm_64x128x128_w4"
195
+ return "ld_fp8_gemm_64x64x128_w4"
196
+ raise RuntimeError("only M=1 decode or 2 <= M <= 64 small-M rows are supported")
197
+
198
+
199
+ def fp8_linear_bf16(
200
+ input: torch.Tensor,
201
+ weight: torch.Tensor,
202
+ alpha: float = 1.0,
203
+ out: torch.Tensor | None = None,
204
+ variant: int = 0,
205
+ ) -> torch.Tensor:
206
+ """Compute ``(input @ weight.T) * alpha`` with BF16 output.
207
+
208
+ ``input`` and ``weight`` must be FP8 E4M3 CUDA tensors with shapes
209
+ ``(M, K)`` and ``(N, K)``. ``alpha`` is a host float, normally the product
210
+ of static per-tensor input and weight scales. SM110 uses the production
211
+ CUTLASS Sq/T1/Wide dispatcher over full model row counts; SM120 uses the
212
+ hand-tuned M<=64 path.
213
+ """
214
+
215
+ if out is None:
216
+ out = torch.empty(
217
+ (input.shape[0], weight.shape[0]),
218
+ device=input.device,
219
+ dtype=torch.bfloat16,
220
+ )
221
+ ops.fp8_linear_bf16(input, weight, float(alpha), int(variant), out)
222
+ return out
223
+
224
+
225
+ def fp8_linear_residual_bf16(
226
+ input: torch.Tensor,
227
+ weight: torch.Tensor,
228
+ residual: torch.Tensor,
229
+ alpha: float = 1.0,
230
+ variant: int = 0,
231
+ ) -> torch.Tensor:
232
+ """In-place ``residual += (input @ weight.T) * alpha`` for M=1 decode."""
233
+
234
+ ops.fp8_linear_residual_bf16(input, weight, float(alpha), int(variant), residual)
235
+ return residual
236
+
237
+
238
+ def fp8_linear_bias_bf16(
239
+ input: torch.Tensor,
240
+ weight: torch.Tensor,
241
+ bias: torch.Tensor,
242
+ alpha: float = 1.0,
243
+ out: torch.Tensor | None = None,
244
+ ) -> torch.Tensor:
245
+ """SM110 FP8 linear with fused BF16 bias and BF16 output."""
246
+ if out is None:
247
+ out = torch.empty(
248
+ (input.shape[0], weight.shape[0]),
249
+ device=input.device,
250
+ dtype=torch.bfloat16,
251
+ )
252
+ ops.fp8_linear_bias_bf16(input, weight, bias, float(alpha), out)
253
+ return out
254
+
255
+
256
+ def fp8_linear_bias_residual_bf16(
257
+ input: torch.Tensor,
258
+ weight: torch.Tensor,
259
+ bias: torch.Tensor,
260
+ residual: torch.Tensor,
261
+ alpha: float = 1.0,
262
+ ) -> torch.Tensor:
263
+ """SM110 fused ``residual += alpha * input @ weight.T + bias``."""
264
+ ops.fp8_linear_bias_residual_bf16(
265
+ input, weight, bias, float(alpha), residual
266
+ )
267
+ return residual
268
+
269
+
270
+ def fp8_linear_bias_gelu_bf16(
271
+ input: torch.Tensor,
272
+ weight: torch.Tensor,
273
+ bias: torch.Tensor,
274
+ alpha: float = 1.0,
275
+ out: torch.Tensor | None = None,
276
+ ) -> torch.Tensor:
277
+ """SM110 FP8 linear with fused BF16 bias and GELU epilogue."""
278
+ if out is None:
279
+ out = torch.empty(
280
+ (input.shape[0], weight.shape[0]),
281
+ device=input.device,
282
+ dtype=torch.bfloat16,
283
+ )
284
+ ops.fp8_linear_bias_gelu_bf16(input, weight, bias, float(alpha), out)
285
+ return out
286
+
287
+
288
+ def fp8_blockwise_linear_bf16(
289
+ input: torch.Tensor,
290
+ weight: torch.Tensor,
291
+ input_scale: torch.Tensor,
292
+ weight_scale: torch.Tensor,
293
+ out: torch.Tensor | None = None,
294
+ ) -> torch.Tensor:
295
+ """Block-128 scaled FP8 linear with BF16 output on SM89/SM120."""
296
+
297
+ if out is None:
298
+ out = torch.empty(
299
+ (input.shape[0], weight.shape[0]),
300
+ device=input.device,
301
+ dtype=torch.bfloat16,
302
+ )
303
+ ops.fp8_blockwise_linear_bf16(
304
+ input, weight, input_scale, weight_scale, out
305
+ )
306
+ return out
307
+
308
+
309
+ def fp8_blockwise_swiglu_quantize_fp8(
310
+ input: torch.Tensor,
311
+ gate_up_weight: torch.Tensor,
312
+ input_scale: torch.Tensor,
313
+ gate_up_weight_scale: torch.Tensor,
314
+ *,
315
+ output: torch.Tensor | None = None,
316
+ output_scale: torch.Tensor | None = None,
317
+ ) -> tuple[torch.Tensor, torch.Tensor]:
318
+ """SM89 block-128 FP8 gate/up GEMM + SiLU + FP8 requant producer."""
319
+
320
+ n = gate_up_weight.shape[0] // 2
321
+ if output is None:
322
+ output = torch.empty(
323
+ (input.shape[0], n), device=input.device, dtype=torch.float8_e4m3fn
324
+ )
325
+ if output_scale is None:
326
+ output_scale = torch.empty(
327
+ (input.shape[0], n // 128), device=input.device, dtype=torch.float32
328
+ )
329
+ ops.fp8_blockwise_swiglu_quantize_fp8(
330
+ input, gate_up_weight, input_scale, gate_up_weight_scale,
331
+ output, output_scale
332
+ )
333
+ return output, output_scale
334
+
335
+
336
+ __all__ = [
337
+ "fp8_linear_bf16",
338
+ "fp8_linear_residual_bf16",
339
+ "fp8_linear_bias_bf16",
340
+ "fp8_linear_bias_residual_bf16",
341
+ "fp8_linear_bias_gelu_bf16",
342
+ "fp8_blockwise_linear_bf16",
343
+ "fp8_blockwise_swiglu_quantize_fp8",
344
+ "select_fp8_linear_tile",
345
+ ]
torch-ext/torch_binding.cpp ADDED
@@ -0,0 +1,577 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // SPDX-License-Identifier: Apache-2.0
2
+
3
+ #include <torch/all.h>
4
+ #include <torch/library.h>
5
+
6
+ #include <limits>
7
+ #include <sstream>
8
+ #include <string>
9
+
10
+ #if defined(CUDA_KERNEL)
11
+ #include <ATen/cuda/CUDAContext.h>
12
+ #include <c10/cuda/CUDAGuard.h>
13
+ #endif
14
+
15
+ #if !defined(FLASHRT_FP8_GEMM_SOURCE_SM89_ONLY) && \
16
+ !defined(FLASHRT_FP8_GEMM_SOURCE_SM110_ONLY)
17
+ #include "fp8_gemv_m1_sm120.cuh"
18
+ #include "fp8_smallM_handtuned_ldmatrix_sm120.cuh"
19
+ #include "fp8_smallM_handtuned_sm120.cuh"
20
+ #include "cutlass_sm120_block128_fp8_gemm.cuh"
21
+ #endif
22
+ #if !defined(FLASHRT_FP8_GEMM_SOURCE_SM120_ONLY) && \
23
+ !defined(FLASHRT_FP8_GEMM_SOURCE_SM110_ONLY)
24
+ #include "fp8_block128_gemm_mma_sm89.cuh"
25
+ #include "fp8_gemv_m1_sm89.cuh"
26
+ #endif
27
+ #if !defined(FLASHRT_FP8_GEMM_SOURCE_SM89_ONLY) && \
28
+ !defined(FLASHRT_FP8_GEMM_SOURCE_SM120_ONLY)
29
+ #include "cutlass_sm110_fp8_gemm.cuh"
30
+ #endif
31
+ #include "cublaslt_fp8_bias_sm110.cuh"
32
+ #include "registration.h"
33
+ #include "torch_binding.h"
34
+
35
+ namespace {
36
+
37
+ using KernelFn = int (*)(const void*, const void*, void*, int, int, int, float, cudaStream_t);
38
+ using Sm110KernelFn = int (*)(void*, void*, void*, int, int, int, float, float,
39
+ cudaStream_t);
40
+
41
+ void check_cuda_contiguous(torch::Tensor const& tensor, const char* name) {
42
+ TORCH_CHECK(tensor.is_cuda(), name, " must be a CUDA tensor");
43
+ TORCH_CHECK(tensor.is_contiguous(), name, " must be contiguous");
44
+ }
45
+
46
+ void check_fp8_matrix(torch::Tensor const& tensor, const char* name) {
47
+ check_cuda_contiguous(tensor, name);
48
+ TORCH_CHECK(tensor.scalar_type() == c10::ScalarType::Float8_e4m3fn,
49
+ name, " must have dtype torch.float8_e4m3fn");
50
+ TORCH_CHECK(tensor.dim() == 2, name, " must have shape (rows, cols)");
51
+ TORCH_CHECK(tensor.size(0) > 0 && tensor.size(1) > 0,
52
+ name, " dimensions must be positive");
53
+ }
54
+
55
+ void check_bf16_matrix(torch::Tensor const& tensor, const char* name) {
56
+ check_cuda_contiguous(tensor, name);
57
+ TORCH_CHECK(tensor.scalar_type() == torch::kBFloat16,
58
+ name, " must have dtype torch.bfloat16");
59
+ TORCH_CHECK(tensor.dim() == 2, name, " must have shape (rows, cols)");
60
+ }
61
+
62
+ void check_fp32_matrix(torch::Tensor const& tensor, const char* name) {
63
+ check_cuda_contiguous(tensor, name);
64
+ TORCH_CHECK(tensor.scalar_type() == torch::kFloat32,
65
+ name, " must have dtype torch.float32");
66
+ TORCH_CHECK(tensor.dim() == 2, name, " must be rank 2");
67
+ }
68
+
69
+ void check_bf16_vector(torch::Tensor const& tensor, const char* name) {
70
+ check_cuda_contiguous(tensor, name);
71
+ TORCH_CHECK(tensor.scalar_type() == torch::kBFloat16,
72
+ name, " must have dtype torch.bfloat16");
73
+ TORCH_CHECK(tensor.dim() == 1, name, " must be rank 1");
74
+ }
75
+
76
+ int checked_positive_int(int64_t value, const char* name) {
77
+ TORCH_CHECK(value > 0 && value <= std::numeric_limits<int>::max(),
78
+ name, " must fit in positive int");
79
+ return static_cast<int>(value);
80
+ }
81
+
82
+ void check_common(
83
+ torch::Tensor const& input,
84
+ torch::Tensor const& weight,
85
+ torch::Tensor const& out) {
86
+ check_fp8_matrix(input, "input");
87
+ check_fp8_matrix(weight, "weight");
88
+ check_bf16_matrix(out, "out");
89
+ TORCH_CHECK(input.get_device() == weight.get_device(),
90
+ "input and weight must be on the same CUDA device");
91
+ TORCH_CHECK(input.get_device() == out.get_device(),
92
+ "input and out must be on the same CUDA device");
93
+ TORCH_CHECK(input.size(1) == weight.size(1),
94
+ "input.shape[1] must equal weight.shape[1]");
95
+ TORCH_CHECK(out.sizes() == torch::IntArrayRef({input.size(0), weight.size(0)}),
96
+ "out must have shape (input.shape[0], weight.shape[0])");
97
+ TORCH_CHECK(input.size(1) % 16 == 0,
98
+ "K must be divisible by 16 for FP8 tensor-core kernels");
99
+ }
100
+
101
+ std::string tile_name_for_shape(int M, int N, int K, int variant) {
102
+ if (M == 1) {
103
+ if (variant == 4) return "gemv_fp8_m1_w4";
104
+ if (variant == 8) return "gemv_fp8_m1_w8";
105
+ if (variant == 16) return "gemv_fp8_m1_w16";
106
+ TORCH_CHECK(variant == 0, "M=1 variant must be 0, 4, 8, or 16");
107
+ if (N <= 2048) return "gemv_fp8_m1_w4";
108
+ if (N <= 8192) return "gemv_fp8_m1_w8";
109
+ return "gemv_fp8_m1_w16";
110
+ }
111
+
112
+ TORCH_CHECK(variant == 0,
113
+ "small-M public dispatcher currently supports variant=0 only; "
114
+ "use benchmark scripts for tile sweeps before promoting a forced variant");
115
+ if (M <= 16) {
116
+ if (K % 256 == 0) {
117
+ if (N % 128 == 0) return "ld_fp8_gemm_16x128x256_w4";
118
+ return "ld_fp8_gemm_16x64x256_w4";
119
+ }
120
+ if (N % 256 == 0) return "ld_fp8_gemm_16x256x128_w8";
121
+ if (N % 192 == 0) return "ld_fp8_gemm_16x192x128_w4";
122
+ if (N % 128 == 0) return "ld_fp8_gemm_16x128x128_w4";
123
+ return "ld_fp8_gemm_16x64x128_w4";
124
+ }
125
+ if (M <= 32) {
126
+ if (K % 256 == 0) {
127
+ if (N % 128 == 0) return "ld_fp8_gemm_32x128x256_w4";
128
+ return "ld_fp8_gemm_32x64x256_w4";
129
+ }
130
+ if (N % 192 == 0) return "ld_fp8_gemm_32x192x128_w4";
131
+ if (N % 128 == 0) return "ld_fp8_gemm_32x128x128_w4";
132
+ return "ld_fp8_gemm_32x64x128_w4";
133
+ }
134
+ if (M <= 64) {
135
+ if (K % 256 == 0) {
136
+ if (N % 128 == 0) return "ld_fp8_gemm_64x128x256_w4";
137
+ return "ld_fp8_gemm_64x64x256_w4";
138
+ }
139
+ if (N % 128 == 0) return "ld_fp8_gemm_64x128x128_w4";
140
+ return "ld_fp8_gemm_64x64x128_w4";
141
+ }
142
+ TORCH_CHECK(false, "M > 64 is not exposed in fp8-gemm v1; pending tile tuning");
143
+ TORCH_CHECK(false, "unsupported M");
144
+ }
145
+
146
+ KernelFn kernel_for_tile(std::string const& tile, bool residual) {
147
+ #if defined(CUDA_KERNEL) && !defined(FLASHRT_FP8_GEMM_SOURCE_SM89_ONLY) && \
148
+ !defined(FLASHRT_FP8_GEMM_SOURCE_SM110_ONLY)
149
+ namespace gemv = flash_rt::gemm::gemv_m1;
150
+ namespace hand = flash_rt::gemm::smallM_hand;
151
+ namespace ld = flash_rt::gemm::smallM_ld;
152
+ if (tile == "gemv_fp8_m1_w4") return residual ? gemv::gemv_fp8_m1_resadd_w4 : gemv::gemv_fp8_m1_w4;
153
+ if (tile == "gemv_fp8_m1_w8") return residual ? gemv::gemv_fp8_m1_resadd_w8 : gemv::gemv_fp8_m1_w8;
154
+ if (tile == "gemv_fp8_m1_w16") {
155
+ TORCH_CHECK(!residual, "residual path supports only GEMV w4/w8 variants");
156
+ return gemv::gemv_fp8_m1_w16;
157
+ }
158
+ TORCH_CHECK(!residual, "residual path supports M=1 GEMV only");
159
+ if (tile == "ld_fp8_gemm_16x64x128_w4") return ld::ld_fp8_gemm_16x64x128_w4;
160
+ if (tile == "ld_fp8_gemm_16x128x128_w4") return ld::ld_fp8_gemm_16x128x128_w4;
161
+ if (tile == "ld_fp8_gemm_16x256x128_w8") return ld::ld_fp8_gemm_16x256x128_w8;
162
+ if (tile == "ld_fp8_gemm_16x192x128_w4") return ld::ld_fp8_gemm_16x192x128_w4;
163
+ if (tile == "ld_fp8_gemm_16x64x256_w4") return ld::ld_fp8_gemm_16x64x256_w4;
164
+ if (tile == "ld_fp8_gemm_16x128x256_w4") return ld::ld_fp8_gemm_16x128x256_w4;
165
+ if (tile == "ld_fp8_gemm_32x64x128_w4") return ld::ld_fp8_gemm_32x64x128_w4;
166
+ if (tile == "ld_fp8_gemm_32x128x128_w4") return ld::ld_fp8_gemm_32x128x128_w4;
167
+ if (tile == "ld_fp8_gemm_32x192x128_w4") return ld::ld_fp8_gemm_32x192x128_w4;
168
+ if (tile == "ld_fp8_gemm_32x64x256_w4") return ld::ld_fp8_gemm_32x64x256_w4;
169
+ if (tile == "ld_fp8_gemm_32x128x256_w4") return ld::ld_fp8_gemm_32x128x256_w4;
170
+ if (tile == "ld_fp8_gemm_64x64x128_w4") return ld::ld_fp8_gemm_64x64x128_w4;
171
+ if (tile == "ld_fp8_gemm_64x128x128_w4") return ld::ld_fp8_gemm_64x128x128_w4;
172
+ if (tile == "ld_fp8_gemm_64x64x256_w4") return ld::ld_fp8_gemm_64x64x256_w4;
173
+ if (tile == "ld_fp8_gemm_64x128x256_w4") return ld::ld_fp8_gemm_64x128x256_w4;
174
+ #else
175
+ (void)tile;
176
+ (void)residual;
177
+ #endif
178
+ TORCH_CHECK(false, "unsupported FP8 GEMM tile: ", tile);
179
+ }
180
+
181
+ const char* sm110_tile_name_for_shape(int M, int N, int K, int variant) {
182
+ if (variant == 1) return "sm110_sq_bf16";
183
+ if (variant == 2) return "sm110_t1_bf16";
184
+ if (variant == 3) return "sm110_wide_bf16";
185
+ // Thor sweep envelope (PI0.5/GROOT/Cosmos Edge/LingBot): Wide wins
186
+ // N>=8K expansions and larger square projections. Sq wins smaller square
187
+ // vision projections and larger-row K>=4N contractions; T1 wins the
188
+ // remaining projection/down paths.
189
+ // The forced variants remain available for diagnostic tile sweeps.
190
+ if (M >= 512 && K == 2048 && N >= 2048 && N <= 2560) {
191
+ return "sm110_sq_bf16";
192
+ }
193
+ if (M >= 512 && N >= 16 * K) {
194
+ return "sm110_t1_bf16";
195
+ }
196
+ if (M >= 512 && K >= 4 * N) {
197
+ return "sm110_wide_bf16";
198
+ }
199
+ if (N >= 8 * K) return "sm110_wide_bf16";
200
+ if (M >= 128 && K >= 4 * N) return "sm110_sq_bf16";
201
+ if (N == K && M >= 512) {
202
+ return K <= 1024 ? "sm110_sq_bf16" : "sm110_wide_bf16";
203
+ }
204
+ if (N == K && M >= 128) {
205
+ return "sm110_wide_bf16";
206
+ }
207
+ return "sm110_t1_bf16";
208
+ }
209
+
210
+ Sm110KernelFn sm110_kernel_for_shape(int M, int N, int K, int variant) {
211
+ #if defined(CUDA_KERNEL) && !defined(FLASHRT_FP8_GEMM_SOURCE_SM89_ONLY) && \
212
+ !defined(FLASHRT_FP8_GEMM_SOURCE_SM120_ONLY)
213
+ const char* tile = sm110_tile_name_for_shape(M, N, K, variant);
214
+ if (std::string(tile) == "sm110_wide_bf16") return &cutlass_fp8_wide_bf16out;
215
+ if (std::string(tile) == "sm110_t1_bf16") return &cutlass_fp8_t1_bf16out;
216
+ return &cutlass_fp8_sq_bf16out;
217
+ #else
218
+ (void)M;
219
+ (void)N;
220
+ (void)K;
221
+ (void)variant;
222
+ TORCH_CHECK(false, "SM110 FP8 GEMM source is not present in this build");
223
+ #endif
224
+ }
225
+
226
+ void launch(
227
+ torch::Tensor const& input,
228
+ torch::Tensor const& weight,
229
+ double alpha,
230
+ int64_t variant64,
231
+ torch::Tensor& out,
232
+ bool residual) {
233
+ check_common(input, weight, out);
234
+ const int M = checked_positive_int(input.size(0), "M");
235
+ const int K = checked_positive_int(input.size(1), "K");
236
+ const int N = checked_positive_int(weight.size(0), "N");
237
+ const int variant = static_cast<int>(variant64);
238
+ if (residual) {
239
+ TORCH_CHECK(M == 1, "fp8_linear_residual_bf16 supports only M=1");
240
+ }
241
+ #if defined(CUDA_KERNEL)
242
+ at::cuda::CUDAGuard device_guard(input.device());
243
+ auto* props = at::cuda::getDeviceProperties(input.get_device());
244
+ TORCH_CHECK((props->major == 11 && props->minor == 0) ||
245
+ (props->major == 12 && props->minor == 0),
246
+ "fp8_linear_bf16 requires SM110 or SM120; got SM",
247
+ props->major, props->minor);
248
+ auto stream = at::cuda::getCurrentCUDAStream(input.get_device()).stream();
249
+ if (props->major == 11) {
250
+ TORCH_CHECK(variant >= 0 && variant <= 3,
251
+ "SM110 variant must be 0 (auto), 1 (Sq), 2 (T1), or 3 (Wide)");
252
+ TORCH_CHECK(N % 16 == 0 && K % 16 == 0,
253
+ "SM110 CUTLASS FP8 GEMM requires N and K divisible by 16");
254
+ #if defined(FLASHRT_FP8_GEMM_SOURCE_SM89_ONLY) || \
255
+ defined(FLASHRT_FP8_GEMM_SOURCE_SM120_ONLY)
256
+ TORCH_CHECK(false, "SM110 FP8 GEMM source is not present in this build");
257
+ #else
258
+ Sm110KernelFn fn = sm110_kernel_for_shape(M, N, K, variant);
259
+ const int rc = fn(input.data_ptr(), weight.data_ptr(), out.data_ptr(),
260
+ M, N, K, static_cast<float>(alpha),
261
+ residual ? 1.0f : 0.0f, stream);
262
+ TORCH_CHECK(rc == 0, sm110_tile_name_for_shape(M, N, K, variant),
263
+ " failed with rc=", rc);
264
+ #endif
265
+ } else {
266
+ TORCH_CHECK(K % 32 == 0,
267
+ "SM120 FP8 GEMM requires K divisible by 32");
268
+ TORCH_CHECK(M <= 64,
269
+ "SM120 per-tensor FP8 path supports only M <= 64; got M=", M);
270
+ if (residual) {
271
+ TORCH_CHECK(M == 1, "SM120 residual path supports only M=1");
272
+ }
273
+ const std::string tile = tile_name_for_shape(M, N, K, variant);
274
+ #if defined(FLASHRT_FP8_GEMM_SOURCE_SM89_ONLY) || \
275
+ defined(FLASHRT_FP8_GEMM_SOURCE_SM110_ONLY)
276
+ TORCH_CHECK(false, "SM120 per-tensor FP8 source is not present in this build");
277
+ #else
278
+ KernelFn fn = kernel_for_tile(tile, residual);
279
+ const int rc = fn(input.data_ptr(), weight.data_ptr(), out.data_ptr(),
280
+ M, N, K, static_cast<float>(alpha), stream);
281
+ TORCH_CHECK(rc == 0, tile, " failed with rc=", rc);
282
+ #endif
283
+ }
284
+ #else
285
+ TORCH_CHECK(false, "fp8-gemm was not built with CUDA support");
286
+ #endif
287
+ }
288
+
289
+ void launch_bias(
290
+ torch::Tensor const& input,
291
+ torch::Tensor const& weight,
292
+ torch::Tensor const& bias,
293
+ double alpha,
294
+ torch::Tensor& out,
295
+ double beta,
296
+ FlashRtFp8BiasEpilogue epilogue,
297
+ const char* op_name) {
298
+ check_common(input, weight, out);
299
+ check_bf16_vector(bias, "bias");
300
+ TORCH_CHECK(bias.size(0) == weight.size(0),
301
+ "bias must have shape (weight.shape[0],)");
302
+ TORCH_CHECK(input.get_device() == bias.get_device(),
303
+ "input and bias must be on the same CUDA device");
304
+ #if defined(CUDA_KERNEL)
305
+ at::cuda::CUDAGuard device_guard(input.device());
306
+ auto* props = at::cuda::getDeviceProperties(input.get_device());
307
+ TORCH_CHECK(props->major == 11 && props->minor == 0,
308
+ op_name, " requires SM110; got SM", props->major, props->minor);
309
+ #if defined(FLASHRT_FP8_GEMM_SOURCE_SM89_ONLY) || \
310
+ defined(FLASHRT_FP8_GEMM_SOURCE_SM120_ONLY)
311
+ TORCH_CHECK(false, "SM110 FP8 bias GEMM source is not present in this build");
312
+ #else
313
+ auto stream = at::cuda::getCurrentCUDAStream(input.get_device()).stream();
314
+ const int M = checked_positive_int(input.size(0), "M");
315
+ const int N = checked_positive_int(weight.size(0), "N");
316
+ const int K = checked_positive_int(input.size(1), "K");
317
+ int rc;
318
+ if (M >= 512 && K >= 3 * N) {
319
+ rc = epilogue == FlashRtFp8BiasEpilogue::kBiasGelu
320
+ ? cutlass_fp8_wide_bias_gelu_bf16out(
321
+ input.data_ptr(), weight.data_ptr(), bias.data_ptr(),
322
+ out.data_ptr(), M, N, K, static_cast<float>(alpha), stream)
323
+ : cutlass_fp8_wide_bias_bf16out(
324
+ input.data_ptr(), weight.data_ptr(), bias.data_ptr(),
325
+ out.data_ptr(), M, N, K, static_cast<float>(alpha),
326
+ static_cast<float>(beta), stream);
327
+ } else {
328
+ rc = fp8_linear_bias_sm110_bf16(
329
+ input.data_ptr(), weight.data_ptr(), bias.data_ptr(), out.data_ptr(),
330
+ M, N, K, static_cast<float>(alpha), static_cast<float>(beta),
331
+ epilogue, stream);
332
+ }
333
+ TORCH_CHECK(rc == 0, op_name, " failed with rc=", rc);
334
+ #endif
335
+ #else
336
+ TORCH_CHECK(false, "fp8-gemm was not built with CUDA support");
337
+ #endif
338
+ }
339
+
340
+ } // namespace
341
+
342
+ void fp8_linear_bf16(
343
+ torch::Tensor const& input,
344
+ torch::Tensor const& weight,
345
+ double alpha,
346
+ int64_t variant,
347
+ torch::Tensor& out) {
348
+ launch(input, weight, alpha, variant, out, false);
349
+ }
350
+
351
+ void fp8_linear_residual_bf16(
352
+ torch::Tensor const& input,
353
+ torch::Tensor const& weight,
354
+ double alpha,
355
+ int64_t variant,
356
+ torch::Tensor& residual) {
357
+ launch(input, weight, alpha, variant, residual, true);
358
+ }
359
+
360
+ void fp8_linear_bias_bf16(
361
+ torch::Tensor const& input,
362
+ torch::Tensor const& weight,
363
+ torch::Tensor const& bias,
364
+ double alpha,
365
+ torch::Tensor& out) {
366
+ launch_bias(input, weight, bias, alpha, out, 0.0,
367
+ FlashRtFp8BiasEpilogue::kBias, "fp8_linear_bias_bf16");
368
+ }
369
+
370
+ void fp8_linear_bias_residual_bf16(
371
+ torch::Tensor const& input,
372
+ torch::Tensor const& weight,
373
+ torch::Tensor const& bias,
374
+ double alpha,
375
+ torch::Tensor& residual) {
376
+ launch_bias(input, weight, bias, alpha, residual, 1.0,
377
+ FlashRtFp8BiasEpilogue::kBias,
378
+ "fp8_linear_bias_residual_bf16");
379
+ }
380
+
381
+ void fp8_linear_bias_gelu_bf16(
382
+ torch::Tensor const& input,
383
+ torch::Tensor const& weight,
384
+ torch::Tensor const& bias,
385
+ double alpha,
386
+ torch::Tensor& out) {
387
+ launch_bias(input, weight, bias, alpha, out, 0.0,
388
+ FlashRtFp8BiasEpilogue::kBiasGelu,
389
+ "fp8_linear_bias_gelu_bf16");
390
+ }
391
+
392
+ void fp8_blockwise_linear_bf16(
393
+ torch::Tensor const& input,
394
+ torch::Tensor const& weight,
395
+ torch::Tensor const& input_scale,
396
+ torch::Tensor const& weight_scale,
397
+ torch::Tensor& out) {
398
+ check_fp8_matrix(input, "input");
399
+ check_fp8_matrix(weight, "weight");
400
+ check_fp32_matrix(input_scale, "input_scale");
401
+ check_fp32_matrix(weight_scale, "weight_scale");
402
+ check_bf16_matrix(out, "out");
403
+ const int64_t M = input.size(0);
404
+ const int64_t K = input.size(1);
405
+ const int64_t N = weight.size(0);
406
+ TORCH_CHECK(weight.size(1) == K,
407
+ "weight must have shape (N, input.shape[1])");
408
+ TORCH_CHECK(K % 128 == 0 && N % 128 == 0,
409
+ "N and K must be divisible by 128");
410
+ TORCH_CHECK(input_scale.sizes() ==
411
+ torch::IntArrayRef({M, K / 128}),
412
+ "input_scale must have shape (M, K / 128)");
413
+ TORCH_CHECK(weight_scale.sizes() ==
414
+ torch::IntArrayRef({N / 128, K / 128}),
415
+ "weight_scale must have shape (N / 128, K / 128)");
416
+ TORCH_CHECK(out.sizes() == torch::IntArrayRef({M, N}),
417
+ "out must have shape (M, N)");
418
+ TORCH_CHECK(input.get_device() == weight.get_device() &&
419
+ input.get_device() == input_scale.get_device() &&
420
+ input.get_device() == weight_scale.get_device() &&
421
+ input.get_device() == out.get_device(),
422
+ "all tensors must be on the same CUDA device");
423
+ #if defined(CUDA_KERNEL)
424
+ at::cuda::CUDAGuard device_guard(input.device());
425
+ auto* props = at::cuda::getDeviceProperties(input.get_device());
426
+ TORCH_CHECK((props->major == 8 && props->minor == 9) ||
427
+ (props->major == 12 && props->minor == 0),
428
+ "fp8_blockwise_linear_bf16 requires SM89 or SM120; got SM",
429
+ props->major, props->minor);
430
+ auto stream = at::cuda::getCurrentCUDAStream(input.get_device()).stream();
431
+ if (props->major == 8) {
432
+ #if defined(FLASHRT_FP8_GEMM_SOURCE_SM120_ONLY) || \
433
+ defined(FLASHRT_FP8_GEMM_SOURCE_SM110_ONLY)
434
+ TORCH_CHECK(false, "SM89 blockwise kernels are not present in this source-test build");
435
+ #else
436
+ int rc;
437
+ if (M == 1) {
438
+ namespace gemv89 = flash_rt::gemm::gemv_m1_sm89;
439
+ if (N <= 2048) {
440
+ rc = gemv89::gemv_fp8_block128_m1_w4(
441
+ input.data_ptr(), weight.data_ptr(), out.data_ptr(), 1,
442
+ checked_positive_int(N, "N"), checked_positive_int(K, "K"),
443
+ input_scale.data_ptr<float>(), weight_scale.data_ptr<float>(),
444
+ 1.0f, stream);
445
+ } else if (N <= 8192) {
446
+ rc = gemv89::gemv_fp8_block128_m1_w8(
447
+ input.data_ptr(), weight.data_ptr(), out.data_ptr(), 1,
448
+ checked_positive_int(N, "N"), checked_positive_int(K, "K"),
449
+ input_scale.data_ptr<float>(), weight_scale.data_ptr<float>(),
450
+ 1.0f, stream);
451
+ } else {
452
+ rc = gemv89::gemv_fp8_block128_m1_w16(
453
+ input.data_ptr(), weight.data_ptr(), out.data_ptr(), 1,
454
+ checked_positive_int(N, "N"), checked_positive_int(K, "K"),
455
+ input_scale.data_ptr<float>(), weight_scale.data_ptr<float>(),
456
+ 1.0f, stream);
457
+ }
458
+ } else {
459
+ rc = flash_rt::gemm::block128_sm89::
460
+ fp8_block128_gemm_blockscaled_sm89_bf16out(
461
+ input.data_ptr(), weight.data_ptr(), out.data_ptr(),
462
+ checked_positive_int(M, "M"), checked_positive_int(N, "N"),
463
+ checked_positive_int(K, "K"), input_scale.data_ptr<float>(),
464
+ weight_scale.data_ptr<float>(), stream);
465
+ }
466
+ TORCH_CHECK(rc == 0, "SM89 blockwise FP8 linear failed with rc=", rc);
467
+ #endif
468
+ } else {
469
+ #if defined(FLASHRT_FP8_GEMM_SOURCE_SM89_ONLY) || \
470
+ defined(FLASHRT_FP8_GEMM_SOURCE_SM110_ONLY)
471
+ TORCH_CHECK(false, "SM120 blockwise kernel is not present in this source-test build");
472
+ #else
473
+ flash_rt::gemm::fp8_block128_gemm_cutlass_sm120_bf16out(
474
+ input.data_ptr(), weight.data_ptr(), out.data_ptr(),
475
+ checked_positive_int(M, "M"), checked_positive_int(N, "N"),
476
+ checked_positive_int(K, "K"), input_scale.data_ptr<float>(),
477
+ weight_scale.data_ptr<float>(), stream);
478
+ #endif
479
+ }
480
+ #else
481
+ TORCH_CHECK(false, "fp8-gemm was not built with CUDA support");
482
+ #endif
483
+ }
484
+
485
+ void fp8_blockwise_swiglu_quantize_fp8(
486
+ torch::Tensor const& input,
487
+ torch::Tensor const& gate_up_weight,
488
+ torch::Tensor const& input_scale,
489
+ torch::Tensor const& gate_up_weight_scale,
490
+ torch::Tensor& output,
491
+ torch::Tensor& output_scale) {
492
+ check_fp8_matrix(input, "input");
493
+ check_fp8_matrix(gate_up_weight, "gate_up_weight");
494
+ check_fp32_matrix(input_scale, "input_scale");
495
+ check_fp32_matrix(gate_up_weight_scale, "gate_up_weight_scale");
496
+ check_fp8_matrix(output, "output");
497
+ check_fp32_matrix(output_scale, "output_scale");
498
+ const int64_t M = input.size(0);
499
+ const int64_t K = input.size(1);
500
+ TORCH_CHECK(gate_up_weight.size(0) % 2 == 0 &&
501
+ gate_up_weight.size(1) == K,
502
+ "gate_up_weight must have shape (2*N, K)");
503
+ const int64_t N = gate_up_weight.size(0) / 2;
504
+ TORCH_CHECK(M > 0 && M <= 256,
505
+ "SM89 fused SwiGLU producer supports 1 <= M <= 256");
506
+ TORCH_CHECK(N % 128 == 0 && K % 128 == 0,
507
+ "N and K must be divisible by 128");
508
+ TORCH_CHECK(input_scale.sizes() == torch::IntArrayRef({M, K / 128}),
509
+ "input_scale must have shape (M, K / 128)");
510
+ TORCH_CHECK(gate_up_weight_scale.sizes() ==
511
+ torch::IntArrayRef({2 * N / 128, K / 128}),
512
+ "gate_up_weight_scale must have shape (2*N/128, K/128)");
513
+ TORCH_CHECK(output.sizes() == torch::IntArrayRef({M, N}),
514
+ "output must have shape (M, N)");
515
+ TORCH_CHECK(output_scale.sizes() == torch::IntArrayRef({M, N / 128}),
516
+ "output_scale must have shape (M, N/128)");
517
+ TORCH_CHECK(input.get_device() == gate_up_weight.get_device() &&
518
+ input.get_device() == input_scale.get_device() &&
519
+ input.get_device() == gate_up_weight_scale.get_device() &&
520
+ input.get_device() == output.get_device() &&
521
+ input.get_device() == output_scale.get_device(),
522
+ "all tensors must be on the same CUDA device");
523
+ #if defined(CUDA_KERNEL)
524
+ at::cuda::CUDAGuard device_guard(input.device());
525
+ auto* props = at::cuda::getDeviceProperties(input.get_device());
526
+ TORCH_CHECK(props->major == 8 && props->minor == 9,
527
+ "fp8_blockwise_swiglu_quantize_fp8 requires SM89; got SM",
528
+ props->major, props->minor);
529
+ #if defined(FLASHRT_FP8_GEMM_SOURCE_SM120_ONLY) || \
530
+ defined(FLASHRT_FP8_GEMM_SOURCE_SM110_ONLY)
531
+ TORCH_CHECK(false, "SM89 fused producer is not present in this source-test build");
532
+ #else
533
+ auto stream = at::cuda::getCurrentCUDAStream(input.get_device()).stream();
534
+ int rc = flash_rt::gemm::block128_sm89::
535
+ fp8_bs_geglu_silu_fold_sm89_32x128_w4_s1(
536
+ input.data_ptr(), gate_up_weight.data_ptr(),
537
+ checked_positive_int(M, "M"), checked_positive_int(N, "N"),
538
+ checked_positive_int(K, "K"), input_scale.data_ptr<float>(),
539
+ gate_up_weight_scale.data_ptr<float>(), output.data_ptr(),
540
+ output_scale.data_ptr<float>(), stream);
541
+ TORCH_CHECK(rc == 0, "SM89 fused SwiGLU FP8 producer failed with rc=", rc);
542
+ #endif
543
+ #else
544
+ TORCH_CHECK(false, "fp8-gemm was not built with CUDA support");
545
+ #endif
546
+ }
547
+
548
+ TORCH_LIBRARY_EXPAND(TORCH_EXTENSION_NAME, ops) {
549
+ ops.def("fp8_linear_bf16(Tensor input, Tensor weight, float alpha, int variant, Tensor! out) -> ()");
550
+ ops.def("fp8_linear_residual_bf16(Tensor input, Tensor weight, float alpha, int variant, Tensor! residual) -> ()");
551
+ ops.def("fp8_linear_bias_bf16(Tensor input, Tensor weight, Tensor bias, float alpha, Tensor! out) -> ()");
552
+ ops.def("fp8_linear_bias_residual_bf16(Tensor input, Tensor weight, Tensor bias, float alpha, Tensor! residual) -> ()");
553
+ ops.def("fp8_linear_bias_gelu_bf16(Tensor input, Tensor weight, Tensor bias, float alpha, Tensor! out) -> ()");
554
+ ops.def("fp8_blockwise_linear_bf16("
555
+ "Tensor input, Tensor weight, Tensor input_scale, "
556
+ "Tensor weight_scale, Tensor! out) -> ()");
557
+ ops.def("fp8_blockwise_swiglu_quantize_fp8("
558
+ "Tensor input, Tensor gate_up_weight, Tensor input_scale, "
559
+ "Tensor gate_up_weight_scale, Tensor! output, Tensor! output_scale) -> ()");
560
+ #if defined(CUDA_KERNEL)
561
+ ops.impl("fp8_linear_bf16", torch::kCUDA, &fp8_linear_bf16);
562
+ ops.impl("fp8_linear_residual_bf16", torch::kCUDA, &fp8_linear_residual_bf16);
563
+ ops.impl("fp8_linear_bias_bf16", torch::kCUDA, &fp8_linear_bias_bf16);
564
+ ops.impl("fp8_linear_bias_residual_bf16", torch::kCUDA,
565
+ &fp8_linear_bias_residual_bf16);
566
+ ops.impl("fp8_linear_bias_gelu_bf16", torch::kCUDA,
567
+ &fp8_linear_bias_gelu_bf16);
568
+ ops.impl("fp8_blockwise_linear_bf16",
569
+ torch::kCUDA,
570
+ &fp8_blockwise_linear_bf16);
571
+ ops.impl("fp8_blockwise_swiglu_quantize_fp8",
572
+ torch::kCUDA,
573
+ &fp8_blockwise_swiglu_quantize_fp8);
574
+ #endif
575
+ }
576
+
577
+ REGISTER_EXTENSION(TORCH_EXTENSION_NAME)
torch-ext/torch_binding.h ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ #pragma once
3
+
4
+ #include <torch/all.h>
5
+
6
+ void fp8_linear_bf16(
7
+ torch::Tensor const& input,
8
+ torch::Tensor const& weight,
9
+ double alpha,
10
+ int64_t variant,
11
+ torch::Tensor& out);
12
+
13
+ void fp8_linear_residual_bf16(
14
+ torch::Tensor const& input,
15
+ torch::Tensor const& weight,
16
+ double alpha,
17
+ int64_t variant,
18
+ torch::Tensor& residual);
19
+
20
+ void fp8_linear_bias_bf16(
21
+ torch::Tensor const& input,
22
+ torch::Tensor const& weight,
23
+ torch::Tensor const& bias,
24
+ double alpha,
25
+ torch::Tensor& out);
26
+
27
+ void fp8_linear_bias_residual_bf16(
28
+ torch::Tensor const& input,
29
+ torch::Tensor const& weight,
30
+ torch::Tensor const& bias,
31
+ double alpha,
32
+ torch::Tensor& residual);
33
+
34
+ void fp8_linear_bias_gelu_bf16(
35
+ torch::Tensor const& input,
36
+ torch::Tensor const& weight,
37
+ torch::Tensor const& bias,
38
+ double alpha,
39
+ torch::Tensor& out);
40
+
41
+ void fp8_blockwise_linear_bf16(
42
+ torch::Tensor const& input,
43
+ torch::Tensor const& weight,
44
+ torch::Tensor const& input_scale,
45
+ torch::Tensor const& weight_scale,
46
+ torch::Tensor& out);
47
+
48
+ void fp8_blockwise_swiglu_quantize_fp8(
49
+ torch::Tensor const& input,
50
+ torch::Tensor const& gate_up_weight,
51
+ torch::Tensor const& input_scale,
52
+ torch::Tensor const& gate_up_weight_scale,
53
+ torch::Tensor& output,
54
+ torch::Tensor& output_scale);