Kernels:
Trusted publisher
Uploaded using `kernel-builder`.
Browse files- build/torch-cuda/__init__.py +14 -26
- build/torch-cuda/_ops.py +1 -1
- build/torch-cuda/act_quant.py +0 -73
- build/torch-cuda/batched.py +374 -146
- build/torch-cuda/bayesian_autotuner.py +358 -0
- build/torch-cuda/fused_batched.py +853 -0
- build/torch-cuda/fused_grouped.py +1110 -0
- build/torch-cuda/grouped.py +480 -198
- build/torch-cuda/matmul.py +574 -168
- build/torch-cuda/metadata.json +17 -2
- build/torch-cuda/metadata.json.sigstore +1 -0
- build/torch-cuda/utils.py +755 -1
- build/torch-rocm/__init__.py +0 -32
- build/torch-rocm/_ops.py +0 -38
- build/torch-rocm/act_quant.py +0 -73
- build/torch-rocm/batched.py +0 -390
- build/torch-rocm/finegrained_fp8/__init__.py +0 -26
- build/torch-rocm/grouped.py +0 -477
- build/torch-rocm/matmul.py +0 -411
- build/torch-rocm/metadata.json +0 -10
- build/torch-rocm/utils.py +0 -13
- build/torch-xpu/__init__.py +0 -32
- build/torch-xpu/_ops.py +0 -38
- build/torch-xpu/act_quant.py +0 -73
- build/torch-xpu/batched.py +0 -398
- build/torch-xpu/finegrained_fp8/__init__.py +0 -26
- build/torch-xpu/grouped.py +0 -477
- build/torch-xpu/matmul.py +0 -411
- build/torch-xpu/metadata.json +0 -10
- build/torch-xpu/utils.py +0 -13
build/torch-cuda/__init__.py
CHANGED
|
@@ -1,32 +1,20 @@
|
|
| 1 |
-
from .
|
| 2 |
-
from .
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
from .grouped import (
|
| 8 |
-
w8a8_fp8_matmul_grouped,
|
| 9 |
-
w8a8_block_fp8_matmul_grouped,
|
| 10 |
-
w8a8_tensor_fp8_matmul_grouped,
|
| 11 |
-
)
|
| 12 |
-
from .matmul import (
|
| 13 |
-
w8a8_fp8_matmul,
|
| 14 |
-
w8a8_block_fp8_matmul,
|
| 15 |
-
w8a8_tensor_fp8_matmul,
|
| 16 |
-
)
|
| 17 |
|
| 18 |
__all__ = [
|
| 19 |
"fp8_act_quant",
|
| 20 |
-
#
|
| 21 |
-
"
|
| 22 |
-
"w8a8_block_fp8_matmul",
|
| 23 |
-
"w8a8_tensor_fp8_matmul",
|
| 24 |
# Batched matmul
|
| 25 |
-
"
|
| 26 |
-
"w8a8_block_fp8_matmul_batched",
|
| 27 |
-
"w8a8_tensor_fp8_matmul_batched",
|
| 28 |
# Grouped matmul
|
| 29 |
-
"
|
| 30 |
-
|
| 31 |
-
"
|
|
|
|
|
|
|
| 32 |
]
|
|
|
|
| 1 |
+
from .utils import fp8_act_quant
|
| 2 |
+
from .matmul import matmul_2d
|
| 3 |
+
from .batched import matmul_batched
|
| 4 |
+
from .grouped import matmul_grouped
|
| 5 |
+
from .fused_batched import moe_fused_batched
|
| 6 |
+
from .fused_grouped import moe_fused_grouped
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
|
| 8 |
__all__ = [
|
| 9 |
"fp8_act_quant",
|
| 10 |
+
# 2D matmul
|
| 11 |
+
"matmul_2d",
|
|
|
|
|
|
|
| 12 |
# Batched matmul
|
| 13 |
+
"matmul_batched",
|
|
|
|
|
|
|
| 14 |
# Grouped matmul
|
| 15 |
+
"matmul_grouped",
|
| 16 |
+
# Fused batched matmul
|
| 17 |
+
"moe_fused_batched",
|
| 18 |
+
# Fused grouped matmul
|
| 19 |
+
"moe_fused_grouped",
|
| 20 |
]
|
build/torch-cuda/_ops.py
CHANGED
|
@@ -22,7 +22,7 @@ def get_backend() -> str:
|
|
| 22 |
|
| 23 |
def _find_ops_name() -> str:
|
| 24 |
kernel_name = "finegrained_fp8"
|
| 25 |
-
unique_id = "
|
| 26 |
backend = get_backend()
|
| 27 |
return f"_{kernel_name}_{backend}_{unique_id}"
|
| 28 |
|
|
|
|
| 22 |
|
| 23 |
def _find_ops_name() -> str:
|
| 24 |
kernel_name = "finegrained_fp8"
|
| 25 |
+
unique_id = "93d7641"
|
| 26 |
backend = get_backend()
|
| 27 |
return f"_{kernel_name}_{backend}_{unique_id}"
|
| 28 |
|
build/torch-cuda/act_quant.py
DELETED
|
@@ -1,73 +0,0 @@
|
|
| 1 |
-
# Copyright 2026 The HuggingFace Inc. team. All rights reserved.
|
| 2 |
-
#
|
| 3 |
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
-
# you may not use this file except in compliance with the License.
|
| 5 |
-
# You may obtain a copy of the License at
|
| 6 |
-
#
|
| 7 |
-
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
-
#
|
| 9 |
-
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
-
# See the License for the specific language governing permissions and
|
| 13 |
-
# limitations under the License.
|
| 14 |
-
|
| 15 |
-
import torch
|
| 16 |
-
import triton
|
| 17 |
-
import triton.language as tl
|
| 18 |
-
from torch.library import triton_op, wrap_triton
|
| 19 |
-
|
| 20 |
-
from .utils import device_context
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
_FP8_DTYPE = torch.float8_e4m3fn
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
# Copied from https://huggingface.co/deepseek-ai/DeepSeek-V3/blob/main/inference/kernel.py
|
| 27 |
-
@triton.jit
|
| 28 |
-
def _fp8_act_quant_kernel(x_ptr, y_ptr, s_ptr, BLOCK_SIZE: tl.constexpr):
|
| 29 |
-
pid = tl.program_id(axis=0)
|
| 30 |
-
offs = pid * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE)
|
| 31 |
-
x = tl.load(x_ptr + offs).to(tl.float32)
|
| 32 |
-
s = tl.max(tl.abs(x)) / 448.0 # float8_e4m3fn max
|
| 33 |
-
y = (x / s).to(y_ptr.dtype.element_ty)
|
| 34 |
-
tl.store(y_ptr + offs, y)
|
| 35 |
-
tl.store(s_ptr + pid, s)
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
@triton_op("finegrained_fp8::fp8_act_quant", mutates_args=())
|
| 39 |
-
def _fp8_act_quant(
|
| 40 |
-
x: torch.Tensor, block_size: int = 128
|
| 41 |
-
) -> tuple[torch.Tensor, torch.Tensor]:
|
| 42 |
-
assert x.is_contiguous()
|
| 43 |
-
assert x.shape[-1] % block_size == 0
|
| 44 |
-
y = torch.empty_like(x, dtype=_FP8_DTYPE)
|
| 45 |
-
grid = (triton.cdiv(x.numel(), block_size),)
|
| 46 |
-
s = x.new_empty(*x.size()[:-1], x.size(-1) // block_size, dtype=torch.float32)
|
| 47 |
-
|
| 48 |
-
with device_context(x.device):
|
| 49 |
-
wrap_triton(_fp8_act_quant_kernel)[grid](x, y, s, BLOCK_SIZE=block_size)
|
| 50 |
-
|
| 51 |
-
return y, s
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
def fp8_act_quant(
|
| 55 |
-
x: torch.Tensor, block_size: int = 128
|
| 56 |
-
) -> tuple[torch.Tensor, torch.Tensor]:
|
| 57 |
-
"""Quantize activations to FP8 with per-block dynamic scaling.
|
| 58 |
-
|
| 59 |
-
Splits the last dimension of ``x`` into blocks of ``block_size`` elements,
|
| 60 |
-
computes ``scale = max(|x_block|) / 448`` per block, and quantizes to
|
| 61 |
-
``float8_e4m3fn``.
|
| 62 |
-
|
| 63 |
-
Args:
|
| 64 |
-
x: Input tensor in bf16/fp16/fp32. Last dimension must be divisible by
|
| 65 |
-
``block_size`` and the tensor must be contiguous.
|
| 66 |
-
block_size: Number of elements per quantization block (default: 128).
|
| 67 |
-
|
| 68 |
-
Returns:
|
| 69 |
-
A tuple ``(quantized, scales)`` where ``quantized`` has dtype
|
| 70 |
-
``float8_e4m3fn`` with the same shape as ``x``, and ``scales`` has
|
| 71 |
-
shape ``(*x.shape[:-1], x.shape[-1] // block_size)`` in float32.
|
| 72 |
-
"""
|
| 73 |
-
return torch.ops.finegrained_fp8.fp8_act_quant(x, block_size)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
build/torch-cuda/batched.py
CHANGED
|
@@ -15,31 +15,101 @@
|
|
| 15 |
import torch
|
| 16 |
import triton
|
| 17 |
import triton.language as tl
|
| 18 |
-
from .act_quant import fp8_act_quant
|
| 19 |
from torch.library import triton_op, wrap_triton
|
| 20 |
|
| 21 |
-
from .
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
|
| 23 |
|
| 24 |
@triton.autotune(
|
| 25 |
-
configs=
|
| 26 |
-
|
| 27 |
-
for w in [2, 4, 8, 16]
|
| 28 |
-
for s in [2, 3, 4, 5]
|
| 29 |
-
],
|
| 30 |
-
key=["N", "K"],
|
| 31 |
)
|
| 32 |
@triton.jit
|
| 33 |
-
def
|
| 34 |
-
A, # (S, K)
|
| 35 |
-
B, # (
|
| 36 |
-
C, # (S, N)
|
| 37 |
-
Bs, # (
|
| 38 |
ExpertIds, # (S,) — which expert each batch element routes to
|
| 39 |
# Shape
|
| 40 |
S,
|
| 41 |
N,
|
| 42 |
K,
|
|
|
|
| 43 |
stride_am,
|
| 44 |
stride_ak,
|
| 45 |
stride_be,
|
|
@@ -50,28 +120,24 @@ def w8a8_block_fp8_matmul_batched_kernel(
|
|
| 50 |
stride_bs_e,
|
| 51 |
stride_bs_k,
|
| 52 |
stride_bs_n,
|
|
|
|
|
|
|
| 53 |
# Meta-parameters
|
|
|
|
| 54 |
BLOCK_SIZE_N: tl.constexpr,
|
| 55 |
BLOCK_SIZE_K: tl.constexpr,
|
| 56 |
-
BLOCK_SIZE_M: tl.constexpr,
|
| 57 |
):
|
| 58 |
"""Block-scale batched FP8 expert matmul kernel.
|
| 59 |
|
| 60 |
Each program handles one routed token row and one N-tile, looks up the
|
| 61 |
owning expert from ``ExpertIds``, and applies fused activation quantization.
|
| 62 |
"""
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
#
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
expert_id = tl.load(ExpertIds + batch_id).to(tl.int64)
|
| 70 |
-
|
| 71 |
-
A = A + batch_id * stride_am
|
| 72 |
-
B = B + expert_id * stride_be
|
| 73 |
-
C = C + batch_id * stride_cm
|
| 74 |
-
Bs = Bs + expert_id * stride_bs_e
|
| 75 |
|
| 76 |
offs_bn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
|
| 77 |
offs_k = tl.arange(0, BLOCK_SIZE_K)
|
|
@@ -82,55 +148,35 @@ def w8a8_block_fp8_matmul_batched_kernel(
|
|
| 82 |
|
| 83 |
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
| 84 |
for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
|
| 85 |
-
# ---- fused fp8_act_quant ----
|
| 86 |
a_raw = tl.load(a_ptrs).to(tl.float32)
|
| 87 |
-
a_s =
|
| 88 |
-
a = (a_raw / tl.maximum(a_s, 1e-12)).to(tl.float8e4nv)
|
| 89 |
-
# ---- matmul ----
|
| 90 |
b = tl.load(b_ptrs)
|
| 91 |
-
b_s = tl.load(bs_ptrs
|
| 92 |
-
accumulator += tl.dot(a, b) * a_s * b_s[None, :]
|
| 93 |
a_ptrs += BLOCK_SIZE_K * stride_ak
|
| 94 |
b_ptrs += BLOCK_SIZE_K * stride_bk
|
|
|
|
| 95 |
|
| 96 |
-
|
| 97 |
-
c = accumulator.to(tl.bfloat16)
|
| 98 |
-
elif C.dtype.element_ty == tl.float16:
|
| 99 |
-
c = accumulator.to(tl.float16)
|
| 100 |
-
else:
|
| 101 |
-
c = accumulator.to(tl.float32)
|
| 102 |
-
|
| 103 |
-
offs_cm = tl.arange(0, BLOCK_SIZE_M)
|
| 104 |
-
offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
|
| 105 |
-
c_ptrs = C + offs_cm[:, None] * 0 + stride_cn * offs_cn[None, :]
|
| 106 |
-
# Fake-batch trick aliases all BLOCK_SIZE_M lanes to the same C row, so emitting
|
| 107 |
-
# `tl.store(c_ptrs, c)` issues BLOCK_SIZE_M duplicate-address stores. On NVIDIA
|
| 108 |
-
# WGMMA this is usually benign (last-write-wins of identical bytes), but on Intel
|
| 109 |
-
# XPU the duplicate-address store has hardware-undefined behavior and corrupts the
|
| 110 |
-
# output. Mask so only lane 0 stores — the (M, N) accumulator rows are
|
| 111 |
-
# mathematically identical (same A row × same B), so lane 0 holds the right value.
|
| 112 |
-
tl.store(c_ptrs, c, mask=(offs_cm == 0)[:, None])
|
| 113 |
|
| 114 |
|
| 115 |
@triton.autotune(
|
| 116 |
-
configs=
|
| 117 |
-
triton.Config({}, num_warps=w, num_stages=s)
|
| 118 |
-
for w in [2, 4, 8, 16]
|
| 119 |
-
for s in [2, 3, 4, 5]
|
| 120 |
-
],
|
| 121 |
key=["N", "K"],
|
| 122 |
)
|
| 123 |
@triton.jit
|
| 124 |
-
def
|
| 125 |
A, # (S, K) pre-quantized FP8 activations
|
| 126 |
-
B, # (
|
| 127 |
C, # (S, N) output
|
| 128 |
-
As, # (S,
|
| 129 |
-
Bs, # (
|
| 130 |
-
ExpertIds,
|
|
|
|
| 131 |
S,
|
| 132 |
N,
|
| 133 |
K,
|
|
|
|
| 134 |
stride_am,
|
| 135 |
stride_ak,
|
| 136 |
stride_be,
|
|
@@ -140,24 +186,24 @@ def w8a8_tensor_fp8_matmul_batched_kernel(
|
|
| 140 |
stride_cn,
|
| 141 |
stride_as_m,
|
| 142 |
stride_bs_e,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 143 |
BLOCK_SIZE_N: tl.constexpr,
|
| 144 |
BLOCK_SIZE_K: tl.constexpr,
|
| 145 |
-
BLOCK_SIZE_M: tl.constexpr,
|
| 146 |
):
|
| 147 |
"""Tensor-scale batched FP8 expert matmul kernel.
|
| 148 |
|
| 149 |
Activations are already quantized; the kernel applies per-token activation
|
| 150 |
scales and per-expert tensor weight scales.
|
| 151 |
"""
|
| 152 |
-
batch_id =
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
B = B + expert_id * stride_be
|
| 159 |
-
C = C + batch_id * stride_cm
|
| 160 |
-
Bs = Bs + expert_id * stride_bs_e
|
| 161 |
|
| 162 |
offs_bn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
|
| 163 |
offs_k = tl.arange(0, BLOCK_SIZE_K)
|
|
@@ -177,45 +223,126 @@ def w8a8_tensor_fp8_matmul_batched_kernel(
|
|
| 177 |
|
| 178 |
accumulator = accumulator * a_s * b_s
|
| 179 |
|
| 180 |
-
|
| 181 |
-
c = accumulator.to(tl.bfloat16)
|
| 182 |
-
elif C.dtype.element_ty == tl.float16:
|
| 183 |
-
c = accumulator.to(tl.float16)
|
| 184 |
-
else:
|
| 185 |
-
c = accumulator.to(tl.float32)
|
| 186 |
|
| 187 |
-
offs_cm = tl.arange(0, BLOCK_SIZE_M)
|
| 188 |
-
offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
|
| 189 |
-
c_ptrs = C + offs_cm[:, None] * 0 + stride_cn * offs_cn[None, :]
|
| 190 |
-
# See block-FP8 kernel above: BLOCK_SIZE_M lanes alias the same C row;
|
| 191 |
-
# mask so only lane 0 stores to avoid hardware-undefined duplicate writes on XPU.
|
| 192 |
-
tl.store(c_ptrs, c, mask=(offs_cm == 0)[:, None])
|
| 193 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 194 |
|
| 195 |
-
|
| 196 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 197 |
A: torch.Tensor,
|
| 198 |
B: torch.Tensor,
|
| 199 |
Bs: torch.Tensor,
|
| 200 |
expert_ids: torch.Tensor,
|
| 201 |
block_size: list[int],
|
|
|
|
| 202 |
) -> torch.Tensor:
|
| 203 |
"""Block-scale batched FP8 matmul: C[s] = A[s] @ B[expert_ids[s]].T, with fused act quant.
|
| 204 |
|
| 205 |
A: (S, K) raw bf16/fp16 activations
|
| 206 |
-
B: (
|
| 207 |
-
Bs: (
|
| 208 |
"""
|
| 209 |
assert A.ndim == 2, f"A must be 2D (S, K), got ndim={A.ndim}"
|
| 210 |
assert A.is_contiguous(), "A must be contiguous"
|
| 211 |
-
assert B.ndim == 3, f"B must be 3D (
|
| 212 |
assert B.is_contiguous(), "B must be contiguous"
|
| 213 |
assert A.shape[1] == B.shape[2], (
|
| 214 |
f"K mismatch: A has K={A.shape[1]}, B has K={B.shape[2]}"
|
| 215 |
)
|
| 216 |
|
| 217 |
S, K = A.shape
|
| 218 |
-
|
| 219 |
|
| 220 |
assert len(block_size) == 2, (
|
| 221 |
f"block_size must be [block_n, block_k], got {block_size}"
|
|
@@ -225,21 +352,18 @@ def _w8a8_block_fp8_matmul_batched(
|
|
| 225 |
assert N % block_n == 0, f"N ({N}) must be divisible by block_n ({block_n})"
|
| 226 |
assert K % block_k == 0, f"K ({K}) must be divisible by block_k ({block_k})"
|
| 227 |
assert Bs.ndim == 3, (
|
| 228 |
-
f"Bs must be 3D (
|
| 229 |
)
|
| 230 |
-
assert Bs.shape == (
|
| 231 |
-
f"Bs shape {tuple(Bs.shape)} != expected ({
|
| 232 |
)
|
| 233 |
|
| 234 |
-
|
| 235 |
-
# Adaptive BLOCK_SIZE_M: smallest power-of-2 >= M, floored at 16, capped at 128.
|
| 236 |
-
# Matches the WGMMA tile to the actual row count — smaller tiles use less
|
| 237 |
-
# register pressure and a better-matched FP8 WGMMA instruction, improving
|
| 238 |
-
# both accuracy and performance for small M (decode).
|
| 239 |
-
BLOCK_SIZE_M = min(max(triton.next_power_of_2((S + E - 1) // E), 16), 128)
|
| 240 |
grid = (S, triton.cdiv(N, block_n))
|
|
|
|
|
|
|
| 241 |
with device_context(A.device):
|
| 242 |
-
wrap_triton(
|
| 243 |
A,
|
| 244 |
B,
|
| 245 |
C,
|
|
@@ -258,60 +382,63 @@ def _w8a8_block_fp8_matmul_batched(
|
|
| 258 |
Bs.stride(0),
|
| 259 |
Bs.stride(2),
|
| 260 |
Bs.stride(1),
|
|
|
|
| 261 |
BLOCK_SIZE_N=block_n,
|
| 262 |
BLOCK_SIZE_K=block_k,
|
| 263 |
-
BLOCK_SIZE_M=
|
|
|
|
| 264 |
)
|
| 265 |
|
| 266 |
return C
|
| 267 |
|
| 268 |
|
| 269 |
-
@triton_op(
|
| 270 |
-
|
|
|
|
|
|
|
| 271 |
A: torch.Tensor,
|
| 272 |
B: torch.Tensor,
|
| 273 |
Bs: torch.Tensor,
|
| 274 |
expert_ids: torch.Tensor,
|
|
|
|
| 275 |
) -> torch.Tensor:
|
| 276 |
"""Tensor-scale batched FP8 matmul: C[s] = A[s] @ B[expert_ids[s]].T, with fused act quant.
|
| 277 |
|
| 278 |
A: (S, K) raw bf16/fp16 activations
|
| 279 |
-
B: (
|
| 280 |
-
Bs: (
|
| 281 |
"""
|
| 282 |
assert A.ndim == 2, f"A must be 2D (S, K), got ndim={A.ndim}"
|
| 283 |
assert A.is_contiguous(), "A must be contiguous"
|
| 284 |
-
assert B.ndim == 3, f"B must be 3D (
|
| 285 |
assert B.is_contiguous(), "B must be contiguous"
|
| 286 |
assert A.shape[1] == B.shape[2], (
|
| 287 |
f"K mismatch: A has K={A.shape[1]}, B has K={B.shape[2]}"
|
| 288 |
)
|
| 289 |
|
| 290 |
S, K = A.shape
|
| 291 |
-
|
| 292 |
|
| 293 |
-
# Normalize Bs to (
|
| 294 |
if Bs.ndim == 1:
|
| 295 |
-
assert Bs.shape[0] ==
|
| 296 |
-
|
|
|
|
|
|
|
| 297 |
else:
|
| 298 |
-
assert Bs.shape == (
|
| 299 |
-
f"Bs shape {tuple(Bs.shape)} != expected ({
|
| 300 |
)
|
| 301 |
|
| 302 |
-
|
| 303 |
-
BLOCK_SIZE_K = 128
|
| 304 |
-
C = A.new_empty(S, N)
|
| 305 |
qA, As = fp8_act_quant(A, K)
|
| 306 |
-
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
|
| 310 |
-
|
| 311 |
-
BLOCK_SIZE_M = min(max(triton.next_power_of_2((S + E - 1) // E), 16), 128)
|
| 312 |
-
grid = (S, triton.cdiv(N, BLOCK_SIZE_N))
|
| 313 |
with device_context(A.device):
|
| 314 |
-
wrap_triton(
|
| 315 |
qA,
|
| 316 |
B,
|
| 317 |
C,
|
|
@@ -330,69 +457,170 @@ def _w8a8_tensor_fp8_matmul_batched(
|
|
| 330 |
C.stride(1),
|
| 331 |
As.stride(0),
|
| 332 |
Bs.stride(0),
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
|
| 336 |
)
|
| 337 |
|
| 338 |
return C
|
| 339 |
|
| 340 |
|
| 341 |
-
def
|
| 342 |
A: torch.Tensor,
|
| 343 |
B: torch.Tensor,
|
| 344 |
Bs: torch.Tensor,
|
| 345 |
expert_ids: torch.Tensor,
|
| 346 |
block_size: list[int],
|
|
|
|
| 347 |
) -> torch.Tensor:
|
| 348 |
"""Block-scale batched FP8 matmul with fused activation quantization.
|
| 349 |
|
| 350 |
A: (S, K) raw activations, bf16/fp16/fp32
|
| 351 |
-
B: (
|
| 352 |
-
Bs: (
|
|
|
|
|
|
|
| 353 |
"""
|
| 354 |
-
return
|
| 355 |
-
A, B, Bs, expert_ids, block_size
|
| 356 |
)
|
| 357 |
|
| 358 |
|
| 359 |
-
def
|
| 360 |
A: torch.Tensor,
|
| 361 |
B: torch.Tensor,
|
| 362 |
Bs: torch.Tensor,
|
| 363 |
expert_ids: torch.Tensor,
|
|
|
|
| 364 |
) -> torch.Tensor:
|
| 365 |
"""Tensor-scale batched FP8 matmul with fused activation quantization.
|
| 366 |
|
| 367 |
A: (S, K) raw activations, bf16/fp16/fp32
|
| 368 |
-
B: (
|
| 369 |
-
Bs: (
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 370 |
"""
|
| 371 |
-
|
| 372 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 373 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 374 |
|
|
|
|
|
|
|
| 375 |
|
| 376 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 377 |
A: torch.Tensor,
|
| 378 |
B: torch.Tensor,
|
| 379 |
Bs: torch.Tensor,
|
| 380 |
expert_ids: torch.Tensor,
|
| 381 |
block_size: list[int] | None,
|
|
|
|
| 382 |
) -> torch.Tensor:
|
| 383 |
-
"""
|
|
|
|
| 384 |
|
| 385 |
-
|
| 386 |
-
- tensor mode when ``block_size is None``
|
| 387 |
-
- tensor mode when ``block_size == [N, K]``
|
| 388 |
-
- otherwise block mode
|
| 389 |
|
| 390 |
-
|
| 391 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 392 |
"""
|
| 393 |
-
if
|
| 394 |
-
|
| 395 |
-
|
| 396 |
-
|
|
|
|
|
|
|
|
|
|
| 397 |
|
| 398 |
-
return
|
|
|
|
|
|
|
|
|
| 15 |
import torch
|
| 16 |
import triton
|
| 17 |
import triton.language as tl
|
|
|
|
| 18 |
from torch.library import triton_op, wrap_triton
|
| 19 |
|
| 20 |
+
from ._ops import add_op_namespace_prefix, ops
|
| 21 |
+
from .bayesian_autotuner import bayesian_autotune
|
| 22 |
+
from .utils import (
|
| 23 |
+
MX_SCALE_GROUP_K,
|
| 24 |
+
NIBBLES_PER_BYTE,
|
| 25 |
+
DECODE_BLOCK_SIZE_M,
|
| 26 |
+
decode_ue8m0_scale,
|
| 27 |
+
device_context,
|
| 28 |
+
mx_dot_rescale,
|
| 29 |
+
mx_dot_scaled,
|
| 30 |
+
mx_scalar_reduce,
|
| 31 |
+
mxfp_act_quant_inline,
|
| 32 |
+
mxfp4_e2m1_to_e4m3,
|
| 33 |
+
fp8_act_quant,
|
| 34 |
+
fp8_act_quant_inline,
|
| 35 |
+
get_accelerator_autotuning_configs,
|
| 36 |
+
get_mxfp_autotuning_configs,
|
| 37 |
+
is_mxfp,
|
| 38 |
+
is_tensor_wide,
|
| 39 |
+
e2m1_as_uint8,
|
| 40 |
+
ue8m0_as_uint8,
|
| 41 |
+
)
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
@triton.jit
|
| 45 |
+
def expert_setup(
|
| 46 |
+
A,
|
| 47 |
+
B,
|
| 48 |
+
C,
|
| 49 |
+
Bs,
|
| 50 |
+
ExpertIds,
|
| 51 |
+
stride_am,
|
| 52 |
+
stride_be,
|
| 53 |
+
stride_cm,
|
| 54 |
+
stride_bs_e,
|
| 55 |
+
stride_eid,
|
| 56 |
+
):
|
| 57 |
+
"""Per-(row, expert) prologue shared by the batched kernels: read the program
|
| 58 |
+
ids, look up the routed expert, and advance the A/B/C/Bs base pointers to this
|
| 59 |
+
row's slice. Returns ``(batch_id, pid_n, expert_id, A, B, C, Bs)``.
|
| 60 |
+
|
| 61 |
+
The caller must early-return on the EP sentinel (``expert_id >= num_experts``)
|
| 62 |
+
before any load — the pointer arithmetic itself is harmless, only the loads on a
|
| 63 |
+
non-local expert would be out of bounds."""
|
| 64 |
+
batch_id = tl.program_id(axis=0)
|
| 65 |
+
pid_n = tl.program_id(axis=1)
|
| 66 |
+
# Cast to int64 to prevent overflow on expert_id * stride_be.
|
| 67 |
+
expert_id = tl.load(ExpertIds + batch_id * stride_eid).to(tl.int64)
|
| 68 |
+
A = A + batch_id * stride_am
|
| 69 |
+
B = B + expert_id * stride_be
|
| 70 |
+
C = C + batch_id * stride_cm
|
| 71 |
+
Bs = Bs + expert_id * stride_bs_e
|
| 72 |
+
return batch_id, pid_n, expert_id, A, B, C, Bs
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
@triton.jit
|
| 76 |
+
def store_row(
|
| 77 |
+
C,
|
| 78 |
+
accumulator,
|
| 79 |
+
pid_n,
|
| 80 |
+
stride_cn,
|
| 81 |
+
BLOCK_SIZE_M: tl.constexpr,
|
| 82 |
+
BLOCK_SIZE_N: tl.constexpr,
|
| 83 |
+
):
|
| 84 |
+
"""Output epilogue shared by the batched kernels (``C`` already advanced to the
|
| 85 |
+
row). The fake-batch trick aliases all ``BLOCK_SIZE_M`` lanes to the same C row,
|
| 86 |
+
so a plain store would issue ``BLOCK_SIZE_M`` duplicate-address writes — benign on
|
| 87 |
+
NVIDIA WGMMA (last-write-wins of identical bytes) but hardware-undefined on Intel
|
| 88 |
+
XPU, where it corrupts the output. Mask so only lane 0 stores; the accumulator
|
| 89 |
+
rows are mathematically identical (same A row × same B), so lane 0 is correct."""
|
| 90 |
+
c = accumulator.to(C.dtype.element_ty)
|
| 91 |
+
offs_cm = tl.arange(0, BLOCK_SIZE_M)
|
| 92 |
+
offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
|
| 93 |
+
c_ptrs = C + offs_cm[:, None] * 0 + stride_cn * offs_cn[None, :]
|
| 94 |
+
tl.store(c_ptrs, c, mask=(offs_cm == 0)[:, None])
|
| 95 |
|
| 96 |
|
| 97 |
@triton.autotune(
|
| 98 |
+
configs=get_accelerator_autotuning_configs(),
|
| 99 |
+
key=["N", "K", "S"],
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
)
|
| 101 |
@triton.jit
|
| 102 |
+
def w8a8_block_dynamic_fp8_matmul_batched_kernel(
|
| 103 |
+
A, # (S, K) raw BF16/FP16 activations
|
| 104 |
+
B, # (num_experts, N, K) FP8 weight matrices
|
| 105 |
+
C, # (S, N) output
|
| 106 |
+
Bs, # (num_experts, N // BLOCK_SIZE_N, K // BLOCK_SIZE_K) weight scales
|
| 107 |
ExpertIds, # (S,) — which expert each batch element routes to
|
| 108 |
# Shape
|
| 109 |
S,
|
| 110 |
N,
|
| 111 |
K,
|
| 112 |
+
# Strides
|
| 113 |
stride_am,
|
| 114 |
stride_ak,
|
| 115 |
stride_be,
|
|
|
|
| 120 |
stride_bs_e,
|
| 121 |
stride_bs_k,
|
| 122 |
stride_bs_n,
|
| 123 |
+
stride_eid,
|
| 124 |
+
num_experts,
|
| 125 |
# Meta-parameters
|
| 126 |
+
BLOCK_SIZE_M: tl.constexpr,
|
| 127 |
BLOCK_SIZE_N: tl.constexpr,
|
| 128 |
BLOCK_SIZE_K: tl.constexpr,
|
|
|
|
| 129 |
):
|
| 130 |
"""Block-scale batched FP8 expert matmul kernel.
|
| 131 |
|
| 132 |
Each program handles one routed token row and one N-tile, looks up the
|
| 133 |
owning expert from ``ExpertIds``, and applies fused activation quantization.
|
| 134 |
"""
|
| 135 |
+
_, pid_n, expert_id, A, B, C, Bs = expert_setup(
|
| 136 |
+
A, B, C, Bs, ExpertIds, stride_am, stride_be, stride_cm, stride_bs_e, stride_eid
|
| 137 |
+
)
|
| 138 |
+
# EP sentinel: row routed to a non-local expert; output is left uninit.
|
| 139 |
+
if expert_id >= num_experts:
|
| 140 |
+
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 141 |
|
| 142 |
offs_bn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
|
| 143 |
offs_k = tl.arange(0, BLOCK_SIZE_K)
|
|
|
|
| 148 |
|
| 149 |
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
| 150 |
for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
|
|
|
|
| 151 |
a_raw = tl.load(a_ptrs).to(tl.float32)
|
| 152 |
+
a, a_s = fp8_act_quant_inline(a_raw)
|
|
|
|
|
|
|
| 153 |
b = tl.load(b_ptrs)
|
| 154 |
+
b_s = decode_ue8m0_scale(tl.load(bs_ptrs))
|
| 155 |
+
accumulator += tl.dot(a, b) * a_s[:, None] * b_s[None, :]
|
| 156 |
a_ptrs += BLOCK_SIZE_K * stride_ak
|
| 157 |
b_ptrs += BLOCK_SIZE_K * stride_bk
|
| 158 |
+
bs_ptrs += stride_bs_k
|
| 159 |
|
| 160 |
+
store_row(C, accumulator, pid_n, stride_cn, BLOCK_SIZE_M, BLOCK_SIZE_N)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 161 |
|
| 162 |
|
| 163 |
@triton.autotune(
|
| 164 |
+
configs=get_accelerator_autotuning_configs(tune_block_nk=True),
|
|
|
|
|
|
|
|
|
|
|
|
|
| 165 |
key=["N", "K"],
|
| 166 |
)
|
| 167 |
@triton.jit
|
| 168 |
+
def w8a8_tensor_dynamic_fp8_matmul_batched_kernel(
|
| 169 |
A, # (S, K) pre-quantized FP8 activations
|
| 170 |
+
B, # (num_experts, N, K) FP8 weight matrices
|
| 171 |
C, # (S, N) output
|
| 172 |
+
As, # (S,) per-token activation scales
|
| 173 |
+
Bs, # (num_experts, 1, 1) per-tensor weight scales
|
| 174 |
+
ExpertIds, # (S,) — which expert each batch element routes to
|
| 175 |
+
# Shape
|
| 176 |
S,
|
| 177 |
N,
|
| 178 |
K,
|
| 179 |
+
# Strides
|
| 180 |
stride_am,
|
| 181 |
stride_ak,
|
| 182 |
stride_be,
|
|
|
|
| 186 |
stride_cn,
|
| 187 |
stride_as_m,
|
| 188 |
stride_bs_e,
|
| 189 |
+
stride_eid,
|
| 190 |
+
num_experts,
|
| 191 |
+
# Meta-parameters
|
| 192 |
+
BLOCK_SIZE_M: tl.constexpr,
|
| 193 |
BLOCK_SIZE_N: tl.constexpr,
|
| 194 |
BLOCK_SIZE_K: tl.constexpr,
|
|
|
|
| 195 |
):
|
| 196 |
"""Tensor-scale batched FP8 expert matmul kernel.
|
| 197 |
|
| 198 |
Activations are already quantized; the kernel applies per-token activation
|
| 199 |
scales and per-expert tensor weight scales.
|
| 200 |
"""
|
| 201 |
+
batch_id, pid_n, expert_id, A, B, C, Bs = expert_setup(
|
| 202 |
+
A, B, C, Bs, ExpertIds, stride_am, stride_be, stride_cm, stride_bs_e, stride_eid
|
| 203 |
+
)
|
| 204 |
+
# EP sentinel: row routed to a non-local expert; output is left uninit.
|
| 205 |
+
if expert_id >= num_experts:
|
| 206 |
+
return
|
|
|
|
|
|
|
|
|
|
| 207 |
|
| 208 |
offs_bn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
|
| 209 |
offs_k = tl.arange(0, BLOCK_SIZE_K)
|
|
|
|
| 223 |
|
| 224 |
accumulator = accumulator * a_s * b_s
|
| 225 |
|
| 226 |
+
store_row(C, accumulator, pid_n, stride_cn, BLOCK_SIZE_M, BLOCK_SIZE_N)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 227 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 228 |
|
| 229 |
+
@bayesian_autotune(get_mxfp_autotuning_configs(), ["N", "K", "S"], n_trials=60)
|
| 230 |
+
@triton.jit
|
| 231 |
+
def mxfp_dynamic_matmul_batched_kernel(
|
| 232 |
+
A, # (S, K) raw BF16/FP16 activations
|
| 233 |
+
B, # (num_experts, N, K) E4M3 (MXFP8) or (num_experts, N, K // 2) packed E2M1 (MXFP4) expert weights
|
| 234 |
+
C, # (S, N) output
|
| 235 |
+
Bs, # (num_experts, N, K // SCALE_GROUP_K) UE8M0 weight scales
|
| 236 |
+
ExpertIds, # (S,) — which expert each routed row uses
|
| 237 |
+
# Shape
|
| 238 |
+
S,
|
| 239 |
+
N,
|
| 240 |
+
K,
|
| 241 |
+
# Strides
|
| 242 |
+
stride_am,
|
| 243 |
+
stride_ak,
|
| 244 |
+
stride_be,
|
| 245 |
+
stride_bk,
|
| 246 |
+
stride_bn,
|
| 247 |
+
stride_cm,
|
| 248 |
+
stride_cn,
|
| 249 |
+
stride_bs_e,
|
| 250 |
+
stride_bs_k,
|
| 251 |
+
stride_bs_n,
|
| 252 |
+
stride_eid,
|
| 253 |
+
num_experts,
|
| 254 |
+
# Meta-parameters
|
| 255 |
+
BLOCK_SIZE_M: tl.constexpr,
|
| 256 |
+
BLOCK_SIZE_N: tl.constexpr,
|
| 257 |
+
BLOCK_SIZE_K: tl.constexpr,
|
| 258 |
+
VALUES_PER_BYTE: tl.constexpr,
|
| 259 |
+
SCALE_GROUP_K: tl.constexpr,
|
| 260 |
+
COMPUTE_MODE: tl.constexpr,
|
| 261 |
+
):
|
| 262 |
+
"""Unified batched MXFP4/MXFP8 (W4A8/W8A8) expert matmul with fused act quant.
|
| 263 |
+
|
| 264 |
+
One routed row + one N-tile per program; expert looked up from ``ExpertIds``. ``A`` is
|
| 265 |
+
quantized to E4M3 per K-group inline (UE8M0 scale). ``VALUES_PER_BYTE`` picks the
|
| 266 |
+
weight format (2 = packed E2M1 / MXFP4, 1 = unpacked E4M3 / MXFP8); ``COMPUTE_MODE``
|
| 267 |
+
picks ``tl.dot_scaled`` (native M=128) vs fp8 ``tl.dot`` + per-group rescale (wins at
|
| 268 |
+
decode where the scaled MMA's M→128 pad is waste; FP4 unpacks E2M1->E4M3, lossless).
|
| 269 |
+
"""
|
| 270 |
+
_, pid_n, expert_id, A, B, C, Bs = expert_setup(
|
| 271 |
+
A, B, C, Bs, ExpertIds, stride_am, stride_be, stride_cm, stride_bs_e, stride_eid
|
| 272 |
+
)
|
| 273 |
+
# EP sentinel: row routed to a non-local expert; output is left uninit.
|
| 274 |
+
if expert_id >= num_experts:
|
| 275 |
+
return
|
| 276 |
+
|
| 277 |
+
offs_bn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
|
| 278 |
+
offs_k = tl.arange(0, BLOCK_SIZE_K)
|
| 279 |
+
offs_kb = tl.arange(0, BLOCK_SIZE_K // VALUES_PER_BYTE)
|
| 280 |
+
offs_sf = tl.arange(0, BLOCK_SIZE_K // SCALE_GROUP_K)
|
| 281 |
+
a_ptrs = A + tl.arange(0, BLOCK_SIZE_M)[:, None] * 0 + offs_k[None, :] * stride_ak
|
| 282 |
+
b_ptrs = B + (offs_kb[:, None] * stride_bk + offs_bn[None, :] * stride_bn)
|
| 283 |
+
bs_ptrs = Bs + offs_bn[:, None] * stride_bs_n + offs_sf[None, :] * stride_bs_k
|
| 284 |
|
| 285 |
+
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
| 286 |
+
for _ in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
|
| 287 |
+
a_raw = tl.load(a_ptrs).to(tl.float32)
|
| 288 |
+
a, a_scale = mxfp_act_quant_inline(
|
| 289 |
+
a_raw, BLOCK_SIZE_M, BLOCK_SIZE_K, SCALE_GROUP_K
|
| 290 |
+
)
|
| 291 |
+
b = tl.load(b_ptrs)
|
| 292 |
+
b_s = tl.load(bs_ptrs).to(tl.uint8)
|
| 293 |
+
bq = mxfp4_e2m1_to_e4m3(b) if VALUES_PER_BYTE == 2 else b
|
| 294 |
+
if COMPUTE_MODE == "dot_scaled":
|
| 295 |
+
accumulator = mx_dot_scaled(
|
| 296 |
+
a, a_scale, b, b_s, accumulator, VALUES_PER_BYTE
|
| 297 |
+
)
|
| 298 |
+
elif COMPUTE_MODE == "dot":
|
| 299 |
+
accumulator = mx_dot_rescale(accumulator, a, bq, a_scale, b_s)
|
| 300 |
+
else: # scalar
|
| 301 |
+
accumulator = mx_scalar_reduce(
|
| 302 |
+
accumulator,
|
| 303 |
+
a,
|
| 304 |
+
a_scale,
|
| 305 |
+
bq,
|
| 306 |
+
b_s,
|
| 307 |
+
BLOCK_SIZE_M,
|
| 308 |
+
BLOCK_SIZE_N,
|
| 309 |
+
BLOCK_SIZE_K,
|
| 310 |
+
SCALE_GROUP_K,
|
| 311 |
+
)
|
| 312 |
+
a_ptrs += BLOCK_SIZE_K * stride_ak
|
| 313 |
+
b_ptrs += (BLOCK_SIZE_K // VALUES_PER_BYTE) * stride_bk
|
| 314 |
+
bs_ptrs += (BLOCK_SIZE_K // SCALE_GROUP_K) * stride_bs_k
|
| 315 |
+
|
| 316 |
+
store_row(C, accumulator, pid_n, stride_cn, BLOCK_SIZE_M, BLOCK_SIZE_N)
|
| 317 |
+
|
| 318 |
+
|
| 319 |
+
@triton_op(
|
| 320 |
+
add_op_namespace_prefix("w8a8_block_dynamic_fp8_matmul_batched"), mutates_args=()
|
| 321 |
+
)
|
| 322 |
+
def _w8a8_block_dynamic_fp8_matmul_batched(
|
| 323 |
A: torch.Tensor,
|
| 324 |
B: torch.Tensor,
|
| 325 |
Bs: torch.Tensor,
|
| 326 |
expert_ids: torch.Tensor,
|
| 327 |
block_size: list[int],
|
| 328 |
+
output_dtype: torch.dtype | None = None,
|
| 329 |
) -> torch.Tensor:
|
| 330 |
"""Block-scale batched FP8 matmul: C[s] = A[s] @ B[expert_ids[s]].T, with fused act quant.
|
| 331 |
|
| 332 |
A: (S, K) raw bf16/fp16 activations
|
| 333 |
+
B: (num_experts, N, K) FP8 expert weights
|
| 334 |
+
Bs: (num_experts, N // block_n, K // block_k) per-block weight scales
|
| 335 |
"""
|
| 336 |
assert A.ndim == 2, f"A must be 2D (S, K), got ndim={A.ndim}"
|
| 337 |
assert A.is_contiguous(), "A must be contiguous"
|
| 338 |
+
assert B.ndim == 3, f"B must be 3D (num_experts, N, K), got ndim={B.ndim}"
|
| 339 |
assert B.is_contiguous(), "B must be contiguous"
|
| 340 |
assert A.shape[1] == B.shape[2], (
|
| 341 |
f"K mismatch: A has K={A.shape[1]}, B has K={B.shape[2]}"
|
| 342 |
)
|
| 343 |
|
| 344 |
S, K = A.shape
|
| 345 |
+
num_experts, N, _ = B.shape
|
| 346 |
|
| 347 |
assert len(block_size) == 2, (
|
| 348 |
f"block_size must be [block_n, block_k], got {block_size}"
|
|
|
|
| 352 |
assert N % block_n == 0, f"N ({N}) must be divisible by block_n ({block_n})"
|
| 353 |
assert K % block_k == 0, f"K ({K}) must be divisible by block_k ({block_k})"
|
| 354 |
assert Bs.ndim == 3, (
|
| 355 |
+
f"Bs must be 3D (num_experts, N//block_n, K//block_k), got ndim={Bs.ndim}"
|
| 356 |
)
|
| 357 |
+
assert Bs.shape == (num_experts, N // block_n, K // block_k), (
|
| 358 |
+
f"Bs shape {tuple(Bs.shape)} != expected ({num_experts}, {N // block_n}, {K // block_k})"
|
| 359 |
)
|
| 360 |
|
| 361 |
+
Bs = ue8m0_as_uint8(Bs)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 362 |
grid = (S, triton.cdiv(N, block_n))
|
| 363 |
+
C = A.new_empty(S, N, dtype=output_dtype)
|
| 364 |
+
|
| 365 |
with device_context(A.device):
|
| 366 |
+
wrap_triton(w8a8_block_dynamic_fp8_matmul_batched_kernel)[grid](
|
| 367 |
A,
|
| 368 |
B,
|
| 369 |
C,
|
|
|
|
| 382 |
Bs.stride(0),
|
| 383 |
Bs.stride(2),
|
| 384 |
Bs.stride(1),
|
| 385 |
+
expert_ids.stride(0),
|
| 386 |
BLOCK_SIZE_N=block_n,
|
| 387 |
BLOCK_SIZE_K=block_k,
|
| 388 |
+
BLOCK_SIZE_M=DECODE_BLOCK_SIZE_M,
|
| 389 |
+
num_experts=num_experts,
|
| 390 |
)
|
| 391 |
|
| 392 |
return C
|
| 393 |
|
| 394 |
|
| 395 |
+
@triton_op(
|
| 396 |
+
add_op_namespace_prefix("w8a8_tensor_dynamic_fp8_matmul_batched"), mutates_args=()
|
| 397 |
+
)
|
| 398 |
+
def _w8a8_tensor_dynamic_fp8_matmul_batched(
|
| 399 |
A: torch.Tensor,
|
| 400 |
B: torch.Tensor,
|
| 401 |
Bs: torch.Tensor,
|
| 402 |
expert_ids: torch.Tensor,
|
| 403 |
+
output_dtype: torch.dtype | None = None,
|
| 404 |
) -> torch.Tensor:
|
| 405 |
"""Tensor-scale batched FP8 matmul: C[s] = A[s] @ B[expert_ids[s]].T, with fused act quant.
|
| 406 |
|
| 407 |
A: (S, K) raw bf16/fp16 activations
|
| 408 |
+
B: (num_experts, N, K) FP8 expert weights
|
| 409 |
+
Bs: (num_experts,) or (num_experts, 1, 1) per-expert weight scales
|
| 410 |
"""
|
| 411 |
assert A.ndim == 2, f"A must be 2D (S, K), got ndim={A.ndim}"
|
| 412 |
assert A.is_contiguous(), "A must be contiguous"
|
| 413 |
+
assert B.ndim == 3, f"B must be 3D (num_experts, N, K), got ndim={B.ndim}"
|
| 414 |
assert B.is_contiguous(), "B must be contiguous"
|
| 415 |
assert A.shape[1] == B.shape[2], (
|
| 416 |
f"K mismatch: A has K={A.shape[1]}, B has K={B.shape[2]}"
|
| 417 |
)
|
| 418 |
|
| 419 |
S, K = A.shape
|
| 420 |
+
num_experts, N, _ = B.shape
|
| 421 |
|
| 422 |
+
# Normalize Bs to (num_experts, 1, 1)
|
| 423 |
if Bs.ndim == 1:
|
| 424 |
+
assert Bs.shape[0] == num_experts, (
|
| 425 |
+
f"Bs shape {tuple(Bs.shape)} != expected ({num_experts},)"
|
| 426 |
+
)
|
| 427 |
+
Bs = Bs.reshape(num_experts, 1, 1)
|
| 428 |
else:
|
| 429 |
+
assert Bs.shape == (num_experts, 1, 1), (
|
| 430 |
+
f"Bs shape {tuple(Bs.shape)} != expected ({num_experts}, 1, 1)"
|
| 431 |
)
|
| 432 |
|
| 433 |
+
Bs = ue8m0_as_uint8(Bs)
|
|
|
|
|
|
|
| 434 |
qA, As = fp8_act_quant(A, K)
|
| 435 |
+
C = A.new_empty(S, N, dtype=output_dtype)
|
| 436 |
+
|
| 437 |
+
def grid(META):
|
| 438 |
+
return (S, triton.cdiv(N, META["BLOCK_SIZE_N"]))
|
| 439 |
+
|
|
|
|
|
|
|
| 440 |
with device_context(A.device):
|
| 441 |
+
wrap_triton(w8a8_tensor_dynamic_fp8_matmul_batched_kernel)[grid](
|
| 442 |
qA,
|
| 443 |
B,
|
| 444 |
C,
|
|
|
|
| 457 |
C.stride(1),
|
| 458 |
As.stride(0),
|
| 459 |
Bs.stride(0),
|
| 460 |
+
expert_ids.stride(0),
|
| 461 |
+
BLOCK_SIZE_M=DECODE_BLOCK_SIZE_M,
|
| 462 |
+
num_experts=num_experts,
|
| 463 |
)
|
| 464 |
|
| 465 |
return C
|
| 466 |
|
| 467 |
|
| 468 |
+
def w8a8_block_dynamic_fp8_matmul_batched(
|
| 469 |
A: torch.Tensor,
|
| 470 |
B: torch.Tensor,
|
| 471 |
Bs: torch.Tensor,
|
| 472 |
expert_ids: torch.Tensor,
|
| 473 |
block_size: list[int],
|
| 474 |
+
output_dtype: torch.dtype | None = None,
|
| 475 |
) -> torch.Tensor:
|
| 476 |
"""Block-scale batched FP8 matmul with fused activation quantization.
|
| 477 |
|
| 478 |
A: (S, K) raw activations, bf16/fp16/fp32
|
| 479 |
+
B: (num_experts, N, K) FP8 expert weights
|
| 480 |
+
Bs: (num_experts, N // block_n, K // block_k) per-block weight scales
|
| 481 |
+
expert_ids: (S,) — kernel loads stride-aware, any int dtype works
|
| 482 |
+
output_dtype: defaults to ``A.dtype``
|
| 483 |
"""
|
| 484 |
+
return ops.w8a8_block_dynamic_fp8_matmul_batched(
|
| 485 |
+
A, B, Bs, expert_ids, block_size, output_dtype
|
| 486 |
)
|
| 487 |
|
| 488 |
|
| 489 |
+
def w8a8_tensor_dynamic_fp8_matmul_batched(
|
| 490 |
A: torch.Tensor,
|
| 491 |
B: torch.Tensor,
|
| 492 |
Bs: torch.Tensor,
|
| 493 |
expert_ids: torch.Tensor,
|
| 494 |
+
output_dtype: torch.dtype | None = None,
|
| 495 |
) -> torch.Tensor:
|
| 496 |
"""Tensor-scale batched FP8 matmul with fused activation quantization.
|
| 497 |
|
| 498 |
A: (S, K) raw activations, bf16/fp16/fp32
|
| 499 |
+
B: (num_experts, N, K) FP8 expert weights
|
| 500 |
+
Bs: (num_experts,) or (num_experts, 1, 1) per-expert weight scales
|
| 501 |
+
output_dtype: defaults to ``A.dtype``
|
| 502 |
+
"""
|
| 503 |
+
return ops.w8a8_tensor_dynamic_fp8_matmul_batched(
|
| 504 |
+
A, B, Bs, expert_ids, output_dtype
|
| 505 |
+
)
|
| 506 |
+
|
| 507 |
+
|
| 508 |
+
@triton_op(add_op_namespace_prefix("mxfp_dynamic_matmul_batched"), mutates_args=())
|
| 509 |
+
def _mxfp_dynamic_matmul_batched(
|
| 510 |
+
A: torch.Tensor,
|
| 511 |
+
B: torch.Tensor,
|
| 512 |
+
Bs: torch.Tensor,
|
| 513 |
+
expert_ids: torch.Tensor,
|
| 514 |
+
output_dtype: torch.dtype | None = None,
|
| 515 |
+
) -> torch.Tensor:
|
| 516 |
+
"""Batched MX matmul ``C[s] = A[s] @ B[expert_ids[s]].T`` with fused act quant.
|
| 517 |
+
Weight format is detected from ``B.dtype``: ``int8`` → packed E2M1 (MXFP4, ``B`` is
|
| 518 |
+
``(num_experts, N, K//2)``); ``float8_e4m3fn`` → unpacked E4M3 (MXFP8, ``(num_experts, N, K)``). Both use
|
| 519 |
+
UE8M0 group-32 scales ``(num_experts, N, K//32)``; tile + dot path are autotuned.
|
| 520 |
+
|
| 521 |
+
A: (S, K) raw activations, bf16/fp16/fp32 (quantized inline to E4M3)
|
| 522 |
+
expert_ids: (S,) which expert each routed row uses
|
| 523 |
"""
|
| 524 |
+
assert A.ndim == 2 and B.ndim == 3 and Bs.ndim == 3
|
| 525 |
+
assert expert_ids.ndim == 1
|
| 526 |
+
assert B.dtype in (torch.int8, torch.float8_e4m3fn), (
|
| 527 |
+
f"B must be int8 (packed E2M1) or float8_e4m3fn (E4M3), got {B.dtype}"
|
| 528 |
+
)
|
| 529 |
+
assert Bs.dtype == torch.float8_e8m0fnu, (
|
| 530 |
+
f"Bs must be float8_e8m0fnu, got {Bs.dtype}"
|
| 531 |
+
)
|
| 532 |
+
VALUES_PER_BYTE = NIBBLES_PER_BYTE if B.dtype == torch.int8 else 1
|
| 533 |
+
|
| 534 |
+
S, K = A.shape
|
| 535 |
+
num_experts, N, K_b = B.shape
|
| 536 |
+
assert expert_ids.shape[0] == S
|
| 537 |
+
assert K == VALUES_PER_BYTE * K_b, (
|
| 538 |
+
f"K (={K}) must equal {VALUES_PER_BYTE} * B.shape[2] (={K_b})"
|
| 539 |
+
)
|
| 540 |
+
assert K % MX_SCALE_GROUP_K == 0, (
|
| 541 |
+
f"K (={K}) must be a multiple of {MX_SCALE_GROUP_K}"
|
| 542 |
)
|
| 543 |
+
assert Bs.shape == (num_experts, N, K // MX_SCALE_GROUP_K), (
|
| 544 |
+
f"Bs shape {tuple(Bs.shape)} != ({num_experts}, {N}, {K // MX_SCALE_GROUP_K})"
|
| 545 |
+
)
|
| 546 |
+
|
| 547 |
+
B = e2m1_as_uint8(B)
|
| 548 |
+
bs_u8 = ue8m0_as_uint8(Bs)
|
| 549 |
+
C = A.new_empty((S, N), dtype=output_dtype)
|
| 550 |
|
| 551 |
+
def grid(META):
|
| 552 |
+
return (S, triton.cdiv(N, META["BLOCK_SIZE_N"]))
|
| 553 |
|
| 554 |
+
with device_context(A.device):
|
| 555 |
+
wrap_triton(mxfp_dynamic_matmul_batched_kernel)[grid](
|
| 556 |
+
A,
|
| 557 |
+
B,
|
| 558 |
+
C,
|
| 559 |
+
bs_u8,
|
| 560 |
+
expert_ids,
|
| 561 |
+
S,
|
| 562 |
+
N,
|
| 563 |
+
K,
|
| 564 |
+
A.stride(0),
|
| 565 |
+
A.stride(1),
|
| 566 |
+
B.stride(0),
|
| 567 |
+
B.stride(2),
|
| 568 |
+
B.stride(1),
|
| 569 |
+
C.stride(0),
|
| 570 |
+
C.stride(1),
|
| 571 |
+
bs_u8.stride(0),
|
| 572 |
+
bs_u8.stride(2),
|
| 573 |
+
bs_u8.stride(1),
|
| 574 |
+
expert_ids.stride(0),
|
| 575 |
+
# Meta-parameters (BLOCK_SIZE_N, BLOCK_SIZE_K come from autotune Config)
|
| 576 |
+
BLOCK_SIZE_M=DECODE_BLOCK_SIZE_M,
|
| 577 |
+
VALUES_PER_BYTE=VALUES_PER_BYTE,
|
| 578 |
+
SCALE_GROUP_K=MX_SCALE_GROUP_K,
|
| 579 |
+
num_experts=num_experts,
|
| 580 |
+
)
|
| 581 |
+
return C
|
| 582 |
+
|
| 583 |
+
|
| 584 |
+
def mxfp_dynamic_matmul_batched(
|
| 585 |
+
A: torch.Tensor,
|
| 586 |
+
B: torch.Tensor,
|
| 587 |
+
Bs: torch.Tensor,
|
| 588 |
+
expert_ids: torch.Tensor,
|
| 589 |
+
output_dtype: torch.dtype | None = None,
|
| 590 |
+
) -> torch.Tensor:
|
| 591 |
+
"""Batched MX (MXFP4/MXFP8) matmul with fused act quant; the weight format
|
| 592 |
+
(packed E2M1 vs E4M3) is detected from ``B.dtype``. Tile + dot path autotuned."""
|
| 593 |
+
return ops.mxfp_dynamic_matmul_batched(A, B, Bs, expert_ids, output_dtype)
|
| 594 |
+
|
| 595 |
+
|
| 596 |
+
def matmul_batched(
|
| 597 |
A: torch.Tensor,
|
| 598 |
B: torch.Tensor,
|
| 599 |
Bs: torch.Tensor,
|
| 600 |
expert_ids: torch.Tensor,
|
| 601 |
block_size: list[int] | None,
|
| 602 |
+
output_dtype: torch.dtype | None = None,
|
| 603 |
) -> torch.Tensor:
|
| 604 |
+
"""Batched quantized matmul dispatcher (W8A8 FP8 or W4A8 FP4). Routes one
|
| 605 |
+
routed row per program to ``B[expert_ids[s]]``.
|
| 606 |
|
| 607 |
+
``output_dtype`` defaults to ``A.dtype``.
|
|
|
|
|
|
|
|
|
|
| 608 |
|
| 609 |
+
Routes by weight dtype and ``block_size``:
|
| 610 |
+
- MX weights — ``int8`` (packed E2M1) or ``float8_e4m3fn`` (E4M3) with UE8M0
|
| 611 |
+
group-32 ``Bs`` (shape ``[num_experts, N, K//32]``) → ``mxfp_dynamic_matmul_batched``
|
| 612 |
+
(``block_size`` ignored; tile + dot path autotuned).
|
| 613 |
+
- ``block_size`` None or full ``[N, K]`` → ``w8a8_tensor_dynamic_fp8_matmul_batched``.
|
| 614 |
+
- otherwise → ``w8a8_block_dynamic_fp8_matmul_batched``.
|
| 615 |
"""
|
| 616 |
+
if is_mxfp(B, Bs):
|
| 617 |
+
return mxfp_dynamic_matmul_batched(A, B, Bs, expert_ids, output_dtype)
|
| 618 |
+
|
| 619 |
+
if is_tensor_wide(block_size, B):
|
| 620 |
+
return w8a8_tensor_dynamic_fp8_matmul_batched(
|
| 621 |
+
A, B, Bs, expert_ids, output_dtype
|
| 622 |
+
)
|
| 623 |
|
| 624 |
+
return w8a8_block_dynamic_fp8_matmul_batched(
|
| 625 |
+
A, B, Bs, expert_ids, block_size, output_dtype
|
| 626 |
+
)
|
build/torch-cuda/bayesian_autotuner.py
ADDED
|
@@ -0,0 +1,358 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2026 The HuggingFace Inc. team. All rights reserved.
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
"""Bayesian-optimization autotuner: benches a budgeted sample of the config grid instead of
|
| 16 |
+
the full grid, via a Tree-structured Parzen Estimator (TPE) over the config *dimensions*
|
| 17 |
+
(``num_warps`` / ``num_stages`` / tile sizes / flags). After a short random seed phase it
|
| 18 |
+
models the good (top-``gamma`` by measured time) vs bad per-dimension value densities and
|
| 19 |
+
benches the unmeasured config that maximizes ``l(x)/g(x)`` — the Expected-Improvement proxy
|
| 20 |
+
— then a coordinate-descent pass polishes the best. Subclasses Triton's ``Autotuner`` and
|
| 21 |
+
uses only the Python stdlib (no external optimizer dependency).
|
| 22 |
+
|
| 23 |
+
Each new key's search is warm-started from the most recently tuned key's best config (nearby
|
| 24 |
+
workloads share tile-shape preferences). Grids smaller than ``n_trials`` defer to the stock
|
| 25 |
+
exhaustive bench-all.
|
| 26 |
+
"""
|
| 27 |
+
|
| 28 |
+
from __future__ import annotations
|
| 29 |
+
|
| 30 |
+
import hashlib
|
| 31 |
+
import json
|
| 32 |
+
import math
|
| 33 |
+
import os
|
| 34 |
+
import random
|
| 35 |
+
import time
|
| 36 |
+
from collections import defaultdict
|
| 37 |
+
from typing import Dict, List
|
| 38 |
+
|
| 39 |
+
from triton.runtime.autotuner import (
|
| 40 |
+
Autotuner,
|
| 41 |
+
Config,
|
| 42 |
+
JITFunction,
|
| 43 |
+
driver,
|
| 44 |
+
get_cache_invalidating_env_vars,
|
| 45 |
+
get_cache_manager,
|
| 46 |
+
knobs,
|
| 47 |
+
triton_key,
|
| 48 |
+
)
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
class BayesianAutotuner(Autotuner):
|
| 52 |
+
"""Drop-in replacement for ``triton.runtime.autotuner.Autotuner`` that
|
| 53 |
+
benches ~``n_trials`` configs per key via TPE Bayesian optimization +
|
| 54 |
+
coordinate-descent refinement, instead of the full grid."""
|
| 55 |
+
|
| 56 |
+
def __init__(
|
| 57 |
+
self,
|
| 58 |
+
*args,
|
| 59 |
+
n_trials: int = 80,
|
| 60 |
+
n_startup_trials: int = 12,
|
| 61 |
+
gamma: float = 0.25,
|
| 62 |
+
refine: bool = True,
|
| 63 |
+
max_refine_iters: int = 5,
|
| 64 |
+
log_path: str | None = None,
|
| 65 |
+
**kwargs,
|
| 66 |
+
):
|
| 67 |
+
super().__init__(*args, **kwargs)
|
| 68 |
+
# Bayesian trial budget — the per-decorator default, overridable via the
|
| 69 |
+
# FINEGRAINED_AUTOTUNE_TRIALS env var (quick sweeps / exhaustive runs without touching
|
| 70 |
+
# the decorators; set it >= grid size to fall back to stock exhaustive bench-all).
|
| 71 |
+
self.n_trials = int(os.environ.get("FINEGRAINED_AUTOTUNE_TRIALS") or n_trials)
|
| 72 |
+
self.n_startup_trials = n_startup_trials
|
| 73 |
+
# top fraction of measured configs the TPE treats as "good"
|
| 74 |
+
self.gamma = gamma
|
| 75 |
+
self.refine = refine
|
| 76 |
+
self.max_refine_iters = max_refine_iters
|
| 77 |
+
# JSONL log of every benched (key, config, ms) — set here or via the
|
| 78 |
+
# FINEGRAINED_AUTOTUNE_LOG env var. Analyse offline to prune bad configs.
|
| 79 |
+
self.log_path = log_path or os.environ.get("FINEGRAINED_AUTOTUNE_LOG")
|
| 80 |
+
|
| 81 |
+
def run(self, *args, **kwargs):
|
| 82 |
+
# Small grid → defer to parent (stock exhaustive bench-all).
|
| 83 |
+
if len(self.configs) <= 1 or self.n_trials >= len(self.configs):
|
| 84 |
+
return super().run(*args, **kwargs)
|
| 85 |
+
|
| 86 |
+
self.nargs = dict(zip(self.arg_names, args))
|
| 87 |
+
all_args = {**self.nargs, **kwargs}
|
| 88 |
+
_args = {k: v for k, v in all_args.items() if k in self.arg_names}
|
| 89 |
+
key = [_args[k] for k in self.keys if k in _args]
|
| 90 |
+
key.extend(str(v.dtype) for v in _args.values() if hasattr(v, "dtype"))
|
| 91 |
+
key = tuple(key)
|
| 92 |
+
|
| 93 |
+
if key not in self.cache:
|
| 94 |
+
pruned = self.prune_configs(kwargs)
|
| 95 |
+
|
| 96 |
+
def benchmark():
|
| 97 |
+
t0 = time.time()
|
| 98 |
+
self.cache[key] = self._bayesian_search(pruned, args, kwargs, key)
|
| 99 |
+
self.bench_time = time.time() - t0
|
| 100 |
+
if knobs.autotuning.print:
|
| 101 |
+
fn_name = getattr(self.fn, "__name__", str(self.fn))
|
| 102 |
+
print(
|
| 103 |
+
f"[bayesian-autotune] {fn_name} tuned "
|
| 104 |
+
f"{len(self.configs_timings)} configs in {self.bench_time:.1f}s — "
|
| 105 |
+
f"key={key}, best={self.cache[key].all_kwargs()}"
|
| 106 |
+
)
|
| 107 |
+
full_nargs = {**self.nargs, **kwargs, **self.cache[key].all_kwargs()}
|
| 108 |
+
self.pre_hook(full_nargs, reset_only=True)
|
| 109 |
+
|
| 110 |
+
if self.cache_results:
|
| 111 |
+
self.check_disk_cache(key, pruned, benchmark)
|
| 112 |
+
else:
|
| 113 |
+
benchmark()
|
| 114 |
+
|
| 115 |
+
config = self.cache[key]
|
| 116 |
+
self.best_config = config
|
| 117 |
+
if config.pre_hook is not None:
|
| 118 |
+
config.pre_hook({**self.nargs, **kwargs, **config.all_kwargs()})
|
| 119 |
+
ret = self.fn.run(*args, **kwargs, **config.all_kwargs())
|
| 120 |
+
self.nargs = None
|
| 121 |
+
return ret
|
| 122 |
+
|
| 123 |
+
def _bayesian_search(self, configs: List[Config], args, kwargs, key) -> Config:
|
| 124 |
+
timings: Dict[int, float] = {}
|
| 125 |
+
sigs = [tuple(sorted(c.all_kwargs().items())) for c in configs]
|
| 126 |
+
|
| 127 |
+
def bench_idx(idx: int) -> float:
|
| 128 |
+
if idx in timings:
|
| 129 |
+
return timings[idx]
|
| 130 |
+
try:
|
| 131 |
+
ms = self._bench(*args, config=configs[idx], **kwargs)
|
| 132 |
+
if isinstance(ms, (tuple, list)):
|
| 133 |
+
ms = ms[0]
|
| 134 |
+
timings[idx] = float(ms)
|
| 135 |
+
except Exception:
|
| 136 |
+
timings[idx] = float("inf")
|
| 137 |
+
self._log_result(key, configs[idx], timings[idx])
|
| 138 |
+
return timings[idx]
|
| 139 |
+
|
| 140 |
+
# Distinct values per config dimension, for the TPE's Laplace smoothing.
|
| 141 |
+
dim_vals: Dict = defaultdict(set)
|
| 142 |
+
for sig in sigs:
|
| 143 |
+
for d, v in sig:
|
| 144 |
+
dim_vals[d].add(v)
|
| 145 |
+
|
| 146 |
+
# Seed phase: a few random configs (seeded → deterministic), warm-started from the
|
| 147 |
+
# most recent cached key's best, to give the TPE an initial good/bad split.
|
| 148 |
+
n_startup = max(2, min(self.n_startup_trials, self.n_trials))
|
| 149 |
+
order = list(range(len(configs)))
|
| 150 |
+
random.Random(0).shuffle(order)
|
| 151 |
+
warm_idx = self._warm_start_index(configs)
|
| 152 |
+
if warm_idx is not None:
|
| 153 |
+
order = [warm_idx] + [i for i in order if i != warm_idx]
|
| 154 |
+
for idx in order[:n_startup]:
|
| 155 |
+
bench_idx(idx)
|
| 156 |
+
|
| 157 |
+
# TPE: split measured configs into good (top-gamma) / bad, build per-dimension value
|
| 158 |
+
# densities for each, and bench the unmeasured config maximizing log l(x) - log g(x)
|
| 159 |
+
# (Expected-Improvement proxy), updating the model after each measurement.
|
| 160 |
+
while len(timings) < self.n_trials:
|
| 161 |
+
ranked = sorted(timings, key=timings.get)
|
| 162 |
+
n_good = max(1, round(self.gamma * len(ranked)))
|
| 163 |
+
good_c: Dict = defaultdict(lambda: defaultdict(int))
|
| 164 |
+
bad_c: Dict = defaultdict(lambda: defaultdict(int))
|
| 165 |
+
for j, i in enumerate(ranked):
|
| 166 |
+
tgt = good_c if j < n_good else bad_c
|
| 167 |
+
for d, v in sigs[i]:
|
| 168 |
+
tgt[d][v] += 1
|
| 169 |
+
n_bad = len(ranked) - n_good
|
| 170 |
+
best_i, best_score = None, -math.inf
|
| 171 |
+
for i in range(len(configs)):
|
| 172 |
+
if i in timings:
|
| 173 |
+
continue
|
| 174 |
+
score = 0.0
|
| 175 |
+
for d, v in sigs[i]:
|
| 176 |
+
V = len(dim_vals[d])
|
| 177 |
+
lp = (good_c[d][v] + 1.0) / (
|
| 178 |
+
n_good + V
|
| 179 |
+
) # P(v | good), Laplace-smoothed
|
| 180 |
+
gp = (bad_c[d][v] + 1.0) / (n_bad + V) # P(v | bad)
|
| 181 |
+
score += math.log(lp) - math.log(gp)
|
| 182 |
+
if score > best_score:
|
| 183 |
+
best_score, best_i = score, i
|
| 184 |
+
if best_i is None:
|
| 185 |
+
break
|
| 186 |
+
bench_idx(best_i)
|
| 187 |
+
|
| 188 |
+
# Coordinate-descent refinement: try single-dim perturbations around
|
| 189 |
+
# the current best until no neighbor improves.
|
| 190 |
+
if self.refine:
|
| 191 |
+
for _ in range(self.max_refine_iters):
|
| 192 |
+
best_idx = min(timings, key=timings.get)
|
| 193 |
+
best_sig = sigs[best_idx]
|
| 194 |
+
best_ms = timings[best_idx]
|
| 195 |
+
improved = False
|
| 196 |
+
for i, s in enumerate(sigs):
|
| 197 |
+
if i in timings:
|
| 198 |
+
continue
|
| 199 |
+
diff = sum(1 for (_, a), (_, b) in zip(best_sig, s) if a != b)
|
| 200 |
+
if diff != 1:
|
| 201 |
+
continue
|
| 202 |
+
if bench_idx(i) < best_ms:
|
| 203 |
+
improved = True
|
| 204 |
+
if not improved:
|
| 205 |
+
break
|
| 206 |
+
|
| 207 |
+
self.configs_timings = {configs[i]: t for i, t in timings.items()}
|
| 208 |
+
return configs[min(timings, key=timings.get)]
|
| 209 |
+
|
| 210 |
+
def _log_result(self, key, config: Config, ms: float):
|
| 211 |
+
"""Append one ``(key, config, ms)`` record as JSONL for offline analysis —
|
| 212 |
+
e.g. pruning configs that are consistently far off the per-key best. ``inf`` ms
|
| 213 |
+
marks a config that failed to compile/run (out of resources, etc.)."""
|
| 214 |
+
if not self.log_path:
|
| 215 |
+
return
|
| 216 |
+
try:
|
| 217 |
+
rec = {
|
| 218 |
+
"timestamp": time.time(),
|
| 219 |
+
"fn_name": getattr(self.fn, "__name__", str(self.fn)),
|
| 220 |
+
"key": list(key),
|
| 221 |
+
"kwargs": config.kwargs,
|
| 222 |
+
"num_warps": config.num_warps,
|
| 223 |
+
"num_stages": config.num_stages,
|
| 224 |
+
"ms": ms,
|
| 225 |
+
}
|
| 226 |
+
with open(self.log_path, "a") as f:
|
| 227 |
+
f.write(json.dumps(rec, default=str) + "\n")
|
| 228 |
+
except Exception:
|
| 229 |
+
pass
|
| 230 |
+
|
| 231 |
+
def _warm_start_index(self, configs: List[Config]):
|
| 232 |
+
"""Return the index in ``configs`` matching the most recently cached
|
| 233 |
+
key's best config (or ``None`` if no prior tune or no match in the
|
| 234 |
+
current pruned list)."""
|
| 235 |
+
if not self.cache:
|
| 236 |
+
return None
|
| 237 |
+
# Python 3.7+ dicts preserve insertion order; last entry = most recent tune.
|
| 238 |
+
prev_best = next(reversed(self.cache.values()))
|
| 239 |
+
prev_kwargs = prev_best.all_kwargs()
|
| 240 |
+
for i, c in enumerate(configs):
|
| 241 |
+
if c.all_kwargs() == prev_kwargs:
|
| 242 |
+
return i
|
| 243 |
+
return None
|
| 244 |
+
|
| 245 |
+
def check_disk_cache(self, tuning_key, configs, bench_fn):
|
| 246 |
+
"""Persist the tuned best config to Triton's on-disk cache so a later run (or process)
|
| 247 |
+
skips the whole minutes-long search+compile — most of which is Triton codegen, not
|
| 248 |
+
benching. Keyed exactly like Triton's own autotune cache (triton version + backend +
|
| 249 |
+
kernel source hash + invalidating env + key + the full config grid), so any of those
|
| 250 |
+
changing re-tunes.
|
| 251 |
+
|
| 252 |
+
Unlike Triton's stock version we do NOT bail when configs carry a ``pre_hook`` (ours
|
| 253 |
+
always do): we persist each config's ``all_kwargs()`` — serializable and pre_hook-free —
|
| 254 |
+
and on a hit re-match it to the live ``Config`` object, so the pre_hook is preserved from
|
| 255 |
+
code rather than lost in (de)serialization. A hit launches only the winning config."""
|
| 256 |
+
if not tuning_key:
|
| 257 |
+
bench_fn()
|
| 258 |
+
return False
|
| 259 |
+
|
| 260 |
+
from triton.compiler.compiler import make_backend
|
| 261 |
+
|
| 262 |
+
fn = self.fn
|
| 263 |
+
while not isinstance(fn, JITFunction):
|
| 264 |
+
fn = fn.fn
|
| 265 |
+
env_vars = get_cache_invalidating_env_vars()
|
| 266 |
+
cache_key = [
|
| 267 |
+
triton_key(),
|
| 268 |
+
make_backend(driver.active.get_current_target()).hash(),
|
| 269 |
+
fn.cache_key,
|
| 270 |
+
str(sorted(env_vars.items())),
|
| 271 |
+
str(tuning_key),
|
| 272 |
+
] + [str(c) for c in configs] # str(Config) is pre_hook-free and process-stable
|
| 273 |
+
cache_key = hashlib.sha256("-".join(cache_key).encode("utf-8")).hexdigest()
|
| 274 |
+
cache = get_cache_manager(cache_key)
|
| 275 |
+
file_name = f"{fn.__name__[:150]}.bayes_autotune.json"
|
| 276 |
+
|
| 277 |
+
# signature -> live Config (carries the pre_hook); used to re-match the cached winner
|
| 278 |
+
by_sig = {tuple(sorted(c.all_kwargs().items())): c for c in configs}
|
| 279 |
+
path = cache.get_file(file_name)
|
| 280 |
+
if path:
|
| 281 |
+
try:
|
| 282 |
+
with open(path) as f:
|
| 283 |
+
data = json.load(f)
|
| 284 |
+
best = by_sig.get(tuple(sorted(data["best"].items())))
|
| 285 |
+
if best is not None:
|
| 286 |
+
self.cache[tuning_key] = best
|
| 287 |
+
self.configs_timings = {
|
| 288 |
+
by_sig[s]: t
|
| 289 |
+
for kw, t in data["timings"]
|
| 290 |
+
for s in (tuple(sorted(kw.items())),)
|
| 291 |
+
if s in by_sig
|
| 292 |
+
}
|
| 293 |
+
return True
|
| 294 |
+
except Exception:
|
| 295 |
+
pass # corrupt/stale cache file → fall through and re-tune
|
| 296 |
+
|
| 297 |
+
bench_fn()
|
| 298 |
+
try:
|
| 299 |
+
cache.put(
|
| 300 |
+
json.dumps(
|
| 301 |
+
{
|
| 302 |
+
"best": self.cache[tuning_key].all_kwargs(),
|
| 303 |
+
"timings": [
|
| 304 |
+
(c.all_kwargs(), t) for c, t in self.configs_timings.items()
|
| 305 |
+
],
|
| 306 |
+
}
|
| 307 |
+
),
|
| 308 |
+
file_name,
|
| 309 |
+
binary=False,
|
| 310 |
+
)
|
| 311 |
+
except Exception:
|
| 312 |
+
pass
|
| 313 |
+
return False
|
| 314 |
+
|
| 315 |
+
|
| 316 |
+
def bayesian_autotune(
|
| 317 |
+
configs,
|
| 318 |
+
key,
|
| 319 |
+
*,
|
| 320 |
+
n_trials: int = 80,
|
| 321 |
+
n_startup_trials: int = 12,
|
| 322 |
+
gamma: float = 0.25,
|
| 323 |
+
refine: bool = True,
|
| 324 |
+
max_refine_iters: int = 5,
|
| 325 |
+
log_path: str | None = None,
|
| 326 |
+
cache_results: bool = True,
|
| 327 |
+
reset_to_zero=None,
|
| 328 |
+
restore_value=None,
|
| 329 |
+
**kwargs,
|
| 330 |
+
):
|
| 331 |
+
"""Decorator mirroring ``@triton.autotune``. Extra kwargs:
|
| 332 |
+
n_trials: total configs benched per key (TPE budget)
|
| 333 |
+
n_startup_trials: random seed configs before the TPE model kicks in
|
| 334 |
+
gamma: top fraction of measured configs treated as "good"
|
| 335 |
+
refine, max_refine_iters: coordinate-descent refinement after the TPE
|
| 336 |
+
log_path: JSONL of benched configs (or FINEGRAINED_AUTOTUNE_LOG)
|
| 337 |
+
cache_results: persist the tuned best config to disk (on by default) so later
|
| 338 |
+
runs skip the search+compile — see BayesianAutotuner.check_disk_cache"""
|
| 339 |
+
|
| 340 |
+
def decorator(fn):
|
| 341 |
+
return BayesianAutotuner(
|
| 342 |
+
fn,
|
| 343 |
+
fn.arg_names,
|
| 344 |
+
configs,
|
| 345 |
+
key,
|
| 346 |
+
reset_to_zero,
|
| 347 |
+
restore_value,
|
| 348 |
+
n_trials=n_trials,
|
| 349 |
+
n_startup_trials=n_startup_trials,
|
| 350 |
+
gamma=gamma,
|
| 351 |
+
refine=refine,
|
| 352 |
+
max_refine_iters=max_refine_iters,
|
| 353 |
+
log_path=log_path,
|
| 354 |
+
cache_results=cache_results,
|
| 355 |
+
**kwargs,
|
| 356 |
+
)
|
| 357 |
+
|
| 358 |
+
return decorator
|
build/torch-cuda/fused_batched.py
ADDED
|
@@ -0,0 +1,853 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2026 The HuggingFace Inc. team. All rights reserved.
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
"""Fused batched MoE: two deterministic kernels (no atomics, no sort), per routed token.
|
| 16 |
+
|
| 17 |
+
Kernel 1 (S x N-tiles): gather -> gate_up GEMM -> SiLU -> FP8 requant -> fp8 intermediate
|
| 18 |
+
Kernel 2 (S x H-tiles): fp8 intermediate -> down GEMM -> routing weight -> output
|
| 19 |
+
|
| 20 |
+
Recipe-named to mirror ``batched.py``; ``moe_fused_batched`` is the neutral dispatcher.
|
| 21 |
+
"""
|
| 22 |
+
|
| 23 |
+
import torch
|
| 24 |
+
import triton
|
| 25 |
+
import triton.language as tl
|
| 26 |
+
from torch.library import triton_op, wrap_triton
|
| 27 |
+
|
| 28 |
+
from ._ops import add_op_namespace_prefix, ops
|
| 29 |
+
from .utils import (
|
| 30 |
+
FP8_DTYPE,
|
| 31 |
+
MX_SCALE_GROUP_K,
|
| 32 |
+
NIBBLES_PER_BYTE,
|
| 33 |
+
DECODE_BLOCK_SIZE_M,
|
| 34 |
+
device_context,
|
| 35 |
+
fp8_act_quant_inline,
|
| 36 |
+
get_mxfp_autotuning_configs,
|
| 37 |
+
is_mxfp,
|
| 38 |
+
is_mxfp4,
|
| 39 |
+
mxfp_act_quant_inline,
|
| 40 |
+
mx_compute,
|
| 41 |
+
mx_compute_gate_up,
|
| 42 |
+
glu,
|
| 43 |
+
topk_reduce_kernel,
|
| 44 |
+
TOPK_REDUCE_BLOCK_H,
|
| 45 |
+
e2m1_as_uint8,
|
| 46 |
+
ue8m0_as_uint8,
|
| 47 |
+
)
|
| 48 |
+
from .batched import store_row
|
| 49 |
+
from .bayesian_autotuner import bayesian_autotune
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
# ── Batched fused: two-kernel approach (no sorting, no atomics) ──────────────
|
| 53 |
+
#
|
| 54 |
+
# Same two-kernel architecture as grouped fused but with per-token dispatch:
|
| 55 |
+
# Kernel 1: (S, N-tiles) — gate_up + SiLU + FP8 quant → intermediate buffer
|
| 56 |
+
# Kernel 2: (S, H-tiles) — fp8 intermediate → down proj → output
|
| 57 |
+
# No sorting needed — expert lookup is per-token via ExpertIds.
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
@triton.autotune(
|
| 61 |
+
configs=[
|
| 62 |
+
triton.Config({}, num_warps=w, num_stages=s)
|
| 63 |
+
for w in [2, 4, 8, 16]
|
| 64 |
+
for s in [2, 3, 4, 5, 6]
|
| 65 |
+
],
|
| 66 |
+
# num_routed_tokens (= grid axis-0, runtime) is in the key so decode re-tunes per
|
| 67 |
+
# batch size — a config tuned at S=8 is wrong at S=256 (GPU unsaturated at S=8). The
|
| 68 |
+
# constexpr dims auto-partition the cache, so they don't need to be in the key.
|
| 69 |
+
key=["num_routed_tokens"],
|
| 70 |
+
)
|
| 71 |
+
@triton.jit
|
| 72 |
+
def w8a8_block_dynamic_fp8_moe_batched_gate_up_kernel(
|
| 73 |
+
HiddenStates,
|
| 74 |
+
GateUp,
|
| 75 |
+
GateUpScale,
|
| 76 |
+
Intermediate,
|
| 77 |
+
IntermediateScale,
|
| 78 |
+
ExpertIds,
|
| 79 |
+
stride_a_m,
|
| 80 |
+
stride_a_k,
|
| 81 |
+
stride_gu_e,
|
| 82 |
+
stride_gu_n,
|
| 83 |
+
stride_gu_k,
|
| 84 |
+
stride_gus_e,
|
| 85 |
+
stride_gus_n,
|
| 86 |
+
stride_gus_k,
|
| 87 |
+
num_routed_tokens, # tuning knob: in the autotune key so decode re-tunes per batch
|
| 88 |
+
NUM_EXPERTS: tl.constexpr,
|
| 89 |
+
NUM_TOP_K: tl.constexpr,
|
| 90 |
+
HIDDEN_DIM: tl.constexpr,
|
| 91 |
+
NUM_N_TILES: tl.constexpr,
|
| 92 |
+
INTERMEDIATE_DIM: tl.constexpr,
|
| 93 |
+
BLOCK_SIZE_N: tl.constexpr,
|
| 94 |
+
BLOCK_SIZE_K: tl.constexpr,
|
| 95 |
+
BLOCK_SIZE_M: tl.constexpr,
|
| 96 |
+
SIMULATE_UNFUSED: tl.constexpr,
|
| 97 |
+
ACT_FN: tl.constexpr,
|
| 98 |
+
):
|
| 99 |
+
"""Batched kernel 1: per-token gate_up + SiLU + FP8 quant. Grid: (S, N-tiles)."""
|
| 100 |
+
batch_id = tl.program_id(axis=0)
|
| 101 |
+
pid_n = tl.program_id(axis=1)
|
| 102 |
+
|
| 103 |
+
# Each token is replicated NUM_TOP_K times in the routed (S,) layout, so the
|
| 104 |
+
# source row is batch_id // NUM_TOP_K — gathered from the unexpanded activations
|
| 105 |
+
# (no token-index tensor, no top_k-replicated copy). int64 against offset overflow.
|
| 106 |
+
token = (batch_id // NUM_TOP_K).to(tl.int64)
|
| 107 |
+
expert_id = tl.load(ExpertIds + batch_id).to(tl.int64)
|
| 108 |
+
# EP sentinel: row routed to a non-local expert — skip. The intermediate row is left
|
| 109 |
+
# uninit; the down kernel writes zeros for this row, so the output stays well-defined.
|
| 110 |
+
if expert_id >= NUM_EXPERTS:
|
| 111 |
+
return
|
| 112 |
+
a_ptr = (
|
| 113 |
+
HiddenStates
|
| 114 |
+
+ (token + tl.arange(0, BLOCK_SIZE_M))[:, None] * stride_a_m
|
| 115 |
+
+ tl.arange(0, BLOCK_SIZE_K)[None, :] * stride_a_k
|
| 116 |
+
)
|
| 117 |
+
b_gate_ptr = (
|
| 118 |
+
GateUp
|
| 119 |
+
+ expert_id * stride_gu_e
|
| 120 |
+
+ tl.arange(0, BLOCK_SIZE_K)[:, None] * stride_gu_k
|
| 121 |
+
+ (pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N))[None, :] * stride_gu_n
|
| 122 |
+
)
|
| 123 |
+
b_up_ptr = (
|
| 124 |
+
GateUp
|
| 125 |
+
+ expert_id * stride_gu_e
|
| 126 |
+
+ tl.arange(0, BLOCK_SIZE_K)[:, None] * stride_gu_k
|
| 127 |
+
+ (INTERMEDIATE_DIM + pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N))[
|
| 128 |
+
None, :
|
| 129 |
+
]
|
| 130 |
+
* stride_gu_n
|
| 131 |
+
)
|
| 132 |
+
bs_gate_ptr = GateUpScale + expert_id * stride_gus_e + pid_n * stride_gus_n
|
| 133 |
+
bs_up_ptr = (
|
| 134 |
+
GateUpScale + expert_id * stride_gus_e + (NUM_N_TILES + pid_n) * stride_gus_n
|
| 135 |
+
)
|
| 136 |
+
|
| 137 |
+
acc_gate = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
| 138 |
+
acc_up = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
| 139 |
+
|
| 140 |
+
for _ in range(0, tl.cdiv(HIDDEN_DIM, BLOCK_SIZE_K)):
|
| 141 |
+
a_raw = tl.load(a_ptr).to(tl.float32)
|
| 142 |
+
a, a_s = fp8_act_quant_inline(a_raw)
|
| 143 |
+
|
| 144 |
+
w_gate = tl.load(b_gate_ptr)
|
| 145 |
+
w_up = tl.load(b_up_ptr)
|
| 146 |
+
w_s_gate = tl.load(bs_gate_ptr)
|
| 147 |
+
w_s_up = tl.load(bs_up_ptr)
|
| 148 |
+
|
| 149 |
+
acc_gate += tl.dot(a, w_gate) * a_s[:, None] * w_s_gate
|
| 150 |
+
acc_up += tl.dot(a, w_up) * a_s[:, None] * w_s_up
|
| 151 |
+
|
| 152 |
+
a_ptr += BLOCK_SIZE_K * stride_a_k
|
| 153 |
+
b_gate_ptr += BLOCK_SIZE_K * stride_gu_k
|
| 154 |
+
b_up_ptr += BLOCK_SIZE_K * stride_gu_k
|
| 155 |
+
bs_gate_ptr += stride_gus_k
|
| 156 |
+
bs_up_ptr += stride_gus_k
|
| 157 |
+
|
| 158 |
+
intermediate = glu(
|
| 159 |
+
acc_gate, acc_up, ACT_FN, SIMULATE_UNFUSED, HiddenStates.dtype.element_ty
|
| 160 |
+
)
|
| 161 |
+
|
| 162 |
+
# Requant the intermediate to FP8 — the same inline per-row act quant as the inputs;
|
| 163 |
+
# with BLOCK_SIZE_M=1 the per-row scale is the single per-tile scalar we store.
|
| 164 |
+
inter, inter_s = fp8_act_quant_inline(intermediate)
|
| 165 |
+
store_row(
|
| 166 |
+
Intermediate + batch_id * INTERMEDIATE_DIM,
|
| 167 |
+
inter,
|
| 168 |
+
pid_n,
|
| 169 |
+
1,
|
| 170 |
+
BLOCK_SIZE_M,
|
| 171 |
+
BLOCK_SIZE_N,
|
| 172 |
+
)
|
| 173 |
+
tl.store(IntermediateScale + batch_id * NUM_N_TILES + pid_n, tl.max(inter_s))
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
@triton.autotune(
|
| 177 |
+
configs=[
|
| 178 |
+
triton.Config({}, num_warps=w, num_stages=s)
|
| 179 |
+
for w in [2, 4, 8, 16]
|
| 180 |
+
for s in [2, 3, 4, 5, 6]
|
| 181 |
+
],
|
| 182 |
+
key=["num_routed_tokens"],
|
| 183 |
+
)
|
| 184 |
+
@triton.jit
|
| 185 |
+
def w8a8_block_dynamic_fp8_moe_batched_down_kernel(
|
| 186 |
+
Intermediate,
|
| 187 |
+
IntermediateScale,
|
| 188 |
+
Down,
|
| 189 |
+
DownScale,
|
| 190 |
+
ExpertIds,
|
| 191 |
+
SampleWeights,
|
| 192 |
+
Out,
|
| 193 |
+
stride_down_e,
|
| 194 |
+
stride_down_n,
|
| 195 |
+
stride_down_k,
|
| 196 |
+
stride_downs_e,
|
| 197 |
+
stride_downs_n,
|
| 198 |
+
stride_downs_k,
|
| 199 |
+
num_routed_tokens, # tuning knob: in the autotune key so decode re-tunes per batch
|
| 200 |
+
NUM_EXPERTS: tl.constexpr,
|
| 201 |
+
HIDDEN_DIM: tl.constexpr,
|
| 202 |
+
INTERMEDIATE_DIM: tl.constexpr,
|
| 203 |
+
BLOCK_SIZE_N: tl.constexpr,
|
| 204 |
+
BLOCK_SIZE_H: tl.constexpr,
|
| 205 |
+
BLOCK_SIZE_M: tl.constexpr,
|
| 206 |
+
NUM_N_TILES: tl.constexpr,
|
| 207 |
+
SIMULATE_UNFUSED: tl.constexpr,
|
| 208 |
+
):
|
| 209 |
+
"""Batched kernel 2: fp8 intermediate → down proj → output. Grid: (S, H-tiles)."""
|
| 210 |
+
batch_id = tl.program_id(axis=0)
|
| 211 |
+
pid_h = tl.program_id(axis=1)
|
| 212 |
+
|
| 213 |
+
expert_id = tl.load(ExpertIds + batch_id).to(tl.int64)
|
| 214 |
+
# EP sentinel: row routed to a non-local expert. The program is already launched, so
|
| 215 |
+
# write its zero tile here (skipping the weight load) — cheaper than a host-side mask
|
| 216 |
+
# pass, and it leaves the output fully defined for a plain top-k sum.
|
| 217 |
+
if expert_id >= NUM_EXPERTS:
|
| 218 |
+
z = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_H), dtype=tl.float32)
|
| 219 |
+
store_row(Out + batch_id * HIDDEN_DIM, z, pid_h, 1, BLOCK_SIZE_M, BLOCK_SIZE_H)
|
| 220 |
+
return
|
| 221 |
+
|
| 222 |
+
w_down_ptr = (
|
| 223 |
+
Down
|
| 224 |
+
+ expert_id * stride_down_e
|
| 225 |
+
+ tl.arange(0, BLOCK_SIZE_N)[:, None] * stride_down_k
|
| 226 |
+
+ (pid_h * BLOCK_SIZE_H + tl.arange(0, BLOCK_SIZE_H))[None, :] * stride_down_n
|
| 227 |
+
)
|
| 228 |
+
inter_ptr = (
|
| 229 |
+
Intermediate
|
| 230 |
+
+ (batch_id + tl.arange(0, BLOCK_SIZE_M))[:, None] * INTERMEDIATE_DIM
|
| 231 |
+
+ tl.arange(0, BLOCK_SIZE_N)[None, :]
|
| 232 |
+
)
|
| 233 |
+
inter_s_ptr = (
|
| 234 |
+
IntermediateScale
|
| 235 |
+
+ (batch_id + tl.arange(0, BLOCK_SIZE_M))[:, None] * NUM_N_TILES
|
| 236 |
+
)
|
| 237 |
+
ws_down_ptr = DownScale + expert_id * stride_downs_e + pid_h * stride_downs_n
|
| 238 |
+
|
| 239 |
+
acc = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_H), dtype=tl.float32)
|
| 240 |
+
|
| 241 |
+
for _ in range(0, NUM_N_TILES):
|
| 242 |
+
inter = tl.load(inter_ptr)
|
| 243 |
+
inter_s = tl.load(inter_s_ptr)
|
| 244 |
+
w_s_down = tl.load(ws_down_ptr)
|
| 245 |
+
w_down = tl.load(w_down_ptr)
|
| 246 |
+
acc += tl.dot(inter, w_down) * inter_s * w_s_down
|
| 247 |
+
inter_ptr += BLOCK_SIZE_N
|
| 248 |
+
inter_s_ptr += 1
|
| 249 |
+
ws_down_ptr += stride_downs_k
|
| 250 |
+
w_down_ptr += BLOCK_SIZE_N * stride_down_k
|
| 251 |
+
|
| 252 |
+
if SIMULATE_UNFUSED:
|
| 253 |
+
acc = acc.to(Out.dtype.element_ty).to(tl.float32)
|
| 254 |
+
|
| 255 |
+
acc = acc * tl.load(SampleWeights + batch_id)
|
| 256 |
+
store_row(Out + batch_id * HIDDEN_DIM, acc, pid_h, 1, BLOCK_SIZE_M, BLOCK_SIZE_H)
|
| 257 |
+
|
| 258 |
+
|
| 259 |
+
@triton_op(
|
| 260 |
+
add_op_namespace_prefix("w8a8_block_dynamic_fp8_moe_batched"), mutates_args=()
|
| 261 |
+
)
|
| 262 |
+
def _w8a8_block_dynamic_fp8_moe_batched(
|
| 263 |
+
hidden_states: torch.Tensor,
|
| 264 |
+
gate_up_proj: torch.Tensor,
|
| 265 |
+
gate_up_proj_scale: torch.Tensor,
|
| 266 |
+
down_proj: torch.Tensor,
|
| 267 |
+
down_proj_scale: torch.Tensor,
|
| 268 |
+
top_k_index: torch.Tensor,
|
| 269 |
+
top_k_weights: torch.Tensor,
|
| 270 |
+
block_size: list[int],
|
| 271 |
+
output_dtype: torch.dtype,
|
| 272 |
+
simulate_unfused: bool = False,
|
| 273 |
+
act_fn: str = "silu",
|
| 274 |
+
) -> torch.Tensor:
|
| 275 |
+
"""Block-dynamic FP8 batched fused MoE in ONE op: gate_up + SiLU + FP8 requant →
|
| 276 |
+
grouped down → routing-weighted per-(token, expert) output. gate_up gathers each routed
|
| 277 |
+
row directly from the unexpanded ``hidden_states`` (source row ``s // NUM_TOP_K``, no
|
| 278 |
+
replicated copy). ``inter``/``inter_scales`` are internal; the caller reduces over top-k."""
|
| 279 |
+
device = hidden_states.device
|
| 280 |
+
HIDDEN_DIM = hidden_states.size(1)
|
| 281 |
+
NUM_EXPERTS = gate_up_proj.size(0)
|
| 282 |
+
num_tokens = hidden_states.size(0)
|
| 283 |
+
num_routed_tokens = top_k_index.numel()
|
| 284 |
+
INTERMEDIATE_DIM = gate_up_proj.size(1) // 2
|
| 285 |
+
NUM_TOP_K = num_routed_tokens // hidden_states.size(0)
|
| 286 |
+
BLOCK_SIZE_N, BLOCK_SIZE_K = block_size
|
| 287 |
+
NUM_N_TILES = triton.cdiv(INTERMEDIATE_DIM, BLOCK_SIZE_N)
|
| 288 |
+
NUM_H_TILES = triton.cdiv(HIDDEN_DIM, BLOCK_SIZE_N)
|
| 289 |
+
|
| 290 |
+
inter = torch.empty(
|
| 291 |
+
num_routed_tokens, INTERMEDIATE_DIM, device=device, dtype=FP8_DTYPE
|
| 292 |
+
)
|
| 293 |
+
inter_scales = torch.empty(
|
| 294 |
+
num_routed_tokens, NUM_N_TILES, device=device, dtype=torch.float32
|
| 295 |
+
)
|
| 296 |
+
|
| 297 |
+
out = torch.empty(num_routed_tokens, HIDDEN_DIM, device=device, dtype=output_dtype)
|
| 298 |
+
reduced = torch.empty(num_tokens, HIDDEN_DIM, device=device, dtype=output_dtype)
|
| 299 |
+
with device_context(device):
|
| 300 |
+
wrap_triton(w8a8_block_dynamic_fp8_moe_batched_gate_up_kernel)[
|
| 301 |
+
(num_routed_tokens, NUM_N_TILES)
|
| 302 |
+
](
|
| 303 |
+
hidden_states,
|
| 304 |
+
gate_up_proj,
|
| 305 |
+
gate_up_proj_scale,
|
| 306 |
+
inter,
|
| 307 |
+
inter_scales,
|
| 308 |
+
top_k_index,
|
| 309 |
+
hidden_states.stride(0),
|
| 310 |
+
hidden_states.stride(1),
|
| 311 |
+
gate_up_proj.stride(0),
|
| 312 |
+
gate_up_proj.stride(1),
|
| 313 |
+
gate_up_proj.stride(2),
|
| 314 |
+
gate_up_proj_scale.stride(0),
|
| 315 |
+
gate_up_proj_scale.stride(1),
|
| 316 |
+
gate_up_proj_scale.stride(2),
|
| 317 |
+
num_routed_tokens=num_routed_tokens,
|
| 318 |
+
NUM_TOP_K=NUM_TOP_K,
|
| 319 |
+
NUM_EXPERTS=NUM_EXPERTS,
|
| 320 |
+
HIDDEN_DIM=HIDDEN_DIM,
|
| 321 |
+
INTERMEDIATE_DIM=INTERMEDIATE_DIM,
|
| 322 |
+
BLOCK_SIZE_N=BLOCK_SIZE_N,
|
| 323 |
+
BLOCK_SIZE_K=BLOCK_SIZE_K,
|
| 324 |
+
BLOCK_SIZE_M=DECODE_BLOCK_SIZE_M,
|
| 325 |
+
NUM_N_TILES=NUM_N_TILES,
|
| 326 |
+
SIMULATE_UNFUSED=simulate_unfused,
|
| 327 |
+
ACT_FN=act_fn,
|
| 328 |
+
)
|
| 329 |
+
wrap_triton(w8a8_block_dynamic_fp8_moe_batched_down_kernel)[
|
| 330 |
+
(num_routed_tokens, NUM_H_TILES)
|
| 331 |
+
](
|
| 332 |
+
inter,
|
| 333 |
+
inter_scales,
|
| 334 |
+
down_proj,
|
| 335 |
+
down_proj_scale,
|
| 336 |
+
top_k_index,
|
| 337 |
+
top_k_weights,
|
| 338 |
+
out,
|
| 339 |
+
down_proj.stride(0),
|
| 340 |
+
down_proj.stride(1),
|
| 341 |
+
down_proj.stride(2),
|
| 342 |
+
down_proj_scale.stride(0),
|
| 343 |
+
down_proj_scale.stride(1),
|
| 344 |
+
down_proj_scale.stride(2),
|
| 345 |
+
num_routed_tokens=num_routed_tokens,
|
| 346 |
+
NUM_EXPERTS=NUM_EXPERTS,
|
| 347 |
+
HIDDEN_DIM=HIDDEN_DIM,
|
| 348 |
+
INTERMEDIATE_DIM=INTERMEDIATE_DIM,
|
| 349 |
+
BLOCK_SIZE_N=BLOCK_SIZE_N,
|
| 350 |
+
BLOCK_SIZE_H=BLOCK_SIZE_N,
|
| 351 |
+
BLOCK_SIZE_M=DECODE_BLOCK_SIZE_M,
|
| 352 |
+
NUM_N_TILES=NUM_N_TILES,
|
| 353 |
+
SIMULATE_UNFUSED=simulate_unfused,
|
| 354 |
+
)
|
| 355 |
+
wrap_triton(topk_reduce_kernel)[
|
| 356 |
+
(num_tokens, triton.cdiv(HIDDEN_DIM, TOPK_REDUCE_BLOCK_H))
|
| 357 |
+
](
|
| 358 |
+
out,
|
| 359 |
+
reduced,
|
| 360 |
+
HIDDEN_DIM,
|
| 361 |
+
out.stride(0),
|
| 362 |
+
out.stride(1),
|
| 363 |
+
reduced.stride(0),
|
| 364 |
+
reduced.stride(1),
|
| 365 |
+
NUM_TOP_K=NUM_TOP_K,
|
| 366 |
+
BLOCK_H=TOPK_REDUCE_BLOCK_H,
|
| 367 |
+
)
|
| 368 |
+
|
| 369 |
+
return reduced
|
| 370 |
+
|
| 371 |
+
|
| 372 |
+
def w8a8_block_dynamic_fp8_moe_batched(
|
| 373 |
+
hidden_states: torch.Tensor,
|
| 374 |
+
top_k_index: torch.Tensor,
|
| 375 |
+
top_k_weights: torch.Tensor,
|
| 376 |
+
gate_up_proj: torch.Tensor,
|
| 377 |
+
down_proj: torch.Tensor,
|
| 378 |
+
gate_up_proj_scale: torch.Tensor,
|
| 379 |
+
down_proj_scale: torch.Tensor,
|
| 380 |
+
block_size: list[int],
|
| 381 |
+
simulate_unfused: bool = False,
|
| 382 |
+
act_fn: str = "silu",
|
| 383 |
+
) -> torch.Tensor:
|
| 384 |
+
"""Batched fused MoE (deterministic, no sorting, no atomics): a single ``triton_op``
|
| 385 |
+
runs gate_up + down (gathering each routed row from the unexpanded ``hidden_states``,
|
| 386 |
+
source row ``s // num_top_k``, no replicated copy). The top-k reduce stays plain torch
|
| 387 |
+
so ``torch.compile`` can fuse it with the surrounding model graph."""
|
| 388 |
+
|
| 389 |
+
out = ops.w8a8_block_dynamic_fp8_moe_batched(
|
| 390 |
+
hidden_states,
|
| 391 |
+
gate_up_proj,
|
| 392 |
+
gate_up_proj_scale,
|
| 393 |
+
down_proj,
|
| 394 |
+
down_proj_scale,
|
| 395 |
+
top_k_index,
|
| 396 |
+
top_k_weights,
|
| 397 |
+
block_size,
|
| 398 |
+
hidden_states.dtype,
|
| 399 |
+
simulate_unfused,
|
| 400 |
+
act_fn,
|
| 401 |
+
)
|
| 402 |
+
return out
|
| 403 |
+
|
| 404 |
+
|
| 405 |
+
# ── MXFP8 fused (tl.dot_scaled, UE8M0 group-32 scales, tunable tiles) ─────────
|
| 406 |
+
#
|
| 407 |
+
# Mirrors the block-dynamic kernels but: activations/intermediate quantize via the MX
|
| 408 |
+
# group-32 inline quant, weights/scales feed tl.dot_scaled, and the tile (BLOCK_SIZE_N/K)
|
| 409 |
+
# is free to autotune — the group-32 scale runs along K independently of the compute
|
| 410 |
+
# tile (the whole reason MXFP8 can tune the tile that the block recipe must lock to 128).
|
| 411 |
+
|
| 412 |
+
|
| 413 |
+
@bayesian_autotune(
|
| 414 |
+
get_mxfp_autotuning_configs(),
|
| 415 |
+
["num_routed_tokens"],
|
| 416 |
+
n_trials=60,
|
| 417 |
+
)
|
| 418 |
+
@triton.jit
|
| 419 |
+
def mxfp_dynamic_moe_batched_gate_up_kernel(
|
| 420 |
+
HiddenStates,
|
| 421 |
+
GateUp,
|
| 422 |
+
GateUpScale,
|
| 423 |
+
Intermediate,
|
| 424 |
+
IntermediateScale,
|
| 425 |
+
ExpertIds,
|
| 426 |
+
stride_a_m,
|
| 427 |
+
stride_a_k,
|
| 428 |
+
stride_gu_e,
|
| 429 |
+
stride_gu_n,
|
| 430 |
+
stride_gu_k,
|
| 431 |
+
stride_gus_e,
|
| 432 |
+
stride_gus_n,
|
| 433 |
+
stride_gus_k,
|
| 434 |
+
num_routed_tokens, # tuning knob: in the autotune key so decode re-tunes per batch
|
| 435 |
+
NUM_EXPERTS: tl.constexpr,
|
| 436 |
+
NUM_TOP_K: tl.constexpr,
|
| 437 |
+
HIDDEN_DIM: tl.constexpr,
|
| 438 |
+
INTERMEDIATE_DIM: tl.constexpr,
|
| 439 |
+
BLOCK_SIZE_N: tl.constexpr,
|
| 440 |
+
BLOCK_SIZE_K: tl.constexpr,
|
| 441 |
+
BLOCK_SIZE_M: tl.constexpr,
|
| 442 |
+
VALUES_PER_BYTE: tl.constexpr,
|
| 443 |
+
SCALE_GROUP_K: tl.constexpr,
|
| 444 |
+
SIMULATE_UNFUSED: tl.constexpr,
|
| 445 |
+
COMPUTE_MODE: tl.constexpr,
|
| 446 |
+
ACT_FN: tl.constexpr,
|
| 447 |
+
):
|
| 448 |
+
"""MXFP4/MXFP8 kernel 1: gate_up + SiLU + MXFP8 requant. N = intermediate
|
| 449 |
+
(output) tile, K = hidden (contraction) tile — both tunable. Grid: (S, N-tiles)."""
|
| 450 |
+
batch_id = tl.program_id(axis=0)
|
| 451 |
+
pid_n = tl.program_id(axis=1)
|
| 452 |
+
token = (batch_id // NUM_TOP_K).to(tl.int64)
|
| 453 |
+
expert_id = tl.load(ExpertIds + batch_id).to(tl.int64)
|
| 454 |
+
if expert_id >= NUM_EXPERTS: # EP sentinel; down kernel zeros the output row
|
| 455 |
+
return
|
| 456 |
+
|
| 457 |
+
a_ptr = (
|
| 458 |
+
HiddenStates
|
| 459 |
+
+ (token + tl.arange(0, BLOCK_SIZE_M))[:, None] * stride_a_m
|
| 460 |
+
+ tl.arange(0, BLOCK_SIZE_K)[None, :] * stride_a_k
|
| 461 |
+
)
|
| 462 |
+
b_gate_ptr = (
|
| 463 |
+
GateUp
|
| 464 |
+
+ expert_id * stride_gu_e
|
| 465 |
+
+ tl.arange(0, BLOCK_SIZE_K // VALUES_PER_BYTE)[:, None] * stride_gu_k
|
| 466 |
+
+ (pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N))[None, :] * stride_gu_n
|
| 467 |
+
)
|
| 468 |
+
b_up_ptr = (
|
| 469 |
+
GateUp
|
| 470 |
+
+ expert_id * stride_gu_e
|
| 471 |
+
+ tl.arange(0, BLOCK_SIZE_K // VALUES_PER_BYTE)[:, None] * stride_gu_k
|
| 472 |
+
+ (INTERMEDIATE_DIM + pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N))[
|
| 473 |
+
None, :
|
| 474 |
+
]
|
| 475 |
+
* stride_gu_n
|
| 476 |
+
)
|
| 477 |
+
bs_gate_ptr = (
|
| 478 |
+
GateUpScale
|
| 479 |
+
+ expert_id * stride_gus_e
|
| 480 |
+
+ (pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N))[:, None] * stride_gus_n
|
| 481 |
+
+ tl.arange(0, BLOCK_SIZE_K // SCALE_GROUP_K)[None, :] * stride_gus_k
|
| 482 |
+
)
|
| 483 |
+
bs_up_ptr = (
|
| 484 |
+
GateUpScale
|
| 485 |
+
+ expert_id * stride_gus_e
|
| 486 |
+
+ (INTERMEDIATE_DIM + pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N))[
|
| 487 |
+
:, None
|
| 488 |
+
]
|
| 489 |
+
* stride_gus_n
|
| 490 |
+
+ tl.arange(0, BLOCK_SIZE_K // SCALE_GROUP_K)[None, :] * stride_gus_k
|
| 491 |
+
)
|
| 492 |
+
|
| 493 |
+
acc_gate = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
| 494 |
+
acc_up = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
| 495 |
+
for _ in range(0, tl.cdiv(HIDDEN_DIM, BLOCK_SIZE_K)):
|
| 496 |
+
a_raw = tl.load(a_ptr).to(tl.float32)
|
| 497 |
+
a, a_scale = mxfp_act_quant_inline(
|
| 498 |
+
a_raw, BLOCK_SIZE_M, BLOCK_SIZE_K, SCALE_GROUP_K
|
| 499 |
+
)
|
| 500 |
+
b_gate = tl.load(b_gate_ptr)
|
| 501 |
+
b_up = tl.load(b_up_ptr)
|
| 502 |
+
bs_gate = tl.load(bs_gate_ptr)
|
| 503 |
+
bs_up = tl.load(bs_up_ptr)
|
| 504 |
+
acc_gate, acc_up = mx_compute_gate_up(
|
| 505 |
+
acc_gate,
|
| 506 |
+
acc_up,
|
| 507 |
+
a,
|
| 508 |
+
a_scale,
|
| 509 |
+
b_gate,
|
| 510 |
+
b_up,
|
| 511 |
+
bs_gate,
|
| 512 |
+
bs_up,
|
| 513 |
+
COMPUTE_MODE,
|
| 514 |
+
VALUES_PER_BYTE,
|
| 515 |
+
BLOCK_SIZE_M,
|
| 516 |
+
BLOCK_SIZE_N,
|
| 517 |
+
BLOCK_SIZE_K,
|
| 518 |
+
SCALE_GROUP_K,
|
| 519 |
+
)
|
| 520 |
+
a_ptr += BLOCK_SIZE_K * stride_a_k
|
| 521 |
+
b_gate_ptr += (BLOCK_SIZE_K // VALUES_PER_BYTE) * stride_gu_k
|
| 522 |
+
b_up_ptr += (BLOCK_SIZE_K // VALUES_PER_BYTE) * stride_gu_k
|
| 523 |
+
bs_gate_ptr += (BLOCK_SIZE_K // SCALE_GROUP_K) * stride_gus_k
|
| 524 |
+
bs_up_ptr += (BLOCK_SIZE_K // SCALE_GROUP_K) * stride_gus_k
|
| 525 |
+
|
| 526 |
+
intermediate = glu(
|
| 527 |
+
acc_gate, acc_up, ACT_FN, SIMULATE_UNFUSED, HiddenStates.dtype.element_ty
|
| 528 |
+
)
|
| 529 |
+
|
| 530 |
+
# MXFP8 requant of the intermediate (E4M3 + UE8M0 group-32 along this N-tile).
|
| 531 |
+
inter, inter_scale = mxfp_act_quant_inline(
|
| 532 |
+
intermediate, BLOCK_SIZE_M, BLOCK_SIZE_N, SCALE_GROUP_K
|
| 533 |
+
)
|
| 534 |
+
store_row(
|
| 535 |
+
Intermediate + batch_id * INTERMEDIATE_DIM,
|
| 536 |
+
inter,
|
| 537 |
+
pid_n,
|
| 538 |
+
1,
|
| 539 |
+
BLOCK_SIZE_M,
|
| 540 |
+
BLOCK_SIZE_N,
|
| 541 |
+
)
|
| 542 |
+
offs_sc = pid_n * (BLOCK_SIZE_N // SCALE_GROUP_K) + tl.arange(
|
| 543 |
+
0, BLOCK_SIZE_N // SCALE_GROUP_K
|
| 544 |
+
)
|
| 545 |
+
sc_ptrs = (
|
| 546 |
+
IntermediateScale
|
| 547 |
+
+ batch_id * (INTERMEDIATE_DIM // SCALE_GROUP_K)
|
| 548 |
+
+ offs_sc[None, :]
|
| 549 |
+
)
|
| 550 |
+
tl.store(sc_ptrs, inter_scale)
|
| 551 |
+
|
| 552 |
+
|
| 553 |
+
@bayesian_autotune(
|
| 554 |
+
get_mxfp_autotuning_configs(),
|
| 555 |
+
["num_routed_tokens"],
|
| 556 |
+
n_trials=60,
|
| 557 |
+
)
|
| 558 |
+
@triton.jit
|
| 559 |
+
def mxfp_dynamic_moe_batched_down_kernel(
|
| 560 |
+
Intermediate,
|
| 561 |
+
IntermediateScale,
|
| 562 |
+
Down,
|
| 563 |
+
DownScale,
|
| 564 |
+
ExpertIds,
|
| 565 |
+
SampleWeights,
|
| 566 |
+
Out,
|
| 567 |
+
stride_down_e,
|
| 568 |
+
stride_down_n,
|
| 569 |
+
stride_down_k,
|
| 570 |
+
stride_downs_e,
|
| 571 |
+
stride_downs_n,
|
| 572 |
+
stride_downs_k,
|
| 573 |
+
num_routed_tokens, # tuning knob: in the autotune key so decode re-tunes per batch
|
| 574 |
+
NUM_EXPERTS: tl.constexpr,
|
| 575 |
+
HIDDEN_DIM: tl.constexpr,
|
| 576 |
+
INTERMEDIATE_DIM: tl.constexpr,
|
| 577 |
+
BLOCK_SIZE_N: tl.constexpr,
|
| 578 |
+
BLOCK_SIZE_K: tl.constexpr,
|
| 579 |
+
BLOCK_SIZE_M: tl.constexpr,
|
| 580 |
+
VALUES_PER_BYTE: tl.constexpr,
|
| 581 |
+
SCALE_GROUP_K: tl.constexpr,
|
| 582 |
+
SIMULATE_UNFUSED: tl.constexpr,
|
| 583 |
+
COMPUTE_MODE: tl.constexpr,
|
| 584 |
+
):
|
| 585 |
+
"""MXFP4/MXFP8 kernel 2: MXFP8 intermediate ��� down proj. N = hidden
|
| 586 |
+
(output) tile, K = intermediate (contraction) tile. Grid: (S, H-tiles)."""
|
| 587 |
+
batch_id = tl.program_id(axis=0)
|
| 588 |
+
pid_n = tl.program_id(axis=1)
|
| 589 |
+
expert_id = tl.load(ExpertIds + batch_id).to(tl.int64)
|
| 590 |
+
if expert_id >= NUM_EXPERTS: # EP sentinel: zero the output row for the top-k sum
|
| 591 |
+
z = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
| 592 |
+
store_row(Out + batch_id * HIDDEN_DIM, z, pid_n, 1, BLOCK_SIZE_M, BLOCK_SIZE_N)
|
| 593 |
+
return
|
| 594 |
+
|
| 595 |
+
a_ptr = (
|
| 596 |
+
Intermediate
|
| 597 |
+
+ (batch_id + tl.arange(0, BLOCK_SIZE_M))[:, None] * INTERMEDIATE_DIM
|
| 598 |
+
+ tl.arange(0, BLOCK_SIZE_K)[None, :]
|
| 599 |
+
)
|
| 600 |
+
as_ptr = (
|
| 601 |
+
IntermediateScale
|
| 602 |
+
+ (batch_id + tl.arange(0, BLOCK_SIZE_M))[:, None]
|
| 603 |
+
* (INTERMEDIATE_DIM // SCALE_GROUP_K)
|
| 604 |
+
+ tl.arange(0, BLOCK_SIZE_K // SCALE_GROUP_K)[None, :]
|
| 605 |
+
)
|
| 606 |
+
w_down_ptr = (
|
| 607 |
+
Down
|
| 608 |
+
+ expert_id * stride_down_e
|
| 609 |
+
+ tl.arange(0, BLOCK_SIZE_K // VALUES_PER_BYTE)[:, None] * stride_down_k
|
| 610 |
+
+ (pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N))[None, :] * stride_down_n
|
| 611 |
+
)
|
| 612 |
+
ws_down_ptr = (
|
| 613 |
+
DownScale
|
| 614 |
+
+ expert_id * stride_downs_e
|
| 615 |
+
+ (pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N))[:, None] * stride_downs_n
|
| 616 |
+
+ tl.arange(0, BLOCK_SIZE_K // SCALE_GROUP_K)[None, :] * stride_downs_k
|
| 617 |
+
)
|
| 618 |
+
|
| 619 |
+
acc = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
| 620 |
+
for _ in range(0, tl.cdiv(INTERMEDIATE_DIM, BLOCK_SIZE_K)):
|
| 621 |
+
a = tl.load(a_ptr)
|
| 622 |
+
a_scale = tl.load(as_ptr)
|
| 623 |
+
w = tl.load(w_down_ptr)
|
| 624 |
+
w_scale = tl.load(ws_down_ptr)
|
| 625 |
+
acc = mx_compute(
|
| 626 |
+
acc,
|
| 627 |
+
a,
|
| 628 |
+
a_scale,
|
| 629 |
+
w,
|
| 630 |
+
w_scale,
|
| 631 |
+
COMPUTE_MODE,
|
| 632 |
+
VALUES_PER_BYTE,
|
| 633 |
+
BLOCK_SIZE_M,
|
| 634 |
+
BLOCK_SIZE_N,
|
| 635 |
+
BLOCK_SIZE_K,
|
| 636 |
+
SCALE_GROUP_K,
|
| 637 |
+
)
|
| 638 |
+
a_ptr += BLOCK_SIZE_K
|
| 639 |
+
as_ptr += BLOCK_SIZE_K // SCALE_GROUP_K
|
| 640 |
+
w_down_ptr += (BLOCK_SIZE_K // VALUES_PER_BYTE) * stride_down_k
|
| 641 |
+
ws_down_ptr += (BLOCK_SIZE_K // SCALE_GROUP_K) * stride_downs_k
|
| 642 |
+
|
| 643 |
+
if SIMULATE_UNFUSED:
|
| 644 |
+
acc = acc.to(Out.dtype.element_ty).to(tl.float32)
|
| 645 |
+
acc = acc * tl.load(SampleWeights + batch_id)
|
| 646 |
+
store_row(Out + batch_id * HIDDEN_DIM, acc, pid_n, 1, BLOCK_SIZE_M, BLOCK_SIZE_N)
|
| 647 |
+
|
| 648 |
+
|
| 649 |
+
@triton_op(add_op_namespace_prefix("mxfp_dynamic_moe_batched"), mutates_args=())
|
| 650 |
+
def _mxfp_dynamic_moe_batched(
|
| 651 |
+
hidden_states: torch.Tensor,
|
| 652 |
+
gate_up_proj: torch.Tensor,
|
| 653 |
+
gate_up_proj_scale: torch.Tensor,
|
| 654 |
+
down_proj: torch.Tensor,
|
| 655 |
+
down_proj_scale: torch.Tensor,
|
| 656 |
+
top_k_index: torch.Tensor,
|
| 657 |
+
top_k_weights: torch.Tensor,
|
| 658 |
+
output_dtype: torch.dtype,
|
| 659 |
+
simulate_unfused: bool = False,
|
| 660 |
+
act_fn: str = "silu",
|
| 661 |
+
) -> torch.Tensor:
|
| 662 |
+
"""MXFP4/MXFP8 batched fused MoE in ONE op: gate_up + SiLU + MXFP8 requant → grouped
|
| 663 |
+
down → routing-weighted per-(token, expert) output. gate_up and down must share the same MX
|
| 664 |
+
format. ``inter``/``inter_scales`` are internal; caller reduces over top-k."""
|
| 665 |
+
gate_up_is_fp4 = is_mxfp4(gate_up_proj, gate_up_proj_scale)
|
| 666 |
+
down_is_fp4 = is_mxfp4(down_proj, down_proj_scale)
|
| 667 |
+
if gate_up_is_fp4 != down_is_fp4:
|
| 668 |
+
raise ValueError(
|
| 669 |
+
"gate_up_proj and down_proj must use the same MX format (both MXFP4 or both MXFP8)."
|
| 670 |
+
)
|
| 671 |
+
|
| 672 |
+
device = hidden_states.device
|
| 673 |
+
HIDDEN_DIM = hidden_states.size(1)
|
| 674 |
+
NUM_EXPERTS = gate_up_proj.size(0)
|
| 675 |
+
num_tokens = hidden_states.size(0)
|
| 676 |
+
num_routed_tokens = top_k_index.numel()
|
| 677 |
+
INTERMEDIATE_DIM = gate_up_proj.size(1) // 2
|
| 678 |
+
NUM_TOP_K = num_routed_tokens // hidden_states.size(0)
|
| 679 |
+
VALUES_PER_BYTE = NIBBLES_PER_BYTE if gate_up_is_fp4 else 1
|
| 680 |
+
gate_up_proj_u8 = e2m1_as_uint8(gate_up_proj)
|
| 681 |
+
gate_up_proj_scale_u8 = ue8m0_as_uint8(gate_up_proj_scale)
|
| 682 |
+
down_proj_u8 = e2m1_as_uint8(down_proj)
|
| 683 |
+
down_proj_scale_u8 = ue8m0_as_uint8(down_proj_scale)
|
| 684 |
+
|
| 685 |
+
inter = torch.empty(
|
| 686 |
+
num_routed_tokens, INTERMEDIATE_DIM, device=device, dtype=FP8_DTYPE
|
| 687 |
+
)
|
| 688 |
+
inter_scales = torch.empty(
|
| 689 |
+
num_routed_tokens,
|
| 690 |
+
INTERMEDIATE_DIM // MX_SCALE_GROUP_K,
|
| 691 |
+
device=device,
|
| 692 |
+
dtype=torch.uint8,
|
| 693 |
+
)
|
| 694 |
+
out = torch.empty(num_routed_tokens, HIDDEN_DIM, device=device, dtype=output_dtype)
|
| 695 |
+
reduced = torch.empty(num_tokens, HIDDEN_DIM, device=device, dtype=output_dtype)
|
| 696 |
+
|
| 697 |
+
def gate_up_grid(META):
|
| 698 |
+
return (num_routed_tokens, triton.cdiv(INTERMEDIATE_DIM, META["BLOCK_SIZE_N"]))
|
| 699 |
+
|
| 700 |
+
def down_grid(META):
|
| 701 |
+
return (num_routed_tokens, triton.cdiv(HIDDEN_DIM, META["BLOCK_SIZE_N"]))
|
| 702 |
+
|
| 703 |
+
with device_context(device):
|
| 704 |
+
wrap_triton(mxfp_dynamic_moe_batched_gate_up_kernel)[gate_up_grid](
|
| 705 |
+
hidden_states,
|
| 706 |
+
gate_up_proj_u8,
|
| 707 |
+
gate_up_proj_scale_u8,
|
| 708 |
+
inter,
|
| 709 |
+
inter_scales,
|
| 710 |
+
top_k_index,
|
| 711 |
+
hidden_states.stride(0),
|
| 712 |
+
hidden_states.stride(1),
|
| 713 |
+
gate_up_proj_u8.stride(0),
|
| 714 |
+
gate_up_proj_u8.stride(1),
|
| 715 |
+
gate_up_proj_u8.stride(2),
|
| 716 |
+
gate_up_proj_scale_u8.stride(0),
|
| 717 |
+
gate_up_proj_scale_u8.stride(1),
|
| 718 |
+
gate_up_proj_scale_u8.stride(2),
|
| 719 |
+
num_routed_tokens=num_routed_tokens,
|
| 720 |
+
NUM_TOP_K=NUM_TOP_K,
|
| 721 |
+
NUM_EXPERTS=NUM_EXPERTS,
|
| 722 |
+
HIDDEN_DIM=HIDDEN_DIM,
|
| 723 |
+
INTERMEDIATE_DIM=INTERMEDIATE_DIM,
|
| 724 |
+
BLOCK_SIZE_M=DECODE_BLOCK_SIZE_M,
|
| 725 |
+
VALUES_PER_BYTE=VALUES_PER_BYTE,
|
| 726 |
+
SCALE_GROUP_K=MX_SCALE_GROUP_K,
|
| 727 |
+
SIMULATE_UNFUSED=simulate_unfused,
|
| 728 |
+
ACT_FN=act_fn,
|
| 729 |
+
)
|
| 730 |
+
wrap_triton(mxfp_dynamic_moe_batched_down_kernel)[down_grid](
|
| 731 |
+
inter,
|
| 732 |
+
inter_scales,
|
| 733 |
+
down_proj_u8,
|
| 734 |
+
down_proj_scale_u8,
|
| 735 |
+
top_k_index,
|
| 736 |
+
top_k_weights,
|
| 737 |
+
out,
|
| 738 |
+
down_proj_u8.stride(0),
|
| 739 |
+
down_proj_u8.stride(1),
|
| 740 |
+
down_proj_u8.stride(2),
|
| 741 |
+
down_proj_scale_u8.stride(0),
|
| 742 |
+
down_proj_scale_u8.stride(1),
|
| 743 |
+
down_proj_scale_u8.stride(2),
|
| 744 |
+
num_routed_tokens=num_routed_tokens,
|
| 745 |
+
NUM_EXPERTS=NUM_EXPERTS,
|
| 746 |
+
HIDDEN_DIM=HIDDEN_DIM,
|
| 747 |
+
INTERMEDIATE_DIM=INTERMEDIATE_DIM,
|
| 748 |
+
BLOCK_SIZE_M=DECODE_BLOCK_SIZE_M,
|
| 749 |
+
VALUES_PER_BYTE=VALUES_PER_BYTE,
|
| 750 |
+
SCALE_GROUP_K=MX_SCALE_GROUP_K,
|
| 751 |
+
SIMULATE_UNFUSED=simulate_unfused,
|
| 752 |
+
)
|
| 753 |
+
wrap_triton(topk_reduce_kernel)[
|
| 754 |
+
(num_tokens, triton.cdiv(HIDDEN_DIM, TOPK_REDUCE_BLOCK_H))
|
| 755 |
+
](
|
| 756 |
+
out,
|
| 757 |
+
reduced,
|
| 758 |
+
HIDDEN_DIM,
|
| 759 |
+
out.stride(0),
|
| 760 |
+
out.stride(1),
|
| 761 |
+
reduced.stride(0),
|
| 762 |
+
reduced.stride(1),
|
| 763 |
+
NUM_TOP_K=NUM_TOP_K,
|
| 764 |
+
BLOCK_H=TOPK_REDUCE_BLOCK_H,
|
| 765 |
+
)
|
| 766 |
+
|
| 767 |
+
return reduced
|
| 768 |
+
|
| 769 |
+
|
| 770 |
+
def mxfp_dynamic_moe_batched(
|
| 771 |
+
hidden_states: torch.Tensor,
|
| 772 |
+
top_k_index: torch.Tensor,
|
| 773 |
+
top_k_weights: torch.Tensor,
|
| 774 |
+
gate_up_proj: torch.Tensor,
|
| 775 |
+
down_proj: torch.Tensor,
|
| 776 |
+
gate_up_proj_scale: torch.Tensor,
|
| 777 |
+
down_proj_scale: torch.Tensor,
|
| 778 |
+
simulate_unfused: bool = False,
|
| 779 |
+
act_fn: str = "silu",
|
| 780 |
+
) -> torch.Tensor:
|
| 781 |
+
"""Two-kernel batched fused MX MoE — MXFP4 or MXFP8 weights (UE8M0 group-32), the
|
| 782 |
+
format picked per-weight by the ops. Same structure as the block-dynamic path but
|
| 783 |
+
with a tunable tile and an MXFP8 group-32 intermediate; ``block_size`` is unused."""
|
| 784 |
+
out = ops.mxfp_dynamic_moe_batched(
|
| 785 |
+
hidden_states,
|
| 786 |
+
gate_up_proj,
|
| 787 |
+
gate_up_proj_scale,
|
| 788 |
+
down_proj,
|
| 789 |
+
down_proj_scale,
|
| 790 |
+
top_k_index,
|
| 791 |
+
top_k_weights,
|
| 792 |
+
hidden_states.dtype,
|
| 793 |
+
simulate_unfused,
|
| 794 |
+
act_fn,
|
| 795 |
+
)
|
| 796 |
+
return out
|
| 797 |
+
|
| 798 |
+
|
| 799 |
+
# ── Dispatcher ────────────────────────────────────────────────────────────────
|
| 800 |
+
|
| 801 |
+
|
| 802 |
+
def moe_fused_batched(
|
| 803 |
+
hidden_states: torch.Tensor,
|
| 804 |
+
top_k_index: torch.Tensor,
|
| 805 |
+
top_k_weights: torch.Tensor,
|
| 806 |
+
gate_up_proj: torch.Tensor,
|
| 807 |
+
down_proj: torch.Tensor,
|
| 808 |
+
gate_up_proj_scale_inv: torch.Tensor,
|
| 809 |
+
down_proj_scale_inv: torch.Tensor,
|
| 810 |
+
block_size: list[int] | None,
|
| 811 |
+
simulate_unfused: bool = False,
|
| 812 |
+
act_fn: str = "silu",
|
| 813 |
+
) -> torch.Tensor:
|
| 814 |
+
"""Fused batched-MoE dispatcher — routes to the recipe matching the weight dtype /
|
| 815 |
+
scale layout, mirroring ``matmul_batched``. Implemented: block-dynamic FP8 and MXFP8
|
| 816 |
+
(tensor-dynamic) and MXFP4. ``simulate_unfused`` (testing) rounds each step through the
|
| 817 |
+
activation dtype so the output matches the unfused reference to reduce order."""
|
| 818 |
+
|
| 819 |
+
gate_up_is_mx = is_mxfp(gate_up_proj, gate_up_proj_scale_inv)
|
| 820 |
+
down_is_mx = is_mxfp(down_proj, down_proj_scale_inv)
|
| 821 |
+
if gate_up_is_mx != down_is_mx:
|
| 822 |
+
raise ValueError(
|
| 823 |
+
"gate_up_proj and down_proj must use the same recipe (both MX or both block-dynamic FP8)."
|
| 824 |
+
)
|
| 825 |
+
|
| 826 |
+
if gate_up_is_mx:
|
| 827 |
+
return mxfp_dynamic_moe_batched(
|
| 828 |
+
hidden_states,
|
| 829 |
+
top_k_index,
|
| 830 |
+
top_k_weights,
|
| 831 |
+
gate_up_proj,
|
| 832 |
+
down_proj,
|
| 833 |
+
gate_up_proj_scale_inv,
|
| 834 |
+
down_proj_scale_inv,
|
| 835 |
+
simulate_unfused,
|
| 836 |
+
act_fn,
|
| 837 |
+
)
|
| 838 |
+
|
| 839 |
+
if block_size is None:
|
| 840 |
+
raise ValueError("block_size is required for block-dynamic FP8 weights.")
|
| 841 |
+
|
| 842 |
+
return w8a8_block_dynamic_fp8_moe_batched(
|
| 843 |
+
hidden_states,
|
| 844 |
+
top_k_index,
|
| 845 |
+
top_k_weights,
|
| 846 |
+
gate_up_proj,
|
| 847 |
+
down_proj,
|
| 848 |
+
gate_up_proj_scale_inv,
|
| 849 |
+
down_proj_scale_inv,
|
| 850 |
+
block_size,
|
| 851 |
+
simulate_unfused,
|
| 852 |
+
act_fn,
|
| 853 |
+
)
|
build/torch-cuda/fused_grouped.py
ADDED
|
@@ -0,0 +1,1110 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2026 The HuggingFace Inc. team. All rights reserved.
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
|
| 6 |
+
"""Fused grouped MoE (prefill counterpart to the fused batched/decode path).
|
| 7 |
+
|
| 8 |
+
Two **persistent** kernels over expert-grouped M-tiles, no ``A_sorted`` materialization:
|
| 9 |
+
1. Routing (on-device): an atomic counting-sort scatter (``_scatter_kernel``, O(S)) →
|
| 10 |
+
``perm`` / ``perm_token`` + an ``expert_start`` exclusive offset vector — the sort is
|
| 11 |
+
just an index; activations stay unsorted.
|
| 12 |
+
2. Phase 1 — gate_up, **gathering** each tile's hidden rows via ``perm_token``, + SiLU,
|
| 13 |
+
then **FP8 requant** of the intermediate — same trick as ``fused_batched``: the down
|
| 14 |
+
kernel reads FP8 directly instead of a bf16 round-trip.
|
| 15 |
+
3. Phase 2 — grouped down over the FP8 intermediate, then **fuses the routing-weight ×
|
| 16 |
+
reorder** (no atomics): each row is scaled by its weight and **scattered** to its flat
|
| 17 |
+
``(token, slot)`` row via ``perm``, leaving the host only a top-k sum back to (T, H).
|
| 18 |
+
|
| 19 |
+
Scheduling is persistent: a fixed ``NUM_SMS`` programs grid-stride over all tiles. The
|
| 20 |
+
expert-tile layout is built once into registers from ``expert_start`` (``_build_tile_layout``)
|
| 21 |
+
and each tile's owner is resolved inline (``_resolve_tile_inline``) — no padded grid, no
|
| 22 |
+
per-tile binary search, no host-side tile-offset precompute.
|
| 23 |
+
|
| 24 |
+
Recipe-named to mirror ``fused_batched``: ``w8a8_block_dynamic_fp8_moe_grouped`` (128x128
|
| 25 |
+
block scales, FP8 intermediate) and ``mxfp_dynamic_moe_grouped`` (MXFP4/MXFP8, UE8M0
|
| 26 |
+
group-32 scales, MXFP8 intermediate); ``moe_fused_grouped`` is the neutral dispatcher.
|
| 27 |
+
"""
|
| 28 |
+
|
| 29 |
+
import torch
|
| 30 |
+
import triton
|
| 31 |
+
import triton.language as tl
|
| 32 |
+
from triton.tools.tensor_descriptor import TensorDescriptor
|
| 33 |
+
|
| 34 |
+
from .bayesian_autotuner import bayesian_autotune
|
| 35 |
+
from .grouped import store_tile
|
| 36 |
+
from .utils import (
|
| 37 |
+
FP8_DTYPE,
|
| 38 |
+
MX_SCALE_GROUP_K,
|
| 39 |
+
NIBBLES_PER_BYTE,
|
| 40 |
+
decode_ue8m0_scale,
|
| 41 |
+
device_context,
|
| 42 |
+
sm_count,
|
| 43 |
+
fp8_act_quant_inline,
|
| 44 |
+
topk_reduce_kernel,
|
| 45 |
+
TOPK_REDUCE_BLOCK_H,
|
| 46 |
+
get_accelerator_autotuning_configs,
|
| 47 |
+
get_mxfp_autotuning_configs,
|
| 48 |
+
is_mxfp,
|
| 49 |
+
is_mxfp4,
|
| 50 |
+
mxfp_act_quant_inline,
|
| 51 |
+
mx_compute,
|
| 52 |
+
smem_config_pruner,
|
| 53 |
+
glu,
|
| 54 |
+
e2m1_as_uint8,
|
| 55 |
+
ue8m0_as_uint8,
|
| 56 |
+
)
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
# ── Persistent tile scheduling (register-resident expert-tile layout) ─────────
|
| 60 |
+
|
| 61 |
+
# Flat-slot tile per program for the O(S) routing kernels (count + scatter). These are small
|
| 62 |
+
# latency-bound atomic kernels that want many programs: a sweep over {256..4096} x prefill shapes
|
| 63 |
+
# put 256 best (or within ~1%) for both, with 1024 up to ~1.5x slower. The grid derives from it
|
| 64 |
+
# so the two can't drift. Power of 2.
|
| 65 |
+
_ROUTING_BLOCK_SIZE = 256
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
@triton.jit
|
| 69 |
+
def _build_tile_layout(
|
| 70 |
+
ExpertStart, NUM_EXPERTS: tl.constexpr, BLOCK_SIZE_M: tl.constexpr
|
| 71 |
+
):
|
| 72 |
+
"""Load ``expert_start`` once and derive the per-BM tile layout vectors (kept in
|
| 73 |
+
registers for the whole persistent loop): per-expert first sorted row, token count,
|
| 74 |
+
exclusive tile-start cumsum, and the total M-tile count. ``ExpertStart`` is
|
| 75 |
+
``(NUM_EXPERTS + 1,)`` with a trailing ``S`` sentinel (``expert_start[E] == S``)."""
|
| 76 |
+
e_offs = tl.arange(0, NUM_EXPERTS)
|
| 77 |
+
exp_start = tl.load(ExpertStart + e_offs)
|
| 78 |
+
exp_end = tl.load(ExpertStart + e_offs + 1)
|
| 79 |
+
freqs = exp_end - exp_start
|
| 80 |
+
tiles_per_e = (freqs + BLOCK_SIZE_M - 1) // BLOCK_SIZE_M
|
| 81 |
+
tile_start_excl = (
|
| 82 |
+
tl.cumsum(tiles_per_e, 0) - tiles_per_e
|
| 83 |
+
) # first tile index of expert e
|
| 84 |
+
total_m_tiles = tl.sum(tiles_per_e, 0)
|
| 85 |
+
return exp_start, freqs, tile_start_excl, total_m_tiles, e_offs
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
@triton.jit
|
| 89 |
+
def _resolve_tile_inline(
|
| 90 |
+
pid_m, exp_start, freqs, tile_start_excl, e_offs, BLOCK_SIZE_M: tl.constexpr
|
| 91 |
+
):
|
| 92 |
+
"""Map an M-tile id to its owning expert + the tile's sorted row range, from the
|
| 93 |
+
register-resident layout (no global loads). Returns ``(expert_id, sorted_indices,
|
| 94 |
+
row_mask)``."""
|
| 95 |
+
# Bucketize via the exclusive tile cumsum: #experts whose tile-start <= pid_m, minus 1.
|
| 96 |
+
expert_id = tl.sum((tile_start_excl <= pid_m).to(tl.int32), 0) - 1
|
| 97 |
+
sel = (
|
| 98 |
+
e_offs == expert_id
|
| 99 |
+
) # scalar-index the E-vectors via mask-sum (no dynamic index)
|
| 100 |
+
e_start = tl.sum(tl.where(sel, exp_start, 0), 0)
|
| 101 |
+
e_tile_start = tl.sum(tl.where(sel, tile_start_excl, 0), 0)
|
| 102 |
+
freq = tl.sum(tl.where(sel, freqs, 0), 0)
|
| 103 |
+
within = pid_m - e_tile_start
|
| 104 |
+
m_start = e_start + within * BLOCK_SIZE_M
|
| 105 |
+
offs = tl.arange(0, BLOCK_SIZE_M)
|
| 106 |
+
row_mask = offs < freq - within * BLOCK_SIZE_M
|
| 107 |
+
sorted_indices = tl.max_contiguous(m_start + offs, BLOCK_SIZE_M)
|
| 108 |
+
return expert_id, sorted_indices, row_mask
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
@triton.jit
|
| 112 |
+
def _exclusive_offsets_kernel(
|
| 113 |
+
ExpertFreq, ExpertStart, Counters, NUM_EXPERTS: tl.constexpr
|
| 114 |
+
):
|
| 115 |
+
"""Exclusive cumsum of per-expert token counts → ``expert_start`` (leading 0, trailing
|
| 116 |
+
S), and zero the scatter counters — one launch."""
|
| 117 |
+
offs = tl.arange(0, NUM_EXPERTS)
|
| 118 |
+
incl = tl.cumsum(tl.load(ExpertFreq + offs), 0)
|
| 119 |
+
tl.store(ExpertStart, 0)
|
| 120 |
+
tl.store(ExpertStart + 1 + offs, incl)
|
| 121 |
+
tl.store(Counters + offs, tl.zeros([NUM_EXPERTS], tl.int32))
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
@triton.jit
|
| 125 |
+
def _scatter_kernel(
|
| 126 |
+
ExpertIds,
|
| 127 |
+
Perm,
|
| 128 |
+
PermToken,
|
| 129 |
+
ExpertStart,
|
| 130 |
+
Counters,
|
| 131 |
+
S,
|
| 132 |
+
NUM_TOP_K: tl.constexpr,
|
| 133 |
+
BLOCK_SIZE: tl.constexpr,
|
| 134 |
+
):
|
| 135 |
+
"""Counting-sort scatter: each flat slot atomically claims the next slot of its expert
|
| 136 |
+
(``expert_start[e] + counter[e]++``). O(S), replaces an O(S·logS) argsort. Within-expert
|
| 137 |
+
order is arbitrary (atomic race) — fine, the per-token reduce is order-invariant."""
|
| 138 |
+
offs = tl.program_id(0) * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE)
|
| 139 |
+
mask = offs < S
|
| 140 |
+
expert_id = tl.load(ExpertIds + offs, mask=mask, other=0)
|
| 141 |
+
dest = tl.load(ExpertStart + expert_id, mask=mask, other=0) + tl.atomic_add(
|
| 142 |
+
Counters + expert_id, 1, mask=mask
|
| 143 |
+
)
|
| 144 |
+
tl.store(Perm + dest, offs, mask=mask)
|
| 145 |
+
tl.store(PermToken + dest, offs // NUM_TOP_K, mask=mask)
|
| 146 |
+
|
| 147 |
+
|
| 148 |
+
@triton.jit
|
| 149 |
+
def _count_kernel(
|
| 150 |
+
ExpertIds, ExpertFreq, S, NUM_EXPERTS: tl.constexpr, BLOCK_SIZE: tl.constexpr
|
| 151 |
+
):
|
| 152 |
+
"""Per-expert token count via atomics — replaces ``torch.histc`` (no float cast), fixed
|
| 153 |
+
``(NUM_EXPERTS,)`` output stays CUDA-graph friendly. ``ExpertFreq`` is pre-zeroed."""
|
| 154 |
+
offs = tl.program_id(0) * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE)
|
| 155 |
+
mask = offs < S
|
| 156 |
+
expert_id = tl.load(ExpertIds + offs, mask=mask, other=NUM_EXPERTS)
|
| 157 |
+
tl.atomic_add(ExpertFreq + expert_id, 1, mask=mask & (expert_id < NUM_EXPERTS))
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
def _grouped_routing(expert_ids: torch.Tensor, num_experts: int, num_top_k: int):
|
| 161 |
+
"""On-device routing: expert-sorted index (no copy of the activations) via two Triton
|
| 162 |
+
launches — exclusive offsets + an atomic counting-sort scatter (replaces host
|
| 163 |
+
``argsort``). Returns ``(perm_token, perm, expert_start, num_experts, num_routed_tokens)``.
|
| 164 |
+
``perm`` is the sorted-position → flat
|
| 165 |
+
``(t*K + j)`` map (gate_up gathers via ``perm_token = perm // K``, down scatters via
|
| 166 |
+
``perm``); ``expert_start`` is ``(E+1,)`` padded with S so the kernels build the tile
|
| 167 |
+
layout in-register (E is a power of 2)."""
|
| 168 |
+
device = expert_ids.device
|
| 169 |
+
expert_ids = expert_ids.int()
|
| 170 |
+
num_routed_tokens = expert_ids.numel() # S = num_tokens * num_top_k
|
| 171 |
+
expert_freq = torch.zeros(num_experts, dtype=torch.int32, device=device)
|
| 172 |
+
expert_start = torch.empty(num_experts + 1, dtype=torch.int32, device=device)
|
| 173 |
+
counters = torch.empty(num_experts, dtype=torch.int32, device=device)
|
| 174 |
+
perm = torch.empty(num_routed_tokens, dtype=torch.int32, device=device)
|
| 175 |
+
perm_token = torch.empty(num_routed_tokens, dtype=torch.int32, device=device)
|
| 176 |
+
with device_context(device):
|
| 177 |
+
_count_kernel[(triton.cdiv(num_routed_tokens, _ROUTING_BLOCK_SIZE),)](
|
| 178 |
+
expert_ids,
|
| 179 |
+
expert_freq,
|
| 180 |
+
num_routed_tokens,
|
| 181 |
+
NUM_EXPERTS=num_experts,
|
| 182 |
+
BLOCK_SIZE=_ROUTING_BLOCK_SIZE,
|
| 183 |
+
)
|
| 184 |
+
_exclusive_offsets_kernel[(1,)](
|
| 185 |
+
expert_freq,
|
| 186 |
+
expert_start,
|
| 187 |
+
counters,
|
| 188 |
+
NUM_EXPERTS=num_experts,
|
| 189 |
+
)
|
| 190 |
+
_scatter_kernel[(triton.cdiv(num_routed_tokens, _ROUTING_BLOCK_SIZE),)](
|
| 191 |
+
expert_ids,
|
| 192 |
+
perm,
|
| 193 |
+
perm_token,
|
| 194 |
+
expert_start,
|
| 195 |
+
counters,
|
| 196 |
+
num_routed_tokens,
|
| 197 |
+
NUM_TOP_K=num_top_k,
|
| 198 |
+
BLOCK_SIZE=_ROUTING_BLOCK_SIZE,
|
| 199 |
+
)
|
| 200 |
+
return perm_token, perm, expert_start, num_experts, num_routed_tokens
|
| 201 |
+
|
| 202 |
+
|
| 203 |
+
# MX MoE autotune axes: the MMA flavor and the weight-load mechanism. The kernels implement all
|
| 204 |
+
# three memory modes; get_mxfp_autotuning_configs drops the device-inapplicable descriptor
|
| 205 |
+
# flavor (host on XPU, device on CUDA).
|
| 206 |
+
_MX_COMPUTE_MODES = ("dot_scaled", "dot")
|
| 207 |
+
_MX_MEMORY_MODES = ("host_descriptor", "device_descriptor", "pointer")
|
| 208 |
+
|
| 209 |
+
|
| 210 |
+
# ── Block-dynamic FP8 ────────────────────────────────────────────────────────
|
| 211 |
+
|
| 212 |
+
|
| 213 |
+
@bayesian_autotune(
|
| 214 |
+
get_accelerator_autotuning_configs(tune_block_m=True),
|
| 215 |
+
["INTERMEDIATE_DIM", "HIDDEN_DIM", "tokens_per_sm_bit_length"],
|
| 216 |
+
n_trials=60,
|
| 217 |
+
)
|
| 218 |
+
@triton.jit
|
| 219 |
+
def w8a8_block_dynamic_fp8_moe_grouped_gate_up_kernel(
|
| 220 |
+
Hidden, # (T, H) raw activations, UNSORTED
|
| 221 |
+
PermToken, # (S,) int32 — sorted position -> source token id
|
| 222 |
+
GateUp, # (E, 2I, H) FP8
|
| 223 |
+
GateUpScale, # (E, 2I//bn, H//bk) UE8M0 block scales
|
| 224 |
+
ExpertStart, # (NUM_EXPERTS+1,) int32 — exclusive sorted-row start per expert (pad S)
|
| 225 |
+
Inter, # (S, I) FP8 — output (expert-sorted)
|
| 226 |
+
InterScale, # (S, NUM_I_TILES) fp32 — per-row, per-I-tile activation scale
|
| 227 |
+
stride_h_t,
|
| 228 |
+
stride_h_k,
|
| 229 |
+
stride_gu_e,
|
| 230 |
+
stride_gu_n,
|
| 231 |
+
stride_gu_k,
|
| 232 |
+
stride_gus_e,
|
| 233 |
+
stride_gus_n,
|
| 234 |
+
stride_gus_k,
|
| 235 |
+
stride_int_m,
|
| 236 |
+
stride_int_n,
|
| 237 |
+
stride_is_m,
|
| 238 |
+
stride_is_n,
|
| 239 |
+
stride_pt,
|
| 240 |
+
tokens_per_sm_bit_length, # autotune key only (log2 tokens-per-SM bucket); unused in body
|
| 241 |
+
HIDDEN_DIM: tl.constexpr,
|
| 242 |
+
INTERMEDIATE_DIM: tl.constexpr,
|
| 243 |
+
BLOCK_SIZE_M: tl.constexpr,
|
| 244 |
+
BLOCK_SIZE_N: tl.constexpr,
|
| 245 |
+
BLOCK_SIZE_K: tl.constexpr,
|
| 246 |
+
NUM_EXPERTS: tl.constexpr,
|
| 247 |
+
NUM_SMS: tl.constexpr,
|
| 248 |
+
SIMULATE_UNFUSED: tl.constexpr,
|
| 249 |
+
ACT_FN: tl.constexpr,
|
| 250 |
+
):
|
| 251 |
+
"""Phase 1: persistent grid-stride over (M-tile, I-tile). Gather hidden rows per expert
|
| 252 |
+
M-tile, gate + up block-FP8 matmuls, SiLU-combine, FP8-requant the intermediate."""
|
| 253 |
+
start_pid = tl.program_id(axis=0)
|
| 254 |
+
exp_start, freqs, tile_start_excl, total_m_tiles, e_offs = _build_tile_layout(
|
| 255 |
+
ExpertStart, NUM_EXPERTS, BLOCK_SIZE_M
|
| 256 |
+
)
|
| 257 |
+
num_n_tiles = tl.cdiv(INTERMEDIATE_DIM, BLOCK_SIZE_N)
|
| 258 |
+
offs_k = tl.arange(0, BLOCK_SIZE_K)
|
| 259 |
+
for tile_id in tl.range(start_pid, total_m_tiles * num_n_tiles, NUM_SMS):
|
| 260 |
+
pid_m = tile_id // num_n_tiles
|
| 261 |
+
pid_n = tile_id % num_n_tiles
|
| 262 |
+
expert_id, offs_global_m, row_mask = _resolve_tile_inline(
|
| 263 |
+
pid_m, exp_start, freqs, tile_start_excl, e_offs, BLOCK_SIZE_M
|
| 264 |
+
)
|
| 265 |
+
offs_bn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
|
| 266 |
+
|
| 267 |
+
token = tl.load(PermToken + offs_global_m * stride_pt, mask=row_mask, other=0)
|
| 268 |
+
a_ptrs = Hidden + token[:, None] * stride_h_t + offs_k[None, :] * stride_h_k
|
| 269 |
+
gate_ptr = (
|
| 270 |
+
GateUp
|
| 271 |
+
+ expert_id * stride_gu_e
|
| 272 |
+
+ tl.arange(0, BLOCK_SIZE_K)[:, None] * stride_gu_k
|
| 273 |
+
+ (pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N))[None, :] * stride_gu_n
|
| 274 |
+
)
|
| 275 |
+
up_ptr = (
|
| 276 |
+
GateUp
|
| 277 |
+
+ expert_id * stride_gu_e
|
| 278 |
+
+ tl.arange(0, BLOCK_SIZE_K)[:, None] * stride_gu_k
|
| 279 |
+
+ (INTERMEDIATE_DIM + pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N))[
|
| 280 |
+
None, :
|
| 281 |
+
]
|
| 282 |
+
* stride_gu_n
|
| 283 |
+
)
|
| 284 |
+
gate_s_ptr = GateUpScale + expert_id * stride_gus_e + pid_n * stride_gus_n
|
| 285 |
+
up_s_ptr = (
|
| 286 |
+
GateUpScale
|
| 287 |
+
+ expert_id * stride_gus_e
|
| 288 |
+
+ (num_n_tiles + pid_n) * stride_gus_n
|
| 289 |
+
)
|
| 290 |
+
|
| 291 |
+
acc_gate = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
| 292 |
+
acc_up = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
| 293 |
+
for _ in range(0, tl.cdiv(HIDDEN_DIM, BLOCK_SIZE_K)):
|
| 294 |
+
a_raw = tl.load(a_ptrs, mask=row_mask[:, None], other=0.0).to(tl.float32)
|
| 295 |
+
a, a_s = fp8_act_quant_inline(a_raw)
|
| 296 |
+
w_gate = tl.load(gate_ptr)
|
| 297 |
+
w_up = tl.load(up_ptr)
|
| 298 |
+
w_s_gate = decode_ue8m0_scale(tl.load(gate_s_ptr))
|
| 299 |
+
w_s_up = decode_ue8m0_scale(tl.load(up_s_ptr))
|
| 300 |
+
acc_gate += tl.dot(a, w_gate) * a_s[:, None] * w_s_gate
|
| 301 |
+
acc_up += tl.dot(a, w_up) * a_s[:, None] * w_s_up
|
| 302 |
+
a_ptrs += BLOCK_SIZE_K * stride_h_k
|
| 303 |
+
gate_ptr += BLOCK_SIZE_K * stride_gu_k
|
| 304 |
+
up_ptr += BLOCK_SIZE_K * stride_gu_k
|
| 305 |
+
gate_s_ptr += stride_gus_k
|
| 306 |
+
up_s_ptr += stride_gus_k
|
| 307 |
+
|
| 308 |
+
intermediate = glu(
|
| 309 |
+
acc_gate, acc_up, ACT_FN, SIMULATE_UNFUSED, Hidden.dtype.element_ty
|
| 310 |
+
)
|
| 311 |
+
inter, inter_s = fp8_act_quant_inline(
|
| 312 |
+
intermediate
|
| 313 |
+
) # FP8 requant, per-row scale
|
| 314 |
+
int_ptrs = (
|
| 315 |
+
Inter
|
| 316 |
+
+ offs_global_m[:, None] * stride_int_m
|
| 317 |
+
+ offs_bn[None, :] * stride_int_n
|
| 318 |
+
)
|
| 319 |
+
tl.store(int_ptrs, inter, mask=row_mask[:, None])
|
| 320 |
+
tl.store(
|
| 321 |
+
InterScale + offs_global_m * stride_is_m + pid_n * stride_is_n,
|
| 322 |
+
inter_s,
|
| 323 |
+
mask=row_mask,
|
| 324 |
+
)
|
| 325 |
+
|
| 326 |
+
|
| 327 |
+
@bayesian_autotune(
|
| 328 |
+
get_accelerator_autotuning_configs(tune_block_m=True),
|
| 329 |
+
["INTERMEDIATE_DIM", "HIDDEN_DIM", "tokens_per_sm_bit_length"],
|
| 330 |
+
n_trials=60,
|
| 331 |
+
)
|
| 332 |
+
@triton.jit
|
| 333 |
+
def w8a8_block_dynamic_fp8_moe_grouped_down_kernel(
|
| 334 |
+
Inter, # (S, I) FP8 — expert-sorted intermediate
|
| 335 |
+
InterScale, # (S, NUM_I_TILES) fp32 — per-row, per-I-tile scale
|
| 336 |
+
Down, # (E, H, I) FP8
|
| 337 |
+
DownScale, # (E, H//bn, I//bk) UE8M0 block scales
|
| 338 |
+
ExpertStart, # (NUM_EXPERTS+1,) int32 — exclusive sorted-row start per expert (pad S)
|
| 339 |
+
Perm, # (S,) int32 — sorted position -> flat (token, slot) row
|
| 340 |
+
SampleWeights, # (S,) routing weight per flat row
|
| 341 |
+
ProjOut, # (S, H) bf16, flat (token, slot) order
|
| 342 |
+
stride_int_m,
|
| 343 |
+
stride_int_n,
|
| 344 |
+
stride_is_m,
|
| 345 |
+
stride_is_n,
|
| 346 |
+
stride_down_e,
|
| 347 |
+
stride_down_h,
|
| 348 |
+
stride_down_i,
|
| 349 |
+
stride_downs_e,
|
| 350 |
+
stride_downs_h,
|
| 351 |
+
stride_downs_i,
|
| 352 |
+
stride_po_m,
|
| 353 |
+
stride_po_n,
|
| 354 |
+
stride_perm,
|
| 355 |
+
tokens_per_sm_bit_length, # autotune key only (log2 tokens-per-SM bucket); unused in body
|
| 356 |
+
HIDDEN_DIM: tl.constexpr,
|
| 357 |
+
INTERMEDIATE_DIM: tl.constexpr,
|
| 358 |
+
NUM_I_TILES: tl.constexpr,
|
| 359 |
+
BLOCK_SIZE_M: tl.constexpr,
|
| 360 |
+
BLOCK_SIZE_H: tl.constexpr,
|
| 361 |
+
BLOCK_SIZE_I: tl.constexpr,
|
| 362 |
+
NUM_EXPERTS: tl.constexpr,
|
| 363 |
+
NUM_SMS: tl.constexpr,
|
| 364 |
+
SIMULATE_UNFUSED: tl.constexpr,
|
| 365 |
+
):
|
| 366 |
+
"""Phase 2: persistent grid-stride over (M-tile, H-tile). Grouped down over the FP8
|
| 367 |
+
intermediate, then routing-weight × scatter to the flat (token, slot) row."""
|
| 368 |
+
start_pid = tl.program_id(axis=0)
|
| 369 |
+
exp_start, freqs, tile_start_excl, total_m_tiles, e_offs = _build_tile_layout(
|
| 370 |
+
ExpertStart, NUM_EXPERTS, BLOCK_SIZE_M
|
| 371 |
+
)
|
| 372 |
+
num_h_tiles = tl.cdiv(HIDDEN_DIM, BLOCK_SIZE_H)
|
| 373 |
+
offs_i = tl.arange(0, BLOCK_SIZE_I)
|
| 374 |
+
for tile_id in tl.range(start_pid, total_m_tiles * num_h_tiles, NUM_SMS):
|
| 375 |
+
pid_m = tile_id // num_h_tiles
|
| 376 |
+
pid_h = tile_id % num_h_tiles
|
| 377 |
+
expert_id, offs_global_m, row_mask = _resolve_tile_inline(
|
| 378 |
+
pid_m, exp_start, freqs, tile_start_excl, e_offs, BLOCK_SIZE_M
|
| 379 |
+
)
|
| 380 |
+
offs_h = pid_h * BLOCK_SIZE_H + tl.arange(0, BLOCK_SIZE_H)
|
| 381 |
+
|
| 382 |
+
w_down_ptr = (
|
| 383 |
+
Down
|
| 384 |
+
+ expert_id * stride_down_e
|
| 385 |
+
+ tl.arange(0, BLOCK_SIZE_I)[:, None] * stride_down_i
|
| 386 |
+
+ (pid_h * BLOCK_SIZE_H + tl.arange(0, BLOCK_SIZE_H))[None, :]
|
| 387 |
+
* stride_down_h
|
| 388 |
+
)
|
| 389 |
+
ws_down_ptr = DownScale + expert_id * stride_downs_e + pid_h * stride_downs_h
|
| 390 |
+
|
| 391 |
+
acc = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_H), dtype=tl.float32)
|
| 392 |
+
for i_tile in range(0, NUM_I_TILES):
|
| 393 |
+
i_off = i_tile * BLOCK_SIZE_I
|
| 394 |
+
inter = tl.load(
|
| 395 |
+
Inter
|
| 396 |
+
+ offs_global_m[:, None] * stride_int_m
|
| 397 |
+
+ (i_off + offs_i)[None, :] * stride_int_n,
|
| 398 |
+
mask=row_mask[:, None],
|
| 399 |
+
other=0.0,
|
| 400 |
+
)
|
| 401 |
+
inter_s = tl.load(
|
| 402 |
+
InterScale + offs_global_m * stride_is_m + i_tile * stride_is_n,
|
| 403 |
+
mask=row_mask,
|
| 404 |
+
other=0.0,
|
| 405 |
+
)
|
| 406 |
+
w_s_down = decode_ue8m0_scale(tl.load(ws_down_ptr))
|
| 407 |
+
w_down = tl.load(w_down_ptr)
|
| 408 |
+
acc += tl.dot(inter, w_down) * inter_s[:, None] * w_s_down
|
| 409 |
+
w_down_ptr += BLOCK_SIZE_I * stride_down_i
|
| 410 |
+
ws_down_ptr += stride_downs_i
|
| 411 |
+
|
| 412 |
+
if SIMULATE_UNFUSED:
|
| 413 |
+
acc = acc.to(ProjOut.dtype.element_ty).to(tl.float32)
|
| 414 |
+
# Fused routing-weight × top-k reorder (no atomics): scale each row by its weight
|
| 415 |
+
# and scatter to its flat (token, slot) row; the host then just sums over slots.
|
| 416 |
+
flat = tl.load(Perm + offs_global_m * stride_perm, mask=row_mask, other=0)
|
| 417 |
+
weight = tl.load(SampleWeights + flat, mask=row_mask, other=0.0)
|
| 418 |
+
acc = acc * weight[:, None]
|
| 419 |
+
store_tile(ProjOut, acc, flat, offs_h, row_mask, stride_po_m, stride_po_n)
|
| 420 |
+
|
| 421 |
+
|
| 422 |
+
def w8a8_block_dynamic_fp8_moe_grouped(
|
| 423 |
+
hidden_states: torch.Tensor, # (T, H)
|
| 424 |
+
top_k_index: torch.Tensor, # (T, K) int
|
| 425 |
+
top_k_weights: torch.Tensor, # (T, K)
|
| 426 |
+
gate_up_proj: torch.Tensor, # (E, 2I, H) FP8
|
| 427 |
+
down_proj: torch.Tensor, # (E, H, I) FP8
|
| 428 |
+
gate_up_proj_scale: torch.Tensor,
|
| 429 |
+
down_proj_scale: torch.Tensor,
|
| 430 |
+
block_size: list[int],
|
| 431 |
+
simulate_unfused: bool = False,
|
| 432 |
+
act_fn: str = "silu",
|
| 433 |
+
) -> torch.Tensor:
|
| 434 |
+
"""Block-dynamic FP8 fused grouped MoE: gather gate_up+SiLU → FP8 intermediate →
|
| 435 |
+
grouped down → routing-weighted top-k reduce. Returns ``(num_tokens, hidden_dim)``."""
|
| 436 |
+
device = hidden_states.device
|
| 437 |
+
num_tokens = hidden_states.size(0)
|
| 438 |
+
num_top_k = top_k_index.size(-1)
|
| 439 |
+
HIDDEN_DIM = hidden_states.size(1)
|
| 440 |
+
INTERMEDIATE_DIM = down_proj.size(2)
|
| 441 |
+
BLOCK_SIZE_N, BLOCK_SIZE_K = block_size
|
| 442 |
+
NUM_I_TILES = INTERMEDIATE_DIM // BLOCK_SIZE_N
|
| 443 |
+
|
| 444 |
+
perm_token, perm, expert_start, NUM_EXPERTS, num_routed_tokens = _grouped_routing(
|
| 445 |
+
top_k_index, gate_up_proj.size(0), num_top_k
|
| 446 |
+
)
|
| 447 |
+
num_sms = sm_count(device.index)
|
| 448 |
+
tokens_per_sm_bit_length = (num_routed_tokens // num_sms).bit_length()
|
| 449 |
+
|
| 450 |
+
gate_up_scale_u8 = ue8m0_as_uint8(gate_up_proj_scale)
|
| 451 |
+
down_scale_u8 = ue8m0_as_uint8(down_proj_scale)
|
| 452 |
+
|
| 453 |
+
inter = torch.empty(
|
| 454 |
+
num_routed_tokens, INTERMEDIATE_DIM, device=device, dtype=FP8_DTYPE
|
| 455 |
+
)
|
| 456 |
+
inter_scale = torch.empty(
|
| 457 |
+
num_routed_tokens, NUM_I_TILES, device=device, dtype=torch.float32
|
| 458 |
+
)
|
| 459 |
+
out = torch.empty(
|
| 460 |
+
num_routed_tokens, HIDDEN_DIM, device=device, dtype=hidden_states.dtype
|
| 461 |
+
)
|
| 462 |
+
reduced = torch.empty(
|
| 463 |
+
num_tokens, HIDDEN_DIM, device=device, dtype=hidden_states.dtype
|
| 464 |
+
)
|
| 465 |
+
with device_context(device):
|
| 466 |
+
w8a8_block_dynamic_fp8_moe_grouped_gate_up_kernel[(num_sms,)](
|
| 467 |
+
hidden_states,
|
| 468 |
+
perm_token,
|
| 469 |
+
gate_up_proj,
|
| 470 |
+
gate_up_scale_u8,
|
| 471 |
+
expert_start,
|
| 472 |
+
inter,
|
| 473 |
+
inter_scale,
|
| 474 |
+
hidden_states.stride(0),
|
| 475 |
+
hidden_states.stride(1),
|
| 476 |
+
gate_up_proj.stride(0),
|
| 477 |
+
gate_up_proj.stride(1),
|
| 478 |
+
gate_up_proj.stride(2),
|
| 479 |
+
gate_up_scale_u8.stride(0),
|
| 480 |
+
gate_up_scale_u8.stride(1),
|
| 481 |
+
gate_up_scale_u8.stride(2),
|
| 482 |
+
inter.stride(0),
|
| 483 |
+
inter.stride(1),
|
| 484 |
+
inter_scale.stride(0),
|
| 485 |
+
inter_scale.stride(1),
|
| 486 |
+
perm_token.stride(0),
|
| 487 |
+
tokens_per_sm_bit_length=tokens_per_sm_bit_length,
|
| 488 |
+
HIDDEN_DIM=HIDDEN_DIM,
|
| 489 |
+
INTERMEDIATE_DIM=INTERMEDIATE_DIM,
|
| 490 |
+
BLOCK_SIZE_N=BLOCK_SIZE_N,
|
| 491 |
+
BLOCK_SIZE_K=BLOCK_SIZE_K,
|
| 492 |
+
NUM_EXPERTS=NUM_EXPERTS,
|
| 493 |
+
NUM_SMS=num_sms,
|
| 494 |
+
SIMULATE_UNFUSED=simulate_unfused,
|
| 495 |
+
ACT_FN=act_fn,
|
| 496 |
+
)
|
| 497 |
+
w8a8_block_dynamic_fp8_moe_grouped_down_kernel[(num_sms,)](
|
| 498 |
+
inter,
|
| 499 |
+
inter_scale,
|
| 500 |
+
down_proj,
|
| 501 |
+
down_scale_u8,
|
| 502 |
+
expert_start,
|
| 503 |
+
perm,
|
| 504 |
+
top_k_weights,
|
| 505 |
+
out,
|
| 506 |
+
inter.stride(0),
|
| 507 |
+
inter.stride(1),
|
| 508 |
+
inter_scale.stride(0),
|
| 509 |
+
inter_scale.stride(1),
|
| 510 |
+
down_proj.stride(0),
|
| 511 |
+
down_proj.stride(1),
|
| 512 |
+
down_proj.stride(2),
|
| 513 |
+
down_scale_u8.stride(0),
|
| 514 |
+
down_scale_u8.stride(1),
|
| 515 |
+
down_scale_u8.stride(2),
|
| 516 |
+
out.stride(0),
|
| 517 |
+
out.stride(1),
|
| 518 |
+
perm.stride(0),
|
| 519 |
+
tokens_per_sm_bit_length=tokens_per_sm_bit_length,
|
| 520 |
+
HIDDEN_DIM=HIDDEN_DIM,
|
| 521 |
+
INTERMEDIATE_DIM=INTERMEDIATE_DIM,
|
| 522 |
+
NUM_I_TILES=NUM_I_TILES,
|
| 523 |
+
BLOCK_SIZE_H=BLOCK_SIZE_N,
|
| 524 |
+
BLOCK_SIZE_I=BLOCK_SIZE_K,
|
| 525 |
+
NUM_EXPERTS=NUM_EXPERTS,
|
| 526 |
+
NUM_SMS=num_sms,
|
| 527 |
+
SIMULATE_UNFUSED=simulate_unfused,
|
| 528 |
+
)
|
| 529 |
+
topk_reduce_kernel[(num_tokens, triton.cdiv(HIDDEN_DIM, TOPK_REDUCE_BLOCK_H))](
|
| 530 |
+
out,
|
| 531 |
+
reduced,
|
| 532 |
+
HIDDEN_DIM,
|
| 533 |
+
out.stride(0),
|
| 534 |
+
out.stride(1),
|
| 535 |
+
reduced.stride(0),
|
| 536 |
+
reduced.stride(1),
|
| 537 |
+
NUM_TOP_K=num_top_k,
|
| 538 |
+
BLOCK_H=TOPK_REDUCE_BLOCK_H,
|
| 539 |
+
)
|
| 540 |
+
|
| 541 |
+
return reduced
|
| 542 |
+
|
| 543 |
+
|
| 544 |
+
# ── MXFP4/MXFP8 (UE8M0 group-32, tunable tile, MXFP8 intermediate) ────────────
|
| 545 |
+
|
| 546 |
+
|
| 547 |
+
def _set_gate_up_descriptor(nargs):
|
| 548 |
+
"""Per-config pre_hook: set the gate_up TMA descriptor box to the autotuned
|
| 549 |
+
[2 (gate|up), BLOCK_SIZE_N, BK//VALUES_PER_BYTE] over the (2E, I, H/vpb) weight view.
|
| 550 |
+
|
| 551 |
+
MUST mutate ``block_shape`` in place, not rebind ``nargs[...]`` to a fresh descriptor:
|
| 552 |
+
the autotuner launches with the original ``*args`` object, so a rebind never reaches the
|
| 553 |
+
kernel (the placeholder box survives → all but BN=32,BK=32 configs fail the reshape). An
|
| 554 |
+
in-place edit of the passed object propagates to both compile-time specialization and the
|
| 555 |
+
launch-time tensor-map fill. ``shape``/``strides`` are already correct from the wrapper's
|
| 556 |
+
``from_tensor`` placeholder; only the per-config box changes. No-op unless MEMORY_MODE is host_descriptor (pointer /
|
| 557 |
+
device_descriptor don't use the host-built descriptor)."""
|
| 558 |
+
if nargs["MEMORY_MODE"] != "host_descriptor":
|
| 559 |
+
return
|
| 560 |
+
nargs["GateUpDescriptor"].block_shape = [
|
| 561 |
+
2,
|
| 562 |
+
nargs["BLOCK_SIZE_N"],
|
| 563 |
+
nargs["BLOCK_SIZE_K"] // nargs["VALUES_PER_BYTE"],
|
| 564 |
+
]
|
| 565 |
+
|
| 566 |
+
|
| 567 |
+
@bayesian_autotune(
|
| 568 |
+
get_mxfp_autotuning_configs(
|
| 569 |
+
pre_hook=_set_gate_up_descriptor,
|
| 570 |
+
compute_modes=_MX_COMPUTE_MODES,
|
| 571 |
+
memory_modes=_MX_MEMORY_MODES,
|
| 572 |
+
tune_block_m=True,
|
| 573 |
+
), # prefill: no scalar; combined gate∪up dot ([2*BN,K] reshape) — TMA vs block-ptr load
|
| 574 |
+
["INTERMEDIATE_DIM", "HIDDEN_DIM", "tokens_per_sm_bit_length"],
|
| 575 |
+
n_trials=60,
|
| 576 |
+
# bf16 activation tile + fused gate|up weight tiles
|
| 577 |
+
prune_configs_by={
|
| 578 |
+
"early_config_prune": smem_config_pruner(act_bytes=2, n_weight_tiles=2)
|
| 579 |
+
},
|
| 580 |
+
)
|
| 581 |
+
@triton.jit
|
| 582 |
+
def mxfp_dynamic_moe_grouped_gate_up_kernel(
|
| 583 |
+
Hidden, # (T, H) raw activations, UNSORTED
|
| 584 |
+
PermToken, # (S,) int32 — sorted position -> source token id
|
| 585 |
+
GateUp, # (E, 2I, H//VALUES_PER_BYTE) MXFP4/MXFP8
|
| 586 |
+
GateUpScale, # (E, 2I, H//32) UE8M0 group-32 scales
|
| 587 |
+
GateUpDescriptor, # TMA descriptor over the (2E, I, H//VALUES_PER_BYTE) weight view
|
| 588 |
+
ExpertStart, # (NUM_EXPERTS+1,) int32 — exclusive sorted-row start per expert (pad S)
|
| 589 |
+
Inter, # (S, I) E4M3 — MXFP8 intermediate (expert-sorted)
|
| 590 |
+
InterScale, # (S, I//32) UE8M0 group-32 scales
|
| 591 |
+
stride_h_t,
|
| 592 |
+
stride_h_k,
|
| 593 |
+
stride_gu_e,
|
| 594 |
+
stride_gu_n,
|
| 595 |
+
stride_gu_k,
|
| 596 |
+
stride_gus_e,
|
| 597 |
+
stride_gus_n,
|
| 598 |
+
stride_gus_k,
|
| 599 |
+
stride_int_m,
|
| 600 |
+
stride_int_n,
|
| 601 |
+
stride_is_m,
|
| 602 |
+
stride_is_n,
|
| 603 |
+
stride_pt,
|
| 604 |
+
tokens_per_sm_bit_length, # autotune key only (log2 tokens-per-SM bucket); unused in body
|
| 605 |
+
HIDDEN_DIM: tl.constexpr,
|
| 606 |
+
INTERMEDIATE_DIM: tl.constexpr,
|
| 607 |
+
BLOCK_SIZE_M: tl.constexpr,
|
| 608 |
+
BLOCK_SIZE_N: tl.constexpr,
|
| 609 |
+
BLOCK_SIZE_K: tl.constexpr,
|
| 610 |
+
VALUES_PER_BYTE: tl.constexpr,
|
| 611 |
+
SCALE_GROUP_K: tl.constexpr,
|
| 612 |
+
NUM_EXPERTS: tl.constexpr,
|
| 613 |
+
NUM_SMS: tl.constexpr,
|
| 614 |
+
SIMULATE_UNFUSED: tl.constexpr,
|
| 615 |
+
COMPUTE_MODE: tl.constexpr,
|
| 616 |
+
MEMORY_MODE: tl.constexpr,
|
| 617 |
+
ACT_FN: tl.constexpr,
|
| 618 |
+
):
|
| 619 |
+
"""MXFP4/MXFP8 phase 1 (persistent): gather hidden rows per expert M-tile, gate + up MX
|
| 620 |
+
matmuls (``tl.dot_scaled`` or fp8 ``tl.dot`` + per-group rescale), SiLU, MXFP8-requant."""
|
| 621 |
+
start_pid = tl.program_id(axis=0)
|
| 622 |
+
exp_start, freqs, tile_start_excl, total_m_tiles, e_offs = _build_tile_layout(
|
| 623 |
+
ExpertStart, NUM_EXPERTS, BLOCK_SIZE_M
|
| 624 |
+
)
|
| 625 |
+
num_n_tiles = tl.cdiv(INTERMEDIATE_DIM, BLOCK_SIZE_N)
|
| 626 |
+
offs_k = tl.arange(0, BLOCK_SIZE_K)
|
| 627 |
+
for tile_id in tl.range(start_pid, total_m_tiles * num_n_tiles, NUM_SMS):
|
| 628 |
+
pid_m = tile_id // num_n_tiles
|
| 629 |
+
pid_n = tile_id % num_n_tiles
|
| 630 |
+
expert_id, offs_global_m, row_mask = _resolve_tile_inline(
|
| 631 |
+
pid_m, exp_start, freqs, tile_start_excl, e_offs, BLOCK_SIZE_M
|
| 632 |
+
)
|
| 633 |
+
offs_bn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
|
| 634 |
+
|
| 635 |
+
token = tl.load(PermToken + offs_global_m * stride_pt, mask=row_mask, other=0)
|
| 636 |
+
a_ptrs = Hidden + token[:, None] * stride_h_t + offs_k[None, :] * stride_h_k
|
| 637 |
+
n_off = pid_n * BLOCK_SIZE_N
|
| 638 |
+
|
| 639 |
+
# Load gate (row 2e) + up (row 2e+1) of the (2E, I, H) view as ONE combined tile, then
|
| 640 |
+
# split — this is the shared loop. MEMORY_MODE picks the LOAD only (decoupled from COMPUTE_MODE):
|
| 641 |
+
# a host/device descriptor (TMA on NVIDIA) vs explicit rank-3 pointers. COMPUTE_MODE picks the compute on the
|
| 642 |
+
# loaded tile: scaled-MMA (dot_scaled) or fp8 dot + per-group software rescale (dot).
|
| 643 |
+
gu_row = expert_id * 2
|
| 644 |
+
# (E, 2I, H//vpb) reinterpreted as (2E, I, H//vpb); host_descriptor uses the passed
|
| 645 |
+
# GateUpDescriptor, device_descriptor builds one in-kernel, pointer indexes it directly.
|
| 646 |
+
if MEMORY_MODE == "pointer":
|
| 647 |
+
gu_ptr = (
|
| 648 |
+
GateUp
|
| 649 |
+
+ (gu_row + tl.arange(0, 2))[:, None, None]
|
| 650 |
+
* (INTERMEDIATE_DIM * stride_gu_n)
|
| 651 |
+
+ (n_off + tl.arange(0, BLOCK_SIZE_N))[None, :, None] * stride_gu_n
|
| 652 |
+
+ tl.arange(0, BLOCK_SIZE_K // VALUES_PER_BYTE)[None, None, :]
|
| 653 |
+
* stride_gu_k
|
| 654 |
+
)
|
| 655 |
+
elif MEMORY_MODE == "device_descriptor":
|
| 656 |
+
gu_desc = tl.make_tensor_descriptor(
|
| 657 |
+
GateUp,
|
| 658 |
+
shape=(
|
| 659 |
+
2 * NUM_EXPERTS,
|
| 660 |
+
INTERMEDIATE_DIM,
|
| 661 |
+
HIDDEN_DIM // VALUES_PER_BYTE,
|
| 662 |
+
),
|
| 663 |
+
strides=(INTERMEDIATE_DIM * stride_gu_n, stride_gu_n, stride_gu_k),
|
| 664 |
+
block_shape=(2, BLOCK_SIZE_N, BLOCK_SIZE_K // VALUES_PER_BYTE),
|
| 665 |
+
)
|
| 666 |
+
gu_scale_ptr = (
|
| 667 |
+
GateUpScale
|
| 668 |
+
+ expert_id * stride_gus_e
|
| 669 |
+
+ tl.arange(0, 2)[:, None, None] * (INTERMEDIATE_DIM * stride_gus_n)
|
| 670 |
+
+ (n_off + tl.arange(0, BLOCK_SIZE_N))[None, :, None] * stride_gus_n
|
| 671 |
+
+ tl.arange(0, BLOCK_SIZE_K // SCALE_GROUP_K)[None, None, :] * stride_gus_k
|
| 672 |
+
)
|
| 673 |
+
acc = tl.zeros((BLOCK_SIZE_M, 2 * BLOCK_SIZE_N), dtype=tl.float32)
|
| 674 |
+
for k_off in tl.range(0, HIDDEN_DIM, BLOCK_SIZE_K):
|
| 675 |
+
a_raw = tl.load(a_ptrs, mask=row_mask[:, None], other=0.0).to(tl.float32)
|
| 676 |
+
a, a_scale = mxfp_act_quant_inline(
|
| 677 |
+
a_raw, BLOCK_SIZE_M, BLOCK_SIZE_K, SCALE_GROUP_K
|
| 678 |
+
)
|
| 679 |
+
if MEMORY_MODE == "host_descriptor":
|
| 680 |
+
gu = tl.reshape(
|
| 681 |
+
GateUpDescriptor.load([gu_row, n_off, k_off // VALUES_PER_BYTE]),
|
| 682 |
+
[2 * BLOCK_SIZE_N, BLOCK_SIZE_K // VALUES_PER_BYTE],
|
| 683 |
+
)
|
| 684 |
+
elif MEMORY_MODE == "device_descriptor":
|
| 685 |
+
gu = tl.reshape(
|
| 686 |
+
gu_desc.load([gu_row, n_off, k_off // VALUES_PER_BYTE]),
|
| 687 |
+
[2 * BLOCK_SIZE_N, BLOCK_SIZE_K // VALUES_PER_BYTE],
|
| 688 |
+
)
|
| 689 |
+
else:
|
| 690 |
+
gu = tl.reshape(
|
| 691 |
+
tl.load(gu_ptr),
|
| 692 |
+
[2 * BLOCK_SIZE_N, BLOCK_SIZE_K // VALUES_PER_BYTE],
|
| 693 |
+
)
|
| 694 |
+
gu_ptr += (BLOCK_SIZE_K // VALUES_PER_BYTE) * stride_gu_k
|
| 695 |
+
gu_scale = tl.reshape(
|
| 696 |
+
tl.load(gu_scale_ptr + (k_off // SCALE_GROUP_K) * stride_gus_k),
|
| 697 |
+
[2 * BLOCK_SIZE_N, BLOCK_SIZE_K // SCALE_GROUP_K],
|
| 698 |
+
)
|
| 699 |
+
gu_t = tl.trans(
|
| 700 |
+
gu
|
| 701 |
+
) # [BK, 2*BN] gate∪up weight; mx_compute decodes it for the fp8 path
|
| 702 |
+
acc = mx_compute(
|
| 703 |
+
acc,
|
| 704 |
+
a,
|
| 705 |
+
a_scale,
|
| 706 |
+
gu_t,
|
| 707 |
+
gu_scale,
|
| 708 |
+
COMPUTE_MODE,
|
| 709 |
+
VALUES_PER_BYTE,
|
| 710 |
+
BLOCK_SIZE_M,
|
| 711 |
+
BLOCK_SIZE_N,
|
| 712 |
+
BLOCK_SIZE_K,
|
| 713 |
+
SCALE_GROUP_K,
|
| 714 |
+
)
|
| 715 |
+
a_ptrs += BLOCK_SIZE_K * stride_h_k
|
| 716 |
+
acc_3d = tl.permute(tl.reshape(acc, [BLOCK_SIZE_M, 2, BLOCK_SIZE_N]), (0, 2, 1))
|
| 717 |
+
acc_gate, acc_up = tl.split(acc_3d)
|
| 718 |
+
|
| 719 |
+
intermediate = glu(
|
| 720 |
+
acc_gate, acc_up, ACT_FN, SIMULATE_UNFUSED, Hidden.dtype.element_ty
|
| 721 |
+
)
|
| 722 |
+
|
| 723 |
+
# MXFP8 requant of the intermediate (E4M3 + UE8M0 group-32 along this N-tile).
|
| 724 |
+
inter, inter_scale = mxfp_act_quant_inline(
|
| 725 |
+
intermediate, BLOCK_SIZE_M, BLOCK_SIZE_N, SCALE_GROUP_K
|
| 726 |
+
)
|
| 727 |
+
int_ptrs = (
|
| 728 |
+
Inter
|
| 729 |
+
+ offs_global_m[:, None] * stride_int_m
|
| 730 |
+
+ offs_bn[None, :] * stride_int_n
|
| 731 |
+
)
|
| 732 |
+
tl.store(int_ptrs, inter, mask=row_mask[:, None])
|
| 733 |
+
offs_sc = pid_n * (BLOCK_SIZE_N // SCALE_GROUP_K) + tl.arange(
|
| 734 |
+
0, BLOCK_SIZE_N // SCALE_GROUP_K
|
| 735 |
+
)
|
| 736 |
+
sc_ptrs = (
|
| 737 |
+
InterScale
|
| 738 |
+
+ offs_global_m[:, None] * stride_is_m
|
| 739 |
+
+ offs_sc[None, :] * stride_is_n
|
| 740 |
+
)
|
| 741 |
+
tl.store(sc_ptrs, inter_scale, mask=row_mask[:, None])
|
| 742 |
+
|
| 743 |
+
|
| 744 |
+
def _set_down_descriptor(nargs):
|
| 745 |
+
"""Per-config pre_hook: in-place set the down TMA descriptor box to
|
| 746 |
+
[BLOCK_SIZE_N, BK//VALUES_PER_BYTE] over the (E*H, I/vpb) weight view. In-place, not
|
| 747 |
+
rebind — see _set_gate_up_descriptor. No-op unless MEMORY_MODE is host_descriptor."""
|
| 748 |
+
if nargs["MEMORY_MODE"] != "host_descriptor":
|
| 749 |
+
return
|
| 750 |
+
nargs["DownDescriptor"].block_shape = [
|
| 751 |
+
nargs["BLOCK_SIZE_N"],
|
| 752 |
+
nargs["BLOCK_SIZE_K"] // nargs["VALUES_PER_BYTE"],
|
| 753 |
+
]
|
| 754 |
+
|
| 755 |
+
|
| 756 |
+
@bayesian_autotune(
|
| 757 |
+
get_mxfp_autotuning_configs(
|
| 758 |
+
pre_hook=_set_down_descriptor,
|
| 759 |
+
compute_modes=_MX_COMPUTE_MODES,
|
| 760 |
+
memory_modes=_MX_MEMORY_MODES,
|
| 761 |
+
tune_block_m=True,
|
| 762 |
+
), # prefill: no scalar; TMA vs block-ptr load
|
| 763 |
+
["INTERMEDIATE_DIM", "HIDDEN_DIM", "tokens_per_sm_bit_length"],
|
| 764 |
+
n_trials=60,
|
| 765 |
+
# fp8 intermediate activation tile + single down weight tile
|
| 766 |
+
prune_configs_by={
|
| 767 |
+
"early_config_prune": smem_config_pruner(act_bytes=1, n_weight_tiles=1)
|
| 768 |
+
},
|
| 769 |
+
)
|
| 770 |
+
@triton.jit
|
| 771 |
+
def mxfp_dynamic_moe_grouped_down_kernel(
|
| 772 |
+
Inter, # (S, I) E4M3 — MXFP8 intermediate (expert-sorted)
|
| 773 |
+
InterScale, # (S, I//32) UE8M0 group-32 scales
|
| 774 |
+
Down, # (E, H, I//VALUES_PER_BYTE) MXFP4/MXFP8
|
| 775 |
+
DownScale, # (E, H, I//32) UE8M0 group-32 scales
|
| 776 |
+
DownDescriptor, # TMA descriptor over the (E*H, I//VALUES_PER_BYTE) weight view
|
| 777 |
+
ExpertStart, # (NUM_EXPERTS+1,) int32 — exclusive sorted-row start per expert (pad S)
|
| 778 |
+
Perm, # (S,) int32 — sorted position -> flat (token, slot) row
|
| 779 |
+
SampleWeights, # (S,) routing weight per flat row
|
| 780 |
+
ProjOut, # (S, H) bf16, flat (token, slot) order
|
| 781 |
+
stride_int_m,
|
| 782 |
+
stride_int_n,
|
| 783 |
+
stride_is_m,
|
| 784 |
+
stride_is_n,
|
| 785 |
+
stride_down_e,
|
| 786 |
+
stride_down_n,
|
| 787 |
+
stride_down_k,
|
| 788 |
+
stride_downs_e,
|
| 789 |
+
stride_downs_n,
|
| 790 |
+
stride_downs_k,
|
| 791 |
+
stride_po_m,
|
| 792 |
+
stride_po_n,
|
| 793 |
+
stride_perm,
|
| 794 |
+
tokens_per_sm_bit_length, # autotune key only (log2 tokens-per-SM bucket); unused in body
|
| 795 |
+
HIDDEN_DIM: tl.constexpr,
|
| 796 |
+
INTERMEDIATE_DIM: tl.constexpr,
|
| 797 |
+
BLOCK_SIZE_M: tl.constexpr,
|
| 798 |
+
BLOCK_SIZE_N: tl.constexpr,
|
| 799 |
+
BLOCK_SIZE_K: tl.constexpr,
|
| 800 |
+
VALUES_PER_BYTE: tl.constexpr,
|
| 801 |
+
SCALE_GROUP_K: tl.constexpr,
|
| 802 |
+
NUM_EXPERTS: tl.constexpr,
|
| 803 |
+
NUM_SMS: tl.constexpr,
|
| 804 |
+
SIMULATE_UNFUSED: tl.constexpr,
|
| 805 |
+
COMPUTE_MODE: tl.constexpr,
|
| 806 |
+
MEMORY_MODE: tl.constexpr,
|
| 807 |
+
):
|
| 808 |
+
"""MXFP4/MXFP8 phase 2 (persistent): MXFP8 intermediate → down proj, then routing-weight
|
| 809 |
+
× scatter to the flat (token, slot) row. N = hidden tile, K = intermediate tile."""
|
| 810 |
+
start_pid = tl.program_id(axis=0)
|
| 811 |
+
exp_start, freqs, tile_start_excl, total_m_tiles, e_offs = _build_tile_layout(
|
| 812 |
+
ExpertStart, NUM_EXPERTS, BLOCK_SIZE_M
|
| 813 |
+
)
|
| 814 |
+
num_n_tiles = tl.cdiv(HIDDEN_DIM, BLOCK_SIZE_N)
|
| 815 |
+
offs_k = tl.arange(0, BLOCK_SIZE_K)
|
| 816 |
+
offs_sf = tl.arange(0, BLOCK_SIZE_K // SCALE_GROUP_K)
|
| 817 |
+
for tile_id in tl.range(start_pid, total_m_tiles * num_n_tiles, NUM_SMS):
|
| 818 |
+
pid_m = tile_id // num_n_tiles
|
| 819 |
+
pid_n = tile_id % num_n_tiles
|
| 820 |
+
expert_id, offs_global_m, row_mask = _resolve_tile_inline(
|
| 821 |
+
pid_m, exp_start, freqs, tile_start_excl, e_offs, BLOCK_SIZE_M
|
| 822 |
+
)
|
| 823 |
+
offs_bn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
|
| 824 |
+
|
| 825 |
+
a_ptrs = (
|
| 826 |
+
Inter
|
| 827 |
+
+ offs_global_m[:, None] * stride_int_m
|
| 828 |
+
+ offs_k[None, :] * stride_int_n
|
| 829 |
+
)
|
| 830 |
+
as_ptrs = (
|
| 831 |
+
InterScale
|
| 832 |
+
+ offs_global_m[:, None] * stride_is_m
|
| 833 |
+
+ offs_sf[None, :] * stride_is_n
|
| 834 |
+
)
|
| 835 |
+
n_off = pid_n * BLOCK_SIZE_N
|
| 836 |
+
ws_down_ptr = (
|
| 837 |
+
DownScale
|
| 838 |
+
+ expert_id * stride_downs_e
|
| 839 |
+
+ (n_off + tl.arange(0, BLOCK_SIZE_N))[:, None] * stride_downs_n
|
| 840 |
+
+ tl.arange(0, BLOCK_SIZE_K // SCALE_GROUP_K)[None, :] * stride_downs_k
|
| 841 |
+
)
|
| 842 |
+
|
| 843 |
+
# Down weight tile [BK//vpb, BN] loaded once per K-chunk: MEMORY_MODE picks the LOAD (a
|
| 844 |
+
# host/device descriptor over the (E*H, I//vpb) view vs explicit pointers), COMPUTE_MODE
|
| 845 |
+
# picks the compute. Scales stay a pointer load (a descriptor needs >=16B inner; the
|
| 846 |
+
# BK//32 row is too narrow).
|
| 847 |
+
if MEMORY_MODE == "pointer":
|
| 848 |
+
w_down_ptr = (
|
| 849 |
+
Down
|
| 850 |
+
+ expert_id * stride_down_e
|
| 851 |
+
+ tl.arange(0, BLOCK_SIZE_K // VALUES_PER_BYTE)[:, None] * stride_down_k
|
| 852 |
+
+ (n_off + tl.arange(0, BLOCK_SIZE_N))[None, :] * stride_down_n
|
| 853 |
+
)
|
| 854 |
+
elif MEMORY_MODE == "device_descriptor":
|
| 855 |
+
down_desc = tl.make_tensor_descriptor(
|
| 856 |
+
Down, # (E, H, I//vpb) flattened to the (E*H, I//vpb) view
|
| 857 |
+
shape=(NUM_EXPERTS * HIDDEN_DIM, INTERMEDIATE_DIM // VALUES_PER_BYTE),
|
| 858 |
+
strides=(stride_down_n, stride_down_k),
|
| 859 |
+
block_shape=(BLOCK_SIZE_N, BLOCK_SIZE_K // VALUES_PER_BYTE),
|
| 860 |
+
)
|
| 861 |
+
acc = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
| 862 |
+
for k_off in tl.range(0, INTERMEDIATE_DIM, BLOCK_SIZE_K):
|
| 863 |
+
a = tl.load(a_ptrs, mask=row_mask[:, None], other=0.0)
|
| 864 |
+
a_scale = tl.load(as_ptrs, mask=row_mask[:, None], other=0)
|
| 865 |
+
if MEMORY_MODE == "host_descriptor":
|
| 866 |
+
w = tl.trans(
|
| 867 |
+
tl.reshape(
|
| 868 |
+
DownDescriptor.load(
|
| 869 |
+
[expert_id * HIDDEN_DIM + n_off, k_off // VALUES_PER_BYTE]
|
| 870 |
+
),
|
| 871 |
+
[BLOCK_SIZE_N, BLOCK_SIZE_K // VALUES_PER_BYTE],
|
| 872 |
+
)
|
| 873 |
+
)
|
| 874 |
+
elif MEMORY_MODE == "device_descriptor":
|
| 875 |
+
w = tl.trans(
|
| 876 |
+
tl.reshape(
|
| 877 |
+
down_desc.load(
|
| 878 |
+
[expert_id * HIDDEN_DIM + n_off, k_off // VALUES_PER_BYTE]
|
| 879 |
+
),
|
| 880 |
+
[BLOCK_SIZE_N, BLOCK_SIZE_K // VALUES_PER_BYTE],
|
| 881 |
+
)
|
| 882 |
+
)
|
| 883 |
+
else:
|
| 884 |
+
w = tl.load(w_down_ptr)
|
| 885 |
+
w_down_ptr += (BLOCK_SIZE_K // VALUES_PER_BYTE) * stride_down_k
|
| 886 |
+
w_scale = tl.load(ws_down_ptr + (k_off // SCALE_GROUP_K) * stride_downs_k)
|
| 887 |
+
acc = mx_compute(
|
| 888 |
+
acc,
|
| 889 |
+
a,
|
| 890 |
+
a_scale,
|
| 891 |
+
w,
|
| 892 |
+
w_scale,
|
| 893 |
+
COMPUTE_MODE,
|
| 894 |
+
VALUES_PER_BYTE,
|
| 895 |
+
BLOCK_SIZE_M,
|
| 896 |
+
BLOCK_SIZE_N,
|
| 897 |
+
BLOCK_SIZE_K,
|
| 898 |
+
SCALE_GROUP_K,
|
| 899 |
+
)
|
| 900 |
+
a_ptrs += BLOCK_SIZE_K * stride_int_n
|
| 901 |
+
as_ptrs += (BLOCK_SIZE_K // SCALE_GROUP_K) * stride_is_n
|
| 902 |
+
|
| 903 |
+
if SIMULATE_UNFUSED:
|
| 904 |
+
acc = acc.to(ProjOut.dtype.element_ty).to(tl.float32)
|
| 905 |
+
# Fused routing-weight × top-k reorder (no atomics): scale each row by its weight
|
| 906 |
+
# and scatter to its flat (token, slot) row; the host then just sums over slots.
|
| 907 |
+
flat = tl.load(Perm + offs_global_m * stride_perm, mask=row_mask, other=0)
|
| 908 |
+
weight = tl.load(SampleWeights + flat, mask=row_mask, other=0.0)
|
| 909 |
+
acc = acc * weight[:, None]
|
| 910 |
+
store_tile(ProjOut, acc, flat, offs_bn, row_mask, stride_po_m, stride_po_n)
|
| 911 |
+
|
| 912 |
+
|
| 913 |
+
def mxfp_dynamic_moe_grouped(
|
| 914 |
+
hidden_states: torch.Tensor,
|
| 915 |
+
top_k_index: torch.Tensor,
|
| 916 |
+
top_k_weights: torch.Tensor,
|
| 917 |
+
gate_up_proj: torch.Tensor,
|
| 918 |
+
down_proj: torch.Tensor,
|
| 919 |
+
gate_up_proj_scale: torch.Tensor,
|
| 920 |
+
down_proj_scale: torch.Tensor,
|
| 921 |
+
simulate_unfused: bool = False,
|
| 922 |
+
act_fn: str = "silu",
|
| 923 |
+
) -> torch.Tensor:
|
| 924 |
+
"""MXFP4/MXFP8 fused grouped MoE (UE8M0 group-32); gate_up and down must share the same MX
|
| 925 |
+
format. Same structure as the block-dynamic path but with a tunable tile and MXFP8 intermediate."""
|
| 926 |
+
gate_up_is_fp4 = is_mxfp4(gate_up_proj, gate_up_proj_scale)
|
| 927 |
+
down_is_fp4 = is_mxfp4(down_proj, down_proj_scale)
|
| 928 |
+
if gate_up_is_fp4 != down_is_fp4:
|
| 929 |
+
raise ValueError(
|
| 930 |
+
"gate_up_proj and down_proj must use the same MX format (both MXFP4 or both MXFP8)."
|
| 931 |
+
)
|
| 932 |
+
|
| 933 |
+
device = hidden_states.device
|
| 934 |
+
num_sms = sm_count(device.index)
|
| 935 |
+
num_top_k = top_k_index.size(-1)
|
| 936 |
+
num_tokens = hidden_states.size(0)
|
| 937 |
+
HIDDEN_DIM = hidden_states.size(1)
|
| 938 |
+
INTERMEDIATE_DIM = gate_up_proj.size(1) // 2
|
| 939 |
+
perm_token, perm, expert_start, NUM_EXPERTS, num_routed_tokens = _grouped_routing(
|
| 940 |
+
top_k_index, gate_up_proj.size(0), num_top_k
|
| 941 |
+
)
|
| 942 |
+
tokens_per_sm_bit_length = (num_routed_tokens // num_sms).bit_length()
|
| 943 |
+
VALUES_PER_BYTE = NIBBLES_PER_BYTE if gate_up_is_fp4 else 1
|
| 944 |
+
gate_up_proj_u8 = e2m1_as_uint8(gate_up_proj)
|
| 945 |
+
down_proj_u8 = e2m1_as_uint8(down_proj)
|
| 946 |
+
gate_up_scale_u8 = ue8m0_as_uint8(gate_up_proj_scale)
|
| 947 |
+
down_scale_u8 = ue8m0_as_uint8(down_proj_scale)
|
| 948 |
+
|
| 949 |
+
inter = torch.empty(
|
| 950 |
+
num_routed_tokens, INTERMEDIATE_DIM, device=device, dtype=FP8_DTYPE
|
| 951 |
+
)
|
| 952 |
+
inter_scale = torch.empty(
|
| 953 |
+
num_routed_tokens,
|
| 954 |
+
INTERMEDIATE_DIM // MX_SCALE_GROUP_K,
|
| 955 |
+
device=device,
|
| 956 |
+
dtype=torch.uint8,
|
| 957 |
+
)
|
| 958 |
+
out = torch.empty(
|
| 959 |
+
num_routed_tokens, HIDDEN_DIM, device=device, dtype=hidden_states.dtype
|
| 960 |
+
)
|
| 961 |
+
reduced = torch.empty(
|
| 962 |
+
num_tokens, HIDDEN_DIM, device=device, dtype=hidden_states.dtype
|
| 963 |
+
)
|
| 964 |
+
|
| 965 |
+
# Host-built descriptors for host_descriptor mode: views (not reshapes) of the weight
|
| 966 |
+
# buffers — gate_up (E, 2I, H/vpb) → (2E, I, H/vpb) so one [2, BN, BK/vpb] box loads gate
|
| 967 |
+
# (row 2e) + up (2e+1); down (E, H, I/vpb) → (E*H, I/vpb), one [BN, BK/vpb] box per (expert,
|
| 968 |
+
# hidden-tile). Cheap to build, so always created (device_descriptor/pointer ignore them).
|
| 969 |
+
gate_up_2e = gate_up_proj_u8.view(
|
| 970 |
+
2 * gate_up_proj_u8.size(0), INTERMEDIATE_DIM, gate_up_proj_u8.size(2)
|
| 971 |
+
)
|
| 972 |
+
gate_up_descriptor = TensorDescriptor.from_tensor(
|
| 973 |
+
gate_up_2e, [2, 32, 32 // VALUES_PER_BYTE]
|
| 974 |
+
)
|
| 975 |
+
down_eh = down_proj_u8.view(down_proj_u8.size(0) * HIDDEN_DIM, down_proj_u8.size(2))
|
| 976 |
+
down_descriptor = TensorDescriptor.from_tensor(down_eh, [32, 32 // VALUES_PER_BYTE])
|
| 977 |
+
with device_context(device):
|
| 978 |
+
mxfp_dynamic_moe_grouped_gate_up_kernel[(num_sms,)](
|
| 979 |
+
hidden_states,
|
| 980 |
+
perm_token,
|
| 981 |
+
gate_up_proj_u8,
|
| 982 |
+
gate_up_scale_u8,
|
| 983 |
+
gate_up_descriptor,
|
| 984 |
+
expert_start,
|
| 985 |
+
inter,
|
| 986 |
+
inter_scale,
|
| 987 |
+
hidden_states.stride(0),
|
| 988 |
+
hidden_states.stride(1),
|
| 989 |
+
gate_up_proj_u8.stride(0),
|
| 990 |
+
gate_up_proj_u8.stride(1),
|
| 991 |
+
gate_up_proj_u8.stride(2),
|
| 992 |
+
gate_up_scale_u8.stride(0),
|
| 993 |
+
gate_up_scale_u8.stride(1),
|
| 994 |
+
gate_up_scale_u8.stride(2),
|
| 995 |
+
inter.stride(0),
|
| 996 |
+
inter.stride(1),
|
| 997 |
+
inter_scale.stride(0),
|
| 998 |
+
inter_scale.stride(1),
|
| 999 |
+
perm_token.stride(0),
|
| 1000 |
+
tokens_per_sm_bit_length=tokens_per_sm_bit_length,
|
| 1001 |
+
HIDDEN_DIM=HIDDEN_DIM,
|
| 1002 |
+
INTERMEDIATE_DIM=INTERMEDIATE_DIM,
|
| 1003 |
+
VALUES_PER_BYTE=VALUES_PER_BYTE,
|
| 1004 |
+
SCALE_GROUP_K=MX_SCALE_GROUP_K,
|
| 1005 |
+
NUM_EXPERTS=NUM_EXPERTS,
|
| 1006 |
+
NUM_SMS=num_sms,
|
| 1007 |
+
SIMULATE_UNFUSED=simulate_unfused,
|
| 1008 |
+
ACT_FN=act_fn,
|
| 1009 |
+
)
|
| 1010 |
+
mxfp_dynamic_moe_grouped_down_kernel[(num_sms,)](
|
| 1011 |
+
inter,
|
| 1012 |
+
inter_scale,
|
| 1013 |
+
down_proj_u8,
|
| 1014 |
+
down_scale_u8,
|
| 1015 |
+
down_descriptor,
|
| 1016 |
+
expert_start,
|
| 1017 |
+
perm,
|
| 1018 |
+
top_k_weights,
|
| 1019 |
+
out,
|
| 1020 |
+
inter.stride(0),
|
| 1021 |
+
inter.stride(1),
|
| 1022 |
+
inter_scale.stride(0),
|
| 1023 |
+
inter_scale.stride(1),
|
| 1024 |
+
down_proj_u8.stride(0),
|
| 1025 |
+
down_proj_u8.stride(1),
|
| 1026 |
+
down_proj_u8.stride(2),
|
| 1027 |
+
down_scale_u8.stride(0),
|
| 1028 |
+
down_scale_u8.stride(1),
|
| 1029 |
+
down_scale_u8.stride(2),
|
| 1030 |
+
out.stride(0),
|
| 1031 |
+
out.stride(1),
|
| 1032 |
+
perm.stride(0),
|
| 1033 |
+
tokens_per_sm_bit_length=tokens_per_sm_bit_length,
|
| 1034 |
+
HIDDEN_DIM=HIDDEN_DIM,
|
| 1035 |
+
INTERMEDIATE_DIM=INTERMEDIATE_DIM,
|
| 1036 |
+
VALUES_PER_BYTE=VALUES_PER_BYTE,
|
| 1037 |
+
SCALE_GROUP_K=MX_SCALE_GROUP_K,
|
| 1038 |
+
NUM_EXPERTS=NUM_EXPERTS,
|
| 1039 |
+
NUM_SMS=num_sms,
|
| 1040 |
+
SIMULATE_UNFUSED=simulate_unfused,
|
| 1041 |
+
)
|
| 1042 |
+
topk_reduce_kernel[(num_tokens, triton.cdiv(HIDDEN_DIM, TOPK_REDUCE_BLOCK_H))](
|
| 1043 |
+
out,
|
| 1044 |
+
reduced,
|
| 1045 |
+
HIDDEN_DIM,
|
| 1046 |
+
out.stride(0),
|
| 1047 |
+
out.stride(1),
|
| 1048 |
+
reduced.stride(0),
|
| 1049 |
+
reduced.stride(1),
|
| 1050 |
+
NUM_TOP_K=num_top_k,
|
| 1051 |
+
BLOCK_H=TOPK_REDUCE_BLOCK_H,
|
| 1052 |
+
)
|
| 1053 |
+
|
| 1054 |
+
return reduced
|
| 1055 |
+
|
| 1056 |
+
|
| 1057 |
+
# ── Dispatcher ────────────────────────────────────────────────────────────────
|
| 1058 |
+
|
| 1059 |
+
|
| 1060 |
+
def moe_fused_grouped(
|
| 1061 |
+
hidden_states: torch.Tensor,
|
| 1062 |
+
top_k_index: torch.Tensor,
|
| 1063 |
+
top_k_weights: torch.Tensor,
|
| 1064 |
+
gate_up_proj: torch.Tensor,
|
| 1065 |
+
down_proj: torch.Tensor,
|
| 1066 |
+
gate_up_proj_scale_inv: torch.Tensor,
|
| 1067 |
+
down_proj_scale_inv: torch.Tensor,
|
| 1068 |
+
block_size: list[int] | None,
|
| 1069 |
+
simulate_unfused: bool = False,
|
| 1070 |
+
act_fn: str = "silu",
|
| 1071 |
+
) -> torch.Tensor:
|
| 1072 |
+
"""Fused grouped-MoE dispatcher — routes to the recipe matching the weight dtype /
|
| 1073 |
+
scale layout, mirroring ``moe_fused_batched``. Implemented: block-dynamic FP8 and MXFP8 /
|
| 1074 |
+
MXFP4 (UE8M0 group-32). ``simulate_unfused`` (testing) rounds each step through the
|
| 1075 |
+
activation dtype so the output matches the unfused reference to reduce order."""
|
| 1076 |
+
gate_up_is_mx = is_mxfp(gate_up_proj, gate_up_proj_scale_inv)
|
| 1077 |
+
down_is_mx = is_mxfp(down_proj, down_proj_scale_inv)
|
| 1078 |
+
if gate_up_is_mx != down_is_mx:
|
| 1079 |
+
raise ValueError(
|
| 1080 |
+
"gate_up_proj and down_proj must use the same recipe (both MX or both block-dynamic FP8)."
|
| 1081 |
+
)
|
| 1082 |
+
|
| 1083 |
+
if gate_up_is_mx:
|
| 1084 |
+
return mxfp_dynamic_moe_grouped(
|
| 1085 |
+
hidden_states,
|
| 1086 |
+
top_k_index,
|
| 1087 |
+
top_k_weights,
|
| 1088 |
+
gate_up_proj,
|
| 1089 |
+
down_proj,
|
| 1090 |
+
gate_up_proj_scale_inv,
|
| 1091 |
+
down_proj_scale_inv,
|
| 1092 |
+
simulate_unfused,
|
| 1093 |
+
act_fn,
|
| 1094 |
+
)
|
| 1095 |
+
|
| 1096 |
+
if block_size is None:
|
| 1097 |
+
raise ValueError("block_size is required for block-dynamic FP8 weights.")
|
| 1098 |
+
|
| 1099 |
+
return w8a8_block_dynamic_fp8_moe_grouped(
|
| 1100 |
+
hidden_states,
|
| 1101 |
+
top_k_index,
|
| 1102 |
+
top_k_weights,
|
| 1103 |
+
gate_up_proj,
|
| 1104 |
+
down_proj,
|
| 1105 |
+
gate_up_proj_scale_inv,
|
| 1106 |
+
down_proj_scale_inv,
|
| 1107 |
+
block_size,
|
| 1108 |
+
simulate_unfused,
|
| 1109 |
+
act_fn,
|
| 1110 |
+
)
|
build/torch-cuda/grouped.py
CHANGED
|
@@ -15,28 +15,138 @@
|
|
| 15 |
import torch
|
| 16 |
import triton
|
| 17 |
import triton.language as tl
|
| 18 |
-
from .act_quant import fp8_act_quant
|
| 19 |
from torch.library import triton_op, wrap_triton
|
| 20 |
|
| 21 |
-
from .
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
|
| 23 |
|
| 24 |
@triton.autotune(
|
| 25 |
-
configs=
|
| 26 |
-
triton.Config({}, num_warps=w, num_stages=s)
|
| 27 |
-
for w in [2, 4, 8, 16]
|
| 28 |
-
for s in [2, 3, 4, 5]
|
| 29 |
-
],
|
| 30 |
key=["N", "K", "BLOCK_SIZE_M"],
|
| 31 |
)
|
| 32 |
@triton.jit
|
| 33 |
-
def
|
| 34 |
-
A, # (S, K)
|
| 35 |
-
B, # (
|
| 36 |
-
C, # (S, N)
|
| 37 |
-
Bs, # (
|
| 38 |
-
Offsets, # (
|
| 39 |
-
TileOffsets, # (
|
| 40 |
# Shape
|
| 41 |
S,
|
| 42 |
N,
|
|
@@ -52,11 +162,13 @@ def w8a8_block_fp8_matmul_grouped_kernel(
|
|
| 52 |
stride_bs_e,
|
| 53 |
stride_bs_k,
|
| 54 |
stride_bs_n,
|
|
|
|
|
|
|
|
|
|
| 55 |
# Meta-parameters
|
| 56 |
-
|
| 57 |
BLOCK_SIZE_N: tl.constexpr,
|
| 58 |
BLOCK_SIZE_K: tl.constexpr,
|
| 59 |
-
BLOCK_SIZE_M: tl.constexpr,
|
| 60 |
NUM_EXPERTS_BIT_LENGTH: tl.constexpr,
|
| 61 |
):
|
| 62 |
"""Block-scale grouped FP8 expert matmul kernel.
|
|
@@ -67,47 +179,23 @@ def w8a8_block_fp8_matmul_grouped_kernel(
|
|
| 67 |
pid_m = tl.program_id(axis=0)
|
| 68 |
pid_n = tl.program_id(axis=1)
|
| 69 |
|
| 70 |
-
|
| 71 |
-
total_tiles = tl.load(TileOffsets + NUM_EXPERTS - 1)
|
| 72 |
if pid_m >= total_tiles:
|
| 73 |
return
|
| 74 |
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
lo = tl.where(is_left, mid + 1, lo)
|
| 89 |
-
hi = tl.where(is_left, hi, mid)
|
| 90 |
-
|
| 91 |
-
# Cast expert_id to int64 to prevent int32 overflow when computing
|
| 92 |
-
# expert_id * stride_be (e.g. 255 * 9_437_184 > 2^31 for 256 experts of
|
| 93 |
-
# 3072×3072 FP8 weights).
|
| 94 |
-
expert_id = lo.to(tl.int64)
|
| 95 |
-
|
| 96 |
-
prev_eid = tl.maximum(expert_id - 1, 0)
|
| 97 |
-
expert_start = tl.where(expert_id == 0, 0, tl.load(Offsets + prev_eid))
|
| 98 |
-
expert_end = tl.load(Offsets + expert_id)
|
| 99 |
-
M_expert = expert_end - expert_start
|
| 100 |
-
|
| 101 |
-
expert_tile_start = tl.where(expert_id == 0, 0, tl.load(TileOffsets + prev_eid))
|
| 102 |
-
local_tile = pid_m - expert_tile_start
|
| 103 |
-
m_off = local_tile * BLOCK_SIZE_M
|
| 104 |
-
|
| 105 |
-
offs_am = m_off + tl.arange(0, BLOCK_SIZE_M)
|
| 106 |
-
row_mask = offs_am < M_expert
|
| 107 |
-
offs_global_m = expert_start + offs_am
|
| 108 |
-
|
| 109 |
-
offs_bn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
|
| 110 |
-
offs_k = tl.arange(0, BLOCK_SIZE_K)
|
| 111 |
|
| 112 |
a_ptrs = A + offs_global_m[:, None] * stride_am + offs_k[None, :] * stride_ak
|
| 113 |
b_ptrs = (
|
|
@@ -120,49 +208,37 @@ def w8a8_block_fp8_matmul_grouped_kernel(
|
|
| 120 |
|
| 121 |
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
| 122 |
for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
|
| 123 |
-
# ---- fused fp8_act_quant ----
|
| 124 |
a_raw = tl.load(a_ptrs, mask=row_mask[:, None], other=0.0).to(tl.float32)
|
| 125 |
-
a_s =
|
| 126 |
-
a = (a_raw / tl.maximum(a_s[:, None], 1e-12)).to(tl.float8e4nv)
|
| 127 |
-
# ---- matmul ----
|
| 128 |
b = tl.load(b_ptrs)
|
| 129 |
-
b_s = tl.load(bs_ptrs
|
| 130 |
accumulator += tl.dot(a, b) * a_s[:, None] * b_s[None, :]
|
| 131 |
a_ptrs += BLOCK_SIZE_K * stride_ak
|
| 132 |
b_ptrs += BLOCK_SIZE_K * stride_bk
|
|
|
|
| 133 |
|
| 134 |
-
|
| 135 |
-
c = accumulator.to(tl.bfloat16)
|
| 136 |
-
elif C.dtype.element_ty == tl.float16:
|
| 137 |
-
c = accumulator.to(tl.float16)
|
| 138 |
-
else:
|
| 139 |
-
c = accumulator.to(tl.float32)
|
| 140 |
-
|
| 141 |
-
c_ptrs = C + stride_cm * offs_global_m[:, None] + stride_cn * offs_bn[None, :]
|
| 142 |
-
c_mask = row_mask[:, None]
|
| 143 |
-
tl.store(c_ptrs, c, mask=c_mask)
|
| 144 |
|
| 145 |
|
| 146 |
-
@
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
for s in [2, 3, 4, 5]
|
| 151 |
-
],
|
| 152 |
-
key=["N", "K", "BLOCK_SIZE_M"],
|
| 153 |
)
|
| 154 |
@triton.jit
|
| 155 |
-
def
|
| 156 |
-
A, # (S, K)
|
| 157 |
-
B, # (
|
| 158 |
C, # (S, N) output
|
| 159 |
-
As, # (S,
|
| 160 |
-
Bs, # (
|
| 161 |
-
Offsets,
|
| 162 |
-
TileOffsets,
|
|
|
|
| 163 |
S,
|
| 164 |
N,
|
| 165 |
K,
|
|
|
|
| 166 |
stride_am,
|
| 167 |
stride_ak,
|
| 168 |
stride_be,
|
|
@@ -172,10 +248,13 @@ def w8a8_tensor_fp8_matmul_grouped_kernel(
|
|
| 172 |
stride_cn,
|
| 173 |
stride_as_m,
|
| 174 |
stride_bs_e,
|
| 175 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 176 |
BLOCK_SIZE_N: tl.constexpr,
|
| 177 |
BLOCK_SIZE_K: tl.constexpr,
|
| 178 |
-
BLOCK_SIZE_M: tl.constexpr,
|
| 179 |
NUM_EXPERTS_BIT_LENGTH: tl.constexpr,
|
| 180 |
):
|
| 181 |
"""Tensor-scale grouped FP8 expert matmul kernel.
|
|
@@ -186,35 +265,23 @@ def w8a8_tensor_fp8_matmul_grouped_kernel(
|
|
| 186 |
pid_m = tl.program_id(axis=0)
|
| 187 |
pid_n = tl.program_id(axis=1)
|
| 188 |
|
| 189 |
-
total_tiles = tl.load(TileOffsets +
|
| 190 |
if pid_m >= total_tiles:
|
| 191 |
return
|
| 192 |
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
M_expert = expert_end - expert_start
|
| 207 |
-
|
| 208 |
-
expert_tile_start = tl.where(expert_id == 0, 0, tl.load(TileOffsets + prev_eid))
|
| 209 |
-
local_tile = pid_m - expert_tile_start
|
| 210 |
-
m_off = local_tile * BLOCK_SIZE_M
|
| 211 |
-
|
| 212 |
-
offs_am = m_off + tl.arange(0, BLOCK_SIZE_M)
|
| 213 |
-
row_mask = offs_am < M_expert
|
| 214 |
-
offs_global_m = expert_start + offs_am
|
| 215 |
-
|
| 216 |
-
offs_bn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
|
| 217 |
-
offs_k = tl.arange(0, BLOCK_SIZE_K)
|
| 218 |
|
| 219 |
a_ptrs = A + offs_global_m[:, None] * stride_am + offs_k[None, :] * stride_ak
|
| 220 |
b_ptrs = (
|
|
@@ -231,50 +298,151 @@ def w8a8_tensor_fp8_matmul_grouped_kernel(
|
|
| 231 |
for _ in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
|
| 232 |
a = tl.load(a_ptrs, mask=row_mask[:, None], other=0.0)
|
| 233 |
b = tl.load(b_ptrs)
|
| 234 |
-
|
| 235 |
accumulator += tl.dot(a, b)
|
| 236 |
a_ptrs += BLOCK_SIZE_K * stride_ak
|
| 237 |
b_ptrs += BLOCK_SIZE_K * stride_bk
|
| 238 |
-
|
| 239 |
accumulator = accumulator * a_s[:, None] * b_s
|
| 240 |
|
| 241 |
-
|
| 242 |
-
c = accumulator.to(tl.bfloat16)
|
| 243 |
-
elif C.dtype.element_ty == tl.float16:
|
| 244 |
-
c = accumulator.to(tl.float16)
|
| 245 |
-
else:
|
| 246 |
-
c = accumulator.to(tl.float32)
|
| 247 |
|
| 248 |
-
c_ptrs = C + stride_cm * offs_global_m[:, None] + stride_cn * offs_bn[None, :]
|
| 249 |
-
c_mask = row_mask[:, None]
|
| 250 |
-
tl.store(c_ptrs, c, mask=c_mask)
|
| 251 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 252 |
|
| 253 |
-
|
| 254 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 255 |
A: torch.Tensor,
|
| 256 |
B: torch.Tensor,
|
| 257 |
Bs: torch.Tensor,
|
| 258 |
offsets: torch.Tensor,
|
| 259 |
tokens_per_expert: torch.Tensor,
|
| 260 |
block_size: list[int],
|
|
|
|
| 261 |
) -> torch.Tensor:
|
| 262 |
"""Block-scale grouped FP8 matmul: C = A @ B.T per expert, with fused act quant.
|
| 263 |
|
| 264 |
A: (S, K) raw bf16/fp16 activations, sorted by expert
|
| 265 |
-
B: (
|
| 266 |
-
Bs: (
|
| 267 |
"""
|
| 268 |
assert A.ndim == 2, f"A must be 2D (S, K), got ndim={A.ndim}"
|
| 269 |
assert A.is_contiguous(), "A must be contiguous"
|
| 270 |
-
assert B.ndim == 3, f"B must be 3D (
|
| 271 |
assert B.is_contiguous(), "B must be contiguous"
|
| 272 |
assert A.shape[1] == B.shape[2], (
|
| 273 |
f"K mismatch: A has K={A.shape[1]}, B has K={B.shape[2]}"
|
| 274 |
)
|
| 275 |
|
| 276 |
S, K = A.shape
|
| 277 |
-
|
| 278 |
|
| 279 |
assert len(block_size) == 2, (
|
| 280 |
f"block_size must be [block_n, block_k], got {block_size}"
|
|
@@ -284,25 +452,22 @@ def _w8a8_block_fp8_matmul_grouped(
|
|
| 284 |
assert N % block_n == 0, f"N ({N}) must be divisible by block_n ({block_n})"
|
| 285 |
assert K % block_k == 0, f"K ({K}) must be divisible by block_k ({block_k})"
|
| 286 |
assert Bs.ndim == 3, (
|
| 287 |
-
f"Bs must be 3D (
|
| 288 |
)
|
| 289 |
-
assert Bs.shape == (
|
| 290 |
-
f"Bs shape {tuple(Bs.shape)} != expected ({
|
| 291 |
)
|
| 292 |
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
BLOCK_SIZE_M =
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
# and keeps the grid size data-independent (cuda-graph / torch.compile safe).
|
| 302 |
-
max_M_tiles = triton.cdiv(S, BLOCK_SIZE_M) + E
|
| 303 |
-
grid = (max_M_tiles, triton.cdiv(N, block_n))
|
| 304 |
with device_context(A.device):
|
| 305 |
-
wrap_triton(
|
| 306 |
A,
|
| 307 |
B,
|
| 308 |
C,
|
|
@@ -322,66 +487,70 @@ def _w8a8_block_fp8_matmul_grouped(
|
|
| 322 |
Bs.stride(0),
|
| 323 |
Bs.stride(2),
|
| 324 |
Bs.stride(1),
|
|
|
|
|
|
|
| 325 |
# Meta-parameters
|
| 326 |
-
|
| 327 |
BLOCK_SIZE_N=block_n,
|
| 328 |
BLOCK_SIZE_K=block_k,
|
| 329 |
BLOCK_SIZE_M=BLOCK_SIZE_M,
|
| 330 |
-
NUM_EXPERTS_BIT_LENGTH=
|
| 331 |
)
|
| 332 |
|
| 333 |
return C
|
| 334 |
|
| 335 |
|
| 336 |
-
@triton_op(
|
| 337 |
-
|
|
|
|
|
|
|
| 338 |
A: torch.Tensor,
|
| 339 |
B: torch.Tensor,
|
| 340 |
Bs: torch.Tensor,
|
| 341 |
offsets: torch.Tensor,
|
| 342 |
tokens_per_expert: torch.Tensor,
|
|
|
|
| 343 |
) -> torch.Tensor:
|
| 344 |
"""Tensor-scale grouped FP8 matmul: C = A @ B.T per expert, with fused act quant.
|
| 345 |
|
| 346 |
A: (S, K) raw bf16/fp16 activations, sorted by expert
|
| 347 |
-
B: (
|
| 348 |
-
Bs: (
|
| 349 |
"""
|
| 350 |
assert A.ndim == 2, f"A must be 2D (S, K), got ndim={A.ndim}"
|
| 351 |
assert A.is_contiguous(), "A must be contiguous"
|
| 352 |
-
assert B.ndim == 3, f"B must be 3D (
|
| 353 |
assert B.is_contiguous(), "B must be contiguous"
|
| 354 |
assert A.shape[1] == B.shape[2], (
|
| 355 |
f"K mismatch: A has K={A.shape[1]}, B has K={B.shape[2]}"
|
| 356 |
)
|
| 357 |
|
| 358 |
S, K = A.shape
|
| 359 |
-
|
| 360 |
|
| 361 |
-
# Normalize Bs to (
|
| 362 |
if Bs.ndim == 1:
|
| 363 |
-
assert Bs.shape[0] ==
|
| 364 |
-
|
|
|
|
|
|
|
| 365 |
else:
|
| 366 |
-
assert Bs.shape == (
|
| 367 |
-
f"Bs shape {tuple(Bs.shape)} != expected ({
|
| 368 |
)
|
| 369 |
|
| 370 |
-
BLOCK_SIZE_N = 128
|
| 371 |
-
BLOCK_SIZE_K = 128
|
| 372 |
-
C = A.new_empty(S, N)
|
| 373 |
qA, As = fp8_act_quant(A, K)
|
| 374 |
-
|
| 375 |
-
|
| 376 |
-
tile_offsets =
|
| 377 |
-
|
| 378 |
-
|
| 379 |
-
|
| 380 |
-
|
| 381 |
-
|
| 382 |
-
|
| 383 |
with device_context(A.device):
|
| 384 |
-
wrap_triton(
|
| 385 |
qA,
|
| 386 |
B,
|
| 387 |
C,
|
|
@@ -401,77 +570,190 @@ def _w8a8_tensor_fp8_matmul_grouped(
|
|
| 401 |
C.stride(1),
|
| 402 |
As.stride(0),
|
| 403 |
Bs.stride(0),
|
| 404 |
-
|
| 405 |
-
|
| 406 |
-
BLOCK_SIZE_N
|
| 407 |
-
|
| 408 |
BLOCK_SIZE_M=BLOCK_SIZE_M,
|
| 409 |
-
NUM_EXPERTS_BIT_LENGTH=
|
| 410 |
)
|
| 411 |
|
| 412 |
return C
|
| 413 |
|
| 414 |
|
| 415 |
-
def
|
| 416 |
A: torch.Tensor,
|
| 417 |
B: torch.Tensor,
|
| 418 |
Bs: torch.Tensor,
|
| 419 |
offsets: torch.Tensor,
|
| 420 |
tokens_per_expert: torch.Tensor,
|
| 421 |
block_size: list[int],
|
|
|
|
| 422 |
) -> torch.Tensor:
|
| 423 |
"""Block-scale grouped FP8 matmul with fused activation quantization.
|
| 424 |
|
| 425 |
A: (S, K) raw activations sorted by expert, bf16/fp16/fp32
|
| 426 |
-
B: (
|
| 427 |
-
Bs: (
|
|
|
|
| 428 |
"""
|
| 429 |
-
return
|
| 430 |
-
A, B, Bs, offsets, tokens_per_expert, block_size
|
| 431 |
)
|
| 432 |
|
| 433 |
|
| 434 |
-
def
|
| 435 |
A: torch.Tensor,
|
| 436 |
B: torch.Tensor,
|
| 437 |
Bs: torch.Tensor,
|
| 438 |
offsets: torch.Tensor,
|
| 439 |
tokens_per_expert: torch.Tensor,
|
|
|
|
| 440 |
) -> torch.Tensor:
|
| 441 |
"""Tensor-scale grouped FP8 matmul with fused activation quantization.
|
| 442 |
|
| 443 |
A: (S, K) raw activations sorted by expert, bf16/fp16/fp32
|
| 444 |
-
B: (
|
| 445 |
-
Bs: (
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 446 |
"""
|
| 447 |
-
|
| 448 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 449 |
)
|
| 450 |
|
| 451 |
|
| 452 |
-
def
|
| 453 |
A: torch.Tensor,
|
| 454 |
B: torch.Tensor,
|
| 455 |
Bs: torch.Tensor,
|
| 456 |
offsets: torch.Tensor,
|
| 457 |
tokens_per_expert: torch.Tensor,
|
| 458 |
block_size: list[int] | None,
|
|
|
|
| 459 |
) -> torch.Tensor:
|
| 460 |
-
"""
|
|
|
|
| 461 |
|
| 462 |
-
|
| 463 |
-
- tensor mode when ``block_size is None``
|
| 464 |
-
- tensor mode when ``block_size == [N, K]``
|
| 465 |
-
- otherwise block mode
|
| 466 |
|
| 467 |
-
|
| 468 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 469 |
"""
|
| 470 |
-
if
|
| 471 |
-
|
| 472 |
-
|
| 473 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 474 |
|
| 475 |
-
return
|
| 476 |
-
A, B, Bs, offsets, tokens_per_expert, block_size
|
| 477 |
)
|
|
|
|
| 15 |
import torch
|
| 16 |
import triton
|
| 17 |
import triton.language as tl
|
|
|
|
| 18 |
from torch.library import triton_op, wrap_triton
|
| 19 |
|
| 20 |
+
from ._ops import add_op_namespace_prefix, ops
|
| 21 |
+
from .bayesian_autotuner import bayesian_autotune
|
| 22 |
+
from .utils import (
|
| 23 |
+
MX_SCALE_GROUP_K,
|
| 24 |
+
NIBBLES_PER_BYTE,
|
| 25 |
+
adaptive_block_size_m,
|
| 26 |
+
device_context,
|
| 27 |
+
mxfp_act_quant_inline,
|
| 28 |
+
mxfp4_e2m1_to_e4m3,
|
| 29 |
+
fp8_act_quant,
|
| 30 |
+
fp8_act_quant_inline,
|
| 31 |
+
get_accelerator_autotuning_configs,
|
| 32 |
+
get_mxfp_autotuning_configs,
|
| 33 |
+
is_mxfp,
|
| 34 |
+
is_tensor_wide,
|
| 35 |
+
e2m1_as_uint8,
|
| 36 |
+
ue8m0_as_uint8,
|
| 37 |
+
decode_ue8m0_scale,
|
| 38 |
+
mx_dot_rescale,
|
| 39 |
+
mx_dot_scaled,
|
| 40 |
+
)
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def _grouped_tile_layout(
|
| 44 |
+
tokens_per_expert: torch.Tensor,
|
| 45 |
+
block_size_m: int,
|
| 46 |
+
S: int,
|
| 47 |
+
num_experts: int,
|
| 48 |
+
) -> tuple[torch.Tensor, int]:
|
| 49 |
+
"""Compute the M-tile layout for the grouped kernels.
|
| 50 |
+
|
| 51 |
+
Returns ``(tile_offsets, max_m_tiles)``:
|
| 52 |
+
- ``tile_offsets``: int32 (num_experts,) cumulative tile-end per expert, used by
|
| 53 |
+
``_grouped_tile_setup`` to locate an M-tile's owning expert.
|
| 54 |
+
- ``max_m_tiles``: upper bound on total M-tiles, used as the grid axis-0
|
| 55 |
+
size. Real tile count <= this; surplus programs early-return inside the
|
| 56 |
+
kernel. Keeps the grid data-independent (cuda-graph / torch.compile safe).
|
| 57 |
+
"""
|
| 58 |
+
tiles_per_expert = (tokens_per_expert + block_size_m - 1) // block_size_m
|
| 59 |
+
tile_offsets = torch.cumsum(tiles_per_expert, dim=0).to(torch.int32)
|
| 60 |
+
max_m_tiles = triton.cdiv(S, block_size_m) + num_experts
|
| 61 |
+
return tile_offsets, max_m_tiles
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
@triton.jit
|
| 65 |
+
def _grouped_tile_setup(
|
| 66 |
+
pid_m,
|
| 67 |
+
pid_n,
|
| 68 |
+
Offsets,
|
| 69 |
+
TileOffsets,
|
| 70 |
+
stride_offs,
|
| 71 |
+
stride_tile,
|
| 72 |
+
num_experts,
|
| 73 |
+
NUM_EXPERTS_BIT_LENGTH: tl.constexpr,
|
| 74 |
+
BLOCK_SIZE_M: tl.constexpr,
|
| 75 |
+
BLOCK_SIZE_N: tl.constexpr,
|
| 76 |
+
BLOCK_SIZE_K: tl.constexpr,
|
| 77 |
+
):
|
| 78 |
+
"""Map a grouped M-tile to its expert and build the per-tile offset vectors —
|
| 79 |
+
the prologue shared by every grouped kernel.
|
| 80 |
+
|
| 81 |
+
Returns ``(expert_id, offs_global_m, row_mask, offs_bn, offs_k)``:
|
| 82 |
+
- ``expert_id``: int64 owning expert
|
| 83 |
+
- ``offs_global_m``: ``(BLOCK_SIZE_M,)`` global row indices into A
|
| 84 |
+
- ``row_mask``: ``(BLOCK_SIZE_M,)`` validity mask within the expert's M
|
| 85 |
+
- ``offs_bn``: ``(BLOCK_SIZE_N,)`` output column offsets
|
| 86 |
+
- ``offs_k``: ``(BLOCK_SIZE_K,)`` K range
|
| 87 |
+
|
| 88 |
+
Caller must have early-returned if ``pid_m`` exceeds total_tiles
|
| 89 |
+
(``TileOffsets[(num_experts - 1) * stride_tile]``) — the ``Offsets`` load below
|
| 90 |
+
is out of bounds for an out-of-range tile otherwise.
|
| 91 |
+
"""
|
| 92 |
+
# Binary search: upper_bound(TileOffsets, pid_m). NUM_EXPERTS_BIT_LENGTH is
|
| 93 |
+
# ceil(log2(num_experts))+1, giving one harmless extra iteration; constexpr so the
|
| 94 |
+
# loop unrolls.
|
| 95 |
+
lo = 0
|
| 96 |
+
hi = num_experts
|
| 97 |
+
for _ in tl.static_range(NUM_EXPERTS_BIT_LENGTH):
|
| 98 |
+
mid = (lo + hi) >> 1
|
| 99 |
+
mid_val = tl.load(TileOffsets + mid * stride_tile)
|
| 100 |
+
is_left = mid_val <= pid_m
|
| 101 |
+
lo = tl.where(is_left, mid + 1, lo)
|
| 102 |
+
hi = tl.where(is_left, hi, mid)
|
| 103 |
+
# Cast to int64 so ``expert_id * stride_be`` doesn't overflow for large num_experts
|
| 104 |
+
# × large weight matrices (e.g. 255 * 9_437_184 > 2^31).
|
| 105 |
+
expert_id = lo.to(tl.int64)
|
| 106 |
+
|
| 107 |
+
prev_eid = tl.maximum(expert_id - 1, 0)
|
| 108 |
+
expert_start = tl.where(
|
| 109 |
+
expert_id == 0, 0, tl.load(Offsets + prev_eid * stride_offs)
|
| 110 |
+
)
|
| 111 |
+
expert_end = tl.load(Offsets + expert_id * stride_offs)
|
| 112 |
+
M_expert = expert_end - expert_start
|
| 113 |
+
|
| 114 |
+
expert_tile_start = tl.where(
|
| 115 |
+
expert_id == 0, 0, tl.load(TileOffsets + prev_eid * stride_tile)
|
| 116 |
+
)
|
| 117 |
+
local_tile = pid_m - expert_tile_start
|
| 118 |
+
m_off = local_tile * BLOCK_SIZE_M
|
| 119 |
+
|
| 120 |
+
offs_am = m_off + tl.arange(0, BLOCK_SIZE_M)
|
| 121 |
+
row_mask = offs_am < M_expert
|
| 122 |
+
offs_global_m = expert_start + offs_am
|
| 123 |
+
offs_bn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
|
| 124 |
+
offs_k = tl.arange(0, BLOCK_SIZE_K)
|
| 125 |
+
return expert_id, offs_global_m, row_mask, offs_bn, offs_k
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
@triton.jit
|
| 129 |
+
def store_tile(C, accumulator, offs_global_m, offs_bn, row_mask, stride_cm, stride_cn):
|
| 130 |
+
"""Output epilogue shared by the grouped kernels: cast the fp32 accumulator to
|
| 131 |
+
``C``'s dtype and store the tile at expert-sorted global rows ``offs_global_m`` ×
|
| 132 |
+
columns ``offs_bn``, masked to the expert's valid rows (``row_mask``)."""
|
| 133 |
+
c = accumulator.to(C.dtype.element_ty)
|
| 134 |
+
c_ptrs = C + stride_cm * offs_global_m[:, None] + stride_cn * offs_bn[None, :]
|
| 135 |
+
tl.store(c_ptrs, c, mask=row_mask[:, None])
|
| 136 |
|
| 137 |
|
| 138 |
@triton.autotune(
|
| 139 |
+
configs=get_accelerator_autotuning_configs(),
|
|
|
|
|
|
|
|
|
|
|
|
|
| 140 |
key=["N", "K", "BLOCK_SIZE_M"],
|
| 141 |
)
|
| 142 |
@triton.jit
|
| 143 |
+
def w8a8_block_dynamic_fp8_matmul_grouped_kernel(
|
| 144 |
+
A, # (S, K) raw BF16/FP16 activations, sorted/grouped by expert id
|
| 145 |
+
B, # (num_experts, N, K) FP8 weight matrices
|
| 146 |
+
C, # (S, N) output
|
| 147 |
+
Bs, # (num_experts, N // BLOCK_SIZE_N, K // BLOCK_SIZE_K) weight scales
|
| 148 |
+
Offsets, # (num_experts,) int32 — cumulative row-end per expert
|
| 149 |
+
TileOffsets, # (num_experts,) int32 — cumulative tile-end per expert
|
| 150 |
# Shape
|
| 151 |
S,
|
| 152 |
N,
|
|
|
|
| 162 |
stride_bs_e,
|
| 163 |
stride_bs_k,
|
| 164 |
stride_bs_n,
|
| 165 |
+
stride_offs,
|
| 166 |
+
stride_tile,
|
| 167 |
+
num_experts,
|
| 168 |
# Meta-parameters
|
| 169 |
+
BLOCK_SIZE_M: tl.constexpr,
|
| 170 |
BLOCK_SIZE_N: tl.constexpr,
|
| 171 |
BLOCK_SIZE_K: tl.constexpr,
|
|
|
|
| 172 |
NUM_EXPERTS_BIT_LENGTH: tl.constexpr,
|
| 173 |
):
|
| 174 |
"""Block-scale grouped FP8 expert matmul kernel.
|
|
|
|
| 179 |
pid_m = tl.program_id(axis=0)
|
| 180 |
pid_n = tl.program_id(axis=1)
|
| 181 |
|
| 182 |
+
total_tiles = tl.load(TileOffsets + (num_experts - 1) * stride_tile)
|
|
|
|
| 183 |
if pid_m >= total_tiles:
|
| 184 |
return
|
| 185 |
|
| 186 |
+
expert_id, offs_global_m, row_mask, offs_bn, offs_k = _grouped_tile_setup(
|
| 187 |
+
pid_m,
|
| 188 |
+
pid_n,
|
| 189 |
+
Offsets,
|
| 190 |
+
TileOffsets,
|
| 191 |
+
stride_offs,
|
| 192 |
+
stride_tile,
|
| 193 |
+
num_experts,
|
| 194 |
+
NUM_EXPERTS_BIT_LENGTH,
|
| 195 |
+
BLOCK_SIZE_M,
|
| 196 |
+
BLOCK_SIZE_N,
|
| 197 |
+
BLOCK_SIZE_K,
|
| 198 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 199 |
|
| 200 |
a_ptrs = A + offs_global_m[:, None] * stride_am + offs_k[None, :] * stride_ak
|
| 201 |
b_ptrs = (
|
|
|
|
| 208 |
|
| 209 |
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
| 210 |
for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
|
|
|
|
| 211 |
a_raw = tl.load(a_ptrs, mask=row_mask[:, None], other=0.0).to(tl.float32)
|
| 212 |
+
a, a_s = fp8_act_quant_inline(a_raw)
|
|
|
|
|
|
|
| 213 |
b = tl.load(b_ptrs)
|
| 214 |
+
b_s = decode_ue8m0_scale(tl.load(bs_ptrs))
|
| 215 |
accumulator += tl.dot(a, b) * a_s[:, None] * b_s[None, :]
|
| 216 |
a_ptrs += BLOCK_SIZE_K * stride_ak
|
| 217 |
b_ptrs += BLOCK_SIZE_K * stride_bk
|
| 218 |
+
bs_ptrs += stride_bs_k
|
| 219 |
|
| 220 |
+
store_tile(C, accumulator, offs_global_m, offs_bn, row_mask, stride_cm, stride_cn)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 221 |
|
| 222 |
|
| 223 |
+
@bayesian_autotune(
|
| 224 |
+
get_accelerator_autotuning_configs(tune_block_nk=True),
|
| 225 |
+
["N", "K", "BLOCK_SIZE_M"],
|
| 226 |
+
n_trials=60,
|
|
|
|
|
|
|
|
|
|
| 227 |
)
|
| 228 |
@triton.jit
|
| 229 |
+
def w8a8_tensor_dynamic_fp8_matmul_grouped_kernel(
|
| 230 |
+
A, # (S, K) pre-quantized FP8 activations, sorted/grouped by expert id
|
| 231 |
+
B, # (num_experts, N, K) FP8 weight matrices
|
| 232 |
C, # (S, N) output
|
| 233 |
+
As, # (S,) per-token activation scales
|
| 234 |
+
Bs, # (num_experts, 1, 1) per-tensor weight scales
|
| 235 |
+
Offsets, # (num_experts,) int32 — cumulative row-end per expert
|
| 236 |
+
TileOffsets, # (num_experts,) int32 — cumulative tile-end per expert
|
| 237 |
+
# Shape
|
| 238 |
S,
|
| 239 |
N,
|
| 240 |
K,
|
| 241 |
+
# Strides
|
| 242 |
stride_am,
|
| 243 |
stride_ak,
|
| 244 |
stride_be,
|
|
|
|
| 248 |
stride_cn,
|
| 249 |
stride_as_m,
|
| 250 |
stride_bs_e,
|
| 251 |
+
stride_offs,
|
| 252 |
+
stride_tile,
|
| 253 |
+
num_experts,
|
| 254 |
+
# Meta-parameters
|
| 255 |
+
BLOCK_SIZE_M: tl.constexpr,
|
| 256 |
BLOCK_SIZE_N: tl.constexpr,
|
| 257 |
BLOCK_SIZE_K: tl.constexpr,
|
|
|
|
| 258 |
NUM_EXPERTS_BIT_LENGTH: tl.constexpr,
|
| 259 |
):
|
| 260 |
"""Tensor-scale grouped FP8 expert matmul kernel.
|
|
|
|
| 265 |
pid_m = tl.program_id(axis=0)
|
| 266 |
pid_n = tl.program_id(axis=1)
|
| 267 |
|
| 268 |
+
total_tiles = tl.load(TileOffsets + (num_experts - 1) * stride_tile)
|
| 269 |
if pid_m >= total_tiles:
|
| 270 |
return
|
| 271 |
|
| 272 |
+
expert_id, offs_global_m, row_mask, offs_bn, offs_k = _grouped_tile_setup(
|
| 273 |
+
pid_m,
|
| 274 |
+
pid_n,
|
| 275 |
+
Offsets,
|
| 276 |
+
TileOffsets,
|
| 277 |
+
stride_offs,
|
| 278 |
+
stride_tile,
|
| 279 |
+
num_experts,
|
| 280 |
+
NUM_EXPERTS_BIT_LENGTH,
|
| 281 |
+
BLOCK_SIZE_M,
|
| 282 |
+
BLOCK_SIZE_N,
|
| 283 |
+
BLOCK_SIZE_K,
|
| 284 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 285 |
|
| 286 |
a_ptrs = A + offs_global_m[:, None] * stride_am + offs_k[None, :] * stride_ak
|
| 287 |
b_ptrs = (
|
|
|
|
| 298 |
for _ in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
|
| 299 |
a = tl.load(a_ptrs, mask=row_mask[:, None], other=0.0)
|
| 300 |
b = tl.load(b_ptrs)
|
|
|
|
| 301 |
accumulator += tl.dot(a, b)
|
| 302 |
a_ptrs += BLOCK_SIZE_K * stride_ak
|
| 303 |
b_ptrs += BLOCK_SIZE_K * stride_bk
|
|
|
|
| 304 |
accumulator = accumulator * a_s[:, None] * b_s
|
| 305 |
|
| 306 |
+
store_tile(C, accumulator, offs_global_m, offs_bn, row_mask, stride_cm, stride_cn)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 307 |
|
|
|
|
|
|
|
|
|
|
| 308 |
|
| 309 |
+
@bayesian_autotune(
|
| 310 |
+
get_mxfp_autotuning_configs(
|
| 311 |
+
compute_modes=("dot_scaled", "dot")
|
| 312 |
+
), # prefill: no scalar branch
|
| 313 |
+
["N", "K", "BLOCK_SIZE_M"],
|
| 314 |
+
n_trials=60,
|
| 315 |
+
)
|
| 316 |
+
@triton.jit
|
| 317 |
+
def mxfp_dynamic_matmul_grouped_kernel(
|
| 318 |
+
A, # (S, K) raw BF16/FP16 activations, sorted by expert id
|
| 319 |
+
B, # (num_experts, N, K) E4M3 (MXFP8) or (num_experts, N, K // 2) packed E2M1 (MXFP4) expert weights
|
| 320 |
+
C, # (S, N) output
|
| 321 |
+
Bs, # (num_experts, N, K // SCALE_GROUP_K) UE8M0 weight scales
|
| 322 |
+
Offsets, # (num_experts,) int32 — cumulative row-end per expert
|
| 323 |
+
TileOffsets, # (num_experts,) int32 — cumulative tile-end per expert
|
| 324 |
+
# Shape
|
| 325 |
+
S,
|
| 326 |
+
N,
|
| 327 |
+
K,
|
| 328 |
+
# Strides
|
| 329 |
+
stride_am,
|
| 330 |
+
stride_ak,
|
| 331 |
+
stride_be,
|
| 332 |
+
stride_bk,
|
| 333 |
+
stride_bn,
|
| 334 |
+
stride_cm,
|
| 335 |
+
stride_cn,
|
| 336 |
+
stride_bs_e,
|
| 337 |
+
stride_bs_k,
|
| 338 |
+
stride_bs_n,
|
| 339 |
+
stride_offs,
|
| 340 |
+
stride_tile,
|
| 341 |
+
num_experts,
|
| 342 |
+
# Meta-parameters
|
| 343 |
+
BLOCK_SIZE_M: tl.constexpr,
|
| 344 |
+
BLOCK_SIZE_N: tl.constexpr,
|
| 345 |
+
BLOCK_SIZE_K: tl.constexpr,
|
| 346 |
+
NUM_EXPERTS_BIT_LENGTH: tl.constexpr,
|
| 347 |
+
VALUES_PER_BYTE: tl.constexpr,
|
| 348 |
+
SCALE_GROUP_K: tl.constexpr,
|
| 349 |
+
COMPUTE_MODE: tl.constexpr,
|
| 350 |
+
):
|
| 351 |
+
"""Unified grouped MXFP4/MXFP8 (W4A8/W8A8) expert matmul with fused act quant.
|
| 352 |
+
|
| 353 |
+
Tokens sorted by expert; each M-tile maps to its expert via ``TileOffsets``. ``A`` is
|
| 354 |
+
quantized to E4M3 per K-group inline (UE8M0 scale). ``VALUES_PER_BYTE`` picks the
|
| 355 |
+
weight format (2 = packed E2M1 / MXFP4, 1 = unpacked E4M3 / MXFP8); ``COMPUTE_MODE``
|
| 356 |
+
picks ``tl.dot_scaled`` vs fp8 ``tl.dot`` + per-group rescale (decode; FP4 unpacks
|
| 357 |
+
E2M1->E4M3 first, lossless).
|
| 358 |
+
"""
|
| 359 |
+
pid_m = tl.program_id(axis=0)
|
| 360 |
+
pid_n = tl.program_id(axis=1)
|
| 361 |
+
|
| 362 |
+
total_tiles = tl.load(TileOffsets + (num_experts - 1) * stride_tile)
|
| 363 |
+
if pid_m >= total_tiles:
|
| 364 |
+
return
|
| 365 |
+
|
| 366 |
+
expert_id, offs_global_m, row_mask, offs_bn, offs_k = _grouped_tile_setup(
|
| 367 |
+
pid_m,
|
| 368 |
+
pid_n,
|
| 369 |
+
Offsets,
|
| 370 |
+
TileOffsets,
|
| 371 |
+
stride_offs,
|
| 372 |
+
stride_tile,
|
| 373 |
+
num_experts,
|
| 374 |
+
NUM_EXPERTS_BIT_LENGTH,
|
| 375 |
+
BLOCK_SIZE_M,
|
| 376 |
+
BLOCK_SIZE_N,
|
| 377 |
+
BLOCK_SIZE_K,
|
| 378 |
+
)
|
| 379 |
+
offs_kb = tl.arange(0, BLOCK_SIZE_K // VALUES_PER_BYTE)
|
| 380 |
+
offs_sf = tl.arange(0, BLOCK_SIZE_K // SCALE_GROUP_K)
|
| 381 |
+
|
| 382 |
+
a_ptrs = A + offs_global_m[:, None] * stride_am + offs_k[None, :] * stride_ak
|
| 383 |
+
b_ptrs = (
|
| 384 |
+
B
|
| 385 |
+
+ expert_id * stride_be
|
| 386 |
+
+ offs_kb[:, None] * stride_bk
|
| 387 |
+
+ offs_bn[None, :] * stride_bn
|
| 388 |
+
)
|
| 389 |
+
bs_ptrs = (
|
| 390 |
+
Bs
|
| 391 |
+
+ expert_id * stride_bs_e
|
| 392 |
+
+ offs_bn[:, None] * stride_bs_n
|
| 393 |
+
+ offs_sf[None, :] * stride_bs_k
|
| 394 |
+
)
|
| 395 |
|
| 396 |
+
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
| 397 |
+
for _ in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
|
| 398 |
+
a_raw = tl.load(a_ptrs, mask=row_mask[:, None], other=0.0).to(tl.float32)
|
| 399 |
+
a, a_scale = mxfp_act_quant_inline(
|
| 400 |
+
a_raw, BLOCK_SIZE_M, BLOCK_SIZE_K, SCALE_GROUP_K
|
| 401 |
+
)
|
| 402 |
+
b = tl.load(b_ptrs)
|
| 403 |
+
b_s = tl.load(bs_ptrs).to(tl.uint8)
|
| 404 |
+
bq = mxfp4_e2m1_to_e4m3(b) if VALUES_PER_BYTE == 2 else b
|
| 405 |
+
if COMPUTE_MODE == "dot_scaled":
|
| 406 |
+
accumulator = mx_dot_scaled(
|
| 407 |
+
a, a_scale, b, b_s, accumulator, VALUES_PER_BYTE
|
| 408 |
+
)
|
| 409 |
+
else: # dot
|
| 410 |
+
accumulator = mx_dot_rescale(accumulator, a, bq, a_scale, b_s)
|
| 411 |
+
a_ptrs += BLOCK_SIZE_K * stride_ak
|
| 412 |
+
b_ptrs += (BLOCK_SIZE_K // VALUES_PER_BYTE) * stride_bk
|
| 413 |
+
bs_ptrs += (BLOCK_SIZE_K // SCALE_GROUP_K) * stride_bs_k
|
| 414 |
+
|
| 415 |
+
store_tile(C, accumulator, offs_global_m, offs_bn, row_mask, stride_cm, stride_cn)
|
| 416 |
+
|
| 417 |
+
|
| 418 |
+
@triton_op(
|
| 419 |
+
add_op_namespace_prefix("w8a8_block_dynamic_fp8_matmul_grouped"), mutates_args=()
|
| 420 |
+
)
|
| 421 |
+
def _w8a8_block_dynamic_fp8_matmul_grouped(
|
| 422 |
A: torch.Tensor,
|
| 423 |
B: torch.Tensor,
|
| 424 |
Bs: torch.Tensor,
|
| 425 |
offsets: torch.Tensor,
|
| 426 |
tokens_per_expert: torch.Tensor,
|
| 427 |
block_size: list[int],
|
| 428 |
+
output_dtype: torch.dtype | None = None,
|
| 429 |
) -> torch.Tensor:
|
| 430 |
"""Block-scale grouped FP8 matmul: C = A @ B.T per expert, with fused act quant.
|
| 431 |
|
| 432 |
A: (S, K) raw bf16/fp16 activations, sorted by expert
|
| 433 |
+
B: (num_experts, N, K) FP8 expert weights
|
| 434 |
+
Bs: (num_experts, N // block_n, K // block_k) per-block weight scales
|
| 435 |
"""
|
| 436 |
assert A.ndim == 2, f"A must be 2D (S, K), got ndim={A.ndim}"
|
| 437 |
assert A.is_contiguous(), "A must be contiguous"
|
| 438 |
+
assert B.ndim == 3, f"B must be 3D (num_experts, N, K), got ndim={B.ndim}"
|
| 439 |
assert B.is_contiguous(), "B must be contiguous"
|
| 440 |
assert A.shape[1] == B.shape[2], (
|
| 441 |
f"K mismatch: A has K={A.shape[1]}, B has K={B.shape[2]}"
|
| 442 |
)
|
| 443 |
|
| 444 |
S, K = A.shape
|
| 445 |
+
num_experts, N, _ = B.shape
|
| 446 |
|
| 447 |
assert len(block_size) == 2, (
|
| 448 |
f"block_size must be [block_n, block_k], got {block_size}"
|
|
|
|
| 452 |
assert N % block_n == 0, f"N ({N}) must be divisible by block_n ({block_n})"
|
| 453 |
assert K % block_k == 0, f"K ({K}) must be divisible by block_k ({block_k})"
|
| 454 |
assert Bs.ndim == 3, (
|
| 455 |
+
f"Bs must be 3D (num_experts, N//block_n, K//block_k), got ndim={Bs.ndim}"
|
| 456 |
)
|
| 457 |
+
assert Bs.shape == (num_experts, N // block_n, K // block_k), (
|
| 458 |
+
f"Bs shape {tuple(Bs.shape)} != expected ({num_experts}, {N // block_n}, {K // block_k})"
|
| 459 |
)
|
| 460 |
|
| 461 |
+
Bs = ue8m0_as_uint8(Bs)
|
| 462 |
+
C = A.new_empty(S, N, dtype=output_dtype)
|
| 463 |
+
BLOCK_SIZE_M = adaptive_block_size_m((S + num_experts - 1) // num_experts)
|
| 464 |
+
tile_offsets, max_m_tiles = _grouped_tile_layout(
|
| 465 |
+
tokens_per_expert, BLOCK_SIZE_M, S, num_experts
|
| 466 |
+
)
|
| 467 |
+
grid = (max_m_tiles, triton.cdiv(N, block_n))
|
| 468 |
+
|
|
|
|
|
|
|
|
|
|
| 469 |
with device_context(A.device):
|
| 470 |
+
wrap_triton(w8a8_block_dynamic_fp8_matmul_grouped_kernel)[grid](
|
| 471 |
A,
|
| 472 |
B,
|
| 473 |
C,
|
|
|
|
| 487 |
Bs.stride(0),
|
| 488 |
Bs.stride(2),
|
| 489 |
Bs.stride(1),
|
| 490 |
+
offsets.stride(0),
|
| 491 |
+
tile_offsets.stride(0),
|
| 492 |
# Meta-parameters
|
| 493 |
+
num_experts=num_experts,
|
| 494 |
BLOCK_SIZE_N=block_n,
|
| 495 |
BLOCK_SIZE_K=block_k,
|
| 496 |
BLOCK_SIZE_M=BLOCK_SIZE_M,
|
| 497 |
+
NUM_EXPERTS_BIT_LENGTH=num_experts.bit_length(),
|
| 498 |
)
|
| 499 |
|
| 500 |
return C
|
| 501 |
|
| 502 |
|
| 503 |
+
@triton_op(
|
| 504 |
+
add_op_namespace_prefix("w8a8_tensor_dynamic_fp8_matmul_grouped"), mutates_args=()
|
| 505 |
+
)
|
| 506 |
+
def _w8a8_tensor_dynamic_fp8_matmul_grouped(
|
| 507 |
A: torch.Tensor,
|
| 508 |
B: torch.Tensor,
|
| 509 |
Bs: torch.Tensor,
|
| 510 |
offsets: torch.Tensor,
|
| 511 |
tokens_per_expert: torch.Tensor,
|
| 512 |
+
output_dtype: torch.dtype | None = None,
|
| 513 |
) -> torch.Tensor:
|
| 514 |
"""Tensor-scale grouped FP8 matmul: C = A @ B.T per expert, with fused act quant.
|
| 515 |
|
| 516 |
A: (S, K) raw bf16/fp16 activations, sorted by expert
|
| 517 |
+
B: (num_experts, N, K) FP8 expert weights
|
| 518 |
+
Bs: (num_experts,) or (num_experts, 1, 1) per-expert weight scales
|
| 519 |
"""
|
| 520 |
assert A.ndim == 2, f"A must be 2D (S, K), got ndim={A.ndim}"
|
| 521 |
assert A.is_contiguous(), "A must be contiguous"
|
| 522 |
+
assert B.ndim == 3, f"B must be 3D (num_experts, N, K), got ndim={B.ndim}"
|
| 523 |
assert B.is_contiguous(), "B must be contiguous"
|
| 524 |
assert A.shape[1] == B.shape[2], (
|
| 525 |
f"K mismatch: A has K={A.shape[1]}, B has K={B.shape[2]}"
|
| 526 |
)
|
| 527 |
|
| 528 |
S, K = A.shape
|
| 529 |
+
num_experts, N, _ = B.shape
|
| 530 |
|
| 531 |
+
# Normalize Bs to (num_experts, 1, 1)
|
| 532 |
if Bs.ndim == 1:
|
| 533 |
+
assert Bs.shape[0] == num_experts, (
|
| 534 |
+
f"Bs shape {tuple(Bs.shape)} != expected ({num_experts},)"
|
| 535 |
+
)
|
| 536 |
+
Bs = Bs.reshape(num_experts, 1, 1)
|
| 537 |
else:
|
| 538 |
+
assert Bs.shape == (num_experts, 1, 1), (
|
| 539 |
+
f"Bs shape {tuple(Bs.shape)} != expected ({num_experts}, 1, 1)"
|
| 540 |
)
|
| 541 |
|
|
|
|
|
|
|
|
|
|
| 542 |
qA, As = fp8_act_quant(A, K)
|
| 543 |
+
C = A.new_empty(S, N, dtype=output_dtype)
|
| 544 |
+
BLOCK_SIZE_M = adaptive_block_size_m((S + num_experts - 1) // num_experts)
|
| 545 |
+
tile_offsets, max_m_tiles = _grouped_tile_layout(
|
| 546 |
+
tokens_per_expert, BLOCK_SIZE_M, S, num_experts
|
| 547 |
+
)
|
| 548 |
+
|
| 549 |
+
def grid(META):
|
| 550 |
+
return (max_m_tiles, triton.cdiv(N, META["BLOCK_SIZE_N"]))
|
| 551 |
+
|
| 552 |
with device_context(A.device):
|
| 553 |
+
wrap_triton(w8a8_tensor_dynamic_fp8_matmul_grouped_kernel)[grid](
|
| 554 |
qA,
|
| 555 |
B,
|
| 556 |
C,
|
|
|
|
| 570 |
C.stride(1),
|
| 571 |
As.stride(0),
|
| 572 |
Bs.stride(0),
|
| 573 |
+
offsets.stride(0),
|
| 574 |
+
tile_offsets.stride(0),
|
| 575 |
+
# Meta-parameters (BLOCK_SIZE_N, BLOCK_SIZE_K come from autotune Config)
|
| 576 |
+
num_experts=num_experts,
|
| 577 |
BLOCK_SIZE_M=BLOCK_SIZE_M,
|
| 578 |
+
NUM_EXPERTS_BIT_LENGTH=num_experts.bit_length(),
|
| 579 |
)
|
| 580 |
|
| 581 |
return C
|
| 582 |
|
| 583 |
|
| 584 |
+
def w8a8_block_dynamic_fp8_matmul_grouped(
|
| 585 |
A: torch.Tensor,
|
| 586 |
B: torch.Tensor,
|
| 587 |
Bs: torch.Tensor,
|
| 588 |
offsets: torch.Tensor,
|
| 589 |
tokens_per_expert: torch.Tensor,
|
| 590 |
block_size: list[int],
|
| 591 |
+
output_dtype: torch.dtype | None = None,
|
| 592 |
) -> torch.Tensor:
|
| 593 |
"""Block-scale grouped FP8 matmul with fused activation quantization.
|
| 594 |
|
| 595 |
A: (S, K) raw activations sorted by expert, bf16/fp16/fp32
|
| 596 |
+
B: (num_experts, N, K) FP8 expert weights
|
| 597 |
+
Bs: (num_experts, N // block_n, K // block_k) per-block weight scales
|
| 598 |
+
output_dtype: defaults to ``A.dtype``
|
| 599 |
"""
|
| 600 |
+
return ops.w8a8_block_dynamic_fp8_matmul_grouped(
|
| 601 |
+
A, B, Bs, offsets, tokens_per_expert, block_size, output_dtype
|
| 602 |
)
|
| 603 |
|
| 604 |
|
| 605 |
+
def w8a8_tensor_dynamic_fp8_matmul_grouped(
|
| 606 |
A: torch.Tensor,
|
| 607 |
B: torch.Tensor,
|
| 608 |
Bs: torch.Tensor,
|
| 609 |
offsets: torch.Tensor,
|
| 610 |
tokens_per_expert: torch.Tensor,
|
| 611 |
+
output_dtype: torch.dtype | None = None,
|
| 612 |
) -> torch.Tensor:
|
| 613 |
"""Tensor-scale grouped FP8 matmul with fused activation quantization.
|
| 614 |
|
| 615 |
A: (S, K) raw activations sorted by expert, bf16/fp16/fp32
|
| 616 |
+
B: (num_experts, N, K) FP8 expert weights
|
| 617 |
+
Bs: (num_experts,) or (num_experts, 1, 1) per-expert weight scales
|
| 618 |
+
output_dtype: defaults to ``A.dtype``
|
| 619 |
+
"""
|
| 620 |
+
return ops.w8a8_tensor_dynamic_fp8_matmul_grouped(
|
| 621 |
+
A, B, Bs, offsets, tokens_per_expert, output_dtype
|
| 622 |
+
)
|
| 623 |
+
|
| 624 |
+
|
| 625 |
+
@triton_op(add_op_namespace_prefix("mxfp_dynamic_matmul_grouped"), mutates_args=())
|
| 626 |
+
def _mxfp_dynamic_matmul_grouped(
|
| 627 |
+
A: torch.Tensor,
|
| 628 |
+
B: torch.Tensor,
|
| 629 |
+
Bs: torch.Tensor,
|
| 630 |
+
offsets: torch.Tensor,
|
| 631 |
+
tokens_per_expert: torch.Tensor,
|
| 632 |
+
output_dtype: torch.dtype | None = None,
|
| 633 |
+
) -> torch.Tensor:
|
| 634 |
+
"""Grouped MX matmul with fused act quant — per-expert ``C[s] = A[s] @ B[e].T`` over
|
| 635 |
+
contiguous, expert-sorted rows. Weight format detected from ``B.dtype``: ``int8`` →
|
| 636 |
+
packed E2M1 (MXFP4, ``B`` is ``(num_experts, N, K//2)``); ``float8_e4m3fn`` → unpacked E4M3
|
| 637 |
+
(MXFP8, ``(num_experts, N, K)``). UE8M0 group-32 scales ``(num_experts, N, K//32)``; tile + dot autotuned.
|
| 638 |
+
|
| 639 |
+
A: (S, K) raw activations, bf16/fp16/fp32, expert-sorted (quantized inline to E4M3)
|
| 640 |
+
offsets: (num_experts,) — exclusive prefix of expert token counts (cumsum)
|
| 641 |
+
tokens_per_expert: (num_experts,) — per-expert row count
|
| 642 |
"""
|
| 643 |
+
assert A.ndim == 2 and B.ndim == 3 and Bs.ndim == 3
|
| 644 |
+
assert offsets.ndim == 1 and tokens_per_expert.ndim == 1
|
| 645 |
+
assert B.dtype in (torch.int8, torch.float8_e4m3fn), (
|
| 646 |
+
f"B must be int8 (packed E2M1) or float8_e4m3fn (E4M3), got {B.dtype}"
|
| 647 |
+
)
|
| 648 |
+
assert Bs.dtype == torch.float8_e8m0fnu, (
|
| 649 |
+
f"Bs must be float8_e8m0fnu, got {Bs.dtype}"
|
| 650 |
+
)
|
| 651 |
+
VALUES_PER_BYTE = NIBBLES_PER_BYTE if B.dtype == torch.int8 else 1
|
| 652 |
+
|
| 653 |
+
S, K = A.shape
|
| 654 |
+
num_experts, N, K_b = B.shape
|
| 655 |
+
assert offsets.shape[0] == num_experts and tokens_per_expert.shape[0] == num_experts
|
| 656 |
+
assert K == VALUES_PER_BYTE * K_b, (
|
| 657 |
+
f"K (={K}) must equal {VALUES_PER_BYTE} * B.shape[2] (={K_b})"
|
| 658 |
+
)
|
| 659 |
+
assert K % MX_SCALE_GROUP_K == 0, (
|
| 660 |
+
f"K (={K}) must be a multiple of {MX_SCALE_GROUP_K}"
|
| 661 |
+
)
|
| 662 |
+
assert Bs.shape == (num_experts, N, K // MX_SCALE_GROUP_K), (
|
| 663 |
+
f"Bs shape {tuple(Bs.shape)} != ({num_experts}, {N}, {K // MX_SCALE_GROUP_K})"
|
| 664 |
+
)
|
| 665 |
+
|
| 666 |
+
B = e2m1_as_uint8(B)
|
| 667 |
+
bs_u8 = ue8m0_as_uint8(Bs)
|
| 668 |
+
C = A.new_empty((S, N), dtype=output_dtype)
|
| 669 |
+
BLOCK_SIZE_M = adaptive_block_size_m((S + num_experts - 1) // num_experts)
|
| 670 |
+
tile_offsets, max_m_tiles = _grouped_tile_layout(
|
| 671 |
+
tokens_per_expert, BLOCK_SIZE_M, S, num_experts
|
| 672 |
+
)
|
| 673 |
+
|
| 674 |
+
def grid(META):
|
| 675 |
+
return (max_m_tiles, triton.cdiv(N, META["BLOCK_SIZE_N"]))
|
| 676 |
+
|
| 677 |
+
with device_context(A.device):
|
| 678 |
+
wrap_triton(mxfp_dynamic_matmul_grouped_kernel)[grid](
|
| 679 |
+
A,
|
| 680 |
+
B,
|
| 681 |
+
C,
|
| 682 |
+
bs_u8,
|
| 683 |
+
offsets,
|
| 684 |
+
tile_offsets,
|
| 685 |
+
S,
|
| 686 |
+
N,
|
| 687 |
+
K,
|
| 688 |
+
A.stride(0),
|
| 689 |
+
A.stride(1),
|
| 690 |
+
B.stride(0),
|
| 691 |
+
B.stride(2),
|
| 692 |
+
B.stride(1),
|
| 693 |
+
C.stride(0),
|
| 694 |
+
C.stride(1),
|
| 695 |
+
bs_u8.stride(0),
|
| 696 |
+
bs_u8.stride(2),
|
| 697 |
+
bs_u8.stride(1),
|
| 698 |
+
offsets.stride(0),
|
| 699 |
+
tile_offsets.stride(0),
|
| 700 |
+
# Meta-parameters (BLOCK_SIZE_N, BLOCK_SIZE_K come from autotune Config)
|
| 701 |
+
num_experts=num_experts,
|
| 702 |
+
BLOCK_SIZE_M=BLOCK_SIZE_M,
|
| 703 |
+
NUM_EXPERTS_BIT_LENGTH=num_experts.bit_length(),
|
| 704 |
+
VALUES_PER_BYTE=VALUES_PER_BYTE,
|
| 705 |
+
SCALE_GROUP_K=MX_SCALE_GROUP_K,
|
| 706 |
+
)
|
| 707 |
+
return C
|
| 708 |
+
|
| 709 |
+
|
| 710 |
+
def mxfp_dynamic_matmul_grouped(
|
| 711 |
+
A: torch.Tensor,
|
| 712 |
+
B: torch.Tensor,
|
| 713 |
+
Bs: torch.Tensor,
|
| 714 |
+
offsets: torch.Tensor,
|
| 715 |
+
tokens_per_expert: torch.Tensor,
|
| 716 |
+
output_dtype: torch.dtype | None = None,
|
| 717 |
+
) -> torch.Tensor:
|
| 718 |
+
"""Grouped MX (MXFP4/MXFP8) matmul with fused act quant — per-expert
|
| 719 |
+
``C[s] = A[s] @ B[e].T`` over expert-sorted rows; weight format detected from
|
| 720 |
+
``B.dtype``. Tile + dot path autotuned."""
|
| 721 |
+
return ops.mxfp_dynamic_matmul_grouped(
|
| 722 |
+
A, B, Bs, offsets, tokens_per_expert, output_dtype
|
| 723 |
)
|
| 724 |
|
| 725 |
|
| 726 |
+
def matmul_grouped(
|
| 727 |
A: torch.Tensor,
|
| 728 |
B: torch.Tensor,
|
| 729 |
Bs: torch.Tensor,
|
| 730 |
offsets: torch.Tensor,
|
| 731 |
tokens_per_expert: torch.Tensor,
|
| 732 |
block_size: list[int] | None,
|
| 733 |
+
output_dtype: torch.dtype | None = None,
|
| 734 |
) -> torch.Tensor:
|
| 735 |
+
"""Grouped quantized matmul dispatcher (W8A8 FP8 or W4A8 FP4). Tokens must
|
| 736 |
+
be sorted by expert; M-tiles are mapped to experts via ``offsets``.
|
| 737 |
|
| 738 |
+
``output_dtype`` defaults to ``A.dtype``.
|
|
|
|
|
|
|
|
|
|
| 739 |
|
| 740 |
+
Routes by weight dtype and ``block_size``:
|
| 741 |
+
- MX weights — ``int8`` (packed E2M1) or ``float8_e4m3fn`` (E4M3) with UE8M0
|
| 742 |
+
group-32 ``Bs`` (shape ``[num_experts, N, K//32]``) → ``mxfp_dynamic_matmul_grouped``
|
| 743 |
+
(``block_size`` ignored; tile + dot path autotuned).
|
| 744 |
+
- ``block_size`` None or full ``[N, K]`` → ``w8a8_tensor_dynamic_fp8_matmul_grouped``.
|
| 745 |
+
- otherwise → ``w8a8_block_dynamic_fp8_matmul_grouped``.
|
| 746 |
"""
|
| 747 |
+
if is_mxfp(B, Bs):
|
| 748 |
+
return mxfp_dynamic_matmul_grouped(
|
| 749 |
+
A, B, Bs, offsets, tokens_per_expert, output_dtype
|
| 750 |
+
)
|
| 751 |
+
|
| 752 |
+
if is_tensor_wide(block_size, B):
|
| 753 |
+
return w8a8_tensor_dynamic_fp8_matmul_grouped(
|
| 754 |
+
A, B, Bs, offsets, tokens_per_expert, output_dtype
|
| 755 |
+
)
|
| 756 |
|
| 757 |
+
return w8a8_block_dynamic_fp8_matmul_grouped(
|
| 758 |
+
A, B, Bs, offsets, tokens_per_expert, block_size, output_dtype
|
| 759 |
)
|
build/torch-cuda/matmul.py
CHANGED
|
@@ -17,38 +17,103 @@ import triton
|
|
| 17 |
import triton.language as tl
|
| 18 |
from torch.library import triton_op, wrap_triton
|
| 19 |
|
| 20 |
-
from .
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
|
| 22 |
|
| 23 |
-
# Adapted from https://github.com/sgl-project/sglang/blob/main/python/sglang/srt/layers/quantization/fp8_kernel.py
|
| 24 |
@triton.autotune(
|
| 25 |
-
configs=
|
| 26 |
-
triton.Config({}, num_warps=w, num_stages=s)
|
| 27 |
-
for w in [2, 4, 8, 16]
|
| 28 |
-
for s in [2, 3, 4]
|
| 29 |
-
],
|
| 30 |
key=["N", "K", "BLOCK_SIZE_M"],
|
| 31 |
)
|
| 32 |
@triton.jit
|
| 33 |
-
def
|
| 34 |
-
#
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
Bs,
|
| 40 |
-
# Shape for matmul
|
| 41 |
M,
|
| 42 |
N,
|
| 43 |
K,
|
|
|
|
| 44 |
stride_am,
|
| 45 |
stride_ak,
|
| 46 |
stride_bk,
|
| 47 |
stride_bn,
|
| 48 |
stride_cm,
|
| 49 |
stride_cn,
|
| 50 |
-
stride_as_m,
|
| 51 |
-
stride_as_k,
|
| 52 |
stride_bs_k,
|
| 53 |
stride_bs_n,
|
| 54 |
# Meta-parameters
|
|
@@ -57,72 +122,65 @@ def w8a8_block_fp8_matmul_kernel(
|
|
| 57 |
BLOCK_SIZE_K: tl.constexpr,
|
| 58 |
GROUP_SIZE_M: tl.constexpr,
|
| 59 |
):
|
| 60 |
-
"""Block-scale FP8 GEMM kernel.
|
| 61 |
|
| 62 |
-
Computes ``C = A @ B.T`` with
|
| 63 |
-
|
|
|
|
| 64 |
"""
|
| 65 |
-
pid_m =
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
num_pid_n = tl.cdiv(N, BLOCK_SIZE_N)
|
| 69 |
-
pid_m, pid_n = tl.swizzle2d(pid_m, pid_n, num_pid_m, num_pid_n, GROUP_SIZE_M)
|
| 70 |
-
|
| 71 |
-
offs_am = (pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)) % M
|
| 72 |
-
offs_bn = (pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)) % N
|
| 73 |
-
offs_k = tl.arange(0, BLOCK_SIZE_K)
|
| 74 |
a_ptrs = A + (offs_am[:, None] * stride_am + offs_k[None, :] * stride_ak)
|
| 75 |
b_ptrs = B + (offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn)
|
| 76 |
|
| 77 |
-
as_ptrs = As + offs_am * stride_as_m
|
| 78 |
offs_bsn = offs_bn // BLOCK_SIZE_N
|
| 79 |
bs_ptrs = Bs + offs_bsn * stride_bs_n
|
| 80 |
|
| 81 |
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
| 82 |
for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
|
| 83 |
k_remaining = K - k * BLOCK_SIZE_K
|
| 84 |
-
|
|
|
|
|
|
|
|
|
|
| 85 |
b = tl.load(b_ptrs, mask=offs_k[:, None] < k_remaining, other=0.0)
|
| 86 |
-
|
| 87 |
-
a_s = tl.load(as_ptrs + k * stride_as_k)
|
| 88 |
-
b_s = tl.load(bs_ptrs + k * stride_bs_k)
|
| 89 |
-
|
| 90 |
accumulator += tl.dot(a, b) * a_s[:, None] * b_s[None, :]
|
| 91 |
a_ptrs += BLOCK_SIZE_K * stride_ak
|
| 92 |
b_ptrs += BLOCK_SIZE_K * stride_bk
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
|
|
|
| 106 |
|
| 107 |
|
| 108 |
@triton.autotune(
|
| 109 |
-
configs=
|
| 110 |
-
triton.Config({}, num_warps=w, num_stages=s)
|
| 111 |
-
for w in [2, 4, 8, 16]
|
| 112 |
-
for s in [2, 3, 4]
|
| 113 |
-
],
|
| 114 |
key=["N", "K", "BLOCK_SIZE_M"],
|
| 115 |
)
|
| 116 |
@triton.jit
|
| 117 |
-
def
|
| 118 |
-
A,
|
| 119 |
-
B,
|
| 120 |
-
C,
|
| 121 |
-
As,
|
| 122 |
-
Bs,
|
|
|
|
| 123 |
M,
|
| 124 |
N,
|
| 125 |
K,
|
|
|
|
| 126 |
stride_am,
|
| 127 |
stride_ak,
|
| 128 |
stride_bk,
|
|
@@ -130,6 +188,7 @@ def w8a8_tensor_fp8_matmul_kernel(
|
|
| 130 |
stride_cm,
|
| 131 |
stride_cn,
|
| 132 |
stride_as_m,
|
|
|
|
| 133 |
BLOCK_SIZE_M: tl.constexpr,
|
| 134 |
BLOCK_SIZE_N: tl.constexpr,
|
| 135 |
BLOCK_SIZE_K: tl.constexpr,
|
|
@@ -141,16 +200,9 @@ def w8a8_tensor_fp8_matmul_kernel(
|
|
| 141 |
weight scale for the full matrix.
|
| 142 |
Uses a 2D grid with swizzle for L2 cache locality on B tiles.
|
| 143 |
"""
|
| 144 |
-
pid_m =
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
num_pid_n = tl.cdiv(N, BLOCK_SIZE_N)
|
| 148 |
-
pid_m, pid_n = tl.swizzle2d(pid_m, pid_n, num_pid_m, num_pid_n, GROUP_SIZE_M)
|
| 149 |
-
|
| 150 |
-
offs_am = (pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)) % M
|
| 151 |
-
offs_bn = (pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)) % N
|
| 152 |
-
offs_k = tl.arange(0, BLOCK_SIZE_K)
|
| 153 |
-
|
| 154 |
a_ptrs = A + offs_am[:, None] * stride_am + offs_k[None, :] * stride_ak
|
| 155 |
b_ptrs = B + offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn
|
| 156 |
|
|
@@ -169,33 +221,201 @@ def w8a8_tensor_fp8_matmul_kernel(
|
|
| 169 |
|
| 170 |
accumulator = accumulator * a_s[:, None] * b_s
|
| 171 |
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 178 |
|
| 179 |
-
offs_cm = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
|
| 180 |
-
offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
|
| 181 |
-
c_ptrs = C + stride_cm * offs_cm[:, None] + stride_cn * offs_cn[None, :]
|
| 182 |
-
c_mask = (offs_cm[:, None] < M) & (offs_cn[None, :] < N)
|
| 183 |
-
tl.store(c_ptrs, c, mask=c_mask)
|
| 184 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 185 |
|
| 186 |
-
|
| 187 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 188 |
A: torch.Tensor,
|
| 189 |
B: torch.Tensor,
|
| 190 |
-
As: torch.Tensor,
|
| 191 |
Bs: torch.Tensor,
|
| 192 |
block_size: list[int],
|
| 193 |
-
output_dtype: torch.dtype =
|
| 194 |
) -> torch.Tensor:
|
| 195 |
-
"""Block-scale FP8 matmul: C = A @ B.T with
|
| 196 |
|
| 197 |
-
|
| 198 |
-
|
|
|
|
| 199 |
"""
|
| 200 |
assert len(block_size) == 2, (
|
| 201 |
f"block_size must be [block_n, block_k], got {block_size}"
|
|
@@ -212,10 +432,6 @@ def _w8a8_block_fp8_matmul(
|
|
| 212 |
N, K = B.shape
|
| 213 |
M = A.numel() // A.shape[-1]
|
| 214 |
|
| 215 |
-
assert As.ndim >= 2, f"As must be at least 2D, got ndim={As.ndim}"
|
| 216 |
-
assert As.shape[-1] == triton.cdiv(K, block_k), (
|
| 217 |
-
f"As last dim {As.shape[-1]} != expected {triton.cdiv(K, block_k)} (cdiv(K={K}, block_k={block_k}))"
|
| 218 |
-
)
|
| 219 |
assert Bs.ndim == 2, f"Bs must be 2D (N//block_n, K//block_k), got ndim={Bs.ndim}"
|
| 220 |
assert Bs.shape == (triton.cdiv(N, block_n), triton.cdiv(K, block_k)), (
|
| 221 |
f"Bs shape {tuple(Bs.shape)} != expected ({triton.cdiv(N, block_n)}, {triton.cdiv(K, block_k)})"
|
|
@@ -223,20 +439,17 @@ def _w8a8_block_fp8_matmul(
|
|
| 223 |
|
| 224 |
BLOCK_SIZE_K = block_k
|
| 225 |
BLOCK_SIZE_N = block_n
|
|
|
|
| 226 |
C_shape = A.shape[:-1] + (N,)
|
|
|
|
| 227 |
C = A.new_empty(C_shape, dtype=output_dtype)
|
| 228 |
-
# Adaptive BLOCK_SIZE_M: smallest power-of-2 >= M, floored at 16, capped at 128.
|
| 229 |
-
# Matches the WGMMA tile to the actual row count — smaller tiles use less
|
| 230 |
-
# register pressure and a better-matched FP8 WGMMA instruction, improving
|
| 231 |
-
# both accuracy and performance for small M (decode).
|
| 232 |
-
BLOCK_SIZE_M = min(max(triton.next_power_of_2(M), 16), 128)
|
| 233 |
grid = (triton.cdiv(M, BLOCK_SIZE_M), triton.cdiv(N, BLOCK_SIZE_N))
|
|
|
|
| 234 |
with device_context(A.device):
|
| 235 |
-
wrap_triton(
|
| 236 |
A,
|
| 237 |
B,
|
| 238 |
C,
|
| 239 |
-
As,
|
| 240 |
Bs,
|
| 241 |
M,
|
| 242 |
N,
|
|
@@ -247,64 +460,73 @@ def _w8a8_block_fp8_matmul(
|
|
| 247 |
B.stride(0),
|
| 248 |
C.stride(-2),
|
| 249 |
C.stride(-1),
|
| 250 |
-
As.stride(-2),
|
| 251 |
-
As.stride(-1),
|
| 252 |
Bs.stride(1),
|
| 253 |
Bs.stride(0),
|
| 254 |
# Meta-parameters
|
| 255 |
BLOCK_SIZE_M=BLOCK_SIZE_M,
|
| 256 |
BLOCK_SIZE_N=BLOCK_SIZE_N,
|
| 257 |
BLOCK_SIZE_K=BLOCK_SIZE_K,
|
| 258 |
-
GROUP_SIZE_M=
|
| 259 |
)
|
| 260 |
|
| 261 |
return C
|
| 262 |
|
| 263 |
|
| 264 |
-
@triton_op("
|
| 265 |
-
def
|
| 266 |
A: torch.Tensor,
|
| 267 |
B: torch.Tensor,
|
| 268 |
-
As: torch.Tensor,
|
| 269 |
Bs: torch.Tensor,
|
| 270 |
-
|
|
|
|
|
|
|
| 271 |
) -> torch.Tensor:
|
| 272 |
-
"""
|
| 273 |
|
| 274 |
-
|
| 275 |
-
|
|
|
|
|
|
|
| 276 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 277 |
assert A.shape[-1] == B.shape[-1], (
|
| 278 |
f"K mismatch: A has K={A.shape[-1]}, B has K={B.shape[-1]}"
|
| 279 |
)
|
| 280 |
assert A.is_contiguous(), "A must be contiguous"
|
| 281 |
assert B.ndim == 2, f"B must be 2D (N, K), got ndim={B.ndim}"
|
| 282 |
assert B.is_contiguous(), "B must be contiguous"
|
|
|
|
| 283 |
|
| 284 |
N, K = B.shape
|
| 285 |
M = A.numel() // A.shape[-1]
|
| 286 |
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
elif As.ndim == 2:
|
| 291 |
-
As = As.reshape(M)
|
| 292 |
-
assert As.ndim == 1 and As.shape[0] == M, (
|
| 293 |
-
f"As must be scalar, (M,), or (M,1) with M={M}, got {tuple(As.shape)}"
|
| 294 |
)
|
| 295 |
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
|
|
|
| 299 |
|
| 300 |
-
|
| 301 |
-
BLOCK_SIZE_K = 128
|
| 302 |
C_shape = A.shape[:-1] + (N,)
|
| 303 |
C = A.new_empty(C_shape, dtype=output_dtype)
|
| 304 |
-
|
| 305 |
-
|
| 306 |
with device_context(A.device):
|
| 307 |
-
wrap_triton(
|
| 308 |
A,
|
| 309 |
B,
|
| 310 |
C,
|
|
@@ -319,93 +541,277 @@ def _w8a8_tensor_fp8_matmul(
|
|
| 319 |
B.stride(0),
|
| 320 |
C.stride(-2),
|
| 321 |
C.stride(-1),
|
| 322 |
-
|
|
|
|
| 323 |
# Meta-parameters
|
| 324 |
BLOCK_SIZE_M=BLOCK_SIZE_M,
|
| 325 |
BLOCK_SIZE_N=BLOCK_SIZE_N,
|
| 326 |
BLOCK_SIZE_K=BLOCK_SIZE_K,
|
| 327 |
-
GROUP_SIZE_M=
|
| 328 |
)
|
| 329 |
|
| 330 |
return C
|
| 331 |
|
| 332 |
|
| 333 |
-
|
|
|
|
| 334 |
A: torch.Tensor,
|
| 335 |
B: torch.Tensor,
|
| 336 |
-
As: torch.Tensor,
|
| 337 |
Bs: torch.Tensor,
|
| 338 |
-
|
| 339 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 340 |
) -> torch.Tensor:
|
| 341 |
-
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 342 |
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 346 |
|
| 347 |
Args:
|
| 348 |
-
A:
|
| 349 |
-
|
| 350 |
-
|
| 351 |
Bs: Per-block weight scales ``[N // block_size[0], K // block_size[1]]``.
|
| 352 |
-
block_size: ``[block_n, block_k]`` quantization block dimensions
|
| 353 |
output_dtype: dtype of the returned tensor (default: ``torch.float32``).
|
| 354 |
|
| 355 |
Returns:
|
| 356 |
-
Output tensor ``[M, N]`` in ``output_dtype``.
|
| 357 |
"""
|
| 358 |
-
return
|
| 359 |
-
A, B, As, Bs, block_size, output_dtype
|
| 360 |
-
)
|
| 361 |
|
| 362 |
|
| 363 |
-
def
|
| 364 |
A: torch.Tensor,
|
| 365 |
B: torch.Tensor,
|
|
|
|
| 366 |
As: torch.Tensor,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 367 |
Bs: torch.Tensor,
|
| 368 |
-
output_dtype: torch.dtype =
|
| 369 |
) -> torch.Tensor:
|
| 370 |
-
"""Tensor-scale W8A8 FP8
|
| 371 |
|
| 372 |
-
Computes ``C = A @ B.T``
|
| 373 |
-
|
| 374 |
|
| 375 |
Args:
|
| 376 |
-
A:
|
| 377 |
-
B:
|
| 378 |
-
As: Per-row activation scales ``[M]``.
|
| 379 |
Bs: Single weight scale, scalar or ``[1]``.
|
| 380 |
output_dtype: dtype of the returned tensor.
|
|
|
|
|
|
|
| 381 |
|
| 382 |
-
|
| 383 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 384 |
"""
|
| 385 |
-
return
|
| 386 |
|
| 387 |
|
| 388 |
-
def
|
| 389 |
A: torch.Tensor,
|
| 390 |
B: torch.Tensor,
|
| 391 |
-
As: torch.Tensor,
|
| 392 |
Bs: torch.Tensor,
|
| 393 |
block_size: list[int] | None,
|
| 394 |
-
output_dtype: torch.dtype =
|
|
|
|
| 395 |
) -> torch.Tensor:
|
| 396 |
-
"""
|
| 397 |
|
| 398 |
-
|
| 399 |
-
|
| 400 |
-
|
| 401 |
-
- otherwise block mode
|
| 402 |
|
| 403 |
-
|
| 404 |
-
Output tensor ``[M, N]`` in ``output_dtype``.
|
| 405 |
-
"""
|
| 406 |
-
if block_size is None or (
|
| 407 |
-
block_size[0] == B.size(0) and block_size[1] == B.size(1)
|
| 408 |
-
):
|
| 409 |
-
return w8a8_tensor_fp8_matmul(A, B, As, Bs, output_dtype)
|
| 410 |
|
| 411 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
import triton.language as tl
|
| 18 |
from torch.library import triton_op, wrap_triton
|
| 19 |
|
| 20 |
+
from ._ops import add_op_namespace_prefix, ops
|
| 21 |
+
from .bayesian_autotuner import bayesian_autotune
|
| 22 |
+
from .utils import (
|
| 23 |
+
NIBBLES_PER_BYTE,
|
| 24 |
+
MX_SCALE_GROUP_K,
|
| 25 |
+
adaptive_block_size_m,
|
| 26 |
+
decode_ue8m0_scale,
|
| 27 |
+
mx_dot_rescale,
|
| 28 |
+
mx_dot_scaled,
|
| 29 |
+
device_context,
|
| 30 |
+
fp8_act_quant,
|
| 31 |
+
fp8_act_quant_inline,
|
| 32 |
+
get_accelerator_autotuning_configs,
|
| 33 |
+
get_mxfp_autotuning_configs,
|
| 34 |
+
is_mxfp,
|
| 35 |
+
is_tensor_wide,
|
| 36 |
+
mxfp_act_quant_inline,
|
| 37 |
+
mxfp4_e2m1_to_e4m3,
|
| 38 |
+
e2m1_as_uint8,
|
| 39 |
+
ue8m0_as_uint8,
|
| 40 |
+
)
|
| 41 |
+
|
| 42 |
+
# Swizzle group size for the 2D-grid kernels' L2-locality tiling (``_swizzle_offsets``) —
|
| 43 |
+
# a perf knob passed as the ``GROUP_SIZE_M`` constexpr, not a correctness parameter.
|
| 44 |
+
GROUP_SIZE_M = 8
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
@triton.jit
|
| 48 |
+
def _swizzle_offsets(
|
| 49 |
+
M,
|
| 50 |
+
N,
|
| 51 |
+
BLOCK_SIZE_M: tl.constexpr,
|
| 52 |
+
BLOCK_SIZE_N: tl.constexpr,
|
| 53 |
+
BLOCK_SIZE_K: tl.constexpr,
|
| 54 |
+
GROUP_SIZE_M: tl.constexpr,
|
| 55 |
+
):
|
| 56 |
+
"""2D-grid tile scheduling shared by the kernels below: swizzle the
|
| 57 |
+
``(pid_m, pid_n)`` program ids for L2 locality on B, then build the operand
|
| 58 |
+
offset vectors. Returns ``(pid_m, pid_n, offs_am, offs_bn, offs_k)`` — the
|
| 59 |
+
swizzled ids (reused by the output store) and the ``%``-wrapped row/col offsets
|
| 60 |
+
plus the K range."""
|
| 61 |
+
pid_m = tl.program_id(axis=0)
|
| 62 |
+
pid_n = tl.program_id(axis=1)
|
| 63 |
+
num_pid_m = tl.cdiv(M, BLOCK_SIZE_M)
|
| 64 |
+
num_pid_n = tl.cdiv(N, BLOCK_SIZE_N)
|
| 65 |
+
pid_m, pid_n = tl.swizzle2d(pid_m, pid_n, num_pid_m, num_pid_n, GROUP_SIZE_M)
|
| 66 |
+
offs_am = (pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)) % M
|
| 67 |
+
offs_bn = (pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)) % N
|
| 68 |
+
offs_k = tl.arange(0, BLOCK_SIZE_K)
|
| 69 |
+
return pid_m, pid_n, offs_am, offs_bn, offs_k
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
@triton.jit
|
| 73 |
+
def _store_masked(
|
| 74 |
+
C,
|
| 75 |
+
accumulator,
|
| 76 |
+
pid_m,
|
| 77 |
+
pid_n,
|
| 78 |
+
M,
|
| 79 |
+
N,
|
| 80 |
+
stride_cm,
|
| 81 |
+
stride_cn,
|
| 82 |
+
BLOCK_SIZE_M: tl.constexpr,
|
| 83 |
+
BLOCK_SIZE_N: tl.constexpr,
|
| 84 |
+
):
|
| 85 |
+
"""Shared output epilogue of the kernels below: cast the fp32 accumulator to
|
| 86 |
+
``C``'s dtype and store the ``(BLOCK_SIZE_M, BLOCK_SIZE_N)`` tile at the swizzled
|
| 87 |
+
``(pid_m, pid_n)``, masked to the ``(M, N)`` bounds."""
|
| 88 |
+
c = accumulator.to(C.dtype.element_ty)
|
| 89 |
+
offs_cm = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
|
| 90 |
+
offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
|
| 91 |
+
c_ptrs = C + stride_cm * offs_cm[:, None] + stride_cn * offs_cn[None, :]
|
| 92 |
+
c_mask = (offs_cm[:, None] < M) & (offs_cn[None, :] < N)
|
| 93 |
+
tl.store(c_ptrs, c, mask=c_mask)
|
| 94 |
|
| 95 |
|
|
|
|
| 96 |
@triton.autotune(
|
| 97 |
+
configs=get_accelerator_autotuning_configs(),
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
key=["N", "K", "BLOCK_SIZE_M"],
|
| 99 |
)
|
| 100 |
@triton.jit
|
| 101 |
+
def w8a8_block_dynamic_fp8_matmul_kernel(
|
| 102 |
+
A, # (M, K) raw BF16/FP16 activations
|
| 103 |
+
B, # (N, K) FP8 weights
|
| 104 |
+
C, # (M, N) output
|
| 105 |
+
Bs, # (N // BLOCK_SIZE_N, K // BLOCK_SIZE_K) weight scales (fp32 or uint8/UE8M0)
|
| 106 |
+
# Shape
|
|
|
|
|
|
|
| 107 |
M,
|
| 108 |
N,
|
| 109 |
K,
|
| 110 |
+
# Strides
|
| 111 |
stride_am,
|
| 112 |
stride_ak,
|
| 113 |
stride_bk,
|
| 114 |
stride_bn,
|
| 115 |
stride_cm,
|
| 116 |
stride_cn,
|
|
|
|
|
|
|
| 117 |
stride_bs_k,
|
| 118 |
stride_bs_n,
|
| 119 |
# Meta-parameters
|
|
|
|
| 122 |
BLOCK_SIZE_K: tl.constexpr,
|
| 123 |
GROUP_SIZE_M: tl.constexpr,
|
| 124 |
):
|
| 125 |
+
"""Block-scale FP8 GEMM kernel with fused activation quantization.
|
| 126 |
|
| 127 |
+
Computes ``C = A @ B.T`` with bf16/fp16 ``A`` quantized to FP8 per-K-tile
|
| 128 |
+
inline (one scale per M-row per BLOCK_SIZE_K) and pre-quantized FP8 weights
|
| 129 |
+
with per-block scales. 2D grid with swizzle for L2 cache locality on B.
|
| 130 |
"""
|
| 131 |
+
pid_m, pid_n, offs_am, offs_bn, offs_k = _swizzle_offsets(
|
| 132 |
+
M, N, BLOCK_SIZE_M, BLOCK_SIZE_N, BLOCK_SIZE_K, GROUP_SIZE_M
|
| 133 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
a_ptrs = A + (offs_am[:, None] * stride_am + offs_k[None, :] * stride_ak)
|
| 135 |
b_ptrs = B + (offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn)
|
| 136 |
|
|
|
|
| 137 |
offs_bsn = offs_bn // BLOCK_SIZE_N
|
| 138 |
bs_ptrs = Bs + offs_bsn * stride_bs_n
|
| 139 |
|
| 140 |
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
| 141 |
for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
|
| 142 |
k_remaining = K - k * BLOCK_SIZE_K
|
| 143 |
+
a_raw = tl.load(a_ptrs, mask=offs_k[None, :] < k_remaining, other=0.0).to(
|
| 144 |
+
tl.float32
|
| 145 |
+
)
|
| 146 |
+
a, a_s = fp8_act_quant_inline(a_raw)
|
| 147 |
b = tl.load(b_ptrs, mask=offs_k[:, None] < k_remaining, other=0.0)
|
| 148 |
+
b_s = decode_ue8m0_scale(tl.load(bs_ptrs))
|
|
|
|
|
|
|
|
|
|
| 149 |
accumulator += tl.dot(a, b) * a_s[:, None] * b_s[None, :]
|
| 150 |
a_ptrs += BLOCK_SIZE_K * stride_ak
|
| 151 |
b_ptrs += BLOCK_SIZE_K * stride_bk
|
| 152 |
+
bs_ptrs += stride_bs_k
|
| 153 |
+
|
| 154 |
+
_store_masked(
|
| 155 |
+
C,
|
| 156 |
+
accumulator,
|
| 157 |
+
pid_m,
|
| 158 |
+
pid_n,
|
| 159 |
+
M,
|
| 160 |
+
N,
|
| 161 |
+
stride_cm,
|
| 162 |
+
stride_cn,
|
| 163 |
+
BLOCK_SIZE_M,
|
| 164 |
+
BLOCK_SIZE_N,
|
| 165 |
+
)
|
| 166 |
|
| 167 |
|
| 168 |
@triton.autotune(
|
| 169 |
+
configs=get_accelerator_autotuning_configs(tune_block_nk=True),
|
|
|
|
|
|
|
|
|
|
|
|
|
| 170 |
key=["N", "K", "BLOCK_SIZE_M"],
|
| 171 |
)
|
| 172 |
@triton.jit
|
| 173 |
+
def w8a8_tensor_dynamic_fp8_matmul_kernel(
|
| 174 |
+
A, # (M, K) pre-quantized FP8 activations
|
| 175 |
+
B, # (N, K) FP8 weights
|
| 176 |
+
C, # (M, N) output
|
| 177 |
+
As, # (M,) per-token activation scales
|
| 178 |
+
Bs, # scalar/(1,) per-tensor weight scale
|
| 179 |
+
# Shape
|
| 180 |
M,
|
| 181 |
N,
|
| 182 |
K,
|
| 183 |
+
# Strides
|
| 184 |
stride_am,
|
| 185 |
stride_ak,
|
| 186 |
stride_bk,
|
|
|
|
| 188 |
stride_cm,
|
| 189 |
stride_cn,
|
| 190 |
stride_as_m,
|
| 191 |
+
# Meta-parameters
|
| 192 |
BLOCK_SIZE_M: tl.constexpr,
|
| 193 |
BLOCK_SIZE_N: tl.constexpr,
|
| 194 |
BLOCK_SIZE_K: tl.constexpr,
|
|
|
|
| 200 |
weight scale for the full matrix.
|
| 201 |
Uses a 2D grid with swizzle for L2 cache locality on B tiles.
|
| 202 |
"""
|
| 203 |
+
pid_m, pid_n, offs_am, offs_bn, offs_k = _swizzle_offsets(
|
| 204 |
+
M, N, BLOCK_SIZE_M, BLOCK_SIZE_N, BLOCK_SIZE_K, GROUP_SIZE_M
|
| 205 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 206 |
a_ptrs = A + offs_am[:, None] * stride_am + offs_k[None, :] * stride_ak
|
| 207 |
b_ptrs = B + offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn
|
| 208 |
|
|
|
|
| 221 |
|
| 222 |
accumulator = accumulator * a_s[:, None] * b_s
|
| 223 |
|
| 224 |
+
_store_masked(
|
| 225 |
+
C,
|
| 226 |
+
accumulator,
|
| 227 |
+
pid_m,
|
| 228 |
+
pid_n,
|
| 229 |
+
M,
|
| 230 |
+
N,
|
| 231 |
+
stride_cm,
|
| 232 |
+
stride_cn,
|
| 233 |
+
BLOCK_SIZE_M,
|
| 234 |
+
BLOCK_SIZE_N,
|
| 235 |
+
)
|
| 236 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 237 |
|
| 238 |
+
@triton.autotune(
|
| 239 |
+
configs=get_accelerator_autotuning_configs(),
|
| 240 |
+
key=["N", "K", "BLOCK_SIZE_M"],
|
| 241 |
+
)
|
| 242 |
+
@triton.jit
|
| 243 |
+
def w8a8_block_static_fp8_matmul_kernel(
|
| 244 |
+
A, # (M, K) raw BF16/FP16 activations
|
| 245 |
+
B, # (N, K) FP8 weights
|
| 246 |
+
C, # (M, N) output
|
| 247 |
+
As, # scalar — static per-tensor activation scale (calibration-time)
|
| 248 |
+
Bs, # (N // BLOCK_SIZE_N, K // BLOCK_SIZE_K) weight scales (fp32 or uint8/UE8M0)
|
| 249 |
+
# Shape
|
| 250 |
+
M,
|
| 251 |
+
N,
|
| 252 |
+
K,
|
| 253 |
+
# Strides
|
| 254 |
+
stride_am,
|
| 255 |
+
stride_ak,
|
| 256 |
+
stride_bk,
|
| 257 |
+
stride_bn,
|
| 258 |
+
stride_cm,
|
| 259 |
+
stride_cn,
|
| 260 |
+
stride_bs_k,
|
| 261 |
+
stride_bs_n,
|
| 262 |
+
# Meta-parameters
|
| 263 |
+
BLOCK_SIZE_M: tl.constexpr,
|
| 264 |
+
BLOCK_SIZE_N: tl.constexpr,
|
| 265 |
+
BLOCK_SIZE_K: tl.constexpr,
|
| 266 |
+
GROUP_SIZE_M: tl.constexpr,
|
| 267 |
+
):
|
| 268 |
+
"""Block-scale FP8 GEMM with static (per-tensor) activation scale.
|
| 269 |
+
|
| 270 |
+
``A`` is raw bf16/fp16; the kernel divides by the scalar ``As`` and casts
|
| 271 |
+
to FP8 inline. Per-block weight scales apply per-K-tile during
|
| 272 |
+
accumulation; the scalar activation scale factors out of the loop and
|
| 273 |
+
is applied once at the end.
|
| 274 |
+
"""
|
| 275 |
+
pid_m, pid_n, offs_am, offs_bn, offs_k = _swizzle_offsets(
|
| 276 |
+
M, N, BLOCK_SIZE_M, BLOCK_SIZE_N, BLOCK_SIZE_K, GROUP_SIZE_M
|
| 277 |
+
)
|
| 278 |
+
a_ptrs = A + (offs_am[:, None] * stride_am + offs_k[None, :] * stride_ak)
|
| 279 |
+
b_ptrs = B + (offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn)
|
| 280 |
+
|
| 281 |
+
offs_bsn = offs_bn // BLOCK_SIZE_N
|
| 282 |
+
bs_ptrs = Bs + offs_bsn * stride_bs_n
|
| 283 |
+
a_s_static = tl.load(As)
|
| 284 |
+
|
| 285 |
+
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
| 286 |
+
for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
|
| 287 |
+
k_remaining = K - k * BLOCK_SIZE_K
|
| 288 |
+
a_raw = tl.load(a_ptrs, mask=offs_k[None, :] < k_remaining, other=0.0).to(
|
| 289 |
+
tl.float32
|
| 290 |
+
)
|
| 291 |
+
a = (a_raw / a_s_static).to(tl.float8e4nv)
|
| 292 |
+
b = tl.load(b_ptrs, mask=offs_k[:, None] < k_remaining, other=0.0)
|
| 293 |
+
b_s = decode_ue8m0_scale(tl.load(bs_ptrs))
|
| 294 |
+
accumulator += tl.dot(a, b) * b_s[None, :]
|
| 295 |
+
a_ptrs += BLOCK_SIZE_K * stride_ak
|
| 296 |
+
b_ptrs += BLOCK_SIZE_K * stride_bk
|
| 297 |
+
bs_ptrs += stride_bs_k
|
| 298 |
+
|
| 299 |
+
accumulator = accumulator * a_s_static
|
| 300 |
+
_store_masked(
|
| 301 |
+
C,
|
| 302 |
+
accumulator,
|
| 303 |
+
pid_m,
|
| 304 |
+
pid_n,
|
| 305 |
+
M,
|
| 306 |
+
N,
|
| 307 |
+
stride_cm,
|
| 308 |
+
stride_cn,
|
| 309 |
+
BLOCK_SIZE_M,
|
| 310 |
+
BLOCK_SIZE_N,
|
| 311 |
+
)
|
| 312 |
+
|
| 313 |
+
|
| 314 |
+
@bayesian_autotune(
|
| 315 |
+
get_mxfp_autotuning_configs(
|
| 316 |
+
compute_modes=("dot_scaled", "dot")
|
| 317 |
+
), # no scalar branch here
|
| 318 |
+
["N", "K", "BLOCK_SIZE_M"],
|
| 319 |
+
n_trials=60,
|
| 320 |
+
)
|
| 321 |
+
@triton.jit
|
| 322 |
+
def mxfp_dynamic_matmul_kernel(
|
| 323 |
+
A, # (M, K) raw BF16/FP16 activations
|
| 324 |
+
B, # (N, K) E4M3 (MXFP8) or (N, K // 2) packed E2M1 (MXFP4) weights
|
| 325 |
+
C, # (M, N) output
|
| 326 |
+
Bs, # (N, K // SCALE_GROUP_K) UE8M0 weight scales
|
| 327 |
+
# Shape
|
| 328 |
+
M,
|
| 329 |
+
N,
|
| 330 |
+
K,
|
| 331 |
+
# Strides
|
| 332 |
+
stride_am,
|
| 333 |
+
stride_ak,
|
| 334 |
+
stride_bk,
|
| 335 |
+
stride_bn,
|
| 336 |
+
stride_cm,
|
| 337 |
+
stride_cn,
|
| 338 |
+
stride_bs_k,
|
| 339 |
+
stride_bs_n,
|
| 340 |
+
# Meta-parameters
|
| 341 |
+
BLOCK_SIZE_M: tl.constexpr,
|
| 342 |
+
BLOCK_SIZE_N: tl.constexpr,
|
| 343 |
+
BLOCK_SIZE_K: tl.constexpr,
|
| 344 |
+
GROUP_SIZE_M: tl.constexpr,
|
| 345 |
+
VALUES_PER_BYTE: tl.constexpr,
|
| 346 |
+
SCALE_GROUP_K: tl.constexpr,
|
| 347 |
+
COMPUTE_MODE: tl.constexpr,
|
| 348 |
+
):
|
| 349 |
+
"""Unified MXFP4/MXFP8 (W4A8/W8A8) GEMM with fused activation quantization.
|
| 350 |
+
|
| 351 |
+
``C = A @ B.T`` with bf16/fp16 ``A`` quantized to E4M3 per K-group inline (UE8M0
|
| 352 |
+
scale). ``VALUES_PER_BYTE`` picks the weight format: 2 = packed E2M1 (MXFP4),
|
| 353 |
+
1 = unpacked E4M3 (MXFP8). ``COMPUTE_MODE`` picks the MMA: ``tl.dot_scaled``
|
| 354 |
+
(native M=128 scaled MMA) vs fp8 ``tl.dot`` + per-group software rescale (wins at
|
| 355 |
+
decode; FP4 unpacks E2M1->E4M3 first — lossless). 2D grid with swizzle for L2 reuse.
|
| 356 |
+
"""
|
| 357 |
+
pid_m, pid_n, offs_am, offs_bn, offs_k = _swizzle_offsets(
|
| 358 |
+
M, N, BLOCK_SIZE_M, BLOCK_SIZE_N, BLOCK_SIZE_K, GROUP_SIZE_M
|
| 359 |
+
)
|
| 360 |
+
offs_kb = tl.arange(0, BLOCK_SIZE_K // VALUES_PER_BYTE)
|
| 361 |
+
offs_sf = tl.arange(0, BLOCK_SIZE_K // SCALE_GROUP_K)
|
| 362 |
+
a_ptrs = A + (offs_am[:, None] * stride_am + offs_k[None, :] * stride_ak)
|
| 363 |
+
b_ptrs = B + (offs_kb[:, None] * stride_bk + offs_bn[None, :] * stride_bn)
|
| 364 |
+
bs_ptrs = Bs + (offs_bn[:, None] * stride_bs_n + offs_sf[None, :] * stride_bs_k)
|
| 365 |
|
| 366 |
+
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
| 367 |
+
for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
|
| 368 |
+
k_remaining = K - k * BLOCK_SIZE_K
|
| 369 |
+
a_raw = tl.load(a_ptrs, mask=offs_k[None, :] < k_remaining, other=0.0).to(
|
| 370 |
+
tl.float32
|
| 371 |
+
)
|
| 372 |
+
a, a_scale = mxfp_act_quant_inline(
|
| 373 |
+
a_raw, BLOCK_SIZE_M, BLOCK_SIZE_K, SCALE_GROUP_K
|
| 374 |
+
)
|
| 375 |
+
b = tl.load(
|
| 376 |
+
b_ptrs, mask=offs_kb[:, None] < k_remaining // VALUES_PER_BYTE, other=0.0
|
| 377 |
+
)
|
| 378 |
+
b_s = tl.load(
|
| 379 |
+
bs_ptrs, mask=offs_sf[None, :] < k_remaining // SCALE_GROUP_K, other=0
|
| 380 |
+
).to(tl.uint8)
|
| 381 |
+
bq = mxfp4_e2m1_to_e4m3(b) if VALUES_PER_BYTE == 2 else b
|
| 382 |
+
if COMPUTE_MODE == "dot_scaled":
|
| 383 |
+
accumulator = mx_dot_scaled(
|
| 384 |
+
a, a_scale, b, b_s, accumulator, VALUES_PER_BYTE
|
| 385 |
+
)
|
| 386 |
+
else: # dot
|
| 387 |
+
accumulator = mx_dot_rescale(accumulator, a, bq, a_scale, b_s)
|
| 388 |
+
a_ptrs += BLOCK_SIZE_K * stride_ak
|
| 389 |
+
b_ptrs += (BLOCK_SIZE_K // VALUES_PER_BYTE) * stride_bk
|
| 390 |
+
bs_ptrs += (BLOCK_SIZE_K // SCALE_GROUP_K) * stride_bs_k
|
| 391 |
+
|
| 392 |
+
_store_masked(
|
| 393 |
+
C,
|
| 394 |
+
accumulator,
|
| 395 |
+
pid_m,
|
| 396 |
+
pid_n,
|
| 397 |
+
M,
|
| 398 |
+
N,
|
| 399 |
+
stride_cm,
|
| 400 |
+
stride_cn,
|
| 401 |
+
BLOCK_SIZE_M,
|
| 402 |
+
BLOCK_SIZE_N,
|
| 403 |
+
)
|
| 404 |
+
|
| 405 |
+
|
| 406 |
+
@triton_op(add_op_namespace_prefix("w8a8_block_dynamic_fp8_matmul"), mutates_args=())
|
| 407 |
+
def _w8a8_block_dynamic_fp8_matmul(
|
| 408 |
A: torch.Tensor,
|
| 409 |
B: torch.Tensor,
|
|
|
|
| 410 |
Bs: torch.Tensor,
|
| 411 |
block_size: list[int],
|
| 412 |
+
output_dtype: torch.dtype | None = None,
|
| 413 |
) -> torch.Tensor:
|
| 414 |
+
"""Block-scale FP8 matmul: ``C = A @ B.T`` with fused activation quantization.
|
| 415 |
|
| 416 |
+
A: (..., K) raw activations, bf16/fp16/fp32 (quantized inline to FP8)
|
| 417 |
+
B: (N, K) FP8 weights
|
| 418 |
+
Bs: (N // block_n, K // block_k) per-block weight scales
|
| 419 |
"""
|
| 420 |
assert len(block_size) == 2, (
|
| 421 |
f"block_size must be [block_n, block_k], got {block_size}"
|
|
|
|
| 432 |
N, K = B.shape
|
| 433 |
M = A.numel() // A.shape[-1]
|
| 434 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 435 |
assert Bs.ndim == 2, f"Bs must be 2D (N//block_n, K//block_k), got ndim={Bs.ndim}"
|
| 436 |
assert Bs.shape == (triton.cdiv(N, block_n), triton.cdiv(K, block_k)), (
|
| 437 |
f"Bs shape {tuple(Bs.shape)} != expected ({triton.cdiv(N, block_n)}, {triton.cdiv(K, block_k)})"
|
|
|
|
| 439 |
|
| 440 |
BLOCK_SIZE_K = block_k
|
| 441 |
BLOCK_SIZE_N = block_n
|
| 442 |
+
Bs = ue8m0_as_uint8(Bs)
|
| 443 |
C_shape = A.shape[:-1] + (N,)
|
| 444 |
+
BLOCK_SIZE_M = adaptive_block_size_m(M)
|
| 445 |
C = A.new_empty(C_shape, dtype=output_dtype)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 446 |
grid = (triton.cdiv(M, BLOCK_SIZE_M), triton.cdiv(N, BLOCK_SIZE_N))
|
| 447 |
+
|
| 448 |
with device_context(A.device):
|
| 449 |
+
wrap_triton(w8a8_block_dynamic_fp8_matmul_kernel)[grid](
|
| 450 |
A,
|
| 451 |
B,
|
| 452 |
C,
|
|
|
|
| 453 |
Bs,
|
| 454 |
M,
|
| 455 |
N,
|
|
|
|
| 460 |
B.stride(0),
|
| 461 |
C.stride(-2),
|
| 462 |
C.stride(-1),
|
|
|
|
|
|
|
| 463 |
Bs.stride(1),
|
| 464 |
Bs.stride(0),
|
| 465 |
# Meta-parameters
|
| 466 |
BLOCK_SIZE_M=BLOCK_SIZE_M,
|
| 467 |
BLOCK_SIZE_N=BLOCK_SIZE_N,
|
| 468 |
BLOCK_SIZE_K=BLOCK_SIZE_K,
|
| 469 |
+
GROUP_SIZE_M=GROUP_SIZE_M,
|
| 470 |
)
|
| 471 |
|
| 472 |
return C
|
| 473 |
|
| 474 |
|
| 475 |
+
@triton_op(add_op_namespace_prefix("w8a8_block_static_fp8_matmul"), mutates_args=())
|
| 476 |
+
def _w8a8_block_static_fp8_matmul(
|
| 477 |
A: torch.Tensor,
|
| 478 |
B: torch.Tensor,
|
|
|
|
| 479 |
Bs: torch.Tensor,
|
| 480 |
+
As: torch.Tensor,
|
| 481 |
+
block_size: list[int],
|
| 482 |
+
output_dtype: torch.dtype | None = None,
|
| 483 |
) -> torch.Tensor:
|
| 484 |
+
"""Block-scale FP8 matmul with static (per-tensor) activation quantization.
|
| 485 |
|
| 486 |
+
A: (..., K) raw bf16/fp16 activations — quantized to FP8 inline against ``As``
|
| 487 |
+
B: (N, K) FP8 weights
|
| 488 |
+
Bs: (N // block_n, K // block_k) per-block weight scales
|
| 489 |
+
As: scalar / (1,) — per-tensor static activation scale
|
| 490 |
"""
|
| 491 |
+
assert len(block_size) == 2, (
|
| 492 |
+
f"block_size must be [block_n, block_k], got {block_size}"
|
| 493 |
+
)
|
| 494 |
+
block_n, block_k = block_size[0], block_size[1]
|
| 495 |
+
|
| 496 |
+
assert B.dtype != torch.int8, (
|
| 497 |
+
"static activation quant is not supported on the FP4 path"
|
| 498 |
+
)
|
| 499 |
+
assert not (block_n == B.size(0) and block_k == B.size(1)), (
|
| 500 |
+
"static activation quant requires block-wise weights, not tensor-mode"
|
| 501 |
+
)
|
| 502 |
assert A.shape[-1] == B.shape[-1], (
|
| 503 |
f"K mismatch: A has K={A.shape[-1]}, B has K={B.shape[-1]}"
|
| 504 |
)
|
| 505 |
assert A.is_contiguous(), "A must be contiguous"
|
| 506 |
assert B.ndim == 2, f"B must be 2D (N, K), got ndim={B.ndim}"
|
| 507 |
assert B.is_contiguous(), "B must be contiguous"
|
| 508 |
+
assert As.numel() == 1, f"As must be scalar or (1,), got {tuple(As.shape)}"
|
| 509 |
|
| 510 |
N, K = B.shape
|
| 511 |
M = A.numel() // A.shape[-1]
|
| 512 |
|
| 513 |
+
assert Bs.ndim == 2, f"Bs must be 2D (N//block_n, K//block_k), got ndim={Bs.ndim}"
|
| 514 |
+
assert Bs.shape == (triton.cdiv(N, block_n), triton.cdiv(K, block_k)), (
|
| 515 |
+
f"Bs shape {tuple(Bs.shape)} != expected ({triton.cdiv(N, block_n)}, {triton.cdiv(K, block_k)})"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 516 |
)
|
| 517 |
|
| 518 |
+
BLOCK_SIZE_K = block_k
|
| 519 |
+
BLOCK_SIZE_N = block_n
|
| 520 |
+
BLOCK_SIZE_M = adaptive_block_size_m(M)
|
| 521 |
+
grid = (triton.cdiv(M, BLOCK_SIZE_M), triton.cdiv(N, BLOCK_SIZE_N))
|
| 522 |
|
| 523 |
+
Bs = ue8m0_as_uint8(Bs)
|
|
|
|
| 524 |
C_shape = A.shape[:-1] + (N,)
|
| 525 |
C = A.new_empty(C_shape, dtype=output_dtype)
|
| 526 |
+
As = As.reshape(1).to(torch.float32)
|
| 527 |
+
|
| 528 |
with device_context(A.device):
|
| 529 |
+
wrap_triton(w8a8_block_static_fp8_matmul_kernel)[grid](
|
| 530 |
A,
|
| 531 |
B,
|
| 532 |
C,
|
|
|
|
| 541 |
B.stride(0),
|
| 542 |
C.stride(-2),
|
| 543 |
C.stride(-1),
|
| 544 |
+
Bs.stride(1),
|
| 545 |
+
Bs.stride(0),
|
| 546 |
# Meta-parameters
|
| 547 |
BLOCK_SIZE_M=BLOCK_SIZE_M,
|
| 548 |
BLOCK_SIZE_N=BLOCK_SIZE_N,
|
| 549 |
BLOCK_SIZE_K=BLOCK_SIZE_K,
|
| 550 |
+
GROUP_SIZE_M=GROUP_SIZE_M,
|
| 551 |
)
|
| 552 |
|
| 553 |
return C
|
| 554 |
|
| 555 |
|
| 556 |
+
@triton_op(add_op_namespace_prefix("w8a8_tensor_dynamic_fp8_matmul"), mutates_args=())
|
| 557 |
+
def _w8a8_tensor_dynamic_fp8_matmul(
|
| 558 |
A: torch.Tensor,
|
| 559 |
B: torch.Tensor,
|
|
|
|
| 560 |
Bs: torch.Tensor,
|
| 561 |
+
output_dtype: torch.dtype | None = None,
|
| 562 |
+
) -> torch.Tensor:
|
| 563 |
+
"""Tensor-scale FP8 matmul: ``C = A @ B.T`` with fused activation quantization.
|
| 564 |
+
|
| 565 |
+
A: (..., K) raw activations, bf16/fp16/fp32 (flattened to (M, K)
|
| 566 |
+
internally) — per-row scales computed via ``fp8_act_quant(A, K)``.
|
| 567 |
+
B: (N, K) FP8 weights.
|
| 568 |
+
Bs: scalar, (1,), or (1, 1) — single tensor-scale weight scale.
|
| 569 |
+
"""
|
| 570 |
+
assert A.shape[-1] == B.shape[-1], (
|
| 571 |
+
f"K mismatch: A has K={A.shape[-1]}, B has K={B.shape[-1]}"
|
| 572 |
+
)
|
| 573 |
+
assert A.is_contiguous(), "A must be contiguous"
|
| 574 |
+
assert B.ndim == 2, f"B must be 2D (N, K), got ndim={B.ndim}"
|
| 575 |
+
assert B.is_contiguous(), "B must be contiguous"
|
| 576 |
+
|
| 577 |
+
N, K = B.shape
|
| 578 |
+
M = A.numel() // A.shape[-1]
|
| 579 |
+
|
| 580 |
+
assert Bs.numel() == 1, f"Bs must be scalar or (1,), got {tuple(Bs.shape)}"
|
| 581 |
+
|
| 582 |
+
# Per-row scalar activation scale (one per token).
|
| 583 |
+
qA, As = fp8_act_quant(A, K)
|
| 584 |
+
As = As.reshape(M)
|
| 585 |
+
Bs = Bs.reshape(1)
|
| 586 |
+
|
| 587 |
+
C_shape = A.shape[:-1] + (N,)
|
| 588 |
+
C = A.new_empty(C_shape, dtype=output_dtype)
|
| 589 |
+
BLOCK_SIZE_M = adaptive_block_size_m(M)
|
| 590 |
+
|
| 591 |
+
def grid(META):
|
| 592 |
+
return (triton.cdiv(M, BLOCK_SIZE_M), triton.cdiv(N, META["BLOCK_SIZE_N"]))
|
| 593 |
+
|
| 594 |
+
with device_context(A.device):
|
| 595 |
+
wrap_triton(w8a8_tensor_dynamic_fp8_matmul_kernel)[grid](
|
| 596 |
+
qA,
|
| 597 |
+
B,
|
| 598 |
+
C,
|
| 599 |
+
As,
|
| 600 |
+
Bs,
|
| 601 |
+
M,
|
| 602 |
+
N,
|
| 603 |
+
K,
|
| 604 |
+
qA.stride(-2),
|
| 605 |
+
qA.stride(-1),
|
| 606 |
+
B.stride(1),
|
| 607 |
+
B.stride(0),
|
| 608 |
+
C.stride(-2),
|
| 609 |
+
C.stride(-1),
|
| 610 |
+
As.stride(0),
|
| 611 |
+
# Meta-parameters (BLOCK_SIZE_N, BLOCK_SIZE_K come from autotune Config)
|
| 612 |
+
BLOCK_SIZE_M=BLOCK_SIZE_M,
|
| 613 |
+
GROUP_SIZE_M=GROUP_SIZE_M,
|
| 614 |
+
)
|
| 615 |
+
|
| 616 |
+
return C
|
| 617 |
+
|
| 618 |
+
|
| 619 |
+
@triton_op(add_op_namespace_prefix("mxfp_dynamic_matmul"), mutates_args=())
|
| 620 |
+
def _mxfp_dynamic_matmul(
|
| 621 |
+
A: torch.Tensor,
|
| 622 |
+
B: torch.Tensor,
|
| 623 |
+
Bs: torch.Tensor,
|
| 624 |
+
output_dtype: torch.dtype | None = None,
|
| 625 |
) -> torch.Tensor:
|
| 626 |
+
"""MX matmul ``C = A @ B.T`` with fused activation quant. Weight format detected
|
| 627 |
+
from ``B.dtype``: ``int8`` → packed E2M1 (MXFP4, ``B`` is ``(N, K//2)``);
|
| 628 |
+
``float8_e4m3fn`` → unpacked E4M3 (MXFP8, ``(N, K)``). Both use UE8M0 group-32 scales
|
| 629 |
+
``(N, K//32)``; tile + dot path are autotuned (scale granularity fixed at 32).
|
| 630 |
+
|
| 631 |
+
A: (M, K) raw activations, bf16/fp16/fp32 (quantized inline to E4M3)
|
| 632 |
+
"""
|
| 633 |
+
assert A.ndim == 2 and B.ndim == 2 and Bs.ndim == 2
|
| 634 |
+
assert B.dtype in (torch.int8, torch.float8_e4m3fn), (
|
| 635 |
+
f"B must be int8 (packed E2M1) or float8_e4m3fn (E4M3), got {B.dtype}"
|
| 636 |
+
)
|
| 637 |
+
assert Bs.dtype == torch.float8_e8m0fnu, (
|
| 638 |
+
f"Bs must be float8_e8m0fnu, got {Bs.dtype}"
|
| 639 |
+
)
|
| 640 |
+
assert A.is_contiguous(), "A must be contiguous"
|
| 641 |
+
assert B.is_contiguous(), "B must be contiguous"
|
| 642 |
+
VALUES_PER_BYTE = NIBBLES_PER_BYTE if B.dtype == torch.int8 else 1
|
| 643 |
+
|
| 644 |
+
M, K = A.shape
|
| 645 |
+
N, K_b = B.shape
|
| 646 |
+
assert K == VALUES_PER_BYTE * K_b, (
|
| 647 |
+
f"K (={K}) must equal {VALUES_PER_BYTE} * B.shape[1] (={K_b})"
|
| 648 |
+
)
|
| 649 |
+
assert K % MX_SCALE_GROUP_K == 0, (
|
| 650 |
+
f"K (={K}) must be a multiple of {MX_SCALE_GROUP_K}"
|
| 651 |
+
)
|
| 652 |
+
assert Bs.shape == (N, K // MX_SCALE_GROUP_K), (
|
| 653 |
+
f"Bs shape {tuple(Bs.shape)} != ({N}, {K // MX_SCALE_GROUP_K})"
|
| 654 |
+
)
|
| 655 |
+
|
| 656 |
+
B = e2m1_as_uint8(B)
|
| 657 |
+
bs_u8 = ue8m0_as_uint8(Bs)
|
| 658 |
+
C = A.new_empty((M, N), dtype=output_dtype)
|
| 659 |
+
BLOCK_SIZE_M = adaptive_block_size_m(M)
|
| 660 |
+
|
| 661 |
+
def grid(META):
|
| 662 |
+
return (triton.cdiv(M, BLOCK_SIZE_M), triton.cdiv(N, META["BLOCK_SIZE_N"]))
|
| 663 |
+
|
| 664 |
+
with device_context(A.device):
|
| 665 |
+
wrap_triton(mxfp_dynamic_matmul_kernel)[grid](
|
| 666 |
+
A,
|
| 667 |
+
B,
|
| 668 |
+
C,
|
| 669 |
+
bs_u8,
|
| 670 |
+
M,
|
| 671 |
+
N,
|
| 672 |
+
K,
|
| 673 |
+
A.stride(0),
|
| 674 |
+
A.stride(1),
|
| 675 |
+
B.stride(1),
|
| 676 |
+
B.stride(0),
|
| 677 |
+
C.stride(0),
|
| 678 |
+
C.stride(1),
|
| 679 |
+
bs_u8.stride(1),
|
| 680 |
+
bs_u8.stride(0),
|
| 681 |
+
# Meta-parameters (BLOCK_SIZE_N, BLOCK_SIZE_K come from autotune Config)
|
| 682 |
+
BLOCK_SIZE_M=BLOCK_SIZE_M,
|
| 683 |
+
GROUP_SIZE_M=GROUP_SIZE_M,
|
| 684 |
+
VALUES_PER_BYTE=VALUES_PER_BYTE,
|
| 685 |
+
SCALE_GROUP_K=MX_SCALE_GROUP_K,
|
| 686 |
+
)
|
| 687 |
+
return C
|
| 688 |
|
| 689 |
+
|
| 690 |
+
def w8a8_block_dynamic_fp8_matmul(
|
| 691 |
+
A: torch.Tensor,
|
| 692 |
+
B: torch.Tensor,
|
| 693 |
+
Bs: torch.Tensor,
|
| 694 |
+
block_size: list[int],
|
| 695 |
+
output_dtype: torch.dtype | None = None,
|
| 696 |
+
) -> torch.Tensor:
|
| 697 |
+
"""Block-wise W8A8 FP8 matrix multiplication with fused activation quantization.
|
| 698 |
|
| 699 |
Args:
|
| 700 |
+
A: Raw activation tensor ``[..., M, K]`` in bf16/fp16/fp32 — quantized
|
| 701 |
+
inline to ``float8_e4m3fn`` against per-K-tile per-row UE8 scales.
|
| 702 |
+
B: FP8 weight tensor ``[N, K]`` in ``float8_e4m3fn``.
|
| 703 |
Bs: Per-block weight scales ``[N // block_size[0], K // block_size[1]]``.
|
| 704 |
+
block_size: ``[block_n, block_k]`` weight quantization block dimensions.
|
| 705 |
output_dtype: dtype of the returned tensor (default: ``torch.float32``).
|
| 706 |
|
| 707 |
Returns:
|
| 708 |
+
Output tensor ``[..., M, N]`` in ``output_dtype``.
|
| 709 |
"""
|
| 710 |
+
return ops.w8a8_block_dynamic_fp8_matmul(A, B, Bs, block_size, output_dtype)
|
|
|
|
|
|
|
| 711 |
|
| 712 |
|
| 713 |
+
def w8a8_block_static_fp8_matmul(
|
| 714 |
A: torch.Tensor,
|
| 715 |
B: torch.Tensor,
|
| 716 |
+
Bs: torch.Tensor,
|
| 717 |
As: torch.Tensor,
|
| 718 |
+
block_size: list[int],
|
| 719 |
+
output_dtype: torch.dtype | None = None,
|
| 720 |
+
) -> torch.Tensor:
|
| 721 |
+
"""Block-wise W8A8 FP8 matmul with static (per-tensor) activation quantization.
|
| 722 |
+
|
| 723 |
+
Args:
|
| 724 |
+
A: Raw activation tensor ``[..., M, K]`` in bf16/fp16/fp32 — quantized
|
| 725 |
+
inline against the per-tensor scalar ``As``.
|
| 726 |
+
B: FP8 weight tensor ``[N, K]`` in ``float8_e4m3fn``.
|
| 727 |
+
Bs: Per-block weight scales ``[N // block_size[0], K // block_size[1]]``.
|
| 728 |
+
As: Static per-tensor activation scale (scalar or ``[1]``).
|
| 729 |
+
block_size: ``[block_n, block_k]`` weight quantization block dimensions.
|
| 730 |
+
output_dtype: dtype of the returned tensor (default: ``torch.float32``).
|
| 731 |
+
|
| 732 |
+
Returns:
|
| 733 |
+
Output tensor ``[..., M, N]`` in ``output_dtype``.
|
| 734 |
+
"""
|
| 735 |
+
return ops.w8a8_block_static_fp8_matmul(A, B, Bs, As, block_size, output_dtype)
|
| 736 |
+
|
| 737 |
+
|
| 738 |
+
def w8a8_tensor_dynamic_fp8_matmul(
|
| 739 |
+
A: torch.Tensor,
|
| 740 |
+
B: torch.Tensor,
|
| 741 |
Bs: torch.Tensor,
|
| 742 |
+
output_dtype: torch.dtype | None = None,
|
| 743 |
) -> torch.Tensor:
|
| 744 |
+
"""Tensor-scale W8A8 FP8 matmul with fused activation quantization.
|
| 745 |
|
| 746 |
+
Computes ``C = A @ B.T`` with raw bf16/fp16/fp32 ``A`` quantized to FP8
|
| 747 |
+
per-row (one scale per token) before the dot.
|
| 748 |
|
| 749 |
Args:
|
| 750 |
+
A: Raw activation tensor ``[M, K]`` in bf16/fp16/fp32.
|
| 751 |
+
B: FP8 weight tensor ``[N, K]`` in ``float8_e4m3fn``.
|
|
|
|
| 752 |
Bs: Single weight scale, scalar or ``[1]``.
|
| 753 |
output_dtype: dtype of the returned tensor.
|
| 754 |
+
"""
|
| 755 |
+
return ops.w8a8_tensor_dynamic_fp8_matmul(A, B, Bs, output_dtype)
|
| 756 |
|
| 757 |
+
|
| 758 |
+
def mxfp_dynamic_matmul(
|
| 759 |
+
A: torch.Tensor,
|
| 760 |
+
B: torch.Tensor,
|
| 761 |
+
Bs: torch.Tensor,
|
| 762 |
+
output_dtype: torch.dtype | None = None,
|
| 763 |
+
) -> torch.Tensor:
|
| 764 |
+
"""MX (MXFP4/MXFP8) matmul ``C = A @ B.T`` with fused activation quant; the weight
|
| 765 |
+
format is detected from ``B.dtype``. Tile + dot path are autotuned; MX scale
|
| 766 |
+
granularity is fixed at 32 (the MX-format spec).
|
| 767 |
+
|
| 768 |
+
Args:
|
| 769 |
+
A: Raw activations ``[M, K]`` in bf16/fp16/fp32.
|
| 770 |
+
B: Packed E2M1 ``[N, K // 2]`` (``int8``) or E4M3 ``[N, K]`` (``float8_e4m3fn``).
|
| 771 |
+
Bs: UE8M0 weight scales ``[N, K // 32]`` (``float8_e8m0fnu``).
|
| 772 |
+
output_dtype: dtype of the returned tensor (default ``bfloat16``).
|
| 773 |
"""
|
| 774 |
+
return ops.mxfp_dynamic_matmul(A, B, Bs, output_dtype)
|
| 775 |
|
| 776 |
|
| 777 |
+
def matmul_2d(
|
| 778 |
A: torch.Tensor,
|
| 779 |
B: torch.Tensor,
|
|
|
|
| 780 |
Bs: torch.Tensor,
|
| 781 |
block_size: list[int] | None,
|
| 782 |
+
output_dtype: torch.dtype | None = None,
|
| 783 |
+
activation_scale: torch.Tensor | None = None,
|
| 784 |
) -> torch.Tensor:
|
| 785 |
+
"""Quantized matmul dispatcher (W8A8 FP8 or W4A8 FP4).
|
| 786 |
|
| 787 |
+
``A`` is always raw bf16/fp16/fp32; quantization is fused into every path.
|
| 788 |
+
With ``activation_scale`` set, the kernel uses that per-tensor scalar
|
| 789 |
+
(static quant); otherwise it computes its own scale from ``A`` (dynamic).
|
|
|
|
| 790 |
|
| 791 |
+
``output_dtype`` defaults to ``A.dtype``.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 792 |
|
| 793 |
+
Routes by weight dtype and ``block_size``:
|
| 794 |
+
- MX weights — ``int8`` (packed E2M1) or ``float8_e4m3fn`` (E4M3) with UE8M0
|
| 795 |
+
group-32 ``Bs`` (shape ``[N, K//32]``) → ``mxfp_dynamic_matmul`` (``block_size``
|
| 796 |
+
ignored, ``activation_scale`` unsupported; scale granularity fixed at 32, autotuned).
|
| 797 |
+
- ``block_size`` None or full ``[N, K]`` → ``w8a8_tensor_dynamic_fp8_matmul``.
|
| 798 |
+
- otherwise → ``w8a8_block_dynamic_fp8_matmul`` (or its static variant when
|
| 799 |
+
``activation_scale`` is given).
|
| 800 |
+
"""
|
| 801 |
+
if is_mxfp(B, Bs):
|
| 802 |
+
if activation_scale is not None:
|
| 803 |
+
raise NotImplementedError(
|
| 804 |
+
"activation_scale (static activation quant) is not supported for MX weights — "
|
| 805 |
+
"the MX path quantizes activations dynamically per group. Omit activation_scale."
|
| 806 |
+
)
|
| 807 |
+
return mxfp_dynamic_matmul(A, B, Bs, output_dtype)
|
| 808 |
+
|
| 809 |
+
if is_tensor_wide(block_size, B):
|
| 810 |
+
return w8a8_tensor_dynamic_fp8_matmul(A, B, Bs, output_dtype)
|
| 811 |
+
|
| 812 |
+
# Block-wise FP8: static when a per-tensor activation scale is supplied, else dynamic.
|
| 813 |
+
if activation_scale is not None:
|
| 814 |
+
return w8a8_block_static_fp8_matmul(
|
| 815 |
+
A, B, Bs, activation_scale, block_size, output_dtype
|
| 816 |
+
)
|
| 817 |
+
return w8a8_block_dynamic_fp8_matmul(A, B, Bs, block_size, output_dtype)
|
build/torch-cuda/metadata.json
CHANGED
|
@@ -1,10 +1,25 @@
|
|
| 1 |
{
|
| 2 |
"name": "finegrained-fp8",
|
| 3 |
-
"id": "
|
| 4 |
-
"version":
|
| 5 |
"license": "Apache-2.0",
|
| 6 |
"python-depends": [],
|
| 7 |
"backend": {
|
| 8 |
"type": "cuda"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
}
|
| 10 |
}
|
|
|
|
| 1 |
{
|
| 2 |
"name": "finegrained-fp8",
|
| 3 |
+
"id": "_finegrained_fp8_cuda_93d7641",
|
| 4 |
+
"version": 4,
|
| 5 |
"license": "Apache-2.0",
|
| 6 |
"python-depends": [],
|
| 7 |
"backend": {
|
| 8 |
"type": "cuda"
|
| 9 |
+
},
|
| 10 |
+
"digest": {
|
| 11 |
+
"algorithm": "sha256",
|
| 12 |
+
"files": {
|
| 13 |
+
"__init__.py": "zhfXqFHpZ1lPv0hUwaOhJHxmcDq2QcE9R+9cgVfLTI4=",
|
| 14 |
+
"_ops.py": "v4IWQ4TB7LvLjTio3GctOEdzLSpc45Z3OBcWILsDeSM=",
|
| 15 |
+
"batched.py": "siX6kWh3I9GzioEw58Y66p1aZX1m6kSAf4E/asiLnVA=",
|
| 16 |
+
"bayesian_autotuner.py": "jBUYvyfpEHsTBgFlpt2Ts5emz+CSMyvjc1TxUfZKVrg=",
|
| 17 |
+
"finegrained_fp8/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY=",
|
| 18 |
+
"fused_batched.py": "09a9bN8fzGBG5CapCwvIUL5589F7Ve+CbT+S1+4+BAI=",
|
| 19 |
+
"fused_grouped.py": "foyMOTru53VKJ1d7ASJpEGWqiF3OqbSQT91tTPXcWKk=",
|
| 20 |
+
"grouped.py": "Gq1YS9LXnFuKWQ+M0XW6WuP2Fg7zwKwwT50o6YhQXnw=",
|
| 21 |
+
"matmul.py": "PEdwUJGaJetm4lWF6de3eYHBHtnHsZhevoDQd6fJYKw=",
|
| 22 |
+
"utils.py": "aUUYq5/POJbaZKmo8QgM89XQrJN3HNHTuJOsPhgYxQw="
|
| 23 |
+
}
|
| 24 |
}
|
| 25 |
}
|
build/torch-cuda/metadata.json.sigstore
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"mediaType":"application/vnd.dev.sigstore.bundle.v0.3+json","verificationMaterial":{"certificate":{"rawBytes":"MIIHSzCCBtCgAwIBAgIUHLiFbbNYXDYPtqIG48HGoOqA7RcwCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjYwNjI5MTcxNDQ5WhcNMjYwNjI5MTcyNDQ5WjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEdD3jvq2C4tEZZAE37ydzk+UsPgoh++aHLcQELDY+a5KZgEqJqxpiW8OHSGEe07m85EHlRXB5sB9BmHJTBi7AuKOCBe8wggXrMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUUaXQbM7F2hw5Au49d8kdUNd+gTkwHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4YZD8wawYDVR0RAQH/BGEwX4ZdaHR0cHM6Ly9naXRodWIuY29tL2h1Z2dpbmdmYWNlL2tlcm5lbHMtY29tbXVuaXR5Ly5naXRodWIvd29ya2Zsb3dzL2J1aWxkLnlhbWxAcmVmcy9oZWFkcy9tYWluMDkGCisGAQQBg78wAQEEK2h0dHBzOi8vdG9rZW4uYWN0aW9ucy5naXRodWJ1c2VyY29udGVudC5jb20wHwYKKwYBBAGDvzABAgQRd29ya2Zsb3dfZGlzcGF0Y2gwNgYKKwYBBAGDvzABAwQoOTNkNzY0MTZlOTY1YTE0Y2IzY2Y1ZGFlOWNjYTdhNTNmNzBjNWU1YjATBgorBgEEAYO/MAEEBAVCdWlsZDArBgorBgEEAYO/MAEFBB1odWdnaW5nZmFjZS9rZXJuZWxzLWNvbW11bml0eTAdBgorBgEEAYO/MAEGBA9yZWZzL2hlYWRzL21haW4wOwYKKwYBBAGDvzABCAQtDCtodHRwczovL3Rva2VuLmFjdGlvbnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tMG0GCisGAQQBg78wAQkEXwxdaHR0cHM6Ly9naXRodWIuY29tL2h1Z2dpbmdmYWNlL2tlcm5lbHMtY29tbXVuaXR5Ly5naXRodWIvd29ya2Zsb3dzL2J1aWxkLnlhbWxAcmVmcy9oZWFkcy9tYWluMDgGCisGAQQBg78wAQoEKgwoOTNkNzY0MTZlOTY1YTE0Y2IzY2Y1ZGFlOWNjYTdhNTNmNzBjNWU1YjAbBgorBgEEAYO/MAELBA0MC3NlbGYtaG9zdGVkMEAGCisGAQQBg78wAQwEMgwwaHR0cHM6Ly9naXRodWIuY29tL2h1Z2dpbmdmYWNlL2tlcm5lbHMtY29tbXVuaXR5MDgGCisGAQQBg78wAQ0EKgwoOTNkNzY0MTZlOTY1YTE0Y2IzY2Y1ZGFlOWNjYTdhNTNmNzBjNWU1YjAfBgorBgEEAYO/MAEOBBEMD3JlZnMvaGVhZHMvbWFpbjAaBgorBgEEAYO/MAEPBAwMCjEwNzE0NzU1MjkwLgYKKwYBBAGDvzABEAQgDB5odHRwczovL2dpdGh1Yi5jb20vaHVnZ2luZ2ZhY2UwGAYKKwYBBAGDvzABEQQKDAgyNTcyMDc0MzBtBgorBgEEAYO/MAESBF8MXWh0dHBzOi8vZ2l0aHViLmNvbS9odWdnaW5nZmFjZS9rZXJuZWxzLWNvbW11bml0eS8uZ2l0aHViL3dvcmtmbG93cy9idWlsZC55YW1sQHJlZnMvaGVhZHMvbWFpbjA4BgorBgEEAYO/MAETBCoMKDkzZDc2NDE2ZTk2NWExNGNiM2NmNWRhZTljY2E3YTUzZjcwYzVlNWIwIQYKKwYBBAGDvzABFAQTDBF3b3JrZmxvd19kaXNwYXRjaDBkBgorBgEEAYO/MAEVBFYMVGh0dHBzOi8vZ2l0aHViLmNvbS9odWdnaW5nZmFjZS9rZXJuZWxzLWNvbW11bml0eS9hY3Rpb25zL3J1bnMvMjgzODk2ODc4NDkvYXR0ZW1wdHMvMTAWBgorBgEEAYO/MAEWBAgMBnB1YmxpYzBGBgorBgEEAYO/MAEYBDgMNnJlcG86aHVnZ2luZ2ZhY2Uva2VybmVscy1jb21tdW5pdHk6cmVmOnJlZnMvaGVhZHMvbWFpbjCBiQYKKwYBBAHWeQIEAgR7BHkAdwB1AN09MGrGxxEyYxkeHJlnNwKiSl643jyt/4eKcoAvKe6OAAABnxRgZgUAAAQDAEYwRAIga3GJTEHsVlnuoI1kG25kPC3sc4On1G1CH7jgrcznaq4CIAEDXVUDkXPlbdYZztMVhEPydBetfSt9x44bOu1mhEqmMAoGCCqGSM49BAMDA2kAMGYCMQD2YFLb+M5svHbxwKLjbe7VlXaa7CuLvdujtpVpwR4WQYwAjZh/NsYfHRTK9YF5d6YCMQDPbSsy3rBtAI4QppFB48c8/PC8BS+YwTaWq1guriywQSt9JXkgOD7TlWMPSrvG5LE="},"tlogEntries":[{"logIndex":"2011412491","logId":{"keyId":"wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="},"kindVersion":{"kind":"hashedrekord","version":"0.0.1"},"integratedTime":"1782753290","inclusionPromise":{"signedEntryTimestamp":"MEQCIBu7joAyJ1Scv9JziiQLgyQ7crhnWnP1MdOfZVZU/belAiATlq9G4be8PKp5mBqEjIZDvKaDOmj4wdqGHFq4ov+8eg=="},"inclusionProof":{"logIndex":"1889508229","rootHash":"Qc/hbi1qBcXqZet3VqITiwbaJ5KYuZZp4AZzYzDFmPY=","treeSize":"1889508650","hashes":["tWE1dKnr+xyVR9DqZl179d4a2pzILWEWoG3QQ016pQA=","eJDrwh6X93nkRtJXPluc2cbg01QfYAH0gkvcZW5fZdg=","2Bvz7mTmsTUXVkKOwxb/onqiaThvKrmBkjB83YlfoYk=","u97qIngM8q1QfW5Lo/9csT+WAgu8+rpNQjqarIgDrnE=","7dbKejwhyjxkywj03FXGECoQ7QTz1yxa3/gxCieD8WU=","RGsebap7/2pdY7rrT3oY8CUkcbwkB+6gHwnx1/CsGAs=","NYulN1dObgLker5XjMR4jAFzRdx6Sy0SXp6X3KRf0m4=","zsh1PjbsqQJic+3EcBbfMKAlRareth4KaBthTv/3/SA=","0Lm4vgxuYggxvPJg+Rhp0EJTjbPoCTRGIaGqkTkZ4Lo=","X9YQHakf+wqAGVCUCm2cEW/JGbf4dyp/Db+Ku1v1Du8=","PORSs03e4HDJYPwDuaM6rQrziDBN3e5Y4SuajVQFHg0=","DGp8UqFvSdPpdWfi6+/wVsDiGX858w1Jtjle0C4QGVc=","uxLbTK4c/VFXh7/w8pb8eAk6P1ppLRLvvQ5tU6i9MFg=","tvcQx4ohRLYBU5QOWeu4jhR3lEKztnlyYTspOSiOMcA=","pu3boNdI/2WREJSDmOgBX37bljG4IyCsSsPMDAzAB8Q=","f2GuSxsX8CM80Bzrdr+iaFcvdbcdx8PsOGfP1A5iZmg=","wznNTIJf/erZkw1aAaotGts1BhEB9zNmnJSKo0zdCeM=","+hhERR0matid5NvHk7ddsPOWJ5JAyyn11f+ruZpMW38=","Mp0blBzkxwj6OGAogVNNNMwQM4XxRTFCrHD5ZZy2wYg=","dIBftsHOWhWti/c7pucPNfamsTNyNuam3ViDx67krLA=","+/VZ56MsIPxMiyLAodzKXo5TEWdQp36z89qLhpzloAo=","daxmZaajRpZV+JxHiOYZhJBiSKN5ucqjh2WnGbHhirw=","DOCeoSMovIvLExkhIvisow9AuNXgeWs4ECkyR6EcqYU="],"checkpoint":{"envelope":"rekor.sigstore.dev - 1193050959916656506\n1889508650\nQc/hbi1qBcXqZet3VqITiwbaJ5KYuZZp4AZzYzDFmPY=\n\n— rekor.sigstore.dev wNI9ajBFAiEAmeN2VwOoIwnC9dJ2+KWMPB3BmzlDx3rjop2jipH6bJECIEFFujbiD917hnqHY5pLSHj0TL9j6+R9/irZ4NLnl49k\n"}},"canonicalizedBody":"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiJkOTQxNDY1ZjQ1Y2FkOGE3M2Q5MjhiNWY1YWUxYmIyZDhjOGRlN2Y3ZjI5MDIzMTdkMGZiODUyOWI1ZjMyMmE0In19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FVUNJUUNGam5tbFJQbG4rUG52T2VNUTN2b1BCR0ZkaDRmMDcvcjJtY05JN2V5eDNBSWdXajhTck42N0FITWFDMHcxYmpEbG9IRXhGSmxCTkhNUUZtUWcyZ01OcHNzPSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVaFRla05EUW5SRFowRjNTVUpCWjBsVlNFeHBSbUppVGxsWVJGbFFkSEZKUnpRNFNFZHZUM0ZCTjFKamQwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcFpkMDVxU1RWTlZHTjRUa1JSTlZkb1kwNU5hbGwzVG1wSk5VMVVZM2xPUkZFMVYycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVZrUkROcWRuRXlRelIwUlZwYVFVVXpOM2xrZW1zclZYTlFaMjlvS3l0aFNFeGpVVVVLVEVSWksyRTFTMXBuUlhGS2NYaHdhVmM0VDBoVFIwVmxNRGR0T0RWRlNHeFNXRUkxYzBJNVFtMUlTbFJDYVRkQmRVdFBRMEpsT0hkbloxaHlUVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXUkNaMDVXU0ZFMFJVWm5VVlZWWVZoUkNtSk5OMFl5YUhjMVFYVTBPV1E0YTJSVlRtUXJaMVJyZDBoM1dVUldVakJxUWtKbmQwWnZRVlV6T1ZCd2VqRlphMFZhWWpWeFRtcHdTMFpYYVhocE5Ga0tXa1E0ZDJGM1dVUldVakJTUVZGSUwwSkhSWGRZTkZwa1lVaFNNR05JVFRaTWVUbHVZVmhTYjJSWFNYVlpNamwwVERKb01Wb3laSEJpYldSdFdWZE9iQXBNTW5Sc1kyMDFiR0pJVFhSWk1qbDBZbGhXZFdGWVVqVk1lVFZ1WVZoU2IyUlhTWFprTWpsNVlUSmFjMkl6WkhwTU1rb3hZVmQ0YTB4dWJHaGlWM2hCQ21OdFZtMWplVGx2V2xkR2EyTjVPWFJaVjJ4MVRVUnJSME5wYzBkQlVWRkNaemM0ZDBGUlJVVkxNbWd3WkVoQ2VrOXBPSFprUnpseVdsYzBkVmxYVGpBS1lWYzVkV041Tlc1aFdGSnZaRmRLTVdNeVZubFpNamwxWkVkV2RXUkROV3BpTWpCM1NIZFpTMHQzV1VKQ1FVZEVkbnBCUWtGblVWSmtNamw1WVRKYWN3cGlNMlJtV2tkc2VtTkhSakJaTW1kM1RtZFpTMHQzV1VKQ1FVZEVkbnBCUWtGM1VXOVBWRTVyVG5wWk1FMVVXbXhQVkZreFdWUkZNRmt5U1hwWk1sa3hDbHBIUm14UFYwNXFXVlJrYUU1VVRtMU9la0pxVGxkVk1WbHFRVlJDWjI5eVFtZEZSVUZaVHk5TlFVVkZRa0ZXUTJSWGJITmFSRUZ5UW1kdmNrSm5SVVVLUVZsUEwwMUJSVVpDUWpGdlpGZGtibUZYTlc1YWJVWnFXbE01Y2xwWVNuVmFWM2g2VEZkT2RtSlhNVEZpYld3d1pWUkJaRUpuYjNKQ1owVkZRVmxQTHdwTlFVVkhRa0U1ZVZwWFducE1NbWhzV1ZkU2Vrd3lNV2hoVnpSM1QzZFpTMHQzV1VKQ1FVZEVkbnBCUWtOQlVYUkVRM1J2WkVoU2QyTjZiM1pNTTFKMkNtRXlWblZNYlVacVpFZHNkbUp1VFhWYU1td3dZVWhXYVdSWVRteGpiVTUyWW01U2JHSnVVWFZaTWpsMFRVY3dSME5wYzBkQlVWRkNaemM0ZDBGUmEwVUtXSGQ0WkdGSVVqQmpTRTAyVEhrNWJtRllVbTlrVjBsMVdUSTVkRXd5YURGYU1tUndZbTFrYlZsWFRteE1NblJzWTIwMWJHSklUWFJaTWpsMFlsaFdkUXBoV0ZJMVRIazFibUZZVW05a1YwbDJaREk1ZVdFeVduTmlNMlI2VERKS01XRlhlR3RNYm14b1lsZDRRV050Vm0xamVUbHZXbGRHYTJONU9YUlpWMngxQ2sxRVowZERhWE5IUVZGUlFtYzNPSGRCVVc5RlMyZDNiMDlVVG10T2Vsa3dUVlJhYkU5VVdURlpWRVV3V1RKSmVsa3lXVEZhUjBac1QxZE9hbGxVWkdnS1RsUk9iVTU2UW1wT1YxVXhXV3BCWWtKbmIzSkNaMFZGUVZsUEwwMUJSVXhDUVRCTlF6Tk9iR0pIV1hSaFJ6bDZaRWRXYTAxRlFVZERhWE5IUVZGUlFncG5OemgzUVZGM1JVMW5kM2RoU0ZJd1kwaE5Oa3g1T1c1aFdGSnZaRmRKZFZreU9YUk1NbWd4V2pKa2NHSnRaRzFaVjA1c1RESjBiR050Tld4aVNFMTBDbGt5T1hSaVdGWjFZVmhTTlUxRVowZERhWE5IUVZGUlFtYzNPSGRCVVRCRlMyZDNiMDlVVG10T2Vsa3dUVlJhYkU5VVdURlpWRVV3V1RKSmVsa3lXVEVLV2tkR2JFOVhUbXBaVkdSb1RsUk9iVTU2UW1wT1YxVXhXV3BCWmtKbmIzSkNaMFZGUVZsUEwwMUJSVTlDUWtWTlJETktiRnB1VFhaaFIxWm9Xa2hOZGdwaVYwWndZbXBCWVVKbmIzSkNaMFZGUVZsUEwwMUJSVkJDUVhkTlEycEZkMDU2UlRCT2VsVXhUV3ByZDB4bldVdExkMWxDUWtGSFJIWjZRVUpGUVZGbkNrUkNOVzlrU0ZKM1kzcHZka3d5WkhCa1IyZ3hXV2sxYW1JeU1IWmhTRlp1V2pKc2RWb3lXbWhaTWxWM1IwRlpTMHQzV1VKQ1FVZEVkbnBCUWtWUlVVc0tSRUZuZVU1VVkzbE5SR013VFhwQ2RFSm5iM0pDWjBWRlFWbFBMMDFCUlZOQ1JqaE5XRmRvTUdSSVFucFBhVGgyV2pKc01HRklWbWxNYlU1MllsTTVid3BrVjJSdVlWYzFibHB0Um1wYVV6bHlXbGhLZFZwWGVIcE1WMDUyWWxjeE1XSnRiREJsVXpoMVdqSnNNR0ZJVm1sTU0yUjJZMjEwYldKSE9UTmplVGxwQ21SWGJITmFRelUxV1ZjeGMxRklTbXhhYmsxMllVZFdhRnBJVFhaaVYwWndZbXBCTkVKbmIzSkNaMFZGUVZsUEwwMUJSVlJDUTI5TlMwUnJlbHBFWXpJS1RrUkZNbHBVYXpKT1YwVjRUa2RPYVUweVRtMU9WMUpvV2xSc2Fsa3lSVE5aVkZWNldtcGpkMWw2Vm14T1YwbDNTVkZaUzB0M1dVSkNRVWRFZG5wQlFncEdRVkZVUkVKR00ySXpTbkphYlhoMlpERTVhMkZZVG5kWldGSnFZVVJDYTBKbmIzSkNaMFZGUVZsUEwwMUJSVlpDUmxsTlZrZG9NR1JJUW5wUGFUaDJDbG95YkRCaFNGWnBURzFPZG1KVE9XOWtWMlJ1WVZjMWJscHRSbXBhVXpseVdsaEtkVnBYZUhwTVYwNTJZbGN4TVdKdGJEQmxVemxvV1ROU2NHSXlOWG9LVEROS01XSnVUWFpOYW1kNlQwUnJNazlFWXpST1JHdDJXVmhTTUZwWE1YZGtTRTEyVFZSQlYwSm5iM0pDWjBWRlFWbFBMMDFCUlZkQ1FXZE5RbTVDTVFwWmJYaHdXWHBDUjBKbmIzSkNaMFZGUVZsUEwwMUJSVmxDUkdkTlRtNUtiR05IT0RaaFNGWnVXakpzZFZveVdtaFpNbFYyWVRKV2VXSnRWbk5qZVRGcUNtSXlNWFJrVnpWd1pFaHJObU50Vm0xUGJrcHNXbTVOZG1GSFZtaGFTRTEyWWxkR2NHSnFRMEpwVVZsTFMzZFpRa0pCU0ZkbFVVbEZRV2RTTjBKSWEwRUtaSGRDTVVGT01EbE5SM0pIZUhoRmVWbDRhMlZJU214dVRuZExhVk5zTmpRemFubDBMelJsUzJOdlFYWkxaVFpQUVVGQlFtNTRVbWRhWjFWQlFVRlJSQXBCUlZsM1VrRkpaMkV6UjBwVVJVaHpWbXh1ZFc5Sk1XdEhNalZyVUVNemMyTTBUMjR4UnpGRFNEZHFaM0pqZW01aGNUUkRTVUZGUkZoV1ZVUnJXRkJzQ21Ka1dWcDZkRTFXYUVWUWVXUkNaWFJtVTNRNWVEUTBZazkxTVcxb1JYRnRUVUZ2UjBORGNVZFRUVFE1UWtGTlJFRXlhMEZOUjFsRFRWRkVNbGxHVEdJS0swMDFjM1pJWW5oM1MweHFZbVUzVm14WVlXRTNRM1ZNZG1SMWFuUndWbkIzVWpSWFVWbDNRV3BhYUM5T2MxbG1TRkpVU3psWlJqVmtObGxEVFZGRVVBcGlVM041TTNKQ2RFRkpORkZ3Y0VaQ05EaGpPQzlRUXpoQ1V5dFpkMVJoVjNFeFozVnlhWGwzVVZOME9VcFlhMmRQUkRkVWJGZE5VRk55ZGtjMVRFVTlDaTB0TFMwdFJVNUVJRU5GVWxSSlJrbERRVlJGTFMwdExTMEsifX19fQ=="}],"timestampVerificationData":{"rfc3161Timestamps":[{"signedTimestamp":"MIICyTADAgEAMIICwAYJKoZIhvcNAQcCoIICsTCCAq0CAQMxDTALBglghkgBZQMEAgEwgbcGCyqGSIb3DQEJEAEEoIGnBIGkMIGhAgEBBgkrBgEEAYO/MAIwMTANBglghkgBZQMEAgEFAAQguFU3H+mook948dfQKNgaoxHAoycH3GX2xe+B/+CeMJACFFbYYWssAU46XsBRISWFOQ8Li12eGA8yMDI2MDYyOTE3MTQ0OVowAwIBAaAypDAwLjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MRUwEwYDVQQDEwxzaWdzdG9yZS10c2GgADGCAdswggHXAgEBMFEwOTEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MSAwHgYDVQQDExdzaWdzdG9yZS10c2Etc2VsZnNpZ25lZAIUOhNULwyQYe68wUMvy4qOiyojiwwwCwYJYIZIAWUDBAIBoIH8MBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAcBgkqhkiG9w0BCQUxDxcNMjYwNjI5MTcxNDQ5WjAvBgkqhkiG9w0BCQQxIgQguvirpEmIhGn3xr9LWY2ECPSMWhGKtDD4yC7y/bDh1KswgY4GCyqGSIb3DQEJEAIvMX8wfTB7MHkEIIX5J7wHq2LKw7RDVsEO/IGyxog/2nq55thw2dE6zQW3MFUwPaQ7MDkxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEgMB4GA1UEAxMXc2lnc3RvcmUtdHNhLXNlbGZzaWduZWQCFDoTVC8MkGHuvMFDL8uKjosqI4sMMAoGCCqGSM49BAMCBGcwZQIwDmJq7ErbKctLqPhm2iwOCTJDHIm8Je4xVwCY/fXufe2r+Z9Dgh8AW+PjeW6Rj9hZAjEAhiq+ySoRLE6b8eGDhPKHxDczbFbxVhtusLTA9bdwkTUDzb2bPMZKHP+wYEQ5La8d"}]}},"messageSignature":{"messageDigest":{"algorithm":"SHA2_256","digest":"2UFGX0XK2Kc9kotfWuG7LYyN5/fykCMX0PuFKbXzIqQ="},"signature":"MEUCIQCFjnmlRPln+PnvOeMQ3voPBGFdh4f07/r2mcNI7eyx3AIgWj8SrN67AHMaC0w1bjDloHExFJlBNHMQFmQg2gMNpss="}}
|
build/torch-cuda/utils.py
CHANGED
|
@@ -1,6 +1,40 @@
|
|
| 1 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
from contextlib import contextmanager
|
| 3 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
|
| 5 |
@contextmanager
|
| 6 |
def device_context(device: torch.device):
|
|
@@ -11,3 +45,723 @@ def device_context(device: torch.device):
|
|
| 11 |
yield
|
| 12 |
else:
|
| 13 |
yield
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2026 The HuggingFace Inc. team. All rights reserved.
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
import functools
|
| 16 |
from contextlib import contextmanager
|
| 17 |
|
| 18 |
+
import torch
|
| 19 |
+
import triton
|
| 20 |
+
import triton.language as tl
|
| 21 |
+
from torch.library import triton_op, wrap_triton
|
| 22 |
+
|
| 23 |
+
from ._ops import add_op_namespace_prefix, ops
|
| 24 |
+
|
| 25 |
+
# ── Format constants ──────────────────────────────────────────────────────────
|
| 26 |
+
|
| 27 |
+
# FP8 (E4M3) is the main format for weights and activations;
|
| 28 |
+
FP8_DTYPE = torch.float8_e4m3fn
|
| 29 |
+
# FP4 (E2M1) packs two 4-bit nibbles per byte. MX formats (MXFP4 weights, MXFP8
|
| 30 |
+
# E4M3 weights/activations) share one UE8M0 scale per 32-element K-group — the OCP
|
| 31 |
+
# MX block size, consumed by ``tl.dot_scaled``. Format constants, not tunables.
|
| 32 |
+
NIBBLES_PER_BYTE = 2
|
| 33 |
+
MX_SCALE_GROUP_K = 32
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
# ── Host-side helpers ─────────────────────────────────────────────────────────
|
| 37 |
+
|
| 38 |
|
| 39 |
@contextmanager
|
| 40 |
def device_context(device: torch.device):
|
|
|
|
| 45 |
yield
|
| 46 |
else:
|
| 47 |
yield
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
@functools.lru_cache(maxsize=8)
|
| 51 |
+
def sm_count(device_index: int) -> int:
|
| 52 |
+
"""Processor (CUDA SM / XPU Xe-core) count for a device (a constant) — cached so callers
|
| 53 |
+
don't re-query it each launch. Read from Triton's active driver so it works on any backend,
|
| 54 |
+
with a CUDA fallback."""
|
| 55 |
+
try:
|
| 56 |
+
return triton.runtime.driver.active.utils.get_device_properties(device_index)[
|
| 57 |
+
"multiprocessor_count"
|
| 58 |
+
]
|
| 59 |
+
except Exception:
|
| 60 |
+
active_device = get_active_device_type()
|
| 61 |
+
if active_device == "cuda":
|
| 62 |
+
return torch.cuda.get_device_properties(device_index).multi_processor_count
|
| 63 |
+
elif active_device == "xpu":
|
| 64 |
+
return torch.xpu.get_device_properties(device_index).multi_processor_count
|
| 65 |
+
else:
|
| 66 |
+
raise RuntimeError(
|
| 67 |
+
f"Unsupported device type {active_device} for sm_count; only cuda/xpu are supported."
|
| 68 |
+
)
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
# H-tile each topk_reduce program reduces (one (token, tile) program per BLOCK_H span). The
|
| 72 |
+
# reduce is bandwidth-bound and saturates at BLOCK_H>=512: a sweep over {128..2048} x prefill
|
| 73 |
+
# shapes put 512/1024/2048 within ~7% (mostly noise) while 128/256 lagged — not worth a tuning
|
| 74 |
+
# axis for this small slice of runtime. 512 = smallest that saturates (best at low token counts,
|
| 75 |
+
# where bigger tiles waste masked lanes). Power of 2 (tl.arange).
|
| 76 |
+
TOPK_REDUCE_BLOCK_H = 512
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
@triton.jit
|
| 80 |
+
def topk_reduce_kernel(
|
| 81 |
+
ProjOut,
|
| 82 |
+
Out,
|
| 83 |
+
H,
|
| 84 |
+
stride_pm,
|
| 85 |
+
stride_ph,
|
| 86 |
+
stride_om,
|
| 87 |
+
stride_oh,
|
| 88 |
+
NUM_TOP_K: tl.constexpr,
|
| 89 |
+
BLOCK_H: tl.constexpr,
|
| 90 |
+
):
|
| 91 |
+
"""Sum the ``NUM_TOP_K`` flat ``(token, slot)`` rows of ``ProjOut`` into ``Out[token]`` —
|
| 92 |
+
a tight replacement for ``proj_out.view(T, K, H).sum(1)`` (~2.8x faster than torch's
|
| 93 |
+
generic reduce; fp32 accumulate, so bit-identical). Caller allocs Out and launches it."""
|
| 94 |
+
t = tl.program_id(0)
|
| 95 |
+
offs_h = tl.program_id(1) * BLOCK_H + tl.arange(0, BLOCK_H)
|
| 96 |
+
mask = offs_h < H
|
| 97 |
+
acc = tl.zeros((BLOCK_H,), tl.float32)
|
| 98 |
+
for k in tl.static_range(NUM_TOP_K):
|
| 99 |
+
acc += tl.load(
|
| 100 |
+
ProjOut + (t * NUM_TOP_K + k) * stride_pm + offs_h * stride_ph,
|
| 101 |
+
mask=mask,
|
| 102 |
+
other=0.0,
|
| 103 |
+
).to(tl.float32)
|
| 104 |
+
tl.store(
|
| 105 |
+
Out + t * stride_om + offs_h * stride_oh,
|
| 106 |
+
acc.to(Out.dtype.element_ty),
|
| 107 |
+
mask=mask,
|
| 108 |
+
)
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
def ue8m0_as_uint8(scale: torch.Tensor) -> torch.Tensor:
|
| 112 |
+
"""View UE8M0 (``float8_e8m0fnu``) weight scales as ``uint8`` for the Triton
|
| 113 |
+
binder, which doesn't recognize the dtype; kernels decode ``2^(exp-127)``
|
| 114 |
+
inline. fp32 (non-UE8M0) scales pass through unchanged."""
|
| 115 |
+
return scale.view(torch.uint8) if scale.dtype == torch.float8_e8m0fnu else scale
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
def e2m1_as_uint8(weight: torch.Tensor) -> torch.Tensor:
|
| 119 |
+
"""View an ``int8``-stored MXFP4 (packed E2M1) weight as ``uint8`` — a zero-cost
|
| 120 |
+
reinterpret. ``tl.dot_scaled`` requires the packed rhs as ``uint8``, so do it once here
|
| 121 |
+
instead of casting in-kernel at every load. E4M3 (MXFP8) weights pass through unchanged."""
|
| 122 |
+
return weight.view(torch.uint8) if weight.dtype == torch.int8 else weight
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
def is_mxfp8(weight: torch.Tensor, scale: torch.Tensor) -> bool:
|
| 126 |
+
"""MXFP8 weight/scale pair: E4M3 weights with UE8M0 group-32 scales — last dim
|
| 127 |
+
``scale.shape[-1] == weight.shape[-1] // MX_SCALE_GROUP_K``, matching leading dims.
|
| 128 |
+
Works for 2D ``(N, K)`` and 3D ``(E, N, K)`` weights. The group-32 layout is what
|
| 129 |
+
separates MXFP8 from 128-block FP8 (which may also carry UE8M0 scales)."""
|
| 130 |
+
return (
|
| 131 |
+
weight.dtype == torch.float8_e4m3fn
|
| 132 |
+
and scale.dtype == torch.float8_e8m0fnu
|
| 133 |
+
and scale.ndim == weight.ndim
|
| 134 |
+
and scale.shape[:-1] == weight.shape[:-1]
|
| 135 |
+
and scale.shape[-1] == weight.shape[-1] // MX_SCALE_GROUP_K
|
| 136 |
+
)
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
def is_mxfp4(weight: torch.Tensor, scale: torch.Tensor) -> bool:
|
| 140 |
+
"""MXFP4 weight/scale pair: packed E2M1 weights (``int8``, two codes/byte) with
|
| 141 |
+
UE8M0 group-32 scales — ``scale.shape[-1] == weight.shape[-1] * NIBBLES_PER_BYTE //
|
| 142 |
+
MX_SCALE_GROUP_K`` (unpacked K = ``2 * K_half``), matching leading dims. 2D or 3D."""
|
| 143 |
+
return (
|
| 144 |
+
weight.dtype == torch.int8
|
| 145 |
+
and scale.dtype == torch.float8_e8m0fnu
|
| 146 |
+
and scale.ndim == weight.ndim
|
| 147 |
+
and scale.shape[:-1] == weight.shape[:-1]
|
| 148 |
+
and scale.shape[-1] == weight.shape[-1] * NIBBLES_PER_BYTE // MX_SCALE_GROUP_K
|
| 149 |
+
)
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
def is_mxfp(weight: torch.Tensor, scale: torch.Tensor) -> bool:
|
| 153 |
+
"""Any MX weight/scale pair — MXFP8 (``float8_e4m3fn``, one value/byte) or MXFP4
|
| 154 |
+
(``int8``, two E2M1 codes/byte), both with UE8M0 group-32 scales. ``values_per_byte``
|
| 155 |
+
folds the two cases: the scale's last dim covers the unpacked K
|
| 156 |
+
(``weight.shape[-1] * values_per_byte``) in groups of ``MX_SCALE_GROUP_K``. The
|
| 157 |
+
dispatchers route on this; the op picks the format from ``weight.dtype``."""
|
| 158 |
+
if weight.dtype == torch.float8_e4m3fn:
|
| 159 |
+
values_per_byte = 1
|
| 160 |
+
elif weight.dtype == torch.int8:
|
| 161 |
+
values_per_byte = NIBBLES_PER_BYTE
|
| 162 |
+
else:
|
| 163 |
+
return False
|
| 164 |
+
return (
|
| 165 |
+
scale.dtype == torch.float8_e8m0fnu
|
| 166 |
+
and scale.ndim == weight.ndim
|
| 167 |
+
and scale.shape[:-1] == weight.shape[:-1]
|
| 168 |
+
and scale.shape[-1] == weight.shape[-1] * values_per_byte // MX_SCALE_GROUP_K
|
| 169 |
+
)
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
def is_tensor_wide(block_size, weight: torch.Tensor) -> bool:
|
| 173 |
+
"""True when ``block_size`` selects per-tensor (tensor-dynamic) scaling: ``None`` or
|
| 174 |
+
equal to the weight's full ``(N, K)`` — one scale block spanning the whole matrix.
|
| 175 |
+
Handles 2D ``(N, K)`` and 3D ``(E, N, K)`` weights via the last two dims."""
|
| 176 |
+
return block_size is None or (
|
| 177 |
+
block_size[0] == weight.shape[-2] and block_size[1] == weight.shape[-1]
|
| 178 |
+
)
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
# The per-token batched/fused kernels are decode-shaped — one routed row per program, so
|
| 182 |
+
# BLOCK_SIZE_M is 1 (a larger tile would just recompute the same row). The 2D matmul sizes
|
| 183 |
+
# its M tile to the workload instead, via ``adaptive_block_size_m``.
|
| 184 |
+
DECODE_BLOCK_SIZE_M = 1
|
| 185 |
+
|
| 186 |
+
|
| 187 |
+
def adaptive_block_size_m(target_m: int) -> int:
|
| 188 |
+
"""Smallest power-of-2 >= ``target_m``, floored at 16 and capped at 128.
|
| 189 |
+
|
| 190 |
+
Used by all matmul wrappers (single / batched / grouped) to size the M tile
|
| 191 |
+
to the workload — small per-expert M wants smaller tiles, large M caps out
|
| 192 |
+
at 128 to keep register pressure bounded. Pass ``M`` for single matmul, or
|
| 193 |
+
``(S + E - 1) // E`` (avg tokens per expert) for batched/grouped.
|
| 194 |
+
"""
|
| 195 |
+
return min(max(triton.next_power_of_2(target_m), 16), 128)
|
| 196 |
+
|
| 197 |
+
|
| 198 |
+
@functools.cache
|
| 199 |
+
def get_active_device_type() -> str:
|
| 200 |
+
"""Active torch device type for the current Triton backend (``"cuda"``, ``"xpu"``, ...).
|
| 201 |
+
|
| 202 |
+
Falls back to ``"cuda"`` when no driver is loaded — Triton raises
|
| 203 |
+
``RuntimeError: 0 active drivers ([])`` on driverless build boxes, and the
|
| 204 |
+
autotune-config builder is evaluated at module-import time under the
|
| 205 |
+
``@triton.autotune`` decorator (no kernel launches there, so the default is
|
| 206 |
+
only used to shape the config list).
|
| 207 |
+
"""
|
| 208 |
+
try:
|
| 209 |
+
return triton.runtime.driver.active.get_active_torch_device().type
|
| 210 |
+
except RuntimeError:
|
| 211 |
+
return "cuda"
|
| 212 |
+
|
| 213 |
+
|
| 214 |
+
def get_accelerator_autotuning_configs(
|
| 215 |
+
*,
|
| 216 |
+
tune_block_m: bool = False,
|
| 217 |
+
tune_block_nk: bool = False,
|
| 218 |
+
):
|
| 219 |
+
"""Autotune search grid for the current accelerator.
|
| 220 |
+
|
| 221 |
+
``num_warps``, ``num_stages`` and ``blocks`` (the ``(BLOCK_SIZE_N, BLOCK_SIZE_K)``
|
| 222 |
+
tile shapes) are fixed up front from ``(is_xpu, tune_block_nk)``, then crossed
|
| 223 |
+
into the config list.
|
| 224 |
+
|
| 225 |
+
``tune_block_nk=True`` sweeps the tile: used by kernels that have no caller
|
| 226 |
+
``block_size`` to fix it — the MX ``tl.dot_scaled`` paths AND the tensor-dynamic
|
| 227 |
+
FP8 paths. ``tune_block_nk=False`` emits a single empty meta-dict (block-scaled
|
| 228 |
+
kernels take the tile from the caller's ``block_size``). ``tune_block_m=True`` also crosses
|
| 229 |
+
in BLOCK_SIZE_M ∈ ``(16, 32, 64, 128)`` (fused grouped, which lets the tuner pick M instead
|
| 230 |
+
of computing it via ``adaptive_block_size_m``).
|
| 231 |
+
|
| 232 |
+
The CUDA tile set is a data-driven prune of a B200 sweep across single (BM=128),
|
| 233 |
+
grouped MoE (BM=16/64) and decode (BM=1): winners only ever used these 4 tiles,
|
| 234 |
+
num_warps in {4,8,16}, num_stages in {2,3} (warps=2, stages=4 and 256x256 never
|
| 235 |
+
won) — 108 → 24. (Tuned on dot_scaled; tensor-dynamic tl.dot reuses it.)
|
| 236 |
+
"""
|
| 237 |
+
is_xpu = get_active_device_type() == "xpu"
|
| 238 |
+
num_warps = [8, 16] if is_xpu else [2, 4, 8, 16]
|
| 239 |
+
num_stages = [2, 3, 4]
|
| 240 |
+
|
| 241 |
+
if tune_block_nk:
|
| 242 |
+
tiles = (
|
| 243 |
+
[(128, 128)] if is_xpu else [(128, 128), (256, 128), (128, 64), (64, 256)]
|
| 244 |
+
)
|
| 245 |
+
blocks = [{"BLOCK_SIZE_N": bn, "BLOCK_SIZE_K": bk} for bn, bk in tiles]
|
| 246 |
+
else:
|
| 247 |
+
blocks = [{}]
|
| 248 |
+
|
| 249 |
+
if tune_block_m:
|
| 250 |
+
blocks = [{**b, "BLOCK_SIZE_M": bm} for b in blocks for bm in (16, 32, 64, 128)]
|
| 251 |
+
|
| 252 |
+
return [
|
| 253 |
+
triton.Config(b, num_warps=w, num_stages=s)
|
| 254 |
+
for b in blocks
|
| 255 |
+
for w in num_warps
|
| 256 |
+
for s in num_stages
|
| 257 |
+
]
|
| 258 |
+
|
| 259 |
+
|
| 260 |
+
def get_mxfp_autotuning_configs(
|
| 261 |
+
pre_hook=None,
|
| 262 |
+
compute_modes=("dot_scaled", "dot", "scalar"),
|
| 263 |
+
memory_modes=(None,),
|
| 264 |
+
tune_block_m=False,
|
| 265 |
+
):
|
| 266 |
+
"""Autotune grid for the MXFP8 MoE/matmul kernels. ``COMPUTE_MODE`` (a constexpr string) picks
|
| 267 |
+
the multiply-accumulate, and the Bayesian tuner selects it per workload (token count is
|
| 268 |
+
in the key):
|
| 269 |
+
|
| 270 |
+
- ``"dot_scaled"``: native group-32 scaled MMA (wide K, ``BLOCK_SIZE_K`` ∈ {128, 256}) —
|
| 271 |
+
wins once the grid saturates (prefill, ~S≥32).
|
| 272 |
+
- ``"dot"``: fp8 ``tl.dot`` + per-group software rescale, one scale group per K-step
|
| 273 |
+
(``BLOCK_SIZE_K == 32``).
|
| 274 |
+
- ``"scalar"``: scalar CUDA-core FMA reduction, no tensor core — same per-group structure
|
| 275 |
+
as ``"dot"`` (hence bit-exact with it) but avoids the scaled-MMA's M→16 pad, so it wins
|
| 276 |
+
for memory-bound **decode** (M=1; ≈2× over ``dot_scaled`` at S≥32 in a sweep). Only emit
|
| 277 |
+
for kernels that implement the branch.
|
| 278 |
+
|
| 279 |
+
``num_warps=16`` and ``BLOCK_SIZE_K=64`` are omitted (dead at M=1 per a sweep).
|
| 280 |
+
|
| 281 |
+
``memory_modes`` (fused_grouped only) is the ``MEMORY_MODE`` axis for the weight load — e.g.
|
| 282 |
+
``("host_descriptor", "pointer")`` on CUDA, ``("device_descriptor", "pointer")`` on XPU
|
| 283 |
+
(a host/device tensor descriptor — TMA on NVIDIA — vs explicit pointers); the
|
| 284 |
+
``(None,)`` (default) emits no axis. Pass all the modes a kernel implements; this drops
|
| 285 |
+
the device-inapplicable descriptor flavor (host-side is NVIDIA-TMA-only, device-side is the
|
| 286 |
+
XPU win). a given pre_hook is attached to every config and self-guards on ``MEMORY_MODE``."""
|
| 287 |
+
drop = (
|
| 288 |
+
"host_descriptor" if get_active_device_type() == "xpu" else "device_descriptor"
|
| 289 |
+
)
|
| 290 |
+
memory_modes = tuple(m for m in memory_modes if m != drop)
|
| 291 |
+
if "device_descriptor" in memory_modes:
|
| 292 |
+
# device-side descriptors build in-kernel tensormaps that need a scratch allocator (the
|
| 293 |
+
# XPU path; CUDA keeps host-built descriptors and never reaches this).
|
| 294 |
+
triton.set_allocator(
|
| 295 |
+
lambda size, alignment, stream: torch.empty(
|
| 296 |
+
size, dtype=torch.int8, device="xpu"
|
| 297 |
+
)
|
| 298 |
+
)
|
| 299 |
+
bms = (16, 32, 64, 128) if tune_block_m else (None,)
|
| 300 |
+
return [
|
| 301 |
+
triton.Config(
|
| 302 |
+
{
|
| 303 |
+
"COMPUTE_MODE": mode,
|
| 304 |
+
"BLOCK_SIZE_N": bn,
|
| 305 |
+
"BLOCK_SIZE_K": bk,
|
| 306 |
+
**({"MEMORY_MODE": mm} if mm is not None else {}),
|
| 307 |
+
**({"BLOCK_SIZE_M": bm} if bm is not None else {}),
|
| 308 |
+
},
|
| 309 |
+
num_warps=w,
|
| 310 |
+
num_stages=s,
|
| 311 |
+
# Always attached when given; the hook self-guards on MEMORY_MODE (only
|
| 312 |
+
# host_descriptor configs use the host-built descriptor).
|
| 313 |
+
pre_hook=pre_hook,
|
| 314 |
+
)
|
| 315 |
+
for mode in compute_modes
|
| 316 |
+
for mm in memory_modes
|
| 317 |
+
for bn in [32, 64, 128, 256]
|
| 318 |
+
# only "dot" pins BK to the group (one scale group per fp8 tl.dot step); "dot_scaled"
|
| 319 |
+
# and "scalar" handle multiple groups per K-chunk, so they use a wide BK.
|
| 320 |
+
for bk in ([32] if mode == "dot" else [128, 256])
|
| 321 |
+
for s in [2, 3, 4, 5, 6]
|
| 322 |
+
for w in [2, 4, 8]
|
| 323 |
+
for bm in bms
|
| 324 |
+
]
|
| 325 |
+
|
| 326 |
+
|
| 327 |
+
@functools.lru_cache(maxsize=None)
|
| 328 |
+
def sm_shared_memory_limit(device_index: int) -> int:
|
| 329 |
+
"""Max dynamic shared memory per block (bytes) for a CUDA device — the cap Triton
|
| 330 |
+
reports as the 'Hardware limit' on an ``out of resource: shared memory`` failure
|
| 331 |
+
(~232 KB on B200, ~227 KB on H100, much less on older/consumer parts). Queried from
|
| 332 |
+
the driver so the prune adapts to the hardware instead of hardcoding one GPU."""
|
| 333 |
+
try:
|
| 334 |
+
return triton.runtime.driver.active.utils.get_device_properties(device_index)[
|
| 335 |
+
"max_shared_mem"
|
| 336 |
+
]
|
| 337 |
+
except Exception:
|
| 338 |
+
if get_active_device_type() == "cuda":
|
| 339 |
+
return torch.cuda.get_device_properties(
|
| 340 |
+
device_index
|
| 341 |
+
).shared_memory_per_block_optin
|
| 342 |
+
elif get_active_device_type() == "xpu":
|
| 343 |
+
return torch.xpu.get_device_properties(
|
| 344 |
+
device_index
|
| 345 |
+
).shared_memory_per_block_optin
|
| 346 |
+
else:
|
| 347 |
+
raise RuntimeError(
|
| 348 |
+
f"Unsupported device type {get_active_device_type()} for sm_shared_memory_limit; only cuda/xpu are supported."
|
| 349 |
+
)
|
| 350 |
+
|
| 351 |
+
|
| 352 |
+
def smem_config_pruner(act_bytes: int, n_weight_tiles: int, weight_bytes: int = 1):
|
| 353 |
+
"""Build an ``early_config_prune`` that drops configs whose pipelined operand shared
|
| 354 |
+
memory would overflow the SM — the source of ``out of resource: shared memory`` autotune
|
| 355 |
+
failures (and the wasted compiles they cause).
|
| 356 |
+
|
| 357 |
+
Per-stage estimate (bytes) = ``BK · (act_bytes·BM + weight_bytes·n_weight_tiles·BN)``:
|
| 358 |
+
one activation tile ``[BM, BK]`` plus ``n_weight_tiles`` weight tiles ``[BN, BK]``
|
| 359 |
+
(gate_up fuses 2; down has 1), times ``num_stages``. ``BM`` is the routing-derived tile,
|
| 360 |
+
read from the launch args. MXFP4 packs 2 weights/byte, so ``weight_bytes=1`` (MXFP8) is a
|
| 361 |
+
safe upper bound. The limit is read from the active device. Never returns empty — keeps
|
| 362 |
+
the smallest-footprint config as a fallback."""
|
| 363 |
+
|
| 364 |
+
def prune(configs, named_args, **kwargs):
|
| 365 |
+
# The estimate needs all three tile dims. Each is either an autotuned config meta
|
| 366 |
+
# (MX) or a launch arg (block-dynamic), so look in both; raise clearly if missing.
|
| 367 |
+
all_args = {**named_args, **kwargs}
|
| 368 |
+
dev = triton.runtime.driver.active.get_active_torch_device()
|
| 369 |
+
limit = sm_shared_memory_limit(dev.index if dev.index is not None else 0)
|
| 370 |
+
|
| 371 |
+
def dim(c, name):
|
| 372 |
+
v = c.kwargs.get(name, all_args.get(name))
|
| 373 |
+
if v is None:
|
| 374 |
+
raise ValueError(
|
| 375 |
+
f"smem_config_pruner needs {name} (autotune config meta or launch arg) "
|
| 376 |
+
"to estimate shared memory; none found."
|
| 377 |
+
)
|
| 378 |
+
return v
|
| 379 |
+
|
| 380 |
+
def smem(c):
|
| 381 |
+
BM, BN, BK = (
|
| 382 |
+
dim(c, n) for n in ("BLOCK_SIZE_M", "BLOCK_SIZE_N", "BLOCK_SIZE_K")
|
| 383 |
+
)
|
| 384 |
+
return (
|
| 385 |
+
c.num_stages
|
| 386 |
+
* BK
|
| 387 |
+
* (act_bytes * BM + weight_bytes * n_weight_tiles * BN)
|
| 388 |
+
)
|
| 389 |
+
|
| 390 |
+
kept = [c for c in configs if smem(c) <= limit]
|
| 391 |
+
return kept or [min(configs, key=smem)]
|
| 392 |
+
|
| 393 |
+
return prune
|
| 394 |
+
|
| 395 |
+
|
| 396 |
+
# ── Triton-side helpers (inlined by ``@triton.jit`` callers) ──────────────────
|
| 397 |
+
|
| 398 |
+
|
| 399 |
+
@triton.jit
|
| 400 |
+
def fp8_act_quant_inline(a_raw):
|
| 401 |
+
"""Inline FP8 (E4M3) activation quant for the W8A8 block-scale path.
|
| 402 |
+
|
| 403 |
+
Per-row amax → fp32 scale ``amax/448`` (floored at 1e-12 against zero rows)
|
| 404 |
+
→ cast values to FP8. Returns ``(a_fp8, a_s)`` with shapes ``(M, K)`` and
|
| 405 |
+
``(M,)``.
|
| 406 |
+
"""
|
| 407 |
+
a_s = tl.max(tl.abs(a_raw), axis=1) / 448.0
|
| 408 |
+
a_fp8 = (a_raw / tl.maximum(a_s[:, None], 1e-12)).to(tl.float8e4nv)
|
| 409 |
+
return a_fp8, a_s
|
| 410 |
+
|
| 411 |
+
|
| 412 |
+
@triton.jit
|
| 413 |
+
def mxfp_act_quant_inline(
|
| 414 |
+
a_raw,
|
| 415 |
+
BLOCK_SIZE_M: tl.constexpr,
|
| 416 |
+
BLOCK_SIZE_K: tl.constexpr,
|
| 417 |
+
SCALE_GROUP_K: tl.constexpr,
|
| 418 |
+
):
|
| 419 |
+
"""Inline E4M3 activation quant for the MX paths (W4A8 MXFP4 / W8A8 MXFP8).
|
| 420 |
+
|
| 421 |
+
Per-row, per-K-group amax → UE8M0 scale (ceil to next power-of-2 via the
|
| 422 |
+
mantissa-nonzero bump trick) → cast values to FP8. Returns ``(a_fp8,
|
| 423 |
+
a_scale_u8)`` with shapes ``(M, K)`` and ``(M, K // SCALE_GROUP_K)``.
|
| 424 |
+
"""
|
| 425 |
+
a_groups = tl.reshape(
|
| 426 |
+
a_raw, (BLOCK_SIZE_M, BLOCK_SIZE_K // SCALE_GROUP_K, SCALE_GROUP_K)
|
| 427 |
+
)
|
| 428 |
+
a_s_fp32 = tl.max(tl.abs(a_groups), axis=2) / 448.0
|
| 429 |
+
bits = a_s_fp32.to(tl.int32, bitcast=True)
|
| 430 |
+
# ceil_to_ue8m0: bump exponent by 1 when mantissa is non-zero.
|
| 431 |
+
exp_ceil = ((bits >> 23) & 0xFF) + ((bits & 0x7FFFFF) != 0).to(tl.int32)
|
| 432 |
+
exp_ceil = tl.minimum(tl.maximum(exp_ceil, 1), 254)
|
| 433 |
+
a_scale_u8 = exp_ceil.to(tl.uint8)
|
| 434 |
+
a_s_pow2 = (exp_ceil << 23).to(tl.float32, bitcast=True)
|
| 435 |
+
a_fp8 = tl.reshape(
|
| 436 |
+
a_groups / tl.maximum(a_s_pow2[:, :, None], 1e-12),
|
| 437 |
+
(BLOCK_SIZE_M, BLOCK_SIZE_K),
|
| 438 |
+
).to(tl.float8e4nv)
|
| 439 |
+
return a_fp8, a_scale_u8
|
| 440 |
+
|
| 441 |
+
|
| 442 |
+
@triton.jit
|
| 443 |
+
def decode_ue8m0_scale(scale):
|
| 444 |
+
"""Decode a UE8M0 weight scale to fp32: when it was loaded as ``uint8`` exponent
|
| 445 |
+
bits, ``value = 2^(exp - 127)``, built directly as the fp32 bit pattern. fp32
|
| 446 |
+
scales (block-dynamic with float scales) pass through. The dtype branch is a
|
| 447 |
+
compile-time constant, so only the taken path is emitted (single return — Triton
|
| 448 |
+
requires all ``return`` statements to share a type)."""
|
| 449 |
+
if scale.dtype == tl.uint8:
|
| 450 |
+
scale = (scale.to(tl.int32) << 23).to(tl.float32, bitcast=True)
|
| 451 |
+
return scale
|
| 452 |
+
|
| 453 |
+
|
| 454 |
+
@triton.jit
|
| 455 |
+
def expand_ue8m0_scale(
|
| 456 |
+
scale_u8, ROWS: tl.constexpr, BK: tl.constexpr, GK: tl.constexpr
|
| 457 |
+
):
|
| 458 |
+
"""Decode a UE8M0 group scale tile ``[ROWS, BK//GK]`` and broadcast each group scale
|
| 459 |
+
across its ``GK`` elements → ``[ROWS, BK]`` fp32 — one scale per K element. Used by the
|
| 460 |
+
scalar (CUDA-core FMA) MX paths to dequant the whole BK chunk before the reduction."""
|
| 461 |
+
s = decode_ue8m0_scale(scale_u8)
|
| 462 |
+
return tl.reshape(tl.broadcast_to(s[:, :, None], (ROWS, BK // GK, GK)), (ROWS, BK))
|
| 463 |
+
|
| 464 |
+
|
| 465 |
+
@triton.jit
|
| 466 |
+
def mx_dot_scaled(a, a_scale, w, w_scale, acc, VALUES_PER_BYTE: tl.constexpr):
|
| 467 |
+
"""MX 'dot_scaled' path: scaled MMA folding the UE8M0 group scales into the tensor core —
|
| 468 |
+
``a`` (E4M3) @ ``w`` (packed E2M1 if MXFP4, else E4M3). The rhs format is picked from
|
| 469 |
+
``VALUES_PER_BYTE``. Caller pre-shapes ``w``/``w_scale`` (e.g. ``tl.trans(gu)``)."""
|
| 470 |
+
rhs_format: tl.constexpr = "e2m1" if VALUES_PER_BYTE == 2 else "e4m3"
|
| 471 |
+
return tl.dot_scaled(a, a_scale, "e4m3", w, w_scale, rhs_format, acc)
|
| 472 |
+
|
| 473 |
+
|
| 474 |
+
@triton.jit
|
| 475 |
+
def mx_dot_rescale(acc, a, wq, a_scale, w_scale):
|
| 476 |
+
"""MX 'dot' path (BK == group): fp8 ``tl.dot`` + per-group software rescale, decoding both
|
| 477 |
+
UE8M0 scales internally and accumulating into ``acc`` (returned updated). Single weight — for
|
| 478 |
+
the gate∪up pair (shared activation scale) use ``mx_dot_rescale_gate_up``."""
|
| 479 |
+
return acc + tl.dot(a, wq) * decode_ue8m0_scale(a_scale) * tl.trans(
|
| 480 |
+
decode_ue8m0_scale(w_scale)
|
| 481 |
+
)
|
| 482 |
+
|
| 483 |
+
|
| 484 |
+
@triton.jit
|
| 485 |
+
def mx_dot_rescale_gate_up(
|
| 486 |
+
acc_gate, acc_up, a, w_gate, w_up, a_scale, gate_scale, up_scale
|
| 487 |
+
):
|
| 488 |
+
"""Gate∪up 'dot' path: decode the SHARED activation scale once, rescale both projections
|
| 489 |
+
and accumulate into ``acc_gate``/``acc_up`` (returned updated)."""
|
| 490 |
+
a_s = decode_ue8m0_scale(a_scale)
|
| 491 |
+
acc_gate += tl.dot(a, w_gate) * a_s * tl.trans(decode_ue8m0_scale(gate_scale))
|
| 492 |
+
acc_up += tl.dot(a, w_up) * a_s * tl.trans(decode_ue8m0_scale(up_scale))
|
| 493 |
+
return acc_gate, acc_up
|
| 494 |
+
|
| 495 |
+
|
| 496 |
+
@triton.jit
|
| 497 |
+
def mx_scalar_reduce(
|
| 498 |
+
acc,
|
| 499 |
+
a,
|
| 500 |
+
a_scale,
|
| 501 |
+
wq,
|
| 502 |
+
w_scale,
|
| 503 |
+
BLOCK_SIZE_M: tl.constexpr,
|
| 504 |
+
ROWS_W: tl.constexpr,
|
| 505 |
+
BK: tl.constexpr,
|
| 506 |
+
GK: tl.constexpr,
|
| 507 |
+
):
|
| 508 |
+
"""MX 'scalar' path: CUDA-core FMA GEMV, dequantizing activation + weight per-element by
|
| 509 |
+
their expanded group scales internally, then reducing and accumulating into ``acc`` (returned
|
| 510 |
+
updated). No tensor core (so no M→16 MMA pad) — wins for the memory-bound decode GEMV (M=1).
|
| 511 |
+
Single weight — for the gate∪up pair (shared activation dequant) use ``mx_scalar_reduce_gate_up``."""
|
| 512 |
+
a_deq = tl.trans(
|
| 513 |
+
a.to(tl.float32) * expand_ue8m0_scale(a_scale, BLOCK_SIZE_M, BK, GK)
|
| 514 |
+
)
|
| 515 |
+
w_exp = tl.trans(expand_ue8m0_scale(w_scale, ROWS_W, BK, GK))
|
| 516 |
+
return acc + tl.sum(a_deq * (wq.to(tl.float32) * w_exp), axis=0)[None, :]
|
| 517 |
+
|
| 518 |
+
|
| 519 |
+
@triton.jit
|
| 520 |
+
def mx_scalar_reduce_gate_up(
|
| 521 |
+
acc_gate,
|
| 522 |
+
acc_up,
|
| 523 |
+
a,
|
| 524 |
+
w_gate,
|
| 525 |
+
w_up,
|
| 526 |
+
a_scale,
|
| 527 |
+
gate_scale,
|
| 528 |
+
up_scale,
|
| 529 |
+
BLOCK_SIZE_M: tl.constexpr,
|
| 530 |
+
ROWS_W: tl.constexpr,
|
| 531 |
+
BK: tl.constexpr,
|
| 532 |
+
GK: tl.constexpr,
|
| 533 |
+
):
|
| 534 |
+
"""Gate∪up 'scalar' path: dequant the SHARED activation once, reduce both projections and
|
| 535 |
+
accumulate into ``acc_gate``/``acc_up`` (returned updated)."""
|
| 536 |
+
a_deq = tl.trans(
|
| 537 |
+
a.to(tl.float32) * expand_ue8m0_scale(a_scale, BLOCK_SIZE_M, BK, GK)
|
| 538 |
+
)
|
| 539 |
+
acc_gate += tl.sum(
|
| 540 |
+
a_deq
|
| 541 |
+
* (
|
| 542 |
+
w_gate.to(tl.float32)
|
| 543 |
+
* tl.trans(expand_ue8m0_scale(gate_scale, ROWS_W, BK, GK))
|
| 544 |
+
),
|
| 545 |
+
axis=0,
|
| 546 |
+
)[None, :]
|
| 547 |
+
acc_up += tl.sum(
|
| 548 |
+
a_deq
|
| 549 |
+
* (
|
| 550 |
+
w_up.to(tl.float32) * tl.trans(expand_ue8m0_scale(up_scale, ROWS_W, BK, GK))
|
| 551 |
+
),
|
| 552 |
+
axis=0,
|
| 553 |
+
)[None, :]
|
| 554 |
+
return acc_gate, acc_up
|
| 555 |
+
|
| 556 |
+
|
| 557 |
+
@triton.jit
|
| 558 |
+
def mx_compute_gate_up(
|
| 559 |
+
acc_gate,
|
| 560 |
+
acc_up,
|
| 561 |
+
a,
|
| 562 |
+
a_scale,
|
| 563 |
+
b_gate,
|
| 564 |
+
b_up,
|
| 565 |
+
gate_scale,
|
| 566 |
+
up_scale,
|
| 567 |
+
COMPUTE_MODE: tl.constexpr,
|
| 568 |
+
VALUES_PER_BYTE: tl.constexpr,
|
| 569 |
+
BLOCK_SIZE_M: tl.constexpr,
|
| 570 |
+
BLOCK_SIZE_N: tl.constexpr,
|
| 571 |
+
BLOCK_SIZE_K: tl.constexpr,
|
| 572 |
+
SCALE_GROUP_K: tl.constexpr,
|
| 573 |
+
):
|
| 574 |
+
"""Gate∪up MMA step dispatched on ``COMPUTE_MODE``: scaled-MMA on the raw weights
|
| 575 |
+
(``b_gate``/``b_up``), or fp8 ``tl.dot`` + per-group rescale / scalar reduce on the
|
| 576 |
+
E4M3-decoded weights. Returns the updated ``(acc_gate, acc_up)``."""
|
| 577 |
+
w_gate = mxfp4_e2m1_to_e4m3(b_gate) if VALUES_PER_BYTE == 2 else b_gate
|
| 578 |
+
w_up = mxfp4_e2m1_to_e4m3(b_up) if VALUES_PER_BYTE == 2 else b_up
|
| 579 |
+
if COMPUTE_MODE == "dot_scaled":
|
| 580 |
+
acc_gate = mx_dot_scaled(
|
| 581 |
+
a, a_scale, b_gate, gate_scale, acc_gate, VALUES_PER_BYTE
|
| 582 |
+
)
|
| 583 |
+
acc_up = mx_dot_scaled(a, a_scale, b_up, up_scale, acc_up, VALUES_PER_BYTE)
|
| 584 |
+
elif COMPUTE_MODE == "dot":
|
| 585 |
+
acc_gate, acc_up = mx_dot_rescale_gate_up(
|
| 586 |
+
acc_gate, acc_up, a, w_gate, w_up, a_scale, gate_scale, up_scale
|
| 587 |
+
)
|
| 588 |
+
else: # scalar
|
| 589 |
+
acc_gate, acc_up = mx_scalar_reduce_gate_up(
|
| 590 |
+
acc_gate,
|
| 591 |
+
acc_up,
|
| 592 |
+
a,
|
| 593 |
+
w_gate,
|
| 594 |
+
w_up,
|
| 595 |
+
a_scale,
|
| 596 |
+
gate_scale,
|
| 597 |
+
up_scale,
|
| 598 |
+
BLOCK_SIZE_M,
|
| 599 |
+
BLOCK_SIZE_N,
|
| 600 |
+
BLOCK_SIZE_K,
|
| 601 |
+
SCALE_GROUP_K,
|
| 602 |
+
)
|
| 603 |
+
return acc_gate, acc_up
|
| 604 |
+
|
| 605 |
+
|
| 606 |
+
@triton.jit
|
| 607 |
+
def mx_compute(
|
| 608 |
+
acc,
|
| 609 |
+
a,
|
| 610 |
+
a_scale,
|
| 611 |
+
w,
|
| 612 |
+
w_scale,
|
| 613 |
+
COMPUTE_MODE: tl.constexpr,
|
| 614 |
+
VALUES_PER_BYTE: tl.constexpr,
|
| 615 |
+
BLOCK_SIZE_M: tl.constexpr,
|
| 616 |
+
BLOCK_SIZE_N: tl.constexpr,
|
| 617 |
+
BLOCK_SIZE_K: tl.constexpr,
|
| 618 |
+
SCALE_GROUP_K: tl.constexpr,
|
| 619 |
+
):
|
| 620 |
+
"""Single-projection MMA step dispatched on ``COMPUTE_MODE``: scaled-MMA on the raw weight
|
| 621 |
+
(``w``), or fp8 ``tl.dot`` + per-group rescale / scalar reduce on the E4M3-decoded weight.
|
| 622 |
+
Returns the updated ``acc``."""
|
| 623 |
+
wq = mxfp4_e2m1_to_e4m3(w) if VALUES_PER_BYTE == 2 else w
|
| 624 |
+
if COMPUTE_MODE == "dot_scaled":
|
| 625 |
+
acc = mx_dot_scaled(a, a_scale, w, w_scale, acc, VALUES_PER_BYTE)
|
| 626 |
+
elif COMPUTE_MODE == "dot":
|
| 627 |
+
acc = mx_dot_rescale(acc, a, wq, a_scale, w_scale)
|
| 628 |
+
else: # scalar
|
| 629 |
+
acc = mx_scalar_reduce(
|
| 630 |
+
acc,
|
| 631 |
+
a,
|
| 632 |
+
a_scale,
|
| 633 |
+
wq,
|
| 634 |
+
w_scale,
|
| 635 |
+
BLOCK_SIZE_M,
|
| 636 |
+
BLOCK_SIZE_N,
|
| 637 |
+
BLOCK_SIZE_K,
|
| 638 |
+
SCALE_GROUP_K,
|
| 639 |
+
)
|
| 640 |
+
return acc
|
| 641 |
+
|
| 642 |
+
|
| 643 |
+
@triton.jit
|
| 644 |
+
def _activation(x, ACT_FN: tl.constexpr):
|
| 645 |
+
"""Pointwise GLU gate activation. ``ACT_FN`` in ``{"silu", "gelu", "relu"}``; gelu is exact
|
| 646 |
+
(erf), matching torch's default ``F.gelu``."""
|
| 647 |
+
if ACT_FN == "silu":
|
| 648 |
+
return x * tl.sigmoid(x)
|
| 649 |
+
elif ACT_FN == "gelu":
|
| 650 |
+
return 0.5 * x * (1.0 + tl.erf(x * 0.7071067811865476))
|
| 651 |
+
elif ACT_FN == "relu":
|
| 652 |
+
return tl.maximum(x, 0.0)
|
| 653 |
+
else:
|
| 654 |
+
tl.static_assert(
|
| 655 |
+
False, "unsupported ACT_FN; expected 'silu', 'gelu', or 'relu'"
|
| 656 |
+
)
|
| 657 |
+
|
| 658 |
+
|
| 659 |
+
@triton.jit
|
| 660 |
+
def glu(
|
| 661 |
+
acc_gate,
|
| 662 |
+
acc_up,
|
| 663 |
+
ACT_FN: tl.constexpr,
|
| 664 |
+
SIMULATE_UNFUSED: tl.constexpr,
|
| 665 |
+
OUT_DTYPE: tl.constexpr,
|
| 666 |
+
):
|
| 667 |
+
"""Gated linear unit: ``ACT_FN(gate) * up`` (SwiGLU / GeGLU / ReGLU). When ``SIMULATE_UNFUSED``,
|
| 668 |
+
round each op through ``OUT_DTYPE`` to match the numerics of the unfused (separate-kernel)
|
| 669 |
+
path, where every intermediate is materialized in that dtype."""
|
| 670 |
+
if SIMULATE_UNFUSED:
|
| 671 |
+
g = acc_gate.to(OUT_DTYPE).to(tl.float32)
|
| 672 |
+
u = acc_up.to(OUT_DTYPE).to(tl.float32)
|
| 673 |
+
act = _activation(g, ACT_FN).to(OUT_DTYPE).to(tl.float32)
|
| 674 |
+
return (act * u).to(OUT_DTYPE).to(tl.float32)
|
| 675 |
+
return _activation(acc_gate, ACT_FN) * acc_up
|
| 676 |
+
|
| 677 |
+
|
| 678 |
+
@triton.jit
|
| 679 |
+
def _e2m1_code_to_f32(code):
|
| 680 |
+
"""One E2M1 4-bit code -> fp32. Layout ``[sign | exp(2) | mant(1)]``; the 8
|
| 681 |
+
magnitudes are ``{0, .5, 1, 1.5, 2, 3, 4, 6}`` (exp==0 is the 0/0.5 subnormal)."""
|
| 682 |
+
code = code.to(tl.int32)
|
| 683 |
+
s = (code >> 3) & 1
|
| 684 |
+
e = (code >> 1) & 3
|
| 685 |
+
m = (code & 1).to(tl.float32)
|
| 686 |
+
pow2 = tl.exp2((e - 1).to(tl.float32)) # e in 0..3 -> 0.5, 1, 2, 4
|
| 687 |
+
mag = tl.where(e == 0, 0.5 * m, (1.0 + 0.5 * m) * pow2)
|
| 688 |
+
return (1.0 - 2.0 * s.to(tl.float32)) * mag
|
| 689 |
+
|
| 690 |
+
|
| 691 |
+
@triton.jit
|
| 692 |
+
def mxfp4_e2m1_to_e4m3(b_packed):
|
| 693 |
+
"""Unpack packed MXFP4 (E2M1, two nibbles/byte along K) to E4M3, doubling the K
|
| 694 |
+
(row) dim: ``(R, C) uint8 -> (2R, C) E4M3``. E2M1's 8 magnitudes are all exact in
|
| 695 |
+
E4M3, so this is lossless — it lets the FP8 ``tl.dot`` path stand in for
|
| 696 |
+
``tl.dot_scaled`` at decode (avoiding its M->128 pad). K order is the low nibble
|
| 697 |
+
first: ``[byte0_lo, byte0_hi, byte1_lo, ...]``."""
|
| 698 |
+
lo = _e2m1_code_to_f32(b_packed & 0xF)
|
| 699 |
+
hi = _e2m1_code_to_f32(b_packed >> 4)
|
| 700 |
+
# interleave along the K (row) dim via trans -> interleave-last-dim -> trans back
|
| 701 |
+
unpacked = tl.trans(tl.interleave(tl.trans(lo), tl.trans(hi)))
|
| 702 |
+
return unpacked.to(tl.float8e4nv)
|
| 703 |
+
|
| 704 |
+
|
| 705 |
+
# ── fp8_act_quant kernel (used by tensor-mode FP8 wrappers) ───────────────────
|
| 706 |
+
|
| 707 |
+
|
| 708 |
+
@triton.jit
|
| 709 |
+
def _fp8_act_quant_kernel(
|
| 710 |
+
x_ptr, y_ptr, s_ptr, BLOCK_SIZE: tl.constexpr, PADDED_BLOCK: tl.constexpr
|
| 711 |
+
):
|
| 712 |
+
# ``tl.arange`` needs a power-of-2 length, so iterate over PADDED_BLOCK (the next
|
| 713 |
+
# power of 2) and mask the tail — lets block_size be non-power-of-2 (e.g. a full
|
| 714 |
+
# row K=14336 in tensor-mode). Masked lanes load 0, which can't affect ``amax``.
|
| 715 |
+
pid = tl.program_id(axis=0)
|
| 716 |
+
cols = tl.arange(0, PADDED_BLOCK)
|
| 717 |
+
mask = cols < BLOCK_SIZE
|
| 718 |
+
offs = pid * BLOCK_SIZE + cols
|
| 719 |
+
x = tl.load(x_ptr + offs, mask=mask, other=0.0).to(tl.float32)
|
| 720 |
+
s = tl.max(tl.abs(x)) / 448.0 # float8_e4m3fn max
|
| 721 |
+
y = (x / tl.maximum(s, 1e-12)).to(y_ptr.dtype.element_ty)
|
| 722 |
+
tl.store(y_ptr + offs, y, mask=mask)
|
| 723 |
+
tl.store(s_ptr + pid, s)
|
| 724 |
+
|
| 725 |
+
|
| 726 |
+
@triton_op(add_op_namespace_prefix("fp8_act_quant"), mutates_args=())
|
| 727 |
+
def _fp8_act_quant(
|
| 728 |
+
x: torch.Tensor, block_size: int = 128
|
| 729 |
+
) -> tuple[torch.Tensor, torch.Tensor]:
|
| 730 |
+
assert x.is_contiguous()
|
| 731 |
+
assert x.shape[-1] % block_size == 0
|
| 732 |
+
y = torch.empty_like(x, dtype=FP8_DTYPE)
|
| 733 |
+
grid = (triton.cdiv(x.numel(), block_size),)
|
| 734 |
+
s = x.new_empty(*x.size()[:-1], x.size(-1) // block_size, dtype=torch.float32)
|
| 735 |
+
|
| 736 |
+
with device_context(x.device):
|
| 737 |
+
wrap_triton(_fp8_act_quant_kernel)[grid](
|
| 738 |
+
x,
|
| 739 |
+
y,
|
| 740 |
+
s,
|
| 741 |
+
BLOCK_SIZE=block_size,
|
| 742 |
+
PADDED_BLOCK=triton.next_power_of_2(block_size),
|
| 743 |
+
)
|
| 744 |
+
|
| 745 |
+
return y, s
|
| 746 |
+
|
| 747 |
+
|
| 748 |
+
def fp8_act_quant(
|
| 749 |
+
x: torch.Tensor, block_size: int = 128
|
| 750 |
+
) -> tuple[torch.Tensor, torch.Tensor]:
|
| 751 |
+
"""Quantize activations to FP8 with per-block dynamic scaling.
|
| 752 |
+
|
| 753 |
+
Splits the last dimension of ``x`` into blocks of ``block_size`` elements,
|
| 754 |
+
computes ``scale = max(|x_block|) / 448`` per block, and quantizes to
|
| 755 |
+
``float8_e4m3fn``.
|
| 756 |
+
|
| 757 |
+
Args:
|
| 758 |
+
x: Input tensor in bf16/fp16/fp32. Last dimension must be divisible by
|
| 759 |
+
``block_size`` and the tensor must be contiguous.
|
| 760 |
+
block_size: Number of elements per quantization block (default: 128).
|
| 761 |
+
|
| 762 |
+
Returns:
|
| 763 |
+
A tuple ``(quantized, scales)`` where ``quantized`` has dtype
|
| 764 |
+
``float8_e4m3fn`` with the same shape as ``x``, and ``scales`` has
|
| 765 |
+
shape ``(*x.shape[:-1], x.shape[-1] // block_size)`` in float32.
|
| 766 |
+
"""
|
| 767 |
+
return ops.fp8_act_quant(x, block_size)
|
build/torch-rocm/__init__.py
DELETED
|
@@ -1,32 +0,0 @@
|
|
| 1 |
-
from .act_quant import fp8_act_quant
|
| 2 |
-
from .batched import (
|
| 3 |
-
w8a8_fp8_matmul_batched,
|
| 4 |
-
w8a8_block_fp8_matmul_batched,
|
| 5 |
-
w8a8_tensor_fp8_matmul_batched,
|
| 6 |
-
)
|
| 7 |
-
from .grouped import (
|
| 8 |
-
w8a8_fp8_matmul_grouped,
|
| 9 |
-
w8a8_block_fp8_matmul_grouped,
|
| 10 |
-
w8a8_tensor_fp8_matmul_grouped,
|
| 11 |
-
)
|
| 12 |
-
from .matmul import (
|
| 13 |
-
w8a8_fp8_matmul,
|
| 14 |
-
w8a8_block_fp8_matmul,
|
| 15 |
-
w8a8_tensor_fp8_matmul,
|
| 16 |
-
)
|
| 17 |
-
|
| 18 |
-
__all__ = [
|
| 19 |
-
"fp8_act_quant",
|
| 20 |
-
# Single matmul
|
| 21 |
-
"w8a8_fp8_matmul",
|
| 22 |
-
"w8a8_block_fp8_matmul",
|
| 23 |
-
"w8a8_tensor_fp8_matmul",
|
| 24 |
-
# Batched matmul
|
| 25 |
-
"w8a8_fp8_matmul_batched",
|
| 26 |
-
"w8a8_block_fp8_matmul_batched",
|
| 27 |
-
"w8a8_tensor_fp8_matmul_batched",
|
| 28 |
-
# Grouped matmul
|
| 29 |
-
"w8a8_fp8_matmul_grouped",
|
| 30 |
-
"w8a8_block_fp8_matmul_grouped",
|
| 31 |
-
"w8a8_tensor_fp8_matmul_grouped",
|
| 32 |
-
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
build/torch-rocm/_ops.py
DELETED
|
@@ -1,38 +0,0 @@
|
|
| 1 |
-
import torch
|
| 2 |
-
|
| 3 |
-
def get_backend() -> str:
|
| 4 |
-
"""Detect the backend by inspecting torch."""
|
| 5 |
-
import torch
|
| 6 |
-
|
| 7 |
-
if hasattr(torch, "neuron"):
|
| 8 |
-
# Needs to be sorted before specific Torch builds, since Neuron
|
| 9 |
-
# extension can be loaded into e.g. CUDA Torch builds.
|
| 10 |
-
return "neuron"
|
| 11 |
-
elif torch.version.cuda is not None:
|
| 12 |
-
return "cuda"
|
| 13 |
-
elif torch.version.hip is not None:
|
| 14 |
-
return "rocm"
|
| 15 |
-
elif torch.backends.mps.is_available():
|
| 16 |
-
return "metal"
|
| 17 |
-
elif hasattr(torch.version, "xpu") and torch.version.xpu is not None:
|
| 18 |
-
return "xpu"
|
| 19 |
-
else:
|
| 20 |
-
return "cpu"
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
def _find_ops_name() -> str:
|
| 24 |
-
kernel_name = "finegrained_fp8"
|
| 25 |
-
unique_id = "bbc7e0f"
|
| 26 |
-
backend = get_backend()
|
| 27 |
-
return f"_{kernel_name}_{backend}_{unique_id}"
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
_OPS_NAME = _find_ops_name()
|
| 31 |
-
|
| 32 |
-
ops = getattr(torch.ops, _OPS_NAME)
|
| 33 |
-
|
| 34 |
-
def add_op_namespace_prefix(op_name: str) -> str:
|
| 35 |
-
"""
|
| 36 |
-
Prefix op by namespace.
|
| 37 |
-
"""
|
| 38 |
-
return f"{_OPS_NAME}::{op_name}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
build/torch-rocm/act_quant.py
DELETED
|
@@ -1,73 +0,0 @@
|
|
| 1 |
-
# Copyright 2026 The HuggingFace Inc. team. All rights reserved.
|
| 2 |
-
#
|
| 3 |
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
-
# you may not use this file except in compliance with the License.
|
| 5 |
-
# You may obtain a copy of the License at
|
| 6 |
-
#
|
| 7 |
-
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
-
#
|
| 9 |
-
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
-
# See the License for the specific language governing permissions and
|
| 13 |
-
# limitations under the License.
|
| 14 |
-
|
| 15 |
-
import torch
|
| 16 |
-
import triton
|
| 17 |
-
import triton.language as tl
|
| 18 |
-
from torch.library import triton_op, wrap_triton
|
| 19 |
-
|
| 20 |
-
from .utils import device_context
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
_FP8_DTYPE = torch.float8_e4m3fn
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
# Copied from https://huggingface.co/deepseek-ai/DeepSeek-V3/blob/main/inference/kernel.py
|
| 27 |
-
@triton.jit
|
| 28 |
-
def _fp8_act_quant_kernel(x_ptr, y_ptr, s_ptr, BLOCK_SIZE: tl.constexpr):
|
| 29 |
-
pid = tl.program_id(axis=0)
|
| 30 |
-
offs = pid * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE)
|
| 31 |
-
x = tl.load(x_ptr + offs).to(tl.float32)
|
| 32 |
-
s = tl.max(tl.abs(x)) / 448.0 # float8_e4m3fn max
|
| 33 |
-
y = (x / s).to(y_ptr.dtype.element_ty)
|
| 34 |
-
tl.store(y_ptr + offs, y)
|
| 35 |
-
tl.store(s_ptr + pid, s)
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
@triton_op("finegrained_fp8::fp8_act_quant", mutates_args=())
|
| 39 |
-
def _fp8_act_quant(
|
| 40 |
-
x: torch.Tensor, block_size: int = 128
|
| 41 |
-
) -> tuple[torch.Tensor, torch.Tensor]:
|
| 42 |
-
assert x.is_contiguous()
|
| 43 |
-
assert x.shape[-1] % block_size == 0
|
| 44 |
-
y = torch.empty_like(x, dtype=_FP8_DTYPE)
|
| 45 |
-
grid = (triton.cdiv(x.numel(), block_size),)
|
| 46 |
-
s = x.new_empty(*x.size()[:-1], x.size(-1) // block_size, dtype=torch.float32)
|
| 47 |
-
|
| 48 |
-
with device_context(x.device):
|
| 49 |
-
wrap_triton(_fp8_act_quant_kernel)[grid](x, y, s, BLOCK_SIZE=block_size)
|
| 50 |
-
|
| 51 |
-
return y, s
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
def fp8_act_quant(
|
| 55 |
-
x: torch.Tensor, block_size: int = 128
|
| 56 |
-
) -> tuple[torch.Tensor, torch.Tensor]:
|
| 57 |
-
"""Quantize activations to FP8 with per-block dynamic scaling.
|
| 58 |
-
|
| 59 |
-
Splits the last dimension of ``x`` into blocks of ``block_size`` elements,
|
| 60 |
-
computes ``scale = max(|x_block|) / 448`` per block, and quantizes to
|
| 61 |
-
``float8_e4m3fn``.
|
| 62 |
-
|
| 63 |
-
Args:
|
| 64 |
-
x: Input tensor in bf16/fp16/fp32. Last dimension must be divisible by
|
| 65 |
-
``block_size`` and the tensor must be contiguous.
|
| 66 |
-
block_size: Number of elements per quantization block (default: 128).
|
| 67 |
-
|
| 68 |
-
Returns:
|
| 69 |
-
A tuple ``(quantized, scales)`` where ``quantized`` has dtype
|
| 70 |
-
``float8_e4m3fn`` with the same shape as ``x``, and ``scales`` has
|
| 71 |
-
shape ``(*x.shape[:-1], x.shape[-1] // block_size)`` in float32.
|
| 72 |
-
"""
|
| 73 |
-
return torch.ops.finegrained_fp8.fp8_act_quant(x, block_size)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
build/torch-rocm/batched.py
DELETED
|
@@ -1,390 +0,0 @@
|
|
| 1 |
-
# Copyright 2026 The HuggingFace Inc. team. All rights reserved.
|
| 2 |
-
#
|
| 3 |
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
-
# you may not use this file except in compliance with the License.
|
| 5 |
-
# You may obtain a copy of the License at
|
| 6 |
-
#
|
| 7 |
-
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
-
#
|
| 9 |
-
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
-
# See the License for the specific language governing permissions and
|
| 13 |
-
# limitations under the License.
|
| 14 |
-
|
| 15 |
-
import torch
|
| 16 |
-
import triton
|
| 17 |
-
import triton.language as tl
|
| 18 |
-
from .act_quant import fp8_act_quant
|
| 19 |
-
from torch.library import triton_op, wrap_triton
|
| 20 |
-
|
| 21 |
-
from .utils import device_context
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
@triton.autotune(
|
| 25 |
-
configs=[
|
| 26 |
-
triton.Config({}, num_warps=w, num_stages=s)
|
| 27 |
-
for w in [2, 4, 8, 16]
|
| 28 |
-
for s in [2, 3, 4, 5]
|
| 29 |
-
],
|
| 30 |
-
key=["N", "K"],
|
| 31 |
-
)
|
| 32 |
-
@triton.jit
|
| 33 |
-
def w8a8_block_fp8_matmul_batched_kernel(
|
| 34 |
-
A, # (S, K) raw BF16/FP16 activations
|
| 35 |
-
B, # (E, N, K) FP8 weight matrices
|
| 36 |
-
C, # (S, N) output
|
| 37 |
-
Bs, # (E, N // BLOCK_SIZE_N, K // BLOCK_SIZE_K) weight scales
|
| 38 |
-
ExpertIds, # (S,) — which expert each batch element routes to
|
| 39 |
-
# Shape
|
| 40 |
-
S,
|
| 41 |
-
N,
|
| 42 |
-
K,
|
| 43 |
-
stride_am,
|
| 44 |
-
stride_ak,
|
| 45 |
-
stride_be,
|
| 46 |
-
stride_bk,
|
| 47 |
-
stride_bn,
|
| 48 |
-
stride_cm,
|
| 49 |
-
stride_cn,
|
| 50 |
-
stride_bs_e,
|
| 51 |
-
stride_bs_k,
|
| 52 |
-
stride_bs_n,
|
| 53 |
-
# Meta-parameters
|
| 54 |
-
BLOCK_SIZE_N: tl.constexpr,
|
| 55 |
-
BLOCK_SIZE_K: tl.constexpr,
|
| 56 |
-
BLOCK_SIZE_M: tl.constexpr,
|
| 57 |
-
):
|
| 58 |
-
"""Block-scale batched FP8 expert matmul kernel.
|
| 59 |
-
|
| 60 |
-
Each program handles one routed token row and one N-tile, looks up the
|
| 61 |
-
owning expert from ``ExpertIds``, and applies fused activation quantization.
|
| 62 |
-
"""
|
| 63 |
-
batch_id = tl.program_id(axis=0)
|
| 64 |
-
pid_n = tl.program_id(axis=1)
|
| 65 |
-
|
| 66 |
-
# Cast expert_id to int64 to prevent int32 overflow when computing
|
| 67 |
-
# expert_id * stride_Eb (e.g. 255 * 9_437_184 > 2^31 for 256 experts of
|
| 68 |
-
# 3072×3072 FP8 weights).
|
| 69 |
-
expert_id = tl.load(ExpertIds + batch_id).to(tl.int64)
|
| 70 |
-
|
| 71 |
-
A = A + batch_id * stride_am
|
| 72 |
-
B = B + expert_id * stride_be
|
| 73 |
-
C = C + batch_id * stride_cm
|
| 74 |
-
Bs = Bs + expert_id * stride_bs_e
|
| 75 |
-
|
| 76 |
-
offs_bn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
|
| 77 |
-
offs_k = tl.arange(0, BLOCK_SIZE_K)
|
| 78 |
-
a_ptrs = A + tl.arange(0, BLOCK_SIZE_M)[:, None] * 0 + offs_k[None, :] * stride_ak
|
| 79 |
-
b_ptrs = B + (offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn)
|
| 80 |
-
|
| 81 |
-
bs_ptrs = Bs + pid_n * stride_bs_n
|
| 82 |
-
|
| 83 |
-
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
| 84 |
-
for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
|
| 85 |
-
# ---- fused fp8_act_quant ----
|
| 86 |
-
a_raw = tl.load(a_ptrs).to(tl.float32)
|
| 87 |
-
a_s = tl.max(tl.abs(a_raw)) / 448.0
|
| 88 |
-
a = (a_raw / tl.maximum(a_s, 1e-12)).to(tl.float8e4nv)
|
| 89 |
-
# ---- matmul ----
|
| 90 |
-
b = tl.load(b_ptrs)
|
| 91 |
-
b_s = tl.load(bs_ptrs + k * stride_bs_k)
|
| 92 |
-
accumulator += tl.dot(a, b) * a_s * b_s[None, :]
|
| 93 |
-
a_ptrs += BLOCK_SIZE_K * stride_ak
|
| 94 |
-
b_ptrs += BLOCK_SIZE_K * stride_bk
|
| 95 |
-
|
| 96 |
-
if C.dtype.element_ty == tl.bfloat16:
|
| 97 |
-
c = accumulator.to(tl.bfloat16)
|
| 98 |
-
elif C.dtype.element_ty == tl.float16:
|
| 99 |
-
c = accumulator.to(tl.float16)
|
| 100 |
-
else:
|
| 101 |
-
c = accumulator.to(tl.float32)
|
| 102 |
-
|
| 103 |
-
offs_cm = tl.arange(0, BLOCK_SIZE_M)
|
| 104 |
-
offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
|
| 105 |
-
c_ptrs = C + offs_cm[:, None] * 0 + stride_cn * offs_cn[None, :]
|
| 106 |
-
tl.store(c_ptrs, c)
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
@triton.autotune(
|
| 110 |
-
configs=[
|
| 111 |
-
triton.Config({}, num_warps=w, num_stages=s)
|
| 112 |
-
for w in [2, 4, 8, 16]
|
| 113 |
-
for s in [2, 3, 4, 5]
|
| 114 |
-
],
|
| 115 |
-
key=["N", "K"],
|
| 116 |
-
)
|
| 117 |
-
@triton.jit
|
| 118 |
-
def w8a8_tensor_fp8_matmul_batched_kernel(
|
| 119 |
-
A, # (S, K) pre-quantized FP8 activations
|
| 120 |
-
B, # (E, N, K) FP8 weight matrices
|
| 121 |
-
C, # (S, N) output
|
| 122 |
-
As, # (S, 1) per-tensor activation scales
|
| 123 |
-
Bs, # (E, 1, 1) per-tensor weight scales
|
| 124 |
-
ExpertIds,
|
| 125 |
-
S,
|
| 126 |
-
N,
|
| 127 |
-
K,
|
| 128 |
-
stride_am,
|
| 129 |
-
stride_ak,
|
| 130 |
-
stride_be,
|
| 131 |
-
stride_bk,
|
| 132 |
-
stride_bn,
|
| 133 |
-
stride_cm,
|
| 134 |
-
stride_cn,
|
| 135 |
-
stride_as_m,
|
| 136 |
-
stride_bs_e,
|
| 137 |
-
BLOCK_SIZE_N: tl.constexpr,
|
| 138 |
-
BLOCK_SIZE_K: tl.constexpr,
|
| 139 |
-
BLOCK_SIZE_M: tl.constexpr,
|
| 140 |
-
):
|
| 141 |
-
"""Tensor-scale batched FP8 expert matmul kernel.
|
| 142 |
-
|
| 143 |
-
Activations are already quantized; the kernel applies per-token activation
|
| 144 |
-
scales and per-expert tensor weight scales.
|
| 145 |
-
"""
|
| 146 |
-
batch_id = tl.program_id(axis=0)
|
| 147 |
-
pid_n = tl.program_id(axis=1)
|
| 148 |
-
|
| 149 |
-
expert_id = tl.load(ExpertIds + batch_id).to(tl.int64)
|
| 150 |
-
|
| 151 |
-
A = A + batch_id * stride_am
|
| 152 |
-
B = B + expert_id * stride_be
|
| 153 |
-
C = C + batch_id * stride_cm
|
| 154 |
-
Bs = Bs + expert_id * stride_bs_e
|
| 155 |
-
|
| 156 |
-
offs_bn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
|
| 157 |
-
offs_k = tl.arange(0, BLOCK_SIZE_K)
|
| 158 |
-
a_ptrs = A + tl.arange(0, BLOCK_SIZE_M)[:, None] * 0 + offs_k[None, :] * stride_ak
|
| 159 |
-
b_ptrs = B + (offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn)
|
| 160 |
-
|
| 161 |
-
b_s = tl.load(Bs)
|
| 162 |
-
a_s = tl.load(As + batch_id * stride_as_m)
|
| 163 |
-
|
| 164 |
-
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
| 165 |
-
for _ in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
|
| 166 |
-
a = tl.load(a_ptrs)
|
| 167 |
-
b = tl.load(b_ptrs)
|
| 168 |
-
accumulator += tl.dot(a, b)
|
| 169 |
-
a_ptrs += BLOCK_SIZE_K * stride_ak
|
| 170 |
-
b_ptrs += BLOCK_SIZE_K * stride_bk
|
| 171 |
-
|
| 172 |
-
accumulator = accumulator * a_s * b_s
|
| 173 |
-
|
| 174 |
-
if C.dtype.element_ty == tl.bfloat16:
|
| 175 |
-
c = accumulator.to(tl.bfloat16)
|
| 176 |
-
elif C.dtype.element_ty == tl.float16:
|
| 177 |
-
c = accumulator.to(tl.float16)
|
| 178 |
-
else:
|
| 179 |
-
c = accumulator.to(tl.float32)
|
| 180 |
-
|
| 181 |
-
offs_cm = tl.arange(0, BLOCK_SIZE_M)
|
| 182 |
-
offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
|
| 183 |
-
c_ptrs = C + offs_cm[:, None] * 0 + stride_cn * offs_cn[None, :]
|
| 184 |
-
tl.store(c_ptrs, c)
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
@triton_op("finegrained_fp8::w8a8_block_fp8_matmul_batched", mutates_args=())
|
| 188 |
-
def _w8a8_block_fp8_matmul_batched(
|
| 189 |
-
A: torch.Tensor,
|
| 190 |
-
B: torch.Tensor,
|
| 191 |
-
Bs: torch.Tensor,
|
| 192 |
-
expert_ids: torch.Tensor,
|
| 193 |
-
block_size: list[int],
|
| 194 |
-
) -> torch.Tensor:
|
| 195 |
-
"""Block-scale batched FP8 matmul: C[s] = A[s] @ B[expert_ids[s]].T, with fused act quant.
|
| 196 |
-
|
| 197 |
-
A: (S, K) raw bf16/fp16 activations
|
| 198 |
-
B: (E, N, K) FP8 expert weights
|
| 199 |
-
Bs: (E, N // block_n, K // block_k) per-block weight scales
|
| 200 |
-
"""
|
| 201 |
-
assert A.ndim == 2, f"A must be 2D (S, K), got ndim={A.ndim}"
|
| 202 |
-
assert A.is_contiguous(), "A must be contiguous"
|
| 203 |
-
assert B.ndim == 3, f"B must be 3D (E, N, K), got ndim={B.ndim}"
|
| 204 |
-
assert B.is_contiguous(), "B must be contiguous"
|
| 205 |
-
assert A.shape[1] == B.shape[2], (
|
| 206 |
-
f"K mismatch: A has K={A.shape[1]}, B has K={B.shape[2]}"
|
| 207 |
-
)
|
| 208 |
-
|
| 209 |
-
S, K = A.shape
|
| 210 |
-
E, N, _ = B.shape
|
| 211 |
-
|
| 212 |
-
assert len(block_size) == 2, (
|
| 213 |
-
f"block_size must be [block_n, block_k], got {block_size}"
|
| 214 |
-
)
|
| 215 |
-
block_n, block_k = block_size[0], block_size[1]
|
| 216 |
-
# MoE expert dimensions must be block-aligned; non-aligned N/K is not supported.
|
| 217 |
-
assert N % block_n == 0, f"N ({N}) must be divisible by block_n ({block_n})"
|
| 218 |
-
assert K % block_k == 0, f"K ({K}) must be divisible by block_k ({block_k})"
|
| 219 |
-
assert Bs.ndim == 3, (
|
| 220 |
-
f"Bs must be 3D (E, N//block_n, K//block_k), got ndim={Bs.ndim}"
|
| 221 |
-
)
|
| 222 |
-
assert Bs.shape == (E, N // block_n, K // block_k), (
|
| 223 |
-
f"Bs shape {tuple(Bs.shape)} != expected ({E}, {N // block_n}, {K // block_k})"
|
| 224 |
-
)
|
| 225 |
-
|
| 226 |
-
C = A.new_empty(S, N)
|
| 227 |
-
# Adaptive BLOCK_SIZE_M: smallest power-of-2 >= M, floored at 16, capped at 128.
|
| 228 |
-
# Matches the WGMMA tile to the actual row count — smaller tiles use less
|
| 229 |
-
# register pressure and a better-matched FP8 WGMMA instruction, improving
|
| 230 |
-
# both accuracy and performance for small M (decode).
|
| 231 |
-
BLOCK_SIZE_M = min(max(triton.next_power_of_2((S + E - 1) // E), 16), 128)
|
| 232 |
-
grid = (S, triton.cdiv(N, block_n))
|
| 233 |
-
with device_context(A.device):
|
| 234 |
-
wrap_triton(w8a8_block_fp8_matmul_batched_kernel)[grid](
|
| 235 |
-
A,
|
| 236 |
-
B,
|
| 237 |
-
C,
|
| 238 |
-
Bs,
|
| 239 |
-
expert_ids,
|
| 240 |
-
S,
|
| 241 |
-
N,
|
| 242 |
-
K,
|
| 243 |
-
A.stride(0),
|
| 244 |
-
A.stride(1),
|
| 245 |
-
B.stride(0),
|
| 246 |
-
B.stride(2),
|
| 247 |
-
B.stride(1),
|
| 248 |
-
C.stride(0),
|
| 249 |
-
C.stride(1),
|
| 250 |
-
Bs.stride(0),
|
| 251 |
-
Bs.stride(2),
|
| 252 |
-
Bs.stride(1),
|
| 253 |
-
BLOCK_SIZE_N=block_n,
|
| 254 |
-
BLOCK_SIZE_K=block_k,
|
| 255 |
-
BLOCK_SIZE_M=BLOCK_SIZE_M,
|
| 256 |
-
)
|
| 257 |
-
|
| 258 |
-
return C
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
@triton_op("finegrained_fp8::w8a8_tensor_fp8_matmul_batched", mutates_args=())
|
| 262 |
-
def _w8a8_tensor_fp8_matmul_batched(
|
| 263 |
-
A: torch.Tensor,
|
| 264 |
-
B: torch.Tensor,
|
| 265 |
-
Bs: torch.Tensor,
|
| 266 |
-
expert_ids: torch.Tensor,
|
| 267 |
-
) -> torch.Tensor:
|
| 268 |
-
"""Tensor-scale batched FP8 matmul: C[s] = A[s] @ B[expert_ids[s]].T, with fused act quant.
|
| 269 |
-
|
| 270 |
-
A: (S, K) raw bf16/fp16 activations
|
| 271 |
-
B: (E, N, K) FP8 expert weights
|
| 272 |
-
Bs: (E,) or (E, 1, 1) per-expert weight scales
|
| 273 |
-
"""
|
| 274 |
-
assert A.ndim == 2, f"A must be 2D (S, K), got ndim={A.ndim}"
|
| 275 |
-
assert A.is_contiguous(), "A must be contiguous"
|
| 276 |
-
assert B.ndim == 3, f"B must be 3D (E, N, K), got ndim={B.ndim}"
|
| 277 |
-
assert B.is_contiguous(), "B must be contiguous"
|
| 278 |
-
assert A.shape[1] == B.shape[2], (
|
| 279 |
-
f"K mismatch: A has K={A.shape[1]}, B has K={B.shape[2]}"
|
| 280 |
-
)
|
| 281 |
-
|
| 282 |
-
S, K = A.shape
|
| 283 |
-
E, N, _ = B.shape
|
| 284 |
-
|
| 285 |
-
# Normalize Bs to (E, 1, 1)
|
| 286 |
-
if Bs.ndim == 1:
|
| 287 |
-
assert Bs.shape[0] == E, f"Bs shape {tuple(Bs.shape)} != expected ({E},)"
|
| 288 |
-
Bs = Bs.reshape(E, 1, 1)
|
| 289 |
-
else:
|
| 290 |
-
assert Bs.shape == (E, 1, 1), (
|
| 291 |
-
f"Bs shape {tuple(Bs.shape)} != expected ({E}, 1, 1)"
|
| 292 |
-
)
|
| 293 |
-
|
| 294 |
-
BLOCK_SIZE_N = 128
|
| 295 |
-
BLOCK_SIZE_K = 128
|
| 296 |
-
C = A.new_empty(S, N)
|
| 297 |
-
qA, As = fp8_act_quant(A, K)
|
| 298 |
-
grid = (S, triton.cdiv(N, BLOCK_SIZE_N))
|
| 299 |
-
# Adaptive BLOCK_SIZE_M: smallest power-of-2 >= M, floored at 16, capped at 128.
|
| 300 |
-
# Matches the WGMMA tile to the actual row count — smaller tiles use less
|
| 301 |
-
# register pressure and a better-matched FP8 WGMMA instruction, improving
|
| 302 |
-
# both accuracy and performance for small M (decode).
|
| 303 |
-
BLOCK_SIZE_M = min(max(triton.next_power_of_2((S + E - 1) // E), 16), 128)
|
| 304 |
-
grid = (S, triton.cdiv(N, BLOCK_SIZE_N))
|
| 305 |
-
with device_context(A.device):
|
| 306 |
-
wrap_triton(w8a8_tensor_fp8_matmul_batched_kernel)[grid](
|
| 307 |
-
qA,
|
| 308 |
-
B,
|
| 309 |
-
C,
|
| 310 |
-
As,
|
| 311 |
-
Bs,
|
| 312 |
-
expert_ids,
|
| 313 |
-
S,
|
| 314 |
-
N,
|
| 315 |
-
K,
|
| 316 |
-
qA.stride(0),
|
| 317 |
-
qA.stride(1),
|
| 318 |
-
B.stride(0),
|
| 319 |
-
B.stride(2),
|
| 320 |
-
B.stride(1),
|
| 321 |
-
C.stride(0),
|
| 322 |
-
C.stride(1),
|
| 323 |
-
As.stride(0),
|
| 324 |
-
Bs.stride(0),
|
| 325 |
-
BLOCK_SIZE_N=BLOCK_SIZE_N,
|
| 326 |
-
BLOCK_SIZE_K=BLOCK_SIZE_K,
|
| 327 |
-
BLOCK_SIZE_M=BLOCK_SIZE_M,
|
| 328 |
-
)
|
| 329 |
-
|
| 330 |
-
return C
|
| 331 |
-
|
| 332 |
-
|
| 333 |
-
def w8a8_block_fp8_matmul_batched(
|
| 334 |
-
A: torch.Tensor,
|
| 335 |
-
B: torch.Tensor,
|
| 336 |
-
Bs: torch.Tensor,
|
| 337 |
-
expert_ids: torch.Tensor,
|
| 338 |
-
block_size: list[int],
|
| 339 |
-
) -> torch.Tensor:
|
| 340 |
-
"""Block-scale batched FP8 matmul with fused activation quantization.
|
| 341 |
-
|
| 342 |
-
A: (S, K) raw activations, bf16/fp16/fp32
|
| 343 |
-
B: (E, N, K) FP8 expert weights
|
| 344 |
-
Bs: (E, N // block_n, K // block_k) per-block weight scales
|
| 345 |
-
"""
|
| 346 |
-
return torch.ops.finegrained_fp8.w8a8_block_fp8_matmul_batched(
|
| 347 |
-
A, B, Bs, expert_ids, block_size
|
| 348 |
-
)
|
| 349 |
-
|
| 350 |
-
|
| 351 |
-
def w8a8_tensor_fp8_matmul_batched(
|
| 352 |
-
A: torch.Tensor,
|
| 353 |
-
B: torch.Tensor,
|
| 354 |
-
Bs: torch.Tensor,
|
| 355 |
-
expert_ids: torch.Tensor,
|
| 356 |
-
) -> torch.Tensor:
|
| 357 |
-
"""Tensor-scale batched FP8 matmul with fused activation quantization.
|
| 358 |
-
|
| 359 |
-
A: (S, K) raw activations, bf16/fp16/fp32
|
| 360 |
-
B: (E, N, K) FP8 expert weights
|
| 361 |
-
Bs: (E,) or (E, 1, 1) per-expert weight scales
|
| 362 |
-
"""
|
| 363 |
-
return torch.ops.finegrained_fp8.w8a8_tensor_fp8_matmul_batched(
|
| 364 |
-
A, B, Bs, expert_ids
|
| 365 |
-
)
|
| 366 |
-
|
| 367 |
-
|
| 368 |
-
def w8a8_fp8_matmul_batched(
|
| 369 |
-
A: torch.Tensor,
|
| 370 |
-
B: torch.Tensor,
|
| 371 |
-
Bs: torch.Tensor,
|
| 372 |
-
expert_ids: torch.Tensor,
|
| 373 |
-
block_size: list[int] | None,
|
| 374 |
-
) -> torch.Tensor:
|
| 375 |
-
"""Unified batched W8A8 FP8 matmul dispatcher.
|
| 376 |
-
|
| 377 |
-
Dispatch rules:
|
| 378 |
-
- tensor mode when ``block_size is None``
|
| 379 |
-
- tensor mode when ``block_size == [N, K]``
|
| 380 |
-
- otherwise block mode
|
| 381 |
-
|
| 382 |
-
Returns:
|
| 383 |
-
Output tensor ``[S, N]`` in the same dtype as ``A``.
|
| 384 |
-
"""
|
| 385 |
-
if block_size is None or (
|
| 386 |
-
block_size[0] == B.size(1) and block_size[1] == B.size(2)
|
| 387 |
-
):
|
| 388 |
-
return w8a8_tensor_fp8_matmul_batched(A, B, Bs, expert_ids)
|
| 389 |
-
|
| 390 |
-
return w8a8_block_fp8_matmul_batched(A, B, Bs, expert_ids, block_size)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
build/torch-rocm/finegrained_fp8/__init__.py
DELETED
|
@@ -1,26 +0,0 @@
|
|
| 1 |
-
import ctypes
|
| 2 |
-
import importlib.util
|
| 3 |
-
import sys
|
| 4 |
-
from pathlib import Path
|
| 5 |
-
from types import ModuleType
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
def _import_from_path(file_path: Path) -> ModuleType:
|
| 9 |
-
# We cannot use the module name as-is, after adding it to `sys.modules`,
|
| 10 |
-
# it would also be used for other imports. So, we make a module name that
|
| 11 |
-
# depends on the path for it to be unique using the hex-encoded hash of
|
| 12 |
-
# the path.
|
| 13 |
-
path_hash = "{:x}".format(ctypes.c_size_t(hash(file_path.absolute())).value)
|
| 14 |
-
module_name = path_hash
|
| 15 |
-
spec = importlib.util.spec_from_file_location(module_name, file_path)
|
| 16 |
-
if spec is None:
|
| 17 |
-
raise ImportError(f"Cannot load spec for {module_name} from {file_path}")
|
| 18 |
-
module = importlib.util.module_from_spec(spec)
|
| 19 |
-
if module is None:
|
| 20 |
-
raise ImportError(f"Cannot load module {module_name} from spec")
|
| 21 |
-
sys.modules[module_name] = module
|
| 22 |
-
spec.loader.exec_module(module) # type: ignore
|
| 23 |
-
return module
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
globals().update(vars(_import_from_path(Path(__file__).parent.parent / "__init__.py")))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
build/torch-rocm/grouped.py
DELETED
|
@@ -1,477 +0,0 @@
|
|
| 1 |
-
# Copyright 2026 The HuggingFace Inc. team. All rights reserved.
|
| 2 |
-
#
|
| 3 |
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
-
# you may not use this file except in compliance with the License.
|
| 5 |
-
# You may obtain a copy of the License at
|
| 6 |
-
#
|
| 7 |
-
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
-
#
|
| 9 |
-
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
-
# See the License for the specific language governing permissions and
|
| 13 |
-
# limitations under the License.
|
| 14 |
-
|
| 15 |
-
import torch
|
| 16 |
-
import triton
|
| 17 |
-
import triton.language as tl
|
| 18 |
-
from .act_quant import fp8_act_quant
|
| 19 |
-
from torch.library import triton_op, wrap_triton
|
| 20 |
-
|
| 21 |
-
from .utils import device_context
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
@triton.autotune(
|
| 25 |
-
configs=[
|
| 26 |
-
triton.Config({}, num_warps=w, num_stages=s)
|
| 27 |
-
for w in [2, 4, 8, 16]
|
| 28 |
-
for s in [2, 3, 4, 5]
|
| 29 |
-
],
|
| 30 |
-
key=["N", "K", "BLOCK_SIZE_M"],
|
| 31 |
-
)
|
| 32 |
-
@triton.jit
|
| 33 |
-
def w8a8_block_fp8_matmul_grouped_kernel(
|
| 34 |
-
A, # (S, K) raw BF16/FP16 activations, sorted/grouped by expert id
|
| 35 |
-
B, # (E, N, K) FP8 weight matrices
|
| 36 |
-
C, # (S, N) output
|
| 37 |
-
Bs, # (E, N // BLOCK_SIZE_N, K // BLOCK_SIZE_K) weight scales
|
| 38 |
-
Offsets, # (E,) int32 — cumulative row-end per expert
|
| 39 |
-
TileOffsets, # (E,) int32 — cumulative tile-end per expert
|
| 40 |
-
# Shape
|
| 41 |
-
S,
|
| 42 |
-
N,
|
| 43 |
-
K,
|
| 44 |
-
# Strides
|
| 45 |
-
stride_am,
|
| 46 |
-
stride_ak,
|
| 47 |
-
stride_be,
|
| 48 |
-
stride_bk,
|
| 49 |
-
stride_bn,
|
| 50 |
-
stride_cm,
|
| 51 |
-
stride_cn,
|
| 52 |
-
stride_bs_e,
|
| 53 |
-
stride_bs_k,
|
| 54 |
-
stride_bs_n,
|
| 55 |
-
# Meta-parameters
|
| 56 |
-
NUM_EXPERTS: tl.constexpr,
|
| 57 |
-
BLOCK_SIZE_N: tl.constexpr,
|
| 58 |
-
BLOCK_SIZE_K: tl.constexpr,
|
| 59 |
-
BLOCK_SIZE_M: tl.constexpr,
|
| 60 |
-
NUM_EXPERTS_BIT_LENGTH: tl.constexpr,
|
| 61 |
-
):
|
| 62 |
-
"""Block-scale grouped FP8 expert matmul kernel.
|
| 63 |
-
|
| 64 |
-
Tokens are assumed sorted by expert. The kernel maps each M-tile to its
|
| 65 |
-
owning expert via ``TileOffsets`` and applies fused activation quantization.
|
| 66 |
-
"""
|
| 67 |
-
pid_m = tl.program_id(axis=0)
|
| 68 |
-
pid_n = tl.program_id(axis=1)
|
| 69 |
-
|
| 70 |
-
# Exit early for programs beyond the actual tile count.
|
| 71 |
-
total_tiles = tl.load(TileOffsets + NUM_EXPERTS - 1)
|
| 72 |
-
if pid_m >= total_tiles:
|
| 73 |
-
return
|
| 74 |
-
|
| 75 |
-
# Binary search in TileOffsets to find the owning expert.
|
| 76 |
-
# Finds the smallest e such that TileOffsets[e] > pid_m (upper_bound semantics),
|
| 77 |
-
# which is the expert whose tile range contains pid_m.
|
| 78 |
-
# O(log2(NUM_EXPERTS)) loads instead of the O(NUM_EXPERTS) linear scan.
|
| 79 |
-
# NUM_EXPERTS_BIT_LENGTH is ceil(log2(E))+1 for powers-of-two, giving one
|
| 80 |
-
# harmless extra iteration when lo==hi; it's a compile-time constant so the
|
| 81 |
-
# loop is fully unrolled by the compiler.
|
| 82 |
-
lo = 0
|
| 83 |
-
hi = NUM_EXPERTS
|
| 84 |
-
for _ in tl.static_range(NUM_EXPERTS_BIT_LENGTH):
|
| 85 |
-
mid = (lo + hi) >> 1
|
| 86 |
-
mid_val = tl.load(TileOffsets + mid)
|
| 87 |
-
is_left = mid_val <= pid_m
|
| 88 |
-
lo = tl.where(is_left, mid + 1, lo)
|
| 89 |
-
hi = tl.where(is_left, hi, mid)
|
| 90 |
-
|
| 91 |
-
# Cast expert_id to int64 to prevent int32 overflow when computing
|
| 92 |
-
# expert_id * stride_be (e.g. 255 * 9_437_184 > 2^31 for 256 experts of
|
| 93 |
-
# 3072×3072 FP8 weights).
|
| 94 |
-
expert_id = lo.to(tl.int64)
|
| 95 |
-
|
| 96 |
-
prev_eid = tl.maximum(expert_id - 1, 0)
|
| 97 |
-
expert_start = tl.where(expert_id == 0, 0, tl.load(Offsets + prev_eid))
|
| 98 |
-
expert_end = tl.load(Offsets + expert_id)
|
| 99 |
-
M_expert = expert_end - expert_start
|
| 100 |
-
|
| 101 |
-
expert_tile_start = tl.where(expert_id == 0, 0, tl.load(TileOffsets + prev_eid))
|
| 102 |
-
local_tile = pid_m - expert_tile_start
|
| 103 |
-
m_off = local_tile * BLOCK_SIZE_M
|
| 104 |
-
|
| 105 |
-
offs_am = m_off + tl.arange(0, BLOCK_SIZE_M)
|
| 106 |
-
row_mask = offs_am < M_expert
|
| 107 |
-
offs_global_m = expert_start + offs_am
|
| 108 |
-
|
| 109 |
-
offs_bn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
|
| 110 |
-
offs_k = tl.arange(0, BLOCK_SIZE_K)
|
| 111 |
-
|
| 112 |
-
a_ptrs = A + offs_global_m[:, None] * stride_am + offs_k[None, :] * stride_ak
|
| 113 |
-
b_ptrs = (
|
| 114 |
-
B
|
| 115 |
-
+ expert_id * stride_be
|
| 116 |
-
+ offs_k[:, None] * stride_bk
|
| 117 |
-
+ offs_bn[None, :] * stride_bn
|
| 118 |
-
)
|
| 119 |
-
bs_ptrs = Bs + expert_id * stride_bs_e + pid_n * stride_bs_n
|
| 120 |
-
|
| 121 |
-
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
| 122 |
-
for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
|
| 123 |
-
# ---- fused fp8_act_quant ----
|
| 124 |
-
a_raw = tl.load(a_ptrs, mask=row_mask[:, None], other=0.0).to(tl.float32)
|
| 125 |
-
a_s = tl.max(tl.abs(a_raw), axis=1) / 448.0
|
| 126 |
-
a = (a_raw / tl.maximum(a_s[:, None], 1e-12)).to(tl.float8e4nv)
|
| 127 |
-
# ---- matmul ----
|
| 128 |
-
b = tl.load(b_ptrs)
|
| 129 |
-
b_s = tl.load(bs_ptrs + k * stride_bs_k)
|
| 130 |
-
accumulator += tl.dot(a, b) * a_s[:, None] * b_s[None, :]
|
| 131 |
-
a_ptrs += BLOCK_SIZE_K * stride_ak
|
| 132 |
-
b_ptrs += BLOCK_SIZE_K * stride_bk
|
| 133 |
-
|
| 134 |
-
if C.dtype.element_ty == tl.bfloat16:
|
| 135 |
-
c = accumulator.to(tl.bfloat16)
|
| 136 |
-
elif C.dtype.element_ty == tl.float16:
|
| 137 |
-
c = accumulator.to(tl.float16)
|
| 138 |
-
else:
|
| 139 |
-
c = accumulator.to(tl.float32)
|
| 140 |
-
|
| 141 |
-
c_ptrs = C + stride_cm * offs_global_m[:, None] + stride_cn * offs_bn[None, :]
|
| 142 |
-
c_mask = row_mask[:, None]
|
| 143 |
-
tl.store(c_ptrs, c, mask=c_mask)
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
@triton.autotune(
|
| 147 |
-
configs=[
|
| 148 |
-
triton.Config({}, num_warps=w, num_stages=s)
|
| 149 |
-
for w in [2, 4, 8, 16]
|
| 150 |
-
for s in [2, 3, 4, 5]
|
| 151 |
-
],
|
| 152 |
-
key=["N", "K", "BLOCK_SIZE_M"],
|
| 153 |
-
)
|
| 154 |
-
@triton.jit
|
| 155 |
-
def w8a8_tensor_fp8_matmul_grouped_kernel(
|
| 156 |
-
A, # (S, K) raw BF16/FP16 activations, sorted/grouped by expert idc
|
| 157 |
-
B, # (E, N, K) FP8 weight matrices
|
| 158 |
-
C, # (S, N) output
|
| 159 |
-
As, # (S, 1) activation scales
|
| 160 |
-
Bs, # (E, 1, 1) per-tensor weight scales
|
| 161 |
-
Offsets,
|
| 162 |
-
TileOffsets,
|
| 163 |
-
S,
|
| 164 |
-
N,
|
| 165 |
-
K,
|
| 166 |
-
stride_am,
|
| 167 |
-
stride_ak,
|
| 168 |
-
stride_be,
|
| 169 |
-
stride_bk,
|
| 170 |
-
stride_bn,
|
| 171 |
-
stride_cm,
|
| 172 |
-
stride_cn,
|
| 173 |
-
stride_as_m,
|
| 174 |
-
stride_bs_e,
|
| 175 |
-
NUM_EXPERTS: tl.constexpr,
|
| 176 |
-
BLOCK_SIZE_N: tl.constexpr,
|
| 177 |
-
BLOCK_SIZE_K: tl.constexpr,
|
| 178 |
-
BLOCK_SIZE_M: tl.constexpr,
|
| 179 |
-
NUM_EXPERTS_BIT_LENGTH: tl.constexpr,
|
| 180 |
-
):
|
| 181 |
-
"""Tensor-scale grouped FP8 expert matmul kernel.
|
| 182 |
-
|
| 183 |
-
Uses grouped expert scheduling with pre-quantized activations plus
|
| 184 |
-
per-token activation scales and per-expert tensor weight scales.
|
| 185 |
-
"""
|
| 186 |
-
pid_m = tl.program_id(axis=0)
|
| 187 |
-
pid_n = tl.program_id(axis=1)
|
| 188 |
-
|
| 189 |
-
total_tiles = tl.load(TileOffsets + NUM_EXPERTS - 1)
|
| 190 |
-
if pid_m >= total_tiles:
|
| 191 |
-
return
|
| 192 |
-
|
| 193 |
-
lo = 0
|
| 194 |
-
hi = NUM_EXPERTS
|
| 195 |
-
for _ in tl.static_range(NUM_EXPERTS_BIT_LENGTH):
|
| 196 |
-
mid = (lo + hi) >> 1
|
| 197 |
-
mid_val = tl.load(TileOffsets + mid)
|
| 198 |
-
is_left = mid_val <= pid_m
|
| 199 |
-
lo = tl.where(is_left, mid + 1, lo)
|
| 200 |
-
hi = tl.where(is_left, hi, mid)
|
| 201 |
-
expert_id = lo.to(tl.int64)
|
| 202 |
-
|
| 203 |
-
prev_eid = tl.maximum(expert_id - 1, 0)
|
| 204 |
-
expert_start = tl.where(expert_id == 0, 0, tl.load(Offsets + prev_eid))
|
| 205 |
-
expert_end = tl.load(Offsets + expert_id)
|
| 206 |
-
M_expert = expert_end - expert_start
|
| 207 |
-
|
| 208 |
-
expert_tile_start = tl.where(expert_id == 0, 0, tl.load(TileOffsets + prev_eid))
|
| 209 |
-
local_tile = pid_m - expert_tile_start
|
| 210 |
-
m_off = local_tile * BLOCK_SIZE_M
|
| 211 |
-
|
| 212 |
-
offs_am = m_off + tl.arange(0, BLOCK_SIZE_M)
|
| 213 |
-
row_mask = offs_am < M_expert
|
| 214 |
-
offs_global_m = expert_start + offs_am
|
| 215 |
-
|
| 216 |
-
offs_bn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
|
| 217 |
-
offs_k = tl.arange(0, BLOCK_SIZE_K)
|
| 218 |
-
|
| 219 |
-
a_ptrs = A + offs_global_m[:, None] * stride_am + offs_k[None, :] * stride_ak
|
| 220 |
-
b_ptrs = (
|
| 221 |
-
B
|
| 222 |
-
+ expert_id * stride_be
|
| 223 |
-
+ offs_k[:, None] * stride_bk
|
| 224 |
-
+ offs_bn[None, :] * stride_bn
|
| 225 |
-
)
|
| 226 |
-
|
| 227 |
-
a_s = tl.load(As + offs_global_m * stride_as_m, mask=row_mask, other=0.0)
|
| 228 |
-
b_s = tl.load(Bs + expert_id * stride_bs_e)
|
| 229 |
-
|
| 230 |
-
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
| 231 |
-
for _ in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
|
| 232 |
-
a = tl.load(a_ptrs, mask=row_mask[:, None], other=0.0)
|
| 233 |
-
b = tl.load(b_ptrs)
|
| 234 |
-
|
| 235 |
-
accumulator += tl.dot(a, b)
|
| 236 |
-
a_ptrs += BLOCK_SIZE_K * stride_ak
|
| 237 |
-
b_ptrs += BLOCK_SIZE_K * stride_bk
|
| 238 |
-
|
| 239 |
-
accumulator = accumulator * a_s[:, None] * b_s
|
| 240 |
-
|
| 241 |
-
if C.dtype.element_ty == tl.bfloat16:
|
| 242 |
-
c = accumulator.to(tl.bfloat16)
|
| 243 |
-
elif C.dtype.element_ty == tl.float16:
|
| 244 |
-
c = accumulator.to(tl.float16)
|
| 245 |
-
else:
|
| 246 |
-
c = accumulator.to(tl.float32)
|
| 247 |
-
|
| 248 |
-
c_ptrs = C + stride_cm * offs_global_m[:, None] + stride_cn * offs_bn[None, :]
|
| 249 |
-
c_mask = row_mask[:, None]
|
| 250 |
-
tl.store(c_ptrs, c, mask=c_mask)
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
@triton_op("finegrained_fp8::w8a8_block_fp8_matmul_grouped", mutates_args=())
|
| 254 |
-
def _w8a8_block_fp8_matmul_grouped(
|
| 255 |
-
A: torch.Tensor,
|
| 256 |
-
B: torch.Tensor,
|
| 257 |
-
Bs: torch.Tensor,
|
| 258 |
-
offsets: torch.Tensor,
|
| 259 |
-
tokens_per_expert: torch.Tensor,
|
| 260 |
-
block_size: list[int],
|
| 261 |
-
) -> torch.Tensor:
|
| 262 |
-
"""Block-scale grouped FP8 matmul: C = A @ B.T per expert, with fused act quant.
|
| 263 |
-
|
| 264 |
-
A: (S, K) raw bf16/fp16 activations, sorted by expert
|
| 265 |
-
B: (E, N, K) FP8 expert weights
|
| 266 |
-
Bs: (E, N // block_n, K // block_k) per-block weight scales
|
| 267 |
-
"""
|
| 268 |
-
assert A.ndim == 2, f"A must be 2D (S, K), got ndim={A.ndim}"
|
| 269 |
-
assert A.is_contiguous(), "A must be contiguous"
|
| 270 |
-
assert B.ndim == 3, f"B must be 3D (E, N, K), got ndim={B.ndim}"
|
| 271 |
-
assert B.is_contiguous(), "B must be contiguous"
|
| 272 |
-
assert A.shape[1] == B.shape[2], (
|
| 273 |
-
f"K mismatch: A has K={A.shape[1]}, B has K={B.shape[2]}"
|
| 274 |
-
)
|
| 275 |
-
|
| 276 |
-
S, K = A.shape
|
| 277 |
-
E, N, _ = B.shape
|
| 278 |
-
|
| 279 |
-
assert len(block_size) == 2, (
|
| 280 |
-
f"block_size must be [block_n, block_k], got {block_size}"
|
| 281 |
-
)
|
| 282 |
-
block_n, block_k = block_size[0], block_size[1]
|
| 283 |
-
# MoE expert dimensions must be block-aligned; non-aligned N/K is not supported.
|
| 284 |
-
assert N % block_n == 0, f"N ({N}) must be divisible by block_n ({block_n})"
|
| 285 |
-
assert K % block_k == 0, f"K ({K}) must be divisible by block_k ({block_k})"
|
| 286 |
-
assert Bs.ndim == 3, (
|
| 287 |
-
f"Bs must be 3D (E, N//block_n, K//block_k), got ndim={Bs.ndim}"
|
| 288 |
-
)
|
| 289 |
-
assert Bs.shape == (E, N // block_n, K // block_k), (
|
| 290 |
-
f"Bs shape {tuple(Bs.shape)} != expected ({E}, {N // block_n}, {K // block_k})"
|
| 291 |
-
)
|
| 292 |
-
|
| 293 |
-
C = A.new_empty(S, N)
|
| 294 |
-
# Adaptive BLOCK_SIZE_M: match tile to average tokens per expert.
|
| 295 |
-
BLOCK_SIZE_M = min(max(triton.next_power_of_2((S + E - 1) // E), 16), 128)
|
| 296 |
-
tiles_per_expert = (tokens_per_expert + BLOCK_SIZE_M - 1) // BLOCK_SIZE_M
|
| 297 |
-
tile_offsets = torch.cumsum(tiles_per_expert, dim=0).to(torch.int32)
|
| 298 |
-
# Upper bound on M-tiles: sum_e ceil(M_e / BLOCK_M) <= ceil(S / BLOCK_M) + E.
|
| 299 |
-
# Programs beyond the real tile count exit immediately via the early-return
|
| 300 |
-
# guard inside the kernel. This is faster than syncing for the exact count
|
| 301 |
-
# and keeps the grid size data-independent (cuda-graph / torch.compile safe).
|
| 302 |
-
max_M_tiles = triton.cdiv(S, BLOCK_SIZE_M) + E
|
| 303 |
-
grid = (max_M_tiles, triton.cdiv(N, block_n))
|
| 304 |
-
with device_context(A.device):
|
| 305 |
-
wrap_triton(w8a8_block_fp8_matmul_grouped_kernel)[grid](
|
| 306 |
-
A,
|
| 307 |
-
B,
|
| 308 |
-
C,
|
| 309 |
-
Bs,
|
| 310 |
-
offsets,
|
| 311 |
-
tile_offsets,
|
| 312 |
-
S,
|
| 313 |
-
N,
|
| 314 |
-
K,
|
| 315 |
-
A.stride(0),
|
| 316 |
-
A.stride(1),
|
| 317 |
-
B.stride(0),
|
| 318 |
-
B.stride(2),
|
| 319 |
-
B.stride(1),
|
| 320 |
-
C.stride(0),
|
| 321 |
-
C.stride(1),
|
| 322 |
-
Bs.stride(0),
|
| 323 |
-
Bs.stride(2),
|
| 324 |
-
Bs.stride(1),
|
| 325 |
-
# Meta-parameters
|
| 326 |
-
NUM_EXPERTS=E,
|
| 327 |
-
BLOCK_SIZE_N=block_n,
|
| 328 |
-
BLOCK_SIZE_K=block_k,
|
| 329 |
-
BLOCK_SIZE_M=BLOCK_SIZE_M,
|
| 330 |
-
NUM_EXPERTS_BIT_LENGTH=E.bit_length(),
|
| 331 |
-
)
|
| 332 |
-
|
| 333 |
-
return C
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
@triton_op("finegrained_fp8::w8a8_tensor_fp8_matmul_grouped", mutates_args=())
|
| 337 |
-
def _w8a8_tensor_fp8_matmul_grouped(
|
| 338 |
-
A: torch.Tensor,
|
| 339 |
-
B: torch.Tensor,
|
| 340 |
-
Bs: torch.Tensor,
|
| 341 |
-
offsets: torch.Tensor,
|
| 342 |
-
tokens_per_expert: torch.Tensor,
|
| 343 |
-
) -> torch.Tensor:
|
| 344 |
-
"""Tensor-scale grouped FP8 matmul: C = A @ B.T per expert, with fused act quant.
|
| 345 |
-
|
| 346 |
-
A: (S, K) raw bf16/fp16 activations, sorted by expert
|
| 347 |
-
B: (E, N, K) FP8 expert weights
|
| 348 |
-
Bs: (E,) or (E, 1, 1) per-expert weight scales
|
| 349 |
-
"""
|
| 350 |
-
assert A.ndim == 2, f"A must be 2D (S, K), got ndim={A.ndim}"
|
| 351 |
-
assert A.is_contiguous(), "A must be contiguous"
|
| 352 |
-
assert B.ndim == 3, f"B must be 3D (E, N, K), got ndim={B.ndim}"
|
| 353 |
-
assert B.is_contiguous(), "B must be contiguous"
|
| 354 |
-
assert A.shape[1] == B.shape[2], (
|
| 355 |
-
f"K mismatch: A has K={A.shape[1]}, B has K={B.shape[2]}"
|
| 356 |
-
)
|
| 357 |
-
|
| 358 |
-
S, K = A.shape
|
| 359 |
-
E, N, _ = B.shape
|
| 360 |
-
|
| 361 |
-
# Normalize Bs to (E, 1, 1)
|
| 362 |
-
if Bs.ndim == 1:
|
| 363 |
-
assert Bs.shape[0] == E, f"Bs shape {tuple(Bs.shape)} != expected ({E},)"
|
| 364 |
-
Bs = Bs.reshape(E, 1, 1)
|
| 365 |
-
else:
|
| 366 |
-
assert Bs.shape == (E, 1, 1), (
|
| 367 |
-
f"Bs shape {tuple(Bs.shape)} != expected ({E}, 1, 1)"
|
| 368 |
-
)
|
| 369 |
-
|
| 370 |
-
BLOCK_SIZE_N = 128
|
| 371 |
-
BLOCK_SIZE_K = 128
|
| 372 |
-
C = A.new_empty(S, N)
|
| 373 |
-
qA, As = fp8_act_quant(A, K)
|
| 374 |
-
BLOCK_SIZE_M = min(max(triton.next_power_of_2((S + E - 1) // E), 16), 128)
|
| 375 |
-
tiles_per_expert = (tokens_per_expert + BLOCK_SIZE_M - 1) // BLOCK_SIZE_M
|
| 376 |
-
tile_offsets = torch.cumsum(tiles_per_expert, dim=0).to(torch.int32)
|
| 377 |
-
# Upper bound on M-tiles: sum_e ceil(M_e / BLOCK_M) <= ceil(S / BLOCK_M) + E.
|
| 378 |
-
# Programs beyond the real tile count exit immediately via the early-return
|
| 379 |
-
# guard inside the kernel. This is faster than syncing for the exact count
|
| 380 |
-
# and keeps the grid size data-independent (cuda-graph / torch.compile safe).
|
| 381 |
-
max_M_tiles = triton.cdiv(S, BLOCK_SIZE_M) + E
|
| 382 |
-
grid = (max_M_tiles, triton.cdiv(N, BLOCK_SIZE_N))
|
| 383 |
-
with device_context(A.device):
|
| 384 |
-
wrap_triton(w8a8_tensor_fp8_matmul_grouped_kernel)[grid](
|
| 385 |
-
qA,
|
| 386 |
-
B,
|
| 387 |
-
C,
|
| 388 |
-
As,
|
| 389 |
-
Bs,
|
| 390 |
-
offsets,
|
| 391 |
-
tile_offsets,
|
| 392 |
-
S,
|
| 393 |
-
N,
|
| 394 |
-
K,
|
| 395 |
-
qA.stride(0),
|
| 396 |
-
qA.stride(1),
|
| 397 |
-
B.stride(0),
|
| 398 |
-
B.stride(2),
|
| 399 |
-
B.stride(1),
|
| 400 |
-
C.stride(0),
|
| 401 |
-
C.stride(1),
|
| 402 |
-
As.stride(0),
|
| 403 |
-
Bs.stride(0),
|
| 404 |
-
# Meta-parameters
|
| 405 |
-
NUM_EXPERTS=E,
|
| 406 |
-
BLOCK_SIZE_N=BLOCK_SIZE_N,
|
| 407 |
-
BLOCK_SIZE_K=BLOCK_SIZE_K,
|
| 408 |
-
BLOCK_SIZE_M=BLOCK_SIZE_M,
|
| 409 |
-
NUM_EXPERTS_BIT_LENGTH=E.bit_length(),
|
| 410 |
-
)
|
| 411 |
-
|
| 412 |
-
return C
|
| 413 |
-
|
| 414 |
-
|
| 415 |
-
def w8a8_block_fp8_matmul_grouped(
|
| 416 |
-
A: torch.Tensor,
|
| 417 |
-
B: torch.Tensor,
|
| 418 |
-
Bs: torch.Tensor,
|
| 419 |
-
offsets: torch.Tensor,
|
| 420 |
-
tokens_per_expert: torch.Tensor,
|
| 421 |
-
block_size: list[int],
|
| 422 |
-
) -> torch.Tensor:
|
| 423 |
-
"""Block-scale grouped FP8 matmul with fused activation quantization.
|
| 424 |
-
|
| 425 |
-
A: (S, K) raw activations sorted by expert, bf16/fp16/fp32
|
| 426 |
-
B: (E, N, K) FP8 expert weights
|
| 427 |
-
Bs: (E, N // block_n, K // block_k) per-block weight scales
|
| 428 |
-
"""
|
| 429 |
-
return torch.ops.finegrained_fp8.w8a8_block_fp8_matmul_grouped(
|
| 430 |
-
A, B, Bs, offsets, tokens_per_expert, block_size
|
| 431 |
-
)
|
| 432 |
-
|
| 433 |
-
|
| 434 |
-
def w8a8_tensor_fp8_matmul_grouped(
|
| 435 |
-
A: torch.Tensor,
|
| 436 |
-
B: torch.Tensor,
|
| 437 |
-
Bs: torch.Tensor,
|
| 438 |
-
offsets: torch.Tensor,
|
| 439 |
-
tokens_per_expert: torch.Tensor,
|
| 440 |
-
) -> torch.Tensor:
|
| 441 |
-
"""Tensor-scale grouped FP8 matmul with fused activation quantization.
|
| 442 |
-
|
| 443 |
-
A: (S, K) raw activations sorted by expert, bf16/fp16/fp32
|
| 444 |
-
B: (E, N, K) FP8 expert weights
|
| 445 |
-
Bs: (E,) or (E, 1, 1) per-expert weight scales
|
| 446 |
-
"""
|
| 447 |
-
return torch.ops.finegrained_fp8.w8a8_tensor_fp8_matmul_grouped(
|
| 448 |
-
A, B, Bs, offsets, tokens_per_expert
|
| 449 |
-
)
|
| 450 |
-
|
| 451 |
-
|
| 452 |
-
def w8a8_fp8_matmul_grouped(
|
| 453 |
-
A: torch.Tensor,
|
| 454 |
-
B: torch.Tensor,
|
| 455 |
-
Bs: torch.Tensor,
|
| 456 |
-
offsets: torch.Tensor,
|
| 457 |
-
tokens_per_expert: torch.Tensor,
|
| 458 |
-
block_size: list[int] | None,
|
| 459 |
-
) -> torch.Tensor:
|
| 460 |
-
"""Unified grouped W8A8 FP8 matmul dispatcher.
|
| 461 |
-
|
| 462 |
-
Dispatch rules:
|
| 463 |
-
- tensor mode when ``block_size is None``
|
| 464 |
-
- tensor mode when ``block_size == [N, K]``
|
| 465 |
-
- otherwise block mode
|
| 466 |
-
|
| 467 |
-
Returns:
|
| 468 |
-
Output tensor ``[S, N]`` in the same dtype as ``A``, in expert-sorted order.
|
| 469 |
-
"""
|
| 470 |
-
if block_size is None or (
|
| 471 |
-
block_size[0] == B.size(1) and block_size[1] == B.size(2)
|
| 472 |
-
):
|
| 473 |
-
return w8a8_tensor_fp8_matmul_grouped(A, B, Bs, offsets, tokens_per_expert)
|
| 474 |
-
|
| 475 |
-
return w8a8_block_fp8_matmul_grouped(
|
| 476 |
-
A, B, Bs, offsets, tokens_per_expert, block_size
|
| 477 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
build/torch-rocm/matmul.py
DELETED
|
@@ -1,411 +0,0 @@
|
|
| 1 |
-
# Copyright 2025 The HuggingFace Inc. team. All rights reserved.
|
| 2 |
-
#
|
| 3 |
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
-
# you may not use this file except in compliance with the License.
|
| 5 |
-
# You may obtain a copy of the License at
|
| 6 |
-
#
|
| 7 |
-
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
-
#
|
| 9 |
-
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
-
# See the License for the specific language governing permissions and
|
| 13 |
-
# limitations under the License.
|
| 14 |
-
|
| 15 |
-
import torch
|
| 16 |
-
import triton
|
| 17 |
-
import triton.language as tl
|
| 18 |
-
from torch.library import triton_op, wrap_triton
|
| 19 |
-
|
| 20 |
-
from .utils import device_context
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
# Adapted from https://github.com/sgl-project/sglang/blob/main/python/sglang/srt/layers/quantization/fp8_kernel.py
|
| 24 |
-
@triton.autotune(
|
| 25 |
-
configs=[
|
| 26 |
-
triton.Config({}, num_warps=w, num_stages=s)
|
| 27 |
-
for w in [2, 4, 8, 16]
|
| 28 |
-
for s in [2, 3, 4]
|
| 29 |
-
],
|
| 30 |
-
key=["N", "K", "BLOCK_SIZE_M"],
|
| 31 |
-
)
|
| 32 |
-
@triton.jit
|
| 33 |
-
def w8a8_block_fp8_matmul_kernel(
|
| 34 |
-
# Pointers to inputs and output
|
| 35 |
-
A,
|
| 36 |
-
B,
|
| 37 |
-
C,
|
| 38 |
-
As,
|
| 39 |
-
Bs,
|
| 40 |
-
# Shape for matmul
|
| 41 |
-
M,
|
| 42 |
-
N,
|
| 43 |
-
K,
|
| 44 |
-
stride_am,
|
| 45 |
-
stride_ak,
|
| 46 |
-
stride_bk,
|
| 47 |
-
stride_bn,
|
| 48 |
-
stride_cm,
|
| 49 |
-
stride_cn,
|
| 50 |
-
stride_as_m,
|
| 51 |
-
stride_as_k,
|
| 52 |
-
stride_bs_k,
|
| 53 |
-
stride_bs_n,
|
| 54 |
-
# Meta-parameters
|
| 55 |
-
BLOCK_SIZE_M: tl.constexpr,
|
| 56 |
-
BLOCK_SIZE_N: tl.constexpr,
|
| 57 |
-
BLOCK_SIZE_K: tl.constexpr,
|
| 58 |
-
GROUP_SIZE_M: tl.constexpr,
|
| 59 |
-
):
|
| 60 |
-
"""Block-scale FP8 GEMM kernel.
|
| 61 |
-
|
| 62 |
-
Computes ``C = A @ B.T`` with block-wise activation/weight scales.
|
| 63 |
-
Uses a 2D grid with swizzle for L2 cache locality on B tiles.
|
| 64 |
-
"""
|
| 65 |
-
pid_m = tl.program_id(axis=0)
|
| 66 |
-
pid_n = tl.program_id(axis=1)
|
| 67 |
-
num_pid_m = tl.cdiv(M, BLOCK_SIZE_M)
|
| 68 |
-
num_pid_n = tl.cdiv(N, BLOCK_SIZE_N)
|
| 69 |
-
pid_m, pid_n = tl.swizzle2d(pid_m, pid_n, num_pid_m, num_pid_n, GROUP_SIZE_M)
|
| 70 |
-
|
| 71 |
-
offs_am = (pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)) % M
|
| 72 |
-
offs_bn = (pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)) % N
|
| 73 |
-
offs_k = tl.arange(0, BLOCK_SIZE_K)
|
| 74 |
-
a_ptrs = A + (offs_am[:, None] * stride_am + offs_k[None, :] * stride_ak)
|
| 75 |
-
b_ptrs = B + (offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn)
|
| 76 |
-
|
| 77 |
-
as_ptrs = As + offs_am * stride_as_m
|
| 78 |
-
offs_bsn = offs_bn // BLOCK_SIZE_N
|
| 79 |
-
bs_ptrs = Bs + offs_bsn * stride_bs_n
|
| 80 |
-
|
| 81 |
-
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
| 82 |
-
for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
|
| 83 |
-
k_remaining = K - k * BLOCK_SIZE_K
|
| 84 |
-
a = tl.load(a_ptrs, mask=offs_k[None, :] < k_remaining, other=0.0)
|
| 85 |
-
b = tl.load(b_ptrs, mask=offs_k[:, None] < k_remaining, other=0.0)
|
| 86 |
-
|
| 87 |
-
a_s = tl.load(as_ptrs + k * stride_as_k)
|
| 88 |
-
b_s = tl.load(bs_ptrs + k * stride_bs_k)
|
| 89 |
-
|
| 90 |
-
accumulator += tl.dot(a, b) * a_s[:, None] * b_s[None, :]
|
| 91 |
-
a_ptrs += BLOCK_SIZE_K * stride_ak
|
| 92 |
-
b_ptrs += BLOCK_SIZE_K * stride_bk
|
| 93 |
-
|
| 94 |
-
if C.dtype.element_ty == tl.bfloat16:
|
| 95 |
-
c = accumulator.to(tl.bfloat16)
|
| 96 |
-
elif C.dtype.element_ty == tl.float16:
|
| 97 |
-
c = accumulator.to(tl.float16)
|
| 98 |
-
else:
|
| 99 |
-
c = accumulator.to(tl.float32)
|
| 100 |
-
|
| 101 |
-
offs_cm = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
|
| 102 |
-
offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
|
| 103 |
-
c_ptrs = C + stride_cm * offs_cm[:, None] + stride_cn * offs_cn[None, :]
|
| 104 |
-
c_mask = (offs_cm[:, None] < M) & (offs_cn[None, :] < N)
|
| 105 |
-
tl.store(c_ptrs, c, mask=c_mask)
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
@triton.autotune(
|
| 109 |
-
configs=[
|
| 110 |
-
triton.Config({}, num_warps=w, num_stages=s)
|
| 111 |
-
for w in [2, 4, 8, 16]
|
| 112 |
-
for s in [2, 3, 4]
|
| 113 |
-
],
|
| 114 |
-
key=["N", "K", "BLOCK_SIZE_M"],
|
| 115 |
-
)
|
| 116 |
-
@triton.jit
|
| 117 |
-
def w8a8_tensor_fp8_matmul_kernel(
|
| 118 |
-
A,
|
| 119 |
-
B,
|
| 120 |
-
C,
|
| 121 |
-
As,
|
| 122 |
-
Bs,
|
| 123 |
-
M,
|
| 124 |
-
N,
|
| 125 |
-
K,
|
| 126 |
-
stride_am,
|
| 127 |
-
stride_ak,
|
| 128 |
-
stride_bk,
|
| 129 |
-
stride_bn,
|
| 130 |
-
stride_cm,
|
| 131 |
-
stride_cn,
|
| 132 |
-
stride_as_m,
|
| 133 |
-
BLOCK_SIZE_M: tl.constexpr,
|
| 134 |
-
BLOCK_SIZE_N: tl.constexpr,
|
| 135 |
-
BLOCK_SIZE_K: tl.constexpr,
|
| 136 |
-
GROUP_SIZE_M: tl.constexpr,
|
| 137 |
-
):
|
| 138 |
-
"""Tensor-scale FP8 GEMM kernel.
|
| 139 |
-
|
| 140 |
-
Computes ``C = A @ B.T`` with one activation scale per row and one
|
| 141 |
-
weight scale for the full matrix.
|
| 142 |
-
Uses a 2D grid with swizzle for L2 cache locality on B tiles.
|
| 143 |
-
"""
|
| 144 |
-
pid_m = tl.program_id(axis=0)
|
| 145 |
-
pid_n = tl.program_id(axis=1)
|
| 146 |
-
num_pid_m = tl.cdiv(M, BLOCK_SIZE_M)
|
| 147 |
-
num_pid_n = tl.cdiv(N, BLOCK_SIZE_N)
|
| 148 |
-
pid_m, pid_n = tl.swizzle2d(pid_m, pid_n, num_pid_m, num_pid_n, GROUP_SIZE_M)
|
| 149 |
-
|
| 150 |
-
offs_am = (pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)) % M
|
| 151 |
-
offs_bn = (pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)) % N
|
| 152 |
-
offs_k = tl.arange(0, BLOCK_SIZE_K)
|
| 153 |
-
|
| 154 |
-
a_ptrs = A + offs_am[:, None] * stride_am + offs_k[None, :] * stride_ak
|
| 155 |
-
b_ptrs = B + offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn
|
| 156 |
-
|
| 157 |
-
a_s = tl.load(As + offs_am * stride_as_m)
|
| 158 |
-
b_s = tl.load(Bs)
|
| 159 |
-
|
| 160 |
-
# Accumulate raw dot products, apply scales once after the loop.
|
| 161 |
-
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
| 162 |
-
for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
|
| 163 |
-
k_remaining = K - k * BLOCK_SIZE_K
|
| 164 |
-
a = tl.load(a_ptrs, mask=offs_k[None, :] < k_remaining, other=0.0)
|
| 165 |
-
b = tl.load(b_ptrs, mask=offs_k[:, None] < k_remaining, other=0.0)
|
| 166 |
-
accumulator += tl.dot(a, b)
|
| 167 |
-
a_ptrs += BLOCK_SIZE_K * stride_ak
|
| 168 |
-
b_ptrs += BLOCK_SIZE_K * stride_bk
|
| 169 |
-
|
| 170 |
-
accumulator = accumulator * a_s[:, None] * b_s
|
| 171 |
-
|
| 172 |
-
if C.dtype.element_ty == tl.bfloat16:
|
| 173 |
-
c = accumulator.to(tl.bfloat16)
|
| 174 |
-
elif C.dtype.element_ty == tl.float16:
|
| 175 |
-
c = accumulator.to(tl.float16)
|
| 176 |
-
else:
|
| 177 |
-
c = accumulator.to(tl.float32)
|
| 178 |
-
|
| 179 |
-
offs_cm = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
|
| 180 |
-
offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
|
| 181 |
-
c_ptrs = C + stride_cm * offs_cm[:, None] + stride_cn * offs_cn[None, :]
|
| 182 |
-
c_mask = (offs_cm[:, None] < M) & (offs_cn[None, :] < N)
|
| 183 |
-
tl.store(c_ptrs, c, mask=c_mask)
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
@triton_op("finegrained_fp8::w8a8_block_fp8_matmul", mutates_args=())
|
| 187 |
-
def _w8a8_block_fp8_matmul(
|
| 188 |
-
A: torch.Tensor,
|
| 189 |
-
B: torch.Tensor,
|
| 190 |
-
As: torch.Tensor,
|
| 191 |
-
Bs: torch.Tensor,
|
| 192 |
-
block_size: list[int],
|
| 193 |
-
output_dtype: torch.dtype = torch.float32,
|
| 194 |
-
) -> torch.Tensor:
|
| 195 |
-
"""Block-scale FP8 matmul: C = A @ B.T with per-block scales.
|
| 196 |
-
|
| 197 |
-
As: (M, K // block_k) — per-token-group activation scales
|
| 198 |
-
Bs: (N // block_n, K // block_k) — per-block weight scales
|
| 199 |
-
"""
|
| 200 |
-
assert len(block_size) == 2, (
|
| 201 |
-
f"block_size must be [block_n, block_k], got {block_size}"
|
| 202 |
-
)
|
| 203 |
-
block_n, block_k = block_size[0], block_size[1]
|
| 204 |
-
|
| 205 |
-
assert A.shape[-1] == B.shape[-1], (
|
| 206 |
-
f"K mismatch: A has K={A.shape[-1]}, B has K={B.shape[-1]}"
|
| 207 |
-
)
|
| 208 |
-
assert A.is_contiguous(), "A must be contiguous"
|
| 209 |
-
assert B.ndim == 2, f"B must be 2D (N, K), got ndim={B.ndim}"
|
| 210 |
-
assert B.is_contiguous(), "B must be contiguous"
|
| 211 |
-
|
| 212 |
-
N, K = B.shape
|
| 213 |
-
M = A.numel() // A.shape[-1]
|
| 214 |
-
|
| 215 |
-
assert As.ndim >= 2, f"As must be at least 2D, got ndim={As.ndim}"
|
| 216 |
-
assert As.shape[-1] == triton.cdiv(K, block_k), (
|
| 217 |
-
f"As last dim {As.shape[-1]} != expected {triton.cdiv(K, block_k)} (cdiv(K={K}, block_k={block_k}))"
|
| 218 |
-
)
|
| 219 |
-
assert Bs.ndim == 2, f"Bs must be 2D (N//block_n, K//block_k), got ndim={Bs.ndim}"
|
| 220 |
-
assert Bs.shape == (triton.cdiv(N, block_n), triton.cdiv(K, block_k)), (
|
| 221 |
-
f"Bs shape {tuple(Bs.shape)} != expected ({triton.cdiv(N, block_n)}, {triton.cdiv(K, block_k)})"
|
| 222 |
-
)
|
| 223 |
-
|
| 224 |
-
BLOCK_SIZE_K = block_k
|
| 225 |
-
BLOCK_SIZE_N = block_n
|
| 226 |
-
C_shape = A.shape[:-1] + (N,)
|
| 227 |
-
C = A.new_empty(C_shape, dtype=output_dtype)
|
| 228 |
-
# Adaptive BLOCK_SIZE_M: smallest power-of-2 >= M, floored at 16, capped at 128.
|
| 229 |
-
# Matches the WGMMA tile to the actual row count — smaller tiles use less
|
| 230 |
-
# register pressure and a better-matched FP8 WGMMA instruction, improving
|
| 231 |
-
# both accuracy and performance for small M (decode).
|
| 232 |
-
BLOCK_SIZE_M = min(max(triton.next_power_of_2(M), 16), 128)
|
| 233 |
-
grid = (triton.cdiv(M, BLOCK_SIZE_M), triton.cdiv(N, BLOCK_SIZE_N))
|
| 234 |
-
with device_context(A.device):
|
| 235 |
-
wrap_triton(w8a8_block_fp8_matmul_kernel)[grid](
|
| 236 |
-
A,
|
| 237 |
-
B,
|
| 238 |
-
C,
|
| 239 |
-
As,
|
| 240 |
-
Bs,
|
| 241 |
-
M,
|
| 242 |
-
N,
|
| 243 |
-
K,
|
| 244 |
-
A.stride(-2),
|
| 245 |
-
A.stride(-1),
|
| 246 |
-
B.stride(1),
|
| 247 |
-
B.stride(0),
|
| 248 |
-
C.stride(-2),
|
| 249 |
-
C.stride(-1),
|
| 250 |
-
As.stride(-2),
|
| 251 |
-
As.stride(-1),
|
| 252 |
-
Bs.stride(1),
|
| 253 |
-
Bs.stride(0),
|
| 254 |
-
# Meta-parameters
|
| 255 |
-
BLOCK_SIZE_M=BLOCK_SIZE_M,
|
| 256 |
-
BLOCK_SIZE_N=BLOCK_SIZE_N,
|
| 257 |
-
BLOCK_SIZE_K=BLOCK_SIZE_K,
|
| 258 |
-
GROUP_SIZE_M=8,
|
| 259 |
-
)
|
| 260 |
-
|
| 261 |
-
return C
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
@triton_op("finegrained_fp8::w8a8_tensor_fp8_matmul", mutates_args=())
|
| 265 |
-
def _w8a8_tensor_fp8_matmul(
|
| 266 |
-
A: torch.Tensor,
|
| 267 |
-
B: torch.Tensor,
|
| 268 |
-
As: torch.Tensor,
|
| 269 |
-
Bs: torch.Tensor,
|
| 270 |
-
output_dtype: torch.dtype = torch.float32,
|
| 271 |
-
) -> torch.Tensor:
|
| 272 |
-
"""Tensor-scale FP8 matmul: C = A @ B.T with per-row / per-tensor scales.
|
| 273 |
-
|
| 274 |
-
As: scalar, (M,), or (M, 1) — per-row activation scales
|
| 275 |
-
Bs: scalar, (1,), or (1, 1) — single weight scale
|
| 276 |
-
"""
|
| 277 |
-
assert A.shape[-1] == B.shape[-1], (
|
| 278 |
-
f"K mismatch: A has K={A.shape[-1]}, B has K={B.shape[-1]}"
|
| 279 |
-
)
|
| 280 |
-
assert A.is_contiguous(), "A must be contiguous"
|
| 281 |
-
assert B.ndim == 2, f"B must be 2D (N, K), got ndim={B.ndim}"
|
| 282 |
-
assert B.is_contiguous(), "B must be contiguous"
|
| 283 |
-
|
| 284 |
-
N, K = B.shape
|
| 285 |
-
M = A.numel() // A.shape[-1]
|
| 286 |
-
|
| 287 |
-
# Normalize As to (M,)
|
| 288 |
-
if As.numel() == 1:
|
| 289 |
-
As = As.reshape(1).expand(M).contiguous()
|
| 290 |
-
elif As.ndim == 2:
|
| 291 |
-
As = As.reshape(M)
|
| 292 |
-
assert As.ndim == 1 and As.shape[0] == M, (
|
| 293 |
-
f"As must be scalar, (M,), or (M,1) with M={M}, got {tuple(As.shape)}"
|
| 294 |
-
)
|
| 295 |
-
|
| 296 |
-
# Normalize Bs to (1,)
|
| 297 |
-
assert Bs.numel() == 1, f"Bs must be scalar or (1,), got {tuple(Bs.shape)}"
|
| 298 |
-
Bs = Bs.reshape(1)
|
| 299 |
-
|
| 300 |
-
BLOCK_SIZE_N = 128
|
| 301 |
-
BLOCK_SIZE_K = 128
|
| 302 |
-
C_shape = A.shape[:-1] + (N,)
|
| 303 |
-
C = A.new_empty(C_shape, dtype=output_dtype)
|
| 304 |
-
BLOCK_SIZE_M = min(max(triton.next_power_of_2(M), 16), 128)
|
| 305 |
-
grid = (triton.cdiv(M, BLOCK_SIZE_M), triton.cdiv(N, BLOCK_SIZE_N))
|
| 306 |
-
with device_context(A.device):
|
| 307 |
-
wrap_triton(w8a8_tensor_fp8_matmul_kernel)[grid](
|
| 308 |
-
A,
|
| 309 |
-
B,
|
| 310 |
-
C,
|
| 311 |
-
As,
|
| 312 |
-
Bs,
|
| 313 |
-
M,
|
| 314 |
-
N,
|
| 315 |
-
K,
|
| 316 |
-
A.stride(-2),
|
| 317 |
-
A.stride(-1),
|
| 318 |
-
B.stride(1),
|
| 319 |
-
B.stride(0),
|
| 320 |
-
C.stride(-2),
|
| 321 |
-
C.stride(-1),
|
| 322 |
-
As.stride(0),
|
| 323 |
-
# Meta-parameters
|
| 324 |
-
BLOCK_SIZE_M=BLOCK_SIZE_M,
|
| 325 |
-
BLOCK_SIZE_N=BLOCK_SIZE_N,
|
| 326 |
-
BLOCK_SIZE_K=BLOCK_SIZE_K,
|
| 327 |
-
GROUP_SIZE_M=8,
|
| 328 |
-
)
|
| 329 |
-
|
| 330 |
-
return C
|
| 331 |
-
|
| 332 |
-
|
| 333 |
-
def w8a8_block_fp8_matmul(
|
| 334 |
-
A: torch.Tensor,
|
| 335 |
-
B: torch.Tensor,
|
| 336 |
-
As: torch.Tensor,
|
| 337 |
-
Bs: torch.Tensor,
|
| 338 |
-
block_size: list[int],
|
| 339 |
-
output_dtype: torch.dtype = torch.float32,
|
| 340 |
-
) -> torch.Tensor:
|
| 341 |
-
"""Block-wise W8A8 FP8 matrix multiplication.
|
| 342 |
-
|
| 343 |
-
Computes ``C = A @ B.T`` where both operands are pre-quantized to
|
| 344 |
-
``float8_e4m3fn`` with per-block scales, and accumulates in float32
|
| 345 |
-
before casting to ``output_dtype``.
|
| 346 |
-
|
| 347 |
-
Args:
|
| 348 |
-
A: Quantized activation tensor ``[M, K]`` in ``float8_e4m3fn``.
|
| 349 |
-
B: Quantized weight tensor ``[N, K]`` in ``float8_e4m3fn``.
|
| 350 |
-
As: Per-token-group activation scales ``[M, K // block_size[1]]``.
|
| 351 |
-
Bs: Per-block weight scales ``[N // block_size[0], K // block_size[1]]``.
|
| 352 |
-
block_size: ``[block_n, block_k]`` quantization block dimensions, e.g. ``[128, 128]``.
|
| 353 |
-
output_dtype: dtype of the returned tensor (default: ``torch.float32``).
|
| 354 |
-
|
| 355 |
-
Returns:
|
| 356 |
-
Output tensor ``[M, N]`` in ``output_dtype``.
|
| 357 |
-
"""
|
| 358 |
-
return torch.ops.finegrained_fp8.w8a8_block_fp8_matmul(
|
| 359 |
-
A, B, As, Bs, block_size, output_dtype
|
| 360 |
-
)
|
| 361 |
-
|
| 362 |
-
|
| 363 |
-
def w8a8_tensor_fp8_matmul(
|
| 364 |
-
A: torch.Tensor,
|
| 365 |
-
B: torch.Tensor,
|
| 366 |
-
As: torch.Tensor,
|
| 367 |
-
Bs: torch.Tensor,
|
| 368 |
-
output_dtype: torch.dtype = torch.float32,
|
| 369 |
-
) -> torch.Tensor:
|
| 370 |
-
"""Tensor-scale W8A8 FP8 matrix multiplication.
|
| 371 |
-
|
| 372 |
-
Computes ``C = A @ B.T`` in tensor-scale mode using pre-quantized FP8
|
| 373 |
-
activations/weights and tensor scales.
|
| 374 |
-
|
| 375 |
-
Args:
|
| 376 |
-
A: Quantized activation tensor ``[M, K]`` in ``float8_e4m3fn``.
|
| 377 |
-
B: Quantized weight tensor ``[N, K]`` in ``float8_e4m3fn``.
|
| 378 |
-
As: Per-row activation scales ``[M]``.
|
| 379 |
-
Bs: Single weight scale, scalar or ``[1]``.
|
| 380 |
-
output_dtype: dtype of the returned tensor.
|
| 381 |
-
|
| 382 |
-
Returns:
|
| 383 |
-
Output tensor ``[M, N]`` in ``output_dtype``.
|
| 384 |
-
"""
|
| 385 |
-
return torch.ops.finegrained_fp8.w8a8_tensor_fp8_matmul(A, B, As, Bs, output_dtype)
|
| 386 |
-
|
| 387 |
-
|
| 388 |
-
def w8a8_fp8_matmul(
|
| 389 |
-
A: torch.Tensor,
|
| 390 |
-
B: torch.Tensor,
|
| 391 |
-
As: torch.Tensor,
|
| 392 |
-
Bs: torch.Tensor,
|
| 393 |
-
block_size: list[int] | None,
|
| 394 |
-
output_dtype: torch.dtype = torch.float32,
|
| 395 |
-
) -> torch.Tensor:
|
| 396 |
-
"""Unified W8A8 FP8 matmul dispatcher.
|
| 397 |
-
|
| 398 |
-
Dispatch rules:
|
| 399 |
-
- tensor mode when ``block_size is None``
|
| 400 |
-
- tensor mode when ``block_size == [N, K]``
|
| 401 |
-
- otherwise block mode
|
| 402 |
-
|
| 403 |
-
Returns:
|
| 404 |
-
Output tensor ``[M, N]`` in ``output_dtype``.
|
| 405 |
-
"""
|
| 406 |
-
if block_size is None or (
|
| 407 |
-
block_size[0] == B.size(0) and block_size[1] == B.size(1)
|
| 408 |
-
):
|
| 409 |
-
return w8a8_tensor_fp8_matmul(A, B, As, Bs, output_dtype)
|
| 410 |
-
|
| 411 |
-
return w8a8_block_fp8_matmul(A, B, As, Bs, block_size, output_dtype)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
build/torch-rocm/metadata.json
DELETED
|
@@ -1,10 +0,0 @@
|
|
| 1 |
-
{
|
| 2 |
-
"name": "finegrained-fp8",
|
| 3 |
-
"id": "_finegrained_fp8_rocm_bbc7e0f",
|
| 4 |
-
"version": 1,
|
| 5 |
-
"license": "Apache-2.0",
|
| 6 |
-
"python-depends": [],
|
| 7 |
-
"backend": {
|
| 8 |
-
"type": "rocm"
|
| 9 |
-
}
|
| 10 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
build/torch-rocm/utils.py
DELETED
|
@@ -1,13 +0,0 @@
|
|
| 1 |
-
import torch
|
| 2 |
-
from contextlib import contextmanager
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
@contextmanager
|
| 6 |
-
def device_context(device: torch.device):
|
| 7 |
-
"""Context manager that sets the active device for any backend (cuda, xpu, etc.)."""
|
| 8 |
-
backend = getattr(torch, device.type, None)
|
| 9 |
-
if backend is not None and hasattr(backend, "device"):
|
| 10 |
-
with backend.device(device):
|
| 11 |
-
yield
|
| 12 |
-
else:
|
| 13 |
-
yield
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
build/torch-xpu/__init__.py
DELETED
|
@@ -1,32 +0,0 @@
|
|
| 1 |
-
from .act_quant import fp8_act_quant
|
| 2 |
-
from .batched import (
|
| 3 |
-
w8a8_fp8_matmul_batched,
|
| 4 |
-
w8a8_block_fp8_matmul_batched,
|
| 5 |
-
w8a8_tensor_fp8_matmul_batched,
|
| 6 |
-
)
|
| 7 |
-
from .grouped import (
|
| 8 |
-
w8a8_fp8_matmul_grouped,
|
| 9 |
-
w8a8_block_fp8_matmul_grouped,
|
| 10 |
-
w8a8_tensor_fp8_matmul_grouped,
|
| 11 |
-
)
|
| 12 |
-
from .matmul import (
|
| 13 |
-
w8a8_fp8_matmul,
|
| 14 |
-
w8a8_block_fp8_matmul,
|
| 15 |
-
w8a8_tensor_fp8_matmul,
|
| 16 |
-
)
|
| 17 |
-
|
| 18 |
-
__all__ = [
|
| 19 |
-
"fp8_act_quant",
|
| 20 |
-
# Single matmul
|
| 21 |
-
"w8a8_fp8_matmul",
|
| 22 |
-
"w8a8_block_fp8_matmul",
|
| 23 |
-
"w8a8_tensor_fp8_matmul",
|
| 24 |
-
# Batched matmul
|
| 25 |
-
"w8a8_fp8_matmul_batched",
|
| 26 |
-
"w8a8_block_fp8_matmul_batched",
|
| 27 |
-
"w8a8_tensor_fp8_matmul_batched",
|
| 28 |
-
# Grouped matmul
|
| 29 |
-
"w8a8_fp8_matmul_grouped",
|
| 30 |
-
"w8a8_block_fp8_matmul_grouped",
|
| 31 |
-
"w8a8_tensor_fp8_matmul_grouped",
|
| 32 |
-
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
build/torch-xpu/_ops.py
DELETED
|
@@ -1,38 +0,0 @@
|
|
| 1 |
-
import torch
|
| 2 |
-
|
| 3 |
-
def get_backend() -> str:
|
| 4 |
-
"""Detect the backend by inspecting torch."""
|
| 5 |
-
import torch
|
| 6 |
-
|
| 7 |
-
if hasattr(torch, "neuron"):
|
| 8 |
-
# Needs to be sorted before specific Torch builds, since Neuron
|
| 9 |
-
# extension can be loaded into e.g. CUDA Torch builds.
|
| 10 |
-
return "neuron"
|
| 11 |
-
elif torch.version.cuda is not None:
|
| 12 |
-
return "cuda"
|
| 13 |
-
elif torch.version.hip is not None:
|
| 14 |
-
return "rocm"
|
| 15 |
-
elif torch.backends.mps.is_available():
|
| 16 |
-
return "metal"
|
| 17 |
-
elif hasattr(torch.version, "xpu") and torch.version.xpu is not None:
|
| 18 |
-
return "xpu"
|
| 19 |
-
else:
|
| 20 |
-
return "cpu"
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
def _find_ops_name() -> str:
|
| 24 |
-
kernel_name = "finegrained_fp8"
|
| 25 |
-
unique_id = "7c5619e"
|
| 26 |
-
backend = get_backend()
|
| 27 |
-
return f"_{kernel_name}_{backend}_{unique_id}"
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
_OPS_NAME = _find_ops_name()
|
| 31 |
-
|
| 32 |
-
ops = getattr(torch.ops, _OPS_NAME)
|
| 33 |
-
|
| 34 |
-
def add_op_namespace_prefix(op_name: str) -> str:
|
| 35 |
-
"""
|
| 36 |
-
Prefix op by namespace.
|
| 37 |
-
"""
|
| 38 |
-
return f"{_OPS_NAME}::{op_name}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
build/torch-xpu/act_quant.py
DELETED
|
@@ -1,73 +0,0 @@
|
|
| 1 |
-
# Copyright 2026 The HuggingFace Inc. team. All rights reserved.
|
| 2 |
-
#
|
| 3 |
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
-
# you may not use this file except in compliance with the License.
|
| 5 |
-
# You may obtain a copy of the License at
|
| 6 |
-
#
|
| 7 |
-
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
-
#
|
| 9 |
-
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
-
# See the License for the specific language governing permissions and
|
| 13 |
-
# limitations under the License.
|
| 14 |
-
|
| 15 |
-
import torch
|
| 16 |
-
import triton
|
| 17 |
-
import triton.language as tl
|
| 18 |
-
from torch.library import triton_op, wrap_triton
|
| 19 |
-
|
| 20 |
-
from .utils import device_context
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
_FP8_DTYPE = torch.float8_e4m3fn
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
# Copied from https://huggingface.co/deepseek-ai/DeepSeek-V3/blob/main/inference/kernel.py
|
| 27 |
-
@triton.jit
|
| 28 |
-
def _fp8_act_quant_kernel(x_ptr, y_ptr, s_ptr, BLOCK_SIZE: tl.constexpr):
|
| 29 |
-
pid = tl.program_id(axis=0)
|
| 30 |
-
offs = pid * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE)
|
| 31 |
-
x = tl.load(x_ptr + offs).to(tl.float32)
|
| 32 |
-
s = tl.max(tl.abs(x)) / 448.0 # float8_e4m3fn max
|
| 33 |
-
y = (x / s).to(y_ptr.dtype.element_ty)
|
| 34 |
-
tl.store(y_ptr + offs, y)
|
| 35 |
-
tl.store(s_ptr + pid, s)
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
@triton_op("finegrained_fp8::fp8_act_quant", mutates_args=())
|
| 39 |
-
def _fp8_act_quant(
|
| 40 |
-
x: torch.Tensor, block_size: int = 128
|
| 41 |
-
) -> tuple[torch.Tensor, torch.Tensor]:
|
| 42 |
-
assert x.is_contiguous()
|
| 43 |
-
assert x.shape[-1] % block_size == 0
|
| 44 |
-
y = torch.empty_like(x, dtype=_FP8_DTYPE)
|
| 45 |
-
grid = (triton.cdiv(x.numel(), block_size),)
|
| 46 |
-
s = x.new_empty(*x.size()[:-1], x.size(-1) // block_size, dtype=torch.float32)
|
| 47 |
-
|
| 48 |
-
with device_context(x.device):
|
| 49 |
-
wrap_triton(_fp8_act_quant_kernel)[grid](x, y, s, BLOCK_SIZE=block_size)
|
| 50 |
-
|
| 51 |
-
return y, s
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
def fp8_act_quant(
|
| 55 |
-
x: torch.Tensor, block_size: int = 128
|
| 56 |
-
) -> tuple[torch.Tensor, torch.Tensor]:
|
| 57 |
-
"""Quantize activations to FP8 with per-block dynamic scaling.
|
| 58 |
-
|
| 59 |
-
Splits the last dimension of ``x`` into blocks of ``block_size`` elements,
|
| 60 |
-
computes ``scale = max(|x_block|) / 448`` per block, and quantizes to
|
| 61 |
-
``float8_e4m3fn``.
|
| 62 |
-
|
| 63 |
-
Args:
|
| 64 |
-
x: Input tensor in bf16/fp16/fp32. Last dimension must be divisible by
|
| 65 |
-
``block_size`` and the tensor must be contiguous.
|
| 66 |
-
block_size: Number of elements per quantization block (default: 128).
|
| 67 |
-
|
| 68 |
-
Returns:
|
| 69 |
-
A tuple ``(quantized, scales)`` where ``quantized`` has dtype
|
| 70 |
-
``float8_e4m3fn`` with the same shape as ``x``, and ``scales`` has
|
| 71 |
-
shape ``(*x.shape[:-1], x.shape[-1] // block_size)`` in float32.
|
| 72 |
-
"""
|
| 73 |
-
return torch.ops.finegrained_fp8.fp8_act_quant(x, block_size)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
build/torch-xpu/batched.py
DELETED
|
@@ -1,398 +0,0 @@
|
|
| 1 |
-
# Copyright 2026 The HuggingFace Inc. team. All rights reserved.
|
| 2 |
-
#
|
| 3 |
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
-
# you may not use this file except in compliance with the License.
|
| 5 |
-
# You may obtain a copy of the License at
|
| 6 |
-
#
|
| 7 |
-
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
-
#
|
| 9 |
-
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
-
# See the License for the specific language governing permissions and
|
| 13 |
-
# limitations under the License.
|
| 14 |
-
|
| 15 |
-
import torch
|
| 16 |
-
import triton
|
| 17 |
-
import triton.language as tl
|
| 18 |
-
from .act_quant import fp8_act_quant
|
| 19 |
-
from torch.library import triton_op, wrap_triton
|
| 20 |
-
|
| 21 |
-
from .utils import device_context
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
@triton.autotune(
|
| 25 |
-
configs=[
|
| 26 |
-
triton.Config({}, num_warps=w, num_stages=s)
|
| 27 |
-
for w in [2, 4, 8, 16]
|
| 28 |
-
for s in [2, 3, 4, 5]
|
| 29 |
-
],
|
| 30 |
-
key=["N", "K"],
|
| 31 |
-
)
|
| 32 |
-
@triton.jit
|
| 33 |
-
def w8a8_block_fp8_matmul_batched_kernel(
|
| 34 |
-
A, # (S, K) raw BF16/FP16 activations
|
| 35 |
-
B, # (E, N, K) FP8 weight matrices
|
| 36 |
-
C, # (S, N) output
|
| 37 |
-
Bs, # (E, N // BLOCK_SIZE_N, K // BLOCK_SIZE_K) weight scales
|
| 38 |
-
ExpertIds, # (S,) — which expert each batch element routes to
|
| 39 |
-
# Shape
|
| 40 |
-
S,
|
| 41 |
-
N,
|
| 42 |
-
K,
|
| 43 |
-
stride_am,
|
| 44 |
-
stride_ak,
|
| 45 |
-
stride_be,
|
| 46 |
-
stride_bk,
|
| 47 |
-
stride_bn,
|
| 48 |
-
stride_cm,
|
| 49 |
-
stride_cn,
|
| 50 |
-
stride_bs_e,
|
| 51 |
-
stride_bs_k,
|
| 52 |
-
stride_bs_n,
|
| 53 |
-
# Meta-parameters
|
| 54 |
-
BLOCK_SIZE_N: tl.constexpr,
|
| 55 |
-
BLOCK_SIZE_K: tl.constexpr,
|
| 56 |
-
BLOCK_SIZE_M: tl.constexpr,
|
| 57 |
-
):
|
| 58 |
-
"""Block-scale batched FP8 expert matmul kernel.
|
| 59 |
-
|
| 60 |
-
Each program handles one routed token row and one N-tile, looks up the
|
| 61 |
-
owning expert from ``ExpertIds``, and applies fused activation quantization.
|
| 62 |
-
"""
|
| 63 |
-
batch_id = tl.program_id(axis=0)
|
| 64 |
-
pid_n = tl.program_id(axis=1)
|
| 65 |
-
|
| 66 |
-
# Cast expert_id to int64 to prevent int32 overflow when computing
|
| 67 |
-
# expert_id * stride_Eb (e.g. 255 * 9_437_184 > 2^31 for 256 experts of
|
| 68 |
-
# 3072×3072 FP8 weights).
|
| 69 |
-
expert_id = tl.load(ExpertIds + batch_id).to(tl.int64)
|
| 70 |
-
|
| 71 |
-
A = A + batch_id * stride_am
|
| 72 |
-
B = B + expert_id * stride_be
|
| 73 |
-
C = C + batch_id * stride_cm
|
| 74 |
-
Bs = Bs + expert_id * stride_bs_e
|
| 75 |
-
|
| 76 |
-
offs_bn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
|
| 77 |
-
offs_k = tl.arange(0, BLOCK_SIZE_K)
|
| 78 |
-
a_ptrs = A + tl.arange(0, BLOCK_SIZE_M)[:, None] * 0 + offs_k[None, :] * stride_ak
|
| 79 |
-
b_ptrs = B + (offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn)
|
| 80 |
-
|
| 81 |
-
bs_ptrs = Bs + pid_n * stride_bs_n
|
| 82 |
-
|
| 83 |
-
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
| 84 |
-
for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
|
| 85 |
-
# ---- fused fp8_act_quant ----
|
| 86 |
-
a_raw = tl.load(a_ptrs).to(tl.float32)
|
| 87 |
-
a_s = tl.max(tl.abs(a_raw)) / 448.0
|
| 88 |
-
a = (a_raw / tl.maximum(a_s, 1e-12)).to(tl.float8e4nv)
|
| 89 |
-
# ---- matmul ----
|
| 90 |
-
b = tl.load(b_ptrs)
|
| 91 |
-
b_s = tl.load(bs_ptrs + k * stride_bs_k)
|
| 92 |
-
accumulator += tl.dot(a, b) * a_s * b_s[None, :]
|
| 93 |
-
a_ptrs += BLOCK_SIZE_K * stride_ak
|
| 94 |
-
b_ptrs += BLOCK_SIZE_K * stride_bk
|
| 95 |
-
|
| 96 |
-
if C.dtype.element_ty == tl.bfloat16:
|
| 97 |
-
c = accumulator.to(tl.bfloat16)
|
| 98 |
-
elif C.dtype.element_ty == tl.float16:
|
| 99 |
-
c = accumulator.to(tl.float16)
|
| 100 |
-
else:
|
| 101 |
-
c = accumulator.to(tl.float32)
|
| 102 |
-
|
| 103 |
-
offs_cm = tl.arange(0, BLOCK_SIZE_M)
|
| 104 |
-
offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
|
| 105 |
-
c_ptrs = C + offs_cm[:, None] * 0 + stride_cn * offs_cn[None, :]
|
| 106 |
-
# Fake-batch trick aliases all BLOCK_SIZE_M lanes to the same C row, so emitting
|
| 107 |
-
# `tl.store(c_ptrs, c)` issues BLOCK_SIZE_M duplicate-address stores. On NVIDIA
|
| 108 |
-
# WGMMA this is usually benign (last-write-wins of identical bytes), but on Intel
|
| 109 |
-
# XPU the duplicate-address store has hardware-undefined behavior and corrupts the
|
| 110 |
-
# output. Mask so only lane 0 stores — the (M, N) accumulator rows are
|
| 111 |
-
# mathematically identical (same A row × same B), so lane 0 holds the right value.
|
| 112 |
-
tl.store(c_ptrs, c, mask=(offs_cm == 0)[:, None])
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
@triton.autotune(
|
| 116 |
-
configs=[
|
| 117 |
-
triton.Config({}, num_warps=w, num_stages=s)
|
| 118 |
-
for w in [2, 4, 8, 16]
|
| 119 |
-
for s in [2, 3, 4, 5]
|
| 120 |
-
],
|
| 121 |
-
key=["N", "K"],
|
| 122 |
-
)
|
| 123 |
-
@triton.jit
|
| 124 |
-
def w8a8_tensor_fp8_matmul_batched_kernel(
|
| 125 |
-
A, # (S, K) pre-quantized FP8 activations
|
| 126 |
-
B, # (E, N, K) FP8 weight matrices
|
| 127 |
-
C, # (S, N) output
|
| 128 |
-
As, # (S, 1) per-tensor activation scales
|
| 129 |
-
Bs, # (E, 1, 1) per-tensor weight scales
|
| 130 |
-
ExpertIds,
|
| 131 |
-
S,
|
| 132 |
-
N,
|
| 133 |
-
K,
|
| 134 |
-
stride_am,
|
| 135 |
-
stride_ak,
|
| 136 |
-
stride_be,
|
| 137 |
-
stride_bk,
|
| 138 |
-
stride_bn,
|
| 139 |
-
stride_cm,
|
| 140 |
-
stride_cn,
|
| 141 |
-
stride_as_m,
|
| 142 |
-
stride_bs_e,
|
| 143 |
-
BLOCK_SIZE_N: tl.constexpr,
|
| 144 |
-
BLOCK_SIZE_K: tl.constexpr,
|
| 145 |
-
BLOCK_SIZE_M: tl.constexpr,
|
| 146 |
-
):
|
| 147 |
-
"""Tensor-scale batched FP8 expert matmul kernel.
|
| 148 |
-
|
| 149 |
-
Activations are already quantized; the kernel applies per-token activation
|
| 150 |
-
scales and per-expert tensor weight scales.
|
| 151 |
-
"""
|
| 152 |
-
batch_id = tl.program_id(axis=0)
|
| 153 |
-
pid_n = tl.program_id(axis=1)
|
| 154 |
-
|
| 155 |
-
expert_id = tl.load(ExpertIds + batch_id).to(tl.int64)
|
| 156 |
-
|
| 157 |
-
A = A + batch_id * stride_am
|
| 158 |
-
B = B + expert_id * stride_be
|
| 159 |
-
C = C + batch_id * stride_cm
|
| 160 |
-
Bs = Bs + expert_id * stride_bs_e
|
| 161 |
-
|
| 162 |
-
offs_bn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
|
| 163 |
-
offs_k = tl.arange(0, BLOCK_SIZE_K)
|
| 164 |
-
a_ptrs = A + tl.arange(0, BLOCK_SIZE_M)[:, None] * 0 + offs_k[None, :] * stride_ak
|
| 165 |
-
b_ptrs = B + (offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn)
|
| 166 |
-
|
| 167 |
-
b_s = tl.load(Bs)
|
| 168 |
-
a_s = tl.load(As + batch_id * stride_as_m)
|
| 169 |
-
|
| 170 |
-
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
| 171 |
-
for _ in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
|
| 172 |
-
a = tl.load(a_ptrs)
|
| 173 |
-
b = tl.load(b_ptrs)
|
| 174 |
-
accumulator += tl.dot(a, b)
|
| 175 |
-
a_ptrs += BLOCK_SIZE_K * stride_ak
|
| 176 |
-
b_ptrs += BLOCK_SIZE_K * stride_bk
|
| 177 |
-
|
| 178 |
-
accumulator = accumulator * a_s * b_s
|
| 179 |
-
|
| 180 |
-
if C.dtype.element_ty == tl.bfloat16:
|
| 181 |
-
c = accumulator.to(tl.bfloat16)
|
| 182 |
-
elif C.dtype.element_ty == tl.float16:
|
| 183 |
-
c = accumulator.to(tl.float16)
|
| 184 |
-
else:
|
| 185 |
-
c = accumulator.to(tl.float32)
|
| 186 |
-
|
| 187 |
-
offs_cm = tl.arange(0, BLOCK_SIZE_M)
|
| 188 |
-
offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
|
| 189 |
-
c_ptrs = C + offs_cm[:, None] * 0 + stride_cn * offs_cn[None, :]
|
| 190 |
-
# See block-FP8 kernel above: BLOCK_SIZE_M lanes alias the same C row;
|
| 191 |
-
# mask so only lane 0 stores to avoid hardware-undefined duplicate writes on XPU.
|
| 192 |
-
tl.store(c_ptrs, c, mask=(offs_cm == 0)[:, None])
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
@triton_op("finegrained_fp8::w8a8_block_fp8_matmul_batched", mutates_args=())
|
| 196 |
-
def _w8a8_block_fp8_matmul_batched(
|
| 197 |
-
A: torch.Tensor,
|
| 198 |
-
B: torch.Tensor,
|
| 199 |
-
Bs: torch.Tensor,
|
| 200 |
-
expert_ids: torch.Tensor,
|
| 201 |
-
block_size: list[int],
|
| 202 |
-
) -> torch.Tensor:
|
| 203 |
-
"""Block-scale batched FP8 matmul: C[s] = A[s] @ B[expert_ids[s]].T, with fused act quant.
|
| 204 |
-
|
| 205 |
-
A: (S, K) raw bf16/fp16 activations
|
| 206 |
-
B: (E, N, K) FP8 expert weights
|
| 207 |
-
Bs: (E, N // block_n, K // block_k) per-block weight scales
|
| 208 |
-
"""
|
| 209 |
-
assert A.ndim == 2, f"A must be 2D (S, K), got ndim={A.ndim}"
|
| 210 |
-
assert A.is_contiguous(), "A must be contiguous"
|
| 211 |
-
assert B.ndim == 3, f"B must be 3D (E, N, K), got ndim={B.ndim}"
|
| 212 |
-
assert B.is_contiguous(), "B must be contiguous"
|
| 213 |
-
assert A.shape[1] == B.shape[2], (
|
| 214 |
-
f"K mismatch: A has K={A.shape[1]}, B has K={B.shape[2]}"
|
| 215 |
-
)
|
| 216 |
-
|
| 217 |
-
S, K = A.shape
|
| 218 |
-
E, N, _ = B.shape
|
| 219 |
-
|
| 220 |
-
assert len(block_size) == 2, (
|
| 221 |
-
f"block_size must be [block_n, block_k], got {block_size}"
|
| 222 |
-
)
|
| 223 |
-
block_n, block_k = block_size[0], block_size[1]
|
| 224 |
-
# MoE expert dimensions must be block-aligned; non-aligned N/K is not supported.
|
| 225 |
-
assert N % block_n == 0, f"N ({N}) must be divisible by block_n ({block_n})"
|
| 226 |
-
assert K % block_k == 0, f"K ({K}) must be divisible by block_k ({block_k})"
|
| 227 |
-
assert Bs.ndim == 3, (
|
| 228 |
-
f"Bs must be 3D (E, N//block_n, K//block_k), got ndim={Bs.ndim}"
|
| 229 |
-
)
|
| 230 |
-
assert Bs.shape == (E, N // block_n, K // block_k), (
|
| 231 |
-
f"Bs shape {tuple(Bs.shape)} != expected ({E}, {N // block_n}, {K // block_k})"
|
| 232 |
-
)
|
| 233 |
-
|
| 234 |
-
C = A.new_empty(S, N)
|
| 235 |
-
# Adaptive BLOCK_SIZE_M: smallest power-of-2 >= M, floored at 16, capped at 128.
|
| 236 |
-
# Matches the WGMMA tile to the actual row count — smaller tiles use less
|
| 237 |
-
# register pressure and a better-matched FP8 WGMMA instruction, improving
|
| 238 |
-
# both accuracy and performance for small M (decode).
|
| 239 |
-
BLOCK_SIZE_M = min(max(triton.next_power_of_2((S + E - 1) // E), 16), 128)
|
| 240 |
-
grid = (S, triton.cdiv(N, block_n))
|
| 241 |
-
with device_context(A.device):
|
| 242 |
-
wrap_triton(w8a8_block_fp8_matmul_batched_kernel)[grid](
|
| 243 |
-
A,
|
| 244 |
-
B,
|
| 245 |
-
C,
|
| 246 |
-
Bs,
|
| 247 |
-
expert_ids,
|
| 248 |
-
S,
|
| 249 |
-
N,
|
| 250 |
-
K,
|
| 251 |
-
A.stride(0),
|
| 252 |
-
A.stride(1),
|
| 253 |
-
B.stride(0),
|
| 254 |
-
B.stride(2),
|
| 255 |
-
B.stride(1),
|
| 256 |
-
C.stride(0),
|
| 257 |
-
C.stride(1),
|
| 258 |
-
Bs.stride(0),
|
| 259 |
-
Bs.stride(2),
|
| 260 |
-
Bs.stride(1),
|
| 261 |
-
BLOCK_SIZE_N=block_n,
|
| 262 |
-
BLOCK_SIZE_K=block_k,
|
| 263 |
-
BLOCK_SIZE_M=BLOCK_SIZE_M,
|
| 264 |
-
)
|
| 265 |
-
|
| 266 |
-
return C
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
@triton_op("finegrained_fp8::w8a8_tensor_fp8_matmul_batched", mutates_args=())
|
| 270 |
-
def _w8a8_tensor_fp8_matmul_batched(
|
| 271 |
-
A: torch.Tensor,
|
| 272 |
-
B: torch.Tensor,
|
| 273 |
-
Bs: torch.Tensor,
|
| 274 |
-
expert_ids: torch.Tensor,
|
| 275 |
-
) -> torch.Tensor:
|
| 276 |
-
"""Tensor-scale batched FP8 matmul: C[s] = A[s] @ B[expert_ids[s]].T, with fused act quant.
|
| 277 |
-
|
| 278 |
-
A: (S, K) raw bf16/fp16 activations
|
| 279 |
-
B: (E, N, K) FP8 expert weights
|
| 280 |
-
Bs: (E,) or (E, 1, 1) per-expert weight scales
|
| 281 |
-
"""
|
| 282 |
-
assert A.ndim == 2, f"A must be 2D (S, K), got ndim={A.ndim}"
|
| 283 |
-
assert A.is_contiguous(), "A must be contiguous"
|
| 284 |
-
assert B.ndim == 3, f"B must be 3D (E, N, K), got ndim={B.ndim}"
|
| 285 |
-
assert B.is_contiguous(), "B must be contiguous"
|
| 286 |
-
assert A.shape[1] == B.shape[2], (
|
| 287 |
-
f"K mismatch: A has K={A.shape[1]}, B has K={B.shape[2]}"
|
| 288 |
-
)
|
| 289 |
-
|
| 290 |
-
S, K = A.shape
|
| 291 |
-
E, N, _ = B.shape
|
| 292 |
-
|
| 293 |
-
# Normalize Bs to (E, 1, 1)
|
| 294 |
-
if Bs.ndim == 1:
|
| 295 |
-
assert Bs.shape[0] == E, f"Bs shape {tuple(Bs.shape)} != expected ({E},)"
|
| 296 |
-
Bs = Bs.reshape(E, 1, 1)
|
| 297 |
-
else:
|
| 298 |
-
assert Bs.shape == (E, 1, 1), (
|
| 299 |
-
f"Bs shape {tuple(Bs.shape)} != expected ({E}, 1, 1)"
|
| 300 |
-
)
|
| 301 |
-
|
| 302 |
-
BLOCK_SIZE_N = 128
|
| 303 |
-
BLOCK_SIZE_K = 128
|
| 304 |
-
C = A.new_empty(S, N)
|
| 305 |
-
qA, As = fp8_act_quant(A, K)
|
| 306 |
-
grid = (S, triton.cdiv(N, BLOCK_SIZE_N))
|
| 307 |
-
# Adaptive BLOCK_SIZE_M: smallest power-of-2 >= M, floored at 16, capped at 128.
|
| 308 |
-
# Matches the WGMMA tile to the actual row count — smaller tiles use less
|
| 309 |
-
# register pressure and a better-matched FP8 WGMMA instruction, improving
|
| 310 |
-
# both accuracy and performance for small M (decode).
|
| 311 |
-
BLOCK_SIZE_M = min(max(triton.next_power_of_2((S + E - 1) // E), 16), 128)
|
| 312 |
-
grid = (S, triton.cdiv(N, BLOCK_SIZE_N))
|
| 313 |
-
with device_context(A.device):
|
| 314 |
-
wrap_triton(w8a8_tensor_fp8_matmul_batched_kernel)[grid](
|
| 315 |
-
qA,
|
| 316 |
-
B,
|
| 317 |
-
C,
|
| 318 |
-
As,
|
| 319 |
-
Bs,
|
| 320 |
-
expert_ids,
|
| 321 |
-
S,
|
| 322 |
-
N,
|
| 323 |
-
K,
|
| 324 |
-
qA.stride(0),
|
| 325 |
-
qA.stride(1),
|
| 326 |
-
B.stride(0),
|
| 327 |
-
B.stride(2),
|
| 328 |
-
B.stride(1),
|
| 329 |
-
C.stride(0),
|
| 330 |
-
C.stride(1),
|
| 331 |
-
As.stride(0),
|
| 332 |
-
Bs.stride(0),
|
| 333 |
-
BLOCK_SIZE_N=BLOCK_SIZE_N,
|
| 334 |
-
BLOCK_SIZE_K=BLOCK_SIZE_K,
|
| 335 |
-
BLOCK_SIZE_M=BLOCK_SIZE_M,
|
| 336 |
-
)
|
| 337 |
-
|
| 338 |
-
return C
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
def w8a8_block_fp8_matmul_batched(
|
| 342 |
-
A: torch.Tensor,
|
| 343 |
-
B: torch.Tensor,
|
| 344 |
-
Bs: torch.Tensor,
|
| 345 |
-
expert_ids: torch.Tensor,
|
| 346 |
-
block_size: list[int],
|
| 347 |
-
) -> torch.Tensor:
|
| 348 |
-
"""Block-scale batched FP8 matmul with fused activation quantization.
|
| 349 |
-
|
| 350 |
-
A: (S, K) raw activations, bf16/fp16/fp32
|
| 351 |
-
B: (E, N, K) FP8 expert weights
|
| 352 |
-
Bs: (E, N // block_n, K // block_k) per-block weight scales
|
| 353 |
-
"""
|
| 354 |
-
return torch.ops.finegrained_fp8.w8a8_block_fp8_matmul_batched(
|
| 355 |
-
A, B, Bs, expert_ids, block_size
|
| 356 |
-
)
|
| 357 |
-
|
| 358 |
-
|
| 359 |
-
def w8a8_tensor_fp8_matmul_batched(
|
| 360 |
-
A: torch.Tensor,
|
| 361 |
-
B: torch.Tensor,
|
| 362 |
-
Bs: torch.Tensor,
|
| 363 |
-
expert_ids: torch.Tensor,
|
| 364 |
-
) -> torch.Tensor:
|
| 365 |
-
"""Tensor-scale batched FP8 matmul with fused activation quantization.
|
| 366 |
-
|
| 367 |
-
A: (S, K) raw activations, bf16/fp16/fp32
|
| 368 |
-
B: (E, N, K) FP8 expert weights
|
| 369 |
-
Bs: (E,) or (E, 1, 1) per-expert weight scales
|
| 370 |
-
"""
|
| 371 |
-
return torch.ops.finegrained_fp8.w8a8_tensor_fp8_matmul_batched(
|
| 372 |
-
A, B, Bs, expert_ids
|
| 373 |
-
)
|
| 374 |
-
|
| 375 |
-
|
| 376 |
-
def w8a8_fp8_matmul_batched(
|
| 377 |
-
A: torch.Tensor,
|
| 378 |
-
B: torch.Tensor,
|
| 379 |
-
Bs: torch.Tensor,
|
| 380 |
-
expert_ids: torch.Tensor,
|
| 381 |
-
block_size: list[int] | None,
|
| 382 |
-
) -> torch.Tensor:
|
| 383 |
-
"""Unified batched W8A8 FP8 matmul dispatcher.
|
| 384 |
-
|
| 385 |
-
Dispatch rules:
|
| 386 |
-
- tensor mode when ``block_size is None``
|
| 387 |
-
- tensor mode when ``block_size == [N, K]``
|
| 388 |
-
- otherwise block mode
|
| 389 |
-
|
| 390 |
-
Returns:
|
| 391 |
-
Output tensor ``[S, N]`` in the same dtype as ``A``.
|
| 392 |
-
"""
|
| 393 |
-
if block_size is None or (
|
| 394 |
-
block_size[0] == B.size(1) and block_size[1] == B.size(2)
|
| 395 |
-
):
|
| 396 |
-
return w8a8_tensor_fp8_matmul_batched(A, B, Bs, expert_ids)
|
| 397 |
-
|
| 398 |
-
return w8a8_block_fp8_matmul_batched(A, B, Bs, expert_ids, block_size)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
build/torch-xpu/finegrained_fp8/__init__.py
DELETED
|
@@ -1,26 +0,0 @@
|
|
| 1 |
-
import ctypes
|
| 2 |
-
import importlib.util
|
| 3 |
-
import sys
|
| 4 |
-
from pathlib import Path
|
| 5 |
-
from types import ModuleType
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
def _import_from_path(file_path: Path) -> ModuleType:
|
| 9 |
-
# We cannot use the module name as-is, after adding it to `sys.modules`,
|
| 10 |
-
# it would also be used for other imports. So, we make a module name that
|
| 11 |
-
# depends on the path for it to be unique using the hex-encoded hash of
|
| 12 |
-
# the path.
|
| 13 |
-
path_hash = "{:x}".format(ctypes.c_size_t(hash(file_path.absolute())).value)
|
| 14 |
-
module_name = path_hash
|
| 15 |
-
spec = importlib.util.spec_from_file_location(module_name, file_path)
|
| 16 |
-
if spec is None:
|
| 17 |
-
raise ImportError(f"Cannot load spec for {module_name} from {file_path}")
|
| 18 |
-
module = importlib.util.module_from_spec(spec)
|
| 19 |
-
if module is None:
|
| 20 |
-
raise ImportError(f"Cannot load module {module_name} from spec")
|
| 21 |
-
sys.modules[module_name] = module
|
| 22 |
-
spec.loader.exec_module(module) # type: ignore
|
| 23 |
-
return module
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
globals().update(vars(_import_from_path(Path(__file__).parent.parent / "__init__.py")))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
build/torch-xpu/grouped.py
DELETED
|
@@ -1,477 +0,0 @@
|
|
| 1 |
-
# Copyright 2026 The HuggingFace Inc. team. All rights reserved.
|
| 2 |
-
#
|
| 3 |
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
-
# you may not use this file except in compliance with the License.
|
| 5 |
-
# You may obtain a copy of the License at
|
| 6 |
-
#
|
| 7 |
-
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
-
#
|
| 9 |
-
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
-
# See the License for the specific language governing permissions and
|
| 13 |
-
# limitations under the License.
|
| 14 |
-
|
| 15 |
-
import torch
|
| 16 |
-
import triton
|
| 17 |
-
import triton.language as tl
|
| 18 |
-
from .act_quant import fp8_act_quant
|
| 19 |
-
from torch.library import triton_op, wrap_triton
|
| 20 |
-
|
| 21 |
-
from .utils import device_context
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
@triton.autotune(
|
| 25 |
-
configs=[
|
| 26 |
-
triton.Config({}, num_warps=w, num_stages=s)
|
| 27 |
-
for w in [2, 4, 8, 16]
|
| 28 |
-
for s in [2, 3, 4, 5]
|
| 29 |
-
],
|
| 30 |
-
key=["N", "K", "BLOCK_SIZE_M"],
|
| 31 |
-
)
|
| 32 |
-
@triton.jit
|
| 33 |
-
def w8a8_block_fp8_matmul_grouped_kernel(
|
| 34 |
-
A, # (S, K) raw BF16/FP16 activations, sorted/grouped by expert id
|
| 35 |
-
B, # (E, N, K) FP8 weight matrices
|
| 36 |
-
C, # (S, N) output
|
| 37 |
-
Bs, # (E, N // BLOCK_SIZE_N, K // BLOCK_SIZE_K) weight scales
|
| 38 |
-
Offsets, # (E,) int32 — cumulative row-end per expert
|
| 39 |
-
TileOffsets, # (E,) int32 — cumulative tile-end per expert
|
| 40 |
-
# Shape
|
| 41 |
-
S,
|
| 42 |
-
N,
|
| 43 |
-
K,
|
| 44 |
-
# Strides
|
| 45 |
-
stride_am,
|
| 46 |
-
stride_ak,
|
| 47 |
-
stride_be,
|
| 48 |
-
stride_bk,
|
| 49 |
-
stride_bn,
|
| 50 |
-
stride_cm,
|
| 51 |
-
stride_cn,
|
| 52 |
-
stride_bs_e,
|
| 53 |
-
stride_bs_k,
|
| 54 |
-
stride_bs_n,
|
| 55 |
-
# Meta-parameters
|
| 56 |
-
NUM_EXPERTS: tl.constexpr,
|
| 57 |
-
BLOCK_SIZE_N: tl.constexpr,
|
| 58 |
-
BLOCK_SIZE_K: tl.constexpr,
|
| 59 |
-
BLOCK_SIZE_M: tl.constexpr,
|
| 60 |
-
NUM_EXPERTS_BIT_LENGTH: tl.constexpr,
|
| 61 |
-
):
|
| 62 |
-
"""Block-scale grouped FP8 expert matmul kernel.
|
| 63 |
-
|
| 64 |
-
Tokens are assumed sorted by expert. The kernel maps each M-tile to its
|
| 65 |
-
owning expert via ``TileOffsets`` and applies fused activation quantization.
|
| 66 |
-
"""
|
| 67 |
-
pid_m = tl.program_id(axis=0)
|
| 68 |
-
pid_n = tl.program_id(axis=1)
|
| 69 |
-
|
| 70 |
-
# Exit early for programs beyond the actual tile count.
|
| 71 |
-
total_tiles = tl.load(TileOffsets + NUM_EXPERTS - 1)
|
| 72 |
-
if pid_m >= total_tiles:
|
| 73 |
-
return
|
| 74 |
-
|
| 75 |
-
# Binary search in TileOffsets to find the owning expert.
|
| 76 |
-
# Finds the smallest e such that TileOffsets[e] > pid_m (upper_bound semantics),
|
| 77 |
-
# which is the expert whose tile range contains pid_m.
|
| 78 |
-
# O(log2(NUM_EXPERTS)) loads instead of the O(NUM_EXPERTS) linear scan.
|
| 79 |
-
# NUM_EXPERTS_BIT_LENGTH is ceil(log2(E))+1 for powers-of-two, giving one
|
| 80 |
-
# harmless extra iteration when lo==hi; it's a compile-time constant so the
|
| 81 |
-
# loop is fully unrolled by the compiler.
|
| 82 |
-
lo = 0
|
| 83 |
-
hi = NUM_EXPERTS
|
| 84 |
-
for _ in tl.static_range(NUM_EXPERTS_BIT_LENGTH):
|
| 85 |
-
mid = (lo + hi) >> 1
|
| 86 |
-
mid_val = tl.load(TileOffsets + mid)
|
| 87 |
-
is_left = mid_val <= pid_m
|
| 88 |
-
lo = tl.where(is_left, mid + 1, lo)
|
| 89 |
-
hi = tl.where(is_left, hi, mid)
|
| 90 |
-
|
| 91 |
-
# Cast expert_id to int64 to prevent int32 overflow when computing
|
| 92 |
-
# expert_id * stride_be (e.g. 255 * 9_437_184 > 2^31 for 256 experts of
|
| 93 |
-
# 3072×3072 FP8 weights).
|
| 94 |
-
expert_id = lo.to(tl.int64)
|
| 95 |
-
|
| 96 |
-
prev_eid = tl.maximum(expert_id - 1, 0)
|
| 97 |
-
expert_start = tl.where(expert_id == 0, 0, tl.load(Offsets + prev_eid))
|
| 98 |
-
expert_end = tl.load(Offsets + expert_id)
|
| 99 |
-
M_expert = expert_end - expert_start
|
| 100 |
-
|
| 101 |
-
expert_tile_start = tl.where(expert_id == 0, 0, tl.load(TileOffsets + prev_eid))
|
| 102 |
-
local_tile = pid_m - expert_tile_start
|
| 103 |
-
m_off = local_tile * BLOCK_SIZE_M
|
| 104 |
-
|
| 105 |
-
offs_am = m_off + tl.arange(0, BLOCK_SIZE_M)
|
| 106 |
-
row_mask = offs_am < M_expert
|
| 107 |
-
offs_global_m = expert_start + offs_am
|
| 108 |
-
|
| 109 |
-
offs_bn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
|
| 110 |
-
offs_k = tl.arange(0, BLOCK_SIZE_K)
|
| 111 |
-
|
| 112 |
-
a_ptrs = A + offs_global_m[:, None] * stride_am + offs_k[None, :] * stride_ak
|
| 113 |
-
b_ptrs = (
|
| 114 |
-
B
|
| 115 |
-
+ expert_id * stride_be
|
| 116 |
-
+ offs_k[:, None] * stride_bk
|
| 117 |
-
+ offs_bn[None, :] * stride_bn
|
| 118 |
-
)
|
| 119 |
-
bs_ptrs = Bs + expert_id * stride_bs_e + pid_n * stride_bs_n
|
| 120 |
-
|
| 121 |
-
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
| 122 |
-
for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
|
| 123 |
-
# ---- fused fp8_act_quant ----
|
| 124 |
-
a_raw = tl.load(a_ptrs, mask=row_mask[:, None], other=0.0).to(tl.float32)
|
| 125 |
-
a_s = tl.max(tl.abs(a_raw), axis=1) / 448.0
|
| 126 |
-
a = (a_raw / tl.maximum(a_s[:, None], 1e-12)).to(tl.float8e4nv)
|
| 127 |
-
# ---- matmul ----
|
| 128 |
-
b = tl.load(b_ptrs)
|
| 129 |
-
b_s = tl.load(bs_ptrs + k * stride_bs_k)
|
| 130 |
-
accumulator += tl.dot(a, b) * a_s[:, None] * b_s[None, :]
|
| 131 |
-
a_ptrs += BLOCK_SIZE_K * stride_ak
|
| 132 |
-
b_ptrs += BLOCK_SIZE_K * stride_bk
|
| 133 |
-
|
| 134 |
-
if C.dtype.element_ty == tl.bfloat16:
|
| 135 |
-
c = accumulator.to(tl.bfloat16)
|
| 136 |
-
elif C.dtype.element_ty == tl.float16:
|
| 137 |
-
c = accumulator.to(tl.float16)
|
| 138 |
-
else:
|
| 139 |
-
c = accumulator.to(tl.float32)
|
| 140 |
-
|
| 141 |
-
c_ptrs = C + stride_cm * offs_global_m[:, None] + stride_cn * offs_bn[None, :]
|
| 142 |
-
c_mask = row_mask[:, None]
|
| 143 |
-
tl.store(c_ptrs, c, mask=c_mask)
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
@triton.autotune(
|
| 147 |
-
configs=[
|
| 148 |
-
triton.Config({}, num_warps=w, num_stages=s)
|
| 149 |
-
for w in [2, 4, 8, 16]
|
| 150 |
-
for s in [2, 3, 4, 5]
|
| 151 |
-
],
|
| 152 |
-
key=["N", "K", "BLOCK_SIZE_M"],
|
| 153 |
-
)
|
| 154 |
-
@triton.jit
|
| 155 |
-
def w8a8_tensor_fp8_matmul_grouped_kernel(
|
| 156 |
-
A, # (S, K) raw BF16/FP16 activations, sorted/grouped by expert idc
|
| 157 |
-
B, # (E, N, K) FP8 weight matrices
|
| 158 |
-
C, # (S, N) output
|
| 159 |
-
As, # (S, 1) activation scales
|
| 160 |
-
Bs, # (E, 1, 1) per-tensor weight scales
|
| 161 |
-
Offsets,
|
| 162 |
-
TileOffsets,
|
| 163 |
-
S,
|
| 164 |
-
N,
|
| 165 |
-
K,
|
| 166 |
-
stride_am,
|
| 167 |
-
stride_ak,
|
| 168 |
-
stride_be,
|
| 169 |
-
stride_bk,
|
| 170 |
-
stride_bn,
|
| 171 |
-
stride_cm,
|
| 172 |
-
stride_cn,
|
| 173 |
-
stride_as_m,
|
| 174 |
-
stride_bs_e,
|
| 175 |
-
NUM_EXPERTS: tl.constexpr,
|
| 176 |
-
BLOCK_SIZE_N: tl.constexpr,
|
| 177 |
-
BLOCK_SIZE_K: tl.constexpr,
|
| 178 |
-
BLOCK_SIZE_M: tl.constexpr,
|
| 179 |
-
NUM_EXPERTS_BIT_LENGTH: tl.constexpr,
|
| 180 |
-
):
|
| 181 |
-
"""Tensor-scale grouped FP8 expert matmul kernel.
|
| 182 |
-
|
| 183 |
-
Uses grouped expert scheduling with pre-quantized activations plus
|
| 184 |
-
per-token activation scales and per-expert tensor weight scales.
|
| 185 |
-
"""
|
| 186 |
-
pid_m = tl.program_id(axis=0)
|
| 187 |
-
pid_n = tl.program_id(axis=1)
|
| 188 |
-
|
| 189 |
-
total_tiles = tl.load(TileOffsets + NUM_EXPERTS - 1)
|
| 190 |
-
if pid_m >= total_tiles:
|
| 191 |
-
return
|
| 192 |
-
|
| 193 |
-
lo = 0
|
| 194 |
-
hi = NUM_EXPERTS
|
| 195 |
-
for _ in tl.static_range(NUM_EXPERTS_BIT_LENGTH):
|
| 196 |
-
mid = (lo + hi) >> 1
|
| 197 |
-
mid_val = tl.load(TileOffsets + mid)
|
| 198 |
-
is_left = mid_val <= pid_m
|
| 199 |
-
lo = tl.where(is_left, mid + 1, lo)
|
| 200 |
-
hi = tl.where(is_left, hi, mid)
|
| 201 |
-
expert_id = lo.to(tl.int64)
|
| 202 |
-
|
| 203 |
-
prev_eid = tl.maximum(expert_id - 1, 0)
|
| 204 |
-
expert_start = tl.where(expert_id == 0, 0, tl.load(Offsets + prev_eid))
|
| 205 |
-
expert_end = tl.load(Offsets + expert_id)
|
| 206 |
-
M_expert = expert_end - expert_start
|
| 207 |
-
|
| 208 |
-
expert_tile_start = tl.where(expert_id == 0, 0, tl.load(TileOffsets + prev_eid))
|
| 209 |
-
local_tile = pid_m - expert_tile_start
|
| 210 |
-
m_off = local_tile * BLOCK_SIZE_M
|
| 211 |
-
|
| 212 |
-
offs_am = m_off + tl.arange(0, BLOCK_SIZE_M)
|
| 213 |
-
row_mask = offs_am < M_expert
|
| 214 |
-
offs_global_m = expert_start + offs_am
|
| 215 |
-
|
| 216 |
-
offs_bn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
|
| 217 |
-
offs_k = tl.arange(0, BLOCK_SIZE_K)
|
| 218 |
-
|
| 219 |
-
a_ptrs = A + offs_global_m[:, None] * stride_am + offs_k[None, :] * stride_ak
|
| 220 |
-
b_ptrs = (
|
| 221 |
-
B
|
| 222 |
-
+ expert_id * stride_be
|
| 223 |
-
+ offs_k[:, None] * stride_bk
|
| 224 |
-
+ offs_bn[None, :] * stride_bn
|
| 225 |
-
)
|
| 226 |
-
|
| 227 |
-
a_s = tl.load(As + offs_global_m * stride_as_m, mask=row_mask, other=0.0)
|
| 228 |
-
b_s = tl.load(Bs + expert_id * stride_bs_e)
|
| 229 |
-
|
| 230 |
-
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
| 231 |
-
for _ in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
|
| 232 |
-
a = tl.load(a_ptrs, mask=row_mask[:, None], other=0.0)
|
| 233 |
-
b = tl.load(b_ptrs)
|
| 234 |
-
|
| 235 |
-
accumulator += tl.dot(a, b)
|
| 236 |
-
a_ptrs += BLOCK_SIZE_K * stride_ak
|
| 237 |
-
b_ptrs += BLOCK_SIZE_K * stride_bk
|
| 238 |
-
|
| 239 |
-
accumulator = accumulator * a_s[:, None] * b_s
|
| 240 |
-
|
| 241 |
-
if C.dtype.element_ty == tl.bfloat16:
|
| 242 |
-
c = accumulator.to(tl.bfloat16)
|
| 243 |
-
elif C.dtype.element_ty == tl.float16:
|
| 244 |
-
c = accumulator.to(tl.float16)
|
| 245 |
-
else:
|
| 246 |
-
c = accumulator.to(tl.float32)
|
| 247 |
-
|
| 248 |
-
c_ptrs = C + stride_cm * offs_global_m[:, None] + stride_cn * offs_bn[None, :]
|
| 249 |
-
c_mask = row_mask[:, None]
|
| 250 |
-
tl.store(c_ptrs, c, mask=c_mask)
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
@triton_op("finegrained_fp8::w8a8_block_fp8_matmul_grouped", mutates_args=())
|
| 254 |
-
def _w8a8_block_fp8_matmul_grouped(
|
| 255 |
-
A: torch.Tensor,
|
| 256 |
-
B: torch.Tensor,
|
| 257 |
-
Bs: torch.Tensor,
|
| 258 |
-
offsets: torch.Tensor,
|
| 259 |
-
tokens_per_expert: torch.Tensor,
|
| 260 |
-
block_size: list[int],
|
| 261 |
-
) -> torch.Tensor:
|
| 262 |
-
"""Block-scale grouped FP8 matmul: C = A @ B.T per expert, with fused act quant.
|
| 263 |
-
|
| 264 |
-
A: (S, K) raw bf16/fp16 activations, sorted by expert
|
| 265 |
-
B: (E, N, K) FP8 expert weights
|
| 266 |
-
Bs: (E, N // block_n, K // block_k) per-block weight scales
|
| 267 |
-
"""
|
| 268 |
-
assert A.ndim == 2, f"A must be 2D (S, K), got ndim={A.ndim}"
|
| 269 |
-
assert A.is_contiguous(), "A must be contiguous"
|
| 270 |
-
assert B.ndim == 3, f"B must be 3D (E, N, K), got ndim={B.ndim}"
|
| 271 |
-
assert B.is_contiguous(), "B must be contiguous"
|
| 272 |
-
assert A.shape[1] == B.shape[2], (
|
| 273 |
-
f"K mismatch: A has K={A.shape[1]}, B has K={B.shape[2]}"
|
| 274 |
-
)
|
| 275 |
-
|
| 276 |
-
S, K = A.shape
|
| 277 |
-
E, N, _ = B.shape
|
| 278 |
-
|
| 279 |
-
assert len(block_size) == 2, (
|
| 280 |
-
f"block_size must be [block_n, block_k], got {block_size}"
|
| 281 |
-
)
|
| 282 |
-
block_n, block_k = block_size[0], block_size[1]
|
| 283 |
-
# MoE expert dimensions must be block-aligned; non-aligned N/K is not supported.
|
| 284 |
-
assert N % block_n == 0, f"N ({N}) must be divisible by block_n ({block_n})"
|
| 285 |
-
assert K % block_k == 0, f"K ({K}) must be divisible by block_k ({block_k})"
|
| 286 |
-
assert Bs.ndim == 3, (
|
| 287 |
-
f"Bs must be 3D (E, N//block_n, K//block_k), got ndim={Bs.ndim}"
|
| 288 |
-
)
|
| 289 |
-
assert Bs.shape == (E, N // block_n, K // block_k), (
|
| 290 |
-
f"Bs shape {tuple(Bs.shape)} != expected ({E}, {N // block_n}, {K // block_k})"
|
| 291 |
-
)
|
| 292 |
-
|
| 293 |
-
C = A.new_empty(S, N)
|
| 294 |
-
# Adaptive BLOCK_SIZE_M: match tile to average tokens per expert.
|
| 295 |
-
BLOCK_SIZE_M = min(max(triton.next_power_of_2((S + E - 1) // E), 16), 128)
|
| 296 |
-
tiles_per_expert = (tokens_per_expert + BLOCK_SIZE_M - 1) // BLOCK_SIZE_M
|
| 297 |
-
tile_offsets = torch.cumsum(tiles_per_expert, dim=0).to(torch.int32)
|
| 298 |
-
# Upper bound on M-tiles: sum_e ceil(M_e / BLOCK_M) <= ceil(S / BLOCK_M) + E.
|
| 299 |
-
# Programs beyond the real tile count exit immediately via the early-return
|
| 300 |
-
# guard inside the kernel. This is faster than syncing for the exact count
|
| 301 |
-
# and keeps the grid size data-independent (cuda-graph / torch.compile safe).
|
| 302 |
-
max_M_tiles = triton.cdiv(S, BLOCK_SIZE_M) + E
|
| 303 |
-
grid = (max_M_tiles, triton.cdiv(N, block_n))
|
| 304 |
-
with device_context(A.device):
|
| 305 |
-
wrap_triton(w8a8_block_fp8_matmul_grouped_kernel)[grid](
|
| 306 |
-
A,
|
| 307 |
-
B,
|
| 308 |
-
C,
|
| 309 |
-
Bs,
|
| 310 |
-
offsets,
|
| 311 |
-
tile_offsets,
|
| 312 |
-
S,
|
| 313 |
-
N,
|
| 314 |
-
K,
|
| 315 |
-
A.stride(0),
|
| 316 |
-
A.stride(1),
|
| 317 |
-
B.stride(0),
|
| 318 |
-
B.stride(2),
|
| 319 |
-
B.stride(1),
|
| 320 |
-
C.stride(0),
|
| 321 |
-
C.stride(1),
|
| 322 |
-
Bs.stride(0),
|
| 323 |
-
Bs.stride(2),
|
| 324 |
-
Bs.stride(1),
|
| 325 |
-
# Meta-parameters
|
| 326 |
-
NUM_EXPERTS=E,
|
| 327 |
-
BLOCK_SIZE_N=block_n,
|
| 328 |
-
BLOCK_SIZE_K=block_k,
|
| 329 |
-
BLOCK_SIZE_M=BLOCK_SIZE_M,
|
| 330 |
-
NUM_EXPERTS_BIT_LENGTH=E.bit_length(),
|
| 331 |
-
)
|
| 332 |
-
|
| 333 |
-
return C
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
@triton_op("finegrained_fp8::w8a8_tensor_fp8_matmul_grouped", mutates_args=())
|
| 337 |
-
def _w8a8_tensor_fp8_matmul_grouped(
|
| 338 |
-
A: torch.Tensor,
|
| 339 |
-
B: torch.Tensor,
|
| 340 |
-
Bs: torch.Tensor,
|
| 341 |
-
offsets: torch.Tensor,
|
| 342 |
-
tokens_per_expert: torch.Tensor,
|
| 343 |
-
) -> torch.Tensor:
|
| 344 |
-
"""Tensor-scale grouped FP8 matmul: C = A @ B.T per expert, with fused act quant.
|
| 345 |
-
|
| 346 |
-
A: (S, K) raw bf16/fp16 activations, sorted by expert
|
| 347 |
-
B: (E, N, K) FP8 expert weights
|
| 348 |
-
Bs: (E,) or (E, 1, 1) per-expert weight scales
|
| 349 |
-
"""
|
| 350 |
-
assert A.ndim == 2, f"A must be 2D (S, K), got ndim={A.ndim}"
|
| 351 |
-
assert A.is_contiguous(), "A must be contiguous"
|
| 352 |
-
assert B.ndim == 3, f"B must be 3D (E, N, K), got ndim={B.ndim}"
|
| 353 |
-
assert B.is_contiguous(), "B must be contiguous"
|
| 354 |
-
assert A.shape[1] == B.shape[2], (
|
| 355 |
-
f"K mismatch: A has K={A.shape[1]}, B has K={B.shape[2]}"
|
| 356 |
-
)
|
| 357 |
-
|
| 358 |
-
S, K = A.shape
|
| 359 |
-
E, N, _ = B.shape
|
| 360 |
-
|
| 361 |
-
# Normalize Bs to (E, 1, 1)
|
| 362 |
-
if Bs.ndim == 1:
|
| 363 |
-
assert Bs.shape[0] == E, f"Bs shape {tuple(Bs.shape)} != expected ({E},)"
|
| 364 |
-
Bs = Bs.reshape(E, 1, 1)
|
| 365 |
-
else:
|
| 366 |
-
assert Bs.shape == (E, 1, 1), (
|
| 367 |
-
f"Bs shape {tuple(Bs.shape)} != expected ({E}, 1, 1)"
|
| 368 |
-
)
|
| 369 |
-
|
| 370 |
-
BLOCK_SIZE_N = 128
|
| 371 |
-
BLOCK_SIZE_K = 128
|
| 372 |
-
C = A.new_empty(S, N)
|
| 373 |
-
qA, As = fp8_act_quant(A, K)
|
| 374 |
-
BLOCK_SIZE_M = min(max(triton.next_power_of_2((S + E - 1) // E), 16), 128)
|
| 375 |
-
tiles_per_expert = (tokens_per_expert + BLOCK_SIZE_M - 1) // BLOCK_SIZE_M
|
| 376 |
-
tile_offsets = torch.cumsum(tiles_per_expert, dim=0).to(torch.int32)
|
| 377 |
-
# Upper bound on M-tiles: sum_e ceil(M_e / BLOCK_M) <= ceil(S / BLOCK_M) + E.
|
| 378 |
-
# Programs beyond the real tile count exit immediately via the early-return
|
| 379 |
-
# guard inside the kernel. This is faster than syncing for the exact count
|
| 380 |
-
# and keeps the grid size data-independent (cuda-graph / torch.compile safe).
|
| 381 |
-
max_M_tiles = triton.cdiv(S, BLOCK_SIZE_M) + E
|
| 382 |
-
grid = (max_M_tiles, triton.cdiv(N, BLOCK_SIZE_N))
|
| 383 |
-
with device_context(A.device):
|
| 384 |
-
wrap_triton(w8a8_tensor_fp8_matmul_grouped_kernel)[grid](
|
| 385 |
-
qA,
|
| 386 |
-
B,
|
| 387 |
-
C,
|
| 388 |
-
As,
|
| 389 |
-
Bs,
|
| 390 |
-
offsets,
|
| 391 |
-
tile_offsets,
|
| 392 |
-
S,
|
| 393 |
-
N,
|
| 394 |
-
K,
|
| 395 |
-
qA.stride(0),
|
| 396 |
-
qA.stride(1),
|
| 397 |
-
B.stride(0),
|
| 398 |
-
B.stride(2),
|
| 399 |
-
B.stride(1),
|
| 400 |
-
C.stride(0),
|
| 401 |
-
C.stride(1),
|
| 402 |
-
As.stride(0),
|
| 403 |
-
Bs.stride(0),
|
| 404 |
-
# Meta-parameters
|
| 405 |
-
NUM_EXPERTS=E,
|
| 406 |
-
BLOCK_SIZE_N=BLOCK_SIZE_N,
|
| 407 |
-
BLOCK_SIZE_K=BLOCK_SIZE_K,
|
| 408 |
-
BLOCK_SIZE_M=BLOCK_SIZE_M,
|
| 409 |
-
NUM_EXPERTS_BIT_LENGTH=E.bit_length(),
|
| 410 |
-
)
|
| 411 |
-
|
| 412 |
-
return C
|
| 413 |
-
|
| 414 |
-
|
| 415 |
-
def w8a8_block_fp8_matmul_grouped(
|
| 416 |
-
A: torch.Tensor,
|
| 417 |
-
B: torch.Tensor,
|
| 418 |
-
Bs: torch.Tensor,
|
| 419 |
-
offsets: torch.Tensor,
|
| 420 |
-
tokens_per_expert: torch.Tensor,
|
| 421 |
-
block_size: list[int],
|
| 422 |
-
) -> torch.Tensor:
|
| 423 |
-
"""Block-scale grouped FP8 matmul with fused activation quantization.
|
| 424 |
-
|
| 425 |
-
A: (S, K) raw activations sorted by expert, bf16/fp16/fp32
|
| 426 |
-
B: (E, N, K) FP8 expert weights
|
| 427 |
-
Bs: (E, N // block_n, K // block_k) per-block weight scales
|
| 428 |
-
"""
|
| 429 |
-
return torch.ops.finegrained_fp8.w8a8_block_fp8_matmul_grouped(
|
| 430 |
-
A, B, Bs, offsets, tokens_per_expert, block_size
|
| 431 |
-
)
|
| 432 |
-
|
| 433 |
-
|
| 434 |
-
def w8a8_tensor_fp8_matmul_grouped(
|
| 435 |
-
A: torch.Tensor,
|
| 436 |
-
B: torch.Tensor,
|
| 437 |
-
Bs: torch.Tensor,
|
| 438 |
-
offsets: torch.Tensor,
|
| 439 |
-
tokens_per_expert: torch.Tensor,
|
| 440 |
-
) -> torch.Tensor:
|
| 441 |
-
"""Tensor-scale grouped FP8 matmul with fused activation quantization.
|
| 442 |
-
|
| 443 |
-
A: (S, K) raw activations sorted by expert, bf16/fp16/fp32
|
| 444 |
-
B: (E, N, K) FP8 expert weights
|
| 445 |
-
Bs: (E,) or (E, 1, 1) per-expert weight scales
|
| 446 |
-
"""
|
| 447 |
-
return torch.ops.finegrained_fp8.w8a8_tensor_fp8_matmul_grouped(
|
| 448 |
-
A, B, Bs, offsets, tokens_per_expert
|
| 449 |
-
)
|
| 450 |
-
|
| 451 |
-
|
| 452 |
-
def w8a8_fp8_matmul_grouped(
|
| 453 |
-
A: torch.Tensor,
|
| 454 |
-
B: torch.Tensor,
|
| 455 |
-
Bs: torch.Tensor,
|
| 456 |
-
offsets: torch.Tensor,
|
| 457 |
-
tokens_per_expert: torch.Tensor,
|
| 458 |
-
block_size: list[int] | None,
|
| 459 |
-
) -> torch.Tensor:
|
| 460 |
-
"""Unified grouped W8A8 FP8 matmul dispatcher.
|
| 461 |
-
|
| 462 |
-
Dispatch rules:
|
| 463 |
-
- tensor mode when ``block_size is None``
|
| 464 |
-
- tensor mode when ``block_size == [N, K]``
|
| 465 |
-
- otherwise block mode
|
| 466 |
-
|
| 467 |
-
Returns:
|
| 468 |
-
Output tensor ``[S, N]`` in the same dtype as ``A``, in expert-sorted order.
|
| 469 |
-
"""
|
| 470 |
-
if block_size is None or (
|
| 471 |
-
block_size[0] == B.size(1) and block_size[1] == B.size(2)
|
| 472 |
-
):
|
| 473 |
-
return w8a8_tensor_fp8_matmul_grouped(A, B, Bs, offsets, tokens_per_expert)
|
| 474 |
-
|
| 475 |
-
return w8a8_block_fp8_matmul_grouped(
|
| 476 |
-
A, B, Bs, offsets, tokens_per_expert, block_size
|
| 477 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
build/torch-xpu/matmul.py
DELETED
|
@@ -1,411 +0,0 @@
|
|
| 1 |
-
# Copyright 2025 The HuggingFace Inc. team. All rights reserved.
|
| 2 |
-
#
|
| 3 |
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
-
# you may not use this file except in compliance with the License.
|
| 5 |
-
# You may obtain a copy of the License at
|
| 6 |
-
#
|
| 7 |
-
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
-
#
|
| 9 |
-
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
-
# See the License for the specific language governing permissions and
|
| 13 |
-
# limitations under the License.
|
| 14 |
-
|
| 15 |
-
import torch
|
| 16 |
-
import triton
|
| 17 |
-
import triton.language as tl
|
| 18 |
-
from torch.library import triton_op, wrap_triton
|
| 19 |
-
|
| 20 |
-
from .utils import device_context
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
# Adapted from https://github.com/sgl-project/sglang/blob/main/python/sglang/srt/layers/quantization/fp8_kernel.py
|
| 24 |
-
@triton.autotune(
|
| 25 |
-
configs=[
|
| 26 |
-
triton.Config({}, num_warps=w, num_stages=s)
|
| 27 |
-
for w in [2, 4, 8, 16]
|
| 28 |
-
for s in [2, 3, 4]
|
| 29 |
-
],
|
| 30 |
-
key=["N", "K", "BLOCK_SIZE_M"],
|
| 31 |
-
)
|
| 32 |
-
@triton.jit
|
| 33 |
-
def w8a8_block_fp8_matmul_kernel(
|
| 34 |
-
# Pointers to inputs and output
|
| 35 |
-
A,
|
| 36 |
-
B,
|
| 37 |
-
C,
|
| 38 |
-
As,
|
| 39 |
-
Bs,
|
| 40 |
-
# Shape for matmul
|
| 41 |
-
M,
|
| 42 |
-
N,
|
| 43 |
-
K,
|
| 44 |
-
stride_am,
|
| 45 |
-
stride_ak,
|
| 46 |
-
stride_bk,
|
| 47 |
-
stride_bn,
|
| 48 |
-
stride_cm,
|
| 49 |
-
stride_cn,
|
| 50 |
-
stride_as_m,
|
| 51 |
-
stride_as_k,
|
| 52 |
-
stride_bs_k,
|
| 53 |
-
stride_bs_n,
|
| 54 |
-
# Meta-parameters
|
| 55 |
-
BLOCK_SIZE_M: tl.constexpr,
|
| 56 |
-
BLOCK_SIZE_N: tl.constexpr,
|
| 57 |
-
BLOCK_SIZE_K: tl.constexpr,
|
| 58 |
-
GROUP_SIZE_M: tl.constexpr,
|
| 59 |
-
):
|
| 60 |
-
"""Block-scale FP8 GEMM kernel.
|
| 61 |
-
|
| 62 |
-
Computes ``C = A @ B.T`` with block-wise activation/weight scales.
|
| 63 |
-
Uses a 2D grid with swizzle for L2 cache locality on B tiles.
|
| 64 |
-
"""
|
| 65 |
-
pid_m = tl.program_id(axis=0)
|
| 66 |
-
pid_n = tl.program_id(axis=1)
|
| 67 |
-
num_pid_m = tl.cdiv(M, BLOCK_SIZE_M)
|
| 68 |
-
num_pid_n = tl.cdiv(N, BLOCK_SIZE_N)
|
| 69 |
-
pid_m, pid_n = tl.swizzle2d(pid_m, pid_n, num_pid_m, num_pid_n, GROUP_SIZE_M)
|
| 70 |
-
|
| 71 |
-
offs_am = (pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)) % M
|
| 72 |
-
offs_bn = (pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)) % N
|
| 73 |
-
offs_k = tl.arange(0, BLOCK_SIZE_K)
|
| 74 |
-
a_ptrs = A + (offs_am[:, None] * stride_am + offs_k[None, :] * stride_ak)
|
| 75 |
-
b_ptrs = B + (offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn)
|
| 76 |
-
|
| 77 |
-
as_ptrs = As + offs_am * stride_as_m
|
| 78 |
-
offs_bsn = offs_bn // BLOCK_SIZE_N
|
| 79 |
-
bs_ptrs = Bs + offs_bsn * stride_bs_n
|
| 80 |
-
|
| 81 |
-
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
| 82 |
-
for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
|
| 83 |
-
k_remaining = K - k * BLOCK_SIZE_K
|
| 84 |
-
a = tl.load(a_ptrs, mask=offs_k[None, :] < k_remaining, other=0.0)
|
| 85 |
-
b = tl.load(b_ptrs, mask=offs_k[:, None] < k_remaining, other=0.0)
|
| 86 |
-
|
| 87 |
-
a_s = tl.load(as_ptrs + k * stride_as_k)
|
| 88 |
-
b_s = tl.load(bs_ptrs + k * stride_bs_k)
|
| 89 |
-
|
| 90 |
-
accumulator += tl.dot(a, b) * a_s[:, None] * b_s[None, :]
|
| 91 |
-
a_ptrs += BLOCK_SIZE_K * stride_ak
|
| 92 |
-
b_ptrs += BLOCK_SIZE_K * stride_bk
|
| 93 |
-
|
| 94 |
-
if C.dtype.element_ty == tl.bfloat16:
|
| 95 |
-
c = accumulator.to(tl.bfloat16)
|
| 96 |
-
elif C.dtype.element_ty == tl.float16:
|
| 97 |
-
c = accumulator.to(tl.float16)
|
| 98 |
-
else:
|
| 99 |
-
c = accumulator.to(tl.float32)
|
| 100 |
-
|
| 101 |
-
offs_cm = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
|
| 102 |
-
offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
|
| 103 |
-
c_ptrs = C + stride_cm * offs_cm[:, None] + stride_cn * offs_cn[None, :]
|
| 104 |
-
c_mask = (offs_cm[:, None] < M) & (offs_cn[None, :] < N)
|
| 105 |
-
tl.store(c_ptrs, c, mask=c_mask)
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
@triton.autotune(
|
| 109 |
-
configs=[
|
| 110 |
-
triton.Config({}, num_warps=w, num_stages=s)
|
| 111 |
-
for w in [2, 4, 8, 16]
|
| 112 |
-
for s in [2, 3, 4]
|
| 113 |
-
],
|
| 114 |
-
key=["N", "K", "BLOCK_SIZE_M"],
|
| 115 |
-
)
|
| 116 |
-
@triton.jit
|
| 117 |
-
def w8a8_tensor_fp8_matmul_kernel(
|
| 118 |
-
A,
|
| 119 |
-
B,
|
| 120 |
-
C,
|
| 121 |
-
As,
|
| 122 |
-
Bs,
|
| 123 |
-
M,
|
| 124 |
-
N,
|
| 125 |
-
K,
|
| 126 |
-
stride_am,
|
| 127 |
-
stride_ak,
|
| 128 |
-
stride_bk,
|
| 129 |
-
stride_bn,
|
| 130 |
-
stride_cm,
|
| 131 |
-
stride_cn,
|
| 132 |
-
stride_as_m,
|
| 133 |
-
BLOCK_SIZE_M: tl.constexpr,
|
| 134 |
-
BLOCK_SIZE_N: tl.constexpr,
|
| 135 |
-
BLOCK_SIZE_K: tl.constexpr,
|
| 136 |
-
GROUP_SIZE_M: tl.constexpr,
|
| 137 |
-
):
|
| 138 |
-
"""Tensor-scale FP8 GEMM kernel.
|
| 139 |
-
|
| 140 |
-
Computes ``C = A @ B.T`` with one activation scale per row and one
|
| 141 |
-
weight scale for the full matrix.
|
| 142 |
-
Uses a 2D grid with swizzle for L2 cache locality on B tiles.
|
| 143 |
-
"""
|
| 144 |
-
pid_m = tl.program_id(axis=0)
|
| 145 |
-
pid_n = tl.program_id(axis=1)
|
| 146 |
-
num_pid_m = tl.cdiv(M, BLOCK_SIZE_M)
|
| 147 |
-
num_pid_n = tl.cdiv(N, BLOCK_SIZE_N)
|
| 148 |
-
pid_m, pid_n = tl.swizzle2d(pid_m, pid_n, num_pid_m, num_pid_n, GROUP_SIZE_M)
|
| 149 |
-
|
| 150 |
-
offs_am = (pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)) % M
|
| 151 |
-
offs_bn = (pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)) % N
|
| 152 |
-
offs_k = tl.arange(0, BLOCK_SIZE_K)
|
| 153 |
-
|
| 154 |
-
a_ptrs = A + offs_am[:, None] * stride_am + offs_k[None, :] * stride_ak
|
| 155 |
-
b_ptrs = B + offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn
|
| 156 |
-
|
| 157 |
-
a_s = tl.load(As + offs_am * stride_as_m)
|
| 158 |
-
b_s = tl.load(Bs)
|
| 159 |
-
|
| 160 |
-
# Accumulate raw dot products, apply scales once after the loop.
|
| 161 |
-
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
| 162 |
-
for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
|
| 163 |
-
k_remaining = K - k * BLOCK_SIZE_K
|
| 164 |
-
a = tl.load(a_ptrs, mask=offs_k[None, :] < k_remaining, other=0.0)
|
| 165 |
-
b = tl.load(b_ptrs, mask=offs_k[:, None] < k_remaining, other=0.0)
|
| 166 |
-
accumulator += tl.dot(a, b)
|
| 167 |
-
a_ptrs += BLOCK_SIZE_K * stride_ak
|
| 168 |
-
b_ptrs += BLOCK_SIZE_K * stride_bk
|
| 169 |
-
|
| 170 |
-
accumulator = accumulator * a_s[:, None] * b_s
|
| 171 |
-
|
| 172 |
-
if C.dtype.element_ty == tl.bfloat16:
|
| 173 |
-
c = accumulator.to(tl.bfloat16)
|
| 174 |
-
elif C.dtype.element_ty == tl.float16:
|
| 175 |
-
c = accumulator.to(tl.float16)
|
| 176 |
-
else:
|
| 177 |
-
c = accumulator.to(tl.float32)
|
| 178 |
-
|
| 179 |
-
offs_cm = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
|
| 180 |
-
offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
|
| 181 |
-
c_ptrs = C + stride_cm * offs_cm[:, None] + stride_cn * offs_cn[None, :]
|
| 182 |
-
c_mask = (offs_cm[:, None] < M) & (offs_cn[None, :] < N)
|
| 183 |
-
tl.store(c_ptrs, c, mask=c_mask)
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
@triton_op("finegrained_fp8::w8a8_block_fp8_matmul", mutates_args=())
|
| 187 |
-
def _w8a8_block_fp8_matmul(
|
| 188 |
-
A: torch.Tensor,
|
| 189 |
-
B: torch.Tensor,
|
| 190 |
-
As: torch.Tensor,
|
| 191 |
-
Bs: torch.Tensor,
|
| 192 |
-
block_size: list[int],
|
| 193 |
-
output_dtype: torch.dtype = torch.float32,
|
| 194 |
-
) -> torch.Tensor:
|
| 195 |
-
"""Block-scale FP8 matmul: C = A @ B.T with per-block scales.
|
| 196 |
-
|
| 197 |
-
As: (M, K // block_k) — per-token-group activation scales
|
| 198 |
-
Bs: (N // block_n, K // block_k) — per-block weight scales
|
| 199 |
-
"""
|
| 200 |
-
assert len(block_size) == 2, (
|
| 201 |
-
f"block_size must be [block_n, block_k], got {block_size}"
|
| 202 |
-
)
|
| 203 |
-
block_n, block_k = block_size[0], block_size[1]
|
| 204 |
-
|
| 205 |
-
assert A.shape[-1] == B.shape[-1], (
|
| 206 |
-
f"K mismatch: A has K={A.shape[-1]}, B has K={B.shape[-1]}"
|
| 207 |
-
)
|
| 208 |
-
assert A.is_contiguous(), "A must be contiguous"
|
| 209 |
-
assert B.ndim == 2, f"B must be 2D (N, K), got ndim={B.ndim}"
|
| 210 |
-
assert B.is_contiguous(), "B must be contiguous"
|
| 211 |
-
|
| 212 |
-
N, K = B.shape
|
| 213 |
-
M = A.numel() // A.shape[-1]
|
| 214 |
-
|
| 215 |
-
assert As.ndim >= 2, f"As must be at least 2D, got ndim={As.ndim}"
|
| 216 |
-
assert As.shape[-1] == triton.cdiv(K, block_k), (
|
| 217 |
-
f"As last dim {As.shape[-1]} != expected {triton.cdiv(K, block_k)} (cdiv(K={K}, block_k={block_k}))"
|
| 218 |
-
)
|
| 219 |
-
assert Bs.ndim == 2, f"Bs must be 2D (N//block_n, K//block_k), got ndim={Bs.ndim}"
|
| 220 |
-
assert Bs.shape == (triton.cdiv(N, block_n), triton.cdiv(K, block_k)), (
|
| 221 |
-
f"Bs shape {tuple(Bs.shape)} != expected ({triton.cdiv(N, block_n)}, {triton.cdiv(K, block_k)})"
|
| 222 |
-
)
|
| 223 |
-
|
| 224 |
-
BLOCK_SIZE_K = block_k
|
| 225 |
-
BLOCK_SIZE_N = block_n
|
| 226 |
-
C_shape = A.shape[:-1] + (N,)
|
| 227 |
-
C = A.new_empty(C_shape, dtype=output_dtype)
|
| 228 |
-
# Adaptive BLOCK_SIZE_M: smallest power-of-2 >= M, floored at 16, capped at 128.
|
| 229 |
-
# Matches the WGMMA tile to the actual row count — smaller tiles use less
|
| 230 |
-
# register pressure and a better-matched FP8 WGMMA instruction, improving
|
| 231 |
-
# both accuracy and performance for small M (decode).
|
| 232 |
-
BLOCK_SIZE_M = min(max(triton.next_power_of_2(M), 16), 128)
|
| 233 |
-
grid = (triton.cdiv(M, BLOCK_SIZE_M), triton.cdiv(N, BLOCK_SIZE_N))
|
| 234 |
-
with device_context(A.device):
|
| 235 |
-
wrap_triton(w8a8_block_fp8_matmul_kernel)[grid](
|
| 236 |
-
A,
|
| 237 |
-
B,
|
| 238 |
-
C,
|
| 239 |
-
As,
|
| 240 |
-
Bs,
|
| 241 |
-
M,
|
| 242 |
-
N,
|
| 243 |
-
K,
|
| 244 |
-
A.stride(-2),
|
| 245 |
-
A.stride(-1),
|
| 246 |
-
B.stride(1),
|
| 247 |
-
B.stride(0),
|
| 248 |
-
C.stride(-2),
|
| 249 |
-
C.stride(-1),
|
| 250 |
-
As.stride(-2),
|
| 251 |
-
As.stride(-1),
|
| 252 |
-
Bs.stride(1),
|
| 253 |
-
Bs.stride(0),
|
| 254 |
-
# Meta-parameters
|
| 255 |
-
BLOCK_SIZE_M=BLOCK_SIZE_M,
|
| 256 |
-
BLOCK_SIZE_N=BLOCK_SIZE_N,
|
| 257 |
-
BLOCK_SIZE_K=BLOCK_SIZE_K,
|
| 258 |
-
GROUP_SIZE_M=8,
|
| 259 |
-
)
|
| 260 |
-
|
| 261 |
-
return C
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
@triton_op("finegrained_fp8::w8a8_tensor_fp8_matmul", mutates_args=())
|
| 265 |
-
def _w8a8_tensor_fp8_matmul(
|
| 266 |
-
A: torch.Tensor,
|
| 267 |
-
B: torch.Tensor,
|
| 268 |
-
As: torch.Tensor,
|
| 269 |
-
Bs: torch.Tensor,
|
| 270 |
-
output_dtype: torch.dtype = torch.float32,
|
| 271 |
-
) -> torch.Tensor:
|
| 272 |
-
"""Tensor-scale FP8 matmul: C = A @ B.T with per-row / per-tensor scales.
|
| 273 |
-
|
| 274 |
-
As: scalar, (M,), or (M, 1) — per-row activation scales
|
| 275 |
-
Bs: scalar, (1,), or (1, 1) — single weight scale
|
| 276 |
-
"""
|
| 277 |
-
assert A.shape[-1] == B.shape[-1], (
|
| 278 |
-
f"K mismatch: A has K={A.shape[-1]}, B has K={B.shape[-1]}"
|
| 279 |
-
)
|
| 280 |
-
assert A.is_contiguous(), "A must be contiguous"
|
| 281 |
-
assert B.ndim == 2, f"B must be 2D (N, K), got ndim={B.ndim}"
|
| 282 |
-
assert B.is_contiguous(), "B must be contiguous"
|
| 283 |
-
|
| 284 |
-
N, K = B.shape
|
| 285 |
-
M = A.numel() // A.shape[-1]
|
| 286 |
-
|
| 287 |
-
# Normalize As to (M,)
|
| 288 |
-
if As.numel() == 1:
|
| 289 |
-
As = As.reshape(1).expand(M).contiguous()
|
| 290 |
-
elif As.ndim == 2:
|
| 291 |
-
As = As.reshape(M)
|
| 292 |
-
assert As.ndim == 1 and As.shape[0] == M, (
|
| 293 |
-
f"As must be scalar, (M,), or (M,1) with M={M}, got {tuple(As.shape)}"
|
| 294 |
-
)
|
| 295 |
-
|
| 296 |
-
# Normalize Bs to (1,)
|
| 297 |
-
assert Bs.numel() == 1, f"Bs must be scalar or (1,), got {tuple(Bs.shape)}"
|
| 298 |
-
Bs = Bs.reshape(1)
|
| 299 |
-
|
| 300 |
-
BLOCK_SIZE_N = 128
|
| 301 |
-
BLOCK_SIZE_K = 128
|
| 302 |
-
C_shape = A.shape[:-1] + (N,)
|
| 303 |
-
C = A.new_empty(C_shape, dtype=output_dtype)
|
| 304 |
-
BLOCK_SIZE_M = min(max(triton.next_power_of_2(M), 16), 128)
|
| 305 |
-
grid = (triton.cdiv(M, BLOCK_SIZE_M), triton.cdiv(N, BLOCK_SIZE_N))
|
| 306 |
-
with device_context(A.device):
|
| 307 |
-
wrap_triton(w8a8_tensor_fp8_matmul_kernel)[grid](
|
| 308 |
-
A,
|
| 309 |
-
B,
|
| 310 |
-
C,
|
| 311 |
-
As,
|
| 312 |
-
Bs,
|
| 313 |
-
M,
|
| 314 |
-
N,
|
| 315 |
-
K,
|
| 316 |
-
A.stride(-2),
|
| 317 |
-
A.stride(-1),
|
| 318 |
-
B.stride(1),
|
| 319 |
-
B.stride(0),
|
| 320 |
-
C.stride(-2),
|
| 321 |
-
C.stride(-1),
|
| 322 |
-
As.stride(0),
|
| 323 |
-
# Meta-parameters
|
| 324 |
-
BLOCK_SIZE_M=BLOCK_SIZE_M,
|
| 325 |
-
BLOCK_SIZE_N=BLOCK_SIZE_N,
|
| 326 |
-
BLOCK_SIZE_K=BLOCK_SIZE_K,
|
| 327 |
-
GROUP_SIZE_M=8,
|
| 328 |
-
)
|
| 329 |
-
|
| 330 |
-
return C
|
| 331 |
-
|
| 332 |
-
|
| 333 |
-
def w8a8_block_fp8_matmul(
|
| 334 |
-
A: torch.Tensor,
|
| 335 |
-
B: torch.Tensor,
|
| 336 |
-
As: torch.Tensor,
|
| 337 |
-
Bs: torch.Tensor,
|
| 338 |
-
block_size: list[int],
|
| 339 |
-
output_dtype: torch.dtype = torch.float32,
|
| 340 |
-
) -> torch.Tensor:
|
| 341 |
-
"""Block-wise W8A8 FP8 matrix multiplication.
|
| 342 |
-
|
| 343 |
-
Computes ``C = A @ B.T`` where both operands are pre-quantized to
|
| 344 |
-
``float8_e4m3fn`` with per-block scales, and accumulates in float32
|
| 345 |
-
before casting to ``output_dtype``.
|
| 346 |
-
|
| 347 |
-
Args:
|
| 348 |
-
A: Quantized activation tensor ``[M, K]`` in ``float8_e4m3fn``.
|
| 349 |
-
B: Quantized weight tensor ``[N, K]`` in ``float8_e4m3fn``.
|
| 350 |
-
As: Per-token-group activation scales ``[M, K // block_size[1]]``.
|
| 351 |
-
Bs: Per-block weight scales ``[N // block_size[0], K // block_size[1]]``.
|
| 352 |
-
block_size: ``[block_n, block_k]`` quantization block dimensions, e.g. ``[128, 128]``.
|
| 353 |
-
output_dtype: dtype of the returned tensor (default: ``torch.float32``).
|
| 354 |
-
|
| 355 |
-
Returns:
|
| 356 |
-
Output tensor ``[M, N]`` in ``output_dtype``.
|
| 357 |
-
"""
|
| 358 |
-
return torch.ops.finegrained_fp8.w8a8_block_fp8_matmul(
|
| 359 |
-
A, B, As, Bs, block_size, output_dtype
|
| 360 |
-
)
|
| 361 |
-
|
| 362 |
-
|
| 363 |
-
def w8a8_tensor_fp8_matmul(
|
| 364 |
-
A: torch.Tensor,
|
| 365 |
-
B: torch.Tensor,
|
| 366 |
-
As: torch.Tensor,
|
| 367 |
-
Bs: torch.Tensor,
|
| 368 |
-
output_dtype: torch.dtype = torch.float32,
|
| 369 |
-
) -> torch.Tensor:
|
| 370 |
-
"""Tensor-scale W8A8 FP8 matrix multiplication.
|
| 371 |
-
|
| 372 |
-
Computes ``C = A @ B.T`` in tensor-scale mode using pre-quantized FP8
|
| 373 |
-
activations/weights and tensor scales.
|
| 374 |
-
|
| 375 |
-
Args:
|
| 376 |
-
A: Quantized activation tensor ``[M, K]`` in ``float8_e4m3fn``.
|
| 377 |
-
B: Quantized weight tensor ``[N, K]`` in ``float8_e4m3fn``.
|
| 378 |
-
As: Per-row activation scales ``[M]``.
|
| 379 |
-
Bs: Single weight scale, scalar or ``[1]``.
|
| 380 |
-
output_dtype: dtype of the returned tensor.
|
| 381 |
-
|
| 382 |
-
Returns:
|
| 383 |
-
Output tensor ``[M, N]`` in ``output_dtype``.
|
| 384 |
-
"""
|
| 385 |
-
return torch.ops.finegrained_fp8.w8a8_tensor_fp8_matmul(A, B, As, Bs, output_dtype)
|
| 386 |
-
|
| 387 |
-
|
| 388 |
-
def w8a8_fp8_matmul(
|
| 389 |
-
A: torch.Tensor,
|
| 390 |
-
B: torch.Tensor,
|
| 391 |
-
As: torch.Tensor,
|
| 392 |
-
Bs: torch.Tensor,
|
| 393 |
-
block_size: list[int] | None,
|
| 394 |
-
output_dtype: torch.dtype = torch.float32,
|
| 395 |
-
) -> torch.Tensor:
|
| 396 |
-
"""Unified W8A8 FP8 matmul dispatcher.
|
| 397 |
-
|
| 398 |
-
Dispatch rules:
|
| 399 |
-
- tensor mode when ``block_size is None``
|
| 400 |
-
- tensor mode when ``block_size == [N, K]``
|
| 401 |
-
- otherwise block mode
|
| 402 |
-
|
| 403 |
-
Returns:
|
| 404 |
-
Output tensor ``[M, N]`` in ``output_dtype``.
|
| 405 |
-
"""
|
| 406 |
-
if block_size is None or (
|
| 407 |
-
block_size[0] == B.size(0) and block_size[1] == B.size(1)
|
| 408 |
-
):
|
| 409 |
-
return w8a8_tensor_fp8_matmul(A, B, As, Bs, output_dtype)
|
| 410 |
-
|
| 411 |
-
return w8a8_block_fp8_matmul(A, B, As, Bs, block_size, output_dtype)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
build/torch-xpu/metadata.json
DELETED
|
@@ -1,10 +0,0 @@
|
|
| 1 |
-
{
|
| 2 |
-
"name": "finegrained-fp8",
|
| 3 |
-
"id": "_finegrained_fp8_xpu_7c5619e",
|
| 4 |
-
"version": 1,
|
| 5 |
-
"license": "Apache-2.0",
|
| 6 |
-
"python-depends": [],
|
| 7 |
-
"backend": {
|
| 8 |
-
"type": "xpu"
|
| 9 |
-
}
|
| 10 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
build/torch-xpu/utils.py
DELETED
|
@@ -1,13 +0,0 @@
|
|
| 1 |
-
import torch
|
| 2 |
-
from contextlib import contextmanager
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
@contextmanager
|
| 6 |
-
def device_context(device: torch.device):
|
| 7 |
-
"""Context manager that sets the active device for any backend (cuda, xpu, etc.)."""
|
| 8 |
-
backend = getattr(torch, device.type, None)
|
| 9 |
-
if backend is not None and hasattr(backend, "device"):
|
| 10 |
-
with backend.device(device):
|
| 11 |
-
yield
|
| 12 |
-
else:
|
| 13 |
-
yield
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|