From e746e5bdb3ff754a7c24f9aabc028c5854532bb1 Mon Sep 17 00:00:00 2001 From: Shengliang Xu Date: Mon, 28 Sep 2026 23:33:48 +0000 Subject: [PATCH 1/4] Move Nemotron-H, Falcon and Llama4 PTQ modeling into modelopt/torch/models Introduce /modeling_ptq.py for model-specific quantized-module wrappers and registrations, and move the first three there verbatim: Nemotron-H's layerwise decoder discovery, the Falcon linear registration and _QuantLlama4TextExperts. The HF plugin imports each modeling_ptq from an explicit list, placed after the generic wrappers they build on and before the homogeneous decoder discoverer, so Nemotron-H's more specific discoverer still matches first. Package __init__.py files keep importing only specs. Signed-off-by: Shengliang Xu --- modelopt/torch/models/README.md | 16 ++++ modelopt/torch/models/__init__.py | 3 + modelopt/torch/models/falcon/modeling_ptq.py | 44 ++++++++++ modelopt/torch/models/falcon/specs.py | 2 +- modelopt/torch/models/llama4/modeling_ptq.py | 55 ++++++++++++ .../torch/models/nemotron_h/modeling_ptq.py | 52 +++++++++++ .../torch/quantization/plugins/huggingface.py | 86 +++---------------- 7 files changed, 185 insertions(+), 73 deletions(-) create mode 100644 modelopt/torch/models/falcon/modeling_ptq.py create mode 100644 modelopt/torch/models/llama4/modeling_ptq.py create mode 100644 modelopt/torch/models/nemotron_h/modeling_ptq.py diff --git a/modelopt/torch/models/README.md b/modelopt/torch/models/README.md index dd280b2d93e..287fd7f7da1 100644 --- a/modelopt/torch/models/README.md +++ b/modelopt/torch/models/README.md @@ -50,6 +50,22 @@ star-import. A value shared between two model types is private and imported expl (`gemma4_text` reuses `gemma4`'s `_GEMMA4_MOE_SPEC`); sub-model types of one family are an intra-family detail, not a package API. +## PTQ modeling + +Quantized-module wrappers and registrations that only one model needs (e.g. Llama4's fused +BMM experts) live in `/modeling_ptq.py`; generic +ones (fused/sequential MoE auto-detection, attention, `FP8Linear`) stay in +`modelopt/torch/quantization/plugins/huggingface.py`. The HF plugin imports every +`modeling_ptq` from an explicit list at its end, so add the new model type there. Leave the +model's `__init__.py` importing only `specs`: `modeling_ptq` pulls in quantization and +transformers, which `import modelopt.torch.models` must not. + +A `modeling_ptq.py` registers on import — static classes via `QuantModuleRegistry.register` +behind a `try`/`except ImportError`, remote-code classes via a callback added to +`CUSTOM_MODEL_PLUGINS` — and exports nothing (`__all__ = []`). Every package still has +a `specs.py`, even when its spec only records `model_type` and where the modeling code comes +from. + ## Sections A `ModelSpec` holds one attribute per section, each `None` unless the model fills it: diff --git a/modelopt/torch/models/__init__.py b/modelopt/torch/models/__init__.py index 2e6b98cf133..98aaf9e57da 100644 --- a/modelopt/torch/models/__init__.py +++ b/modelopt/torch/models/__init__.py @@ -26,6 +26,9 @@ The per-model file is named for what it holds, not for who reads it: a model's spec is general model data, and export is only its first consumer. + +A package may also hold ``modeling_ptq.py``, the model-specific PTQ wrappers. It is not imported +here; the HF quantization plugin imports it (see ``README.md``). """ from .moe import is_moe diff --git a/modelopt/torch/models/falcon/modeling_ptq.py b/modelopt/torch/models/falcon/modeling_ptq.py new file mode 100644 index 00000000000..caeaec2d843 --- /dev/null +++ b/modelopt/torch/models/falcon/modeling_ptq.py @@ -0,0 +1,44 @@ +# SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Falcon PTQ modeling (HF model type ``falcon``).""" + +from transformers.models.falcon.modeling_falcon import FalconLinear + +from modelopt.torch.quantization.nn import QuantModuleRegistry +from modelopt.torch.quantization.nn.modules.quant_linear import _QuantLinear +from modelopt.torch.quantization.plugins.custom import CUSTOM_MODEL_PLUGINS + +__all__: list[str] = [] + + +if FalconLinear not in QuantModuleRegistry: + QuantModuleRegistry.register({FalconLinear: "hf.FalconLinear"})(_QuantLinear) + + +def register_falcon_linears_on_the_fly(model): + """Register Falcon linear modules as a QUANT_MODULE. + + Certain falcon models (for example, falcon 40b) use remote code, which are loaded dynamically, to build their model. + Therefore, we need to register the linear on the fly before quantization. + """ + if type(model).__name__ in ["RWForCausalLM", "FalconForCausalLM"]: + linear_type = type(model.transformer.h[0].self_attention.dense) + # Create a QuantFalconLinear class on the fly + if QuantModuleRegistry.get(linear_type) is None: + QuantModuleRegistry.register({linear_type: linear_type.__name__})(_QuantLinear) + + +CUSTOM_MODEL_PLUGINS.add(register_falcon_linears_on_the_fly) diff --git a/modelopt/torch/models/falcon/specs.py b/modelopt/torch/models/falcon/specs.py index 0e4d9dca8eb..fd40ca93c8c 100644 --- a/modelopt/torch/models/falcon/specs.py +++ b/modelopt/torch/models/falcon/specs.py @@ -20,5 +20,5 @@ __all__: list[str] = [] # Dense, and nothing model-specific for export. Older remote-code Falcon checkpoints -# (RWForCausalLM) report other model types and are handled by the HF quantization plugin alone. +# (RWForCausalLM) report other model types and are handled by modeling_ptq.py alone. register(ModelSpec(model_type="falcon", min_transformers_version="4.57")) diff --git a/modelopt/torch/models/llama4/modeling_ptq.py b/modelopt/torch/models/llama4/modeling_ptq.py new file mode 100644 index 00000000000..6079a0ffe27 --- /dev/null +++ b/modelopt/torch/models/llama4/modeling_ptq.py @@ -0,0 +1,55 @@ +# SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Llama4 PTQ modeling (HF model type ``llama4``).""" + +import torch +from transformers.models.llama4.modeling_llama4 import Llama4TextExperts + +from modelopt.torch.quantization.nn import QuantModule, QuantModuleRegistry, TensorQuantizer +from modelopt.torch.quantization.plugins.huggingface import ( + _transposed_quantize, + _TransposedExpertsCalibMixin, +) + +__all__: list[str] = [] + + +class _QuantLlama4TextExperts(_TransposedExpertsCalibMixin, QuantModule): + def _setup(self): + self.gate_up_proj_input_quantizer = TensorQuantizer() + self.gate_up_proj_weight_quantizer = TensorQuantizer() + self.down_proj_input_quantizer = TensorQuantizer() + self.down_proj_weight_quantizer = TensorQuantizer() + + def forward(self, hidden_states: torch.Tensor) -> torch.Tensor: + hidden_states = hidden_states.view(self.num_experts, -1, self.hidden_size) + gate_up = torch.bmm( + self.gate_up_proj_input_quantizer(hidden_states), + _transposed_quantize(self.gate_up_proj, self.gate_up_proj_weight_quantizer), + ) + gate, up = gate_up.chunk(2, dim=-1) # not supported for DTensors + next_states = torch.bmm( + self.down_proj_input_quantizer(up * self.act_fn(gate)), + _transposed_quantize(self.down_proj, self.down_proj_weight_quantizer), + ) + next_states = next_states.view(-1, self.hidden_size) + return next_states + + +if Llama4TextExperts not in QuantModuleRegistry: + QuantModuleRegistry.register({Llama4TextExperts: "hf.Llama4TextExperts"})( + _QuantLlama4TextExperts + ) diff --git a/modelopt/torch/models/nemotron_h/modeling_ptq.py b/modelopt/torch/models/nemotron_h/modeling_ptq.py new file mode 100644 index 00000000000..46670b9b7eb --- /dev/null +++ b/modelopt/torch/models/nemotron_h/modeling_ptq.py @@ -0,0 +1,52 @@ +# SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Nemotron-H PTQ modeling (HF model type ``nemotron_h``).""" + +import torch.nn as nn + +from modelopt.torch.quantization.plugins.huggingface import _is_supported_hf_model +from modelopt.torch.quantization.utils.layerwise_calib import LayerActivationCollector + +__all__: list[str] = [] + + +def is_nemotron_h_model(model: nn.Module) -> bool: + return get_nemotron_h_decoder_layers(model) is not None + + +def get_nemotron_h_decoder_layers(model: nn.Module) -> nn.ModuleList | None: + if not _is_supported_hf_model(model): + return None + + # Custom remote-code checkpoint uses model.backbone.layers; + # native transformers NemotronHModel uses model.model.layers. + for container_attr in ("backbone", "model"): + container = getattr(model, container_attr, None) + if container is not None and hasattr(container, "layers"): + layers = container.layers + if layers and hasattr(layers[0], "block_type"): + return layers + + return None + + +# Order matters: more specific predicates must be registered first because +# the first matching entry wins. Nemotron-H must precede the generic +# homogeneous HF discoverer (which explicitly rejects Nemotron-H); the HF plugin +# imports this module before registering that discoverer. +LayerActivationCollector.register_decoder_layer_support( + is_nemotron_h_model, get_nemotron_h_decoder_layers +) diff --git a/modelopt/torch/quantization/plugins/huggingface.py b/modelopt/torch/quantization/plugins/huggingface.py index 8df82d94b3a..5eae0ae937a 100644 --- a/modelopt/torch/quantization/plugins/huggingface.py +++ b/modelopt/torch/quantization/plugins/huggingface.py @@ -15,6 +15,7 @@ """Support quantization for huggingface layers.""" +import importlib import inspect import logging import re @@ -29,9 +30,7 @@ from torch import Tensor from torch.nn.functional import linear from transformers.integrations.finegrained_fp8 import FP8Linear -from transformers.models.falcon.modeling_falcon import FalconLinear from transformers.models.gpt_oss.modeling_gpt_oss import GptOssExperts -from transformers.models.llama4.modeling_llama4 import Llama4TextExperts from transformers.models.t5.modeling_t5 import T5Attention from modelopt.torch.kernels.common.attention import IS_AVAILABLE as TRITON_FA_AVAILABLE @@ -769,28 +768,6 @@ def layer_sync_moe_local_experts_amax(self, sync_weight_amax=False): sync_moe_expert_amax(self.experts, sync_weight_amax=sync_weight_amax) -class _QuantLlama4TextExperts(_TransposedExpertsCalibMixin, QuantModule): - def _setup(self): - self.gate_up_proj_input_quantizer = TensorQuantizer() - self.gate_up_proj_weight_quantizer = TensorQuantizer() - self.down_proj_input_quantizer = TensorQuantizer() - self.down_proj_weight_quantizer = TensorQuantizer() - - def forward(self, hidden_states: torch.Tensor) -> torch.Tensor: - hidden_states = hidden_states.view(self.num_experts, -1, self.hidden_size) - gate_up = torch.bmm( - self.gate_up_proj_input_quantizer(hidden_states), - _transposed_quantize(self.gate_up_proj, self.gate_up_proj_weight_quantizer), - ) - gate, up = gate_up.chunk(2, dim=-1) # not supported for DTensors - next_states = torch.bmm( - self.down_proj_input_quantizer(up * self.act_fn(gate)), - _transposed_quantize(self.down_proj, self.down_proj_weight_quantizer), - ) - next_states = next_states.view(-1, self.hidden_size) - return next_states - - def _get_fused_expert_intermediate_dim(module): """Resolve the intermediate (expert) dimension from a fused expert module. @@ -1215,14 +1192,6 @@ def unpack_weight(self): del self.weight_scale_inv -if Llama4TextExperts not in QuantModuleRegistry: - QuantModuleRegistry.register({Llama4TextExperts: "hf.Llama4TextExperts"})( - _QuantLlama4TextExperts - ) - -if FalconLinear not in QuantModuleRegistry: - QuantModuleRegistry.register({FalconLinear: "hf.FalconLinear"})(_QuantLinear) - try: from compressed_tensors.linear.compressed_linear import CompressedLinear @@ -1327,19 +1296,6 @@ def forward( QuantModuleRegistry.register({GptOssExperts: "hf.GptOssExperts"})(_QuantGptOssExperts) -def register_falcon_linears_on_the_fly(model): - """Register Falcon linear modules as a QUANT_MODULE. - - Certain falcon models (for example, falcon 40b) use remote code, which are loaded dynamically, to build their model. - Therefore, we need to register the linear on the fly before quantization. - """ - if type(model).__name__ in ["RWForCausalLM", "FalconForCausalLM"]: - linear_type = type(model.transformer.h[0].self_attention.dense) - # Create a QuantFalconLinear class on the fly - if QuantModuleRegistry.get(linear_type) is None: - QuantModuleRegistry.register({linear_type: linear_type.__name__})(_QuantLinear) - - def _has_num_experts(obj): # n_routed_experts: NemotronH-style MoE return hasattr(obj, "num_experts") or hasattr(obj, "n_routed_experts") @@ -1529,27 +1485,9 @@ def _is_supported_hf_model(model): return isinstance(model, tuple(supported_models)) -def is_nemotron_h_model(model: nn.Module) -> bool: - return get_nemotron_h_decoder_layers(model) is not None - - -def get_nemotron_h_decoder_layers(model: nn.Module) -> nn.ModuleList | None: - if not _is_supported_hf_model(model): - return None - - # Custom remote-code checkpoint uses model.backbone.layers; - # native transformers NemotronHModel uses model.model.layers. - for container_attr in ("backbone", "model"): - container = getattr(model, container_attr, None) - if container is not None and hasattr(container, "layers"): - layers = container.layers - if layers and hasattr(layers[0], "block_type"): - return layers - - return None - - def is_homogeneous_hf_model(model: nn.Module) -> bool: + from modelopt.torch.models.nemotron_h.modeling_ptq import is_nemotron_h_model + if is_nemotron_h_model(model): return False decoder_layers = get_homogeneous_hf_decoder_layers(model) @@ -1627,12 +1565,17 @@ def _is_param_grad_enabled_for_auto_quantize(pname, model): _is_param_grad_enabled_for_auto_quantize, ) -# Order matters: more specific predicates must be registered first because -# the first matching entry wins. Nemotron-H must precede the generic -# homogeneous HF discoverer (which explicitly rejects Nemotron-H). -LayerActivationCollector.register_decoder_layer_support( - is_nemotron_h_model, get_nemotron_h_decoder_layers -) +# Model-specific PTQ support lives with its model in +# ``modelopt/torch/models//modeling_ptq.py`` and registers itself on import. It is +# imported here: after the generic wrappers it builds on are defined, and before the +# homogeneous decoder discoverer below, since the first matching discoverer wins and +# Nemotron-H's is the more specific one. +for _model_type in ( + "falcon", + "llama4", + "nemotron_h", +): + importlib.import_module(f"modelopt.torch.models.{_model_type}.modeling_ptq") LayerActivationCollector.register_decoder_layer_support( is_homogeneous_hf_model, get_homogeneous_hf_decoder_layers @@ -1867,7 +1810,6 @@ def _reconstruct_fused_moe_linear(model: nn.Module) -> None: CUSTOM_MODEL_PLUGINS.update( [ - register_falcon_linears_on_the_fly, register_moe_linear_on_the_fly, register_fused_experts_on_the_fly, force_eager_experts_impl_on_the_fly, From 047bc063a8776d99b9f212d60dcc74a83a2e2286 Mon Sep 17 00:00:00 2001 From: Shengliang Xu Date: Wed, 7 Oct 2026 00:09:44 +0000 Subject: [PATCH 2/4] Test modeling_ptq registration regardless of import order Each case imports one module first in a fresh interpreter (a modeling_ptq, quantization, or export) and checks that Falcon and Llama4 are registered, the Falcon callback is installed, and Nemotron-H's decoder discoverer is registered once and ahead of the homogeneous HF one. Signed-off-by: Shengliang Xu --- .../models/test_modeling_ptq_registration.py | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 tests/unit/torch/models/test_modeling_ptq_registration.py diff --git a/tests/unit/torch/models/test_modeling_ptq_registration.py b/tests/unit/torch/models/test_modeling_ptq_registration.py new file mode 100644 index 00000000000..5639087688e --- /dev/null +++ b/tests/unit/torch/models/test_modeling_ptq_registration.py @@ -0,0 +1,81 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Model-specific PTQ modeling registers on import, whichever module is imported first. + +The HF quantization plugin imports every ``/modeling_ptq.py`` from an explicit +list, and those modules import back from the plugin, so registration depends on import +order. Each case runs in a fresh interpreter: registration happens once per process, so an +in-process test would only ever see the import order of whichever test ran first. +""" + +import subprocess +import sys + +import pytest + +pytest.importorskip("transformers") + +FIRST_IMPORTS = [ + "modelopt.torch.models.falcon.modeling_ptq", + "modelopt.torch.models.llama4.modeling_ptq", + "modelopt.torch.models.nemotron_h.modeling_ptq", + "modelopt.torch.quantization", + "modelopt.torch.export", +] + +# Runs in the subprocess after importing ``sys.argv[1]`` first. +CHECK = """ +import importlib +import sys + +importlib.import_module(sys.argv[1]) + +from modelopt.torch.models.falcon.modeling_ptq import register_falcon_linears_on_the_fly +from modelopt.torch.models.nemotron_h.modeling_ptq import is_nemotron_h_model +from modelopt.torch.quantization.nn import QuantModuleRegistry +from modelopt.torch.quantization.plugins.custom import CUSTOM_MODEL_PLUGINS +from modelopt.torch.quantization.plugins.huggingface import is_homogeneous_hf_model +from modelopt.torch.quantization.utils.layerwise_calib import LayerActivationCollector + +try: + from transformers.models.falcon.modeling_falcon import FalconLinear +except ImportError: + pass +else: + assert FalconLinear in QuantModuleRegistry, "FalconLinear not registered" +try: + from transformers.models.llama4.modeling_llama4 import Llama4TextExperts +except ImportError: + pass +else: + assert Llama4TextExperts in QuantModuleRegistry, "Llama4TextExperts not registered" + +assert register_falcon_linears_on_the_fly in CUSTOM_MODEL_PLUGINS, "Falcon callback missing" + +# The first matching decoder discoverer wins, so Nemotron-H's must precede the generic one. +predicates = [is_supported for is_supported, _ in LayerActivationCollector._decoder_layer_support] +assert predicates.count(is_nemotron_h_model) == 1, predicates +assert predicates.count(is_homogeneous_hf_model) == 1, predicates +assert predicates.index(is_nemotron_h_model) < predicates.index(is_homogeneous_hf_model) +""" + + +@pytest.mark.parametrize("first_import", FIRST_IMPORTS) +def test_registration_is_independent_of_import_order(first_import): + result = subprocess.run( + [sys.executable, "-c", CHECK, first_import], capture_output=True, text=True, check=False + ) + assert result.returncode == 0, result.stderr From 7b064c2f28abe2a17dbe6ca26b6e4e2f599c63d4 Mon Sep 17 00:00:00 2001 From: Shengliang Xu Date: Thu, 8 Oct 2026 00:21:22 +0000 Subject: [PATCH 3/4] Raise the new model specs to the transformers 5.5 floor Signed-off-by: Shengliang Xu #2670 raised the minimum supported transformers to 5.5 and moved every spec's min_transformers_version to the floor, but llama4, qwen3_vl_moe and falcon landed with 4.57 in #2576. It also removed the pre-5.12 Qwen3-VL-MoE PTQ wrapper, so drop the qwen3_vl_moe spec's pointer to it. --- modelopt/torch/models/falcon/specs.py | 2 +- modelopt/torch/models/llama4/specs.py | 2 +- modelopt/torch/models/qwen3_vl_moe/specs.py | 7 +++---- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/modelopt/torch/models/falcon/specs.py b/modelopt/torch/models/falcon/specs.py index fd40ca93c8c..793fbba0dcc 100644 --- a/modelopt/torch/models/falcon/specs.py +++ b/modelopt/torch/models/falcon/specs.py @@ -21,4 +21,4 @@ # Dense, and nothing model-specific for export. Older remote-code Falcon checkpoints # (RWForCausalLM) report other model types and are handled by modeling_ptq.py alone. -register(ModelSpec(model_type="falcon", min_transformers_version="4.57")) +register(ModelSpec(model_type="falcon", min_transformers_version="5.5")) diff --git a/modelopt/torch/models/llama4/specs.py b/modelopt/torch/models/llama4/specs.py index c2efaef42ae..dc733398d87 100644 --- a/modelopt/torch/models/llama4/specs.py +++ b/modelopt/torch/models/llama4/specs.py @@ -26,7 +26,7 @@ register( ModelSpec( model_type="llama4", - min_transformers_version="4.57", + min_transformers_version="5.5", moe_spec=MoESpec( block_names=("Llama4TextMoe",), expert_linear_names=("gate_up_proj", "down_proj"), diff --git a/modelopt/torch/models/qwen3_vl_moe/specs.py b/modelopt/torch/models/qwen3_vl_moe/specs.py index 1416d7e107d..9fe6bcf3d79 100644 --- a/modelopt/torch/models/qwen3_vl_moe/specs.py +++ b/modelopt/torch/models/qwen3_vl_moe/specs.py @@ -20,13 +20,12 @@ __all__: list[str] = [] # Qwen3VLMoeTextExperts is fused on every supported transformers: 3-D gate_up_proj and -# down_proj parameters (transposed before 5.12, standard layout from 5.12 on). Before 5.12 -# the PTQ wrapper unrolls them into gate_proj/up_proj/down_proj ModuleLists (see -# quantization/plugins/huggingface.py); that rewrite is not iterable per expert, so no export path groups it. +# down_proj parameters (transposed before 5.12, standard layout from 5.12 on), so there is no +# (gate, up) pair left to fuse and no per-expert grouping for export. register( ModelSpec( model_type="qwen3_vl_moe", - min_transformers_version="4.57", + min_transformers_version="5.5", moe_spec=MoESpec( block_names=("Qwen3VLMoeTextSparseMoeBlock",), expert_linear_names=("gate_up_proj", "down_proj"), From d6ce3d3687839a4f4095fb2284e70031e5599504 Mon Sep 17 00:00:00 2001 From: Shengliang Xu Date: Thu, 8 Oct 2026 09:03:41 +0000 Subject: [PATCH 4/4] Run the registration test's interpreters concurrently Each first import needs its own interpreter; starting them one after another added over a minute to the unit-test job, which already runs close to its 20-minute limit. Launch them all at once and collect the results, which reports every failing first import together. Signed-off-by: Shengliang Xu --- .../models/test_modeling_ptq_registration.py | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/tests/unit/torch/models/test_modeling_ptq_registration.py b/tests/unit/torch/models/test_modeling_ptq_registration.py index 5639087688e..b97a2294a67 100644 --- a/tests/unit/torch/models/test_modeling_ptq_registration.py +++ b/tests/unit/torch/models/test_modeling_ptq_registration.py @@ -17,8 +17,8 @@ The HF quantization plugin imports every ``/modeling_ptq.py`` from an explicit list, and those modules import back from the plugin, so registration depends on import -order. Each case runs in a fresh interpreter: registration happens once per process, so an -in-process test would only ever see the import order of whichever test ran first. +order. Each first import runs in a fresh interpreter: registration happens once per process, +so an in-process test would only ever see the import order of whichever test ran first. """ import subprocess @@ -73,9 +73,23 @@ """ -@pytest.mark.parametrize("first_import", FIRST_IMPORTS) -def test_registration_is_independent_of_import_order(first_import): - result = subprocess.run( - [sys.executable, "-c", CHECK, first_import], capture_output=True, text=True, check=False +def test_registration_is_independent_of_import_order(): + # Each first import needs its own interpreter, but starting them one after another would + # add over a minute to the unit-test job, so launch them all and then collect the results. + procs = { + first_import: subprocess.Popen( + [sys.executable, "-c", CHECK, first_import], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) + for first_import in FIRST_IMPORTS + } + failures = {} + for first_import, proc in procs.items(): + _, stderr = proc.communicate(timeout=600) + if proc.returncode != 0: + failures[first_import] = stderr + assert not failures, "\n\n".join( + f"importing {name} first:\n{err}" for name, err in failures.items() ) - assert result.returncode == 0, result.stderr