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

548 lines
16 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 typing import Tuple
import torch
from tokenspeed_kernel._triton import tl, triton
from tokenspeed_kernel.platform import Platform
from tokenspeed_kernel.registry import error_fn
_is_amd = Platform.get().is_amd
_is_nvidia = Platform.get().is_nvidia
platform = Platform.get()
fp8_dtype = platform.fp8e4m3fn.dtype
fp8_max = platform.fp8e4m3fn.max
fp8_min = platform.fp8e4m3fn.min
if _is_nvidia:
from tokenspeed_kernel.ops.quantization.flashinfer import (
fp8_blockscale_quantize_runner_sm90 as _flashinfer_fp8_blockscale_quantize_runner_sm90,
)
from tokenspeed_kernel.thirdparty.trtllm import (
per_token_group_quant_8bit as _trtllm_per_token_group_quant_fp8,
)
from tokenspeed_kernel.thirdparty.trtllm import (
per_token_quant_fp8 as _trtllm_per_token_quant_fp8,
)
def align(x: int, y: int) -> int:
return ceil_div(x, y) * y
def ceil_div(x: int, y: int) -> int:
return (x + y - 1) // y
@triton.jit
def _per_token_group_quant_8bit(
# Pointers to inputs and output
y_ptr,
y_q_ptr,
y_s_ptr,
# Stride of input
y_stride,
# Columns of input
N,
# Avoid to divide zero
eps,
# Information for float8
bit8_min,
bit8_max,
# Meta-parameters
BLOCK: tl.constexpr,
):
"""A Triton-accelerated function to perform per-token-group quantization on a
tensor.
This function converts the tensor values into float8 values.
"""
# Map the program id to the row of X and Y it should compute.
g_id = tl.program_id(0)
y_ptr += g_id * y_stride
y_q_ptr += g_id * y_stride
y_s_ptr += g_id
cols = tl.arange(0, BLOCK) # N <= BLOCK
mask = cols < N
y = tl.load(y_ptr + cols, mask=mask, other=0.0).to(tl.float32)
# Quant
_absmax = tl.maximum(tl.max(tl.abs(y)), eps)
y_s = _absmax / bit8_max
y_s_inv = 1.0 / y_s
y_q = tl.clamp(y * y_s_inv, bit8_min, bit8_max).to(y_q_ptr.dtype.element_ty)
tl.store(y_q_ptr + cols, y_q, mask=mask)
tl.store(y_s_ptr, y_s)
@triton.jit
def _per_token_group_quant_8bit_colmajor(
# Pointers to inputs and output
y_ptr,
y_q_ptr,
y_s_ptr,
group_size,
# Num columns of y
y_num_columns,
# Stride from one column to the next of y_s
y_s_col_stride,
# Avoid to divide zero
eps,
# Information for float8
bit8_min,
bit8_max,
# Meta-parameters
BLOCK: tl.constexpr,
SCALE_UE8M0: tl.constexpr,
):
"""A Triton-accelerated function to perform per-token-group
quantization on a tensor.
This function converts the tensor values into float8 values.
"""
# Map the program id to the row of X and Y it should compute.
g_id = tl.program_id(0)
y_ptr += g_id.to(tl.int64) * group_size
y_q_ptr += g_id.to(tl.int64) * group_size
# Convert g_id the flattened block coordinate to 2D so we can index
# into the output y_scales matrix
blocks_per_row = y_num_columns // group_size
scale_col = g_id % blocks_per_row
scale_row = g_id // blocks_per_row
y_s_ptr += scale_col * y_s_col_stride + scale_row
cols = tl.arange(0, BLOCK) # group_size <= BLOCK
mask = cols < group_size
y = tl.load(y_ptr + cols, mask=mask, other=0.0).to(tl.float32)
# Quant
_absmax = tl.maximum(tl.max(tl.abs(y)), eps)
y_s = _absmax / bit8_max
if SCALE_UE8M0:
y_s = tl.exp2(tl.ceil(tl.log2(tl.abs(y_s))))
y_q = tl.clamp(y / y_s, bit8_min, bit8_max).to(y_q_ptr.dtype.element_ty)
tl.store(y_q_ptr + cols, y_q, mask=mask)
tl.store(y_s_ptr, y_s)
@triton.jit
def _per_token_group_quant_8bit_packed_ue8m0(
# Pointers to inputs and output
y_ptr,
y_q_ptr,
y_s_ptr,
group_size,
# Num columns of y
y_num_columns,
# Stride from one packed scale column to the next of y_s
y_s_col_stride,
# Avoid to divide zero
eps,
# Information for float8
bit8_min,
bit8_max,
# Meta-parameters
BLOCK: tl.constexpr,
):
"""Quantize per token group and pack UE8M0 scales for DeepGEMM."""
g_id = tl.program_id(0)
groups_per_row = y_num_columns // group_size
row = g_id // groups_per_row
group_col = g_id % groups_per_row
y_offset = row.to(tl.int64) * y_num_columns + group_col.to(tl.int64) * group_size
y_ptr += y_offset
y_q_ptr += y_offset
scale_pack_col = group_col // 4
scale_pack_pos = group_col % 4
y_s_ptr += scale_pack_col.to(tl.int64) * y_s_col_stride + row.to(tl.int64)
cols = tl.arange(0, BLOCK)
mask = cols < group_size
y = tl.load(y_ptr + cols, mask=mask, other=0.0).to(tl.float32)
_absmax = tl.max(tl.abs(y))
scale_raw = tl.maximum(_absmax / bit8_max, eps)
exponent = tl.ceil(tl.log2(scale_raw))
y_s = tl.exp2(exponent)
y_q = tl.clamp(y / y_s, bit8_min, bit8_max).to(y_q_ptr.dtype.element_ty)
exponent_biased = tl.clamp(exponent + 127.0, 0.0, 255.0).to(tl.uint32)
packed_scale = exponent_biased << (scale_pack_pos * 8)
tl.store(y_q_ptr + cols, y_q, mask=mask)
tl.atomic_or(y_s_ptr, packed_scale, sem="relaxed")
def create_per_token_group_quant_fp8_output_scale(
x_shape,
device,
group_size,
column_major_scales: bool,
scale_tma_aligned: bool,
scale_ue8m0: bool,
):
if scale_ue8m0:
assert column_major_scales and scale_tma_aligned
assert len(x_shape) == 2, "UE8M0 packed scales currently require 2D input"
assert group_size == 128, "UE8M0 packed scales currently require group_size=128"
*x_batch, x_q_mn, x_q_k = x_shape
x_s_mn, x_s_k = x_q_mn, x_q_k // group_size
aligned_mn = align(x_s_mn, 4)
packed_k = ceil_div(x_s_k, 4)
scale_base = torch.empty(
(*x_batch, packed_k, aligned_mn),
device=device,
dtype=torch.int,
)
scale_base.zero_()
return scale_base.transpose(-1, -2)[..., :x_s_mn, :]
elif column_major_scales:
if scale_tma_aligned:
# aligned to 4 * sizeof(float)
aligned_size = align(x_shape[-2], 4)
return torch.empty(
x_shape[:-2] + (x_shape[-1] // group_size, aligned_size),
device=device,
dtype=torch.float32,
).permute(-1, -2)[: x_shape[-2], :]
else:
return torch.empty(
(x_shape[-1] // group_size,) + x_shape[:-1],
device=device,
dtype=torch.float32,
).permute(-1, -2)
else:
return torch.empty(
x_shape[:-1] + (x_shape[-1] // group_size,),
device=device,
dtype=torch.float32,
)
def _per_token_group_quant_8bit_raw(
x: torch.Tensor,
group_size: int,
eps: float = 1e-10,
dtype: torch.dtype = platform.fp8e4m3fn.dtype,
column_major_scales: bool = False,
scale_tma_aligned: bool = False,
scale_ue8m0: bool = False,
) -> Tuple[torch.Tensor, torch.Tensor]:
"""Function to perform per-token-group quantization on an input tensor `x`.
It converts the tensor values into signed float8 values and returns the
quantized tensor along with the scaling factor used for quantization.
Args:
x: The input tenosr with ndim >= 2.
group_size: The group size used for quantization.
eps: The minimum to avoid dividing zero.
dtype: The dype of output tensor.
Returns:
Tuple[torch.Tensor, torch.Tensor]: The quantized tensor and the scaling factor for quantization.
"""
assert (
x.shape[-1] % group_size == 0
), "the last dimension of `x` cannot be divisible by `group_size`"
assert x.is_contiguous(), "`x` is not contiguous"
if _is_amd:
if dtype == torch.int8:
bit8_max = 127.0
bit8_min = -128.0
else:
bit8_max = platform.fp8e4m3fn.max
bit8_min = -bit8_max
else:
if dtype == torch.int8:
info = torch.iinfo(dtype)
else:
info = torch.finfo(dtype)
bit8_max = info.max
bit8_min = info.min
x_q = torch.empty_like(x, device=x.device, dtype=dtype)
x_s = create_per_token_group_quant_fp8_output_scale(
x_shape=x.shape,
device=x.device,
group_size=group_size,
column_major_scales=column_major_scales,
scale_tma_aligned=scale_tma_aligned,
scale_ue8m0=scale_ue8m0,
)
M = x.numel() // group_size
N = group_size
BLOCK = triton.next_power_of_2(N)
# heuristics for number of warps
num_warps = min(max(BLOCK // 256, 1), 8)
num_stages = 1
if scale_ue8m0:
assert column_major_scales and scale_tma_aligned
assert group_size == 128
_per_token_group_quant_8bit_packed_ue8m0[(M,)](
x,
x_q,
x_s,
group_size,
x.shape[1],
x_s.stride(-1),
eps,
bit8_min=bit8_min,
bit8_max=bit8_max,
BLOCK=BLOCK,
num_warps=num_warps,
num_stages=num_stages,
)
elif column_major_scales:
_per_token_group_quant_8bit_colmajor[(M,)](
x,
x_q,
x_s,
group_size,
x.shape[1],
x_s.stride(1),
eps,
bit8_min=bit8_min,
bit8_max=bit8_max,
BLOCK=BLOCK,
num_warps=num_warps,
num_stages=num_stages,
SCALE_UE8M0=scale_ue8m0,
)
else:
assert not scale_ue8m0
_per_token_group_quant_8bit[(M,)](
x,
x_q,
x_s,
group_size,
N,
eps,
bit8_min=bit8_min,
bit8_max=bit8_max,
BLOCK=BLOCK,
num_warps=num_warps,
num_stages=num_stages,
)
return x_q, x_s
def _flashinfer_sm90_per_token_group_quant_fp8(
x: torch.Tensor,
group_size: int,
column_major_scales: bool,
scale_tma_aligned: bool,
scale_ue8m0: bool,
) -> Tuple[torch.Tensor, torch.Tensor] | None:
if not (
_is_nvidia
and platform.is_hopper
and group_size == 128
and x.ndim == 2
and x.dtype == torch.bfloat16
and x.is_contiguous()
and column_major_scales
and scale_tma_aligned
and not scale_ue8m0
):
return None
x_q = torch.empty_like(x, device=x.device, dtype=fp8_dtype)
x_s = create_per_token_group_quant_fp8_output_scale(
x_shape=x.shape,
device=x.device,
group_size=group_size,
column_major_scales=column_major_scales,
scale_tma_aligned=scale_tma_aligned,
scale_ue8m0=False,
)
if _flashinfer_fp8_blockscale_quantize_runner_sm90 is error_fn:
return None
try:
runner = _flashinfer_fp8_blockscale_quantize_runner_sm90()
runner.fp8_quantize_1x128(x, x_q, x_s, False)
except RuntimeError:
return None
return x_q, x_s
def per_token_group_quant_fp8(
x: torch.Tensor,
group_size: int,
column_major_scales: bool = False,
scale_tma_aligned: bool = False,
scale_ue8m0: bool = False,
):
flashinfer_quantized = _flashinfer_sm90_per_token_group_quant_fp8(
x,
group_size,
column_major_scales=column_major_scales,
scale_tma_aligned=scale_tma_aligned,
scale_ue8m0=scale_ue8m0,
)
if flashinfer_quantized is not None:
return flashinfer_quantized
if (
_is_nvidia
and not column_major_scales
and not scale_tma_aligned
and not scale_ue8m0
):
return _trtllm_per_token_group_quant_fp8(x, group_size)
return _per_token_group_quant_8bit_raw(
x,
group_size,
dtype=fp8_dtype,
column_major_scales=column_major_scales,
scale_tma_aligned=scale_tma_aligned,
scale_ue8m0=scale_ue8m0,
)
def per_token_quant_fp8(
x: torch.Tensor,
dtype: torch.dtype = fp8_dtype,
):
assert x.is_contiguous(), "`x` is not contiguous"
x_q = torch.empty_like(x, device=x.device, dtype=dtype)
x_s = torch.empty(
x.shape[0],
1,
device=x.device,
dtype=torch.float32,
)
_trtllm_per_token_quant_fp8(x, x_q, x_s)
return x_q, x_s
@triton.jit
def _static_quant_fp8(
# Pointers to inputs and output
y_ptr,
y_q_ptr,
y_s_ptr,
y_s_repeat_ptr,
# Stride of input
y_stride,
# Columns of input
N,
# Information for float8
fp8_min,
fp8_max,
# Meta-parameters
BLOCK: tl.constexpr,
REPEAT_SCALE: tl.constexpr,
):
"""A Triton-accelerated function to perform quantization using the given scale on a
tensor
This function converts the tensor values into float8 values.
"""
# Map the program id to the row of X and Y it should compute.
g_id = tl.program_id(0)
y_ptr += g_id * y_stride
y_q_ptr += g_id * y_stride
if REPEAT_SCALE:
y_s_repeat_ptr += g_id
cols = tl.arange(0, BLOCK) # N <= BLOCK
mask = cols < N
y = tl.load(y_ptr + cols, mask=mask, other=0.0).to(tl.float32)
y_s = tl.load(y_s_ptr).to(tl.float32)
y_s_inv = 1.0 / y_s
y_q = tl.clamp(y * y_s_inv, fp8_min, fp8_max).to(y_q_ptr.dtype.element_ty)
tl.store(y_q_ptr + cols, y_q, mask=mask)
if REPEAT_SCALE:
tl.store(y_s_repeat_ptr, y_s)
def static_quant_fp8(
x: torch.Tensor,
x_s: torch.Tensor,
repeat_scale: bool = False,
) -> Tuple[torch.Tensor, torch.Tensor]:
"""Function to perform static quantization using the given scale on an input tensor `x`.
It converts the tensor values into signed float8 values and returns the
quantized tensor along with the scaling factor used for quantization.
Args:
x: The input tenosr with ndim >= 2.
x_s: The quantization scale.
repeat_scale: Whether to broadcast per-tensor scale to per-channel scale.
dtype: The dype of output tensor.
Returns:
Tuple[torch.Tensor, torch.Tensor]: The quantized tensor and the scaling factor for quantization.
"""
assert x.is_contiguous(), "`x` is not contiguous"
assert x_s.numel() == 1, "only supports per-tensor scale"
x_q = torch.empty_like(x, device=x.device, dtype=fp8_dtype)
M = x.numel() // x.shape[-1]
N = x.shape[-1]
if repeat_scale:
x_s_repeat = torch.empty(
(M, 1),
device=x.device,
dtype=torch.float32,
)
else:
x_s_repeat = None
BLOCK = triton.next_power_of_2(N)
# heuristics for number of warps
num_warps = min(max(BLOCK // 256, 1), 8)
num_stages = 1
_static_quant_fp8[(M,)](
x,
x_q,
x_s,
x_s_repeat,
N,
N,
fp8_min=fp8_min,
fp8_max=fp8_max,
BLOCK=BLOCK,
REPEAT_SCALE=repeat_scale,
num_warps=num_warps,
num_stages=num_stages,
)
x_s = x_s_repeat if repeat_scale else x_s
return x_q, x_s