Fix block FP8 MTP in ModelOpt mixed checkpoints (#55513)
Signed-off-by: Shiyang Chen <shiychen@nvidia.com> Signed-off-by: Isotr0py <Isotr0py@outlook.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> Co-authored-by: Isotr0py <Isotr0py@outlook.com>
This commit is contained in:
co-authored by
mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Isotr0py
parent
28a2ccee78
commit
60ad959b6f
@@ -1,6 +1,7 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
|
||||
from importlib import import_module
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import patch
|
||||
|
||||
@@ -19,6 +20,8 @@ from vllm.models.qwen4_exp.config import (
|
||||
from vllm.models.qwen4_exp.nvidia.model_state import Qwen4ExpModelState
|
||||
from vllm.v1.worker.gpu.model_states.mamba_hybrid import MambaHybridModelState
|
||||
|
||||
from ...utils import spawn_new_process_for_each_test
|
||||
|
||||
|
||||
def _text_config(**kwargs) -> Qwen4ExpTextConfig:
|
||||
values = {
|
||||
@@ -89,6 +92,28 @@ def test_qwen4_exp_mtp_returns_sample_and_multi_streams() -> None:
|
||||
assert returned_multi_hidden is multi_hidden
|
||||
|
||||
|
||||
@spawn_new_process_for_each_test
|
||||
@pytest.mark.parametrize("backend", ["amd", "nvidia"])
|
||||
def test_qwen4_exp_mtp_remaps_mixed_precision_layer_indices(backend: str) -> None:
|
||||
mtp_module = import_module(f"vllm.models.qwen4_exp.{backend}.mtp")
|
||||
|
||||
quantized_layers = {
|
||||
"model.language_model.layers.0.mlp.experts": {"quant_algo": "NVFP4"},
|
||||
"mtp.layers.0.mlp.experts": {
|
||||
"quant_algo": "FP8_BLOCK_SCALES",
|
||||
"group_size": 128,
|
||||
},
|
||||
}
|
||||
|
||||
assert mtp_module._remap_quantized_layers(quantized_layers, 48) == {
|
||||
"model.language_model.layers.0.mlp.experts": {"quant_algo": "NVFP4"},
|
||||
"mtp.layers.48.mlp.experts": {
|
||||
"quant_algo": "FP8_BLOCK_SCALES",
|
||||
"group_size": 128,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("wrapped_config", [False, True])
|
||||
def test_qwen4_exp_mtp_override_sets_draft_config(
|
||||
wrapped_config: bool,
|
||||
|
||||
@@ -25,6 +25,7 @@ from vllm.model_executor.kernels.linear import (
|
||||
MarlinNvFp4LinearKernel,
|
||||
)
|
||||
from vllm.model_executor.layers.attention import Attention
|
||||
from vllm.model_executor.layers.fused_moe import RoutedExperts
|
||||
from vllm.model_executor.layers.linear import UnquantizedLinearMethod
|
||||
from vllm.model_executor.layers.quantization.modelopt import (
|
||||
LINEAR_ALGOS,
|
||||
@@ -224,6 +225,37 @@ def test_modelopt_mixed_precision_dispatches_every_linear_algo(algo):
|
||||
assert isinstance(method, ModelOptLinearMethod), (algo, type(method).__name__)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("algo", ["FP8_PB_WO", "FP8_BLOCK_SCALES"])
|
||||
def test_modelopt_mixed_precision_dispatches_block_fp8_moe(algo):
|
||||
config = ModelOptMixedPrecisionConfig.from_config(
|
||||
{
|
||||
"quantization": {
|
||||
"quant_algo": "MIXED_PRECISION",
|
||||
"quantized_layers": {
|
||||
"mtp.layers.48.mlp.experts": {
|
||||
"quant_algo": algo,
|
||||
"group_size": 128,
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
)
|
||||
layer = MagicMock(spec=RoutedExperts)
|
||||
expected = object()
|
||||
|
||||
with patch(
|
||||
"vllm.model_executor.layers.quantization.modelopt.Fp8MoEMethod",
|
||||
return_value=expected,
|
||||
) as method_cls:
|
||||
method = config.get_quant_method(layer, "mtp.layers.48.mlp.experts")
|
||||
|
||||
assert method is expected
|
||||
fp8_config, called_layer = method_cls.call_args.args
|
||||
assert called_layer is layer
|
||||
assert fp8_config.weight_block_size == [128, 128]
|
||||
assert fp8_config.activation_scheme == "dynamic"
|
||||
|
||||
|
||||
def test_modelopt_nvfp4_leaves_excluded_parallel_lm_head_unquantized():
|
||||
config = ModelOptNvFp4Config(
|
||||
is_checkpoint_nvfp4_serialized=True,
|
||||
|
||||
@@ -60,6 +60,7 @@ from vllm.model_executor.layers.quantization.base_config import (
|
||||
QuantizationConfig,
|
||||
QuantizeMethodBase,
|
||||
)
|
||||
from vllm.model_executor.layers.quantization.fp8 import Fp8Config, Fp8MoEMethod
|
||||
from vllm.model_executor.layers.quantization.kv_cache import BaseKVCacheMethod
|
||||
from vllm.model_executor.layers.quantization.utils.fp8_utils import (
|
||||
FP8_SCALE_SENTINEL,
|
||||
@@ -107,6 +108,11 @@ if TYPE_CHECKING:
|
||||
|
||||
logger = init_logger(__name__)
|
||||
|
||||
# ``FP8_PB_WO`` is ModelOpt's canonical 2D block-FP8 name. Early composed
|
||||
# Qwen3.8-Flash-Next checkpoints used ``FP8_BLOCK_SCALES`` for the same tensor
|
||||
# layout, so retain it as a checkpoint-compatibility alias.
|
||||
_BLOCK_FP8_MOE_ALGOS = ("FP8_PB_WO", "FP8_BLOCK_SCALES")
|
||||
|
||||
# Single source of truth for the ModelOpt linear algos.
|
||||
#
|
||||
# Each entry is (owning config's name, mixed-precision sub-config attribute).
|
||||
@@ -1491,11 +1497,28 @@ class ModelOptMixedPrecisionConfig(ModelOptQuantConfigBase):
|
||||
self.w4a16_nvfp4_config = w4a16_nvfp4_config
|
||||
self.mxfp8_config = mxfp8_config
|
||||
|
||||
block_sizes = {
|
||||
int(layer_info.get("group_size", 128))
|
||||
for layer_info in quantized_layers.values()
|
||||
if layer_info.get("quant_algo", "").upper() in _BLOCK_FP8_MOE_ALGOS
|
||||
}
|
||||
if len(block_sizes) > 1:
|
||||
raise ValueError(
|
||||
"MIXED_PRECISION currently requires all block-FP8 MoE layers "
|
||||
f"to use one group_size, got {sorted(block_sizes)}."
|
||||
)
|
||||
block_size = next(iter(block_sizes), 128)
|
||||
self.fp8_block_config = Fp8Config(
|
||||
is_checkpoint_fp8_serialized=True,
|
||||
activation_scheme="dynamic",
|
||||
weight_block_size=[block_size, block_size],
|
||||
)
|
||||
|
||||
def has_blocked_weights(self) -> bool:
|
||||
# Same gate as ModelOptFp8Config.has_blocked_weights, resolved per
|
||||
# layer: "+quant_fp8" must be on as soon as any layer is block-scaled.
|
||||
return any(
|
||||
info.get("quant_algo", "").upper() == "FP8_PB_WO"
|
||||
info.get("quant_algo", "").upper() in _BLOCK_FP8_MOE_ALGOS
|
||||
for info in self.quantized_layers.values()
|
||||
)
|
||||
|
||||
@@ -1730,6 +1753,8 @@ class ModelOptMixedPrecisionConfig(ModelOptQuantConfigBase):
|
||||
return build_linear_method(getattr(self, subcfg_attr), quant_algo, prefix)
|
||||
|
||||
if isinstance(layer, RoutedExperts):
|
||||
if quant_algo in _BLOCK_FP8_MOE_ALGOS:
|
||||
return Fp8MoEMethod(self.fp8_block_config, layer)
|
||||
if quant_algo == "FP8":
|
||||
return ModelOptFp8MoEMethod(
|
||||
quant_config=self.fp8_config,
|
||||
|
||||
@@ -78,6 +78,17 @@ def _remap_ignored_layers(
|
||||
return remapped
|
||||
|
||||
|
||||
def _remap_quantized_layers(
|
||||
quantized_layers: dict[str, dict],
|
||||
mtp_start_layer_idx: int,
|
||||
) -> dict[str, dict]:
|
||||
"""Map checkpoint MTP layer indices to standalone draft indices."""
|
||||
return {
|
||||
_remap_ignored_layers([name], mtp_start_layer_idx)[0]: layer_info
|
||||
for name, layer_info in quantized_layers.items()
|
||||
}
|
||||
|
||||
|
||||
def _remap_mtp_weight_name(name: str) -> str | None:
|
||||
"""Map Qwen4Exp checkpoint paths into the standalone draft model."""
|
||||
|
||||
@@ -136,6 +147,13 @@ def _make_draft_vllm_config(
|
||||
"exclude_modules",
|
||||
_remap_ignored_layers(exclude_modules, mtp_start_layer_idx),
|
||||
)
|
||||
quantized_layers = getattr(draft_quant_config, "quantized_layers", None)
|
||||
if quantized_layers:
|
||||
setattr( # noqa: B010
|
||||
draft_quant_config,
|
||||
"quantized_layers",
|
||||
_remap_quantized_layers(quantized_layers, mtp_start_layer_idx),
|
||||
)
|
||||
|
||||
draft_vllm_config = replace(
|
||||
vllm_config,
|
||||
|
||||
@@ -77,6 +77,17 @@ def _remap_ignored_layers(
|
||||
return remapped
|
||||
|
||||
|
||||
def _remap_quantized_layers(
|
||||
quantized_layers: dict[str, dict],
|
||||
mtp_start_layer_idx: int,
|
||||
) -> dict[str, dict]:
|
||||
"""Map checkpoint MTP layer indices to standalone draft indices."""
|
||||
return {
|
||||
_remap_ignored_layers([name], mtp_start_layer_idx)[0]: layer_info
|
||||
for name, layer_info in quantized_layers.items()
|
||||
}
|
||||
|
||||
|
||||
def _remap_mtp_weight_name(name: str) -> str | None:
|
||||
"""Map Qwen4Exp checkpoint paths into the standalone draft model."""
|
||||
|
||||
@@ -135,6 +146,13 @@ def _make_draft_vllm_config(
|
||||
"exclude_modules",
|
||||
_remap_ignored_layers(exclude_modules, mtp_start_layer_idx),
|
||||
)
|
||||
quantized_layers = getattr(draft_quant_config, "quantized_layers", None)
|
||||
if quantized_layers:
|
||||
setattr( # noqa: B010
|
||||
draft_quant_config,
|
||||
"quantized_layers",
|
||||
_remap_quantized_layers(quantized_layers, mtp_start_layer_idx),
|
||||
)
|
||||
|
||||
draft_vllm_config = replace(
|
||||
vllm_config,
|
||||
|
||||
Reference in New Issue
Block a user