Files
sglang/python/sglang/srt/layers/amx_utils.py
T
2026-05-29 16:05:26 +08:00

225 lines
7.1 KiB
Python
Executable File

import logging
import torch
import transformers
from sglang.srt.utils import cpu_has_amx_support
logger = logging.getLogger(__name__)
from enum import IntEnum
class CPUQuantMethod(IntEnum):
UNQUANT = 0
INT8_W8A8 = 1
FP8_W8A16 = 2
INT4_W4A8 = 3
MXFP4 = 4
class CPUQuantAlgo(IntEnum):
AWQ = 0
GPTQ = 1
def fast_preprocess_cpu(
self,
images: list["torch.Tensor"],
do_resize: bool,
size,
interpolation,
do_rescale: bool,
rescale_factor: float,
do_normalize: bool,
image_mean,
image_std,
patch_size: int,
temporal_patch_size: int,
merge_size: int,
disable_grouping,
return_tensors,
**kwargs,
):
pixel_values, image_grid_thw = torch.ops.sgl_kernel.image_preprocess_cpu(
images,
True,
do_resize,
size["shortest_edge"],
size["longest_edge"],
"bicubic",
do_rescale,
rescale_factor,
do_normalize,
image_mean,
image_std,
patch_size,
temporal_patch_size,
merge_size,
True,
torch.bfloat16,
)
return transformers.image_processing_base.BatchFeature(
data={"pixel_values": pixel_values, "image_grid_thw": image_grid_thw},
tensor_type=return_tensors,
)
def amx_process_weight_after_loading(weight, is_conv=False):
if weight.device != torch.device("cpu"):
return weight
if not cpu_has_amx_support():
return weight
if is_conv:
if weight.dim() == 5:
return torch.ops.sgl_kernel.conv3d_embed_weight_pack(weight)
return torch.ops.sgl_kernel.causal_conv1d_weight_pack(
weight.view(-1, weight.size(-1))
)
return torch.ops.sgl_kernel.convert_weight_packed(weight)
# TODO: currently gemm kernel has the below requirements:
# OC: OC % TILE_N == 0 or OC < TILE_N, where TILE_N = 16
# IC: IC % TILE_K == 0, where TILE_K = 32
def dim_is_supported(weight):
TILE_N = 16
TILE_K = 32
ndim = weight.ndim
OC = weight.size(1) if ndim == 3 else weight.size(0)
IC = weight.size(2) if ndim == 3 else weight.size(1)
is_oc_support = OC < TILE_N or OC % TILE_N == 0
is_ic_support = IC % TILE_K == 0
return is_oc_support and is_ic_support
def dtype_is_supported(weight):
return weight.dtype in [
torch.float16,
torch.bfloat16,
torch.uint8,
torch.int8,
torch.float8_e4m3fn,
]
def is_dim_conv_weight(weight):
return (weight.dim() == 3 and weight.size(1) == 1) or weight.dim() == 5
def _init_amx_conv_state(conv_state):
# CPU AMX layout for conv_state kernel optimization
conv_state_cpu = []
for conv_shape_t in conv_state:
conv_shape_new = conv_shape_t.as_strided_(
conv_shape_t.size(),
(
conv_shape_t.stride(0),
conv_shape_t.stride(1),
1,
conv_shape_t.size(2),
),
)
conv_state_cpu.append(conv_shape_new)
return conv_state_cpu
def _amx_process_weight_after_loading(
module, weight_names, transpose_dims=None, qweight_packed_method=None
) -> None:
# Pack weight for get better performance on CPU
devices = {getattr(module, weight_name).device for weight_name in weight_names}
assert len(devices) == 1, f"Expects all weights to be on the same device"
device = devices.pop()
if transpose_dims:
assert len(weight_names) == len(
transpose_dims
), "len(weight_names) should be equal to len(transpose_dims)"
module.use_intel_amx_backend = (
device == torch.device("cpu") and cpu_has_amx_support()
)
if qweight_packed_method is None:
for i, weight_name in enumerate(weight_names):
weight_tensor = getattr(module, weight_name)
if transpose_dims and transpose_dims[i]:
weight_tensor = weight_tensor.transpose(*transpose_dims[i])
is_conv_weight = is_dim_conv_weight(weight_tensor)
# We don't pack weight or use intel amx backend if any weight of this module has unsupported dim.
if (
(not dim_is_supported(weight_tensor))
or not dtype_is_supported(weight_tensor)
) and (not is_conv_weight):
logger.warning(
f"Unsupported dimension or dtype for prepacking for weight '{weight_name}' with shape {weight_tensor.shape} and dtype {weight_tensor.dtype} in {module}. "
f"The derived (OC, IC) dimensions must be divisible by (16, 32). "
)
module.use_intel_amx_backend = False
return
packed_weight = torch.nn.Parameter(
amx_process_weight_after_loading(weight_tensor, is_conv_weight),
requires_grad=False,
)
packed_weight.__dict__ = weight_tensor.__dict__
setattr(module, weight_name, packed_weight)
if is_conv_weight and weight_tensor.dim() != 5:
# need to use inplace copy for conv weight amx packing,
# as its usage in radix_linear_attention will use the original conv weight.
weight_tensor = weight_tensor.view(-1, weight_tensor.size(-1))
weight_tensor.copy_(packed_weight)
else:
assert qweight_packed_method in ["awq", "gptq"]
qweight_tensor = getattr(module, weight_names[0])
qzeros_tensor = getattr(module, weight_names[1])
scales_tensor = getattr(module, weight_names[2])
qweight, qzeros, scales = torch.ops.sgl_kernel.convert_weight_packed_scale_zp(
qweight_tensor,
qzeros_tensor,
scales_tensor,
CPUQuantAlgo.AWQ if qweight_packed_method == "awq" else CPUQuantAlgo.GPTQ,
)
packed_qweight = torch.nn.Parameter(
qweight.detach(),
requires_grad=False,
)
packed_qzeros = torch.nn.Parameter(
qzeros.detach(),
requires_grad=False,
)
packed_scales = torch.nn.Parameter(
scales.detach(),
requires_grad=False,
)
packed_qweight.__dict__ = qweight_tensor.__dict__
packed_qzeros.__dict__ = qzeros_tensor.__dict__
packed_scales.__dict__ = scales_tensor.__dict__
setattr(module, weight_names[0], packed_qweight)
setattr(module, weight_names[1], packed_qzeros)
setattr(module, weight_names[2], packed_scales)
if (
module.use_intel_amx_backend
and hasattr(module, "bias")
and module.bias is not None
):
if is_conv_weight and module.weight.data.dim() == 5:
module.bias = torch.nn.Parameter(module.bias.data, requires_grad=False)
else:
module.bias = torch.nn.Parameter(
module.bias.data.float(), requires_grad=False
)
class PackWeightMethod:
def __init__(self, weight_names, transpose_dims=None):
self.weight_names = weight_names
self.transpose_dims = transpose_dims
def process_weights_after_loading(self, module) -> None:
_amx_process_weight_after_loading(
module, self.weight_names, self.transpose_dims
)