Files
vllm-project--vllm/vllm/model_executor/layers/quantization/utils/humming_utils.py
T
wehub-resource-sync 7ce4c8e27e
pre-commit / pre-run-check (push) Has been cancelled
pre-commit / pre-commit (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:55:37 +08:00

1016 lines
33 KiB
Python

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
import json
from typing import TYPE_CHECKING, Any
import regex as re
import torch
import vllm.model_executor.layers.fused_moe.modular_kernel as mk
from vllm import envs
from vllm.logger import init_logger
from vllm.model_executor.layers.fused_moe.all2all_utils import (
maybe_make_prepare_finalize,
)
from vllm.model_executor.layers.fused_moe.config import (
FusedMoEConfig,
FusedMoEQuantConfig,
FusedMoEQuantDesc,
)
from vllm.model_executor.layers.fused_moe.experts.fused_humming_moe import (
BatchedHummingGroupedExperts,
HummingGroupedExperts,
HummingIndexedExperts,
)
from vllm.model_executor.layers.linear import LinearBase
from vllm.model_executor.layers.quantization.utils.quant_utils import (
FP4_DTYPE,
FP8_DTYPE,
INT4_DTYPE,
INT8_DTYPE,
MXFP_SCALE_DTYPE,
GroupShape,
QuantKey,
ScaleDesc,
)
from vllm.utils.import_utils import has_humming
if TYPE_CHECKING:
from vllm.model_executor.layers.fused_moe.routed_experts import RoutedExperts
from vllm.utils.humming import (
AWQWeightSchema,
BaseInputSchema,
BaseWeightSchema,
CompressedTensorsInputSchema,
CompressedTensorsWeightSchema,
Fp8WeightSchema,
GPTQWeightSchema,
HummingInputSchema,
HummingWeightSchema,
)
from vllm.utils.humming import dtypes as humming_dtypes
logger = init_logger(__name__)
if has_humming():
from vllm.utils.humming import dtypes as humming_dtypes
_HUMMING_TO_QUANT_DTYPE: dict[humming_dtypes.DataType, Any] = {
humming_dtypes.float4e2m1: FP4_DTYPE,
humming_dtypes.float8e4m3: FP8_DTYPE,
humming_dtypes.float8e5m2: torch.float8_e5m2,
humming_dtypes.int8: torch.int8,
humming_dtypes.uint4: INT4_DTYPE,
humming_dtypes.uint8: INT8_DTYPE,
humming_dtypes.uint2: torch.uint8,
humming_dtypes.uint3: torch.uint8,
}
_HUMMING_TO_SCALE_DTYPE: dict[humming_dtypes.DataType, torch.dtype] = {
humming_dtypes.float8e8m0: MXFP_SCALE_DTYPE,
humming_dtypes.float8e4m3: FP8_DTYPE,
humming_dtypes.float16: torch.float16,
humming_dtypes.bfloat16: torch.bfloat16,
humming_dtypes.float32: torch.float32,
}
def _group_shape(group_size: int, group_size_n: int = 0) -> GroupShape:
"""
Map humming group sizes to QuantKey GroupShape.
group_size: elements per group along K (col); 0 means full dimension.
group_size_n: elements per group along N (row); 0 means 1 (per-row).
GroupShape convention: row = N dim, col = K dim.
"""
if group_size == 0 and group_size_n == 0:
return GroupShape.PER_CHANNEL
row = group_size_n if group_size_n > 0 else 1
col = group_size if group_size > 0 else -1
return GroupShape(row=row, col=col)
# ---- HummingWeightSchema (post-conversion) --------------------------------
def _humming_weight_schema_to_quant_key(
schema: "HummingWeightSchema",
) -> QuantKey:
from vllm.utils.humming import WeightScaleType
"""Convert a HummingWeightSchema to a QuantKey."""
dtype = _HUMMING_TO_QUANT_DTYPE[schema.b_dtype]
if schema.bs_dtype is not None:
scale_dtype = _HUMMING_TO_SCALE_DTYPE[schema.bs_dtype]
else:
scale_dtype = torch.float32
group_shape = _group_shape(
schema.weight_scale_group_size,
schema.weight_scale_group_size_n,
)
scale = ScaleDesc(dtype=scale_dtype, static=True, group_shape=group_shape)
scale2 = None
if schema.weight_scale_type == WeightScaleType.GROUP_TENSOR:
scale2 = ScaleDesc(
dtype=torch.float32,
static=True,
group_shape=GroupShape.PER_TENSOR,
)
return QuantKey(
dtype=dtype,
scale=scale,
scale2=scale2,
symmetric=not schema.has_zero_point,
)
# ---- Checkpoint-format weight schemas (pre-conversion) --------------------
def _fp8_weight_schema_to_quant_key(schema: "Fp8WeightSchema") -> QuantKey:
if schema.weight_block_size is not None:
gs_n, gs_k = schema.weight_block_size
group_shape = GroupShape(row=gs_n, col=gs_k)
else:
group_shape = GroupShape.PER_CHANNEL
scale = ScaleDesc(dtype=torch.float32, static=True, group_shape=group_shape)
return QuantKey(dtype=FP8_DTYPE, scale=scale, symmetric=True)
def _awq_weight_schema_to_quant_key(schema: "AWQWeightSchema") -> QuantKey:
group_shape = _group_shape(schema.group_size)
scale = ScaleDesc(
dtype=torch.float16,
static=True,
group_shape=group_shape,
)
return QuantKey(
dtype=INT4_DTYPE,
scale=scale,
symmetric=not schema.zero_point,
)
def _gptq_weight_schema_to_quant_key(schema: "GPTQWeightSchema") -> QuantKey:
group_shape = _group_shape(schema.group_size)
scale = ScaleDesc(
dtype=torch.float16,
static=True,
group_shape=group_shape,
)
return QuantKey(dtype=INT4_DTYPE, scale=scale, symmetric=schema.sym)
def _compressed_tensors_weight_schema_to_quant_key(
schema: "CompressedTensorsWeightSchema",
) -> QuantKey:
# Determine dtype from format/type/num_bits
fmt = schema.format
if fmt in ("int-quantized", "float-quantized", "naive-quantized"):
dtype = INT8_DTYPE if schema.type == "int" else FP8_DTYPE
elif "nvfp4" in fmt or "mxfp4" in fmt:
dtype = FP4_DTYPE
else:
dtype = _HUMMING_TO_QUANT_DTYPE[
humming_dtypes.DataType.from_str(f"uint{schema.num_bits}")
]
# Determine group shape from strategy
if schema.strategy in ("group", "tensor_group"):
group_shape = _group_shape(schema.group_size or 0)
elif schema.strategy == "block" and schema.block_structure is not None:
group_shape = GroupShape(
row=schema.block_structure[0],
col=schema.block_structure[1],
)
else:
group_shape = GroupShape.PER_CHANNEL
# Determine scale dtype
if "mxfp" in fmt:
scale_dtype = MXFP_SCALE_DTYPE
elif "nvfp4" in fmt:
scale_dtype = FP8_DTYPE
else:
scale_dtype = torch.float32
scale = ScaleDesc(dtype=scale_dtype, static=True, group_shape=group_shape)
scale2 = None
if "nvfp4" in fmt or schema.strategy == "tensor_group":
scale2 = ScaleDesc(
dtype=torch.float32,
static=True,
group_shape=GroupShape.PER_TENSOR,
)
return QuantKey(
dtype=dtype,
scale=scale,
scale2=scale2,
symmetric=schema.symmetric,
)
# ---- Dispatch for any BaseWeightSchema ------------------------------------
def weight_schema_to_quant_key(
schema: "BaseWeightSchema",
) -> QuantKey:
from vllm.utils.humming import (
AWQWeightSchema,
BitnetWeightSchema,
CompressedTensorsWeightSchema,
Fp8WeightSchema,
GptOssMxfp4WeightSchema,
GPTQWeightSchema,
HummingWeightSchema,
ModeloptMxfp8WeightSchema,
ModeloptNvfp4WeightSchema,
Mxfp4WeightSchema,
)
"""Convert any BaseWeightSchema to a QuantKey."""
if isinstance(schema, HummingWeightSchema):
return _humming_weight_schema_to_quant_key(schema)
# Schemas with fixed QuantKeys
if isinstance(schema, (Mxfp4WeightSchema, GptOssMxfp4WeightSchema)):
return QuantKey(
dtype=FP4_DTYPE,
scale=ScaleDesc(MXFP_SCALE_DTYPE, True, GroupShape(1, 32)),
)
if isinstance(schema, ModeloptMxfp8WeightSchema):
return QuantKey(
dtype=FP8_DTYPE,
scale=ScaleDesc(MXFP_SCALE_DTYPE, True, GroupShape(1, 32)),
)
if isinstance(schema, ModeloptNvfp4WeightSchema):
return QuantKey(
dtype=FP4_DTYPE,
scale=ScaleDesc(FP8_DTYPE, True, GroupShape(1, 16)),
scale2=ScaleDesc(torch.float32, True, GroupShape.PER_TENSOR),
)
if isinstance(schema, BitnetWeightSchema):
return QuantKey(
dtype=torch.uint8,
scale=ScaleDesc(torch.float32, True, GroupShape.PER_CHANNEL),
)
# Schemas requiring config inspection
if isinstance(schema, Fp8WeightSchema):
return _fp8_weight_schema_to_quant_key(schema)
if isinstance(schema, AWQWeightSchema):
return _awq_weight_schema_to_quant_key(schema)
if isinstance(schema, GPTQWeightSchema):
return _gptq_weight_schema_to_quant_key(schema)
if isinstance(schema, CompressedTensorsWeightSchema):
return _compressed_tensors_weight_schema_to_quant_key(schema)
raise TypeError(f"Unsupported weight schema type: {type(schema)}")
# ---- HummingInputSchema (post-conversion) ----------------------------------
def _humming_input_schema_to_quant_key(
schema: "HummingInputSchema",
) -> QuantKey | None:
"""Convert a HummingInputSchema to a QuantKey. Returns None if
the schema represents unquantized (bf16/fp16) inputs."""
if schema.a_dtype is None or schema.a_dtype.num_bits >= 16:
return None
dtype = _HUMMING_TO_QUANT_DTYPE[schema.a_dtype]
gs = schema.input_scale_group_size
group_shape = GroupShape(row=1, col=gs) if gs > 0 else GroupShape.PER_TOKEN
scale_dtype = MXFP_SCALE_DTYPE if gs > 0 else torch.float32
scale = ScaleDesc(dtype=scale_dtype, static=False, group_shape=group_shape)
return QuantKey(dtype=dtype, scale=scale, symmetric=True)
# ---- Checkpoint-format input schemas (pre-conversion) ----------------------
def _resolve_input_quant_key(
origin_a_dtype: "humming_dtypes.DataType",
group_size: int,
) -> QuantKey | None:
from vllm.utils.humming import HummingInputSchema
"""Resolve the actual activation QuantKey after platform fallback."""
a_dtype = HummingInputSchema().get_fallback_input_dtype(origin_a_dtype)
if a_dtype is None or a_dtype.num_bits >= 16:
return None
dtype = _HUMMING_TO_QUANT_DTYPE[a_dtype]
gs = group_size if a_dtype == humming_dtypes.float4e2m1 else 0
group_shape = GroupShape(row=1, col=gs) if gs > 0 else GroupShape.PER_TOKEN
scale_dtype = MXFP_SCALE_DTYPE if gs > 0 else torch.float32
scale = ScaleDesc(dtype=scale_dtype, static=False, group_shape=group_shape)
return QuantKey(dtype=dtype, scale=scale, symmetric=True)
def _compressed_tensors_input_schema_to_quant_key(
schema: "CompressedTensorsInputSchema",
) -> QuantKey | None:
type_bits_to_dtype = {
("float", 8): humming_dtypes.float8e4m3,
("float", 4): humming_dtypes.float4e2m1,
("int", 8): humming_dtypes.int8,
("int", 4): humming_dtypes.int4,
}
origin = type_bits_to_dtype.get((schema.type, schema.num_bits))
if origin is None:
return None
return _resolve_input_quant_key(origin, schema.group_size)
# ---- Dispatch for any BaseInputSchema -------------------------------------
def input_schema_to_quant_key(
schema: "BaseInputSchema",
) -> QuantKey | None:
from vllm.utils.humming import (
CompressedTensorsInputSchema,
Fp8InputSchema,
HummingInputSchema,
ModeloptNvfp4InputSchema,
)
"""Convert any BaseInputSchema to a QuantKey. Returns None if
the schema represents unquantized (bf16/fp16) inputs."""
if isinstance(schema, HummingInputSchema):
return _humming_input_schema_to_quant_key(schema)
if isinstance(schema, Fp8InputSchema):
return _resolve_input_quant_key(humming_dtypes.float8e4m3, 0)
if isinstance(schema, ModeloptNvfp4InputSchema):
return _resolve_input_quant_key(
humming_dtypes.float8e4m3,
schema.group_size,
)
if isinstance(schema, CompressedTensorsInputSchema):
return _compressed_tensors_input_schema_to_quant_key(schema)
raise TypeError(f"Unsupported input schema type: {type(schema)}")
def humming_is_layer_skipped(config: dict[str, Any], prefix: str):
if not config:
return True
keys = ["ignored_layers", "ignore", "modules_to_not_convert"]
ignored_layers: list[str] = []
for key in keys:
candidate = config.get(key, []) or []
if candidate:
ignored_layers = candidate
break
if any(module_name in prefix for module_name in ignored_layers):
return True
if "lm_head" in prefix:
return True
for regex in config.get("dynamic", {}):
if regex[:1] != "-":
continue
if re.match(regex[2:], prefix):
return True
return False
def convert_linear_layer_to_humming_standard(
layer: LinearBase, name_map: dict[str, str]
):
"""Rename/reshape a linear layer's quantized params (the canonical MPLinear
layout: ``weight_packed`` int32 + ``weight_scale``) into the parameter names
and layout humming's weight schema expects (``weight`` / ``weight_scale``)."""
for name, checkpoint_name in name_map.items():
tensor = getattr(layer, checkpoint_name)
delattr(layer, checkpoint_name)
if name == "weight":
input_dim = getattr(tensor, "input_dim", 1)
output_dim = getattr(tensor, "output_dim", 0)
if input_dim == 0 and output_dim == 1:
tensor = tensor.transpose(1, 0).contiguous()
else:
assert output_dim == 0 and input_dim == 1
tensor = tensor.view(tensor.size(0), -1).view(torch.int32)
elif name in ["weight_scale", "zero_point"]:
if getattr(tensor, "output_dim", 0) == 1:
tensor = tensor.transpose(0, 1).contiguous()
if tensor.ndim == 1:
tensor = tensor.unsqueeze(1)
tensor = tensor.view(torch.int32) if name == "zero_point" else tensor
if isinstance(tensor, torch.nn.Parameter):
param = tensor
else:
param = torch.nn.Parameter(tensor, requires_grad=False)
setattr(layer, name, param)
def prepare_humming_layer(layer: LinearBase, quant_config: dict):
from vllm.utils.humming import (
BaseWeightSchema,
HummingInputSchema,
HummingMethod,
)
weight_schema = BaseWeightSchema.from_config(quant_config)
input_schema = HummingInputSchema()
# ReplicatedLinear has no TP partitioning and so does not set
# input_size_per_partition; for it that is just input_size. Use hasattr
# rather than getattr's default arg, which is evaluated eagerly and would
# raise on layers lacking input_size (e.g. ParallelLMHead).
if hasattr(layer, "input_size_per_partition"):
input_size_per_partition = layer.input_size_per_partition
else:
input_size_per_partition = layer.input_size
shape_k_stacks = [input_size_per_partition]
shape_n_stacks = layer.output_partition_sizes
# Step 1: convert weight to humming standard format
weight_schema, tensors = weight_schema.convert_humming(
tensors=dict(layer.named_parameters()),
shape_n_stacks=shape_n_stacks,
shape_k_stacks=shape_k_stacks,
param_dtype=layer.params_dtype,
)
layer.weight_schema = weight_schema
for name, _ in list(layer.named_parameters()):
delattr(layer, name)
for name, tensor in tensors.items():
if isinstance(tensor, torch.nn.Parameter):
tensor = tensor.data
param = torch.nn.Parameter(tensor, requires_grad=False)
setattr(layer, name, param)
# Step 2: transform weight (humming standard format) for forwarding
HummingMethod.prepare_layer_meta(
layer=layer,
shape_n=sum(layer.output_partition_sizes),
shape_k=input_size_per_partition,
weight_schema=weight_schema,
input_schema=input_schema,
pad_n_to_multiple=256,
pad_k_to_multiple=128,
has_bias=layer.has_bias,
torch_dtype=layer.params_dtype,
)
HummingMethod.transform_humming_layer(layer)
if not hasattr(layer, "locks"):
device = layer.weight.device
locks = torch.zeros(1024, dtype=torch.int32, device=device)
layer.register_buffer("locks", locks)
compute_config = {
"use_batch_invariant": envs.VLLM_BATCH_INVARIANT,
"use_f16_accum": envs.VLLM_HUMMING_USE_F16_ACCUM,
"gemm_type": "dense",
}
layer.compute_config = json.dumps(compute_config)
def make_humming_moe_quant_config(
quant_dtype: torch.dtype | str | None,
weight_dtype: torch.dtype | str | None,
weight_group_shape: GroupShape | None = None,
w1_scale: torch.Tensor | None = None,
w2_scale: torch.Tensor | None = None,
w1_zp: torch.Tensor | None = None,
w2_zp: torch.Tensor | None = None,
w1_bias: torch.Tensor | None = None,
w2_bias: torch.Tensor | None = None,
w1_gscale: torch.Tensor | None = None,
w2_gscale: torch.Tensor | None = None,
gemm1_alpha: float | None = None,
gemm1_beta: float | None = None,
gemm1_clamp_limit: float | None = None,
) -> FusedMoEQuantConfig:
if quant_dtype is None:
a_quant_desc = FusedMoEQuantDesc(dtype=None)
else:
shape = GroupShape(row=1, col=-1)
a_quant_desc = FusedMoEQuantDesc(dtype=quant_dtype, shape=shape)
w1_quant_desc = FusedMoEQuantDesc(
dtype=weight_dtype,
shape=weight_group_shape,
scale=w1_scale,
alpha_or_gscale=w1_gscale,
zp=w1_zp,
bias=w1_bias,
)
w2_quant_desc = FusedMoEQuantDesc(
dtype=weight_dtype,
shape=weight_group_shape,
scale=w2_scale,
alpha_or_gscale=w2_gscale,
zp=w2_zp,
bias=w2_bias,
)
return FusedMoEQuantConfig(
_a1=a_quant_desc,
_a2=a_quant_desc,
_w1=w1_quant_desc,
_w2=w2_quant_desc,
gemm1_alpha=gemm1_alpha,
gemm1_beta=gemm1_beta,
gemm1_clamp_limit=gemm1_clamp_limit,
)
def get_humming_moe_quant_config(
layer: "RoutedExperts",
gemm1_alpha: float | None = None,
gemm1_beta: float | None = None,
gemm1_clamp_limit: float | None = None,
):
input_schema = layer.input_schemas["w13"]
weight_schema = layer.weight_schemas["w13"]
if input_schema.a_dtype is None or input_schema.a_dtype.num_bits == 16:
q_dtype = None
else:
q_dtype = str(input_schema.a_dtype)
weight_scale_group_size = weight_schema.weight_scale_group_size
weight_scale_group_size_n = weight_schema.weight_scale_group_size_n
weight_group_shape: tuple[int, ...] = ()
if weight_scale_group_size_n > 1:
weight_group_shape = GroupShape(
row=weight_scale_group_size,
col=weight_scale_group_size_n,
)
elif weight_scale_group_size == 0:
weight_group_shape = GroupShape(row=-1, col=1)
else:
weight_group_shape = GroupShape(row=weight_scale_group_size, col=1)
return make_humming_moe_quant_config(
quant_dtype=q_dtype,
weight_dtype=str(weight_schema.b_dtype),
weight_group_shape=weight_group_shape,
w1_scale=getattr(layer, "w13_weight_scale", None),
w1_gscale=getattr(layer, "w13_global_scale", None),
w1_zp=getattr(layer, "w13_zero_point", None),
w1_bias=getattr(layer, "w13_bias", None),
w2_scale=getattr(layer, "w2_weight_scale", None),
w2_gscale=getattr(layer, "w2_global_scale", None),
w2_zp=getattr(layer, "w2_zero_point", None),
w2_bias=getattr(layer, "w2_bias", None),
)
def select_humming_moe_experts(
config: FusedMoEConfig,
weight_key: QuantKey | None,
activation_key: QuantKey | None,
) -> type[mk.FusedMoEExperts] | None:
"""
Select the primary Humming MoE Experts class
Note: Shape-specific fallbacks may still occur at runtime.
"""
if not has_humming():
return None
# NOTE: the kernels are selected in the following order.
AVAILABLE_EXPERTS: list[type[mk.FusedMoEExperts]] = [
BatchedHummingGroupedExperts,
HummingGroupedExperts,
HummingIndexedExperts,
]
# NOTE(rob): We need to peak into the P/F selection to determine
# if we are using the batched or standard expert format, which
# if not ideal. Once we unify TP + DP/EP, we can select P/F first.
activation_format = (
mk.FusedMoEActivationFormat.BatchedExperts
if config.moe_parallel_config.use_batched_activation_format
else mk.FusedMoEActivationFormat.Standard
)
def _make_log_backend(experts_cls: type[mk.FusedMoEExperts]):
return f"Using {experts_cls.__name__} Humming MoE backend."
def _make_log_unsupported(
experts_cls: type[mk.FusedMoEExperts], reason: str | None
) -> str:
if reason:
return (
f"Humming MoE experts {experts_cls.__name__} does not support the "
f"deployment configuration since {reason}."
)
else:
return (
f"Humming MoE experts '{experts_cls.__name__}' does not support the "
"deployment configuration."
)
for k_cls in AVAILABLE_EXPERTS:
supported, reason = k_cls.is_supported_config(
k_cls,
config,
weight_key,
activation_key,
activation_format,
)
if supported:
logger.info_once(_make_log_backend(k_cls))
return k_cls
else:
logger.debug_once(_make_log_unsupported(k_cls, reason))
return None
def make_humming_moe_kernel(
moe_quant_config: FusedMoEQuantConfig,
moe_config: FusedMoEConfig,
experts_cls: type[mk.FusedMoEExperts],
layer: "RoutedExperts",
routing_tables: tuple[torch.Tensor, torch.Tensor, torch.Tensor] | None = None,
) -> mk.FusedMoEKernel:
# Create Prepare/Finalize.
prepare_finalize = maybe_make_prepare_finalize(
moe=moe_config,
quant_config=moe_quant_config,
routing_tables=routing_tables,
allow_new_interface=True,
use_monolithic=issubclass(experts_cls, mk.FusedMoEExpertsMonolithic),
)
assert prepare_finalize is not None
logger.info_once("Using %s", prepare_finalize.__class__.__name__)
extra_args: dict[str, Any] = {"layer": layer}
# Create Experts.
if prepare_finalize.activation_format == mk.FusedMoEActivationFormat.BatchedExperts:
max_num_tokens = prepare_finalize.max_num_tokens_per_rank()
assert max_num_tokens is not None
experts = experts_cls(
moe_config=moe_config,
quant_config=moe_quant_config,
max_num_tokens=max_num_tokens,
num_dispatchers=prepare_finalize.num_dispatchers(),
**extra_args,
)
else:
experts = experts_cls(
moe_config=moe_config,
quant_config=moe_quant_config,
**extra_args,
)
kernel = mk.FusedMoEKernel(
prepare_finalize,
experts,
)
return kernel
def _extract_sublayer_tensors(
layer: "RoutedExperts",
sublayer_name: str,
) -> dict[str, torch.Tensor]:
"""Extract tensors for a specific sublayer from the layer's state dict."""
return dict(
(key.removeprefix(sublayer_name + "_"), value)
for key, value in layer.state_dict().items()
if key.startswith(sublayer_name + "_")
)
def _replace_layer_parameters(
layer: "RoutedExperts",
sublayer_name: str,
tensors: dict[str, torch.Tensor],
preserve_bias: bool = False,
) -> None:
"""
Replace layer parameters for a sublayer with new tensors.
Args:
layer: The RoutedExperts layer
sublayer_name: Name of the sublayer (e.g., "w13", "w2")
tensors: Dict of parameter name to tensor
preserve_bias: If True, don't delete bias parameters
"""
# Delete old parameters
for name, _ in list(layer.named_parameters()):
if not name.startswith(sublayer_name + "_"):
continue
if preserve_bias and name == sublayer_name + "_bias":
continue
delattr(layer, name)
# Set new parameters
for name, tensor in tensors.items():
param_name = f"{sublayer_name}_{name}"
param = torch.nn.Parameter(tensor, requires_grad=False)
setattr(layer, param_name, param)
def _convert_sublayer_to_humming(
layer: "RoutedExperts",
sublayer_name: str,
shape_n: int,
shape_k: int,
weight_schema: Any,
input_schema: Any,
num_experts: int,
param_dtype: torch.dtype,
) -> tuple[Any, Any]:
"""
Convert a sublayer's weights from checkpoint format to Humming format.
Returns:
Tuple of (converted_weight_schema, converted_input_schema)
"""
from vllm.utils.humming import HummingWeightSchema
if isinstance(weight_schema, HummingWeightSchema):
# Already in Humming format
return weight_schema, input_schema
tensors = _extract_sublayer_tensors(layer, sublayer_name)
shape_k_stacks = [shape_k]
shape_n_stacks = [shape_n]
if sublayer_name == "w13":
shape_n_stacks = [shape_n // 2] * 2
converted_weight_schema, converted_tensors = weight_schema.convert_humming(
tensors=tensors,
shape_n_stacks=shape_n_stacks,
shape_k_stacks=shape_k_stacks,
param_dtype=param_dtype,
num_experts=num_experts,
)
converted_input_schema, _ = input_schema.convert_humming(
tensors=converted_tensors,
shape_n_stacks=shape_n_stacks,
shape_k_stacks=shape_k_stacks,
param_dtype=param_dtype,
num_experts=num_experts,
)
_replace_layer_parameters(layer, sublayer_name, converted_tensors)
return converted_weight_schema, converted_input_schema
def _prepare_and_transform_sublayer(
layer: "RoutedExperts",
sublayer_name: str,
shape_n: int,
shape_k: int,
weight_schema: Any,
input_schema: Any,
has_bias: bool,
num_experts: int,
param_dtype: torch.dtype,
) -> None:
"""
Prepare layer metadata and transform weights for a sublayer.
This calls Humming's prepare_layer_meta and transform_humming_layer.
"""
from vllm.utils.humming import HummingMethod
HummingMethod.prepare_layer_meta(
layer=layer,
shape_n=shape_n,
shape_k=shape_k,
pad_n_to_multiple=256,
pad_k_to_multiple=128,
input_schema=input_schema,
weight_schema=weight_schema,
has_bias=has_bias,
num_experts=num_experts,
torch_dtype=param_dtype,
sublayer_name=sublayer_name,
)
HummingMethod.transform_humming_layer(layer, sublayer_name=sublayer_name)
def _process_single_sublayer(
layer: "RoutedExperts",
sublayer_name: str,
shape_n: int,
shape_k: int,
weight_schema: Any,
input_schema: Any,
has_bias: bool,
num_experts: int,
param_dtype: torch.dtype,
force_weight_schema: Any | None = None,
) -> tuple[Any, Any]:
"""
Process a single sublayer: convert, optionally requant, prepare, and transform.
This combines the common logic from convert_to_humming_moe_kernel_format
for processing a single sublayer.
Args:
layer: The RoutedExperts layer
sublayer_name: Name of the sublayer (e.g., "w13", "w2")
shape_n: Output dimension size
shape_k: Input dimension size
weight_schema: Initial weight quantization schema
input_schema: Initial input quantization schema
has_bias: Whether the layer has bias terms
num_experts: Number of experts
param_dtype: Parameter data type
force_weight_schema: Optional schema to force requantization to
Returns:
Tuple of (final_weight_schema, final_input_schema)
"""
from vllm.utils.humming import HummingWeightSchema
# Step 1: Convert from checkpoint format to humming format if needed
current_weight_schema, current_input_schema = _convert_sublayer_to_humming(
layer=layer,
sublayer_name=sublayer_name,
shape_n=shape_n,
shape_k=shape_k,
weight_schema=weight_schema,
input_schema=input_schema,
num_experts=num_experts,
param_dtype=param_dtype,
)
# Step 2: Force requant if needed
assert isinstance(current_weight_schema, HummingWeightSchema)
if force_weight_schema is not None and current_weight_schema != force_weight_schema:
tensors = _extract_sublayer_tensors(layer, sublayer_name)
tensors = current_weight_schema.requant_tensors(
tensors=tensors,
target_weight_schema=force_weight_schema,
param_dtype=param_dtype,
)
current_weight_schema = force_weight_schema
_replace_layer_parameters(layer, sublayer_name, tensors, preserve_bias=True)
del tensors
# Step 3: Prepare layer metadata and transform weights
_prepare_and_transform_sublayer(
layer=layer,
sublayer_name=sublayer_name,
shape_n=shape_n,
shape_k=shape_k,
weight_schema=current_weight_schema,
input_schema=current_input_schema,
has_bias=has_bias,
num_experts=num_experts,
param_dtype=param_dtype,
)
return current_weight_schema, current_input_schema
def convert_to_humming_moe_kernel_format(
layer: "RoutedExperts",
quant_config: dict | None = None,
sublayer_configs: dict[str, Any] | None = None,
weight_schema: Any | None = None,
input_schema: Any | None = None,
force_weight_schema: Any | None = None,
) -> None:
"""
Convert MoE weights from checkpoint format to Humming kernel format.
This function processes weights for each sublayer (w13, w2) by:
1. Converting from checkpoint format to humming format if needed
2. Force requanting if a different quantization schema is specified
3. Preparing layer metadata for the Humming kernel
4. Transforming weights for inference
Args:
layer: The RoutedExperts layer containing weights to process
quant_config: Optional quantization config dict. Required if weight_schema
or input_schema are None. Used to build schemas via
BaseWeightSchema.from_config().
sublayer_configs: Optional configuration dict for each sublayer (w13, w2).
Each config must have "shape_n" and "shape_k" keys.
If None, configs are built from layer.moe_config properties.
weight_schema: Optional initial weight quantization schema.
If None, built from quant_config.
input_schema: Optional initial input quantization schema.
If None, built from quant_config or env vars.
force_weight_schema: Optional schema to force requantization to
Side effects:
- Modifies layer parameters in place
- Sets layer.weight_schemas and layer.input_schemas
"""
# Build schemas from quant_config if not provided
has_bias = layer.moe_config.has_bias
num_experts = layer.moe_config.num_local_experts
param_dtype = layer.params_dtype
if weight_schema is None or input_schema is None:
if quant_config is None:
raise ValueError(
"Must provide either weight_schema/input_schema or quant_config"
)
from vllm.model_executor.layers.quantization.utils.humming_utils import (
humming_is_layer_skipped,
)
from vllm.utils.humming import BaseWeightSchema, HummingInputSchema
if weight_schema is None:
weight_schema = BaseWeightSchema.from_config(quant_config)
if input_schema is None:
input_quant_config = envs.VLLM_HUMMING_INPUT_QUANT_CONFIG or {}
if humming_is_layer_skipped(input_quant_config, layer.layer_name):
input_schema = HummingInputSchema()
else:
# TODO: read input_quant_config from quant_config
input_schema = HummingInputSchema.from_config(input_quant_config)
# Build sublayer configs from layer properties if not provided
if sublayer_configs is None:
is_gated = layer.moe_config.activation.is_gated
sublayer_configs = {
"w13": {
"shape_n": layer.moe_config.intermediate_size_per_partition * 2,
"shape_k": layer.moe_config.hidden_dim,
},
"w2": {
"shape_n": layer.moe_config.hidden_dim,
"shape_k": layer.moe_config.intermediate_size_per_partition
* (1 if is_gated else 2),
},
}
layer.weight_schemas = {}
layer.input_schemas = {}
for sublayer_name, configs in sublayer_configs.items():
final_weight_schema, final_input_schema = _process_single_sublayer(
layer=layer,
sublayer_name=sublayer_name,
shape_n=configs["shape_n"],
shape_k=configs["shape_k"],
weight_schema=weight_schema,
input_schema=input_schema,
has_bias=has_bias,
num_experts=num_experts,
param_dtype=param_dtype,
force_weight_schema=force_weight_schema,
)
layer.weight_schemas[sublayer_name] = final_weight_schema
layer.input_schemas[sublayer_name] = final_input_schema
if not hasattr(layer, "locks"):
device = layer.w13_weight.device
locks = torch.zeros(1024, dtype=torch.int32, device=device)
layer.register_buffer("locks", locks)