Kernels
medmekk commited on
Commit
e9d1f46
·
verified ·
1 Parent(s): 53848dc

Build uploaded using `kernels`.

Browse files
build/torch-cuda/__init__.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .act_quant import fp8_act_quant
2
+ from .batched import w8a8_block_fp8_matmul_batched
3
+ from .grouped import w8a8_block_fp8_matmul_grouped
4
+ from .matmul import w8a8_block_fp8_matmul
5
+
6
+ __all__ = [
7
+ "fp8_act_quant",
8
+ "w8a8_block_fp8_matmul",
9
+ "w8a8_block_fp8_matmul_batched",
10
+ "w8a8_block_fp8_matmul_grouped",
11
+ ]
build/torch-cuda/_ops.py ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ ops = torch.ops._finegrained_fp8_2c10f72
3
+
4
+ def add_op_namespace_prefix(op_name: str):
5
+ """
6
+ Prefix op by namespace.
7
+ """
8
+ return f"_finegrained_fp8_2c10f72::{op_name}"
build/torch-cuda/act_quant.py ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
21
+ _FP8_DTYPE = torch.float8_e4m3fn
22
+
23
+
24
+ # Copied from https://huggingface.co/deepseek-ai/DeepSeek-V3/blob/main/inference/kernel.py
25
+ @triton.jit
26
+ def _fp8_act_quant_kernel(x_ptr, y_ptr, s_ptr, BLOCK_SIZE: tl.constexpr):
27
+ pid = tl.program_id(axis=0)
28
+ offs = pid * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE)
29
+ x = tl.load(x_ptr + offs).to(tl.float32)
30
+ s = tl.max(tl.abs(x)) / 448.0 # float8_e4m3fn max
31
+ y = x / s
32
+ y = y.to(y_ptr.dtype.element_ty)
33
+ tl.store(y_ptr + offs, y)
34
+ tl.store(s_ptr + pid, s)
35
+
36
+
37
+ @triton_op("finegrained_fp8::fp8_act_quant", mutates_args=())
38
+ def _fp8_act_quant(
39
+ x: torch.Tensor, block_size: int = 128
40
+ ) -> tuple[torch.Tensor, torch.Tensor]:
41
+ assert x.is_contiguous()
42
+ assert x.shape[-1] % block_size == 0
43
+ y = torch.empty_like(x, dtype=_FP8_DTYPE)
44
+ s = x.new_empty(*x.size()[:-1], x.size(-1) // block_size, dtype=torch.float32)
45
+
46
+ grid = (triton.cdiv(x.numel(), block_size),)
47
+ wrap_triton(_fp8_act_quant_kernel)[grid](x, y, s, BLOCK_SIZE=block_size)
48
+ return y, s
49
+
50
+
51
+ def fp8_act_quant(
52
+ x: torch.Tensor, block_size: int = 128
53
+ ) -> tuple[torch.Tensor, torch.Tensor]:
54
+ """Quantize activations to FP8 with per-block dynamic scaling.
55
+
56
+ Splits the last dimension of ``x`` into blocks of ``block_size`` elements,
57
+ computes ``scale = max(|x_block|) / 448`` per block, and quantizes to
58
+ ``float8_e4m3fn``.
59
+
60
+ Args:
61
+ x: Input tensor in bf16/fp16/fp32. Last dimension must be divisible by
62
+ ``block_size`` and the tensor must be contiguous.
63
+ block_size: Number of elements per quantization block (default: 128).
64
+
65
+ Returns:
66
+ A tuple ``(quantized, scales)`` where ``quantized`` has dtype
67
+ ``float8_e4m3fn`` with the same shape as ``x``, and ``scales`` has
68
+ shape ``(*x.shape[:-1], x.shape[-1] // block_size)`` in float32.
69
+ """
70
+ return torch.ops.finegrained_fp8.fp8_act_quant(x, block_size)
build/torch-cuda/batched.py ADDED
@@ -0,0 +1,196 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
21
+ @triton.jit
22
+ def w8a8_block_fp8_matmul_batched_kernel(
23
+ A, # (S, K) raw BF16/FP16 activations
24
+ B, # (E, N, K) FP8 weight matrices
25
+ C, # (S, N) output
26
+ Bs, # (E, N // group_n, K // group_k) weight scales
27
+ ExpertIds, # (S,) — which expert each batch element routes to
28
+ # Shape
29
+ N,
30
+ K,
31
+ # Block size for block-wise quantization
32
+ group_n,
33
+ group_k,
34
+ # Per-row strides
35
+ stride_ak,
36
+ stride_bk,
37
+ stride_bn,
38
+ stride_cn,
39
+ stride_Bs_k,
40
+ stride_Bs_n,
41
+ # Batch / expert strides
42
+ stride_Ab, # stride between rows in A (one token per program)
43
+ stride_Eb, # stride between experts in B
44
+ stride_Cb,
45
+ stride_Esb, # stride between experts in Bs
46
+ # Meta-parameters
47
+ BLOCK_SIZE_M: tl.constexpr,
48
+ BLOCK_SIZE_N: tl.constexpr,
49
+ BLOCK_SIZE_K: tl.constexpr,
50
+ ):
51
+ pid_n = tl.program_id(axis=0)
52
+ batch_id = tl.program_id(axis=1)
53
+
54
+ # Advance base pointers to this token's activation row and its expert's
55
+ # weight / scale slice. No pre-gather of weights needed (like in non-fp8 impls)
56
+ expert_id = tl.load(ExpertIds + batch_id)
57
+ A = A + batch_id * stride_Ab
58
+ B = B + expert_id * stride_Eb
59
+ C = C + batch_id * stride_Cb
60
+ Bs = Bs + expert_id * stride_Esb
61
+
62
+ offs_bn = (pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)) % N
63
+ offs_k = tl.arange(0, BLOCK_SIZE_K)
64
+ # M=1: broadcast the single activation row to BLOCK_SIZE_M identical rows
65
+ # so tl.dot gets the required (BLOCK_SIZE_M, BLOCK_SIZE_K) shape.
66
+ a_ptrs = A + tl.arange(0, BLOCK_SIZE_M)[:, None] * 0 + offs_k[None, :] * stride_ak
67
+ b_ptrs = B + (offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn)
68
+
69
+ offs_bsn = offs_bn // group_n
70
+ Bs_ptrs = Bs + offs_bsn * stride_Bs_n
71
+
72
+ accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
73
+ for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
74
+ # ---- fused act_quant (replaces: a = tl.load(a_ptrs); a_s = tl.load(As_ptrs)) ----
75
+ a_raw = tl.load(a_ptrs, mask=offs_k[None, :] < K - k * BLOCK_SIZE_K, other=0.0).to(tl.float32)
76
+ a_s = tl.max(tl.abs(a_raw)) / 448.0 # per-block scale (scalar for M=1)
77
+ a = (a_raw / tl.maximum(a_s, 1e-12)).to(tl.float8e4nv)
78
+ # ---- same as baseline from here ----
79
+ b = tl.load(b_ptrs, mask=offs_k[:, None] < K - k * BLOCK_SIZE_K, other=0.0)
80
+
81
+ k_start = k * BLOCK_SIZE_K
82
+ offs_ks = k_start // group_k
83
+ b_s = tl.load(Bs_ptrs + offs_ks * stride_Bs_k)
84
+
85
+ accumulator += tl.dot(a, b) * a_s * b_s[None, :]
86
+ a_ptrs += BLOCK_SIZE_K * stride_ak
87
+ b_ptrs += BLOCK_SIZE_K * stride_bk
88
+
89
+ if C.dtype.element_ty == tl.bfloat16:
90
+ c = accumulator.to(tl.bfloat16)
91
+ elif C.dtype.element_ty == tl.float16:
92
+ c = accumulator.to(tl.float16)
93
+ else:
94
+ c = accumulator.to(tl.float32)
95
+
96
+ # Only write row 0 (M=1); the broadcast rows are discarded.
97
+ offs_cm = tl.arange(0, BLOCK_SIZE_M)
98
+ offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
99
+ c_ptrs = C + offs_cm[:, None] * 0 + stride_cn * offs_cn[None, :]
100
+ c_mask = (offs_cm[:, None] < 1) & (offs_cn[None, :] < N)
101
+ tl.store(c_ptrs, c, mask=c_mask)
102
+
103
+
104
+ @triton_op("finegrained_fp8::w8a8_block_fp8_matmul_batched", mutates_args=())
105
+ def _w8a8_block_fp8_matmul_batched(
106
+ A: torch.Tensor,
107
+ B: torch.Tensor,
108
+ Bs: torch.Tensor,
109
+ expert_ids: torch.Tensor,
110
+ block_size: list[int] | None,
111
+ ) -> torch.Tensor:
112
+ """Batched FP8 block-wise matmul with fused activation quantization.
113
+
114
+ Mirrors ``_batched_linear`` for FP8 weights: A is the raw (BF16/FP16)
115
+ activation matrix, B / Bs are the stacked expert weights / scales.
116
+ The kernel looks up ``expert_ids[batch_id]`` to address the correct expert
117
+ slice of B directly — no (S, N, K) weight gather is needed.
118
+ Activation quantization (``act_quant``) is fused into the matmul loop.
119
+ """
120
+ assert A.ndim == 2, "A must be (S, K)"
121
+ assert A.is_contiguous()
122
+
123
+ assert B.ndim == 3, "B must be (E, N, K)"
124
+ assert B.is_contiguous()
125
+
126
+ assert A.shape[1] == B.shape[2], "K dimension mismatch between A and B"
127
+ assert expert_ids.is_contiguous()
128
+ assert Bs.is_contiguous()
129
+
130
+ if block_size is None:
131
+ block_n, block_k = 128, 128
132
+ else:
133
+ assert len(block_size) == 2
134
+ block_n, block_k = block_size[0], block_size[1]
135
+
136
+ S, K = A.shape
137
+ E, N, _ = B.shape
138
+ C = A.new_empty(S, N)
139
+
140
+ # Adaptive BLOCK_SIZE_M: match the tile to the average tokens per expert
141
+ BLOCK_SIZE_M = min(max(triton.next_power_of_2((S + E - 1) // E), 16), 128)
142
+
143
+ grid = (triton.cdiv(N, block_n), S)
144
+ wrap_triton(w8a8_block_fp8_matmul_batched_kernel)[grid](
145
+ A,
146
+ B,
147
+ C,
148
+ Bs,
149
+ expert_ids,
150
+ N,
151
+ K,
152
+ block_n,
153
+ block_k,
154
+ A.stride(1), # stride_ak
155
+ B.stride(2), # stride_bk
156
+ B.stride(1), # stride_bn
157
+ C.stride(1), # stride_cn
158
+ Bs.stride(2), # stride_Bs_k
159
+ Bs.stride(1), # stride_Bs_n
160
+ A.stride(0), # stride_Ab
161
+ B.stride(0), # stride_Eb
162
+ C.stride(0), # stride_Cb
163
+ Bs.stride(0), # stride_Esb
164
+ BLOCK_SIZE_M=BLOCK_SIZE_M,
165
+ BLOCK_SIZE_N=block_n,
166
+ BLOCK_SIZE_K=block_k,
167
+ )
168
+
169
+ return C
170
+
171
+
172
+ def w8a8_block_fp8_matmul_batched(
173
+ A: torch.Tensor,
174
+ B: torch.Tensor,
175
+ Bs: torch.Tensor,
176
+ expert_ids: torch.Tensor,
177
+ block_size: list[int] | None,
178
+ ) -> torch.Tensor:
179
+ """Batched W8A8 FP8 matmul for MoE expert dispatch with fused activation quantization.
180
+
181
+ Each token in ``A`` is routed to one expert via ``expert_ids``. The kernel
182
+ quantizes ``A`` to FP8 on-the-fly (fused ``act_quant``), reads the correct
183
+ expert weight slice of ``B`` directly using ``expert_ids[batch_id]`` — no
184
+ ``(S, N, K)`` weight gather needed — and accumulates in float32.
185
+
186
+ Args:
187
+ A: Raw activation matrix ``[S, K]`` in bf16/fp16/fp32.
188
+ B: Stacked expert weight tensor ``[E, N, K]`` in ``float8_e4m3fn``.
189
+ Bs: Stacked expert weight scales ``[E, N // block_size[0], K // block_size[1]]``.
190
+ expert_ids: Expert index per token ``[S]``, values in ``[0, E)``.
191
+ block_size: ``[block_n, block_k]`` quantization block dimensions, e.g. ``[128, 128]``.
192
+
193
+ Returns:
194
+ Output tensor ``[S, N]`` in the same dtype as ``A``.
195
+ """
196
+ return torch.ops.finegrained_fp8.w8a8_block_fp8_matmul_batched(A, B, Bs, expert_ids, block_size)
build/torch-cuda/finegrained_fp8/__init__.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ctypes
2
+ import sys
3
+
4
+ import importlib
5
+ from pathlib import Path
6
+ from types import ModuleType
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-cuda/grouped.py ADDED
@@ -0,0 +1,252 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
21
+ @triton.jit
22
+ def w8a8_block_fp8_grouped_mm_kernel(
23
+ A, # (S, K) raw BF16/FP16 activations, sorted/grouped by expert id
24
+ B, # (E, N, K) FP8 weight matrices
25
+ C, # (S, N) output
26
+ Bs, # (E, N // group_n, K // group_k) weight scales
27
+ Offsets, # (E,) int32 — cumulative row-end per expert
28
+ TileOffsets, # (E,) int32 — cumulative tile-end per expert
29
+ # Shape
30
+ S,
31
+ N,
32
+ K,
33
+ # Block size for block-wise quantization
34
+ group_n,
35
+ group_k,
36
+ # Strides
37
+ stride_am,
38
+ stride_ak,
39
+ stride_Eb,
40
+ stride_bk,
41
+ stride_bn,
42
+ stride_cm,
43
+ stride_cn,
44
+ stride_Esb,
45
+ stride_Bsk,
46
+ stride_Bsn,
47
+ # Meta-parameters
48
+ NUM_EXPERTS: tl.constexpr,
49
+ BLOCK_SIZE_M: tl.constexpr,
50
+ BLOCK_SIZE_N: tl.constexpr,
51
+ BLOCK_SIZE_K: tl.constexpr,
52
+ ):
53
+ pid = tl.program_id(axis=0)
54
+ num_pid_n = tl.cdiv(N, BLOCK_SIZE_N)
55
+ pid_m = pid // num_pid_n
56
+ pid_n = pid % num_pid_n
57
+
58
+ # Exit early for programs beyond the actual tile count.
59
+ total_tiles = tl.load(TileOffsets + NUM_EXPERTS - 1)
60
+ if pid_m >= total_tiles:
61
+ return
62
+
63
+ # Binary search in TileOffsets to find the owning expert.
64
+ # Finds the smallest e such that TileOffsets[e] > pid_m (upper_bound semantics),
65
+ # which is the expert whose tile range contains pid_m.
66
+ # O(log2(NUM_EXPERTS)) loads instead of the O(NUM_EXPERTS) linear scan.
67
+ # NUM_EXPERTS.bit_length() is ceil(log2(E))+1 for powers-of-two, giving one
68
+ # harmless extra iteration when lo==hi; it's a compile-time constant so the
69
+ # loop is fully unrolled by the compiler.
70
+ lo = 0
71
+ hi = NUM_EXPERTS
72
+ for _ in tl.static_range(NUM_EXPERTS.bit_length()):
73
+ mid = (lo + hi) >> 1
74
+ mid_val = tl.load(TileOffsets + mid)
75
+ is_left = mid_val <= pid_m
76
+ lo = tl.where(is_left, mid + 1, lo)
77
+ hi = tl.where(is_left, hi, mid)
78
+ expert_id = lo
79
+
80
+ prev_eid = tl.maximum(expert_id - 1, 0)
81
+
82
+ expert_start = tl.where(expert_id == 0, 0, tl.load(Offsets + prev_eid))
83
+ expert_end = tl.load(Offsets + expert_id)
84
+ M_expert = expert_end - expert_start
85
+
86
+ expert_tile_start = tl.where(expert_id == 0, 0, tl.load(TileOffsets + prev_eid))
87
+ local_tile = pid_m - expert_tile_start
88
+ m_off = local_tile * BLOCK_SIZE_M
89
+
90
+ offs_am = m_off + tl.arange(0, BLOCK_SIZE_M)
91
+ row_mask = offs_am < M_expert
92
+ offs_global_m = expert_start + offs_am
93
+
94
+ offs_bn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
95
+ offs_bn_safe = offs_bn % N
96
+ offs_k = tl.arange(0, BLOCK_SIZE_K)
97
+
98
+ offs_am_safe = offs_global_m % S
99
+
100
+ a_ptrs = A + offs_am_safe[:, None] * stride_am + offs_k[None, :] * stride_ak
101
+ b_ptrs = (
102
+ B
103
+ + expert_id * stride_Eb
104
+ + offs_k[:, None] * stride_bk
105
+ + offs_bn_safe[None, :] * stride_bn
106
+ )
107
+ offs_bsn_safe = offs_bn_safe // group_n
108
+ Bs_ptrs = Bs + expert_id * stride_Esb + offs_bsn_safe * stride_Bsn
109
+
110
+ accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
111
+ for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
112
+ # ---- fused act_quant (replaces: a = tl.load(a_ptrs); a_s = tl.load(As_ptrs)) ----
113
+ a_raw = tl.load(
114
+ a_ptrs,
115
+ mask=row_mask[:, None] & (offs_k[None, :] < K - k * BLOCK_SIZE_K),
116
+ other=0.0,
117
+ ).to(tl.float32)
118
+ a_s = tl.max(tl.abs(a_raw), axis=1) / 448.0 # per-row scale (BLOCK_SIZE_M,)
119
+ # clamp denominator so masked all-zero rows don't produce NaN
120
+ # (their a_s multiplier is 0 anyway, so the output row is correct)
121
+ a = (a_raw / tl.maximum(a_s[:, None], 1e-12)).to(tl.float8e4nv)
122
+ # ---- same as baseline from here ----
123
+ b = tl.load(b_ptrs, mask=offs_k[:, None] < K - k * BLOCK_SIZE_K, other=0.0)
124
+
125
+ k_start = k * BLOCK_SIZE_K
126
+ offs_ks = k_start // group_k
127
+ b_s = tl.load(Bs_ptrs + offs_ks * stride_Bsk)
128
+
129
+ accumulator += tl.dot(a, b) * a_s[:, None] * b_s[None, :]
130
+ a_ptrs += BLOCK_SIZE_K * stride_ak
131
+ b_ptrs += BLOCK_SIZE_K * stride_bk
132
+
133
+ if C.dtype.element_ty == tl.bfloat16:
134
+ c = accumulator.to(tl.bfloat16)
135
+ elif C.dtype.element_ty == tl.float16:
136
+ c = accumulator.to(tl.float16)
137
+ else:
138
+ c = accumulator.to(tl.float32)
139
+
140
+ c_ptrs = C + stride_cm * offs_global_m[:, None] + stride_cn * offs_bn[None, :]
141
+ c_mask = row_mask[:, None] & (offs_bn[None, :] < N)
142
+ tl.store(c_ptrs, c, mask=c_mask)
143
+
144
+
145
+ @triton_op("finegrained_fp8::w8a8_block_fp8_matmul_grouped", mutates_args=())
146
+ def _w8a8_block_fp8_matmul_grouped(
147
+ A: torch.Tensor,
148
+ B: torch.Tensor,
149
+ Bs: torch.Tensor,
150
+ offsets: torch.Tensor,
151
+ tokens_per_expert: torch.Tensor,
152
+ block_size: list[int] | None,
153
+ ) -> torch.Tensor:
154
+ """Grouped FP8 block-wise matmul with fused activation quantization.
155
+
156
+ Mirrors ``_grouped_linear`` / ``_grouped_mm`` for FP8 weights: A is the
157
+ raw (BF16/FP16) activation matrix sorted by expert, B / Bs are the stacked
158
+ expert weights / scales. Activation quantization (``act_quant``) is fused
159
+ into the matmul loop. ``tokens_per_expert`` is needed (in addition to
160
+ ``offsets``) to build the per-expert tile schedule inside the kernel.
161
+ """
162
+ assert A.ndim == 2, "A must be (S, K)"
163
+ assert A.is_contiguous()
164
+
165
+ assert B.ndim == 3, "B must be (E, N, K)"
166
+ assert B.is_contiguous()
167
+
168
+ assert tokens_per_expert.is_contiguous()
169
+ assert offsets.is_contiguous()
170
+ assert Bs.is_contiguous()
171
+
172
+ if block_size is None:
173
+ block_n, block_k = 128, 128
174
+ else:
175
+ assert len(block_size) == 2
176
+ block_n, block_k = block_size[0], block_size[1]
177
+
178
+ S, K = A.shape
179
+ E, N, _ = B.shape
180
+ C = A.new_empty(S, N)
181
+
182
+ # Adaptive BLOCK_SIZE_M: match tile to average tokens per expert.
183
+ BLOCK_SIZE_M = min(max(triton.next_power_of_2((S + E - 1) // E), 16), 128)
184
+ tiles_per_expert = (tokens_per_expert + BLOCK_SIZE_M - 1) // BLOCK_SIZE_M
185
+ tile_offsets = torch.cumsum(tiles_per_expert, dim=0).to(torch.int32)
186
+ # Upper bound on M-tiles: sum_e ceil(M_e / BLOCK_M) <= ceil(S / BLOCK_M) + E.
187
+ # Using a static upper bound keeps the grid size data-independent, which is
188
+ # required for cuda-graph compatibility. Programs beyond the real tile count
189
+ # exit immediately via the early-return guard inside the kernel.
190
+ max_M_tiles = triton.cdiv(S, BLOCK_SIZE_M) + E
191
+
192
+ grid = (max_M_tiles * triton.cdiv(N, block_n),)
193
+ wrap_triton(w8a8_block_fp8_grouped_mm_kernel)[grid](
194
+ A,
195
+ B,
196
+ C,
197
+ Bs,
198
+ offsets,
199
+ tile_offsets,
200
+ S,
201
+ N,
202
+ K,
203
+ block_n,
204
+ block_k,
205
+ A.stride(0), # stride_am
206
+ A.stride(1), # stride_ak
207
+ B.stride(0), # stride_Eb
208
+ B.stride(2), # stride_bk
209
+ B.stride(1), # stride_bn
210
+ C.stride(0), # stride_cm
211
+ C.stride(1), # stride_cn
212
+ Bs.stride(0), # stride_Esb
213
+ Bs.stride(2), # stride_Bsk
214
+ Bs.stride(1), # stride_Bsn
215
+ NUM_EXPERTS=E,
216
+ BLOCK_SIZE_M=BLOCK_SIZE_M,
217
+ BLOCK_SIZE_N=block_n,
218
+ BLOCK_SIZE_K=block_k,
219
+ )
220
+
221
+ return C
222
+
223
+
224
+ def w8a8_block_fp8_matmul_grouped(
225
+ A: torch.Tensor,
226
+ B: torch.Tensor,
227
+ Bs: torch.Tensor,
228
+ offsets: torch.Tensor,
229
+ tokens_per_expert: torch.Tensor,
230
+ block_size: list[int] | None,
231
+ ) -> torch.Tensor:
232
+ """Grouped W8A8 FP8 matmul for MoE expert dispatch with fused activation quantization.
233
+
234
+ Tokens in ``A`` must be pre-sorted by expert id. The kernel quantizes ``A``
235
+ to FP8 on-the-fly (fused ``act_quant``), uses a static over-provisioned grid
236
+ for CUDA-graph compatibility, and resolves each tile's expert via an O(log E)
237
+ binary search over ``offsets``.
238
+
239
+ Args:
240
+ A: Raw activation matrix ``[S, K]`` sorted by expert, in bf16/fp16/fp32.
241
+ B: Stacked expert weight tensor ``[E, N, K]`` in ``float8_e4m3fn``.
242
+ Bs: Stacked expert weight scales ``[E, N // block_size[0], K // block_size[1]]``.
243
+ offsets: Cumulative token counts per expert ``[E]`` (i.e. ``cumsum(tokens_per_expert)``).
244
+ tokens_per_expert: Number of tokens routed to each expert ``[E]``.
245
+ block_size: ``[block_n, block_k]`` quantization block dimensions, e.g. ``[128, 128]``.
246
+
247
+ Returns:
248
+ Output tensor ``[S, N]`` in the same dtype as ``A``, in expert-sorted order.
249
+ """
250
+ return torch.ops.finegrained_fp8.w8a8_block_fp8_matmul_grouped(
251
+ A, B, Bs, offsets, tokens_per_expert, block_size
252
+ )
build/torch-cuda/matmul.py ADDED
@@ -0,0 +1,232 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
21
+ # Adapted from https://github.com/sgl-project/sglang/blob/main/python/sglang/srt/layers/quantization/fp8_kernel.py
22
+ @triton.jit
23
+ def w8a8_block_fp8_matmul_kernel(
24
+ # Pointers to inputs and output
25
+ A,
26
+ B,
27
+ C,
28
+ As,
29
+ Bs,
30
+ # Shape for matmul
31
+ M,
32
+ N,
33
+ K,
34
+ # Block size for block-wise quantization
35
+ group_n,
36
+ group_k,
37
+ # Stride for inputs and output
38
+ stride_am,
39
+ stride_ak,
40
+ stride_bk,
41
+ stride_bn,
42
+ stride_cm,
43
+ stride_cn,
44
+ stride_As_m,
45
+ stride_As_k,
46
+ stride_Bs_k,
47
+ stride_Bs_n,
48
+ # Meta-parameters
49
+ BLOCK_SIZE_M: tl.constexpr,
50
+ BLOCK_SIZE_N: tl.constexpr,
51
+ BLOCK_SIZE_K: tl.constexpr,
52
+ GROUP_SIZE_M: tl.constexpr,
53
+ ):
54
+ """Triton-accelerated function used to perform linear operations (dot
55
+ product) on input tensors `A` and `B` with block-wise quantization, and
56
+ store the result in output tensor `C`.
57
+ """
58
+
59
+ pid = tl.program_id(axis=0)
60
+ num_pid_m = tl.cdiv(M, BLOCK_SIZE_M)
61
+ num_pid_n = tl.cdiv(N, BLOCK_SIZE_N)
62
+ num_pid_in_group = GROUP_SIZE_M * num_pid_n
63
+ group_id = pid // num_pid_in_group
64
+ first_pid_m = group_id * GROUP_SIZE_M
65
+ group_size_m = min(num_pid_m - first_pid_m, GROUP_SIZE_M)
66
+ pid_m = first_pid_m + (pid % group_size_m)
67
+ pid_n = (pid % num_pid_in_group) // group_size_m
68
+
69
+ offs_am = (pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)) % M
70
+ offs_bn = (pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)) % N
71
+ offs_k = tl.arange(0, BLOCK_SIZE_K)
72
+ a_ptrs = A + (offs_am[:, None] * stride_am + offs_k[None, :] * stride_ak)
73
+ b_ptrs = B + (offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn)
74
+
75
+ As_ptrs = As + offs_am * stride_As_m
76
+ offs_bsn = offs_bn // group_n
77
+ Bs_ptrs = Bs + offs_bsn * stride_Bs_n
78
+
79
+ accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
80
+ for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
81
+ a = tl.load(a_ptrs, mask=offs_k[None, :] < K - k * BLOCK_SIZE_K, other=0.0)
82
+ b = tl.load(b_ptrs, mask=offs_k[:, None] < K - k * BLOCK_SIZE_K, other=0.0)
83
+
84
+ k_start = k * BLOCK_SIZE_K
85
+ offs_ks = k_start // group_k
86
+ a_s = tl.load(As_ptrs + offs_ks * stride_As_k)
87
+ b_s = tl.load(Bs_ptrs + offs_ks * stride_Bs_k)
88
+
89
+ accumulator += tl.dot(a, b) * a_s[:, None] * b_s[None, :]
90
+ a_ptrs += BLOCK_SIZE_K * stride_ak
91
+ b_ptrs += BLOCK_SIZE_K * stride_bk
92
+
93
+ if C.dtype.element_ty == tl.bfloat16:
94
+ c = accumulator.to(tl.bfloat16)
95
+ elif C.dtype.element_ty == tl.float16:
96
+ c = accumulator.to(tl.float16)
97
+ else:
98
+ c = accumulator.to(tl.float32)
99
+
100
+ offs_cm = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
101
+ offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
102
+ c_ptrs = C + stride_cm * offs_cm[:, None] + stride_cn * offs_cn[None, :]
103
+ c_mask = (offs_cm[:, None] < M) & (offs_cn[None, :] < N)
104
+ tl.store(c_ptrs, c, mask=c_mask)
105
+
106
+
107
+ @triton_op("finegrained_fp8::w8a8_block_fp8_matmul", mutates_args=())
108
+ def _w8a8_block_fp8_matmul(
109
+ A: torch.Tensor,
110
+ B: torch.Tensor,
111
+ As: torch.Tensor,
112
+ Bs: torch.Tensor,
113
+ block_size: list[int] | None,
114
+ output_dtype: torch.dtype = torch.float32,
115
+ ) -> torch.Tensor:
116
+ """This function performs matrix multiplication with block-wise
117
+ quantization.
118
+ It takes two input tensors `A` and `B` with scales `As` and `Bs`.
119
+ The output is returned in the specified `output_dtype`.
120
+ Args:
121
+ A: The input tensor, e.g., activation.
122
+ B: The input tensor, e.g., weight.
123
+ As: The per-token-group quantization scale for `A`.
124
+ Bs: The per-block quantization scale for `B`.
125
+ block_size: The block size for per-block quantization. It should
126
+ be 2-dim, e.g., [128, 128].
127
+ output_dtype: The dtype of the returned tensor.
128
+ Returns:
129
+ torch.Tensor: The result of matmul.
130
+ """
131
+ if block_size is None:
132
+ block_n, block_k = 128, 128
133
+ else:
134
+ assert len(block_size) == 2
135
+ block_n, block_k = block_size[0], block_size[1]
136
+
137
+ # if we have per-tensor quantization, we use 128x128 block size for tiled matmul multiplication
138
+ if block_n == B.shape[-2] and block_k == B.shape[-1]:
139
+ block_n = 128
140
+ block_k = 128
141
+
142
+ assert A.shape[-1] == B.shape[-1]
143
+ assert A.is_contiguous()
144
+
145
+ assert B.ndim == 2
146
+ assert B.is_contiguous()
147
+
148
+ N, K = B.shape
149
+ M = A.numel() // A.shape[-1]
150
+
151
+ # For per-tensor scales (scalar), expand to block-scale shape with strides (0, 0).
152
+ # This is a zero-copy view; all loads inside the kernel hit the same cached value.
153
+ if As.numel() == 1:
154
+ As = As.reshape(1, 1).expand(M, triton.cdiv(K, block_k))
155
+ else:
156
+ assert A.shape[:-1] == As.shape[:-1]
157
+ assert triton.cdiv(K, block_k) == As.shape[-1]
158
+ if Bs.numel() == 1:
159
+ Bs = Bs.reshape(1, 1).expand(triton.cdiv(N, block_n), triton.cdiv(K, block_k))
160
+ else:
161
+ assert Bs.ndim == 2
162
+ assert triton.cdiv(N, block_n) == Bs.shape[0], f"{N}, {block_n}, {Bs.shape}"
163
+ assert triton.cdiv(K, block_k) == Bs.shape[1], f"{K}, {block_k}, {Bs.shape}"
164
+
165
+ C_shape = A.shape[:-1] + (N,)
166
+ C = A.new_empty(C_shape, dtype=output_dtype)
167
+
168
+ # Adaptive BLOCK_SIZE_M: smallest power-of-2 >= M, floored at 16, capped at 128.
169
+ # Matches the WGMMA tile to the actual row count — smaller tiles use less
170
+ # register pressure and a better-matched FP8 WGMMA instruction, improving
171
+ # both accuracy and performance for small M (decode).
172
+ BLOCK_SIZE_M = min(max(triton.next_power_of_2(M), 16), 128)
173
+ BLOCK_SIZE_K = block_k
174
+ BLOCK_SIZE_N = block_n
175
+
176
+ grid = (triton.cdiv(M, BLOCK_SIZE_M) * triton.cdiv(N, BLOCK_SIZE_N),)
177
+ wrap_triton(w8a8_block_fp8_matmul_kernel)[grid](
178
+ A,
179
+ B,
180
+ C,
181
+ As,
182
+ Bs,
183
+ M,
184
+ N,
185
+ K,
186
+ block_n,
187
+ block_k,
188
+ A.stride(-2),
189
+ A.stride(-1),
190
+ B.stride(1),
191
+ B.stride(0),
192
+ C.stride(-2),
193
+ C.stride(-1),
194
+ As.stride(-2),
195
+ As.stride(-1),
196
+ Bs.stride(1),
197
+ Bs.stride(0),
198
+ BLOCK_SIZE_M=BLOCK_SIZE_M,
199
+ BLOCK_SIZE_N=BLOCK_SIZE_N,
200
+ BLOCK_SIZE_K=BLOCK_SIZE_K,
201
+ GROUP_SIZE_M=8,
202
+ )
203
+
204
+ return C
205
+
206
+
207
+ def w8a8_block_fp8_matmul(
208
+ A: torch.Tensor,
209
+ B: torch.Tensor,
210
+ As: torch.Tensor,
211
+ Bs: torch.Tensor,
212
+ block_size: list[int] | None,
213
+ output_dtype: torch.dtype = torch.float32,
214
+ ) -> torch.Tensor:
215
+ """Block-wise W8A8 FP8 matrix multiplication.
216
+
217
+ Computes ``C = A @ B.T`` where both operands are pre-quantized to
218
+ ``float8_e4m3fn`` with per-block scales, and accumulates in float32
219
+ before casting to ``output_dtype``.
220
+
221
+ Args:
222
+ A: Quantized activation tensor ``[M, K]`` in ``float8_e4m3fn``.
223
+ B: Quantized weight tensor ``[N, K]`` in ``float8_e4m3fn``.
224
+ As: Per-token-group activation scales ``[M, K // block_size[1]]``.
225
+ Bs: Per-block weight scales ``[N // block_size[0], K // block_size[1]]``.
226
+ block_size: ``[block_n, block_k]`` quantization block dimensions, e.g. ``[128, 128]``.
227
+ output_dtype: dtype of the returned tensor (default: ``torch.float32``).
228
+
229
+ Returns:
230
+ Output tensor ``[M, N]`` in ``output_dtype``.
231
+ """
232
+ return torch.ops.finegrained_fp8.w8a8_block_fp8_matmul(A, B, As, Bs, block_size, output_dtype)
build/torch-cuda/metadata-cuda.json ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ {
2
+ "version": 1,
3
+ "python-depends": []
4
+ }
build/torch-rocm/__init__.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .act_quant import fp8_act_quant
2
+ from .batched import w8a8_block_fp8_matmul_batched
3
+ from .grouped import w8a8_block_fp8_matmul_grouped
4
+ from .matmul import w8a8_block_fp8_matmul
5
+
6
+ __all__ = [
7
+ "fp8_act_quant",
8
+ "w8a8_block_fp8_matmul",
9
+ "w8a8_block_fp8_matmul_batched",
10
+ "w8a8_block_fp8_matmul_grouped",
11
+ ]
build/torch-rocm/_ops.py ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ ops = torch.ops._finegrained_fp8_2c10f72
3
+
4
+ def add_op_namespace_prefix(op_name: str):
5
+ """
6
+ Prefix op by namespace.
7
+ """
8
+ return f"_finegrained_fp8_2c10f72::{op_name}"
build/torch-rocm/act_quant.py ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
21
+ _FP8_DTYPE = torch.float8_e4m3fn
22
+
23
+
24
+ # Copied from https://huggingface.co/deepseek-ai/DeepSeek-V3/blob/main/inference/kernel.py
25
+ @triton.jit
26
+ def _fp8_act_quant_kernel(x_ptr, y_ptr, s_ptr, BLOCK_SIZE: tl.constexpr):
27
+ pid = tl.program_id(axis=0)
28
+ offs = pid * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE)
29
+ x = tl.load(x_ptr + offs).to(tl.float32)
30
+ s = tl.max(tl.abs(x)) / 448.0 # float8_e4m3fn max
31
+ y = x / s
32
+ y = y.to(y_ptr.dtype.element_ty)
33
+ tl.store(y_ptr + offs, y)
34
+ tl.store(s_ptr + pid, s)
35
+
36
+
37
+ @triton_op("finegrained_fp8::fp8_act_quant", mutates_args=())
38
+ def _fp8_act_quant(
39
+ x: torch.Tensor, block_size: int = 128
40
+ ) -> tuple[torch.Tensor, torch.Tensor]:
41
+ assert x.is_contiguous()
42
+ assert x.shape[-1] % block_size == 0
43
+ y = torch.empty_like(x, dtype=_FP8_DTYPE)
44
+ s = x.new_empty(*x.size()[:-1], x.size(-1) // block_size, dtype=torch.float32)
45
+
46
+ grid = (triton.cdiv(x.numel(), block_size),)
47
+ wrap_triton(_fp8_act_quant_kernel)[grid](x, y, s, BLOCK_SIZE=block_size)
48
+ return y, s
49
+
50
+
51
+ def fp8_act_quant(
52
+ x: torch.Tensor, block_size: int = 128
53
+ ) -> tuple[torch.Tensor, torch.Tensor]:
54
+ """Quantize activations to FP8 with per-block dynamic scaling.
55
+
56
+ Splits the last dimension of ``x`` into blocks of ``block_size`` elements,
57
+ computes ``scale = max(|x_block|) / 448`` per block, and quantizes to
58
+ ``float8_e4m3fn``.
59
+
60
+ Args:
61
+ x: Input tensor in bf16/fp16/fp32. Last dimension must be divisible by
62
+ ``block_size`` and the tensor must be contiguous.
63
+ block_size: Number of elements per quantization block (default: 128).
64
+
65
+ Returns:
66
+ A tuple ``(quantized, scales)`` where ``quantized`` has dtype
67
+ ``float8_e4m3fn`` with the same shape as ``x``, and ``scales`` has
68
+ shape ``(*x.shape[:-1], x.shape[-1] // block_size)`` in float32.
69
+ """
70
+ return torch.ops.finegrained_fp8.fp8_act_quant(x, block_size)
build/torch-rocm/batched.py ADDED
@@ -0,0 +1,196 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
21
+ @triton.jit
22
+ def w8a8_block_fp8_matmul_batched_kernel(
23
+ A, # (S, K) raw BF16/FP16 activations
24
+ B, # (E, N, K) FP8 weight matrices
25
+ C, # (S, N) output
26
+ Bs, # (E, N // group_n, K // group_k) weight scales
27
+ ExpertIds, # (S,) — which expert each batch element routes to
28
+ # Shape
29
+ N,
30
+ K,
31
+ # Block size for block-wise quantization
32
+ group_n,
33
+ group_k,
34
+ # Per-row strides
35
+ stride_ak,
36
+ stride_bk,
37
+ stride_bn,
38
+ stride_cn,
39
+ stride_Bs_k,
40
+ stride_Bs_n,
41
+ # Batch / expert strides
42
+ stride_Ab, # stride between rows in A (one token per program)
43
+ stride_Eb, # stride between experts in B
44
+ stride_Cb,
45
+ stride_Esb, # stride between experts in Bs
46
+ # Meta-parameters
47
+ BLOCK_SIZE_M: tl.constexpr,
48
+ BLOCK_SIZE_N: tl.constexpr,
49
+ BLOCK_SIZE_K: tl.constexpr,
50
+ ):
51
+ pid_n = tl.program_id(axis=0)
52
+ batch_id = tl.program_id(axis=1)
53
+
54
+ # Advance base pointers to this token's activation row and its expert's
55
+ # weight / scale slice. No pre-gather of weights needed (like in non-fp8 impls)
56
+ expert_id = tl.load(ExpertIds + batch_id)
57
+ A = A + batch_id * stride_Ab
58
+ B = B + expert_id * stride_Eb
59
+ C = C + batch_id * stride_Cb
60
+ Bs = Bs + expert_id * stride_Esb
61
+
62
+ offs_bn = (pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)) % N
63
+ offs_k = tl.arange(0, BLOCK_SIZE_K)
64
+ # M=1: broadcast the single activation row to BLOCK_SIZE_M identical rows
65
+ # so tl.dot gets the required (BLOCK_SIZE_M, BLOCK_SIZE_K) shape.
66
+ a_ptrs = A + tl.arange(0, BLOCK_SIZE_M)[:, None] * 0 + offs_k[None, :] * stride_ak
67
+ b_ptrs = B + (offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn)
68
+
69
+ offs_bsn = offs_bn // group_n
70
+ Bs_ptrs = Bs + offs_bsn * stride_Bs_n
71
+
72
+ accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
73
+ for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
74
+ # ---- fused act_quant (replaces: a = tl.load(a_ptrs); a_s = tl.load(As_ptrs)) ----
75
+ a_raw = tl.load(a_ptrs, mask=offs_k[None, :] < K - k * BLOCK_SIZE_K, other=0.0).to(tl.float32)
76
+ a_s = tl.max(tl.abs(a_raw)) / 448.0 # per-block scale (scalar for M=1)
77
+ a = (a_raw / tl.maximum(a_s, 1e-12)).to(tl.float8e4nv)
78
+ # ---- same as baseline from here ----
79
+ b = tl.load(b_ptrs, mask=offs_k[:, None] < K - k * BLOCK_SIZE_K, other=0.0)
80
+
81
+ k_start = k * BLOCK_SIZE_K
82
+ offs_ks = k_start // group_k
83
+ b_s = tl.load(Bs_ptrs + offs_ks * stride_Bs_k)
84
+
85
+ accumulator += tl.dot(a, b) * a_s * b_s[None, :]
86
+ a_ptrs += BLOCK_SIZE_K * stride_ak
87
+ b_ptrs += BLOCK_SIZE_K * stride_bk
88
+
89
+ if C.dtype.element_ty == tl.bfloat16:
90
+ c = accumulator.to(tl.bfloat16)
91
+ elif C.dtype.element_ty == tl.float16:
92
+ c = accumulator.to(tl.float16)
93
+ else:
94
+ c = accumulator.to(tl.float32)
95
+
96
+ # Only write row 0 (M=1); the broadcast rows are discarded.
97
+ offs_cm = tl.arange(0, BLOCK_SIZE_M)
98
+ offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
99
+ c_ptrs = C + offs_cm[:, None] * 0 + stride_cn * offs_cn[None, :]
100
+ c_mask = (offs_cm[:, None] < 1) & (offs_cn[None, :] < N)
101
+ tl.store(c_ptrs, c, mask=c_mask)
102
+
103
+
104
+ @triton_op("finegrained_fp8::w8a8_block_fp8_matmul_batched", mutates_args=())
105
+ def _w8a8_block_fp8_matmul_batched(
106
+ A: torch.Tensor,
107
+ B: torch.Tensor,
108
+ Bs: torch.Tensor,
109
+ expert_ids: torch.Tensor,
110
+ block_size: list[int] | None,
111
+ ) -> torch.Tensor:
112
+ """Batched FP8 block-wise matmul with fused activation quantization.
113
+
114
+ Mirrors ``_batched_linear`` for FP8 weights: A is the raw (BF16/FP16)
115
+ activation matrix, B / Bs are the stacked expert weights / scales.
116
+ The kernel looks up ``expert_ids[batch_id]`` to address the correct expert
117
+ slice of B directly — no (S, N, K) weight gather is needed.
118
+ Activation quantization (``act_quant``) is fused into the matmul loop.
119
+ """
120
+ assert A.ndim == 2, "A must be (S, K)"
121
+ assert A.is_contiguous()
122
+
123
+ assert B.ndim == 3, "B must be (E, N, K)"
124
+ assert B.is_contiguous()
125
+
126
+ assert A.shape[1] == B.shape[2], "K dimension mismatch between A and B"
127
+ assert expert_ids.is_contiguous()
128
+ assert Bs.is_contiguous()
129
+
130
+ if block_size is None:
131
+ block_n, block_k = 128, 128
132
+ else:
133
+ assert len(block_size) == 2
134
+ block_n, block_k = block_size[0], block_size[1]
135
+
136
+ S, K = A.shape
137
+ E, N, _ = B.shape
138
+ C = A.new_empty(S, N)
139
+
140
+ # Adaptive BLOCK_SIZE_M: match the tile to the average tokens per expert
141
+ BLOCK_SIZE_M = min(max(triton.next_power_of_2((S + E - 1) // E), 16), 128)
142
+
143
+ grid = (triton.cdiv(N, block_n), S)
144
+ wrap_triton(w8a8_block_fp8_matmul_batched_kernel)[grid](
145
+ A,
146
+ B,
147
+ C,
148
+ Bs,
149
+ expert_ids,
150
+ N,
151
+ K,
152
+ block_n,
153
+ block_k,
154
+ A.stride(1), # stride_ak
155
+ B.stride(2), # stride_bk
156
+ B.stride(1), # stride_bn
157
+ C.stride(1), # stride_cn
158
+ Bs.stride(2), # stride_Bs_k
159
+ Bs.stride(1), # stride_Bs_n
160
+ A.stride(0), # stride_Ab
161
+ B.stride(0), # stride_Eb
162
+ C.stride(0), # stride_Cb
163
+ Bs.stride(0), # stride_Esb
164
+ BLOCK_SIZE_M=BLOCK_SIZE_M,
165
+ BLOCK_SIZE_N=block_n,
166
+ BLOCK_SIZE_K=block_k,
167
+ )
168
+
169
+ return C
170
+
171
+
172
+ def w8a8_block_fp8_matmul_batched(
173
+ A: torch.Tensor,
174
+ B: torch.Tensor,
175
+ Bs: torch.Tensor,
176
+ expert_ids: torch.Tensor,
177
+ block_size: list[int] | None,
178
+ ) -> torch.Tensor:
179
+ """Batched W8A8 FP8 matmul for MoE expert dispatch with fused activation quantization.
180
+
181
+ Each token in ``A`` is routed to one expert via ``expert_ids``. The kernel
182
+ quantizes ``A`` to FP8 on-the-fly (fused ``act_quant``), reads the correct
183
+ expert weight slice of ``B`` directly using ``expert_ids[batch_id]`` — no
184
+ ``(S, N, K)`` weight gather needed — and accumulates in float32.
185
+
186
+ Args:
187
+ A: Raw activation matrix ``[S, K]`` in bf16/fp16/fp32.
188
+ B: Stacked expert weight tensor ``[E, N, K]`` in ``float8_e4m3fn``.
189
+ Bs: Stacked expert weight scales ``[E, N // block_size[0], K // block_size[1]]``.
190
+ expert_ids: Expert index per token ``[S]``, values in ``[0, E)``.
191
+ block_size: ``[block_n, block_k]`` quantization block dimensions, e.g. ``[128, 128]``.
192
+
193
+ Returns:
194
+ Output tensor ``[S, N]`` in the same dtype as ``A``.
195
+ """
196
+ return torch.ops.finegrained_fp8.w8a8_block_fp8_matmul_batched(A, B, Bs, expert_ids, block_size)
build/torch-rocm/finegrained_fp8/__init__.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ctypes
2
+ import sys
3
+
4
+ import importlib
5
+ from pathlib import Path
6
+ from types import ModuleType
7
+
8
+ def _import_from_path(file_path: Path) -> ModuleType:
9
+ # We cannot use the module name as-is, after adding it to `sys.modules`,
10
+ # it would also be used for other imports. So, we make a module name that
11
+ # depends on the path for it to be unique using the hex-encoded hash of
12
+ # the path.
13
+ path_hash = "{:x}".format(ctypes.c_size_t(hash(file_path.absolute())).value)
14
+ module_name = path_hash
15
+ spec = importlib.util.spec_from_file_location(module_name, file_path)
16
+ if spec is None:
17
+ raise ImportError(f"Cannot load spec for {module_name} from {file_path}")
18
+ module = importlib.util.module_from_spec(spec)
19
+ if module is None:
20
+ raise ImportError(f"Cannot load module {module_name} from spec")
21
+ sys.modules[module_name] = module
22
+ spec.loader.exec_module(module) # type: ignore
23
+ return module
24
+
25
+
26
+ globals().update(vars(_import_from_path(Path(__file__).parent.parent / "__init__.py")))
build/torch-rocm/grouped.py ADDED
@@ -0,0 +1,252 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
21
+ @triton.jit
22
+ def w8a8_block_fp8_grouped_mm_kernel(
23
+ A, # (S, K) raw BF16/FP16 activations, sorted/grouped by expert id
24
+ B, # (E, N, K) FP8 weight matrices
25
+ C, # (S, N) output
26
+ Bs, # (E, N // group_n, K // group_k) weight scales
27
+ Offsets, # (E,) int32 — cumulative row-end per expert
28
+ TileOffsets, # (E,) int32 — cumulative tile-end per expert
29
+ # Shape
30
+ S,
31
+ N,
32
+ K,
33
+ # Block size for block-wise quantization
34
+ group_n,
35
+ group_k,
36
+ # Strides
37
+ stride_am,
38
+ stride_ak,
39
+ stride_Eb,
40
+ stride_bk,
41
+ stride_bn,
42
+ stride_cm,
43
+ stride_cn,
44
+ stride_Esb,
45
+ stride_Bsk,
46
+ stride_Bsn,
47
+ # Meta-parameters
48
+ NUM_EXPERTS: tl.constexpr,
49
+ BLOCK_SIZE_M: tl.constexpr,
50
+ BLOCK_SIZE_N: tl.constexpr,
51
+ BLOCK_SIZE_K: tl.constexpr,
52
+ ):
53
+ pid = tl.program_id(axis=0)
54
+ num_pid_n = tl.cdiv(N, BLOCK_SIZE_N)
55
+ pid_m = pid // num_pid_n
56
+ pid_n = pid % num_pid_n
57
+
58
+ # Exit early for programs beyond the actual tile count.
59
+ total_tiles = tl.load(TileOffsets + NUM_EXPERTS - 1)
60
+ if pid_m >= total_tiles:
61
+ return
62
+
63
+ # Binary search in TileOffsets to find the owning expert.
64
+ # Finds the smallest e such that TileOffsets[e] > pid_m (upper_bound semantics),
65
+ # which is the expert whose tile range contains pid_m.
66
+ # O(log2(NUM_EXPERTS)) loads instead of the O(NUM_EXPERTS) linear scan.
67
+ # NUM_EXPERTS.bit_length() is ceil(log2(E))+1 for powers-of-two, giving one
68
+ # harmless extra iteration when lo==hi; it's a compile-time constant so the
69
+ # loop is fully unrolled by the compiler.
70
+ lo = 0
71
+ hi = NUM_EXPERTS
72
+ for _ in tl.static_range(NUM_EXPERTS.bit_length()):
73
+ mid = (lo + hi) >> 1
74
+ mid_val = tl.load(TileOffsets + mid)
75
+ is_left = mid_val <= pid_m
76
+ lo = tl.where(is_left, mid + 1, lo)
77
+ hi = tl.where(is_left, hi, mid)
78
+ expert_id = lo
79
+
80
+ prev_eid = tl.maximum(expert_id - 1, 0)
81
+
82
+ expert_start = tl.where(expert_id == 0, 0, tl.load(Offsets + prev_eid))
83
+ expert_end = tl.load(Offsets + expert_id)
84
+ M_expert = expert_end - expert_start
85
+
86
+ expert_tile_start = tl.where(expert_id == 0, 0, tl.load(TileOffsets + prev_eid))
87
+ local_tile = pid_m - expert_tile_start
88
+ m_off = local_tile * BLOCK_SIZE_M
89
+
90
+ offs_am = m_off + tl.arange(0, BLOCK_SIZE_M)
91
+ row_mask = offs_am < M_expert
92
+ offs_global_m = expert_start + offs_am
93
+
94
+ offs_bn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
95
+ offs_bn_safe = offs_bn % N
96
+ offs_k = tl.arange(0, BLOCK_SIZE_K)
97
+
98
+ offs_am_safe = offs_global_m % S
99
+
100
+ a_ptrs = A + offs_am_safe[:, None] * stride_am + offs_k[None, :] * stride_ak
101
+ b_ptrs = (
102
+ B
103
+ + expert_id * stride_Eb
104
+ + offs_k[:, None] * stride_bk
105
+ + offs_bn_safe[None, :] * stride_bn
106
+ )
107
+ offs_bsn_safe = offs_bn_safe // group_n
108
+ Bs_ptrs = Bs + expert_id * stride_Esb + offs_bsn_safe * stride_Bsn
109
+
110
+ accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
111
+ for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
112
+ # ---- fused act_quant (replaces: a = tl.load(a_ptrs); a_s = tl.load(As_ptrs)) ----
113
+ a_raw = tl.load(
114
+ a_ptrs,
115
+ mask=row_mask[:, None] & (offs_k[None, :] < K - k * BLOCK_SIZE_K),
116
+ other=0.0,
117
+ ).to(tl.float32)
118
+ a_s = tl.max(tl.abs(a_raw), axis=1) / 448.0 # per-row scale (BLOCK_SIZE_M,)
119
+ # clamp denominator so masked all-zero rows don't produce NaN
120
+ # (their a_s multiplier is 0 anyway, so the output row is correct)
121
+ a = (a_raw / tl.maximum(a_s[:, None], 1e-12)).to(tl.float8e4nv)
122
+ # ---- same as baseline from here ----
123
+ b = tl.load(b_ptrs, mask=offs_k[:, None] < K - k * BLOCK_SIZE_K, other=0.0)
124
+
125
+ k_start = k * BLOCK_SIZE_K
126
+ offs_ks = k_start // group_k
127
+ b_s = tl.load(Bs_ptrs + offs_ks * stride_Bsk)
128
+
129
+ accumulator += tl.dot(a, b) * a_s[:, None] * b_s[None, :]
130
+ a_ptrs += BLOCK_SIZE_K * stride_ak
131
+ b_ptrs += BLOCK_SIZE_K * stride_bk
132
+
133
+ if C.dtype.element_ty == tl.bfloat16:
134
+ c = accumulator.to(tl.bfloat16)
135
+ elif C.dtype.element_ty == tl.float16:
136
+ c = accumulator.to(tl.float16)
137
+ else:
138
+ c = accumulator.to(tl.float32)
139
+
140
+ c_ptrs = C + stride_cm * offs_global_m[:, None] + stride_cn * offs_bn[None, :]
141
+ c_mask = row_mask[:, None] & (offs_bn[None, :] < N)
142
+ tl.store(c_ptrs, c, mask=c_mask)
143
+
144
+
145
+ @triton_op("finegrained_fp8::w8a8_block_fp8_matmul_grouped", mutates_args=())
146
+ def _w8a8_block_fp8_matmul_grouped(
147
+ A: torch.Tensor,
148
+ B: torch.Tensor,
149
+ Bs: torch.Tensor,
150
+ offsets: torch.Tensor,
151
+ tokens_per_expert: torch.Tensor,
152
+ block_size: list[int] | None,
153
+ ) -> torch.Tensor:
154
+ """Grouped FP8 block-wise matmul with fused activation quantization.
155
+
156
+ Mirrors ``_grouped_linear`` / ``_grouped_mm`` for FP8 weights: A is the
157
+ raw (BF16/FP16) activation matrix sorted by expert, B / Bs are the stacked
158
+ expert weights / scales. Activation quantization (``act_quant``) is fused
159
+ into the matmul loop. ``tokens_per_expert`` is needed (in addition to
160
+ ``offsets``) to build the per-expert tile schedule inside the kernel.
161
+ """
162
+ assert A.ndim == 2, "A must be (S, K)"
163
+ assert A.is_contiguous()
164
+
165
+ assert B.ndim == 3, "B must be (E, N, K)"
166
+ assert B.is_contiguous()
167
+
168
+ assert tokens_per_expert.is_contiguous()
169
+ assert offsets.is_contiguous()
170
+ assert Bs.is_contiguous()
171
+
172
+ if block_size is None:
173
+ block_n, block_k = 128, 128
174
+ else:
175
+ assert len(block_size) == 2
176
+ block_n, block_k = block_size[0], block_size[1]
177
+
178
+ S, K = A.shape
179
+ E, N, _ = B.shape
180
+ C = A.new_empty(S, N)
181
+
182
+ # Adaptive BLOCK_SIZE_M: match tile to average tokens per expert.
183
+ BLOCK_SIZE_M = min(max(triton.next_power_of_2((S + E - 1) // E), 16), 128)
184
+ tiles_per_expert = (tokens_per_expert + BLOCK_SIZE_M - 1) // BLOCK_SIZE_M
185
+ tile_offsets = torch.cumsum(tiles_per_expert, dim=0).to(torch.int32)
186
+ # Upper bound on M-tiles: sum_e ceil(M_e / BLOCK_M) <= ceil(S / BLOCK_M) + E.
187
+ # Using a static upper bound keeps the grid size data-independent, which is
188
+ # required for cuda-graph compatibility. Programs beyond the real tile count
189
+ # exit immediately via the early-return guard inside the kernel.
190
+ max_M_tiles = triton.cdiv(S, BLOCK_SIZE_M) + E
191
+
192
+ grid = (max_M_tiles * triton.cdiv(N, block_n),)
193
+ wrap_triton(w8a8_block_fp8_grouped_mm_kernel)[grid](
194
+ A,
195
+ B,
196
+ C,
197
+ Bs,
198
+ offsets,
199
+ tile_offsets,
200
+ S,
201
+ N,
202
+ K,
203
+ block_n,
204
+ block_k,
205
+ A.stride(0), # stride_am
206
+ A.stride(1), # stride_ak
207
+ B.stride(0), # stride_Eb
208
+ B.stride(2), # stride_bk
209
+ B.stride(1), # stride_bn
210
+ C.stride(0), # stride_cm
211
+ C.stride(1), # stride_cn
212
+ Bs.stride(0), # stride_Esb
213
+ Bs.stride(2), # stride_Bsk
214
+ Bs.stride(1), # stride_Bsn
215
+ NUM_EXPERTS=E,
216
+ BLOCK_SIZE_M=BLOCK_SIZE_M,
217
+ BLOCK_SIZE_N=block_n,
218
+ BLOCK_SIZE_K=block_k,
219
+ )
220
+
221
+ return C
222
+
223
+
224
+ def w8a8_block_fp8_matmul_grouped(
225
+ A: torch.Tensor,
226
+ B: torch.Tensor,
227
+ Bs: torch.Tensor,
228
+ offsets: torch.Tensor,
229
+ tokens_per_expert: torch.Tensor,
230
+ block_size: list[int] | None,
231
+ ) -> torch.Tensor:
232
+ """Grouped W8A8 FP8 matmul for MoE expert dispatch with fused activation quantization.
233
+
234
+ Tokens in ``A`` must be pre-sorted by expert id. The kernel quantizes ``A``
235
+ to FP8 on-the-fly (fused ``act_quant``), uses a static over-provisioned grid
236
+ for CUDA-graph compatibility, and resolves each tile's expert via an O(log E)
237
+ binary search over ``offsets``.
238
+
239
+ Args:
240
+ A: Raw activation matrix ``[S, K]`` sorted by expert, in bf16/fp16/fp32.
241
+ B: Stacked expert weight tensor ``[E, N, K]`` in ``float8_e4m3fn``.
242
+ Bs: Stacked expert weight scales ``[E, N // block_size[0], K // block_size[1]]``.
243
+ offsets: Cumulative token counts per expert ``[E]`` (i.e. ``cumsum(tokens_per_expert)``).
244
+ tokens_per_expert: Number of tokens routed to each expert ``[E]``.
245
+ block_size: ``[block_n, block_k]`` quantization block dimensions, e.g. ``[128, 128]``.
246
+
247
+ Returns:
248
+ Output tensor ``[S, N]`` in the same dtype as ``A``, in expert-sorted order.
249
+ """
250
+ return torch.ops.finegrained_fp8.w8a8_block_fp8_matmul_grouped(
251
+ A, B, Bs, offsets, tokens_per_expert, block_size
252
+ )
build/torch-rocm/matmul.py ADDED
@@ -0,0 +1,232 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
21
+ # Adapted from https://github.com/sgl-project/sglang/blob/main/python/sglang/srt/layers/quantization/fp8_kernel.py
22
+ @triton.jit
23
+ def w8a8_block_fp8_matmul_kernel(
24
+ # Pointers to inputs and output
25
+ A,
26
+ B,
27
+ C,
28
+ As,
29
+ Bs,
30
+ # Shape for matmul
31
+ M,
32
+ N,
33
+ K,
34
+ # Block size for block-wise quantization
35
+ group_n,
36
+ group_k,
37
+ # Stride for inputs and output
38
+ stride_am,
39
+ stride_ak,
40
+ stride_bk,
41
+ stride_bn,
42
+ stride_cm,
43
+ stride_cn,
44
+ stride_As_m,
45
+ stride_As_k,
46
+ stride_Bs_k,
47
+ stride_Bs_n,
48
+ # Meta-parameters
49
+ BLOCK_SIZE_M: tl.constexpr,
50
+ BLOCK_SIZE_N: tl.constexpr,
51
+ BLOCK_SIZE_K: tl.constexpr,
52
+ GROUP_SIZE_M: tl.constexpr,
53
+ ):
54
+ """Triton-accelerated function used to perform linear operations (dot
55
+ product) on input tensors `A` and `B` with block-wise quantization, and
56
+ store the result in output tensor `C`.
57
+ """
58
+
59
+ pid = tl.program_id(axis=0)
60
+ num_pid_m = tl.cdiv(M, BLOCK_SIZE_M)
61
+ num_pid_n = tl.cdiv(N, BLOCK_SIZE_N)
62
+ num_pid_in_group = GROUP_SIZE_M * num_pid_n
63
+ group_id = pid // num_pid_in_group
64
+ first_pid_m = group_id * GROUP_SIZE_M
65
+ group_size_m = min(num_pid_m - first_pid_m, GROUP_SIZE_M)
66
+ pid_m = first_pid_m + (pid % group_size_m)
67
+ pid_n = (pid % num_pid_in_group) // group_size_m
68
+
69
+ offs_am = (pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)) % M
70
+ offs_bn = (pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)) % N
71
+ offs_k = tl.arange(0, BLOCK_SIZE_K)
72
+ a_ptrs = A + (offs_am[:, None] * stride_am + offs_k[None, :] * stride_ak)
73
+ b_ptrs = B + (offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn)
74
+
75
+ As_ptrs = As + offs_am * stride_As_m
76
+ offs_bsn = offs_bn // group_n
77
+ Bs_ptrs = Bs + offs_bsn * stride_Bs_n
78
+
79
+ accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
80
+ for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
81
+ a = tl.load(a_ptrs, mask=offs_k[None, :] < K - k * BLOCK_SIZE_K, other=0.0)
82
+ b = tl.load(b_ptrs, mask=offs_k[:, None] < K - k * BLOCK_SIZE_K, other=0.0)
83
+
84
+ k_start = k * BLOCK_SIZE_K
85
+ offs_ks = k_start // group_k
86
+ a_s = tl.load(As_ptrs + offs_ks * stride_As_k)
87
+ b_s = tl.load(Bs_ptrs + offs_ks * stride_Bs_k)
88
+
89
+ accumulator += tl.dot(a, b) * a_s[:, None] * b_s[None, :]
90
+ a_ptrs += BLOCK_SIZE_K * stride_ak
91
+ b_ptrs += BLOCK_SIZE_K * stride_bk
92
+
93
+ if C.dtype.element_ty == tl.bfloat16:
94
+ c = accumulator.to(tl.bfloat16)
95
+ elif C.dtype.element_ty == tl.float16:
96
+ c = accumulator.to(tl.float16)
97
+ else:
98
+ c = accumulator.to(tl.float32)
99
+
100
+ offs_cm = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
101
+ offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
102
+ c_ptrs = C + stride_cm * offs_cm[:, None] + stride_cn * offs_cn[None, :]
103
+ c_mask = (offs_cm[:, None] < M) & (offs_cn[None, :] < N)
104
+ tl.store(c_ptrs, c, mask=c_mask)
105
+
106
+
107
+ @triton_op("finegrained_fp8::w8a8_block_fp8_matmul", mutates_args=())
108
+ def _w8a8_block_fp8_matmul(
109
+ A: torch.Tensor,
110
+ B: torch.Tensor,
111
+ As: torch.Tensor,
112
+ Bs: torch.Tensor,
113
+ block_size: list[int] | None,
114
+ output_dtype: torch.dtype = torch.float32,
115
+ ) -> torch.Tensor:
116
+ """This function performs matrix multiplication with block-wise
117
+ quantization.
118
+ It takes two input tensors `A` and `B` with scales `As` and `Bs`.
119
+ The output is returned in the specified `output_dtype`.
120
+ Args:
121
+ A: The input tensor, e.g., activation.
122
+ B: The input tensor, e.g., weight.
123
+ As: The per-token-group quantization scale for `A`.
124
+ Bs: The per-block quantization scale for `B`.
125
+ block_size: The block size for per-block quantization. It should
126
+ be 2-dim, e.g., [128, 128].
127
+ output_dtype: The dtype of the returned tensor.
128
+ Returns:
129
+ torch.Tensor: The result of matmul.
130
+ """
131
+ if block_size is None:
132
+ block_n, block_k = 128, 128
133
+ else:
134
+ assert len(block_size) == 2
135
+ block_n, block_k = block_size[0], block_size[1]
136
+
137
+ # if we have per-tensor quantization, we use 128x128 block size for tiled matmul multiplication
138
+ if block_n == B.shape[-2] and block_k == B.shape[-1]:
139
+ block_n = 128
140
+ block_k = 128
141
+
142
+ assert A.shape[-1] == B.shape[-1]
143
+ assert A.is_contiguous()
144
+
145
+ assert B.ndim == 2
146
+ assert B.is_contiguous()
147
+
148
+ N, K = B.shape
149
+ M = A.numel() // A.shape[-1]
150
+
151
+ # For per-tensor scales (scalar), expand to block-scale shape with strides (0, 0).
152
+ # This is a zero-copy view; all loads inside the kernel hit the same cached value.
153
+ if As.numel() == 1:
154
+ As = As.reshape(1, 1).expand(M, triton.cdiv(K, block_k))
155
+ else:
156
+ assert A.shape[:-1] == As.shape[:-1]
157
+ assert triton.cdiv(K, block_k) == As.shape[-1]
158
+ if Bs.numel() == 1:
159
+ Bs = Bs.reshape(1, 1).expand(triton.cdiv(N, block_n), triton.cdiv(K, block_k))
160
+ else:
161
+ assert Bs.ndim == 2
162
+ assert triton.cdiv(N, block_n) == Bs.shape[0], f"{N}, {block_n}, {Bs.shape}"
163
+ assert triton.cdiv(K, block_k) == Bs.shape[1], f"{K}, {block_k}, {Bs.shape}"
164
+
165
+ C_shape = A.shape[:-1] + (N,)
166
+ C = A.new_empty(C_shape, dtype=output_dtype)
167
+
168
+ # Adaptive BLOCK_SIZE_M: smallest power-of-2 >= M, floored at 16, capped at 128.
169
+ # Matches the WGMMA tile to the actual row count — smaller tiles use less
170
+ # register pressure and a better-matched FP8 WGMMA instruction, improving
171
+ # both accuracy and performance for small M (decode).
172
+ BLOCK_SIZE_M = min(max(triton.next_power_of_2(M), 16), 128)
173
+ BLOCK_SIZE_K = block_k
174
+ BLOCK_SIZE_N = block_n
175
+
176
+ grid = (triton.cdiv(M, BLOCK_SIZE_M) * triton.cdiv(N, BLOCK_SIZE_N),)
177
+ wrap_triton(w8a8_block_fp8_matmul_kernel)[grid](
178
+ A,
179
+ B,
180
+ C,
181
+ As,
182
+ Bs,
183
+ M,
184
+ N,
185
+ K,
186
+ block_n,
187
+ block_k,
188
+ A.stride(-2),
189
+ A.stride(-1),
190
+ B.stride(1),
191
+ B.stride(0),
192
+ C.stride(-2),
193
+ C.stride(-1),
194
+ As.stride(-2),
195
+ As.stride(-1),
196
+ Bs.stride(1),
197
+ Bs.stride(0),
198
+ BLOCK_SIZE_M=BLOCK_SIZE_M,
199
+ BLOCK_SIZE_N=BLOCK_SIZE_N,
200
+ BLOCK_SIZE_K=BLOCK_SIZE_K,
201
+ GROUP_SIZE_M=8,
202
+ )
203
+
204
+ return C
205
+
206
+
207
+ def w8a8_block_fp8_matmul(
208
+ A: torch.Tensor,
209
+ B: torch.Tensor,
210
+ As: torch.Tensor,
211
+ Bs: torch.Tensor,
212
+ block_size: list[int] | None,
213
+ output_dtype: torch.dtype = torch.float32,
214
+ ) -> torch.Tensor:
215
+ """Block-wise W8A8 FP8 matrix multiplication.
216
+
217
+ Computes ``C = A @ B.T`` where both operands are pre-quantized to
218
+ ``float8_e4m3fn`` with per-block scales, and accumulates in float32
219
+ before casting to ``output_dtype``.
220
+
221
+ Args:
222
+ A: Quantized activation tensor ``[M, K]`` in ``float8_e4m3fn``.
223
+ B: Quantized weight tensor ``[N, K]`` in ``float8_e4m3fn``.
224
+ As: Per-token-group activation scales ``[M, K // block_size[1]]``.
225
+ Bs: Per-block weight scales ``[N // block_size[0], K // block_size[1]]``.
226
+ block_size: ``[block_n, block_k]`` quantization block dimensions, e.g. ``[128, 128]``.
227
+ output_dtype: dtype of the returned tensor (default: ``torch.float32``).
228
+
229
+ Returns:
230
+ Output tensor ``[M, N]`` in ``output_dtype``.
231
+ """
232
+ return torch.ops.finegrained_fp8.w8a8_block_fp8_matmul(A, B, As, Bs, block_size, output_dtype)
build/torch-rocm/metadata-rocm.json ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ {
2
+ "version": 1,
3
+ "python-depends": []
4
+ }
build/torch-xpu/__init__.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .act_quant import fp8_act_quant
2
+ from .batched import w8a8_block_fp8_matmul_batched
3
+ from .grouped import w8a8_block_fp8_matmul_grouped
4
+ from .matmul import w8a8_block_fp8_matmul
5
+
6
+ __all__ = [
7
+ "fp8_act_quant",
8
+ "w8a8_block_fp8_matmul",
9
+ "w8a8_block_fp8_matmul_batched",
10
+ "w8a8_block_fp8_matmul_grouped",
11
+ ]
build/torch-xpu/_ops.py ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ ops = torch.ops._finegrained_fp8_2c10f72
3
+
4
+ def add_op_namespace_prefix(op_name: str):
5
+ """
6
+ Prefix op by namespace.
7
+ """
8
+ return f"_finegrained_fp8_2c10f72::{op_name}"
build/torch-xpu/act_quant.py ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
21
+ _FP8_DTYPE = torch.float8_e4m3fn
22
+
23
+
24
+ # Copied from https://huggingface.co/deepseek-ai/DeepSeek-V3/blob/main/inference/kernel.py
25
+ @triton.jit
26
+ def _fp8_act_quant_kernel(x_ptr, y_ptr, s_ptr, BLOCK_SIZE: tl.constexpr):
27
+ pid = tl.program_id(axis=0)
28
+ offs = pid * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE)
29
+ x = tl.load(x_ptr + offs).to(tl.float32)
30
+ s = tl.max(tl.abs(x)) / 448.0 # float8_e4m3fn max
31
+ y = x / s
32
+ y = y.to(y_ptr.dtype.element_ty)
33
+ tl.store(y_ptr + offs, y)
34
+ tl.store(s_ptr + pid, s)
35
+
36
+
37
+ @triton_op("finegrained_fp8::fp8_act_quant", mutates_args=())
38
+ def _fp8_act_quant(
39
+ x: torch.Tensor, block_size: int = 128
40
+ ) -> tuple[torch.Tensor, torch.Tensor]:
41
+ assert x.is_contiguous()
42
+ assert x.shape[-1] % block_size == 0
43
+ y = torch.empty_like(x, dtype=_FP8_DTYPE)
44
+ s = x.new_empty(*x.size()[:-1], x.size(-1) // block_size, dtype=torch.float32)
45
+
46
+ grid = (triton.cdiv(x.numel(), block_size),)
47
+ wrap_triton(_fp8_act_quant_kernel)[grid](x, y, s, BLOCK_SIZE=block_size)
48
+ return y, s
49
+
50
+
51
+ def fp8_act_quant(
52
+ x: torch.Tensor, block_size: int = 128
53
+ ) -> tuple[torch.Tensor, torch.Tensor]:
54
+ """Quantize activations to FP8 with per-block dynamic scaling.
55
+
56
+ Splits the last dimension of ``x`` into blocks of ``block_size`` elements,
57
+ computes ``scale = max(|x_block|) / 448`` per block, and quantizes to
58
+ ``float8_e4m3fn``.
59
+
60
+ Args:
61
+ x: Input tensor in bf16/fp16/fp32. Last dimension must be divisible by
62
+ ``block_size`` and the tensor must be contiguous.
63
+ block_size: Number of elements per quantization block (default: 128).
64
+
65
+ Returns:
66
+ A tuple ``(quantized, scales)`` where ``quantized`` has dtype
67
+ ``float8_e4m3fn`` with the same shape as ``x``, and ``scales`` has
68
+ shape ``(*x.shape[:-1], x.shape[-1] // block_size)`` in float32.
69
+ """
70
+ return torch.ops.finegrained_fp8.fp8_act_quant(x, block_size)
build/torch-xpu/batched.py ADDED
@@ -0,0 +1,196 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
21
+ @triton.jit
22
+ def w8a8_block_fp8_matmul_batched_kernel(
23
+ A, # (S, K) raw BF16/FP16 activations
24
+ B, # (E, N, K) FP8 weight matrices
25
+ C, # (S, N) output
26
+ Bs, # (E, N // group_n, K // group_k) weight scales
27
+ ExpertIds, # (S,) — which expert each batch element routes to
28
+ # Shape
29
+ N,
30
+ K,
31
+ # Block size for block-wise quantization
32
+ group_n,
33
+ group_k,
34
+ # Per-row strides
35
+ stride_ak,
36
+ stride_bk,
37
+ stride_bn,
38
+ stride_cn,
39
+ stride_Bs_k,
40
+ stride_Bs_n,
41
+ # Batch / expert strides
42
+ stride_Ab, # stride between rows in A (one token per program)
43
+ stride_Eb, # stride between experts in B
44
+ stride_Cb,
45
+ stride_Esb, # stride between experts in Bs
46
+ # Meta-parameters
47
+ BLOCK_SIZE_M: tl.constexpr,
48
+ BLOCK_SIZE_N: tl.constexpr,
49
+ BLOCK_SIZE_K: tl.constexpr,
50
+ ):
51
+ pid_n = tl.program_id(axis=0)
52
+ batch_id = tl.program_id(axis=1)
53
+
54
+ # Advance base pointers to this token's activation row and its expert's
55
+ # weight / scale slice. No pre-gather of weights needed (like in non-fp8 impls)
56
+ expert_id = tl.load(ExpertIds + batch_id)
57
+ A = A + batch_id * stride_Ab
58
+ B = B + expert_id * stride_Eb
59
+ C = C + batch_id * stride_Cb
60
+ Bs = Bs + expert_id * stride_Esb
61
+
62
+ offs_bn = (pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)) % N
63
+ offs_k = tl.arange(0, BLOCK_SIZE_K)
64
+ # M=1: broadcast the single activation row to BLOCK_SIZE_M identical rows
65
+ # so tl.dot gets the required (BLOCK_SIZE_M, BLOCK_SIZE_K) shape.
66
+ a_ptrs = A + tl.arange(0, BLOCK_SIZE_M)[:, None] * 0 + offs_k[None, :] * stride_ak
67
+ b_ptrs = B + (offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn)
68
+
69
+ offs_bsn = offs_bn // group_n
70
+ Bs_ptrs = Bs + offs_bsn * stride_Bs_n
71
+
72
+ accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
73
+ for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
74
+ # ---- fused act_quant (replaces: a = tl.load(a_ptrs); a_s = tl.load(As_ptrs)) ----
75
+ a_raw = tl.load(a_ptrs, mask=offs_k[None, :] < K - k * BLOCK_SIZE_K, other=0.0).to(tl.float32)
76
+ a_s = tl.max(tl.abs(a_raw)) / 448.0 # per-block scale (scalar for M=1)
77
+ a = (a_raw / tl.maximum(a_s, 1e-12)).to(tl.float8e4nv)
78
+ # ---- same as baseline from here ----
79
+ b = tl.load(b_ptrs, mask=offs_k[:, None] < K - k * BLOCK_SIZE_K, other=0.0)
80
+
81
+ k_start = k * BLOCK_SIZE_K
82
+ offs_ks = k_start // group_k
83
+ b_s = tl.load(Bs_ptrs + offs_ks * stride_Bs_k)
84
+
85
+ accumulator += tl.dot(a, b) * a_s * b_s[None, :]
86
+ a_ptrs += BLOCK_SIZE_K * stride_ak
87
+ b_ptrs += BLOCK_SIZE_K * stride_bk
88
+
89
+ if C.dtype.element_ty == tl.bfloat16:
90
+ c = accumulator.to(tl.bfloat16)
91
+ elif C.dtype.element_ty == tl.float16:
92
+ c = accumulator.to(tl.float16)
93
+ else:
94
+ c = accumulator.to(tl.float32)
95
+
96
+ # Only write row 0 (M=1); the broadcast rows are discarded.
97
+ offs_cm = tl.arange(0, BLOCK_SIZE_M)
98
+ offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
99
+ c_ptrs = C + offs_cm[:, None] * 0 + stride_cn * offs_cn[None, :]
100
+ c_mask = (offs_cm[:, None] < 1) & (offs_cn[None, :] < N)
101
+ tl.store(c_ptrs, c, mask=c_mask)
102
+
103
+
104
+ @triton_op("finegrained_fp8::w8a8_block_fp8_matmul_batched", mutates_args=())
105
+ def _w8a8_block_fp8_matmul_batched(
106
+ A: torch.Tensor,
107
+ B: torch.Tensor,
108
+ Bs: torch.Tensor,
109
+ expert_ids: torch.Tensor,
110
+ block_size: list[int] | None,
111
+ ) -> torch.Tensor:
112
+ """Batched FP8 block-wise matmul with fused activation quantization.
113
+
114
+ Mirrors ``_batched_linear`` for FP8 weights: A is the raw (BF16/FP16)
115
+ activation matrix, B / Bs are the stacked expert weights / scales.
116
+ The kernel looks up ``expert_ids[batch_id]`` to address the correct expert
117
+ slice of B directly — no (S, N, K) weight gather is needed.
118
+ Activation quantization (``act_quant``) is fused into the matmul loop.
119
+ """
120
+ assert A.ndim == 2, "A must be (S, K)"
121
+ assert A.is_contiguous()
122
+
123
+ assert B.ndim == 3, "B must be (E, N, K)"
124
+ assert B.is_contiguous()
125
+
126
+ assert A.shape[1] == B.shape[2], "K dimension mismatch between A and B"
127
+ assert expert_ids.is_contiguous()
128
+ assert Bs.is_contiguous()
129
+
130
+ if block_size is None:
131
+ block_n, block_k = 128, 128
132
+ else:
133
+ assert len(block_size) == 2
134
+ block_n, block_k = block_size[0], block_size[1]
135
+
136
+ S, K = A.shape
137
+ E, N, _ = B.shape
138
+ C = A.new_empty(S, N)
139
+
140
+ # Adaptive BLOCK_SIZE_M: match the tile to the average tokens per expert
141
+ BLOCK_SIZE_M = min(max(triton.next_power_of_2((S + E - 1) // E), 16), 128)
142
+
143
+ grid = (triton.cdiv(N, block_n), S)
144
+ wrap_triton(w8a8_block_fp8_matmul_batched_kernel)[grid](
145
+ A,
146
+ B,
147
+ C,
148
+ Bs,
149
+ expert_ids,
150
+ N,
151
+ K,
152
+ block_n,
153
+ block_k,
154
+ A.stride(1), # stride_ak
155
+ B.stride(2), # stride_bk
156
+ B.stride(1), # stride_bn
157
+ C.stride(1), # stride_cn
158
+ Bs.stride(2), # stride_Bs_k
159
+ Bs.stride(1), # stride_Bs_n
160
+ A.stride(0), # stride_Ab
161
+ B.stride(0), # stride_Eb
162
+ C.stride(0), # stride_Cb
163
+ Bs.stride(0), # stride_Esb
164
+ BLOCK_SIZE_M=BLOCK_SIZE_M,
165
+ BLOCK_SIZE_N=block_n,
166
+ BLOCK_SIZE_K=block_k,
167
+ )
168
+
169
+ return C
170
+
171
+
172
+ def w8a8_block_fp8_matmul_batched(
173
+ A: torch.Tensor,
174
+ B: torch.Tensor,
175
+ Bs: torch.Tensor,
176
+ expert_ids: torch.Tensor,
177
+ block_size: list[int] | None,
178
+ ) -> torch.Tensor:
179
+ """Batched W8A8 FP8 matmul for MoE expert dispatch with fused activation quantization.
180
+
181
+ Each token in ``A`` is routed to one expert via ``expert_ids``. The kernel
182
+ quantizes ``A`` to FP8 on-the-fly (fused ``act_quant``), reads the correct
183
+ expert weight slice of ``B`` directly using ``expert_ids[batch_id]`` — no
184
+ ``(S, N, K)`` weight gather needed — and accumulates in float32.
185
+
186
+ Args:
187
+ A: Raw activation matrix ``[S, K]`` in bf16/fp16/fp32.
188
+ B: Stacked expert weight tensor ``[E, N, K]`` in ``float8_e4m3fn``.
189
+ Bs: Stacked expert weight scales ``[E, N // block_size[0], K // block_size[1]]``.
190
+ expert_ids: Expert index per token ``[S]``, values in ``[0, E)``.
191
+ block_size: ``[block_n, block_k]`` quantization block dimensions, e.g. ``[128, 128]``.
192
+
193
+ Returns:
194
+ Output tensor ``[S, N]`` in the same dtype as ``A``.
195
+ """
196
+ return torch.ops.finegrained_fp8.w8a8_block_fp8_matmul_batched(A, B, Bs, expert_ids, block_size)
build/torch-xpu/finegrained_fp8/__init__.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ctypes
2
+ import sys
3
+
4
+ import importlib
5
+ from pathlib import Path
6
+ from types import ModuleType
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 ADDED
@@ -0,0 +1,252 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
21
+ @triton.jit
22
+ def w8a8_block_fp8_grouped_mm_kernel(
23
+ A, # (S, K) raw BF16/FP16 activations, sorted/grouped by expert id
24
+ B, # (E, N, K) FP8 weight matrices
25
+ C, # (S, N) output
26
+ Bs, # (E, N // group_n, K // group_k) weight scales
27
+ Offsets, # (E,) int32 — cumulative row-end per expert
28
+ TileOffsets, # (E,) int32 — cumulative tile-end per expert
29
+ # Shape
30
+ S,
31
+ N,
32
+ K,
33
+ # Block size for block-wise quantization
34
+ group_n,
35
+ group_k,
36
+ # Strides
37
+ stride_am,
38
+ stride_ak,
39
+ stride_Eb,
40
+ stride_bk,
41
+ stride_bn,
42
+ stride_cm,
43
+ stride_cn,
44
+ stride_Esb,
45
+ stride_Bsk,
46
+ stride_Bsn,
47
+ # Meta-parameters
48
+ NUM_EXPERTS: tl.constexpr,
49
+ BLOCK_SIZE_M: tl.constexpr,
50
+ BLOCK_SIZE_N: tl.constexpr,
51
+ BLOCK_SIZE_K: tl.constexpr,
52
+ ):
53
+ pid = tl.program_id(axis=0)
54
+ num_pid_n = tl.cdiv(N, BLOCK_SIZE_N)
55
+ pid_m = pid // num_pid_n
56
+ pid_n = pid % num_pid_n
57
+
58
+ # Exit early for programs beyond the actual tile count.
59
+ total_tiles = tl.load(TileOffsets + NUM_EXPERTS - 1)
60
+ if pid_m >= total_tiles:
61
+ return
62
+
63
+ # Binary search in TileOffsets to find the owning expert.
64
+ # Finds the smallest e such that TileOffsets[e] > pid_m (upper_bound semantics),
65
+ # which is the expert whose tile range contains pid_m.
66
+ # O(log2(NUM_EXPERTS)) loads instead of the O(NUM_EXPERTS) linear scan.
67
+ # NUM_EXPERTS.bit_length() is ceil(log2(E))+1 for powers-of-two, giving one
68
+ # harmless extra iteration when lo==hi; it's a compile-time constant so the
69
+ # loop is fully unrolled by the compiler.
70
+ lo = 0
71
+ hi = NUM_EXPERTS
72
+ for _ in tl.static_range(NUM_EXPERTS.bit_length()):
73
+ mid = (lo + hi) >> 1
74
+ mid_val = tl.load(TileOffsets + mid)
75
+ is_left = mid_val <= pid_m
76
+ lo = tl.where(is_left, mid + 1, lo)
77
+ hi = tl.where(is_left, hi, mid)
78
+ expert_id = lo
79
+
80
+ prev_eid = tl.maximum(expert_id - 1, 0)
81
+
82
+ expert_start = tl.where(expert_id == 0, 0, tl.load(Offsets + prev_eid))
83
+ expert_end = tl.load(Offsets + expert_id)
84
+ M_expert = expert_end - expert_start
85
+
86
+ expert_tile_start = tl.where(expert_id == 0, 0, tl.load(TileOffsets + prev_eid))
87
+ local_tile = pid_m - expert_tile_start
88
+ m_off = local_tile * BLOCK_SIZE_M
89
+
90
+ offs_am = m_off + tl.arange(0, BLOCK_SIZE_M)
91
+ row_mask = offs_am < M_expert
92
+ offs_global_m = expert_start + offs_am
93
+
94
+ offs_bn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
95
+ offs_bn_safe = offs_bn % N
96
+ offs_k = tl.arange(0, BLOCK_SIZE_K)
97
+
98
+ offs_am_safe = offs_global_m % S
99
+
100
+ a_ptrs = A + offs_am_safe[:, None] * stride_am + offs_k[None, :] * stride_ak
101
+ b_ptrs = (
102
+ B
103
+ + expert_id * stride_Eb
104
+ + offs_k[:, None] * stride_bk
105
+ + offs_bn_safe[None, :] * stride_bn
106
+ )
107
+ offs_bsn_safe = offs_bn_safe // group_n
108
+ Bs_ptrs = Bs + expert_id * stride_Esb + offs_bsn_safe * stride_Bsn
109
+
110
+ accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
111
+ for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
112
+ # ---- fused act_quant (replaces: a = tl.load(a_ptrs); a_s = tl.load(As_ptrs)) ----
113
+ a_raw = tl.load(
114
+ a_ptrs,
115
+ mask=row_mask[:, None] & (offs_k[None, :] < K - k * BLOCK_SIZE_K),
116
+ other=0.0,
117
+ ).to(tl.float32)
118
+ a_s = tl.max(tl.abs(a_raw), axis=1) / 448.0 # per-row scale (BLOCK_SIZE_M,)
119
+ # clamp denominator so masked all-zero rows don't produce NaN
120
+ # (their a_s multiplier is 0 anyway, so the output row is correct)
121
+ a = (a_raw / tl.maximum(a_s[:, None], 1e-12)).to(tl.float8e4nv)
122
+ # ---- same as baseline from here ----
123
+ b = tl.load(b_ptrs, mask=offs_k[:, None] < K - k * BLOCK_SIZE_K, other=0.0)
124
+
125
+ k_start = k * BLOCK_SIZE_K
126
+ offs_ks = k_start // group_k
127
+ b_s = tl.load(Bs_ptrs + offs_ks * stride_Bsk)
128
+
129
+ accumulator += tl.dot(a, b) * a_s[:, None] * b_s[None, :]
130
+ a_ptrs += BLOCK_SIZE_K * stride_ak
131
+ b_ptrs += BLOCK_SIZE_K * stride_bk
132
+
133
+ if C.dtype.element_ty == tl.bfloat16:
134
+ c = accumulator.to(tl.bfloat16)
135
+ elif C.dtype.element_ty == tl.float16:
136
+ c = accumulator.to(tl.float16)
137
+ else:
138
+ c = accumulator.to(tl.float32)
139
+
140
+ c_ptrs = C + stride_cm * offs_global_m[:, None] + stride_cn * offs_bn[None, :]
141
+ c_mask = row_mask[:, None] & (offs_bn[None, :] < N)
142
+ tl.store(c_ptrs, c, mask=c_mask)
143
+
144
+
145
+ @triton_op("finegrained_fp8::w8a8_block_fp8_matmul_grouped", mutates_args=())
146
+ def _w8a8_block_fp8_matmul_grouped(
147
+ A: torch.Tensor,
148
+ B: torch.Tensor,
149
+ Bs: torch.Tensor,
150
+ offsets: torch.Tensor,
151
+ tokens_per_expert: torch.Tensor,
152
+ block_size: list[int] | None,
153
+ ) -> torch.Tensor:
154
+ """Grouped FP8 block-wise matmul with fused activation quantization.
155
+
156
+ Mirrors ``_grouped_linear`` / ``_grouped_mm`` for FP8 weights: A is the
157
+ raw (BF16/FP16) activation matrix sorted by expert, B / Bs are the stacked
158
+ expert weights / scales. Activation quantization (``act_quant``) is fused
159
+ into the matmul loop. ``tokens_per_expert`` is needed (in addition to
160
+ ``offsets``) to build the per-expert tile schedule inside the kernel.
161
+ """
162
+ assert A.ndim == 2, "A must be (S, K)"
163
+ assert A.is_contiguous()
164
+
165
+ assert B.ndim == 3, "B must be (E, N, K)"
166
+ assert B.is_contiguous()
167
+
168
+ assert tokens_per_expert.is_contiguous()
169
+ assert offsets.is_contiguous()
170
+ assert Bs.is_contiguous()
171
+
172
+ if block_size is None:
173
+ block_n, block_k = 128, 128
174
+ else:
175
+ assert len(block_size) == 2
176
+ block_n, block_k = block_size[0], block_size[1]
177
+
178
+ S, K = A.shape
179
+ E, N, _ = B.shape
180
+ C = A.new_empty(S, N)
181
+
182
+ # Adaptive BLOCK_SIZE_M: match tile to average tokens per expert.
183
+ BLOCK_SIZE_M = min(max(triton.next_power_of_2((S + E - 1) // E), 16), 128)
184
+ tiles_per_expert = (tokens_per_expert + BLOCK_SIZE_M - 1) // BLOCK_SIZE_M
185
+ tile_offsets = torch.cumsum(tiles_per_expert, dim=0).to(torch.int32)
186
+ # Upper bound on M-tiles: sum_e ceil(M_e / BLOCK_M) <= ceil(S / BLOCK_M) + E.
187
+ # Using a static upper bound keeps the grid size data-independent, which is
188
+ # required for cuda-graph compatibility. Programs beyond the real tile count
189
+ # exit immediately via the early-return guard inside the kernel.
190
+ max_M_tiles = triton.cdiv(S, BLOCK_SIZE_M) + E
191
+
192
+ grid = (max_M_tiles * triton.cdiv(N, block_n),)
193
+ wrap_triton(w8a8_block_fp8_grouped_mm_kernel)[grid](
194
+ A,
195
+ B,
196
+ C,
197
+ Bs,
198
+ offsets,
199
+ tile_offsets,
200
+ S,
201
+ N,
202
+ K,
203
+ block_n,
204
+ block_k,
205
+ A.stride(0), # stride_am
206
+ A.stride(1), # stride_ak
207
+ B.stride(0), # stride_Eb
208
+ B.stride(2), # stride_bk
209
+ B.stride(1), # stride_bn
210
+ C.stride(0), # stride_cm
211
+ C.stride(1), # stride_cn
212
+ Bs.stride(0), # stride_Esb
213
+ Bs.stride(2), # stride_Bsk
214
+ Bs.stride(1), # stride_Bsn
215
+ NUM_EXPERTS=E,
216
+ BLOCK_SIZE_M=BLOCK_SIZE_M,
217
+ BLOCK_SIZE_N=block_n,
218
+ BLOCK_SIZE_K=block_k,
219
+ )
220
+
221
+ return C
222
+
223
+
224
+ def w8a8_block_fp8_matmul_grouped(
225
+ A: torch.Tensor,
226
+ B: torch.Tensor,
227
+ Bs: torch.Tensor,
228
+ offsets: torch.Tensor,
229
+ tokens_per_expert: torch.Tensor,
230
+ block_size: list[int] | None,
231
+ ) -> torch.Tensor:
232
+ """Grouped W8A8 FP8 matmul for MoE expert dispatch with fused activation quantization.
233
+
234
+ Tokens in ``A`` must be pre-sorted by expert id. The kernel quantizes ``A``
235
+ to FP8 on-the-fly (fused ``act_quant``), uses a static over-provisioned grid
236
+ for CUDA-graph compatibility, and resolves each tile's expert via an O(log E)
237
+ binary search over ``offsets``.
238
+
239
+ Args:
240
+ A: Raw activation matrix ``[S, K]`` sorted by expert, in bf16/fp16/fp32.
241
+ B: Stacked expert weight tensor ``[E, N, K]`` in ``float8_e4m3fn``.
242
+ Bs: Stacked expert weight scales ``[E, N // block_size[0], K // block_size[1]]``.
243
+ offsets: Cumulative token counts per expert ``[E]`` (i.e. ``cumsum(tokens_per_expert)``).
244
+ tokens_per_expert: Number of tokens routed to each expert ``[E]``.
245
+ block_size: ``[block_n, block_k]`` quantization block dimensions, e.g. ``[128, 128]``.
246
+
247
+ Returns:
248
+ Output tensor ``[S, N]`` in the same dtype as ``A``, in expert-sorted order.
249
+ """
250
+ return torch.ops.finegrained_fp8.w8a8_block_fp8_matmul_grouped(
251
+ A, B, Bs, offsets, tokens_per_expert, block_size
252
+ )
build/torch-xpu/matmul.py ADDED
@@ -0,0 +1,232 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
21
+ # Adapted from https://github.com/sgl-project/sglang/blob/main/python/sglang/srt/layers/quantization/fp8_kernel.py
22
+ @triton.jit
23
+ def w8a8_block_fp8_matmul_kernel(
24
+ # Pointers to inputs and output
25
+ A,
26
+ B,
27
+ C,
28
+ As,
29
+ Bs,
30
+ # Shape for matmul
31
+ M,
32
+ N,
33
+ K,
34
+ # Block size for block-wise quantization
35
+ group_n,
36
+ group_k,
37
+ # Stride for inputs and output
38
+ stride_am,
39
+ stride_ak,
40
+ stride_bk,
41
+ stride_bn,
42
+ stride_cm,
43
+ stride_cn,
44
+ stride_As_m,
45
+ stride_As_k,
46
+ stride_Bs_k,
47
+ stride_Bs_n,
48
+ # Meta-parameters
49
+ BLOCK_SIZE_M: tl.constexpr,
50
+ BLOCK_SIZE_N: tl.constexpr,
51
+ BLOCK_SIZE_K: tl.constexpr,
52
+ GROUP_SIZE_M: tl.constexpr,
53
+ ):
54
+ """Triton-accelerated function used to perform linear operations (dot
55
+ product) on input tensors `A` and `B` with block-wise quantization, and
56
+ store the result in output tensor `C`.
57
+ """
58
+
59
+ pid = tl.program_id(axis=0)
60
+ num_pid_m = tl.cdiv(M, BLOCK_SIZE_M)
61
+ num_pid_n = tl.cdiv(N, BLOCK_SIZE_N)
62
+ num_pid_in_group = GROUP_SIZE_M * num_pid_n
63
+ group_id = pid // num_pid_in_group
64
+ first_pid_m = group_id * GROUP_SIZE_M
65
+ group_size_m = min(num_pid_m - first_pid_m, GROUP_SIZE_M)
66
+ pid_m = first_pid_m + (pid % group_size_m)
67
+ pid_n = (pid % num_pid_in_group) // group_size_m
68
+
69
+ offs_am = (pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)) % M
70
+ offs_bn = (pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)) % N
71
+ offs_k = tl.arange(0, BLOCK_SIZE_K)
72
+ a_ptrs = A + (offs_am[:, None] * stride_am + offs_k[None, :] * stride_ak)
73
+ b_ptrs = B + (offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn)
74
+
75
+ As_ptrs = As + offs_am * stride_As_m
76
+ offs_bsn = offs_bn // group_n
77
+ Bs_ptrs = Bs + offs_bsn * stride_Bs_n
78
+
79
+ accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
80
+ for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
81
+ a = tl.load(a_ptrs, mask=offs_k[None, :] < K - k * BLOCK_SIZE_K, other=0.0)
82
+ b = tl.load(b_ptrs, mask=offs_k[:, None] < K - k * BLOCK_SIZE_K, other=0.0)
83
+
84
+ k_start = k * BLOCK_SIZE_K
85
+ offs_ks = k_start // group_k
86
+ a_s = tl.load(As_ptrs + offs_ks * stride_As_k)
87
+ b_s = tl.load(Bs_ptrs + offs_ks * stride_Bs_k)
88
+
89
+ accumulator += tl.dot(a, b) * a_s[:, None] * b_s[None, :]
90
+ a_ptrs += BLOCK_SIZE_K * stride_ak
91
+ b_ptrs += BLOCK_SIZE_K * stride_bk
92
+
93
+ if C.dtype.element_ty == tl.bfloat16:
94
+ c = accumulator.to(tl.bfloat16)
95
+ elif C.dtype.element_ty == tl.float16:
96
+ c = accumulator.to(tl.float16)
97
+ else:
98
+ c = accumulator.to(tl.float32)
99
+
100
+ offs_cm = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
101
+ offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
102
+ c_ptrs = C + stride_cm * offs_cm[:, None] + stride_cn * offs_cn[None, :]
103
+ c_mask = (offs_cm[:, None] < M) & (offs_cn[None, :] < N)
104
+ tl.store(c_ptrs, c, mask=c_mask)
105
+
106
+
107
+ @triton_op("finegrained_fp8::w8a8_block_fp8_matmul", mutates_args=())
108
+ def _w8a8_block_fp8_matmul(
109
+ A: torch.Tensor,
110
+ B: torch.Tensor,
111
+ As: torch.Tensor,
112
+ Bs: torch.Tensor,
113
+ block_size: list[int] | None,
114
+ output_dtype: torch.dtype = torch.float32,
115
+ ) -> torch.Tensor:
116
+ """This function performs matrix multiplication with block-wise
117
+ quantization.
118
+ It takes two input tensors `A` and `B` with scales `As` and `Bs`.
119
+ The output is returned in the specified `output_dtype`.
120
+ Args:
121
+ A: The input tensor, e.g., activation.
122
+ B: The input tensor, e.g., weight.
123
+ As: The per-token-group quantization scale for `A`.
124
+ Bs: The per-block quantization scale for `B`.
125
+ block_size: The block size for per-block quantization. It should
126
+ be 2-dim, e.g., [128, 128].
127
+ output_dtype: The dtype of the returned tensor.
128
+ Returns:
129
+ torch.Tensor: The result of matmul.
130
+ """
131
+ if block_size is None:
132
+ block_n, block_k = 128, 128
133
+ else:
134
+ assert len(block_size) == 2
135
+ block_n, block_k = block_size[0], block_size[1]
136
+
137
+ # if we have per-tensor quantization, we use 128x128 block size for tiled matmul multiplication
138
+ if block_n == B.shape[-2] and block_k == B.shape[-1]:
139
+ block_n = 128
140
+ block_k = 128
141
+
142
+ assert A.shape[-1] == B.shape[-1]
143
+ assert A.is_contiguous()
144
+
145
+ assert B.ndim == 2
146
+ assert B.is_contiguous()
147
+
148
+ N, K = B.shape
149
+ M = A.numel() // A.shape[-1]
150
+
151
+ # For per-tensor scales (scalar), expand to block-scale shape with strides (0, 0).
152
+ # This is a zero-copy view; all loads inside the kernel hit the same cached value.
153
+ if As.numel() == 1:
154
+ As = As.reshape(1, 1).expand(M, triton.cdiv(K, block_k))
155
+ else:
156
+ assert A.shape[:-1] == As.shape[:-1]
157
+ assert triton.cdiv(K, block_k) == As.shape[-1]
158
+ if Bs.numel() == 1:
159
+ Bs = Bs.reshape(1, 1).expand(triton.cdiv(N, block_n), triton.cdiv(K, block_k))
160
+ else:
161
+ assert Bs.ndim == 2
162
+ assert triton.cdiv(N, block_n) == Bs.shape[0], f"{N}, {block_n}, {Bs.shape}"
163
+ assert triton.cdiv(K, block_k) == Bs.shape[1], f"{K}, {block_k}, {Bs.shape}"
164
+
165
+ C_shape = A.shape[:-1] + (N,)
166
+ C = A.new_empty(C_shape, dtype=output_dtype)
167
+
168
+ # Adaptive BLOCK_SIZE_M: smallest power-of-2 >= M, floored at 16, capped at 128.
169
+ # Matches the WGMMA tile to the actual row count — smaller tiles use less
170
+ # register pressure and a better-matched FP8 WGMMA instruction, improving
171
+ # both accuracy and performance for small M (decode).
172
+ BLOCK_SIZE_M = min(max(triton.next_power_of_2(M), 16), 128)
173
+ BLOCK_SIZE_K = block_k
174
+ BLOCK_SIZE_N = block_n
175
+
176
+ grid = (triton.cdiv(M, BLOCK_SIZE_M) * triton.cdiv(N, BLOCK_SIZE_N),)
177
+ wrap_triton(w8a8_block_fp8_matmul_kernel)[grid](
178
+ A,
179
+ B,
180
+ C,
181
+ As,
182
+ Bs,
183
+ M,
184
+ N,
185
+ K,
186
+ block_n,
187
+ block_k,
188
+ A.stride(-2),
189
+ A.stride(-1),
190
+ B.stride(1),
191
+ B.stride(0),
192
+ C.stride(-2),
193
+ C.stride(-1),
194
+ As.stride(-2),
195
+ As.stride(-1),
196
+ Bs.stride(1),
197
+ Bs.stride(0),
198
+ BLOCK_SIZE_M=BLOCK_SIZE_M,
199
+ BLOCK_SIZE_N=BLOCK_SIZE_N,
200
+ BLOCK_SIZE_K=BLOCK_SIZE_K,
201
+ GROUP_SIZE_M=8,
202
+ )
203
+
204
+ return C
205
+
206
+
207
+ def w8a8_block_fp8_matmul(
208
+ A: torch.Tensor,
209
+ B: torch.Tensor,
210
+ As: torch.Tensor,
211
+ Bs: torch.Tensor,
212
+ block_size: list[int] | None,
213
+ output_dtype: torch.dtype = torch.float32,
214
+ ) -> torch.Tensor:
215
+ """Block-wise W8A8 FP8 matrix multiplication.
216
+
217
+ Computes ``C = A @ B.T`` where both operands are pre-quantized to
218
+ ``float8_e4m3fn`` with per-block scales, and accumulates in float32
219
+ before casting to ``output_dtype``.
220
+
221
+ Args:
222
+ A: Quantized activation tensor ``[M, K]`` in ``float8_e4m3fn``.
223
+ B: Quantized weight tensor ``[N, K]`` in ``float8_e4m3fn``.
224
+ As: Per-token-group activation scales ``[M, K // block_size[1]]``.
225
+ Bs: Per-block weight scales ``[N // block_size[0], K // block_size[1]]``.
226
+ block_size: ``[block_n, block_k]`` quantization block dimensions, e.g. ``[128, 128]``.
227
+ output_dtype: dtype of the returned tensor (default: ``torch.float32``).
228
+
229
+ Returns:
230
+ Output tensor ``[M, N]`` in ``output_dtype``.
231
+ """
232
+ return torch.ops.finegrained_fp8.w8a8_block_fp8_matmul(A, B, As, Bs, block_size, output_dtype)
build/torch-xpu/metadata-xpu.json ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ {
2
+ "version": 1,
3
+ "python-depends": []
4
+ }