Files
wehub-resource-sync 59a0a3844c
PR Test AMD / cancel-on-close (push) Has been skipped
PR Test NVIDIA ARM / scan (push) Has been skipped
PR Test NVIDIA / cancel-on-close (push) Has been skipped
PR Test AMD / scan (push) Has been skipped
PR Test NVIDIA ARM / cancel-on-close (push) Has been skipped
PR Test NVIDIA / scan (push) Has been skipped
Release Docker Images / build (cu129-torch-2.11.0) (push) Has been skipped
Release Docker Images / build (cu130-torch-2.11.0) (push) Has been skipped
Release PyPI / publish (push) Has been skipped
Scheduler Python Test / test (push) Successful in 27m19s
Docs / build (push) Successful in 28m8s
Scheduler C++ Test / test (push) Successful in 28m19s
Scheduler C++ Test / test-flat (push) Successful in 28m18s
Docs / deploy (push) Has been cancelled
PR Test AMD / finish (push) Has been cancelled
PR Test NVIDIA / finish (push) Has been cancelled
PR Test NVIDIA ARM / finish (push) Has been cancelled
PR Test NVIDIA ARM / ${{ matrix.name }} (${{ matrix.runner }}) (push) Has been cancelled
PR Test AMD / ${{ matrix.name }} (${{ matrix.runner }}) (push) Has been cancelled
PR Test NVIDIA / ${{ matrix.name }} (${{ matrix.runner }}) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:32:31 +08:00

331 lines
11 KiB
Python

# Copyright (c) 2026 LightSeek Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from __future__ import annotations
from typing import Optional, Tuple
from tokenspeed_kernel.platform import current_platform
from tokenspeed_kernel.registry import error_fn
platform = current_platform()
nvfp4_gemm_swiglu_nvfp4_quant = error_fn
if platform.is_nvidia:
import cuda.bindings.driver as cuda
import cutlass
import cutlass.cute as cute
import torch
from flashinfer.cute_dsl.utils import (
get_cutlass_dtype,
get_max_active_clusters,
make_ptr,
)
from flashinfer.utils import get_compute_capability
from tokenspeed_kernel.thirdparty.cute_dsl.nvfp4_gemm_swiglu_nvfp4_quant import (
Sm100BlockScaledPersistentDenseGemmKernel,
)
_nvfp4_gemm_swiglu_nvfp4_quant_kernel_cache: dict[tuple, object] = {}
def _round_up(value: int, multiple: int) -> int:
return (value + multiple - 1) // multiple * multiple
def _get_compiled_nvfp4_gemm_swiglu_nvfp4_quant_kernel(
*,
a_ptr,
b_ptr,
a_sf_ptr,
b_sf_ptr,
c_ptr,
c_sf_ptr,
alpha_ptr,
norm_const_ptr,
m: int,
n: int,
k: int,
l: int,
max_active_clusters: int,
stream,
ab_dtype: str,
sf_dtype: str,
c_dtype: str,
sf_vec_size: int,
mma_tiler_mn: Tuple[int, int],
cluster_shape_mn: Tuple[int, int],
use_prefetch: bool,
prefetch_dist: int,
vectorized_f32: bool,
enable_pdl: bool,
):
cache_key = (
n,
k,
ab_dtype,
sf_dtype,
c_dtype,
sf_vec_size,
mma_tiler_mn,
cluster_shape_mn,
use_prefetch,
prefetch_dist,
vectorized_f32,
enable_pdl,
)
if cache_key not in _nvfp4_gemm_swiglu_nvfp4_quant_kernel_cache:
gemm = Sm100BlockScaledPersistentDenseGemmKernel(
sf_vec_size=sf_vec_size,
mma_tiler_mn=mma_tiler_mn,
cluster_shape_mn=cluster_shape_mn,
use_prefetch=use_prefetch,
prefetch_dist=prefetch_dist,
vectorized_f32=vectorized_f32,
)
_nvfp4_gemm_swiglu_nvfp4_quant_kernel_cache[cache_key] = cute.compile(
gemm.wrapper,
a_ptr,
b_ptr,
a_sf_ptr,
b_sf_ptr,
c_ptr,
c_sf_ptr,
alpha_ptr,
norm_const_ptr,
m,
n,
k,
l,
scaling_vector_size=sf_vec_size,
max_active_clusters=max_active_clusters,
stream=stream,
use_pdl=enable_pdl,
)
return _nvfp4_gemm_swiglu_nvfp4_quant_kernel_cache[cache_key]
def nvfp4_gemm_swiglu_nvfp4_quant(
a: torch.Tensor,
a_scale: torch.Tensor,
b: torch.Tensor,
b_scale: torch.Tensor,
alpha: torch.Tensor,
output_global_scale: torch.Tensor,
*,
out: Optional[torch.Tensor] = None,
out_scale: Optional[torch.Tensor] = None,
ab_dtype: str = "float4_e2m1fn",
sf_dtype: str = "float8_e4m3fn",
c_dtype: str = "float4_e2m1fn",
sf_vec_size: int = 16,
use_prefetch: bool = False,
prefetch_dist: int = 3,
vectorized_f32: bool = True,
enable_pdl: bool = False,
) -> tuple[torch.Tensor, torch.Tensor]:
"""NVFP4 GEMM fused with SwiGLU and NVFP4 output quantization.
Args:
a: FP4-packed input activation, shape ``[M, K / 2]``.
a_scale: Swizzled NVFP4 input scales, shape ``[round_up(M,128), round_up(K/16,4)]``.
b: FP4-packed interleaved FC1 weight, shape ``[2 * I, K / 2]``.
b_scale: Swizzled interleaved FC1 weight scales.
alpha: GEMM global dequant scale, scalar or ``[1, 1]``.
output_global_scale: Output quantization scale-up factor.
enable_pdl: Enable Programmatic Dependent Launch for this fused kernel.
Returns:
``(out_fp4, out_scale)`` directly consumable by NVFP4 ``down_proj``.
"""
if ab_dtype != "float4_e2m1fn" or c_dtype != "float4_e2m1fn":
raise ValueError(
"nvfp4_gemm_swiglu_nvfp4_quant currently supports NVFP4 input and output only"
)
if a.device.type != "cuda" or b.device.type != "cuda":
raise ValueError("nvfp4_gemm_swiglu_nvfp4_quant requires CUDA tensors")
major, minor = get_compute_capability(a.device)
if major != 10:
raise ValueError(
"nvfp4_gemm_swiglu_nvfp4_quant requires Blackwell SM100 family, "
f"got SM{major}{minor}"
)
m = a.shape[0]
k = a.shape[1] * 2
n = b.shape[0]
if b.shape[1] * 2 != k:
raise ValueError(f"Shape mismatch: A K={k}, B K={b.shape[1] * 2}")
if n % 2 != 0:
raise ValueError(f"Interleaved FC1 N must be even, got {n}")
l = 1
n_out = n // 2
if n_out % sf_vec_size != 0:
raise ValueError(
f"Output N={n_out} must be divisible by sf_vec_size={sf_vec_size}"
)
scale_n_out = n_out // sf_vec_size
padded_m = _round_up(m, 128)
padded_scale_n = _round_up(scale_n_out, 4)
ab_dtype_cutlass = get_cutlass_dtype(ab_dtype)
sf_dtype_cutlass = get_cutlass_dtype(sf_dtype)
c_dtype_cutlass = get_cutlass_dtype(c_dtype)
# Select the tile shape from the current M dimension.
if m <= 128:
mma_tiler_mn, cluster_shape_mn = (128, 128), (1, 2)
else:
mma_tiler_mn, cluster_shape_mn = (256, 128), (2, 1)
if not Sm100BlockScaledPersistentDenseGemmKernel.can_implement(
ab_dtype_cutlass,
sf_dtype_cutlass,
sf_vec_size,
c_dtype_cutlass,
mma_tiler_mn,
cluster_shape_mn,
m,
n,
k,
l,
a_major="k",
b_major="k",
c_major="n",
):
raise ValueError(
"Unsupported nvfp4_gemm_swiglu_nvfp4_quant configuration: "
f"shape=(M={m}, N={n}, K={k}), mma_tiler_mn={mma_tiler_mn}, "
f"cluster_shape_mn={cluster_shape_mn}"
)
if out is None:
out = torch.empty((m, n_out // 2), dtype=torch.uint8, device=a.device)
if out_scale is None:
out_scale = torch.empty(
(padded_m, padded_scale_n),
dtype=torch.float8_e4m3fn,
device=a.device,
)
if alpha.dim() == 0:
alpha = alpha.view(1, 1)
elif alpha.dim() == 1:
alpha = alpha.view(1, 1)
if output_global_scale.dim() == 0:
output_global_scale = output_global_scale.view(1)
a_ptr = make_ptr(
ab_dtype_cutlass,
a.data_ptr(),
cute.AddressSpace.gmem,
assumed_align=32,
)
b_ptr = make_ptr(
ab_dtype_cutlass,
b.data_ptr(),
cute.AddressSpace.gmem,
assumed_align=32,
)
a_sf_ptr = make_ptr(
sf_dtype_cutlass,
a_scale.data_ptr(),
cute.AddressSpace.gmem,
assumed_align=16,
)
b_sf_ptr = make_ptr(
sf_dtype_cutlass,
b_scale.data_ptr(),
cute.AddressSpace.gmem,
assumed_align=16,
)
c_ptr = make_ptr(
c_dtype_cutlass,
out.data_ptr(),
cute.AddressSpace.gmem,
assumed_align=32,
)
c_sf_ptr = make_ptr(
sf_dtype_cutlass,
out_scale.data_ptr(),
cute.AddressSpace.gmem,
assumed_align=16,
)
alpha_ptr = make_ptr(cutlass.Float32, alpha.data_ptr(), cute.AddressSpace.gmem)
norm_const_ptr = make_ptr(
cutlass.Float32,
output_global_scale.data_ptr(),
cute.AddressSpace.gmem,
)
stream = cuda.CUstream(torch.cuda.current_stream().cuda_stream)
max_active_clusters = get_max_active_clusters(
cluster_shape_mn[0] * cluster_shape_mn[1]
)
compiled_gemm = _get_compiled_nvfp4_gemm_swiglu_nvfp4_quant_kernel(
a_ptr=a_ptr,
b_ptr=b_ptr,
a_sf_ptr=a_sf_ptr,
b_sf_ptr=b_sf_ptr,
c_ptr=c_ptr,
c_sf_ptr=c_sf_ptr,
alpha_ptr=alpha_ptr,
norm_const_ptr=norm_const_ptr,
m=m,
n=n,
k=k,
l=l,
max_active_clusters=max_active_clusters,
stream=stream,
ab_dtype=ab_dtype,
sf_dtype=sf_dtype,
c_dtype=c_dtype,
sf_vec_size=sf_vec_size,
mma_tiler_mn=mma_tiler_mn,
cluster_shape_mn=cluster_shape_mn,
use_prefetch=use_prefetch,
prefetch_dist=prefetch_dist,
vectorized_f32=vectorized_f32,
enable_pdl=bool(enable_pdl),
)
compiled_gemm(
a_ptr,
b_ptr,
a_sf_ptr,
b_sf_ptr,
c_ptr,
c_sf_ptr,
alpha_ptr,
norm_const_ptr,
m,
n,
k,
l,
stream=stream,
)
return out, out_scale
__all__ = ["nvfp4_gemm_swiglu_nvfp4_quant"]