kernels-bot commited on
Commit
099314d
·
verified ·
1 Parent(s): 0c9fbb9

Uploaded using `kernel-builder`.

Browse files
build/torch-cuda/__init__.py CHANGED
@@ -1,32 +1,14 @@
1
- from .act_quant import fp8_act_quant
2
- from .batched import (
3
- w8a8_fp8_matmul_batched,
4
- w8a8_block_fp8_matmul_batched,
5
- w8a8_tensor_fp8_matmul_batched,
6
- )
7
- from .grouped import (
8
- w8a8_fp8_matmul_grouped,
9
- w8a8_block_fp8_matmul_grouped,
10
- w8a8_tensor_fp8_matmul_grouped,
11
- )
12
- from .matmul import (
13
- w8a8_fp8_matmul,
14
- w8a8_block_fp8_matmul,
15
- w8a8_tensor_fp8_matmul,
16
- )
17
 
18
  __all__ = [
19
  "fp8_act_quant",
20
- # Single matmul
21
- "w8a8_fp8_matmul",
22
- "w8a8_block_fp8_matmul",
23
- "w8a8_tensor_fp8_matmul",
24
  # Batched matmul
25
- "w8a8_fp8_matmul_batched",
26
- "w8a8_block_fp8_matmul_batched",
27
- "w8a8_tensor_fp8_matmul_batched",
28
  # Grouped matmul
29
- "w8a8_fp8_matmul_grouped",
30
- "w8a8_block_fp8_matmul_grouped",
31
- "w8a8_tensor_fp8_matmul_grouped",
32
  ]
 
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-cuda/_ops.py CHANGED
@@ -22,7 +22,7 @@ def get_backend() -> str:
22
 
23
  def _find_ops_name() -> str:
24
  kernel_name = "finegrained_fp8"
25
- unique_id = "7c5619e"
26
  backend = get_backend()
27
  return f"_{kernel_name}_{backend}_{unique_id}"
28
 
 
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
 
build/torch-cuda/act_quant.py DELETED
@@ -1,73 +0,0 @@
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 .utils import device_context
21
-
22
-
23
- _FP8_DTYPE = torch.float8_e4m3fn
24
-
25
-
26
- # Copied from https://huggingface.co/deepseek-ai/DeepSeek-V3/blob/main/inference/kernel.py
27
- @triton.jit
28
- def _fp8_act_quant_kernel(x_ptr, y_ptr, s_ptr, BLOCK_SIZE: tl.constexpr):
29
- pid = tl.program_id(axis=0)
30
- offs = pid * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE)
31
- x = tl.load(x_ptr + offs).to(tl.float32)
32
- s = tl.max(tl.abs(x)) / 448.0 # float8_e4m3fn max
33
- y = (x / s).to(y_ptr.dtype.element_ty)
34
- tl.store(y_ptr + offs, y)
35
- tl.store(s_ptr + pid, s)
36
-
37
-
38
- @triton_op("finegrained_fp8::fp8_act_quant", mutates_args=())
39
- def _fp8_act_quant(
40
- x: torch.Tensor, block_size: int = 128
41
- ) -> tuple[torch.Tensor, torch.Tensor]:
42
- assert x.is_contiguous()
43
- assert x.shape[-1] % block_size == 0
44
- y = torch.empty_like(x, dtype=_FP8_DTYPE)
45
- grid = (triton.cdiv(x.numel(), block_size),)
46
- s = x.new_empty(*x.size()[:-1], x.size(-1) // block_size, dtype=torch.float32)
47
-
48
- with device_context(x.device):
49
- wrap_triton(_fp8_act_quant_kernel)[grid](x, y, s, BLOCK_SIZE=block_size)
50
-
51
- return y, s
52
-
53
-
54
- def fp8_act_quant(
55
- x: torch.Tensor, block_size: int = 128
56
- ) -> tuple[torch.Tensor, torch.Tensor]:
57
- """Quantize activations to FP8 with per-block dynamic scaling.
58
-
59
- Splits the last dimension of ``x`` into blocks of ``block_size`` elements,
60
- computes ``scale = max(|x_block|) / 448`` per block, and quantizes to
61
- ``float8_e4m3fn``.
62
-
63
- Args:
64
- x: Input tensor in bf16/fp16/fp32. Last dimension must be divisible by
65
- ``block_size`` and the tensor must be contiguous.
66
- block_size: Number of elements per quantization block (default: 128).
67
-
68
- Returns:
69
- A tuple ``(quantized, scales)`` where ``quantized`` has dtype
70
- ``float8_e4m3fn`` with the same shape as ``x``, and ``scales`` has
71
- shape ``(*x.shape[:-1], x.shape[-1] // block_size)`` in float32.
72
- """
73
- return torch.ops.finegrained_fp8.fp8_act_quant(x, block_size)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
build/torch-cuda/batched.py CHANGED
@@ -15,31 +15,93 @@
15
  import torch
16
  import triton
17
  import triton.language as tl
18
- from .act_quant import fp8_act_quant
19
  from torch.library import triton_op, wrap_triton
20
 
21
- from .utils import device_context
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
 
23
 
24
  @triton.autotune(
25
- configs=[
26
- triton.Config({}, num_warps=w, num_stages=s)
27
- for w in [2, 4, 8, 16]
28
- for s in [2, 3, 4, 5]
29
- ],
30
  key=["N", "K"],
31
  )
32
  @triton.jit
33
- def w8a8_block_fp8_matmul_batched_kernel(
34
- A, # (S, K) raw BF16/FP16 activations
35
  B, # (E, N, K) FP8 weight matrices
36
- C, # (S, N) output
37
  Bs, # (E, N // BLOCK_SIZE_N, K // BLOCK_SIZE_K) weight scales
38
  ExpertIds, # (S,) — which expert each batch element routes to
39
  # Shape
40
  S,
41
  N,
42
  K,
 
43
  stride_am,
44
  stride_ak,
45
  stride_be,
@@ -50,28 +112,24 @@ def w8a8_block_fp8_matmul_batched_kernel(
50
  stride_bs_e,
51
  stride_bs_k,
52
  stride_bs_n,
 
53
  # Meta-parameters
 
54
  BLOCK_SIZE_N: tl.constexpr,
55
  BLOCK_SIZE_K: tl.constexpr,
56
- BLOCK_SIZE_M: tl.constexpr,
57
  ):
58
  """Block-scale batched FP8 expert matmul kernel.
59
 
60
  Each program handles one routed token row and one N-tile, looks up the
61
  owning expert from ``ExpertIds``, and applies fused activation quantization.
62
  """
63
- batch_id = tl.program_id(axis=0)
64
- pid_n = tl.program_id(axis=1)
65
-
66
- # Cast expert_id to int64 to prevent int32 overflow when computing
67
- # expert_id * stride_Eb (e.g. 255 * 9_437_184 > 2^31 for 256 experts of
68
- # 3072×3072 FP8 weights).
69
- expert_id = tl.load(ExpertIds + batch_id).to(tl.int64)
70
-
71
- A = A + batch_id * stride_am
72
- B = B + expert_id * stride_be
73
- C = C + batch_id * stride_cm
74
- Bs = Bs + expert_id * stride_bs_e
75
 
76
  offs_bn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
77
  offs_k = tl.arange(0, BLOCK_SIZE_K)
@@ -82,55 +140,35 @@ def w8a8_block_fp8_matmul_batched_kernel(
82
 
83
  accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
84
  for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
85
- # ---- fused fp8_act_quant ----
86
  a_raw = tl.load(a_ptrs).to(tl.float32)
87
- a_s = tl.max(tl.abs(a_raw)) / 448.0
88
- a = (a_raw / tl.maximum(a_s, 1e-12)).to(tl.float8e4nv)
89
- # ---- matmul ----
90
  b = tl.load(b_ptrs)
91
- b_s = tl.load(bs_ptrs + k * stride_bs_k)
92
- accumulator += tl.dot(a, b) * a_s * b_s[None, :]
93
  a_ptrs += BLOCK_SIZE_K * stride_ak
94
  b_ptrs += BLOCK_SIZE_K * stride_bk
 
95
 
96
- if C.dtype.element_ty == tl.bfloat16:
97
- c = accumulator.to(tl.bfloat16)
98
- elif C.dtype.element_ty == tl.float16:
99
- c = accumulator.to(tl.float16)
100
- else:
101
- c = accumulator.to(tl.float32)
102
-
103
- offs_cm = tl.arange(0, BLOCK_SIZE_M)
104
- offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
105
- c_ptrs = C + offs_cm[:, None] * 0 + stride_cn * offs_cn[None, :]
106
- # Fake-batch trick aliases all BLOCK_SIZE_M lanes to the same C row, so emitting
107
- # `tl.store(c_ptrs, c)` issues BLOCK_SIZE_M duplicate-address stores. On NVIDIA
108
- # WGMMA this is usually benign (last-write-wins of identical bytes), but on Intel
109
- # XPU the duplicate-address store has hardware-undefined behavior and corrupts the
110
- # output. Mask so only lane 0 stores — the (M, N) accumulator rows are
111
- # mathematically identical (same A row × same B), so lane 0 holds the right value.
112
- tl.store(c_ptrs, c, mask=(offs_cm == 0)[:, None])
113
 
114
 
115
  @triton.autotune(
116
- configs=[
117
- triton.Config({}, num_warps=w, num_stages=s)
118
- for w in [2, 4, 8, 16]
119
- for s in [2, 3, 4, 5]
120
- ],
121
  key=["N", "K"],
122
  )
123
  @triton.jit
124
- def w8a8_tensor_fp8_matmul_batched_kernel(
125
  A, # (S, K) pre-quantized FP8 activations
126
  B, # (E, N, K) FP8 weight matrices
127
  C, # (S, N) output
128
- As, # (S, 1) per-tensor activation scales
129
  Bs, # (E, 1, 1) per-tensor weight scales
130
- ExpertIds,
 
131
  S,
132
  N,
133
  K,
 
134
  stride_am,
135
  stride_ak,
136
  stride_be,
@@ -140,24 +178,24 @@ def w8a8_tensor_fp8_matmul_batched_kernel(
140
  stride_cn,
141
  stride_as_m,
142
  stride_bs_e,
 
 
 
143
  BLOCK_SIZE_N: tl.constexpr,
144
  BLOCK_SIZE_K: tl.constexpr,
145
- BLOCK_SIZE_M: tl.constexpr,
146
  ):
147
  """Tensor-scale batched FP8 expert matmul kernel.
148
 
149
  Activations are already quantized; the kernel applies per-token activation
150
  scales and per-expert tensor weight scales.
151
  """
152
- batch_id = tl.program_id(axis=0)
153
- pid_n = tl.program_id(axis=1)
154
-
155
- expert_id = tl.load(ExpertIds + batch_id).to(tl.int64)
156
-
157
- A = A + batch_id * stride_am
158
- B = B + expert_id * stride_be
159
- C = C + batch_id * stride_cm
160
- Bs = Bs + expert_id * stride_bs_e
161
 
162
  offs_bn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
163
  offs_k = tl.arange(0, BLOCK_SIZE_K)
@@ -177,28 +215,162 @@ def w8a8_tensor_fp8_matmul_batched_kernel(
177
 
178
  accumulator = accumulator * a_s * b_s
179
 
180
- if C.dtype.element_ty == tl.bfloat16:
181
- c = accumulator.to(tl.bfloat16)
182
- elif C.dtype.element_ty == tl.float16:
183
- c = accumulator.to(tl.float16)
184
- else:
185
- c = accumulator.to(tl.float32)
186
 
187
- offs_cm = tl.arange(0, BLOCK_SIZE_M)
188
- offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
189
- c_ptrs = C + offs_cm[:, None] * 0 + stride_cn * offs_cn[None, :]
190
- # See block-FP8 kernel above: BLOCK_SIZE_M lanes alias the same C row;
191
- # mask so only lane 0 stores to avoid hardware-undefined duplicate writes on XPU.
192
- tl.store(c_ptrs, c, mask=(offs_cm == 0)[:, None])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
193
 
194
 
195
- @triton_op("finegrained_fp8::w8a8_block_fp8_matmul_batched", mutates_args=())
196
- def _w8a8_block_fp8_matmul_batched(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
197
  A: torch.Tensor,
198
  B: torch.Tensor,
199
  Bs: torch.Tensor,
200
  expert_ids: torch.Tensor,
201
  block_size: list[int],
 
202
  ) -> torch.Tensor:
203
  """Block-scale batched FP8 matmul: C[s] = A[s] @ B[expert_ids[s]].T, with fused act quant.
204
 
@@ -231,15 +403,15 @@ def _w8a8_block_fp8_matmul_batched(
231
  f"Bs shape {tuple(Bs.shape)} != expected ({E}, {N // block_n}, {K // block_k})"
232
  )
233
 
234
- C = A.new_empty(S, N)
235
- # Adaptive BLOCK_SIZE_M: smallest power-of-2 >= M, floored at 16, capped at 128.
236
- # Matches the WGMMA tile to the actual row count — smaller tiles use less
237
- # register pressure and a better-matched FP8 WGMMA instruction, improving
238
- # both accuracy and performance for small M (decode).
239
- BLOCK_SIZE_M = min(max(triton.next_power_of_2((S + E - 1) // E), 16), 128)
240
  grid = (S, triton.cdiv(N, block_n))
 
 
241
  with device_context(A.device):
242
- wrap_triton(w8a8_block_fp8_matmul_batched_kernel)[grid](
243
  A,
244
  B,
245
  C,
@@ -258,20 +430,25 @@ def _w8a8_block_fp8_matmul_batched(
258
  Bs.stride(0),
259
  Bs.stride(2),
260
  Bs.stride(1),
 
261
  BLOCK_SIZE_N=block_n,
262
  BLOCK_SIZE_K=block_k,
263
  BLOCK_SIZE_M=BLOCK_SIZE_M,
 
264
  )
265
 
266
  return C
267
 
268
 
269
- @triton_op("finegrained_fp8::w8a8_tensor_fp8_matmul_batched", mutates_args=())
270
- def _w8a8_tensor_fp8_matmul_batched(
 
 
271
  A: torch.Tensor,
272
  B: torch.Tensor,
273
  Bs: torch.Tensor,
274
  expert_ids: torch.Tensor,
 
275
  ) -> torch.Tensor:
276
  """Tensor-scale batched FP8 matmul: C[s] = A[s] @ B[expert_ids[s]].T, with fused act quant.
277
 
@@ -299,19 +476,18 @@ def _w8a8_tensor_fp8_matmul_batched(
299
  f"Bs shape {tuple(Bs.shape)} != expected ({E}, 1, 1)"
300
  )
301
 
302
- BLOCK_SIZE_N = 128
303
- BLOCK_SIZE_K = 128
304
- C = A.new_empty(S, N)
 
305
  qA, As = fp8_act_quant(A, K)
306
- grid = (S, triton.cdiv(N, BLOCK_SIZE_N))
307
- # Adaptive BLOCK_SIZE_M: smallest power-of-2 >= M, floored at 16, capped at 128.
308
- # Matches the WGMMA tile to the actual row count — smaller tiles use less
309
- # register pressure and a better-matched FP8 WGMMA instruction, improving
310
- # both accuracy and performance for small M (decode).
311
- BLOCK_SIZE_M = min(max(triton.next_power_of_2((S + E - 1) // E), 16), 128)
312
- grid = (S, triton.cdiv(N, BLOCK_SIZE_N))
313
  with device_context(A.device):
314
- wrap_triton(w8a8_tensor_fp8_matmul_batched_kernel)[grid](
315
  qA,
316
  B,
317
  C,
@@ -330,69 +506,268 @@ def _w8a8_tensor_fp8_matmul_batched(
330
  C.stride(1),
331
  As.stride(0),
332
  Bs.stride(0),
333
- BLOCK_SIZE_N=BLOCK_SIZE_N,
334
- BLOCK_SIZE_K=BLOCK_SIZE_K,
335
  BLOCK_SIZE_M=BLOCK_SIZE_M,
 
336
  )
337
 
338
  return C
339
 
340
 
341
- def w8a8_block_fp8_matmul_batched(
342
  A: torch.Tensor,
343
  B: torch.Tensor,
344
  Bs: torch.Tensor,
345
  expert_ids: torch.Tensor,
346
  block_size: list[int],
 
347
  ) -> torch.Tensor:
348
  """Block-scale batched FP8 matmul with fused activation quantization.
349
 
350
  A: (S, K) raw activations, bf16/fp16/fp32
351
  B: (E, N, K) FP8 expert weights
352
  Bs: (E, N // block_n, K // block_k) per-block weight scales
 
 
353
  """
354
- return torch.ops.finegrained_fp8.w8a8_block_fp8_matmul_batched(
355
- A, B, Bs, expert_ids, block_size
356
  )
357
 
358
 
359
- def w8a8_tensor_fp8_matmul_batched(
360
  A: torch.Tensor,
361
  B: torch.Tensor,
362
  Bs: torch.Tensor,
363
  expert_ids: torch.Tensor,
 
364
  ) -> torch.Tensor:
365
  """Tensor-scale batched FP8 matmul with fused activation quantization.
366
 
367
  A: (S, K) raw activations, bf16/fp16/fp32
368
  B: (E, N, K) FP8 expert weights
369
  Bs: (E,) or (E, 1, 1) per-expert weight scales
 
370
  """
371
- return torch.ops.finegrained_fp8.w8a8_tensor_fp8_matmul_batched(
372
- A, B, Bs, expert_ids
373
  )
374
 
375
 
376
- def w8a8_fp8_matmul_batched(
 
 
 
377
  A: torch.Tensor,
378
  B: torch.Tensor,
379
  Bs: torch.Tensor,
380
  expert_ids: torch.Tensor,
381
- block_size: list[int] | None,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
382
  ) -> torch.Tensor:
383
- """Unified batched W8A8 FP8 matmul dispatcher.
 
 
384
 
385
- Dispatch rules:
386
- - tensor mode when ``block_size is None``
387
- - tensor mode when ``block_size == [N, K]``
388
- - otherwise block mode
389
 
390
- Returns:
391
- Output tensor ``[S, N]`` in the same dtype as ``A``.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
392
  """
 
 
 
 
 
 
393
  if block_size is None or (
394
  block_size[0] == B.size(1) and block_size[1] == B.size(2)
395
  ):
396
- return w8a8_tensor_fp8_matmul_batched(A, B, Bs, expert_ids)
 
 
397
 
398
- return w8a8_block_fp8_matmul_batched(A, B, Bs, expert_ids, block_size)
 
 
 
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,
 
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)
 
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,
 
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)
 
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
 
 
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,
 
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
 
 
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,
 
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-cuda/grouped.py CHANGED
@@ -15,25 +15,131 @@
15
  import torch
16
  import triton
17
  import triton.language as tl
18
- from .act_quant import fp8_act_quant
19
  from torch.library import triton_op, wrap_triton
20
 
21
- from .utils import device_context
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
 
23
 
24
  @triton.autotune(
25
- configs=[
26
- triton.Config({}, num_warps=w, num_stages=s)
27
- for w in [2, 4, 8, 16]
28
- for s in [2, 3, 4, 5]
29
- ],
30
  key=["N", "K", "BLOCK_SIZE_M"],
31
  )
32
  @triton.jit
33
- def w8a8_block_fp8_matmul_grouped_kernel(
34
- A, # (S, K) raw BF16/FP16 activations, sorted/grouped by expert id
35
  B, # (E, N, K) FP8 weight matrices
36
- C, # (S, N) output
37
  Bs, # (E, N // BLOCK_SIZE_N, K // BLOCK_SIZE_K) weight scales
38
  Offsets, # (E,) int32 — cumulative row-end per expert
39
  TileOffsets, # (E,) int32 — cumulative tile-end per expert
@@ -52,11 +158,13 @@ def w8a8_block_fp8_matmul_grouped_kernel(
52
  stride_bs_e,
53
  stride_bs_k,
54
  stride_bs_n,
 
 
55
  # Meta-parameters
56
- NUM_EXPERTS: tl.constexpr,
57
  BLOCK_SIZE_N: tl.constexpr,
58
  BLOCK_SIZE_K: tl.constexpr,
59
- BLOCK_SIZE_M: tl.constexpr,
60
  NUM_EXPERTS_BIT_LENGTH: tl.constexpr,
61
  ):
62
  """Block-scale grouped FP8 expert matmul kernel.
@@ -67,47 +175,23 @@ def w8a8_block_fp8_matmul_grouped_kernel(
67
  pid_m = tl.program_id(axis=0)
68
  pid_n = tl.program_id(axis=1)
69
 
70
- # Exit early for programs beyond the actual tile count.
71
- total_tiles = tl.load(TileOffsets + NUM_EXPERTS - 1)
72
  if pid_m >= total_tiles:
73
  return
74
 
75
- # Binary search in TileOffsets to find the owning expert.
76
- # Finds the smallest e such that TileOffsets[e] > pid_m (upper_bound semantics),
77
- # which is the expert whose tile range contains pid_m.
78
- # O(log2(NUM_EXPERTS)) loads instead of the O(NUM_EXPERTS) linear scan.
79
- # NUM_EXPERTS_BIT_LENGTH is ceil(log2(E))+1 for powers-of-two, giving one
80
- # harmless extra iteration when lo==hi; it's a compile-time constant so the
81
- # loop is fully unrolled by the compiler.
82
- lo = 0
83
- hi = NUM_EXPERTS
84
- for _ in tl.static_range(NUM_EXPERTS_BIT_LENGTH):
85
- mid = (lo + hi) >> 1
86
- mid_val = tl.load(TileOffsets + mid)
87
- is_left = mid_val <= pid_m
88
- lo = tl.where(is_left, mid + 1, lo)
89
- hi = tl.where(is_left, hi, mid)
90
-
91
- # Cast expert_id to int64 to prevent int32 overflow when computing
92
- # expert_id * stride_be (e.g. 255 * 9_437_184 > 2^31 for 256 experts of
93
- # 3072×3072 FP8 weights).
94
- expert_id = lo.to(tl.int64)
95
-
96
- prev_eid = tl.maximum(expert_id - 1, 0)
97
- expert_start = tl.where(expert_id == 0, 0, tl.load(Offsets + prev_eid))
98
- expert_end = tl.load(Offsets + expert_id)
99
- M_expert = expert_end - expert_start
100
-
101
- expert_tile_start = tl.where(expert_id == 0, 0, tl.load(TileOffsets + prev_eid))
102
- local_tile = pid_m - expert_tile_start
103
- m_off = local_tile * BLOCK_SIZE_M
104
-
105
- offs_am = m_off + tl.arange(0, BLOCK_SIZE_M)
106
- row_mask = offs_am < M_expert
107
- offs_global_m = expert_start + offs_am
108
-
109
- offs_bn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
110
- offs_k = tl.arange(0, BLOCK_SIZE_K)
111
 
112
  a_ptrs = A + offs_global_m[:, None] * stride_am + offs_k[None, :] * stride_ak
113
  b_ptrs = (
@@ -120,49 +204,38 @@ def w8a8_block_fp8_matmul_grouped_kernel(
120
 
121
  accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
122
  for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
123
- # ---- fused fp8_act_quant ----
124
  a_raw = tl.load(a_ptrs, mask=row_mask[:, None], other=0.0).to(tl.float32)
125
- a_s = tl.max(tl.abs(a_raw), axis=1) / 448.0
126
- a = (a_raw / tl.maximum(a_s[:, None], 1e-12)).to(tl.float8e4nv)
127
- # ---- matmul ----
128
  b = tl.load(b_ptrs)
129
- b_s = tl.load(bs_ptrs + k * stride_bs_k)
130
  accumulator += tl.dot(a, b) * a_s[:, None] * b_s[None, :]
131
  a_ptrs += BLOCK_SIZE_K * stride_ak
132
  b_ptrs += BLOCK_SIZE_K * stride_bk
 
133
 
134
- if C.dtype.element_ty == tl.bfloat16:
135
- c = accumulator.to(tl.bfloat16)
136
- elif C.dtype.element_ty == tl.float16:
137
- c = accumulator.to(tl.float16)
138
- else:
139
- c = accumulator.to(tl.float32)
140
-
141
- c_ptrs = C + stride_cm * offs_global_m[:, None] + stride_cn * offs_bn[None, :]
142
- c_mask = row_mask[:, None]
143
- tl.store(c_ptrs, c, mask=c_mask)
144
 
145
 
146
  @triton.autotune(
147
- configs=[
148
- triton.Config({}, num_warps=w, num_stages=s)
149
- for w in [2, 4, 8, 16]
150
- for s in [2, 3, 4, 5]
151
- ],
152
  key=["N", "K", "BLOCK_SIZE_M"],
153
  )
154
  @triton.jit
155
- def w8a8_tensor_fp8_matmul_grouped_kernel(
156
- A, # (S, K) raw BF16/FP16 activations, sorted/grouped by expert idc
157
  B, # (E, N, K) FP8 weight matrices
158
  C, # (S, N) output
159
- As, # (S, 1) activation scales
160
  Bs, # (E, 1, 1) per-tensor weight scales
161
- Offsets,
162
- TileOffsets,
 
163
  S,
164
  N,
165
  K,
 
166
  stride_am,
167
  stride_ak,
168
  stride_be,
@@ -172,10 +245,13 @@ def w8a8_tensor_fp8_matmul_grouped_kernel(
172
  stride_cn,
173
  stride_as_m,
174
  stride_bs_e,
175
- NUM_EXPERTS: tl.constexpr,
 
 
 
176
  BLOCK_SIZE_N: tl.constexpr,
177
  BLOCK_SIZE_K: tl.constexpr,
178
- BLOCK_SIZE_M: tl.constexpr,
179
  NUM_EXPERTS_BIT_LENGTH: tl.constexpr,
180
  ):
181
  """Tensor-scale grouped FP8 expert matmul kernel.
@@ -186,35 +262,23 @@ def w8a8_tensor_fp8_matmul_grouped_kernel(
186
  pid_m = tl.program_id(axis=0)
187
  pid_n = tl.program_id(axis=1)
188
 
189
- total_tiles = tl.load(TileOffsets + NUM_EXPERTS - 1)
190
  if pid_m >= total_tiles:
191
  return
192
 
193
- lo = 0
194
- hi = NUM_EXPERTS
195
- for _ in tl.static_range(NUM_EXPERTS_BIT_LENGTH):
196
- mid = (lo + hi) >> 1
197
- mid_val = tl.load(TileOffsets + mid)
198
- is_left = mid_val <= pid_m
199
- lo = tl.where(is_left, mid + 1, lo)
200
- hi = tl.where(is_left, hi, mid)
201
- expert_id = lo.to(tl.int64)
202
-
203
- prev_eid = tl.maximum(expert_id - 1, 0)
204
- expert_start = tl.where(expert_id == 0, 0, tl.load(Offsets + prev_eid))
205
- expert_end = tl.load(Offsets + expert_id)
206
- M_expert = expert_end - expert_start
207
-
208
- expert_tile_start = tl.where(expert_id == 0, 0, tl.load(TileOffsets + prev_eid))
209
- local_tile = pid_m - expert_tile_start
210
- m_off = local_tile * BLOCK_SIZE_M
211
-
212
- offs_am = m_off + tl.arange(0, BLOCK_SIZE_M)
213
- row_mask = offs_am < M_expert
214
- offs_global_m = expert_start + offs_am
215
-
216
- offs_bn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
217
- offs_k = tl.arange(0, BLOCK_SIZE_K)
218
 
219
  a_ptrs = A + offs_global_m[:, None] * stride_am + offs_k[None, :] * stride_ak
220
  b_ptrs = (
@@ -231,33 +295,224 @@ def w8a8_tensor_fp8_matmul_grouped_kernel(
231
  for _ in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
232
  a = tl.load(a_ptrs, mask=row_mask[:, None], other=0.0)
233
  b = tl.load(b_ptrs)
234
-
235
  accumulator += tl.dot(a, b)
236
  a_ptrs += BLOCK_SIZE_K * stride_ak
237
  b_ptrs += BLOCK_SIZE_K * stride_bk
238
-
239
  accumulator = accumulator * a_s[:, None] * b_s
240
 
241
- if C.dtype.element_ty == tl.bfloat16:
242
- c = accumulator.to(tl.bfloat16)
243
- elif C.dtype.element_ty == tl.float16:
244
- c = accumulator.to(tl.float16)
245
- else:
246
- c = accumulator.to(tl.float32)
247
 
248
- c_ptrs = C + stride_cm * offs_global_m[:, None] + stride_cn * offs_bn[None, :]
249
- c_mask = row_mask[:, None]
250
- tl.store(c_ptrs, c, mask=c_mask)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
251
 
252
 
253
- @triton_op("finegrained_fp8::w8a8_block_fp8_matmul_grouped", mutates_args=())
254
- def _w8a8_block_fp8_matmul_grouped(
 
 
255
  A: torch.Tensor,
256
  B: torch.Tensor,
257
  Bs: torch.Tensor,
258
  offsets: torch.Tensor,
259
  tokens_per_expert: torch.Tensor,
260
  block_size: list[int],
 
261
  ) -> torch.Tensor:
262
  """Block-scale grouped FP8 matmul: C = A @ B.T per expert, with fused act quant.
263
 
@@ -290,19 +545,16 @@ def _w8a8_block_fp8_matmul_grouped(
290
  f"Bs shape {tuple(Bs.shape)} != expected ({E}, {N // block_n}, {K // block_k})"
291
  )
292
 
293
- C = A.new_empty(S, N)
294
- # Adaptive BLOCK_SIZE_M: match tile to average tokens per expert.
295
- BLOCK_SIZE_M = min(max(triton.next_power_of_2((S + E - 1) // E), 16), 128)
296
- tiles_per_expert = (tokens_per_expert + BLOCK_SIZE_M - 1) // BLOCK_SIZE_M
297
- tile_offsets = torch.cumsum(tiles_per_expert, dim=0).to(torch.int32)
298
- # Upper bound on M-tiles: sum_e ceil(M_e / BLOCK_M) <= ceil(S / BLOCK_M) + E.
299
- # Programs beyond the real tile count exit immediately via the early-return
300
- # guard inside the kernel. This is faster than syncing for the exact count
301
- # and keeps the grid size data-independent (cuda-graph / torch.compile safe).
302
- max_M_tiles = triton.cdiv(S, BLOCK_SIZE_M) + E
303
- grid = (max_M_tiles, triton.cdiv(N, block_n))
304
  with device_context(A.device):
305
- wrap_triton(w8a8_block_fp8_matmul_grouped_kernel)[grid](
306
  A,
307
  B,
308
  C,
@@ -322,6 +574,8 @@ def _w8a8_block_fp8_matmul_grouped(
322
  Bs.stride(0),
323
  Bs.stride(2),
324
  Bs.stride(1),
 
 
325
  # Meta-parameters
326
  NUM_EXPERTS=E,
327
  BLOCK_SIZE_N=block_n,
@@ -333,13 +587,16 @@ def _w8a8_block_fp8_matmul_grouped(
333
  return C
334
 
335
 
336
- @triton_op("finegrained_fp8::w8a8_tensor_fp8_matmul_grouped", mutates_args=())
337
- def _w8a8_tensor_fp8_matmul_grouped(
 
 
338
  A: torch.Tensor,
339
  B: torch.Tensor,
340
  Bs: torch.Tensor,
341
  offsets: torch.Tensor,
342
  tokens_per_expert: torch.Tensor,
 
343
  ) -> torch.Tensor:
344
  """Tensor-scale grouped FP8 matmul: C = A @ B.T per expert, with fused act quant.
345
 
@@ -367,21 +624,18 @@ def _w8a8_tensor_fp8_matmul_grouped(
367
  f"Bs shape {tuple(Bs.shape)} != expected ({E}, 1, 1)"
368
  )
369
 
370
- BLOCK_SIZE_N = 128
371
- BLOCK_SIZE_K = 128
372
- C = A.new_empty(S, N)
373
  qA, As = fp8_act_quant(A, K)
374
- BLOCK_SIZE_M = min(max(triton.next_power_of_2((S + E - 1) // E), 16), 128)
375
- tiles_per_expert = (tokens_per_expert + BLOCK_SIZE_M - 1) // BLOCK_SIZE_M
376
- tile_offsets = torch.cumsum(tiles_per_expert, dim=0).to(torch.int32)
377
- # Upper bound on M-tiles: sum_e ceil(M_e / BLOCK_M) <= ceil(S / BLOCK_M) + E.
378
- # Programs beyond the real tile count exit immediately via the early-return
379
- # guard inside the kernel. This is faster than syncing for the exact count
380
- # and keeps the grid size data-independent (cuda-graph / torch.compile safe).
381
- max_M_tiles = triton.cdiv(S, BLOCK_SIZE_M) + E
382
- grid = (max_M_tiles, triton.cdiv(N, BLOCK_SIZE_N))
383
  with device_context(A.device):
384
- wrap_triton(w8a8_tensor_fp8_matmul_grouped_kernel)[grid](
385
  qA,
386
  B,
387
  C,
@@ -401,10 +655,10 @@ def _w8a8_tensor_fp8_matmul_grouped(
401
  C.stride(1),
402
  As.stride(0),
403
  Bs.stride(0),
404
- # Meta-parameters
 
 
405
  NUM_EXPERTS=E,
406
- BLOCK_SIZE_N=BLOCK_SIZE_N,
407
- BLOCK_SIZE_K=BLOCK_SIZE_K,
408
  BLOCK_SIZE_M=BLOCK_SIZE_M,
409
  NUM_EXPERTS_BIT_LENGTH=E.bit_length(),
410
  )
@@ -412,66 +666,286 @@ def _w8a8_tensor_fp8_matmul_grouped(
412
  return C
413
 
414
 
415
- def w8a8_block_fp8_matmul_grouped(
416
  A: torch.Tensor,
417
  B: torch.Tensor,
418
  Bs: torch.Tensor,
419
  offsets: torch.Tensor,
420
  tokens_per_expert: torch.Tensor,
421
  block_size: list[int],
 
422
  ) -> torch.Tensor:
423
  """Block-scale grouped FP8 matmul with fused activation quantization.
424
 
425
  A: (S, K) raw activations sorted by expert, bf16/fp16/fp32
426
  B: (E, N, K) FP8 expert weights
427
  Bs: (E, N // block_n, K // block_k) per-block weight scales
 
428
  """
429
- return torch.ops.finegrained_fp8.w8a8_block_fp8_matmul_grouped(
430
- A, B, Bs, offsets, tokens_per_expert, block_size
431
  )
432
 
433
 
434
- def w8a8_tensor_fp8_matmul_grouped(
435
  A: torch.Tensor,
436
  B: torch.Tensor,
437
  Bs: torch.Tensor,
438
  offsets: torch.Tensor,
439
  tokens_per_expert: torch.Tensor,
 
440
  ) -> torch.Tensor:
441
  """Tensor-scale grouped FP8 matmul with fused activation quantization.
442
 
443
  A: (S, K) raw activations sorted by expert, bf16/fp16/fp32
444
  B: (E, N, K) FP8 expert weights
445
  Bs: (E,) or (E, 1, 1) per-expert weight scales
 
446
  """
447
- return torch.ops.finegrained_fp8.w8a8_tensor_fp8_matmul_grouped(
448
- A, B, Bs, offsets, tokens_per_expert
449
  )
450
 
451
 
452
- def w8a8_fp8_matmul_grouped(
 
 
 
453
  A: torch.Tensor,
454
  B: torch.Tensor,
455
  Bs: torch.Tensor,
456
  offsets: torch.Tensor,
457
  tokens_per_expert: torch.Tensor,
458
- block_size: list[int] | None,
459
  ) -> torch.Tensor:
460
- """Unified grouped W8A8 FP8 matmul dispatcher.
461
 
462
- Dispatch rules:
463
- - tensor mode when ``block_size is None``
464
- - tensor mode when ``block_size == [N, K]``
465
- - otherwise block mode
 
466
 
467
- Returns:
468
- Output tensor ``[S, N]`` in the same dtype as ``A``, in expert-sorted order.
469
  """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
470
  if block_size is None or (
471
  block_size[0] == B.size(1) and block_size[1] == B.size(2)
472
  ):
473
- return w8a8_tensor_fp8_matmul_grouped(A, B, Bs, offsets, tokens_per_expert)
 
 
474
 
475
- return w8a8_block_fp8_matmul_grouped(
476
- A, B, Bs, offsets, tokens_per_expert, block_size
477
  )
 
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
 
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.
 
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 = (
 
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,
 
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.
 
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 = (
 
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
 
 
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,
 
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,
 
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
 
 
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,
 
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
  )
 
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-cuda/matmul.py CHANGED
@@ -17,38 +17,93 @@ import triton
17
  import triton.language as tl
18
  from torch.library import triton_op, wrap_triton
19
 
20
- from .utils import device_context
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
 
22
 
23
- # Adapted from https://github.com/sgl-project/sglang/blob/main/python/sglang/srt/layers/quantization/fp8_kernel.py
24
  @triton.autotune(
25
- configs=[
26
- triton.Config({}, num_warps=w, num_stages=s)
27
- for w in [2, 4, 8, 16]
28
- for s in [2, 3, 4]
29
- ],
30
  key=["N", "K", "BLOCK_SIZE_M"],
31
  )
32
  @triton.jit
33
- def w8a8_block_fp8_matmul_kernel(
34
- # Pointers to inputs and output
35
- A,
36
- B,
37
- C,
38
- As,
39
- Bs,
40
- # Shape for matmul
41
  M,
42
  N,
43
  K,
 
44
  stride_am,
45
  stride_ak,
46
  stride_bk,
47
  stride_bn,
48
  stride_cm,
49
  stride_cn,
50
- stride_as_m,
51
- stride_as_k,
52
  stride_bs_k,
53
  stride_bs_n,
54
  # Meta-parameters
@@ -57,72 +112,57 @@ def w8a8_block_fp8_matmul_kernel(
57
  BLOCK_SIZE_K: tl.constexpr,
58
  GROUP_SIZE_M: tl.constexpr,
59
  ):
60
- """Block-scale FP8 GEMM kernel.
61
 
62
- Computes ``C = A @ B.T`` with block-wise activation/weight scales.
63
- Uses a 2D grid with swizzle for L2 cache locality on B tiles.
 
64
  """
65
- pid_m = tl.program_id(axis=0)
66
- pid_n = tl.program_id(axis=1)
67
- num_pid_m = tl.cdiv(M, BLOCK_SIZE_M)
68
- num_pid_n = tl.cdiv(N, BLOCK_SIZE_N)
69
- pid_m, pid_n = tl.swizzle2d(pid_m, pid_n, num_pid_m, num_pid_n, GROUP_SIZE_M)
70
-
71
- offs_am = (pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)) % M
72
- offs_bn = (pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)) % N
73
- offs_k = tl.arange(0, BLOCK_SIZE_K)
74
  a_ptrs = A + (offs_am[:, None] * stride_am + offs_k[None, :] * stride_ak)
75
  b_ptrs = B + (offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn)
76
 
77
- as_ptrs = As + offs_am * stride_as_m
78
  offs_bsn = offs_bn // BLOCK_SIZE_N
79
  bs_ptrs = Bs + offs_bsn * stride_bs_n
80
 
81
  accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
82
  for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
83
  k_remaining = K - k * BLOCK_SIZE_K
84
- a = tl.load(a_ptrs, mask=offs_k[None, :] < k_remaining, other=0.0)
 
 
 
85
  b = tl.load(b_ptrs, mask=offs_k[:, None] < k_remaining, other=0.0)
86
-
87
- a_s = tl.load(as_ptrs + k * stride_as_k)
88
- b_s = tl.load(bs_ptrs + k * stride_bs_k)
89
-
90
  accumulator += tl.dot(a, b) * a_s[:, None] * b_s[None, :]
91
  a_ptrs += BLOCK_SIZE_K * stride_ak
92
  b_ptrs += BLOCK_SIZE_K * stride_bk
 
93
 
94
- if C.dtype.element_ty == tl.bfloat16:
95
- c = accumulator.to(tl.bfloat16)
96
- elif C.dtype.element_ty == tl.float16:
97
- c = accumulator.to(tl.float16)
98
- else:
99
- c = accumulator.to(tl.float32)
100
-
101
- offs_cm = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
102
- offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
103
- c_ptrs = C + stride_cm * offs_cm[:, None] + stride_cn * offs_cn[None, :]
104
- c_mask = (offs_cm[:, None] < M) & (offs_cn[None, :] < N)
105
- tl.store(c_ptrs, c, mask=c_mask)
106
 
107
 
108
  @triton.autotune(
109
- configs=[
110
- triton.Config({}, num_warps=w, num_stages=s)
111
- for w in [2, 4, 8, 16]
112
- for s in [2, 3, 4]
113
- ],
114
  key=["N", "K", "BLOCK_SIZE_M"],
115
  )
116
  @triton.jit
117
- def w8a8_tensor_fp8_matmul_kernel(
118
- A,
119
- B,
120
- C,
121
- As,
122
- Bs,
 
123
  M,
124
  N,
125
  K,
 
126
  stride_am,
127
  stride_ak,
128
  stride_bk,
@@ -130,6 +170,7 @@ def w8a8_tensor_fp8_matmul_kernel(
130
  stride_cm,
131
  stride_cn,
132
  stride_as_m,
 
133
  BLOCK_SIZE_M: tl.constexpr,
134
  BLOCK_SIZE_N: tl.constexpr,
135
  BLOCK_SIZE_K: tl.constexpr,
@@ -141,16 +182,9 @@ def w8a8_tensor_fp8_matmul_kernel(
141
  weight scale for the full matrix.
142
  Uses a 2D grid with swizzle for L2 cache locality on B tiles.
143
  """
144
- pid_m = tl.program_id(axis=0)
145
- pid_n = tl.program_id(axis=1)
146
- num_pid_m = tl.cdiv(M, BLOCK_SIZE_M)
147
- num_pid_n = tl.cdiv(N, BLOCK_SIZE_N)
148
- pid_m, pid_n = tl.swizzle2d(pid_m, pid_n, num_pid_m, num_pid_n, GROUP_SIZE_M)
149
-
150
- offs_am = (pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)) % M
151
- offs_bn = (pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)) % N
152
- offs_k = tl.arange(0, BLOCK_SIZE_K)
153
-
154
  a_ptrs = A + offs_am[:, None] * stride_am + offs_k[None, :] * stride_ak
155
  b_ptrs = B + offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn
156
 
@@ -169,33 +203,236 @@ def w8a8_tensor_fp8_matmul_kernel(
169
 
170
  accumulator = accumulator * a_s[:, None] * b_s
171
 
172
- if C.dtype.element_ty == tl.bfloat16:
173
- c = accumulator.to(tl.bfloat16)
174
- elif C.dtype.element_ty == tl.float16:
175
- c = accumulator.to(tl.float16)
176
- else:
177
- c = accumulator.to(tl.float32)
178
 
179
- offs_cm = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
180
- offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
181
- c_ptrs = C + stride_cm * offs_cm[:, None] + stride_cn * offs_cn[None, :]
182
- c_mask = (offs_cm[:, None] < M) & (offs_cn[None, :] < N)
183
- tl.store(c_ptrs, c, mask=c_mask)
184
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
185
 
186
- @triton_op("finegrained_fp8::w8a8_block_fp8_matmul", mutates_args=())
187
- def _w8a8_block_fp8_matmul(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
188
  A: torch.Tensor,
189
  B: torch.Tensor,
190
- As: torch.Tensor,
191
  Bs: torch.Tensor,
192
  block_size: list[int],
193
- output_dtype: torch.dtype = torch.float32,
194
  ) -> torch.Tensor:
195
- """Block-scale FP8 matmul: C = A @ B.T with per-block scales.
196
 
197
- As: (M, K // block_k) per-token-group activation scales
198
- Bs: (N // block_n, K // block_k) per-block weight scales
 
199
  """
200
  assert len(block_size) == 2, (
201
  f"block_size must be [block_n, block_k], got {block_size}"
@@ -212,10 +449,6 @@ def _w8a8_block_fp8_matmul(
212
  N, K = B.shape
213
  M = A.numel() // A.shape[-1]
214
 
215
- assert As.ndim >= 2, f"As must be at least 2D, got ndim={As.ndim}"
216
- assert As.shape[-1] == triton.cdiv(K, block_k), (
217
- f"As last dim {As.shape[-1]} != expected {triton.cdiv(K, block_k)} (cdiv(K={K}, block_k={block_k}))"
218
- )
219
  assert Bs.ndim == 2, f"Bs must be 2D (N//block_n, K//block_k), got ndim={Bs.ndim}"
220
  assert Bs.shape == (triton.cdiv(N, block_n), triton.cdiv(K, block_k)), (
221
  f"Bs shape {tuple(Bs.shape)} != expected ({triton.cdiv(N, block_n)}, {triton.cdiv(K, block_k)})"
@@ -223,20 +456,17 @@ def _w8a8_block_fp8_matmul(
223
 
224
  BLOCK_SIZE_K = block_k
225
  BLOCK_SIZE_N = block_n
 
226
  C_shape = A.shape[:-1] + (N,)
 
227
  C = A.new_empty(C_shape, dtype=output_dtype)
228
- # Adaptive BLOCK_SIZE_M: smallest power-of-2 >= M, floored at 16, capped at 128.
229
- # Matches the WGMMA tile to the actual row count — smaller tiles use less
230
- # register pressure and a better-matched FP8 WGMMA instruction, improving
231
- # both accuracy and performance for small M (decode).
232
- BLOCK_SIZE_M = min(max(triton.next_power_of_2(M), 16), 128)
233
  grid = (triton.cdiv(M, BLOCK_SIZE_M), triton.cdiv(N, BLOCK_SIZE_N))
 
234
  with device_context(A.device):
235
- wrap_triton(w8a8_block_fp8_matmul_kernel)[grid](
236
  A,
237
  B,
238
  C,
239
- As,
240
  Bs,
241
  M,
242
  N,
@@ -247,8 +477,6 @@ def _w8a8_block_fp8_matmul(
247
  B.stride(0),
248
  C.stride(-2),
249
  C.stride(-1),
250
- As.stride(-2),
251
- As.stride(-1),
252
  Bs.stride(1),
253
  Bs.stride(0),
254
  # Meta-parameters
@@ -261,50 +489,61 @@ def _w8a8_block_fp8_matmul(
261
  return C
262
 
263
 
264
- @triton_op("finegrained_fp8::w8a8_tensor_fp8_matmul", mutates_args=())
265
- def _w8a8_tensor_fp8_matmul(
266
  A: torch.Tensor,
267
  B: torch.Tensor,
268
- As: torch.Tensor,
269
  Bs: torch.Tensor,
270
- output_dtype: torch.dtype = torch.float32,
 
 
271
  ) -> torch.Tensor:
272
- """Tensor-scale FP8 matmul: C = A @ B.T with per-row / per-tensor scales.
273
 
274
- As: scalar, (M,), or (M, 1)per-row activation scales
275
- Bs: scalar, (1,), or (1, 1) single weight scale
 
 
276
  """
 
 
 
 
 
 
 
 
 
 
 
277
  assert A.shape[-1] == B.shape[-1], (
278
  f"K mismatch: A has K={A.shape[-1]}, B has K={B.shape[-1]}"
279
  )
280
  assert A.is_contiguous(), "A must be contiguous"
281
  assert B.ndim == 2, f"B must be 2D (N, K), got ndim={B.ndim}"
282
  assert B.is_contiguous(), "B must be contiguous"
 
283
 
284
  N, K = B.shape
285
  M = A.numel() // A.shape[-1]
286
 
287
- # Normalize As to (M,)
288
- if As.numel() == 1:
289
- As = As.reshape(1).expand(M).contiguous()
290
- elif As.ndim == 2:
291
- As = As.reshape(M)
292
- assert As.ndim == 1 and As.shape[0] == M, (
293
- f"As must be scalar, (M,), or (M,1) with M={M}, got {tuple(As.shape)}"
294
  )
295
 
296
- # Normalize Bs to (1,)
297
- assert Bs.numel() == 1, f"Bs must be scalar or (1,), got {tuple(Bs.shape)}"
298
- Bs = Bs.reshape(1)
 
299
 
300
- BLOCK_SIZE_N = 128
301
- BLOCK_SIZE_K = 128
302
  C_shape = A.shape[:-1] + (N,)
303
  C = A.new_empty(C_shape, dtype=output_dtype)
304
- BLOCK_SIZE_M = min(max(triton.next_power_of_2(M), 16), 128)
305
- grid = (triton.cdiv(M, BLOCK_SIZE_M), triton.cdiv(N, BLOCK_SIZE_N))
306
  with device_context(A.device):
307
- wrap_triton(w8a8_tensor_fp8_matmul_kernel)[grid](
308
  A,
309
  B,
310
  C,
@@ -319,7 +558,8 @@ def _w8a8_tensor_fp8_matmul(
319
  B.stride(0),
320
  C.stride(-2),
321
  C.stride(-1),
322
- As.stride(0),
 
323
  # Meta-parameters
324
  BLOCK_SIZE_M=BLOCK_SIZE_M,
325
  BLOCK_SIZE_N=BLOCK_SIZE_N,
@@ -330,82 +570,372 @@ def _w8a8_tensor_fp8_matmul(
330
  return C
331
 
332
 
333
- def w8a8_block_fp8_matmul(
 
334
  A: torch.Tensor,
335
  B: torch.Tensor,
336
- As: torch.Tensor,
337
  Bs: torch.Tensor,
338
- block_size: list[int],
339
- output_dtype: torch.dtype = torch.float32,
340
  ) -> torch.Tensor:
341
- """Block-wise W8A8 FP8 matrix multiplication.
342
 
343
- Computes ``C = A @ B.T`` where both operands are pre-quantized to
344
- ``float8_e4m3fn`` with per-block scales, and accumulates in float32
345
- before casting to ``output_dtype``.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
346
 
347
  Args:
348
- A: Quantized activation tensor ``[M, K]`` in ``float8_e4m3fn``.
349
- B: Quantized weight tensor ``[N, K]`` in ``float8_e4m3fn``.
350
- As: Per-token-group activation scales ``[M, K // block_size[1]]``.
351
  Bs: Per-block weight scales ``[N // block_size[0], K // block_size[1]]``.
352
- block_size: ``[block_n, block_k]`` quantization block dimensions, e.g. ``[128, 128]``.
353
  output_dtype: dtype of the returned tensor (default: ``torch.float32``).
354
 
355
  Returns:
356
- Output tensor ``[M, N]`` in ``output_dtype``.
357
  """
358
- return torch.ops.finegrained_fp8.w8a8_block_fp8_matmul(
359
- A, B, As, Bs, block_size, output_dtype
360
- )
361
 
362
 
363
- def w8a8_tensor_fp8_matmul(
364
  A: torch.Tensor,
365
  B: torch.Tensor,
 
366
  As: torch.Tensor,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
367
  Bs: torch.Tensor,
368
- output_dtype: torch.dtype = torch.float32,
369
  ) -> torch.Tensor:
370
- """Tensor-scale W8A8 FP8 matrix multiplication.
371
 
372
- Computes ``C = A @ B.T`` in tensor-scale mode using pre-quantized FP8
373
- activations/weights and tensor scales.
374
 
375
  Args:
376
- A: Quantized activation tensor ``[M, K]`` in ``float8_e4m3fn``.
377
- B: Quantized weight tensor ``[N, K]`` in ``float8_e4m3fn``.
378
- As: Per-row activation scales ``[M]``.
379
  Bs: Single weight scale, scalar or ``[1]``.
380
  output_dtype: dtype of the returned tensor.
 
 
381
 
382
- Returns:
383
- Output tensor ``[M, N]`` in ``output_dtype``.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
384
  """
385
- return torch.ops.finegrained_fp8.w8a8_tensor_fp8_matmul(A, B, As, Bs, output_dtype)
386
 
387
 
388
- def w8a8_fp8_matmul(
389
  A: torch.Tensor,
390
  B: torch.Tensor,
391
- As: torch.Tensor,
392
  Bs: torch.Tensor,
393
- block_size: list[int] | None,
394
- output_dtype: torch.dtype = torch.float32,
395
  ) -> torch.Tensor:
396
- """Unified W8A8 FP8 matmul dispatcher.
397
 
398
- Dispatch rules:
399
- - tensor mode when ``block_size is None``
400
- - tensor mode when ``block_size == [N, K]``
401
- - otherwise block mode
 
402
 
403
- Returns:
404
- Output tensor ``[M, N]`` in ``output_dtype``.
 
 
 
405
  """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
406
  if block_size is None or (
407
  block_size[0] == B.size(0) and block_size[1] == B.size(1)
408
  ):
409
- return w8a8_tensor_fp8_matmul(A, B, As, Bs, output_dtype)
410
 
411
- return w8a8_block_fp8_matmul(A, B, As, Bs, block_size, output_dtype)
 
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
 
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,
 
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,
 
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
 
 
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}"
 
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)})"
 
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,
 
477
  B.stride(0),
478
  C.stride(-2),
479
  C.stride(-1),
 
 
480
  Bs.stride(1),
481
  Bs.stride(0),
482
  # Meta-parameters
 
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 activationsquantized 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,
 
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,
 
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-cuda/metadata.json CHANGED
@@ -1,10 +1,22 @@
1
  {
2
  "name": "finegrained-fp8",
3
- "id": "_finegrained_fp8_cuda_7c5619e",
4
- "version": 1,
5
  "license": "Apache-2.0",
6
  "python-depends": [],
7
  "backend": {
8
  "type": "cuda"
 
 
 
 
 
 
 
 
 
 
 
 
9
  }
10
  }
 
1
  {
2
  "name": "finegrained-fp8",
3
+ "id": "_finegrained_fp8_cuda_846165b",
4
+ "version": 3,
5
  "license": "Apache-2.0",
6
  "python-depends": [],
7
  "backend": {
8
  "type": "cuda"
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-cuda/metadata.json.sigstore ADDED
@@ -0,0 +1 @@
 
 
1
+ {"mediaType":"application/vnd.dev.sigstore.bundle.v0.3+json","verificationMaterial":{"certificate":{"rawBytes":"MIIHSzCCBtGgAwIBAgIUK7eu4ilTcA9MKi79s+pe6U8MDSAwCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjYwNjE3MTMxMjA3WhcNMjYwNjE3MTMyMjA3WjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAECZZ+JP1Y7BHCrd++8XaWUPtIBgQzWsQXm4M11h7CRoh50JcA1HGMeBp9PXfC1tklfJm0yLlkcFuO412eiXFNjqOCBfAwggXsMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUraCbypzc0xBBe6P5b4UxBwI6smwwHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4YZD8wawYDVR0RAQH/BGEwX4ZdaHR0cHM6Ly9naXRodWIuY29tL2h1Z2dpbmdmYWNlL2tlcm5lbHMtY29tbXVuaXR5Ly5naXRodWIvd29ya2Zsb3dzL2J1aWxkLnlhbWxAcmVmcy9oZWFkcy9tYWluMDkGCisGAQQBg78wAQEEK2h0dHBzOi8vdG9rZW4uYWN0aW9ucy5naXRodWJ1c2VyY29udGVudC5jb20wHwYKKwYBBAGDvzABAgQRd29ya2Zsb3dfZGlzcGF0Y2gwNgYKKwYBBAGDvzABAwQoODQ2MTY1YjExZDU3MTA1ODI5MmQ1NDRiNDIyMzQ2MzE3ZGUxMDJkZjATBgorBgEEAYO/MAEEBAVCdWlsZDArBgorBgEEAYO/MAEFBB1odWdnaW5nZmFjZS9rZXJuZWxzLWNvbW11bml0eTAdBgorBgEEAYO/MAEGBA9yZWZzL2hlYWRzL21haW4wOwYKKwYBBAGDvzABCAQtDCtodHRwczovL3Rva2VuLmFjdGlvbnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tMG0GCisGAQQBg78wAQkEXwxdaHR0cHM6Ly9naXRodWIuY29tL2h1Z2dpbmdmYWNlL2tlcm5lbHMtY29tbXVuaXR5Ly5naXRodWIvd29ya2Zsb3dzL2J1aWxkLnlhbWxAcmVmcy9oZWFkcy9tYWluMDgGCisGAQQBg78wAQoEKgwoODQ2MTY1YjExZDU3MTA1ODI5MmQ1NDRiNDIyMzQ2MzE3ZGUxMDJkZjAbBgorBgEEAYO/MAELBA0MC3NlbGYtaG9zdGVkMEAGCisGAQQBg78wAQwEMgwwaHR0cHM6Ly9naXRodWIuY29tL2h1Z2dpbmdmYWNlL2tlcm5lbHMtY29tbXVuaXR5MDgGCisGAQQBg78wAQ0EKgwoODQ2MTY1YjExZDU3MTA1ODI5MmQ1NDRiNDIyMzQ2MzE3ZGUxMDJkZjAfBgorBgEEAYO/MAEOBBEMD3JlZnMvaGVhZHMvbWFpbjAaBgorBgEEAYO/MAEPBAwMCjEwNzE0NzU1MjkwLgYKKwYBBAGDvzABEAQgDB5odHRwczovL2dpdGh1Yi5jb20vaHVnZ2luZ2ZhY2UwGAYKKwYBBAGDvzABEQQKDAgyNTcyMDc0MzBtBgorBgEEAYO/MAESBF8MXWh0dHBzOi8vZ2l0aHViLmNvbS9odWdnaW5nZmFjZS9rZXJuZWxzLWNvbW11bml0eS8uZ2l0aHViL3dvcmtmbG93cy9idWlsZC55YW1sQHJlZnMvaGVhZHMvbWFpbjA4BgorBgEEAYO/MAETBCoMKDg0NjE2NWIxMWQ1NzEwNTgyOTJkNTQ0YjQyMjM0NjMxN2RlMTAyZGYwIQYKKwYBBAGDvzABFAQTDBF3b3JrZmxvd19kaXNwYXRjaDBkBgorBgEEAYO/MAEVBFYMVGh0dHBzOi8vZ2l0aHViLmNvbS9odWdnaW5nZmFjZS9rZXJuZWxzLWNvbW11bml0eS9hY3Rpb25zL3J1bnMvMjc2OTEyMTUxMzkvYXR0ZW1wdHMvMTAWBgorBgEEAYO/MAEWBAgMBnB1YmxpYzBGBgorBgEEAYO/MAEYBDgMNnJlcG86aHVnZ2luZ2ZhY2Uva2VybmVscy1jb21tdW5pdHk6cmVmOnJlZnMvaGVhZHMvbWFpbjCBigYKKwYBBAHWeQIEAgR8BHoAeAB2AN09MGrGxxEyYxkeHJlnNwKiSl643jyt/4eKcoAvKe6OAAABntW14zAAAAQDAEcwRQIhANR0FINBlVxQ+UrWqe1Gm1asRyym4uVoCvI6Xi68HdS8AiAwqtFKjJoz4ZpWp1/pr2GAJF2bAAQ9Nny7688Fb3xtbjAKBggqhkjOPQQDAwNoADBlAjEA9duWR/CLVkZJwUaI2+LNZjLBAym9nsaLgByw++Htoke2IXx15s4jD568fDI8lWhjAjBdtOz6szSSLeZFRnSyD90l7ssm9INZR1+HC5iczBnJmcyhSa1sXznWwpkaU/j1FYw="},"tlogEntries":[{"logIndex":"1851547484","logId":{"keyId":"wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="},"kindVersion":{"kind":"hashedrekord","version":"0.0.1"},"integratedTime":"1781701928","inclusionPromise":{"signedEntryTimestamp":"MEYCIQDl/9r4CLLrS4QQ3pgoERXkqgOu8J175XMWRogZd0pD9QIhAKKqKcFs/urgUIojMcTzma/v0miHksvtf/e0CYAcTyf/"},"inclusionProof":{"logIndex":"1729643222","rootHash":"d2YR8PsiavxlU1mWQTuknMnLG0hB94xQrxkWQH4MAWg=","treeSize":"1729643237","hashes":["WOkbi8t5T96oGLPXvMJLBG6tjgE+FbwH5WD/DlXW7V8=","96lbMQvpGQmDasvoGfaSrU6aALFCpwNgHChWFhXWNQw=","scSXRqlAwZCM9FW1ZTArFT+WdafQWCPkvi63WjCwGSI=","2JH/NjO8pW7Tl4rmHFgPPciYyS7eHC5p6lCAWN4Hwgc=","Ucph7+nD7Xsl136N9pouoYNdji3ZbYWg+jl98wduUpc=","2kfcco1m25Ift/pgK5YZ3iVPOrz9r83x+S2FJjhnOqg=","1lDJ0gtYDj4AY6Y0aeoHLvrfFCnfZT39PmXC6qldZ/A=","N3X9cecXj3ztkQd5ni4RsrA6ynSQZ/BYoisbueVosuY=","WOT6l6C/2O+HgFq4iZSilKHsx7x+Vnf9VvTqGgb/odM=","v8dESBp721LFExEAv/0tR1e6Rh1pBFHHAvDk7Oc7U1E=","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\n1729643237\nd2YR8PsiavxlU1mWQTuknMnLG0hB94xQrxkWQH4MAWg=\n\n— rekor.sigstore.dev wNI9ajBFAiABOnfelsKpgYmcBv15SCy1mX6GPGnZa0WFylcmDc26vAIhAMMMND1TTD6sbL4B3XGxjHQOG5oLWNpXx2vrB+FABV43\n"}},"canonicalizedBody":"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI2NmRiYTFlNTBjZmE4ZGMxZmFhMDk1ZGNjNzNkZGQ3ZWYxODYzYThlNGIwMzAwODQzMGZjMDZkZDQ0YTQ3NzNlIn19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FVUNJUURCYkI3UXRoa1FvL2JOUWY3dVlzakhkV0FzZjV2TkRJaUFLVFB1UTAzL2xRSWdEWjFyVGJpWW9pQXFlMUhqUWlwcU0ra3QzTzN4M1JDcEZEU3FnT2pSdlVjPSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVaFRla05EUW5SSFowRjNTVUpCWjBsVlN6ZGxkVFJwYkZSalFUbE5TMmszT1hNcmNHVTJWVGhOUkZOQmQwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcFpkMDVxUlROTlZFMTRUV3BCTTFkb1kwNU5hbGwzVG1wRk0wMVVUWGxOYWtFelYycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVZEV2xvclNsQXhXVGRDU0VOeVpDc3JPRmhoVjFWUWRFbENaMUY2VjNOUldHMDBUVEVLTVdnM1ExSnZhRFV3U21OQk1VaEhUV1ZDY0RsUVdHWkRNWFJyYkdaS2JUQjVUR3hyWTBaMVR6UXhNbVZwV0VaT2FuRlBRMEptUVhkbloxaHpUVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXUkNaMDVXU0ZFMFJVWm5VVlZ5WVVOaUNubHdlbU13ZUVKQ1pUWlFOV0kwVlhoQ2QwazJjMjEzZDBoM1dVUldVakJxUWtKbmQwWnZRVlV6T1ZCd2VqRlphMFZhWWpWeFRtcHdTMFpYYVhocE5Ga0tXa1E0ZDJGM1dVUldVakJTUVZGSUwwSkhSWGRZTkZwa1lVaFNNR05JVFRaTWVUbHVZVmhTYjJSWFNYVlpNamwwVERKb01Wb3laSEJpYldSdFdWZE9iQXBNTW5Sc1kyMDFiR0pJVFhSWk1qbDBZbGhXZFdGWVVqVk1lVFZ1WVZoU2IyUlhTWFprTWpsNVlUSmFjMkl6WkhwTU1rb3hZVmQ0YTB4dWJHaGlWM2hCQ21OdFZtMWplVGx2V2xkR2EyTjVPWFJaVjJ4MVRVUnJSME5wYzBkQlVWRkNaemM0ZDBGUlJVVkxNbWd3WkVoQ2VrOXBPSFprUnpseVdsYzBkVmxYVGpBS1lWYzVkV041Tlc1aFdGSnZaRmRLTVdNeVZubFpNamwxWkVkV2RXUkROV3BpTWpCM1NIZFpTMHQzV1VKQ1FVZEVkbnBCUWtGblVWSmtNamw1WVRKYWN3cGlNMlJtV2tkc2VtTkhSakJaTW1kM1RtZFpTMHQzV1VKQ1FVZEVkbnBCUWtGM1VXOVBSRkV5VFZSWk1WbHFSWGhhUkZVelRWUkJNVTlFU1RWTmJWRXhDazVFVW1sT1JFbDVUWHBSTWsxNlJUTmFSMVY0VFVSS2ExcHFRVlJDWjI5eVFtZEZSVUZaVHk5TlFVVkZRa0ZXUTJSWGJITmFSRUZ5UW1kdmNrSm5SVVVLUVZsUEwwMUJSVVpDUWpGdlpGZGtibUZYTlc1YWJVWnFXbE01Y2xwWVNuVmFWM2g2VEZkT2RtSlhNVEZpYld3d1pWUkJaRUpuYjNKQ1owVkZRVmxQTHdwTlFVVkhRa0U1ZVZwWFducE1NbWhzV1ZkU2Vrd3lNV2hoVnpSM1QzZFpTMHQzV1VKQ1FVZEVkbnBCUWtOQlVYUkVRM1J2WkVoU2QyTjZiM1pNTTFKMkNtRXlWblZNYlVacVpFZHNkbUp1VFhWYU1td3dZVWhXYVdSWVRteGpiVTUyWW01U2JHSnVVWFZaTWpsMFRVY3dSME5wYzBkQlVWRkNaemM0ZDBGUmEwVUtXSGQ0WkdGSVVqQmpTRTAyVEhrNWJtRllVbTlrVjBsMVdUSTVkRXd5YURGYU1tUndZbTFrYlZsWFRteE1NblJzWTIwMWJHSklUWFJaTWpsMFlsaFdkUXBoV0ZJMVRIazFibUZZVW05a1YwbDJaREk1ZVdFeVduTmlNMlI2VERKS01XRlhlR3RNYm14b1lsZDRRV050Vm0xamVUbHZXbGRHYTJONU9YUlpWMngxQ2sxRVowZERhWE5IUVZGUlFtYzNPSGRCVVc5RlMyZDNiMDlFVVRKTlZGa3hXV3BGZUZwRVZUTk5WRUV4VDBSSk5VMXRVVEZPUkZKcFRrUkplVTE2VVRJS1RYcEZNMXBIVlhoTlJFcHJXbXBCWWtKbmIzSkNaMFZGUVZsUEwwMUJSVXhDUVRCTlF6Tk9iR0pIV1hSaFJ6bDZaRWRXYTAxRlFVZERhWE5IUVZGUlFncG5OemgzUVZGM1JVMW5kM2RoU0ZJd1kwaE5Oa3g1T1c1aFdGSnZaRmRKZFZreU9YUk1NbWd4V2pKa2NHSnRaRzFaVjA1c1RESjBiR050Tld4aVNFMTBDbGt5T1hSaVdGWjFZVmhTTlUxRVowZERhWE5IUVZGUlFtYzNPSGRCVVRCRlMyZDNiMDlFVVRKTlZGa3hXV3BGZUZwRVZUTk5WRUV4VDBSSk5VMXRVVEVLVGtSU2FVNUVTWGxOZWxFeVRYcEZNMXBIVlhoTlJFcHJXbXBCWmtKbmIzSkNaMFZGUVZsUEwwMUJSVTlDUWtWTlJETktiRnB1VFhaaFIxWm9Xa2hOZGdwaVYwWndZbXBCWVVKbmIzSkNaMFZGUVZsUEwwMUJSVkJDUVhkTlEycEZkMDU2UlRCT2VsVXhUV3ByZDB4bldVdExkMWxDUWtGSFJIWjZRVUpGUVZGbkNrUkNOVzlrU0ZKM1kzcHZka3d5WkhCa1IyZ3hXV2sxYW1JeU1IWmhTRlp1V2pKc2RWb3lXbWhaTWxWM1IwRlpTMHQzV1VKQ1FVZEVkbnBCUWtWUlVVc0tSRUZuZVU1VVkzbE5SR013VFhwQ2RFSm5iM0pDWjBWRlFWbFBMMDFCUlZOQ1JqaE5XRmRvTUdSSVFucFBhVGgyV2pKc01HRklWbWxNYlU1MllsTTVid3BrVjJSdVlWYzFibHB0Um1wYVV6bHlXbGhLZFZwWGVIcE1WMDUyWWxjeE1XSnRiREJsVXpoMVdqSnNNR0ZJVm1sTU0yUjJZMjEwYldKSE9UTmplVGxwQ21SWGJITmFRelUxV1ZjeGMxRklTbXhhYmsxMllVZFdhRnBJVFhaaVYwWndZbXBCTkVKbmIzSkNaMFZGUVZsUEwwMUJSVlJDUTI5TlMwUm5NRTVxUlRJS1RsZEplRTFYVVRGT2VrVjNUbFJuZVU5VVNtdE9WRkV3V1dwUmVVMXFUVEJPYWsxNFRqSlNiRTFVUVhsYVIxbDNTVkZaUzB0M1dVSkNRVWRFZG5wQlFncEdRVkZVUkVKR00ySXpTbkphYlhoMlpERTVhMkZZVG5kWldGSnFZVVJDYTBKbmIzSkNaMFZGUVZsUEwwMUJSVlpDUmxsTlZrZG9NR1JJUW5wUGFUaDJDbG95YkRCaFNGWnBURzFPZG1KVE9XOWtWMlJ1WVZjMWJscHRSbXBhVXpseVdsaEtkVnBYZUhwTVYwNTJZbGN4TVdKdGJEQmxVemxvV1ROU2NHSXlOWG9LVEROS01XSnVUWFpOYW1NeVQxUkZlVTFVVlhoTmVtdDJXVmhTTUZwWE1YZGtTRTEyVFZSQlYwSm5iM0pDWjBWRlFWbFBMMDFCUlZkQ1FXZE5RbTVDTVFwWmJYaHdXWHBDUjBKbmIzSkNaMFZGUVZsUEwwMUJSVmxDUkdkTlRtNUtiR05IT0RaaFNGWnVXakpzZFZveVdtaFpNbFYyWVRKV2VXSnRWbk5qZVRGcUNtSXlNWFJrVnpWd1pFaHJObU50Vm0xUGJrcHNXbTVOZG1GSFZtaGFTRTEyWWxkR2NHSnFRMEpwWjFsTFMzZFpRa0pCU0ZkbFVVbEZRV2RTT0VKSWIwRUtaVUZDTWtGT01EbE5SM0pIZUhoRmVWbDRhMlZJU214dVRuZExhVk5zTmpRemFubDBMelJsUzJOdlFYWkxaVFpQUVVGQlFtNTBWekUwZWtGQlFVRlJSQXBCUldOM1VsRkphRUZPVWpCR1NVNUNiRlo0VVN0VmNsZHhaVEZIYlRGaGMxSjVlVzAwZFZadlEzWkpObGhwTmpoSVpGTTRRV2xCZDNGMFJrdHFTbTk2Q2pSYWNGZHdNUzl3Y2pKSFFVcEdNbUpCUVZFNVRtNTVOelk0T0VaaU0zaDBZbXBCUzBKblozRm9hMnBQVUZGUlJFRjNUbTlCUkVKc1FXcEZRVGxrZFZjS1VpOURURlpyV2twM1ZXRkpNaXRNVGxwcVRFSkJlVzA1Ym5OaFRHZENlWGNySzBoMGIydGxNa2xZZURFMWN6UnFSRFUyT0daRVNUaHNWMmhxUVdwQ1pBcDBUM28yYzNwVFUweGxXa1pTYmxONVJEa3diRGR6YzIwNVNVNWFVakVyU0VNMWFXTjZRbTVLYldONWFGTmhNWE5ZZW01WGQzQnJZVlV2YWpGR1dYYzlDaTB0TFMwdFJVNUVJRU5GVWxSSlJrbERRVlJGTFMwdExTMEsifX19fQ=="}],"timestampVerificationData":{"rfc3161Timestamps":[{"signedTimestamp":"MIICyTADAgEAMIICwAYJKoZIhvcNAQcCoIICsTCCAq0CAQMxDTALBglghkgBZQMEAgEwgbcGCyqGSIb3DQEJEAEEoIGnBIGkMIGhAgEBBgkrBgEEAYO/MAIwMTANBglghkgBZQMEAgEFAAQg95ymQflF1KOugV3rCEAcia0It+U6GO/o+inRA6Ukw/MCFH/a74pw2WWfo0u5xXACrtDVUl1qGA8yMDI2MDYxNzEzMTIwN1owAwIBAaAypDAwLjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MRUwEwYDVQQDEwxzaWdzdG9yZS10c2GgADGCAdswggHXAgEBMFEwOTEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MSAwHgYDVQQDExdzaWdzdG9yZS10c2Etc2VsZnNpZ25lZAIUOhNULwyQYe68wUMvy4qOiyojiwwwCwYJYIZIAWUDBAIBoIH8MBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAcBgkqhkiG9w0BCQUxDxcNMjYwNjE3MTMxMjA3WjAvBgkqhkiG9w0BCQQxIgQgMSUrKLihMpIxLMfbbVMWPYDLZVdNCYeXctPeWo8abpYwgY4GCyqGSIb3DQEJEAIvMX8wfTB7MHkEIIX5J7wHq2LKw7RDVsEO/IGyxog/2nq55thw2dE6zQW3MFUwPaQ7MDkxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEgMB4GA1UEAxMXc2lnc3RvcmUtdHNhLXNlbGZzaWduZWQCFDoTVC8MkGHuvMFDL8uKjosqI4sMMAoGCCqGSM49BAMCBGcwZQIxALro8l8xdiK/G4V4W4HOiShvXk+S4Tm2a9UbLWfAdViCoS6oGyXhq40R01O+dYsb9AIwOaeIGFnr6SpMU4s117Qf6v8XdnkgsyROipTnBQ97AKa4/1/EV73fmQzxk5M8VDtM"}]}},"messageSignature":{"messageDigest":{"algorithm":"SHA2_256","digest":"Ztuh5Qz6jcH6oJXcxz3dfvGGOo5LAwCEMPwG3USkdz4="},"signature":"MEUCIQDBbB7QthkQo/bNQf7uYsjHdWAsf5vNDIiAKTPuQ03/lQIgDZ1rTbiYoiAqe1HjQipqM+kt3O3x3RCpFDSqgOjRvUc="}}
build/torch-cuda/utils.py CHANGED
@@ -1,6 +1,40 @@
1
- import torch
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  from contextlib import contextmanager
3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
 
5
  @contextmanager
6
  def device_context(device: torch.device):
@@ -11,3 +45,225 @@ def device_context(device: torch.device):
11
  yield
12
  else:
13
  yield
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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):
 
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)
build/torch-rocm/__init__.py DELETED
@@ -1,32 +0,0 @@
1
- from .act_quant import fp8_act_quant
2
- from .batched import (
3
- w8a8_fp8_matmul_batched,
4
- w8a8_block_fp8_matmul_batched,
5
- w8a8_tensor_fp8_matmul_batched,
6
- )
7
- from .grouped import (
8
- w8a8_fp8_matmul_grouped,
9
- w8a8_block_fp8_matmul_grouped,
10
- w8a8_tensor_fp8_matmul_grouped,
11
- )
12
- from .matmul import (
13
- w8a8_fp8_matmul,
14
- w8a8_block_fp8_matmul,
15
- w8a8_tensor_fp8_matmul,
16
- )
17
-
18
- __all__ = [
19
- "fp8_act_quant",
20
- # Single matmul
21
- "w8a8_fp8_matmul",
22
- "w8a8_block_fp8_matmul",
23
- "w8a8_tensor_fp8_matmul",
24
- # Batched matmul
25
- "w8a8_fp8_matmul_batched",
26
- "w8a8_block_fp8_matmul_batched",
27
- "w8a8_tensor_fp8_matmul_batched",
28
- # Grouped matmul
29
- "w8a8_fp8_matmul_grouped",
30
- "w8a8_block_fp8_matmul_grouped",
31
- "w8a8_tensor_fp8_matmul_grouped",
32
- ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
build/torch-rocm/_ops.py DELETED
@@ -1,38 +0,0 @@
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 = "bbc7e0f"
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/act_quant.py DELETED
@@ -1,73 +0,0 @@
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 .utils import device_context
21
-
22
-
23
- _FP8_DTYPE = torch.float8_e4m3fn
24
-
25
-
26
- # Copied from https://huggingface.co/deepseek-ai/DeepSeek-V3/blob/main/inference/kernel.py
27
- @triton.jit
28
- def _fp8_act_quant_kernel(x_ptr, y_ptr, s_ptr, BLOCK_SIZE: tl.constexpr):
29
- pid = tl.program_id(axis=0)
30
- offs = pid * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE)
31
- x = tl.load(x_ptr + offs).to(tl.float32)
32
- s = tl.max(tl.abs(x)) / 448.0 # float8_e4m3fn max
33
- y = (x / s).to(y_ptr.dtype.element_ty)
34
- tl.store(y_ptr + offs, y)
35
- tl.store(s_ptr + pid, s)
36
-
37
-
38
- @triton_op("finegrained_fp8::fp8_act_quant", mutates_args=())
39
- def _fp8_act_quant(
40
- x: torch.Tensor, block_size: int = 128
41
- ) -> tuple[torch.Tensor, torch.Tensor]:
42
- assert x.is_contiguous()
43
- assert x.shape[-1] % block_size == 0
44
- y = torch.empty_like(x, dtype=_FP8_DTYPE)
45
- grid = (triton.cdiv(x.numel(), block_size),)
46
- s = x.new_empty(*x.size()[:-1], x.size(-1) // block_size, dtype=torch.float32)
47
-
48
- with device_context(x.device):
49
- wrap_triton(_fp8_act_quant_kernel)[grid](x, y, s, BLOCK_SIZE=block_size)
50
-
51
- return y, s
52
-
53
-
54
- def fp8_act_quant(
55
- x: torch.Tensor, block_size: int = 128
56
- ) -> tuple[torch.Tensor, torch.Tensor]:
57
- """Quantize activations to FP8 with per-block dynamic scaling.
58
-
59
- Splits the last dimension of ``x`` into blocks of ``block_size`` elements,
60
- computes ``scale = max(|x_block|) / 448`` per block, and quantizes to
61
- ``float8_e4m3fn``.
62
-
63
- Args:
64
- x: Input tensor in bf16/fp16/fp32. Last dimension must be divisible by
65
- ``block_size`` and the tensor must be contiguous.
66
- block_size: Number of elements per quantization block (default: 128).
67
-
68
- Returns:
69
- A tuple ``(quantized, scales)`` where ``quantized`` has dtype
70
- ``float8_e4m3fn`` with the same shape as ``x``, and ``scales`` has
71
- shape ``(*x.shape[:-1], x.shape[-1] // block_size)`` in float32.
72
- """
73
- return torch.ops.finegrained_fp8.fp8_act_quant(x, block_size)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
build/torch-rocm/batched.py DELETED
@@ -1,390 +0,0 @@
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 .act_quant import fp8_act_quant
19
- from torch.library import triton_op, wrap_triton
20
-
21
- from .utils import device_context
22
-
23
-
24
- @triton.autotune(
25
- configs=[
26
- triton.Config({}, num_warps=w, num_stages=s)
27
- for w in [2, 4, 8, 16]
28
- for s in [2, 3, 4, 5]
29
- ],
30
- key=["N", "K"],
31
- )
32
- @triton.jit
33
- def w8a8_block_fp8_matmul_batched_kernel(
34
- A, # (S, K) raw BF16/FP16 activations
35
- B, # (E, N, K) FP8 weight matrices
36
- C, # (S, N) output
37
- Bs, # (E, N // BLOCK_SIZE_N, K // BLOCK_SIZE_K) weight scales
38
- ExpertIds, # (S,) — which expert each batch element routes to
39
- # Shape
40
- S,
41
- N,
42
- K,
43
- stride_am,
44
- stride_ak,
45
- stride_be,
46
- stride_bk,
47
- stride_bn,
48
- stride_cm,
49
- stride_cn,
50
- stride_bs_e,
51
- stride_bs_k,
52
- stride_bs_n,
53
- # Meta-parameters
54
- BLOCK_SIZE_N: tl.constexpr,
55
- BLOCK_SIZE_K: tl.constexpr,
56
- BLOCK_SIZE_M: tl.constexpr,
57
- ):
58
- """Block-scale batched FP8 expert matmul kernel.
59
-
60
- Each program handles one routed token row and one N-tile, looks up the
61
- owning expert from ``ExpertIds``, and applies fused activation quantization.
62
- """
63
- batch_id = tl.program_id(axis=0)
64
- pid_n = tl.program_id(axis=1)
65
-
66
- # Cast expert_id to int64 to prevent int32 overflow when computing
67
- # expert_id * stride_Eb (e.g. 255 * 9_437_184 > 2^31 for 256 experts of
68
- # 3072×3072 FP8 weights).
69
- expert_id = tl.load(ExpertIds + batch_id).to(tl.int64)
70
-
71
- A = A + batch_id * stride_am
72
- B = B + expert_id * stride_be
73
- C = C + batch_id * stride_cm
74
- Bs = Bs + expert_id * stride_bs_e
75
-
76
- offs_bn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
77
- offs_k = tl.arange(0, BLOCK_SIZE_K)
78
- a_ptrs = A + tl.arange(0, BLOCK_SIZE_M)[:, None] * 0 + offs_k[None, :] * stride_ak
79
- b_ptrs = B + (offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn)
80
-
81
- bs_ptrs = Bs + pid_n * stride_bs_n
82
-
83
- accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
84
- for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
85
- # ---- fused fp8_act_quant ----
86
- a_raw = tl.load(a_ptrs).to(tl.float32)
87
- a_s = tl.max(tl.abs(a_raw)) / 448.0
88
- a = (a_raw / tl.maximum(a_s, 1e-12)).to(tl.float8e4nv)
89
- # ---- matmul ----
90
- b = tl.load(b_ptrs)
91
- b_s = tl.load(bs_ptrs + k * stride_bs_k)
92
- accumulator += tl.dot(a, b) * a_s * b_s[None, :]
93
- a_ptrs += BLOCK_SIZE_K * stride_ak
94
- b_ptrs += BLOCK_SIZE_K * stride_bk
95
-
96
- if C.dtype.element_ty == tl.bfloat16:
97
- c = accumulator.to(tl.bfloat16)
98
- elif C.dtype.element_ty == tl.float16:
99
- c = accumulator.to(tl.float16)
100
- else:
101
- c = accumulator.to(tl.float32)
102
-
103
- offs_cm = tl.arange(0, BLOCK_SIZE_M)
104
- offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
105
- c_ptrs = C + offs_cm[:, None] * 0 + stride_cn * offs_cn[None, :]
106
- tl.store(c_ptrs, c)
107
-
108
-
109
- @triton.autotune(
110
- configs=[
111
- triton.Config({}, num_warps=w, num_stages=s)
112
- for w in [2, 4, 8, 16]
113
- for s in [2, 3, 4, 5]
114
- ],
115
- key=["N", "K"],
116
- )
117
- @triton.jit
118
- def w8a8_tensor_fp8_matmul_batched_kernel(
119
- A, # (S, K) pre-quantized FP8 activations
120
- B, # (E, N, K) FP8 weight matrices
121
- C, # (S, N) output
122
- As, # (S, 1) per-tensor activation scales
123
- Bs, # (E, 1, 1) per-tensor weight scales
124
- ExpertIds,
125
- S,
126
- N,
127
- K,
128
- stride_am,
129
- stride_ak,
130
- stride_be,
131
- stride_bk,
132
- stride_bn,
133
- stride_cm,
134
- stride_cn,
135
- stride_as_m,
136
- stride_bs_e,
137
- BLOCK_SIZE_N: tl.constexpr,
138
- BLOCK_SIZE_K: tl.constexpr,
139
- BLOCK_SIZE_M: tl.constexpr,
140
- ):
141
- """Tensor-scale batched FP8 expert matmul kernel.
142
-
143
- Activations are already quantized; the kernel applies per-token activation
144
- scales and per-expert tensor weight scales.
145
- """
146
- batch_id = tl.program_id(axis=0)
147
- pid_n = tl.program_id(axis=1)
148
-
149
- expert_id = tl.load(ExpertIds + batch_id).to(tl.int64)
150
-
151
- A = A + batch_id * stride_am
152
- B = B + expert_id * stride_be
153
- C = C + batch_id * stride_cm
154
- Bs = Bs + expert_id * stride_bs_e
155
-
156
- offs_bn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
157
- offs_k = tl.arange(0, BLOCK_SIZE_K)
158
- a_ptrs = A + tl.arange(0, BLOCK_SIZE_M)[:, None] * 0 + offs_k[None, :] * stride_ak
159
- b_ptrs = B + (offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn)
160
-
161
- b_s = tl.load(Bs)
162
- a_s = tl.load(As + batch_id * stride_as_m)
163
-
164
- accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
165
- for _ in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
166
- a = tl.load(a_ptrs)
167
- b = tl.load(b_ptrs)
168
- accumulator += tl.dot(a, b)
169
- a_ptrs += BLOCK_SIZE_K * stride_ak
170
- b_ptrs += BLOCK_SIZE_K * stride_bk
171
-
172
- accumulator = accumulator * a_s * b_s
173
-
174
- if C.dtype.element_ty == tl.bfloat16:
175
- c = accumulator.to(tl.bfloat16)
176
- elif C.dtype.element_ty == tl.float16:
177
- c = accumulator.to(tl.float16)
178
- else:
179
- c = accumulator.to(tl.float32)
180
-
181
- offs_cm = tl.arange(0, BLOCK_SIZE_M)
182
- offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
183
- c_ptrs = C + offs_cm[:, None] * 0 + stride_cn * offs_cn[None, :]
184
- tl.store(c_ptrs, c)
185
-
186
-
187
- @triton_op("finegrained_fp8::w8a8_block_fp8_matmul_batched", mutates_args=())
188
- def _w8a8_block_fp8_matmul_batched(
189
- A: torch.Tensor,
190
- B: torch.Tensor,
191
- Bs: torch.Tensor,
192
- expert_ids: torch.Tensor,
193
- block_size: list[int],
194
- ) -> torch.Tensor:
195
- """Block-scale batched FP8 matmul: C[s] = A[s] @ B[expert_ids[s]].T, with fused act quant.
196
-
197
- A: (S, K) raw bf16/fp16 activations
198
- B: (E, N, K) FP8 expert weights
199
- Bs: (E, N // block_n, K // block_k) per-block weight scales
200
- """
201
- assert A.ndim == 2, f"A must be 2D (S, K), got ndim={A.ndim}"
202
- assert A.is_contiguous(), "A must be contiguous"
203
- assert B.ndim == 3, f"B must be 3D (E, N, K), got ndim={B.ndim}"
204
- assert B.is_contiguous(), "B must be contiguous"
205
- assert A.shape[1] == B.shape[2], (
206
- f"K mismatch: A has K={A.shape[1]}, B has K={B.shape[2]}"
207
- )
208
-
209
- S, K = A.shape
210
- E, N, _ = B.shape
211
-
212
- assert len(block_size) == 2, (
213
- f"block_size must be [block_n, block_k], got {block_size}"
214
- )
215
- block_n, block_k = block_size[0], block_size[1]
216
- # MoE expert dimensions must be block-aligned; non-aligned N/K is not supported.
217
- assert N % block_n == 0, f"N ({N}) must be divisible by block_n ({block_n})"
218
- assert K % block_k == 0, f"K ({K}) must be divisible by block_k ({block_k})"
219
- assert Bs.ndim == 3, (
220
- f"Bs must be 3D (E, N//block_n, K//block_k), got ndim={Bs.ndim}"
221
- )
222
- assert Bs.shape == (E, N // block_n, K // block_k), (
223
- f"Bs shape {tuple(Bs.shape)} != expected ({E}, {N // block_n}, {K // block_k})"
224
- )
225
-
226
- C = A.new_empty(S, N)
227
- # Adaptive BLOCK_SIZE_M: smallest power-of-2 >= M, floored at 16, capped at 128.
228
- # Matches the WGMMA tile to the actual row count — smaller tiles use less
229
- # register pressure and a better-matched FP8 WGMMA instruction, improving
230
- # both accuracy and performance for small M (decode).
231
- BLOCK_SIZE_M = min(max(triton.next_power_of_2((S + E - 1) // E), 16), 128)
232
- grid = (S, triton.cdiv(N, block_n))
233
- with device_context(A.device):
234
- wrap_triton(w8a8_block_fp8_matmul_batched_kernel)[grid](
235
- A,
236
- B,
237
- C,
238
- Bs,
239
- expert_ids,
240
- S,
241
- N,
242
- K,
243
- A.stride(0),
244
- A.stride(1),
245
- B.stride(0),
246
- B.stride(2),
247
- B.stride(1),
248
- C.stride(0),
249
- C.stride(1),
250
- Bs.stride(0),
251
- Bs.stride(2),
252
- Bs.stride(1),
253
- BLOCK_SIZE_N=block_n,
254
- BLOCK_SIZE_K=block_k,
255
- BLOCK_SIZE_M=BLOCK_SIZE_M,
256
- )
257
-
258
- return C
259
-
260
-
261
- @triton_op("finegrained_fp8::w8a8_tensor_fp8_matmul_batched", mutates_args=())
262
- def _w8a8_tensor_fp8_matmul_batched(
263
- A: torch.Tensor,
264
- B: torch.Tensor,
265
- Bs: torch.Tensor,
266
- expert_ids: torch.Tensor,
267
- ) -> torch.Tensor:
268
- """Tensor-scale batched FP8 matmul: C[s] = A[s] @ B[expert_ids[s]].T, with fused act quant.
269
-
270
- A: (S, K) raw bf16/fp16 activations
271
- B: (E, N, K) FP8 expert weights
272
- Bs: (E,) or (E, 1, 1) per-expert weight scales
273
- """
274
- assert A.ndim == 2, f"A must be 2D (S, K), got ndim={A.ndim}"
275
- assert A.is_contiguous(), "A must be contiguous"
276
- assert B.ndim == 3, f"B must be 3D (E, N, K), got ndim={B.ndim}"
277
- assert B.is_contiguous(), "B must be contiguous"
278
- assert A.shape[1] == B.shape[2], (
279
- f"K mismatch: A has K={A.shape[1]}, B has K={B.shape[2]}"
280
- )
281
-
282
- S, K = A.shape
283
- E, N, _ = B.shape
284
-
285
- # Normalize Bs to (E, 1, 1)
286
- if Bs.ndim == 1:
287
- assert Bs.shape[0] == E, f"Bs shape {tuple(Bs.shape)} != expected ({E},)"
288
- Bs = Bs.reshape(E, 1, 1)
289
- else:
290
- assert Bs.shape == (E, 1, 1), (
291
- f"Bs shape {tuple(Bs.shape)} != expected ({E}, 1, 1)"
292
- )
293
-
294
- BLOCK_SIZE_N = 128
295
- BLOCK_SIZE_K = 128
296
- C = A.new_empty(S, N)
297
- qA, As = fp8_act_quant(A, K)
298
- grid = (S, triton.cdiv(N, BLOCK_SIZE_N))
299
- # Adaptive BLOCK_SIZE_M: smallest power-of-2 >= M, floored at 16, capped at 128.
300
- # Matches the WGMMA tile to the actual row count — smaller tiles use less
301
- # register pressure and a better-matched FP8 WGMMA instruction, improving
302
- # both accuracy and performance for small M (decode).
303
- BLOCK_SIZE_M = min(max(triton.next_power_of_2((S + E - 1) // E), 16), 128)
304
- grid = (S, triton.cdiv(N, BLOCK_SIZE_N))
305
- with device_context(A.device):
306
- wrap_triton(w8a8_tensor_fp8_matmul_batched_kernel)[grid](
307
- qA,
308
- B,
309
- C,
310
- As,
311
- Bs,
312
- expert_ids,
313
- S,
314
- N,
315
- K,
316
- qA.stride(0),
317
- qA.stride(1),
318
- B.stride(0),
319
- B.stride(2),
320
- B.stride(1),
321
- C.stride(0),
322
- C.stride(1),
323
- As.stride(0),
324
- Bs.stride(0),
325
- BLOCK_SIZE_N=BLOCK_SIZE_N,
326
- BLOCK_SIZE_K=BLOCK_SIZE_K,
327
- BLOCK_SIZE_M=BLOCK_SIZE_M,
328
- )
329
-
330
- return C
331
-
332
-
333
- def w8a8_block_fp8_matmul_batched(
334
- A: torch.Tensor,
335
- B: torch.Tensor,
336
- Bs: torch.Tensor,
337
- expert_ids: torch.Tensor,
338
- block_size: list[int],
339
- ) -> torch.Tensor:
340
- """Block-scale batched FP8 matmul with fused activation quantization.
341
-
342
- A: (S, K) raw activations, bf16/fp16/fp32
343
- B: (E, N, K) FP8 expert weights
344
- Bs: (E, N // block_n, K // block_k) per-block weight scales
345
- """
346
- return torch.ops.finegrained_fp8.w8a8_block_fp8_matmul_batched(
347
- A, B, Bs, expert_ids, block_size
348
- )
349
-
350
-
351
- def w8a8_tensor_fp8_matmul_batched(
352
- A: torch.Tensor,
353
- B: torch.Tensor,
354
- Bs: torch.Tensor,
355
- expert_ids: torch.Tensor,
356
- ) -> torch.Tensor:
357
- """Tensor-scale batched FP8 matmul with fused activation quantization.
358
-
359
- A: (S, K) raw activations, bf16/fp16/fp32
360
- B: (E, N, K) FP8 expert weights
361
- Bs: (E,) or (E, 1, 1) per-expert weight scales
362
- """
363
- return torch.ops.finegrained_fp8.w8a8_tensor_fp8_matmul_batched(
364
- A, B, Bs, expert_ids
365
- )
366
-
367
-
368
- def w8a8_fp8_matmul_batched(
369
- A: torch.Tensor,
370
- B: torch.Tensor,
371
- Bs: torch.Tensor,
372
- expert_ids: torch.Tensor,
373
- block_size: list[int] | None,
374
- ) -> torch.Tensor:
375
- """Unified batched W8A8 FP8 matmul dispatcher.
376
-
377
- Dispatch rules:
378
- - tensor mode when ``block_size is None``
379
- - tensor mode when ``block_size == [N, K]``
380
- - otherwise block mode
381
-
382
- Returns:
383
- Output tensor ``[S, N]`` in the same dtype as ``A``.
384
- """
385
- if block_size is None or (
386
- block_size[0] == B.size(1) and block_size[1] == B.size(2)
387
- ):
388
- return w8a8_tensor_fp8_matmul_batched(A, B, Bs, expert_ids)
389
-
390
- return w8a8_block_fp8_matmul_batched(A, B, Bs, expert_ids, block_size)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
build/torch-rocm/finegrained_fp8/__init__.py DELETED
@@ -1,26 +0,0 @@
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 DELETED
@@ -1,477 +0,0 @@
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 .act_quant import fp8_act_quant
19
- from torch.library import triton_op, wrap_triton
20
-
21
- from .utils import device_context
22
-
23
-
24
- @triton.autotune(
25
- configs=[
26
- triton.Config({}, num_warps=w, num_stages=s)
27
- for w in [2, 4, 8, 16]
28
- for s in [2, 3, 4, 5]
29
- ],
30
- key=["N", "K", "BLOCK_SIZE_M"],
31
- )
32
- @triton.jit
33
- def w8a8_block_fp8_matmul_grouped_kernel(
34
- A, # (S, K) raw BF16/FP16 activations, sorted/grouped by expert id
35
- B, # (E, N, K) FP8 weight matrices
36
- C, # (S, N) output
37
- Bs, # (E, N // BLOCK_SIZE_N, K // BLOCK_SIZE_K) weight scales
38
- Offsets, # (E,) int32 — cumulative row-end per expert
39
- TileOffsets, # (E,) int32 — cumulative tile-end per expert
40
- # Shape
41
- S,
42
- N,
43
- K,
44
- # Strides
45
- stride_am,
46
- stride_ak,
47
- stride_be,
48
- stride_bk,
49
- stride_bn,
50
- stride_cm,
51
- stride_cn,
52
- stride_bs_e,
53
- stride_bs_k,
54
- stride_bs_n,
55
- # Meta-parameters
56
- NUM_EXPERTS: tl.constexpr,
57
- BLOCK_SIZE_N: tl.constexpr,
58
- BLOCK_SIZE_K: tl.constexpr,
59
- BLOCK_SIZE_M: tl.constexpr,
60
- NUM_EXPERTS_BIT_LENGTH: tl.constexpr,
61
- ):
62
- """Block-scale grouped FP8 expert matmul kernel.
63
-
64
- Tokens are assumed sorted by expert. The kernel maps each M-tile to its
65
- owning expert via ``TileOffsets`` and applies fused activation quantization.
66
- """
67
- pid_m = tl.program_id(axis=0)
68
- pid_n = tl.program_id(axis=1)
69
-
70
- # Exit early for programs beyond the actual tile count.
71
- total_tiles = tl.load(TileOffsets + NUM_EXPERTS - 1)
72
- if pid_m >= total_tiles:
73
- return
74
-
75
- # Binary search in TileOffsets to find the owning expert.
76
- # Finds the smallest e such that TileOffsets[e] > pid_m (upper_bound semantics),
77
- # which is the expert whose tile range contains pid_m.
78
- # O(log2(NUM_EXPERTS)) loads instead of the O(NUM_EXPERTS) linear scan.
79
- # NUM_EXPERTS_BIT_LENGTH is ceil(log2(E))+1 for powers-of-two, giving one
80
- # harmless extra iteration when lo==hi; it's a compile-time constant so the
81
- # loop is fully unrolled by the compiler.
82
- lo = 0
83
- hi = NUM_EXPERTS
84
- for _ in tl.static_range(NUM_EXPERTS_BIT_LENGTH):
85
- mid = (lo + hi) >> 1
86
- mid_val = tl.load(TileOffsets + mid)
87
- is_left = mid_val <= pid_m
88
- lo = tl.where(is_left, mid + 1, lo)
89
- hi = tl.where(is_left, hi, mid)
90
-
91
- # Cast expert_id to int64 to prevent int32 overflow when computing
92
- # expert_id * stride_be (e.g. 255 * 9_437_184 > 2^31 for 256 experts of
93
- # 3072×3072 FP8 weights).
94
- expert_id = lo.to(tl.int64)
95
-
96
- prev_eid = tl.maximum(expert_id - 1, 0)
97
- expert_start = tl.where(expert_id == 0, 0, tl.load(Offsets + prev_eid))
98
- expert_end = tl.load(Offsets + expert_id)
99
- M_expert = expert_end - expert_start
100
-
101
- expert_tile_start = tl.where(expert_id == 0, 0, tl.load(TileOffsets + prev_eid))
102
- local_tile = pid_m - expert_tile_start
103
- m_off = local_tile * BLOCK_SIZE_M
104
-
105
- offs_am = m_off + tl.arange(0, BLOCK_SIZE_M)
106
- row_mask = offs_am < M_expert
107
- offs_global_m = expert_start + offs_am
108
-
109
- offs_bn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
110
- offs_k = tl.arange(0, BLOCK_SIZE_K)
111
-
112
- a_ptrs = A + offs_global_m[:, None] * stride_am + offs_k[None, :] * stride_ak
113
- b_ptrs = (
114
- B
115
- + expert_id * stride_be
116
- + offs_k[:, None] * stride_bk
117
- + offs_bn[None, :] * stride_bn
118
- )
119
- bs_ptrs = Bs + expert_id * stride_bs_e + pid_n * stride_bs_n
120
-
121
- accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
122
- for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
123
- # ---- fused fp8_act_quant ----
124
- a_raw = tl.load(a_ptrs, mask=row_mask[:, None], other=0.0).to(tl.float32)
125
- a_s = tl.max(tl.abs(a_raw), axis=1) / 448.0
126
- a = (a_raw / tl.maximum(a_s[:, None], 1e-12)).to(tl.float8e4nv)
127
- # ---- matmul ----
128
- b = tl.load(b_ptrs)
129
- b_s = tl.load(bs_ptrs + k * stride_bs_k)
130
- accumulator += tl.dot(a, b) * a_s[:, None] * b_s[None, :]
131
- a_ptrs += BLOCK_SIZE_K * stride_ak
132
- b_ptrs += BLOCK_SIZE_K * stride_bk
133
-
134
- if C.dtype.element_ty == tl.bfloat16:
135
- c = accumulator.to(tl.bfloat16)
136
- elif C.dtype.element_ty == tl.float16:
137
- c = accumulator.to(tl.float16)
138
- else:
139
- c = accumulator.to(tl.float32)
140
-
141
- c_ptrs = C + stride_cm * offs_global_m[:, None] + stride_cn * offs_bn[None, :]
142
- c_mask = row_mask[:, None]
143
- tl.store(c_ptrs, c, mask=c_mask)
144
-
145
-
146
- @triton.autotune(
147
- configs=[
148
- triton.Config({}, num_warps=w, num_stages=s)
149
- for w in [2, 4, 8, 16]
150
- for s in [2, 3, 4, 5]
151
- ],
152
- key=["N", "K", "BLOCK_SIZE_M"],
153
- )
154
- @triton.jit
155
- def w8a8_tensor_fp8_matmul_grouped_kernel(
156
- A, # (S, K) raw BF16/FP16 activations, sorted/grouped by expert idc
157
- B, # (E, N, K) FP8 weight matrices
158
- C, # (S, N) output
159
- As, # (S, 1) activation scales
160
- Bs, # (E, 1, 1) per-tensor weight scales
161
- Offsets,
162
- TileOffsets,
163
- S,
164
- N,
165
- K,
166
- stride_am,
167
- stride_ak,
168
- stride_be,
169
- stride_bk,
170
- stride_bn,
171
- stride_cm,
172
- stride_cn,
173
- stride_as_m,
174
- stride_bs_e,
175
- NUM_EXPERTS: tl.constexpr,
176
- BLOCK_SIZE_N: tl.constexpr,
177
- BLOCK_SIZE_K: tl.constexpr,
178
- BLOCK_SIZE_M: tl.constexpr,
179
- NUM_EXPERTS_BIT_LENGTH: tl.constexpr,
180
- ):
181
- """Tensor-scale grouped FP8 expert matmul kernel.
182
-
183
- Uses grouped expert scheduling with pre-quantized activations plus
184
- per-token activation scales and per-expert tensor weight scales.
185
- """
186
- pid_m = tl.program_id(axis=0)
187
- pid_n = tl.program_id(axis=1)
188
-
189
- total_tiles = tl.load(TileOffsets + NUM_EXPERTS - 1)
190
- if pid_m >= total_tiles:
191
- return
192
-
193
- lo = 0
194
- hi = NUM_EXPERTS
195
- for _ in tl.static_range(NUM_EXPERTS_BIT_LENGTH):
196
- mid = (lo + hi) >> 1
197
- mid_val = tl.load(TileOffsets + mid)
198
- is_left = mid_val <= pid_m
199
- lo = tl.where(is_left, mid + 1, lo)
200
- hi = tl.where(is_left, hi, mid)
201
- expert_id = lo.to(tl.int64)
202
-
203
- prev_eid = tl.maximum(expert_id - 1, 0)
204
- expert_start = tl.where(expert_id == 0, 0, tl.load(Offsets + prev_eid))
205
- expert_end = tl.load(Offsets + expert_id)
206
- M_expert = expert_end - expert_start
207
-
208
- expert_tile_start = tl.where(expert_id == 0, 0, tl.load(TileOffsets + prev_eid))
209
- local_tile = pid_m - expert_tile_start
210
- m_off = local_tile * BLOCK_SIZE_M
211
-
212
- offs_am = m_off + tl.arange(0, BLOCK_SIZE_M)
213
- row_mask = offs_am < M_expert
214
- offs_global_m = expert_start + offs_am
215
-
216
- offs_bn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
217
- offs_k = tl.arange(0, BLOCK_SIZE_K)
218
-
219
- a_ptrs = A + offs_global_m[:, None] * stride_am + offs_k[None, :] * stride_ak
220
- b_ptrs = (
221
- B
222
- + expert_id * stride_be
223
- + offs_k[:, None] * stride_bk
224
- + offs_bn[None, :] * stride_bn
225
- )
226
-
227
- a_s = tl.load(As + offs_global_m * stride_as_m, mask=row_mask, other=0.0)
228
- b_s = tl.load(Bs + expert_id * stride_bs_e)
229
-
230
- accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
231
- for _ in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
232
- a = tl.load(a_ptrs, mask=row_mask[:, None], other=0.0)
233
- b = tl.load(b_ptrs)
234
-
235
- accumulator += tl.dot(a, b)
236
- a_ptrs += BLOCK_SIZE_K * stride_ak
237
- b_ptrs += BLOCK_SIZE_K * stride_bk
238
-
239
- accumulator = accumulator * a_s[:, None] * b_s
240
-
241
- if C.dtype.element_ty == tl.bfloat16:
242
- c = accumulator.to(tl.bfloat16)
243
- elif C.dtype.element_ty == tl.float16:
244
- c = accumulator.to(tl.float16)
245
- else:
246
- c = accumulator.to(tl.float32)
247
-
248
- c_ptrs = C + stride_cm * offs_global_m[:, None] + stride_cn * offs_bn[None, :]
249
- c_mask = row_mask[:, None]
250
- tl.store(c_ptrs, c, mask=c_mask)
251
-
252
-
253
- @triton_op("finegrained_fp8::w8a8_block_fp8_matmul_grouped", mutates_args=())
254
- def _w8a8_block_fp8_matmul_grouped(
255
- A: torch.Tensor,
256
- B: torch.Tensor,
257
- Bs: torch.Tensor,
258
- offsets: torch.Tensor,
259
- tokens_per_expert: torch.Tensor,
260
- block_size: list[int],
261
- ) -> torch.Tensor:
262
- """Block-scale grouped FP8 matmul: C = A @ B.T per expert, with fused act quant.
263
-
264
- A: (S, K) raw bf16/fp16 activations, sorted by expert
265
- B: (E, N, K) FP8 expert weights
266
- Bs: (E, N // block_n, K // block_k) per-block weight scales
267
- """
268
- assert A.ndim == 2, f"A must be 2D (S, K), got ndim={A.ndim}"
269
- assert A.is_contiguous(), "A must be contiguous"
270
- assert B.ndim == 3, f"B must be 3D (E, N, K), got ndim={B.ndim}"
271
- assert B.is_contiguous(), "B must be contiguous"
272
- assert A.shape[1] == B.shape[2], (
273
- f"K mismatch: A has K={A.shape[1]}, B has K={B.shape[2]}"
274
- )
275
-
276
- S, K = A.shape
277
- E, N, _ = B.shape
278
-
279
- assert len(block_size) == 2, (
280
- f"block_size must be [block_n, block_k], got {block_size}"
281
- )
282
- block_n, block_k = block_size[0], block_size[1]
283
- # MoE expert dimensions must be block-aligned; non-aligned N/K is not supported.
284
- assert N % block_n == 0, f"N ({N}) must be divisible by block_n ({block_n})"
285
- assert K % block_k == 0, f"K ({K}) must be divisible by block_k ({block_k})"
286
- assert Bs.ndim == 3, (
287
- f"Bs must be 3D (E, N//block_n, K//block_k), got ndim={Bs.ndim}"
288
- )
289
- assert Bs.shape == (E, N // block_n, K // block_k), (
290
- f"Bs shape {tuple(Bs.shape)} != expected ({E}, {N // block_n}, {K // block_k})"
291
- )
292
-
293
- C = A.new_empty(S, N)
294
- # Adaptive BLOCK_SIZE_M: match tile to average tokens per expert.
295
- BLOCK_SIZE_M = min(max(triton.next_power_of_2((S + E - 1) // E), 16), 128)
296
- tiles_per_expert = (tokens_per_expert + BLOCK_SIZE_M - 1) // BLOCK_SIZE_M
297
- tile_offsets = torch.cumsum(tiles_per_expert, dim=0).to(torch.int32)
298
- # Upper bound on M-tiles: sum_e ceil(M_e / BLOCK_M) <= ceil(S / BLOCK_M) + E.
299
- # Programs beyond the real tile count exit immediately via the early-return
300
- # guard inside the kernel. This is faster than syncing for the exact count
301
- # and keeps the grid size data-independent (cuda-graph / torch.compile safe).
302
- max_M_tiles = triton.cdiv(S, BLOCK_SIZE_M) + E
303
- grid = (max_M_tiles, triton.cdiv(N, block_n))
304
- with device_context(A.device):
305
- wrap_triton(w8a8_block_fp8_matmul_grouped_kernel)[grid](
306
- A,
307
- B,
308
- C,
309
- Bs,
310
- offsets,
311
- tile_offsets,
312
- S,
313
- N,
314
- K,
315
- A.stride(0),
316
- A.stride(1),
317
- B.stride(0),
318
- B.stride(2),
319
- B.stride(1),
320
- C.stride(0),
321
- C.stride(1),
322
- Bs.stride(0),
323
- Bs.stride(2),
324
- Bs.stride(1),
325
- # Meta-parameters
326
- NUM_EXPERTS=E,
327
- BLOCK_SIZE_N=block_n,
328
- BLOCK_SIZE_K=block_k,
329
- BLOCK_SIZE_M=BLOCK_SIZE_M,
330
- NUM_EXPERTS_BIT_LENGTH=E.bit_length(),
331
- )
332
-
333
- return C
334
-
335
-
336
- @triton_op("finegrained_fp8::w8a8_tensor_fp8_matmul_grouped", mutates_args=())
337
- def _w8a8_tensor_fp8_matmul_grouped(
338
- A: torch.Tensor,
339
- B: torch.Tensor,
340
- Bs: torch.Tensor,
341
- offsets: torch.Tensor,
342
- tokens_per_expert: torch.Tensor,
343
- ) -> torch.Tensor:
344
- """Tensor-scale grouped FP8 matmul: C = A @ B.T per expert, with fused act quant.
345
-
346
- A: (S, K) raw bf16/fp16 activations, sorted by expert
347
- B: (E, N, K) FP8 expert weights
348
- Bs: (E,) or (E, 1, 1) per-expert weight scales
349
- """
350
- assert A.ndim == 2, f"A must be 2D (S, K), got ndim={A.ndim}"
351
- assert A.is_contiguous(), "A must be contiguous"
352
- assert B.ndim == 3, f"B must be 3D (E, N, K), got ndim={B.ndim}"
353
- assert B.is_contiguous(), "B must be contiguous"
354
- assert A.shape[1] == B.shape[2], (
355
- f"K mismatch: A has K={A.shape[1]}, B has K={B.shape[2]}"
356
- )
357
-
358
- S, K = A.shape
359
- E, N, _ = B.shape
360
-
361
- # Normalize Bs to (E, 1, 1)
362
- if Bs.ndim == 1:
363
- assert Bs.shape[0] == E, f"Bs shape {tuple(Bs.shape)} != expected ({E},)"
364
- Bs = Bs.reshape(E, 1, 1)
365
- else:
366
- assert Bs.shape == (E, 1, 1), (
367
- f"Bs shape {tuple(Bs.shape)} != expected ({E}, 1, 1)"
368
- )
369
-
370
- BLOCK_SIZE_N = 128
371
- BLOCK_SIZE_K = 128
372
- C = A.new_empty(S, N)
373
- qA, As = fp8_act_quant(A, K)
374
- BLOCK_SIZE_M = min(max(triton.next_power_of_2((S + E - 1) // E), 16), 128)
375
- tiles_per_expert = (tokens_per_expert + BLOCK_SIZE_M - 1) // BLOCK_SIZE_M
376
- tile_offsets = torch.cumsum(tiles_per_expert, dim=0).to(torch.int32)
377
- # Upper bound on M-tiles: sum_e ceil(M_e / BLOCK_M) <= ceil(S / BLOCK_M) + E.
378
- # Programs beyond the real tile count exit immediately via the early-return
379
- # guard inside the kernel. This is faster than syncing for the exact count
380
- # and keeps the grid size data-independent (cuda-graph / torch.compile safe).
381
- max_M_tiles = triton.cdiv(S, BLOCK_SIZE_M) + E
382
- grid = (max_M_tiles, triton.cdiv(N, BLOCK_SIZE_N))
383
- with device_context(A.device):
384
- wrap_triton(w8a8_tensor_fp8_matmul_grouped_kernel)[grid](
385
- qA,
386
- B,
387
- C,
388
- As,
389
- Bs,
390
- offsets,
391
- tile_offsets,
392
- S,
393
- N,
394
- K,
395
- qA.stride(0),
396
- qA.stride(1),
397
- B.stride(0),
398
- B.stride(2),
399
- B.stride(1),
400
- C.stride(0),
401
- C.stride(1),
402
- As.stride(0),
403
- Bs.stride(0),
404
- # Meta-parameters
405
- NUM_EXPERTS=E,
406
- BLOCK_SIZE_N=BLOCK_SIZE_N,
407
- BLOCK_SIZE_K=BLOCK_SIZE_K,
408
- BLOCK_SIZE_M=BLOCK_SIZE_M,
409
- NUM_EXPERTS_BIT_LENGTH=E.bit_length(),
410
- )
411
-
412
- return C
413
-
414
-
415
- def w8a8_block_fp8_matmul_grouped(
416
- A: torch.Tensor,
417
- B: torch.Tensor,
418
- Bs: torch.Tensor,
419
- offsets: torch.Tensor,
420
- tokens_per_expert: torch.Tensor,
421
- block_size: list[int],
422
- ) -> torch.Tensor:
423
- """Block-scale grouped FP8 matmul with fused activation quantization.
424
-
425
- A: (S, K) raw activations sorted by expert, bf16/fp16/fp32
426
- B: (E, N, K) FP8 expert weights
427
- Bs: (E, N // block_n, K // block_k) per-block weight scales
428
- """
429
- return torch.ops.finegrained_fp8.w8a8_block_fp8_matmul_grouped(
430
- A, B, Bs, offsets, tokens_per_expert, block_size
431
- )
432
-
433
-
434
- def w8a8_tensor_fp8_matmul_grouped(
435
- A: torch.Tensor,
436
- B: torch.Tensor,
437
- Bs: torch.Tensor,
438
- offsets: torch.Tensor,
439
- tokens_per_expert: torch.Tensor,
440
- ) -> torch.Tensor:
441
- """Tensor-scale grouped FP8 matmul with fused activation quantization.
442
-
443
- A: (S, K) raw activations sorted by expert, bf16/fp16/fp32
444
- B: (E, N, K) FP8 expert weights
445
- Bs: (E,) or (E, 1, 1) per-expert weight scales
446
- """
447
- return torch.ops.finegrained_fp8.w8a8_tensor_fp8_matmul_grouped(
448
- A, B, Bs, offsets, tokens_per_expert
449
- )
450
-
451
-
452
- def w8a8_fp8_matmul_grouped(
453
- A: torch.Tensor,
454
- B: torch.Tensor,
455
- Bs: torch.Tensor,
456
- offsets: torch.Tensor,
457
- tokens_per_expert: torch.Tensor,
458
- block_size: list[int] | None,
459
- ) -> torch.Tensor:
460
- """Unified grouped W8A8 FP8 matmul dispatcher.
461
-
462
- Dispatch rules:
463
- - tensor mode when ``block_size is None``
464
- - tensor mode when ``block_size == [N, K]``
465
- - otherwise block mode
466
-
467
- Returns:
468
- Output tensor ``[S, N]`` in the same dtype as ``A``, in expert-sorted order.
469
- """
470
- if block_size is None or (
471
- block_size[0] == B.size(1) and block_size[1] == B.size(2)
472
- ):
473
- return w8a8_tensor_fp8_matmul_grouped(A, B, Bs, offsets, tokens_per_expert)
474
-
475
- return w8a8_block_fp8_matmul_grouped(
476
- A, B, Bs, offsets, tokens_per_expert, block_size
477
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
build/torch-rocm/matmul.py DELETED
@@ -1,411 +0,0 @@
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 .utils import device_context
21
-
22
-
23
- # Adapted from https://github.com/sgl-project/sglang/blob/main/python/sglang/srt/layers/quantization/fp8_kernel.py
24
- @triton.autotune(
25
- configs=[
26
- triton.Config({}, num_warps=w, num_stages=s)
27
- for w in [2, 4, 8, 16]
28
- for s in [2, 3, 4]
29
- ],
30
- key=["N", "K", "BLOCK_SIZE_M"],
31
- )
32
- @triton.jit
33
- def w8a8_block_fp8_matmul_kernel(
34
- # Pointers to inputs and output
35
- A,
36
- B,
37
- C,
38
- As,
39
- Bs,
40
- # Shape for matmul
41
- M,
42
- N,
43
- K,
44
- stride_am,
45
- stride_ak,
46
- stride_bk,
47
- stride_bn,
48
- stride_cm,
49
- stride_cn,
50
- stride_as_m,
51
- stride_as_k,
52
- stride_bs_k,
53
- stride_bs_n,
54
- # Meta-parameters
55
- BLOCK_SIZE_M: tl.constexpr,
56
- BLOCK_SIZE_N: tl.constexpr,
57
- BLOCK_SIZE_K: tl.constexpr,
58
- GROUP_SIZE_M: tl.constexpr,
59
- ):
60
- """Block-scale FP8 GEMM kernel.
61
-
62
- Computes ``C = A @ B.T`` with block-wise activation/weight scales.
63
- Uses a 2D grid with swizzle for L2 cache locality on B tiles.
64
- """
65
- pid_m = tl.program_id(axis=0)
66
- pid_n = tl.program_id(axis=1)
67
- num_pid_m = tl.cdiv(M, BLOCK_SIZE_M)
68
- num_pid_n = tl.cdiv(N, BLOCK_SIZE_N)
69
- pid_m, pid_n = tl.swizzle2d(pid_m, pid_n, num_pid_m, num_pid_n, GROUP_SIZE_M)
70
-
71
- offs_am = (pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)) % M
72
- offs_bn = (pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)) % N
73
- offs_k = tl.arange(0, BLOCK_SIZE_K)
74
- a_ptrs = A + (offs_am[:, None] * stride_am + offs_k[None, :] * stride_ak)
75
- b_ptrs = B + (offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn)
76
-
77
- as_ptrs = As + offs_am * stride_as_m
78
- offs_bsn = offs_bn // BLOCK_SIZE_N
79
- bs_ptrs = Bs + offs_bsn * stride_bs_n
80
-
81
- accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
82
- for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
83
- k_remaining = K - k * BLOCK_SIZE_K
84
- a = tl.load(a_ptrs, mask=offs_k[None, :] < k_remaining, other=0.0)
85
- b = tl.load(b_ptrs, mask=offs_k[:, None] < k_remaining, other=0.0)
86
-
87
- a_s = tl.load(as_ptrs + k * stride_as_k)
88
- b_s = tl.load(bs_ptrs + k * stride_bs_k)
89
-
90
- accumulator += tl.dot(a, b) * a_s[:, None] * b_s[None, :]
91
- a_ptrs += BLOCK_SIZE_K * stride_ak
92
- b_ptrs += BLOCK_SIZE_K * stride_bk
93
-
94
- if C.dtype.element_ty == tl.bfloat16:
95
- c = accumulator.to(tl.bfloat16)
96
- elif C.dtype.element_ty == tl.float16:
97
- c = accumulator.to(tl.float16)
98
- else:
99
- c = accumulator.to(tl.float32)
100
-
101
- offs_cm = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
102
- offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
103
- c_ptrs = C + stride_cm * offs_cm[:, None] + stride_cn * offs_cn[None, :]
104
- c_mask = (offs_cm[:, None] < M) & (offs_cn[None, :] < N)
105
- tl.store(c_ptrs, c, mask=c_mask)
106
-
107
-
108
- @triton.autotune(
109
- configs=[
110
- triton.Config({}, num_warps=w, num_stages=s)
111
- for w in [2, 4, 8, 16]
112
- for s in [2, 3, 4]
113
- ],
114
- key=["N", "K", "BLOCK_SIZE_M"],
115
- )
116
- @triton.jit
117
- def w8a8_tensor_fp8_matmul_kernel(
118
- A,
119
- B,
120
- C,
121
- As,
122
- Bs,
123
- M,
124
- N,
125
- K,
126
- stride_am,
127
- stride_ak,
128
- stride_bk,
129
- stride_bn,
130
- stride_cm,
131
- stride_cn,
132
- stride_as_m,
133
- BLOCK_SIZE_M: tl.constexpr,
134
- BLOCK_SIZE_N: tl.constexpr,
135
- BLOCK_SIZE_K: tl.constexpr,
136
- GROUP_SIZE_M: tl.constexpr,
137
- ):
138
- """Tensor-scale FP8 GEMM kernel.
139
-
140
- Computes ``C = A @ B.T`` with one activation scale per row and one
141
- weight scale for the full matrix.
142
- Uses a 2D grid with swizzle for L2 cache locality on B tiles.
143
- """
144
- pid_m = tl.program_id(axis=0)
145
- pid_n = tl.program_id(axis=1)
146
- num_pid_m = tl.cdiv(M, BLOCK_SIZE_M)
147
- num_pid_n = tl.cdiv(N, BLOCK_SIZE_N)
148
- pid_m, pid_n = tl.swizzle2d(pid_m, pid_n, num_pid_m, num_pid_n, GROUP_SIZE_M)
149
-
150
- offs_am = (pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)) % M
151
- offs_bn = (pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)) % N
152
- offs_k = tl.arange(0, BLOCK_SIZE_K)
153
-
154
- a_ptrs = A + offs_am[:, None] * stride_am + offs_k[None, :] * stride_ak
155
- b_ptrs = B + offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn
156
-
157
- a_s = tl.load(As + offs_am * stride_as_m)
158
- b_s = tl.load(Bs)
159
-
160
- # Accumulate raw dot products, apply scales once after the loop.
161
- accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
162
- for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
163
- k_remaining = K - k * BLOCK_SIZE_K
164
- a = tl.load(a_ptrs, mask=offs_k[None, :] < k_remaining, other=0.0)
165
- b = tl.load(b_ptrs, mask=offs_k[:, None] < k_remaining, other=0.0)
166
- accumulator += tl.dot(a, b)
167
- a_ptrs += BLOCK_SIZE_K * stride_ak
168
- b_ptrs += BLOCK_SIZE_K * stride_bk
169
-
170
- accumulator = accumulator * a_s[:, None] * b_s
171
-
172
- if C.dtype.element_ty == tl.bfloat16:
173
- c = accumulator.to(tl.bfloat16)
174
- elif C.dtype.element_ty == tl.float16:
175
- c = accumulator.to(tl.float16)
176
- else:
177
- c = accumulator.to(tl.float32)
178
-
179
- offs_cm = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
180
- offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
181
- c_ptrs = C + stride_cm * offs_cm[:, None] + stride_cn * offs_cn[None, :]
182
- c_mask = (offs_cm[:, None] < M) & (offs_cn[None, :] < N)
183
- tl.store(c_ptrs, c, mask=c_mask)
184
-
185
-
186
- @triton_op("finegrained_fp8::w8a8_block_fp8_matmul", mutates_args=())
187
- def _w8a8_block_fp8_matmul(
188
- A: torch.Tensor,
189
- B: torch.Tensor,
190
- As: torch.Tensor,
191
- Bs: torch.Tensor,
192
- block_size: list[int],
193
- output_dtype: torch.dtype = torch.float32,
194
- ) -> torch.Tensor:
195
- """Block-scale FP8 matmul: C = A @ B.T with per-block scales.
196
-
197
- As: (M, K // block_k) — per-token-group activation scales
198
- Bs: (N // block_n, K // block_k) — per-block weight scales
199
- """
200
- assert len(block_size) == 2, (
201
- f"block_size must be [block_n, block_k], got {block_size}"
202
- )
203
- block_n, block_k = block_size[0], block_size[1]
204
-
205
- assert A.shape[-1] == B.shape[-1], (
206
- f"K mismatch: A has K={A.shape[-1]}, B has K={B.shape[-1]}"
207
- )
208
- assert A.is_contiguous(), "A must be contiguous"
209
- assert B.ndim == 2, f"B must be 2D (N, K), got ndim={B.ndim}"
210
- assert B.is_contiguous(), "B must be contiguous"
211
-
212
- N, K = B.shape
213
- M = A.numel() // A.shape[-1]
214
-
215
- assert As.ndim >= 2, f"As must be at least 2D, got ndim={As.ndim}"
216
- assert As.shape[-1] == triton.cdiv(K, block_k), (
217
- f"As last dim {As.shape[-1]} != expected {triton.cdiv(K, block_k)} (cdiv(K={K}, block_k={block_k}))"
218
- )
219
- assert Bs.ndim == 2, f"Bs must be 2D (N//block_n, K//block_k), got ndim={Bs.ndim}"
220
- assert Bs.shape == (triton.cdiv(N, block_n), triton.cdiv(K, block_k)), (
221
- f"Bs shape {tuple(Bs.shape)} != expected ({triton.cdiv(N, block_n)}, {triton.cdiv(K, block_k)})"
222
- )
223
-
224
- BLOCK_SIZE_K = block_k
225
- BLOCK_SIZE_N = block_n
226
- C_shape = A.shape[:-1] + (N,)
227
- C = A.new_empty(C_shape, dtype=output_dtype)
228
- # Adaptive BLOCK_SIZE_M: smallest power-of-2 >= M, floored at 16, capped at 128.
229
- # Matches the WGMMA tile to the actual row count — smaller tiles use less
230
- # register pressure and a better-matched FP8 WGMMA instruction, improving
231
- # both accuracy and performance for small M (decode).
232
- BLOCK_SIZE_M = min(max(triton.next_power_of_2(M), 16), 128)
233
- grid = (triton.cdiv(M, BLOCK_SIZE_M), triton.cdiv(N, BLOCK_SIZE_N))
234
- with device_context(A.device):
235
- wrap_triton(w8a8_block_fp8_matmul_kernel)[grid](
236
- A,
237
- B,
238
- C,
239
- As,
240
- Bs,
241
- M,
242
- N,
243
- K,
244
- A.stride(-2),
245
- A.stride(-1),
246
- B.stride(1),
247
- B.stride(0),
248
- C.stride(-2),
249
- C.stride(-1),
250
- As.stride(-2),
251
- As.stride(-1),
252
- Bs.stride(1),
253
- Bs.stride(0),
254
- # Meta-parameters
255
- BLOCK_SIZE_M=BLOCK_SIZE_M,
256
- BLOCK_SIZE_N=BLOCK_SIZE_N,
257
- BLOCK_SIZE_K=BLOCK_SIZE_K,
258
- GROUP_SIZE_M=8,
259
- )
260
-
261
- return C
262
-
263
-
264
- @triton_op("finegrained_fp8::w8a8_tensor_fp8_matmul", mutates_args=())
265
- def _w8a8_tensor_fp8_matmul(
266
- A: torch.Tensor,
267
- B: torch.Tensor,
268
- As: torch.Tensor,
269
- Bs: torch.Tensor,
270
- output_dtype: torch.dtype = torch.float32,
271
- ) -> torch.Tensor:
272
- """Tensor-scale FP8 matmul: C = A @ B.T with per-row / per-tensor scales.
273
-
274
- As: scalar, (M,), or (M, 1) — per-row activation scales
275
- Bs: scalar, (1,), or (1, 1) — single weight scale
276
- """
277
- assert A.shape[-1] == B.shape[-1], (
278
- f"K mismatch: A has K={A.shape[-1]}, B has K={B.shape[-1]}"
279
- )
280
- assert A.is_contiguous(), "A must be contiguous"
281
- assert B.ndim == 2, f"B must be 2D (N, K), got ndim={B.ndim}"
282
- assert B.is_contiguous(), "B must be contiguous"
283
-
284
- N, K = B.shape
285
- M = A.numel() // A.shape[-1]
286
-
287
- # Normalize As to (M,)
288
- if As.numel() == 1:
289
- As = As.reshape(1).expand(M).contiguous()
290
- elif As.ndim == 2:
291
- As = As.reshape(M)
292
- assert As.ndim == 1 and As.shape[0] == M, (
293
- f"As must be scalar, (M,), or (M,1) with M={M}, got {tuple(As.shape)}"
294
- )
295
-
296
- # Normalize Bs to (1,)
297
- assert Bs.numel() == 1, f"Bs must be scalar or (1,), got {tuple(Bs.shape)}"
298
- Bs = Bs.reshape(1)
299
-
300
- BLOCK_SIZE_N = 128
301
- BLOCK_SIZE_K = 128
302
- C_shape = A.shape[:-1] + (N,)
303
- C = A.new_empty(C_shape, dtype=output_dtype)
304
- BLOCK_SIZE_M = min(max(triton.next_power_of_2(M), 16), 128)
305
- grid = (triton.cdiv(M, BLOCK_SIZE_M), triton.cdiv(N, BLOCK_SIZE_N))
306
- with device_context(A.device):
307
- wrap_triton(w8a8_tensor_fp8_matmul_kernel)[grid](
308
- A,
309
- B,
310
- C,
311
- As,
312
- Bs,
313
- M,
314
- N,
315
- K,
316
- A.stride(-2),
317
- A.stride(-1),
318
- B.stride(1),
319
- B.stride(0),
320
- C.stride(-2),
321
- C.stride(-1),
322
- As.stride(0),
323
- # Meta-parameters
324
- BLOCK_SIZE_M=BLOCK_SIZE_M,
325
- BLOCK_SIZE_N=BLOCK_SIZE_N,
326
- BLOCK_SIZE_K=BLOCK_SIZE_K,
327
- GROUP_SIZE_M=8,
328
- )
329
-
330
- return C
331
-
332
-
333
- def w8a8_block_fp8_matmul(
334
- A: torch.Tensor,
335
- B: torch.Tensor,
336
- As: torch.Tensor,
337
- Bs: torch.Tensor,
338
- block_size: list[int],
339
- output_dtype: torch.dtype = torch.float32,
340
- ) -> torch.Tensor:
341
- """Block-wise W8A8 FP8 matrix multiplication.
342
-
343
- Computes ``C = A @ B.T`` where both operands are pre-quantized to
344
- ``float8_e4m3fn`` with per-block scales, and accumulates in float32
345
- before casting to ``output_dtype``.
346
-
347
- Args:
348
- A: Quantized activation tensor ``[M, K]`` in ``float8_e4m3fn``.
349
- B: Quantized weight tensor ``[N, K]`` in ``float8_e4m3fn``.
350
- As: Per-token-group activation scales ``[M, K // block_size[1]]``.
351
- Bs: Per-block weight scales ``[N // block_size[0], K // block_size[1]]``.
352
- block_size: ``[block_n, block_k]`` quantization block dimensions, e.g. ``[128, 128]``.
353
- output_dtype: dtype of the returned tensor (default: ``torch.float32``).
354
-
355
- Returns:
356
- Output tensor ``[M, N]`` in ``output_dtype``.
357
- """
358
- return torch.ops.finegrained_fp8.w8a8_block_fp8_matmul(
359
- A, B, As, Bs, block_size, output_dtype
360
- )
361
-
362
-
363
- def w8a8_tensor_fp8_matmul(
364
- A: torch.Tensor,
365
- B: torch.Tensor,
366
- As: torch.Tensor,
367
- Bs: torch.Tensor,
368
- output_dtype: torch.dtype = torch.float32,
369
- ) -> torch.Tensor:
370
- """Tensor-scale W8A8 FP8 matrix multiplication.
371
-
372
- Computes ``C = A @ B.T`` in tensor-scale mode using pre-quantized FP8
373
- activations/weights and tensor scales.
374
-
375
- Args:
376
- A: Quantized activation tensor ``[M, K]`` in ``float8_e4m3fn``.
377
- B: Quantized weight tensor ``[N, K]`` in ``float8_e4m3fn``.
378
- As: Per-row activation scales ``[M]``.
379
- Bs: Single weight scale, scalar or ``[1]``.
380
- output_dtype: dtype of the returned tensor.
381
-
382
- Returns:
383
- Output tensor ``[M, N]`` in ``output_dtype``.
384
- """
385
- return torch.ops.finegrained_fp8.w8a8_tensor_fp8_matmul(A, B, As, Bs, output_dtype)
386
-
387
-
388
- def w8a8_fp8_matmul(
389
- A: torch.Tensor,
390
- B: torch.Tensor,
391
- As: torch.Tensor,
392
- Bs: torch.Tensor,
393
- block_size: list[int] | None,
394
- output_dtype: torch.dtype = torch.float32,
395
- ) -> torch.Tensor:
396
- """Unified W8A8 FP8 matmul dispatcher.
397
-
398
- Dispatch rules:
399
- - tensor mode when ``block_size is None``
400
- - tensor mode when ``block_size == [N, K]``
401
- - otherwise block mode
402
-
403
- Returns:
404
- Output tensor ``[M, N]`` in ``output_dtype``.
405
- """
406
- if block_size is None or (
407
- block_size[0] == B.size(0) and block_size[1] == B.size(1)
408
- ):
409
- return w8a8_tensor_fp8_matmul(A, B, As, Bs, output_dtype)
410
-
411
- return w8a8_block_fp8_matmul(A, B, As, Bs, block_size, output_dtype)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
build/torch-rocm/metadata.json DELETED
@@ -1,10 +0,0 @@
1
- {
2
- "name": "finegrained-fp8",
3
- "id": "_finegrained_fp8_rocm_bbc7e0f",
4
- "version": 1,
5
- "license": "Apache-2.0",
6
- "python-depends": [],
7
- "backend": {
8
- "type": "rocm"
9
- }
10
- }
 
 
 
 
 
 
 
 
 
 
 
build/torch-rocm/utils.py DELETED
@@ -1,13 +0,0 @@
1
- import torch
2
- from contextlib import contextmanager
3
-
4
-
5
- @contextmanager
6
- def device_context(device: torch.device):
7
- """Context manager that sets the active device for any backend (cuda, xpu, etc.)."""
8
- backend = getattr(torch, device.type, None)
9
- if backend is not None and hasattr(backend, "device"):
10
- with backend.device(device):
11
- yield
12
- else:
13
- yield
 
 
 
 
 
 
 
 
 
 
 
 
 
 
build/torch-xpu/__init__.py DELETED
@@ -1,32 +0,0 @@
1
- from .act_quant import fp8_act_quant
2
- from .batched import (
3
- w8a8_fp8_matmul_batched,
4
- w8a8_block_fp8_matmul_batched,
5
- w8a8_tensor_fp8_matmul_batched,
6
- )
7
- from .grouped import (
8
- w8a8_fp8_matmul_grouped,
9
- w8a8_block_fp8_matmul_grouped,
10
- w8a8_tensor_fp8_matmul_grouped,
11
- )
12
- from .matmul import (
13
- w8a8_fp8_matmul,
14
- w8a8_block_fp8_matmul,
15
- w8a8_tensor_fp8_matmul,
16
- )
17
-
18
- __all__ = [
19
- "fp8_act_quant",
20
- # Single matmul
21
- "w8a8_fp8_matmul",
22
- "w8a8_block_fp8_matmul",
23
- "w8a8_tensor_fp8_matmul",
24
- # Batched matmul
25
- "w8a8_fp8_matmul_batched",
26
- "w8a8_block_fp8_matmul_batched",
27
- "w8a8_tensor_fp8_matmul_batched",
28
- # Grouped matmul
29
- "w8a8_fp8_matmul_grouped",
30
- "w8a8_block_fp8_matmul_grouped",
31
- "w8a8_tensor_fp8_matmul_grouped",
32
- ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
build/torch-xpu/_ops.py DELETED
@@ -1,38 +0,0 @@
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 = "7c5619e"
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-xpu/act_quant.py DELETED
@@ -1,73 +0,0 @@
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 .utils import device_context
21
-
22
-
23
- _FP8_DTYPE = torch.float8_e4m3fn
24
-
25
-
26
- # Copied from https://huggingface.co/deepseek-ai/DeepSeek-V3/blob/main/inference/kernel.py
27
- @triton.jit
28
- def _fp8_act_quant_kernel(x_ptr, y_ptr, s_ptr, BLOCK_SIZE: tl.constexpr):
29
- pid = tl.program_id(axis=0)
30
- offs = pid * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE)
31
- x = tl.load(x_ptr + offs).to(tl.float32)
32
- s = tl.max(tl.abs(x)) / 448.0 # float8_e4m3fn max
33
- y = (x / s).to(y_ptr.dtype.element_ty)
34
- tl.store(y_ptr + offs, y)
35
- tl.store(s_ptr + pid, s)
36
-
37
-
38
- @triton_op("finegrained_fp8::fp8_act_quant", mutates_args=())
39
- def _fp8_act_quant(
40
- x: torch.Tensor, block_size: int = 128
41
- ) -> tuple[torch.Tensor, torch.Tensor]:
42
- assert x.is_contiguous()
43
- assert x.shape[-1] % block_size == 0
44
- y = torch.empty_like(x, dtype=_FP8_DTYPE)
45
- grid = (triton.cdiv(x.numel(), block_size),)
46
- s = x.new_empty(*x.size()[:-1], x.size(-1) // block_size, dtype=torch.float32)
47
-
48
- with device_context(x.device):
49
- wrap_triton(_fp8_act_quant_kernel)[grid](x, y, s, BLOCK_SIZE=block_size)
50
-
51
- return y, s
52
-
53
-
54
- def fp8_act_quant(
55
- x: torch.Tensor, block_size: int = 128
56
- ) -> tuple[torch.Tensor, torch.Tensor]:
57
- """Quantize activations to FP8 with per-block dynamic scaling.
58
-
59
- Splits the last dimension of ``x`` into blocks of ``block_size`` elements,
60
- computes ``scale = max(|x_block|) / 448`` per block, and quantizes to
61
- ``float8_e4m3fn``.
62
-
63
- Args:
64
- x: Input tensor in bf16/fp16/fp32. Last dimension must be divisible by
65
- ``block_size`` and the tensor must be contiguous.
66
- block_size: Number of elements per quantization block (default: 128).
67
-
68
- Returns:
69
- A tuple ``(quantized, scales)`` where ``quantized`` has dtype
70
- ``float8_e4m3fn`` with the same shape as ``x``, and ``scales`` has
71
- shape ``(*x.shape[:-1], x.shape[-1] // block_size)`` in float32.
72
- """
73
- return torch.ops.finegrained_fp8.fp8_act_quant(x, block_size)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
build/torch-xpu/batched.py DELETED
@@ -1,398 +0,0 @@
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 .act_quant import fp8_act_quant
19
- from torch.library import triton_op, wrap_triton
20
-
21
- from .utils import device_context
22
-
23
-
24
- @triton.autotune(
25
- configs=[
26
- triton.Config({}, num_warps=w, num_stages=s)
27
- for w in [2, 4, 8, 16]
28
- for s in [2, 3, 4, 5]
29
- ],
30
- key=["N", "K"],
31
- )
32
- @triton.jit
33
- def w8a8_block_fp8_matmul_batched_kernel(
34
- A, # (S, K) raw BF16/FP16 activations
35
- B, # (E, N, K) FP8 weight matrices
36
- C, # (S, N) output
37
- Bs, # (E, N // BLOCK_SIZE_N, K // BLOCK_SIZE_K) weight scales
38
- ExpertIds, # (S,) — which expert each batch element routes to
39
- # Shape
40
- S,
41
- N,
42
- K,
43
- stride_am,
44
- stride_ak,
45
- stride_be,
46
- stride_bk,
47
- stride_bn,
48
- stride_cm,
49
- stride_cn,
50
- stride_bs_e,
51
- stride_bs_k,
52
- stride_bs_n,
53
- # Meta-parameters
54
- BLOCK_SIZE_N: tl.constexpr,
55
- BLOCK_SIZE_K: tl.constexpr,
56
- BLOCK_SIZE_M: tl.constexpr,
57
- ):
58
- """Block-scale batched FP8 expert matmul kernel.
59
-
60
- Each program handles one routed token row and one N-tile, looks up the
61
- owning expert from ``ExpertIds``, and applies fused activation quantization.
62
- """
63
- batch_id = tl.program_id(axis=0)
64
- pid_n = tl.program_id(axis=1)
65
-
66
- # Cast expert_id to int64 to prevent int32 overflow when computing
67
- # expert_id * stride_Eb (e.g. 255 * 9_437_184 > 2^31 for 256 experts of
68
- # 3072×3072 FP8 weights).
69
- expert_id = tl.load(ExpertIds + batch_id).to(tl.int64)
70
-
71
- A = A + batch_id * stride_am
72
- B = B + expert_id * stride_be
73
- C = C + batch_id * stride_cm
74
- Bs = Bs + expert_id * stride_bs_e
75
-
76
- offs_bn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
77
- offs_k = tl.arange(0, BLOCK_SIZE_K)
78
- a_ptrs = A + tl.arange(0, BLOCK_SIZE_M)[:, None] * 0 + offs_k[None, :] * stride_ak
79
- b_ptrs = B + (offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn)
80
-
81
- bs_ptrs = Bs + pid_n * stride_bs_n
82
-
83
- accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
84
- for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
85
- # ---- fused fp8_act_quant ----
86
- a_raw = tl.load(a_ptrs).to(tl.float32)
87
- a_s = tl.max(tl.abs(a_raw)) / 448.0
88
- a = (a_raw / tl.maximum(a_s, 1e-12)).to(tl.float8e4nv)
89
- # ---- matmul ----
90
- b = tl.load(b_ptrs)
91
- b_s = tl.load(bs_ptrs + k * stride_bs_k)
92
- accumulator += tl.dot(a, b) * a_s * b_s[None, :]
93
- a_ptrs += BLOCK_SIZE_K * stride_ak
94
- b_ptrs += BLOCK_SIZE_K * stride_bk
95
-
96
- if C.dtype.element_ty == tl.bfloat16:
97
- c = accumulator.to(tl.bfloat16)
98
- elif C.dtype.element_ty == tl.float16:
99
- c = accumulator.to(tl.float16)
100
- else:
101
- c = accumulator.to(tl.float32)
102
-
103
- offs_cm = tl.arange(0, BLOCK_SIZE_M)
104
- offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
105
- c_ptrs = C + offs_cm[:, None] * 0 + stride_cn * offs_cn[None, :]
106
- # Fake-batch trick aliases all BLOCK_SIZE_M lanes to the same C row, so emitting
107
- # `tl.store(c_ptrs, c)` issues BLOCK_SIZE_M duplicate-address stores. On NVIDIA
108
- # WGMMA this is usually benign (last-write-wins of identical bytes), but on Intel
109
- # XPU the duplicate-address store has hardware-undefined behavior and corrupts the
110
- # output. Mask so only lane 0 stores — the (M, N) accumulator rows are
111
- # mathematically identical (same A row × same B), so lane 0 holds the right value.
112
- tl.store(c_ptrs, c, mask=(offs_cm == 0)[:, None])
113
-
114
-
115
- @triton.autotune(
116
- configs=[
117
- triton.Config({}, num_warps=w, num_stages=s)
118
- for w in [2, 4, 8, 16]
119
- for s in [2, 3, 4, 5]
120
- ],
121
- key=["N", "K"],
122
- )
123
- @triton.jit
124
- def w8a8_tensor_fp8_matmul_batched_kernel(
125
- A, # (S, K) pre-quantized FP8 activations
126
- B, # (E, N, K) FP8 weight matrices
127
- C, # (S, N) output
128
- As, # (S, 1) per-tensor activation scales
129
- Bs, # (E, 1, 1) per-tensor weight scales
130
- ExpertIds,
131
- S,
132
- N,
133
- K,
134
- stride_am,
135
- stride_ak,
136
- stride_be,
137
- stride_bk,
138
- stride_bn,
139
- stride_cm,
140
- stride_cn,
141
- stride_as_m,
142
- stride_bs_e,
143
- BLOCK_SIZE_N: tl.constexpr,
144
- BLOCK_SIZE_K: tl.constexpr,
145
- BLOCK_SIZE_M: tl.constexpr,
146
- ):
147
- """Tensor-scale batched FP8 expert matmul kernel.
148
-
149
- Activations are already quantized; the kernel applies per-token activation
150
- scales and per-expert tensor weight scales.
151
- """
152
- batch_id = tl.program_id(axis=0)
153
- pid_n = tl.program_id(axis=1)
154
-
155
- expert_id = tl.load(ExpertIds + batch_id).to(tl.int64)
156
-
157
- A = A + batch_id * stride_am
158
- B = B + expert_id * stride_be
159
- C = C + batch_id * stride_cm
160
- Bs = Bs + expert_id * stride_bs_e
161
-
162
- offs_bn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
163
- offs_k = tl.arange(0, BLOCK_SIZE_K)
164
- a_ptrs = A + tl.arange(0, BLOCK_SIZE_M)[:, None] * 0 + offs_k[None, :] * stride_ak
165
- b_ptrs = B + (offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn)
166
-
167
- b_s = tl.load(Bs)
168
- a_s = tl.load(As + batch_id * stride_as_m)
169
-
170
- accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
171
- for _ in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
172
- a = tl.load(a_ptrs)
173
- b = tl.load(b_ptrs)
174
- accumulator += tl.dot(a, b)
175
- a_ptrs += BLOCK_SIZE_K * stride_ak
176
- b_ptrs += BLOCK_SIZE_K * stride_bk
177
-
178
- accumulator = accumulator * a_s * b_s
179
-
180
- if C.dtype.element_ty == tl.bfloat16:
181
- c = accumulator.to(tl.bfloat16)
182
- elif C.dtype.element_ty == tl.float16:
183
- c = accumulator.to(tl.float16)
184
- else:
185
- c = accumulator.to(tl.float32)
186
-
187
- offs_cm = tl.arange(0, BLOCK_SIZE_M)
188
- offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
189
- c_ptrs = C + offs_cm[:, None] * 0 + stride_cn * offs_cn[None, :]
190
- # See block-FP8 kernel above: BLOCK_SIZE_M lanes alias the same C row;
191
- # mask so only lane 0 stores to avoid hardware-undefined duplicate writes on XPU.
192
- tl.store(c_ptrs, c, mask=(offs_cm == 0)[:, None])
193
-
194
-
195
- @triton_op("finegrained_fp8::w8a8_block_fp8_matmul_batched", mutates_args=())
196
- def _w8a8_block_fp8_matmul_batched(
197
- A: torch.Tensor,
198
- B: torch.Tensor,
199
- Bs: torch.Tensor,
200
- expert_ids: torch.Tensor,
201
- block_size: list[int],
202
- ) -> torch.Tensor:
203
- """Block-scale batched FP8 matmul: C[s] = A[s] @ B[expert_ids[s]].T, with fused act quant.
204
-
205
- A: (S, K) raw bf16/fp16 activations
206
- B: (E, N, K) FP8 expert weights
207
- Bs: (E, N // block_n, K // block_k) per-block weight scales
208
- """
209
- assert A.ndim == 2, f"A must be 2D (S, K), got ndim={A.ndim}"
210
- assert A.is_contiguous(), "A must be contiguous"
211
- assert B.ndim == 3, f"B must be 3D (E, N, K), got ndim={B.ndim}"
212
- assert B.is_contiguous(), "B must be contiguous"
213
- assert A.shape[1] == B.shape[2], (
214
- f"K mismatch: A has K={A.shape[1]}, B has K={B.shape[2]}"
215
- )
216
-
217
- S, K = A.shape
218
- E, N, _ = B.shape
219
-
220
- assert len(block_size) == 2, (
221
- f"block_size must be [block_n, block_k], got {block_size}"
222
- )
223
- block_n, block_k = block_size[0], block_size[1]
224
- # MoE expert dimensions must be block-aligned; non-aligned N/K is not supported.
225
- assert N % block_n == 0, f"N ({N}) must be divisible by block_n ({block_n})"
226
- assert K % block_k == 0, f"K ({K}) must be divisible by block_k ({block_k})"
227
- assert Bs.ndim == 3, (
228
- f"Bs must be 3D (E, N//block_n, K//block_k), got ndim={Bs.ndim}"
229
- )
230
- assert Bs.shape == (E, N // block_n, K // block_k), (
231
- f"Bs shape {tuple(Bs.shape)} != expected ({E}, {N // block_n}, {K // block_k})"
232
- )
233
-
234
- C = A.new_empty(S, N)
235
- # Adaptive BLOCK_SIZE_M: smallest power-of-2 >= M, floored at 16, capped at 128.
236
- # Matches the WGMMA tile to the actual row count — smaller tiles use less
237
- # register pressure and a better-matched FP8 WGMMA instruction, improving
238
- # both accuracy and performance for small M (decode).
239
- BLOCK_SIZE_M = min(max(triton.next_power_of_2((S + E - 1) // E), 16), 128)
240
- grid = (S, triton.cdiv(N, block_n))
241
- with device_context(A.device):
242
- wrap_triton(w8a8_block_fp8_matmul_batched_kernel)[grid](
243
- A,
244
- B,
245
- C,
246
- Bs,
247
- expert_ids,
248
- S,
249
- N,
250
- K,
251
- A.stride(0),
252
- A.stride(1),
253
- B.stride(0),
254
- B.stride(2),
255
- B.stride(1),
256
- C.stride(0),
257
- C.stride(1),
258
- Bs.stride(0),
259
- Bs.stride(2),
260
- Bs.stride(1),
261
- BLOCK_SIZE_N=block_n,
262
- BLOCK_SIZE_K=block_k,
263
- BLOCK_SIZE_M=BLOCK_SIZE_M,
264
- )
265
-
266
- return C
267
-
268
-
269
- @triton_op("finegrained_fp8::w8a8_tensor_fp8_matmul_batched", mutates_args=())
270
- def _w8a8_tensor_fp8_matmul_batched(
271
- A: torch.Tensor,
272
- B: torch.Tensor,
273
- Bs: torch.Tensor,
274
- expert_ids: torch.Tensor,
275
- ) -> torch.Tensor:
276
- """Tensor-scale batched FP8 matmul: C[s] = A[s] @ B[expert_ids[s]].T, with fused act quant.
277
-
278
- A: (S, K) raw bf16/fp16 activations
279
- B: (E, N, K) FP8 expert weights
280
- Bs: (E,) or (E, 1, 1) per-expert weight scales
281
- """
282
- assert A.ndim == 2, f"A must be 2D (S, K), got ndim={A.ndim}"
283
- assert A.is_contiguous(), "A must be contiguous"
284
- assert B.ndim == 3, f"B must be 3D (E, N, K), got ndim={B.ndim}"
285
- assert B.is_contiguous(), "B must be contiguous"
286
- assert A.shape[1] == B.shape[2], (
287
- f"K mismatch: A has K={A.shape[1]}, B has K={B.shape[2]}"
288
- )
289
-
290
- S, K = A.shape
291
- E, N, _ = B.shape
292
-
293
- # Normalize Bs to (E, 1, 1)
294
- if Bs.ndim == 1:
295
- assert Bs.shape[0] == E, f"Bs shape {tuple(Bs.shape)} != expected ({E},)"
296
- Bs = Bs.reshape(E, 1, 1)
297
- else:
298
- assert Bs.shape == (E, 1, 1), (
299
- f"Bs shape {tuple(Bs.shape)} != expected ({E}, 1, 1)"
300
- )
301
-
302
- BLOCK_SIZE_N = 128
303
- BLOCK_SIZE_K = 128
304
- C = A.new_empty(S, N)
305
- qA, As = fp8_act_quant(A, K)
306
- grid = (S, triton.cdiv(N, BLOCK_SIZE_N))
307
- # Adaptive BLOCK_SIZE_M: smallest power-of-2 >= M, floored at 16, capped at 128.
308
- # Matches the WGMMA tile to the actual row count — smaller tiles use less
309
- # register pressure and a better-matched FP8 WGMMA instruction, improving
310
- # both accuracy and performance for small M (decode).
311
- BLOCK_SIZE_M = min(max(triton.next_power_of_2((S + E - 1) // E), 16), 128)
312
- grid = (S, triton.cdiv(N, BLOCK_SIZE_N))
313
- with device_context(A.device):
314
- wrap_triton(w8a8_tensor_fp8_matmul_batched_kernel)[grid](
315
- qA,
316
- B,
317
- C,
318
- As,
319
- Bs,
320
- expert_ids,
321
- S,
322
- N,
323
- K,
324
- qA.stride(0),
325
- qA.stride(1),
326
- B.stride(0),
327
- B.stride(2),
328
- B.stride(1),
329
- C.stride(0),
330
- C.stride(1),
331
- As.stride(0),
332
- Bs.stride(0),
333
- BLOCK_SIZE_N=BLOCK_SIZE_N,
334
- BLOCK_SIZE_K=BLOCK_SIZE_K,
335
- BLOCK_SIZE_M=BLOCK_SIZE_M,
336
- )
337
-
338
- return C
339
-
340
-
341
- def w8a8_block_fp8_matmul_batched(
342
- A: torch.Tensor,
343
- B: torch.Tensor,
344
- Bs: torch.Tensor,
345
- expert_ids: torch.Tensor,
346
- block_size: list[int],
347
- ) -> torch.Tensor:
348
- """Block-scale batched FP8 matmul with fused activation quantization.
349
-
350
- A: (S, K) raw activations, bf16/fp16/fp32
351
- B: (E, N, K) FP8 expert weights
352
- Bs: (E, N // block_n, K // block_k) per-block weight scales
353
- """
354
- return torch.ops.finegrained_fp8.w8a8_block_fp8_matmul_batched(
355
- A, B, Bs, expert_ids, block_size
356
- )
357
-
358
-
359
- def w8a8_tensor_fp8_matmul_batched(
360
- A: torch.Tensor,
361
- B: torch.Tensor,
362
- Bs: torch.Tensor,
363
- expert_ids: torch.Tensor,
364
- ) -> torch.Tensor:
365
- """Tensor-scale batched FP8 matmul with fused activation quantization.
366
-
367
- A: (S, K) raw activations, bf16/fp16/fp32
368
- B: (E, N, K) FP8 expert weights
369
- Bs: (E,) or (E, 1, 1) per-expert weight scales
370
- """
371
- return torch.ops.finegrained_fp8.w8a8_tensor_fp8_matmul_batched(
372
- A, B, Bs, expert_ids
373
- )
374
-
375
-
376
- def w8a8_fp8_matmul_batched(
377
- A: torch.Tensor,
378
- B: torch.Tensor,
379
- Bs: torch.Tensor,
380
- expert_ids: torch.Tensor,
381
- block_size: list[int] | None,
382
- ) -> torch.Tensor:
383
- """Unified batched W8A8 FP8 matmul dispatcher.
384
-
385
- Dispatch rules:
386
- - tensor mode when ``block_size is None``
387
- - tensor mode when ``block_size == [N, K]``
388
- - otherwise block mode
389
-
390
- Returns:
391
- Output tensor ``[S, N]`` in the same dtype as ``A``.
392
- """
393
- if block_size is None or (
394
- block_size[0] == B.size(1) and block_size[1] == B.size(2)
395
- ):
396
- return w8a8_tensor_fp8_matmul_batched(A, B, Bs, expert_ids)
397
-
398
- return w8a8_block_fp8_matmul_batched(A, B, Bs, expert_ids, block_size)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
build/torch-xpu/finegrained_fp8/__init__.py DELETED
@@ -1,26 +0,0 @@
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-xpu/grouped.py DELETED
@@ -1,477 +0,0 @@
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 .act_quant import fp8_act_quant
19
- from torch.library import triton_op, wrap_triton
20
-
21
- from .utils import device_context
22
-
23
-
24
- @triton.autotune(
25
- configs=[
26
- triton.Config({}, num_warps=w, num_stages=s)
27
- for w in [2, 4, 8, 16]
28
- for s in [2, 3, 4, 5]
29
- ],
30
- key=["N", "K", "BLOCK_SIZE_M"],
31
- )
32
- @triton.jit
33
- def w8a8_block_fp8_matmul_grouped_kernel(
34
- A, # (S, K) raw BF16/FP16 activations, sorted/grouped by expert id
35
- B, # (E, N, K) FP8 weight matrices
36
- C, # (S, N) output
37
- Bs, # (E, N // BLOCK_SIZE_N, K // BLOCK_SIZE_K) weight scales
38
- Offsets, # (E,) int32 — cumulative row-end per expert
39
- TileOffsets, # (E,) int32 — cumulative tile-end per expert
40
- # Shape
41
- S,
42
- N,
43
- K,
44
- # Strides
45
- stride_am,
46
- stride_ak,
47
- stride_be,
48
- stride_bk,
49
- stride_bn,
50
- stride_cm,
51
- stride_cn,
52
- stride_bs_e,
53
- stride_bs_k,
54
- stride_bs_n,
55
- # Meta-parameters
56
- NUM_EXPERTS: tl.constexpr,
57
- BLOCK_SIZE_N: tl.constexpr,
58
- BLOCK_SIZE_K: tl.constexpr,
59
- BLOCK_SIZE_M: tl.constexpr,
60
- NUM_EXPERTS_BIT_LENGTH: tl.constexpr,
61
- ):
62
- """Block-scale grouped FP8 expert matmul kernel.
63
-
64
- Tokens are assumed sorted by expert. The kernel maps each M-tile to its
65
- owning expert via ``TileOffsets`` and applies fused activation quantization.
66
- """
67
- pid_m = tl.program_id(axis=0)
68
- pid_n = tl.program_id(axis=1)
69
-
70
- # Exit early for programs beyond the actual tile count.
71
- total_tiles = tl.load(TileOffsets + NUM_EXPERTS - 1)
72
- if pid_m >= total_tiles:
73
- return
74
-
75
- # Binary search in TileOffsets to find the owning expert.
76
- # Finds the smallest e such that TileOffsets[e] > pid_m (upper_bound semantics),
77
- # which is the expert whose tile range contains pid_m.
78
- # O(log2(NUM_EXPERTS)) loads instead of the O(NUM_EXPERTS) linear scan.
79
- # NUM_EXPERTS_BIT_LENGTH is ceil(log2(E))+1 for powers-of-two, giving one
80
- # harmless extra iteration when lo==hi; it's a compile-time constant so the
81
- # loop is fully unrolled by the compiler.
82
- lo = 0
83
- hi = NUM_EXPERTS
84
- for _ in tl.static_range(NUM_EXPERTS_BIT_LENGTH):
85
- mid = (lo + hi) >> 1
86
- mid_val = tl.load(TileOffsets + mid)
87
- is_left = mid_val <= pid_m
88
- lo = tl.where(is_left, mid + 1, lo)
89
- hi = tl.where(is_left, hi, mid)
90
-
91
- # Cast expert_id to int64 to prevent int32 overflow when computing
92
- # expert_id * stride_be (e.g. 255 * 9_437_184 > 2^31 for 256 experts of
93
- # 3072×3072 FP8 weights).
94
- expert_id = lo.to(tl.int64)
95
-
96
- prev_eid = tl.maximum(expert_id - 1, 0)
97
- expert_start = tl.where(expert_id == 0, 0, tl.load(Offsets + prev_eid))
98
- expert_end = tl.load(Offsets + expert_id)
99
- M_expert = expert_end - expert_start
100
-
101
- expert_tile_start = tl.where(expert_id == 0, 0, tl.load(TileOffsets + prev_eid))
102
- local_tile = pid_m - expert_tile_start
103
- m_off = local_tile * BLOCK_SIZE_M
104
-
105
- offs_am = m_off + tl.arange(0, BLOCK_SIZE_M)
106
- row_mask = offs_am < M_expert
107
- offs_global_m = expert_start + offs_am
108
-
109
- offs_bn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
110
- offs_k = tl.arange(0, BLOCK_SIZE_K)
111
-
112
- a_ptrs = A + offs_global_m[:, None] * stride_am + offs_k[None, :] * stride_ak
113
- b_ptrs = (
114
- B
115
- + expert_id * stride_be
116
- + offs_k[:, None] * stride_bk
117
- + offs_bn[None, :] * stride_bn
118
- )
119
- bs_ptrs = Bs + expert_id * stride_bs_e + pid_n * stride_bs_n
120
-
121
- accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
122
- for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
123
- # ---- fused fp8_act_quant ----
124
- a_raw = tl.load(a_ptrs, mask=row_mask[:, None], other=0.0).to(tl.float32)
125
- a_s = tl.max(tl.abs(a_raw), axis=1) / 448.0
126
- a = (a_raw / tl.maximum(a_s[:, None], 1e-12)).to(tl.float8e4nv)
127
- # ---- matmul ----
128
- b = tl.load(b_ptrs)
129
- b_s = tl.load(bs_ptrs + k * stride_bs_k)
130
- accumulator += tl.dot(a, b) * a_s[:, None] * b_s[None, :]
131
- a_ptrs += BLOCK_SIZE_K * stride_ak
132
- b_ptrs += BLOCK_SIZE_K * stride_bk
133
-
134
- if C.dtype.element_ty == tl.bfloat16:
135
- c = accumulator.to(tl.bfloat16)
136
- elif C.dtype.element_ty == tl.float16:
137
- c = accumulator.to(tl.float16)
138
- else:
139
- c = accumulator.to(tl.float32)
140
-
141
- c_ptrs = C + stride_cm * offs_global_m[:, None] + stride_cn * offs_bn[None, :]
142
- c_mask = row_mask[:, None]
143
- tl.store(c_ptrs, c, mask=c_mask)
144
-
145
-
146
- @triton.autotune(
147
- configs=[
148
- triton.Config({}, num_warps=w, num_stages=s)
149
- for w in [2, 4, 8, 16]
150
- for s in [2, 3, 4, 5]
151
- ],
152
- key=["N", "K", "BLOCK_SIZE_M"],
153
- )
154
- @triton.jit
155
- def w8a8_tensor_fp8_matmul_grouped_kernel(
156
- A, # (S, K) raw BF16/FP16 activations, sorted/grouped by expert idc
157
- B, # (E, N, K) FP8 weight matrices
158
- C, # (S, N) output
159
- As, # (S, 1) activation scales
160
- Bs, # (E, 1, 1) per-tensor weight scales
161
- Offsets,
162
- TileOffsets,
163
- S,
164
- N,
165
- K,
166
- stride_am,
167
- stride_ak,
168
- stride_be,
169
- stride_bk,
170
- stride_bn,
171
- stride_cm,
172
- stride_cn,
173
- stride_as_m,
174
- stride_bs_e,
175
- NUM_EXPERTS: tl.constexpr,
176
- BLOCK_SIZE_N: tl.constexpr,
177
- BLOCK_SIZE_K: tl.constexpr,
178
- BLOCK_SIZE_M: tl.constexpr,
179
- NUM_EXPERTS_BIT_LENGTH: tl.constexpr,
180
- ):
181
- """Tensor-scale grouped FP8 expert matmul kernel.
182
-
183
- Uses grouped expert scheduling with pre-quantized activations plus
184
- per-token activation scales and per-expert tensor weight scales.
185
- """
186
- pid_m = tl.program_id(axis=0)
187
- pid_n = tl.program_id(axis=1)
188
-
189
- total_tiles = tl.load(TileOffsets + NUM_EXPERTS - 1)
190
- if pid_m >= total_tiles:
191
- return
192
-
193
- lo = 0
194
- hi = NUM_EXPERTS
195
- for _ in tl.static_range(NUM_EXPERTS_BIT_LENGTH):
196
- mid = (lo + hi) >> 1
197
- mid_val = tl.load(TileOffsets + mid)
198
- is_left = mid_val <= pid_m
199
- lo = tl.where(is_left, mid + 1, lo)
200
- hi = tl.where(is_left, hi, mid)
201
- expert_id = lo.to(tl.int64)
202
-
203
- prev_eid = tl.maximum(expert_id - 1, 0)
204
- expert_start = tl.where(expert_id == 0, 0, tl.load(Offsets + prev_eid))
205
- expert_end = tl.load(Offsets + expert_id)
206
- M_expert = expert_end - expert_start
207
-
208
- expert_tile_start = tl.where(expert_id == 0, 0, tl.load(TileOffsets + prev_eid))
209
- local_tile = pid_m - expert_tile_start
210
- m_off = local_tile * BLOCK_SIZE_M
211
-
212
- offs_am = m_off + tl.arange(0, BLOCK_SIZE_M)
213
- row_mask = offs_am < M_expert
214
- offs_global_m = expert_start + offs_am
215
-
216
- offs_bn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
217
- offs_k = tl.arange(0, BLOCK_SIZE_K)
218
-
219
- a_ptrs = A + offs_global_m[:, None] * stride_am + offs_k[None, :] * stride_ak
220
- b_ptrs = (
221
- B
222
- + expert_id * stride_be
223
- + offs_k[:, None] * stride_bk
224
- + offs_bn[None, :] * stride_bn
225
- )
226
-
227
- a_s = tl.load(As + offs_global_m * stride_as_m, mask=row_mask, other=0.0)
228
- b_s = tl.load(Bs + expert_id * stride_bs_e)
229
-
230
- accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
231
- for _ in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
232
- a = tl.load(a_ptrs, mask=row_mask[:, None], other=0.0)
233
- b = tl.load(b_ptrs)
234
-
235
- accumulator += tl.dot(a, b)
236
- a_ptrs += BLOCK_SIZE_K * stride_ak
237
- b_ptrs += BLOCK_SIZE_K * stride_bk
238
-
239
- accumulator = accumulator * a_s[:, None] * b_s
240
-
241
- if C.dtype.element_ty == tl.bfloat16:
242
- c = accumulator.to(tl.bfloat16)
243
- elif C.dtype.element_ty == tl.float16:
244
- c = accumulator.to(tl.float16)
245
- else:
246
- c = accumulator.to(tl.float32)
247
-
248
- c_ptrs = C + stride_cm * offs_global_m[:, None] + stride_cn * offs_bn[None, :]
249
- c_mask = row_mask[:, None]
250
- tl.store(c_ptrs, c, mask=c_mask)
251
-
252
-
253
- @triton_op("finegrained_fp8::w8a8_block_fp8_matmul_grouped", mutates_args=())
254
- def _w8a8_block_fp8_matmul_grouped(
255
- A: torch.Tensor,
256
- B: torch.Tensor,
257
- Bs: torch.Tensor,
258
- offsets: torch.Tensor,
259
- tokens_per_expert: torch.Tensor,
260
- block_size: list[int],
261
- ) -> torch.Tensor:
262
- """Block-scale grouped FP8 matmul: C = A @ B.T per expert, with fused act quant.
263
-
264
- A: (S, K) raw bf16/fp16 activations, sorted by expert
265
- B: (E, N, K) FP8 expert weights
266
- Bs: (E, N // block_n, K // block_k) per-block weight scales
267
- """
268
- assert A.ndim == 2, f"A must be 2D (S, K), got ndim={A.ndim}"
269
- assert A.is_contiguous(), "A must be contiguous"
270
- assert B.ndim == 3, f"B must be 3D (E, N, K), got ndim={B.ndim}"
271
- assert B.is_contiguous(), "B must be contiguous"
272
- assert A.shape[1] == B.shape[2], (
273
- f"K mismatch: A has K={A.shape[1]}, B has K={B.shape[2]}"
274
- )
275
-
276
- S, K = A.shape
277
- E, N, _ = B.shape
278
-
279
- assert len(block_size) == 2, (
280
- f"block_size must be [block_n, block_k], got {block_size}"
281
- )
282
- block_n, block_k = block_size[0], block_size[1]
283
- # MoE expert dimensions must be block-aligned; non-aligned N/K is not supported.
284
- assert N % block_n == 0, f"N ({N}) must be divisible by block_n ({block_n})"
285
- assert K % block_k == 0, f"K ({K}) must be divisible by block_k ({block_k})"
286
- assert Bs.ndim == 3, (
287
- f"Bs must be 3D (E, N//block_n, K//block_k), got ndim={Bs.ndim}"
288
- )
289
- assert Bs.shape == (E, N // block_n, K // block_k), (
290
- f"Bs shape {tuple(Bs.shape)} != expected ({E}, {N // block_n}, {K // block_k})"
291
- )
292
-
293
- C = A.new_empty(S, N)
294
- # Adaptive BLOCK_SIZE_M: match tile to average tokens per expert.
295
- BLOCK_SIZE_M = min(max(triton.next_power_of_2((S + E - 1) // E), 16), 128)
296
- tiles_per_expert = (tokens_per_expert + BLOCK_SIZE_M - 1) // BLOCK_SIZE_M
297
- tile_offsets = torch.cumsum(tiles_per_expert, dim=0).to(torch.int32)
298
- # Upper bound on M-tiles: sum_e ceil(M_e / BLOCK_M) <= ceil(S / BLOCK_M) + E.
299
- # Programs beyond the real tile count exit immediately via the early-return
300
- # guard inside the kernel. This is faster than syncing for the exact count
301
- # and keeps the grid size data-independent (cuda-graph / torch.compile safe).
302
- max_M_tiles = triton.cdiv(S, BLOCK_SIZE_M) + E
303
- grid = (max_M_tiles, triton.cdiv(N, block_n))
304
- with device_context(A.device):
305
- wrap_triton(w8a8_block_fp8_matmul_grouped_kernel)[grid](
306
- A,
307
- B,
308
- C,
309
- Bs,
310
- offsets,
311
- tile_offsets,
312
- S,
313
- N,
314
- K,
315
- A.stride(0),
316
- A.stride(1),
317
- B.stride(0),
318
- B.stride(2),
319
- B.stride(1),
320
- C.stride(0),
321
- C.stride(1),
322
- Bs.stride(0),
323
- Bs.stride(2),
324
- Bs.stride(1),
325
- # Meta-parameters
326
- NUM_EXPERTS=E,
327
- BLOCK_SIZE_N=block_n,
328
- BLOCK_SIZE_K=block_k,
329
- BLOCK_SIZE_M=BLOCK_SIZE_M,
330
- NUM_EXPERTS_BIT_LENGTH=E.bit_length(),
331
- )
332
-
333
- return C
334
-
335
-
336
- @triton_op("finegrained_fp8::w8a8_tensor_fp8_matmul_grouped", mutates_args=())
337
- def _w8a8_tensor_fp8_matmul_grouped(
338
- A: torch.Tensor,
339
- B: torch.Tensor,
340
- Bs: torch.Tensor,
341
- offsets: torch.Tensor,
342
- tokens_per_expert: torch.Tensor,
343
- ) -> torch.Tensor:
344
- """Tensor-scale grouped FP8 matmul: C = A @ B.T per expert, with fused act quant.
345
-
346
- A: (S, K) raw bf16/fp16 activations, sorted by expert
347
- B: (E, N, K) FP8 expert weights
348
- Bs: (E,) or (E, 1, 1) per-expert weight scales
349
- """
350
- assert A.ndim == 2, f"A must be 2D (S, K), got ndim={A.ndim}"
351
- assert A.is_contiguous(), "A must be contiguous"
352
- assert B.ndim == 3, f"B must be 3D (E, N, K), got ndim={B.ndim}"
353
- assert B.is_contiguous(), "B must be contiguous"
354
- assert A.shape[1] == B.shape[2], (
355
- f"K mismatch: A has K={A.shape[1]}, B has K={B.shape[2]}"
356
- )
357
-
358
- S, K = A.shape
359
- E, N, _ = B.shape
360
-
361
- # Normalize Bs to (E, 1, 1)
362
- if Bs.ndim == 1:
363
- assert Bs.shape[0] == E, f"Bs shape {tuple(Bs.shape)} != expected ({E},)"
364
- Bs = Bs.reshape(E, 1, 1)
365
- else:
366
- assert Bs.shape == (E, 1, 1), (
367
- f"Bs shape {tuple(Bs.shape)} != expected ({E}, 1, 1)"
368
- )
369
-
370
- BLOCK_SIZE_N = 128
371
- BLOCK_SIZE_K = 128
372
- C = A.new_empty(S, N)
373
- qA, As = fp8_act_quant(A, K)
374
- BLOCK_SIZE_M = min(max(triton.next_power_of_2((S + E - 1) // E), 16), 128)
375
- tiles_per_expert = (tokens_per_expert + BLOCK_SIZE_M - 1) // BLOCK_SIZE_M
376
- tile_offsets = torch.cumsum(tiles_per_expert, dim=0).to(torch.int32)
377
- # Upper bound on M-tiles: sum_e ceil(M_e / BLOCK_M) <= ceil(S / BLOCK_M) + E.
378
- # Programs beyond the real tile count exit immediately via the early-return
379
- # guard inside the kernel. This is faster than syncing for the exact count
380
- # and keeps the grid size data-independent (cuda-graph / torch.compile safe).
381
- max_M_tiles = triton.cdiv(S, BLOCK_SIZE_M) + E
382
- grid = (max_M_tiles, triton.cdiv(N, BLOCK_SIZE_N))
383
- with device_context(A.device):
384
- wrap_triton(w8a8_tensor_fp8_matmul_grouped_kernel)[grid](
385
- qA,
386
- B,
387
- C,
388
- As,
389
- Bs,
390
- offsets,
391
- tile_offsets,
392
- S,
393
- N,
394
- K,
395
- qA.stride(0),
396
- qA.stride(1),
397
- B.stride(0),
398
- B.stride(2),
399
- B.stride(1),
400
- C.stride(0),
401
- C.stride(1),
402
- As.stride(0),
403
- Bs.stride(0),
404
- # Meta-parameters
405
- NUM_EXPERTS=E,
406
- BLOCK_SIZE_N=BLOCK_SIZE_N,
407
- BLOCK_SIZE_K=BLOCK_SIZE_K,
408
- BLOCK_SIZE_M=BLOCK_SIZE_M,
409
- NUM_EXPERTS_BIT_LENGTH=E.bit_length(),
410
- )
411
-
412
- return C
413
-
414
-
415
- def w8a8_block_fp8_matmul_grouped(
416
- A: torch.Tensor,
417
- B: torch.Tensor,
418
- Bs: torch.Tensor,
419
- offsets: torch.Tensor,
420
- tokens_per_expert: torch.Tensor,
421
- block_size: list[int],
422
- ) -> torch.Tensor:
423
- """Block-scale grouped FP8 matmul with fused activation quantization.
424
-
425
- A: (S, K) raw activations sorted by expert, bf16/fp16/fp32
426
- B: (E, N, K) FP8 expert weights
427
- Bs: (E, N // block_n, K // block_k) per-block weight scales
428
- """
429
- return torch.ops.finegrained_fp8.w8a8_block_fp8_matmul_grouped(
430
- A, B, Bs, offsets, tokens_per_expert, block_size
431
- )
432
-
433
-
434
- def w8a8_tensor_fp8_matmul_grouped(
435
- A: torch.Tensor,
436
- B: torch.Tensor,
437
- Bs: torch.Tensor,
438
- offsets: torch.Tensor,
439
- tokens_per_expert: torch.Tensor,
440
- ) -> torch.Tensor:
441
- """Tensor-scale grouped FP8 matmul with fused activation quantization.
442
-
443
- A: (S, K) raw activations sorted by expert, bf16/fp16/fp32
444
- B: (E, N, K) FP8 expert weights
445
- Bs: (E,) or (E, 1, 1) per-expert weight scales
446
- """
447
- return torch.ops.finegrained_fp8.w8a8_tensor_fp8_matmul_grouped(
448
- A, B, Bs, offsets, tokens_per_expert
449
- )
450
-
451
-
452
- def w8a8_fp8_matmul_grouped(
453
- A: torch.Tensor,
454
- B: torch.Tensor,
455
- Bs: torch.Tensor,
456
- offsets: torch.Tensor,
457
- tokens_per_expert: torch.Tensor,
458
- block_size: list[int] | None,
459
- ) -> torch.Tensor:
460
- """Unified grouped W8A8 FP8 matmul dispatcher.
461
-
462
- Dispatch rules:
463
- - tensor mode when ``block_size is None``
464
- - tensor mode when ``block_size == [N, K]``
465
- - otherwise block mode
466
-
467
- Returns:
468
- Output tensor ``[S, N]`` in the same dtype as ``A``, in expert-sorted order.
469
- """
470
- if block_size is None or (
471
- block_size[0] == B.size(1) and block_size[1] == B.size(2)
472
- ):
473
- return w8a8_tensor_fp8_matmul_grouped(A, B, Bs, offsets, tokens_per_expert)
474
-
475
- return w8a8_block_fp8_matmul_grouped(
476
- A, B, Bs, offsets, tokens_per_expert, block_size
477
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
build/torch-xpu/matmul.py DELETED
@@ -1,411 +0,0 @@
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 .utils import device_context
21
-
22
-
23
- # Adapted from https://github.com/sgl-project/sglang/blob/main/python/sglang/srt/layers/quantization/fp8_kernel.py
24
- @triton.autotune(
25
- configs=[
26
- triton.Config({}, num_warps=w, num_stages=s)
27
- for w in [2, 4, 8, 16]
28
- for s in [2, 3, 4]
29
- ],
30
- key=["N", "K", "BLOCK_SIZE_M"],
31
- )
32
- @triton.jit
33
- def w8a8_block_fp8_matmul_kernel(
34
- # Pointers to inputs and output
35
- A,
36
- B,
37
- C,
38
- As,
39
- Bs,
40
- # Shape for matmul
41
- M,
42
- N,
43
- K,
44
- stride_am,
45
- stride_ak,
46
- stride_bk,
47
- stride_bn,
48
- stride_cm,
49
- stride_cn,
50
- stride_as_m,
51
- stride_as_k,
52
- stride_bs_k,
53
- stride_bs_n,
54
- # Meta-parameters
55
- BLOCK_SIZE_M: tl.constexpr,
56
- BLOCK_SIZE_N: tl.constexpr,
57
- BLOCK_SIZE_K: tl.constexpr,
58
- GROUP_SIZE_M: tl.constexpr,
59
- ):
60
- """Block-scale FP8 GEMM kernel.
61
-
62
- Computes ``C = A @ B.T`` with block-wise activation/weight scales.
63
- Uses a 2D grid with swizzle for L2 cache locality on B tiles.
64
- """
65
- pid_m = tl.program_id(axis=0)
66
- pid_n = tl.program_id(axis=1)
67
- num_pid_m = tl.cdiv(M, BLOCK_SIZE_M)
68
- num_pid_n = tl.cdiv(N, BLOCK_SIZE_N)
69
- pid_m, pid_n = tl.swizzle2d(pid_m, pid_n, num_pid_m, num_pid_n, GROUP_SIZE_M)
70
-
71
- offs_am = (pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)) % M
72
- offs_bn = (pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)) % N
73
- offs_k = tl.arange(0, BLOCK_SIZE_K)
74
- a_ptrs = A + (offs_am[:, None] * stride_am + offs_k[None, :] * stride_ak)
75
- b_ptrs = B + (offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn)
76
-
77
- as_ptrs = As + offs_am * stride_as_m
78
- offs_bsn = offs_bn // BLOCK_SIZE_N
79
- bs_ptrs = Bs + offs_bsn * stride_bs_n
80
-
81
- accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
82
- for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
83
- k_remaining = K - k * BLOCK_SIZE_K
84
- a = tl.load(a_ptrs, mask=offs_k[None, :] < k_remaining, other=0.0)
85
- b = tl.load(b_ptrs, mask=offs_k[:, None] < k_remaining, other=0.0)
86
-
87
- a_s = tl.load(as_ptrs + k * stride_as_k)
88
- b_s = tl.load(bs_ptrs + k * stride_bs_k)
89
-
90
- accumulator += tl.dot(a, b) * a_s[:, None] * b_s[None, :]
91
- a_ptrs += BLOCK_SIZE_K * stride_ak
92
- b_ptrs += BLOCK_SIZE_K * stride_bk
93
-
94
- if C.dtype.element_ty == tl.bfloat16:
95
- c = accumulator.to(tl.bfloat16)
96
- elif C.dtype.element_ty == tl.float16:
97
- c = accumulator.to(tl.float16)
98
- else:
99
- c = accumulator.to(tl.float32)
100
-
101
- offs_cm = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
102
- offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
103
- c_ptrs = C + stride_cm * offs_cm[:, None] + stride_cn * offs_cn[None, :]
104
- c_mask = (offs_cm[:, None] < M) & (offs_cn[None, :] < N)
105
- tl.store(c_ptrs, c, mask=c_mask)
106
-
107
-
108
- @triton.autotune(
109
- configs=[
110
- triton.Config({}, num_warps=w, num_stages=s)
111
- for w in [2, 4, 8, 16]
112
- for s in [2, 3, 4]
113
- ],
114
- key=["N", "K", "BLOCK_SIZE_M"],
115
- )
116
- @triton.jit
117
- def w8a8_tensor_fp8_matmul_kernel(
118
- A,
119
- B,
120
- C,
121
- As,
122
- Bs,
123
- M,
124
- N,
125
- K,
126
- stride_am,
127
- stride_ak,
128
- stride_bk,
129
- stride_bn,
130
- stride_cm,
131
- stride_cn,
132
- stride_as_m,
133
- BLOCK_SIZE_M: tl.constexpr,
134
- BLOCK_SIZE_N: tl.constexpr,
135
- BLOCK_SIZE_K: tl.constexpr,
136
- GROUP_SIZE_M: tl.constexpr,
137
- ):
138
- """Tensor-scale FP8 GEMM kernel.
139
-
140
- Computes ``C = A @ B.T`` with one activation scale per row and one
141
- weight scale for the full matrix.
142
- Uses a 2D grid with swizzle for L2 cache locality on B tiles.
143
- """
144
- pid_m = tl.program_id(axis=0)
145
- pid_n = tl.program_id(axis=1)
146
- num_pid_m = tl.cdiv(M, BLOCK_SIZE_M)
147
- num_pid_n = tl.cdiv(N, BLOCK_SIZE_N)
148
- pid_m, pid_n = tl.swizzle2d(pid_m, pid_n, num_pid_m, num_pid_n, GROUP_SIZE_M)
149
-
150
- offs_am = (pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)) % M
151
- offs_bn = (pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)) % N
152
- offs_k = tl.arange(0, BLOCK_SIZE_K)
153
-
154
- a_ptrs = A + offs_am[:, None] * stride_am + offs_k[None, :] * stride_ak
155
- b_ptrs = B + offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn
156
-
157
- a_s = tl.load(As + offs_am * stride_as_m)
158
- b_s = tl.load(Bs)
159
-
160
- # Accumulate raw dot products, apply scales once after the loop.
161
- accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
162
- for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
163
- k_remaining = K - k * BLOCK_SIZE_K
164
- a = tl.load(a_ptrs, mask=offs_k[None, :] < k_remaining, other=0.0)
165
- b = tl.load(b_ptrs, mask=offs_k[:, None] < k_remaining, other=0.0)
166
- accumulator += tl.dot(a, b)
167
- a_ptrs += BLOCK_SIZE_K * stride_ak
168
- b_ptrs += BLOCK_SIZE_K * stride_bk
169
-
170
- accumulator = accumulator * a_s[:, None] * b_s
171
-
172
- if C.dtype.element_ty == tl.bfloat16:
173
- c = accumulator.to(tl.bfloat16)
174
- elif C.dtype.element_ty == tl.float16:
175
- c = accumulator.to(tl.float16)
176
- else:
177
- c = accumulator.to(tl.float32)
178
-
179
- offs_cm = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
180
- offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
181
- c_ptrs = C + stride_cm * offs_cm[:, None] + stride_cn * offs_cn[None, :]
182
- c_mask = (offs_cm[:, None] < M) & (offs_cn[None, :] < N)
183
- tl.store(c_ptrs, c, mask=c_mask)
184
-
185
-
186
- @triton_op("finegrained_fp8::w8a8_block_fp8_matmul", mutates_args=())
187
- def _w8a8_block_fp8_matmul(
188
- A: torch.Tensor,
189
- B: torch.Tensor,
190
- As: torch.Tensor,
191
- Bs: torch.Tensor,
192
- block_size: list[int],
193
- output_dtype: torch.dtype = torch.float32,
194
- ) -> torch.Tensor:
195
- """Block-scale FP8 matmul: C = A @ B.T with per-block scales.
196
-
197
- As: (M, K // block_k) — per-token-group activation scales
198
- Bs: (N // block_n, K // block_k) — per-block weight scales
199
- """
200
- assert len(block_size) == 2, (
201
- f"block_size must be [block_n, block_k], got {block_size}"
202
- )
203
- block_n, block_k = block_size[0], block_size[1]
204
-
205
- assert A.shape[-1] == B.shape[-1], (
206
- f"K mismatch: A has K={A.shape[-1]}, B has K={B.shape[-1]}"
207
- )
208
- assert A.is_contiguous(), "A must be contiguous"
209
- assert B.ndim == 2, f"B must be 2D (N, K), got ndim={B.ndim}"
210
- assert B.is_contiguous(), "B must be contiguous"
211
-
212
- N, K = B.shape
213
- M = A.numel() // A.shape[-1]
214
-
215
- assert As.ndim >= 2, f"As must be at least 2D, got ndim={As.ndim}"
216
- assert As.shape[-1] == triton.cdiv(K, block_k), (
217
- f"As last dim {As.shape[-1]} != expected {triton.cdiv(K, block_k)} (cdiv(K={K}, block_k={block_k}))"
218
- )
219
- assert Bs.ndim == 2, f"Bs must be 2D (N//block_n, K//block_k), got ndim={Bs.ndim}"
220
- assert Bs.shape == (triton.cdiv(N, block_n), triton.cdiv(K, block_k)), (
221
- f"Bs shape {tuple(Bs.shape)} != expected ({triton.cdiv(N, block_n)}, {triton.cdiv(K, block_k)})"
222
- )
223
-
224
- BLOCK_SIZE_K = block_k
225
- BLOCK_SIZE_N = block_n
226
- C_shape = A.shape[:-1] + (N,)
227
- C = A.new_empty(C_shape, dtype=output_dtype)
228
- # Adaptive BLOCK_SIZE_M: smallest power-of-2 >= M, floored at 16, capped at 128.
229
- # Matches the WGMMA tile to the actual row count — smaller tiles use less
230
- # register pressure and a better-matched FP8 WGMMA instruction, improving
231
- # both accuracy and performance for small M (decode).
232
- BLOCK_SIZE_M = min(max(triton.next_power_of_2(M), 16), 128)
233
- grid = (triton.cdiv(M, BLOCK_SIZE_M), triton.cdiv(N, BLOCK_SIZE_N))
234
- with device_context(A.device):
235
- wrap_triton(w8a8_block_fp8_matmul_kernel)[grid](
236
- A,
237
- B,
238
- C,
239
- As,
240
- Bs,
241
- M,
242
- N,
243
- K,
244
- A.stride(-2),
245
- A.stride(-1),
246
- B.stride(1),
247
- B.stride(0),
248
- C.stride(-2),
249
- C.stride(-1),
250
- As.stride(-2),
251
- As.stride(-1),
252
- Bs.stride(1),
253
- Bs.stride(0),
254
- # Meta-parameters
255
- BLOCK_SIZE_M=BLOCK_SIZE_M,
256
- BLOCK_SIZE_N=BLOCK_SIZE_N,
257
- BLOCK_SIZE_K=BLOCK_SIZE_K,
258
- GROUP_SIZE_M=8,
259
- )
260
-
261
- return C
262
-
263
-
264
- @triton_op("finegrained_fp8::w8a8_tensor_fp8_matmul", mutates_args=())
265
- def _w8a8_tensor_fp8_matmul(
266
- A: torch.Tensor,
267
- B: torch.Tensor,
268
- As: torch.Tensor,
269
- Bs: torch.Tensor,
270
- output_dtype: torch.dtype = torch.float32,
271
- ) -> torch.Tensor:
272
- """Tensor-scale FP8 matmul: C = A @ B.T with per-row / per-tensor scales.
273
-
274
- As: scalar, (M,), or (M, 1) — per-row activation scales
275
- Bs: scalar, (1,), or (1, 1) — single weight scale
276
- """
277
- assert A.shape[-1] == B.shape[-1], (
278
- f"K mismatch: A has K={A.shape[-1]}, B has K={B.shape[-1]}"
279
- )
280
- assert A.is_contiguous(), "A must be contiguous"
281
- assert B.ndim == 2, f"B must be 2D (N, K), got ndim={B.ndim}"
282
- assert B.is_contiguous(), "B must be contiguous"
283
-
284
- N, K = B.shape
285
- M = A.numel() // A.shape[-1]
286
-
287
- # Normalize As to (M,)
288
- if As.numel() == 1:
289
- As = As.reshape(1).expand(M).contiguous()
290
- elif As.ndim == 2:
291
- As = As.reshape(M)
292
- assert As.ndim == 1 and As.shape[0] == M, (
293
- f"As must be scalar, (M,), or (M,1) with M={M}, got {tuple(As.shape)}"
294
- )
295
-
296
- # Normalize Bs to (1,)
297
- assert Bs.numel() == 1, f"Bs must be scalar or (1,), got {tuple(Bs.shape)}"
298
- Bs = Bs.reshape(1)
299
-
300
- BLOCK_SIZE_N = 128
301
- BLOCK_SIZE_K = 128
302
- C_shape = A.shape[:-1] + (N,)
303
- C = A.new_empty(C_shape, dtype=output_dtype)
304
- BLOCK_SIZE_M = min(max(triton.next_power_of_2(M), 16), 128)
305
- grid = (triton.cdiv(M, BLOCK_SIZE_M), triton.cdiv(N, BLOCK_SIZE_N))
306
- with device_context(A.device):
307
- wrap_triton(w8a8_tensor_fp8_matmul_kernel)[grid](
308
- A,
309
- B,
310
- C,
311
- As,
312
- Bs,
313
- M,
314
- N,
315
- K,
316
- A.stride(-2),
317
- A.stride(-1),
318
- B.stride(1),
319
- B.stride(0),
320
- C.stride(-2),
321
- C.stride(-1),
322
- As.stride(0),
323
- # Meta-parameters
324
- BLOCK_SIZE_M=BLOCK_SIZE_M,
325
- BLOCK_SIZE_N=BLOCK_SIZE_N,
326
- BLOCK_SIZE_K=BLOCK_SIZE_K,
327
- GROUP_SIZE_M=8,
328
- )
329
-
330
- return C
331
-
332
-
333
- def w8a8_block_fp8_matmul(
334
- A: torch.Tensor,
335
- B: torch.Tensor,
336
- As: torch.Tensor,
337
- Bs: torch.Tensor,
338
- block_size: list[int],
339
- output_dtype: torch.dtype = torch.float32,
340
- ) -> torch.Tensor:
341
- """Block-wise W8A8 FP8 matrix multiplication.
342
-
343
- Computes ``C = A @ B.T`` where both operands are pre-quantized to
344
- ``float8_e4m3fn`` with per-block scales, and accumulates in float32
345
- before casting to ``output_dtype``.
346
-
347
- Args:
348
- A: Quantized activation tensor ``[M, K]`` in ``float8_e4m3fn``.
349
- B: Quantized weight tensor ``[N, K]`` in ``float8_e4m3fn``.
350
- As: Per-token-group activation scales ``[M, K // block_size[1]]``.
351
- Bs: Per-block weight scales ``[N // block_size[0], K // block_size[1]]``.
352
- block_size: ``[block_n, block_k]`` quantization block dimensions, e.g. ``[128, 128]``.
353
- output_dtype: dtype of the returned tensor (default: ``torch.float32``).
354
-
355
- Returns:
356
- Output tensor ``[M, N]`` in ``output_dtype``.
357
- """
358
- return torch.ops.finegrained_fp8.w8a8_block_fp8_matmul(
359
- A, B, As, Bs, block_size, output_dtype
360
- )
361
-
362
-
363
- def w8a8_tensor_fp8_matmul(
364
- A: torch.Tensor,
365
- B: torch.Tensor,
366
- As: torch.Tensor,
367
- Bs: torch.Tensor,
368
- output_dtype: torch.dtype = torch.float32,
369
- ) -> torch.Tensor:
370
- """Tensor-scale W8A8 FP8 matrix multiplication.
371
-
372
- Computes ``C = A @ B.T`` in tensor-scale mode using pre-quantized FP8
373
- activations/weights and tensor scales.
374
-
375
- Args:
376
- A: Quantized activation tensor ``[M, K]`` in ``float8_e4m3fn``.
377
- B: Quantized weight tensor ``[N, K]`` in ``float8_e4m3fn``.
378
- As: Per-row activation scales ``[M]``.
379
- Bs: Single weight scale, scalar or ``[1]``.
380
- output_dtype: dtype of the returned tensor.
381
-
382
- Returns:
383
- Output tensor ``[M, N]`` in ``output_dtype``.
384
- """
385
- return torch.ops.finegrained_fp8.w8a8_tensor_fp8_matmul(A, B, As, Bs, output_dtype)
386
-
387
-
388
- def w8a8_fp8_matmul(
389
- A: torch.Tensor,
390
- B: torch.Tensor,
391
- As: torch.Tensor,
392
- Bs: torch.Tensor,
393
- block_size: list[int] | None,
394
- output_dtype: torch.dtype = torch.float32,
395
- ) -> torch.Tensor:
396
- """Unified W8A8 FP8 matmul dispatcher.
397
-
398
- Dispatch rules:
399
- - tensor mode when ``block_size is None``
400
- - tensor mode when ``block_size == [N, K]``
401
- - otherwise block mode
402
-
403
- Returns:
404
- Output tensor ``[M, N]`` in ``output_dtype``.
405
- """
406
- if block_size is None or (
407
- block_size[0] == B.size(0) and block_size[1] == B.size(1)
408
- ):
409
- return w8a8_tensor_fp8_matmul(A, B, As, Bs, output_dtype)
410
-
411
- return w8a8_block_fp8_matmul(A, B, As, Bs, block_size, output_dtype)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
build/torch-xpu/metadata.json DELETED
@@ -1,10 +0,0 @@
1
- {
2
- "name": "finegrained-fp8",
3
- "id": "_finegrained_fp8_xpu_7c5619e",
4
- "version": 1,
5
- "license": "Apache-2.0",
6
- "python-depends": [],
7
- "backend": {
8
- "type": "xpu"
9
- }
10
- }
 
 
 
 
 
 
 
 
 
 
 
build/torch-xpu/utils.py DELETED
@@ -1,13 +0,0 @@
1
- import torch
2
- from contextlib import contextmanager
3
-
4
-
5
- @contextmanager
6
- def device_context(device: torch.device):
7
- """Context manager that sets the active device for any backend (cuda, xpu, etc.)."""
8
- backend = getattr(torch, device.type, None)
9
- if backend is not None and hasattr(backend, "device"):
10
- with backend.device(device):
11
- yield
12
- else:
13
- yield