kernels-bot commited on
Commit
682a416
·
verified ·
1 Parent(s): 9f00a55

Uploaded using `kernel-builder`.

Browse files
build/torch-rocm/__init__.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .utils import fp8_act_quant
2
+ from .matmul import matmul_2d
3
+ from .batched import matmul_batched
4
+ from .grouped import matmul_grouped
5
+
6
+ __all__ = [
7
+ "fp8_act_quant",
8
+ # 2D matmul
9
+ "matmul_2d",
10
+ # Batched matmul
11
+ "matmul_batched",
12
+ # Grouped matmul
13
+ "matmul_grouped",
14
+ ]
build/torch-rocm/_ops.py ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+
3
+ def get_backend() -> str:
4
+ """Detect the backend by inspecting torch."""
5
+ import torch
6
+
7
+ if hasattr(torch, "neuron"):
8
+ # Needs to be sorted before specific Torch builds, since Neuron
9
+ # extension can be loaded into e.g. CUDA Torch builds.
10
+ return "neuron"
11
+ elif torch.version.cuda is not None:
12
+ return "cuda"
13
+ elif torch.version.hip is not None:
14
+ return "rocm"
15
+ elif torch.backends.mps.is_available():
16
+ return "metal"
17
+ elif hasattr(torch.version, "xpu") and torch.version.xpu is not None:
18
+ return "xpu"
19
+ else:
20
+ return "cpu"
21
+
22
+
23
+ def _find_ops_name() -> str:
24
+ kernel_name = "finegrained_fp8"
25
+ unique_id = "846165b"
26
+ backend = get_backend()
27
+ return f"_{kernel_name}_{backend}_{unique_id}"
28
+
29
+
30
+ _OPS_NAME = _find_ops_name()
31
+
32
+ ops = getattr(torch.ops, _OPS_NAME)
33
+
34
+ def add_op_namespace_prefix(op_name: str) -> str:
35
+ """
36
+ Prefix op by namespace.
37
+ """
38
+ return f"{_OPS_NAME}::{op_name}"
build/torch-rocm/batched.py ADDED
@@ -0,0 +1,773 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2026 The HuggingFace Inc. team. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import torch
16
+ import triton
17
+ import triton.language as tl
18
+ from torch.library import triton_op, wrap_triton
19
+
20
+ from ._ops import add_op_namespace_prefix, ops
21
+ from .utils import (
22
+ MX_SCALE_GROUP_K,
23
+ NIBBLES_PER_BYTE,
24
+ decode_ue8m0_scale,
25
+ device_context,
26
+ mx_act_quant_inline,
27
+ fp8_act_quant,
28
+ fp8_act_quant_inline,
29
+ get_accelerator_autotuning_configs,
30
+ is_mxfp4,
31
+ is_mxfp8,
32
+ ue8m0_as_uint8,
33
+ )
34
+
35
+
36
+ @triton.jit
37
+ def _expert_setup(
38
+ A,
39
+ B,
40
+ C,
41
+ Bs,
42
+ ExpertIds,
43
+ stride_am,
44
+ stride_be,
45
+ stride_cm,
46
+ stride_bs_e,
47
+ stride_eid,
48
+ ):
49
+ """Per-(row, expert) prologue shared by the batched kernels: read the program
50
+ ids, look up the routed expert, and advance the A/B/C/Bs base pointers to this
51
+ row's slice. Returns ``(batch_id, pid_n, expert_id, A, B, C, Bs)``.
52
+
53
+ The caller must early-return on the EP sentinel (``expert_id >= NUM_EXPERTS``)
54
+ before any load — the pointer arithmetic itself is harmless, only the loads on a
55
+ non-local expert would be out of bounds."""
56
+ batch_id = tl.program_id(axis=0)
57
+ pid_n = tl.program_id(axis=1)
58
+ # Cast to int64 to prevent overflow on expert_id * stride_be.
59
+ expert_id = tl.load(ExpertIds + batch_id * stride_eid).to(tl.int64)
60
+ A = A + batch_id * stride_am
61
+ B = B + expert_id * stride_be
62
+ C = C + batch_id * stride_cm
63
+ Bs = Bs + expert_id * stride_bs_e
64
+ return batch_id, pid_n, expert_id, A, B, C, Bs
65
+
66
+
67
+ @triton.jit
68
+ def _store_row(
69
+ C,
70
+ accumulator,
71
+ pid_n,
72
+ stride_cn,
73
+ BLOCK_SIZE_M: tl.constexpr,
74
+ BLOCK_SIZE_N: tl.constexpr,
75
+ ):
76
+ """Output epilogue shared by the batched kernels (``C`` already advanced to the
77
+ row). The fake-batch trick aliases all ``BLOCK_SIZE_M`` lanes to the same C row,
78
+ so a plain store would issue ``BLOCK_SIZE_M`` duplicate-address writes — benign on
79
+ NVIDIA WGMMA (last-write-wins of identical bytes) but hardware-undefined on Intel
80
+ XPU, where it corrupts the output. Mask so only lane 0 stores; the accumulator
81
+ rows are mathematically identical (same A row × same B), so lane 0 is correct."""
82
+ c = accumulator.to(C.dtype.element_ty)
83
+ offs_cm = tl.arange(0, BLOCK_SIZE_M)
84
+ offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
85
+ c_ptrs = C + offs_cm[:, None] * 0 + stride_cn * offs_cn[None, :]
86
+ tl.store(c_ptrs, c, mask=(offs_cm == 0)[:, None])
87
+
88
+
89
+ @triton.autotune(
90
+ configs=get_accelerator_autotuning_configs(),
91
+ key=["N", "K"],
92
+ )
93
+ @triton.jit
94
+ def w8a8_block_dynamic_fp8_matmul_batched_kernel(
95
+ A, # (S, K) raw BF16/FP16 activations
96
+ B, # (E, N, K) FP8 weight matrices
97
+ C, # (S, N) output
98
+ Bs, # (E, N // BLOCK_SIZE_N, K // BLOCK_SIZE_K) weight scales
99
+ ExpertIds, # (S,) — which expert each batch element routes to
100
+ # Shape
101
+ S,
102
+ N,
103
+ K,
104
+ # Strides
105
+ stride_am,
106
+ stride_ak,
107
+ stride_be,
108
+ stride_bk,
109
+ stride_bn,
110
+ stride_cm,
111
+ stride_cn,
112
+ stride_bs_e,
113
+ stride_bs_k,
114
+ stride_bs_n,
115
+ stride_eid,
116
+ # Meta-parameters
117
+ BLOCK_SIZE_M: tl.constexpr,
118
+ BLOCK_SIZE_N: tl.constexpr,
119
+ BLOCK_SIZE_K: tl.constexpr,
120
+ NUM_EXPERTS: tl.constexpr,
121
+ ):
122
+ """Block-scale batched FP8 expert matmul kernel.
123
+
124
+ Each program handles one routed token row and one N-tile, looks up the
125
+ owning expert from ``ExpertIds``, and applies fused activation quantization.
126
+ """
127
+ _, pid_n, expert_id, A, B, C, Bs = _expert_setup(
128
+ A, B, C, Bs, ExpertIds, stride_am, stride_be, stride_cm, stride_bs_e, stride_eid
129
+ )
130
+ # EP sentinel: row routed to a non-local expert; output is left uninit.
131
+ if expert_id >= NUM_EXPERTS:
132
+ return
133
+
134
+ offs_bn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
135
+ offs_k = tl.arange(0, BLOCK_SIZE_K)
136
+ a_ptrs = A + tl.arange(0, BLOCK_SIZE_M)[:, None] * 0 + offs_k[None, :] * stride_ak
137
+ b_ptrs = B + (offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn)
138
+
139
+ bs_ptrs = Bs + pid_n * stride_bs_n
140
+
141
+ accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
142
+ for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
143
+ a_raw = tl.load(a_ptrs).to(tl.float32)
144
+ a, a_s = fp8_act_quant_inline(a_raw)
145
+ b = tl.load(b_ptrs)
146
+ b_s = decode_ue8m0_scale(tl.load(bs_ptrs))
147
+ accumulator += tl.dot(a, b) * a_s[:, None] * b_s[None, :]
148
+ a_ptrs += BLOCK_SIZE_K * stride_ak
149
+ b_ptrs += BLOCK_SIZE_K * stride_bk
150
+ bs_ptrs += stride_bs_k
151
+
152
+ _store_row(C, accumulator, pid_n, stride_cn, BLOCK_SIZE_M, BLOCK_SIZE_N)
153
+
154
+
155
+ @triton.autotune(
156
+ configs=get_accelerator_autotuning_configs(with_block_sizes=True),
157
+ key=["N", "K"],
158
+ )
159
+ @triton.jit
160
+ def w8a8_tensor_dynamic_fp8_matmul_batched_kernel(
161
+ A, # (S, K) pre-quantized FP8 activations
162
+ B, # (E, N, K) FP8 weight matrices
163
+ C, # (S, N) output
164
+ As, # (S,) per-token activation scales
165
+ Bs, # (E, 1, 1) per-tensor weight scales
166
+ ExpertIds, # (S,) — which expert each batch element routes to
167
+ # Shape
168
+ S,
169
+ N,
170
+ K,
171
+ # Strides
172
+ stride_am,
173
+ stride_ak,
174
+ stride_be,
175
+ stride_bk,
176
+ stride_bn,
177
+ stride_cm,
178
+ stride_cn,
179
+ stride_as_m,
180
+ stride_bs_e,
181
+ stride_eid,
182
+ # Meta-parameters
183
+ BLOCK_SIZE_M: tl.constexpr,
184
+ BLOCK_SIZE_N: tl.constexpr,
185
+ BLOCK_SIZE_K: tl.constexpr,
186
+ NUM_EXPERTS: tl.constexpr,
187
+ ):
188
+ """Tensor-scale batched FP8 expert matmul kernel.
189
+
190
+ Activations are already quantized; the kernel applies per-token activation
191
+ scales and per-expert tensor weight scales.
192
+ """
193
+ batch_id, pid_n, expert_id, A, B, C, Bs = _expert_setup(
194
+ A, B, C, Bs, ExpertIds, stride_am, stride_be, stride_cm, stride_bs_e, stride_eid
195
+ )
196
+ # EP sentinel: row routed to a non-local expert; output is left uninit.
197
+ if expert_id >= NUM_EXPERTS:
198
+ return
199
+
200
+ offs_bn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
201
+ offs_k = tl.arange(0, BLOCK_SIZE_K)
202
+ a_ptrs = A + tl.arange(0, BLOCK_SIZE_M)[:, None] * 0 + offs_k[None, :] * stride_ak
203
+ b_ptrs = B + (offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn)
204
+
205
+ b_s = tl.load(Bs)
206
+ a_s = tl.load(As + batch_id * stride_as_m)
207
+
208
+ accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
209
+ for _ in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
210
+ a = tl.load(a_ptrs)
211
+ b = tl.load(b_ptrs)
212
+ accumulator += tl.dot(a, b)
213
+ a_ptrs += BLOCK_SIZE_K * stride_ak
214
+ b_ptrs += BLOCK_SIZE_K * stride_bk
215
+
216
+ accumulator = accumulator * a_s * b_s
217
+
218
+ _store_row(C, accumulator, pid_n, stride_cn, BLOCK_SIZE_M, BLOCK_SIZE_N)
219
+
220
+
221
+ @triton.autotune(
222
+ configs=get_accelerator_autotuning_configs(with_block_sizes=True),
223
+ key=["N", "K"],
224
+ )
225
+ @triton.jit
226
+ def w4a8_mx_dynamic_fp4_matmul_batched_kernel(
227
+ A, # (S, K) raw BF16/FP16 activations
228
+ B, # (E, N, K // 2) packed FP4 (E2M1) expert weights as int8
229
+ C, # (S, N) output
230
+ Bs, # (E, N, K // SCALE_GROUP_K) UE8M0 weight scales
231
+ ExpertIds, # (S,) — which expert each routed row uses
232
+ # Shape
233
+ S,
234
+ N,
235
+ K,
236
+ # Strides
237
+ stride_am,
238
+ stride_ak,
239
+ stride_be,
240
+ stride_bk,
241
+ stride_bn,
242
+ stride_cm,
243
+ stride_cn,
244
+ stride_bs_e,
245
+ stride_bs_k,
246
+ stride_bs_n,
247
+ stride_eid,
248
+ # Meta-parameters
249
+ BLOCK_SIZE_M: tl.constexpr,
250
+ BLOCK_SIZE_N: tl.constexpr,
251
+ BLOCK_SIZE_K: tl.constexpr,
252
+ NIBBLES_PER_BYTE: tl.constexpr,
253
+ SCALE_GROUP_K: tl.constexpr,
254
+ NUM_EXPERTS: tl.constexpr,
255
+ ):
256
+ """Batched MXFP4 (W4A8) expert matmul with fused activation quant.
257
+
258
+ Each program handles one routed token row and one N-tile, looks up the
259
+ owning expert from ``ExpertIds``, quantizes ``A`` to FP8 per K-group inline
260
+ (UE8M0 scale), then ``tl.dot_scaled`` against packed FP4 weights.
261
+ """
262
+ _, pid_n, expert_id, A, B, C, Bs = _expert_setup(
263
+ A, B, C, Bs, ExpertIds, stride_am, stride_be, stride_cm, stride_bs_e, stride_eid
264
+ )
265
+ # EP sentinel: row routed to a non-local expert; output is left uninit.
266
+ if expert_id >= NUM_EXPERTS:
267
+ return
268
+
269
+ offs_bn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
270
+ offs_k = tl.arange(0, BLOCK_SIZE_K)
271
+ offs_k_byte = tl.arange(0, BLOCK_SIZE_K // NIBBLES_PER_BYTE)
272
+ offs_sf = tl.arange(0, BLOCK_SIZE_K // SCALE_GROUP_K)
273
+ a_ptrs = A + tl.arange(0, BLOCK_SIZE_M)[:, None] * 0 + offs_k[None, :] * stride_ak
274
+ b_ptrs = B + (offs_k_byte[:, None] * stride_bk + offs_bn[None, :] * stride_bn)
275
+ bs_ptrs = Bs + offs_bn[:, None] * stride_bs_n + offs_sf[None, :] * stride_bs_k
276
+
277
+ accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
278
+ for _ in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
279
+ a_raw = tl.load(a_ptrs).to(tl.float32)
280
+ a, a_scale = mx_act_quant_inline(
281
+ a_raw, BLOCK_SIZE_M, BLOCK_SIZE_K, SCALE_GROUP_K
282
+ )
283
+ b = tl.load(b_ptrs).to(tl.uint8)
284
+ b_s = tl.load(bs_ptrs).to(tl.uint8)
285
+ accumulator += tl.dot_scaled(a, a_scale, "e4m3", b, b_s, "e2m1")
286
+ a_ptrs += BLOCK_SIZE_K * stride_ak
287
+ b_ptrs += (BLOCK_SIZE_K // NIBBLES_PER_BYTE) * stride_bk
288
+ bs_ptrs += (BLOCK_SIZE_K // SCALE_GROUP_K) * stride_bs_k
289
+
290
+ _store_row(C, accumulator, pid_n, stride_cn, BLOCK_SIZE_M, BLOCK_SIZE_N)
291
+
292
+
293
+ @triton.autotune(
294
+ configs=get_accelerator_autotuning_configs(with_block_sizes=True),
295
+ key=["N", "K"],
296
+ )
297
+ @triton.jit
298
+ def w8a8_mx_dynamic_fp8_matmul_batched_kernel(
299
+ A, # (S, K) raw BF16/FP16 activations
300
+ B, # (E, N, K) E4M3 expert weights (unpacked)
301
+ C, # (S, N) output
302
+ Bs, # (E, N, K // SCALE_GROUP_K) UE8M0 weight scales
303
+ ExpertIds, # (S,) — which expert each routed row uses
304
+ # Shape
305
+ S,
306
+ N,
307
+ K,
308
+ # Strides
309
+ stride_am,
310
+ stride_ak,
311
+ stride_be,
312
+ stride_bk,
313
+ stride_bn,
314
+ stride_cm,
315
+ stride_cn,
316
+ stride_bs_e,
317
+ stride_bs_k,
318
+ stride_bs_n,
319
+ stride_eid,
320
+ # Meta-parameters
321
+ BLOCK_SIZE_M: tl.constexpr,
322
+ BLOCK_SIZE_N: tl.constexpr,
323
+ BLOCK_SIZE_K: tl.constexpr,
324
+ SCALE_GROUP_K: tl.constexpr,
325
+ NUM_EXPERTS: tl.constexpr,
326
+ ):
327
+ """Batched MXFP8 (W8A8) expert matmul with fused activation quant.
328
+
329
+ Each program handles one routed token row and one N-tile, looks up the
330
+ owning expert from ``ExpertIds``, quantizes ``A`` to E4M3 per K-group inline
331
+ (UE8M0 scale), then ``tl.dot_scaled`` against E4M3 weights. Mirrors the
332
+ batched MXFP4 kernel but with unpacked weights and ``"e4m3"`` on both operands.
333
+ """
334
+ _, pid_n, expert_id, A, B, C, Bs = _expert_setup(
335
+ A, B, C, Bs, ExpertIds, stride_am, stride_be, stride_cm, stride_bs_e, stride_eid
336
+ )
337
+ # EP sentinel: row routed to a non-local expert; output is left uninit.
338
+ if expert_id >= NUM_EXPERTS:
339
+ return
340
+
341
+ offs_bn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
342
+ offs_k = tl.arange(0, BLOCK_SIZE_K)
343
+ offs_sf = tl.arange(0, BLOCK_SIZE_K // SCALE_GROUP_K)
344
+ a_ptrs = A + tl.arange(0, BLOCK_SIZE_M)[:, None] * 0 + offs_k[None, :] * stride_ak
345
+ b_ptrs = B + (offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn)
346
+ bs_ptrs = Bs + offs_bn[:, None] * stride_bs_n + offs_sf[None, :] * stride_bs_k
347
+
348
+ accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
349
+ for _ in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
350
+ a_raw = tl.load(a_ptrs).to(tl.float32)
351
+ a, a_scale = mx_act_quant_inline(
352
+ a_raw, BLOCK_SIZE_M, BLOCK_SIZE_K, SCALE_GROUP_K
353
+ )
354
+ b = tl.load(b_ptrs)
355
+ b_s = tl.load(bs_ptrs).to(tl.uint8)
356
+ accumulator += tl.dot_scaled(a, a_scale, "e4m3", b, b_s, "e4m3")
357
+ a_ptrs += BLOCK_SIZE_K * stride_ak
358
+ b_ptrs += BLOCK_SIZE_K * stride_bk
359
+ bs_ptrs += (BLOCK_SIZE_K // SCALE_GROUP_K) * stride_bs_k
360
+
361
+ _store_row(C, accumulator, pid_n, stride_cn, BLOCK_SIZE_M, BLOCK_SIZE_N)
362
+
363
+
364
+ @triton_op(
365
+ add_op_namespace_prefix("w8a8_block_dynamic_fp8_matmul_batched"), mutates_args=()
366
+ )
367
+ def _w8a8_block_dynamic_fp8_matmul_batched(
368
+ A: torch.Tensor,
369
+ B: torch.Tensor,
370
+ Bs: torch.Tensor,
371
+ expert_ids: torch.Tensor,
372
+ block_size: list[int],
373
+ output_dtype: torch.dtype | None = None,
374
+ ) -> torch.Tensor:
375
+ """Block-scale batched FP8 matmul: C[s] = A[s] @ B[expert_ids[s]].T, with fused act quant.
376
+
377
+ A: (S, K) raw bf16/fp16 activations
378
+ B: (E, N, K) FP8 expert weights
379
+ Bs: (E, N // block_n, K // block_k) per-block weight scales
380
+ """
381
+ assert A.ndim == 2, f"A must be 2D (S, K), got ndim={A.ndim}"
382
+ assert A.is_contiguous(), "A must be contiguous"
383
+ assert B.ndim == 3, f"B must be 3D (E, N, K), got ndim={B.ndim}"
384
+ assert B.is_contiguous(), "B must be contiguous"
385
+ assert A.shape[1] == B.shape[2], (
386
+ f"K mismatch: A has K={A.shape[1]}, B has K={B.shape[2]}"
387
+ )
388
+
389
+ S, K = A.shape
390
+ E, N, _ = B.shape
391
+
392
+ assert len(block_size) == 2, (
393
+ f"block_size must be [block_n, block_k], got {block_size}"
394
+ )
395
+ block_n, block_k = block_size[0], block_size[1]
396
+ # MoE expert dimensions must be block-aligned; non-aligned N/K is not supported.
397
+ assert N % block_n == 0, f"N ({N}) must be divisible by block_n ({block_n})"
398
+ assert K % block_k == 0, f"K ({K}) must be divisible by block_k ({block_k})"
399
+ assert Bs.ndim == 3, (
400
+ f"Bs must be 3D (E, N//block_n, K//block_k), got ndim={Bs.ndim}"
401
+ )
402
+ assert Bs.shape == (E, N // block_n, K // block_k), (
403
+ f"Bs shape {tuple(Bs.shape)} != expected ({E}, {N // block_n}, {K // block_k})"
404
+ )
405
+
406
+ # Decode handles one routed row per program; BLOCK_SIZE_M > 1 would just
407
+ # duplicate the same row computation and keep one row on store.
408
+ BLOCK_SIZE_M = 1
409
+ Bs = ue8m0_as_uint8(Bs)
410
+ grid = (S, triton.cdiv(N, block_n))
411
+ C = A.new_empty(S, N, dtype=output_dtype)
412
+
413
+ with device_context(A.device):
414
+ wrap_triton(w8a8_block_dynamic_fp8_matmul_batched_kernel)[grid](
415
+ A,
416
+ B,
417
+ C,
418
+ Bs,
419
+ expert_ids,
420
+ S,
421
+ N,
422
+ K,
423
+ A.stride(0),
424
+ A.stride(1),
425
+ B.stride(0),
426
+ B.stride(2),
427
+ B.stride(1),
428
+ C.stride(0),
429
+ C.stride(1),
430
+ Bs.stride(0),
431
+ Bs.stride(2),
432
+ Bs.stride(1),
433
+ expert_ids.stride(0),
434
+ BLOCK_SIZE_N=block_n,
435
+ BLOCK_SIZE_K=block_k,
436
+ BLOCK_SIZE_M=BLOCK_SIZE_M,
437
+ NUM_EXPERTS=E,
438
+ )
439
+
440
+ return C
441
+
442
+
443
+ @triton_op(
444
+ add_op_namespace_prefix("w8a8_tensor_dynamic_fp8_matmul_batched"), mutates_args=()
445
+ )
446
+ def _w8a8_tensor_dynamic_fp8_matmul_batched(
447
+ A: torch.Tensor,
448
+ B: torch.Tensor,
449
+ Bs: torch.Tensor,
450
+ expert_ids: torch.Tensor,
451
+ output_dtype: torch.dtype | None = None,
452
+ ) -> torch.Tensor:
453
+ """Tensor-scale batched FP8 matmul: C[s] = A[s] @ B[expert_ids[s]].T, with fused act quant.
454
+
455
+ A: (S, K) raw bf16/fp16 activations
456
+ B: (E, N, K) FP8 expert weights
457
+ Bs: (E,) or (E, 1, 1) per-expert weight scales
458
+ """
459
+ assert A.ndim == 2, f"A must be 2D (S, K), got ndim={A.ndim}"
460
+ assert A.is_contiguous(), "A must be contiguous"
461
+ assert B.ndim == 3, f"B must be 3D (E, N, K), got ndim={B.ndim}"
462
+ assert B.is_contiguous(), "B must be contiguous"
463
+ assert A.shape[1] == B.shape[2], (
464
+ f"K mismatch: A has K={A.shape[1]}, B has K={B.shape[2]}"
465
+ )
466
+
467
+ S, K = A.shape
468
+ E, N, _ = B.shape
469
+
470
+ # Normalize Bs to (E, 1, 1)
471
+ if Bs.ndim == 1:
472
+ assert Bs.shape[0] == E, f"Bs shape {tuple(Bs.shape)} != expected ({E},)"
473
+ Bs = Bs.reshape(E, 1, 1)
474
+ else:
475
+ assert Bs.shape == (E, 1, 1), (
476
+ f"Bs shape {tuple(Bs.shape)} != expected ({E}, 1, 1)"
477
+ )
478
+
479
+ # Decode handles one routed row per program; BLOCK_SIZE_M > 1 would just
480
+ # duplicate the same row computation and keep one row on store.
481
+ BLOCK_SIZE_M = 1
482
+ Bs = ue8m0_as_uint8(Bs)
483
+ qA, As = fp8_act_quant(A, K)
484
+ C = A.new_empty(S, N, dtype=output_dtype)
485
+
486
+ def grid(META):
487
+ return (S, triton.cdiv(N, META["BLOCK_SIZE_N"]))
488
+
489
+ with device_context(A.device):
490
+ wrap_triton(w8a8_tensor_dynamic_fp8_matmul_batched_kernel)[grid](
491
+ qA,
492
+ B,
493
+ C,
494
+ As,
495
+ Bs,
496
+ expert_ids,
497
+ S,
498
+ N,
499
+ K,
500
+ qA.stride(0),
501
+ qA.stride(1),
502
+ B.stride(0),
503
+ B.stride(2),
504
+ B.stride(1),
505
+ C.stride(0),
506
+ C.stride(1),
507
+ As.stride(0),
508
+ Bs.stride(0),
509
+ expert_ids.stride(0),
510
+ BLOCK_SIZE_M=BLOCK_SIZE_M,
511
+ NUM_EXPERTS=E,
512
+ )
513
+
514
+ return C
515
+
516
+
517
+ def w8a8_block_dynamic_fp8_matmul_batched(
518
+ A: torch.Tensor,
519
+ B: torch.Tensor,
520
+ Bs: torch.Tensor,
521
+ expert_ids: torch.Tensor,
522
+ block_size: list[int],
523
+ output_dtype: torch.dtype | None = None,
524
+ ) -> torch.Tensor:
525
+ """Block-scale batched FP8 matmul with fused activation quantization.
526
+
527
+ A: (S, K) raw activations, bf16/fp16/fp32
528
+ B: (E, N, K) FP8 expert weights
529
+ Bs: (E, N // block_n, K // block_k) per-block weight scales
530
+ expert_ids: (S,) — kernel loads stride-aware, any int dtype works
531
+ output_dtype: defaults to ``A.dtype``
532
+ """
533
+ return ops.w8a8_block_dynamic_fp8_matmul_batched(
534
+ A, B, Bs, expert_ids, block_size, output_dtype
535
+ )
536
+
537
+
538
+ def w8a8_tensor_dynamic_fp8_matmul_batched(
539
+ A: torch.Tensor,
540
+ B: torch.Tensor,
541
+ Bs: torch.Tensor,
542
+ expert_ids: torch.Tensor,
543
+ output_dtype: torch.dtype | None = None,
544
+ ) -> torch.Tensor:
545
+ """Tensor-scale batched FP8 matmul with fused activation quantization.
546
+
547
+ A: (S, K) raw activations, bf16/fp16/fp32
548
+ B: (E, N, K) FP8 expert weights
549
+ Bs: (E,) or (E, 1, 1) per-expert weight scales
550
+ output_dtype: defaults to ``A.dtype``
551
+ """
552
+ return ops.w8a8_tensor_dynamic_fp8_matmul_batched(
553
+ A, B, Bs, expert_ids, output_dtype
554
+ )
555
+
556
+
557
+ @triton_op(
558
+ add_op_namespace_prefix("w4a8_mx_dynamic_fp4_matmul_batched"), mutates_args=()
559
+ )
560
+ def _w4a8_mx_dynamic_fp4_matmul_batched(
561
+ A: torch.Tensor,
562
+ B: torch.Tensor,
563
+ Bs: torch.Tensor,
564
+ expert_ids: torch.Tensor,
565
+ output_dtype: torch.dtype | None = None,
566
+ ) -> torch.Tensor:
567
+ """Batched MXFP4 (W4A8) matmul with fused activation quant.
568
+
569
+ A: (S, K) raw activations, bf16/fp16/fp32 (quantized inline to FP8)
570
+ B: (E, N, K // 2) packed FP4 (E2M1) expert weights, two codes per int8
571
+ Bs: (E, N, K // 32) UE8M0 weight scales
572
+ expert_ids: (S,) which expert each routed row uses
573
+
574
+ BLOCK_SIZE_N and BLOCK_SIZE_K are autotuned.
575
+ """
576
+ assert A.ndim == 2 and B.ndim == 3 and Bs.ndim == 3
577
+ assert expert_ids.ndim == 1
578
+ assert B.dtype == torch.int8, f"B must be int8 (packed FP4), got {B.dtype}"
579
+ assert Bs.dtype == torch.float8_e8m0fnu, (
580
+ f"Bs must be float8_e8m0fnu, got {Bs.dtype}"
581
+ )
582
+
583
+ S, K = A.shape
584
+ E, N, K_half = B.shape
585
+ assert expert_ids.shape[0] == S
586
+ assert K == NIBBLES_PER_BYTE * K_half, (
587
+ f"K (={K}) must equal {NIBBLES_PER_BYTE} * B.shape[2] (={K_half})"
588
+ )
589
+ assert K % MX_SCALE_GROUP_K == 0, (
590
+ f"K (={K}) must be a multiple of {MX_SCALE_GROUP_K}"
591
+ )
592
+ assert Bs.shape == (E, N, K // MX_SCALE_GROUP_K), (
593
+ f"Bs shape {tuple(Bs.shape)} != ({E}, {N}, {K // MX_SCALE_GROUP_K})"
594
+ )
595
+
596
+ # Decode handles one routed row per program; BLOCK_SIZE_M > 1 would just
597
+ # duplicate the same row computation and keep one row on store.
598
+ BLOCK_SIZE_M = 1
599
+ bs_u8 = ue8m0_as_uint8(Bs)
600
+ C = A.new_empty((S, N), dtype=output_dtype)
601
+
602
+ def grid(META):
603
+ return (S, triton.cdiv(N, META["BLOCK_SIZE_N"]))
604
+
605
+ with device_context(A.device):
606
+ wrap_triton(w4a8_mx_dynamic_fp4_matmul_batched_kernel)[grid](
607
+ A,
608
+ B,
609
+ C,
610
+ bs_u8,
611
+ expert_ids,
612
+ S,
613
+ N,
614
+ K,
615
+ A.stride(0),
616
+ A.stride(1),
617
+ B.stride(0),
618
+ B.stride(2),
619
+ B.stride(1),
620
+ C.stride(0),
621
+ C.stride(1),
622
+ bs_u8.stride(0),
623
+ bs_u8.stride(2),
624
+ bs_u8.stride(1),
625
+ expert_ids.stride(0),
626
+ # Meta-parameters (BLOCK_SIZE_N, BLOCK_SIZE_K come from autotune Config)
627
+ BLOCK_SIZE_M=BLOCK_SIZE_M,
628
+ NIBBLES_PER_BYTE=NIBBLES_PER_BYTE,
629
+ SCALE_GROUP_K=MX_SCALE_GROUP_K,
630
+ NUM_EXPERTS=E,
631
+ )
632
+ return C
633
+
634
+
635
+ def w4a8_mx_dynamic_fp4_matmul_batched(
636
+ A: torch.Tensor,
637
+ B: torch.Tensor,
638
+ Bs: torch.Tensor,
639
+ expert_ids: torch.Tensor,
640
+ output_dtype: torch.dtype | None = None,
641
+ ) -> torch.Tensor:
642
+ """Batched MXFP4 (W4A8) matmul with fused activation quant. Tile
643
+ shape autotuned; FP4 scale granularity is fixed at 32."""
644
+ return ops.w4a8_mx_dynamic_fp4_matmul_batched(A, B, Bs, expert_ids, output_dtype)
645
+
646
+
647
+ @triton_op(
648
+ add_op_namespace_prefix("w8a8_mx_dynamic_fp8_matmul_batched"), mutates_args=()
649
+ )
650
+ def _w8a8_mx_dynamic_fp8_matmul_batched(
651
+ A: torch.Tensor,
652
+ B: torch.Tensor,
653
+ Bs: torch.Tensor,
654
+ expert_ids: torch.Tensor,
655
+ output_dtype: torch.dtype | None = None,
656
+ ) -> torch.Tensor:
657
+ """Batched MXFP8 (W8A8) matmul: ``C[s] = A[s] @ B[expert_ids[s]].T`` (E4M3 ×
658
+ E4M3, UE8M0 group-32 scales) with fused activation quant.
659
+
660
+ A: (S, K) raw activations, bf16/fp16/fp32 (quantized inline to E4M3)
661
+ B: (E, N, K) E4M3 expert weights (unpacked)
662
+ Bs: (E, N, K // 32) UE8M0 weight scales
663
+ expert_ids: (S,) which expert each routed row uses
664
+
665
+ BLOCK_SIZE_N and BLOCK_SIZE_K are autotuned.
666
+ """
667
+ assert A.ndim == 2 and B.ndim == 3 and Bs.ndim == 3
668
+ assert expert_ids.ndim == 1
669
+ assert B.dtype == torch.float8_e4m3fn, (
670
+ f"B must be float8_e4m3fn (E4M3 weights), got {B.dtype}"
671
+ )
672
+ assert Bs.dtype == torch.float8_e8m0fnu, (
673
+ f"Bs must be float8_e8m0fnu, got {Bs.dtype}"
674
+ )
675
+
676
+ S, K = A.shape
677
+ E, N, K_b = B.shape
678
+ assert expert_ids.shape[0] == S
679
+ assert K == K_b, f"K mismatch: A has K={K}, B has K={K_b}"
680
+ assert K % MX_SCALE_GROUP_K == 0, (
681
+ f"K (={K}) must be a multiple of {MX_SCALE_GROUP_K}"
682
+ )
683
+ assert Bs.shape == (E, N, K // MX_SCALE_GROUP_K), (
684
+ f"Bs shape {tuple(Bs.shape)} != ({E}, {N}, {K // MX_SCALE_GROUP_K})"
685
+ )
686
+
687
+ # Decode handles one routed row per program; BLOCK_SIZE_M > 1 would just
688
+ # duplicate the same row computation and keep one row on store.
689
+ BLOCK_SIZE_M = 1
690
+ bs_u8 = ue8m0_as_uint8(Bs)
691
+ C = A.new_empty((S, N), dtype=output_dtype)
692
+
693
+ def grid(META):
694
+ return (S, triton.cdiv(N, META["BLOCK_SIZE_N"]))
695
+
696
+ with device_context(A.device):
697
+ wrap_triton(w8a8_mx_dynamic_fp8_matmul_batched_kernel)[grid](
698
+ A,
699
+ B,
700
+ C,
701
+ bs_u8,
702
+ expert_ids,
703
+ S,
704
+ N,
705
+ K,
706
+ A.stride(0),
707
+ A.stride(1),
708
+ B.stride(0),
709
+ B.stride(2),
710
+ B.stride(1),
711
+ C.stride(0),
712
+ C.stride(1),
713
+ bs_u8.stride(0),
714
+ bs_u8.stride(2),
715
+ bs_u8.stride(1),
716
+ expert_ids.stride(0),
717
+ # Meta-parameters (BLOCK_SIZE_N, BLOCK_SIZE_K come from autotune Config)
718
+ BLOCK_SIZE_M=BLOCK_SIZE_M,
719
+ SCALE_GROUP_K=MX_SCALE_GROUP_K,
720
+ NUM_EXPERTS=E,
721
+ )
722
+ return C
723
+
724
+
725
+ def w8a8_mx_dynamic_fp8_matmul_batched(
726
+ A: torch.Tensor,
727
+ B: torch.Tensor,
728
+ Bs: torch.Tensor,
729
+ expert_ids: torch.Tensor,
730
+ output_dtype: torch.dtype | None = None,
731
+ ) -> torch.Tensor:
732
+ """Batched MXFP8 (W8A8) matmul with fused activation quant (E4M3 × E4M3,
733
+ UE8M0 group-32) via the MX scaled MMA. Tile shape autotuned."""
734
+ return ops.w8a8_mx_dynamic_fp8_matmul_batched(A, B, Bs, expert_ids, output_dtype)
735
+
736
+
737
+ def matmul_batched(
738
+ A: torch.Tensor,
739
+ B: torch.Tensor,
740
+ Bs: torch.Tensor,
741
+ expert_ids: torch.Tensor,
742
+ block_size: list[int] | None,
743
+ output_dtype: torch.dtype | None = None,
744
+ ) -> torch.Tensor:
745
+ """Batched quantized matmul dispatcher (W8A8 FP8 or W4A8 FP4). Routes one
746
+ routed row per program to ``B[expert_ids[s]]``.
747
+
748
+ ``output_dtype`` defaults to ``A.dtype``.
749
+
750
+ Routes by weight dtype and ``block_size``:
751
+ - ``B.dtype == int8`` (packed FP4) → ``w4a8_mx_dynamic_fp4_matmul_batched``
752
+ (``block_size`` is ignored; FP4 tile shape is autotuned).
753
+ - ``B.dtype == float8_e4m3fn`` with UE8M0 group-32 ``Bs`` (shape ``[E, N, K//32]``)
754
+ → ``w8a8_mx_dynamic_fp8_matmul_batched`` (MX scaled MMA; ``block_size`` ignored).
755
+ - ``block_size`` None or full ``[N, K]`` → ``w8a8_tensor_dynamic_fp8_matmul_batched``.
756
+ - otherwise → ``w8a8_block_dynamic_fp8_matmul_batched``.
757
+ """
758
+ if is_mxfp4(B, Bs):
759
+ return w4a8_mx_dynamic_fp4_matmul_batched(A, B, Bs, expert_ids, output_dtype)
760
+
761
+ if is_mxfp8(B, Bs):
762
+ return w8a8_mx_dynamic_fp8_matmul_batched(A, B, Bs, expert_ids, output_dtype)
763
+
764
+ if block_size is None or (
765
+ block_size[0] == B.size(1) and block_size[1] == B.size(2)
766
+ ):
767
+ return w8a8_tensor_dynamic_fp8_matmul_batched(
768
+ A, B, Bs, expert_ids, output_dtype
769
+ )
770
+
771
+ return w8a8_block_dynamic_fp8_matmul_batched(
772
+ A, B, Bs, expert_ids, block_size, output_dtype
773
+ )
build/torch-rocm/finegrained_fp8/__init__.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 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")))
build/torch-rocm/grouped.py ADDED
@@ -0,0 +1,951 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2026 The HuggingFace Inc. team. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import torch
16
+ import triton
17
+ import triton.language as tl
18
+ from torch.library import triton_op, wrap_triton
19
+
20
+ from ._ops import add_op_namespace_prefix, ops
21
+ from .utils import (
22
+ MX_SCALE_GROUP_K,
23
+ NIBBLES_PER_BYTE,
24
+ adaptive_block_size_m,
25
+ device_context,
26
+ mx_act_quant_inline,
27
+ fp8_act_quant,
28
+ fp8_act_quant_inline,
29
+ get_accelerator_autotuning_configs,
30
+ is_mxfp4,
31
+ is_mxfp8,
32
+ ue8m0_as_uint8,
33
+ decode_ue8m0_scale,
34
+ )
35
+
36
+
37
+ def grouped_tile_layout(
38
+ tokens_per_expert: torch.Tensor,
39
+ block_size_m: int,
40
+ S: int,
41
+ E: int,
42
+ ) -> tuple[torch.Tensor, int]:
43
+ """Compute the M-tile layout for the grouped kernels.
44
+
45
+ Returns ``(tile_offsets, max_m_tiles)``:
46
+ - ``tile_offsets``: int32 (E,) cumulative tile-end per expert, used by
47
+ ``_grouped_tile_setup`` to locate an M-tile's owning expert.
48
+ - ``max_m_tiles``: upper bound on total M-tiles, used as the grid axis-0
49
+ size. Real tile count <= this; surplus programs early-return inside the
50
+ kernel. Keeps the grid data-independent (cuda-graph / torch.compile safe).
51
+ """
52
+ tiles_per_expert = (tokens_per_expert + block_size_m - 1) // block_size_m
53
+ tile_offsets = torch.cumsum(tiles_per_expert, dim=0).to(torch.int32)
54
+ max_m_tiles = triton.cdiv(S, block_size_m) + E
55
+ return tile_offsets, max_m_tiles
56
+
57
+
58
+ @triton.jit
59
+ def _grouped_tile_setup(
60
+ pid_m,
61
+ pid_n,
62
+ Offsets,
63
+ TileOffsets,
64
+ stride_offs,
65
+ stride_tile,
66
+ NUM_EXPERTS: tl.constexpr,
67
+ NUM_EXPERTS_BIT_LENGTH: tl.constexpr,
68
+ BLOCK_SIZE_M: tl.constexpr,
69
+ BLOCK_SIZE_N: tl.constexpr,
70
+ BLOCK_SIZE_K: tl.constexpr,
71
+ ):
72
+ """Map a grouped M-tile to its expert and build the per-tile offset vectors —
73
+ the prologue shared by every grouped kernel.
74
+
75
+ Returns ``(expert_id, offs_global_m, row_mask, offs_bn, offs_k)``:
76
+ - ``expert_id``: int64 owning expert
77
+ - ``offs_global_m``: ``(BLOCK_SIZE_M,)`` global row indices into A
78
+ - ``row_mask``: ``(BLOCK_SIZE_M,)`` validity mask within the expert's M
79
+ - ``offs_bn``: ``(BLOCK_SIZE_N,)`` output column offsets
80
+ - ``offs_k``: ``(BLOCK_SIZE_K,)`` K range
81
+
82
+ Caller must have early-returned if ``pid_m`` exceeds total_tiles
83
+ (``TileOffsets[(NUM_EXPERTS - 1) * stride_tile]``) — the ``Offsets`` load below
84
+ is out of bounds for an out-of-range tile otherwise.
85
+ """
86
+ # Binary search: upper_bound(TileOffsets, pid_m). NUM_EXPERTS_BIT_LENGTH is
87
+ # ceil(log2(E))+1, giving one harmless extra iteration; constexpr so the
88
+ # loop unrolls.
89
+ lo = 0
90
+ hi = NUM_EXPERTS
91
+ for _ in tl.static_range(NUM_EXPERTS_BIT_LENGTH):
92
+ mid = (lo + hi) >> 1
93
+ mid_val = tl.load(TileOffsets + mid * stride_tile)
94
+ is_left = mid_val <= pid_m
95
+ lo = tl.where(is_left, mid + 1, lo)
96
+ hi = tl.where(is_left, hi, mid)
97
+ # Cast to int64 so ``expert_id * stride_be`` doesn't overflow for large E
98
+ # × large weight matrices (e.g. 255 * 9_437_184 > 2^31).
99
+ expert_id = lo.to(tl.int64)
100
+
101
+ prev_eid = tl.maximum(expert_id - 1, 0)
102
+ expert_start = tl.where(
103
+ expert_id == 0, 0, tl.load(Offsets + prev_eid * stride_offs)
104
+ )
105
+ expert_end = tl.load(Offsets + expert_id * stride_offs)
106
+ M_expert = expert_end - expert_start
107
+
108
+ expert_tile_start = tl.where(
109
+ expert_id == 0, 0, tl.load(TileOffsets + prev_eid * stride_tile)
110
+ )
111
+ local_tile = pid_m - expert_tile_start
112
+ m_off = local_tile * BLOCK_SIZE_M
113
+
114
+ offs_am = m_off + tl.arange(0, BLOCK_SIZE_M)
115
+ row_mask = offs_am < M_expert
116
+ offs_global_m = expert_start + offs_am
117
+ offs_bn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
118
+ offs_k = tl.arange(0, BLOCK_SIZE_K)
119
+ return expert_id, offs_global_m, row_mask, offs_bn, offs_k
120
+
121
+
122
+ @triton.jit
123
+ def _store_tile(
124
+ C, accumulator, offs_global_m, offs_bn, row_mask, stride_cm, stride_cn
125
+ ):
126
+ """Output epilogue shared by the grouped kernels: cast the fp32 accumulator to
127
+ ``C``'s dtype and store the tile at expert-sorted global rows ``offs_global_m`` ×
128
+ columns ``offs_bn``, masked to the expert's valid rows (``row_mask``)."""
129
+ c = accumulator.to(C.dtype.element_ty)
130
+ c_ptrs = C + stride_cm * offs_global_m[:, None] + stride_cn * offs_bn[None, :]
131
+ tl.store(c_ptrs, c, mask=row_mask[:, None])
132
+
133
+
134
+ @triton.autotune(
135
+ configs=get_accelerator_autotuning_configs(),
136
+ key=["N", "K", "BLOCK_SIZE_M"],
137
+ )
138
+ @triton.jit
139
+ def w8a8_block_dynamic_fp8_matmul_grouped_kernel(
140
+ A, # (S, K) raw BF16/FP16 activations, sorted/grouped by expert id
141
+ B, # (E, N, K) FP8 weight matrices
142
+ C, # (S, N) output
143
+ Bs, # (E, N // BLOCK_SIZE_N, K // BLOCK_SIZE_K) weight scales
144
+ Offsets, # (E,) int32 — cumulative row-end per expert
145
+ TileOffsets, # (E,) int32 — cumulative tile-end per expert
146
+ # Shape
147
+ S,
148
+ N,
149
+ K,
150
+ # Strides
151
+ stride_am,
152
+ stride_ak,
153
+ stride_be,
154
+ stride_bk,
155
+ stride_bn,
156
+ stride_cm,
157
+ stride_cn,
158
+ stride_bs_e,
159
+ stride_bs_k,
160
+ stride_bs_n,
161
+ stride_offs,
162
+ stride_tile,
163
+ # Meta-parameters
164
+ BLOCK_SIZE_M: tl.constexpr,
165
+ BLOCK_SIZE_N: tl.constexpr,
166
+ BLOCK_SIZE_K: tl.constexpr,
167
+ NUM_EXPERTS: tl.constexpr,
168
+ NUM_EXPERTS_BIT_LENGTH: tl.constexpr,
169
+ ):
170
+ """Block-scale grouped FP8 expert matmul kernel.
171
+
172
+ Tokens are assumed sorted by expert. The kernel maps each M-tile to its
173
+ owning expert via ``TileOffsets`` and applies fused activation quantization.
174
+ """
175
+ pid_m = tl.program_id(axis=0)
176
+ pid_n = tl.program_id(axis=1)
177
+
178
+ total_tiles = tl.load(TileOffsets + (NUM_EXPERTS - 1) * stride_tile)
179
+ if pid_m >= total_tiles:
180
+ return
181
+
182
+ expert_id, offs_global_m, row_mask, offs_bn, offs_k = _grouped_tile_setup(
183
+ pid_m,
184
+ pid_n,
185
+ Offsets,
186
+ TileOffsets,
187
+ stride_offs,
188
+ stride_tile,
189
+ NUM_EXPERTS,
190
+ NUM_EXPERTS_BIT_LENGTH,
191
+ BLOCK_SIZE_M,
192
+ BLOCK_SIZE_N,
193
+ BLOCK_SIZE_K,
194
+ )
195
+
196
+ a_ptrs = A + offs_global_m[:, None] * stride_am + offs_k[None, :] * stride_ak
197
+ b_ptrs = (
198
+ B
199
+ + expert_id * stride_be
200
+ + offs_k[:, None] * stride_bk
201
+ + offs_bn[None, :] * stride_bn
202
+ )
203
+ bs_ptrs = Bs + expert_id * stride_bs_e + pid_n * stride_bs_n
204
+
205
+ accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
206
+ for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
207
+ a_raw = tl.load(a_ptrs, mask=row_mask[:, None], other=0.0).to(tl.float32)
208
+ a, a_s = fp8_act_quant_inline(a_raw)
209
+ b = tl.load(b_ptrs)
210
+ b_s = decode_ue8m0_scale(tl.load(bs_ptrs))
211
+ accumulator += tl.dot(a, b) * a_s[:, None] * b_s[None, :]
212
+ a_ptrs += BLOCK_SIZE_K * stride_ak
213
+ b_ptrs += BLOCK_SIZE_K * stride_bk
214
+ bs_ptrs += stride_bs_k
215
+
216
+ _store_tile(
217
+ C, accumulator, offs_global_m, offs_bn, row_mask, stride_cm, stride_cn
218
+ )
219
+
220
+
221
+ @triton.autotune(
222
+ configs=get_accelerator_autotuning_configs(with_block_sizes=True),
223
+ key=["N", "K", "BLOCK_SIZE_M"],
224
+ )
225
+ @triton.jit
226
+ def w8a8_tensor_dynamic_fp8_matmul_grouped_kernel(
227
+ A, # (S, K) pre-quantized FP8 activations, sorted/grouped by expert id
228
+ B, # (E, N, K) FP8 weight matrices
229
+ C, # (S, N) output
230
+ As, # (S,) per-token activation scales
231
+ Bs, # (E, 1, 1) per-tensor weight scales
232
+ Offsets, # (E,) int32 — cumulative row-end per expert
233
+ TileOffsets, # (E,) int32 — cumulative tile-end per expert
234
+ # Shape
235
+ S,
236
+ N,
237
+ K,
238
+ # Strides
239
+ stride_am,
240
+ stride_ak,
241
+ stride_be,
242
+ stride_bk,
243
+ stride_bn,
244
+ stride_cm,
245
+ stride_cn,
246
+ stride_as_m,
247
+ stride_bs_e,
248
+ stride_offs,
249
+ stride_tile,
250
+ # Meta-parameters
251
+ BLOCK_SIZE_M: tl.constexpr,
252
+ BLOCK_SIZE_N: tl.constexpr,
253
+ BLOCK_SIZE_K: tl.constexpr,
254
+ NUM_EXPERTS: tl.constexpr,
255
+ NUM_EXPERTS_BIT_LENGTH: tl.constexpr,
256
+ ):
257
+ """Tensor-scale grouped FP8 expert matmul kernel.
258
+
259
+ Uses grouped expert scheduling with pre-quantized activations plus
260
+ per-token activation scales and per-expert tensor weight scales.
261
+ """
262
+ pid_m = tl.program_id(axis=0)
263
+ pid_n = tl.program_id(axis=1)
264
+
265
+ total_tiles = tl.load(TileOffsets + (NUM_EXPERTS - 1) * stride_tile)
266
+ if pid_m >= total_tiles:
267
+ return
268
+
269
+ expert_id, offs_global_m, row_mask, offs_bn, offs_k = _grouped_tile_setup(
270
+ pid_m,
271
+ pid_n,
272
+ Offsets,
273
+ TileOffsets,
274
+ stride_offs,
275
+ stride_tile,
276
+ NUM_EXPERTS,
277
+ NUM_EXPERTS_BIT_LENGTH,
278
+ BLOCK_SIZE_M,
279
+ BLOCK_SIZE_N,
280
+ BLOCK_SIZE_K,
281
+ )
282
+
283
+ a_ptrs = A + offs_global_m[:, None] * stride_am + offs_k[None, :] * stride_ak
284
+ b_ptrs = (
285
+ B
286
+ + expert_id * stride_be
287
+ + offs_k[:, None] * stride_bk
288
+ + offs_bn[None, :] * stride_bn
289
+ )
290
+
291
+ a_s = tl.load(As + offs_global_m * stride_as_m, mask=row_mask, other=0.0)
292
+ b_s = tl.load(Bs + expert_id * stride_bs_e)
293
+
294
+ accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
295
+ for _ in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
296
+ a = tl.load(a_ptrs, mask=row_mask[:, None], other=0.0)
297
+ b = tl.load(b_ptrs)
298
+ accumulator += tl.dot(a, b)
299
+ a_ptrs += BLOCK_SIZE_K * stride_ak
300
+ b_ptrs += BLOCK_SIZE_K * stride_bk
301
+ accumulator = accumulator * a_s[:, None] * b_s
302
+
303
+ _store_tile(
304
+ C, accumulator, offs_global_m, offs_bn, row_mask, stride_cm, stride_cn
305
+ )
306
+
307
+
308
+ @triton.autotune(
309
+ configs=get_accelerator_autotuning_configs(with_block_sizes=True),
310
+ key=["N", "K", "BLOCK_SIZE_M"],
311
+ )
312
+ @triton.jit
313
+ def w4a8_mx_dynamic_fp4_matmul_grouped_kernel(
314
+ A, # (S, K) raw BF16/FP16 activations, sorted by expert id
315
+ B, # (E, N, K // 2) packed FP4 (E2M1) expert weights as int8
316
+ C, # (S, N) output
317
+ Bs, # (E, N, K // SCALE_GROUP_K) UE8M0 weight scales
318
+ Offsets, # (E,) int32 — cumulative row-end per expert
319
+ TileOffsets, # (E,) int32 — cumulative tile-end per expert
320
+ # Shape
321
+ S,
322
+ N,
323
+ K,
324
+ # Strides
325
+ stride_am,
326
+ stride_ak,
327
+ stride_be,
328
+ stride_bk,
329
+ stride_bn,
330
+ stride_cm,
331
+ stride_cn,
332
+ stride_bs_e,
333
+ stride_bs_k,
334
+ stride_bs_n,
335
+ stride_offs,
336
+ stride_tile,
337
+ # Meta-parameters
338
+ BLOCK_SIZE_M: tl.constexpr,
339
+ BLOCK_SIZE_N: tl.constexpr,
340
+ BLOCK_SIZE_K: tl.constexpr,
341
+ NUM_EXPERTS: tl.constexpr,
342
+ NUM_EXPERTS_BIT_LENGTH: tl.constexpr,
343
+ NIBBLES_PER_BYTE: tl.constexpr,
344
+ SCALE_GROUP_K: tl.constexpr,
345
+ ):
346
+ """Grouped MXFP4 (W4A8) expert matmul with fused activation quant.
347
+
348
+ Tokens are assumed sorted by expert. The kernel maps each M-tile to its
349
+ owning expert via ``TileOffsets``, quantizes ``A`` to FP8 per K-group inline
350
+ (UE8M0 scale), then ``tl.dot_scaled`` against packed FP4 weights.
351
+ """
352
+ pid_m = tl.program_id(axis=0)
353
+ pid_n = tl.program_id(axis=1)
354
+
355
+ total_tiles = tl.load(TileOffsets + (NUM_EXPERTS - 1) * stride_tile)
356
+ if pid_m >= total_tiles:
357
+ return
358
+
359
+ expert_id, offs_global_m, row_mask, offs_bn, offs_k = _grouped_tile_setup(
360
+ pid_m,
361
+ pid_n,
362
+ Offsets,
363
+ TileOffsets,
364
+ stride_offs,
365
+ stride_tile,
366
+ NUM_EXPERTS,
367
+ NUM_EXPERTS_BIT_LENGTH,
368
+ BLOCK_SIZE_M,
369
+ BLOCK_SIZE_N,
370
+ BLOCK_SIZE_K,
371
+ )
372
+ offs_k_byte = tl.arange(0, BLOCK_SIZE_K // NIBBLES_PER_BYTE)
373
+ offs_sf = tl.arange(0, BLOCK_SIZE_K // SCALE_GROUP_K)
374
+
375
+ a_ptrs = A + offs_global_m[:, None] * stride_am + offs_k[None, :] * stride_ak
376
+ b_ptrs = (
377
+ B
378
+ + expert_id * stride_be
379
+ + offs_k_byte[:, None] * stride_bk
380
+ + offs_bn[None, :] * stride_bn
381
+ )
382
+ bs_ptrs = (
383
+ Bs
384
+ + expert_id * stride_bs_e
385
+ + offs_bn[:, None] * stride_bs_n
386
+ + offs_sf[None, :] * stride_bs_k
387
+ )
388
+
389
+ accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
390
+ for _ in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
391
+ a_raw = tl.load(a_ptrs, mask=row_mask[:, None], other=0.0).to(tl.float32)
392
+ a, a_scale = mx_act_quant_inline(
393
+ a_raw, BLOCK_SIZE_M, BLOCK_SIZE_K, SCALE_GROUP_K
394
+ )
395
+ b = tl.load(b_ptrs).to(tl.uint8)
396
+ b_s = tl.load(bs_ptrs).to(tl.uint8)
397
+ accumulator += tl.dot_scaled(a, a_scale, "e4m3", b, b_s, "e2m1")
398
+ a_ptrs += BLOCK_SIZE_K * stride_ak
399
+ b_ptrs += (BLOCK_SIZE_K // NIBBLES_PER_BYTE) * stride_bk
400
+ bs_ptrs += (BLOCK_SIZE_K // SCALE_GROUP_K) * stride_bs_k
401
+
402
+ _store_tile(
403
+ C, accumulator, offs_global_m, offs_bn, row_mask, stride_cm, stride_cn
404
+ )
405
+
406
+
407
+ @triton.autotune(
408
+ configs=get_accelerator_autotuning_configs(with_block_sizes=True),
409
+ key=["N", "K", "BLOCK_SIZE_M"],
410
+ )
411
+ @triton.jit
412
+ def w8a8_mx_dynamic_fp8_matmul_grouped_kernel(
413
+ A, # (S, K) raw BF16/FP16 activations, sorted by expert id
414
+ B, # (E, N, K) E4M3 expert weights (unpacked)
415
+ C, # (S, N) output
416
+ Bs, # (E, N, K // SCALE_GROUP_K) UE8M0 weight scales
417
+ Offsets, # (E,) int32 — cumulative row-end per expert
418
+ TileOffsets, # (E,) int32 — cumulative tile-end per expert
419
+ # Shape
420
+ S,
421
+ N,
422
+ K,
423
+ # Strides
424
+ stride_am,
425
+ stride_ak,
426
+ stride_be,
427
+ stride_bk,
428
+ stride_bn,
429
+ stride_cm,
430
+ stride_cn,
431
+ stride_bs_e,
432
+ stride_bs_k,
433
+ stride_bs_n,
434
+ stride_offs,
435
+ stride_tile,
436
+ # Meta-parameters
437
+ BLOCK_SIZE_M: tl.constexpr,
438
+ BLOCK_SIZE_N: tl.constexpr,
439
+ BLOCK_SIZE_K: tl.constexpr,
440
+ NUM_EXPERTS: tl.constexpr,
441
+ NUM_EXPERTS_BIT_LENGTH: tl.constexpr,
442
+ SCALE_GROUP_K: tl.constexpr,
443
+ ):
444
+ """Grouped MXFP8 (W8A8) expert matmul with fused activation quant.
445
+
446
+ Tokens are assumed sorted by expert. The kernel maps each M-tile to its
447
+ owning expert via ``TileOffsets``, quantizes ``A`` to E4M3 per K-group inline
448
+ (UE8M0 scale), then ``tl.dot_scaled`` against E4M3 weights. Mirrors the
449
+ grouped MXFP4 kernel but with unpacked weights and ``"e4m3"`` on both operands.
450
+ """
451
+ pid_m = tl.program_id(axis=0)
452
+ pid_n = tl.program_id(axis=1)
453
+
454
+ total_tiles = tl.load(TileOffsets + (NUM_EXPERTS - 1) * stride_tile)
455
+ if pid_m >= total_tiles:
456
+ return
457
+
458
+ expert_id, offs_global_m, row_mask, offs_bn, offs_k = _grouped_tile_setup(
459
+ pid_m,
460
+ pid_n,
461
+ Offsets,
462
+ TileOffsets,
463
+ stride_offs,
464
+ stride_tile,
465
+ NUM_EXPERTS,
466
+ NUM_EXPERTS_BIT_LENGTH,
467
+ BLOCK_SIZE_M,
468
+ BLOCK_SIZE_N,
469
+ BLOCK_SIZE_K,
470
+ )
471
+ offs_sf = tl.arange(0, BLOCK_SIZE_K // SCALE_GROUP_K)
472
+
473
+ a_ptrs = A + offs_global_m[:, None] * stride_am + offs_k[None, :] * stride_ak
474
+ b_ptrs = (
475
+ B
476
+ + expert_id * stride_be
477
+ + offs_k[:, None] * stride_bk
478
+ + offs_bn[None, :] * stride_bn
479
+ )
480
+ bs_ptrs = (
481
+ Bs
482
+ + expert_id * stride_bs_e
483
+ + offs_bn[:, None] * stride_bs_n
484
+ + offs_sf[None, :] * stride_bs_k
485
+ )
486
+
487
+ accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
488
+ for _ in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
489
+ a_raw = tl.load(a_ptrs, mask=row_mask[:, None], other=0.0).to(tl.float32)
490
+ a, a_scale = mx_act_quant_inline(
491
+ a_raw, BLOCK_SIZE_M, BLOCK_SIZE_K, SCALE_GROUP_K
492
+ )
493
+ b = tl.load(b_ptrs)
494
+ b_s = tl.load(bs_ptrs).to(tl.uint8)
495
+ accumulator += tl.dot_scaled(a, a_scale, "e4m3", b, b_s, "e4m3")
496
+ a_ptrs += BLOCK_SIZE_K * stride_ak
497
+ b_ptrs += BLOCK_SIZE_K * stride_bk
498
+ bs_ptrs += (BLOCK_SIZE_K // SCALE_GROUP_K) * stride_bs_k
499
+
500
+ _store_tile(
501
+ C, accumulator, offs_global_m, offs_bn, row_mask, stride_cm, stride_cn
502
+ )
503
+
504
+
505
+ @triton_op(
506
+ add_op_namespace_prefix("w8a8_block_dynamic_fp8_matmul_grouped"), mutates_args=()
507
+ )
508
+ def _w8a8_block_dynamic_fp8_matmul_grouped(
509
+ A: torch.Tensor,
510
+ B: torch.Tensor,
511
+ Bs: torch.Tensor,
512
+ offsets: torch.Tensor,
513
+ tokens_per_expert: torch.Tensor,
514
+ block_size: list[int],
515
+ output_dtype: torch.dtype | None = None,
516
+ ) -> torch.Tensor:
517
+ """Block-scale grouped FP8 matmul: C = A @ B.T per expert, with fused act quant.
518
+
519
+ A: (S, K) raw bf16/fp16 activations, sorted by expert
520
+ B: (E, N, K) FP8 expert weights
521
+ Bs: (E, N // block_n, K // block_k) per-block weight scales
522
+ """
523
+ assert A.ndim == 2, f"A must be 2D (S, K), got ndim={A.ndim}"
524
+ assert A.is_contiguous(), "A must be contiguous"
525
+ assert B.ndim == 3, f"B must be 3D (E, N, K), got ndim={B.ndim}"
526
+ assert B.is_contiguous(), "B must be contiguous"
527
+ assert A.shape[1] == B.shape[2], (
528
+ f"K mismatch: A has K={A.shape[1]}, B has K={B.shape[2]}"
529
+ )
530
+
531
+ S, K = A.shape
532
+ E, N, _ = B.shape
533
+
534
+ assert len(block_size) == 2, (
535
+ f"block_size must be [block_n, block_k], got {block_size}"
536
+ )
537
+ block_n, block_k = block_size[0], block_size[1]
538
+ # MoE expert dimensions must be block-aligned; non-aligned N/K is not supported.
539
+ assert N % block_n == 0, f"N ({N}) must be divisible by block_n ({block_n})"
540
+ assert K % block_k == 0, f"K ({K}) must be divisible by block_k ({block_k})"
541
+ assert Bs.ndim == 3, (
542
+ f"Bs must be 3D (E, N//block_n, K//block_k), got ndim={Bs.ndim}"
543
+ )
544
+ assert Bs.shape == (E, N // block_n, K // block_k), (
545
+ f"Bs shape {tuple(Bs.shape)} != expected ({E}, {N // block_n}, {K // block_k})"
546
+ )
547
+
548
+ Bs = ue8m0_as_uint8(Bs)
549
+ C = A.new_empty(S, N, dtype=output_dtype)
550
+ BLOCK_SIZE_M = adaptive_block_size_m((S + E - 1) // E)
551
+ tile_offsets, max_m_tiles = grouped_tile_layout(
552
+ tokens_per_expert, BLOCK_SIZE_M, S, E
553
+ )
554
+ grid = (max_m_tiles, triton.cdiv(N, block_n))
555
+
556
+ with device_context(A.device):
557
+ wrap_triton(w8a8_block_dynamic_fp8_matmul_grouped_kernel)[grid](
558
+ A,
559
+ B,
560
+ C,
561
+ Bs,
562
+ offsets,
563
+ tile_offsets,
564
+ S,
565
+ N,
566
+ K,
567
+ A.stride(0),
568
+ A.stride(1),
569
+ B.stride(0),
570
+ B.stride(2),
571
+ B.stride(1),
572
+ C.stride(0),
573
+ C.stride(1),
574
+ Bs.stride(0),
575
+ Bs.stride(2),
576
+ Bs.stride(1),
577
+ offsets.stride(0),
578
+ tile_offsets.stride(0),
579
+ # Meta-parameters
580
+ NUM_EXPERTS=E,
581
+ BLOCK_SIZE_N=block_n,
582
+ BLOCK_SIZE_K=block_k,
583
+ BLOCK_SIZE_M=BLOCK_SIZE_M,
584
+ NUM_EXPERTS_BIT_LENGTH=E.bit_length(),
585
+ )
586
+
587
+ return C
588
+
589
+
590
+ @triton_op(
591
+ add_op_namespace_prefix("w8a8_tensor_dynamic_fp8_matmul_grouped"), mutates_args=()
592
+ )
593
+ def _w8a8_tensor_dynamic_fp8_matmul_grouped(
594
+ A: torch.Tensor,
595
+ B: torch.Tensor,
596
+ Bs: torch.Tensor,
597
+ offsets: torch.Tensor,
598
+ tokens_per_expert: torch.Tensor,
599
+ output_dtype: torch.dtype | None = None,
600
+ ) -> torch.Tensor:
601
+ """Tensor-scale grouped FP8 matmul: C = A @ B.T per expert, with fused act quant.
602
+
603
+ A: (S, K) raw bf16/fp16 activations, sorted by expert
604
+ B: (E, N, K) FP8 expert weights
605
+ Bs: (E,) or (E, 1, 1) per-expert weight scales
606
+ """
607
+ assert A.ndim == 2, f"A must be 2D (S, K), got ndim={A.ndim}"
608
+ assert A.is_contiguous(), "A must be contiguous"
609
+ assert B.ndim == 3, f"B must be 3D (E, N, K), got ndim={B.ndim}"
610
+ assert B.is_contiguous(), "B must be contiguous"
611
+ assert A.shape[1] == B.shape[2], (
612
+ f"K mismatch: A has K={A.shape[1]}, B has K={B.shape[2]}"
613
+ )
614
+
615
+ S, K = A.shape
616
+ E, N, _ = B.shape
617
+
618
+ # Normalize Bs to (E, 1, 1)
619
+ if Bs.ndim == 1:
620
+ assert Bs.shape[0] == E, f"Bs shape {tuple(Bs.shape)} != expected ({E},)"
621
+ Bs = Bs.reshape(E, 1, 1)
622
+ else:
623
+ assert Bs.shape == (E, 1, 1), (
624
+ f"Bs shape {tuple(Bs.shape)} != expected ({E}, 1, 1)"
625
+ )
626
+
627
+ qA, As = fp8_act_quant(A, K)
628
+ C = A.new_empty(S, N, dtype=output_dtype)
629
+ BLOCK_SIZE_M = adaptive_block_size_m((S + E - 1) // E)
630
+ tile_offsets, max_m_tiles = grouped_tile_layout(
631
+ tokens_per_expert, BLOCK_SIZE_M, S, E
632
+ )
633
+
634
+ def grid(META):
635
+ return (max_m_tiles, triton.cdiv(N, META["BLOCK_SIZE_N"]))
636
+
637
+ with device_context(A.device):
638
+ wrap_triton(w8a8_tensor_dynamic_fp8_matmul_grouped_kernel)[grid](
639
+ qA,
640
+ B,
641
+ C,
642
+ As,
643
+ Bs,
644
+ offsets,
645
+ tile_offsets,
646
+ S,
647
+ N,
648
+ K,
649
+ qA.stride(0),
650
+ qA.stride(1),
651
+ B.stride(0),
652
+ B.stride(2),
653
+ B.stride(1),
654
+ C.stride(0),
655
+ C.stride(1),
656
+ As.stride(0),
657
+ Bs.stride(0),
658
+ offsets.stride(0),
659
+ tile_offsets.stride(0),
660
+ # Meta-parameters (BLOCK_SIZE_N, BLOCK_SIZE_K come from autotune Config)
661
+ NUM_EXPERTS=E,
662
+ BLOCK_SIZE_M=BLOCK_SIZE_M,
663
+ NUM_EXPERTS_BIT_LENGTH=E.bit_length(),
664
+ )
665
+
666
+ return C
667
+
668
+
669
+ def w8a8_block_dynamic_fp8_matmul_grouped(
670
+ A: torch.Tensor,
671
+ B: torch.Tensor,
672
+ Bs: torch.Tensor,
673
+ offsets: torch.Tensor,
674
+ tokens_per_expert: torch.Tensor,
675
+ block_size: list[int],
676
+ output_dtype: torch.dtype | None = None,
677
+ ) -> torch.Tensor:
678
+ """Block-scale grouped FP8 matmul with fused activation quantization.
679
+
680
+ A: (S, K) raw activations sorted by expert, bf16/fp16/fp32
681
+ B: (E, N, K) FP8 expert weights
682
+ Bs: (E, N // block_n, K // block_k) per-block weight scales
683
+ output_dtype: defaults to ``A.dtype``
684
+ """
685
+ return ops.w8a8_block_dynamic_fp8_matmul_grouped(
686
+ A, B, Bs, offsets, tokens_per_expert, block_size, output_dtype
687
+ )
688
+
689
+
690
+ def w8a8_tensor_dynamic_fp8_matmul_grouped(
691
+ A: torch.Tensor,
692
+ B: torch.Tensor,
693
+ Bs: torch.Tensor,
694
+ offsets: torch.Tensor,
695
+ tokens_per_expert: torch.Tensor,
696
+ output_dtype: torch.dtype | None = None,
697
+ ) -> torch.Tensor:
698
+ """Tensor-scale grouped FP8 matmul with fused activation quantization.
699
+
700
+ A: (S, K) raw activations sorted by expert, bf16/fp16/fp32
701
+ B: (E, N, K) FP8 expert weights
702
+ Bs: (E,) or (E, 1, 1) per-expert weight scales
703
+ output_dtype: defaults to ``A.dtype``
704
+ """
705
+ return ops.w8a8_tensor_dynamic_fp8_matmul_grouped(
706
+ A, B, Bs, offsets, tokens_per_expert, output_dtype
707
+ )
708
+
709
+
710
+ @triton_op(
711
+ add_op_namespace_prefix("w4a8_mx_dynamic_fp4_matmul_grouped"), mutates_args=()
712
+ )
713
+ def _w4a8_mx_dynamic_fp4_matmul_grouped(
714
+ A: torch.Tensor,
715
+ B: torch.Tensor,
716
+ Bs: torch.Tensor,
717
+ offsets: torch.Tensor,
718
+ tokens_per_expert: torch.Tensor,
719
+ output_dtype: torch.dtype | None = None,
720
+ ) -> torch.Tensor:
721
+ """Grouped MXFP4 (W4A8) matmul with fused activation quant.
722
+
723
+ A: (S, K) raw activations, bf16/fp16/fp32, expert-sorted (quantized inline)
724
+ B: (E, N, K // 2) packed FP4 (E2M1) expert weights, two codes per int8
725
+ Bs: (E, N, K // 32) UE8M0 weight scales
726
+ offsets: (E,) — exclusive prefix of expert token counts (cumsum)
727
+ tokens_per_expert: (E,) — per-expert row count
728
+
729
+ BLOCK_SIZE_N and BLOCK_SIZE_K are autotuned.
730
+ """
731
+ assert A.ndim == 2 and B.ndim == 3 and Bs.ndim == 3
732
+ assert offsets.ndim == 1 and tokens_per_expert.ndim == 1
733
+ assert B.dtype == torch.int8, f"B must be int8 (packed FP4), got {B.dtype}"
734
+ assert Bs.dtype == torch.float8_e8m0fnu, (
735
+ f"Bs must be float8_e8m0fnu, got {Bs.dtype}"
736
+ )
737
+
738
+ S, K = A.shape
739
+ E, N, K_half = B.shape
740
+ assert offsets.shape[0] == E and tokens_per_expert.shape[0] == E
741
+ assert K == NIBBLES_PER_BYTE * K_half, (
742
+ f"K (={K}) must equal {NIBBLES_PER_BYTE} * B.shape[2] (={K_half})"
743
+ )
744
+ assert K % MX_SCALE_GROUP_K == 0, (
745
+ f"K (={K}) must be a multiple of {MX_SCALE_GROUP_K}"
746
+ )
747
+ assert Bs.shape == (E, N, K // MX_SCALE_GROUP_K), (
748
+ f"Bs shape {tuple(Bs.shape)} != ({E}, {N}, {K // MX_SCALE_GROUP_K})"
749
+ )
750
+
751
+ bs_u8 = ue8m0_as_uint8(Bs)
752
+ C = A.new_empty((S, N), dtype=output_dtype)
753
+ BLOCK_SIZE_M = adaptive_block_size_m((S + E - 1) // E)
754
+ tile_offsets, max_m_tiles = grouped_tile_layout(
755
+ tokens_per_expert, BLOCK_SIZE_M, S, E
756
+ )
757
+
758
+ def grid(META):
759
+ return (max_m_tiles, triton.cdiv(N, META["BLOCK_SIZE_N"]))
760
+
761
+ with device_context(A.device):
762
+ wrap_triton(w4a8_mx_dynamic_fp4_matmul_grouped_kernel)[grid](
763
+ A,
764
+ B,
765
+ C,
766
+ bs_u8,
767
+ offsets,
768
+ tile_offsets,
769
+ S,
770
+ N,
771
+ K,
772
+ A.stride(0),
773
+ A.stride(1),
774
+ B.stride(0),
775
+ B.stride(2),
776
+ B.stride(1),
777
+ C.stride(0),
778
+ C.stride(1),
779
+ bs_u8.stride(0),
780
+ bs_u8.stride(2),
781
+ bs_u8.stride(1),
782
+ offsets.stride(0),
783
+ tile_offsets.stride(0),
784
+ # Meta-parameters (BLOCK_SIZE_N, BLOCK_SIZE_K come from autotune Config)
785
+ NUM_EXPERTS=E,
786
+ BLOCK_SIZE_M=BLOCK_SIZE_M,
787
+ NUM_EXPERTS_BIT_LENGTH=E.bit_length(),
788
+ NIBBLES_PER_BYTE=NIBBLES_PER_BYTE,
789
+ SCALE_GROUP_K=MX_SCALE_GROUP_K,
790
+ )
791
+ return C
792
+
793
+
794
+ def w4a8_mx_dynamic_fp4_matmul_grouped(
795
+ A: torch.Tensor,
796
+ B: torch.Tensor,
797
+ Bs: torch.Tensor,
798
+ offsets: torch.Tensor,
799
+ tokens_per_expert: torch.Tensor,
800
+ output_dtype: torch.dtype | None = None,
801
+ ) -> torch.Tensor:
802
+ """Grouped MXFP4 (W4A8) matmul with fused activation quant. Per-expert
803
+ ``C[s] = A[s] @ B[e].T`` over contiguous, expert-sorted rows. Tile shape
804
+ autotuned; FP4 scale granularity is fixed at 32."""
805
+ return ops.w4a8_mx_dynamic_fp4_matmul_grouped(
806
+ A, B, Bs, offsets, tokens_per_expert, output_dtype
807
+ )
808
+
809
+
810
+ @triton_op(
811
+ add_op_namespace_prefix("w8a8_mx_dynamic_fp8_matmul_grouped"), mutates_args=()
812
+ )
813
+ def _w8a8_mx_dynamic_fp8_matmul_grouped(
814
+ A: torch.Tensor,
815
+ B: torch.Tensor,
816
+ Bs: torch.Tensor,
817
+ offsets: torch.Tensor,
818
+ tokens_per_expert: torch.Tensor,
819
+ output_dtype: torch.dtype | None = None,
820
+ ) -> torch.Tensor:
821
+ """Grouped MXFP8 (W8A8) matmul: ``C[s] = A[s] @ B[e].T`` (E4M3 × E4M3, UE8M0
822
+ group-32 scales) with fused activation quant.
823
+
824
+ A: (S, K) raw activations, bf16/fp16/fp32, expert-sorted (quantized inline)
825
+ B: (E, N, K) E4M3 expert weights (unpacked)
826
+ Bs: (E, N, K // 32) UE8M0 weight scales
827
+ offsets: (E,) — exclusive prefix of expert token counts (cumsum)
828
+ tokens_per_expert: (E,) — per-expert row count
829
+
830
+ BLOCK_SIZE_N and BLOCK_SIZE_K are autotuned; MX scale granularity is fixed at 32.
831
+ """
832
+ assert A.ndim == 2 and B.ndim == 3 and Bs.ndim == 3
833
+ assert offsets.ndim == 1 and tokens_per_expert.ndim == 1
834
+ assert B.dtype == torch.float8_e4m3fn, (
835
+ f"B must be float8_e4m3fn (E4M3 weights), got {B.dtype}"
836
+ )
837
+ assert Bs.dtype == torch.float8_e8m0fnu, (
838
+ f"Bs must be float8_e8m0fnu, got {Bs.dtype}"
839
+ )
840
+
841
+ S, K = A.shape
842
+ E, N, K_b = B.shape
843
+ assert offsets.shape[0] == E and tokens_per_expert.shape[0] == E
844
+ assert K == K_b, f"K mismatch: A has K={K}, B has K={K_b}"
845
+ assert K % MX_SCALE_GROUP_K == 0, (
846
+ f"K (={K}) must be a multiple of {MX_SCALE_GROUP_K}"
847
+ )
848
+ assert Bs.shape == (E, N, K // MX_SCALE_GROUP_K), (
849
+ f"Bs shape {tuple(Bs.shape)} != ({E}, {N}, {K // MX_SCALE_GROUP_K})"
850
+ )
851
+
852
+ bs_u8 = ue8m0_as_uint8(Bs)
853
+ C = A.new_empty((S, N), dtype=output_dtype)
854
+ BLOCK_SIZE_M = adaptive_block_size_m((S + E - 1) // E)
855
+ tile_offsets, max_m_tiles = grouped_tile_layout(
856
+ tokens_per_expert, BLOCK_SIZE_M, S, E
857
+ )
858
+
859
+ def grid(META):
860
+ return (max_m_tiles, triton.cdiv(N, META["BLOCK_SIZE_N"]))
861
+
862
+ with device_context(A.device):
863
+ wrap_triton(w8a8_mx_dynamic_fp8_matmul_grouped_kernel)[grid](
864
+ A,
865
+ B,
866
+ C,
867
+ bs_u8,
868
+ offsets,
869
+ tile_offsets,
870
+ S,
871
+ N,
872
+ K,
873
+ A.stride(0),
874
+ A.stride(1),
875
+ B.stride(0),
876
+ B.stride(2),
877
+ B.stride(1),
878
+ C.stride(0),
879
+ C.stride(1),
880
+ bs_u8.stride(0),
881
+ bs_u8.stride(2),
882
+ bs_u8.stride(1),
883
+ offsets.stride(0),
884
+ tile_offsets.stride(0),
885
+ # Meta-parameters (BLOCK_SIZE_N, BLOCK_SIZE_K come from autotune Config)
886
+ NUM_EXPERTS=E,
887
+ BLOCK_SIZE_M=BLOCK_SIZE_M,
888
+ NUM_EXPERTS_BIT_LENGTH=E.bit_length(),
889
+ SCALE_GROUP_K=MX_SCALE_GROUP_K,
890
+ )
891
+ return C
892
+
893
+
894
+ def w8a8_mx_dynamic_fp8_matmul_grouped(
895
+ A: torch.Tensor,
896
+ B: torch.Tensor,
897
+ Bs: torch.Tensor,
898
+ offsets: torch.Tensor,
899
+ tokens_per_expert: torch.Tensor,
900
+ output_dtype: torch.dtype | None = None,
901
+ ) -> torch.Tensor:
902
+ """Grouped MXFP8 (W8A8) matmul with fused activation quant. Per-expert
903
+ ``C[s] = A[s] @ B[e].T`` over contiguous, expert-sorted rows via the MX
904
+ scaled MMA (E4M3 × E4M3, UE8M0 group-32). Tile shape autotuned."""
905
+ return ops.w8a8_mx_dynamic_fp8_matmul_grouped(
906
+ A, B, Bs, offsets, tokens_per_expert, output_dtype
907
+ )
908
+
909
+
910
+ def matmul_grouped(
911
+ A: torch.Tensor,
912
+ B: torch.Tensor,
913
+ Bs: torch.Tensor,
914
+ offsets: torch.Tensor,
915
+ tokens_per_expert: torch.Tensor,
916
+ block_size: list[int] | None,
917
+ output_dtype: torch.dtype | None = None,
918
+ ) -> torch.Tensor:
919
+ """Grouped quantized matmul dispatcher (W8A8 FP8 or W4A8 FP4). Tokens must
920
+ be sorted by expert; M-tiles are mapped to experts via ``offsets``.
921
+
922
+ ``output_dtype`` defaults to ``A.dtype``.
923
+
924
+ Routes by weight dtype and ``block_size``:
925
+ - ``B.dtype == int8`` (packed FP4) → ``w4a8_mx_dynamic_fp4_matmul_grouped``
926
+ (``block_size`` is ignored; FP4 tile shape is autotuned).
927
+ - ``B.dtype == float8_e4m3fn`` with UE8M0 group-32 ``Bs`` (shape ``[E, N, K//32]``)
928
+ → ``w8a8_mx_dynamic_fp8_matmul_grouped`` (MX scaled MMA; ``block_size`` ignored).
929
+ - ``block_size`` None or full ``[N, K]`` → ``w8a8_tensor_dynamic_fp8_matmul_grouped``.
930
+ - otherwise → ``w8a8_block_dynamic_fp8_matmul_grouped``.
931
+ """
932
+ if is_mxfp4(B, Bs):
933
+ return w4a8_mx_dynamic_fp4_matmul_grouped(
934
+ A, B, Bs, offsets, tokens_per_expert, output_dtype
935
+ )
936
+
937
+ if is_mxfp8(B, Bs):
938
+ return w8a8_mx_dynamic_fp8_matmul_grouped(
939
+ A, B, Bs, offsets, tokens_per_expert, output_dtype
940
+ )
941
+
942
+ if block_size is None or (
943
+ block_size[0] == B.size(1) and block_size[1] == B.size(2)
944
+ ):
945
+ return w8a8_tensor_dynamic_fp8_matmul_grouped(
946
+ A, B, Bs, offsets, tokens_per_expert, output_dtype
947
+ )
948
+
949
+ return w8a8_block_dynamic_fp8_matmul_grouped(
950
+ A, B, Bs, offsets, tokens_per_expert, block_size, output_dtype
951
+ )
build/torch-rocm/matmul.py ADDED
@@ -0,0 +1,941 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2025 The HuggingFace Inc. team. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import torch
16
+ import triton
17
+ import triton.language as tl
18
+ from torch.library import triton_op, wrap_triton
19
+
20
+ from ._ops import add_op_namespace_prefix, ops
21
+ from .utils import (
22
+ NIBBLES_PER_BYTE,
23
+ MX_SCALE_GROUP_K,
24
+ adaptive_block_size_m,
25
+ decode_ue8m0_scale,
26
+ device_context,
27
+ fp8_act_quant,
28
+ fp8_act_quant_inline,
29
+ get_accelerator_autotuning_configs,
30
+ is_mxfp4,
31
+ is_mxfp8,
32
+ mx_act_quant_inline,
33
+ ue8m0_as_uint8,
34
+ )
35
+
36
+
37
+ @triton.jit
38
+ def _swizzle_offsets(
39
+ M,
40
+ N,
41
+ BLOCK_SIZE_M: tl.constexpr,
42
+ BLOCK_SIZE_N: tl.constexpr,
43
+ BLOCK_SIZE_K: tl.constexpr,
44
+ GROUP_SIZE_M: tl.constexpr,
45
+ ):
46
+ """2D-grid tile scheduling shared by the kernels below: swizzle the
47
+ ``(pid_m, pid_n)`` program ids for L2 locality on B, then build the operand
48
+ offset vectors. Returns ``(pid_m, pid_n, offs_am, offs_bn, offs_k)`` — the
49
+ swizzled ids (reused by the output store) and the ``%``-wrapped row/col offsets
50
+ plus the K range."""
51
+ pid_m = tl.program_id(axis=0)
52
+ pid_n = tl.program_id(axis=1)
53
+ num_pid_m = tl.cdiv(M, BLOCK_SIZE_M)
54
+ num_pid_n = tl.cdiv(N, BLOCK_SIZE_N)
55
+ pid_m, pid_n = tl.swizzle2d(pid_m, pid_n, num_pid_m, num_pid_n, GROUP_SIZE_M)
56
+ offs_am = (pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)) % M
57
+ offs_bn = (pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)) % N
58
+ offs_k = tl.arange(0, BLOCK_SIZE_K)
59
+ return pid_m, pid_n, offs_am, offs_bn, offs_k
60
+
61
+
62
+ @triton.jit
63
+ def _store_masked(
64
+ C,
65
+ accumulator,
66
+ pid_m,
67
+ pid_n,
68
+ M,
69
+ N,
70
+ stride_cm,
71
+ stride_cn,
72
+ BLOCK_SIZE_M: tl.constexpr,
73
+ BLOCK_SIZE_N: tl.constexpr,
74
+ ):
75
+ """Shared output epilogue of the kernels below: cast the fp32 accumulator to
76
+ ``C``'s dtype and store the ``(BLOCK_SIZE_M, BLOCK_SIZE_N)`` tile at the swizzled
77
+ ``(pid_m, pid_n)``, masked to the ``(M, N)`` bounds."""
78
+ c = accumulator.to(C.dtype.element_ty)
79
+ offs_cm = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
80
+ offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
81
+ c_ptrs = C + stride_cm * offs_cm[:, None] + stride_cn * offs_cn[None, :]
82
+ c_mask = (offs_cm[:, None] < M) & (offs_cn[None, :] < N)
83
+ tl.store(c_ptrs, c, mask=c_mask)
84
+
85
+
86
+ @triton.autotune(
87
+ configs=get_accelerator_autotuning_configs(),
88
+ key=["N", "K", "BLOCK_SIZE_M"],
89
+ )
90
+ @triton.jit
91
+ def w8a8_block_dynamic_fp8_matmul_kernel(
92
+ A, # (M, K) raw BF16/FP16 activations
93
+ B, # (N, K) FP8 weights
94
+ C, # (M, N) output
95
+ Bs, # (N // BLOCK_SIZE_N, K // BLOCK_SIZE_K) weight scales (fp32 or uint8/UE8M0)
96
+ # Shape
97
+ M,
98
+ N,
99
+ K,
100
+ # Strides
101
+ stride_am,
102
+ stride_ak,
103
+ stride_bk,
104
+ stride_bn,
105
+ stride_cm,
106
+ stride_cn,
107
+ stride_bs_k,
108
+ stride_bs_n,
109
+ # Meta-parameters
110
+ BLOCK_SIZE_M: tl.constexpr,
111
+ BLOCK_SIZE_N: tl.constexpr,
112
+ BLOCK_SIZE_K: tl.constexpr,
113
+ GROUP_SIZE_M: tl.constexpr,
114
+ ):
115
+ """Block-scale FP8 GEMM kernel with fused activation quantization.
116
+
117
+ Computes ``C = A @ B.T`` with bf16/fp16 ``A`` quantized to FP8 per-K-tile
118
+ inline (one scale per M-row per BLOCK_SIZE_K) and pre-quantized FP8 weights
119
+ with per-block scales. 2D grid with swizzle for L2 cache locality on B.
120
+ """
121
+ pid_m, pid_n, offs_am, offs_bn, offs_k = _swizzle_offsets(
122
+ M, N, BLOCK_SIZE_M, BLOCK_SIZE_N, BLOCK_SIZE_K, GROUP_SIZE_M
123
+ )
124
+ a_ptrs = A + (offs_am[:, None] * stride_am + offs_k[None, :] * stride_ak)
125
+ b_ptrs = B + (offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn)
126
+
127
+ offs_bsn = offs_bn // BLOCK_SIZE_N
128
+ bs_ptrs = Bs + offs_bsn * stride_bs_n
129
+
130
+ accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
131
+ for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
132
+ k_remaining = K - k * BLOCK_SIZE_K
133
+ a_raw = tl.load(a_ptrs, mask=offs_k[None, :] < k_remaining, other=0.0).to(
134
+ tl.float32
135
+ )
136
+ a, a_s = fp8_act_quant_inline(a_raw)
137
+ b = tl.load(b_ptrs, mask=offs_k[:, None] < k_remaining, other=0.0)
138
+ b_s = decode_ue8m0_scale(tl.load(bs_ptrs))
139
+ accumulator += tl.dot(a, b) * a_s[:, None] * b_s[None, :]
140
+ a_ptrs += BLOCK_SIZE_K * stride_ak
141
+ b_ptrs += BLOCK_SIZE_K * stride_bk
142
+ bs_ptrs += stride_bs_k
143
+
144
+ _store_masked(
145
+ C, accumulator, pid_m, pid_n, M, N, stride_cm, stride_cn,
146
+ BLOCK_SIZE_M, BLOCK_SIZE_N,
147
+ )
148
+
149
+
150
+ @triton.autotune(
151
+ configs=get_accelerator_autotuning_configs(with_block_sizes=True),
152
+ key=["N", "K", "BLOCK_SIZE_M"],
153
+ )
154
+ @triton.jit
155
+ def w8a8_tensor_dynamic_fp8_matmul_kernel(
156
+ A, # (M, K) pre-quantized FP8 activations
157
+ B, # (N, K) FP8 weights
158
+ C, # (M, N) output
159
+ As, # (M,) per-token activation scales
160
+ Bs, # scalar/(1,) per-tensor weight scale
161
+ # Shape
162
+ M,
163
+ N,
164
+ K,
165
+ # Strides
166
+ stride_am,
167
+ stride_ak,
168
+ stride_bk,
169
+ stride_bn,
170
+ stride_cm,
171
+ stride_cn,
172
+ stride_as_m,
173
+ # Meta-parameters
174
+ BLOCK_SIZE_M: tl.constexpr,
175
+ BLOCK_SIZE_N: tl.constexpr,
176
+ BLOCK_SIZE_K: tl.constexpr,
177
+ GROUP_SIZE_M: tl.constexpr,
178
+ ):
179
+ """Tensor-scale FP8 GEMM kernel.
180
+
181
+ Computes ``C = A @ B.T`` with one activation scale per row and one
182
+ weight scale for the full matrix.
183
+ Uses a 2D grid with swizzle for L2 cache locality on B tiles.
184
+ """
185
+ pid_m, pid_n, offs_am, offs_bn, offs_k = _swizzle_offsets(
186
+ M, N, BLOCK_SIZE_M, BLOCK_SIZE_N, BLOCK_SIZE_K, GROUP_SIZE_M
187
+ )
188
+ a_ptrs = A + offs_am[:, None] * stride_am + offs_k[None, :] * stride_ak
189
+ b_ptrs = B + offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn
190
+
191
+ a_s = tl.load(As + offs_am * stride_as_m)
192
+ b_s = tl.load(Bs)
193
+
194
+ # Accumulate raw dot products, apply scales once after the loop.
195
+ accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
196
+ for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
197
+ k_remaining = K - k * BLOCK_SIZE_K
198
+ a = tl.load(a_ptrs, mask=offs_k[None, :] < k_remaining, other=0.0)
199
+ b = tl.load(b_ptrs, mask=offs_k[:, None] < k_remaining, other=0.0)
200
+ accumulator += tl.dot(a, b)
201
+ a_ptrs += BLOCK_SIZE_K * stride_ak
202
+ b_ptrs += BLOCK_SIZE_K * stride_bk
203
+
204
+ accumulator = accumulator * a_s[:, None] * b_s
205
+
206
+ _store_masked(
207
+ C, accumulator, pid_m, pid_n, M, N, stride_cm, stride_cn,
208
+ BLOCK_SIZE_M, BLOCK_SIZE_N,
209
+ )
210
+
211
+
212
+ @triton.autotune(
213
+ configs=get_accelerator_autotuning_configs(),
214
+ key=["N", "K", "BLOCK_SIZE_M"],
215
+ )
216
+ @triton.jit
217
+ def w8a8_block_static_fp8_matmul_kernel(
218
+ A, # (M, K) raw BF16/FP16 activations
219
+ B, # (N, K) FP8 weights
220
+ C, # (M, N) output
221
+ As, # scalar — static per-tensor activation scale (calibration-time)
222
+ Bs, # (N // BLOCK_SIZE_N, K // BLOCK_SIZE_K) weight scales (fp32 or uint8/UE8M0)
223
+ # Shape
224
+ M,
225
+ N,
226
+ K,
227
+ # Strides
228
+ stride_am,
229
+ stride_ak,
230
+ stride_bk,
231
+ stride_bn,
232
+ stride_cm,
233
+ stride_cn,
234
+ stride_bs_k,
235
+ stride_bs_n,
236
+ # Meta-parameters
237
+ BLOCK_SIZE_M: tl.constexpr,
238
+ BLOCK_SIZE_N: tl.constexpr,
239
+ BLOCK_SIZE_K: tl.constexpr,
240
+ GROUP_SIZE_M: tl.constexpr,
241
+ ):
242
+ """Block-scale FP8 GEMM with static (per-tensor) activation scale.
243
+
244
+ ``A`` is raw bf16/fp16; the kernel divides by the scalar ``As`` and casts
245
+ to FP8 inline. Per-block weight scales apply per-K-tile during
246
+ accumulation; the scalar activation scale factors out of the loop and
247
+ is applied once at the end.
248
+ """
249
+ pid_m, pid_n, offs_am, offs_bn, offs_k = _swizzle_offsets(
250
+ M, N, BLOCK_SIZE_M, BLOCK_SIZE_N, BLOCK_SIZE_K, GROUP_SIZE_M
251
+ )
252
+ a_ptrs = A + (offs_am[:, None] * stride_am + offs_k[None, :] * stride_ak)
253
+ b_ptrs = B + (offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn)
254
+
255
+ offs_bsn = offs_bn // BLOCK_SIZE_N
256
+ bs_ptrs = Bs + offs_bsn * stride_bs_n
257
+ a_s_static = tl.load(As)
258
+
259
+ accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
260
+ for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
261
+ k_remaining = K - k * BLOCK_SIZE_K
262
+ a_raw = tl.load(a_ptrs, mask=offs_k[None, :] < k_remaining, other=0.0).to(
263
+ tl.float32
264
+ )
265
+ a = (a_raw / a_s_static).to(tl.float8e4nv)
266
+ b = tl.load(b_ptrs, mask=offs_k[:, None] < k_remaining, other=0.0)
267
+ b_s = decode_ue8m0_scale(tl.load(bs_ptrs))
268
+ accumulator += tl.dot(a, b) * b_s[None, :]
269
+ a_ptrs += BLOCK_SIZE_K * stride_ak
270
+ b_ptrs += BLOCK_SIZE_K * stride_bk
271
+ bs_ptrs += stride_bs_k
272
+
273
+ accumulator = accumulator * a_s_static
274
+ _store_masked(
275
+ C, accumulator, pid_m, pid_n, M, N, stride_cm, stride_cn,
276
+ BLOCK_SIZE_M, BLOCK_SIZE_N,
277
+ )
278
+
279
+
280
+ @triton.autotune(
281
+ configs=get_accelerator_autotuning_configs(with_block_sizes=True),
282
+ key=["N", "K", "BLOCK_SIZE_M"],
283
+ )
284
+ @triton.jit
285
+ def w4a8_mx_dynamic_fp4_matmul_kernel(
286
+ A, # (M, K) raw BF16/FP16 activations
287
+ B, # (N, K // 2) packed FP4 (E2M1) weights as int8
288
+ C, # (M, N) output
289
+ Bs, # (N, K // SCALE_GROUP_K) UE8M0 weight scales
290
+ # Shape
291
+ M,
292
+ N,
293
+ K,
294
+ # Strides
295
+ stride_am,
296
+ stride_ak,
297
+ stride_bk,
298
+ stride_bn,
299
+ stride_cm,
300
+ stride_cn,
301
+ stride_bs_k,
302
+ stride_bs_n,
303
+ # Meta-parameters
304
+ BLOCK_SIZE_M: tl.constexpr,
305
+ BLOCK_SIZE_N: tl.constexpr,
306
+ BLOCK_SIZE_K: tl.constexpr,
307
+ GROUP_SIZE_M: tl.constexpr,
308
+ NIBBLES_PER_BYTE: tl.constexpr,
309
+ SCALE_GROUP_K: tl.constexpr,
310
+ ):
311
+ """MXFP4 (W4A8) GEMM kernel with fused activation quantization.
312
+
313
+ Computes ``C = A @ B.T`` with bf16/fp16 ``A`` quantized to FP8 (E4M3) per
314
+ K-group of ``SCALE_GROUP_K`` elements inline (UE8M0 scale), and packed FP4
315
+ (E2M1) weights with their own UE8M0 scales. 2D grid with swizzle for L2
316
+ cache locality on B tiles, ``tl.dot_scaled`` for the scaled MMA.
317
+ """
318
+ pid_m, pid_n, offs_am, offs_bn, offs_k = _swizzle_offsets(
319
+ M, N, BLOCK_SIZE_M, BLOCK_SIZE_N, BLOCK_SIZE_K, GROUP_SIZE_M
320
+ )
321
+ offs_k_byte = tl.arange(0, BLOCK_SIZE_K // NIBBLES_PER_BYTE)
322
+ offs_sf = tl.arange(0, BLOCK_SIZE_K // SCALE_GROUP_K)
323
+ a_ptrs = A + (offs_am[:, None] * stride_am + offs_k[None, :] * stride_ak)
324
+ b_ptrs = B + (offs_k_byte[:, None] * stride_bk + offs_bn[None, :] * stride_bn)
325
+ bs_ptrs = Bs + (offs_bn[:, None] * stride_bs_n + offs_sf[None, :] * stride_bs_k)
326
+
327
+ accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
328
+ for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
329
+ k_remaining = K - k * BLOCK_SIZE_K
330
+ a_raw = tl.load(a_ptrs, mask=offs_k[None, :] < k_remaining, other=0.0).to(
331
+ tl.float32
332
+ )
333
+ a, a_scale = mx_act_quant_inline(
334
+ a_raw, BLOCK_SIZE_M, BLOCK_SIZE_K, SCALE_GROUP_K
335
+ )
336
+ b = tl.load(
337
+ b_ptrs, mask=offs_k_byte[:, None] < k_remaining // NIBBLES_PER_BYTE, other=0
338
+ ).to(tl.uint8)
339
+ b_s = tl.load(
340
+ bs_ptrs, mask=offs_sf[None, :] < k_remaining // SCALE_GROUP_K, other=0
341
+ ).to(tl.uint8)
342
+ accumulator += tl.dot_scaled(a, a_scale, "e4m3", b, b_s, "e2m1")
343
+ a_ptrs += BLOCK_SIZE_K * stride_ak
344
+ b_ptrs += (BLOCK_SIZE_K // NIBBLES_PER_BYTE) * stride_bk
345
+ bs_ptrs += (BLOCK_SIZE_K // SCALE_GROUP_K) * stride_bs_k
346
+
347
+ _store_masked(
348
+ C, accumulator, pid_m, pid_n, M, N, stride_cm, stride_cn,
349
+ BLOCK_SIZE_M, BLOCK_SIZE_N,
350
+ )
351
+
352
+
353
+ @triton.autotune(
354
+ configs=get_accelerator_autotuning_configs(with_block_sizes=True),
355
+ key=["N", "K", "BLOCK_SIZE_M"],
356
+ )
357
+ @triton.jit
358
+ def w8a8_mx_dynamic_fp8_matmul_kernel(
359
+ A, # (M, K) raw BF16/FP16 activations
360
+ B, # (N, K) E4M3 weights (unpacked, one byte per value)
361
+ C, # (M, N) output
362
+ Bs, # (N, K // SCALE_GROUP_K) UE8M0 weight scales
363
+ # Shape
364
+ M,
365
+ N,
366
+ K,
367
+ # Strides
368
+ stride_am,
369
+ stride_ak,
370
+ stride_bk,
371
+ stride_bn,
372
+ stride_cm,
373
+ stride_cn,
374
+ stride_bs_k,
375
+ stride_bs_n,
376
+ # Meta-parameters
377
+ BLOCK_SIZE_M: tl.constexpr,
378
+ BLOCK_SIZE_N: tl.constexpr,
379
+ BLOCK_SIZE_K: tl.constexpr,
380
+ GROUP_SIZE_M: tl.constexpr,
381
+ SCALE_GROUP_K: tl.constexpr,
382
+ ):
383
+ """MXFP8 block-scale GEMM kernel with fused activation quantization.
384
+
385
+ Computes ``C = A @ B.T`` with bf16/fp16 ``A`` quantized to FP8 (E4M3) per
386
+ K-group of ``SCALE_GROUP_K`` elements inline (UE8M0 scale), against E4M3
387
+ weights with their own UE8M0 K-group scales. Mirrors the W4A8 FP4 kernel but
388
+ keeps weights unpacked (one E4M3 byte per value); ``tl.dot_scaled`` drives
389
+ the MX scaled MMA with ``"e4m3"`` on both operands.
390
+ """
391
+ pid_m, pid_n, offs_am, offs_bn, offs_k = _swizzle_offsets(
392
+ M, N, BLOCK_SIZE_M, BLOCK_SIZE_N, BLOCK_SIZE_K, GROUP_SIZE_M
393
+ )
394
+ offs_sf = tl.arange(0, BLOCK_SIZE_K // SCALE_GROUP_K)
395
+ a_ptrs = A + (offs_am[:, None] * stride_am + offs_k[None, :] * stride_ak)
396
+ b_ptrs = B + (offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn)
397
+ bs_ptrs = Bs + (offs_bn[:, None] * stride_bs_n + offs_sf[None, :] * stride_bs_k)
398
+
399
+ accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
400
+ for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
401
+ k_remaining = K - k * BLOCK_SIZE_K
402
+ a_raw = tl.load(a_ptrs, mask=offs_k[None, :] < k_remaining, other=0.0).to(
403
+ tl.float32
404
+ )
405
+ a, a_scale = mx_act_quant_inline(
406
+ a_raw, BLOCK_SIZE_M, BLOCK_SIZE_K, SCALE_GROUP_K
407
+ )
408
+ b = tl.load(b_ptrs, mask=offs_k[:, None] < k_remaining, other=0.0)
409
+ b_s = tl.load(
410
+ bs_ptrs, mask=offs_sf[None, :] < k_remaining // SCALE_GROUP_K, other=0
411
+ ).to(tl.uint8)
412
+ accumulator += tl.dot_scaled(a, a_scale, "e4m3", b, b_s, "e4m3")
413
+ a_ptrs += BLOCK_SIZE_K * stride_ak
414
+ b_ptrs += BLOCK_SIZE_K * stride_bk
415
+ bs_ptrs += (BLOCK_SIZE_K // SCALE_GROUP_K) * stride_bs_k
416
+
417
+ _store_masked(
418
+ C, accumulator, pid_m, pid_n, M, N, stride_cm, stride_cn,
419
+ BLOCK_SIZE_M, BLOCK_SIZE_N,
420
+ )
421
+
422
+
423
+ @triton_op(add_op_namespace_prefix("w8a8_block_dynamic_fp8_matmul"), mutates_args=())
424
+ def _w8a8_block_dynamic_fp8_matmul(
425
+ A: torch.Tensor,
426
+ B: torch.Tensor,
427
+ Bs: torch.Tensor,
428
+ block_size: list[int],
429
+ output_dtype: torch.dtype | None = None,
430
+ ) -> torch.Tensor:
431
+ """Block-scale FP8 matmul: ``C = A @ B.T`` with fused activation quantization.
432
+
433
+ A: (..., K) raw activations, bf16/fp16/fp32 (quantized inline to FP8)
434
+ B: (N, K) FP8 weights
435
+ Bs: (N // block_n, K // block_k) per-block weight scales
436
+ """
437
+ assert len(block_size) == 2, (
438
+ f"block_size must be [block_n, block_k], got {block_size}"
439
+ )
440
+ block_n, block_k = block_size[0], block_size[1]
441
+
442
+ assert A.shape[-1] == B.shape[-1], (
443
+ f"K mismatch: A has K={A.shape[-1]}, B has K={B.shape[-1]}"
444
+ )
445
+ assert A.is_contiguous(), "A must be contiguous"
446
+ assert B.ndim == 2, f"B must be 2D (N, K), got ndim={B.ndim}"
447
+ assert B.is_contiguous(), "B must be contiguous"
448
+
449
+ N, K = B.shape
450
+ M = A.numel() // A.shape[-1]
451
+
452
+ assert Bs.ndim == 2, f"Bs must be 2D (N//block_n, K//block_k), got ndim={Bs.ndim}"
453
+ assert Bs.shape == (triton.cdiv(N, block_n), triton.cdiv(K, block_k)), (
454
+ f"Bs shape {tuple(Bs.shape)} != expected ({triton.cdiv(N, block_n)}, {triton.cdiv(K, block_k)})"
455
+ )
456
+
457
+ BLOCK_SIZE_K = block_k
458
+ BLOCK_SIZE_N = block_n
459
+ Bs = ue8m0_as_uint8(Bs)
460
+ C_shape = A.shape[:-1] + (N,)
461
+ BLOCK_SIZE_M = adaptive_block_size_m(M)
462
+ C = A.new_empty(C_shape, dtype=output_dtype)
463
+ grid = (triton.cdiv(M, BLOCK_SIZE_M), triton.cdiv(N, BLOCK_SIZE_N))
464
+
465
+ with device_context(A.device):
466
+ wrap_triton(w8a8_block_dynamic_fp8_matmul_kernel)[grid](
467
+ A,
468
+ B,
469
+ C,
470
+ Bs,
471
+ M,
472
+ N,
473
+ K,
474
+ A.stride(-2),
475
+ A.stride(-1),
476
+ B.stride(1),
477
+ B.stride(0),
478
+ C.stride(-2),
479
+ C.stride(-1),
480
+ Bs.stride(1),
481
+ Bs.stride(0),
482
+ # Meta-parameters
483
+ BLOCK_SIZE_M=BLOCK_SIZE_M,
484
+ BLOCK_SIZE_N=BLOCK_SIZE_N,
485
+ BLOCK_SIZE_K=BLOCK_SIZE_K,
486
+ GROUP_SIZE_M=8,
487
+ )
488
+
489
+ return C
490
+
491
+
492
+ @triton_op(add_op_namespace_prefix("w8a8_block_static_fp8_matmul"), mutates_args=())
493
+ def _w8a8_block_static_fp8_matmul(
494
+ A: torch.Tensor,
495
+ B: torch.Tensor,
496
+ Bs: torch.Tensor,
497
+ As: torch.Tensor,
498
+ block_size: list[int],
499
+ output_dtype: torch.dtype | None = None,
500
+ ) -> torch.Tensor:
501
+ """Block-scale FP8 matmul with static (per-tensor) activation quantization.
502
+
503
+ A: (..., K) raw bf16/fp16 activations — quantized to FP8 inline against ``As``
504
+ B: (N, K) FP8 weights
505
+ Bs: (N // block_n, K // block_k) per-block weight scales
506
+ As: scalar / (1,) — per-tensor static activation scale
507
+ """
508
+ assert len(block_size) == 2, (
509
+ f"block_size must be [block_n, block_k], got {block_size}"
510
+ )
511
+ block_n, block_k = block_size[0], block_size[1]
512
+
513
+ assert B.dtype != torch.int8, (
514
+ "static activation quant is not supported on the FP4 path"
515
+ )
516
+ assert not (block_n == B.size(0) and block_k == B.size(1)), (
517
+ "static activation quant requires block-wise weights, not tensor-mode"
518
+ )
519
+ assert A.shape[-1] == B.shape[-1], (
520
+ f"K mismatch: A has K={A.shape[-1]}, B has K={B.shape[-1]}"
521
+ )
522
+ assert A.is_contiguous(), "A must be contiguous"
523
+ assert B.ndim == 2, f"B must be 2D (N, K), got ndim={B.ndim}"
524
+ assert B.is_contiguous(), "B must be contiguous"
525
+ assert As.numel() == 1, f"As must be scalar or (1,), got {tuple(As.shape)}"
526
+
527
+ N, K = B.shape
528
+ M = A.numel() // A.shape[-1]
529
+
530
+ assert Bs.ndim == 2, f"Bs must be 2D (N//block_n, K//block_k), got ndim={Bs.ndim}"
531
+ assert Bs.shape == (triton.cdiv(N, block_n), triton.cdiv(K, block_k)), (
532
+ f"Bs shape {tuple(Bs.shape)} != expected ({triton.cdiv(N, block_n)}, {triton.cdiv(K, block_k)})"
533
+ )
534
+
535
+ BLOCK_SIZE_K = block_k
536
+ BLOCK_SIZE_N = block_n
537
+ BLOCK_SIZE_M = adaptive_block_size_m(M)
538
+ grid = (triton.cdiv(M, BLOCK_SIZE_M), triton.cdiv(N, BLOCK_SIZE_N))
539
+
540
+ Bs = ue8m0_as_uint8(Bs)
541
+ C_shape = A.shape[:-1] + (N,)
542
+ C = A.new_empty(C_shape, dtype=output_dtype)
543
+ As = As.reshape(1).to(torch.float32)
544
+
545
+ with device_context(A.device):
546
+ wrap_triton(w8a8_block_static_fp8_matmul_kernel)[grid](
547
+ A,
548
+ B,
549
+ C,
550
+ As,
551
+ Bs,
552
+ M,
553
+ N,
554
+ K,
555
+ A.stride(-2),
556
+ A.stride(-1),
557
+ B.stride(1),
558
+ B.stride(0),
559
+ C.stride(-2),
560
+ C.stride(-1),
561
+ Bs.stride(1),
562
+ Bs.stride(0),
563
+ # Meta-parameters
564
+ BLOCK_SIZE_M=BLOCK_SIZE_M,
565
+ BLOCK_SIZE_N=BLOCK_SIZE_N,
566
+ BLOCK_SIZE_K=BLOCK_SIZE_K,
567
+ GROUP_SIZE_M=8,
568
+ )
569
+
570
+ return C
571
+
572
+
573
+ @triton_op(add_op_namespace_prefix("w8a8_tensor_dynamic_fp8_matmul"), mutates_args=())
574
+ def _w8a8_tensor_dynamic_fp8_matmul(
575
+ A: torch.Tensor,
576
+ B: torch.Tensor,
577
+ Bs: torch.Tensor,
578
+ output_dtype: torch.dtype | None = None,
579
+ ) -> torch.Tensor:
580
+ """Tensor-scale FP8 matmul: ``C = A @ B.T`` with fused activation quantization.
581
+
582
+ A: (..., K) raw activations, bf16/fp16/fp32 (flattened to (M, K)
583
+ internally) — per-row scales computed via ``fp8_act_quant(A, K)``.
584
+ B: (N, K) FP8 weights.
585
+ Bs: scalar, (1,), or (1, 1) — single tensor-scale weight scale.
586
+ """
587
+ assert A.shape[-1] == B.shape[-1], (
588
+ f"K mismatch: A has K={A.shape[-1]}, B has K={B.shape[-1]}"
589
+ )
590
+ assert A.is_contiguous(), "A must be contiguous"
591
+ assert B.ndim == 2, f"B must be 2D (N, K), got ndim={B.ndim}"
592
+ assert B.is_contiguous(), "B must be contiguous"
593
+
594
+ N, K = B.shape
595
+ M = A.numel() // A.shape[-1]
596
+
597
+ assert Bs.numel() == 1, f"Bs must be scalar or (1,), got {tuple(Bs.shape)}"
598
+
599
+ # Per-row scalar activation scale (one per token).
600
+ qA, As = fp8_act_quant(A, K)
601
+ As = As.reshape(M)
602
+ Bs = Bs.reshape(1)
603
+
604
+ C_shape = A.shape[:-1] + (N,)
605
+ C = A.new_empty(C_shape, dtype=output_dtype)
606
+ BLOCK_SIZE_M = adaptive_block_size_m(M)
607
+
608
+ def grid(META):
609
+ return (triton.cdiv(M, BLOCK_SIZE_M), triton.cdiv(N, META["BLOCK_SIZE_N"]))
610
+
611
+ with device_context(A.device):
612
+ wrap_triton(w8a8_tensor_dynamic_fp8_matmul_kernel)[grid](
613
+ qA,
614
+ B,
615
+ C,
616
+ As,
617
+ Bs,
618
+ M,
619
+ N,
620
+ K,
621
+ qA.stride(-2),
622
+ qA.stride(-1),
623
+ B.stride(1),
624
+ B.stride(0),
625
+ C.stride(-2),
626
+ C.stride(-1),
627
+ As.stride(0),
628
+ # Meta-parameters (BLOCK_SIZE_N, BLOCK_SIZE_K come from autotune Config)
629
+ BLOCK_SIZE_M=BLOCK_SIZE_M,
630
+ GROUP_SIZE_M=8,
631
+ )
632
+
633
+ return C
634
+
635
+
636
+ @triton_op(add_op_namespace_prefix("w4a8_mx_dynamic_fp4_matmul"), mutates_args=())
637
+ def _w4a8_mx_dynamic_fp4_matmul(
638
+ A: torch.Tensor,
639
+ B: torch.Tensor,
640
+ Bs: torch.Tensor,
641
+ output_dtype: torch.dtype | None = None,
642
+ ) -> torch.Tensor:
643
+ """MXFP4 (W4A8) matmul: ``C = A @ B.T`` with fused activation quant.
644
+
645
+ A: (M, K) raw activations, bf16/fp16/fp32 (quantized inline to FP8)
646
+ B: (N, K // 2) packed FP4 (E2M1) weights, two codes per int8
647
+ Bs: (N, K // 32) UE8M0 weight scales
648
+
649
+ On CUDA and other backends, BLOCK_SIZE_N and BLOCK_SIZE_K are autotuned.
650
+ On XPU they are fixed to 128x128 and only num_warps/num_stages are autotuned.
651
+ FP4 scale granularity is fixed at 32, so tile shape is purely a perf knob.
652
+ """
653
+ assert A.ndim == 2 and B.ndim == 2 and Bs.ndim == 2
654
+ assert B.dtype == torch.int8, f"B must be int8 (packed FP4), got {B.dtype}"
655
+ assert Bs.dtype == torch.float8_e8m0fnu, (
656
+ f"Bs must be float8_e8m0fnu, got {Bs.dtype}"
657
+ )
658
+ assert A.is_contiguous(), "A must be contiguous"
659
+ assert B.is_contiguous(), "B must be contiguous"
660
+
661
+ M, K = A.shape
662
+ N, K_half = B.shape
663
+ assert K == NIBBLES_PER_BYTE * K_half, (
664
+ f"K (={K}) must equal {NIBBLES_PER_BYTE} * B.shape[1] (={K_half})"
665
+ )
666
+ assert K % MX_SCALE_GROUP_K == 0, (
667
+ f"K (={K}) must be a multiple of {MX_SCALE_GROUP_K}"
668
+ )
669
+ assert Bs.shape == (N, K // MX_SCALE_GROUP_K), (
670
+ f"Bs shape {tuple(Bs.shape)} != ({N}, {K // MX_SCALE_GROUP_K})"
671
+ )
672
+
673
+ bs_u8 = ue8m0_as_uint8(Bs)
674
+ C = A.new_empty((M, N), dtype=output_dtype)
675
+ BLOCK_SIZE_M = adaptive_block_size_m(M)
676
+
677
+ def grid(META):
678
+ return (triton.cdiv(M, BLOCK_SIZE_M), triton.cdiv(N, META["BLOCK_SIZE_N"]))
679
+
680
+ with device_context(A.device):
681
+ wrap_triton(w4a8_mx_dynamic_fp4_matmul_kernel)[grid](
682
+ A,
683
+ B,
684
+ C,
685
+ bs_u8,
686
+ M,
687
+ N,
688
+ K,
689
+ A.stride(0),
690
+ A.stride(1),
691
+ B.stride(1),
692
+ B.stride(0),
693
+ C.stride(0),
694
+ C.stride(1),
695
+ bs_u8.stride(1),
696
+ bs_u8.stride(0),
697
+ # Meta-parameters (BLOCK_SIZE_N, BLOCK_SIZE_K come from autotune Config)
698
+ BLOCK_SIZE_M=BLOCK_SIZE_M,
699
+ GROUP_SIZE_M=8,
700
+ NIBBLES_PER_BYTE=NIBBLES_PER_BYTE,
701
+ SCALE_GROUP_K=MX_SCALE_GROUP_K,
702
+ )
703
+ return C
704
+
705
+
706
+ @triton_op(add_op_namespace_prefix("w8a8_mx_dynamic_fp8_matmul"), mutates_args=())
707
+ def _w8a8_mx_dynamic_fp8_matmul(
708
+ A: torch.Tensor,
709
+ B: torch.Tensor,
710
+ Bs: torch.Tensor,
711
+ output_dtype: torch.dtype | None = None,
712
+ ) -> torch.Tensor:
713
+ """MXFP8 W8A8 matmul: ``C = A @ B.T`` (E4M3 × E4M3, UE8M0 group-32 scales).
714
+
715
+ A: (M, K) raw activations, bf16/fp16/fp32 (quantized inline to E4M3,
716
+ MX group-32 UE8M0 scales)
717
+ B: (N, K) E4M3 weights (unpacked)
718
+ Bs: (N, K // 32) UE8M0 weight scales
719
+
720
+ Tile shape (BLOCK_SIZE_N, BLOCK_SIZE_K) is autotuned; MX scale granularity is
721
+ fixed at 32 (the MX-format spec), so tile shape is purely a perf knob.
722
+ """
723
+ assert A.ndim == 2 and B.ndim == 2 and Bs.ndim == 2
724
+ assert B.dtype == torch.float8_e4m3fn, (
725
+ f"B must be float8_e4m3fn (E4M3 weights), got {B.dtype}"
726
+ )
727
+ assert Bs.dtype == torch.float8_e8m0fnu, (
728
+ f"Bs must be float8_e8m0fnu, got {Bs.dtype}"
729
+ )
730
+ assert A.is_contiguous(), "A must be contiguous"
731
+ assert B.is_contiguous(), "B must be contiguous"
732
+
733
+ M, K = A.shape
734
+ N, K_b = B.shape
735
+ assert K == K_b, f"K mismatch: A has K={K}, B has K={K_b}"
736
+ assert K % MX_SCALE_GROUP_K == 0, (
737
+ f"K (={K}) must be a multiple of {MX_SCALE_GROUP_K}"
738
+ )
739
+ assert Bs.shape == (N, K // MX_SCALE_GROUP_K), (
740
+ f"Bs shape {tuple(Bs.shape)} != ({N}, {K // MX_SCALE_GROUP_K})"
741
+ )
742
+
743
+ bs_u8 = ue8m0_as_uint8(Bs)
744
+ C = A.new_empty((M, N), dtype=output_dtype)
745
+ BLOCK_SIZE_M = adaptive_block_size_m(M)
746
+
747
+ def grid(META):
748
+ return (triton.cdiv(M, BLOCK_SIZE_M), triton.cdiv(N, META["BLOCK_SIZE_N"]))
749
+
750
+ with device_context(A.device):
751
+ wrap_triton(w8a8_mx_dynamic_fp8_matmul_kernel)[grid](
752
+ A,
753
+ B,
754
+ C,
755
+ bs_u8,
756
+ M,
757
+ N,
758
+ K,
759
+ A.stride(0),
760
+ A.stride(1),
761
+ B.stride(1),
762
+ B.stride(0),
763
+ C.stride(0),
764
+ C.stride(1),
765
+ bs_u8.stride(1),
766
+ bs_u8.stride(0),
767
+ # Meta-parameters (BLOCK_SIZE_N, BLOCK_SIZE_K come from autotune Config)
768
+ BLOCK_SIZE_M=BLOCK_SIZE_M,
769
+ GROUP_SIZE_M=8,
770
+ SCALE_GROUP_K=MX_SCALE_GROUP_K,
771
+ )
772
+ return C
773
+
774
+
775
+ def w8a8_block_dynamic_fp8_matmul(
776
+ A: torch.Tensor,
777
+ B: torch.Tensor,
778
+ Bs: torch.Tensor,
779
+ block_size: list[int],
780
+ output_dtype: torch.dtype | None = None,
781
+ ) -> torch.Tensor:
782
+ """Block-wise W8A8 FP8 matrix multiplication with fused activation quantization.
783
+
784
+ Args:
785
+ A: Raw activation tensor ``[..., M, K]`` in bf16/fp16/fp32 — quantized
786
+ inline to ``float8_e4m3fn`` against per-K-tile per-row UE8 scales.
787
+ B: FP8 weight tensor ``[N, K]`` in ``float8_e4m3fn``.
788
+ Bs: Per-block weight scales ``[N // block_size[0], K // block_size[1]]``.
789
+ block_size: ``[block_n, block_k]`` weight quantization block dimensions.
790
+ output_dtype: dtype of the returned tensor (default: ``torch.float32``).
791
+
792
+ Returns:
793
+ Output tensor ``[..., M, N]`` in ``output_dtype``.
794
+ """
795
+ return ops.w8a8_block_dynamic_fp8_matmul(A, B, Bs, block_size, output_dtype)
796
+
797
+
798
+ def w8a8_block_static_fp8_matmul(
799
+ A: torch.Tensor,
800
+ B: torch.Tensor,
801
+ Bs: torch.Tensor,
802
+ As: torch.Tensor,
803
+ block_size: list[int],
804
+ output_dtype: torch.dtype | None = None,
805
+ ) -> torch.Tensor:
806
+ """Block-wise W8A8 FP8 matmul with static (per-tensor) activation quantization.
807
+
808
+ Args:
809
+ A: Raw activation tensor ``[..., M, K]`` in bf16/fp16/fp32 — quantized
810
+ inline against the per-tensor scalar ``As``.
811
+ B: FP8 weight tensor ``[N, K]`` in ``float8_e4m3fn``.
812
+ Bs: Per-block weight scales ``[N // block_size[0], K // block_size[1]]``.
813
+ As: Static per-tensor activation scale (scalar or ``[1]``).
814
+ block_size: ``[block_n, block_k]`` weight quantization block dimensions.
815
+ output_dtype: dtype of the returned tensor (default: ``torch.float32``).
816
+
817
+ Returns:
818
+ Output tensor ``[..., M, N]`` in ``output_dtype``.
819
+ """
820
+ return ops.w8a8_block_static_fp8_matmul(A, B, Bs, As, block_size, output_dtype)
821
+
822
+
823
+ def w8a8_tensor_dynamic_fp8_matmul(
824
+ A: torch.Tensor,
825
+ B: torch.Tensor,
826
+ Bs: torch.Tensor,
827
+ output_dtype: torch.dtype | None = None,
828
+ ) -> torch.Tensor:
829
+ """Tensor-scale W8A8 FP8 matmul with fused activation quantization.
830
+
831
+ Computes ``C = A @ B.T`` with raw bf16/fp16/fp32 ``A`` quantized to FP8
832
+ per-row (one scale per token) before the dot.
833
+
834
+ Args:
835
+ A: Raw activation tensor ``[M, K]`` in bf16/fp16/fp32.
836
+ B: FP8 weight tensor ``[N, K]`` in ``float8_e4m3fn``.
837
+ Bs: Single weight scale, scalar or ``[1]``.
838
+ output_dtype: dtype of the returned tensor.
839
+ """
840
+ return ops.w8a8_tensor_dynamic_fp8_matmul(A, B, Bs, output_dtype)
841
+
842
+
843
+ def w4a8_mx_dynamic_fp4_matmul(
844
+ A: torch.Tensor,
845
+ B: torch.Tensor,
846
+ Bs: torch.Tensor,
847
+ output_dtype: torch.dtype | None = None,
848
+ ) -> torch.Tensor:
849
+ """MXFP4 (W4A8) matmul with fused activation quantization.
850
+
851
+ Computes ``C = A @ B.T`` with bf16/fp16/fp32 ``A`` quantized to FP8 (E4M3)
852
+ inline and packed FP4 (E2M1) weights; both scales are UE8M0 at K-group
853
+ granularity 32 (the MX-format spec). Tile shape (BLOCK_SIZE_N, BLOCK_SIZE_K)
854
+ is autotuned — FP4 has no caller-controlled quantization block.
855
+
856
+ Args:
857
+ A: Raw activations ``[M, K]`` in bf16/fp16/fp32.
858
+ B: Packed FP4 weights ``[N, K // 2]`` (``int8``, two codes per byte).
859
+ Bs: UE8M0 weight scales ``[N, K // 32]``.
860
+ output_dtype: dtype of the returned tensor (default ``bfloat16``).
861
+ """
862
+ return ops.w4a8_mx_dynamic_fp4_matmul(A, B, Bs, output_dtype)
863
+
864
+
865
+ def w8a8_mx_dynamic_fp8_matmul(
866
+ A: torch.Tensor,
867
+ B: torch.Tensor,
868
+ Bs: torch.Tensor,
869
+ output_dtype: torch.dtype | None = None,
870
+ ) -> torch.Tensor:
871
+ """MXFP8 W8A8 matmul (E4M3 × E4M3, UE8M0 group-32 scales) with fused act quant.
872
+
873
+ Computes ``C = A @ B.T`` with bf16/fp16/fp32 ``A`` quantized inline to E4M3
874
+ against per-row, per-32-K-group UE8M0 scales, and E4M3 weights with their own
875
+ UE8M0 K-group scales. Both scales feed ``tl.dot_scaled`` (the MX scaled MMA),
876
+ unlike ``w8a8_block_dynamic_fp8_matmul`` which applies 128×128 block scales in
877
+ software. Tile shape is autotuned; MX scale granularity is fixed at 32.
878
+
879
+ Args:
880
+ A: Raw activations ``[M, K]`` in bf16/fp16/fp32.
881
+ B: E4M3 weights ``[N, K]`` (``float8_e4m3fn``, unpacked).
882
+ Bs: UE8M0 weight scales ``[N, K // 32]`` (``float8_e8m0fnu``).
883
+ output_dtype: dtype of the returned tensor (default ``bfloat16``).
884
+ """
885
+ return ops.w8a8_mx_dynamic_fp8_matmul(A, B, Bs, output_dtype)
886
+
887
+
888
+ def matmul_2d(
889
+ A: torch.Tensor,
890
+ B: torch.Tensor,
891
+ Bs: torch.Tensor,
892
+ block_size: list[int] | None,
893
+ output_dtype: torch.dtype | None = None,
894
+ activation_scale: torch.Tensor | None = None,
895
+ ) -> torch.Tensor:
896
+ """Quantized matmul dispatcher (W8A8 FP8 or W4A8 FP4).
897
+
898
+ ``A`` is always raw bf16/fp16/fp32; quantization is fused into every path.
899
+ With ``activation_scale`` set, the kernel uses that per-tensor scalar
900
+ (static quant); otherwise it computes its own scale from ``A`` (dynamic).
901
+
902
+ ``output_dtype`` defaults to ``A.dtype``.
903
+
904
+ Routes by weight dtype and ``block_size``:
905
+ - ``B.dtype == int8`` (packed FP4) → ``w4a8_mx_dynamic_fp4_matmul``
906
+ (``block_size`` is ignored; FP4 scale granularity is fixed at 32 and
907
+ tile sizes are autotuned).
908
+ - ``B.dtype == float8_e4m3fn`` with UE8M0 group-32 ``Bs`` (shape ``[N, K//32]``)
909
+ → ``w8a8_mx_dynamic_fp8_matmul`` (MX scaled MMA; ``block_size`` ignored).
910
+ - ``block_size`` None or full ``[N, K]`` → ``w8a8_tensor_dynamic_fp8_matmul``.
911
+ - otherwise → ``w8a8_block_dynamic_fp8_matmul`` (or its static variant when
912
+ ``activation_scale`` is given).
913
+ """
914
+ if activation_scale is not None:
915
+ if B.dtype == torch.int8:
916
+ raise NotImplementedError(
917
+ "static activation_scale is not supported on the FP4 path"
918
+ )
919
+ if block_size is None or (
920
+ block_size[0] == B.size(0) and block_size[1] == B.size(1)
921
+ ):
922
+ raise NotImplementedError(
923
+ "static activation_scale requires block-wise weights, "
924
+ "not tensor-mode (block_size None or full [N, K])"
925
+ )
926
+ return w8a8_block_static_fp8_matmul(
927
+ A, B, Bs, activation_scale, block_size, output_dtype
928
+ )
929
+
930
+ if is_mxfp4(B, Bs):
931
+ return w4a8_mx_dynamic_fp4_matmul(A, B, Bs, output_dtype)
932
+
933
+ if is_mxfp8(B, Bs):
934
+ return w8a8_mx_dynamic_fp8_matmul(A, B, Bs, output_dtype)
935
+
936
+ if block_size is None or (
937
+ block_size[0] == B.size(0) and block_size[1] == B.size(1)
938
+ ):
939
+ return w8a8_tensor_dynamic_fp8_matmul(A, B, Bs, output_dtype)
940
+
941
+ return w8a8_block_dynamic_fp8_matmul(A, B, Bs, block_size, output_dtype)
build/torch-rocm/metadata.json ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "finegrained-fp8",
3
+ "id": "_finegrained_fp8_rocm_846165b",
4
+ "version": 3,
5
+ "license": "Apache-2.0",
6
+ "python-depends": [],
7
+ "backend": {
8
+ "type": "rocm"
9
+ },
10
+ "digest": {
11
+ "algorithm": "sha256",
12
+ "files": {
13
+ "__init__.py": "jOglX0eto6QNL9D8T9DzKIb3XYUF5vZAjvEL32MCAHY=",
14
+ "_ops.py": "2e6XSotI60yh7+FU07X7EGCLbnBQRgMN9Kh06vIw9CY=",
15
+ "batched.py": "/ZuVQYNILMt6KBM321fgXxhFRKQM9tMWvvfAEKZY42U=",
16
+ "finegrained_fp8/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY=",
17
+ "grouped.py": "EiotInBQUPtX35Ms+eEZ6b7YyWcxhWTWGq3EiMN0fMs=",
18
+ "matmul.py": "AfyGST+Lrr8AjWqcIx4txrekpKH4iwyS8177TiyeW/M=",
19
+ "utils.py": "pJ6NJrMMz/mSHXZakTmcXW/ZgQ91YnphRk8PVa8yj+U="
20
+ }
21
+ }
22
+ }
build/torch-rocm/metadata.json.sigstore ADDED
@@ -0,0 +1 @@
 
 
1
+ {"mediaType":"application/vnd.dev.sigstore.bundle.v0.3+json","verificationMaterial":{"certificate":{"rawBytes":"MIIHSzCCBtKgAwIBAgIUePTBhgrVPrG0jyrZOpoWjJWMrN4wCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjYwNjE3MTMxNDA0WhcNMjYwNjE3MTMyNDA0WjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE0VUnz693BcejXkNTO62Z4hPYtmgg22tjv3G+w7jq79WyMCmcSVaofu2ZJlOMsA9v36EETYobwETef0B9lLMqAKOCBfEwggXtMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUWg0Gm4lpyM98cT8KBL1cWUOoUKAwHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4YZD8wawYDVR0RAQH/BGEwX4ZdaHR0cHM6Ly9naXRodWIuY29tL2h1Z2dpbmdmYWNlL2tlcm5lbHMtY29tbXVuaXR5Ly5naXRodWIvd29ya2Zsb3dzL2J1aWxkLnlhbWxAcmVmcy9oZWFkcy9tYWluMDkGCisGAQQBg78wAQEEK2h0dHBzOi8vdG9rZW4uYWN0aW9ucy5naXRodWJ1c2VyY29udGVudC5jb20wHwYKKwYBBAGDvzABAgQRd29ya2Zsb3dfZGlzcGF0Y2gwNgYKKwYBBAGDvzABAwQoODQ2MTY1YjExZDU3MTA1ODI5MmQ1NDRiNDIyMzQ2MzE3ZGUxMDJkZjATBgorBgEEAYO/MAEEBAVCdWlsZDArBgorBgEEAYO/MAEFBB1odWdnaW5nZmFjZS9rZXJuZWxzLWNvbW11bml0eTAdBgorBgEEAYO/MAEGBA9yZWZzL2hlYWRzL21haW4wOwYKKwYBBAGDvzABCAQtDCtodHRwczovL3Rva2VuLmFjdGlvbnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tMG0GCisGAQQBg78wAQkEXwxdaHR0cHM6Ly9naXRodWIuY29tL2h1Z2dpbmdmYWNlL2tlcm5lbHMtY29tbXVuaXR5Ly5naXRodWIvd29ya2Zsb3dzL2J1aWxkLnlhbWxAcmVmcy9oZWFkcy9tYWluMDgGCisGAQQBg78wAQoEKgwoODQ2MTY1YjExZDU3MTA1ODI5MmQ1NDRiNDIyMzQ2MzE3ZGUxMDJkZjAbBgorBgEEAYO/MAELBA0MC3NlbGYtaG9zdGVkMEAGCisGAQQBg78wAQwEMgwwaHR0cHM6Ly9naXRodWIuY29tL2h1Z2dpbmdmYWNlL2tlcm5lbHMtY29tbXVuaXR5MDgGCisGAQQBg78wAQ0EKgwoODQ2MTY1YjExZDU3MTA1ODI5MmQ1NDRiNDIyMzQ2MzE3ZGUxMDJkZjAfBgorBgEEAYO/MAEOBBEMD3JlZnMvaGVhZHMvbWFpbjAaBgorBgEEAYO/MAEPBAwMCjEwNzE0NzU1MjkwLgYKKwYBBAGDvzABEAQgDB5odHRwczovL2dpdGh1Yi5jb20vaHVnZ2luZ2ZhY2UwGAYKKwYBBAGDvzABEQQKDAgyNTcyMDc0MzBtBgorBgEEAYO/MAESBF8MXWh0dHBzOi8vZ2l0aHViLmNvbS9odWdnaW5nZmFjZS9rZXJuZWxzLWNvbW11bml0eS8uZ2l0aHViL3dvcmtmbG93cy9idWlsZC55YW1sQHJlZnMvaGVhZHMvbWFpbjA4BgorBgEEAYO/MAETBCoMKDg0NjE2NWIxMWQ1NzEwNTgyOTJkNTQ0YjQyMjM0NjMxN2RlMTAyZGYwIQYKKwYBBAGDvzABFAQTDBF3b3JrZmxvd19kaXNwYXRjaDBkBgorBgEEAYO/MAEVBFYMVGh0dHBzOi8vZ2l0aHViLmNvbS9odWdnaW5nZmFjZS9rZXJuZWxzLWNvbW11bml0eS9hY3Rpb25zL3J1bnMvMjc2OTEyMTUxMzkvYXR0ZW1wdHMvMTAWBgorBgEEAYO/MAEWBAgMBnB1YmxpYzBGBgorBgEEAYO/MAEYBDgMNnJlcG86aHVnZ2luZ2ZhY2Uva2VybmVscy1jb21tdW5pdHk6cmVmOnJlZnMvaGVhZHMvbWFpbjCBiwYKKwYBBAHWeQIEAgR9BHsAeQB3AN09MGrGxxEyYxkeHJlnNwKiSl643jyt/4eKcoAvKe6OAAABntW3q3sAAAQDAEgwRgIhAJbEJ3yIOVCYyNmuAJfsYvWyfk+1pDFQY8XlT3ChiK4jAiEA3eQjC77zGyucbZa6WiNYOz2RO+RYirxBVNgK4f5YQ38wCgYIKoZIzj0EAwMDZwAwZAIwG8x9gZM5WmgUIeDX2l+fLc8BMs/EZFaKD/MTa15tB3hpVYaqTyom3dQSIglzETtxAjAbp0rCeLzhA5nmE+ctKvOwCvxYpD06P51lfdqcUHrgSoK0hNCk26zd8j2S5sAkITM="},"tlogEntries":[{"logIndex":"1851564527","logId":{"keyId":"wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="},"kindVersion":{"kind":"hashedrekord","version":"0.0.1"},"integratedTime":"1781702045","inclusionPromise":{"signedEntryTimestamp":"MEUCIQCXnosi1Fkyem6EcQ7sHvaUqAmtQuF+6Rqe5C3zf6nAzgIgWFY8FhIFCZtR/KGwJjUXFmLG/q6h2SxlBmN4MD2SHUE="},"inclusionProof":{"logIndex":"1729660265","rootHash":"bA+ZSMR5ebA+bxzst9Muh7lqhA3+EZn+j04BxfvmN9w=","treeSize":"1729660347","hashes":["G0kerrCVnmrmJIucj6z35eeb9nQSM/bHQgcR4XUI+h0=","lF6pG0uLVoW8P9IFRM9FuVBL878Tb4de5y8Nos8MY1U=","0us52j044UZxn5OwNPtbzHCxxWeaCW5w/tP1ey0bbdg=","zrWsV+4wyv1S7Qp+yH/FBzQ1C+W8SYogESc7f1QzoH0=","1E3pfOGXxKWEmoIzMvVEkmCUtzhElCK1v5f/rXUgKto=","inihgnOSx1WccBIuIvfshXFGfocE8mH19j08bmnPgMQ=","Xiy1HnHrEBvLAb8qevuD1odTsmP2TEbHNBPhdHPs738=","G75SHivXVw4HNLVI6MwIJ8rOpL3N5b3UTo+0mwcasrU=","/MUwk7ZfPoiJVazjBloofRJ6hS2B8Az3apT2GBKtJ3g=","DT5JAn4B3Dm8gmFcDKslYJrA/3Q9NNFCX4dpHJUGPEU=","cRNcuWZEC/h+ACT/IKS0cgSD29B0pvP9++uNHzrSlj8=","4up1iqXVaWkl4o3JcV/XPRm3hVSZfjTk1Iaqp+cfi/o=","M9+xE/67JCQM3UvBz/mjGSEhOSJ8QCD3Xi7mH8EKFEk=","mqM7J+i75IpuD09QejiUBjeH85AZa+fSm4RXXFmj3lI=","72FC5FYLhxB4a4iiC956o0B/fT54ip4R41vsw2QBKtU=","lYGQ9ibwC8+smMkPQ6TchJm3H9Nc/aTYLfdRacGFChw=","daxmZaajRpZV+JxHiOYZhJBiSKN5ucqjh2WnGbHhirw=","DOCeoSMovIvLExkhIvisow9AuNXgeWs4ECkyR6EcqYU="],"checkpoint":{"envelope":"rekor.sigstore.dev - 1193050959916656506\n1729660347\nbA+ZSMR5ebA+bxzst9Muh7lqhA3+EZn+j04BxfvmN9w=\n\n— rekor.sigstore.dev wNI9ajBFAiEAuxuJjQEyMmOUsx8f2UggYVM4d36K6WVhM8FqnsaH+0wCIE0MIegCiydXCBDLCY7uPBMp08uzZiISGY+h/oc+gsp/\n"}},"canonicalizedBody":"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiJiMWZhMGY0YWIyY2ZhNzJkNzc4YzUzOTMxNTNlYzQ1ZGUwZDMzNjFhMDFiNTQ1NjNlNjQyZTkxNWFlYjU4NjZmIn19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FVUNJRy9lenNqWnRlTWhBY1U5TmhMWURNUDFrcllDd0t6bkF3LzVaaUZnbm5VbkFpRUEyMnJhcXZzMm92OGtIcnJJa2JteDhCSnVPU0g4dmh0WmlQMnRBTUdSa0NzPSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVaFRla05EUW5STFowRjNTVUpCWjBsVlpWQlVRbWhuY2xaUWNrY3dhbmx5V2s5d2IxZHFTbGROY2s0MGQwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcFpkMDVxUlROTlZFMTRUa1JCTUZkb1kwNU5hbGwzVG1wRk0wMVVUWGxPUkVFd1YycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVV3VmxWdWVqWTVNMEpqWldwWWEwNVVUell5V2pSb1VGbDBiV2RuTWpKMGFuWXpSeXNLZHpkcWNUYzVWM2xOUTIxalUxWmhiMloxTWxwS2JFOU5jMEU1ZGpNMlJVVlVXVzlpZDBWVVpXWXdRamxzVEUxeFFVdFBRMEptUlhkbloxaDBUVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXUkNaMDVXU0ZFMFJVWm5VVlZYWnpCSENtMDBiSEI1VFRrNFkxUTRTMEpNTVdOWFZVOXZWVXRCZDBoM1dVUldVakJxUWtKbmQwWnZRVlV6T1ZCd2VqRlphMFZhWWpWeFRtcHdTMFpYYVhocE5Ga0tXa1E0ZDJGM1dVUldVakJTUVZGSUwwSkhSWGRZTkZwa1lVaFNNR05JVFRaTWVUbHVZVmhTYjJSWFNYVlpNamwwVERKb01Wb3laSEJpYldSdFdWZE9iQXBNTW5Sc1kyMDFiR0pJVFhSWk1qbDBZbGhXZFdGWVVqVk1lVFZ1WVZoU2IyUlhTWFprTWpsNVlUSmFjMkl6WkhwTU1rb3hZVmQ0YTB4dWJHaGlWM2hCQ21OdFZtMWplVGx2V2xkR2EyTjVPWFJaVjJ4MVRVUnJSME5wYzBkQlVWRkNaemM0ZDBGUlJVVkxNbWd3WkVoQ2VrOXBPSFprUnpseVdsYzBkVmxYVGpBS1lWYzVkV041Tlc1aFdGSnZaRmRLTVdNeVZubFpNamwxWkVkV2RXUkROV3BpTWpCM1NIZFpTMHQzV1VKQ1FVZEVkbnBCUWtGblVWSmtNamw1WVRKYWN3cGlNMlJtV2tkc2VtTkhSakJaTW1kM1RtZFpTMHQzV1VKQ1FVZEVkbnBCUWtGM1VXOVBSRkV5VFZSWk1WbHFSWGhhUkZVelRWUkJNVTlFU1RWTmJWRXhDazVFVW1sT1JFbDVUWHBSTWsxNlJUTmFSMVY0VFVSS2ExcHFRVlJDWjI5eVFtZEZSVUZaVHk5TlFVVkZRa0ZXUTJSWGJITmFSRUZ5UW1kdmNrSm5SVVVLUVZsUEwwMUJSVVpDUWpGdlpGZGtibUZYTlc1YWJVWnFXbE01Y2xwWVNuVmFWM2g2VEZkT2RtSlhNVEZpYld3d1pWUkJaRUpuYjNKQ1owVkZRVmxQTHdwTlFVVkhRa0U1ZVZwWFducE1NbWhzV1ZkU2Vrd3lNV2hoVnpSM1QzZFpTMHQzV1VKQ1FVZEVkbnBCUWtOQlVYUkVRM1J2WkVoU2QyTjZiM1pNTTFKMkNtRXlWblZNYlVacVpFZHNkbUp1VFhWYU1td3dZVWhXYVdSWVRteGpiVTUyWW01U2JHSnVVWFZaTWpsMFRVY3dSME5wYzBkQlVWRkNaemM0ZDBGUmEwVUtXSGQ0WkdGSVVqQmpTRTAyVEhrNWJtRllVbTlrVjBsMVdUSTVkRXd5YURGYU1tUndZbTFrYlZsWFRteE1NblJzWTIwMWJHSklUWFJaTWpsMFlsaFdkUXBoV0ZJMVRIazFibUZZVW05a1YwbDJaREk1ZVdFeVduTmlNMlI2VERKS01XRlhlR3RNYm14b1lsZDRRV050Vm0xamVUbHZXbGRHYTJONU9YUlpWMngxQ2sxRVowZERhWE5IUVZGUlFtYzNPSGRCVVc5RlMyZDNiMDlFVVRKTlZGa3hXV3BGZUZwRVZUTk5WRUV4VDBSSk5VMXRVVEZPUkZKcFRrUkplVTE2VVRJS1RYcEZNMXBIVlhoTlJFcHJXbXBCWWtKbmIzSkNaMFZGUVZsUEwwMUJSVXhDUVRCTlF6Tk9iR0pIV1hSaFJ6bDZaRWRXYTAxRlFVZERhWE5IUVZGUlFncG5OemgzUVZGM1JVMW5kM2RoU0ZJd1kwaE5Oa3g1T1c1aFdGSnZaRmRKZFZreU9YUk1NbWd4V2pKa2NHSnRaRzFaVjA1c1RESjBiR050Tld4aVNFMTBDbGt5T1hSaVdGWjFZVmhTTlUxRVowZERhWE5IUVZGUlFtYzNPSGRCVVRCRlMyZDNiMDlFVVRKTlZGa3hXV3BGZUZwRVZUTk5WRUV4VDBSSk5VMXRVVEVLVGtSU2FVNUVTWGxOZWxFeVRYcEZNMXBIVlhoTlJFcHJXbXBCWmtKbmIzSkNaMFZGUVZsUEwwMUJSVTlDUWtWTlJETktiRnB1VFhaaFIxWm9Xa2hOZGdwaVYwWndZbXBCWVVKbmIzSkNaMFZGUVZsUEwwMUJSVkJDUVhkTlEycEZkMDU2UlRCT2VsVXhUV3ByZDB4bldVdExkMWxDUWtGSFJIWjZRVUpGUVZGbkNrUkNOVzlrU0ZKM1kzcHZka3d5WkhCa1IyZ3hXV2sxYW1JeU1IWmhTRlp1V2pKc2RWb3lXbWhaTWxWM1IwRlpTMHQzV1VKQ1FVZEVkbnBCUWtWUlVVc0tSRUZuZVU1VVkzbE5SR013VFhwQ2RFSm5iM0pDWjBWRlFWbFBMMDFCUlZOQ1JqaE5XRmRvTUdSSVFucFBhVGgyV2pKc01HRklWbWxNYlU1MllsTTVid3BrVjJSdVlWYzFibHB0Um1wYVV6bHlXbGhLZFZwWGVIcE1WMDUyWWxjeE1XSnRiREJsVXpoMVdqSnNNR0ZJVm1sTU0yUjJZMjEwYldKSE9UTmplVGxwQ21SWGJITmFRelUxV1ZjeGMxRklTbXhhYmsxMllVZFdhRnBJVFhaaVYwWndZbXBCTkVKbmIzSkNaMFZGUVZsUEwwMUJSVlJDUTI5TlMwUm5NRTVxUlRJS1RsZEplRTFYVVRGT2VrVjNUbFJuZVU5VVNtdE9WRkV3V1dwUmVVMXFUVEJPYWsxNFRqSlNiRTFVUVhsYVIxbDNTVkZaUzB0M1dVSkNRVWRFZG5wQlFncEdRVkZVUkVKR00ySXpTbkphYlhoMlpERTVhMkZZVG5kWldGSnFZVVJDYTBKbmIzSkNaMFZGUVZsUEwwMUJSVlpDUmxsTlZrZG9NR1JJUW5wUGFUaDJDbG95YkRCaFNGWnBURzFPZG1KVE9XOWtWMlJ1WVZjMWJscHRSbXBhVXpseVdsaEtkVnBYZUhwTVYwNTJZbGN4TVdKdGJEQmxVemxvV1ROU2NHSXlOWG9LVEROS01XSnVUWFpOYW1NeVQxUkZlVTFVVlhoTmVtdDJXVmhTTUZwWE1YZGtTRTEyVFZSQlYwSm5iM0pDWjBWRlFWbFBMMDFCUlZkQ1FXZE5RbTVDTVFwWmJYaHdXWHBDUjBKbmIzSkNaMFZGUVZsUEwwMUJSVmxDUkdkTlRtNUtiR05IT0RaaFNGWnVXakpzZFZveVdtaFpNbFYyWVRKV2VXSnRWbk5qZVRGcUNtSXlNWFJrVnpWd1pFaHJObU50Vm0xUGJrcHNXbTVOZG1GSFZtaGFTRTEyWWxkR2NHSnFRMEpwZDFsTFMzZFpRa0pCU0ZkbFVVbEZRV2RTT1VKSWMwRUtaVkZDTTBGT01EbE5SM0pIZUhoRmVWbDRhMlZJU214dVRuZExhVk5zTmpRemFubDBMelJsUzJOdlFYWkxaVFpQUVVGQlFtNTBWek54TTNOQlFVRlJSQXBCUldkM1VtZEphRUZLWWtWS00zbEpUMVpEV1hsT2JYVkJTbVp6V1haWGVXWnJLekZ3UkVaUldUaFliRlF6UTJocFN6UnFRV2xGUVRObFVXcEROemQ2Q2tkNWRXTmlXbUUyVjJsT1dVOTZNbEpQSzFKWmFYSjRRbFpPWjBzMFpqVlpVVE00ZDBObldVbExiMXBKZW1vd1JVRjNUVVJhZDBGM1drRkpkMGM0ZURrS1oxcE5OVmR0WjFWSlpVUllNbXdyWmt4ak9FSk5jeTlGV2taaFMwUXZUVlJoTVRWMFFqTm9jRlpaWVhGVWVXOXRNMlJSVTBsbmJIcEZWSFI0UVdwQllncHdNSEpEWlV4NmFFRTFibTFGSzJOMFMzWlBkME4yZUZsd1JEQTJVRFV4Ykdaa2NXTlZTSEpuVTI5TE1HaE9RMnN5Tm5wa09Hb3lVelZ6UVd0SlZFMDlDaTB0TFMwdFJVNUVJRU5GVWxSSlJrbERRVlJGTFMwdExTMEsifX19fQ=="}],"timestampVerificationData":{"rfc3161Timestamps":[{"signedTimestamp":"MIICyDADAgEAMIICvwYJKoZIhvcNAQcCoIICsDCCAqwCAQMxDTALBglghkgBZQMEAgEwgbcGCyqGSIb3DQEJEAEEoIGnBIGkMIGhAgEBBgkrBgEEAYO/MAIwMTANBglghkgBZQMEAgEFAAQgirsf3iN2RuZt8kLZKD5VUGqJ0tF1saNu4OqNKmEEAQkCFEvPI1QROe8iGgplF9//L1TeLChVGA8yMDI2MDYxNzEzMTQwNFowAwIBAaAypDAwLjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MRUwEwYDVQQDEwxzaWdzdG9yZS10c2GgADGCAdowggHWAgEBMFEwOTEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MSAwHgYDVQQDExdzaWdzdG9yZS10c2Etc2VsZnNpZ25lZAIUOhNULwyQYe68wUMvy4qOiyojiwwwCwYJYIZIAWUDBAIBoIH8MBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAcBgkqhkiG9w0BCQUxDxcNMjYwNjE3MTMxNDA0WjAvBgkqhkiG9w0BCQQxIgQg4zyqLLBaOA3qBIJ3rbimqn9CbsEwnKlOhCtR7uq4B7cwgY4GCyqGSIb3DQEJEAIvMX8wfTB7MHkEIIX5J7wHq2LKw7RDVsEO/IGyxog/2nq55thw2dE6zQW3MFUwPaQ7MDkxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEgMB4GA1UEAxMXc2lnc3RvcmUtdHNhLXNlbGZzaWduZWQCFDoTVC8MkGHuvMFDL8uKjosqI4sMMAoGCCqGSM49BAMCBGYwZAIwT9UpLg/DWWo/eugeIjzk42GOVBb1Gb6MAuIheAGnW2fNlhwC+NrHfPPdktaiStmtAjBmoKRxlPXAqSOmvHg1DYzFdMGSGdm2JJ1wfV3/oYNMuymIIXPWookWoIWZQHgKKzE="}]}},"messageSignature":{"messageDigest":{"algorithm":"SHA2_256","digest":"sfoPSrLPpy13jFOTFT7EXeDTNhoBtUVj5kLpFa61hm8="},"signature":"MEUCIG/ezsjZteMhAcU9NhLYDMP1krYCwKznAw/5ZiFgnnUnAiEA22raqvs2ov8kHrrIkbmx8BJuOSH8vhtZiP2tAMGRkCs="}}
build/torch-rocm/utils.py ADDED
@@ -0,0 +1,269 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2026 The HuggingFace Inc. team. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import functools
16
+ from contextlib import contextmanager
17
+
18
+ import torch
19
+ import triton
20
+ import triton.language as tl
21
+ from torch.library import triton_op, wrap_triton
22
+
23
+ from ._ops import add_op_namespace_prefix, ops
24
+
25
+ # ── Format constants ──────────────────────────────────────────────────────────
26
+
27
+ # FP8 (E4M3) is the main format for weights and activations;
28
+ FP8_DTYPE = torch.float8_e4m3fn
29
+ # FP4 (E2M1) packs two 4-bit nibbles per byte. MX formats (MXFP4 weights, MXFP8
30
+ # E4M3 weights/activations) share one UE8M0 scale per 32-element K-group — the OCP
31
+ # MX block size, consumed by ``tl.dot_scaled``. Format constants, not tunables.
32
+ NIBBLES_PER_BYTE = 2
33
+ MX_SCALE_GROUP_K = 32
34
+
35
+
36
+ # ── Host-side helpers ─────────────────────────────────────────────────────────
37
+
38
+
39
+ @contextmanager
40
+ def device_context(device: torch.device):
41
+ """Context manager that sets the active device for any backend (cuda, xpu, etc.)."""
42
+ backend = getattr(torch, device.type, None)
43
+ if backend is not None and hasattr(backend, "device"):
44
+ with backend.device(device):
45
+ yield
46
+ else:
47
+ yield
48
+
49
+
50
+ def ue8m0_as_uint8(scale: torch.Tensor) -> torch.Tensor:
51
+ """View UE8M0 (``float8_e8m0fnu``) weight scales as ``uint8`` for the Triton
52
+ binder, which doesn't recognize the dtype; kernels decode ``2^(exp-127)``
53
+ inline. fp32 (non-UE8M0) scales pass through unchanged."""
54
+ return scale.view(torch.uint8) if scale.dtype == torch.float8_e8m0fnu else scale
55
+
56
+
57
+ def is_mxfp8(weight: torch.Tensor, scale: torch.Tensor) -> bool:
58
+ """MXFP8 weight/scale pair: E4M3 weights with UE8M0 group-32 scales — last dim
59
+ ``scale.shape[-1] == weight.shape[-1] // MX_SCALE_GROUP_K``, matching leading dims.
60
+ Works for 2D ``(N, K)`` and 3D ``(E, N, K)`` weights. The group-32 layout is what
61
+ separates MXFP8 from 128-block FP8 (which may also carry UE8M0 scales)."""
62
+ return (
63
+ weight.dtype == torch.float8_e4m3fn
64
+ and scale.dtype == torch.float8_e8m0fnu
65
+ and scale.ndim == weight.ndim
66
+ and scale.shape[:-1] == weight.shape[:-1]
67
+ and scale.shape[-1] == weight.shape[-1] // MX_SCALE_GROUP_K
68
+ )
69
+
70
+
71
+ def is_mxfp4(weight: torch.Tensor, scale: torch.Tensor) -> bool:
72
+ """MXFP4 weight/scale pair: packed E2M1 weights (``int8``, two codes/byte) with
73
+ UE8M0 group-32 scales — ``scale.shape[-1] == weight.shape[-1] * NIBBLES_PER_BYTE //
74
+ MX_SCALE_GROUP_K`` (unpacked K = ``2 * K_half``), matching leading dims. 2D or 3D."""
75
+ return (
76
+ weight.dtype == torch.int8
77
+ and scale.dtype == torch.float8_e8m0fnu
78
+ and scale.ndim == weight.ndim
79
+ and scale.shape[:-1] == weight.shape[:-1]
80
+ and scale.shape[-1] == weight.shape[-1] * NIBBLES_PER_BYTE // MX_SCALE_GROUP_K
81
+ )
82
+
83
+
84
+ def adaptive_block_size_m(target_m: int) -> int:
85
+ """Smallest power-of-2 >= ``target_m``, floored at 16 and capped at 128.
86
+
87
+ Used by all matmul wrappers (single / batched / grouped) to size the M tile
88
+ to the workload — small per-expert M wants smaller tiles, large M caps out
89
+ at 128 to keep register pressure bounded. Pass ``M`` for single matmul, or
90
+ ``(S + E - 1) // E`` (avg tokens per expert) for batched/grouped.
91
+ """
92
+ return min(max(triton.next_power_of_2(target_m), 16), 128)
93
+
94
+
95
+ @functools.cache
96
+ def get_active_device_type() -> str:
97
+ """Active torch device type for the current Triton backend (``"cuda"``, ``"xpu"``, ...).
98
+
99
+ Falls back to ``"cuda"`` when no driver is loaded — Triton raises
100
+ ``RuntimeError: 0 active drivers ([])`` on driverless build boxes, and the
101
+ autotune-config builder is evaluated at module-import time under the
102
+ ``@triton.autotune`` decorator (no kernel launches there, so the default is
103
+ only used to shape the config list).
104
+ """
105
+ try:
106
+ return triton.runtime.driver.active.get_active_torch_device().type
107
+ except RuntimeError:
108
+ return "cuda"
109
+
110
+
111
+ def get_accelerator_autotuning_configs(*, with_block_sizes: bool = False):
112
+ """Autotune search grid for the current accelerator.
113
+
114
+ ``num_warps``, ``num_stages`` and ``blocks`` (the ``(BLOCK_SIZE_N, BLOCK_SIZE_K)``
115
+ tile shapes) are fixed up front from ``(is_xpu, with_block_sizes)``, then crossed
116
+ into the config list.
117
+
118
+ ``with_block_sizes=True`` sweeps the tile: used by kernels that have no caller
119
+ ``block_size`` to fix it — the MX ``tl.dot_scaled`` paths AND the tensor-dynamic
120
+ FP8 paths. ``with_block_sizes=False`` emits a single empty meta-dict (block-scaled
121
+ kernels take the tile from the caller's ``block_size``).
122
+
123
+ The CUDA tile set is a data-driven prune of a B200 sweep across single (BM=128),
124
+ grouped MoE (BM=16/64) and decode (BM=1): winners only ever used these 4 tiles,
125
+ num_warps in {4,8,16}, num_stages in {2,3} (warps=2, stages=4 and 256x256 never
126
+ won) — 108 → 24. (Tuned on dot_scaled; tensor-dynamic tl.dot reuses it.)
127
+ """
128
+ is_xpu = get_active_device_type() == "xpu"
129
+ if with_block_sizes:
130
+ num_stages = [2, 3]
131
+ num_warps = [8, 16] if is_xpu else [4, 8, 16]
132
+ tiles = (
133
+ [(128, 128)] if is_xpu else [(128, 128), (256, 128), (128, 64), (64, 256)]
134
+ )
135
+ blocks = [{"BLOCK_SIZE_N": bn, "BLOCK_SIZE_K": bk} for bn, bk in tiles]
136
+ else:
137
+ num_stages = [2, 3, 4]
138
+ num_warps = [8, 16] if is_xpu else [2, 4, 8, 16]
139
+ blocks = [{}]
140
+
141
+ return [
142
+ triton.Config(b, num_warps=w, num_stages=s)
143
+ for b in blocks
144
+ for w in num_warps
145
+ for s in num_stages
146
+ ]
147
+
148
+
149
+ # ── Triton-side helpers (inlined by ``@triton.jit`` callers) ──────────────────
150
+
151
+
152
+ @triton.jit
153
+ def fp8_act_quant_inline(a_raw):
154
+ """Inline FP8 (E4M3) activation quant for the W8A8 block-scale path.
155
+
156
+ Per-row amax → fp32 scale ``amax/448`` (floored at 1e-12 against zero rows)
157
+ → cast values to FP8. Returns ``(a_fp8, a_s)`` with shapes ``(M, K)`` and
158
+ ``(M,)``.
159
+ """
160
+ a_s = tl.max(tl.abs(a_raw), axis=1) / 448.0
161
+ a_fp8 = (a_raw / tl.maximum(a_s[:, None], 1e-12)).to(tl.float8e4nv)
162
+ return a_fp8, a_s
163
+
164
+
165
+ @triton.jit
166
+ def mx_act_quant_inline(
167
+ a_raw,
168
+ BLOCK_SIZE_M: tl.constexpr,
169
+ BLOCK_SIZE_K: tl.constexpr,
170
+ SCALE_GROUP_K: tl.constexpr,
171
+ ):
172
+ """Inline E4M3 activation quant for the MX paths (W4A8 MXFP4 / W8A8 MXFP8).
173
+
174
+ Per-row, per-K-group amax → UE8M0 scale (ceil to next power-of-2 via the
175
+ mantissa-nonzero bump trick) → cast values to FP8. Returns ``(a_fp8,
176
+ a_scale_u8)`` with shapes ``(M, K)`` and ``(M, K // SCALE_GROUP_K)``.
177
+ """
178
+ a_groups = tl.reshape(
179
+ a_raw, (BLOCK_SIZE_M, BLOCK_SIZE_K // SCALE_GROUP_K, SCALE_GROUP_K)
180
+ )
181
+ a_s_fp32 = tl.max(tl.abs(a_groups), axis=2) / 448.0
182
+ bits = a_s_fp32.to(tl.int32, bitcast=True)
183
+ # ceil_to_ue8m0: bump exponent by 1 when mantissa is non-zero.
184
+ exp_ceil = ((bits >> 23) & 0xFF) + ((bits & 0x7FFFFF) != 0).to(tl.int32)
185
+ exp_ceil = tl.minimum(tl.maximum(exp_ceil, 1), 254)
186
+ a_scale_u8 = exp_ceil.to(tl.uint8)
187
+ a_s_pow2 = (exp_ceil << 23).to(tl.float32, bitcast=True)
188
+ a_fp8 = tl.reshape(
189
+ a_groups / tl.maximum(a_s_pow2[:, :, None], 1e-12),
190
+ (BLOCK_SIZE_M, BLOCK_SIZE_K),
191
+ ).to(tl.float8e4nv)
192
+ return a_fp8, a_scale_u8
193
+
194
+
195
+ @triton.jit
196
+ def decode_ue8m0_scale(scale):
197
+ """Decode a UE8M0 weight scale to fp32: when it was loaded as ``uint8`` exponent
198
+ bits, ``value = 2^(exp - 127)``, built directly as the fp32 bit pattern. fp32
199
+ scales (block-dynamic with float scales) pass through. The dtype branch is a
200
+ compile-time constant, so only the taken path is emitted (single return — Triton
201
+ requires all ``return`` statements to share a type)."""
202
+ if scale.dtype == tl.uint8:
203
+ scale = (scale.to(tl.int32) << 23).to(tl.float32, bitcast=True)
204
+ return scale
205
+
206
+
207
+ # ── fp8_act_quant kernel (used by tensor-mode FP8 wrappers) ───────────────────
208
+
209
+
210
+ @triton.jit
211
+ def _fp8_act_quant_kernel(
212
+ x_ptr, y_ptr, s_ptr, BLOCK_SIZE: tl.constexpr, PADDED_BLOCK: tl.constexpr
213
+ ):
214
+ # ``tl.arange`` needs a power-of-2 length, so iterate over PADDED_BLOCK (the next
215
+ # power of 2) and mask the tail — lets block_size be non-power-of-2 (e.g. a full
216
+ # row K=14336 in tensor-mode). Masked lanes load 0, which can't affect ``amax``.
217
+ pid = tl.program_id(axis=0)
218
+ cols = tl.arange(0, PADDED_BLOCK)
219
+ mask = cols < BLOCK_SIZE
220
+ offs = pid * BLOCK_SIZE + cols
221
+ x = tl.load(x_ptr + offs, mask=mask, other=0.0).to(tl.float32)
222
+ s = tl.max(tl.abs(x)) / 448.0 # float8_e4m3fn max
223
+ y = (x / tl.maximum(s, 1e-12)).to(y_ptr.dtype.element_ty)
224
+ tl.store(y_ptr + offs, y, mask=mask)
225
+ tl.store(s_ptr + pid, s)
226
+
227
+
228
+ @triton_op(add_op_namespace_prefix("fp8_act_quant"), mutates_args=())
229
+ def _fp8_act_quant(
230
+ x: torch.Tensor, block_size: int = 128
231
+ ) -> tuple[torch.Tensor, torch.Tensor]:
232
+ assert x.is_contiguous()
233
+ assert x.shape[-1] % block_size == 0
234
+ y = torch.empty_like(x, dtype=FP8_DTYPE)
235
+ grid = (triton.cdiv(x.numel(), block_size),)
236
+ s = x.new_empty(*x.size()[:-1], x.size(-1) // block_size, dtype=torch.float32)
237
+
238
+ with device_context(x.device):
239
+ wrap_triton(_fp8_act_quant_kernel)[grid](
240
+ x,
241
+ y,
242
+ s,
243
+ BLOCK_SIZE=block_size,
244
+ PADDED_BLOCK=triton.next_power_of_2(block_size),
245
+ )
246
+
247
+ return y, s
248
+
249
+
250
+ def fp8_act_quant(
251
+ x: torch.Tensor, block_size: int = 128
252
+ ) -> tuple[torch.Tensor, torch.Tensor]:
253
+ """Quantize activations to FP8 with per-block dynamic scaling.
254
+
255
+ Splits the last dimension of ``x`` into blocks of ``block_size`` elements,
256
+ computes ``scale = max(|x_block|) / 448`` per block, and quantizes to
257
+ ``float8_e4m3fn``.
258
+
259
+ Args:
260
+ x: Input tensor in bf16/fp16/fp32. Last dimension must be divisible by
261
+ ``block_size`` and the tensor must be contiguous.
262
+ block_size: Number of elements per quantization block (default: 128).
263
+
264
+ Returns:
265
+ A tuple ``(quantized, scales)`` where ``quantized`` has dtype
266
+ ``float8_e4m3fn`` with the same shape as ``x``, and ``scales`` has
267
+ shape ``(*x.shape[:-1], x.shape[-1] // block_size)`` in float32.
268
+ """
269
+ return ops.fp8_act_quant(x, block_size)