liangsu9988 commited on
Commit
76522c0
·
verified ·
1 Parent(s): ec63d06

Promote latest kernel artifacts to main

Browse files
README.md CHANGED
@@ -1,9 +1,116 @@
1
- # flashrt/fp4-gemm
2
 
3
- This repository is a compatibility mirror for older `kernels` clients
4
- that resolve repositories through the default Hugging Face model repo API.
5
 
6
- Canonical Kernel Hub repo: https://huggingface.co/kernels/flashrt/fp4-gemm
 
 
 
7
 
8
- Do not edit this mirror by hand. It is generated from the Kernel Hub
9
- `vN` branches and contains the same `build/**` artifacts.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # fp4-gemm
2
 
3
+ FlashRT native Blackwell NVFP4 A4W4 GEMM kernels.
 
4
 
5
+ This package consumes packed FP4 E2M1 tensors plus CUTLASS Sm1xx SFA/SFB scale
6
+ buffers and produces BF16 output. It is designed to pair with
7
+ `flashrt/fp4-fused-ops` and other static low-bit transformer/diffuser runtime
8
+ paths.
9
 
10
+ ## Available Functions
11
+
12
+ - `sfa_size_bytes(rows, dim)`
13
+ - `quantize_fp4_sfa_fp16(x, packed=None, sfa=None, is_sfb=False)`
14
+ - `quantize_fp4_sfa_bf16(x, packed=None, sfa=None, is_sfb=False)`
15
+ - `dequantize_fp4_sfa_fp16(packed, sfa, out=None, is_sfb=False)`
16
+ - `nvfp4_gemm_bf16(a_packed, b_packed, sfa, sfb, alpha=1.0, out=None, variant=-1)`
17
+ - `nvfp4_gemm_bias_bf16(a_packed, b_packed, sfa, sfb, bias, out=None)`
18
+ - `nvfp4_gemm_bias_residual_bf16(a_packed, b_packed, sfa, sfb, bias, residual, out=None)`
19
+ - `nvfp4_gemm_residual_bf16(a_packed, b_packed, sfa, sfb, residual, alpha=1.0, out=None)`
20
+ - `nvfp4_gemm_bias_gelu_bf16(a_packed, b_packed, sfa, sfb, bias, alpha=1.0, out=None)`
21
+ - `nvfp4_gemm_bias_gelu_nvfp4(a_packed, b_packed, sfa, sfb, bias, alpha=1.0, out_packed=None, out_sfa=None)`
22
+ - `nvfp4_gemm_streamk_bf16(a_packed, b_packed, sfa, sfb, alpha=1.0, out=None)`
23
+ - `nvfp4_gemm_streamk_bias_bf16(a_packed, b_packed, sfa, sfb, bias, alpha=1.0, out=None)`
24
+ - `fp4_w4a16_linear_bf16(...)` is retained as a compatibility alias
25
+
26
+ ## Tensor Contract
27
+
28
+ - `a_packed`: `torch.uint8`, shape `(M, K / 2)`.
29
+ - `b_packed`: `torch.uint8`, shape `(N, K / 2)`.
30
+ - `sfa`: `torch.uint8`, CUTLASS SFA layout for `(M, K)`.
31
+ - `sfb`: `torch.uint8`, CUTLASS SFB layout for `(N, K)`.
32
+ - output: `torch.bfloat16`, shape `(M, N)`.
33
+ - `K` must be divisible by 16.
34
+ - Targets: Blackwell `sm_110a` (Jetson AGX Thor, CUDA 13+) and `sm_120a`
35
+ (RTX Blackwell, CUDA 12.8+).
36
+
37
+ `variant` selects the CUTLASS schedule:
38
+
39
+ - `-1`: architecture-aware auto-dispatch (public default).
40
+ - `0`: default `<128,128,256>` cooperative schedule.
41
+ - `1`: widen `<128,256,128>` schedule, intended for very large `N`.
42
+ - `2`: pingpong schedule for A/B testing shape-specific wins.
43
+
44
+ The canonical linear API and FP4/SFA quantize/dequantize helpers are available
45
+ on both SM110 and SM120. SM110 additionally provides the GROOT N1.7 production
46
+ epilogues `nvfp4_gemm_bias_bf16`, `nvfp4_gemm_bias_residual_bf16`, and
47
+ `nvfp4_gemm_bias_gelu_nvfp4`. The latter emits packed FP4 plus CUTLASS SFA so
48
+ the following projection can consume it without a BF16 materialization and a
49
+ standalone quantization launch. Stream-K and the older BF16 GELU epilogue keep
50
+ their existing SM120 dispatch and reject unsupported architectures explicitly.
51
+
52
+ The SM110 release gate includes the production `(M,N,K)` shapes
53
+ `(41,4608,1536)`, `(41,6144,1536)`, and `(41,1536,6144)`, plus the legacy
54
+ `M=51` compatibility row. The kernels are the native sources used by FlashRT's
55
+ GROOT N1.7 Thor NVFP4 pipeline.
56
+
57
+ ## Minimal Usage
58
+
59
+ ```python
60
+ from kernels import get_kernel
61
+ import torch
62
+
63
+ ops = get_kernel("flashrt/fp4-gemm", version=1, trust_remote_code=True)
64
+
65
+ x = torch.randn((32, 256), device="cuda", dtype=torch.float16)
66
+ w = torch.randn((512, 256), device="cuda", dtype=torch.float16)
67
+
68
+ a_packed, sfa = ops.quantize_fp4_sfa_fp16(x, is_sfb=False)
69
+ b_packed, sfb = ops.quantize_fp4_sfa_fp16(w, is_sfb=True)
70
+
71
+ y = ops.nvfp4_gemm_bf16(a_packed, b_packed, sfa, sfb, alpha=1.0)
72
+ ```
73
+
74
+ For BF16 model activations, use the direct producer so the hot path does not
75
+ materialize an intermediate FP16 tensor:
76
+
77
+ ```python
78
+ x_bf16 = torch.randn((1, 5120), device="cuda", dtype=torch.bfloat16)
79
+ a_packed, sfa = ops.quantize_fp4_sfa_bf16(x_bf16)
80
+ ```
81
+
82
+ The BF16 entry writes the same E2M1 bytes and CUTLASS SFA/SFB layout as
83
+ `quantize_fp4_sfa_fp16(x_bf16.to(torch.float16))` for finite FP16-range
84
+ inputs. It is an additive API; the existing FP16 producer remains unchanged.
85
+
86
+ The quantize/dequantize helpers are included for examples and validation. A
87
+ production runtime should keep weights prepacked and should avoid quantizing in
88
+ the hot path unless that producer kernel is part of the intended low-bit block.
89
+
90
+ Use the bias/GELU and residual variants to avoid returning to BF16
91
+ elementwise code between low-bit GEMMs. Stream-K variants are selected only
92
+ for the validated large down-projection shapes; unsupported shapes reject
93
+ rather than silently selecting a losing schedule.
94
+
95
+ ## Validation
96
+
97
+ ```bash
98
+ python fp4-gemm/tests/test_fp4_gemm.py --backend source --mode full
99
+ python fp4-gemm/tests/test_fp4_gemm.py --backend installed --mode full \
100
+ --artifact fp4-gemm/build/torch211-cxx11-cu128-x86_64-linux
101
+ python fp4-gemm/benchmarks/benchmark.py --backend installed --mode headline \
102
+ --artifact fp4-gemm/build/torch211-cxx11-cu128-x86_64-linux
103
+
104
+ # Thor model-shape gate
105
+ python fp4-gemm/tests/test_fp4_gemm.py --backend installed \
106
+ --mode thor-models \
107
+ --artifact fp4-gemm/build/torch211-cxx11-cu130-aarch64-linux
108
+ ```
109
+
110
+ The correctness reference dequantizes the same FP4/SFA and FP4/SFB inputs used
111
+ by the kernel, then computes the PyTorch GEMM reference from those dequantized
112
+ low-bit values.
113
+
114
+ The producer gate also checks the BF16 direct entry byte-for-byte against the
115
+ established FP16 compatibility chain at decode widths 5120, 6144 and 17408,
116
+ plus multi-row activation and SFB layouts.
build/torch211-cxx11-cu128-x86_64-linux/__init__.py CHANGED
@@ -19,7 +19,8 @@ def sfa_size_bytes(rows: int, dim: int) -> int:
19
  def _alloc_fp4(rows: int, dim: int, device: torch.device | str):
20
  return (
21
  torch.empty((rows, dim // 2), device=device, dtype=torch.uint8),
22
- torch.empty((sfa_size_bytes(rows, dim),), device=device, dtype=torch.uint8),
 
23
  )
24
 
25
 
@@ -36,6 +37,36 @@ def _linear_fake(
36
  return None
37
 
38
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  @torch.library.register_fake(add_op_namespace_prefix("fp4_w4a4_gemv_warpsplit_bf16"))
40
  def _gemv_warpsplit_fake(a_packed, b_packed, sfa, sfb, out, alpha: float = 1.0, warps: int = 4, stages: int = 4) -> None:
41
  if a_packed.shape[0] != 1:
@@ -58,11 +89,46 @@ def _legacy_linear_fake(
58
  return None
59
 
60
 
 
 
 
 
 
 
 
 
 
 
61
  @torch.library.register_fake(add_op_namespace_prefix("quantize_fp4_sfa_fp16"))
62
  def _quant_fake(x: torch.Tensor, packed: torch.Tensor, sfa: torch.Tensor, is_sfb: bool = False) -> None:
63
  return None
64
 
65
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  @torch.library.register_fake(add_op_namespace_prefix("quantize_fp4_sfa_bf16"))
67
  def _quant_bf16_fake(x: torch.Tensor, packed: torch.Tensor, sfa: torch.Tensor, is_sfb: bool = False) -> None:
68
  return None
@@ -112,6 +178,160 @@ def quantize_fp4_sfa_fp16(
112
  return packed, sfa
113
 
114
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
  def quantize_fp4_sfa_bf16(
116
  x: torch.Tensor,
117
  packed: torch.Tensor | None = None,
@@ -152,6 +372,169 @@ def nvfp4_gemm_bf16(
152
  return out
153
 
154
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
  def fp4_w4a4_gemv_warpsplit_bf16(
156
  a_packed: torch.Tensor,
157
  b_packed: torch.Tensor,
@@ -191,6 +574,45 @@ def fp4_w4a16_linear_bf16(
191
  )
192
 
193
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
194
  def nvfp4_gemm_residual_bf16(
195
  a_packed: torch.Tensor,
196
  b_packed: torch.Tensor,
@@ -243,7 +665,7 @@ def nvfp4_gemm_bias_gelu_nvfp4(
243
  if out_packed is None:
244
  out_packed = torch.empty((m, n // 2), device=a_packed.device, dtype=torch.uint8)
245
  if out_sfa is None:
246
- out_sfa = torch.empty((sfa_size_bytes(m, n),), device=a_packed.device, dtype=torch.uint8)
247
  ops.nvfp4_gemm_bias_gelu_nvfp4(
248
  a_packed, b_packed, sfa, sfb, bias, out_packed, out_sfa, float(alpha)
249
  )
@@ -292,16 +714,33 @@ def nvfp4_gemm_streamk_bias_bf16(
292
 
293
 
294
  __all__ = [
 
 
295
  "dequantize_fp4_sfa_fp16",
 
296
  "fp4_w4a16_linear_bf16",
297
  "fp4_w4a4_gemv_warpsplit_bf16",
298
  "nvfp4_gemm_bf16",
 
 
 
 
 
 
 
299
  "nvfp4_gemm_bias_gelu_bf16",
300
  "nvfp4_gemm_bias_gelu_nvfp4",
 
301
  "nvfp4_gemm_residual_bf16",
 
302
  "nvfp4_gemm_streamk_bf16",
303
  "nvfp4_gemm_streamk_bias_bf16",
304
  "quantize_fp4_sfa_fp16",
 
 
 
 
 
305
  "quantize_fp4_sfa_bf16",
306
  "sfa_size_bytes",
307
  ]
 
19
  def _alloc_fp4(rows: int, dim: int, device: torch.device | str):
20
  return (
21
  torch.empty((rows, dim // 2), device=device, dtype=torch.uint8),
22
+ # Tile-layout padding entries are not written by every quantizer.
23
+ torch.zeros((sfa_size_bytes(rows, dim),), device=device, dtype=torch.uint8),
24
  )
25
 
26
 
 
37
  return None
38
 
39
 
40
+ @torch.library.register_fake(add_op_namespace_prefix("nvfp4_gemm_fp16"))
41
+ def _linear_fp16_fake(a_packed, b_packed, sfa, sfb, out, alpha: float = 1.0, variant: int = -1) -> None:
42
+ return None
43
+
44
+
45
+ @torch.library.register_fake(add_op_namespace_prefix("nvfp4_gemm_variant_bf16"))
46
+ def _linear_variant_bf16_fake(a, b, sfa, sfb, out, alpha: float = 1.0, variant: int = 7) -> None:
47
+ return None
48
+
49
+
50
+ @torch.library.register_fake(add_op_namespace_prefix("nvfp4_gemm_nvfp4"))
51
+ def _linear_fp4out_fake(a, b, sfa, sfb, out_packed, out_sfa) -> None:
52
+ return None
53
+
54
+
55
+ @torch.library.register_fake(add_op_namespace_prefix("nvfp4_gemm_geglu_nvfp4_fp16"))
56
+ def _geglu_fp4_fake(a, b, sfa, sfb, scratch, out_packed, out_sfa, skinny: bool = False) -> None:
57
+ return None
58
+
59
+
60
+ @torch.library.register_fake(add_op_namespace_prefix("nvfp4_gemm_bias_gelu_nvfp4_fp16"))
61
+ def _bias_gelu_fp4_fp16_fake(a, b, sfa, sfb, bias, out_packed, out_sfa) -> None:
62
+ return None
63
+
64
+
65
+ @torch.library.register_fake(add_op_namespace_prefix("nvfp4_gemm_bias_residual_fp16"))
66
+ def _bias_residual_fp16_fake(a, b, sfa, sfb, bias, residual, out) -> None:
67
+ return None
68
+
69
+
70
  @torch.library.register_fake(add_op_namespace_prefix("fp4_w4a4_gemv_warpsplit_bf16"))
71
  def _gemv_warpsplit_fake(a_packed, b_packed, sfa, sfb, out, alpha: float = 1.0, warps: int = 4, stages: int = 4) -> None:
72
  if a_packed.shape[0] != 1:
 
89
  return None
90
 
91
 
92
+ @torch.library.register_fake(add_op_namespace_prefix("nvfp4_gemm_bias_bf16"))
93
+ def _bias_fake(a, b, sfa, sfb, bias, out) -> None:
94
+ return None
95
+
96
+
97
+ @torch.library.register_fake(add_op_namespace_prefix("nvfp4_gemm_bias_residual_bf16"))
98
+ def _bias_residual_fake(a, b, sfa, sfb, bias, residual, out) -> None:
99
+ return None
100
+
101
+
102
  @torch.library.register_fake(add_op_namespace_prefix("quantize_fp4_sfa_fp16"))
103
  def _quant_fake(x: torch.Tensor, packed: torch.Tensor, sfa: torch.Tensor, is_sfb: bool = False) -> None:
104
  return None
105
 
106
 
107
+ @torch.library.register_fake(add_op_namespace_prefix("quantize_fp4_sfa_mse_fp16"))
108
+ def _quant_mse_fake(x, packed, sfa, is_sfb: bool = False) -> None:
109
+ return None
110
+
111
+
112
+ @torch.library.register_fake(add_op_namespace_prefix("quantize_fp4_sfa_mse_bf16"))
113
+ def _quant_mse_bf16_fake(x, packed, sfa, is_sfb: bool = False) -> None:
114
+ return None
115
+
116
+
117
+ @torch.library.register_fake(add_op_namespace_prefix("quantize_e0m3_sfa_fp16"))
118
+ def _quant_e0m3_fake(x, packed, sfa, is_sfb: bool = False) -> None:
119
+ return None
120
+
121
+
122
+ @torch.library.register_fake(add_op_namespace_prefix("e0m3_weight_gemm_fp16"))
123
+ def _e0m3_gemm_fake(a, b, sfa, sfb, out, alpha: float = 1.0, a_format: int = 1) -> None:
124
+ return None
125
+
126
+
127
+ @torch.library.register_fake(add_op_namespace_prefix("nvfp4_gemm_relu2_nvfp4"))
128
+ def _relu2_gemm_fake(a, b, sfa, sfb, out_packed, out_sfa) -> None:
129
+ return None
130
+
131
+
132
  @torch.library.register_fake(add_op_namespace_prefix("quantize_fp4_sfa_bf16"))
133
  def _quant_bf16_fake(x: torch.Tensor, packed: torch.Tensor, sfa: torch.Tensor, is_sfb: bool = False) -> None:
134
  return None
 
178
  return packed, sfa
179
 
180
 
181
+ def quantize_fp4_sfa_mse_fp16(
182
+ x: torch.Tensor,
183
+ packed: torch.Tensor | None = None,
184
+ sfa: torch.Tensor | None = None,
185
+ is_sfb: bool = False,
186
+ ):
187
+ """Pack FP16 weights using per-block reconstruction-MSE scale search."""
188
+ if packed is None or sfa is None:
189
+ packed, sfa = _alloc_fp4(x.shape[0], x.shape[1], x.device)
190
+ ops.quantize_fp4_sfa_mse_fp16(x, packed, sfa, bool(is_sfb))
191
+ return packed, sfa
192
+
193
+
194
+ def quantize_fp4_sfa_mse_bf16(
195
+ x: torch.Tensor,
196
+ packed: torch.Tensor | None = None,
197
+ sfa: torch.Tensor | None = None,
198
+ is_sfb: bool = False,
199
+ ):
200
+ """Pack BF16 weights using per-block reconstruction-MSE scale search."""
201
+ if packed is None or sfa is None:
202
+ packed, sfa = _alloc_fp4(x.shape[0], x.shape[1], x.device)
203
+ ops.quantize_fp4_sfa_mse_bf16(x, packed, sfa, bool(is_sfb))
204
+ return packed, sfa
205
+
206
+
207
+ def aligned_fp4_dim(dim: int, alignment: int = 32) -> int:
208
+ """Return the physical FP4 GEMM dimension for a logical dimension."""
209
+ if dim <= 0 or alignment <= 0 or alignment % 16 != 0:
210
+ raise ValueError("dim must be positive and alignment divisible by 16")
211
+ return ((dim + alignment - 1) // alignment) * alignment
212
+
213
+
214
+ def quantize_fp4_sfa_padded_bf16(
215
+ x: torch.Tensor,
216
+ *,
217
+ alignment: int = 32,
218
+ is_sfb: bool = False,
219
+ ):
220
+ """Bind-time BF16 pack with zero padding on the final dimension.
221
+
222
+ The returned packed tensor uses the physical aligned width. Keep the
223
+ logical width returned as the third item for slicing final model outputs.
224
+ This helper allocates and is intentionally not a runtime hot-path op.
225
+ """
226
+ if x.ndim != 2 or x.dtype != torch.bfloat16:
227
+ raise ValueError("x must be a 2-D BF16 tensor")
228
+ logical_dim = int(x.shape[1])
229
+ physical_dim = aligned_fp4_dim(logical_dim, alignment)
230
+ if physical_dim == logical_dim:
231
+ padded = x.contiguous()
232
+ else:
233
+ padded = torch.zeros(
234
+ (x.shape[0], physical_dim), device=x.device, dtype=x.dtype
235
+ )
236
+ padded[:, :logical_dim].copy_(x)
237
+ packed, sfa = quantize_fp4_sfa_bf16(padded, is_sfb=is_sfb)
238
+ return packed, sfa, logical_dim
239
+
240
+
241
+ def pack_nvfp4_weight_bf16(
242
+ weight: torch.Tensor,
243
+ bias: torch.Tensor | None = None,
244
+ *,
245
+ alignment: int = 32,
246
+ mse: bool = False,
247
+ ):
248
+ """Zero-pad and pack a logical ``(N, K)`` BF16 weight at bind time.
249
+
250
+ Both physical ``N`` and ``K`` are aligned. This is the model-neutral
251
+ contract used by widths such as SigLIP's logical 4304, which becomes 4320
252
+ for Blackwell NVFP4 TMA. The hot path consumes the returned static tensors
253
+ directly and performs no padding or allocation.
254
+ """
255
+ if weight.ndim != 2 or weight.dtype != torch.bfloat16:
256
+ raise ValueError("weight must be a 2-D BF16 tensor")
257
+ logical_n, logical_k = map(int, weight.shape)
258
+ physical_n = aligned_fp4_dim(logical_n, alignment)
259
+ physical_k = aligned_fp4_dim(logical_k, alignment)
260
+ padded_weight = torch.zeros(
261
+ (physical_n, physical_k), device=weight.device, dtype=weight.dtype
262
+ )
263
+ padded_weight[:logical_n, :logical_k].copy_(weight)
264
+ quantize = quantize_fp4_sfa_mse_bf16 if mse else quantize_fp4_sfa_bf16
265
+ packed, sfb = quantize(padded_weight, is_sfb=True)
266
+ padded_bias = None
267
+ if bias is not None:
268
+ if bias.ndim != 1 or bias.numel() != logical_n:
269
+ raise ValueError("bias must have shape (N,)")
270
+ if bias.dtype != torch.bfloat16 or bias.device != weight.device:
271
+ raise ValueError("bias must match the weight dtype and device")
272
+ padded_bias = torch.zeros(
273
+ (physical_n,), device=bias.device, dtype=bias.dtype
274
+ )
275
+ padded_bias[:logical_n].copy_(bias)
276
+ return packed, sfb, padded_bias, (logical_n, logical_k)
277
+
278
+
279
+ def quantize_e0m3_sfa_fp16(
280
+ x: torch.Tensor,
281
+ packed: torch.Tensor | None = None,
282
+ sfa: torch.Tensor | None = None,
283
+ is_sfb: bool = False,
284
+ ):
285
+ """Quantize FP16 to packed E0M3 with CUTLASS SFA/SFB layout."""
286
+ if packed is None or sfa is None:
287
+ packed, sfa = _alloc_fp4(x.shape[0], x.shape[1], x.device)
288
+ ops.quantize_e0m3_sfa_fp16(x, packed, sfa, bool(is_sfb))
289
+ return packed, sfa
290
+
291
+
292
+ def e0m3_weight_gemm_fp16(
293
+ a_packed: torch.Tensor,
294
+ b_packed: torch.Tensor,
295
+ sfa: torch.Tensor,
296
+ sfb: torch.Tensor,
297
+ *,
298
+ alpha: float = 1.0,
299
+ a_format: int = 1,
300
+ out: torch.Tensor | None = None,
301
+ ):
302
+ """SM110 GEMM with E0M3 weights and E2M1 or E0M3 activations."""
303
+ if out is None:
304
+ out = torch.empty(
305
+ (a_packed.shape[0], b_packed.shape[0]),
306
+ device=a_packed.device,
307
+ dtype=torch.float16,
308
+ )
309
+ ops.e0m3_weight_gemm_fp16(
310
+ a_packed, b_packed, sfa, sfb, out, float(alpha), int(a_format)
311
+ )
312
+ return out
313
+
314
+
315
+ def nvfp4_gemm_relu2_nvfp4(
316
+ a_packed: torch.Tensor,
317
+ b_packed: torch.Tensor,
318
+ sfa: torch.Tensor,
319
+ sfb: torch.Tensor,
320
+ *,
321
+ out_packed: torch.Tensor | None = None,
322
+ out_sfa: torch.Tensor | None = None,
323
+ ):
324
+ """SM110 NVFP4 GEMM with fused ReLU-squared and NVFP4 output."""
325
+ if out_packed is None or out_sfa is None:
326
+ out_packed, out_sfa = _alloc_fp4(
327
+ a_packed.shape[0], b_packed.shape[0], a_packed.device
328
+ )
329
+ ops.nvfp4_gemm_relu2_nvfp4(
330
+ a_packed, b_packed, sfa, sfb, out_packed, out_sfa
331
+ )
332
+ return out_packed, out_sfa
333
+
334
+
335
  def quantize_fp4_sfa_bf16(
336
  x: torch.Tensor,
337
  packed: torch.Tensor | None = None,
 
372
  return out
373
 
374
 
375
+ def nvfp4_gemm_fp16(
376
+ a_packed: torch.Tensor,
377
+ b_packed: torch.Tensor,
378
+ sfa: torch.Tensor,
379
+ sfb: torch.Tensor,
380
+ alpha: float = 1.0,
381
+ out: torch.Tensor | None = None,
382
+ variant: int = -1,
383
+ ) -> torch.Tensor:
384
+ """SM110 native NVFP4 GEMM with FP16 output."""
385
+ if out is None:
386
+ out = torch.empty(
387
+ (a_packed.shape[0], b_packed.shape[0]),
388
+ device=a_packed.device,
389
+ dtype=torch.float16,
390
+ )
391
+ ops.nvfp4_gemm_fp16(
392
+ a_packed, b_packed, sfa, sfb, out, float(alpha), int(variant)
393
+ )
394
+ return out
395
+
396
+
397
+ def nvfp4_gemm_variant_bf16(
398
+ a_packed: torch.Tensor,
399
+ b_packed: torch.Tensor,
400
+ sfa: torch.Tensor,
401
+ sfb: torch.Tensor,
402
+ alpha: float = 1.0,
403
+ out: torch.Tensor | None = None,
404
+ variant: int = 7,
405
+ ) -> torch.Tensor:
406
+ """SM110 native NVFP4 GEMM with BF16 output.
407
+
408
+ ``variant=7`` serves large-M encoder/SigLIP shapes; ``variant=10`` is
409
+ the narrow-N PI0.5 decoder tile. The indices match FlashRT native.
410
+ """
411
+ if out is None:
412
+ out = torch.empty(
413
+ (a_packed.shape[0], b_packed.shape[0]),
414
+ device=a_packed.device,
415
+ dtype=torch.bfloat16,
416
+ )
417
+ ops.nvfp4_gemm_variant_bf16(
418
+ a_packed, b_packed, sfa, sfb, out, float(alpha), int(variant)
419
+ )
420
+ return out
421
+
422
+
423
+ def nvfp4_gemm_nvfp4(
424
+ a_packed: torch.Tensor,
425
+ b_packed: torch.Tensor,
426
+ sfa: torch.Tensor,
427
+ sfb: torch.Tensor,
428
+ *,
429
+ out_packed: torch.Tensor | None = None,
430
+ out_sfa: torch.Tensor | None = None,
431
+ ) -> tuple[torch.Tensor, torch.Tensor]:
432
+ """SM110 NVFP4 GEMM with an NVFP4 output and CUTLASS SFA layout."""
433
+ if out_packed is None or out_sfa is None:
434
+ out_packed, out_sfa = _alloc_fp4(
435
+ a_packed.shape[0], b_packed.shape[0], a_packed.device
436
+ )
437
+ ops.nvfp4_gemm_nvfp4(
438
+ a_packed, b_packed, sfa, sfb, out_packed, out_sfa
439
+ )
440
+ return out_packed, out_sfa
441
+
442
+
443
+ def nvfp4_gemm_geglu_nvfp4_fp16(
444
+ a_packed: torch.Tensor,
445
+ b_interleaved_packed: torch.Tensor,
446
+ sfa: torch.Tensor,
447
+ sfb: torch.Tensor,
448
+ *,
449
+ skinny: bool = False,
450
+ scratch: torch.Tensor | None = None,
451
+ out_packed: torch.Tensor | None = None,
452
+ out_sfa: torch.Tensor | None = None,
453
+ ) -> tuple[torch.Tensor, torch.Tensor]:
454
+ """GEMM with fused GeGLU and compact NVFP4 output on SM110.
455
+
456
+ ``b_interleaved_packed`` stores gate/up rows pairwise, so its first
457
+ dimension is twice the logical hidden width.
458
+ """
459
+ m, n_twice = a_packed.shape[0], b_interleaved_packed.shape[0]
460
+ hidden = n_twice // 2
461
+ if scratch is None:
462
+ scratch = torch.empty((m, hidden), device=a_packed.device, dtype=torch.uint8)
463
+ if out_packed is None:
464
+ out_packed = torch.empty((m, hidden // 2), device=a_packed.device, dtype=torch.uint8)
465
+ if out_sfa is None:
466
+ out_sfa = torch.zeros((sfa_size_bytes(m, hidden),), device=a_packed.device, dtype=torch.uint8)
467
+ ops.nvfp4_gemm_geglu_nvfp4_fp16(
468
+ a_packed, b_interleaved_packed, sfa, sfb, scratch,
469
+ out_packed, out_sfa, bool(skinny)
470
+ )
471
+ return out_packed, out_sfa
472
+
473
+
474
+ def cutlass_fp4_gemm_geglu_il_hw_v10(
475
+ a_packed: torch.Tensor,
476
+ b_interleaved_packed: torch.Tensor,
477
+ sfa: torch.Tensor,
478
+ sfb: torch.Tensor,
479
+ *,
480
+ scratch: torch.Tensor | None = None,
481
+ out_packed: torch.Tensor | None = None,
482
+ out_sfa: torch.Tensor | None = None,
483
+ ) -> tuple[torch.Tensor, torch.Tensor]:
484
+ """Explicit public alias for the native SM110 v10 GeGLU schedule."""
485
+ return nvfp4_gemm_geglu_nvfp4_fp16(
486
+ a_packed,
487
+ b_interleaved_packed,
488
+ sfa,
489
+ sfb,
490
+ skinny=True,
491
+ scratch=scratch,
492
+ out_packed=out_packed,
493
+ out_sfa=out_sfa,
494
+ )
495
+
496
+
497
+ def nvfp4_gemm_bias_gelu_nvfp4_fp16(
498
+ a_packed: torch.Tensor,
499
+ b_packed: torch.Tensor,
500
+ sfa: torch.Tensor,
501
+ sfb: torch.Tensor,
502
+ bias: torch.Tensor,
503
+ *,
504
+ out_packed: torch.Tensor | None = None,
505
+ out_sfa: torch.Tensor | None = None,
506
+ ) -> tuple[torch.Tensor, torch.Tensor]:
507
+ """FP16-bias GEMM with fused GELU and NVFP4 output on SM110."""
508
+ m, n = a_packed.shape[0], b_packed.shape[0]
509
+ if out_packed is None:
510
+ out_packed = torch.empty((m, n // 2), device=a_packed.device, dtype=torch.uint8)
511
+ if out_sfa is None:
512
+ out_sfa = torch.zeros((sfa_size_bytes(m, n),), device=a_packed.device, dtype=torch.uint8)
513
+ ops.nvfp4_gemm_bias_gelu_nvfp4_fp16(
514
+ a_packed, b_packed, sfa, sfb, bias, out_packed, out_sfa
515
+ )
516
+ return out_packed, out_sfa
517
+
518
+
519
+ def nvfp4_gemm_bias_residual_fp16(
520
+ a_packed: torch.Tensor,
521
+ b_packed: torch.Tensor,
522
+ sfa: torch.Tensor,
523
+ sfb: torch.Tensor,
524
+ bias: torch.Tensor,
525
+ residual: torch.Tensor,
526
+ *,
527
+ out: torch.Tensor | None = None,
528
+ ) -> torch.Tensor:
529
+ """FP16-output GEMM with fused FP16 bias and residual on SM110."""
530
+ if out is None:
531
+ out = torch.empty_like(residual)
532
+ ops.nvfp4_gemm_bias_residual_fp16(
533
+ a_packed, b_packed, sfa, sfb, bias, residual, out
534
+ )
535
+ return out
536
+
537
+
538
  def fp4_w4a4_gemv_warpsplit_bf16(
539
  a_packed: torch.Tensor,
540
  b_packed: torch.Tensor,
 
574
  )
575
 
576
 
577
+ def nvfp4_gemm_bias_bf16(
578
+ a_packed: torch.Tensor,
579
+ b_packed: torch.Tensor,
580
+ sfa: torch.Tensor,
581
+ sfb: torch.Tensor,
582
+ bias: torch.Tensor,
583
+ *,
584
+ out: torch.Tensor | None = None,
585
+ ) -> torch.Tensor:
586
+ """SM110 NVFP4 GEMM with a fused per-column BF16 bias."""
587
+ if out is None:
588
+ out = torch.empty(
589
+ (a_packed.shape[0], b_packed.shape[0]),
590
+ device=a_packed.device,
591
+ dtype=torch.bfloat16,
592
+ )
593
+ ops.nvfp4_gemm_bias_bf16(a_packed, b_packed, sfa, sfb, bias, out)
594
+ return out
595
+
596
+
597
+ def nvfp4_gemm_bias_residual_bf16(
598
+ a_packed: torch.Tensor,
599
+ b_packed: torch.Tensor,
600
+ sfa: torch.Tensor,
601
+ sfb: torch.Tensor,
602
+ bias: torch.Tensor,
603
+ residual: torch.Tensor,
604
+ *,
605
+ out: torch.Tensor | None = None,
606
+ ) -> torch.Tensor:
607
+ """SM110 NVFP4 GEMM with fused BF16 bias and residual add."""
608
+ if out is None:
609
+ out = torch.empty_like(residual)
610
+ ops.nvfp4_gemm_bias_residual_bf16(
611
+ a_packed, b_packed, sfa, sfb, bias, residual, out
612
+ )
613
+ return out
614
+
615
+
616
  def nvfp4_gemm_residual_bf16(
617
  a_packed: torch.Tensor,
618
  b_packed: torch.Tensor,
 
665
  if out_packed is None:
666
  out_packed = torch.empty((m, n // 2), device=a_packed.device, dtype=torch.uint8)
667
  if out_sfa is None:
668
+ out_sfa = torch.zeros((sfa_size_bytes(m, n),), device=a_packed.device, dtype=torch.uint8)
669
  ops.nvfp4_gemm_bias_gelu_nvfp4(
670
  a_packed, b_packed, sfa, sfb, bias, out_packed, out_sfa, float(alpha)
671
  )
 
714
 
715
 
716
  __all__ = [
717
+ "aligned_fp4_dim",
718
+ "cutlass_fp4_gemm_geglu_il_hw_v10",
719
  "dequantize_fp4_sfa_fp16",
720
+ "e0m3_weight_gemm_fp16",
721
  "fp4_w4a16_linear_bf16",
722
  "fp4_w4a4_gemv_warpsplit_bf16",
723
  "nvfp4_gemm_bf16",
724
+ "nvfp4_gemm_fp16",
725
+ "nvfp4_gemm_variant_bf16",
726
+ "nvfp4_gemm_nvfp4",
727
+ "nvfp4_gemm_geglu_nvfp4_fp16",
728
+ "nvfp4_gemm_bias_gelu_nvfp4_fp16",
729
+ "nvfp4_gemm_bias_residual_fp16",
730
+ "nvfp4_gemm_bias_bf16",
731
  "nvfp4_gemm_bias_gelu_bf16",
732
  "nvfp4_gemm_bias_gelu_nvfp4",
733
+ "nvfp4_gemm_bias_residual_bf16",
734
  "nvfp4_gemm_residual_bf16",
735
+ "nvfp4_gemm_relu2_nvfp4",
736
  "nvfp4_gemm_streamk_bf16",
737
  "nvfp4_gemm_streamk_bias_bf16",
738
  "quantize_fp4_sfa_fp16",
739
+ "quantize_fp4_sfa_mse_fp16",
740
+ "quantize_fp4_sfa_mse_bf16",
741
+ "quantize_fp4_sfa_padded_bf16",
742
+ "pack_nvfp4_weight_bf16",
743
+ "quantize_e0m3_sfa_fp16",
744
  "quantize_fp4_sfa_bf16",
745
  "sfa_size_bytes",
746
  ]
build/torch211-cxx11-cu128-x86_64-linux/{_fp4_gemm_cuda_83a7cd0.abi3.so → _fp4_gemm_cuda_92c4b99.abi3.so} RENAMED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:ac9c00df31abab395c44ea7f019effc0a40fe5e71a6461ca9004540a24f440c2
3
- size 1825840
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:63a46e5ec6142207d1f9f881d2d01238e219823e3e514b75ac232cab42d6c0be
3
+ size 2207552
build/torch211-cxx11-cu128-x86_64-linux/_ops.py CHANGED
@@ -1,9 +1,6 @@
1
  import torch
2
- from . import _fp4_gemm_cuda_83a7cd0
3
- ops = torch.ops._fp4_gemm_cuda_83a7cd0
4
 
5
  def add_op_namespace_prefix(op_name: str):
6
- """
7
- Prefix op by namespace.
8
- """
9
- return f"_fp4_gemm_cuda_83a7cd0::{op_name}"
 
1
  import torch
2
+ from . import _fp4_gemm_cuda_92c4b99
3
+ ops = torch.ops._fp4_gemm_cuda_92c4b99
4
 
5
  def add_op_namespace_prefix(op_name: str):
6
+ return f"_fp4_gemm_cuda_92c4b99::{op_name}"
 
 
 
build/torch211-cxx11-cu128-x86_64-linux/fp4_gemm/__init__.py CHANGED
@@ -2,25 +2,13 @@ import ctypes
2
  import importlib.util
3
  import sys
4
  from pathlib import Path
5
- from types import ModuleType
6
 
7
-
8
- def _import_from_path(file_path: Path) -> ModuleType:
9
- # We cannot use the module name as-is, after adding it to `sys.modules`,
10
- # it would also be used for other imports. So, we make a module name that
11
- # depends on the path for it to be unique using the hex-encoded hash of
12
- # the path.
13
- path_hash = "{:x}".format(ctypes.c_size_t(hash(file_path.absolute())).value)
14
- module_name = path_hash
15
- spec = importlib.util.spec_from_file_location(module_name, file_path)
16
- if spec is None:
17
- raise ImportError(f"Cannot load spec for {module_name} from {file_path}")
18
  module = importlib.util.module_from_spec(spec)
19
- if module is None:
20
- raise ImportError(f"Cannot load module {module_name} from spec")
21
- sys.modules[module_name] = module
22
- spec.loader.exec_module(module) # type: ignore
23
  return module
24
 
25
-
26
- globals().update(vars(_import_from_path(Path(__file__).parent.parent / "__init__.py")))
 
2
  import importlib.util
3
  import sys
4
  from pathlib import Path
 
5
 
6
+ def _import_from_path(file_path: Path):
7
+ path_hash = '{:x}'.format(ctypes.c_size_t(hash(file_path.absolute())).value)
8
+ spec = importlib.util.spec_from_file_location(path_hash, file_path)
 
 
 
 
 
 
 
 
9
  module = importlib.util.module_from_spec(spec)
10
+ sys.modules[path_hash] = module
11
+ spec.loader.exec_module(module)
 
 
12
  return module
13
 
14
+ globals().update(vars(_import_from_path(Path(__file__).parent.parent / '__init__.py')))
 
build/torch211-cxx11-cu128-x86_64-linux/metadata.json CHANGED
@@ -1,6 +1,6 @@
1
  {
2
  "name": "fp4-gemm",
3
- "id": "_fp4_gemm_cuda_83a7cd0",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "python-depends": [],
@@ -9,5 +9,24 @@
9
  "archs": [
10
  "12.0a"
11
  ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  }
13
  }
 
1
  {
2
  "name": "fp4-gemm",
3
+ "id": "_fp4_gemm_cuda_92c4b99",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "python-depends": [],
 
9
  "archs": [
10
  "12.0a"
11
  ]
12
+ },
13
+ "digest": {
14
+ "algorithm": "sha256",
15
+ "files": {
16
+ "__init__.py": "Mr1/bAUzs+af68D+bRXX6iONDof//dzKgn6QU5pKdu8=",
17
+ "_fp4_gemm_cuda_92c4b99.abi3.so": "Y6RuXsYUIgfR+fiB0tASOOIZgj4+UUt1rCMsq0LWwL4=",
18
+ "_ops.py": "twu0zeqTyaITBKAdr9FudRbkbXpvBQdmAHcVf/OXw2I=",
19
+ "fp4_gemm/__init__.py": "v6p5XMfQzddhi1fLSAw4HX9CyS0rQsidvu9VsT01xi4="
20
+ }
21
+ },
22
+ "provenance": {
23
+ "kernel": {
24
+ "sha": "92c4b991b6f1fbd89744aa6f46f62b1bfac7ca7e",
25
+ "dirty": false
26
+ },
27
+ "validation": {
28
+ "torch": "2.11.0+cu128",
29
+ "cuda": "12.8"
30
+ }
31
  }
32
  }
build/torch211-cxx11-cu130-aarch64-linux/__init__.py CHANGED
@@ -42,6 +42,16 @@ def _linear_fp16_fake(a_packed, b_packed, sfa, sfb, out, alpha: float = 1.0, var
42
  return None
43
 
44
 
 
 
 
 
 
 
 
 
 
 
45
  @torch.library.register_fake(add_op_namespace_prefix("nvfp4_gemm_geglu_nvfp4_fp16"))
46
  def _geglu_fp4_fake(a, b, sfa, sfb, scratch, out_packed, out_sfa, skinny: bool = False) -> None:
47
  return None
@@ -94,6 +104,16 @@ def _quant_fake(x: torch.Tensor, packed: torch.Tensor, sfa: torch.Tensor, is_sfb
94
  return None
95
 
96
 
 
 
 
 
 
 
 
 
 
 
97
  @torch.library.register_fake(add_op_namespace_prefix("quantize_e0m3_sfa_fp16"))
98
  def _quant_e0m3_fake(x, packed, sfa, is_sfb: bool = False) -> None:
99
  return None
@@ -158,6 +178,104 @@ def quantize_fp4_sfa_fp16(
158
  return packed, sfa
159
 
160
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
  def quantize_e0m3_sfa_fp16(
162
  x: torch.Tensor,
163
  packed: torch.Tensor | None = None,
@@ -276,6 +394,52 @@ def nvfp4_gemm_fp16(
276
  return out
277
 
278
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
279
  def nvfp4_gemm_geglu_nvfp4_fp16(
280
  a_packed: torch.Tensor,
281
  b_interleaved_packed: torch.Tensor,
@@ -307,6 +471,29 @@ def nvfp4_gemm_geglu_nvfp4_fp16(
307
  return out_packed, out_sfa
308
 
309
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
310
  def nvfp4_gemm_bias_gelu_nvfp4_fp16(
311
  a_packed: torch.Tensor,
312
  b_packed: torch.Tensor,
@@ -527,12 +714,16 @@ def nvfp4_gemm_streamk_bias_bf16(
527
 
528
 
529
  __all__ = [
 
 
530
  "dequantize_fp4_sfa_fp16",
531
  "e0m3_weight_gemm_fp16",
532
  "fp4_w4a16_linear_bf16",
533
  "fp4_w4a4_gemv_warpsplit_bf16",
534
  "nvfp4_gemm_bf16",
535
  "nvfp4_gemm_fp16",
 
 
536
  "nvfp4_gemm_geglu_nvfp4_fp16",
537
  "nvfp4_gemm_bias_gelu_nvfp4_fp16",
538
  "nvfp4_gemm_bias_residual_fp16",
@@ -545,6 +736,10 @@ __all__ = [
545
  "nvfp4_gemm_streamk_bf16",
546
  "nvfp4_gemm_streamk_bias_bf16",
547
  "quantize_fp4_sfa_fp16",
 
 
 
 
548
  "quantize_e0m3_sfa_fp16",
549
  "quantize_fp4_sfa_bf16",
550
  "sfa_size_bytes",
 
42
  return None
43
 
44
 
45
+ @torch.library.register_fake(add_op_namespace_prefix("nvfp4_gemm_variant_bf16"))
46
+ def _linear_variant_bf16_fake(a, b, sfa, sfb, out, alpha: float = 1.0, variant: int = 7) -> None:
47
+ return None
48
+
49
+
50
+ @torch.library.register_fake(add_op_namespace_prefix("nvfp4_gemm_nvfp4"))
51
+ def _linear_fp4out_fake(a, b, sfa, sfb, out_packed, out_sfa) -> None:
52
+ return None
53
+
54
+
55
  @torch.library.register_fake(add_op_namespace_prefix("nvfp4_gemm_geglu_nvfp4_fp16"))
56
  def _geglu_fp4_fake(a, b, sfa, sfb, scratch, out_packed, out_sfa, skinny: bool = False) -> None:
57
  return None
 
104
  return None
105
 
106
 
107
+ @torch.library.register_fake(add_op_namespace_prefix("quantize_fp4_sfa_mse_fp16"))
108
+ def _quant_mse_fake(x, packed, sfa, is_sfb: bool = False) -> None:
109
+ return None
110
+
111
+
112
+ @torch.library.register_fake(add_op_namespace_prefix("quantize_fp4_sfa_mse_bf16"))
113
+ def _quant_mse_bf16_fake(x, packed, sfa, is_sfb: bool = False) -> None:
114
+ return None
115
+
116
+
117
  @torch.library.register_fake(add_op_namespace_prefix("quantize_e0m3_sfa_fp16"))
118
  def _quant_e0m3_fake(x, packed, sfa, is_sfb: bool = False) -> None:
119
  return None
 
178
  return packed, sfa
179
 
180
 
181
+ def quantize_fp4_sfa_mse_fp16(
182
+ x: torch.Tensor,
183
+ packed: torch.Tensor | None = None,
184
+ sfa: torch.Tensor | None = None,
185
+ is_sfb: bool = False,
186
+ ):
187
+ """Pack FP16 weights using per-block reconstruction-MSE scale search."""
188
+ if packed is None or sfa is None:
189
+ packed, sfa = _alloc_fp4(x.shape[0], x.shape[1], x.device)
190
+ ops.quantize_fp4_sfa_mse_fp16(x, packed, sfa, bool(is_sfb))
191
+ return packed, sfa
192
+
193
+
194
+ def quantize_fp4_sfa_mse_bf16(
195
+ x: torch.Tensor,
196
+ packed: torch.Tensor | None = None,
197
+ sfa: torch.Tensor | None = None,
198
+ is_sfb: bool = False,
199
+ ):
200
+ """Pack BF16 weights using per-block reconstruction-MSE scale search."""
201
+ if packed is None or sfa is None:
202
+ packed, sfa = _alloc_fp4(x.shape[0], x.shape[1], x.device)
203
+ ops.quantize_fp4_sfa_mse_bf16(x, packed, sfa, bool(is_sfb))
204
+ return packed, sfa
205
+
206
+
207
+ def aligned_fp4_dim(dim: int, alignment: int = 32) -> int:
208
+ """Return the physical FP4 GEMM dimension for a logical dimension."""
209
+ if dim <= 0 or alignment <= 0 or alignment % 16 != 0:
210
+ raise ValueError("dim must be positive and alignment divisible by 16")
211
+ return ((dim + alignment - 1) // alignment) * alignment
212
+
213
+
214
+ def quantize_fp4_sfa_padded_bf16(
215
+ x: torch.Tensor,
216
+ *,
217
+ alignment: int = 32,
218
+ is_sfb: bool = False,
219
+ ):
220
+ """Bind-time BF16 pack with zero padding on the final dimension.
221
+
222
+ The returned packed tensor uses the physical aligned width. Keep the
223
+ logical width returned as the third item for slicing final model outputs.
224
+ This helper allocates and is intentionally not a runtime hot-path op.
225
+ """
226
+ if x.ndim != 2 or x.dtype != torch.bfloat16:
227
+ raise ValueError("x must be a 2-D BF16 tensor")
228
+ logical_dim = int(x.shape[1])
229
+ physical_dim = aligned_fp4_dim(logical_dim, alignment)
230
+ if physical_dim == logical_dim:
231
+ padded = x.contiguous()
232
+ else:
233
+ padded = torch.zeros(
234
+ (x.shape[0], physical_dim), device=x.device, dtype=x.dtype
235
+ )
236
+ padded[:, :logical_dim].copy_(x)
237
+ packed, sfa = quantize_fp4_sfa_bf16(padded, is_sfb=is_sfb)
238
+ return packed, sfa, logical_dim
239
+
240
+
241
+ def pack_nvfp4_weight_bf16(
242
+ weight: torch.Tensor,
243
+ bias: torch.Tensor | None = None,
244
+ *,
245
+ alignment: int = 32,
246
+ mse: bool = False,
247
+ ):
248
+ """Zero-pad and pack a logical ``(N, K)`` BF16 weight at bind time.
249
+
250
+ Both physical ``N`` and ``K`` are aligned. This is the model-neutral
251
+ contract used by widths such as SigLIP's logical 4304, which becomes 4320
252
+ for Blackwell NVFP4 TMA. The hot path consumes the returned static tensors
253
+ directly and performs no padding or allocation.
254
+ """
255
+ if weight.ndim != 2 or weight.dtype != torch.bfloat16:
256
+ raise ValueError("weight must be a 2-D BF16 tensor")
257
+ logical_n, logical_k = map(int, weight.shape)
258
+ physical_n = aligned_fp4_dim(logical_n, alignment)
259
+ physical_k = aligned_fp4_dim(logical_k, alignment)
260
+ padded_weight = torch.zeros(
261
+ (physical_n, physical_k), device=weight.device, dtype=weight.dtype
262
+ )
263
+ padded_weight[:logical_n, :logical_k].copy_(weight)
264
+ quantize = quantize_fp4_sfa_mse_bf16 if mse else quantize_fp4_sfa_bf16
265
+ packed, sfb = quantize(padded_weight, is_sfb=True)
266
+ padded_bias = None
267
+ if bias is not None:
268
+ if bias.ndim != 1 or bias.numel() != logical_n:
269
+ raise ValueError("bias must have shape (N,)")
270
+ if bias.dtype != torch.bfloat16 or bias.device != weight.device:
271
+ raise ValueError("bias must match the weight dtype and device")
272
+ padded_bias = torch.zeros(
273
+ (physical_n,), device=bias.device, dtype=bias.dtype
274
+ )
275
+ padded_bias[:logical_n].copy_(bias)
276
+ return packed, sfb, padded_bias, (logical_n, logical_k)
277
+
278
+
279
  def quantize_e0m3_sfa_fp16(
280
  x: torch.Tensor,
281
  packed: torch.Tensor | None = None,
 
394
  return out
395
 
396
 
397
+ def nvfp4_gemm_variant_bf16(
398
+ a_packed: torch.Tensor,
399
+ b_packed: torch.Tensor,
400
+ sfa: torch.Tensor,
401
+ sfb: torch.Tensor,
402
+ alpha: float = 1.0,
403
+ out: torch.Tensor | None = None,
404
+ variant: int = 7,
405
+ ) -> torch.Tensor:
406
+ """SM110 native NVFP4 GEMM with BF16 output.
407
+
408
+ ``variant=7`` serves large-M encoder/SigLIP shapes; ``variant=10`` is
409
+ the narrow-N PI0.5 decoder tile. The indices match FlashRT native.
410
+ """
411
+ if out is None:
412
+ out = torch.empty(
413
+ (a_packed.shape[0], b_packed.shape[0]),
414
+ device=a_packed.device,
415
+ dtype=torch.bfloat16,
416
+ )
417
+ ops.nvfp4_gemm_variant_bf16(
418
+ a_packed, b_packed, sfa, sfb, out, float(alpha), int(variant)
419
+ )
420
+ return out
421
+
422
+
423
+ def nvfp4_gemm_nvfp4(
424
+ a_packed: torch.Tensor,
425
+ b_packed: torch.Tensor,
426
+ sfa: torch.Tensor,
427
+ sfb: torch.Tensor,
428
+ *,
429
+ out_packed: torch.Tensor | None = None,
430
+ out_sfa: torch.Tensor | None = None,
431
+ ) -> tuple[torch.Tensor, torch.Tensor]:
432
+ """SM110 NVFP4 GEMM with an NVFP4 output and CUTLASS SFA layout."""
433
+ if out_packed is None or out_sfa is None:
434
+ out_packed, out_sfa = _alloc_fp4(
435
+ a_packed.shape[0], b_packed.shape[0], a_packed.device
436
+ )
437
+ ops.nvfp4_gemm_nvfp4(
438
+ a_packed, b_packed, sfa, sfb, out_packed, out_sfa
439
+ )
440
+ return out_packed, out_sfa
441
+
442
+
443
  def nvfp4_gemm_geglu_nvfp4_fp16(
444
  a_packed: torch.Tensor,
445
  b_interleaved_packed: torch.Tensor,
 
471
  return out_packed, out_sfa
472
 
473
 
474
+ def cutlass_fp4_gemm_geglu_il_hw_v10(
475
+ a_packed: torch.Tensor,
476
+ b_interleaved_packed: torch.Tensor,
477
+ sfa: torch.Tensor,
478
+ sfb: torch.Tensor,
479
+ *,
480
+ scratch: torch.Tensor | None = None,
481
+ out_packed: torch.Tensor | None = None,
482
+ out_sfa: torch.Tensor | None = None,
483
+ ) -> tuple[torch.Tensor, torch.Tensor]:
484
+ """Explicit public alias for the native SM110 v10 GeGLU schedule."""
485
+ return nvfp4_gemm_geglu_nvfp4_fp16(
486
+ a_packed,
487
+ b_interleaved_packed,
488
+ sfa,
489
+ sfb,
490
+ skinny=True,
491
+ scratch=scratch,
492
+ out_packed=out_packed,
493
+ out_sfa=out_sfa,
494
+ )
495
+
496
+
497
  def nvfp4_gemm_bias_gelu_nvfp4_fp16(
498
  a_packed: torch.Tensor,
499
  b_packed: torch.Tensor,
 
714
 
715
 
716
  __all__ = [
717
+ "aligned_fp4_dim",
718
+ "cutlass_fp4_gemm_geglu_il_hw_v10",
719
  "dequantize_fp4_sfa_fp16",
720
  "e0m3_weight_gemm_fp16",
721
  "fp4_w4a16_linear_bf16",
722
  "fp4_w4a4_gemv_warpsplit_bf16",
723
  "nvfp4_gemm_bf16",
724
  "nvfp4_gemm_fp16",
725
+ "nvfp4_gemm_variant_bf16",
726
+ "nvfp4_gemm_nvfp4",
727
  "nvfp4_gemm_geglu_nvfp4_fp16",
728
  "nvfp4_gemm_bias_gelu_nvfp4_fp16",
729
  "nvfp4_gemm_bias_residual_fp16",
 
736
  "nvfp4_gemm_streamk_bf16",
737
  "nvfp4_gemm_streamk_bias_bf16",
738
  "quantize_fp4_sfa_fp16",
739
+ "quantize_fp4_sfa_mse_fp16",
740
+ "quantize_fp4_sfa_mse_bf16",
741
+ "quantize_fp4_sfa_padded_bf16",
742
+ "pack_nvfp4_weight_bf16",
743
  "quantize_e0m3_sfa_fp16",
744
  "quantize_fp4_sfa_bf16",
745
  "sfa_size_bytes",
build/torch211-cxx11-cu130-aarch64-linux/{_fp4_gemm_cuda_230e467.abi3.so → _fp4_gemm_cuda_92c4b99.abi3.so} RENAMED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:f92ea3a945832ebd737cf074eac4983674fd00b160afa1481cd24148e8e32297
3
- size 5528088
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:fa36d417fac09d8ed49fbb867c35562ed06ca80b67747c09624406e21afcd38e
3
+ size 6002944
build/torch211-cxx11-cu130-aarch64-linux/_ops.py CHANGED
@@ -1,6 +1,6 @@
1
  import torch
2
- from . import _fp4_gemm_cuda_230e467
3
- ops = torch.ops._fp4_gemm_cuda_230e467
4
 
5
  def add_op_namespace_prefix(op_name: str):
6
- return f"_fp4_gemm_cuda_230e467::{op_name}"
 
1
  import torch
2
+ from . import _fp4_gemm_cuda_92c4b99
3
+ ops = torch.ops._fp4_gemm_cuda_92c4b99
4
 
5
  def add_op_namespace_prefix(op_name: str):
6
+ return f"_fp4_gemm_cuda_92c4b99::{op_name}"
build/torch211-cxx11-cu130-aarch64-linux/metadata.json CHANGED
@@ -1,6 +1,6 @@
1
  {
2
  "name": "fp4-gemm",
3
- "id": "_fp4_gemm_cuda_230e467",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "python-depends": [],
@@ -13,16 +13,20 @@
13
  "digest": {
14
  "algorithm": "sha256",
15
  "files": {
16
- "__init__.py": "Qfdvg/9EedWLGh34xAalDfC0IsAHCOL44L4ShaP/w5Y=",
17
- "_fp4_gemm_cuda_230e467.abi3.so": "+S6jqUWDLr1zfPB06sSYNnT9ALFgr6FIHNJBSOjjIpc=",
18
- "_ops.py": "IfU2iMVrWYRnoe6q8HI3hU8TPnAlLrXDerlv117PcXI=",
19
  "fp4_gemm/__init__.py": "v6p5XMfQzddhi1fLSAw4HX9CyS0rQsidvu9VsT01xi4="
20
  }
21
  },
22
  "provenance": {
23
  "kernel": {
24
- "sha": "230e467",
25
  "dirty": false
 
 
 
 
26
  }
27
  }
28
  }
 
1
  {
2
  "name": "fp4-gemm",
3
+ "id": "_fp4_gemm_cuda_92c4b99",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "python-depends": [],
 
13
  "digest": {
14
  "algorithm": "sha256",
15
  "files": {
16
+ "__init__.py": "Mr1/bAUzs+af68D+bRXX6iONDof//dzKgn6QU5pKdu8=",
17
+ "_fp4_gemm_cuda_92c4b99.abi3.so": "+jbUF/rAnY7Un7uGfDVWLtBsqAtndHwJYkQG4hr8044=",
18
+ "_ops.py": "twu0zeqTyaITBKAdr9FudRbkbXpvBQdmAHcVf/OXw2I=",
19
  "fp4_gemm/__init__.py": "v6p5XMfQzddhi1fLSAw4HX9CyS0rQsidvu9VsT01xi4="
20
  }
21
  },
22
  "provenance": {
23
  "kernel": {
24
+ "sha": "92c4b991b6f1fbd89744aa6f46f62b1bfac7ca7e",
25
  "dirty": false
26
+ },
27
+ "validation": {
28
+ "torch": "2.11.0+cu130",
29
+ "cuda": "13.0"
30
  }
31
  }
32
  }
build/torch211-cxx11-cu130-x86_64-linux/__init__.py CHANGED
@@ -19,7 +19,8 @@ def sfa_size_bytes(rows: int, dim: int) -> int:
19
  def _alloc_fp4(rows: int, dim: int, device: torch.device | str):
20
  return (
21
  torch.empty((rows, dim // 2), device=device, dtype=torch.uint8),
22
- torch.empty((sfa_size_bytes(rows, dim),), device=device, dtype=torch.uint8),
 
23
  )
24
 
25
 
@@ -36,6 +37,36 @@ def _linear_fake(
36
  return None
37
 
38
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  @torch.library.register_fake(add_op_namespace_prefix("fp4_w4a4_gemv_warpsplit_bf16"))
40
  def _gemv_warpsplit_fake(a_packed, b_packed, sfa, sfb, out, alpha: float = 1.0, warps: int = 4, stages: int = 4) -> None:
41
  if a_packed.shape[0] != 1:
@@ -58,11 +89,46 @@ def _legacy_linear_fake(
58
  return None
59
 
60
 
 
 
 
 
 
 
 
 
 
 
61
  @torch.library.register_fake(add_op_namespace_prefix("quantize_fp4_sfa_fp16"))
62
  def _quant_fake(x: torch.Tensor, packed: torch.Tensor, sfa: torch.Tensor, is_sfb: bool = False) -> None:
63
  return None
64
 
65
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  @torch.library.register_fake(add_op_namespace_prefix("quantize_fp4_sfa_bf16"))
67
  def _quant_bf16_fake(x: torch.Tensor, packed: torch.Tensor, sfa: torch.Tensor, is_sfb: bool = False) -> None:
68
  return None
@@ -112,6 +178,160 @@ def quantize_fp4_sfa_fp16(
112
  return packed, sfa
113
 
114
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
  def quantize_fp4_sfa_bf16(
116
  x: torch.Tensor,
117
  packed: torch.Tensor | None = None,
@@ -152,6 +372,169 @@ def nvfp4_gemm_bf16(
152
  return out
153
 
154
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
  def fp4_w4a4_gemv_warpsplit_bf16(
156
  a_packed: torch.Tensor,
157
  b_packed: torch.Tensor,
@@ -191,6 +574,45 @@ def fp4_w4a16_linear_bf16(
191
  )
192
 
193
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
194
  def nvfp4_gemm_residual_bf16(
195
  a_packed: torch.Tensor,
196
  b_packed: torch.Tensor,
@@ -243,7 +665,7 @@ def nvfp4_gemm_bias_gelu_nvfp4(
243
  if out_packed is None:
244
  out_packed = torch.empty((m, n // 2), device=a_packed.device, dtype=torch.uint8)
245
  if out_sfa is None:
246
- out_sfa = torch.empty((sfa_size_bytes(m, n),), device=a_packed.device, dtype=torch.uint8)
247
  ops.nvfp4_gemm_bias_gelu_nvfp4(
248
  a_packed, b_packed, sfa, sfb, bias, out_packed, out_sfa, float(alpha)
249
  )
@@ -292,16 +714,33 @@ def nvfp4_gemm_streamk_bias_bf16(
292
 
293
 
294
  __all__ = [
 
 
295
  "dequantize_fp4_sfa_fp16",
 
296
  "fp4_w4a16_linear_bf16",
297
  "fp4_w4a4_gemv_warpsplit_bf16",
298
  "nvfp4_gemm_bf16",
 
 
 
 
 
 
 
299
  "nvfp4_gemm_bias_gelu_bf16",
300
  "nvfp4_gemm_bias_gelu_nvfp4",
 
301
  "nvfp4_gemm_residual_bf16",
 
302
  "nvfp4_gemm_streamk_bf16",
303
  "nvfp4_gemm_streamk_bias_bf16",
304
  "quantize_fp4_sfa_fp16",
 
 
 
 
 
305
  "quantize_fp4_sfa_bf16",
306
  "sfa_size_bytes",
307
  ]
 
19
  def _alloc_fp4(rows: int, dim: int, device: torch.device | str):
20
  return (
21
  torch.empty((rows, dim // 2), device=device, dtype=torch.uint8),
22
+ # Tile-layout padding entries are not written by every quantizer.
23
+ torch.zeros((sfa_size_bytes(rows, dim),), device=device, dtype=torch.uint8),
24
  )
25
 
26
 
 
37
  return None
38
 
39
 
40
+ @torch.library.register_fake(add_op_namespace_prefix("nvfp4_gemm_fp16"))
41
+ def _linear_fp16_fake(a_packed, b_packed, sfa, sfb, out, alpha: float = 1.0, variant: int = -1) -> None:
42
+ return None
43
+
44
+
45
+ @torch.library.register_fake(add_op_namespace_prefix("nvfp4_gemm_variant_bf16"))
46
+ def _linear_variant_bf16_fake(a, b, sfa, sfb, out, alpha: float = 1.0, variant: int = 7) -> None:
47
+ return None
48
+
49
+
50
+ @torch.library.register_fake(add_op_namespace_prefix("nvfp4_gemm_nvfp4"))
51
+ def _linear_fp4out_fake(a, b, sfa, sfb, out_packed, out_sfa) -> None:
52
+ return None
53
+
54
+
55
+ @torch.library.register_fake(add_op_namespace_prefix("nvfp4_gemm_geglu_nvfp4_fp16"))
56
+ def _geglu_fp4_fake(a, b, sfa, sfb, scratch, out_packed, out_sfa, skinny: bool = False) -> None:
57
+ return None
58
+
59
+
60
+ @torch.library.register_fake(add_op_namespace_prefix("nvfp4_gemm_bias_gelu_nvfp4_fp16"))
61
+ def _bias_gelu_fp4_fp16_fake(a, b, sfa, sfb, bias, out_packed, out_sfa) -> None:
62
+ return None
63
+
64
+
65
+ @torch.library.register_fake(add_op_namespace_prefix("nvfp4_gemm_bias_residual_fp16"))
66
+ def _bias_residual_fp16_fake(a, b, sfa, sfb, bias, residual, out) -> None:
67
+ return None
68
+
69
+
70
  @torch.library.register_fake(add_op_namespace_prefix("fp4_w4a4_gemv_warpsplit_bf16"))
71
  def _gemv_warpsplit_fake(a_packed, b_packed, sfa, sfb, out, alpha: float = 1.0, warps: int = 4, stages: int = 4) -> None:
72
  if a_packed.shape[0] != 1:
 
89
  return None
90
 
91
 
92
+ @torch.library.register_fake(add_op_namespace_prefix("nvfp4_gemm_bias_bf16"))
93
+ def _bias_fake(a, b, sfa, sfb, bias, out) -> None:
94
+ return None
95
+
96
+
97
+ @torch.library.register_fake(add_op_namespace_prefix("nvfp4_gemm_bias_residual_bf16"))
98
+ def _bias_residual_fake(a, b, sfa, sfb, bias, residual, out) -> None:
99
+ return None
100
+
101
+
102
  @torch.library.register_fake(add_op_namespace_prefix("quantize_fp4_sfa_fp16"))
103
  def _quant_fake(x: torch.Tensor, packed: torch.Tensor, sfa: torch.Tensor, is_sfb: bool = False) -> None:
104
  return None
105
 
106
 
107
+ @torch.library.register_fake(add_op_namespace_prefix("quantize_fp4_sfa_mse_fp16"))
108
+ def _quant_mse_fake(x, packed, sfa, is_sfb: bool = False) -> None:
109
+ return None
110
+
111
+
112
+ @torch.library.register_fake(add_op_namespace_prefix("quantize_fp4_sfa_mse_bf16"))
113
+ def _quant_mse_bf16_fake(x, packed, sfa, is_sfb: bool = False) -> None:
114
+ return None
115
+
116
+
117
+ @torch.library.register_fake(add_op_namespace_prefix("quantize_e0m3_sfa_fp16"))
118
+ def _quant_e0m3_fake(x, packed, sfa, is_sfb: bool = False) -> None:
119
+ return None
120
+
121
+
122
+ @torch.library.register_fake(add_op_namespace_prefix("e0m3_weight_gemm_fp16"))
123
+ def _e0m3_gemm_fake(a, b, sfa, sfb, out, alpha: float = 1.0, a_format: int = 1) -> None:
124
+ return None
125
+
126
+
127
+ @torch.library.register_fake(add_op_namespace_prefix("nvfp4_gemm_relu2_nvfp4"))
128
+ def _relu2_gemm_fake(a, b, sfa, sfb, out_packed, out_sfa) -> None:
129
+ return None
130
+
131
+
132
  @torch.library.register_fake(add_op_namespace_prefix("quantize_fp4_sfa_bf16"))
133
  def _quant_bf16_fake(x: torch.Tensor, packed: torch.Tensor, sfa: torch.Tensor, is_sfb: bool = False) -> None:
134
  return None
 
178
  return packed, sfa
179
 
180
 
181
+ def quantize_fp4_sfa_mse_fp16(
182
+ x: torch.Tensor,
183
+ packed: torch.Tensor | None = None,
184
+ sfa: torch.Tensor | None = None,
185
+ is_sfb: bool = False,
186
+ ):
187
+ """Pack FP16 weights using per-block reconstruction-MSE scale search."""
188
+ if packed is None or sfa is None:
189
+ packed, sfa = _alloc_fp4(x.shape[0], x.shape[1], x.device)
190
+ ops.quantize_fp4_sfa_mse_fp16(x, packed, sfa, bool(is_sfb))
191
+ return packed, sfa
192
+
193
+
194
+ def quantize_fp4_sfa_mse_bf16(
195
+ x: torch.Tensor,
196
+ packed: torch.Tensor | None = None,
197
+ sfa: torch.Tensor | None = None,
198
+ is_sfb: bool = False,
199
+ ):
200
+ """Pack BF16 weights using per-block reconstruction-MSE scale search."""
201
+ if packed is None or sfa is None:
202
+ packed, sfa = _alloc_fp4(x.shape[0], x.shape[1], x.device)
203
+ ops.quantize_fp4_sfa_mse_bf16(x, packed, sfa, bool(is_sfb))
204
+ return packed, sfa
205
+
206
+
207
+ def aligned_fp4_dim(dim: int, alignment: int = 32) -> int:
208
+ """Return the physical FP4 GEMM dimension for a logical dimension."""
209
+ if dim <= 0 or alignment <= 0 or alignment % 16 != 0:
210
+ raise ValueError("dim must be positive and alignment divisible by 16")
211
+ return ((dim + alignment - 1) // alignment) * alignment
212
+
213
+
214
+ def quantize_fp4_sfa_padded_bf16(
215
+ x: torch.Tensor,
216
+ *,
217
+ alignment: int = 32,
218
+ is_sfb: bool = False,
219
+ ):
220
+ """Bind-time BF16 pack with zero padding on the final dimension.
221
+
222
+ The returned packed tensor uses the physical aligned width. Keep the
223
+ logical width returned as the third item for slicing final model outputs.
224
+ This helper allocates and is intentionally not a runtime hot-path op.
225
+ """
226
+ if x.ndim != 2 or x.dtype != torch.bfloat16:
227
+ raise ValueError("x must be a 2-D BF16 tensor")
228
+ logical_dim = int(x.shape[1])
229
+ physical_dim = aligned_fp4_dim(logical_dim, alignment)
230
+ if physical_dim == logical_dim:
231
+ padded = x.contiguous()
232
+ else:
233
+ padded = torch.zeros(
234
+ (x.shape[0], physical_dim), device=x.device, dtype=x.dtype
235
+ )
236
+ padded[:, :logical_dim].copy_(x)
237
+ packed, sfa = quantize_fp4_sfa_bf16(padded, is_sfb=is_sfb)
238
+ return packed, sfa, logical_dim
239
+
240
+
241
+ def pack_nvfp4_weight_bf16(
242
+ weight: torch.Tensor,
243
+ bias: torch.Tensor | None = None,
244
+ *,
245
+ alignment: int = 32,
246
+ mse: bool = False,
247
+ ):
248
+ """Zero-pad and pack a logical ``(N, K)`` BF16 weight at bind time.
249
+
250
+ Both physical ``N`` and ``K`` are aligned. This is the model-neutral
251
+ contract used by widths such as SigLIP's logical 4304, which becomes 4320
252
+ for Blackwell NVFP4 TMA. The hot path consumes the returned static tensors
253
+ directly and performs no padding or allocation.
254
+ """
255
+ if weight.ndim != 2 or weight.dtype != torch.bfloat16:
256
+ raise ValueError("weight must be a 2-D BF16 tensor")
257
+ logical_n, logical_k = map(int, weight.shape)
258
+ physical_n = aligned_fp4_dim(logical_n, alignment)
259
+ physical_k = aligned_fp4_dim(logical_k, alignment)
260
+ padded_weight = torch.zeros(
261
+ (physical_n, physical_k), device=weight.device, dtype=weight.dtype
262
+ )
263
+ padded_weight[:logical_n, :logical_k].copy_(weight)
264
+ quantize = quantize_fp4_sfa_mse_bf16 if mse else quantize_fp4_sfa_bf16
265
+ packed, sfb = quantize(padded_weight, is_sfb=True)
266
+ padded_bias = None
267
+ if bias is not None:
268
+ if bias.ndim != 1 or bias.numel() != logical_n:
269
+ raise ValueError("bias must have shape (N,)")
270
+ if bias.dtype != torch.bfloat16 or bias.device != weight.device:
271
+ raise ValueError("bias must match the weight dtype and device")
272
+ padded_bias = torch.zeros(
273
+ (physical_n,), device=bias.device, dtype=bias.dtype
274
+ )
275
+ padded_bias[:logical_n].copy_(bias)
276
+ return packed, sfb, padded_bias, (logical_n, logical_k)
277
+
278
+
279
+ def quantize_e0m3_sfa_fp16(
280
+ x: torch.Tensor,
281
+ packed: torch.Tensor | None = None,
282
+ sfa: torch.Tensor | None = None,
283
+ is_sfb: bool = False,
284
+ ):
285
+ """Quantize FP16 to packed E0M3 with CUTLASS SFA/SFB layout."""
286
+ if packed is None or sfa is None:
287
+ packed, sfa = _alloc_fp4(x.shape[0], x.shape[1], x.device)
288
+ ops.quantize_e0m3_sfa_fp16(x, packed, sfa, bool(is_sfb))
289
+ return packed, sfa
290
+
291
+
292
+ def e0m3_weight_gemm_fp16(
293
+ a_packed: torch.Tensor,
294
+ b_packed: torch.Tensor,
295
+ sfa: torch.Tensor,
296
+ sfb: torch.Tensor,
297
+ *,
298
+ alpha: float = 1.0,
299
+ a_format: int = 1,
300
+ out: torch.Tensor | None = None,
301
+ ):
302
+ """SM110 GEMM with E0M3 weights and E2M1 or E0M3 activations."""
303
+ if out is None:
304
+ out = torch.empty(
305
+ (a_packed.shape[0], b_packed.shape[0]),
306
+ device=a_packed.device,
307
+ dtype=torch.float16,
308
+ )
309
+ ops.e0m3_weight_gemm_fp16(
310
+ a_packed, b_packed, sfa, sfb, out, float(alpha), int(a_format)
311
+ )
312
+ return out
313
+
314
+
315
+ def nvfp4_gemm_relu2_nvfp4(
316
+ a_packed: torch.Tensor,
317
+ b_packed: torch.Tensor,
318
+ sfa: torch.Tensor,
319
+ sfb: torch.Tensor,
320
+ *,
321
+ out_packed: torch.Tensor | None = None,
322
+ out_sfa: torch.Tensor | None = None,
323
+ ):
324
+ """SM110 NVFP4 GEMM with fused ReLU-squared and NVFP4 output."""
325
+ if out_packed is None or out_sfa is None:
326
+ out_packed, out_sfa = _alloc_fp4(
327
+ a_packed.shape[0], b_packed.shape[0], a_packed.device
328
+ )
329
+ ops.nvfp4_gemm_relu2_nvfp4(
330
+ a_packed, b_packed, sfa, sfb, out_packed, out_sfa
331
+ )
332
+ return out_packed, out_sfa
333
+
334
+
335
  def quantize_fp4_sfa_bf16(
336
  x: torch.Tensor,
337
  packed: torch.Tensor | None = None,
 
372
  return out
373
 
374
 
375
+ def nvfp4_gemm_fp16(
376
+ a_packed: torch.Tensor,
377
+ b_packed: torch.Tensor,
378
+ sfa: torch.Tensor,
379
+ sfb: torch.Tensor,
380
+ alpha: float = 1.0,
381
+ out: torch.Tensor | None = None,
382
+ variant: int = -1,
383
+ ) -> torch.Tensor:
384
+ """SM110 native NVFP4 GEMM with FP16 output."""
385
+ if out is None:
386
+ out = torch.empty(
387
+ (a_packed.shape[0], b_packed.shape[0]),
388
+ device=a_packed.device,
389
+ dtype=torch.float16,
390
+ )
391
+ ops.nvfp4_gemm_fp16(
392
+ a_packed, b_packed, sfa, sfb, out, float(alpha), int(variant)
393
+ )
394
+ return out
395
+
396
+
397
+ def nvfp4_gemm_variant_bf16(
398
+ a_packed: torch.Tensor,
399
+ b_packed: torch.Tensor,
400
+ sfa: torch.Tensor,
401
+ sfb: torch.Tensor,
402
+ alpha: float = 1.0,
403
+ out: torch.Tensor | None = None,
404
+ variant: int = 7,
405
+ ) -> torch.Tensor:
406
+ """SM110 native NVFP4 GEMM with BF16 output.
407
+
408
+ ``variant=7`` serves large-M encoder/SigLIP shapes; ``variant=10`` is
409
+ the narrow-N PI0.5 decoder tile. The indices match FlashRT native.
410
+ """
411
+ if out is None:
412
+ out = torch.empty(
413
+ (a_packed.shape[0], b_packed.shape[0]),
414
+ device=a_packed.device,
415
+ dtype=torch.bfloat16,
416
+ )
417
+ ops.nvfp4_gemm_variant_bf16(
418
+ a_packed, b_packed, sfa, sfb, out, float(alpha), int(variant)
419
+ )
420
+ return out
421
+
422
+
423
+ def nvfp4_gemm_nvfp4(
424
+ a_packed: torch.Tensor,
425
+ b_packed: torch.Tensor,
426
+ sfa: torch.Tensor,
427
+ sfb: torch.Tensor,
428
+ *,
429
+ out_packed: torch.Tensor | None = None,
430
+ out_sfa: torch.Tensor | None = None,
431
+ ) -> tuple[torch.Tensor, torch.Tensor]:
432
+ """SM110 NVFP4 GEMM with an NVFP4 output and CUTLASS SFA layout."""
433
+ if out_packed is None or out_sfa is None:
434
+ out_packed, out_sfa = _alloc_fp4(
435
+ a_packed.shape[0], b_packed.shape[0], a_packed.device
436
+ )
437
+ ops.nvfp4_gemm_nvfp4(
438
+ a_packed, b_packed, sfa, sfb, out_packed, out_sfa
439
+ )
440
+ return out_packed, out_sfa
441
+
442
+
443
+ def nvfp4_gemm_geglu_nvfp4_fp16(
444
+ a_packed: torch.Tensor,
445
+ b_interleaved_packed: torch.Tensor,
446
+ sfa: torch.Tensor,
447
+ sfb: torch.Tensor,
448
+ *,
449
+ skinny: bool = False,
450
+ scratch: torch.Tensor | None = None,
451
+ out_packed: torch.Tensor | None = None,
452
+ out_sfa: torch.Tensor | None = None,
453
+ ) -> tuple[torch.Tensor, torch.Tensor]:
454
+ """GEMM with fused GeGLU and compact NVFP4 output on SM110.
455
+
456
+ ``b_interleaved_packed`` stores gate/up rows pairwise, so its first
457
+ dimension is twice the logical hidden width.
458
+ """
459
+ m, n_twice = a_packed.shape[0], b_interleaved_packed.shape[0]
460
+ hidden = n_twice // 2
461
+ if scratch is None:
462
+ scratch = torch.empty((m, hidden), device=a_packed.device, dtype=torch.uint8)
463
+ if out_packed is None:
464
+ out_packed = torch.empty((m, hidden // 2), device=a_packed.device, dtype=torch.uint8)
465
+ if out_sfa is None:
466
+ out_sfa = torch.zeros((sfa_size_bytes(m, hidden),), device=a_packed.device, dtype=torch.uint8)
467
+ ops.nvfp4_gemm_geglu_nvfp4_fp16(
468
+ a_packed, b_interleaved_packed, sfa, sfb, scratch,
469
+ out_packed, out_sfa, bool(skinny)
470
+ )
471
+ return out_packed, out_sfa
472
+
473
+
474
+ def cutlass_fp4_gemm_geglu_il_hw_v10(
475
+ a_packed: torch.Tensor,
476
+ b_interleaved_packed: torch.Tensor,
477
+ sfa: torch.Tensor,
478
+ sfb: torch.Tensor,
479
+ *,
480
+ scratch: torch.Tensor | None = None,
481
+ out_packed: torch.Tensor | None = None,
482
+ out_sfa: torch.Tensor | None = None,
483
+ ) -> tuple[torch.Tensor, torch.Tensor]:
484
+ """Explicit public alias for the native SM110 v10 GeGLU schedule."""
485
+ return nvfp4_gemm_geglu_nvfp4_fp16(
486
+ a_packed,
487
+ b_interleaved_packed,
488
+ sfa,
489
+ sfb,
490
+ skinny=True,
491
+ scratch=scratch,
492
+ out_packed=out_packed,
493
+ out_sfa=out_sfa,
494
+ )
495
+
496
+
497
+ def nvfp4_gemm_bias_gelu_nvfp4_fp16(
498
+ a_packed: torch.Tensor,
499
+ b_packed: torch.Tensor,
500
+ sfa: torch.Tensor,
501
+ sfb: torch.Tensor,
502
+ bias: torch.Tensor,
503
+ *,
504
+ out_packed: torch.Tensor | None = None,
505
+ out_sfa: torch.Tensor | None = None,
506
+ ) -> tuple[torch.Tensor, torch.Tensor]:
507
+ """FP16-bias GEMM with fused GELU and NVFP4 output on SM110."""
508
+ m, n = a_packed.shape[0], b_packed.shape[0]
509
+ if out_packed is None:
510
+ out_packed = torch.empty((m, n // 2), device=a_packed.device, dtype=torch.uint8)
511
+ if out_sfa is None:
512
+ out_sfa = torch.zeros((sfa_size_bytes(m, n),), device=a_packed.device, dtype=torch.uint8)
513
+ ops.nvfp4_gemm_bias_gelu_nvfp4_fp16(
514
+ a_packed, b_packed, sfa, sfb, bias, out_packed, out_sfa
515
+ )
516
+ return out_packed, out_sfa
517
+
518
+
519
+ def nvfp4_gemm_bias_residual_fp16(
520
+ a_packed: torch.Tensor,
521
+ b_packed: torch.Tensor,
522
+ sfa: torch.Tensor,
523
+ sfb: torch.Tensor,
524
+ bias: torch.Tensor,
525
+ residual: torch.Tensor,
526
+ *,
527
+ out: torch.Tensor | None = None,
528
+ ) -> torch.Tensor:
529
+ """FP16-output GEMM with fused FP16 bias and residual on SM110."""
530
+ if out is None:
531
+ out = torch.empty_like(residual)
532
+ ops.nvfp4_gemm_bias_residual_fp16(
533
+ a_packed, b_packed, sfa, sfb, bias, residual, out
534
+ )
535
+ return out
536
+
537
+
538
  def fp4_w4a4_gemv_warpsplit_bf16(
539
  a_packed: torch.Tensor,
540
  b_packed: torch.Tensor,
 
574
  )
575
 
576
 
577
+ def nvfp4_gemm_bias_bf16(
578
+ a_packed: torch.Tensor,
579
+ b_packed: torch.Tensor,
580
+ sfa: torch.Tensor,
581
+ sfb: torch.Tensor,
582
+ bias: torch.Tensor,
583
+ *,
584
+ out: torch.Tensor | None = None,
585
+ ) -> torch.Tensor:
586
+ """SM110 NVFP4 GEMM with a fused per-column BF16 bias."""
587
+ if out is None:
588
+ out = torch.empty(
589
+ (a_packed.shape[0], b_packed.shape[0]),
590
+ device=a_packed.device,
591
+ dtype=torch.bfloat16,
592
+ )
593
+ ops.nvfp4_gemm_bias_bf16(a_packed, b_packed, sfa, sfb, bias, out)
594
+ return out
595
+
596
+
597
+ def nvfp4_gemm_bias_residual_bf16(
598
+ a_packed: torch.Tensor,
599
+ b_packed: torch.Tensor,
600
+ sfa: torch.Tensor,
601
+ sfb: torch.Tensor,
602
+ bias: torch.Tensor,
603
+ residual: torch.Tensor,
604
+ *,
605
+ out: torch.Tensor | None = None,
606
+ ) -> torch.Tensor:
607
+ """SM110 NVFP4 GEMM with fused BF16 bias and residual add."""
608
+ if out is None:
609
+ out = torch.empty_like(residual)
610
+ ops.nvfp4_gemm_bias_residual_bf16(
611
+ a_packed, b_packed, sfa, sfb, bias, residual, out
612
+ )
613
+ return out
614
+
615
+
616
  def nvfp4_gemm_residual_bf16(
617
  a_packed: torch.Tensor,
618
  b_packed: torch.Tensor,
 
665
  if out_packed is None:
666
  out_packed = torch.empty((m, n // 2), device=a_packed.device, dtype=torch.uint8)
667
  if out_sfa is None:
668
+ out_sfa = torch.zeros((sfa_size_bytes(m, n),), device=a_packed.device, dtype=torch.uint8)
669
  ops.nvfp4_gemm_bias_gelu_nvfp4(
670
  a_packed, b_packed, sfa, sfb, bias, out_packed, out_sfa, float(alpha)
671
  )
 
714
 
715
 
716
  __all__ = [
717
+ "aligned_fp4_dim",
718
+ "cutlass_fp4_gemm_geglu_il_hw_v10",
719
  "dequantize_fp4_sfa_fp16",
720
+ "e0m3_weight_gemm_fp16",
721
  "fp4_w4a16_linear_bf16",
722
  "fp4_w4a4_gemv_warpsplit_bf16",
723
  "nvfp4_gemm_bf16",
724
+ "nvfp4_gemm_fp16",
725
+ "nvfp4_gemm_variant_bf16",
726
+ "nvfp4_gemm_nvfp4",
727
+ "nvfp4_gemm_geglu_nvfp4_fp16",
728
+ "nvfp4_gemm_bias_gelu_nvfp4_fp16",
729
+ "nvfp4_gemm_bias_residual_fp16",
730
+ "nvfp4_gemm_bias_bf16",
731
  "nvfp4_gemm_bias_gelu_bf16",
732
  "nvfp4_gemm_bias_gelu_nvfp4",
733
+ "nvfp4_gemm_bias_residual_bf16",
734
  "nvfp4_gemm_residual_bf16",
735
+ "nvfp4_gemm_relu2_nvfp4",
736
  "nvfp4_gemm_streamk_bf16",
737
  "nvfp4_gemm_streamk_bias_bf16",
738
  "quantize_fp4_sfa_fp16",
739
+ "quantize_fp4_sfa_mse_fp16",
740
+ "quantize_fp4_sfa_mse_bf16",
741
+ "quantize_fp4_sfa_padded_bf16",
742
+ "pack_nvfp4_weight_bf16",
743
+ "quantize_e0m3_sfa_fp16",
744
  "quantize_fp4_sfa_bf16",
745
  "sfa_size_bytes",
746
  ]
build/torch211-cxx11-cu130-x86_64-linux/{_fp4_gemm_cuda_83a7cd0.abi3.so → _fp4_gemm_cuda_92c4b99.abi3.so} RENAMED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:dd0e48fd6d7fd9be6c613a6bca6c7bbd6a8e713c8743aca800d28bd0e0a8237b
3
- size 2422232
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:199bf055035b1b7d840c3b83094f0b95fef7be344a8e4ee5e180798a26b74b10
3
+ size 2330536
build/torch211-cxx11-cu130-x86_64-linux/_ops.py CHANGED
@@ -1,9 +1,6 @@
1
  import torch
2
- from . import _fp4_gemm_cuda_83a7cd0
3
- ops = torch.ops._fp4_gemm_cuda_83a7cd0
4
 
5
  def add_op_namespace_prefix(op_name: str):
6
- """
7
- Prefix op by namespace.
8
- """
9
- return f"_fp4_gemm_cuda_83a7cd0::{op_name}"
 
1
  import torch
2
+ from . import _fp4_gemm_cuda_92c4b99
3
+ ops = torch.ops._fp4_gemm_cuda_92c4b99
4
 
5
  def add_op_namespace_prefix(op_name: str):
6
+ return f"_fp4_gemm_cuda_92c4b99::{op_name}"
 
 
 
build/torch211-cxx11-cu130-x86_64-linux/fp4_gemm/__init__.py CHANGED
@@ -2,25 +2,13 @@ import ctypes
2
  import importlib.util
3
  import sys
4
  from pathlib import Path
5
- from types import ModuleType
6
 
7
-
8
- def _import_from_path(file_path: Path) -> ModuleType:
9
- # We cannot use the module name as-is, after adding it to `sys.modules`,
10
- # it would also be used for other imports. So, we make a module name that
11
- # depends on the path for it to be unique using the hex-encoded hash of
12
- # the path.
13
- path_hash = "{:x}".format(ctypes.c_size_t(hash(file_path.absolute())).value)
14
- module_name = path_hash
15
- spec = importlib.util.spec_from_file_location(module_name, file_path)
16
- if spec is None:
17
- raise ImportError(f"Cannot load spec for {module_name} from {file_path}")
18
  module = importlib.util.module_from_spec(spec)
19
- if module is None:
20
- raise ImportError(f"Cannot load module {module_name} from spec")
21
- sys.modules[module_name] = module
22
- spec.loader.exec_module(module) # type: ignore
23
  return module
24
 
25
-
26
- globals().update(vars(_import_from_path(Path(__file__).parent.parent / "__init__.py")))
 
2
  import importlib.util
3
  import sys
4
  from pathlib import Path
 
5
 
6
+ def _import_from_path(file_path: Path):
7
+ path_hash = '{:x}'.format(ctypes.c_size_t(hash(file_path.absolute())).value)
8
+ spec = importlib.util.spec_from_file_location(path_hash, file_path)
 
 
 
 
 
 
 
 
9
  module = importlib.util.module_from_spec(spec)
10
+ sys.modules[path_hash] = module
11
+ spec.loader.exec_module(module)
 
 
12
  return module
13
 
14
+ globals().update(vars(_import_from_path(Path(__file__).parent.parent / '__init__.py')))
 
build/torch211-cxx11-cu130-x86_64-linux/metadata.json CHANGED
@@ -1,14 +1,32 @@
1
  {
2
  "name": "fp4-gemm",
3
- "id": "_fp4_gemm_cuda_83a7cd0",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "python-depends": [],
7
  "backend": {
8
  "type": "cuda",
9
  "archs": [
10
- "11.0a",
11
  "12.0a"
12
  ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  }
14
  }
 
1
  {
2
  "name": "fp4-gemm",
3
+ "id": "_fp4_gemm_cuda_92c4b99",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "python-depends": [],
7
  "backend": {
8
  "type": "cuda",
9
  "archs": [
 
10
  "12.0a"
11
  ]
12
+ },
13
+ "digest": {
14
+ "algorithm": "sha256",
15
+ "files": {
16
+ "__init__.py": "Mr1/bAUzs+af68D+bRXX6iONDof//dzKgn6QU5pKdu8=",
17
+ "_fp4_gemm_cuda_92c4b99.abi3.so": "GZvwVQNbG32EDDuDCU8Llf73vjRKjk7l4YB5iia3SxA=",
18
+ "_ops.py": "twu0zeqTyaITBKAdr9FudRbkbXpvBQdmAHcVf/OXw2I=",
19
+ "fp4_gemm/__init__.py": "v6p5XMfQzddhi1fLSAw4HX9CyS0rQsidvu9VsT01xi4="
20
+ }
21
+ },
22
+ "provenance": {
23
+ "kernel": {
24
+ "sha": "92c4b991b6f1fbd89744aa6f46f62b1bfac7ca7e",
25
+ "dirty": false
26
+ },
27
+ "validation": {
28
+ "torch": "2.11.0+cu130",
29
+ "cuda": "13.0"
30
+ }
31
  }
32
  }
build/torch213-cxx11-cu130-aarch64-linux/__init__.py CHANGED
@@ -109,6 +109,11 @@ def _quant_mse_fake(x, packed, sfa, is_sfb: bool = False) -> None:
109
  return None
110
 
111
 
 
 
 
 
 
112
  @torch.library.register_fake(add_op_namespace_prefix("quantize_e0m3_sfa_fp16"))
113
  def _quant_e0m3_fake(x, packed, sfa, is_sfb: bool = False) -> None:
114
  return None
@@ -186,6 +191,91 @@ def quantize_fp4_sfa_mse_fp16(
186
  return packed, sfa
187
 
188
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
189
  def quantize_e0m3_sfa_fp16(
190
  x: torch.Tensor,
191
  packed: torch.Tensor | None = None,
@@ -381,6 +471,29 @@ def nvfp4_gemm_geglu_nvfp4_fp16(
381
  return out_packed, out_sfa
382
 
383
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
384
  def nvfp4_gemm_bias_gelu_nvfp4_fp16(
385
  a_packed: torch.Tensor,
386
  b_packed: torch.Tensor,
@@ -601,6 +714,8 @@ def nvfp4_gemm_streamk_bias_bf16(
601
 
602
 
603
  __all__ = [
 
 
604
  "dequantize_fp4_sfa_fp16",
605
  "e0m3_weight_gemm_fp16",
606
  "fp4_w4a16_linear_bf16",
@@ -622,6 +737,9 @@ __all__ = [
622
  "nvfp4_gemm_streamk_bias_bf16",
623
  "quantize_fp4_sfa_fp16",
624
  "quantize_fp4_sfa_mse_fp16",
 
 
 
625
  "quantize_e0m3_sfa_fp16",
626
  "quantize_fp4_sfa_bf16",
627
  "sfa_size_bytes",
 
109
  return None
110
 
111
 
112
+ @torch.library.register_fake(add_op_namespace_prefix("quantize_fp4_sfa_mse_bf16"))
113
+ def _quant_mse_bf16_fake(x, packed, sfa, is_sfb: bool = False) -> None:
114
+ return None
115
+
116
+
117
  @torch.library.register_fake(add_op_namespace_prefix("quantize_e0m3_sfa_fp16"))
118
  def _quant_e0m3_fake(x, packed, sfa, is_sfb: bool = False) -> None:
119
  return None
 
191
  return packed, sfa
192
 
193
 
194
+ def quantize_fp4_sfa_mse_bf16(
195
+ x: torch.Tensor,
196
+ packed: torch.Tensor | None = None,
197
+ sfa: torch.Tensor | None = None,
198
+ is_sfb: bool = False,
199
+ ):
200
+ """Pack BF16 weights using per-block reconstruction-MSE scale search."""
201
+ if packed is None or sfa is None:
202
+ packed, sfa = _alloc_fp4(x.shape[0], x.shape[1], x.device)
203
+ ops.quantize_fp4_sfa_mse_bf16(x, packed, sfa, bool(is_sfb))
204
+ return packed, sfa
205
+
206
+
207
+ def aligned_fp4_dim(dim: int, alignment: int = 32) -> int:
208
+ """Return the physical FP4 GEMM dimension for a logical dimension."""
209
+ if dim <= 0 or alignment <= 0 or alignment % 16 != 0:
210
+ raise ValueError("dim must be positive and alignment divisible by 16")
211
+ return ((dim + alignment - 1) // alignment) * alignment
212
+
213
+
214
+ def quantize_fp4_sfa_padded_bf16(
215
+ x: torch.Tensor,
216
+ *,
217
+ alignment: int = 32,
218
+ is_sfb: bool = False,
219
+ ):
220
+ """Bind-time BF16 pack with zero padding on the final dimension.
221
+
222
+ The returned packed tensor uses the physical aligned width. Keep the
223
+ logical width returned as the third item for slicing final model outputs.
224
+ This helper allocates and is intentionally not a runtime hot-path op.
225
+ """
226
+ if x.ndim != 2 or x.dtype != torch.bfloat16:
227
+ raise ValueError("x must be a 2-D BF16 tensor")
228
+ logical_dim = int(x.shape[1])
229
+ physical_dim = aligned_fp4_dim(logical_dim, alignment)
230
+ if physical_dim == logical_dim:
231
+ padded = x.contiguous()
232
+ else:
233
+ padded = torch.zeros(
234
+ (x.shape[0], physical_dim), device=x.device, dtype=x.dtype
235
+ )
236
+ padded[:, :logical_dim].copy_(x)
237
+ packed, sfa = quantize_fp4_sfa_bf16(padded, is_sfb=is_sfb)
238
+ return packed, sfa, logical_dim
239
+
240
+
241
+ def pack_nvfp4_weight_bf16(
242
+ weight: torch.Tensor,
243
+ bias: torch.Tensor | None = None,
244
+ *,
245
+ alignment: int = 32,
246
+ mse: bool = False,
247
+ ):
248
+ """Zero-pad and pack a logical ``(N, K)`` BF16 weight at bind time.
249
+
250
+ Both physical ``N`` and ``K`` are aligned. This is the model-neutral
251
+ contract used by widths such as SigLIP's logical 4304, which becomes 4320
252
+ for Blackwell NVFP4 TMA. The hot path consumes the returned static tensors
253
+ directly and performs no padding or allocation.
254
+ """
255
+ if weight.ndim != 2 or weight.dtype != torch.bfloat16:
256
+ raise ValueError("weight must be a 2-D BF16 tensor")
257
+ logical_n, logical_k = map(int, weight.shape)
258
+ physical_n = aligned_fp4_dim(logical_n, alignment)
259
+ physical_k = aligned_fp4_dim(logical_k, alignment)
260
+ padded_weight = torch.zeros(
261
+ (physical_n, physical_k), device=weight.device, dtype=weight.dtype
262
+ )
263
+ padded_weight[:logical_n, :logical_k].copy_(weight)
264
+ quantize = quantize_fp4_sfa_mse_bf16 if mse else quantize_fp4_sfa_bf16
265
+ packed, sfb = quantize(padded_weight, is_sfb=True)
266
+ padded_bias = None
267
+ if bias is not None:
268
+ if bias.ndim != 1 or bias.numel() != logical_n:
269
+ raise ValueError("bias must have shape (N,)")
270
+ if bias.dtype != torch.bfloat16 or bias.device != weight.device:
271
+ raise ValueError("bias must match the weight dtype and device")
272
+ padded_bias = torch.zeros(
273
+ (physical_n,), device=bias.device, dtype=bias.dtype
274
+ )
275
+ padded_bias[:logical_n].copy_(bias)
276
+ return packed, sfb, padded_bias, (logical_n, logical_k)
277
+
278
+
279
  def quantize_e0m3_sfa_fp16(
280
  x: torch.Tensor,
281
  packed: torch.Tensor | None = None,
 
471
  return out_packed, out_sfa
472
 
473
 
474
+ def cutlass_fp4_gemm_geglu_il_hw_v10(
475
+ a_packed: torch.Tensor,
476
+ b_interleaved_packed: torch.Tensor,
477
+ sfa: torch.Tensor,
478
+ sfb: torch.Tensor,
479
+ *,
480
+ scratch: torch.Tensor | None = None,
481
+ out_packed: torch.Tensor | None = None,
482
+ out_sfa: torch.Tensor | None = None,
483
+ ) -> tuple[torch.Tensor, torch.Tensor]:
484
+ """Explicit public alias for the native SM110 v10 GeGLU schedule."""
485
+ return nvfp4_gemm_geglu_nvfp4_fp16(
486
+ a_packed,
487
+ b_interleaved_packed,
488
+ sfa,
489
+ sfb,
490
+ skinny=True,
491
+ scratch=scratch,
492
+ out_packed=out_packed,
493
+ out_sfa=out_sfa,
494
+ )
495
+
496
+
497
  def nvfp4_gemm_bias_gelu_nvfp4_fp16(
498
  a_packed: torch.Tensor,
499
  b_packed: torch.Tensor,
 
714
 
715
 
716
  __all__ = [
717
+ "aligned_fp4_dim",
718
+ "cutlass_fp4_gemm_geglu_il_hw_v10",
719
  "dequantize_fp4_sfa_fp16",
720
  "e0m3_weight_gemm_fp16",
721
  "fp4_w4a16_linear_bf16",
 
737
  "nvfp4_gemm_streamk_bias_bf16",
738
  "quantize_fp4_sfa_fp16",
739
  "quantize_fp4_sfa_mse_fp16",
740
+ "quantize_fp4_sfa_mse_bf16",
741
+ "quantize_fp4_sfa_padded_bf16",
742
+ "pack_nvfp4_weight_bf16",
743
  "quantize_e0m3_sfa_fp16",
744
  "quantize_fp4_sfa_bf16",
745
  "sfa_size_bytes",
build/torch213-cxx11-cu130-aarch64-linux/{fp4_gemm_source_test.abi3.so → _fp4_gemm_cuda_92c4b99.abi3.so} RENAMED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:73b4cbcb15d13cc6452007e59961f3b217d21771f0c0937cc121cc8e0e6b1e12
3
- size 6529016
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b40a60dd7cfb9dfd94077016907dae286cd1222905ae26c740a993f73ff0d9d2
3
+ size 6070984
build/torch213-cxx11-cu130-aarch64-linux/_ops.py CHANGED
@@ -1,6 +1,6 @@
1
  import torch
2
- from . import fp4_gemm_source_test
3
- ops = torch.ops.fp4_gemm_source_test
4
 
5
  def add_op_namespace_prefix(op_name: str):
6
- return f"fp4_gemm_source_test::{op_name}"
 
1
  import torch
2
+ from . import _fp4_gemm_cuda_92c4b99
3
+ ops = torch.ops._fp4_gemm_cuda_92c4b99
4
 
5
  def add_op_namespace_prefix(op_name: str):
6
+ return f"_fp4_gemm_cuda_92c4b99::{op_name}"
build/torch213-cxx11-cu130-aarch64-linux/metadata.json CHANGED
@@ -1,27 +1,27 @@
1
  {
2
  "name": "fp4-gemm",
3
- "id": "fp4_gemm_source_test",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "python-depends": [],
7
  "backend": {
8
  "type": "cuda",
9
  "archs": [
10
- "11.0"
11
  ]
12
  },
13
  "digest": {
14
  "algorithm": "sha256",
15
  "files": {
16
- "__init__.py": "Fb/FDYOpDw+YGlevblUsv4AkILDQCJdB807qdDUDI1c=",
17
- "fp4_gemm_source_test.abi3.so": "c7TLyxXRPMZFIAflmWHzshfSF3HwwJN8wSHMjg5rHhI=",
18
- "_ops.py": "ZPjRxrYk7lCA0ZHoCjg8BaJyFY7a5i4NC3jIL6VMRJ4=",
19
  "fp4_gemm/__init__.py": "v6p5XMfQzddhi1fLSAw4HX9CyS0rQsidvu9VsT01xi4="
20
  }
21
  },
22
  "provenance": {
23
  "kernel": {
24
- "sha": "b6588aa59dd4f8c646f90b58312a0a66886e061f",
25
  "dirty": false
26
  },
27
  "validation": {
 
1
  {
2
  "name": "fp4-gemm",
3
+ "id": "_fp4_gemm_cuda_92c4b99",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "python-depends": [],
7
  "backend": {
8
  "type": "cuda",
9
  "archs": [
10
+ "11.0a"
11
  ]
12
  },
13
  "digest": {
14
  "algorithm": "sha256",
15
  "files": {
16
+ "__init__.py": "Mr1/bAUzs+af68D+bRXX6iONDof//dzKgn6QU5pKdu8=",
17
+ "_fp4_gemm_cuda_92c4b99.abi3.so": "tApg3Xz7nf2UB3AWkH2uKGzRIikFribHQKmT9z/w2dI=",
18
+ "_ops.py": "twu0zeqTyaITBKAdr9FudRbkbXpvBQdmAHcVf/OXw2I=",
19
  "fp4_gemm/__init__.py": "v6p5XMfQzddhi1fLSAw4HX9CyS0rQsidvu9VsT01xi4="
20
  }
21
  },
22
  "provenance": {
23
  "kernel": {
24
+ "sha": "92c4b991b6f1fbd89744aa6f46f62b1bfac7ca7e",
25
  "dirty": false
26
  },
27
  "validation": {