Signed-off-by: yiliu30 <yi4.liu@intel.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
133 lines
4.8 KiB
Python
133 lines
4.8 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
|
"""Per-component quantization routing for multi-stage models.
|
|
|
|
Routes get_quant_method() to different configs based on longest-prefix match:
|
|
{"transformer": fp8_config, "vae": None}
|
|
"transformer.blocks.0.attn.to_q" -> fp8_config
|
|
"vae.encoder.conv_in" -> None
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING, Any
|
|
|
|
import torch
|
|
from vllm.model_executor.layers.quantization.base_config import (
|
|
QuantizationConfig,
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
from vllm.model_executor.layers.quantization.base_config import (
|
|
QuantizeMethodBase,
|
|
)
|
|
|
|
|
|
# Pre-quantized checkpoints (modelopt FP8/FP4/MXFP8/mixed) only quantize the
|
|
# Thinker LM. Vision and audio encoder weights remain in BF16 with no
|
|
# corresponding scale tensors in the checkpoint.
|
|
PRE_QUANTIZED_METHODS: frozenset[str] = frozenset({"modelopt", "modelopt_fp4", "modelopt_mxfp8", "modelopt_mixed"})
|
|
|
|
|
|
def resolve_encoder_quant_config(
|
|
quant_config: QuantizationConfig | None,
|
|
) -> QuantizationConfig | None:
|
|
"""Resolve quantization config for vision / audio encoders.
|
|
|
|
Returns *None* for pre-quantized methods so that FP8 kernels are never
|
|
applied to BF16 encoder weights (which lack scale tensors). All other
|
|
configs — including ``ComponentQuantizationConfig`` and ``None`` — are
|
|
returned as-is so the caller can handle them.
|
|
"""
|
|
if (
|
|
quant_config is not None
|
|
and not isinstance(quant_config, ComponentQuantizationConfig)
|
|
and quant_config.get_name() in PRE_QUANTIZED_METHODS
|
|
):
|
|
return None
|
|
return quant_config
|
|
|
|
|
|
def safe_quant_config(
|
|
quant_config: QuantizationConfig | None,
|
|
) -> QuantizationConfig | None:
|
|
"""Return *quant_config* only if it is safe for norm/modulation layers.
|
|
|
|
Norm and modulation layers (LayerNorm, RMSNorm, AdaLayerNorm, img_mod,
|
|
txt_mod, etc.) produce precision-sensitive shift/scale/gate values and
|
|
should not receive FP8 quant configs (see #2728). Pre-quantized methods
|
|
like INC/AutoRound W4A16 need the config propagated so packed weights
|
|
load correctly.
|
|
|
|
This is the inverse of :func:`resolve_encoder_quant_config`: that function
|
|
strips pre-quantized configs from encoders, while this one strips
|
|
*every config except* pre-quantized configs from norm/mod layers.
|
|
"""
|
|
if quant_config is None:
|
|
return None
|
|
from vllm.model_executor.layers.quantization.inc import INCConfig
|
|
|
|
if isinstance(quant_config, INCConfig):
|
|
return quant_config
|
|
return None
|
|
|
|
|
|
class ComponentQuantizationConfig(QuantizationConfig):
|
|
"""Routes quantization to different configs by layer prefix."""
|
|
|
|
def __init__(
|
|
self,
|
|
component_configs: dict[str, QuantizationConfig | None],
|
|
default_config: QuantizationConfig | None = None,
|
|
) -> None:
|
|
self._components = component_configs
|
|
self._default = default_config
|
|
self._sorted_prefixes = sorted(self._components.keys(), key=len, reverse=True)
|
|
|
|
def resolve(self, prefix: str) -> QuantizationConfig | None:
|
|
"""Find the config for a given layer prefix (longest-prefix match).
|
|
|
|
Note: vLLM may remap quantization prefixes vs model definition
|
|
prefixes (e.g. via WeightsMapper). If prefixes don't match after
|
|
remapping, layers may fall through to the default config.
|
|
"""
|
|
for comp_prefix in self._sorted_prefixes:
|
|
if prefix.startswith(comp_prefix):
|
|
return self._components[comp_prefix]
|
|
return self._default
|
|
|
|
def get_name(self) -> str:
|
|
return "component"
|
|
|
|
def get_quant_method(self, layer: torch.nn.Module, prefix: str) -> QuantizeMethodBase | None:
|
|
config = self.resolve(prefix)
|
|
if config is None:
|
|
return None
|
|
return config.get_quant_method(layer, prefix)
|
|
|
|
@classmethod
|
|
def get_supported_act_dtypes(cls) -> list[torch.dtype]:
|
|
return [torch.bfloat16, torch.float16]
|
|
|
|
def get_min_capability(self) -> int:
|
|
"""Return the minimum capability across all component configs."""
|
|
caps = [c.get_min_capability() for c in self._components.values() if c is not None]
|
|
if self._default is not None:
|
|
caps.append(self._default.get_min_capability())
|
|
return min(caps) if caps else 0
|
|
|
|
@classmethod
|
|
def from_config(cls, config: dict[str, Any]) -> ComponentQuantizationConfig:
|
|
raise NotImplementedError("Use build_quant_config() instead")
|
|
|
|
def get_config_filenames(self) -> list[str]:
|
|
return []
|
|
|
|
@property
|
|
def component_configs(self) -> dict[str, QuantizationConfig | None]:
|
|
return self._components
|
|
|
|
@property
|
|
def default_config(self) -> QuantizationConfig | None:
|
|
return self._default
|