eousphoros commited on
Commit
ba0d7e9
·
verified ·
1 Parent(s): 08c3802

Upload inference/kernel.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. inference/kernel.py +328 -176
inference/kernel.py CHANGED
@@ -1,88 +1,340 @@
1
  import torch
2
- import tilelang
3
- import tilelang.language as T
4
  from typing import Tuple, Optional
5
 
6
-
7
- tilelang.set_log_level("WARNING")
8
-
9
- pass_configs = {
10
- tilelang.PassConfigKey.TL_DISABLE_WARP_SPECIALIZED: True,
11
- tilelang.PassConfigKey.TL_DISABLE_TMA_LOWER: True,
12
- tilelang.PassConfigKey.TL_DISABLE_FAST_MATH: True,
13
- }
 
 
 
 
 
 
 
14
 
15
  FP8 = "float8_e4m3"
16
  BF16 = "bfloat16"
17
  FP32 = "float32"
18
 
19
 
20
- def fast_log2_ceil(x):
21
- bits_x = T.reinterpret("uint32", x)
22
- exp_x = (bits_x >> 23) & 0xFF
23
- man_bits = bits_x & ((1 << 23) - 1)
24
- return T.Cast("int32", exp_x - 127 + T.if_then_else(man_bits != 0, 1, 0))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
 
 
26
 
27
- def fast_pow2(x):
28
- bits_x = (x + 127) << 23
29
- return T.reinterpret("float32", bits_x)
30
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
 
32
- def fast_round_scale(amax, fp8_max_inv):
33
- return fast_pow2(fast_log2_ceil(amax * fp8_max_inv))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
 
 
35
 
36
- @tilelang.jit(pass_configs=pass_configs)
37
- def act_quant_kernel(
38
- N, in_dtype=BF16, out_dtype=FP8, scale_dtype=FP32, round_scale=False
39
- ):
40
- M = T.symbolic("M")
41
- fp8_min = -448.0
42
- fp8_max = 448.0
43
- fp8_max_inv = 1 / fp8_max
44
- num_stages = 0 if round_scale else 2
45
- blk_m = 32
46
- group_size = 128
47
 
48
- @T.prim_func
49
- def act_quant_kernel_(
50
- X: T.Tensor[(M, N), in_dtype],
51
- Y: T.Tensor[(M, N), out_dtype],
52
- S: T.Tensor[(M, T.ceildiv(N, group_size)), scale_dtype],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  ):
54
- with T.Kernel(T.ceildiv(M, blk_m), T.ceildiv(N, group_size), threads=128) as (
55
- pid_m,
56
- pid_n,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  ):
58
- x_shared = T.alloc_shared((blk_m, group_size), in_dtype)
59
- x_local = T.alloc_fragment((blk_m, group_size), in_dtype)
60
- amax_local = T.alloc_fragment((blk_m,), scale_dtype)
61
- s_local = T.alloc_fragment((blk_m,), scale_dtype)
62
- y_local = T.alloc_fragment((blk_m, group_size), out_dtype)
63
- y_shared = T.alloc_shared((blk_m, group_size), out_dtype)
64
-
65
- for _ in T.Pipelined(1, num_stages=num_stages):
66
- T.copy(X[pid_m * blk_m, pid_n * group_size], x_shared)
67
- T.copy(x_shared, x_local)
68
- T.reduce_absmax(x_local, amax_local, dim=1)
69
- for i in T.Parallel(blk_m):
70
- amax_local[i] = T.max(amax_local[i], 1e-4)
71
- if round_scale:
72
- s_local[i] = fast_round_scale(amax_local[i], fp8_max_inv)
73
- else:
74
- s_local[i] = amax_local[i] * fp8_max_inv
75
- for i, j in T.Parallel(blk_m, group_size):
76
- y_local[i, j] = T.clamp(
77
- x_local[i, j] / s_local[i], fp8_min, fp8_max
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  )
79
- for i in T.Parallel(blk_m):
80
- S[pid_m * blk_m + i, pid_n] = s_local[i]
81
- T.copy(y_local, y_shared)
82
- T.copy(y_shared, Y[pid_m * blk_m, pid_n * group_size])
83
 
84
- return act_quant_kernel_
 
 
 
 
 
 
 
85
 
 
 
 
 
 
 
 
 
86
 
87
  def act_quant(
88
  x: torch.Tensor, block_size: int = 128, scale_fmt: Optional[str] = None
@@ -99,6 +351,10 @@ def act_quant(
99
  - The quantized tensor with dtype `torch.float8_e4m3fn`.
100
  - A tensor of scaling factors with dtype `torch.float32`.
101
  """
 
 
 
 
102
  assert x.is_contiguous(), "Input tensor must be contiguous"
103
  assert x.size(-1) % block_size == 0, (
104
  f"Last dimension size must be divisible by block_size (block_size={block_size})"
@@ -111,63 +367,6 @@ def act_quant(
111
  return y, s
112
 
113
 
114
- @tilelang.jit(pass_configs=pass_configs)
115
- def fp8_gemm_kernel(N, K, out_dtype=BF16, accum_dtype="float32"):
116
- assert out_dtype in [BF16, "float32"]
117
-
118
- M = T.symbolic("M")
119
- group_size = 128
120
- block_M = 32
121
- block_N = 128
122
- block_K = 128
123
-
124
- @T.prim_func
125
- def fp8_gemm_kernel_(
126
- A: T.Tensor[(M, K), FP8],
127
- B: T.Tensor[(N, K), FP8],
128
- C: T.Tensor[(M, N), out_dtype],
129
- scales_a: T.Tensor[(M, T.ceildiv(K, group_size)), FP32],
130
- scales_b: T.Tensor[(T.ceildiv(N, group_size), T.ceildiv(K, group_size)), FP32],
131
- ):
132
- with T.Kernel(T.ceildiv(N, block_N), T.ceildiv(M, block_M), threads=128) as (
133
- bx,
134
- by,
135
- ):
136
- A_shared = T.alloc_shared((block_M, block_K), FP8)
137
- B_shared = T.alloc_shared((block_N, block_K), FP8)
138
- C_shared = T.alloc_shared((block_M, block_N), out_dtype)
139
- Scale_C_shared = T.alloc_shared((block_M), FP32)
140
- C_local = T.alloc_fragment((block_M, block_N), accum_dtype)
141
- C_local_accum = T.alloc_fragment((block_M, block_N), accum_dtype)
142
-
143
- # Improve L2 Cache
144
- T.use_swizzle(panel_size=10)
145
-
146
- T.clear(C_local)
147
- T.clear(C_local_accum)
148
- K_iters = T.ceildiv(K, block_K)
149
- for k in T.Pipelined(K_iters, num_stages=4):
150
- # Load A into shared memory
151
- T.copy(A[by * block_M, k * block_K], A_shared)
152
- # Load B into shared memory
153
- T.copy(B[bx * block_N, k * block_K], B_shared)
154
- # Load scale into shared memory
155
- Scale_B = scales_b[bx * block_N // group_size, k]
156
- for i in T.Parallel(block_M):
157
- Scale_C_shared[i] = scales_a[by * block_M + i, k] * Scale_B
158
-
159
- T.gemm(A_shared, B_shared, C_local, transpose_B=True)
160
- # Promote to enable 2xAcc
161
- for i, j in T.Parallel(block_M, block_N):
162
- C_local_accum[i, j] += C_local[i, j] * Scale_C_shared[i]
163
- T.clear(C_local)
164
- # TMA store
165
- T.copy(C_local_accum, C_shared)
166
- T.copy(C_shared, C[by * block_M, bx * block_N])
167
-
168
- return fp8_gemm_kernel_
169
-
170
-
171
  def fp8_gemm(
172
  a: torch.Tensor, a_s: torch.Tensor, b: torch.Tensor, b_s: torch.Tensor
173
  ) -> torch.Tensor:
@@ -183,6 +382,10 @@ def fp8_gemm(
183
  Returns:
184
  torch.Tensor: The result of the matrix multiplication.
185
  """
 
 
 
 
186
  assert a.is_contiguous() and b.is_contiguous(), "Input tensors must be contiguous"
187
  assert a_s.is_contiguous() and b_s.is_contiguous(), (
188
  "Scaling factor tensors must be contiguous"
@@ -196,61 +399,6 @@ def fp8_gemm(
196
  return c
197
 
198
 
199
- @tilelang.jit(out_idx=[4], pass_configs=pass_configs)
200
- def fp8_index_kernel(h: int, d: int):
201
- b = T.symbolic("b")
202
- m = T.symbolic("m")
203
- n = T.symbolic("n")
204
-
205
- blk_n1 = 512
206
- blk_n2 = 128
207
-
208
- @T.prim_func
209
- def fp8_index_kernel_(
210
- q: T.Tensor[(b, m, h, d), FP8],
211
- q_s: T.Tensor[(b, m, h), FP32],
212
- k: T.Tensor[(b, n, d), FP8],
213
- k_s: T.Tensor[(b, n), FP32],
214
- o: T.Tensor[(b, m, n), FP32],
215
- ) -> None:
216
- with T.Kernel(b, m, T.ceildiv(n, blk_n1)) as (i_b, i_m, i1_n):
217
- q_smem = T.alloc_shared((h, d), FP8)
218
- T.copy(q[i_b, i_m, 0, 0], q_smem)
219
-
220
- q_s_frag = T.alloc_fragment(h, FP32)
221
- T.copy(q_s[i_b, i_m, 0], q_s_frag)
222
-
223
- for i2_n in T.Pipelined(blk_n1 // blk_n2, num_stages=2):
224
- k_smem = T.alloc_shared((blk_n2, d), FP8)
225
- T.copy(k[i_b, i1_n * blk_n1 + i2_n * blk_n2, 0], k_smem)
226
-
227
- k_s_frag = T.alloc_fragment(blk_n2, FP32)
228
- T.copy(k_s[i_b, i1_n * blk_n1 + i2_n * blk_n2], k_s_frag)
229
-
230
- logits = T.alloc_fragment((blk_n2, h), FP32)
231
- T.gemm(
232
- k_smem,
233
- q_smem,
234
- logits,
235
- transpose_A=False,
236
- transpose_B=True,
237
- clear_accum=True,
238
- )
239
-
240
- for i_h, i3_n in T.Parallel(h, blk_n2):
241
- logits[i3_n, i_h] = T.max(logits[i3_n, i_h], 0) * q_s_frag[i_h]
242
-
243
- logits_sum = T.alloc_fragment(blk_n2, FP32)
244
- T.reduce_sum(logits, logits_sum, dim=1)
245
-
246
- for i3_n in T.Parallel(blk_n2):
247
- logits_sum[i3_n] *= k_s_frag[i3_n]
248
-
249
- T.copy(logits_sum, o[i_b, i_m, i1_n * blk_n1 + i2_n * blk_n2])
250
-
251
- return fp8_index_kernel_
252
-
253
-
254
  def fp8_index(
255
  q: torch.Tensor,
256
  q_s: torch.Tensor,
@@ -271,4 +419,8 @@ def fp8_index(
271
  fp32 logits -> fp32 logits_sum
272
  fp32 logits_sum * k_s (e8m0) -> fp32 index_score
273
  """
 
 
 
 
274
  return fp8_index_kernel(q.shape[2], q.shape[3])(q, q_s, k, k_s)
 
1
  import torch
 
 
2
  from typing import Tuple, Optional
3
 
4
+ # Check if CUDA is available for tilelang kernels
5
+ USE_TILELANG = torch.cuda.is_available()
6
+
7
+ if USE_TILELANG:
8
+ try:
9
+ import tilelang
10
+ import tilelang.language as T
11
+ tilelang.set_log_level("WARNING")
12
+ pass_configs = {
13
+ tilelang.PassConfigKey.TL_DISABLE_WARP_SPECIALIZED: True,
14
+ tilelang.PassConfigKey.TL_DISABLE_TMA_LOWER: True,
15
+ tilelang.PassConfigKey.TL_DISABLE_FAST_MATH: True,
16
+ }
17
+ except ImportError:
18
+ USE_TILELANG = False
19
 
20
  FP8 = "float8_e4m3"
21
  BF16 = "bfloat16"
22
  FP32 = "float32"
23
 
24
 
25
+ # ============================================================================
26
+ # CPU Fallback Implementations
27
+ # ============================================================================
28
+
29
+ def act_quant_cpu(
30
+ x: torch.Tensor, block_size: int = 128, scale_fmt: Optional[str] = None
31
+ ) -> Tuple[torch.Tensor, torch.Tensor]:
32
+ """
33
+ CPU fallback: Quantizes input tensor to FP8 with per-block scales.
34
+ Uses simple per-block max scaling for FP8 quantization on CPU.
35
+ """
36
+ assert x.is_contiguous(), "Input tensor must be contiguous"
37
+ assert x.size(-1) % block_size == 0, (
38
+ f"Last dimension size must be divisible by block_size (block_size={block_size})"
39
+ )
40
+
41
+ N = x.size(-1)
42
+ fp8_max = 448.0 # Max representable value in FP8 E4M3
43
+
44
+ # Reshape for block-wise operations: [..., N] -> [..., N//block_size, block_size]
45
+ orig_shape = x.shape
46
+ x_blocks = x.view(*orig_shape[:-1], N // block_size, block_size)
47
+
48
+ # Compute per-block max (absolute value)
49
+ amax = x_blocks.abs().amax(dim=-1, keepdim=True).clamp(min=1e-4)
50
+
51
+ # Compute scales: scale = amax / fp8_max
52
+ s = (amax / fp8_max).squeeze(-1) # [..., N//block_size]
53
+
54
+ # Quantize: y = clamp(x / scale, -fp8_max, fp8_max)
55
+ y_scaled = x_blocks / amax * fp8_max
56
+ y_scaled = y_scaled.clamp(-fp8_max, fp8_max)
57
+
58
+ # Reshape back and convert to FP8
59
+ y = y_scaled.view(orig_shape).to(torch.float8_e4m3fn)
60
 
61
+ return y, s.to(torch.float32)
62
 
 
 
 
63
 
64
+ def fp8_gemm_cpu(
65
+ a: torch.Tensor, a_s: torch.Tensor, b: torch.Tensor, b_s: torch.Tensor,
66
+ block_size: int = 128
67
+ ) -> torch.Tensor:
68
+ """
69
+ CPU fallback: FP8 GEMM with block-scaled dequantization.
70
+
71
+ Args:
72
+ a: [M, K] FP8 activations
73
+ a_s: [M, K//block_size] activation scales
74
+ b: [N, K] FP8 weights
75
+ b_s: [N//block_size, K//block_size] weight scales
76
+
77
+ Returns:
78
+ [M, N] output in default dtype (bf16)
79
+ """
80
+ M = a.numel() // a.size(-1)
81
+ K = a.size(-1)
82
+ N = b.size(0)
83
 
84
+ # Dequantize A: [M, K] = fp8_a * scale_a (broadcast over blocks)
85
+ a_f32 = a.view(M, K // block_size, block_size).float()
86
+ a_dequant = (a_f32 * a_s.view(M, -1, 1)).view(M, K)
87
+
88
+ # Dequantize B: [N, K] = fp8_b * scale_b (broadcast over blocks)
89
+ b_f32 = b.view(N, K // block_size, block_size).float()
90
+ # b_s is [N//block_size, K//block_size], need to broadcast
91
+ b_s_expanded = b_s.view(N // block_size, 1, K // block_size, 1).expand(
92
+ N // block_size, block_size, K // block_size, block_size
93
+ ).reshape(N, K)
94
+ b_dequant = b_f32.view(N, K) * b_s_expanded
95
+
96
+ # Standard matmul: [M, K] @ [K, N] -> [M, N]
97
+ return torch.matmul(a_dequant.to(torch.bfloat16), b_dequant.T.to(torch.bfloat16))
98
+
99
+
100
+ def fp8_index_cpu(
101
+ q: torch.Tensor,
102
+ q_s: torch.Tensor,
103
+ k: torch.Tensor,
104
+ k_s: torch.Tensor,
105
+ block_size: int = 128
106
+ ) -> torch.Tensor:
107
+ """
108
+ CPU fallback: Index scoring for sparse attention.
109
 
110
+ This computes index scores for selecting top-k positions in sparse attention.
111
 
112
+ Args:
113
+ q: [b, m, h, d] FP8 queries
114
+ q_s: [b, m, h] or [b, m, h, d//block_size] query weights (includes scales)
115
+ k: [b, n, d] FP8 keys
116
+ k_s: [b, n] or [b, n, d//block_size] key scales
 
 
 
 
 
 
117
 
118
+ Returns:
119
+ [b, m, n] index scores
120
+ """
121
+ b, m, h, d = q.shape
122
+ n = k.shape[1]
123
+
124
+ # Dequantize q and k from FP8 to float32
125
+ q_f32 = q.float() # [b, m, h, d]
126
+ k_f32 = k.float() # [b, n, d]
127
+
128
+ # Compute attention logits: q @ k^T -> [b, m, h, n]
129
+ logits = torch.einsum("bmhd,bnd->bmhn", q_f32, k_f32)
130
+
131
+ # Apply ReLU
132
+ logits = torch.relu(logits)
133
+
134
+ # Scale by q_s (query weights)
135
+ # q_s may have shape [b, m, h] or [b, m, h, num_scales]
136
+ if q_s.dim() == 3:
137
+ logits = logits * q_s.unsqueeze(-1) # [b, m, h, 1] broadcast
138
+ else:
139
+ # q_s is [b, m, h, num_scales] - sum/average over last dim
140
+ logits = logits * q_s.mean(dim=-1, keepdim=True)
141
+
142
+ # Sum over heads -> [b, m, n]
143
+ logits_sum = logits.sum(dim=2)
144
+
145
+ # Scale by k_s (key scales)
146
+ # k_s may have shape [b, n] or [b, n, num_scales]
147
+ if k_s.dim() == 2:
148
+ logits_sum = logits_sum * k_s.unsqueeze(1) # [b, 1, n] broadcast
149
+ else:
150
+ # k_s is [b, n, num_scales] - sum/average over last dim
151
+ logits_sum = logits_sum * k_s.mean(dim=-1).unsqueeze(1)
152
+
153
+ return logits_sum.to(torch.float32)
154
+
155
+
156
+ # ============================================================================
157
+ # Tilelang CUDA Kernels (only defined if tilelang available)
158
+ # ============================================================================
159
+
160
+ if USE_TILELANG:
161
+ def fast_log2_ceil(x):
162
+ bits_x = T.reinterpret("uint32", x)
163
+ exp_x = (bits_x >> 23) & 0xFF
164
+ man_bits = bits_x & ((1 << 23) - 1)
165
+ return T.Cast("int32", exp_x - 127 + T.if_then_else(man_bits != 0, 1, 0))
166
+
167
+ def fast_pow2(x):
168
+ bits_x = (x + 127) << 23
169
+ return T.reinterpret("float32", bits_x)
170
+
171
+ def fast_round_scale(amax, fp8_max_inv):
172
+ return fast_pow2(fast_log2_ceil(amax * fp8_max_inv))
173
+
174
+ @tilelang.jit(pass_configs=pass_configs)
175
+ def act_quant_kernel(
176
+ N, in_dtype=BF16, out_dtype=FP8, scale_dtype=FP32, round_scale=False
177
  ):
178
+ M = T.symbolic("M")
179
+ fp8_min = -448.0
180
+ fp8_max = 448.0
181
+ fp8_max_inv = 1 / fp8_max
182
+ num_stages = 0 if round_scale else 2
183
+ blk_m = 32
184
+ group_size = 128
185
+
186
+ @T.prim_func
187
+ def act_quant_kernel_(
188
+ X: T.Tensor[(M, N), in_dtype],
189
+ Y: T.Tensor[(M, N), out_dtype],
190
+ S: T.Tensor[(M, T.ceildiv(N, group_size)), scale_dtype],
191
+ ):
192
+ with T.Kernel(T.ceildiv(M, blk_m), T.ceildiv(N, group_size), threads=128) as (
193
+ pid_m,
194
+ pid_n,
195
+ ):
196
+ x_shared = T.alloc_shared((blk_m, group_size), in_dtype)
197
+ x_local = T.alloc_fragment((blk_m, group_size), in_dtype)
198
+ amax_local = T.alloc_fragment((blk_m,), scale_dtype)
199
+ s_local = T.alloc_fragment((blk_m,), scale_dtype)
200
+ y_local = T.alloc_fragment((blk_m, group_size), out_dtype)
201
+ y_shared = T.alloc_shared((blk_m, group_size), out_dtype)
202
+
203
+ for _ in T.Pipelined(1, num_stages=num_stages):
204
+ T.copy(X[pid_m * blk_m, pid_n * group_size], x_shared)
205
+ T.copy(x_shared, x_local)
206
+ T.reduce_absmax(x_local, amax_local, dim=1)
207
+ for i in T.Parallel(blk_m):
208
+ amax_local[i] = T.max(amax_local[i], 1e-4)
209
+ if round_scale:
210
+ s_local[i] = fast_round_scale(amax_local[i], fp8_max_inv)
211
+ else:
212
+ s_local[i] = amax_local[i] * fp8_max_inv
213
+ for i, j in T.Parallel(blk_m, group_size):
214
+ y_local[i, j] = T.clamp(
215
+ x_local[i, j] / s_local[i], fp8_min, fp8_max
216
+ )
217
+ for i in T.Parallel(blk_m):
218
+ S[pid_m * blk_m + i, pid_n] = s_local[i]
219
+ T.copy(y_local, y_shared)
220
+ T.copy(y_shared, Y[pid_m * blk_m, pid_n * group_size])
221
+
222
+ return act_quant_kernel_
223
+
224
+ @tilelang.jit(pass_configs=pass_configs)
225
+ def fp8_gemm_kernel(N, K, out_dtype=BF16, accum_dtype="float32"):
226
+ assert out_dtype in [BF16, "float32"]
227
+
228
+ M = T.symbolic("M")
229
+ group_size = 128
230
+ block_M = 32
231
+ block_N = 128
232
+ block_K = 128
233
+
234
+ @T.prim_func
235
+ def fp8_gemm_kernel_(
236
+ A: T.Tensor[(M, K), FP8],
237
+ B: T.Tensor[(N, K), FP8],
238
+ C: T.Tensor[(M, N), out_dtype],
239
+ scales_a: T.Tensor[(M, T.ceildiv(K, group_size)), FP32],
240
+ scales_b: T.Tensor[(T.ceildiv(N, group_size), T.ceildiv(K, group_size)), FP32],
241
  ):
242
+ with T.Kernel(T.ceildiv(N, block_N), T.ceildiv(M, block_M), threads=128) as (
243
+ bx,
244
+ by,
245
+ ):
246
+ A_shared = T.alloc_shared((block_M, block_K), FP8)
247
+ B_shared = T.alloc_shared((block_N, block_K), FP8)
248
+ C_shared = T.alloc_shared((block_M, block_N), out_dtype)
249
+ Scale_C_shared = T.alloc_shared((block_M), FP32)
250
+ C_local = T.alloc_fragment((block_M, block_N), accum_dtype)
251
+ C_local_accum = T.alloc_fragment((block_M, block_N), accum_dtype)
252
+
253
+ # Improve L2 Cache
254
+ T.use_swizzle(panel_size=10)
255
+
256
+ T.clear(C_local)
257
+ T.clear(C_local_accum)
258
+ K_iters = T.ceildiv(K, block_K)
259
+ for k in T.Pipelined(K_iters, num_stages=4):
260
+ # Load A into shared memory
261
+ T.copy(A[by * block_M, k * block_K], A_shared)
262
+ # Load B into shared memory
263
+ T.copy(B[bx * block_N, k * block_K], B_shared)
264
+ # Load scale into shared memory
265
+ Scale_B = scales_b[bx * block_N // group_size, k]
266
+ for i in T.Parallel(block_M):
267
+ Scale_C_shared[i] = scales_a[by * block_M + i, k] * Scale_B
268
+
269
+ T.gemm(A_shared, B_shared, C_local, transpose_B=True)
270
+ # Promote to enable 2xAcc
271
+ for i, j in T.Parallel(block_M, block_N):
272
+ C_local_accum[i, j] += C_local[i, j] * Scale_C_shared[i]
273
+ T.clear(C_local)
274
+ # TMA store
275
+ T.copy(C_local_accum, C_shared)
276
+ T.copy(C_shared, C[by * block_M, bx * block_N])
277
+
278
+ return fp8_gemm_kernel_
279
+
280
+ @tilelang.jit(out_idx=[4], pass_configs=pass_configs)
281
+ def fp8_index_kernel(h: int, d: int):
282
+ b = T.symbolic("b")
283
+ m = T.symbolic("m")
284
+ n = T.symbolic("n")
285
+
286
+ blk_n1 = 512
287
+ blk_n2 = 128
288
+
289
+ @T.prim_func
290
+ def fp8_index_kernel_(
291
+ q: T.Tensor[(b, m, h, d), FP8],
292
+ q_s: T.Tensor[(b, m, h), FP32],
293
+ k: T.Tensor[(b, n, d), FP8],
294
+ k_s: T.Tensor[(b, n), FP32],
295
+ o: T.Tensor[(b, m, n), FP32],
296
+ ) -> None:
297
+ with T.Kernel(b, m, T.ceildiv(n, blk_n1)) as (i_b, i_m, i1_n):
298
+ q_smem = T.alloc_shared((h, d), FP8)
299
+ T.copy(q[i_b, i_m, 0, 0], q_smem)
300
+
301
+ q_s_frag = T.alloc_fragment(h, FP32)
302
+ T.copy(q_s[i_b, i_m, 0], q_s_frag)
303
+
304
+ for i2_n in T.Pipelined(blk_n1 // blk_n2, num_stages=2):
305
+ k_smem = T.alloc_shared((blk_n2, d), FP8)
306
+ T.copy(k[i_b, i1_n * blk_n1 + i2_n * blk_n2, 0], k_smem)
307
+
308
+ k_s_frag = T.alloc_fragment(blk_n2, FP32)
309
+ T.copy(k_s[i_b, i1_n * blk_n1 + i2_n * blk_n2], k_s_frag)
310
+
311
+ logits = T.alloc_fragment((blk_n2, h), FP32)
312
+ T.gemm(
313
+ k_smem,
314
+ q_smem,
315
+ logits,
316
+ transpose_A=False,
317
+ transpose_B=True,
318
+ clear_accum=True,
319
  )
 
 
 
 
320
 
321
+ for i_h, i3_n in T.Parallel(h, blk_n2):
322
+ logits[i3_n, i_h] = T.max(logits[i3_n, i_h], 0) * q_s_frag[i_h]
323
+
324
+ logits_sum = T.alloc_fragment(blk_n2, FP32)
325
+ T.reduce_sum(logits, logits_sum, dim=1)
326
+
327
+ for i3_n in T.Parallel(blk_n2):
328
+ logits_sum[i3_n] *= k_s_frag[i3_n]
329
 
330
+ T.copy(logits_sum, o[i_b, i_m, i1_n * blk_n1 + i2_n * blk_n2])
331
+
332
+ return fp8_index_kernel_
333
+
334
+
335
+ # ============================================================================
336
+ # Public API - dispatches to CUDA or CPU implementations
337
+ # ============================================================================
338
 
339
  def act_quant(
340
  x: torch.Tensor, block_size: int = 128, scale_fmt: Optional[str] = None
 
351
  - The quantized tensor with dtype `torch.float8_e4m3fn`.
352
  - A tensor of scaling factors with dtype `torch.float32`.
353
  """
354
+ # Use CPU fallback if not on CUDA or tilelang not available
355
+ if not x.is_cuda or not USE_TILELANG:
356
+ return act_quant_cpu(x, block_size, scale_fmt)
357
+
358
  assert x.is_contiguous(), "Input tensor must be contiguous"
359
  assert x.size(-1) % block_size == 0, (
360
  f"Last dimension size must be divisible by block_size (block_size={block_size})"
 
367
  return y, s
368
 
369
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
370
  def fp8_gemm(
371
  a: torch.Tensor, a_s: torch.Tensor, b: torch.Tensor, b_s: torch.Tensor
372
  ) -> torch.Tensor:
 
382
  Returns:
383
  torch.Tensor: The result of the matrix multiplication.
384
  """
385
+ # Use CPU fallback if not on CUDA or tilelang not available
386
+ if not a.is_cuda or not USE_TILELANG:
387
+ return fp8_gemm_cpu(a, a_s, b, b_s)
388
+
389
  assert a.is_contiguous() and b.is_contiguous(), "Input tensors must be contiguous"
390
  assert a_s.is_contiguous() and b_s.is_contiguous(), (
391
  "Scaling factor tensors must be contiguous"
 
399
  return c
400
 
401
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
402
  def fp8_index(
403
  q: torch.Tensor,
404
  q_s: torch.Tensor,
 
419
  fp32 logits -> fp32 logits_sum
420
  fp32 logits_sum * k_s (e8m0) -> fp32 index_score
421
  """
422
+ # Use CPU fallback if not on CUDA or tilelang not available
423
+ if not q.is_cuda or not USE_TILELANG:
424
+ return fp8_index_cpu(q, q_s, k, k_s)
425
+
426
  return fp8_index_kernel(q.shape[2], q.shape[3])(q, q_s, k, k_s)