Kernels:
Trusted publisher
Uploaded using `kernel-builder`.
Browse files- build/torch-rocm/__init__.py +14 -0
- build/torch-rocm/_ops.py +38 -0
- build/torch-rocm/batched.py +773 -0
- build/torch-rocm/finegrained_fp8/__init__.py +26 -0
- build/torch-rocm/grouped.py +951 -0
- build/torch-rocm/matmul.py +941 -0
- build/torch-rocm/metadata.json +22 -0
- build/torch-rocm/metadata.json.sigstore +1 -0
- build/torch-rocm/utils.py +269 -0
build/torch-rocm/__init__.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
|
| 6 |
+
__all__ = [
|
| 7 |
+
"fp8_act_quant",
|
| 8 |
+
# 2D matmul
|
| 9 |
+
"matmul_2d",
|
| 10 |
+
# Batched matmul
|
| 11 |
+
"matmul_batched",
|
| 12 |
+
# Grouped matmul
|
| 13 |
+
"matmul_grouped",
|
| 14 |
+
]
|
build/torch-rocm/_ops.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 = "846165b"
|
| 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/batched.py
ADDED
|
@@ -0,0 +1,773 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 ._ops import add_op_namespace_prefix, ops
|
| 21 |
+
from .utils import (
|
| 22 |
+
MX_SCALE_GROUP_K,
|
| 23 |
+
NIBBLES_PER_BYTE,
|
| 24 |
+
decode_ue8m0_scale,
|
| 25 |
+
device_context,
|
| 26 |
+
mx_act_quant_inline,
|
| 27 |
+
fp8_act_quant,
|
| 28 |
+
fp8_act_quant_inline,
|
| 29 |
+
get_accelerator_autotuning_configs,
|
| 30 |
+
is_mxfp4,
|
| 31 |
+
is_mxfp8,
|
| 32 |
+
ue8m0_as_uint8,
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
@triton.jit
|
| 37 |
+
def _expert_setup(
|
| 38 |
+
A,
|
| 39 |
+
B,
|
| 40 |
+
C,
|
| 41 |
+
Bs,
|
| 42 |
+
ExpertIds,
|
| 43 |
+
stride_am,
|
| 44 |
+
stride_be,
|
| 45 |
+
stride_cm,
|
| 46 |
+
stride_bs_e,
|
| 47 |
+
stride_eid,
|
| 48 |
+
):
|
| 49 |
+
"""Per-(row, expert) prologue shared by the batched kernels: read the program
|
| 50 |
+
ids, look up the routed expert, and advance the A/B/C/Bs base pointers to this
|
| 51 |
+
row's slice. Returns ``(batch_id, pid_n, expert_id, A, B, C, Bs)``.
|
| 52 |
+
|
| 53 |
+
The caller must early-return on the EP sentinel (``expert_id >= NUM_EXPERTS``)
|
| 54 |
+
before any load — the pointer arithmetic itself is harmless, only the loads on a
|
| 55 |
+
non-local expert would be out of bounds."""
|
| 56 |
+
batch_id = tl.program_id(axis=0)
|
| 57 |
+
pid_n = tl.program_id(axis=1)
|
| 58 |
+
# Cast to int64 to prevent overflow on expert_id * stride_be.
|
| 59 |
+
expert_id = tl.load(ExpertIds + batch_id * stride_eid).to(tl.int64)
|
| 60 |
+
A = A + batch_id * stride_am
|
| 61 |
+
B = B + expert_id * stride_be
|
| 62 |
+
C = C + batch_id * stride_cm
|
| 63 |
+
Bs = Bs + expert_id * stride_bs_e
|
| 64 |
+
return batch_id, pid_n, expert_id, A, B, C, Bs
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
@triton.jit
|
| 68 |
+
def _store_row(
|
| 69 |
+
C,
|
| 70 |
+
accumulator,
|
| 71 |
+
pid_n,
|
| 72 |
+
stride_cn,
|
| 73 |
+
BLOCK_SIZE_M: tl.constexpr,
|
| 74 |
+
BLOCK_SIZE_N: tl.constexpr,
|
| 75 |
+
):
|
| 76 |
+
"""Output epilogue shared by the batched kernels (``C`` already advanced to the
|
| 77 |
+
row). The fake-batch trick aliases all ``BLOCK_SIZE_M`` lanes to the same C row,
|
| 78 |
+
so a plain store would issue ``BLOCK_SIZE_M`` duplicate-address writes — benign on
|
| 79 |
+
NVIDIA WGMMA (last-write-wins of identical bytes) but hardware-undefined on Intel
|
| 80 |
+
XPU, where it corrupts the output. Mask so only lane 0 stores; the accumulator
|
| 81 |
+
rows are mathematically identical (same A row × same B), so lane 0 is correct."""
|
| 82 |
+
c = accumulator.to(C.dtype.element_ty)
|
| 83 |
+
offs_cm = tl.arange(0, BLOCK_SIZE_M)
|
| 84 |
+
offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
|
| 85 |
+
c_ptrs = C + offs_cm[:, None] * 0 + stride_cn * offs_cn[None, :]
|
| 86 |
+
tl.store(c_ptrs, c, mask=(offs_cm == 0)[:, None])
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
@triton.autotune(
|
| 90 |
+
configs=get_accelerator_autotuning_configs(),
|
| 91 |
+
key=["N", "K"],
|
| 92 |
+
)
|
| 93 |
+
@triton.jit
|
| 94 |
+
def w8a8_block_dynamic_fp8_matmul_batched_kernel(
|
| 95 |
+
A, # (S, K) raw BF16/FP16 activations
|
| 96 |
+
B, # (E, N, K) FP8 weight matrices
|
| 97 |
+
C, # (S, N) output
|
| 98 |
+
Bs, # (E, N // BLOCK_SIZE_N, K // BLOCK_SIZE_K) weight scales
|
| 99 |
+
ExpertIds, # (S,) — which expert each batch element routes to
|
| 100 |
+
# Shape
|
| 101 |
+
S,
|
| 102 |
+
N,
|
| 103 |
+
K,
|
| 104 |
+
# Strides
|
| 105 |
+
stride_am,
|
| 106 |
+
stride_ak,
|
| 107 |
+
stride_be,
|
| 108 |
+
stride_bk,
|
| 109 |
+
stride_bn,
|
| 110 |
+
stride_cm,
|
| 111 |
+
stride_cn,
|
| 112 |
+
stride_bs_e,
|
| 113 |
+
stride_bs_k,
|
| 114 |
+
stride_bs_n,
|
| 115 |
+
stride_eid,
|
| 116 |
+
# Meta-parameters
|
| 117 |
+
BLOCK_SIZE_M: tl.constexpr,
|
| 118 |
+
BLOCK_SIZE_N: tl.constexpr,
|
| 119 |
+
BLOCK_SIZE_K: tl.constexpr,
|
| 120 |
+
NUM_EXPERTS: tl.constexpr,
|
| 121 |
+
):
|
| 122 |
+
"""Block-scale batched FP8 expert matmul kernel.
|
| 123 |
+
|
| 124 |
+
Each program handles one routed token row and one N-tile, looks up the
|
| 125 |
+
owning expert from ``ExpertIds``, and applies fused activation quantization.
|
| 126 |
+
"""
|
| 127 |
+
_, pid_n, expert_id, A, B, C, Bs = _expert_setup(
|
| 128 |
+
A, B, C, Bs, ExpertIds, stride_am, stride_be, stride_cm, stride_bs_e, stride_eid
|
| 129 |
+
)
|
| 130 |
+
# EP sentinel: row routed to a non-local expert; output is left uninit.
|
| 131 |
+
if expert_id >= NUM_EXPERTS:
|
| 132 |
+
return
|
| 133 |
+
|
| 134 |
+
offs_bn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
|
| 135 |
+
offs_k = tl.arange(0, BLOCK_SIZE_K)
|
| 136 |
+
a_ptrs = A + tl.arange(0, BLOCK_SIZE_M)[:, None] * 0 + offs_k[None, :] * stride_ak
|
| 137 |
+
b_ptrs = B + (offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn)
|
| 138 |
+
|
| 139 |
+
bs_ptrs = Bs + pid_n * stride_bs_n
|
| 140 |
+
|
| 141 |
+
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
| 142 |
+
for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
|
| 143 |
+
a_raw = tl.load(a_ptrs).to(tl.float32)
|
| 144 |
+
a, a_s = fp8_act_quant_inline(a_raw)
|
| 145 |
+
b = tl.load(b_ptrs)
|
| 146 |
+
b_s = decode_ue8m0_scale(tl.load(bs_ptrs))
|
| 147 |
+
accumulator += tl.dot(a, b) * a_s[:, None] * b_s[None, :]
|
| 148 |
+
a_ptrs += BLOCK_SIZE_K * stride_ak
|
| 149 |
+
b_ptrs += BLOCK_SIZE_K * stride_bk
|
| 150 |
+
bs_ptrs += stride_bs_k
|
| 151 |
+
|
| 152 |
+
_store_row(C, accumulator, pid_n, stride_cn, BLOCK_SIZE_M, BLOCK_SIZE_N)
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
@triton.autotune(
|
| 156 |
+
configs=get_accelerator_autotuning_configs(with_block_sizes=True),
|
| 157 |
+
key=["N", "K"],
|
| 158 |
+
)
|
| 159 |
+
@triton.jit
|
| 160 |
+
def w8a8_tensor_dynamic_fp8_matmul_batched_kernel(
|
| 161 |
+
A, # (S, K) pre-quantized FP8 activations
|
| 162 |
+
B, # (E, N, K) FP8 weight matrices
|
| 163 |
+
C, # (S, N) output
|
| 164 |
+
As, # (S,) per-token activation scales
|
| 165 |
+
Bs, # (E, 1, 1) per-tensor weight scales
|
| 166 |
+
ExpertIds, # (S,) — which expert each batch element routes to
|
| 167 |
+
# Shape
|
| 168 |
+
S,
|
| 169 |
+
N,
|
| 170 |
+
K,
|
| 171 |
+
# Strides
|
| 172 |
+
stride_am,
|
| 173 |
+
stride_ak,
|
| 174 |
+
stride_be,
|
| 175 |
+
stride_bk,
|
| 176 |
+
stride_bn,
|
| 177 |
+
stride_cm,
|
| 178 |
+
stride_cn,
|
| 179 |
+
stride_as_m,
|
| 180 |
+
stride_bs_e,
|
| 181 |
+
stride_eid,
|
| 182 |
+
# Meta-parameters
|
| 183 |
+
BLOCK_SIZE_M: tl.constexpr,
|
| 184 |
+
BLOCK_SIZE_N: tl.constexpr,
|
| 185 |
+
BLOCK_SIZE_K: tl.constexpr,
|
| 186 |
+
NUM_EXPERTS: tl.constexpr,
|
| 187 |
+
):
|
| 188 |
+
"""Tensor-scale batched FP8 expert matmul kernel.
|
| 189 |
+
|
| 190 |
+
Activations are already quantized; the kernel applies per-token activation
|
| 191 |
+
scales and per-expert tensor weight scales.
|
| 192 |
+
"""
|
| 193 |
+
batch_id, pid_n, expert_id, A, B, C, Bs = _expert_setup(
|
| 194 |
+
A, B, C, Bs, ExpertIds, stride_am, stride_be, stride_cm, stride_bs_e, stride_eid
|
| 195 |
+
)
|
| 196 |
+
# EP sentinel: row routed to a non-local expert; output is left uninit.
|
| 197 |
+
if expert_id >= NUM_EXPERTS:
|
| 198 |
+
return
|
| 199 |
+
|
| 200 |
+
offs_bn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
|
| 201 |
+
offs_k = tl.arange(0, BLOCK_SIZE_K)
|
| 202 |
+
a_ptrs = A + tl.arange(0, BLOCK_SIZE_M)[:, None] * 0 + offs_k[None, :] * stride_ak
|
| 203 |
+
b_ptrs = B + (offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn)
|
| 204 |
+
|
| 205 |
+
b_s = tl.load(Bs)
|
| 206 |
+
a_s = tl.load(As + batch_id * stride_as_m)
|
| 207 |
+
|
| 208 |
+
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
| 209 |
+
for _ in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
|
| 210 |
+
a = tl.load(a_ptrs)
|
| 211 |
+
b = tl.load(b_ptrs)
|
| 212 |
+
accumulator += tl.dot(a, b)
|
| 213 |
+
a_ptrs += BLOCK_SIZE_K * stride_ak
|
| 214 |
+
b_ptrs += BLOCK_SIZE_K * stride_bk
|
| 215 |
+
|
| 216 |
+
accumulator = accumulator * a_s * b_s
|
| 217 |
+
|
| 218 |
+
_store_row(C, accumulator, pid_n, stride_cn, BLOCK_SIZE_M, BLOCK_SIZE_N)
|
| 219 |
+
|
| 220 |
+
|
| 221 |
+
@triton.autotune(
|
| 222 |
+
configs=get_accelerator_autotuning_configs(with_block_sizes=True),
|
| 223 |
+
key=["N", "K"],
|
| 224 |
+
)
|
| 225 |
+
@triton.jit
|
| 226 |
+
def w4a8_mx_dynamic_fp4_matmul_batched_kernel(
|
| 227 |
+
A, # (S, K) raw BF16/FP16 activations
|
| 228 |
+
B, # (E, N, K // 2) packed FP4 (E2M1) expert weights as int8
|
| 229 |
+
C, # (S, N) output
|
| 230 |
+
Bs, # (E, N, K // SCALE_GROUP_K) UE8M0 weight scales
|
| 231 |
+
ExpertIds, # (S,) — which expert each routed row uses
|
| 232 |
+
# Shape
|
| 233 |
+
S,
|
| 234 |
+
N,
|
| 235 |
+
K,
|
| 236 |
+
# Strides
|
| 237 |
+
stride_am,
|
| 238 |
+
stride_ak,
|
| 239 |
+
stride_be,
|
| 240 |
+
stride_bk,
|
| 241 |
+
stride_bn,
|
| 242 |
+
stride_cm,
|
| 243 |
+
stride_cn,
|
| 244 |
+
stride_bs_e,
|
| 245 |
+
stride_bs_k,
|
| 246 |
+
stride_bs_n,
|
| 247 |
+
stride_eid,
|
| 248 |
+
# Meta-parameters
|
| 249 |
+
BLOCK_SIZE_M: tl.constexpr,
|
| 250 |
+
BLOCK_SIZE_N: tl.constexpr,
|
| 251 |
+
BLOCK_SIZE_K: tl.constexpr,
|
| 252 |
+
NIBBLES_PER_BYTE: tl.constexpr,
|
| 253 |
+
SCALE_GROUP_K: tl.constexpr,
|
| 254 |
+
NUM_EXPERTS: tl.constexpr,
|
| 255 |
+
):
|
| 256 |
+
"""Batched MXFP4 (W4A8) expert matmul with fused activation quant.
|
| 257 |
+
|
| 258 |
+
Each program handles one routed token row and one N-tile, looks up the
|
| 259 |
+
owning expert from ``ExpertIds``, quantizes ``A`` to FP8 per K-group inline
|
| 260 |
+
(UE8M0 scale), then ``tl.dot_scaled`` against packed FP4 weights.
|
| 261 |
+
"""
|
| 262 |
+
_, pid_n, expert_id, A, B, C, Bs = _expert_setup(
|
| 263 |
+
A, B, C, Bs, ExpertIds, stride_am, stride_be, stride_cm, stride_bs_e, stride_eid
|
| 264 |
+
)
|
| 265 |
+
# EP sentinel: row routed to a non-local expert; output is left uninit.
|
| 266 |
+
if expert_id >= NUM_EXPERTS:
|
| 267 |
+
return
|
| 268 |
+
|
| 269 |
+
offs_bn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
|
| 270 |
+
offs_k = tl.arange(0, BLOCK_SIZE_K)
|
| 271 |
+
offs_k_byte = tl.arange(0, BLOCK_SIZE_K // NIBBLES_PER_BYTE)
|
| 272 |
+
offs_sf = tl.arange(0, BLOCK_SIZE_K // SCALE_GROUP_K)
|
| 273 |
+
a_ptrs = A + tl.arange(0, BLOCK_SIZE_M)[:, None] * 0 + offs_k[None, :] * stride_ak
|
| 274 |
+
b_ptrs = B + (offs_k_byte[:, None] * stride_bk + offs_bn[None, :] * stride_bn)
|
| 275 |
+
bs_ptrs = Bs + offs_bn[:, None] * stride_bs_n + offs_sf[None, :] * stride_bs_k
|
| 276 |
+
|
| 277 |
+
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
| 278 |
+
for _ in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
|
| 279 |
+
a_raw = tl.load(a_ptrs).to(tl.float32)
|
| 280 |
+
a, a_scale = mx_act_quant_inline(
|
| 281 |
+
a_raw, BLOCK_SIZE_M, BLOCK_SIZE_K, SCALE_GROUP_K
|
| 282 |
+
)
|
| 283 |
+
b = tl.load(b_ptrs).to(tl.uint8)
|
| 284 |
+
b_s = tl.load(bs_ptrs).to(tl.uint8)
|
| 285 |
+
accumulator += tl.dot_scaled(a, a_scale, "e4m3", b, b_s, "e2m1")
|
| 286 |
+
a_ptrs += BLOCK_SIZE_K * stride_ak
|
| 287 |
+
b_ptrs += (BLOCK_SIZE_K // NIBBLES_PER_BYTE) * stride_bk
|
| 288 |
+
bs_ptrs += (BLOCK_SIZE_K // SCALE_GROUP_K) * stride_bs_k
|
| 289 |
+
|
| 290 |
+
_store_row(C, accumulator, pid_n, stride_cn, BLOCK_SIZE_M, BLOCK_SIZE_N)
|
| 291 |
+
|
| 292 |
+
|
| 293 |
+
@triton.autotune(
|
| 294 |
+
configs=get_accelerator_autotuning_configs(with_block_sizes=True),
|
| 295 |
+
key=["N", "K"],
|
| 296 |
+
)
|
| 297 |
+
@triton.jit
|
| 298 |
+
def w8a8_mx_dynamic_fp8_matmul_batched_kernel(
|
| 299 |
+
A, # (S, K) raw BF16/FP16 activations
|
| 300 |
+
B, # (E, N, K) E4M3 expert weights (unpacked)
|
| 301 |
+
C, # (S, N) output
|
| 302 |
+
Bs, # (E, N, K // SCALE_GROUP_K) UE8M0 weight scales
|
| 303 |
+
ExpertIds, # (S,) — which expert each routed row uses
|
| 304 |
+
# Shape
|
| 305 |
+
S,
|
| 306 |
+
N,
|
| 307 |
+
K,
|
| 308 |
+
# Strides
|
| 309 |
+
stride_am,
|
| 310 |
+
stride_ak,
|
| 311 |
+
stride_be,
|
| 312 |
+
stride_bk,
|
| 313 |
+
stride_bn,
|
| 314 |
+
stride_cm,
|
| 315 |
+
stride_cn,
|
| 316 |
+
stride_bs_e,
|
| 317 |
+
stride_bs_k,
|
| 318 |
+
stride_bs_n,
|
| 319 |
+
stride_eid,
|
| 320 |
+
# Meta-parameters
|
| 321 |
+
BLOCK_SIZE_M: tl.constexpr,
|
| 322 |
+
BLOCK_SIZE_N: tl.constexpr,
|
| 323 |
+
BLOCK_SIZE_K: tl.constexpr,
|
| 324 |
+
SCALE_GROUP_K: tl.constexpr,
|
| 325 |
+
NUM_EXPERTS: tl.constexpr,
|
| 326 |
+
):
|
| 327 |
+
"""Batched MXFP8 (W8A8) expert matmul with fused activation quant.
|
| 328 |
+
|
| 329 |
+
Each program handles one routed token row and one N-tile, looks up the
|
| 330 |
+
owning expert from ``ExpertIds``, quantizes ``A`` to E4M3 per K-group inline
|
| 331 |
+
(UE8M0 scale), then ``tl.dot_scaled`` against E4M3 weights. Mirrors the
|
| 332 |
+
batched MXFP4 kernel but with unpacked weights and ``"e4m3"`` on both operands.
|
| 333 |
+
"""
|
| 334 |
+
_, pid_n, expert_id, A, B, C, Bs = _expert_setup(
|
| 335 |
+
A, B, C, Bs, ExpertIds, stride_am, stride_be, stride_cm, stride_bs_e, stride_eid
|
| 336 |
+
)
|
| 337 |
+
# EP sentinel: row routed to a non-local expert; output is left uninit.
|
| 338 |
+
if expert_id >= NUM_EXPERTS:
|
| 339 |
+
return
|
| 340 |
+
|
| 341 |
+
offs_bn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
|
| 342 |
+
offs_k = tl.arange(0, BLOCK_SIZE_K)
|
| 343 |
+
offs_sf = tl.arange(0, BLOCK_SIZE_K // SCALE_GROUP_K)
|
| 344 |
+
a_ptrs = A + tl.arange(0, BLOCK_SIZE_M)[:, None] * 0 + offs_k[None, :] * stride_ak
|
| 345 |
+
b_ptrs = B + (offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn)
|
| 346 |
+
bs_ptrs = Bs + offs_bn[:, None] * stride_bs_n + offs_sf[None, :] * stride_bs_k
|
| 347 |
+
|
| 348 |
+
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
| 349 |
+
for _ in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
|
| 350 |
+
a_raw = tl.load(a_ptrs).to(tl.float32)
|
| 351 |
+
a, a_scale = mx_act_quant_inline(
|
| 352 |
+
a_raw, BLOCK_SIZE_M, BLOCK_SIZE_K, SCALE_GROUP_K
|
| 353 |
+
)
|
| 354 |
+
b = tl.load(b_ptrs)
|
| 355 |
+
b_s = tl.load(bs_ptrs).to(tl.uint8)
|
| 356 |
+
accumulator += tl.dot_scaled(a, a_scale, "e4m3", b, b_s, "e4m3")
|
| 357 |
+
a_ptrs += BLOCK_SIZE_K * stride_ak
|
| 358 |
+
b_ptrs += BLOCK_SIZE_K * stride_bk
|
| 359 |
+
bs_ptrs += (BLOCK_SIZE_K // SCALE_GROUP_K) * stride_bs_k
|
| 360 |
+
|
| 361 |
+
_store_row(C, accumulator, pid_n, stride_cn, BLOCK_SIZE_M, BLOCK_SIZE_N)
|
| 362 |
+
|
| 363 |
+
|
| 364 |
+
@triton_op(
|
| 365 |
+
add_op_namespace_prefix("w8a8_block_dynamic_fp8_matmul_batched"), mutates_args=()
|
| 366 |
+
)
|
| 367 |
+
def _w8a8_block_dynamic_fp8_matmul_batched(
|
| 368 |
+
A: torch.Tensor,
|
| 369 |
+
B: torch.Tensor,
|
| 370 |
+
Bs: torch.Tensor,
|
| 371 |
+
expert_ids: torch.Tensor,
|
| 372 |
+
block_size: list[int],
|
| 373 |
+
output_dtype: torch.dtype | None = None,
|
| 374 |
+
) -> torch.Tensor:
|
| 375 |
+
"""Block-scale batched FP8 matmul: C[s] = A[s] @ B[expert_ids[s]].T, with fused act quant.
|
| 376 |
+
|
| 377 |
+
A: (S, K) raw bf16/fp16 activations
|
| 378 |
+
B: (E, N, K) FP8 expert weights
|
| 379 |
+
Bs: (E, N // block_n, K // block_k) per-block weight scales
|
| 380 |
+
"""
|
| 381 |
+
assert A.ndim == 2, f"A must be 2D (S, K), got ndim={A.ndim}"
|
| 382 |
+
assert A.is_contiguous(), "A must be contiguous"
|
| 383 |
+
assert B.ndim == 3, f"B must be 3D (E, N, K), got ndim={B.ndim}"
|
| 384 |
+
assert B.is_contiguous(), "B must be contiguous"
|
| 385 |
+
assert A.shape[1] == B.shape[2], (
|
| 386 |
+
f"K mismatch: A has K={A.shape[1]}, B has K={B.shape[2]}"
|
| 387 |
+
)
|
| 388 |
+
|
| 389 |
+
S, K = A.shape
|
| 390 |
+
E, N, _ = B.shape
|
| 391 |
+
|
| 392 |
+
assert len(block_size) == 2, (
|
| 393 |
+
f"block_size must be [block_n, block_k], got {block_size}"
|
| 394 |
+
)
|
| 395 |
+
block_n, block_k = block_size[0], block_size[1]
|
| 396 |
+
# MoE expert dimensions must be block-aligned; non-aligned N/K is not supported.
|
| 397 |
+
assert N % block_n == 0, f"N ({N}) must be divisible by block_n ({block_n})"
|
| 398 |
+
assert K % block_k == 0, f"K ({K}) must be divisible by block_k ({block_k})"
|
| 399 |
+
assert Bs.ndim == 3, (
|
| 400 |
+
f"Bs must be 3D (E, N//block_n, K//block_k), got ndim={Bs.ndim}"
|
| 401 |
+
)
|
| 402 |
+
assert Bs.shape == (E, N // block_n, K // block_k), (
|
| 403 |
+
f"Bs shape {tuple(Bs.shape)} != expected ({E}, {N // block_n}, {K // block_k})"
|
| 404 |
+
)
|
| 405 |
+
|
| 406 |
+
# Decode handles one routed row per program; BLOCK_SIZE_M > 1 would just
|
| 407 |
+
# duplicate the same row computation and keep one row on store.
|
| 408 |
+
BLOCK_SIZE_M = 1
|
| 409 |
+
Bs = ue8m0_as_uint8(Bs)
|
| 410 |
+
grid = (S, triton.cdiv(N, block_n))
|
| 411 |
+
C = A.new_empty(S, N, dtype=output_dtype)
|
| 412 |
+
|
| 413 |
+
with device_context(A.device):
|
| 414 |
+
wrap_triton(w8a8_block_dynamic_fp8_matmul_batched_kernel)[grid](
|
| 415 |
+
A,
|
| 416 |
+
B,
|
| 417 |
+
C,
|
| 418 |
+
Bs,
|
| 419 |
+
expert_ids,
|
| 420 |
+
S,
|
| 421 |
+
N,
|
| 422 |
+
K,
|
| 423 |
+
A.stride(0),
|
| 424 |
+
A.stride(1),
|
| 425 |
+
B.stride(0),
|
| 426 |
+
B.stride(2),
|
| 427 |
+
B.stride(1),
|
| 428 |
+
C.stride(0),
|
| 429 |
+
C.stride(1),
|
| 430 |
+
Bs.stride(0),
|
| 431 |
+
Bs.stride(2),
|
| 432 |
+
Bs.stride(1),
|
| 433 |
+
expert_ids.stride(0),
|
| 434 |
+
BLOCK_SIZE_N=block_n,
|
| 435 |
+
BLOCK_SIZE_K=block_k,
|
| 436 |
+
BLOCK_SIZE_M=BLOCK_SIZE_M,
|
| 437 |
+
NUM_EXPERTS=E,
|
| 438 |
+
)
|
| 439 |
+
|
| 440 |
+
return C
|
| 441 |
+
|
| 442 |
+
|
| 443 |
+
@triton_op(
|
| 444 |
+
add_op_namespace_prefix("w8a8_tensor_dynamic_fp8_matmul_batched"), mutates_args=()
|
| 445 |
+
)
|
| 446 |
+
def _w8a8_tensor_dynamic_fp8_matmul_batched(
|
| 447 |
+
A: torch.Tensor,
|
| 448 |
+
B: torch.Tensor,
|
| 449 |
+
Bs: torch.Tensor,
|
| 450 |
+
expert_ids: torch.Tensor,
|
| 451 |
+
output_dtype: torch.dtype | None = None,
|
| 452 |
+
) -> torch.Tensor:
|
| 453 |
+
"""Tensor-scale batched FP8 matmul: C[s] = A[s] @ B[expert_ids[s]].T, with fused act quant.
|
| 454 |
+
|
| 455 |
+
A: (S, K) raw bf16/fp16 activations
|
| 456 |
+
B: (E, N, K) FP8 expert weights
|
| 457 |
+
Bs: (E,) or (E, 1, 1) per-expert weight scales
|
| 458 |
+
"""
|
| 459 |
+
assert A.ndim == 2, f"A must be 2D (S, K), got ndim={A.ndim}"
|
| 460 |
+
assert A.is_contiguous(), "A must be contiguous"
|
| 461 |
+
assert B.ndim == 3, f"B must be 3D (E, N, K), got ndim={B.ndim}"
|
| 462 |
+
assert B.is_contiguous(), "B must be contiguous"
|
| 463 |
+
assert A.shape[1] == B.shape[2], (
|
| 464 |
+
f"K mismatch: A has K={A.shape[1]}, B has K={B.shape[2]}"
|
| 465 |
+
)
|
| 466 |
+
|
| 467 |
+
S, K = A.shape
|
| 468 |
+
E, N, _ = B.shape
|
| 469 |
+
|
| 470 |
+
# Normalize Bs to (E, 1, 1)
|
| 471 |
+
if Bs.ndim == 1:
|
| 472 |
+
assert Bs.shape[0] == E, f"Bs shape {tuple(Bs.shape)} != expected ({E},)"
|
| 473 |
+
Bs = Bs.reshape(E, 1, 1)
|
| 474 |
+
else:
|
| 475 |
+
assert Bs.shape == (E, 1, 1), (
|
| 476 |
+
f"Bs shape {tuple(Bs.shape)} != expected ({E}, 1, 1)"
|
| 477 |
+
)
|
| 478 |
+
|
| 479 |
+
# Decode handles one routed row per program; BLOCK_SIZE_M > 1 would just
|
| 480 |
+
# duplicate the same row computation and keep one row on store.
|
| 481 |
+
BLOCK_SIZE_M = 1
|
| 482 |
+
Bs = ue8m0_as_uint8(Bs)
|
| 483 |
+
qA, As = fp8_act_quant(A, K)
|
| 484 |
+
C = A.new_empty(S, N, dtype=output_dtype)
|
| 485 |
+
|
| 486 |
+
def grid(META):
|
| 487 |
+
return (S, triton.cdiv(N, META["BLOCK_SIZE_N"]))
|
| 488 |
+
|
| 489 |
+
with device_context(A.device):
|
| 490 |
+
wrap_triton(w8a8_tensor_dynamic_fp8_matmul_batched_kernel)[grid](
|
| 491 |
+
qA,
|
| 492 |
+
B,
|
| 493 |
+
C,
|
| 494 |
+
As,
|
| 495 |
+
Bs,
|
| 496 |
+
expert_ids,
|
| 497 |
+
S,
|
| 498 |
+
N,
|
| 499 |
+
K,
|
| 500 |
+
qA.stride(0),
|
| 501 |
+
qA.stride(1),
|
| 502 |
+
B.stride(0),
|
| 503 |
+
B.stride(2),
|
| 504 |
+
B.stride(1),
|
| 505 |
+
C.stride(0),
|
| 506 |
+
C.stride(1),
|
| 507 |
+
As.stride(0),
|
| 508 |
+
Bs.stride(0),
|
| 509 |
+
expert_ids.stride(0),
|
| 510 |
+
BLOCK_SIZE_M=BLOCK_SIZE_M,
|
| 511 |
+
NUM_EXPERTS=E,
|
| 512 |
+
)
|
| 513 |
+
|
| 514 |
+
return C
|
| 515 |
+
|
| 516 |
+
|
| 517 |
+
def w8a8_block_dynamic_fp8_matmul_batched(
|
| 518 |
+
A: torch.Tensor,
|
| 519 |
+
B: torch.Tensor,
|
| 520 |
+
Bs: torch.Tensor,
|
| 521 |
+
expert_ids: torch.Tensor,
|
| 522 |
+
block_size: list[int],
|
| 523 |
+
output_dtype: torch.dtype | None = None,
|
| 524 |
+
) -> torch.Tensor:
|
| 525 |
+
"""Block-scale batched FP8 matmul with fused activation quantization.
|
| 526 |
+
|
| 527 |
+
A: (S, K) raw activations, bf16/fp16/fp32
|
| 528 |
+
B: (E, N, K) FP8 expert weights
|
| 529 |
+
Bs: (E, N // block_n, K // block_k) per-block weight scales
|
| 530 |
+
expert_ids: (S,) — kernel loads stride-aware, any int dtype works
|
| 531 |
+
output_dtype: defaults to ``A.dtype``
|
| 532 |
+
"""
|
| 533 |
+
return ops.w8a8_block_dynamic_fp8_matmul_batched(
|
| 534 |
+
A, B, Bs, expert_ids, block_size, output_dtype
|
| 535 |
+
)
|
| 536 |
+
|
| 537 |
+
|
| 538 |
+
def w8a8_tensor_dynamic_fp8_matmul_batched(
|
| 539 |
+
A: torch.Tensor,
|
| 540 |
+
B: torch.Tensor,
|
| 541 |
+
Bs: torch.Tensor,
|
| 542 |
+
expert_ids: torch.Tensor,
|
| 543 |
+
output_dtype: torch.dtype | None = None,
|
| 544 |
+
) -> torch.Tensor:
|
| 545 |
+
"""Tensor-scale batched FP8 matmul with fused activation quantization.
|
| 546 |
+
|
| 547 |
+
A: (S, K) raw activations, bf16/fp16/fp32
|
| 548 |
+
B: (E, N, K) FP8 expert weights
|
| 549 |
+
Bs: (E,) or (E, 1, 1) per-expert weight scales
|
| 550 |
+
output_dtype: defaults to ``A.dtype``
|
| 551 |
+
"""
|
| 552 |
+
return ops.w8a8_tensor_dynamic_fp8_matmul_batched(
|
| 553 |
+
A, B, Bs, expert_ids, output_dtype
|
| 554 |
+
)
|
| 555 |
+
|
| 556 |
+
|
| 557 |
+
@triton_op(
|
| 558 |
+
add_op_namespace_prefix("w4a8_mx_dynamic_fp4_matmul_batched"), mutates_args=()
|
| 559 |
+
)
|
| 560 |
+
def _w4a8_mx_dynamic_fp4_matmul_batched(
|
| 561 |
+
A: torch.Tensor,
|
| 562 |
+
B: torch.Tensor,
|
| 563 |
+
Bs: torch.Tensor,
|
| 564 |
+
expert_ids: torch.Tensor,
|
| 565 |
+
output_dtype: torch.dtype | None = None,
|
| 566 |
+
) -> torch.Tensor:
|
| 567 |
+
"""Batched MXFP4 (W4A8) matmul with fused activation quant.
|
| 568 |
+
|
| 569 |
+
A: (S, K) raw activations, bf16/fp16/fp32 (quantized inline to FP8)
|
| 570 |
+
B: (E, N, K // 2) packed FP4 (E2M1) expert weights, two codes per int8
|
| 571 |
+
Bs: (E, N, K // 32) UE8M0 weight scales
|
| 572 |
+
expert_ids: (S,) which expert each routed row uses
|
| 573 |
+
|
| 574 |
+
BLOCK_SIZE_N and BLOCK_SIZE_K are autotuned.
|
| 575 |
+
"""
|
| 576 |
+
assert A.ndim == 2 and B.ndim == 3 and Bs.ndim == 3
|
| 577 |
+
assert expert_ids.ndim == 1
|
| 578 |
+
assert B.dtype == torch.int8, f"B must be int8 (packed FP4), got {B.dtype}"
|
| 579 |
+
assert Bs.dtype == torch.float8_e8m0fnu, (
|
| 580 |
+
f"Bs must be float8_e8m0fnu, got {Bs.dtype}"
|
| 581 |
+
)
|
| 582 |
+
|
| 583 |
+
S, K = A.shape
|
| 584 |
+
E, N, K_half = B.shape
|
| 585 |
+
assert expert_ids.shape[0] == S
|
| 586 |
+
assert K == NIBBLES_PER_BYTE * K_half, (
|
| 587 |
+
f"K (={K}) must equal {NIBBLES_PER_BYTE} * B.shape[2] (={K_half})"
|
| 588 |
+
)
|
| 589 |
+
assert K % MX_SCALE_GROUP_K == 0, (
|
| 590 |
+
f"K (={K}) must be a multiple of {MX_SCALE_GROUP_K}"
|
| 591 |
+
)
|
| 592 |
+
assert Bs.shape == (E, N, K // MX_SCALE_GROUP_K), (
|
| 593 |
+
f"Bs shape {tuple(Bs.shape)} != ({E}, {N}, {K // MX_SCALE_GROUP_K})"
|
| 594 |
+
)
|
| 595 |
+
|
| 596 |
+
# Decode handles one routed row per program; BLOCK_SIZE_M > 1 would just
|
| 597 |
+
# duplicate the same row computation and keep one row on store.
|
| 598 |
+
BLOCK_SIZE_M = 1
|
| 599 |
+
bs_u8 = ue8m0_as_uint8(Bs)
|
| 600 |
+
C = A.new_empty((S, N), dtype=output_dtype)
|
| 601 |
+
|
| 602 |
+
def grid(META):
|
| 603 |
+
return (S, triton.cdiv(N, META["BLOCK_SIZE_N"]))
|
| 604 |
+
|
| 605 |
+
with device_context(A.device):
|
| 606 |
+
wrap_triton(w4a8_mx_dynamic_fp4_matmul_batched_kernel)[grid](
|
| 607 |
+
A,
|
| 608 |
+
B,
|
| 609 |
+
C,
|
| 610 |
+
bs_u8,
|
| 611 |
+
expert_ids,
|
| 612 |
+
S,
|
| 613 |
+
N,
|
| 614 |
+
K,
|
| 615 |
+
A.stride(0),
|
| 616 |
+
A.stride(1),
|
| 617 |
+
B.stride(0),
|
| 618 |
+
B.stride(2),
|
| 619 |
+
B.stride(1),
|
| 620 |
+
C.stride(0),
|
| 621 |
+
C.stride(1),
|
| 622 |
+
bs_u8.stride(0),
|
| 623 |
+
bs_u8.stride(2),
|
| 624 |
+
bs_u8.stride(1),
|
| 625 |
+
expert_ids.stride(0),
|
| 626 |
+
# Meta-parameters (BLOCK_SIZE_N, BLOCK_SIZE_K come from autotune Config)
|
| 627 |
+
BLOCK_SIZE_M=BLOCK_SIZE_M,
|
| 628 |
+
NIBBLES_PER_BYTE=NIBBLES_PER_BYTE,
|
| 629 |
+
SCALE_GROUP_K=MX_SCALE_GROUP_K,
|
| 630 |
+
NUM_EXPERTS=E,
|
| 631 |
+
)
|
| 632 |
+
return C
|
| 633 |
+
|
| 634 |
+
|
| 635 |
+
def w4a8_mx_dynamic_fp4_matmul_batched(
|
| 636 |
+
A: torch.Tensor,
|
| 637 |
+
B: torch.Tensor,
|
| 638 |
+
Bs: torch.Tensor,
|
| 639 |
+
expert_ids: torch.Tensor,
|
| 640 |
+
output_dtype: torch.dtype | None = None,
|
| 641 |
+
) -> torch.Tensor:
|
| 642 |
+
"""Batched MXFP4 (W4A8) matmul with fused activation quant. Tile
|
| 643 |
+
shape autotuned; FP4 scale granularity is fixed at 32."""
|
| 644 |
+
return ops.w4a8_mx_dynamic_fp4_matmul_batched(A, B, Bs, expert_ids, output_dtype)
|
| 645 |
+
|
| 646 |
+
|
| 647 |
+
@triton_op(
|
| 648 |
+
add_op_namespace_prefix("w8a8_mx_dynamic_fp8_matmul_batched"), mutates_args=()
|
| 649 |
+
)
|
| 650 |
+
def _w8a8_mx_dynamic_fp8_matmul_batched(
|
| 651 |
+
A: torch.Tensor,
|
| 652 |
+
B: torch.Tensor,
|
| 653 |
+
Bs: torch.Tensor,
|
| 654 |
+
expert_ids: torch.Tensor,
|
| 655 |
+
output_dtype: torch.dtype | None = None,
|
| 656 |
+
) -> torch.Tensor:
|
| 657 |
+
"""Batched MXFP8 (W8A8) matmul: ``C[s] = A[s] @ B[expert_ids[s]].T`` (E4M3 ×
|
| 658 |
+
E4M3, UE8M0 group-32 scales) with fused activation quant.
|
| 659 |
+
|
| 660 |
+
A: (S, K) raw activations, bf16/fp16/fp32 (quantized inline to E4M3)
|
| 661 |
+
B: (E, N, K) E4M3 expert weights (unpacked)
|
| 662 |
+
Bs: (E, N, K // 32) UE8M0 weight scales
|
| 663 |
+
expert_ids: (S,) which expert each routed row uses
|
| 664 |
+
|
| 665 |
+
BLOCK_SIZE_N and BLOCK_SIZE_K are autotuned.
|
| 666 |
+
"""
|
| 667 |
+
assert A.ndim == 2 and B.ndim == 3 and Bs.ndim == 3
|
| 668 |
+
assert expert_ids.ndim == 1
|
| 669 |
+
assert B.dtype == torch.float8_e4m3fn, (
|
| 670 |
+
f"B must be float8_e4m3fn (E4M3 weights), got {B.dtype}"
|
| 671 |
+
)
|
| 672 |
+
assert Bs.dtype == torch.float8_e8m0fnu, (
|
| 673 |
+
f"Bs must be float8_e8m0fnu, got {Bs.dtype}"
|
| 674 |
+
)
|
| 675 |
+
|
| 676 |
+
S, K = A.shape
|
| 677 |
+
E, N, K_b = B.shape
|
| 678 |
+
assert expert_ids.shape[0] == S
|
| 679 |
+
assert K == K_b, f"K mismatch: A has K={K}, B has K={K_b}"
|
| 680 |
+
assert K % MX_SCALE_GROUP_K == 0, (
|
| 681 |
+
f"K (={K}) must be a multiple of {MX_SCALE_GROUP_K}"
|
| 682 |
+
)
|
| 683 |
+
assert Bs.shape == (E, N, K // MX_SCALE_GROUP_K), (
|
| 684 |
+
f"Bs shape {tuple(Bs.shape)} != ({E}, {N}, {K // MX_SCALE_GROUP_K})"
|
| 685 |
+
)
|
| 686 |
+
|
| 687 |
+
# Decode handles one routed row per program; BLOCK_SIZE_M > 1 would just
|
| 688 |
+
# duplicate the same row computation and keep one row on store.
|
| 689 |
+
BLOCK_SIZE_M = 1
|
| 690 |
+
bs_u8 = ue8m0_as_uint8(Bs)
|
| 691 |
+
C = A.new_empty((S, N), dtype=output_dtype)
|
| 692 |
+
|
| 693 |
+
def grid(META):
|
| 694 |
+
return (S, triton.cdiv(N, META["BLOCK_SIZE_N"]))
|
| 695 |
+
|
| 696 |
+
with device_context(A.device):
|
| 697 |
+
wrap_triton(w8a8_mx_dynamic_fp8_matmul_batched_kernel)[grid](
|
| 698 |
+
A,
|
| 699 |
+
B,
|
| 700 |
+
C,
|
| 701 |
+
bs_u8,
|
| 702 |
+
expert_ids,
|
| 703 |
+
S,
|
| 704 |
+
N,
|
| 705 |
+
K,
|
| 706 |
+
A.stride(0),
|
| 707 |
+
A.stride(1),
|
| 708 |
+
B.stride(0),
|
| 709 |
+
B.stride(2),
|
| 710 |
+
B.stride(1),
|
| 711 |
+
C.stride(0),
|
| 712 |
+
C.stride(1),
|
| 713 |
+
bs_u8.stride(0),
|
| 714 |
+
bs_u8.stride(2),
|
| 715 |
+
bs_u8.stride(1),
|
| 716 |
+
expert_ids.stride(0),
|
| 717 |
+
# Meta-parameters (BLOCK_SIZE_N, BLOCK_SIZE_K come from autotune Config)
|
| 718 |
+
BLOCK_SIZE_M=BLOCK_SIZE_M,
|
| 719 |
+
SCALE_GROUP_K=MX_SCALE_GROUP_K,
|
| 720 |
+
NUM_EXPERTS=E,
|
| 721 |
+
)
|
| 722 |
+
return C
|
| 723 |
+
|
| 724 |
+
|
| 725 |
+
def w8a8_mx_dynamic_fp8_matmul_batched(
|
| 726 |
+
A: torch.Tensor,
|
| 727 |
+
B: torch.Tensor,
|
| 728 |
+
Bs: torch.Tensor,
|
| 729 |
+
expert_ids: torch.Tensor,
|
| 730 |
+
output_dtype: torch.dtype | None = None,
|
| 731 |
+
) -> torch.Tensor:
|
| 732 |
+
"""Batched MXFP8 (W8A8) matmul with fused activation quant (E4M3 × E4M3,
|
| 733 |
+
UE8M0 group-32) via the MX scaled MMA. Tile shape autotuned."""
|
| 734 |
+
return ops.w8a8_mx_dynamic_fp8_matmul_batched(A, B, Bs, expert_ids, output_dtype)
|
| 735 |
+
|
| 736 |
+
|
| 737 |
+
def matmul_batched(
|
| 738 |
+
A: torch.Tensor,
|
| 739 |
+
B: torch.Tensor,
|
| 740 |
+
Bs: torch.Tensor,
|
| 741 |
+
expert_ids: torch.Tensor,
|
| 742 |
+
block_size: list[int] | None,
|
| 743 |
+
output_dtype: torch.dtype | None = None,
|
| 744 |
+
) -> torch.Tensor:
|
| 745 |
+
"""Batched quantized matmul dispatcher (W8A8 FP8 or W4A8 FP4). Routes one
|
| 746 |
+
routed row per program to ``B[expert_ids[s]]``.
|
| 747 |
+
|
| 748 |
+
``output_dtype`` defaults to ``A.dtype``.
|
| 749 |
+
|
| 750 |
+
Routes by weight dtype and ``block_size``:
|
| 751 |
+
- ``B.dtype == int8`` (packed FP4) → ``w4a8_mx_dynamic_fp4_matmul_batched``
|
| 752 |
+
(``block_size`` is ignored; FP4 tile shape is autotuned).
|
| 753 |
+
- ``B.dtype == float8_e4m3fn`` with UE8M0 group-32 ``Bs`` (shape ``[E, N, K//32]``)
|
| 754 |
+
→ ``w8a8_mx_dynamic_fp8_matmul_batched`` (MX scaled MMA; ``block_size`` ignored).
|
| 755 |
+
- ``block_size`` None or full ``[N, K]`` → ``w8a8_tensor_dynamic_fp8_matmul_batched``.
|
| 756 |
+
- otherwise → ``w8a8_block_dynamic_fp8_matmul_batched``.
|
| 757 |
+
"""
|
| 758 |
+
if is_mxfp4(B, Bs):
|
| 759 |
+
return w4a8_mx_dynamic_fp4_matmul_batched(A, B, Bs, expert_ids, output_dtype)
|
| 760 |
+
|
| 761 |
+
if is_mxfp8(B, Bs):
|
| 762 |
+
return w8a8_mx_dynamic_fp8_matmul_batched(A, B, Bs, expert_ids, output_dtype)
|
| 763 |
+
|
| 764 |
+
if block_size is None or (
|
| 765 |
+
block_size[0] == B.size(1) and block_size[1] == B.size(2)
|
| 766 |
+
):
|
| 767 |
+
return w8a8_tensor_dynamic_fp8_matmul_batched(
|
| 768 |
+
A, B, Bs, expert_ids, output_dtype
|
| 769 |
+
)
|
| 770 |
+
|
| 771 |
+
return w8a8_block_dynamic_fp8_matmul_batched(
|
| 772 |
+
A, B, Bs, expert_ids, block_size, output_dtype
|
| 773 |
+
)
|
build/torch-rocm/finegrained_fp8/__init__.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,951 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 ._ops import add_op_namespace_prefix, ops
|
| 21 |
+
from .utils import (
|
| 22 |
+
MX_SCALE_GROUP_K,
|
| 23 |
+
NIBBLES_PER_BYTE,
|
| 24 |
+
adaptive_block_size_m,
|
| 25 |
+
device_context,
|
| 26 |
+
mx_act_quant_inline,
|
| 27 |
+
fp8_act_quant,
|
| 28 |
+
fp8_act_quant_inline,
|
| 29 |
+
get_accelerator_autotuning_configs,
|
| 30 |
+
is_mxfp4,
|
| 31 |
+
is_mxfp8,
|
| 32 |
+
ue8m0_as_uint8,
|
| 33 |
+
decode_ue8m0_scale,
|
| 34 |
+
)
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def grouped_tile_layout(
|
| 38 |
+
tokens_per_expert: torch.Tensor,
|
| 39 |
+
block_size_m: int,
|
| 40 |
+
S: int,
|
| 41 |
+
E: int,
|
| 42 |
+
) -> tuple[torch.Tensor, int]:
|
| 43 |
+
"""Compute the M-tile layout for the grouped kernels.
|
| 44 |
+
|
| 45 |
+
Returns ``(tile_offsets, max_m_tiles)``:
|
| 46 |
+
- ``tile_offsets``: int32 (E,) cumulative tile-end per expert, used by
|
| 47 |
+
``_grouped_tile_setup`` to locate an M-tile's owning expert.
|
| 48 |
+
- ``max_m_tiles``: upper bound on total M-tiles, used as the grid axis-0
|
| 49 |
+
size. Real tile count <= this; surplus programs early-return inside the
|
| 50 |
+
kernel. Keeps the grid data-independent (cuda-graph / torch.compile safe).
|
| 51 |
+
"""
|
| 52 |
+
tiles_per_expert = (tokens_per_expert + block_size_m - 1) // block_size_m
|
| 53 |
+
tile_offsets = torch.cumsum(tiles_per_expert, dim=0).to(torch.int32)
|
| 54 |
+
max_m_tiles = triton.cdiv(S, block_size_m) + E
|
| 55 |
+
return tile_offsets, max_m_tiles
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
@triton.jit
|
| 59 |
+
def _grouped_tile_setup(
|
| 60 |
+
pid_m,
|
| 61 |
+
pid_n,
|
| 62 |
+
Offsets,
|
| 63 |
+
TileOffsets,
|
| 64 |
+
stride_offs,
|
| 65 |
+
stride_tile,
|
| 66 |
+
NUM_EXPERTS: tl.constexpr,
|
| 67 |
+
NUM_EXPERTS_BIT_LENGTH: tl.constexpr,
|
| 68 |
+
BLOCK_SIZE_M: tl.constexpr,
|
| 69 |
+
BLOCK_SIZE_N: tl.constexpr,
|
| 70 |
+
BLOCK_SIZE_K: tl.constexpr,
|
| 71 |
+
):
|
| 72 |
+
"""Map a grouped M-tile to its expert and build the per-tile offset vectors —
|
| 73 |
+
the prologue shared by every grouped kernel.
|
| 74 |
+
|
| 75 |
+
Returns ``(expert_id, offs_global_m, row_mask, offs_bn, offs_k)``:
|
| 76 |
+
- ``expert_id``: int64 owning expert
|
| 77 |
+
- ``offs_global_m``: ``(BLOCK_SIZE_M,)`` global row indices into A
|
| 78 |
+
- ``row_mask``: ``(BLOCK_SIZE_M,)`` validity mask within the expert's M
|
| 79 |
+
- ``offs_bn``: ``(BLOCK_SIZE_N,)`` output column offsets
|
| 80 |
+
- ``offs_k``: ``(BLOCK_SIZE_K,)`` K range
|
| 81 |
+
|
| 82 |
+
Caller must have early-returned if ``pid_m`` exceeds total_tiles
|
| 83 |
+
(``TileOffsets[(NUM_EXPERTS - 1) * stride_tile]``) — the ``Offsets`` load below
|
| 84 |
+
is out of bounds for an out-of-range tile otherwise.
|
| 85 |
+
"""
|
| 86 |
+
# Binary search: upper_bound(TileOffsets, pid_m). NUM_EXPERTS_BIT_LENGTH is
|
| 87 |
+
# ceil(log2(E))+1, giving one harmless extra iteration; constexpr so the
|
| 88 |
+
# loop unrolls.
|
| 89 |
+
lo = 0
|
| 90 |
+
hi = NUM_EXPERTS
|
| 91 |
+
for _ in tl.static_range(NUM_EXPERTS_BIT_LENGTH):
|
| 92 |
+
mid = (lo + hi) >> 1
|
| 93 |
+
mid_val = tl.load(TileOffsets + mid * stride_tile)
|
| 94 |
+
is_left = mid_val <= pid_m
|
| 95 |
+
lo = tl.where(is_left, mid + 1, lo)
|
| 96 |
+
hi = tl.where(is_left, hi, mid)
|
| 97 |
+
# Cast to int64 so ``expert_id * stride_be`` doesn't overflow for large E
|
| 98 |
+
# × large weight matrices (e.g. 255 * 9_437_184 > 2^31).
|
| 99 |
+
expert_id = lo.to(tl.int64)
|
| 100 |
+
|
| 101 |
+
prev_eid = tl.maximum(expert_id - 1, 0)
|
| 102 |
+
expert_start = tl.where(
|
| 103 |
+
expert_id == 0, 0, tl.load(Offsets + prev_eid * stride_offs)
|
| 104 |
+
)
|
| 105 |
+
expert_end = tl.load(Offsets + expert_id * stride_offs)
|
| 106 |
+
M_expert = expert_end - expert_start
|
| 107 |
+
|
| 108 |
+
expert_tile_start = tl.where(
|
| 109 |
+
expert_id == 0, 0, tl.load(TileOffsets + prev_eid * stride_tile)
|
| 110 |
+
)
|
| 111 |
+
local_tile = pid_m - expert_tile_start
|
| 112 |
+
m_off = local_tile * BLOCK_SIZE_M
|
| 113 |
+
|
| 114 |
+
offs_am = m_off + tl.arange(0, BLOCK_SIZE_M)
|
| 115 |
+
row_mask = offs_am < M_expert
|
| 116 |
+
offs_global_m = expert_start + offs_am
|
| 117 |
+
offs_bn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
|
| 118 |
+
offs_k = tl.arange(0, BLOCK_SIZE_K)
|
| 119 |
+
return expert_id, offs_global_m, row_mask, offs_bn, offs_k
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
@triton.jit
|
| 123 |
+
def _store_tile(
|
| 124 |
+
C, accumulator, offs_global_m, offs_bn, row_mask, stride_cm, stride_cn
|
| 125 |
+
):
|
| 126 |
+
"""Output epilogue shared by the grouped kernels: cast the fp32 accumulator to
|
| 127 |
+
``C``'s dtype and store the tile at expert-sorted global rows ``offs_global_m`` ×
|
| 128 |
+
columns ``offs_bn``, masked to the expert's valid rows (``row_mask``)."""
|
| 129 |
+
c = accumulator.to(C.dtype.element_ty)
|
| 130 |
+
c_ptrs = C + stride_cm * offs_global_m[:, None] + stride_cn * offs_bn[None, :]
|
| 131 |
+
tl.store(c_ptrs, c, mask=row_mask[:, None])
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
@triton.autotune(
|
| 135 |
+
configs=get_accelerator_autotuning_configs(),
|
| 136 |
+
key=["N", "K", "BLOCK_SIZE_M"],
|
| 137 |
+
)
|
| 138 |
+
@triton.jit
|
| 139 |
+
def w8a8_block_dynamic_fp8_matmul_grouped_kernel(
|
| 140 |
+
A, # (S, K) raw BF16/FP16 activations, sorted/grouped by expert id
|
| 141 |
+
B, # (E, N, K) FP8 weight matrices
|
| 142 |
+
C, # (S, N) output
|
| 143 |
+
Bs, # (E, N // BLOCK_SIZE_N, K // BLOCK_SIZE_K) weight scales
|
| 144 |
+
Offsets, # (E,) int32 — cumulative row-end per expert
|
| 145 |
+
TileOffsets, # (E,) int32 — cumulative tile-end per expert
|
| 146 |
+
# Shape
|
| 147 |
+
S,
|
| 148 |
+
N,
|
| 149 |
+
K,
|
| 150 |
+
# Strides
|
| 151 |
+
stride_am,
|
| 152 |
+
stride_ak,
|
| 153 |
+
stride_be,
|
| 154 |
+
stride_bk,
|
| 155 |
+
stride_bn,
|
| 156 |
+
stride_cm,
|
| 157 |
+
stride_cn,
|
| 158 |
+
stride_bs_e,
|
| 159 |
+
stride_bs_k,
|
| 160 |
+
stride_bs_n,
|
| 161 |
+
stride_offs,
|
| 162 |
+
stride_tile,
|
| 163 |
+
# Meta-parameters
|
| 164 |
+
BLOCK_SIZE_M: tl.constexpr,
|
| 165 |
+
BLOCK_SIZE_N: tl.constexpr,
|
| 166 |
+
BLOCK_SIZE_K: tl.constexpr,
|
| 167 |
+
NUM_EXPERTS: tl.constexpr,
|
| 168 |
+
NUM_EXPERTS_BIT_LENGTH: tl.constexpr,
|
| 169 |
+
):
|
| 170 |
+
"""Block-scale grouped FP8 expert matmul kernel.
|
| 171 |
+
|
| 172 |
+
Tokens are assumed sorted by expert. The kernel maps each M-tile to its
|
| 173 |
+
owning expert via ``TileOffsets`` and applies fused activation quantization.
|
| 174 |
+
"""
|
| 175 |
+
pid_m = tl.program_id(axis=0)
|
| 176 |
+
pid_n = tl.program_id(axis=1)
|
| 177 |
+
|
| 178 |
+
total_tiles = tl.load(TileOffsets + (NUM_EXPERTS - 1) * stride_tile)
|
| 179 |
+
if pid_m >= total_tiles:
|
| 180 |
+
return
|
| 181 |
+
|
| 182 |
+
expert_id, offs_global_m, row_mask, offs_bn, offs_k = _grouped_tile_setup(
|
| 183 |
+
pid_m,
|
| 184 |
+
pid_n,
|
| 185 |
+
Offsets,
|
| 186 |
+
TileOffsets,
|
| 187 |
+
stride_offs,
|
| 188 |
+
stride_tile,
|
| 189 |
+
NUM_EXPERTS,
|
| 190 |
+
NUM_EXPERTS_BIT_LENGTH,
|
| 191 |
+
BLOCK_SIZE_M,
|
| 192 |
+
BLOCK_SIZE_N,
|
| 193 |
+
BLOCK_SIZE_K,
|
| 194 |
+
)
|
| 195 |
+
|
| 196 |
+
a_ptrs = A + offs_global_m[:, None] * stride_am + offs_k[None, :] * stride_ak
|
| 197 |
+
b_ptrs = (
|
| 198 |
+
B
|
| 199 |
+
+ expert_id * stride_be
|
| 200 |
+
+ offs_k[:, None] * stride_bk
|
| 201 |
+
+ offs_bn[None, :] * stride_bn
|
| 202 |
+
)
|
| 203 |
+
bs_ptrs = Bs + expert_id * stride_bs_e + pid_n * stride_bs_n
|
| 204 |
+
|
| 205 |
+
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
| 206 |
+
for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
|
| 207 |
+
a_raw = tl.load(a_ptrs, mask=row_mask[:, None], other=0.0).to(tl.float32)
|
| 208 |
+
a, a_s = fp8_act_quant_inline(a_raw)
|
| 209 |
+
b = tl.load(b_ptrs)
|
| 210 |
+
b_s = decode_ue8m0_scale(tl.load(bs_ptrs))
|
| 211 |
+
accumulator += tl.dot(a, b) * a_s[:, None] * b_s[None, :]
|
| 212 |
+
a_ptrs += BLOCK_SIZE_K * stride_ak
|
| 213 |
+
b_ptrs += BLOCK_SIZE_K * stride_bk
|
| 214 |
+
bs_ptrs += stride_bs_k
|
| 215 |
+
|
| 216 |
+
_store_tile(
|
| 217 |
+
C, accumulator, offs_global_m, offs_bn, row_mask, stride_cm, stride_cn
|
| 218 |
+
)
|
| 219 |
+
|
| 220 |
+
|
| 221 |
+
@triton.autotune(
|
| 222 |
+
configs=get_accelerator_autotuning_configs(with_block_sizes=True),
|
| 223 |
+
key=["N", "K", "BLOCK_SIZE_M"],
|
| 224 |
+
)
|
| 225 |
+
@triton.jit
|
| 226 |
+
def w8a8_tensor_dynamic_fp8_matmul_grouped_kernel(
|
| 227 |
+
A, # (S, K) pre-quantized FP8 activations, sorted/grouped by expert id
|
| 228 |
+
B, # (E, N, K) FP8 weight matrices
|
| 229 |
+
C, # (S, N) output
|
| 230 |
+
As, # (S,) per-token activation scales
|
| 231 |
+
Bs, # (E, 1, 1) per-tensor weight scales
|
| 232 |
+
Offsets, # (E,) int32 — cumulative row-end per expert
|
| 233 |
+
TileOffsets, # (E,) int32 — cumulative tile-end per expert
|
| 234 |
+
# Shape
|
| 235 |
+
S,
|
| 236 |
+
N,
|
| 237 |
+
K,
|
| 238 |
+
# Strides
|
| 239 |
+
stride_am,
|
| 240 |
+
stride_ak,
|
| 241 |
+
stride_be,
|
| 242 |
+
stride_bk,
|
| 243 |
+
stride_bn,
|
| 244 |
+
stride_cm,
|
| 245 |
+
stride_cn,
|
| 246 |
+
stride_as_m,
|
| 247 |
+
stride_bs_e,
|
| 248 |
+
stride_offs,
|
| 249 |
+
stride_tile,
|
| 250 |
+
# Meta-parameters
|
| 251 |
+
BLOCK_SIZE_M: tl.constexpr,
|
| 252 |
+
BLOCK_SIZE_N: tl.constexpr,
|
| 253 |
+
BLOCK_SIZE_K: tl.constexpr,
|
| 254 |
+
NUM_EXPERTS: tl.constexpr,
|
| 255 |
+
NUM_EXPERTS_BIT_LENGTH: tl.constexpr,
|
| 256 |
+
):
|
| 257 |
+
"""Tensor-scale grouped FP8 expert matmul kernel.
|
| 258 |
+
|
| 259 |
+
Uses grouped expert scheduling with pre-quantized activations plus
|
| 260 |
+
per-token activation scales and per-expert tensor weight scales.
|
| 261 |
+
"""
|
| 262 |
+
pid_m = tl.program_id(axis=0)
|
| 263 |
+
pid_n = tl.program_id(axis=1)
|
| 264 |
+
|
| 265 |
+
total_tiles = tl.load(TileOffsets + (NUM_EXPERTS - 1) * stride_tile)
|
| 266 |
+
if pid_m >= total_tiles:
|
| 267 |
+
return
|
| 268 |
+
|
| 269 |
+
expert_id, offs_global_m, row_mask, offs_bn, offs_k = _grouped_tile_setup(
|
| 270 |
+
pid_m,
|
| 271 |
+
pid_n,
|
| 272 |
+
Offsets,
|
| 273 |
+
TileOffsets,
|
| 274 |
+
stride_offs,
|
| 275 |
+
stride_tile,
|
| 276 |
+
NUM_EXPERTS,
|
| 277 |
+
NUM_EXPERTS_BIT_LENGTH,
|
| 278 |
+
BLOCK_SIZE_M,
|
| 279 |
+
BLOCK_SIZE_N,
|
| 280 |
+
BLOCK_SIZE_K,
|
| 281 |
+
)
|
| 282 |
+
|
| 283 |
+
a_ptrs = A + offs_global_m[:, None] * stride_am + offs_k[None, :] * stride_ak
|
| 284 |
+
b_ptrs = (
|
| 285 |
+
B
|
| 286 |
+
+ expert_id * stride_be
|
| 287 |
+
+ offs_k[:, None] * stride_bk
|
| 288 |
+
+ offs_bn[None, :] * stride_bn
|
| 289 |
+
)
|
| 290 |
+
|
| 291 |
+
a_s = tl.load(As + offs_global_m * stride_as_m, mask=row_mask, other=0.0)
|
| 292 |
+
b_s = tl.load(Bs + expert_id * stride_bs_e)
|
| 293 |
+
|
| 294 |
+
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
| 295 |
+
for _ in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
|
| 296 |
+
a = tl.load(a_ptrs, mask=row_mask[:, None], other=0.0)
|
| 297 |
+
b = tl.load(b_ptrs)
|
| 298 |
+
accumulator += tl.dot(a, b)
|
| 299 |
+
a_ptrs += BLOCK_SIZE_K * stride_ak
|
| 300 |
+
b_ptrs += BLOCK_SIZE_K * stride_bk
|
| 301 |
+
accumulator = accumulator * a_s[:, None] * b_s
|
| 302 |
+
|
| 303 |
+
_store_tile(
|
| 304 |
+
C, accumulator, offs_global_m, offs_bn, row_mask, stride_cm, stride_cn
|
| 305 |
+
)
|
| 306 |
+
|
| 307 |
+
|
| 308 |
+
@triton.autotune(
|
| 309 |
+
configs=get_accelerator_autotuning_configs(with_block_sizes=True),
|
| 310 |
+
key=["N", "K", "BLOCK_SIZE_M"],
|
| 311 |
+
)
|
| 312 |
+
@triton.jit
|
| 313 |
+
def w4a8_mx_dynamic_fp4_matmul_grouped_kernel(
|
| 314 |
+
A, # (S, K) raw BF16/FP16 activations, sorted by expert id
|
| 315 |
+
B, # (E, N, K // 2) packed FP4 (E2M1) expert weights as int8
|
| 316 |
+
C, # (S, N) output
|
| 317 |
+
Bs, # (E, N, K // SCALE_GROUP_K) UE8M0 weight scales
|
| 318 |
+
Offsets, # (E,) int32 — cumulative row-end per expert
|
| 319 |
+
TileOffsets, # (E,) int32 — cumulative tile-end per expert
|
| 320 |
+
# Shape
|
| 321 |
+
S,
|
| 322 |
+
N,
|
| 323 |
+
K,
|
| 324 |
+
# Strides
|
| 325 |
+
stride_am,
|
| 326 |
+
stride_ak,
|
| 327 |
+
stride_be,
|
| 328 |
+
stride_bk,
|
| 329 |
+
stride_bn,
|
| 330 |
+
stride_cm,
|
| 331 |
+
stride_cn,
|
| 332 |
+
stride_bs_e,
|
| 333 |
+
stride_bs_k,
|
| 334 |
+
stride_bs_n,
|
| 335 |
+
stride_offs,
|
| 336 |
+
stride_tile,
|
| 337 |
+
# Meta-parameters
|
| 338 |
+
BLOCK_SIZE_M: tl.constexpr,
|
| 339 |
+
BLOCK_SIZE_N: tl.constexpr,
|
| 340 |
+
BLOCK_SIZE_K: tl.constexpr,
|
| 341 |
+
NUM_EXPERTS: tl.constexpr,
|
| 342 |
+
NUM_EXPERTS_BIT_LENGTH: tl.constexpr,
|
| 343 |
+
NIBBLES_PER_BYTE: tl.constexpr,
|
| 344 |
+
SCALE_GROUP_K: tl.constexpr,
|
| 345 |
+
):
|
| 346 |
+
"""Grouped MXFP4 (W4A8) expert matmul with fused activation quant.
|
| 347 |
+
|
| 348 |
+
Tokens are assumed sorted by expert. The kernel maps each M-tile to its
|
| 349 |
+
owning expert via ``TileOffsets``, quantizes ``A`` to FP8 per K-group inline
|
| 350 |
+
(UE8M0 scale), then ``tl.dot_scaled`` against packed FP4 weights.
|
| 351 |
+
"""
|
| 352 |
+
pid_m = tl.program_id(axis=0)
|
| 353 |
+
pid_n = tl.program_id(axis=1)
|
| 354 |
+
|
| 355 |
+
total_tiles = tl.load(TileOffsets + (NUM_EXPERTS - 1) * stride_tile)
|
| 356 |
+
if pid_m >= total_tiles:
|
| 357 |
+
return
|
| 358 |
+
|
| 359 |
+
expert_id, offs_global_m, row_mask, offs_bn, offs_k = _grouped_tile_setup(
|
| 360 |
+
pid_m,
|
| 361 |
+
pid_n,
|
| 362 |
+
Offsets,
|
| 363 |
+
TileOffsets,
|
| 364 |
+
stride_offs,
|
| 365 |
+
stride_tile,
|
| 366 |
+
NUM_EXPERTS,
|
| 367 |
+
NUM_EXPERTS_BIT_LENGTH,
|
| 368 |
+
BLOCK_SIZE_M,
|
| 369 |
+
BLOCK_SIZE_N,
|
| 370 |
+
BLOCK_SIZE_K,
|
| 371 |
+
)
|
| 372 |
+
offs_k_byte = tl.arange(0, BLOCK_SIZE_K // NIBBLES_PER_BYTE)
|
| 373 |
+
offs_sf = tl.arange(0, BLOCK_SIZE_K // SCALE_GROUP_K)
|
| 374 |
+
|
| 375 |
+
a_ptrs = A + offs_global_m[:, None] * stride_am + offs_k[None, :] * stride_ak
|
| 376 |
+
b_ptrs = (
|
| 377 |
+
B
|
| 378 |
+
+ expert_id * stride_be
|
| 379 |
+
+ offs_k_byte[:, None] * stride_bk
|
| 380 |
+
+ offs_bn[None, :] * stride_bn
|
| 381 |
+
)
|
| 382 |
+
bs_ptrs = (
|
| 383 |
+
Bs
|
| 384 |
+
+ expert_id * stride_bs_e
|
| 385 |
+
+ offs_bn[:, None] * stride_bs_n
|
| 386 |
+
+ offs_sf[None, :] * stride_bs_k
|
| 387 |
+
)
|
| 388 |
+
|
| 389 |
+
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
| 390 |
+
for _ in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
|
| 391 |
+
a_raw = tl.load(a_ptrs, mask=row_mask[:, None], other=0.0).to(tl.float32)
|
| 392 |
+
a, a_scale = mx_act_quant_inline(
|
| 393 |
+
a_raw, BLOCK_SIZE_M, BLOCK_SIZE_K, SCALE_GROUP_K
|
| 394 |
+
)
|
| 395 |
+
b = tl.load(b_ptrs).to(tl.uint8)
|
| 396 |
+
b_s = tl.load(bs_ptrs).to(tl.uint8)
|
| 397 |
+
accumulator += tl.dot_scaled(a, a_scale, "e4m3", b, b_s, "e2m1")
|
| 398 |
+
a_ptrs += BLOCK_SIZE_K * stride_ak
|
| 399 |
+
b_ptrs += (BLOCK_SIZE_K // NIBBLES_PER_BYTE) * stride_bk
|
| 400 |
+
bs_ptrs += (BLOCK_SIZE_K // SCALE_GROUP_K) * stride_bs_k
|
| 401 |
+
|
| 402 |
+
_store_tile(
|
| 403 |
+
C, accumulator, offs_global_m, offs_bn, row_mask, stride_cm, stride_cn
|
| 404 |
+
)
|
| 405 |
+
|
| 406 |
+
|
| 407 |
+
@triton.autotune(
|
| 408 |
+
configs=get_accelerator_autotuning_configs(with_block_sizes=True),
|
| 409 |
+
key=["N", "K", "BLOCK_SIZE_M"],
|
| 410 |
+
)
|
| 411 |
+
@triton.jit
|
| 412 |
+
def w8a8_mx_dynamic_fp8_matmul_grouped_kernel(
|
| 413 |
+
A, # (S, K) raw BF16/FP16 activations, sorted by expert id
|
| 414 |
+
B, # (E, N, K) E4M3 expert weights (unpacked)
|
| 415 |
+
C, # (S, N) output
|
| 416 |
+
Bs, # (E, N, K // SCALE_GROUP_K) UE8M0 weight scales
|
| 417 |
+
Offsets, # (E,) int32 — cumulative row-end per expert
|
| 418 |
+
TileOffsets, # (E,) int32 — cumulative tile-end per expert
|
| 419 |
+
# Shape
|
| 420 |
+
S,
|
| 421 |
+
N,
|
| 422 |
+
K,
|
| 423 |
+
# Strides
|
| 424 |
+
stride_am,
|
| 425 |
+
stride_ak,
|
| 426 |
+
stride_be,
|
| 427 |
+
stride_bk,
|
| 428 |
+
stride_bn,
|
| 429 |
+
stride_cm,
|
| 430 |
+
stride_cn,
|
| 431 |
+
stride_bs_e,
|
| 432 |
+
stride_bs_k,
|
| 433 |
+
stride_bs_n,
|
| 434 |
+
stride_offs,
|
| 435 |
+
stride_tile,
|
| 436 |
+
# Meta-parameters
|
| 437 |
+
BLOCK_SIZE_M: tl.constexpr,
|
| 438 |
+
BLOCK_SIZE_N: tl.constexpr,
|
| 439 |
+
BLOCK_SIZE_K: tl.constexpr,
|
| 440 |
+
NUM_EXPERTS: tl.constexpr,
|
| 441 |
+
NUM_EXPERTS_BIT_LENGTH: tl.constexpr,
|
| 442 |
+
SCALE_GROUP_K: tl.constexpr,
|
| 443 |
+
):
|
| 444 |
+
"""Grouped MXFP8 (W8A8) expert matmul with fused activation quant.
|
| 445 |
+
|
| 446 |
+
Tokens are assumed sorted by expert. The kernel maps each M-tile to its
|
| 447 |
+
owning expert via ``TileOffsets``, quantizes ``A`` to E4M3 per K-group inline
|
| 448 |
+
(UE8M0 scale), then ``tl.dot_scaled`` against E4M3 weights. Mirrors the
|
| 449 |
+
grouped MXFP4 kernel but with unpacked weights and ``"e4m3"`` on both operands.
|
| 450 |
+
"""
|
| 451 |
+
pid_m = tl.program_id(axis=0)
|
| 452 |
+
pid_n = tl.program_id(axis=1)
|
| 453 |
+
|
| 454 |
+
total_tiles = tl.load(TileOffsets + (NUM_EXPERTS - 1) * stride_tile)
|
| 455 |
+
if pid_m >= total_tiles:
|
| 456 |
+
return
|
| 457 |
+
|
| 458 |
+
expert_id, offs_global_m, row_mask, offs_bn, offs_k = _grouped_tile_setup(
|
| 459 |
+
pid_m,
|
| 460 |
+
pid_n,
|
| 461 |
+
Offsets,
|
| 462 |
+
TileOffsets,
|
| 463 |
+
stride_offs,
|
| 464 |
+
stride_tile,
|
| 465 |
+
NUM_EXPERTS,
|
| 466 |
+
NUM_EXPERTS_BIT_LENGTH,
|
| 467 |
+
BLOCK_SIZE_M,
|
| 468 |
+
BLOCK_SIZE_N,
|
| 469 |
+
BLOCK_SIZE_K,
|
| 470 |
+
)
|
| 471 |
+
offs_sf = tl.arange(0, BLOCK_SIZE_K // SCALE_GROUP_K)
|
| 472 |
+
|
| 473 |
+
a_ptrs = A + offs_global_m[:, None] * stride_am + offs_k[None, :] * stride_ak
|
| 474 |
+
b_ptrs = (
|
| 475 |
+
B
|
| 476 |
+
+ expert_id * stride_be
|
| 477 |
+
+ offs_k[:, None] * stride_bk
|
| 478 |
+
+ offs_bn[None, :] * stride_bn
|
| 479 |
+
)
|
| 480 |
+
bs_ptrs = (
|
| 481 |
+
Bs
|
| 482 |
+
+ expert_id * stride_bs_e
|
| 483 |
+
+ offs_bn[:, None] * stride_bs_n
|
| 484 |
+
+ offs_sf[None, :] * stride_bs_k
|
| 485 |
+
)
|
| 486 |
+
|
| 487 |
+
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
| 488 |
+
for _ in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
|
| 489 |
+
a_raw = tl.load(a_ptrs, mask=row_mask[:, None], other=0.0).to(tl.float32)
|
| 490 |
+
a, a_scale = mx_act_quant_inline(
|
| 491 |
+
a_raw, BLOCK_SIZE_M, BLOCK_SIZE_K, SCALE_GROUP_K
|
| 492 |
+
)
|
| 493 |
+
b = tl.load(b_ptrs)
|
| 494 |
+
b_s = tl.load(bs_ptrs).to(tl.uint8)
|
| 495 |
+
accumulator += tl.dot_scaled(a, a_scale, "e4m3", b, b_s, "e4m3")
|
| 496 |
+
a_ptrs += BLOCK_SIZE_K * stride_ak
|
| 497 |
+
b_ptrs += BLOCK_SIZE_K * stride_bk
|
| 498 |
+
bs_ptrs += (BLOCK_SIZE_K // SCALE_GROUP_K) * stride_bs_k
|
| 499 |
+
|
| 500 |
+
_store_tile(
|
| 501 |
+
C, accumulator, offs_global_m, offs_bn, row_mask, stride_cm, stride_cn
|
| 502 |
+
)
|
| 503 |
+
|
| 504 |
+
|
| 505 |
+
@triton_op(
|
| 506 |
+
add_op_namespace_prefix("w8a8_block_dynamic_fp8_matmul_grouped"), mutates_args=()
|
| 507 |
+
)
|
| 508 |
+
def _w8a8_block_dynamic_fp8_matmul_grouped(
|
| 509 |
+
A: torch.Tensor,
|
| 510 |
+
B: torch.Tensor,
|
| 511 |
+
Bs: torch.Tensor,
|
| 512 |
+
offsets: torch.Tensor,
|
| 513 |
+
tokens_per_expert: torch.Tensor,
|
| 514 |
+
block_size: list[int],
|
| 515 |
+
output_dtype: torch.dtype | None = None,
|
| 516 |
+
) -> torch.Tensor:
|
| 517 |
+
"""Block-scale grouped FP8 matmul: C = A @ B.T per expert, with fused act quant.
|
| 518 |
+
|
| 519 |
+
A: (S, K) raw bf16/fp16 activations, sorted by expert
|
| 520 |
+
B: (E, N, K) FP8 expert weights
|
| 521 |
+
Bs: (E, N // block_n, K // block_k) per-block weight scales
|
| 522 |
+
"""
|
| 523 |
+
assert A.ndim == 2, f"A must be 2D (S, K), got ndim={A.ndim}"
|
| 524 |
+
assert A.is_contiguous(), "A must be contiguous"
|
| 525 |
+
assert B.ndim == 3, f"B must be 3D (E, N, K), got ndim={B.ndim}"
|
| 526 |
+
assert B.is_contiguous(), "B must be contiguous"
|
| 527 |
+
assert A.shape[1] == B.shape[2], (
|
| 528 |
+
f"K mismatch: A has K={A.shape[1]}, B has K={B.shape[2]}"
|
| 529 |
+
)
|
| 530 |
+
|
| 531 |
+
S, K = A.shape
|
| 532 |
+
E, N, _ = B.shape
|
| 533 |
+
|
| 534 |
+
assert len(block_size) == 2, (
|
| 535 |
+
f"block_size must be [block_n, block_k], got {block_size}"
|
| 536 |
+
)
|
| 537 |
+
block_n, block_k = block_size[0], block_size[1]
|
| 538 |
+
# MoE expert dimensions must be block-aligned; non-aligned N/K is not supported.
|
| 539 |
+
assert N % block_n == 0, f"N ({N}) must be divisible by block_n ({block_n})"
|
| 540 |
+
assert K % block_k == 0, f"K ({K}) must be divisible by block_k ({block_k})"
|
| 541 |
+
assert Bs.ndim == 3, (
|
| 542 |
+
f"Bs must be 3D (E, N//block_n, K//block_k), got ndim={Bs.ndim}"
|
| 543 |
+
)
|
| 544 |
+
assert Bs.shape == (E, N // block_n, K // block_k), (
|
| 545 |
+
f"Bs shape {tuple(Bs.shape)} != expected ({E}, {N // block_n}, {K // block_k})"
|
| 546 |
+
)
|
| 547 |
+
|
| 548 |
+
Bs = ue8m0_as_uint8(Bs)
|
| 549 |
+
C = A.new_empty(S, N, dtype=output_dtype)
|
| 550 |
+
BLOCK_SIZE_M = adaptive_block_size_m((S + E - 1) // E)
|
| 551 |
+
tile_offsets, max_m_tiles = grouped_tile_layout(
|
| 552 |
+
tokens_per_expert, BLOCK_SIZE_M, S, E
|
| 553 |
+
)
|
| 554 |
+
grid = (max_m_tiles, triton.cdiv(N, block_n))
|
| 555 |
+
|
| 556 |
+
with device_context(A.device):
|
| 557 |
+
wrap_triton(w8a8_block_dynamic_fp8_matmul_grouped_kernel)[grid](
|
| 558 |
+
A,
|
| 559 |
+
B,
|
| 560 |
+
C,
|
| 561 |
+
Bs,
|
| 562 |
+
offsets,
|
| 563 |
+
tile_offsets,
|
| 564 |
+
S,
|
| 565 |
+
N,
|
| 566 |
+
K,
|
| 567 |
+
A.stride(0),
|
| 568 |
+
A.stride(1),
|
| 569 |
+
B.stride(0),
|
| 570 |
+
B.stride(2),
|
| 571 |
+
B.stride(1),
|
| 572 |
+
C.stride(0),
|
| 573 |
+
C.stride(1),
|
| 574 |
+
Bs.stride(0),
|
| 575 |
+
Bs.stride(2),
|
| 576 |
+
Bs.stride(1),
|
| 577 |
+
offsets.stride(0),
|
| 578 |
+
tile_offsets.stride(0),
|
| 579 |
+
# Meta-parameters
|
| 580 |
+
NUM_EXPERTS=E,
|
| 581 |
+
BLOCK_SIZE_N=block_n,
|
| 582 |
+
BLOCK_SIZE_K=block_k,
|
| 583 |
+
BLOCK_SIZE_M=BLOCK_SIZE_M,
|
| 584 |
+
NUM_EXPERTS_BIT_LENGTH=E.bit_length(),
|
| 585 |
+
)
|
| 586 |
+
|
| 587 |
+
return C
|
| 588 |
+
|
| 589 |
+
|
| 590 |
+
@triton_op(
|
| 591 |
+
add_op_namespace_prefix("w8a8_tensor_dynamic_fp8_matmul_grouped"), mutates_args=()
|
| 592 |
+
)
|
| 593 |
+
def _w8a8_tensor_dynamic_fp8_matmul_grouped(
|
| 594 |
+
A: torch.Tensor,
|
| 595 |
+
B: torch.Tensor,
|
| 596 |
+
Bs: torch.Tensor,
|
| 597 |
+
offsets: torch.Tensor,
|
| 598 |
+
tokens_per_expert: torch.Tensor,
|
| 599 |
+
output_dtype: torch.dtype | None = None,
|
| 600 |
+
) -> torch.Tensor:
|
| 601 |
+
"""Tensor-scale grouped FP8 matmul: C = A @ B.T per expert, with fused act quant.
|
| 602 |
+
|
| 603 |
+
A: (S, K) raw bf16/fp16 activations, sorted by expert
|
| 604 |
+
B: (E, N, K) FP8 expert weights
|
| 605 |
+
Bs: (E,) or (E, 1, 1) per-expert weight scales
|
| 606 |
+
"""
|
| 607 |
+
assert A.ndim == 2, f"A must be 2D (S, K), got ndim={A.ndim}"
|
| 608 |
+
assert A.is_contiguous(), "A must be contiguous"
|
| 609 |
+
assert B.ndim == 3, f"B must be 3D (E, N, K), got ndim={B.ndim}"
|
| 610 |
+
assert B.is_contiguous(), "B must be contiguous"
|
| 611 |
+
assert A.shape[1] == B.shape[2], (
|
| 612 |
+
f"K mismatch: A has K={A.shape[1]}, B has K={B.shape[2]}"
|
| 613 |
+
)
|
| 614 |
+
|
| 615 |
+
S, K = A.shape
|
| 616 |
+
E, N, _ = B.shape
|
| 617 |
+
|
| 618 |
+
# Normalize Bs to (E, 1, 1)
|
| 619 |
+
if Bs.ndim == 1:
|
| 620 |
+
assert Bs.shape[0] == E, f"Bs shape {tuple(Bs.shape)} != expected ({E},)"
|
| 621 |
+
Bs = Bs.reshape(E, 1, 1)
|
| 622 |
+
else:
|
| 623 |
+
assert Bs.shape == (E, 1, 1), (
|
| 624 |
+
f"Bs shape {tuple(Bs.shape)} != expected ({E}, 1, 1)"
|
| 625 |
+
)
|
| 626 |
+
|
| 627 |
+
qA, As = fp8_act_quant(A, K)
|
| 628 |
+
C = A.new_empty(S, N, dtype=output_dtype)
|
| 629 |
+
BLOCK_SIZE_M = adaptive_block_size_m((S + E - 1) // E)
|
| 630 |
+
tile_offsets, max_m_tiles = grouped_tile_layout(
|
| 631 |
+
tokens_per_expert, BLOCK_SIZE_M, S, E
|
| 632 |
+
)
|
| 633 |
+
|
| 634 |
+
def grid(META):
|
| 635 |
+
return (max_m_tiles, triton.cdiv(N, META["BLOCK_SIZE_N"]))
|
| 636 |
+
|
| 637 |
+
with device_context(A.device):
|
| 638 |
+
wrap_triton(w8a8_tensor_dynamic_fp8_matmul_grouped_kernel)[grid](
|
| 639 |
+
qA,
|
| 640 |
+
B,
|
| 641 |
+
C,
|
| 642 |
+
As,
|
| 643 |
+
Bs,
|
| 644 |
+
offsets,
|
| 645 |
+
tile_offsets,
|
| 646 |
+
S,
|
| 647 |
+
N,
|
| 648 |
+
K,
|
| 649 |
+
qA.stride(0),
|
| 650 |
+
qA.stride(1),
|
| 651 |
+
B.stride(0),
|
| 652 |
+
B.stride(2),
|
| 653 |
+
B.stride(1),
|
| 654 |
+
C.stride(0),
|
| 655 |
+
C.stride(1),
|
| 656 |
+
As.stride(0),
|
| 657 |
+
Bs.stride(0),
|
| 658 |
+
offsets.stride(0),
|
| 659 |
+
tile_offsets.stride(0),
|
| 660 |
+
# Meta-parameters (BLOCK_SIZE_N, BLOCK_SIZE_K come from autotune Config)
|
| 661 |
+
NUM_EXPERTS=E,
|
| 662 |
+
BLOCK_SIZE_M=BLOCK_SIZE_M,
|
| 663 |
+
NUM_EXPERTS_BIT_LENGTH=E.bit_length(),
|
| 664 |
+
)
|
| 665 |
+
|
| 666 |
+
return C
|
| 667 |
+
|
| 668 |
+
|
| 669 |
+
def w8a8_block_dynamic_fp8_matmul_grouped(
|
| 670 |
+
A: torch.Tensor,
|
| 671 |
+
B: torch.Tensor,
|
| 672 |
+
Bs: torch.Tensor,
|
| 673 |
+
offsets: torch.Tensor,
|
| 674 |
+
tokens_per_expert: torch.Tensor,
|
| 675 |
+
block_size: list[int],
|
| 676 |
+
output_dtype: torch.dtype | None = None,
|
| 677 |
+
) -> torch.Tensor:
|
| 678 |
+
"""Block-scale grouped FP8 matmul with fused activation quantization.
|
| 679 |
+
|
| 680 |
+
A: (S, K) raw activations sorted by expert, bf16/fp16/fp32
|
| 681 |
+
B: (E, N, K) FP8 expert weights
|
| 682 |
+
Bs: (E, N // block_n, K // block_k) per-block weight scales
|
| 683 |
+
output_dtype: defaults to ``A.dtype``
|
| 684 |
+
"""
|
| 685 |
+
return ops.w8a8_block_dynamic_fp8_matmul_grouped(
|
| 686 |
+
A, B, Bs, offsets, tokens_per_expert, block_size, output_dtype
|
| 687 |
+
)
|
| 688 |
+
|
| 689 |
+
|
| 690 |
+
def w8a8_tensor_dynamic_fp8_matmul_grouped(
|
| 691 |
+
A: torch.Tensor,
|
| 692 |
+
B: torch.Tensor,
|
| 693 |
+
Bs: torch.Tensor,
|
| 694 |
+
offsets: torch.Tensor,
|
| 695 |
+
tokens_per_expert: torch.Tensor,
|
| 696 |
+
output_dtype: torch.dtype | None = None,
|
| 697 |
+
) -> torch.Tensor:
|
| 698 |
+
"""Tensor-scale grouped FP8 matmul with fused activation quantization.
|
| 699 |
+
|
| 700 |
+
A: (S, K) raw activations sorted by expert, bf16/fp16/fp32
|
| 701 |
+
B: (E, N, K) FP8 expert weights
|
| 702 |
+
Bs: (E,) or (E, 1, 1) per-expert weight scales
|
| 703 |
+
output_dtype: defaults to ``A.dtype``
|
| 704 |
+
"""
|
| 705 |
+
return ops.w8a8_tensor_dynamic_fp8_matmul_grouped(
|
| 706 |
+
A, B, Bs, offsets, tokens_per_expert, output_dtype
|
| 707 |
+
)
|
| 708 |
+
|
| 709 |
+
|
| 710 |
+
@triton_op(
|
| 711 |
+
add_op_namespace_prefix("w4a8_mx_dynamic_fp4_matmul_grouped"), mutates_args=()
|
| 712 |
+
)
|
| 713 |
+
def _w4a8_mx_dynamic_fp4_matmul_grouped(
|
| 714 |
+
A: torch.Tensor,
|
| 715 |
+
B: torch.Tensor,
|
| 716 |
+
Bs: torch.Tensor,
|
| 717 |
+
offsets: torch.Tensor,
|
| 718 |
+
tokens_per_expert: torch.Tensor,
|
| 719 |
+
output_dtype: torch.dtype | None = None,
|
| 720 |
+
) -> torch.Tensor:
|
| 721 |
+
"""Grouped MXFP4 (W4A8) matmul with fused activation quant.
|
| 722 |
+
|
| 723 |
+
A: (S, K) raw activations, bf16/fp16/fp32, expert-sorted (quantized inline)
|
| 724 |
+
B: (E, N, K // 2) packed FP4 (E2M1) expert weights, two codes per int8
|
| 725 |
+
Bs: (E, N, K // 32) UE8M0 weight scales
|
| 726 |
+
offsets: (E,) — exclusive prefix of expert token counts (cumsum)
|
| 727 |
+
tokens_per_expert: (E,) — per-expert row count
|
| 728 |
+
|
| 729 |
+
BLOCK_SIZE_N and BLOCK_SIZE_K are autotuned.
|
| 730 |
+
"""
|
| 731 |
+
assert A.ndim == 2 and B.ndim == 3 and Bs.ndim == 3
|
| 732 |
+
assert offsets.ndim == 1 and tokens_per_expert.ndim == 1
|
| 733 |
+
assert B.dtype == torch.int8, f"B must be int8 (packed FP4), got {B.dtype}"
|
| 734 |
+
assert Bs.dtype == torch.float8_e8m0fnu, (
|
| 735 |
+
f"Bs must be float8_e8m0fnu, got {Bs.dtype}"
|
| 736 |
+
)
|
| 737 |
+
|
| 738 |
+
S, K = A.shape
|
| 739 |
+
E, N, K_half = B.shape
|
| 740 |
+
assert offsets.shape[0] == E and tokens_per_expert.shape[0] == E
|
| 741 |
+
assert K == NIBBLES_PER_BYTE * K_half, (
|
| 742 |
+
f"K (={K}) must equal {NIBBLES_PER_BYTE} * B.shape[2] (={K_half})"
|
| 743 |
+
)
|
| 744 |
+
assert K % MX_SCALE_GROUP_K == 0, (
|
| 745 |
+
f"K (={K}) must be a multiple of {MX_SCALE_GROUP_K}"
|
| 746 |
+
)
|
| 747 |
+
assert Bs.shape == (E, N, K // MX_SCALE_GROUP_K), (
|
| 748 |
+
f"Bs shape {tuple(Bs.shape)} != ({E}, {N}, {K // MX_SCALE_GROUP_K})"
|
| 749 |
+
)
|
| 750 |
+
|
| 751 |
+
bs_u8 = ue8m0_as_uint8(Bs)
|
| 752 |
+
C = A.new_empty((S, N), dtype=output_dtype)
|
| 753 |
+
BLOCK_SIZE_M = adaptive_block_size_m((S + E - 1) // E)
|
| 754 |
+
tile_offsets, max_m_tiles = grouped_tile_layout(
|
| 755 |
+
tokens_per_expert, BLOCK_SIZE_M, S, E
|
| 756 |
+
)
|
| 757 |
+
|
| 758 |
+
def grid(META):
|
| 759 |
+
return (max_m_tiles, triton.cdiv(N, META["BLOCK_SIZE_N"]))
|
| 760 |
+
|
| 761 |
+
with device_context(A.device):
|
| 762 |
+
wrap_triton(w4a8_mx_dynamic_fp4_matmul_grouped_kernel)[grid](
|
| 763 |
+
A,
|
| 764 |
+
B,
|
| 765 |
+
C,
|
| 766 |
+
bs_u8,
|
| 767 |
+
offsets,
|
| 768 |
+
tile_offsets,
|
| 769 |
+
S,
|
| 770 |
+
N,
|
| 771 |
+
K,
|
| 772 |
+
A.stride(0),
|
| 773 |
+
A.stride(1),
|
| 774 |
+
B.stride(0),
|
| 775 |
+
B.stride(2),
|
| 776 |
+
B.stride(1),
|
| 777 |
+
C.stride(0),
|
| 778 |
+
C.stride(1),
|
| 779 |
+
bs_u8.stride(0),
|
| 780 |
+
bs_u8.stride(2),
|
| 781 |
+
bs_u8.stride(1),
|
| 782 |
+
offsets.stride(0),
|
| 783 |
+
tile_offsets.stride(0),
|
| 784 |
+
# Meta-parameters (BLOCK_SIZE_N, BLOCK_SIZE_K come from autotune Config)
|
| 785 |
+
NUM_EXPERTS=E,
|
| 786 |
+
BLOCK_SIZE_M=BLOCK_SIZE_M,
|
| 787 |
+
NUM_EXPERTS_BIT_LENGTH=E.bit_length(),
|
| 788 |
+
NIBBLES_PER_BYTE=NIBBLES_PER_BYTE,
|
| 789 |
+
SCALE_GROUP_K=MX_SCALE_GROUP_K,
|
| 790 |
+
)
|
| 791 |
+
return C
|
| 792 |
+
|
| 793 |
+
|
| 794 |
+
def w4a8_mx_dynamic_fp4_matmul_grouped(
|
| 795 |
+
A: torch.Tensor,
|
| 796 |
+
B: torch.Tensor,
|
| 797 |
+
Bs: torch.Tensor,
|
| 798 |
+
offsets: torch.Tensor,
|
| 799 |
+
tokens_per_expert: torch.Tensor,
|
| 800 |
+
output_dtype: torch.dtype | None = None,
|
| 801 |
+
) -> torch.Tensor:
|
| 802 |
+
"""Grouped MXFP4 (W4A8) matmul with fused activation quant. Per-expert
|
| 803 |
+
``C[s] = A[s] @ B[e].T`` over contiguous, expert-sorted rows. Tile shape
|
| 804 |
+
autotuned; FP4 scale granularity is fixed at 32."""
|
| 805 |
+
return ops.w4a8_mx_dynamic_fp4_matmul_grouped(
|
| 806 |
+
A, B, Bs, offsets, tokens_per_expert, output_dtype
|
| 807 |
+
)
|
| 808 |
+
|
| 809 |
+
|
| 810 |
+
@triton_op(
|
| 811 |
+
add_op_namespace_prefix("w8a8_mx_dynamic_fp8_matmul_grouped"), mutates_args=()
|
| 812 |
+
)
|
| 813 |
+
def _w8a8_mx_dynamic_fp8_matmul_grouped(
|
| 814 |
+
A: torch.Tensor,
|
| 815 |
+
B: torch.Tensor,
|
| 816 |
+
Bs: torch.Tensor,
|
| 817 |
+
offsets: torch.Tensor,
|
| 818 |
+
tokens_per_expert: torch.Tensor,
|
| 819 |
+
output_dtype: torch.dtype | None = None,
|
| 820 |
+
) -> torch.Tensor:
|
| 821 |
+
"""Grouped MXFP8 (W8A8) matmul: ``C[s] = A[s] @ B[e].T`` (E4M3 × E4M3, UE8M0
|
| 822 |
+
group-32 scales) with fused activation quant.
|
| 823 |
+
|
| 824 |
+
A: (S, K) raw activations, bf16/fp16/fp32, expert-sorted (quantized inline)
|
| 825 |
+
B: (E, N, K) E4M3 expert weights (unpacked)
|
| 826 |
+
Bs: (E, N, K // 32) UE8M0 weight scales
|
| 827 |
+
offsets: (E,) — exclusive prefix of expert token counts (cumsum)
|
| 828 |
+
tokens_per_expert: (E,) — per-expert row count
|
| 829 |
+
|
| 830 |
+
BLOCK_SIZE_N and BLOCK_SIZE_K are autotuned; MX scale granularity is fixed at 32.
|
| 831 |
+
"""
|
| 832 |
+
assert A.ndim == 2 and B.ndim == 3 and Bs.ndim == 3
|
| 833 |
+
assert offsets.ndim == 1 and tokens_per_expert.ndim == 1
|
| 834 |
+
assert B.dtype == torch.float8_e4m3fn, (
|
| 835 |
+
f"B must be float8_e4m3fn (E4M3 weights), got {B.dtype}"
|
| 836 |
+
)
|
| 837 |
+
assert Bs.dtype == torch.float8_e8m0fnu, (
|
| 838 |
+
f"Bs must be float8_e8m0fnu, got {Bs.dtype}"
|
| 839 |
+
)
|
| 840 |
+
|
| 841 |
+
S, K = A.shape
|
| 842 |
+
E, N, K_b = B.shape
|
| 843 |
+
assert offsets.shape[0] == E and tokens_per_expert.shape[0] == E
|
| 844 |
+
assert K == K_b, f"K mismatch: A has K={K}, B has K={K_b}"
|
| 845 |
+
assert K % MX_SCALE_GROUP_K == 0, (
|
| 846 |
+
f"K (={K}) must be a multiple of {MX_SCALE_GROUP_K}"
|
| 847 |
+
)
|
| 848 |
+
assert Bs.shape == (E, N, K // MX_SCALE_GROUP_K), (
|
| 849 |
+
f"Bs shape {tuple(Bs.shape)} != ({E}, {N}, {K // MX_SCALE_GROUP_K})"
|
| 850 |
+
)
|
| 851 |
+
|
| 852 |
+
bs_u8 = ue8m0_as_uint8(Bs)
|
| 853 |
+
C = A.new_empty((S, N), dtype=output_dtype)
|
| 854 |
+
BLOCK_SIZE_M = adaptive_block_size_m((S + E - 1) // E)
|
| 855 |
+
tile_offsets, max_m_tiles = grouped_tile_layout(
|
| 856 |
+
tokens_per_expert, BLOCK_SIZE_M, S, E
|
| 857 |
+
)
|
| 858 |
+
|
| 859 |
+
def grid(META):
|
| 860 |
+
return (max_m_tiles, triton.cdiv(N, META["BLOCK_SIZE_N"]))
|
| 861 |
+
|
| 862 |
+
with device_context(A.device):
|
| 863 |
+
wrap_triton(w8a8_mx_dynamic_fp8_matmul_grouped_kernel)[grid](
|
| 864 |
+
A,
|
| 865 |
+
B,
|
| 866 |
+
C,
|
| 867 |
+
bs_u8,
|
| 868 |
+
offsets,
|
| 869 |
+
tile_offsets,
|
| 870 |
+
S,
|
| 871 |
+
N,
|
| 872 |
+
K,
|
| 873 |
+
A.stride(0),
|
| 874 |
+
A.stride(1),
|
| 875 |
+
B.stride(0),
|
| 876 |
+
B.stride(2),
|
| 877 |
+
B.stride(1),
|
| 878 |
+
C.stride(0),
|
| 879 |
+
C.stride(1),
|
| 880 |
+
bs_u8.stride(0),
|
| 881 |
+
bs_u8.stride(2),
|
| 882 |
+
bs_u8.stride(1),
|
| 883 |
+
offsets.stride(0),
|
| 884 |
+
tile_offsets.stride(0),
|
| 885 |
+
# Meta-parameters (BLOCK_SIZE_N, BLOCK_SIZE_K come from autotune Config)
|
| 886 |
+
NUM_EXPERTS=E,
|
| 887 |
+
BLOCK_SIZE_M=BLOCK_SIZE_M,
|
| 888 |
+
NUM_EXPERTS_BIT_LENGTH=E.bit_length(),
|
| 889 |
+
SCALE_GROUP_K=MX_SCALE_GROUP_K,
|
| 890 |
+
)
|
| 891 |
+
return C
|
| 892 |
+
|
| 893 |
+
|
| 894 |
+
def w8a8_mx_dynamic_fp8_matmul_grouped(
|
| 895 |
+
A: torch.Tensor,
|
| 896 |
+
B: torch.Tensor,
|
| 897 |
+
Bs: torch.Tensor,
|
| 898 |
+
offsets: torch.Tensor,
|
| 899 |
+
tokens_per_expert: torch.Tensor,
|
| 900 |
+
output_dtype: torch.dtype | None = None,
|
| 901 |
+
) -> torch.Tensor:
|
| 902 |
+
"""Grouped MXFP8 (W8A8) matmul with fused activation quant. Per-expert
|
| 903 |
+
``C[s] = A[s] @ B[e].T`` over contiguous, expert-sorted rows via the MX
|
| 904 |
+
scaled MMA (E4M3 × E4M3, UE8M0 group-32). Tile shape autotuned."""
|
| 905 |
+
return ops.w8a8_mx_dynamic_fp8_matmul_grouped(
|
| 906 |
+
A, B, Bs, offsets, tokens_per_expert, output_dtype
|
| 907 |
+
)
|
| 908 |
+
|
| 909 |
+
|
| 910 |
+
def matmul_grouped(
|
| 911 |
+
A: torch.Tensor,
|
| 912 |
+
B: torch.Tensor,
|
| 913 |
+
Bs: torch.Tensor,
|
| 914 |
+
offsets: torch.Tensor,
|
| 915 |
+
tokens_per_expert: torch.Tensor,
|
| 916 |
+
block_size: list[int] | None,
|
| 917 |
+
output_dtype: torch.dtype | None = None,
|
| 918 |
+
) -> torch.Tensor:
|
| 919 |
+
"""Grouped quantized matmul dispatcher (W8A8 FP8 or W4A8 FP4). Tokens must
|
| 920 |
+
be sorted by expert; M-tiles are mapped to experts via ``offsets``.
|
| 921 |
+
|
| 922 |
+
``output_dtype`` defaults to ``A.dtype``.
|
| 923 |
+
|
| 924 |
+
Routes by weight dtype and ``block_size``:
|
| 925 |
+
- ``B.dtype == int8`` (packed FP4) → ``w4a8_mx_dynamic_fp4_matmul_grouped``
|
| 926 |
+
(``block_size`` is ignored; FP4 tile shape is autotuned).
|
| 927 |
+
- ``B.dtype == float8_e4m3fn`` with UE8M0 group-32 ``Bs`` (shape ``[E, N, K//32]``)
|
| 928 |
+
→ ``w8a8_mx_dynamic_fp8_matmul_grouped`` (MX scaled MMA; ``block_size`` ignored).
|
| 929 |
+
- ``block_size`` None or full ``[N, K]`` → ``w8a8_tensor_dynamic_fp8_matmul_grouped``.
|
| 930 |
+
- otherwise → ``w8a8_block_dynamic_fp8_matmul_grouped``.
|
| 931 |
+
"""
|
| 932 |
+
if is_mxfp4(B, Bs):
|
| 933 |
+
return w4a8_mx_dynamic_fp4_matmul_grouped(
|
| 934 |
+
A, B, Bs, offsets, tokens_per_expert, output_dtype
|
| 935 |
+
)
|
| 936 |
+
|
| 937 |
+
if is_mxfp8(B, Bs):
|
| 938 |
+
return w8a8_mx_dynamic_fp8_matmul_grouped(
|
| 939 |
+
A, B, Bs, offsets, tokens_per_expert, output_dtype
|
| 940 |
+
)
|
| 941 |
+
|
| 942 |
+
if block_size is None or (
|
| 943 |
+
block_size[0] == B.size(1) and block_size[1] == B.size(2)
|
| 944 |
+
):
|
| 945 |
+
return w8a8_tensor_dynamic_fp8_matmul_grouped(
|
| 946 |
+
A, B, Bs, offsets, tokens_per_expert, output_dtype
|
| 947 |
+
)
|
| 948 |
+
|
| 949 |
+
return w8a8_block_dynamic_fp8_matmul_grouped(
|
| 950 |
+
A, B, Bs, offsets, tokens_per_expert, block_size, output_dtype
|
| 951 |
+
)
|
build/torch-rocm/matmul.py
ADDED
|
@@ -0,0 +1,941 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 ._ops import add_op_namespace_prefix, ops
|
| 21 |
+
from .utils import (
|
| 22 |
+
NIBBLES_PER_BYTE,
|
| 23 |
+
MX_SCALE_GROUP_K,
|
| 24 |
+
adaptive_block_size_m,
|
| 25 |
+
decode_ue8m0_scale,
|
| 26 |
+
device_context,
|
| 27 |
+
fp8_act_quant,
|
| 28 |
+
fp8_act_quant_inline,
|
| 29 |
+
get_accelerator_autotuning_configs,
|
| 30 |
+
is_mxfp4,
|
| 31 |
+
is_mxfp8,
|
| 32 |
+
mx_act_quant_inline,
|
| 33 |
+
ue8m0_as_uint8,
|
| 34 |
+
)
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
@triton.jit
|
| 38 |
+
def _swizzle_offsets(
|
| 39 |
+
M,
|
| 40 |
+
N,
|
| 41 |
+
BLOCK_SIZE_M: tl.constexpr,
|
| 42 |
+
BLOCK_SIZE_N: tl.constexpr,
|
| 43 |
+
BLOCK_SIZE_K: tl.constexpr,
|
| 44 |
+
GROUP_SIZE_M: tl.constexpr,
|
| 45 |
+
):
|
| 46 |
+
"""2D-grid tile scheduling shared by the kernels below: swizzle the
|
| 47 |
+
``(pid_m, pid_n)`` program ids for L2 locality on B, then build the operand
|
| 48 |
+
offset vectors. Returns ``(pid_m, pid_n, offs_am, offs_bn, offs_k)`` — the
|
| 49 |
+
swizzled ids (reused by the output store) and the ``%``-wrapped row/col offsets
|
| 50 |
+
plus the K range."""
|
| 51 |
+
pid_m = tl.program_id(axis=0)
|
| 52 |
+
pid_n = tl.program_id(axis=1)
|
| 53 |
+
num_pid_m = tl.cdiv(M, BLOCK_SIZE_M)
|
| 54 |
+
num_pid_n = tl.cdiv(N, BLOCK_SIZE_N)
|
| 55 |
+
pid_m, pid_n = tl.swizzle2d(pid_m, pid_n, num_pid_m, num_pid_n, GROUP_SIZE_M)
|
| 56 |
+
offs_am = (pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)) % M
|
| 57 |
+
offs_bn = (pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)) % N
|
| 58 |
+
offs_k = tl.arange(0, BLOCK_SIZE_K)
|
| 59 |
+
return pid_m, pid_n, offs_am, offs_bn, offs_k
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
@triton.jit
|
| 63 |
+
def _store_masked(
|
| 64 |
+
C,
|
| 65 |
+
accumulator,
|
| 66 |
+
pid_m,
|
| 67 |
+
pid_n,
|
| 68 |
+
M,
|
| 69 |
+
N,
|
| 70 |
+
stride_cm,
|
| 71 |
+
stride_cn,
|
| 72 |
+
BLOCK_SIZE_M: tl.constexpr,
|
| 73 |
+
BLOCK_SIZE_N: tl.constexpr,
|
| 74 |
+
):
|
| 75 |
+
"""Shared output epilogue of the kernels below: cast the fp32 accumulator to
|
| 76 |
+
``C``'s dtype and store the ``(BLOCK_SIZE_M, BLOCK_SIZE_N)`` tile at the swizzled
|
| 77 |
+
``(pid_m, pid_n)``, masked to the ``(M, N)`` bounds."""
|
| 78 |
+
c = accumulator.to(C.dtype.element_ty)
|
| 79 |
+
offs_cm = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
|
| 80 |
+
offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
|
| 81 |
+
c_ptrs = C + stride_cm * offs_cm[:, None] + stride_cn * offs_cn[None, :]
|
| 82 |
+
c_mask = (offs_cm[:, None] < M) & (offs_cn[None, :] < N)
|
| 83 |
+
tl.store(c_ptrs, c, mask=c_mask)
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
@triton.autotune(
|
| 87 |
+
configs=get_accelerator_autotuning_configs(),
|
| 88 |
+
key=["N", "K", "BLOCK_SIZE_M"],
|
| 89 |
+
)
|
| 90 |
+
@triton.jit
|
| 91 |
+
def w8a8_block_dynamic_fp8_matmul_kernel(
|
| 92 |
+
A, # (M, K) raw BF16/FP16 activations
|
| 93 |
+
B, # (N, K) FP8 weights
|
| 94 |
+
C, # (M, N) output
|
| 95 |
+
Bs, # (N // BLOCK_SIZE_N, K // BLOCK_SIZE_K) weight scales (fp32 or uint8/UE8M0)
|
| 96 |
+
# Shape
|
| 97 |
+
M,
|
| 98 |
+
N,
|
| 99 |
+
K,
|
| 100 |
+
# Strides
|
| 101 |
+
stride_am,
|
| 102 |
+
stride_ak,
|
| 103 |
+
stride_bk,
|
| 104 |
+
stride_bn,
|
| 105 |
+
stride_cm,
|
| 106 |
+
stride_cn,
|
| 107 |
+
stride_bs_k,
|
| 108 |
+
stride_bs_n,
|
| 109 |
+
# Meta-parameters
|
| 110 |
+
BLOCK_SIZE_M: tl.constexpr,
|
| 111 |
+
BLOCK_SIZE_N: tl.constexpr,
|
| 112 |
+
BLOCK_SIZE_K: tl.constexpr,
|
| 113 |
+
GROUP_SIZE_M: tl.constexpr,
|
| 114 |
+
):
|
| 115 |
+
"""Block-scale FP8 GEMM kernel with fused activation quantization.
|
| 116 |
+
|
| 117 |
+
Computes ``C = A @ B.T`` with bf16/fp16 ``A`` quantized to FP8 per-K-tile
|
| 118 |
+
inline (one scale per M-row per BLOCK_SIZE_K) and pre-quantized FP8 weights
|
| 119 |
+
with per-block scales. 2D grid with swizzle for L2 cache locality on B.
|
| 120 |
+
"""
|
| 121 |
+
pid_m, pid_n, offs_am, offs_bn, offs_k = _swizzle_offsets(
|
| 122 |
+
M, N, BLOCK_SIZE_M, BLOCK_SIZE_N, BLOCK_SIZE_K, GROUP_SIZE_M
|
| 123 |
+
)
|
| 124 |
+
a_ptrs = A + (offs_am[:, None] * stride_am + offs_k[None, :] * stride_ak)
|
| 125 |
+
b_ptrs = B + (offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn)
|
| 126 |
+
|
| 127 |
+
offs_bsn = offs_bn // BLOCK_SIZE_N
|
| 128 |
+
bs_ptrs = Bs + offs_bsn * stride_bs_n
|
| 129 |
+
|
| 130 |
+
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
| 131 |
+
for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
|
| 132 |
+
k_remaining = K - k * BLOCK_SIZE_K
|
| 133 |
+
a_raw = tl.load(a_ptrs, mask=offs_k[None, :] < k_remaining, other=0.0).to(
|
| 134 |
+
tl.float32
|
| 135 |
+
)
|
| 136 |
+
a, a_s = fp8_act_quant_inline(a_raw)
|
| 137 |
+
b = tl.load(b_ptrs, mask=offs_k[:, None] < k_remaining, other=0.0)
|
| 138 |
+
b_s = decode_ue8m0_scale(tl.load(bs_ptrs))
|
| 139 |
+
accumulator += tl.dot(a, b) * a_s[:, None] * b_s[None, :]
|
| 140 |
+
a_ptrs += BLOCK_SIZE_K * stride_ak
|
| 141 |
+
b_ptrs += BLOCK_SIZE_K * stride_bk
|
| 142 |
+
bs_ptrs += stride_bs_k
|
| 143 |
+
|
| 144 |
+
_store_masked(
|
| 145 |
+
C, accumulator, pid_m, pid_n, M, N, stride_cm, stride_cn,
|
| 146 |
+
BLOCK_SIZE_M, BLOCK_SIZE_N,
|
| 147 |
+
)
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
@triton.autotune(
|
| 151 |
+
configs=get_accelerator_autotuning_configs(with_block_sizes=True),
|
| 152 |
+
key=["N", "K", "BLOCK_SIZE_M"],
|
| 153 |
+
)
|
| 154 |
+
@triton.jit
|
| 155 |
+
def w8a8_tensor_dynamic_fp8_matmul_kernel(
|
| 156 |
+
A, # (M, K) pre-quantized FP8 activations
|
| 157 |
+
B, # (N, K) FP8 weights
|
| 158 |
+
C, # (M, N) output
|
| 159 |
+
As, # (M,) per-token activation scales
|
| 160 |
+
Bs, # scalar/(1,) per-tensor weight scale
|
| 161 |
+
# Shape
|
| 162 |
+
M,
|
| 163 |
+
N,
|
| 164 |
+
K,
|
| 165 |
+
# Strides
|
| 166 |
+
stride_am,
|
| 167 |
+
stride_ak,
|
| 168 |
+
stride_bk,
|
| 169 |
+
stride_bn,
|
| 170 |
+
stride_cm,
|
| 171 |
+
stride_cn,
|
| 172 |
+
stride_as_m,
|
| 173 |
+
# Meta-parameters
|
| 174 |
+
BLOCK_SIZE_M: tl.constexpr,
|
| 175 |
+
BLOCK_SIZE_N: tl.constexpr,
|
| 176 |
+
BLOCK_SIZE_K: tl.constexpr,
|
| 177 |
+
GROUP_SIZE_M: tl.constexpr,
|
| 178 |
+
):
|
| 179 |
+
"""Tensor-scale FP8 GEMM kernel.
|
| 180 |
+
|
| 181 |
+
Computes ``C = A @ B.T`` with one activation scale per row and one
|
| 182 |
+
weight scale for the full matrix.
|
| 183 |
+
Uses a 2D grid with swizzle for L2 cache locality on B tiles.
|
| 184 |
+
"""
|
| 185 |
+
pid_m, pid_n, offs_am, offs_bn, offs_k = _swizzle_offsets(
|
| 186 |
+
M, N, BLOCK_SIZE_M, BLOCK_SIZE_N, BLOCK_SIZE_K, GROUP_SIZE_M
|
| 187 |
+
)
|
| 188 |
+
a_ptrs = A + offs_am[:, None] * stride_am + offs_k[None, :] * stride_ak
|
| 189 |
+
b_ptrs = B + offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn
|
| 190 |
+
|
| 191 |
+
a_s = tl.load(As + offs_am * stride_as_m)
|
| 192 |
+
b_s = tl.load(Bs)
|
| 193 |
+
|
| 194 |
+
# Accumulate raw dot products, apply scales once after the loop.
|
| 195 |
+
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
| 196 |
+
for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
|
| 197 |
+
k_remaining = K - k * BLOCK_SIZE_K
|
| 198 |
+
a = tl.load(a_ptrs, mask=offs_k[None, :] < k_remaining, other=0.0)
|
| 199 |
+
b = tl.load(b_ptrs, mask=offs_k[:, None] < k_remaining, other=0.0)
|
| 200 |
+
accumulator += tl.dot(a, b)
|
| 201 |
+
a_ptrs += BLOCK_SIZE_K * stride_ak
|
| 202 |
+
b_ptrs += BLOCK_SIZE_K * stride_bk
|
| 203 |
+
|
| 204 |
+
accumulator = accumulator * a_s[:, None] * b_s
|
| 205 |
+
|
| 206 |
+
_store_masked(
|
| 207 |
+
C, accumulator, pid_m, pid_n, M, N, stride_cm, stride_cn,
|
| 208 |
+
BLOCK_SIZE_M, BLOCK_SIZE_N,
|
| 209 |
+
)
|
| 210 |
+
|
| 211 |
+
|
| 212 |
+
@triton.autotune(
|
| 213 |
+
configs=get_accelerator_autotuning_configs(),
|
| 214 |
+
key=["N", "K", "BLOCK_SIZE_M"],
|
| 215 |
+
)
|
| 216 |
+
@triton.jit
|
| 217 |
+
def w8a8_block_static_fp8_matmul_kernel(
|
| 218 |
+
A, # (M, K) raw BF16/FP16 activations
|
| 219 |
+
B, # (N, K) FP8 weights
|
| 220 |
+
C, # (M, N) output
|
| 221 |
+
As, # scalar — static per-tensor activation scale (calibration-time)
|
| 222 |
+
Bs, # (N // BLOCK_SIZE_N, K // BLOCK_SIZE_K) weight scales (fp32 or uint8/UE8M0)
|
| 223 |
+
# Shape
|
| 224 |
+
M,
|
| 225 |
+
N,
|
| 226 |
+
K,
|
| 227 |
+
# Strides
|
| 228 |
+
stride_am,
|
| 229 |
+
stride_ak,
|
| 230 |
+
stride_bk,
|
| 231 |
+
stride_bn,
|
| 232 |
+
stride_cm,
|
| 233 |
+
stride_cn,
|
| 234 |
+
stride_bs_k,
|
| 235 |
+
stride_bs_n,
|
| 236 |
+
# Meta-parameters
|
| 237 |
+
BLOCK_SIZE_M: tl.constexpr,
|
| 238 |
+
BLOCK_SIZE_N: tl.constexpr,
|
| 239 |
+
BLOCK_SIZE_K: tl.constexpr,
|
| 240 |
+
GROUP_SIZE_M: tl.constexpr,
|
| 241 |
+
):
|
| 242 |
+
"""Block-scale FP8 GEMM with static (per-tensor) activation scale.
|
| 243 |
+
|
| 244 |
+
``A`` is raw bf16/fp16; the kernel divides by the scalar ``As`` and casts
|
| 245 |
+
to FP8 inline. Per-block weight scales apply per-K-tile during
|
| 246 |
+
accumulation; the scalar activation scale factors out of the loop and
|
| 247 |
+
is applied once at the end.
|
| 248 |
+
"""
|
| 249 |
+
pid_m, pid_n, offs_am, offs_bn, offs_k = _swizzle_offsets(
|
| 250 |
+
M, N, BLOCK_SIZE_M, BLOCK_SIZE_N, BLOCK_SIZE_K, GROUP_SIZE_M
|
| 251 |
+
)
|
| 252 |
+
a_ptrs = A + (offs_am[:, None] * stride_am + offs_k[None, :] * stride_ak)
|
| 253 |
+
b_ptrs = B + (offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn)
|
| 254 |
+
|
| 255 |
+
offs_bsn = offs_bn // BLOCK_SIZE_N
|
| 256 |
+
bs_ptrs = Bs + offs_bsn * stride_bs_n
|
| 257 |
+
a_s_static = tl.load(As)
|
| 258 |
+
|
| 259 |
+
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
| 260 |
+
for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
|
| 261 |
+
k_remaining = K - k * BLOCK_SIZE_K
|
| 262 |
+
a_raw = tl.load(a_ptrs, mask=offs_k[None, :] < k_remaining, other=0.0).to(
|
| 263 |
+
tl.float32
|
| 264 |
+
)
|
| 265 |
+
a = (a_raw / a_s_static).to(tl.float8e4nv)
|
| 266 |
+
b = tl.load(b_ptrs, mask=offs_k[:, None] < k_remaining, other=0.0)
|
| 267 |
+
b_s = decode_ue8m0_scale(tl.load(bs_ptrs))
|
| 268 |
+
accumulator += tl.dot(a, b) * b_s[None, :]
|
| 269 |
+
a_ptrs += BLOCK_SIZE_K * stride_ak
|
| 270 |
+
b_ptrs += BLOCK_SIZE_K * stride_bk
|
| 271 |
+
bs_ptrs += stride_bs_k
|
| 272 |
+
|
| 273 |
+
accumulator = accumulator * a_s_static
|
| 274 |
+
_store_masked(
|
| 275 |
+
C, accumulator, pid_m, pid_n, M, N, stride_cm, stride_cn,
|
| 276 |
+
BLOCK_SIZE_M, BLOCK_SIZE_N,
|
| 277 |
+
)
|
| 278 |
+
|
| 279 |
+
|
| 280 |
+
@triton.autotune(
|
| 281 |
+
configs=get_accelerator_autotuning_configs(with_block_sizes=True),
|
| 282 |
+
key=["N", "K", "BLOCK_SIZE_M"],
|
| 283 |
+
)
|
| 284 |
+
@triton.jit
|
| 285 |
+
def w4a8_mx_dynamic_fp4_matmul_kernel(
|
| 286 |
+
A, # (M, K) raw BF16/FP16 activations
|
| 287 |
+
B, # (N, K // 2) packed FP4 (E2M1) weights as int8
|
| 288 |
+
C, # (M, N) output
|
| 289 |
+
Bs, # (N, K // SCALE_GROUP_K) UE8M0 weight scales
|
| 290 |
+
# Shape
|
| 291 |
+
M,
|
| 292 |
+
N,
|
| 293 |
+
K,
|
| 294 |
+
# Strides
|
| 295 |
+
stride_am,
|
| 296 |
+
stride_ak,
|
| 297 |
+
stride_bk,
|
| 298 |
+
stride_bn,
|
| 299 |
+
stride_cm,
|
| 300 |
+
stride_cn,
|
| 301 |
+
stride_bs_k,
|
| 302 |
+
stride_bs_n,
|
| 303 |
+
# Meta-parameters
|
| 304 |
+
BLOCK_SIZE_M: tl.constexpr,
|
| 305 |
+
BLOCK_SIZE_N: tl.constexpr,
|
| 306 |
+
BLOCK_SIZE_K: tl.constexpr,
|
| 307 |
+
GROUP_SIZE_M: tl.constexpr,
|
| 308 |
+
NIBBLES_PER_BYTE: tl.constexpr,
|
| 309 |
+
SCALE_GROUP_K: tl.constexpr,
|
| 310 |
+
):
|
| 311 |
+
"""MXFP4 (W4A8) GEMM kernel with fused activation quantization.
|
| 312 |
+
|
| 313 |
+
Computes ``C = A @ B.T`` with bf16/fp16 ``A`` quantized to FP8 (E4M3) per
|
| 314 |
+
K-group of ``SCALE_GROUP_K`` elements inline (UE8M0 scale), and packed FP4
|
| 315 |
+
(E2M1) weights with their own UE8M0 scales. 2D grid with swizzle for L2
|
| 316 |
+
cache locality on B tiles, ``tl.dot_scaled`` for the scaled MMA.
|
| 317 |
+
"""
|
| 318 |
+
pid_m, pid_n, offs_am, offs_bn, offs_k = _swizzle_offsets(
|
| 319 |
+
M, N, BLOCK_SIZE_M, BLOCK_SIZE_N, BLOCK_SIZE_K, GROUP_SIZE_M
|
| 320 |
+
)
|
| 321 |
+
offs_k_byte = tl.arange(0, BLOCK_SIZE_K // NIBBLES_PER_BYTE)
|
| 322 |
+
offs_sf = tl.arange(0, BLOCK_SIZE_K // SCALE_GROUP_K)
|
| 323 |
+
a_ptrs = A + (offs_am[:, None] * stride_am + offs_k[None, :] * stride_ak)
|
| 324 |
+
b_ptrs = B + (offs_k_byte[:, None] * stride_bk + offs_bn[None, :] * stride_bn)
|
| 325 |
+
bs_ptrs = Bs + (offs_bn[:, None] * stride_bs_n + offs_sf[None, :] * stride_bs_k)
|
| 326 |
+
|
| 327 |
+
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
| 328 |
+
for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
|
| 329 |
+
k_remaining = K - k * BLOCK_SIZE_K
|
| 330 |
+
a_raw = tl.load(a_ptrs, mask=offs_k[None, :] < k_remaining, other=0.0).to(
|
| 331 |
+
tl.float32
|
| 332 |
+
)
|
| 333 |
+
a, a_scale = mx_act_quant_inline(
|
| 334 |
+
a_raw, BLOCK_SIZE_M, BLOCK_SIZE_K, SCALE_GROUP_K
|
| 335 |
+
)
|
| 336 |
+
b = tl.load(
|
| 337 |
+
b_ptrs, mask=offs_k_byte[:, None] < k_remaining // NIBBLES_PER_BYTE, other=0
|
| 338 |
+
).to(tl.uint8)
|
| 339 |
+
b_s = tl.load(
|
| 340 |
+
bs_ptrs, mask=offs_sf[None, :] < k_remaining // SCALE_GROUP_K, other=0
|
| 341 |
+
).to(tl.uint8)
|
| 342 |
+
accumulator += tl.dot_scaled(a, a_scale, "e4m3", b, b_s, "e2m1")
|
| 343 |
+
a_ptrs += BLOCK_SIZE_K * stride_ak
|
| 344 |
+
b_ptrs += (BLOCK_SIZE_K // NIBBLES_PER_BYTE) * stride_bk
|
| 345 |
+
bs_ptrs += (BLOCK_SIZE_K // SCALE_GROUP_K) * stride_bs_k
|
| 346 |
+
|
| 347 |
+
_store_masked(
|
| 348 |
+
C, accumulator, pid_m, pid_n, M, N, stride_cm, stride_cn,
|
| 349 |
+
BLOCK_SIZE_M, BLOCK_SIZE_N,
|
| 350 |
+
)
|
| 351 |
+
|
| 352 |
+
|
| 353 |
+
@triton.autotune(
|
| 354 |
+
configs=get_accelerator_autotuning_configs(with_block_sizes=True),
|
| 355 |
+
key=["N", "K", "BLOCK_SIZE_M"],
|
| 356 |
+
)
|
| 357 |
+
@triton.jit
|
| 358 |
+
def w8a8_mx_dynamic_fp8_matmul_kernel(
|
| 359 |
+
A, # (M, K) raw BF16/FP16 activations
|
| 360 |
+
B, # (N, K) E4M3 weights (unpacked, one byte per value)
|
| 361 |
+
C, # (M, N) output
|
| 362 |
+
Bs, # (N, K // SCALE_GROUP_K) UE8M0 weight scales
|
| 363 |
+
# Shape
|
| 364 |
+
M,
|
| 365 |
+
N,
|
| 366 |
+
K,
|
| 367 |
+
# Strides
|
| 368 |
+
stride_am,
|
| 369 |
+
stride_ak,
|
| 370 |
+
stride_bk,
|
| 371 |
+
stride_bn,
|
| 372 |
+
stride_cm,
|
| 373 |
+
stride_cn,
|
| 374 |
+
stride_bs_k,
|
| 375 |
+
stride_bs_n,
|
| 376 |
+
# Meta-parameters
|
| 377 |
+
BLOCK_SIZE_M: tl.constexpr,
|
| 378 |
+
BLOCK_SIZE_N: tl.constexpr,
|
| 379 |
+
BLOCK_SIZE_K: tl.constexpr,
|
| 380 |
+
GROUP_SIZE_M: tl.constexpr,
|
| 381 |
+
SCALE_GROUP_K: tl.constexpr,
|
| 382 |
+
):
|
| 383 |
+
"""MXFP8 block-scale GEMM kernel with fused activation quantization.
|
| 384 |
+
|
| 385 |
+
Computes ``C = A @ B.T`` with bf16/fp16 ``A`` quantized to FP8 (E4M3) per
|
| 386 |
+
K-group of ``SCALE_GROUP_K`` elements inline (UE8M0 scale), against E4M3
|
| 387 |
+
weights with their own UE8M0 K-group scales. Mirrors the W4A8 FP4 kernel but
|
| 388 |
+
keeps weights unpacked (one E4M3 byte per value); ``tl.dot_scaled`` drives
|
| 389 |
+
the MX scaled MMA with ``"e4m3"`` on both operands.
|
| 390 |
+
"""
|
| 391 |
+
pid_m, pid_n, offs_am, offs_bn, offs_k = _swizzle_offsets(
|
| 392 |
+
M, N, BLOCK_SIZE_M, BLOCK_SIZE_N, BLOCK_SIZE_K, GROUP_SIZE_M
|
| 393 |
+
)
|
| 394 |
+
offs_sf = tl.arange(0, BLOCK_SIZE_K // SCALE_GROUP_K)
|
| 395 |
+
a_ptrs = A + (offs_am[:, None] * stride_am + offs_k[None, :] * stride_ak)
|
| 396 |
+
b_ptrs = B + (offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn)
|
| 397 |
+
bs_ptrs = Bs + (offs_bn[:, None] * stride_bs_n + offs_sf[None, :] * stride_bs_k)
|
| 398 |
+
|
| 399 |
+
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
| 400 |
+
for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
|
| 401 |
+
k_remaining = K - k * BLOCK_SIZE_K
|
| 402 |
+
a_raw = tl.load(a_ptrs, mask=offs_k[None, :] < k_remaining, other=0.0).to(
|
| 403 |
+
tl.float32
|
| 404 |
+
)
|
| 405 |
+
a, a_scale = mx_act_quant_inline(
|
| 406 |
+
a_raw, BLOCK_SIZE_M, BLOCK_SIZE_K, SCALE_GROUP_K
|
| 407 |
+
)
|
| 408 |
+
b = tl.load(b_ptrs, mask=offs_k[:, None] < k_remaining, other=0.0)
|
| 409 |
+
b_s = tl.load(
|
| 410 |
+
bs_ptrs, mask=offs_sf[None, :] < k_remaining // SCALE_GROUP_K, other=0
|
| 411 |
+
).to(tl.uint8)
|
| 412 |
+
accumulator += tl.dot_scaled(a, a_scale, "e4m3", b, b_s, "e4m3")
|
| 413 |
+
a_ptrs += BLOCK_SIZE_K * stride_ak
|
| 414 |
+
b_ptrs += BLOCK_SIZE_K * stride_bk
|
| 415 |
+
bs_ptrs += (BLOCK_SIZE_K // SCALE_GROUP_K) * stride_bs_k
|
| 416 |
+
|
| 417 |
+
_store_masked(
|
| 418 |
+
C, accumulator, pid_m, pid_n, M, N, stride_cm, stride_cn,
|
| 419 |
+
BLOCK_SIZE_M, BLOCK_SIZE_N,
|
| 420 |
+
)
|
| 421 |
+
|
| 422 |
+
|
| 423 |
+
@triton_op(add_op_namespace_prefix("w8a8_block_dynamic_fp8_matmul"), mutates_args=())
|
| 424 |
+
def _w8a8_block_dynamic_fp8_matmul(
|
| 425 |
+
A: torch.Tensor,
|
| 426 |
+
B: torch.Tensor,
|
| 427 |
+
Bs: torch.Tensor,
|
| 428 |
+
block_size: list[int],
|
| 429 |
+
output_dtype: torch.dtype | None = None,
|
| 430 |
+
) -> torch.Tensor:
|
| 431 |
+
"""Block-scale FP8 matmul: ``C = A @ B.T`` with fused activation quantization.
|
| 432 |
+
|
| 433 |
+
A: (..., K) raw activations, bf16/fp16/fp32 (quantized inline to FP8)
|
| 434 |
+
B: (N, K) FP8 weights
|
| 435 |
+
Bs: (N // block_n, K // block_k) per-block weight scales
|
| 436 |
+
"""
|
| 437 |
+
assert len(block_size) == 2, (
|
| 438 |
+
f"block_size must be [block_n, block_k], got {block_size}"
|
| 439 |
+
)
|
| 440 |
+
block_n, block_k = block_size[0], block_size[1]
|
| 441 |
+
|
| 442 |
+
assert A.shape[-1] == B.shape[-1], (
|
| 443 |
+
f"K mismatch: A has K={A.shape[-1]}, B has K={B.shape[-1]}"
|
| 444 |
+
)
|
| 445 |
+
assert A.is_contiguous(), "A must be contiguous"
|
| 446 |
+
assert B.ndim == 2, f"B must be 2D (N, K), got ndim={B.ndim}"
|
| 447 |
+
assert B.is_contiguous(), "B must be contiguous"
|
| 448 |
+
|
| 449 |
+
N, K = B.shape
|
| 450 |
+
M = A.numel() // A.shape[-1]
|
| 451 |
+
|
| 452 |
+
assert Bs.ndim == 2, f"Bs must be 2D (N//block_n, K//block_k), got ndim={Bs.ndim}"
|
| 453 |
+
assert Bs.shape == (triton.cdiv(N, block_n), triton.cdiv(K, block_k)), (
|
| 454 |
+
f"Bs shape {tuple(Bs.shape)} != expected ({triton.cdiv(N, block_n)}, {triton.cdiv(K, block_k)})"
|
| 455 |
+
)
|
| 456 |
+
|
| 457 |
+
BLOCK_SIZE_K = block_k
|
| 458 |
+
BLOCK_SIZE_N = block_n
|
| 459 |
+
Bs = ue8m0_as_uint8(Bs)
|
| 460 |
+
C_shape = A.shape[:-1] + (N,)
|
| 461 |
+
BLOCK_SIZE_M = adaptive_block_size_m(M)
|
| 462 |
+
C = A.new_empty(C_shape, dtype=output_dtype)
|
| 463 |
+
grid = (triton.cdiv(M, BLOCK_SIZE_M), triton.cdiv(N, BLOCK_SIZE_N))
|
| 464 |
+
|
| 465 |
+
with device_context(A.device):
|
| 466 |
+
wrap_triton(w8a8_block_dynamic_fp8_matmul_kernel)[grid](
|
| 467 |
+
A,
|
| 468 |
+
B,
|
| 469 |
+
C,
|
| 470 |
+
Bs,
|
| 471 |
+
M,
|
| 472 |
+
N,
|
| 473 |
+
K,
|
| 474 |
+
A.stride(-2),
|
| 475 |
+
A.stride(-1),
|
| 476 |
+
B.stride(1),
|
| 477 |
+
B.stride(0),
|
| 478 |
+
C.stride(-2),
|
| 479 |
+
C.stride(-1),
|
| 480 |
+
Bs.stride(1),
|
| 481 |
+
Bs.stride(0),
|
| 482 |
+
# Meta-parameters
|
| 483 |
+
BLOCK_SIZE_M=BLOCK_SIZE_M,
|
| 484 |
+
BLOCK_SIZE_N=BLOCK_SIZE_N,
|
| 485 |
+
BLOCK_SIZE_K=BLOCK_SIZE_K,
|
| 486 |
+
GROUP_SIZE_M=8,
|
| 487 |
+
)
|
| 488 |
+
|
| 489 |
+
return C
|
| 490 |
+
|
| 491 |
+
|
| 492 |
+
@triton_op(add_op_namespace_prefix("w8a8_block_static_fp8_matmul"), mutates_args=())
|
| 493 |
+
def _w8a8_block_static_fp8_matmul(
|
| 494 |
+
A: torch.Tensor,
|
| 495 |
+
B: torch.Tensor,
|
| 496 |
+
Bs: torch.Tensor,
|
| 497 |
+
As: torch.Tensor,
|
| 498 |
+
block_size: list[int],
|
| 499 |
+
output_dtype: torch.dtype | None = None,
|
| 500 |
+
) -> torch.Tensor:
|
| 501 |
+
"""Block-scale FP8 matmul with static (per-tensor) activation quantization.
|
| 502 |
+
|
| 503 |
+
A: (..., K) raw bf16/fp16 activations — quantized to FP8 inline against ``As``
|
| 504 |
+
B: (N, K) FP8 weights
|
| 505 |
+
Bs: (N // block_n, K // block_k) per-block weight scales
|
| 506 |
+
As: scalar / (1,) — per-tensor static activation scale
|
| 507 |
+
"""
|
| 508 |
+
assert len(block_size) == 2, (
|
| 509 |
+
f"block_size must be [block_n, block_k], got {block_size}"
|
| 510 |
+
)
|
| 511 |
+
block_n, block_k = block_size[0], block_size[1]
|
| 512 |
+
|
| 513 |
+
assert B.dtype != torch.int8, (
|
| 514 |
+
"static activation quant is not supported on the FP4 path"
|
| 515 |
+
)
|
| 516 |
+
assert not (block_n == B.size(0) and block_k == B.size(1)), (
|
| 517 |
+
"static activation quant requires block-wise weights, not tensor-mode"
|
| 518 |
+
)
|
| 519 |
+
assert A.shape[-1] == B.shape[-1], (
|
| 520 |
+
f"K mismatch: A has K={A.shape[-1]}, B has K={B.shape[-1]}"
|
| 521 |
+
)
|
| 522 |
+
assert A.is_contiguous(), "A must be contiguous"
|
| 523 |
+
assert B.ndim == 2, f"B must be 2D (N, K), got ndim={B.ndim}"
|
| 524 |
+
assert B.is_contiguous(), "B must be contiguous"
|
| 525 |
+
assert As.numel() == 1, f"As must be scalar or (1,), got {tuple(As.shape)}"
|
| 526 |
+
|
| 527 |
+
N, K = B.shape
|
| 528 |
+
M = A.numel() // A.shape[-1]
|
| 529 |
+
|
| 530 |
+
assert Bs.ndim == 2, f"Bs must be 2D (N//block_n, K//block_k), got ndim={Bs.ndim}"
|
| 531 |
+
assert Bs.shape == (triton.cdiv(N, block_n), triton.cdiv(K, block_k)), (
|
| 532 |
+
f"Bs shape {tuple(Bs.shape)} != expected ({triton.cdiv(N, block_n)}, {triton.cdiv(K, block_k)})"
|
| 533 |
+
)
|
| 534 |
+
|
| 535 |
+
BLOCK_SIZE_K = block_k
|
| 536 |
+
BLOCK_SIZE_N = block_n
|
| 537 |
+
BLOCK_SIZE_M = adaptive_block_size_m(M)
|
| 538 |
+
grid = (triton.cdiv(M, BLOCK_SIZE_M), triton.cdiv(N, BLOCK_SIZE_N))
|
| 539 |
+
|
| 540 |
+
Bs = ue8m0_as_uint8(Bs)
|
| 541 |
+
C_shape = A.shape[:-1] + (N,)
|
| 542 |
+
C = A.new_empty(C_shape, dtype=output_dtype)
|
| 543 |
+
As = As.reshape(1).to(torch.float32)
|
| 544 |
+
|
| 545 |
+
with device_context(A.device):
|
| 546 |
+
wrap_triton(w8a8_block_static_fp8_matmul_kernel)[grid](
|
| 547 |
+
A,
|
| 548 |
+
B,
|
| 549 |
+
C,
|
| 550 |
+
As,
|
| 551 |
+
Bs,
|
| 552 |
+
M,
|
| 553 |
+
N,
|
| 554 |
+
K,
|
| 555 |
+
A.stride(-2),
|
| 556 |
+
A.stride(-1),
|
| 557 |
+
B.stride(1),
|
| 558 |
+
B.stride(0),
|
| 559 |
+
C.stride(-2),
|
| 560 |
+
C.stride(-1),
|
| 561 |
+
Bs.stride(1),
|
| 562 |
+
Bs.stride(0),
|
| 563 |
+
# Meta-parameters
|
| 564 |
+
BLOCK_SIZE_M=BLOCK_SIZE_M,
|
| 565 |
+
BLOCK_SIZE_N=BLOCK_SIZE_N,
|
| 566 |
+
BLOCK_SIZE_K=BLOCK_SIZE_K,
|
| 567 |
+
GROUP_SIZE_M=8,
|
| 568 |
+
)
|
| 569 |
+
|
| 570 |
+
return C
|
| 571 |
+
|
| 572 |
+
|
| 573 |
+
@triton_op(add_op_namespace_prefix("w8a8_tensor_dynamic_fp8_matmul"), mutates_args=())
|
| 574 |
+
def _w8a8_tensor_dynamic_fp8_matmul(
|
| 575 |
+
A: torch.Tensor,
|
| 576 |
+
B: torch.Tensor,
|
| 577 |
+
Bs: torch.Tensor,
|
| 578 |
+
output_dtype: torch.dtype | None = None,
|
| 579 |
+
) -> torch.Tensor:
|
| 580 |
+
"""Tensor-scale FP8 matmul: ``C = A @ B.T`` with fused activation quantization.
|
| 581 |
+
|
| 582 |
+
A: (..., K) raw activations, bf16/fp16/fp32 (flattened to (M, K)
|
| 583 |
+
internally) — per-row scales computed via ``fp8_act_quant(A, K)``.
|
| 584 |
+
B: (N, K) FP8 weights.
|
| 585 |
+
Bs: scalar, (1,), or (1, 1) — single tensor-scale weight scale.
|
| 586 |
+
"""
|
| 587 |
+
assert A.shape[-1] == B.shape[-1], (
|
| 588 |
+
f"K mismatch: A has K={A.shape[-1]}, B has K={B.shape[-1]}"
|
| 589 |
+
)
|
| 590 |
+
assert A.is_contiguous(), "A must be contiguous"
|
| 591 |
+
assert B.ndim == 2, f"B must be 2D (N, K), got ndim={B.ndim}"
|
| 592 |
+
assert B.is_contiguous(), "B must be contiguous"
|
| 593 |
+
|
| 594 |
+
N, K = B.shape
|
| 595 |
+
M = A.numel() // A.shape[-1]
|
| 596 |
+
|
| 597 |
+
assert Bs.numel() == 1, f"Bs must be scalar or (1,), got {tuple(Bs.shape)}"
|
| 598 |
+
|
| 599 |
+
# Per-row scalar activation scale (one per token).
|
| 600 |
+
qA, As = fp8_act_quant(A, K)
|
| 601 |
+
As = As.reshape(M)
|
| 602 |
+
Bs = Bs.reshape(1)
|
| 603 |
+
|
| 604 |
+
C_shape = A.shape[:-1] + (N,)
|
| 605 |
+
C = A.new_empty(C_shape, dtype=output_dtype)
|
| 606 |
+
BLOCK_SIZE_M = adaptive_block_size_m(M)
|
| 607 |
+
|
| 608 |
+
def grid(META):
|
| 609 |
+
return (triton.cdiv(M, BLOCK_SIZE_M), triton.cdiv(N, META["BLOCK_SIZE_N"]))
|
| 610 |
+
|
| 611 |
+
with device_context(A.device):
|
| 612 |
+
wrap_triton(w8a8_tensor_dynamic_fp8_matmul_kernel)[grid](
|
| 613 |
+
qA,
|
| 614 |
+
B,
|
| 615 |
+
C,
|
| 616 |
+
As,
|
| 617 |
+
Bs,
|
| 618 |
+
M,
|
| 619 |
+
N,
|
| 620 |
+
K,
|
| 621 |
+
qA.stride(-2),
|
| 622 |
+
qA.stride(-1),
|
| 623 |
+
B.stride(1),
|
| 624 |
+
B.stride(0),
|
| 625 |
+
C.stride(-2),
|
| 626 |
+
C.stride(-1),
|
| 627 |
+
As.stride(0),
|
| 628 |
+
# Meta-parameters (BLOCK_SIZE_N, BLOCK_SIZE_K come from autotune Config)
|
| 629 |
+
BLOCK_SIZE_M=BLOCK_SIZE_M,
|
| 630 |
+
GROUP_SIZE_M=8,
|
| 631 |
+
)
|
| 632 |
+
|
| 633 |
+
return C
|
| 634 |
+
|
| 635 |
+
|
| 636 |
+
@triton_op(add_op_namespace_prefix("w4a8_mx_dynamic_fp4_matmul"), mutates_args=())
|
| 637 |
+
def _w4a8_mx_dynamic_fp4_matmul(
|
| 638 |
+
A: torch.Tensor,
|
| 639 |
+
B: torch.Tensor,
|
| 640 |
+
Bs: torch.Tensor,
|
| 641 |
+
output_dtype: torch.dtype | None = None,
|
| 642 |
+
) -> torch.Tensor:
|
| 643 |
+
"""MXFP4 (W4A8) matmul: ``C = A @ B.T`` with fused activation quant.
|
| 644 |
+
|
| 645 |
+
A: (M, K) raw activations, bf16/fp16/fp32 (quantized inline to FP8)
|
| 646 |
+
B: (N, K // 2) packed FP4 (E2M1) weights, two codes per int8
|
| 647 |
+
Bs: (N, K // 32) UE8M0 weight scales
|
| 648 |
+
|
| 649 |
+
On CUDA and other backends, BLOCK_SIZE_N and BLOCK_SIZE_K are autotuned.
|
| 650 |
+
On XPU they are fixed to 128x128 and only num_warps/num_stages are autotuned.
|
| 651 |
+
FP4 scale granularity is fixed at 32, so tile shape is purely a perf knob.
|
| 652 |
+
"""
|
| 653 |
+
assert A.ndim == 2 and B.ndim == 2 and Bs.ndim == 2
|
| 654 |
+
assert B.dtype == torch.int8, f"B must be int8 (packed FP4), got {B.dtype}"
|
| 655 |
+
assert Bs.dtype == torch.float8_e8m0fnu, (
|
| 656 |
+
f"Bs must be float8_e8m0fnu, got {Bs.dtype}"
|
| 657 |
+
)
|
| 658 |
+
assert A.is_contiguous(), "A must be contiguous"
|
| 659 |
+
assert B.is_contiguous(), "B must be contiguous"
|
| 660 |
+
|
| 661 |
+
M, K = A.shape
|
| 662 |
+
N, K_half = B.shape
|
| 663 |
+
assert K == NIBBLES_PER_BYTE * K_half, (
|
| 664 |
+
f"K (={K}) must equal {NIBBLES_PER_BYTE} * B.shape[1] (={K_half})"
|
| 665 |
+
)
|
| 666 |
+
assert K % MX_SCALE_GROUP_K == 0, (
|
| 667 |
+
f"K (={K}) must be a multiple of {MX_SCALE_GROUP_K}"
|
| 668 |
+
)
|
| 669 |
+
assert Bs.shape == (N, K // MX_SCALE_GROUP_K), (
|
| 670 |
+
f"Bs shape {tuple(Bs.shape)} != ({N}, {K // MX_SCALE_GROUP_K})"
|
| 671 |
+
)
|
| 672 |
+
|
| 673 |
+
bs_u8 = ue8m0_as_uint8(Bs)
|
| 674 |
+
C = A.new_empty((M, N), dtype=output_dtype)
|
| 675 |
+
BLOCK_SIZE_M = adaptive_block_size_m(M)
|
| 676 |
+
|
| 677 |
+
def grid(META):
|
| 678 |
+
return (triton.cdiv(M, BLOCK_SIZE_M), triton.cdiv(N, META["BLOCK_SIZE_N"]))
|
| 679 |
+
|
| 680 |
+
with device_context(A.device):
|
| 681 |
+
wrap_triton(w4a8_mx_dynamic_fp4_matmul_kernel)[grid](
|
| 682 |
+
A,
|
| 683 |
+
B,
|
| 684 |
+
C,
|
| 685 |
+
bs_u8,
|
| 686 |
+
M,
|
| 687 |
+
N,
|
| 688 |
+
K,
|
| 689 |
+
A.stride(0),
|
| 690 |
+
A.stride(1),
|
| 691 |
+
B.stride(1),
|
| 692 |
+
B.stride(0),
|
| 693 |
+
C.stride(0),
|
| 694 |
+
C.stride(1),
|
| 695 |
+
bs_u8.stride(1),
|
| 696 |
+
bs_u8.stride(0),
|
| 697 |
+
# Meta-parameters (BLOCK_SIZE_N, BLOCK_SIZE_K come from autotune Config)
|
| 698 |
+
BLOCK_SIZE_M=BLOCK_SIZE_M,
|
| 699 |
+
GROUP_SIZE_M=8,
|
| 700 |
+
NIBBLES_PER_BYTE=NIBBLES_PER_BYTE,
|
| 701 |
+
SCALE_GROUP_K=MX_SCALE_GROUP_K,
|
| 702 |
+
)
|
| 703 |
+
return C
|
| 704 |
+
|
| 705 |
+
|
| 706 |
+
@triton_op(add_op_namespace_prefix("w8a8_mx_dynamic_fp8_matmul"), mutates_args=())
|
| 707 |
+
def _w8a8_mx_dynamic_fp8_matmul(
|
| 708 |
+
A: torch.Tensor,
|
| 709 |
+
B: torch.Tensor,
|
| 710 |
+
Bs: torch.Tensor,
|
| 711 |
+
output_dtype: torch.dtype | None = None,
|
| 712 |
+
) -> torch.Tensor:
|
| 713 |
+
"""MXFP8 W8A8 matmul: ``C = A @ B.T`` (E4M3 × E4M3, UE8M0 group-32 scales).
|
| 714 |
+
|
| 715 |
+
A: (M, K) raw activations, bf16/fp16/fp32 (quantized inline to E4M3,
|
| 716 |
+
MX group-32 UE8M0 scales)
|
| 717 |
+
B: (N, K) E4M3 weights (unpacked)
|
| 718 |
+
Bs: (N, K // 32) UE8M0 weight scales
|
| 719 |
+
|
| 720 |
+
Tile shape (BLOCK_SIZE_N, BLOCK_SIZE_K) is autotuned; MX scale granularity is
|
| 721 |
+
fixed at 32 (the MX-format spec), so tile shape is purely a perf knob.
|
| 722 |
+
"""
|
| 723 |
+
assert A.ndim == 2 and B.ndim == 2 and Bs.ndim == 2
|
| 724 |
+
assert B.dtype == torch.float8_e4m3fn, (
|
| 725 |
+
f"B must be float8_e4m3fn (E4M3 weights), got {B.dtype}"
|
| 726 |
+
)
|
| 727 |
+
assert Bs.dtype == torch.float8_e8m0fnu, (
|
| 728 |
+
f"Bs must be float8_e8m0fnu, got {Bs.dtype}"
|
| 729 |
+
)
|
| 730 |
+
assert A.is_contiguous(), "A must be contiguous"
|
| 731 |
+
assert B.is_contiguous(), "B must be contiguous"
|
| 732 |
+
|
| 733 |
+
M, K = A.shape
|
| 734 |
+
N, K_b = B.shape
|
| 735 |
+
assert K == K_b, f"K mismatch: A has K={K}, B has K={K_b}"
|
| 736 |
+
assert K % MX_SCALE_GROUP_K == 0, (
|
| 737 |
+
f"K (={K}) must be a multiple of {MX_SCALE_GROUP_K}"
|
| 738 |
+
)
|
| 739 |
+
assert Bs.shape == (N, K // MX_SCALE_GROUP_K), (
|
| 740 |
+
f"Bs shape {tuple(Bs.shape)} != ({N}, {K // MX_SCALE_GROUP_K})"
|
| 741 |
+
)
|
| 742 |
+
|
| 743 |
+
bs_u8 = ue8m0_as_uint8(Bs)
|
| 744 |
+
C = A.new_empty((M, N), dtype=output_dtype)
|
| 745 |
+
BLOCK_SIZE_M = adaptive_block_size_m(M)
|
| 746 |
+
|
| 747 |
+
def grid(META):
|
| 748 |
+
return (triton.cdiv(M, BLOCK_SIZE_M), triton.cdiv(N, META["BLOCK_SIZE_N"]))
|
| 749 |
+
|
| 750 |
+
with device_context(A.device):
|
| 751 |
+
wrap_triton(w8a8_mx_dynamic_fp8_matmul_kernel)[grid](
|
| 752 |
+
A,
|
| 753 |
+
B,
|
| 754 |
+
C,
|
| 755 |
+
bs_u8,
|
| 756 |
+
M,
|
| 757 |
+
N,
|
| 758 |
+
K,
|
| 759 |
+
A.stride(0),
|
| 760 |
+
A.stride(1),
|
| 761 |
+
B.stride(1),
|
| 762 |
+
B.stride(0),
|
| 763 |
+
C.stride(0),
|
| 764 |
+
C.stride(1),
|
| 765 |
+
bs_u8.stride(1),
|
| 766 |
+
bs_u8.stride(0),
|
| 767 |
+
# Meta-parameters (BLOCK_SIZE_N, BLOCK_SIZE_K come from autotune Config)
|
| 768 |
+
BLOCK_SIZE_M=BLOCK_SIZE_M,
|
| 769 |
+
GROUP_SIZE_M=8,
|
| 770 |
+
SCALE_GROUP_K=MX_SCALE_GROUP_K,
|
| 771 |
+
)
|
| 772 |
+
return C
|
| 773 |
+
|
| 774 |
+
|
| 775 |
+
def w8a8_block_dynamic_fp8_matmul(
|
| 776 |
+
A: torch.Tensor,
|
| 777 |
+
B: torch.Tensor,
|
| 778 |
+
Bs: torch.Tensor,
|
| 779 |
+
block_size: list[int],
|
| 780 |
+
output_dtype: torch.dtype | None = None,
|
| 781 |
+
) -> torch.Tensor:
|
| 782 |
+
"""Block-wise W8A8 FP8 matrix multiplication with fused activation quantization.
|
| 783 |
+
|
| 784 |
+
Args:
|
| 785 |
+
A: Raw activation tensor ``[..., M, K]`` in bf16/fp16/fp32 — quantized
|
| 786 |
+
inline to ``float8_e4m3fn`` against per-K-tile per-row UE8 scales.
|
| 787 |
+
B: FP8 weight tensor ``[N, K]`` in ``float8_e4m3fn``.
|
| 788 |
+
Bs: Per-block weight scales ``[N // block_size[0], K // block_size[1]]``.
|
| 789 |
+
block_size: ``[block_n, block_k]`` weight quantization block dimensions.
|
| 790 |
+
output_dtype: dtype of the returned tensor (default: ``torch.float32``).
|
| 791 |
+
|
| 792 |
+
Returns:
|
| 793 |
+
Output tensor ``[..., M, N]`` in ``output_dtype``.
|
| 794 |
+
"""
|
| 795 |
+
return ops.w8a8_block_dynamic_fp8_matmul(A, B, Bs, block_size, output_dtype)
|
| 796 |
+
|
| 797 |
+
|
| 798 |
+
def w8a8_block_static_fp8_matmul(
|
| 799 |
+
A: torch.Tensor,
|
| 800 |
+
B: torch.Tensor,
|
| 801 |
+
Bs: torch.Tensor,
|
| 802 |
+
As: torch.Tensor,
|
| 803 |
+
block_size: list[int],
|
| 804 |
+
output_dtype: torch.dtype | None = None,
|
| 805 |
+
) -> torch.Tensor:
|
| 806 |
+
"""Block-wise W8A8 FP8 matmul with static (per-tensor) activation quantization.
|
| 807 |
+
|
| 808 |
+
Args:
|
| 809 |
+
A: Raw activation tensor ``[..., M, K]`` in bf16/fp16/fp32 — quantized
|
| 810 |
+
inline against the per-tensor scalar ``As``.
|
| 811 |
+
B: FP8 weight tensor ``[N, K]`` in ``float8_e4m3fn``.
|
| 812 |
+
Bs: Per-block weight scales ``[N // block_size[0], K // block_size[1]]``.
|
| 813 |
+
As: Static per-tensor activation scale (scalar or ``[1]``).
|
| 814 |
+
block_size: ``[block_n, block_k]`` weight quantization block dimensions.
|
| 815 |
+
output_dtype: dtype of the returned tensor (default: ``torch.float32``).
|
| 816 |
+
|
| 817 |
+
Returns:
|
| 818 |
+
Output tensor ``[..., M, N]`` in ``output_dtype``.
|
| 819 |
+
"""
|
| 820 |
+
return ops.w8a8_block_static_fp8_matmul(A, B, Bs, As, block_size, output_dtype)
|
| 821 |
+
|
| 822 |
+
|
| 823 |
+
def w8a8_tensor_dynamic_fp8_matmul(
|
| 824 |
+
A: torch.Tensor,
|
| 825 |
+
B: torch.Tensor,
|
| 826 |
+
Bs: torch.Tensor,
|
| 827 |
+
output_dtype: torch.dtype | None = None,
|
| 828 |
+
) -> torch.Tensor:
|
| 829 |
+
"""Tensor-scale W8A8 FP8 matmul with fused activation quantization.
|
| 830 |
+
|
| 831 |
+
Computes ``C = A @ B.T`` with raw bf16/fp16/fp32 ``A`` quantized to FP8
|
| 832 |
+
per-row (one scale per token) before the dot.
|
| 833 |
+
|
| 834 |
+
Args:
|
| 835 |
+
A: Raw activation tensor ``[M, K]`` in bf16/fp16/fp32.
|
| 836 |
+
B: FP8 weight tensor ``[N, K]`` in ``float8_e4m3fn``.
|
| 837 |
+
Bs: Single weight scale, scalar or ``[1]``.
|
| 838 |
+
output_dtype: dtype of the returned tensor.
|
| 839 |
+
"""
|
| 840 |
+
return ops.w8a8_tensor_dynamic_fp8_matmul(A, B, Bs, output_dtype)
|
| 841 |
+
|
| 842 |
+
|
| 843 |
+
def w4a8_mx_dynamic_fp4_matmul(
|
| 844 |
+
A: torch.Tensor,
|
| 845 |
+
B: torch.Tensor,
|
| 846 |
+
Bs: torch.Tensor,
|
| 847 |
+
output_dtype: torch.dtype | None = None,
|
| 848 |
+
) -> torch.Tensor:
|
| 849 |
+
"""MXFP4 (W4A8) matmul with fused activation quantization.
|
| 850 |
+
|
| 851 |
+
Computes ``C = A @ B.T`` with bf16/fp16/fp32 ``A`` quantized to FP8 (E4M3)
|
| 852 |
+
inline and packed FP4 (E2M1) weights; both scales are UE8M0 at K-group
|
| 853 |
+
granularity 32 (the MX-format spec). Tile shape (BLOCK_SIZE_N, BLOCK_SIZE_K)
|
| 854 |
+
is autotuned — FP4 has no caller-controlled quantization block.
|
| 855 |
+
|
| 856 |
+
Args:
|
| 857 |
+
A: Raw activations ``[M, K]`` in bf16/fp16/fp32.
|
| 858 |
+
B: Packed FP4 weights ``[N, K // 2]`` (``int8``, two codes per byte).
|
| 859 |
+
Bs: UE8M0 weight scales ``[N, K // 32]``.
|
| 860 |
+
output_dtype: dtype of the returned tensor (default ``bfloat16``).
|
| 861 |
+
"""
|
| 862 |
+
return ops.w4a8_mx_dynamic_fp4_matmul(A, B, Bs, output_dtype)
|
| 863 |
+
|
| 864 |
+
|
| 865 |
+
def w8a8_mx_dynamic_fp8_matmul(
|
| 866 |
+
A: torch.Tensor,
|
| 867 |
+
B: torch.Tensor,
|
| 868 |
+
Bs: torch.Tensor,
|
| 869 |
+
output_dtype: torch.dtype | None = None,
|
| 870 |
+
) -> torch.Tensor:
|
| 871 |
+
"""MXFP8 W8A8 matmul (E4M3 × E4M3, UE8M0 group-32 scales) with fused act quant.
|
| 872 |
+
|
| 873 |
+
Computes ``C = A @ B.T`` with bf16/fp16/fp32 ``A`` quantized inline to E4M3
|
| 874 |
+
against per-row, per-32-K-group UE8M0 scales, and E4M3 weights with their own
|
| 875 |
+
UE8M0 K-group scales. Both scales feed ``tl.dot_scaled`` (the MX scaled MMA),
|
| 876 |
+
unlike ``w8a8_block_dynamic_fp8_matmul`` which applies 128×128 block scales in
|
| 877 |
+
software. Tile shape is autotuned; MX scale granularity is fixed at 32.
|
| 878 |
+
|
| 879 |
+
Args:
|
| 880 |
+
A: Raw activations ``[M, K]`` in bf16/fp16/fp32.
|
| 881 |
+
B: E4M3 weights ``[N, K]`` (``float8_e4m3fn``, unpacked).
|
| 882 |
+
Bs: UE8M0 weight scales ``[N, K // 32]`` (``float8_e8m0fnu``).
|
| 883 |
+
output_dtype: dtype of the returned tensor (default ``bfloat16``).
|
| 884 |
+
"""
|
| 885 |
+
return ops.w8a8_mx_dynamic_fp8_matmul(A, B, Bs, output_dtype)
|
| 886 |
+
|
| 887 |
+
|
| 888 |
+
def matmul_2d(
|
| 889 |
+
A: torch.Tensor,
|
| 890 |
+
B: torch.Tensor,
|
| 891 |
+
Bs: torch.Tensor,
|
| 892 |
+
block_size: list[int] | None,
|
| 893 |
+
output_dtype: torch.dtype | None = None,
|
| 894 |
+
activation_scale: torch.Tensor | None = None,
|
| 895 |
+
) -> torch.Tensor:
|
| 896 |
+
"""Quantized matmul dispatcher (W8A8 FP8 or W4A8 FP4).
|
| 897 |
+
|
| 898 |
+
``A`` is always raw bf16/fp16/fp32; quantization is fused into every path.
|
| 899 |
+
With ``activation_scale`` set, the kernel uses that per-tensor scalar
|
| 900 |
+
(static quant); otherwise it computes its own scale from ``A`` (dynamic).
|
| 901 |
+
|
| 902 |
+
``output_dtype`` defaults to ``A.dtype``.
|
| 903 |
+
|
| 904 |
+
Routes by weight dtype and ``block_size``:
|
| 905 |
+
- ``B.dtype == int8`` (packed FP4) → ``w4a8_mx_dynamic_fp4_matmul``
|
| 906 |
+
(``block_size`` is ignored; FP4 scale granularity is fixed at 32 and
|
| 907 |
+
tile sizes are autotuned).
|
| 908 |
+
- ``B.dtype == float8_e4m3fn`` with UE8M0 group-32 ``Bs`` (shape ``[N, K//32]``)
|
| 909 |
+
→ ``w8a8_mx_dynamic_fp8_matmul`` (MX scaled MMA; ``block_size`` ignored).
|
| 910 |
+
- ``block_size`` None or full ``[N, K]`` → ``w8a8_tensor_dynamic_fp8_matmul``.
|
| 911 |
+
- otherwise → ``w8a8_block_dynamic_fp8_matmul`` (or its static variant when
|
| 912 |
+
``activation_scale`` is given).
|
| 913 |
+
"""
|
| 914 |
+
if activation_scale is not None:
|
| 915 |
+
if B.dtype == torch.int8:
|
| 916 |
+
raise NotImplementedError(
|
| 917 |
+
"static activation_scale is not supported on the FP4 path"
|
| 918 |
+
)
|
| 919 |
+
if block_size is None or (
|
| 920 |
+
block_size[0] == B.size(0) and block_size[1] == B.size(1)
|
| 921 |
+
):
|
| 922 |
+
raise NotImplementedError(
|
| 923 |
+
"static activation_scale requires block-wise weights, "
|
| 924 |
+
"not tensor-mode (block_size None or full [N, K])"
|
| 925 |
+
)
|
| 926 |
+
return w8a8_block_static_fp8_matmul(
|
| 927 |
+
A, B, Bs, activation_scale, block_size, output_dtype
|
| 928 |
+
)
|
| 929 |
+
|
| 930 |
+
if is_mxfp4(B, Bs):
|
| 931 |
+
return w4a8_mx_dynamic_fp4_matmul(A, B, Bs, output_dtype)
|
| 932 |
+
|
| 933 |
+
if is_mxfp8(B, Bs):
|
| 934 |
+
return w8a8_mx_dynamic_fp8_matmul(A, B, Bs, output_dtype)
|
| 935 |
+
|
| 936 |
+
if block_size is None or (
|
| 937 |
+
block_size[0] == B.size(0) and block_size[1] == B.size(1)
|
| 938 |
+
):
|
| 939 |
+
return w8a8_tensor_dynamic_fp8_matmul(A, B, Bs, output_dtype)
|
| 940 |
+
|
| 941 |
+
return w8a8_block_dynamic_fp8_matmul(A, B, Bs, block_size, output_dtype)
|
build/torch-rocm/metadata.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "finegrained-fp8",
|
| 3 |
+
"id": "_finegrained_fp8_rocm_846165b",
|
| 4 |
+
"version": 3,
|
| 5 |
+
"license": "Apache-2.0",
|
| 6 |
+
"python-depends": [],
|
| 7 |
+
"backend": {
|
| 8 |
+
"type": "rocm"
|
| 9 |
+
},
|
| 10 |
+
"digest": {
|
| 11 |
+
"algorithm": "sha256",
|
| 12 |
+
"files": {
|
| 13 |
+
"__init__.py": "jOglX0eto6QNL9D8T9DzKIb3XYUF5vZAjvEL32MCAHY=",
|
| 14 |
+
"_ops.py": "2e6XSotI60yh7+FU07X7EGCLbnBQRgMN9Kh06vIw9CY=",
|
| 15 |
+
"batched.py": "/ZuVQYNILMt6KBM321fgXxhFRKQM9tMWvvfAEKZY42U=",
|
| 16 |
+
"finegrained_fp8/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY=",
|
| 17 |
+
"grouped.py": "EiotInBQUPtX35Ms+eEZ6b7YyWcxhWTWGq3EiMN0fMs=",
|
| 18 |
+
"matmul.py": "AfyGST+Lrr8AjWqcIx4txrekpKH4iwyS8177TiyeW/M=",
|
| 19 |
+
"utils.py": "pJ6NJrMMz/mSHXZakTmcXW/ZgQ91YnphRk8PVa8yj+U="
|
| 20 |
+
}
|
| 21 |
+
}
|
| 22 |
+
}
|
build/torch-rocm/metadata.json.sigstore
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"mediaType":"application/vnd.dev.sigstore.bundle.v0.3+json","verificationMaterial":{"certificate":{"rawBytes":"MIIHSzCCBtKgAwIBAgIUePTBhgrVPrG0jyrZOpoWjJWMrN4wCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjYwNjE3MTMxNDA0WhcNMjYwNjE3MTMyNDA0WjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE0VUnz693BcejXkNTO62Z4hPYtmgg22tjv3G+w7jq79WyMCmcSVaofu2ZJlOMsA9v36EETYobwETef0B9lLMqAKOCBfEwggXtMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUWg0Gm4lpyM98cT8KBL1cWUOoUKAwHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4YZD8wawYDVR0RAQH/BGEwX4ZdaHR0cHM6Ly9naXRodWIuY29tL2h1Z2dpbmdmYWNlL2tlcm5lbHMtY29tbXVuaXR5Ly5naXRodWIvd29ya2Zsb3dzL2J1aWxkLnlhbWxAcmVmcy9oZWFkcy9tYWluMDkGCisGAQQBg78wAQEEK2h0dHBzOi8vdG9rZW4uYWN0aW9ucy5naXRodWJ1c2VyY29udGVudC5jb20wHwYKKwYBBAGDvzABAgQRd29ya2Zsb3dfZGlzcGF0Y2gwNgYKKwYBBAGDvzABAwQoODQ2MTY1YjExZDU3MTA1ODI5MmQ1NDRiNDIyMzQ2MzE3ZGUxMDJkZjATBgorBgEEAYO/MAEEBAVCdWlsZDArBgorBgEEAYO/MAEFBB1odWdnaW5nZmFjZS9rZXJuZWxzLWNvbW11bml0eTAdBgorBgEEAYO/MAEGBA9yZWZzL2hlYWRzL21haW4wOwYKKwYBBAGDvzABCAQtDCtodHRwczovL3Rva2VuLmFjdGlvbnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tMG0GCisGAQQBg78wAQkEXwxdaHR0cHM6Ly9naXRodWIuY29tL2h1Z2dpbmdmYWNlL2tlcm5lbHMtY29tbXVuaXR5Ly5naXRodWIvd29ya2Zsb3dzL2J1aWxkLnlhbWxAcmVmcy9oZWFkcy9tYWluMDgGCisGAQQBg78wAQoEKgwoODQ2MTY1YjExZDU3MTA1ODI5MmQ1NDRiNDIyMzQ2MzE3ZGUxMDJkZjAbBgorBgEEAYO/MAELBA0MC3NlbGYtaG9zdGVkMEAGCisGAQQBg78wAQwEMgwwaHR0cHM6Ly9naXRodWIuY29tL2h1Z2dpbmdmYWNlL2tlcm5lbHMtY29tbXVuaXR5MDgGCisGAQQBg78wAQ0EKgwoODQ2MTY1YjExZDU3MTA1ODI5MmQ1NDRiNDIyMzQ2MzE3ZGUxMDJkZjAfBgorBgEEAYO/MAEOBBEMD3JlZnMvaGVhZHMvbWFpbjAaBgorBgEEAYO/MAEPBAwMCjEwNzE0NzU1MjkwLgYKKwYBBAGDvzABEAQgDB5odHRwczovL2dpdGh1Yi5jb20vaHVnZ2luZ2ZhY2UwGAYKKwYBBAGDvzABEQQKDAgyNTcyMDc0MzBtBgorBgEEAYO/MAESBF8MXWh0dHBzOi8vZ2l0aHViLmNvbS9odWdnaW5nZmFjZS9rZXJuZWxzLWNvbW11bml0eS8uZ2l0aHViL3dvcmtmbG93cy9idWlsZC55YW1sQHJlZnMvaGVhZHMvbWFpbjA4BgorBgEEAYO/MAETBCoMKDg0NjE2NWIxMWQ1NzEwNTgyOTJkNTQ0YjQyMjM0NjMxN2RlMTAyZGYwIQYKKwYBBAGDvzABFAQTDBF3b3JrZmxvd19kaXNwYXRjaDBkBgorBgEEAYO/MAEVBFYMVGh0dHBzOi8vZ2l0aHViLmNvbS9odWdnaW5nZmFjZS9rZXJuZWxzLWNvbW11bml0eS9hY3Rpb25zL3J1bnMvMjc2OTEyMTUxMzkvYXR0ZW1wdHMvMTAWBgorBgEEAYO/MAEWBAgMBnB1YmxpYzBGBgorBgEEAYO/MAEYBDgMNnJlcG86aHVnZ2luZ2ZhY2Uva2VybmVscy1jb21tdW5pdHk6cmVmOnJlZnMvaGVhZHMvbWFpbjCBiwYKKwYBBAHWeQIEAgR9BHsAeQB3AN09MGrGxxEyYxkeHJlnNwKiSl643jyt/4eKcoAvKe6OAAABntW3q3sAAAQDAEgwRgIhAJbEJ3yIOVCYyNmuAJfsYvWyfk+1pDFQY8XlT3ChiK4jAiEA3eQjC77zGyucbZa6WiNYOz2RO+RYirxBVNgK4f5YQ38wCgYIKoZIzj0EAwMDZwAwZAIwG8x9gZM5WmgUIeDX2l+fLc8BMs/EZFaKD/MTa15tB3hpVYaqTyom3dQSIglzETtxAjAbp0rCeLzhA5nmE+ctKvOwCvxYpD06P51lfdqcUHrgSoK0hNCk26zd8j2S5sAkITM="},"tlogEntries":[{"logIndex":"1851564527","logId":{"keyId":"wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="},"kindVersion":{"kind":"hashedrekord","version":"0.0.1"},"integratedTime":"1781702045","inclusionPromise":{"signedEntryTimestamp":"MEUCIQCXnosi1Fkyem6EcQ7sHvaUqAmtQuF+6Rqe5C3zf6nAzgIgWFY8FhIFCZtR/KGwJjUXFmLG/q6h2SxlBmN4MD2SHUE="},"inclusionProof":{"logIndex":"1729660265","rootHash":"bA+ZSMR5ebA+bxzst9Muh7lqhA3+EZn+j04BxfvmN9w=","treeSize":"1729660347","hashes":["G0kerrCVnmrmJIucj6z35eeb9nQSM/bHQgcR4XUI+h0=","lF6pG0uLVoW8P9IFRM9FuVBL878Tb4de5y8Nos8MY1U=","0us52j044UZxn5OwNPtbzHCxxWeaCW5w/tP1ey0bbdg=","zrWsV+4wyv1S7Qp+yH/FBzQ1C+W8SYogESc7f1QzoH0=","1E3pfOGXxKWEmoIzMvVEkmCUtzhElCK1v5f/rXUgKto=","inihgnOSx1WccBIuIvfshXFGfocE8mH19j08bmnPgMQ=","Xiy1HnHrEBvLAb8qevuD1odTsmP2TEbHNBPhdHPs738=","G75SHivXVw4HNLVI6MwIJ8rOpL3N5b3UTo+0mwcasrU=","/MUwk7ZfPoiJVazjBloofRJ6hS2B8Az3apT2GBKtJ3g=","DT5JAn4B3Dm8gmFcDKslYJrA/3Q9NNFCX4dpHJUGPEU=","cRNcuWZEC/h+ACT/IKS0cgSD29B0pvP9++uNHzrSlj8=","4up1iqXVaWkl4o3JcV/XPRm3hVSZfjTk1Iaqp+cfi/o=","M9+xE/67JCQM3UvBz/mjGSEhOSJ8QCD3Xi7mH8EKFEk=","mqM7J+i75IpuD09QejiUBjeH85AZa+fSm4RXXFmj3lI=","72FC5FYLhxB4a4iiC956o0B/fT54ip4R41vsw2QBKtU=","lYGQ9ibwC8+smMkPQ6TchJm3H9Nc/aTYLfdRacGFChw=","daxmZaajRpZV+JxHiOYZhJBiSKN5ucqjh2WnGbHhirw=","DOCeoSMovIvLExkhIvisow9AuNXgeWs4ECkyR6EcqYU="],"checkpoint":{"envelope":"rekor.sigstore.dev - 1193050959916656506\n1729660347\nbA+ZSMR5ebA+bxzst9Muh7lqhA3+EZn+j04BxfvmN9w=\n\n— rekor.sigstore.dev wNI9ajBFAiEAuxuJjQEyMmOUsx8f2UggYVM4d36K6WVhM8FqnsaH+0wCIE0MIegCiydXCBDLCY7uPBMp08uzZiISGY+h/oc+gsp/\n"}},"canonicalizedBody":"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiJiMWZhMGY0YWIyY2ZhNzJkNzc4YzUzOTMxNTNlYzQ1ZGUwZDMzNjFhMDFiNTQ1NjNlNjQyZTkxNWFlYjU4NjZmIn19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FVUNJRy9lenNqWnRlTWhBY1U5TmhMWURNUDFrcllDd0t6bkF3LzVaaUZnbm5VbkFpRUEyMnJhcXZzMm92OGtIcnJJa2JteDhCSnVPU0g4dmh0WmlQMnRBTUdSa0NzPSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVaFRla05EUW5STFowRjNTVUpCWjBsVlpWQlVRbWhuY2xaUWNrY3dhbmx5V2s5d2IxZHFTbGROY2s0MGQwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcFpkMDVxUlROTlZFMTRUa1JCTUZkb1kwNU5hbGwzVG1wRk0wMVVUWGxPUkVFd1YycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVV3VmxWdWVqWTVNMEpqWldwWWEwNVVUell5V2pSb1VGbDBiV2RuTWpKMGFuWXpSeXNLZHpkcWNUYzVWM2xOUTIxalUxWmhiMloxTWxwS2JFOU5jMEU1ZGpNMlJVVlVXVzlpZDBWVVpXWXdRamxzVEUxeFFVdFBRMEptUlhkbloxaDBUVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXUkNaMDVXU0ZFMFJVWm5VVlZYWnpCSENtMDBiSEI1VFRrNFkxUTRTMEpNTVdOWFZVOXZWVXRCZDBoM1dVUldVakJxUWtKbmQwWnZRVlV6T1ZCd2VqRlphMFZhWWpWeFRtcHdTMFpYYVhocE5Ga0tXa1E0ZDJGM1dVUldVakJTUVZGSUwwSkhSWGRZTkZwa1lVaFNNR05JVFRaTWVUbHVZVmhTYjJSWFNYVlpNamwwVERKb01Wb3laSEJpYldSdFdWZE9iQXBNTW5Sc1kyMDFiR0pJVFhSWk1qbDBZbGhXZFdGWVVqVk1lVFZ1WVZoU2IyUlhTWFprTWpsNVlUSmFjMkl6WkhwTU1rb3hZVmQ0YTB4dWJHaGlWM2hCQ21OdFZtMWplVGx2V2xkR2EyTjVPWFJaVjJ4MVRVUnJSME5wYzBkQlVWRkNaemM0ZDBGUlJVVkxNbWd3WkVoQ2VrOXBPSFprUnpseVdsYzBkVmxYVGpBS1lWYzVkV041Tlc1aFdGSnZaRmRLTVdNeVZubFpNamwxWkVkV2RXUkROV3BpTWpCM1NIZFpTMHQzV1VKQ1FVZEVkbnBCUWtGblVWSmtNamw1WVRKYWN3cGlNMlJtV2tkc2VtTkhSakJaTW1kM1RtZFpTMHQzV1VKQ1FVZEVkbnBCUWtGM1VXOVBSRkV5VFZSWk1WbHFSWGhhUkZVelRWUkJNVTlFU1RWTmJWRXhDazVFVW1sT1JFbDVUWHBSTWsxNlJUTmFSMVY0VFVSS2ExcHFRVlJDWjI5eVFtZEZSVUZaVHk5TlFVVkZRa0ZXUTJSWGJITmFSRUZ5UW1kdmNrSm5SVVVLUVZsUEwwMUJSVVpDUWpGdlpGZGtibUZYTlc1YWJVWnFXbE01Y2xwWVNuVmFWM2g2VEZkT2RtSlhNVEZpYld3d1pWUkJaRUpuYjNKQ1owVkZRVmxQTHdwTlFVVkhRa0U1ZVZwWFducE1NbWhzV1ZkU2Vrd3lNV2hoVnpSM1QzZFpTMHQzV1VKQ1FVZEVkbnBCUWtOQlVYUkVRM1J2WkVoU2QyTjZiM1pNTTFKMkNtRXlWblZNYlVacVpFZHNkbUp1VFhWYU1td3dZVWhXYVdSWVRteGpiVTUyWW01U2JHSnVVWFZaTWpsMFRVY3dSME5wYzBkQlVWRkNaemM0ZDBGUmEwVUtXSGQ0WkdGSVVqQmpTRTAyVEhrNWJtRllVbTlrVjBsMVdUSTVkRXd5YURGYU1tUndZbTFrYlZsWFRteE1NblJzWTIwMWJHSklUWFJaTWpsMFlsaFdkUXBoV0ZJMVRIazFibUZZVW05a1YwbDJaREk1ZVdFeVduTmlNMlI2VERKS01XRlhlR3RNYm14b1lsZDRRV050Vm0xamVUbHZXbGRHYTJONU9YUlpWMngxQ2sxRVowZERhWE5IUVZGUlFtYzNPSGRCVVc5RlMyZDNiMDlFVVRKTlZGa3hXV3BGZUZwRVZUTk5WRUV4VDBSSk5VMXRVVEZPUkZKcFRrUkplVTE2VVRJS1RYcEZNMXBIVlhoTlJFcHJXbXBCWWtKbmIzSkNaMFZGUVZsUEwwMUJSVXhDUVRCTlF6Tk9iR0pIV1hSaFJ6bDZaRWRXYTAxRlFVZERhWE5IUVZGUlFncG5OemgzUVZGM1JVMW5kM2RoU0ZJd1kwaE5Oa3g1T1c1aFdGSnZaRmRKZFZreU9YUk1NbWd4V2pKa2NHSnRaRzFaVjA1c1RESjBiR050Tld4aVNFMTBDbGt5T1hSaVdGWjFZVmhTTlUxRVowZERhWE5IUVZGUlFtYzNPSGRCVVRCRlMyZDNiMDlFVVRKTlZGa3hXV3BGZUZwRVZUTk5WRUV4VDBSSk5VMXRVVEVLVGtSU2FVNUVTWGxOZWxFeVRYcEZNMXBIVlhoTlJFcHJXbXBCWmtKbmIzSkNaMFZGUVZsUEwwMUJSVTlDUWtWTlJETktiRnB1VFhaaFIxWm9Xa2hOZGdwaVYwWndZbXBCWVVKbmIzSkNaMFZGUVZsUEwwMUJSVkJDUVhkTlEycEZkMDU2UlRCT2VsVXhUV3ByZDB4bldVdExkMWxDUWtGSFJIWjZRVUpGUVZGbkNrUkNOVzlrU0ZKM1kzcHZka3d5WkhCa1IyZ3hXV2sxYW1JeU1IWmhTRlp1V2pKc2RWb3lXbWhaTWxWM1IwRlpTMHQzV1VKQ1FVZEVkbnBCUWtWUlVVc0tSRUZuZVU1VVkzbE5SR013VFhwQ2RFSm5iM0pDWjBWRlFWbFBMMDFCUlZOQ1JqaE5XRmRvTUdSSVFucFBhVGgyV2pKc01HRklWbWxNYlU1MllsTTVid3BrVjJSdVlWYzFibHB0Um1wYVV6bHlXbGhLZFZwWGVIcE1WMDUyWWxjeE1XSnRiREJsVXpoMVdqSnNNR0ZJVm1sTU0yUjJZMjEwYldKSE9UTmplVGxwQ21SWGJITmFRelUxV1ZjeGMxRklTbXhhYmsxMllVZFdhRnBJVFhaaVYwWndZbXBCTkVKbmIzSkNaMFZGUVZsUEwwMUJSVlJDUTI5TlMwUm5NRTVxUlRJS1RsZEplRTFYVVRGT2VrVjNUbFJuZVU5VVNtdE9WRkV3V1dwUmVVMXFUVEJPYWsxNFRqSlNiRTFVUVhsYVIxbDNTVkZaUzB0M1dVSkNRVWRFZG5wQlFncEdRVkZVUkVKR00ySXpTbkphYlhoMlpERTVhMkZZVG5kWldGSnFZVVJDYTBKbmIzSkNaMFZGUVZsUEwwMUJSVlpDUmxsTlZrZG9NR1JJUW5wUGFUaDJDbG95YkRCaFNGWnBURzFPZG1KVE9XOWtWMlJ1WVZjMWJscHRSbXBhVXpseVdsaEtkVnBYZUhwTVYwNTJZbGN4TVdKdGJEQmxVemxvV1ROU2NHSXlOWG9LVEROS01XSnVUWFpOYW1NeVQxUkZlVTFVVlhoTmVtdDJXVmhTTUZwWE1YZGtTRTEyVFZSQlYwSm5iM0pDWjBWRlFWbFBMMDFCUlZkQ1FXZE5RbTVDTVFwWmJYaHdXWHBDUjBKbmIzSkNaMFZGUVZsUEwwMUJSVmxDUkdkTlRtNUtiR05IT0RaaFNGWnVXakpzZFZveVdtaFpNbFYyWVRKV2VXSnRWbk5qZVRGcUNtSXlNWFJrVnpWd1pFaHJObU50Vm0xUGJrcHNXbTVOZG1GSFZtaGFTRTEyWWxkR2NHSnFRMEpwZDFsTFMzZFpRa0pCU0ZkbFVVbEZRV2RTT1VKSWMwRUtaVkZDTTBGT01EbE5SM0pIZUhoRmVWbDRhMlZJU214dVRuZExhVk5zTmpRemFubDBMelJsUzJOdlFYWkxaVFpQUVVGQlFtNTBWek54TTNOQlFVRlJSQXBCUldkM1VtZEphRUZLWWtWS00zbEpUMVpEV1hsT2JYVkJTbVp6V1haWGVXWnJLekZ3UkVaUldUaFliRlF6UTJocFN6UnFRV2xGUVRObFVXcEROemQ2Q2tkNWRXTmlXbUUyVjJsT1dVOTZNbEpQSzFKWmFYSjRRbFpPWjBzMFpqVlpVVE00ZDBObldVbExiMXBKZW1vd1JVRjNUVVJhZDBGM1drRkpkMGM0ZURrS1oxcE5OVmR0WjFWSlpVUllNbXdyWmt4ak9FSk5jeTlGV2taaFMwUXZUVlJoTVRWMFFqTm9jRlpaWVhGVWVXOXRNMlJSVTBsbmJIcEZWSFI0UVdwQllncHdNSEpEWlV4NmFFRTFibTFGSzJOMFMzWlBkME4yZUZsd1JEQTJVRFV4Ykdaa2NXTlZTSEpuVTI5TE1HaE9RMnN5Tm5wa09Hb3lVelZ6UVd0SlZFMDlDaTB0TFMwdFJVNUVJRU5GVWxSSlJrbERRVlJGTFMwdExTMEsifX19fQ=="}],"timestampVerificationData":{"rfc3161Timestamps":[{"signedTimestamp":"MIICyDADAgEAMIICvwYJKoZIhvcNAQcCoIICsDCCAqwCAQMxDTALBglghkgBZQMEAgEwgbcGCyqGSIb3DQEJEAEEoIGnBIGkMIGhAgEBBgkrBgEEAYO/MAIwMTANBglghkgBZQMEAgEFAAQgirsf3iN2RuZt8kLZKD5VUGqJ0tF1saNu4OqNKmEEAQkCFEvPI1QROe8iGgplF9//L1TeLChVGA8yMDI2MDYxNzEzMTQwNFowAwIBAaAypDAwLjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MRUwEwYDVQQDEwxzaWdzdG9yZS10c2GgADGCAdowggHWAgEBMFEwOTEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MSAwHgYDVQQDExdzaWdzdG9yZS10c2Etc2VsZnNpZ25lZAIUOhNULwyQYe68wUMvy4qOiyojiwwwCwYJYIZIAWUDBAIBoIH8MBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAcBgkqhkiG9w0BCQUxDxcNMjYwNjE3MTMxNDA0WjAvBgkqhkiG9w0BCQQxIgQg4zyqLLBaOA3qBIJ3rbimqn9CbsEwnKlOhCtR7uq4B7cwgY4GCyqGSIb3DQEJEAIvMX8wfTB7MHkEIIX5J7wHq2LKw7RDVsEO/IGyxog/2nq55thw2dE6zQW3MFUwPaQ7MDkxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEgMB4GA1UEAxMXc2lnc3RvcmUtdHNhLXNlbGZzaWduZWQCFDoTVC8MkGHuvMFDL8uKjosqI4sMMAoGCCqGSM49BAMCBGYwZAIwT9UpLg/DWWo/eugeIjzk42GOVBb1Gb6MAuIheAGnW2fNlhwC+NrHfPPdktaiStmtAjBmoKRxlPXAqSOmvHg1DYzFdMGSGdm2JJ1wfV3/oYNMuymIIXPWookWoIWZQHgKKzE="}]}},"messageSignature":{"messageDigest":{"algorithm":"SHA2_256","digest":"sfoPSrLPpy13jFOTFT7EXeDTNhoBtUVj5kLpFa61hm8="},"signature":"MEUCIG/ezsjZteMhAcU9NhLYDMP1krYCwKznAw/5ZiFgnnUnAiEA22raqvs2ov8kHrrIkbmx8BJuOSH8vhtZiP2tAMGRkCs="}}
|
build/torch-rocm/utils.py
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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):
|
| 41 |
+
"""Context manager that sets the active device for any backend (cuda, xpu, etc.)."""
|
| 42 |
+
backend = getattr(torch, device.type, None)
|
| 43 |
+
if backend is not None and hasattr(backend, "device"):
|
| 44 |
+
with backend.device(device):
|
| 45 |
+
yield
|
| 46 |
+
else:
|
| 47 |
+
yield
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def ue8m0_as_uint8(scale: torch.Tensor) -> torch.Tensor:
|
| 51 |
+
"""View UE8M0 (``float8_e8m0fnu``) weight scales as ``uint8`` for the Triton
|
| 52 |
+
binder, which doesn't recognize the dtype; kernels decode ``2^(exp-127)``
|
| 53 |
+
inline. fp32 (non-UE8M0) scales pass through unchanged."""
|
| 54 |
+
return scale.view(torch.uint8) if scale.dtype == torch.float8_e8m0fnu else scale
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def is_mxfp8(weight: torch.Tensor, scale: torch.Tensor) -> bool:
|
| 58 |
+
"""MXFP8 weight/scale pair: E4M3 weights with UE8M0 group-32 scales — last dim
|
| 59 |
+
``scale.shape[-1] == weight.shape[-1] // MX_SCALE_GROUP_K``, matching leading dims.
|
| 60 |
+
Works for 2D ``(N, K)`` and 3D ``(E, N, K)`` weights. The group-32 layout is what
|
| 61 |
+
separates MXFP8 from 128-block FP8 (which may also carry UE8M0 scales)."""
|
| 62 |
+
return (
|
| 63 |
+
weight.dtype == torch.float8_e4m3fn
|
| 64 |
+
and scale.dtype == torch.float8_e8m0fnu
|
| 65 |
+
and scale.ndim == weight.ndim
|
| 66 |
+
and scale.shape[:-1] == weight.shape[:-1]
|
| 67 |
+
and scale.shape[-1] == weight.shape[-1] // MX_SCALE_GROUP_K
|
| 68 |
+
)
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
def is_mxfp4(weight: torch.Tensor, scale: torch.Tensor) -> bool:
|
| 72 |
+
"""MXFP4 weight/scale pair: packed E2M1 weights (``int8``, two codes/byte) with
|
| 73 |
+
UE8M0 group-32 scales — ``scale.shape[-1] == weight.shape[-1] * NIBBLES_PER_BYTE //
|
| 74 |
+
MX_SCALE_GROUP_K`` (unpacked K = ``2 * K_half``), matching leading dims. 2D or 3D."""
|
| 75 |
+
return (
|
| 76 |
+
weight.dtype == torch.int8
|
| 77 |
+
and scale.dtype == torch.float8_e8m0fnu
|
| 78 |
+
and scale.ndim == weight.ndim
|
| 79 |
+
and scale.shape[:-1] == weight.shape[:-1]
|
| 80 |
+
and scale.shape[-1] == weight.shape[-1] * NIBBLES_PER_BYTE // MX_SCALE_GROUP_K
|
| 81 |
+
)
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
def adaptive_block_size_m(target_m: int) -> int:
|
| 85 |
+
"""Smallest power-of-2 >= ``target_m``, floored at 16 and capped at 128.
|
| 86 |
+
|
| 87 |
+
Used by all matmul wrappers (single / batched / grouped) to size the M tile
|
| 88 |
+
to the workload — small per-expert M wants smaller tiles, large M caps out
|
| 89 |
+
at 128 to keep register pressure bounded. Pass ``M`` for single matmul, or
|
| 90 |
+
``(S + E - 1) // E`` (avg tokens per expert) for batched/grouped.
|
| 91 |
+
"""
|
| 92 |
+
return min(max(triton.next_power_of_2(target_m), 16), 128)
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
@functools.cache
|
| 96 |
+
def get_active_device_type() -> str:
|
| 97 |
+
"""Active torch device type for the current Triton backend (``"cuda"``, ``"xpu"``, ...).
|
| 98 |
+
|
| 99 |
+
Falls back to ``"cuda"`` when no driver is loaded — Triton raises
|
| 100 |
+
``RuntimeError: 0 active drivers ([])`` on driverless build boxes, and the
|
| 101 |
+
autotune-config builder is evaluated at module-import time under the
|
| 102 |
+
``@triton.autotune`` decorator (no kernel launches there, so the default is
|
| 103 |
+
only used to shape the config list).
|
| 104 |
+
"""
|
| 105 |
+
try:
|
| 106 |
+
return triton.runtime.driver.active.get_active_torch_device().type
|
| 107 |
+
except RuntimeError:
|
| 108 |
+
return "cuda"
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
def get_accelerator_autotuning_configs(*, with_block_sizes: bool = False):
|
| 112 |
+
"""Autotune search grid for the current accelerator.
|
| 113 |
+
|
| 114 |
+
``num_warps``, ``num_stages`` and ``blocks`` (the ``(BLOCK_SIZE_N, BLOCK_SIZE_K)``
|
| 115 |
+
tile shapes) are fixed up front from ``(is_xpu, with_block_sizes)``, then crossed
|
| 116 |
+
into the config list.
|
| 117 |
+
|
| 118 |
+
``with_block_sizes=True`` sweeps the tile: used by kernels that have no caller
|
| 119 |
+
``block_size`` to fix it — the MX ``tl.dot_scaled`` paths AND the tensor-dynamic
|
| 120 |
+
FP8 paths. ``with_block_sizes=False`` emits a single empty meta-dict (block-scaled
|
| 121 |
+
kernels take the tile from the caller's ``block_size``).
|
| 122 |
+
|
| 123 |
+
The CUDA tile set is a data-driven prune of a B200 sweep across single (BM=128),
|
| 124 |
+
grouped MoE (BM=16/64) and decode (BM=1): winners only ever used these 4 tiles,
|
| 125 |
+
num_warps in {4,8,16}, num_stages in {2,3} (warps=2, stages=4 and 256x256 never
|
| 126 |
+
won) — 108 → 24. (Tuned on dot_scaled; tensor-dynamic tl.dot reuses it.)
|
| 127 |
+
"""
|
| 128 |
+
is_xpu = get_active_device_type() == "xpu"
|
| 129 |
+
if with_block_sizes:
|
| 130 |
+
num_stages = [2, 3]
|
| 131 |
+
num_warps = [8, 16] if is_xpu else [4, 8, 16]
|
| 132 |
+
tiles = (
|
| 133 |
+
[(128, 128)] if is_xpu else [(128, 128), (256, 128), (128, 64), (64, 256)]
|
| 134 |
+
)
|
| 135 |
+
blocks = [{"BLOCK_SIZE_N": bn, "BLOCK_SIZE_K": bk} for bn, bk in tiles]
|
| 136 |
+
else:
|
| 137 |
+
num_stages = [2, 3, 4]
|
| 138 |
+
num_warps = [8, 16] if is_xpu else [2, 4, 8, 16]
|
| 139 |
+
blocks = [{}]
|
| 140 |
+
|
| 141 |
+
return [
|
| 142 |
+
triton.Config(b, num_warps=w, num_stages=s)
|
| 143 |
+
for b in blocks
|
| 144 |
+
for w in num_warps
|
| 145 |
+
for s in num_stages
|
| 146 |
+
]
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
# ── Triton-side helpers (inlined by ``@triton.jit`` callers) ──────────────────
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
@triton.jit
|
| 153 |
+
def fp8_act_quant_inline(a_raw):
|
| 154 |
+
"""Inline FP8 (E4M3) activation quant for the W8A8 block-scale path.
|
| 155 |
+
|
| 156 |
+
Per-row amax → fp32 scale ``amax/448`` (floored at 1e-12 against zero rows)
|
| 157 |
+
→ cast values to FP8. Returns ``(a_fp8, a_s)`` with shapes ``(M, K)`` and
|
| 158 |
+
``(M,)``.
|
| 159 |
+
"""
|
| 160 |
+
a_s = tl.max(tl.abs(a_raw), axis=1) / 448.0
|
| 161 |
+
a_fp8 = (a_raw / tl.maximum(a_s[:, None], 1e-12)).to(tl.float8e4nv)
|
| 162 |
+
return a_fp8, a_s
|
| 163 |
+
|
| 164 |
+
|
| 165 |
+
@triton.jit
|
| 166 |
+
def mx_act_quant_inline(
|
| 167 |
+
a_raw,
|
| 168 |
+
BLOCK_SIZE_M: tl.constexpr,
|
| 169 |
+
BLOCK_SIZE_K: tl.constexpr,
|
| 170 |
+
SCALE_GROUP_K: tl.constexpr,
|
| 171 |
+
):
|
| 172 |
+
"""Inline E4M3 activation quant for the MX paths (W4A8 MXFP4 / W8A8 MXFP8).
|
| 173 |
+
|
| 174 |
+
Per-row, per-K-group amax → UE8M0 scale (ceil to next power-of-2 via the
|
| 175 |
+
mantissa-nonzero bump trick) → cast values to FP8. Returns ``(a_fp8,
|
| 176 |
+
a_scale_u8)`` with shapes ``(M, K)`` and ``(M, K // SCALE_GROUP_K)``.
|
| 177 |
+
"""
|
| 178 |
+
a_groups = tl.reshape(
|
| 179 |
+
a_raw, (BLOCK_SIZE_M, BLOCK_SIZE_K // SCALE_GROUP_K, SCALE_GROUP_K)
|
| 180 |
+
)
|
| 181 |
+
a_s_fp32 = tl.max(tl.abs(a_groups), axis=2) / 448.0
|
| 182 |
+
bits = a_s_fp32.to(tl.int32, bitcast=True)
|
| 183 |
+
# ceil_to_ue8m0: bump exponent by 1 when mantissa is non-zero.
|
| 184 |
+
exp_ceil = ((bits >> 23) & 0xFF) + ((bits & 0x7FFFFF) != 0).to(tl.int32)
|
| 185 |
+
exp_ceil = tl.minimum(tl.maximum(exp_ceil, 1), 254)
|
| 186 |
+
a_scale_u8 = exp_ceil.to(tl.uint8)
|
| 187 |
+
a_s_pow2 = (exp_ceil << 23).to(tl.float32, bitcast=True)
|
| 188 |
+
a_fp8 = tl.reshape(
|
| 189 |
+
a_groups / tl.maximum(a_s_pow2[:, :, None], 1e-12),
|
| 190 |
+
(BLOCK_SIZE_M, BLOCK_SIZE_K),
|
| 191 |
+
).to(tl.float8e4nv)
|
| 192 |
+
return a_fp8, a_scale_u8
|
| 193 |
+
|
| 194 |
+
|
| 195 |
+
@triton.jit
|
| 196 |
+
def decode_ue8m0_scale(scale):
|
| 197 |
+
"""Decode a UE8M0 weight scale to fp32: when it was loaded as ``uint8`` exponent
|
| 198 |
+
bits, ``value = 2^(exp - 127)``, built directly as the fp32 bit pattern. fp32
|
| 199 |
+
scales (block-dynamic with float scales) pass through. The dtype branch is a
|
| 200 |
+
compile-time constant, so only the taken path is emitted (single return — Triton
|
| 201 |
+
requires all ``return`` statements to share a type)."""
|
| 202 |
+
if scale.dtype == tl.uint8:
|
| 203 |
+
scale = (scale.to(tl.int32) << 23).to(tl.float32, bitcast=True)
|
| 204 |
+
return scale
|
| 205 |
+
|
| 206 |
+
|
| 207 |
+
# ── fp8_act_quant kernel (used by tensor-mode FP8 wrappers) ───────────────────
|
| 208 |
+
|
| 209 |
+
|
| 210 |
+
@triton.jit
|
| 211 |
+
def _fp8_act_quant_kernel(
|
| 212 |
+
x_ptr, y_ptr, s_ptr, BLOCK_SIZE: tl.constexpr, PADDED_BLOCK: tl.constexpr
|
| 213 |
+
):
|
| 214 |
+
# ``tl.arange`` needs a power-of-2 length, so iterate over PADDED_BLOCK (the next
|
| 215 |
+
# power of 2) and mask the tail — lets block_size be non-power-of-2 (e.g. a full
|
| 216 |
+
# row K=14336 in tensor-mode). Masked lanes load 0, which can't affect ``amax``.
|
| 217 |
+
pid = tl.program_id(axis=0)
|
| 218 |
+
cols = tl.arange(0, PADDED_BLOCK)
|
| 219 |
+
mask = cols < BLOCK_SIZE
|
| 220 |
+
offs = pid * BLOCK_SIZE + cols
|
| 221 |
+
x = tl.load(x_ptr + offs, mask=mask, other=0.0).to(tl.float32)
|
| 222 |
+
s = tl.max(tl.abs(x)) / 448.0 # float8_e4m3fn max
|
| 223 |
+
y = (x / tl.maximum(s, 1e-12)).to(y_ptr.dtype.element_ty)
|
| 224 |
+
tl.store(y_ptr + offs, y, mask=mask)
|
| 225 |
+
tl.store(s_ptr + pid, s)
|
| 226 |
+
|
| 227 |
+
|
| 228 |
+
@triton_op(add_op_namespace_prefix("fp8_act_quant"), mutates_args=())
|
| 229 |
+
def _fp8_act_quant(
|
| 230 |
+
x: torch.Tensor, block_size: int = 128
|
| 231 |
+
) -> tuple[torch.Tensor, torch.Tensor]:
|
| 232 |
+
assert x.is_contiguous()
|
| 233 |
+
assert x.shape[-1] % block_size == 0
|
| 234 |
+
y = torch.empty_like(x, dtype=FP8_DTYPE)
|
| 235 |
+
grid = (triton.cdiv(x.numel(), block_size),)
|
| 236 |
+
s = x.new_empty(*x.size()[:-1], x.size(-1) // block_size, dtype=torch.float32)
|
| 237 |
+
|
| 238 |
+
with device_context(x.device):
|
| 239 |
+
wrap_triton(_fp8_act_quant_kernel)[grid](
|
| 240 |
+
x,
|
| 241 |
+
y,
|
| 242 |
+
s,
|
| 243 |
+
BLOCK_SIZE=block_size,
|
| 244 |
+
PADDED_BLOCK=triton.next_power_of_2(block_size),
|
| 245 |
+
)
|
| 246 |
+
|
| 247 |
+
return y, s
|
| 248 |
+
|
| 249 |
+
|
| 250 |
+
def fp8_act_quant(
|
| 251 |
+
x: torch.Tensor, block_size: int = 128
|
| 252 |
+
) -> tuple[torch.Tensor, torch.Tensor]:
|
| 253 |
+
"""Quantize activations to FP8 with per-block dynamic scaling.
|
| 254 |
+
|
| 255 |
+
Splits the last dimension of ``x`` into blocks of ``block_size`` elements,
|
| 256 |
+
computes ``scale = max(|x_block|) / 448`` per block, and quantizes to
|
| 257 |
+
``float8_e4m3fn``.
|
| 258 |
+
|
| 259 |
+
Args:
|
| 260 |
+
x: Input tensor in bf16/fp16/fp32. Last dimension must be divisible by
|
| 261 |
+
``block_size`` and the tensor must be contiguous.
|
| 262 |
+
block_size: Number of elements per quantization block (default: 128).
|
| 263 |
+
|
| 264 |
+
Returns:
|
| 265 |
+
A tuple ``(quantized, scales)`` where ``quantized`` has dtype
|
| 266 |
+
``float8_e4m3fn`` with the same shape as ``x``, and ``scales`` has
|
| 267 |
+
shape ``(*x.shape[:-1], x.shape[-1] // block_size)`` in float32.
|
| 268 |
+
"""
|
| 269 |
+
return ops.fp8_act_quant(x, block_size)
|