multimodalart's picture
multimodalart HF Staff
Initial ABot-World interactive rollout demo
28404e6 verified
Raw
History Blame Contribute Delete
21.1 kB
# Copyright 2025 Tencent Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import time
import torch
from lightx2v_kernel.gemm import (
cutlass_scaled_mxfp4_mm,
cutlass_scaled_mxfp6_mxfp8_mm,
cutlass_scaled_mxfp8_mm,
cutlass_scaled_nvfp4_mm,
)
try:
from torchao.quantization.utils import quant_int8_per_token_matmul as torchao_int8_gemm
from torchao.quantization.utils import quantize_activation_per_token_absmax as torchao_int8_quant
except ImportError:
try:
from torchao.quantization.utils import _quant_int8_per_token_matmul as torchao_int8_gemm
from torchao.quantization.utils import _quantize_activation_per_token_absmax as torchao_int8_quant
except ImportError:
torchao_int8_gemm, torchao_int8_quant = None, None
try:
from vllm import _custom_ops as vllm_ops
except ImportError:
vllm_ops = None
try:
from ...kernels.python.sgl.int8_kernel import per_token_quant_int8 as sglang_int8_act_quant
except ImportError:
sglang_int8_act_quant = None
try:
import sgl_kernel
except ImportError:
sgl_kernel = None
try:
from q8_kernels.functional.linear import q8_linear
except ImportError:
q8_linear = None
try:
from ...kernels.python.mm.triton_kernels import (
int8_gemm_bias_triton,
int8_gemm_triton,
int8_quantize_triton,
)
except ImportError:
int8_gemm_bias_triton, int8_gemm_triton, int8_quantize_triton = None, None, None
from ..quant_func import (
fp8_gemm,
fp8_per_block_quant,
fp8_per_tensor_quant,
fp8_per_token_group_quant,
fp8_per_token_quant_sgl,
fp8_weight_only_gemm,
mxfp4_per_tensor_quant,
mxfp6_per_tensor_quant,
mxfp8_per_tensor_quant,
nvfp4_per_tensor_quant,
)
# modified from https://github.com/neuralmagic/AutoFP8/blob/main/auto_fp8/quantize.py
class FP8DynamicLinear(torch.nn.Module):
def __init__(
self,
weight: torch.Tensor,
weight_scale: torch.Tensor,
bias: torch.nn.Parameter,
native_fp8_support: bool = False,
quant_type: str = "fp8-per-tensor",
block_size: int = 128,
):
super().__init__()
self.weight = torch.nn.Parameter(weight, requires_grad=False)
self.weight_scale = torch.nn.Parameter(weight_scale, requires_grad=False)
self.bias = bias
self.native_fp8_support = native_fp8_support
self.quant_type = quant_type
self.block_size = block_size
self.profile_enabled = os.environ.get("ANGELSLIM_FP8_PROFILE", "0") == "1"
@torch.compiler.disable(recursive=True)
def forward(self, x):
ori_dtype = x.dtype
assert ori_dtype in [
torch.float32,
torch.bfloat16,
torch.float16,
], "x.dtype must be float32, bfloat16, or float16"
if ori_dtype == torch.float32:
x = x.to(torch.bfloat16)
if self.profile_enabled and x.is_cuda:
torch.cuda.synchronize(x.device)
t0 = time.perf_counter()
if self.quant_type == "fp8-per-tensor":
origin_shape = None
qinput, x_scale = fp8_per_tensor_quant(x)
elif self.quant_type == "fp8-per-token":
origin_shape = None
x_2d = x.view(-1, x.shape[-1])
qinput, x_scale = fp8_per_token_group_quant(x_2d, x_2d.shape[-1])
elif self.quant_type == "fp8-per-token-sgl" and self.native_fp8_support:
origin_shape = x.shape
x_2d = x.view(-1, x.shape[-1])
qinput, x_scale = fp8_per_token_quant_sgl(x_2d)
elif self.quant_type == "fp8-per-block" and self.native_fp8_support:
origin_shape = x.shape
x = x.view(-1, x.shape[-1])
qinput, x_scale = fp8_per_token_group_quant(
x, group_size=128, column_major_scales=True, scale_tma_aligned=True
)
elif self.quant_type == "fp8-per-block" and not self.native_fp8_support:
origin_shape = x.shape
x_2d = x.view(-1, x.shape[-1])
qinput, x_scale = fp8_per_block_quant(x_2d, block_size=128)
elif self.quant_type == "fp8-per-channel-vllm":
if vllm_ops is None:
raise ImportError(
"quant_type='fp8-per-channel-vllm' requires vllm._custom_ops, but vllm is not installed"
)
origin_shape = x.shape if x.dim() == 3 else None
x_2d = x.view(-1, x.shape[-1]) if x.dim() == 3 else x
qinput, x_scale = vllm_ops.scaled_fp8_quant(
x_2d, None, scale_ub=None, use_per_token_if_dynamic=True
)
else:
raise ValueError(f"Invalid quant_type: {self.quant_type}")
if self.profile_enabled and qinput.is_cuda:
torch.cuda.synchronize(qinput.device)
t1 = time.perf_counter()
output = fp8_gemm(
A=qinput,
A_scale=x_scale,
B=self.weight,
B_scale=self.weight_scale,
bias=self.bias,
out_dtype=x.dtype,
native_fp8_support=self.native_fp8_support,
quant_type=self.quant_type,
origin_shape=origin_shape,
)
if self.profile_enabled and output.is_cuda:
torch.cuda.synchronize(output.device)
t2 = time.perf_counter()
if self.profile_enabled:
qshape = tuple(qinput.shape)
print(
f"[FP8Linear:{self.quant_type}] quant_ms={(t1 - t0) * 1000:.3f}, "
f"gemm_ms={(t2 - t1) * 1000:.3f}, qshape={qshape}"
)
if (
self.quant_type in ["fp8-per-token", "fp8-per-token-sgl"]
and x.dim() == 3
and output.dim() == 2
):
output = output.unsqueeze(0)
# Restore original shape for fp8-per-block with native_fp8_support=False
# (native_fp8_support=True case is handled in fp8_gemm_deepgemm_block)
if (
(
(self.quant_type == "fp8-per-block" and not self.native_fp8_support)
or self.quant_type == "fp8-per-channel-vllm"
)
and origin_shape is not None
and len(origin_shape) == 3
and output.dim() == 2
):
output = output.view(origin_shape[0], origin_shape[1], -1)
return output
class FP8WeightOnlyLinear(torch.nn.Module):
"""
FP8 Weight-Only Quantized Linear Layer.
This layer quantizes only the weights to FP8 while keeping activations
in higher precision (bfloat16/float16). This provides a good balance
between memory savings and accuracy.
"""
def __init__(
self,
weight: torch.Tensor,
weight_scale: torch.Tensor,
bias: torch.nn.Parameter,
native_fp8_support: bool = False, # not used
quant_type: str = "fp8-per-tensor-weight-only",
):
super().__init__()
self.weight = torch.nn.Parameter(weight, requires_grad=False)
self.weight_scale = torch.nn.Parameter(weight_scale, requires_grad=False)
self.bias = bias
self.native_fp8_support = native_fp8_support # not used
self.quant_type = quant_type
@torch.compiler.disable(recursive=True)
def forward(self, x):
ori_dtype = x.dtype
assert ori_dtype in [
torch.float32,
torch.bfloat16,
torch.float16,
], "x.dtype must be float32, bfloat16, or float16"
if ori_dtype == torch.float32:
x = x.to(torch.bfloat16)
# For weight-only quantization, we don't quantize activations
# Just use the original activations with quantized weights
output = fp8_weight_only_gemm(
A=x, # Keep activations in original precision
B=self.weight,
B_scale=self.weight_scale,
bias=self.bias,
out_dtype=x.dtype,
)
return output
class INT8DynamicLinear(torch.nn.Module):
"""
INT8 weight-only linear layer with per-channel scales.
"""
def __init__(
self,
weight: torch.Tensor,
weight_scale: torch.Tensor,
bias: torch.nn.Parameter,
native_fp8_support: bool = False, # not used
quant_type: str = "int8",
):
super().__init__()
self.weight = torch.nn.Parameter(weight, requires_grad=False)
self.weight_scale = torch.nn.Parameter(weight_scale, requires_grad=False)
self.bias = bias
self.native_fp8_support = native_fp8_support # not used
self.quant_type = quant_type
@staticmethod
def _is_backend_available(backend: str) -> bool:
if backend == "torchao":
return torchao_int8_quant is not None and torchao_int8_gemm is not None
if backend == "vllm":
return vllm_ops is not None and hasattr(torch.ops, "_C")
if backend == "triton":
return (
int8_quantize_triton is not None
and int8_gemm_triton is not None
and int8_gemm_bias_triton is not None
)
if backend == "sgl":
has_act = (
sglang_int8_act_quant is not None
or vllm_ops is not None
or torchao_int8_quant is not None
or int8_quantize_triton is not None
)
return sgl_kernel is not None and has_act
if backend == "q8f":
has_act = (
vllm_ops is not None
or torchao_int8_quant is not None
or int8_quantize_triton is not None
)
return q8_linear is not None and has_act
return False
def _resolve_int8_backend(self) -> str:
explicit_backend = {
"int8-torchao": "torchao",
"int8-vllm": "vllm",
"int8-triton": "triton",
"int8-sgl": "sgl",
"int8-q8f": "q8f",
}
if self.quant_type in explicit_backend:
backend = explicit_backend[self.quant_type]
if not self._is_backend_available(backend):
raise ImportError(
f"quant_type='{self.quant_type}' requires '{backend}' backend dependencies"
)
return backend
# quant_type='int8' uses auto priority: sgl > vllm > torchao > triton
for backend in ("sgl", "vllm", "torchao", "triton"):
if self._is_backend_available(backend):
return backend
raise ImportError(
"quant_type='int8' requires one of backends [sgl, vllm, torchao, triton], but none is available"
)
def _act_quant_int8_torchao(self, x_2d: torch.Tensor):
input_tensor_quant, input_tensor_scale = torchao_int8_quant(x_2d)
return input_tensor_quant, input_tensor_scale.float()
def _act_quant_int8_vllm(self, x_2d: torch.Tensor):
input_tensor_quant, input_tensor_scale, _ = vllm_ops.scaled_int8_quant(
x_2d, scale=None, azp=None, symmetric=True
)
return input_tensor_quant, input_tensor_scale.float()
def _act_quant_int8_triton(self, x_2d: torch.Tensor):
input_tensor_quant, input_tensor_scale = int8_quantize_triton(x_2d)
return input_tensor_quant, input_tensor_scale.float()
def _act_quant_int8_sgl(self, x_2d: torch.Tensor):
if sglang_int8_act_quant is not None:
input_tensor_quant, input_tensor_scale = sglang_int8_act_quant(x_2d)
return input_tensor_quant, input_tensor_scale.float()
if vllm_ops is not None:
return self._act_quant_int8_vllm(x_2d)
if torchao_int8_quant is not None:
return self._act_quant_int8_torchao(x_2d)
if int8_quantize_triton is not None:
return self._act_quant_int8_triton(x_2d)
raise ImportError("int8-sgl activation quantization requires sglang/vllm/torchao/triton")
def _act_quant_by_backend(self, x_2d: torch.Tensor, backend: str):
if backend == "torchao":
return self._act_quant_int8_torchao(x_2d)
if backend == "vllm":
return self._act_quant_int8_vllm(x_2d)
if backend == "triton":
return self._act_quant_int8_triton(x_2d)
if backend == "sgl":
return self._act_quant_int8_sgl(x_2d)
if backend == "q8f":
if vllm_ops is not None:
return self._act_quant_int8_vllm(x_2d)
if torchao_int8_quant is not None:
return self._act_quant_int8_torchao(x_2d)
return self._act_quant_int8_triton(x_2d)
raise ValueError(f"Unsupported int8 backend: {backend}")
def _gemm_int8_torchao(self, qinput, x_scale, out_dtype):
output = torchao_int8_gemm(
qinput,
x_scale,
self.weight.t(),
self.weight_scale.t().float(),
output_dtype=out_dtype,
)
if self.bias is not None:
output.add_(self.bias.to(output.dtype))
return output
def _gemm_int8_vllm(self, qinput, x_scale, out_dtype):
shape = (qinput.shape[0], self.weight.shape[0])
output = torch.empty(shape, dtype=out_dtype, device=qinput.device, requires_grad=False)
torch.ops._C.cutlass_scaled_mm(
output,
qinput,
self.weight.t(),
x_scale,
self.weight_scale.t(),
self.bias,
)
return output
def _gemm_int8_triton(self, qinput, x_scale, out_dtype):
if self.bias is not None:
return int8_gemm_bias_triton(
qinput,
self.weight,
self.bias,
x_scale,
self.weight_scale,
output_dtype=out_dtype,
)
return int8_gemm_triton(
qinput,
self.weight,
x_scale,
self.weight_scale,
output_dtype=out_dtype,
)
def _gemm_int8_sgl(self, qinput, x_scale, out_dtype):
return sgl_kernel.int8_scaled_mm(
qinput,
self.weight.t(),
x_scale,
self.weight_scale.t(),
out_dtype,
self.bias,
)
def _gemm_int8_q8f(self, qinput, x_scale, out_dtype):
bias_fp32 = self.bias.float() if self.bias is not None else None
return q8_linear(
qinput,
self.weight,
bias_fp32,
x_scale.float(),
self.weight_scale,
fuse_gelu=False,
out_dtype=out_dtype,
)
def _gemm_by_backend(self, qinput, x_scale, out_dtype, backend: str):
if backend == "torchao":
return self._gemm_int8_torchao(qinput, x_scale, out_dtype)
if backend == "vllm":
return self._gemm_int8_vllm(qinput, x_scale, out_dtype)
if backend == "triton":
return self._gemm_int8_triton(qinput, x_scale, out_dtype)
if backend == "sgl":
return self._gemm_int8_sgl(qinput, x_scale, out_dtype)
if backend == "q8f":
return self._gemm_int8_q8f(qinput, x_scale, out_dtype)
raise ValueError(f"Unsupported int8 backend: {backend}")
@torch.compiler.disable(recursive=True)
def forward(self, x):
ori_dtype = x.dtype
assert ori_dtype in [
torch.float32,
torch.bfloat16,
torch.float16,
], "x.dtype must be float32, bfloat16, or float16"
if ori_dtype == torch.float32:
x = x.to(torch.bfloat16)
need_reshape = x.dim() == 3
if need_reshape:
origin_shape = x.shape
x_2d = x.view(-1, x.shape[-1])
else:
origin_shape = None
x_2d = x
backend = self._resolve_int8_backend()
qinput, x_scale = self._act_quant_by_backend(x_2d, backend)
output = self._gemm_by_backend(qinput, x_scale, x.dtype, backend)
if need_reshape and output.dim() == 2:
output = output.view(origin_shape[0], origin_shape[1], -1)
return output.to(ori_dtype)
class FP4DynamicLinear(torch.nn.Module):
def __init__(
self,
weight: torch.Tensor,
weight_scale: torch.Tensor,
bias: torch.nn.Parameter,
weight_global_scale: torch.Tensor = None,
native_fp8_support: bool = False,
quant_type: str = "nvfp4",
block_size: int = 16,
):
super().__init__()
self.weight = torch.nn.Parameter(weight, requires_grad=False)
self.weight_scale = torch.nn.Parameter(weight_scale, requires_grad=False)
self.bias = bias
self.native_fp8_support = native_fp8_support
self.quant_type = quant_type
self.block_size = block_size
self.profile_enabled = os.environ.get("ANGELSLIM_NVFP4_PROFILE", "0") == "1"
if weight_global_scale is None:
weight_global_scale = torch.tensor(1.0, dtype=torch.float32, device=weight.device)
self.weight_global_scale = torch.nn.Parameter(
weight_global_scale.to(dtype=torch.float32), requires_grad=False
)
self.calibrate_x_absmax()
def calibrate_x_absmax(self):
if self.quant_type in ("mxfp4", "mxfp6", "mxfp8"):
self.x_absmax = torch.tensor(1.0, dtype=torch.float32, device=self.weight.device)
self.input_global_scale = torch.tensor(
1.0, dtype=torch.float32, device=self.weight.device
)
else:
self.x_absmax = torch.tensor(5.0, dtype=torch.float32, device=self.weight.device)
self.input_global_scale = (2688.0 / self.x_absmax).to(torch.float32)
self.alpha = 1.0 / (self.input_global_scale * self.weight_global_scale)
@torch.compiler.disable(recursive=True)
def forward(self, x):
ori_dtype = x.dtype
assert ori_dtype in [
torch.float32,
torch.bfloat16,
torch.float16,
], "x.dtype must be float32, bfloat16, or float16"
if ori_dtype == torch.float32:
x = x.to(torch.bfloat16)
need_reshape = x.dim() == 3
if need_reshape:
origin_shape = x.shape
x_2d = x.view(-1, x.shape[-1])
else:
x_2d = x
if self.profile_enabled and x_2d.is_cuda:
torch.cuda.synchronize(x_2d.device)
t0 = time.perf_counter()
if self.quant_type == "nvfp4":
qinput, x_scale, _ = nvfp4_per_tensor_quant(x_2d, self.input_global_scale)
output = cutlass_scaled_nvfp4_mm(
qinput,
self.weight,
x_scale,
self.weight_scale,
self.alpha,
bias=self.bias,
)
elif self.quant_type == "mxfp4":
qinput, x_scale, _ = mxfp4_per_tensor_quant(x_2d)
output = cutlass_scaled_mxfp4_mm(
qinput,
self.weight,
x_scale,
self.weight_scale,
self.alpha,
bias=self.bias,
)
elif self.quant_type == "mxfp8":
qinput, x_scale, _ = mxfp8_per_tensor_quant(x_2d)
output = cutlass_scaled_mxfp8_mm(
qinput,
self.weight,
x_scale,
self.weight_scale,
self.alpha,
bias=self.bias,
)
elif self.quant_type == "mxfp6":
qinput, x_scale, _ = mxfp8_per_tensor_quant(x_2d)
output = cutlass_scaled_mxfp6_mxfp8_mm(
qinput,
self.weight,
x_scale,
self.weight_scale,
self.alpha,
bias=self.bias,
)
else:
raise ValueError(f"Invalid quant_type for FP4DynamicLinear: {self.quant_type}")
if self.profile_enabled and x_2d.is_cuda:
torch.cuda.synchronize(x_2d.device)
t1 = time.perf_counter()
if self.profile_enabled and x_2d.is_cuda:
torch.cuda.synchronize(x_2d.device)
t2 = time.perf_counter()
if self.profile_enabled:
print(
f"[NVFP4Linear] quant_ms={(t1 - t0) * 1000:.3f}, "
f"gemm_ms={(t2 - t1) * 1000:.3f}, "
f"shape=({x_2d.shape[0]}, {x_2d.shape[1]})"
)
if need_reshape:
output = output.view(origin_shape[0], origin_shape[1], -1)
return output.to(ori_dtype)