[Quantization] Enable shared expert fusion compatibility with online shared_expert quantization (showcase: along Quark MXFP4 routed experts) (#55686)
Signed-off-by: Felix Marty <Felix.Marty@amd.com> Signed-off-by: fxmarty-amd <felmarty@amd.com> Co-authored-by: OpenAI Codex <codex@openai.com> Co-authored-by: Misha Goin <mgoin64@gmail.com>
This commit is contained in:
co-authored by
OpenAI Codex
Misha Goin
parent
e2414ec1db
commit
9f3d70e081
@@ -15,10 +15,12 @@ from torch import nn
|
||||
|
||||
import vllm.config as vllm_config_module
|
||||
from vllm.config import ParallelConfig, VllmConfig, set_current_vllm_config
|
||||
from vllm.config.quantization import QuantizationConfigArgs
|
||||
from vllm.model_executor.layers.fused_moe import utils as fused_moe_utils
|
||||
from vllm.model_executor.layers.fused_moe.layer import determine_expert_counts
|
||||
from vllm.model_executor.layers.quantization.base_config import QuantizationConfig
|
||||
from vllm.model_executor.layers.quantization.experts_int8 import ExpertsInt8Config
|
||||
from vllm.model_executor.layers.quantization.fp8 import Fp8Config
|
||||
from vllm.model_executor.layers.quantization.online.base import OnlineQuantizationConfig
|
||||
from vllm.model_executor.layers.quantization.quark.quark import QuarkConfig
|
||||
from vllm.model_executor.layers.quantization.utils.config_utils import (
|
||||
get_quark_ocp_mx_group_size,
|
||||
@@ -141,10 +143,6 @@ def get_deepseek_v4_quark_config(exclude: list[str]) -> dict[str, Any]:
|
||||
return quantization_config
|
||||
|
||||
|
||||
def _stub_quant_config() -> QuantizationConfig:
|
||||
return cast(QuantizationConfig, object())
|
||||
|
||||
|
||||
class _StubGlm5NextAttention(nn.Module):
|
||||
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
||||
super().__init__()
|
||||
@@ -340,7 +338,7 @@ def test_resolve_layer_fused_shared_expert_skips_compatibility_when_disabled(
|
||||
)
|
||||
|
||||
assert not fused_moe_utils.resolve_layer_fused_shared_expert(
|
||||
_stub_quant_config(), "model.layers.0.mlp"
|
||||
ExpertsInt8Config(), "model.layers.0.mlp"
|
||||
)
|
||||
|
||||
|
||||
@@ -355,7 +353,7 @@ def test_resolve_layer_fused_shared_expert_normalizes_unavailable_aiter(
|
||||
|
||||
assert (
|
||||
fused_moe_utils.resolve_layer_fused_shared_expert(
|
||||
_stub_quant_config(), "model.layers.0.mlp"
|
||||
ExpertsInt8Config(), "model.layers.0.mlp"
|
||||
)
|
||||
is False
|
||||
)
|
||||
@@ -364,7 +362,7 @@ def test_resolve_layer_fused_shared_expert_normalizes_unavailable_aiter(
|
||||
def test_resolve_layer_fused_shared_expert_passes_module_prefixes(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
quant_config = _stub_quant_config()
|
||||
quant_config = ExpertsInt8Config()
|
||||
monkeypatch.setattr(
|
||||
fused_moe_utils.rocm_aiter_ops,
|
||||
"is_fusion_moe_shared_experts_enabled",
|
||||
@@ -413,7 +411,7 @@ def test_resolve_layer_fused_shared_expert_rejects_incompatible_quantization(
|
||||
)
|
||||
|
||||
assert not fused_moe_utils.resolve_layer_fused_shared_expert(
|
||||
_stub_quant_config(), "model.layers.0.mlp"
|
||||
ExpertsInt8Config(), "model.layers.0.mlp"
|
||||
)
|
||||
assert "shared experts are excluded" in caplog.text
|
||||
|
||||
@@ -423,6 +421,7 @@ def test_deepseek_v4_shared_expert_fse_uses_mtp_quantization_config_prefix(
|
||||
) -> None:
|
||||
class DeepseekV4Config:
|
||||
expert_dtype = "fp4"
|
||||
online_quantization_config = None
|
||||
|
||||
def _is_quark_mxfp4_ocp(self, hf_config: object) -> bool:
|
||||
return True
|
||||
@@ -1070,6 +1069,73 @@ def test_quark_shared_expert_fse_compatibility(
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("exclude_shared_expert", [False, True])
|
||||
def test_online_targets_off_shared_expert_defer_to_checkpoint(
|
||||
exclude_shared_expert: bool,
|
||||
) -> None:
|
||||
"""Unrelated online targets preserve the checkpoint's FSE decision."""
|
||||
shared_prefix = "model.layers.0.mlp.shared_expert"
|
||||
quant_config = QuarkConfig(
|
||||
{
|
||||
**_QUARK_FSE_CONFIG,
|
||||
"exclude": (
|
||||
[f"{shared_prefix}.down_proj"] if exclude_shared_expert else []
|
||||
),
|
||||
}
|
||||
)
|
||||
quant_config.online_quantization_config = OnlineQuantizationConfig(
|
||||
QuantizationConfigArgs(targets={"*linear_attn.out_proj": "mxfp4"})
|
||||
)
|
||||
|
||||
compatible, reason = is_shared_expert_quant_fse_compatible(
|
||||
quant_config,
|
||||
"model.layers.0.mlp.experts",
|
||||
shared_prefix,
|
||||
)
|
||||
|
||||
assert compatible is not exclude_shared_expert
|
||||
assert reason == (
|
||||
f"Quark excludes shared experts at {shared_prefix}"
|
||||
if exclude_shared_expert
|
||||
else None
|
||||
)
|
||||
|
||||
|
||||
def test_online_targeting_only_one_shared_projection_rejects_fse() -> None:
|
||||
shared_prefix = "model.layers.0.mlp.shared_expert"
|
||||
quant_config = QuarkConfig({**_QUARK_FSE_CONFIG, "exclude": []})
|
||||
quant_config.online_quantization_config = OnlineQuantizationConfig(
|
||||
QuantizationConfigArgs(targets={f"{shared_prefix}.down_proj": "mxfp4"})
|
||||
)
|
||||
|
||||
assert is_shared_expert_quant_fse_compatible(
|
||||
quant_config,
|
||||
"model.layers.0.mlp.experts",
|
||||
shared_prefix,
|
||||
) == (
|
||||
False,
|
||||
"online quantization targets only part of the shared expert at "
|
||||
f"{shared_prefix}",
|
||||
)
|
||||
|
||||
|
||||
def test_online_fp8_shared_expert_rejects_quark_mxfp4_fse() -> None:
|
||||
shared_prefix = "model.layers.0.mlp.shared_expert"
|
||||
quant_config = QuarkConfig({**_QUARK_FSE_CONFIG, "exclude": []})
|
||||
quant_config.online_quantization_config = OnlineQuantizationConfig(
|
||||
QuantizationConfigArgs(targets={f"{shared_prefix}*": "fp8_per_tensor"})
|
||||
)
|
||||
|
||||
assert is_shared_expert_quant_fse_compatible(
|
||||
quant_config,
|
||||
"model.layers.0.mlp.experts",
|
||||
shared_prefix,
|
||||
) == (
|
||||
False,
|
||||
"online shared-expert quantization keys do not match the routed expert keys",
|
||||
)
|
||||
|
||||
|
||||
def test_quark_shared_expert_fse_exclude_is_scoped_to_the_layer() -> None:
|
||||
"""Excluding one layer's shared experts must not disable FSE elsewhere.
|
||||
|
||||
@@ -1335,14 +1401,15 @@ def test_quark_packed_layer_config_must_match_global_config() -> None:
|
||||
|
||||
def test_non_quark_shared_expert_fse_is_incompatible() -> None:
|
||||
compatible, reason = is_shared_expert_quant_fse_compatible(
|
||||
_stub_quant_config(),
|
||||
ExpertsInt8Config(),
|
||||
"model.layers.0.mlp.experts",
|
||||
"model.layers.0.mlp.shared_experts",
|
||||
)
|
||||
|
||||
assert not compatible
|
||||
assert reason == (
|
||||
"shared-expert FSE quantization compatibility is not implemented for object"
|
||||
"shared-expert FSE quantization compatibility is not implemented for "
|
||||
"ExpertsInt8Config"
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -528,6 +528,7 @@ def test_padded_moe_reload_releases_each_layer(
|
||||
layer.quant_method = method
|
||||
layer.expert_map_manager = SimpleNamespace(map_global_to_local=lambda i: i)
|
||||
layer._loaded_expert_biases = set()
|
||||
layer._fused_shared_expert_quantizer = None
|
||||
method.create_weights(
|
||||
layer,
|
||||
experts,
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import sys
|
||||
from collections.abc import Callable
|
||||
from types import ModuleType, SimpleNamespace
|
||||
from unittest.mock import Mock, patch
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -91,9 +91,7 @@ def test_int4_per_group_32_is_not_an_online_moe_method():
|
||||
QuantizationConfigArgs(moe={"weight": "int4_per_group_32"})
|
||||
)
|
||||
assert (
|
||||
config.resolve_quant_method_cls(
|
||||
Mock(spec=RoutedExperts), "model.layers.0.mlp.experts"
|
||||
)
|
||||
config.resolve_quant_method_cls(RoutedExperts, "model.layers.0.mlp.experts")
|
||||
is None
|
||||
)
|
||||
|
||||
@@ -103,6 +101,4 @@ def test_unknown_online_moe_weight_still_raises():
|
||||
QuantizationConfigArgs(moe={"weight": "fp8_per_token"})
|
||||
)
|
||||
with pytest.raises(ValueError, match="online quantization"):
|
||||
config.resolve_quant_method_cls(
|
||||
Mock(spec=RoutedExperts), "model.layers.0.mlp.experts"
|
||||
)
|
||||
config.resolve_quant_method_cls(RoutedExperts, "model.layers.0.mlp.experts")
|
||||
|
||||
@@ -162,6 +162,7 @@ def test_routed_experts_loads_per_expert_biases():
|
||||
class Loader:
|
||||
quant_config = None
|
||||
quant_method = object()
|
||||
_fused_shared_expert_quantizer = None
|
||||
moe_config = SimpleNamespace(
|
||||
is_act_and_mul=True,
|
||||
tp_rank=0,
|
||||
|
||||
@@ -2,11 +2,10 @@
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
"""Tests quantization configuration matching utilities."""
|
||||
|
||||
from unittest.mock import Mock
|
||||
|
||||
import pytest
|
||||
|
||||
from vllm.config.quantization import QuantizationConfigArgs
|
||||
from vllm.model_executor.layers.linear import LinearBase
|
||||
from vllm.model_executor.layers.quantization.compressed_tensors.utils import (
|
||||
should_ignore_layer,
|
||||
)
|
||||
@@ -58,7 +57,7 @@ def test_online_ignore_supports_fnmatch_patterns():
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError, match="matches both quantization_config.ignore"):
|
||||
config.resolve_quant_method_cls(Mock(), layer_name)
|
||||
config.resolve_quant_method_cls(LinearBase, layer_name)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
||||
@@ -195,7 +195,6 @@ def test_qianfan_online_fp8_keeps_vision_layers_unquantized(
|
||||
config,
|
||||
)
|
||||
|
||||
layer = Mock(spec=LinearBase)
|
||||
for prefix in (
|
||||
"vision_model.encoder.layers.0.attn.qkv",
|
||||
"vision_model.encoder.layers.1.mlp.fc2",
|
||||
@@ -203,10 +202,10 @@ def test_qianfan_online_fp8_keeps_vision_layers_unquantized(
|
||||
"mlp1.1",
|
||||
"mlp1.3",
|
||||
):
|
||||
assert config.resolve_quant_method_cls(layer, prefix) is None
|
||||
assert config.resolve_quant_method_cls(LinearBase, prefix) is None
|
||||
|
||||
resolved = config.resolve_quant_method_cls(
|
||||
layer, "language_model.model.layers.0.self_attn.qkv_proj"
|
||||
LinearBase, "language_model.model.layers.0.self_attn.qkv_proj"
|
||||
)
|
||||
assert resolved is not None and resolved[-1] is method_cls
|
||||
assert other_config.ignored_layers == []
|
||||
@@ -1320,9 +1319,7 @@ def test_online_quantization_targets_ignore_collision() -> None:
|
||||
)
|
||||
)
|
||||
with pytest.raises(ValueError, match="matches both quantization_config.ignore"):
|
||||
config.resolve_quant_method_cls(
|
||||
Mock(spec=LinearBase), "model.layers.0.self_attn.o_proj"
|
||||
)
|
||||
config.resolve_quant_method_cls(LinearBase, "model.layers.0.self_attn.o_proj")
|
||||
|
||||
|
||||
def test_online_quantization_targets_reject_unsupported_layer() -> None:
|
||||
|
||||
@@ -0,0 +1,637 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
"""Tests ROCm AITER fused shared experts with online quantization."""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
import torch
|
||||
|
||||
from tests.quantization.utils import load_model_without_vllm_runner
|
||||
from vllm._aiter_ops import rocm_aiter_ops
|
||||
from vllm.config.model import ModelConfig
|
||||
from vllm.config.quantization import (
|
||||
QuantizationConfigArgs,
|
||||
resolve_quantization_config,
|
||||
)
|
||||
from vllm.model_executor.layers.fused_moe import FusedMoEFactory
|
||||
from vllm.model_executor.layers.fused_moe.experts import rocm_aiter_moe
|
||||
from vllm.model_executor.layers.fused_moe.oracle.mxfp4 import Mxfp4MoeBackend
|
||||
from vllm.model_executor.layers.quantization.online.base import OnlineQuantizationConfig
|
||||
from vllm.model_executor.layers.quantization.quark.quark import QuarkConfig
|
||||
from vllm.model_executor.layers.quantization.quark.quark_moe import (
|
||||
QuarkOCP_MX_MoEMethod,
|
||||
)
|
||||
from vllm.model_executor.layers.quantization.utils.mxfp4_utils import (
|
||||
mxfp4_quantize,
|
||||
)
|
||||
from vllm.model_executor.model_loader.dummy_loader import DummyModelLoader
|
||||
from vllm.model_executor.model_loader.reload.layerwise import (
|
||||
finalize_layerwise_reload,
|
||||
initialize_layerwise_reload,
|
||||
record_metadata_for_reloading,
|
||||
)
|
||||
from vllm.model_executor.model_loader.utils import get_model_architecture
|
||||
from vllm.platforms import current_platform
|
||||
|
||||
pytestmark = pytest.mark.skipif(
|
||||
not current_platform.is_rocm(),
|
||||
reason="ROCm AITER shared-expert tests require ROCm.",
|
||||
)
|
||||
|
||||
_QUARK_MXFP4_CONFIG: dict[str, Any] = {
|
||||
"global_quant_config": {
|
||||
"input_tensors": {
|
||||
"dtype": "fp4",
|
||||
"is_dynamic": True,
|
||||
"qscheme": "per_group",
|
||||
"ch_axis": -1,
|
||||
"group_size": 32,
|
||||
"block_size": None,
|
||||
"symmetric": None,
|
||||
"round_method": "half_even",
|
||||
"scale_type": "float",
|
||||
"scale_format": "e8m0",
|
||||
"scale_calculation_mode": "even",
|
||||
"mx_element_dtype": None,
|
||||
"observer_cls": "PerBlockMXObserver",
|
||||
"is_scale_quant": False,
|
||||
"enable_buffer_reuse": False,
|
||||
"max_input_numel": 4194304,
|
||||
},
|
||||
"output_tensors": None,
|
||||
"weight": {
|
||||
"dtype": "fp4",
|
||||
"is_dynamic": False,
|
||||
"qscheme": "per_group",
|
||||
"ch_axis": -1,
|
||||
"group_size": 32,
|
||||
"block_size": None,
|
||||
"symmetric": None,
|
||||
"round_method": "half_even",
|
||||
"scale_type": "float",
|
||||
"scale_format": "e8m0",
|
||||
"scale_calculation_mode": "even",
|
||||
"mx_element_dtype": None,
|
||||
"observer_cls": "PerBlockMXObserver",
|
||||
"is_scale_quant": False,
|
||||
"enable_buffer_reuse": False,
|
||||
"max_input_numel": 4194304,
|
||||
},
|
||||
"bias": None,
|
||||
"target_device": None,
|
||||
},
|
||||
"algo_config": None,
|
||||
"softmax_quant_spec": None,
|
||||
"quant_method": "quark",
|
||||
"layer_type_quant_config": {},
|
||||
"layer_quant_config": {},
|
||||
"exclude": ["re:^(?!.*\\.mlp\\.experts(?:\\.|$)).*$"],
|
||||
"kv_cache_quant_config": {},
|
||||
"kv_cache_post_rope": False,
|
||||
"quant_mode": "eager_mode",
|
||||
"version": "0.12+9d3d471cdf1",
|
||||
"export": {
|
||||
"kv_cache_group": [],
|
||||
"min_kv_scale": 0.0,
|
||||
"pack_method": "reorder",
|
||||
"weight_format": "real_quantized",
|
||||
"weight_merge_groups": None,
|
||||
},
|
||||
}
|
||||
|
||||
_QUARK_MXFP4_LAYER_CONFIG: dict[str, Any] = _QUARK_MXFP4_CONFIG["global_quant_config"]
|
||||
_QUARK_MXFP4_CONFIG = {
|
||||
**_QUARK_MXFP4_CONFIG,
|
||||
"global_quant_config": {
|
||||
"input_tensors": {},
|
||||
"output_tensors": None,
|
||||
"weight": {},
|
||||
"bias": None,
|
||||
"target_device": None,
|
||||
},
|
||||
"layer_quant_config": {"*mlp.experts*": _QUARK_MXFP4_LAYER_CONFIG},
|
||||
}
|
||||
|
||||
|
||||
class _StubAttention(torch.nn.Module):
|
||||
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
||||
super().__init__()
|
||||
self.o_proj = SimpleNamespace(reduce_results=True)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def reset_aiter_shared_expert_topk_metadata(monkeypatch: pytest.MonkeyPatch):
|
||||
"""Isolate AITER's process-global shared-expert routing buffer."""
|
||||
monkeypatch.setattr(rocm_aiter_moe, "aiter_topK_meta_data", None)
|
||||
rocm_aiter_moe.init_aiter_topK_meta_data.cache_clear()
|
||||
yield
|
||||
rocm_aiter_moe.init_aiter_topK_meta_data.cache_clear()
|
||||
|
||||
|
||||
def _write_minimal_moe_config(model_path: Path, architecture: str) -> None:
|
||||
"""Write a compact MoE config with one shared expert."""
|
||||
config = {
|
||||
"architectures": [architecture],
|
||||
"hidden_size": 256,
|
||||
"intermediate_size": 512,
|
||||
"moe_intermediate_size": 256,
|
||||
"num_attention_heads": 4,
|
||||
"num_key_value_heads": 4,
|
||||
"head_dim": 64,
|
||||
"num_hidden_layers": 1,
|
||||
"vocab_size": 256,
|
||||
"max_position_embeddings": 64,
|
||||
"q_lora_rank": 64,
|
||||
"kv_lora_rank": 512,
|
||||
"qk_nope_head_dim": 128,
|
||||
"qk_rope_head_dim": 64,
|
||||
"v_head_dim": 128,
|
||||
"n_routed_experts": 4,
|
||||
"num_experts": 4,
|
||||
"num_experts_per_tok": 1,
|
||||
"n_group": 1,
|
||||
"topk_group": 1,
|
||||
"n_shared_experts": 1,
|
||||
"first_k_dense_replace": 0,
|
||||
"moe_layer_freq": 1,
|
||||
"rms_norm_eps": 1e-6,
|
||||
"hidden_act": "silu",
|
||||
"tie_word_embeddings": False,
|
||||
"rope_theta": 10000,
|
||||
"quantization_config": _QUARK_MXFP4_CONFIG,
|
||||
}
|
||||
if architecture == "AXK1ForCausalLM":
|
||||
config["model_type"] = "axk1"
|
||||
elif architecture == "MiniMaxM3SparseForCausalLM":
|
||||
text_config = {
|
||||
**config,
|
||||
"model_type": "minimax_m3_text",
|
||||
"dense_intermediate_size": 512,
|
||||
"hidden_act": "swigluoai",
|
||||
"num_local_experts": 4,
|
||||
"n_shared_experts": 1,
|
||||
"moe_layer_freq": [1],
|
||||
"sparse_attention_config": None,
|
||||
"quantization_config": {
|
||||
**_QUARK_MXFP4_CONFIG,
|
||||
"layer_quant_config": {
|
||||
"*block_sparse_moe.experts*": _QUARK_MXFP4_LAYER_CONFIG
|
||||
},
|
||||
"exclude": [r"re:^(?!.*\.block_sparse_moe\.experts(?:\.|$)).*$"],
|
||||
},
|
||||
}
|
||||
config = {
|
||||
"architectures": [architecture],
|
||||
"model_type": "minimax_m3_vl",
|
||||
"text_config": text_config,
|
||||
}
|
||||
elif architecture == "Glm4MoeForCausalLM":
|
||||
config["model_type"] = "glm4_moe"
|
||||
elif architecture == "Glm4MoeLiteForCausalLM":
|
||||
config["model_type"] = "glm4_moe_lite"
|
||||
elif architecture in ("Qwen3NextForCausalLM", "Qwen3_5MoeForCausalLM"):
|
||||
config.update(
|
||||
model_type=(
|
||||
"qwen3_next"
|
||||
if architecture == "Qwen3NextForCausalLM"
|
||||
else "qwen3_5_moe_text"
|
||||
),
|
||||
layer_types=[
|
||||
"linear_attention"
|
||||
if architecture == "Qwen3NextForCausalLM"
|
||||
else "full_attention"
|
||||
],
|
||||
head_dim=64,
|
||||
linear_key_head_dim=64,
|
||||
linear_value_head_dim=64,
|
||||
linear_num_key_heads=4,
|
||||
linear_num_value_heads=4,
|
||||
shared_expert_intermediate_size=256,
|
||||
)
|
||||
elif architecture == "Qwen4ExpForCausalLM":
|
||||
config.update(
|
||||
model_type="qwen4_exp_text",
|
||||
layer_types=["full_attention"],
|
||||
head_dim=64,
|
||||
linear_key_head_dim=64,
|
||||
linear_value_head_dim=64,
|
||||
linear_num_key_heads=4,
|
||||
linear_num_value_heads=4,
|
||||
shared_expert_intermediate_size=256,
|
||||
eos_token_id=1,
|
||||
hc_count=2,
|
||||
hc_lowrank=4,
|
||||
ple_layer_ids=[],
|
||||
)
|
||||
elif architecture == "Glm5NextForCausalLM":
|
||||
config.update(
|
||||
model_type="glm5_next",
|
||||
layer_types=["linear_attention"],
|
||||
mlp_layer_types=["sparse"],
|
||||
qk_rope_head_dim=0,
|
||||
index_topk=1,
|
||||
index_kpool=1,
|
||||
pad_token_id=None,
|
||||
scoring_func="sigmoid",
|
||||
topk_method="noaux_tc",
|
||||
mhc=False,
|
||||
)
|
||||
else:
|
||||
config["model_type"] = (
|
||||
"deepseek_v3"
|
||||
if "V3" in architecture
|
||||
else "deepseek_v32"
|
||||
if architecture == "DeepseekV32ForCausalLM"
|
||||
else "deepseek_v2"
|
||||
)
|
||||
if architecture in ("DeepseekV32ForCausalLM", "GlmMoeDsaForCausalLM"):
|
||||
config.update(
|
||||
index_topk=1,
|
||||
index_kpool=1,
|
||||
index_n_heads=1,
|
||||
index_head_dim=32,
|
||||
index_kv_lora_rank=32,
|
||||
)
|
||||
(model_path / "config.json").write_text(json.dumps(config))
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"load_path", ["load_weights", "fused_gate_up", "weight_loader"]
|
||||
)
|
||||
def test_online_shared_expert_loads_bf16_weights_into_mxfp4_slot(
|
||||
default_vllm_config,
|
||||
dist_init,
|
||||
load_path: str,
|
||||
) -> None:
|
||||
"""A BF16 shared expert is quantized while routed MXFP4 weights are loaded.
|
||||
|
||||
`weight_loader` covers DeepSeek-style model loaders, which bypass
|
||||
`RoutedExperts.load_weights`.
|
||||
"""
|
||||
default_vllm_config.model_config = ModelConfig()
|
||||
hidden_size = intermediate_size = 64
|
||||
num_routed_experts = 2
|
||||
device = current_platform.device_type
|
||||
online_config = OnlineQuantizationConfig(
|
||||
QuantizationConfigArgs(targets={"*shared_expert*": "mxfp4"})
|
||||
)
|
||||
quant_config = QuarkConfig(_QUARK_MXFP4_CONFIG)
|
||||
quant_config.online_quantization_config = online_config
|
||||
|
||||
with torch.device(device):
|
||||
runner = FusedMoEFactory(
|
||||
num_experts=num_routed_experts,
|
||||
top_k=1,
|
||||
hidden_size=hidden_size,
|
||||
intermediate_size=intermediate_size,
|
||||
n_shared_experts=1,
|
||||
fuse_shared_experts=True,
|
||||
shared_expert_prefix="model.layers.0.mlp.shared_expert",
|
||||
prefix="model.layers.0.mlp.experts",
|
||||
quant_config=quant_config,
|
||||
)
|
||||
layer = runner.routed_experts
|
||||
assert isinstance(layer.quant_method, QuarkOCP_MX_MoEMethod)
|
||||
|
||||
routed_gate, _ = mxfp4_quantize(
|
||||
torch.randn(intermediate_size, hidden_size, dtype=torch.bfloat16)
|
||||
)
|
||||
routed_up, _ = mxfp4_quantize(
|
||||
torch.randn(intermediate_size, hidden_size, dtype=torch.bfloat16)
|
||||
)
|
||||
routed_down, _ = mxfp4_quantize(
|
||||
torch.randn(hidden_size, intermediate_size, dtype=torch.bfloat16)
|
||||
)
|
||||
shared_gate = torch.randn(intermediate_size, hidden_size, dtype=torch.bfloat16)
|
||||
shared_up = torch.randn(intermediate_size, hidden_size, dtype=torch.bfloat16)
|
||||
shared_down = torch.randn(hidden_size, intermediate_size, dtype=torch.bfloat16)
|
||||
|
||||
weights = [
|
||||
("0.gate_proj.weight", routed_gate),
|
||||
("0.up_proj.weight", routed_up),
|
||||
("0.down_proj.weight", routed_down),
|
||||
]
|
||||
if load_path == "load_weights":
|
||||
weights += [
|
||||
("2.gate_proj.weight", shared_gate),
|
||||
("2.up_proj.weight", shared_up),
|
||||
("2.down_proj.weight", shared_down),
|
||||
]
|
||||
elif load_path == "fused_gate_up":
|
||||
weights += [
|
||||
("2.gate_up_proj.weight", torch.cat([shared_gate, shared_up])),
|
||||
("2.down_proj.weight", shared_down),
|
||||
]
|
||||
list(layer.load_weights(weights))
|
||||
|
||||
if load_path == "weight_loader":
|
||||
for stem, shard_id, weight in (
|
||||
("w13", "w1", shared_gate),
|
||||
("w13", "w3", shared_up),
|
||||
("w2", "w2", shared_down),
|
||||
):
|
||||
param = getattr(layer, f"{stem}_weight")
|
||||
param.weight_loader(
|
||||
param, weight, f"{layer.layer_name}.{stem}_weight", shard_id, 2
|
||||
)
|
||||
|
||||
expected_gate, expected_gate_scale = mxfp4_quantize(shared_gate)
|
||||
expected_up, expected_up_scale = mxfp4_quantize(shared_up)
|
||||
expected_down, expected_down_scale = mxfp4_quantize(shared_down)
|
||||
assert torch.equal(layer.w13_weight[2, :intermediate_size], expected_gate)
|
||||
assert torch.equal(layer.w13_weight[2, intermediate_size:], expected_up)
|
||||
assert torch.equal(layer.w2_weight[2], expected_down)
|
||||
assert torch.equal(
|
||||
layer.w13_weight_scale[2, :intermediate_size], expected_gate_scale
|
||||
)
|
||||
assert torch.equal(layer.w13_weight_scale[2, intermediate_size:], expected_up_scale)
|
||||
assert torch.equal(layer.w2_weight_scale[2], expected_down_scale)
|
||||
|
||||
|
||||
def test_online_shared_expert_reload_compatibility(
|
||||
default_vllm_config,
|
||||
dist_init,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
"""Reload a BF16 shared weight into its MXFP4 slot via meta staging."""
|
||||
default_vllm_config.model_config = ModelConfig()
|
||||
online_config = OnlineQuantizationConfig(
|
||||
QuantizationConfigArgs(targets={"*shared_expert*": "mxfp4"})
|
||||
)
|
||||
quant_config = QuarkConfig(_QUARK_MXFP4_CONFIG)
|
||||
quant_config.online_quantization_config = online_config
|
||||
|
||||
with torch.device(current_platform.device_type):
|
||||
layer = FusedMoEFactory(
|
||||
num_experts=2,
|
||||
top_k=1,
|
||||
hidden_size=64,
|
||||
intermediate_size=64,
|
||||
n_shared_experts=1,
|
||||
fuse_shared_experts=True,
|
||||
shared_expert_prefix="model.layers.0.mlp.shared_expert",
|
||||
prefix="model.layers.0.mlp.experts",
|
||||
quant_config=quant_config,
|
||||
).routed_experts
|
||||
monkeypatch.setattr(
|
||||
layer.quant_method, "process_weights_after_loading", lambda _: None
|
||||
)
|
||||
|
||||
routed_weights = []
|
||||
for expert_id in range(2):
|
||||
for projection in ("gate_proj", "up_proj", "down_proj"):
|
||||
weight, scale = mxfp4_quantize(
|
||||
torch.randn(64, 64, dtype=torch.bfloat16)
|
||||
)
|
||||
prefix = f"{expert_id}.{projection}"
|
||||
routed_weights.extend(
|
||||
[(f"{prefix}.weight", weight), (f"{prefix}.weight_scale", scale)]
|
||||
)
|
||||
|
||||
shared_gate = torch.randn(64, 64, dtype=torch.bfloat16)
|
||||
shared_up = torch.randn(64, 64, dtype=torch.bfloat16)
|
||||
shared_down = torch.randn(64, 64, dtype=torch.bfloat16)
|
||||
|
||||
def checkpoint_weights(gate: torch.Tensor):
|
||||
return [
|
||||
*routed_weights,
|
||||
("2.gate_proj.weight", gate),
|
||||
("2.up_proj.weight", shared_up),
|
||||
("2.down_proj.weight", shared_down),
|
||||
]
|
||||
|
||||
list(layer.load_weights(checkpoint_weights(shared_gate)))
|
||||
old_weight = layer.w13_weight[2, :64].clone()
|
||||
reloaded_gate = shared_gate + 1
|
||||
expected_weight, expected_scale = mxfp4_quantize(reloaded_gate)
|
||||
assert not torch.equal(old_weight, expected_weight)
|
||||
|
||||
record_metadata_for_reloading(layer)
|
||||
initialize_layerwise_reload(layer)
|
||||
|
||||
assert layer.w13_weight.is_meta
|
||||
list(layer.load_weights(checkpoint_weights(reloaded_gate)))
|
||||
finalize_layerwise_reload(layer, default_vllm_config.model_config)
|
||||
assert not layer.w13_weight.is_meta
|
||||
assert torch.equal(layer.w13_weight[2, :64], expected_weight)
|
||||
assert torch.equal(layer.w13_weight_scale[2, :64], expected_scale)
|
||||
|
||||
|
||||
# TODO: Add DeepseekV4ForConditionalGeneration once it uses
|
||||
# resolve_layer_fused_shared_expert.
|
||||
@pytest.mark.parametrize(
|
||||
"architecture",
|
||||
[
|
||||
"AXK1ForCausalLM",
|
||||
"DeepseekForCausalLM",
|
||||
"DeepseekV2ForCausalLM",
|
||||
"DeepseekV32ForCausalLM",
|
||||
"DeepseekV3ForCausalLM",
|
||||
"Glm4MoeForCausalLM",
|
||||
"Glm4MoeLiteForCausalLM",
|
||||
"Glm5NextForCausalLM",
|
||||
"GlmMoeDsaForCausalLM",
|
||||
"MiniMaxM3SparseForCausalLM",
|
||||
"Qwen3NextForCausalLM",
|
||||
"Qwen3_5MoeForCausalLM",
|
||||
"Qwen4ExpForCausalLM",
|
||||
],
|
||||
)
|
||||
def test_online_quantization(
|
||||
architecture: str,
|
||||
tmp_path: Path,
|
||||
monkeypatch,
|
||||
dist_init,
|
||||
workspace_init,
|
||||
) -> None:
|
||||
"""Online MXFP4 fuses each architecture's shared expert into its MoE."""
|
||||
monkeypatch.setenv("VLLM_ROCM_USE_AITER", "1")
|
||||
monkeypatch.setenv("VLLM_ROCM_USE_AITER_FUSION_SHARED_EXPERTS", "1")
|
||||
rocm_aiter_ops.refresh_env_variables()
|
||||
if architecture == "Glm5NextForCausalLM":
|
||||
from vllm.models.glm5next.common import model as glm5_next_model
|
||||
|
||||
for attention in ("Glm5NextLinearAttention", "Glm5NextMLAAttention"):
|
||||
monkeypatch.setattr(glm5_next_model, attention, _StubAttention)
|
||||
monkeypatch.setattr(
|
||||
glm5_next_model, "_fused_shared_experts_tuned", lambda _: True
|
||||
)
|
||||
elif architecture == "Glm4MoeLiteForCausalLM":
|
||||
from vllm.model_executor.models import glm4_moe_lite
|
||||
|
||||
monkeypatch.setattr(glm4_moe_lite, "Glm4MoeLiteAttention", _StubAttention)
|
||||
monkeypatch.setattr(glm4_moe_lite, "Glm4MoeLiteMLAAttention", _StubAttention)
|
||||
elif architecture == "DeepseekV32ForCausalLM":
|
||||
from vllm.models.deepseek_v32.amd import model as deepseek_v32_model
|
||||
|
||||
monkeypatch.setattr(
|
||||
deepseek_v32_model, "DeepseekV32MLAAttention", _StubAttention
|
||||
)
|
||||
elif architecture == "Qwen3_5MoeForCausalLM":
|
||||
from vllm.model_executor.models import qwen3_5
|
||||
|
||||
monkeypatch.setattr(qwen3_5, "Qwen3NextAttention", _StubAttention)
|
||||
elif architecture == "Qwen4ExpForCausalLM":
|
||||
from vllm.models.qwen4_exp.amd import model as qwen4_exp_model
|
||||
|
||||
monkeypatch.setattr(qwen4_exp_model, "Qwen3NextAttention", _StubAttention)
|
||||
_write_minimal_moe_config(tmp_path, architecture)
|
||||
expected_shared_expert_name = (
|
||||
"shared_expert" if architecture.startswith("Qwen") else "shared_experts"
|
||||
)
|
||||
target_pattern = f"*{expected_shared_expert_name}*"
|
||||
|
||||
logged_messages: list[str] = []
|
||||
logged_warnings: list[str] = []
|
||||
|
||||
def record_info(message: str, *args: object, **_kwargs: object) -> None:
|
||||
if "Quantizing " in message and "of types" in message:
|
||||
logged_messages.append(message % args)
|
||||
|
||||
def record_warning(message: str, *args: object, **_kwargs: object) -> None:
|
||||
logged_warnings.append(message % args)
|
||||
|
||||
monkeypatch.setattr(
|
||||
"vllm.model_executor.model_loader.base_loader.logger.info", record_info
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"vllm.model_executor.layers.fused_moe.utils.logger.warning", record_warning
|
||||
)
|
||||
|
||||
model, vllm_config = load_model_without_vllm_runner(
|
||||
str(tmp_path),
|
||||
dtype="bfloat16",
|
||||
quantization="quark",
|
||||
model_config_kwargs={
|
||||
"quantization_config": resolve_quantization_config(
|
||||
"quark", {"targets": {target_pattern: "mxfp4"}}
|
||||
)
|
||||
},
|
||||
model_loader_cls=DummyModelLoader,
|
||||
)
|
||||
expected_model_cls, resolved_architecture = get_model_architecture(
|
||||
vllm_config.model_config
|
||||
)
|
||||
assert resolved_architecture == architecture
|
||||
assert isinstance(model, expected_model_cls)
|
||||
|
||||
fused_moes = [
|
||||
module
|
||||
for module in model.modules()
|
||||
if getattr(module, "is_fused_shared_expert_enabled", False)
|
||||
and hasattr(module, "experts")
|
||||
]
|
||||
assert len(fused_moes) == 1, f"{architecture} did not enable FSE"
|
||||
moe = fused_moes[0]
|
||||
shared_expert_name = (
|
||||
"shared_expert" if hasattr(moe, "shared_expert") else "shared_experts"
|
||||
)
|
||||
assert shared_expert_name == expected_shared_expert_name
|
||||
assert getattr(moe, shared_expert_name) is None
|
||||
|
||||
routed_experts = moe.experts.routed_experts
|
||||
assert isinstance(routed_experts.quant_method, QuarkOCP_MX_MoEMethod)
|
||||
shared_expert_prefix = routed_experts.moe_config.shared_expert_prefix
|
||||
assert shared_expert_prefix is not None
|
||||
assert shared_expert_prefix.endswith(shared_expert_name)
|
||||
assert routed_experts._fused_shared_expert_quantizer is not None
|
||||
expert_map_manager = routed_experts.expert_map_manager
|
||||
assert expert_map_manager.num_fused_shared_experts == 1
|
||||
assert expert_map_manager.map_global_to_local(
|
||||
routed_experts.moe_config.num_experts
|
||||
) == (routed_experts.moe_config.num_experts)
|
||||
expected_weight_dtype = {
|
||||
Mxfp4MoeBackend.AITER_MXFP4_BF16: torch.float4_e2m1fn_x2,
|
||||
Mxfp4MoeBackend.AITER_MXFP4_MXFP4: torch.float4_e2m1fn_x2,
|
||||
Mxfp4MoeBackend.EMULATION: torch.uint8,
|
||||
}[routed_experts.quant_method.mxfp4_backend]
|
||||
assert routed_experts.w13_weight.dtype == expected_weight_dtype
|
||||
assert routed_experts.w13_weight_scale.dtype == torch.uint8
|
||||
assert routed_experts.w2_weight.dtype == expected_weight_dtype
|
||||
assert routed_experts.w2_weight_scale.dtype == torch.uint8
|
||||
assert vllm_config.quant_config is not None
|
||||
assert vllm_config.quant_config.online_quantization_config is not None
|
||||
assert not any(
|
||||
"VLLM_ROCM_USE_AITER_FUSION_SHARED_EXPERTS is enabled but "
|
||||
"cannot be enabled" in warning
|
||||
for warning in logged_warnings
|
||||
)
|
||||
if architecture == "MiniMaxM3SparseForCausalLM":
|
||||
# TODO: Use resolve_layer_fused_shared_expert in MiniMax M3 and remove
|
||||
# this branch once its virtual fused projections are registered.
|
||||
assert logged_messages == ["Quantizing 0 layers of types: "]
|
||||
else:
|
||||
assert logged_messages == [
|
||||
"Quantizing 2 layers of types: "
|
||||
f"mlp.{shared_expert_name}.down_proj: 1 "
|
||||
f"(from targets: {target_pattern}, mxfp4); "
|
||||
f"mlp.{shared_expert_name}.gate_up_proj: 1 "
|
||||
f"(from targets: {target_pattern}, mxfp4)"
|
||||
]
|
||||
|
||||
|
||||
def test_online_gate_up_proj_target_disables_fse(
|
||||
tmp_path: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
dist_init,
|
||||
workspace_init,
|
||||
) -> None:
|
||||
"""A partial shared-expert target quantizes while disabling FSE."""
|
||||
architecture = "DeepseekForCausalLM"
|
||||
target_pattern = "*shared_experts.gate_up_proj*"
|
||||
monkeypatch.setenv("VLLM_ROCM_USE_AITER", "1")
|
||||
monkeypatch.setenv("VLLM_ROCM_USE_AITER_FUSION_SHARED_EXPERTS", "1")
|
||||
rocm_aiter_ops.refresh_env_variables()
|
||||
_write_minimal_moe_config(tmp_path, architecture)
|
||||
|
||||
logged_messages: list[str] = []
|
||||
logged_warnings: list[str] = []
|
||||
|
||||
def record_info(message: str, *args: object, **_kwargs: object) -> None:
|
||||
if "Quantizing " in message and "of types" in message:
|
||||
logged_messages.append(message % args)
|
||||
|
||||
def record_warning(message: str, *args: object, **_kwargs: object) -> None:
|
||||
logged_warnings.append(message % args)
|
||||
|
||||
monkeypatch.setattr(
|
||||
"vllm.model_executor.model_loader.base_loader.logger.info", record_info
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"vllm.model_executor.layers.fused_moe.utils.logger.warning", record_warning
|
||||
)
|
||||
|
||||
model, _ = load_model_without_vllm_runner(
|
||||
str(tmp_path),
|
||||
dtype="bfloat16",
|
||||
quantization="quark",
|
||||
model_config_kwargs={
|
||||
"quantization_config": resolve_quantization_config(
|
||||
"quark", {"targets": {target_pattern: "mxfp4"}}
|
||||
)
|
||||
},
|
||||
model_loader_cls=DummyModelLoader,
|
||||
)
|
||||
|
||||
moe = next(
|
||||
module
|
||||
for module in model.modules()
|
||||
if hasattr(module, "is_fused_shared_expert_enabled")
|
||||
and hasattr(module, "shared_experts")
|
||||
)
|
||||
assert not moe.is_fused_shared_expert_enabled
|
||||
assert moe.shared_experts is not None
|
||||
assert logged_messages == [
|
||||
"Quantizing 1 layers of types: "
|
||||
"mlp.shared_experts.gate_up_proj: 1 "
|
||||
f"(from targets: {target_pattern}, mxfp4)"
|
||||
]
|
||||
assert logged_warnings == [
|
||||
"VLLM_ROCM_USE_AITER_FUSION_SHARED_EXPERTS is enabled but cannot be "
|
||||
"enabled - skipping for this layer: online quantization targets only "
|
||||
"part of the shared expert at model.layers.0.mlp.shared_experts."
|
||||
]
|
||||
@@ -2,8 +2,6 @@
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
"""Unit tests for QuantizationConfigArgs parsing."""
|
||||
|
||||
from unittest.mock import Mock
|
||||
|
||||
import pytest
|
||||
|
||||
from tests.quantization.utils import quant_config_args, quant_spec
|
||||
@@ -244,6 +242,4 @@ def test_targets_reject_moe_only_shorthand_for_linear_layer():
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError, match="does not define a QuantSpec"):
|
||||
config.resolve_quant_method_cls(
|
||||
Mock(spec=LinearBase), "model.layers.0.self_attn.o_proj"
|
||||
)
|
||||
config.resolve_quant_method_cls(LinearBase, "model.layers.0.self_attn.o_proj")
|
||||
|
||||
@@ -15,7 +15,11 @@ from vllm.config import (
|
||||
VllmConfig,
|
||||
set_current_vllm_config,
|
||||
)
|
||||
from vllm.config.quantization import QuantizationConfigArgs, QuantSpec
|
||||
from vllm.config.quantization import (
|
||||
QuantizationConfigArgs,
|
||||
QuantSpec,
|
||||
resolve_quantization_config,
|
||||
)
|
||||
from vllm.model_executor.layers.quantization import get_quantization_config
|
||||
from vllm.model_executor.model_loader.default_loader import DefaultModelLoader
|
||||
from vllm.platforms import current_platform
|
||||
@@ -40,10 +44,17 @@ def _limit_num_hidden_layers(
|
||||
original_load_weights = model.load_weights
|
||||
|
||||
def should_load_weight(name: str) -> bool:
|
||||
for prefix in ("model.layers.", "layers."):
|
||||
if name.startswith(prefix):
|
||||
layer_idx = int(name.removeprefix(prefix).split(".", 1)[0])
|
||||
return layer_idx < num_hidden_layers
|
||||
layers_prefix = "layers."
|
||||
if name.startswith(layers_prefix):
|
||||
layer_name = name.removeprefix(layers_prefix)
|
||||
elif ".layers." in name:
|
||||
layer_name = name.split(".layers.", maxsplit=1)[1]
|
||||
else:
|
||||
return True
|
||||
|
||||
layer_idx_str = layer_name.split(".", maxsplit=1)[0]
|
||||
if layer_idx_str.isdigit():
|
||||
return int(layer_idx_str) < num_hidden_layers
|
||||
return True
|
||||
|
||||
def load_weights(weights):
|
||||
@@ -71,13 +82,19 @@ def load_model_without_vllm_runner(
|
||||
quantization=quantization,
|
||||
**(model_config_kwargs or {}),
|
||||
)
|
||||
model_config.quantization_config = resolve_quantization_config(
|
||||
model_config.quantization, model_config.quantization_config
|
||||
)
|
||||
vllm_config_args = dict(vllm_config_kwargs or {})
|
||||
vllm_config_args.setdefault(
|
||||
"compilation_config", CompilationConfig(mode=CompilationMode.NONE)
|
||||
)
|
||||
vllm_config = VllmConfig(model_config=model_config, **vllm_config_args)
|
||||
hf_overrides = (model_config_kwargs or {}).get("hf_overrides") or {}
|
||||
num_hidden_layers = hf_overrides.get("num_hidden_layers")
|
||||
text_config_overrides = hf_overrides.get("text_config") or {}
|
||||
num_hidden_layers = text_config_overrides.get(
|
||||
"num_hidden_layers", hf_overrides.get("num_hidden_layers")
|
||||
)
|
||||
|
||||
with set_current_vllm_config(vllm_config):
|
||||
model_loader = model_loader_cls(vllm_config.load_config)
|
||||
|
||||
@@ -1293,6 +1293,7 @@ class FusedMoEConfig:
|
||||
has_bias: bool = False
|
||||
is_lora_enabled: bool = False
|
||||
has_hash_routing: bool = False
|
||||
shared_expert_prefix: str | None = None
|
||||
|
||||
# When True, the MoE skips its final cross-rank all-reduce (and the separate
|
||||
# shared-expert reduce), returning the partial per-rank sum. The caller is
|
||||
|
||||
@@ -122,6 +122,7 @@ def FusedMoEFactory(
|
||||
is_fused_checkpoint_transposed: bool = False,
|
||||
n_shared_experts: int | None = None,
|
||||
fuse_shared_experts: bool = False,
|
||||
shared_expert_prefix: str | None = None,
|
||||
router_logits_dtype: torch.dtype | None = None,
|
||||
gate: torch.nn.Module | None = None,
|
||||
shared_experts: torch.nn.Module | None = None,
|
||||
@@ -197,6 +198,7 @@ def FusedMoEFactory(
|
||||
n_shared_experts: Number of shared experts to fuse into the routed
|
||||
grouped GEMM (ROCm; requires aiter FSE or the router-append path)
|
||||
fuse_shared_experts: Whether to enable shared-expert fusion.
|
||||
shared_expert_prefix: Checkpoint prefix for the fused shared expert.
|
||||
router_logits_dtype: Data type for router logits buffers
|
||||
gate: Pre-configured gate module
|
||||
shared_experts: Pre-configured shared experts module
|
||||
@@ -360,6 +362,7 @@ def FusedMoEFactory(
|
||||
elastic_ep_max_dp_size=vllm_config.parallel_config.elastic_ep_max_dp_size,
|
||||
has_bias=has_bias,
|
||||
is_lora_enabled=vllm_config.lora_config is not None,
|
||||
shared_expert_prefix=shared_expert_prefix,
|
||||
activation=moe_activation,
|
||||
device=vllm_config.device_config.device,
|
||||
routing_method=router.routing_method_type, # Not ideal
|
||||
|
||||
@@ -26,6 +26,7 @@ from vllm.model_executor.layers.fused_moe.routed_experts_capturer import (
|
||||
from vllm.model_executor.layers.fused_moe.unquantized_fused_moe_method import (
|
||||
UnquantizedFusedMoEMethod,
|
||||
)
|
||||
from vllm.model_executor.layers.linear import LinearBase
|
||||
from vllm.model_executor.layers.quantization.base_config import (
|
||||
QuantizationConfig,
|
||||
resolve_quant_method,
|
||||
@@ -153,6 +154,7 @@ class RoutedExperts(PluggableLayer):
|
||||
self.apply_router_weight_on_input = apply_router_weight_on_input
|
||||
# End random parameters
|
||||
self._loaded_expert_biases: set[str] = set()
|
||||
self._fused_shared_expert_quantizer = self._get_fused_shared_expert_quantizer()
|
||||
|
||||
self.quant_method = self._get_quant_method(
|
||||
self.layer_name,
|
||||
@@ -630,6 +632,31 @@ class RoutedExperts(PluggableLayer):
|
||||
# _to_scalar's reshape(()) would reject the size-2 weight_shape.
|
||||
param_data[expert_id] = loaded_weight
|
||||
|
||||
def _get_fused_shared_expert_quantizer(
|
||||
self,
|
||||
) -> Callable[[torch.Tensor], tuple[torch.Tensor, torch.Tensor]] | None:
|
||||
"""Return the quantizer for an online-quantized fused shared expert."""
|
||||
online_config = getattr(self.quant_config, "online_quantization_config", None)
|
||||
prefix = self.moe_config.shared_expert_prefix
|
||||
if (
|
||||
online_config is None
|
||||
or prefix is None
|
||||
or self.expert_map_manager.num_fused_shared_experts == 0
|
||||
):
|
||||
return None
|
||||
# FSE compatibility checks require every shared-expert projection to
|
||||
# use the routed experts' weight key.
|
||||
resolved = online_config.resolve_quant_method_cls(
|
||||
LinearBase, f"{prefix}.down_proj"
|
||||
)
|
||||
if resolved is None:
|
||||
return None
|
||||
from vllm.model_executor.layers.quantization.online.base import (
|
||||
ONLINE_SHARED_EXPERT_QUANTIZERS,
|
||||
)
|
||||
|
||||
return ONLINE_SHARED_EXPERT_QUANTIZERS[resolved[3].weight]
|
||||
|
||||
@overload
|
||||
def weight_loader(
|
||||
self,
|
||||
@@ -687,6 +714,35 @@ class RoutedExperts(PluggableLayer):
|
||||
return False if return_success else None
|
||||
# Hereafter, `expert_id` is local physical id
|
||||
|
||||
# A full-precision fused shared expert is quantized into the checkpoint
|
||||
# layout, then loaded like a pre-quantized expert weight and scale.
|
||||
if (
|
||||
self._fused_shared_expert_quantizer is not None
|
||||
and self.moe_config.num_logical_experts
|
||||
<= global_expert_id
|
||||
< self.moe_config.num_logical_experts
|
||||
+ self.expert_map_manager.num_fused_shared_experts
|
||||
and weight_name.endswith("_weight")
|
||||
and loaded_weight.is_floating_point()
|
||||
and loaded_weight.dtype != param.dtype
|
||||
):
|
||||
weight, weight_scale = self._fused_shared_expert_quantizer(
|
||||
loaded_weight.to(self.moe_config.device)
|
||||
)
|
||||
stem = "w2" if shard_id == "w2" else "w13"
|
||||
self.weight_loader(
|
||||
getattr(self, f"{stem}_weight_scale"),
|
||||
weight_scale,
|
||||
f"{weight_name}_scale",
|
||||
shard_id,
|
||||
global_expert_id,
|
||||
return_success=True,
|
||||
)
|
||||
loaded = self.weight_loader(
|
||||
param, weight, weight_name, shard_id, global_expert_id, True
|
||||
)
|
||||
return loaded if return_success else None
|
||||
|
||||
# is_transposed: if the dim to shard the weight
|
||||
# should be flipped. Required by GPTQ/AWQ (K-first format).
|
||||
# should be whatever dimension intermediate_size_per_partition is
|
||||
|
||||
@@ -84,6 +84,40 @@ def resolve_layer_fused_shared_expert(
|
||||
else (True, None)
|
||||
)
|
||||
is_fused_shared_expert_enabled = fse_requested and fse_compatible
|
||||
|
||||
# In online shared_expert quantization is used, register it to
|
||||
# `online_quantization_config.quantized_layers` ahead of weight loading.
|
||||
if is_fused_shared_expert_enabled and quant_config is not None:
|
||||
online_quantization_config = quant_config.online_quantization_config
|
||||
if online_quantization_config is not None:
|
||||
from vllm.model_executor.layers.linear import (
|
||||
LinearBase,
|
||||
UnquantizedLinearMethod,
|
||||
)
|
||||
|
||||
for projection_name in ("gate_up_proj", "down_proj"):
|
||||
projection_prefix = f"{prefix}.{shared_expert_name}.{projection_name}"
|
||||
quant_method_metadata = (
|
||||
online_quantization_config.resolve_quant_method_cls(
|
||||
LinearBase, projection_prefix
|
||||
)
|
||||
)
|
||||
|
||||
if quant_method_metadata is None:
|
||||
continue
|
||||
|
||||
source, quant_key_str, target_pattern, _, quant_method_cls = (
|
||||
quant_method_metadata
|
||||
)
|
||||
|
||||
if quant_method_cls in (None, UnquantizedLinearMethod):
|
||||
continue
|
||||
|
||||
online_quantization_config.quantized_layers[projection_prefix] = (
|
||||
source.value,
|
||||
quant_key_str,
|
||||
target_pattern,
|
||||
)
|
||||
if fse_requested and not is_fused_shared_expert_enabled:
|
||||
logger.warning(
|
||||
"VLLM_ROCM_USE_AITER_FUSION_SHARED_EXPERTS is enabled but "
|
||||
|
||||
@@ -320,7 +320,7 @@ def resolve_quant_method(
|
||||
base_quant_method, (UnquantizedLinearMethod, UnquantizedFusedMoEMethod)
|
||||
)
|
||||
online_target = quant_config.online_quantization_config.resolve_quant_method_cls(
|
||||
layer, prefix
|
||||
type(layer), prefix
|
||||
)
|
||||
|
||||
if checkpoint_is_quantized:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
|
||||
from collections.abc import Mapping
|
||||
from collections.abc import Callable, Mapping
|
||||
from enum import Enum
|
||||
from types import MappingProxyType
|
||||
from typing import Any
|
||||
@@ -60,6 +60,7 @@ from vllm.model_executor.layers.quantization.utils.config_utils import (
|
||||
find_matching_patterns,
|
||||
get_layer_name_after_index,
|
||||
)
|
||||
from vllm.model_executor.layers.quantization.utils.mxfp4_utils import mxfp4_quantize
|
||||
from vllm.model_executor.layers.quantization.utils.quant_utils import (
|
||||
QuantKey,
|
||||
kFp8Static128BlockSym,
|
||||
@@ -104,6 +105,15 @@ _ONLINE_MOE_METHODS: dict[QuantKey, type] = {
|
||||
kNvfp4Static: Nvfp4OnlineMoEMethod,
|
||||
}
|
||||
|
||||
# Quantizers for full-precision fused shared-expert weights, keyed by the routed
|
||||
# experts' weight QuantKey. Each shard is quantized to the checkpoint layout on
|
||||
# arrival, so only block-local schemes are supported.
|
||||
ONLINE_SHARED_EXPERT_QUANTIZERS: dict[
|
||||
QuantKey, Callable[[torch.Tensor], tuple[torch.Tensor, torch.Tensor]]
|
||||
] = {
|
||||
kMxfp4Static: mxfp4_quantize,
|
||||
}
|
||||
|
||||
|
||||
def _find_matching_targets(
|
||||
prefix: str,
|
||||
@@ -217,14 +227,14 @@ class OnlineQuantizationConfig(QuantizationConfig):
|
||||
self,
|
||||
spec: QuantSpec | None,
|
||||
table: dict[QuantKey, type],
|
||||
layer: torch.nn.Module,
|
||||
layer_cls: type[torch.nn.Module],
|
||||
) -> type | None:
|
||||
"""Resolve the online method class for a layer's quantization spec.
|
||||
|
||||
Args:
|
||||
spec: Quantization specification to resolve.
|
||||
table: Mapping from weight quantization keys to method classes.
|
||||
layer: Layer that will use the resolved method.
|
||||
layer_cls: Type of the layer that will use the resolved method.
|
||||
|
||||
Returns:
|
||||
The matching method class, or None when ``spec`` has no weight
|
||||
@@ -239,7 +249,7 @@ class OnlineQuantizationConfig(QuantizationConfig):
|
||||
cls = table.get(spec.weight)
|
||||
if cls is None:
|
||||
raise ValueError(
|
||||
f"online quantization for {type(layer).__name__} with "
|
||||
f"online quantization for {layer_cls.__name__} with "
|
||||
f"weight={spec.weight} is not supported; supported weight "
|
||||
f"keys: {sorted(str(k) for k in table)}"
|
||||
)
|
||||
@@ -254,12 +264,12 @@ class OnlineQuantizationConfig(QuantizationConfig):
|
||||
return cls
|
||||
|
||||
def resolve_quant_method_cls(
|
||||
self, layer: torch.nn.Module, prefix: str
|
||||
self, layer_cls: type[torch.nn.Module], prefix: str
|
||||
) -> tuple[OnlineQuantizationSource, str, str | None, QuantSpec, type] | None:
|
||||
"""Resolve quantization metadata and method class without instantiating it.
|
||||
|
||||
Args:
|
||||
layer: Layer for which to resolve online quantization.
|
||||
layer_cls: Type of layer for which to resolve online quantization.
|
||||
prefix: Fully qualified layer name.
|
||||
|
||||
Returns:
|
||||
@@ -271,17 +281,17 @@ class OnlineQuantizationConfig(QuantizationConfig):
|
||||
quant_spec: QuantSpec | None
|
||||
if self.args.targets is not None:
|
||||
resolved_pattern = self._resolve_targets_quant_method_metadata(
|
||||
prefix, layer
|
||||
prefix, layer_cls
|
||||
)
|
||||
if resolved_pattern is None:
|
||||
return None
|
||||
source, quant_key_str, target_pattern, quant_spec, table = resolved_pattern
|
||||
else:
|
||||
if isinstance(layer, LinearBase):
|
||||
if issubclass(layer_cls, LinearBase):
|
||||
source = OnlineQuantizationSource.linear
|
||||
quant_spec = self.args.linear
|
||||
table = _ONLINE_LINEAR_METHODS
|
||||
elif isinstance(layer, RoutedExperts):
|
||||
elif issubclass(layer_cls, RoutedExperts):
|
||||
source = OnlineQuantizationSource.moe
|
||||
quant_spec = self.args.moe
|
||||
table = _ONLINE_MOE_METHODS
|
||||
@@ -298,14 +308,14 @@ class OnlineQuantizationConfig(QuantizationConfig):
|
||||
quant_key_str = str(quant_spec)
|
||||
target_pattern = None
|
||||
|
||||
quant_method_cls = self._get_method_cls(quant_spec, table, layer)
|
||||
quant_method_cls = self._get_method_cls(quant_spec, table, layer_cls)
|
||||
if quant_method_cls is None:
|
||||
return None
|
||||
assert quant_spec is not None
|
||||
return source, quant_key_str, target_pattern, quant_spec, quant_method_cls
|
||||
|
||||
def _resolve_targets_quant_method_metadata(
|
||||
self, prefix: str, layer: torch.nn.Module
|
||||
self, prefix: str, layer_cls: type[torch.nn.Module]
|
||||
) -> (
|
||||
tuple[OnlineQuantizationSource, str, str, QuantSpec, dict[QuantKey, type]]
|
||||
| None
|
||||
@@ -314,7 +324,7 @@ class OnlineQuantizationConfig(QuantizationConfig):
|
||||
|
||||
Args:
|
||||
prefix: Fully qualified layer name.
|
||||
layer: Layer matched against configured target patterns.
|
||||
layer_cls: Type of layer matched against configured target patterns.
|
||||
|
||||
Returns:
|
||||
A tuple of source, quantization key string, target pattern, spec,
|
||||
@@ -349,22 +359,22 @@ class OnlineQuantizationConfig(QuantizationConfig):
|
||||
target_pattern = matches[0]
|
||||
quant_key_str = self.args.targets[target_pattern]
|
||||
shorthand = _ONLINE_SHORTHANDS[quant_key_str]
|
||||
if isinstance(layer, LinearBase):
|
||||
if issubclass(layer_cls, LinearBase):
|
||||
quant_spec = shorthand.linear
|
||||
table = _ONLINE_LINEAR_METHODS
|
||||
elif isinstance(layer, RoutedExperts):
|
||||
elif issubclass(layer_cls, RoutedExperts):
|
||||
quant_spec = shorthand.moe
|
||||
table = _ONLINE_MOE_METHODS
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Layer {prefix} was matched by quantization_config.targets "
|
||||
f"({target_pattern}), but online quantization is not supported for "
|
||||
f"{type(layer).__name__}."
|
||||
f"{layer_cls.__name__}."
|
||||
)
|
||||
if quant_spec is None:
|
||||
raise ValueError(
|
||||
f"targets pattern {target_pattern} = {quant_key_str} does "
|
||||
f"not define a QuantSpec for {type(layer).__name__} layers "
|
||||
f"not define a QuantSpec for {layer_cls.__name__} layers "
|
||||
f"(matched at {prefix})."
|
||||
)
|
||||
return (
|
||||
@@ -379,7 +389,7 @@ class OnlineQuantizationConfig(QuantizationConfig):
|
||||
self, layer: torch.nn.Module, prefix: str
|
||||
) -> "QuantizeMethodBase | None":
|
||||
# `targets` takes precedence over `moe` and `linear` and is exclusive.
|
||||
resolved = self.resolve_quant_method_cls(layer, prefix)
|
||||
resolved = self.resolve_quant_method_cls(type(layer), prefix)
|
||||
if resolved is not None:
|
||||
source, quant_key_str, target_pattern, _, quant_method_cls = resolved
|
||||
self.quantized_layers[prefix] = (
|
||||
|
||||
@@ -12,8 +12,6 @@ if TYPE_CHECKING:
|
||||
FusedMoEQuantConfig,
|
||||
)
|
||||
from vllm.model_executor.layers.fused_moe.oracle.fp8 import Fp8MoeBackend
|
||||
from vllm.model_executor.layers.quantization.utils.quant_utils import QuantKey
|
||||
|
||||
import vllm.envs as envs
|
||||
from vllm import _custom_ops as ops
|
||||
from vllm.config import get_current_vllm_config
|
||||
@@ -40,6 +38,7 @@ from vllm.model_executor.layers.quantization.online.moe_base import (
|
||||
)
|
||||
from vllm.model_executor.layers.quantization.utils.quant_utils import (
|
||||
GroupShape,
|
||||
QuantKey,
|
||||
amax_for_moe_weight_quant,
|
||||
amax_for_tp_weight_quant,
|
||||
create_fp8_quant_key,
|
||||
@@ -122,6 +121,7 @@ class OnlineLinearBase(LinearMethodBase):
|
||||
weights onto meta device and materializes them just-in-time."""
|
||||
|
||||
uses_meta_device: bool = True
|
||||
activation_quant_key: QuantKey | None
|
||||
|
||||
def __init__(self):
|
||||
self.out_dtype = torch.get_default_dtype()
|
||||
@@ -203,6 +203,10 @@ class Fp8PerTensorOnlineLinearMethod(OnlineLinearBase):
|
||||
"""Online tensorwise FP8 linear quantization.
|
||||
Loads fp16/bf16 weights and quantizes them per-tensor during loading."""
|
||||
|
||||
activation_quant_key = (
|
||||
kFp8DynamicTokenSym if cutlass_fp8_supported() else kFp8DynamicTensorSym
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
@@ -211,11 +215,6 @@ class Fp8PerTensorOnlineLinearMethod(OnlineLinearBase):
|
||||
self.use_marlin = False
|
||||
self.marlin_input_dtype = None
|
||||
self.weight_quant_key = kFp8StaticTensorSym
|
||||
# Use per-token quantization for better perf if dynamic and cutlass
|
||||
if cutlass_fp8_supported():
|
||||
self.activation_quant_key = kFp8DynamicTokenSym
|
||||
else:
|
||||
self.activation_quant_key = kFp8DynamicTensorSym
|
||||
|
||||
def create_weights(
|
||||
self,
|
||||
@@ -237,6 +236,8 @@ class Fp8PerTensorOnlineLinearMethod(OnlineLinearBase):
|
||||
**extra_weight_attrs,
|
||||
)
|
||||
|
||||
# TODO: init_fp8_linear_kernel should support activation_quant_key=None
|
||||
assert self.activation_quant_key is not None
|
||||
self.fp8_linear = init_fp8_linear_kernel(
|
||||
activation_quant_key=self.activation_quant_key,
|
||||
weight_quant_key=self.weight_quant_key,
|
||||
@@ -305,13 +306,11 @@ class Fp8PerBlockOnlineLinearMethod(OnlineLinearBase):
|
||||
"""Online blockwise FP8 linear quantization.
|
||||
Loads fp16/bf16 weights and quantizes them per-block during loading."""
|
||||
|
||||
activation_quant_key = kFp8Dynamic128Sym
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.weight_block_size = [128, 128]
|
||||
self.activation_quant_key = create_fp8_quant_key(
|
||||
static=False,
|
||||
group_shape=GroupShape(1, self.weight_block_size[0]),
|
||||
)
|
||||
self.weight_quant_key = create_fp8_quant_key(
|
||||
static=True, group_shape=GroupShape(*self.weight_block_size)
|
||||
)
|
||||
@@ -337,6 +336,8 @@ class Fp8PerBlockOnlineLinearMethod(OnlineLinearBase):
|
||||
)
|
||||
layer.weight_block_size = self.weight_block_size
|
||||
|
||||
# TODO: init_fp8_linear_kernel should support activation_quant_key=None
|
||||
assert self.activation_quant_key is not None
|
||||
self.fp8_linear = init_fp8_linear_kernel(
|
||||
activation_quant_key=self.activation_quant_key,
|
||||
weight_quant_key=self.weight_quant_key,
|
||||
|
||||
@@ -9,6 +9,7 @@ import torch
|
||||
from torch.nn import Module
|
||||
|
||||
from vllm.model_executor.layers.fused_moe.config import FusedMoEConfig
|
||||
from vllm.model_executor.layers.quantization.utils.quant_utils import QuantKey
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import vllm.model_executor.layers.fused_moe.modular_kernel as mk
|
||||
@@ -42,6 +43,8 @@ class Mxfp8OnlineLinearMethod(OnlineLinearBase):
|
||||
FP8 with block-32 scales) during weight loading.
|
||||
"""
|
||||
|
||||
activation_quant_key: QuantKey | None = None
|
||||
|
||||
def create_weights(
|
||||
self,
|
||||
layer: torch.nn.Module,
|
||||
|
||||
@@ -8,9 +8,13 @@ from typing import TYPE_CHECKING
|
||||
|
||||
import regex as re
|
||||
|
||||
from vllm.logger import init_logger
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from vllm.model_executor.layers.quantization.base_config import QuantizationConfig
|
||||
|
||||
logger = init_logger(__name__)
|
||||
|
||||
|
||||
def get_quark_ocp_mx_group_size(
|
||||
quant_config: "QuantizationConfig | None",
|
||||
@@ -70,18 +74,100 @@ def is_shared_expert_quant_fse_compatible(
|
||||
A compatibility flag and, when incompatible, the reason.
|
||||
|
||||
"""
|
||||
from vllm.model_executor.layers.quantization.fp8 import Fp8Config
|
||||
from vllm.model_executor.layers.quantization.online.fp8 import OnlineLinearBase
|
||||
from vllm.model_executor.layers.quantization.quark.quark import QuarkConfig
|
||||
from vllm.model_executor.layers.quantization.utils.quant_utils import (
|
||||
is_layer_skipped,
|
||||
)
|
||||
from vllm.models.deepseek_v4.quant_config import DeepseekV4FP8Config
|
||||
|
||||
if projection_names is None:
|
||||
projection_names = ["gate_up_proj", "down_proj"]
|
||||
|
||||
if quant_config is None:
|
||||
return True, None
|
||||
|
||||
from vllm.model_executor.layers.quantization.fp8 import Fp8Config
|
||||
from vllm.model_executor.layers.quantization.quark.quark import QuarkConfig
|
||||
from vllm.model_executor.layers.quantization.utils.quant_utils import (
|
||||
is_layer_skipped,
|
||||
)
|
||||
from vllm.models.deepseek_v4.quant_config import DeepseekV4FP8Config
|
||||
online_quant_config = quant_config.online_quantization_config
|
||||
shared_expert_targets = None
|
||||
if online_quant_config is not None:
|
||||
from vllm.model_executor.layers.linear import LinearBase
|
||||
|
||||
online_quant_config.packed_modules_mapping = quant_config.packed_modules_mapping
|
||||
shared_expert_targets = [
|
||||
online_quant_config.resolve_quant_method_cls(
|
||||
LinearBase, f"{shared_expert_prefix}.{projection_name}"
|
||||
)
|
||||
for projection_name in projection_names
|
||||
]
|
||||
if all(target is None for target in shared_expert_targets):
|
||||
shared_expert_targets = None
|
||||
|
||||
if shared_expert_targets is not None:
|
||||
from vllm.model_executor.layers.fused_moe import RoutedExperts
|
||||
from vllm.model_executor.layers.linear import UnquantizedLinearMethod
|
||||
from vllm.model_executor.layers.quantization.online.base import (
|
||||
ONLINE_SHARED_EXPERT_QUANTIZERS,
|
||||
)
|
||||
|
||||
# NOTE: online shared experts quantization check is only implemented for quark
|
||||
# quant method at the moment. This can be extended here for other quant methods.
|
||||
# Requirement is:
|
||||
# - Retrieve routed weight quant key
|
||||
# - Retrieve routed activation quant key
|
||||
# and ensure the online quantization set on shared expert match them.
|
||||
if isinstance(quant_config, QuarkConfig):
|
||||
routed_weight_key, routed_activation_key, _ = (
|
||||
quant_config.get_quant_method_target(expert_prefix, RoutedExperts)
|
||||
)
|
||||
else:
|
||||
reason = (
|
||||
"online quantization FSE compatibility check is not implemented for "
|
||||
f"{type(quant_config).__name__}"
|
||||
)
|
||||
logger.warning_once(reason)
|
||||
return False, reason
|
||||
|
||||
if routed_weight_key not in ONLINE_SHARED_EXPERT_QUANTIZERS:
|
||||
return (
|
||||
False,
|
||||
"online shared-expert quantization is not supported for routed "
|
||||
f"expert weight key {routed_weight_key}",
|
||||
)
|
||||
|
||||
for shared_expert_target in shared_expert_targets:
|
||||
if shared_expert_target is None:
|
||||
return (
|
||||
False,
|
||||
"online quantization targets only part of the shared expert at "
|
||||
f"{shared_expert_prefix}",
|
||||
)
|
||||
|
||||
_, _, _, shared_quant_spec, shared_method_cls = shared_expert_target
|
||||
if shared_method_cls in (None, UnquantizedLinearMethod):
|
||||
return (
|
||||
False,
|
||||
"online quantization excludes FSE weights at "
|
||||
f"{shared_expert_prefix}",
|
||||
)
|
||||
assert issubclass(shared_method_cls, OnlineLinearBase)
|
||||
|
||||
# NOTE: We can not rely on shared_quant_spec.activation_quant_key
|
||||
# as _ONLINE_SHORTHANDS: dict[str, QuantSpec] in quantization.py
|
||||
# does not define the default activation quant key.
|
||||
# `online_quant_config.resolve_quant_method_cls` should return the
|
||||
# activation quant key as well. Only some online methods define it
|
||||
# on the class, so compare the weight key first.
|
||||
if (
|
||||
shared_quant_spec.weight != routed_weight_key
|
||||
or shared_method_cls.activation_quant_key != routed_activation_key
|
||||
):
|
||||
return (
|
||||
False,
|
||||
"online shared-expert quantization keys do not match the "
|
||||
"routed expert keys",
|
||||
)
|
||||
return True, None
|
||||
|
||||
if isinstance(quant_config, DeepseekV4FP8Config):
|
||||
from vllm.config import get_current_vllm_config
|
||||
|
||||
@@ -199,6 +199,7 @@ class AXK1MoE(nn.Module):
|
||||
if self.is_fused_shared_expert_enabled
|
||||
else None,
|
||||
fuse_shared_experts=self.is_fused_shared_expert_enabled,
|
||||
shared_expert_prefix=f"{prefix}.shared_experts",
|
||||
)
|
||||
|
||||
def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
|
||||
|
||||
@@ -396,6 +396,7 @@ class DeepseekV2MoE(nn.Module):
|
||||
if self.is_fused_shared_expert_enabled
|
||||
else None,
|
||||
fuse_shared_experts=self.is_fused_shared_expert_enabled,
|
||||
shared_expert_prefix=f"{prefix}.shared_experts",
|
||||
router_logits_dtype=self.gate.out_dtype,
|
||||
)
|
||||
|
||||
|
||||
@@ -204,6 +204,7 @@ class Glm4MoE(nn.Module):
|
||||
config.n_shared_experts if self.is_fused_shared_expert_enabled else None
|
||||
),
|
||||
fuse_shared_experts=self.is_fused_shared_expert_enabled,
|
||||
shared_expert_prefix=f"{prefix}.shared_experts",
|
||||
)
|
||||
|
||||
def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
|
||||
|
||||
@@ -232,6 +232,7 @@ class Qwen3NextSparseMoeBlock(nn.Module):
|
||||
is_sequence_parallel=self.is_sequence_parallel,
|
||||
n_shared_experts=1 if self.shared_expert is None else None,
|
||||
fuse_shared_experts=self.is_fused_shared_expert_enabled,
|
||||
shared_expert_prefix=f"{prefix}.shared_expert",
|
||||
shared_expert_gate=self.shared_expert_gate
|
||||
if self.shared_expert is None
|
||||
else None,
|
||||
|
||||
@@ -307,6 +307,7 @@ class Glm5NextMoE(nn.Module):
|
||||
if self.is_fused_shared_expert_enabled
|
||||
else None,
|
||||
fuse_shared_experts=self.is_fused_shared_expert_enabled,
|
||||
shared_expert_prefix=f"{prefix}.shared_experts",
|
||||
router_logits_dtype=self.gate.out_dtype,
|
||||
swiglu_limit=swiglu_limit,
|
||||
)
|
||||
|
||||
@@ -465,6 +465,7 @@ class MiniMaxM3MoE(nn.Module):
|
||||
self.n_shared_experts if self.is_fused_shared_expert_enabled else None
|
||||
),
|
||||
fuse_shared_experts=self.is_fused_shared_expert_enabled,
|
||||
shared_expert_prefix=f"{prefix}.shared_experts",
|
||||
quant_config=quant_config,
|
||||
prefix=f"{prefix}.experts",
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user