Publish PI0.5 Thor kernels from 503100c (torch213-cxx11-cu130-aarch64-linux)
Browse files- CARD.md +28 -0
- README.md +135 -0
- SYNC.md +80 -0
- VALIDATION.md +155 -0
- benchmarks/README.md +11 -0
- benchmarks/RESULTS.md +144 -0
- benchmarks/benchmark.py +22 -2
- benchmarks/benchmark_bias.py +241 -0
- build.toml +75 -0
- build/torch213-cxx11-cu130-aarch64-linux/__init__.py +93 -2
- build/torch213-cxx11-cu130-aarch64-linux/{fp8_gemm_source_test.abi3.so → _fp8_gemm_cuda_503100c.abi3.so} +2 -2
- build/torch213-cxx11-cu130-aarch64-linux/_ops.py +3 -3
- build/torch213-cxx11-cu130-aarch64-linux/metadata.json +5 -9
- csrc/README.md +7 -0
- csrc/cublaslt_fp8_bias_sm110.cu +179 -0
- csrc/cublaslt_fp8_bias_sm110.cuh +21 -0
- csrc/cutlass_sm110_fp8_gemm.cu +176 -0
- csrc/cutlass_sm110_fp8_gemm.cuh +31 -0
- csrc/cutlass_sm120_block128_fp8_gemm.cu +273 -0
- csrc/cutlass_sm120_block128_fp8_gemm.cuh +51 -0
- csrc/fp8_block128_gemm_mma_sm89.cu +318 -0
- csrc/fp8_block128_gemm_mma_sm89.cuh +102 -0
- csrc/fp8_bs_gemm_device.cuh +1327 -0
- csrc/fp8_gemv_m1_sm120.cu +159 -0
- csrc/fp8_gemv_m1_sm120.cuh +31 -0
- csrc/fp8_gemv_m1_sm89.cu +188 -0
- csrc/fp8_gemv_m1_sm89.cuh +38 -0
- csrc/fp8_smallM_handtuned_ldmatrix_sm120.cu +377 -0
- csrc/fp8_smallM_handtuned_ldmatrix_sm120.cuh +68 -0
- csrc/fp8_smallM_handtuned_sm120.cu +338 -0
- csrc/fp8_smallM_handtuned_sm120.cuh +89 -0
- csrc/gemm_types_sm110.h +362 -0
- examples/README.md +3 -0
- flake.lock +118 -0
- flake.nix +20 -0
- scripts/README.md +3 -0
- tests/README.md +9 -0
- tests/test_fp8_gemm.py +698 -0
- torch-ext/README.md +6 -0
- torch-ext/fp8_gemm/__init__.py +345 -0
- torch-ext/torch_binding.cpp +577 -0
- 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 = [
|
|
|
|
|
|
|
|
|
|
| 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(
|
| 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 %
|
| 99 |
-
raise RuntimeError("k must be divisible by
|
| 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:
|
| 3 |
-
size
|
|
|
|
| 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
|
| 3 |
-
ops = torch.ops.
|
| 4 |
|
| 5 |
def add_op_namespace_prefix(op_name: str):
|
| 6 |
-
return f"
|
|
|
|
| 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": "
|
| 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": "
|
| 17 |
-
"
|
| 18 |
-
"_ops.py": "
|
| 19 |
"fp8_gemm/__init__.py": "v6p5XMfQzddhi1fLSAw4HX9CyS0rQsidvu9VsT01xi4="
|
| 20 |
}
|
| 21 |
},
|
| 22 |
"provenance": {
|
| 23 |
"kernel": {
|
| 24 |
-
"sha": "
|
| 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);
|