# 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