Instructions to use replicate/finegrained-fp8 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Kernels
How to use replicate/finegrained-fp8 with Kernels:
# !pip install kernels from kernels import get_kernel # a version (or an explicit revision) is required; see the "Files and versions" tab for the available ones kernel = get_kernel("replicate/finegrained-fp8", version=1) - Notebooks
- Google Colab
- Kaggle
Build uploaded using `kernels`.
Browse files- build/torch-rocm/_ops.py +2 -2
- build/torch-rocm/act_quant.py +1 -2
- build/torch-rocm/batched.py +80 -53
- build/torch-rocm/grouped.py +69 -54
- build/torch-rocm/{metadata-rocm.json → metadata.json} +1 -0
- build/torch-xpu/_ops.py +2 -2
- build/torch-xpu/act_quant.py +1 -2
- build/torch-xpu/batched.py +80 -53
- build/torch-xpu/grouped.py +69 -54
- build/torch-xpu/{metadata-xpu.json → metadata.json} +1 -0
build/torch-rocm/_ops.py
CHANGED
|
@@ -1,8 +1,8 @@
|
|
| 1 |
import torch
|
| 2 |
-
ops = torch.ops.
|
| 3 |
|
| 4 |
def add_op_namespace_prefix(op_name: str):
|
| 5 |
"""
|
| 6 |
Prefix op by namespace.
|
| 7 |
"""
|
| 8 |
-
return f"
|
|
|
|
| 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 //
|
| 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 |
-
|
| 52 |
-
|
| 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
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 =
|
| 63 |
-
offs_k = tl.arange(0,
|
| 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 //
|
| 70 |
Bs_ptrs = Bs + offs_bsn * stride_Bs_n
|
| 71 |
|
| 72 |
-
accumulator = tl.zeros((BLOCK_SIZE_M,
|
| 73 |
-
for k in range(0, tl.cdiv(K,
|
| 74 |
-
# ---- fused
|
| 75 |
-
a_raw = tl.load(a_ptrs
|
| 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 |
-
|
| 81 |
-
|
| 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 +=
|
| 87 |
-
b_ptrs +=
|
| 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 *
|
| 99 |
c_ptrs = C + offs_cm[:, None] * 0 + stride_cn * offs_cn[None, :]
|
| 100 |
-
|
| 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 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
|
| 140 |
-
#
|
| 141 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 142 |
|
| 143 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 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),
|
| 161 |
-
B.stride(0),
|
| 162 |
-
C.stride(0),
|
| 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(
|
|
|
|
|
|
|
|
|
| 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 //
|
| 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 |
-
|
|
|
|
| 49 |
BLOCK_SIZE_M: tl.constexpr,
|
| 50 |
-
|
| 51 |
-
BLOCK_SIZE_K: tl.constexpr,
|
| 52 |
):
|
| 53 |
-
|
| 54 |
-
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 *
|
| 95 |
-
|
| 96 |
-
offs_k = tl.arange(0, BLOCK_SIZE_K)
|
| 97 |
|
| 98 |
-
|
| 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 |
-
+
|
| 106 |
)
|
| 107 |
-
|
| 108 |
-
Bs_ptrs = Bs + expert_id * stride_Esb +
|
| 109 |
-
|
| 110 |
-
accumulator = tl.zeros((BLOCK_SIZE_M,
|
| 111 |
-
for k in range(0, tl.cdiv(K,
|
| 112 |
-
# ---- fused
|
| 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
|
| 124 |
-
|
| 125 |
-
|
| 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 +=
|
| 131 |
-
b_ptrs +=
|
| 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]
|
| 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
|
| 169 |
-
assert
|
| 170 |
-
assert
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 179 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 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 |
-
|
|
|
|
|
|
|
| 216 |
BLOCK_SIZE_M=BLOCK_SIZE_M,
|
| 217 |
-
|
| 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.
|
| 3 |
|
| 4 |
def add_op_namespace_prefix(op_name: str):
|
| 5 |
"""
|
| 6 |
Prefix op by namespace.
|
| 7 |
"""
|
| 8 |
-
return f"
|
|
|
|
| 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 //
|
| 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 |
-
|
| 52 |
-
|
| 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
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 =
|
| 63 |
-
offs_k = tl.arange(0,
|
| 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 //
|
| 70 |
Bs_ptrs = Bs + offs_bsn * stride_Bs_n
|
| 71 |
|
| 72 |
-
accumulator = tl.zeros((BLOCK_SIZE_M,
|
| 73 |
-
for k in range(0, tl.cdiv(K,
|
| 74 |
-
# ---- fused
|
| 75 |
-
a_raw = tl.load(a_ptrs
|
| 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 |
-
|
| 81 |
-
|
| 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 +=
|
| 87 |
-
b_ptrs +=
|
| 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 *
|
| 99 |
c_ptrs = C + offs_cm[:, None] * 0 + stride_cn * offs_cn[None, :]
|
| 100 |
-
|
| 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 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
|
| 140 |
-
#
|
| 141 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 142 |
|
| 143 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 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),
|
| 161 |
-
B.stride(0),
|
| 162 |
-
C.stride(0),
|
| 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(
|
|
|
|
|
|
|
|
|
| 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 //
|
| 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 |
-
|
|
|
|
| 49 |
BLOCK_SIZE_M: tl.constexpr,
|
| 50 |
-
|
| 51 |
-
BLOCK_SIZE_K: tl.constexpr,
|
| 52 |
):
|
| 53 |
-
|
| 54 |
-
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 *
|
| 95 |
-
|
| 96 |
-
offs_k = tl.arange(0, BLOCK_SIZE_K)
|
| 97 |
|
| 98 |
-
|
| 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 |
-
+
|
| 106 |
)
|
| 107 |
-
|
| 108 |
-
Bs_ptrs = Bs + expert_id * stride_Esb +
|
| 109 |
-
|
| 110 |
-
accumulator = tl.zeros((BLOCK_SIZE_M,
|
| 111 |
-
for k in range(0, tl.cdiv(K,
|
| 112 |
-
# ---- fused
|
| 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
|
| 124 |
-
|
| 125 |
-
|
| 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 +=
|
| 131 |
-
b_ptrs +=
|
| 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]
|
| 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
|
| 169 |
-
assert
|
| 170 |
-
assert
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 179 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 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 |
-
|
|
|
|
|
|
|
| 216 |
BLOCK_SIZE_M=BLOCK_SIZE_M,
|
| 217 |
-
|
| 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 |
}
|