Kernels
danieldk HF Staff commited on
Commit
0c86a40
·
verified ·
1 Parent(s): 73d4076

Build uploaded using `kernels`.

Browse files
build/torch-rocm/_ops.py CHANGED
@@ -1,8 +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}"
 
1
  import torch
2
+ ops = torch.ops._finegrained_fp8_026e669
3
 
4
  def add_op_namespace_prefix(op_name: str):
5
  """
6
  Prefix op by namespace.
7
  """
8
+ return f"_finegrained_fp8_026e669::{op_name}"
build/torch-rocm/act_quant.py CHANGED
@@ -28,8 +28,7 @@ def _fp8_act_quant_kernel(x_ptr, y_ptr, s_ptr, BLOCK_SIZE: tl.constexpr):
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
 
 
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).to(y_ptr.dtype.element_ty)
 
32
  tl.store(y_ptr + offs, y)
33
  tl.store(s_ptr + pid, s)
34
 
build/torch-rocm/batched.py CHANGED
@@ -23,14 +23,12 @@ 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,
@@ -41,50 +39,50 @@ def w8a8_block_fp8_matmul_batched_kernel(
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)
@@ -93,12 +91,10 @@ def w8a8_block_fp8_matmul_batched_kernel(
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=())
@@ -118,52 +114,81 @@ def _w8a8_block_fp8_matmul_batched(
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
@@ -193,4 +218,6 @@ def w8a8_block_fp8_matmul_batched(
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)
 
 
 
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 // block_n, K // block_k) weight scales
27
  ExpertIds, # (S,) — which expert each batch element routes to
28
  # Shape
29
+ S,
30
  N,
31
  K,
 
 
 
32
  # Per-row strides
33
  stride_ak,
34
  stride_bk,
 
39
  # Batch / expert strides
40
  stride_Ab, # stride between rows in A (one token per program)
41
  stride_Eb, # stride between experts in B
42
+ stride_Cb, # stride between rows in C (one token per program)
43
  stride_Esb, # stride between experts in Bs
44
  # Meta-parameters
45
+ block_n: tl.constexpr,
46
+ block_k: tl.constexpr,
47
  BLOCK_SIZE_M: tl.constexpr,
 
 
48
  ):
49
+ batch_id = tl.program_id(axis=0)
50
+ pid_n = tl.program_id(axis=1)
51
 
52
  # Advance base pointers to this token's activation row and its expert's
53
  # weight / scale slice. No pre-gather of weights needed (like in non-fp8 impls)
54
+ # Cast expert_id to int64 to prevent int32 overflow when computing
55
+ # expert_id * stride_Eb (e.g. 255 * 9_437_184 > 2^31 for 256 experts of
56
+ # 3072×3072 FP8 weights).
57
+ expert_id = tl.load(ExpertIds + batch_id).to(tl.int64)
58
+
59
  A = A + batch_id * stride_Ab
60
  B = B + expert_id * stride_Eb
61
  C = C + batch_id * stride_Cb
62
  Bs = Bs + expert_id * stride_Esb
63
 
64
+ offs_bn = pid_n * block_n + tl.arange(0, block_n)
65
+ offs_k = tl.arange(0, block_k)
 
 
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 // block_n
70
  Bs_ptrs = Bs + offs_bsn * stride_Bs_n
71
 
72
+ accumulator = tl.zeros((BLOCK_SIZE_M, block_n), dtype=tl.float32)
73
+ for k in range(0, tl.cdiv(K, block_k)):
74
+ # ---- fused fp8_act_quant ----
75
+ a_raw = tl.load(a_ptrs).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)
80
+ k_start = k * block_k
81
+ offs_ks = k_start // block_k
 
82
  b_s = tl.load(Bs_ptrs + offs_ks * stride_Bs_k)
 
83
  accumulator += tl.dot(a, b) * a_s * b_s[None, :]
84
+ a_ptrs += block_k * stride_ak
85
+ b_ptrs += block_k * stride_bk
86
 
87
  if C.dtype.element_ty == tl.bfloat16:
88
  c = accumulator.to(tl.bfloat16)
 
91
  else:
92
  c = accumulator.to(tl.float32)
93
 
 
94
  offs_cm = tl.arange(0, BLOCK_SIZE_M)
95
+ offs_cn = pid_n * block_n + tl.arange(0, block_n)
96
  c_ptrs = C + offs_cm[:, None] * 0 + stride_cn * offs_cn[None, :]
97
+ tl.store(c_ptrs, c)
 
98
 
99
 
100
  @triton_op("finegrained_fp8::w8a8_block_fp8_matmul_batched", mutates_args=())
 
114
  Activation quantization (``act_quant``) is fused into the matmul loop.
115
  """
116
  assert A.ndim == 2, "A must be (S, K)"
117
+ assert A.is_contiguous(), "A must be contiguous"
118
 
119
  assert B.ndim == 3, "B must be (E, N, K)"
120
+ assert B.is_contiguous(), "B must be contiguous"
121
 
122
  assert A.shape[1] == B.shape[2], "K dimension mismatch between A and B"
123
+ assert expert_ids.is_contiguous(), "expert_ids must be contiguous"
124
+ assert Bs.is_contiguous(), "Bs must be contiguous"
125
+
126
+ S, K = A.shape
127
+ E, N, K = B.shape
128
 
129
  if block_size is None:
130
  block_n, block_k = 128, 128
131
  else:
 
132
  block_n, block_k = block_size[0], block_size[1]
133
 
134
+ # if we have per-tensor quantization, we use 128x128 block size for tiled matmul multiplication
135
+ if block_n == N and block_k == K:
136
+ block_n, block_k = 128, 128
137
 
138
+ # we specifically require block-aligned shapes to keep the kernel simpler
139
+ assert N % block_n == 0, f"N ({N}) must be divisible by block_n ({block_n})"
140
+ assert K % block_k == 0, f"K ({K}) must be divisible by block_k ({block_k})"
141
+
142
+ # For per-tensor scales, expand to block-scale shape with strides (0, 0).
143
+ # This is a zero-copy view; all loads for a given expert hit the same value.
144
+ if Bs.ndim == 1:
145
+ Bs = Bs.reshape(E, 1, 1).expand(E, N // block_n, K // block_k)
146
+ elif Bs.ndim == 3 and Bs.shape[0] == E and Bs.shape[1] == 1 and Bs.shape[2] == 1:
147
+ Bs = Bs.expand(E, N // block_n, K // block_k)
148
+ else:
149
+ assert Bs.ndim == 3, (
150
+ "Bs must be either (E,) / (E,1,1) for per-tensor scales or (E,N//block_n,K//block_k)"
151
+ )
152
+ assert Bs.shape[0] == E, (
153
+ f"Bs expert dim mismatch: expected {E}, got {Bs.shape[0]}"
154
+ )
155
+ assert Bs.shape[1] == N // block_n, (
156
+ f"Bs N-block dim mismatch: expected {N // block_n}, got {Bs.shape[1]}"
157
+ )
158
+ assert Bs.shape[2] == K // block_k, (
159
+ f"Bs K-block dim mismatch: expected {K // block_k}, got {Bs.shape[2]}"
160
+ )
161
 
162
+ C = A.new_empty(S, N)
163
+ grid = (S, triton.cdiv(N, block_n))
164
+ # Adaptive BLOCK_SIZE_M: smallest power-of-2 >= M, floored at 16, capped at 128.
165
+ # Matches the WGMMA tile to the actual row count — smaller tiles use less
166
+ # register pressure and a better-matched FP8 WGMMA instruction, improving
167
+ # both accuracy and performance for small M (decode).
168
+ BLOCK_SIZE_M = min(max(triton.next_power_of_2((S + E - 1) // E), 16), 128)
169
  wrap_triton(w8a8_block_fp8_matmul_batched_kernel)[grid](
170
  A,
171
  B,
172
  C,
173
  Bs,
174
  expert_ids,
175
+ S,
176
  N,
177
  K,
178
+ A.stride(1), # stride_ak
179
+ B.stride(2), # stride_bk
180
+ B.stride(1), # stride_bn
181
+ C.stride(1), # stride_cn
 
 
182
  Bs.stride(2), # stride_Bs_k
183
  Bs.stride(1), # stride_Bs_n
184
+ A.stride(0), # stride_Ab
185
+ B.stride(0), # stride_Eb
186
+ C.stride(0), # stride_Cb
187
  Bs.stride(0), # stride_Esb
188
+ # Meta-parameters
189
+ block_n=block_n,
190
+ block_k=block_k,
191
  BLOCK_SIZE_M=BLOCK_SIZE_M,
 
 
192
  )
193
 
194
  return C
 
218
  Returns:
219
  Output tensor ``[S, N]`` in the same dtype as ``A``.
220
  """
221
+ return torch.ops.finegrained_fp8.w8a8_block_fp8_matmul_batched(
222
+ A, B, Bs, expert_ids, block_size
223
+ )
build/torch-rocm/grouped.py CHANGED
@@ -23,16 +23,13 @@ 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,
@@ -45,15 +42,13 @@ def w8a8_block_fp8_grouped_mm_kernel(
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)
@@ -75,7 +70,11 @@ def w8a8_block_fp8_grouped_mm_kernel(
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
 
@@ -91,44 +90,33 @@ def w8a8_block_fp8_grouped_mm_kernel(
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)
@@ -138,7 +126,7 @@ def w8a8_block_fp8_grouped_mm_kernel(
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
 
@@ -160,23 +148,51 @@ def _w8a8_block_fp8_matmul_grouped(
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.
@@ -189,7 +205,7 @@ def _w8a8_block_fp8_matmul_grouped(
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,
@@ -200,8 +216,6 @@ def _w8a8_block_fp8_matmul_grouped(
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
@@ -212,10 +226,11 @@ def _w8a8_block_fp8_matmul_grouped(
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
 
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 // block_n, K // block_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
  # Strides
34
  stride_am,
35
  stride_ak,
 
42
  stride_Bsk,
43
  stride_Bsn,
44
  # Meta-parameters
45
+ block_n: tl.constexpr,
46
+ block_k: tl.constexpr,
47
  BLOCK_SIZE_M: tl.constexpr,
48
+ NUM_EXPERTS: tl.constexpr,
 
49
  ):
50
+ pid_m = tl.program_id(axis=0)
51
+ pid_n = tl.program_id(axis=1)
 
 
52
 
53
  # Exit early for programs beyond the actual tile count.
54
  total_tiles = tl.load(TileOffsets + NUM_EXPERTS - 1)
 
70
  is_left = mid_val <= pid_m
71
  lo = tl.where(is_left, mid + 1, lo)
72
  hi = tl.where(is_left, hi, mid)
73
+
74
+ # Cast expert_id to int64 to prevent int32 overflow when computing
75
+ # expert_id * stride_Eb (e.g. 255 * 9_437_184 > 2^31 for 256 experts of
76
+ # 3072×3072 FP8 weights).
77
+ expert_id = lo.to(tl.int64)
78
 
79
  prev_eid = tl.maximum(expert_id - 1, 0)
80
 
 
90
  row_mask = offs_am < M_expert
91
  offs_global_m = expert_start + offs_am
92
 
93
+ offs_bn = pid_n * block_n + tl.arange(0, block_n)
94
+ offs_k = tl.arange(0, block_k)
 
95
 
96
+ a_ptrs = A + offs_global_m[:, None] * stride_am + offs_k[None, :] * stride_ak
 
 
97
  b_ptrs = (
98
  B
99
  + expert_id * stride_Eb
100
  + offs_k[:, None] * stride_bk
101
+ + offs_bn[None, :] * stride_bn
102
  )
103
+ offs_bsn = offs_bn // block_n
104
+ Bs_ptrs = Bs + expert_id * stride_Esb + offs_bsn * stride_Bsn
105
+
106
+ accumulator = tl.zeros((BLOCK_SIZE_M, block_n), dtype=tl.float32)
107
+ for k in range(0, tl.cdiv(K, block_k)):
108
+ # ---- fused fp8_act_quant ----
109
+ a_raw = tl.load(a_ptrs, mask=row_mask[:, None], other=0.0).to(tl.float32)
 
 
 
 
110
  a_s = tl.max(tl.abs(a_raw), axis=1) / 448.0 # per-row scale (BLOCK_SIZE_M,)
 
 
111
  a = (a_raw / tl.maximum(a_s[:, None], 1e-12)).to(tl.float8e4nv)
112
  # ---- same as baseline from here ----
113
+ b = tl.load(b_ptrs)
114
+ k_start = k * block_k
115
+ offs_ks = k_start // block_k
 
116
  b_s = tl.load(Bs_ptrs + offs_ks * stride_Bsk)
 
117
  accumulator += tl.dot(a, b) * a_s[:, None] * b_s[None, :]
118
+ a_ptrs += block_k * stride_ak
119
+ b_ptrs += block_k * stride_bk
120
 
121
  if C.dtype.element_ty == tl.bfloat16:
122
  c = accumulator.to(tl.bfloat16)
 
126
  c = accumulator.to(tl.float32)
127
 
128
  c_ptrs = C + stride_cm * offs_global_m[:, None] + stride_cn * offs_bn[None, :]
129
+ c_mask = row_mask[:, None]
130
  tl.store(c_ptrs, c, mask=c_mask)
131
 
132
 
 
148
  ``offsets``) to build the per-expert tile schedule inside the kernel.
149
  """
150
  assert A.ndim == 2, "A must be (S, K)"
151
+ assert A.is_contiguous(), "A must be contiguous"
152
 
153
  assert B.ndim == 3, "B must be (E, N, K)"
154
+ assert B.is_contiguous(), "B must be contiguous"
155
 
156
+ assert A.shape[1] == B.shape[2], "K dimension mismatch between A and B"
157
+ assert tokens_per_expert.is_contiguous(), "tokens_per_expert must be contiguous"
158
+ assert offsets.is_contiguous(), "offsets must be contiguous"
159
+ assert Bs.is_contiguous(), "Bs must be contiguous"
160
+
161
+ S, K = A.shape
162
+ E, N, K = B.shape
163
 
164
  if block_size is None:
165
  block_n, block_k = 128, 128
166
  else:
 
167
  block_n, block_k = block_size[0], block_size[1]
168
 
169
+ if block_n == N and block_k == K:
170
+ block_n, block_k = 128, 128
171
+
172
+ # we specifically require block-aligned shapes to keep the kernel simpler
173
+ assert N % block_n == 0, f"N ({N}) must be divisible by block_n ({block_n})"
174
+ assert K % block_k == 0, f"K ({K}) must be divisible by block_k ({block_k})"
175
+
176
+ # For per-tensor scales, expand to block-scale shape with strides (0, 0).
177
+ # This is a zero-copy view; all loads for a given expert hit the same value.
178
+ if Bs.ndim == 1:
179
+ Bs = Bs.reshape(E, 1, 1).expand(E, N // block_n, K // block_k)
180
+ elif Bs.ndim == 3 and Bs.shape[0] == E and Bs.shape[1] == 1 and Bs.shape[2] == 1:
181
+ Bs = Bs.expand(E, N // block_n, K // block_k)
182
+ else:
183
+ assert Bs.ndim == 3, (
184
+ "Bs must be either (E,) / (E,1,1) for per-tensor scales or (E,N//block_n,K//block_k)"
185
+ )
186
+ assert Bs.shape[0] == E, (
187
+ f"Bs expert dim mismatch: expected {E}, got {Bs.shape[0]}"
188
+ )
189
+ assert Bs.shape[1] == N // block_n, (
190
+ f"Bs N-block dim mismatch: expected {N // block_n}, got {Bs.shape[1]}"
191
+ )
192
+ assert Bs.shape[2] == K // block_k, (
193
+ f"Bs K-block dim mismatch: expected {K // block_k}, got {Bs.shape[2]}"
194
+ )
195
+
196
  C = A.new_empty(S, N)
197
 
198
  # Adaptive BLOCK_SIZE_M: match tile to average tokens per expert.
 
205
  # exit immediately via the early-return guard inside the kernel.
206
  max_M_tiles = triton.cdiv(S, BLOCK_SIZE_M) + E
207
 
208
+ grid = (max_M_tiles, triton.cdiv(N, block_n))
209
  wrap_triton(w8a8_block_fp8_grouped_mm_kernel)[grid](
210
  A,
211
  B,
 
216
  S,
217
  N,
218
  K,
 
 
219
  A.stride(0), # stride_am
220
  A.stride(1), # stride_ak
221
  B.stride(0), # stride_Eb
 
226
  Bs.stride(0), # stride_Esb
227
  Bs.stride(2), # stride_Bsk
228
  Bs.stride(1), # stride_Bsn
229
+ # Meta-parameters
230
+ block_n=block_n,
231
+ block_k=block_k,
232
  BLOCK_SIZE_M=BLOCK_SIZE_M,
233
+ NUM_EXPERTS=E,
 
234
  )
235
 
236
  return C
build/torch-rocm/{metadata-rocm.json → metadata.json} RENAMED
@@ -1,4 +1,5 @@
1
  {
2
  "version": 1,
 
3
  "python-depends": []
4
  }
 
1
  {
2
  "version": 1,
3
+ "license": "Apache-2.0",
4
  "python-depends": []
5
  }
build/torch-xpu/_ops.py CHANGED
@@ -1,8 +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}"
 
1
  import torch
2
+ ops = torch.ops._finegrained_fp8_026e669
3
 
4
  def add_op_namespace_prefix(op_name: str):
5
  """
6
  Prefix op by namespace.
7
  """
8
+ return f"_finegrained_fp8_026e669::{op_name}"
build/torch-xpu/act_quant.py CHANGED
@@ -28,8 +28,7 @@ def _fp8_act_quant_kernel(x_ptr, y_ptr, s_ptr, BLOCK_SIZE: tl.constexpr):
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
 
 
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).to(y_ptr.dtype.element_ty)
 
32
  tl.store(y_ptr + offs, y)
33
  tl.store(s_ptr + pid, s)
34
 
build/torch-xpu/batched.py CHANGED
@@ -23,14 +23,12 @@ 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,
@@ -41,50 +39,50 @@ def w8a8_block_fp8_matmul_batched_kernel(
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)
@@ -93,12 +91,10 @@ def w8a8_block_fp8_matmul_batched_kernel(
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=())
@@ -118,52 +114,81 @@ def _w8a8_block_fp8_matmul_batched(
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
@@ -193,4 +218,6 @@ def w8a8_block_fp8_matmul_batched(
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)
 
 
 
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 // block_n, K // block_k) weight scales
27
  ExpertIds, # (S,) — which expert each batch element routes to
28
  # Shape
29
+ S,
30
  N,
31
  K,
 
 
 
32
  # Per-row strides
33
  stride_ak,
34
  stride_bk,
 
39
  # Batch / expert strides
40
  stride_Ab, # stride between rows in A (one token per program)
41
  stride_Eb, # stride between experts in B
42
+ stride_Cb, # stride between rows in C (one token per program)
43
  stride_Esb, # stride between experts in Bs
44
  # Meta-parameters
45
+ block_n: tl.constexpr,
46
+ block_k: tl.constexpr,
47
  BLOCK_SIZE_M: tl.constexpr,
 
 
48
  ):
49
+ batch_id = tl.program_id(axis=0)
50
+ pid_n = tl.program_id(axis=1)
51
 
52
  # Advance base pointers to this token's activation row and its expert's
53
  # weight / scale slice. No pre-gather of weights needed (like in non-fp8 impls)
54
+ # Cast expert_id to int64 to prevent int32 overflow when computing
55
+ # expert_id * stride_Eb (e.g. 255 * 9_437_184 > 2^31 for 256 experts of
56
+ # 3072×3072 FP8 weights).
57
+ expert_id = tl.load(ExpertIds + batch_id).to(tl.int64)
58
+
59
  A = A + batch_id * stride_Ab
60
  B = B + expert_id * stride_Eb
61
  C = C + batch_id * stride_Cb
62
  Bs = Bs + expert_id * stride_Esb
63
 
64
+ offs_bn = pid_n * block_n + tl.arange(0, block_n)
65
+ offs_k = tl.arange(0, block_k)
 
 
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 // block_n
70
  Bs_ptrs = Bs + offs_bsn * stride_Bs_n
71
 
72
+ accumulator = tl.zeros((BLOCK_SIZE_M, block_n), dtype=tl.float32)
73
+ for k in range(0, tl.cdiv(K, block_k)):
74
+ # ---- fused fp8_act_quant ----
75
+ a_raw = tl.load(a_ptrs).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)
80
+ k_start = k * block_k
81
+ offs_ks = k_start // block_k
 
82
  b_s = tl.load(Bs_ptrs + offs_ks * stride_Bs_k)
 
83
  accumulator += tl.dot(a, b) * a_s * b_s[None, :]
84
+ a_ptrs += block_k * stride_ak
85
+ b_ptrs += block_k * stride_bk
86
 
87
  if C.dtype.element_ty == tl.bfloat16:
88
  c = accumulator.to(tl.bfloat16)
 
91
  else:
92
  c = accumulator.to(tl.float32)
93
 
 
94
  offs_cm = tl.arange(0, BLOCK_SIZE_M)
95
+ offs_cn = pid_n * block_n + tl.arange(0, block_n)
96
  c_ptrs = C + offs_cm[:, None] * 0 + stride_cn * offs_cn[None, :]
97
+ tl.store(c_ptrs, c)
 
98
 
99
 
100
  @triton_op("finegrained_fp8::w8a8_block_fp8_matmul_batched", mutates_args=())
 
114
  Activation quantization (``act_quant``) is fused into the matmul loop.
115
  """
116
  assert A.ndim == 2, "A must be (S, K)"
117
+ assert A.is_contiguous(), "A must be contiguous"
118
 
119
  assert B.ndim == 3, "B must be (E, N, K)"
120
+ assert B.is_contiguous(), "B must be contiguous"
121
 
122
  assert A.shape[1] == B.shape[2], "K dimension mismatch between A and B"
123
+ assert expert_ids.is_contiguous(), "expert_ids must be contiguous"
124
+ assert Bs.is_contiguous(), "Bs must be contiguous"
125
+
126
+ S, K = A.shape
127
+ E, N, K = B.shape
128
 
129
  if block_size is None:
130
  block_n, block_k = 128, 128
131
  else:
 
132
  block_n, block_k = block_size[0], block_size[1]
133
 
134
+ # if we have per-tensor quantization, we use 128x128 block size for tiled matmul multiplication
135
+ if block_n == N and block_k == K:
136
+ block_n, block_k = 128, 128
137
 
138
+ # we specifically require block-aligned shapes to keep the kernel simpler
139
+ assert N % block_n == 0, f"N ({N}) must be divisible by block_n ({block_n})"
140
+ assert K % block_k == 0, f"K ({K}) must be divisible by block_k ({block_k})"
141
+
142
+ # For per-tensor scales, expand to block-scale shape with strides (0, 0).
143
+ # This is a zero-copy view; all loads for a given expert hit the same value.
144
+ if Bs.ndim == 1:
145
+ Bs = Bs.reshape(E, 1, 1).expand(E, N // block_n, K // block_k)
146
+ elif Bs.ndim == 3 and Bs.shape[0] == E and Bs.shape[1] == 1 and Bs.shape[2] == 1:
147
+ Bs = Bs.expand(E, N // block_n, K // block_k)
148
+ else:
149
+ assert Bs.ndim == 3, (
150
+ "Bs must be either (E,) / (E,1,1) for per-tensor scales or (E,N//block_n,K//block_k)"
151
+ )
152
+ assert Bs.shape[0] == E, (
153
+ f"Bs expert dim mismatch: expected {E}, got {Bs.shape[0]}"
154
+ )
155
+ assert Bs.shape[1] == N // block_n, (
156
+ f"Bs N-block dim mismatch: expected {N // block_n}, got {Bs.shape[1]}"
157
+ )
158
+ assert Bs.shape[2] == K // block_k, (
159
+ f"Bs K-block dim mismatch: expected {K // block_k}, got {Bs.shape[2]}"
160
+ )
161
 
162
+ C = A.new_empty(S, N)
163
+ grid = (S, triton.cdiv(N, block_n))
164
+ # Adaptive BLOCK_SIZE_M: smallest power-of-2 >= M, floored at 16, capped at 128.
165
+ # Matches the WGMMA tile to the actual row count — smaller tiles use less
166
+ # register pressure and a better-matched FP8 WGMMA instruction, improving
167
+ # both accuracy and performance for small M (decode).
168
+ BLOCK_SIZE_M = min(max(triton.next_power_of_2((S + E - 1) // E), 16), 128)
169
  wrap_triton(w8a8_block_fp8_matmul_batched_kernel)[grid](
170
  A,
171
  B,
172
  C,
173
  Bs,
174
  expert_ids,
175
+ S,
176
  N,
177
  K,
178
+ A.stride(1), # stride_ak
179
+ B.stride(2), # stride_bk
180
+ B.stride(1), # stride_bn
181
+ C.stride(1), # stride_cn
 
 
182
  Bs.stride(2), # stride_Bs_k
183
  Bs.stride(1), # stride_Bs_n
184
+ A.stride(0), # stride_Ab
185
+ B.stride(0), # stride_Eb
186
+ C.stride(0), # stride_Cb
187
  Bs.stride(0), # stride_Esb
188
+ # Meta-parameters
189
+ block_n=block_n,
190
+ block_k=block_k,
191
  BLOCK_SIZE_M=BLOCK_SIZE_M,
 
 
192
  )
193
 
194
  return C
 
218
  Returns:
219
  Output tensor ``[S, N]`` in the same dtype as ``A``.
220
  """
221
+ return torch.ops.finegrained_fp8.w8a8_block_fp8_matmul_batched(
222
+ A, B, Bs, expert_ids, block_size
223
+ )
build/torch-xpu/grouped.py CHANGED
@@ -23,16 +23,13 @@ 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,
@@ -45,15 +42,13 @@ def w8a8_block_fp8_grouped_mm_kernel(
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)
@@ -75,7 +70,11 @@ def w8a8_block_fp8_grouped_mm_kernel(
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
 
@@ -91,44 +90,33 @@ def w8a8_block_fp8_grouped_mm_kernel(
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)
@@ -138,7 +126,7 @@ def w8a8_block_fp8_grouped_mm_kernel(
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
 
@@ -160,23 +148,51 @@ def _w8a8_block_fp8_matmul_grouped(
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.
@@ -189,7 +205,7 @@ def _w8a8_block_fp8_matmul_grouped(
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,
@@ -200,8 +216,6 @@ def _w8a8_block_fp8_matmul_grouped(
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
@@ -212,10 +226,11 @@ def _w8a8_block_fp8_matmul_grouped(
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
 
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 // block_n, K // block_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
  # Strides
34
  stride_am,
35
  stride_ak,
 
42
  stride_Bsk,
43
  stride_Bsn,
44
  # Meta-parameters
45
+ block_n: tl.constexpr,
46
+ block_k: tl.constexpr,
47
  BLOCK_SIZE_M: tl.constexpr,
48
+ NUM_EXPERTS: tl.constexpr,
 
49
  ):
50
+ pid_m = tl.program_id(axis=0)
51
+ pid_n = tl.program_id(axis=1)
 
 
52
 
53
  # Exit early for programs beyond the actual tile count.
54
  total_tiles = tl.load(TileOffsets + NUM_EXPERTS - 1)
 
70
  is_left = mid_val <= pid_m
71
  lo = tl.where(is_left, mid + 1, lo)
72
  hi = tl.where(is_left, hi, mid)
73
+
74
+ # Cast expert_id to int64 to prevent int32 overflow when computing
75
+ # expert_id * stride_Eb (e.g. 255 * 9_437_184 > 2^31 for 256 experts of
76
+ # 3072×3072 FP8 weights).
77
+ expert_id = lo.to(tl.int64)
78
 
79
  prev_eid = tl.maximum(expert_id - 1, 0)
80
 
 
90
  row_mask = offs_am < M_expert
91
  offs_global_m = expert_start + offs_am
92
 
93
+ offs_bn = pid_n * block_n + tl.arange(0, block_n)
94
+ offs_k = tl.arange(0, block_k)
 
95
 
96
+ a_ptrs = A + offs_global_m[:, None] * stride_am + offs_k[None, :] * stride_ak
 
 
97
  b_ptrs = (
98
  B
99
  + expert_id * stride_Eb
100
  + offs_k[:, None] * stride_bk
101
+ + offs_bn[None, :] * stride_bn
102
  )
103
+ offs_bsn = offs_bn // block_n
104
+ Bs_ptrs = Bs + expert_id * stride_Esb + offs_bsn * stride_Bsn
105
+
106
+ accumulator = tl.zeros((BLOCK_SIZE_M, block_n), dtype=tl.float32)
107
+ for k in range(0, tl.cdiv(K, block_k)):
108
+ # ---- fused fp8_act_quant ----
109
+ a_raw = tl.load(a_ptrs, mask=row_mask[:, None], other=0.0).to(tl.float32)
 
 
 
 
110
  a_s = tl.max(tl.abs(a_raw), axis=1) / 448.0 # per-row scale (BLOCK_SIZE_M,)
 
 
111
  a = (a_raw / tl.maximum(a_s[:, None], 1e-12)).to(tl.float8e4nv)
112
  # ---- same as baseline from here ----
113
+ b = tl.load(b_ptrs)
114
+ k_start = k * block_k
115
+ offs_ks = k_start // block_k
 
116
  b_s = tl.load(Bs_ptrs + offs_ks * stride_Bsk)
 
117
  accumulator += tl.dot(a, b) * a_s[:, None] * b_s[None, :]
118
+ a_ptrs += block_k * stride_ak
119
+ b_ptrs += block_k * stride_bk
120
 
121
  if C.dtype.element_ty == tl.bfloat16:
122
  c = accumulator.to(tl.bfloat16)
 
126
  c = accumulator.to(tl.float32)
127
 
128
  c_ptrs = C + stride_cm * offs_global_m[:, None] + stride_cn * offs_bn[None, :]
129
+ c_mask = row_mask[:, None]
130
  tl.store(c_ptrs, c, mask=c_mask)
131
 
132
 
 
148
  ``offsets``) to build the per-expert tile schedule inside the kernel.
149
  """
150
  assert A.ndim == 2, "A must be (S, K)"
151
+ assert A.is_contiguous(), "A must be contiguous"
152
 
153
  assert B.ndim == 3, "B must be (E, N, K)"
154
+ assert B.is_contiguous(), "B must be contiguous"
155
 
156
+ assert A.shape[1] == B.shape[2], "K dimension mismatch between A and B"
157
+ assert tokens_per_expert.is_contiguous(), "tokens_per_expert must be contiguous"
158
+ assert offsets.is_contiguous(), "offsets must be contiguous"
159
+ assert Bs.is_contiguous(), "Bs must be contiguous"
160
+
161
+ S, K = A.shape
162
+ E, N, K = B.shape
163
 
164
  if block_size is None:
165
  block_n, block_k = 128, 128
166
  else:
 
167
  block_n, block_k = block_size[0], block_size[1]
168
 
169
+ if block_n == N and block_k == K:
170
+ block_n, block_k = 128, 128
171
+
172
+ # we specifically require block-aligned shapes to keep the kernel simpler
173
+ assert N % block_n == 0, f"N ({N}) must be divisible by block_n ({block_n})"
174
+ assert K % block_k == 0, f"K ({K}) must be divisible by block_k ({block_k})"
175
+
176
+ # For per-tensor scales, expand to block-scale shape with strides (0, 0).
177
+ # This is a zero-copy view; all loads for a given expert hit the same value.
178
+ if Bs.ndim == 1:
179
+ Bs = Bs.reshape(E, 1, 1).expand(E, N // block_n, K // block_k)
180
+ elif Bs.ndim == 3 and Bs.shape[0] == E and Bs.shape[1] == 1 and Bs.shape[2] == 1:
181
+ Bs = Bs.expand(E, N // block_n, K // block_k)
182
+ else:
183
+ assert Bs.ndim == 3, (
184
+ "Bs must be either (E,) / (E,1,1) for per-tensor scales or (E,N//block_n,K//block_k)"
185
+ )
186
+ assert Bs.shape[0] == E, (
187
+ f"Bs expert dim mismatch: expected {E}, got {Bs.shape[0]}"
188
+ )
189
+ assert Bs.shape[1] == N // block_n, (
190
+ f"Bs N-block dim mismatch: expected {N // block_n}, got {Bs.shape[1]}"
191
+ )
192
+ assert Bs.shape[2] == K // block_k, (
193
+ f"Bs K-block dim mismatch: expected {K // block_k}, got {Bs.shape[2]}"
194
+ )
195
+
196
  C = A.new_empty(S, N)
197
 
198
  # Adaptive BLOCK_SIZE_M: match tile to average tokens per expert.
 
205
  # exit immediately via the early-return guard inside the kernel.
206
  max_M_tiles = triton.cdiv(S, BLOCK_SIZE_M) + E
207
 
208
+ grid = (max_M_tiles, triton.cdiv(N, block_n))
209
  wrap_triton(w8a8_block_fp8_grouped_mm_kernel)[grid](
210
  A,
211
  B,
 
216
  S,
217
  N,
218
  K,
 
 
219
  A.stride(0), # stride_am
220
  A.stride(1), # stride_ak
221
  B.stride(0), # stride_Eb
 
226
  Bs.stride(0), # stride_Esb
227
  Bs.stride(2), # stride_Bsk
228
  Bs.stride(1), # stride_Bsn
229
+ # Meta-parameters
230
+ block_n=block_n,
231
+ block_k=block_k,
232
  BLOCK_SIZE_M=BLOCK_SIZE_M,
233
+ NUM_EXPERTS=E,
 
234
  )
235
 
236
  return C
build/torch-xpu/{metadata-xpu.json → metadata.json} RENAMED
@@ -1,4 +1,5 @@
1
  {
2
  "version": 1,
 
3
  "python-depends": []
4
  }
 
1
  {
2
  "version": 1,
3
+ "license": "Apache-2.0",
4
  "python-depends": []
5
  }