From f35bea1fb475ce291c2c5db851b9ed232ea85677 Mon Sep 17 00:00:00 2001 From: Keval Morabia <28916987+kevalmorabia97@users.noreply.github.com> Date: Wed, 28 Oct 2026 13:45:54 -0700 Subject: [PATCH 1/9] Bump diffusers to >=0.37 and drop older-diffusers code paths 0.37 is the first release with QwenEmbedRope's max_txt_seq_len, which the Qwen-Image FastGen plugin passes. Also raise huggingface_hub to >=1.5 and accelerate to >=1.1, which transformers>=5.5 already requires, and remove the unused skip_if_onnx_version_above_1_18 helper. Co-Authored-By: Claude Opus 5.5 Signed-off-by: Keval Morabia <28916987+kevalmorabia97@users.noreply.github.com> --- CHANGELOG.rst | 1 + .../diffusers/quantization/models_utils.py | 14 +---- examples/diffusers/quantization/utils.py | 2 +- modelopt/torch/export/unified_export_hf.py | 2 +- .../plugins/diffusion/diffusers.py | 55 ++++++---------- pyproject.toml | 6 +- tests/_test_utils/import_helper.py | 17 ----- tests/_test_utils/torch/diffusers_models.py | 62 +++---------------- 8 files changed, 36 insertions(+), 123 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 90aa9a89b4c..8295ddb6788 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -47,6 +47,7 @@ Changelog - ``export_mcore_gpt_to_hf`` given a Hugging Face Hub model ID no longer writes the tokenizer, processor, generation config or remote-code files. Pass a local directory instead, e.g. from ``modelopt.torch.export.ensure_local_checkpoint``. - Drop support for Python 3.10, which has reached end-of-life. ModelOpt now requires Python 3.11 or later. - Bump minimum required PyTorch version to 2.10. +- Bump minimum required ``diffusers`` version to 0.37 for the ``[hf]`` extra. - Diffusion HF exports now omit attention quantizer buffers by default, except for FLUX.1, which retains enabled Q/K/V ``_amax`` keys for runtime compatibility; other consumers of those keys can opt in with ``export_attention_quantizers=True`` (CLI: ``--extra-param export_attention_quantizers=true``). Disabled attention quantizers remain excluded, and diffusion components, including LTX-2, no longer emit KV-cache quantization metadata. - ``hf_quant_config.json`` of an INT4 or W4A8 AWQ checkpoint now sets ``pre_quant_scale`` per layer, only for layers that have the tensor. A model whose fused-MoE experts have none (AWQ never applies to them) is now exported as ``MIXED_PRECISION`` with per-layer entries instead of a uniform algorithm. - The Windows FVD example rejects I3D checkpoints requiring unrestricted pickle loading. Custom TorchScript archives now require ``--weights-sha256`` with a trusted checksum; tensor state dictionaries continue to load directly. diff --git a/examples/diffusers/quantization/models_utils.py b/examples/diffusers/quantization/models_utils.py index 7a0750bc9a3..2c5c2d12795 100644 --- a/examples/diffusers/quantization/models_utils.py +++ b/examples/diffusers/quantization/models_utils.py @@ -21,23 +21,13 @@ import torch from diffusers import ( DiffusionPipeline, + Flux2Pipeline, FluxPipeline, LTXConditionPipeline, + QwenImagePipeline, StableDiffusion3Pipeline, WanPipeline, ) - -try: - from diffusers import Flux2Pipeline -except ImportError: - Flux2Pipeline = None - -# Qwen-Image classes were added in a recent diffusers release; import lazily so -# this example still imports on older diffusers versions. -try: - from diffusers import QwenImagePipeline -except ImportError: - QwenImagePipeline = None from utils import ( filter_func_default, filter_func_flux_dev, diff --git a/examples/diffusers/quantization/utils.py b/examples/diffusers/quantization/utils.py index b7a79e49e70..24dcf4f355a 100644 --- a/examples/diffusers/quantization/utils.py +++ b/examples/diffusers/quantization/utils.py @@ -20,12 +20,12 @@ import torch import torch.nn.functional as F from datasets import load_dataset +from diffusers.models.attention import AttentionModuleMixin from diffusers.models.attention_processor import Attention, AttnProcessor from diffusers.models.lora import LoRACompatibleConv, LoRACompatibleLinear from diffusers.utils import load_image import modelopt.torch.quantization as mtq -from modelopt.torch.quantization.plugins.diffusion.diffusers import AttentionModuleMixin USE_PEFT = True try: diff --git a/modelopt/torch/export/unified_export_hf.py b/modelopt/torch/export/unified_export_hf.py index 14f3c82fd36..ffbd62ddfba 100644 --- a/modelopt/torch/export/unified_export_hf.py +++ b/modelopt/torch/export/unified_export_hf.py @@ -1355,7 +1355,7 @@ def _export_diffusers_checkpoint( # Image-conditioned Wan also requires a successful dummy forward for fusion. is_wan_i2v_component = ( HAS_DIFFUSERS - and isinstance(component, getattr(diffusers, "WanTransformer3DModel", ())) + and isinstance(component, diffusers.WanTransformer3DModel) and getattr(getattr(component, "config", None), "image_dim", None) is not None ) _fuse_qkv_linears_diffusion(component, strict=is_qwen_component or is_wan_i2v_component) diff --git a/modelopt/torch/quantization/plugins/diffusion/diffusers.py b/modelopt/torch/quantization/plugins/diffusion/diffusers.py index d096ddda7a3..1f3952dfadf 100644 --- a/modelopt/torch/quantization/plugins/diffusion/diffusers.py +++ b/modelopt/torch/quantization/plugins/diffusion/diffusers.py @@ -20,30 +20,18 @@ from types import ModuleType from typing import TYPE_CHECKING -import diffusers import onnx import torch +from diffusers.models.attention_dispatch import AttentionBackendName, attention_backend from diffusers.models.attention_processor import Attention from diffusers.models.lora import LoRACompatibleConv, LoRACompatibleLinear -from packaging.version import parse as parse_version - -if parse_version(diffusers.__version__) >= parse_version("0.35.0"): - from diffusers.models.attention import AttentionModuleMixin - from diffusers.models.attention_dispatch import AttentionBackendName, attention_backend - from diffusers.models.transformers.transformer_flux import FluxAttention - from diffusers.models.transformers.transformer_ltx import LTXAttention - from diffusers.models.transformers.transformer_wan import WanAttention - - try: - from diffusers.models.transformers.transformer_flux2 import ( - Flux2Attention, - Flux2ParallelSelfAttention, - ) - except ImportError: - Flux2Attention = None - Flux2ParallelSelfAttention = None -else: - AttentionModuleMixin = type("_dummy_type_no_instance", (), {}) # pylint: disable=invalid-name +from diffusers.models.transformers.transformer_flux import FluxAttention +from diffusers.models.transformers.transformer_flux2 import ( + Flux2Attention, + Flux2ParallelSelfAttention, +) +from diffusers.models.transformers.transformer_ltx import LTXAttention +from diffusers.models.transformers.transformer_wan import WanAttention from torch.autograd import Function from torch.nn import functional as F from torch.onnx import symbolic_helper @@ -193,24 +181,21 @@ def _setup(self): QuantModuleRegistry.register({Attention: "Attention"})(_QuantAttention) -if AttentionModuleMixin.__module__.startswith(diffusers.__name__): +class _QuantAttentionModuleMixin(_QuantAttention): + """Quantized AttentionModuleMixin for performing attention-related computations.""" - class _QuantAttentionModuleMixin(_QuantAttention): - """Quantized AttentionModuleMixin for performing attention-related computations.""" + def forward(self, *args, **kwargs): + with attention_backend(AttentionBackendName.NATIVE): + return super().forward(*args, **kwargs) - def forward(self, *args, **kwargs): - with attention_backend(AttentionBackendName.NATIVE): - return super().forward(*args, **kwargs) - QuantModuleRegistry.register({FluxAttention: "FluxAttention"})(_QuantAttentionModuleMixin) - QuantModuleRegistry.register({WanAttention: "WanAttention"})(_QuantAttentionModuleMixin) - QuantModuleRegistry.register({LTXAttention: "LTXAttention"})(_QuantAttentionModuleMixin) - if Flux2Attention is not None: - QuantModuleRegistry.register({Flux2Attention: "Flux2Attention"})(_QuantAttentionModuleMixin) - if Flux2ParallelSelfAttention is not None: - QuantModuleRegistry.register({Flux2ParallelSelfAttention: "Flux2ParallelSelfAttention"})( - _QuantAttentionModuleMixin - ) +QuantModuleRegistry.register({FluxAttention: "FluxAttention"})(_QuantAttentionModuleMixin) +QuantModuleRegistry.register({WanAttention: "WanAttention"})(_QuantAttentionModuleMixin) +QuantModuleRegistry.register({LTXAttention: "LTXAttention"})(_QuantAttentionModuleMixin) +QuantModuleRegistry.register({Flux2Attention: "Flux2Attention"})(_QuantAttentionModuleMixin) +QuantModuleRegistry.register({Flux2ParallelSelfAttention: "Flux2ParallelSelfAttention"})( + _QuantAttentionModuleMixin +) original_scaled_dot_product_attention = F.scaled_dot_product_attention diff --git a/pyproject.toml b/pyproject.toml index 3c07d934ea1..d911a6e2260 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -76,12 +76,12 @@ onnx = [ "onnxruntime-ep-nv-tensorrt-rtx-cu13==0.4.0; platform_system == 'Windows' and platform_machine == 'ARM64'", ] hf = [ - "accelerate>=1.0.0", + "accelerate>=1.1.0", "datasets>=3.0.0", "deepspeed>=0.9.6; platform_system != 'Darwin' and platform_system != 'Windows'", - "diffusers>=0.32.2", + "diffusers>=0.37.0", "httpx>=0.23.0,<1", - "huggingface_hub>=0.24.0", + "huggingface_hub>=1.5.0", "nltk", "peft>=0.17.0", "sentencepiece>=0.2.1", # Also implicitly used in test_unified_export_megatron, test_vllm_fakequant_megatron_export diff --git a/tests/_test_utils/import_helper.py b/tests/_test_utils/import_helper.py index a2b46a5b1c2..4480e050d1a 100644 --- a/tests/_test_utils/import_helper.py +++ b/tests/_test_utils/import_helper.py @@ -13,12 +13,10 @@ # See the License for the specific language governing permissions and # limitations under the License. import ctypes -import importlib.metadata import os import shutil import pytest -from packaging import version def skip_if_no_tensorrt(): @@ -66,18 +64,3 @@ def skip_if_no_mamba(): import mamba_ssm # noqa: F401 except ImportError: pytest.skip("Mamba required for Megatron test", allow_module_level=True) - - -def skip_if_onnx_version_above_1_18(): - package_name = "onnx" - required_version = "1.18.0" - - try: - installed_version = importlib.metadata.version(package_name) - except importlib.metadata.PackageNotFoundError: - pytest.skip(f"{package_name} is not installed") - - if version.parse(installed_version) > version.parse(required_version): - pytest.skip( - f"{package_name} version {installed_version} is greater than required {required_version}" - ) diff --git a/tests/_test_utils/torch/diffusers_models.py b/tests/_test_utils/torch/diffusers_models.py index 849d1ae6f2e..b2954200ebb 100644 --- a/tests/_test_utils/torch/diffusers_models.py +++ b/tests/_test_utils/torch/diffusers_models.py @@ -21,37 +21,14 @@ pytest.importorskip("diffusers") from diffusers import UNet2DConditionModel - -try: - from diffusers.models.transformers import DiTTransformer2DModel, FluxTransformer2DModel -except Exception: # pragma: no cover - optional diffusers models - DiTTransformer2DModel = None - FluxTransformer2DModel = None - -try: - from diffusers.models.transformers import Flux2Transformer2DModel -except Exception: # pragma: no cover - optional diffusers models - Flux2Transformer2DModel = None - -try: - from diffusers.models.transformers import WanTransformer3DModel -except Exception: # pragma: no cover - optional diffusers models - WanTransformer3DModel = None - -try: - from diffusers.models.autoencoders import AutoencoderKLWan -except Exception: # pragma: no cover - optional diffusers models - AutoencoderKLWan = None - -try: - from diffusers.models.transformers import QwenImageTransformer2DModel -except Exception: # pragma: no cover - optional diffusers models - QwenImageTransformer2DModel = None - -try: - from diffusers.models.autoencoders import AutoencoderKLQwenImage -except Exception: # pragma: no cover - optional diffusers models - AutoencoderKLQwenImage = None +from diffusers.models.autoencoders import AutoencoderKLQwenImage, AutoencoderKLWan +from diffusers.models.transformers import ( + DiTTransformer2DModel, + Flux2Transformer2DModel, + FluxTransformer2DModel, + QwenImageTransformer2DModel, + WanTransformer3DModel, +) import modelopt.torch.opt as mto @@ -79,9 +56,6 @@ def get_tiny_unet(**config_kwargs) -> UNet2DConditionModel: def get_tiny_dit(**config_kwargs): """Create a tiny DiTTransformer2DModel for testing.""" - if DiTTransformer2DModel is None: - pytest.skip("DiTTransformer2DModel is not available in this diffusers version.") - kwargs = { "num_attention_heads": 2, "attention_head_dim": 8, @@ -99,9 +73,6 @@ def get_tiny_dit(**config_kwargs): def get_tiny_flux(**config_kwargs): """Create a tiny FluxTransformer2DModel for testing.""" - if FluxTransformer2DModel is None: - pytest.skip("FluxTransformer2DModel is not available in this diffusers version.") - kwargs = { "patch_size": 1, "in_channels": 4, @@ -121,9 +92,6 @@ def get_tiny_flux(**config_kwargs): def get_tiny_flux2(**config_kwargs): """Create a tiny Flux2Transformer2DModel for testing.""" - if Flux2Transformer2DModel is None: - pytest.skip("Flux2Transformer2DModel is not available in this diffusers version.") - kwargs = { "patch_size": 1, "in_channels": 16, @@ -182,9 +150,6 @@ def df_modelopt_state_and_output_tester(model_ref, model_test): def get_tiny_wan22_transformer(**config_kwargs): """Create a tiny WanTransformer3DModel for testing.""" - if WanTransformer3DModel is None: - pytest.skip("WanTransformer3DModel is not available in this diffusers version.") - kwargs = { "patch_size": (1, 2, 2), "num_attention_heads": 2, @@ -205,9 +170,6 @@ def get_tiny_wan22_transformer(**config_kwargs): def get_tiny_wan22_vae(**config_kwargs): """Create a tiny AutoencoderKLWan for testing.""" - if AutoencoderKLWan is None: - pytest.skip("AutoencoderKLWan is not available in this diffusers version.") - kwargs = { "base_dim": 3, "z_dim": 16, @@ -324,9 +286,6 @@ def get_tiny_qwen_image_transformer(**config_kwargs): (the bundled text encoder is bypassed), so pair this with ``--data.dataloader.text_embed_dim=``. """ - if QwenImageTransformer2DModel is None: - pytest.skip("QwenImageTransformer2DModel is not available in this diffusers version.") - kwargs = { "patch_size": 2, "in_channels": 64, # vae z_dim (16) * 2x2 patch @@ -351,9 +310,6 @@ def get_tiny_qwen_image_transformer(**config_kwargs): def get_tiny_qwen_image_vae(**config_kwargs): """Create a tiny AutoencoderKLQwenImage for testing (z_dim=16 to match the transformer).""" - if AutoencoderKLQwenImage is None: - pytest.skip("AutoencoderKLQwenImage is not available in this diffusers version.") - kwargs = { "base_dim": 8, "z_dim": 16, # = transformer out_channels @@ -438,8 +394,6 @@ def create_tiny_qwen_image_pipeline_dir(tmp_path: Path) -> Path: (16) so the pipeline runs end-to-end during quantization calibration. The saved dir loads with ``QwenImagePipeline.from_pretrained(path)``. """ - if QwenImageTransformer2DModel is None or AutoencoderKLQwenImage is None: - pytest.skip("QwenImage diffusers classes not available in this diffusers version.") from diffusers import FlowMatchEulerDiscreteScheduler, QwenImagePipeline transformers = pytest.importorskip("transformers") From 2e3c90b8ac82e058ec00059ba211882e46e9ff7d Mon Sep 17 00:00:00 2001 From: Keval Morabia <28916987+kevalmorabia97@users.noreply.github.com> Date: Wed, 28 Oct 2026 14:16:34 -0700 Subject: [PATCH 2/9] Require diffusers>=0.39 for Qwen-Image PDD diffusers 0.39 moved the joint text/image mask from the root forward into the attention processor. The PDD forward replaces the root forward, so on older diffusers padded text tokens leak into attention; fail loudly instead. Co-Authored-By: Claude Opus 5.5 Signed-off-by: Keval Morabia <28916987+kevalmorabia97@users.noreply.github.com> --- modelopt/torch/fastgen/plugins/qwen_image_pdd.py | 6 ++++++ tests/unit/torch/fastgen/test_qwen_image_pdd_plugin.py | 9 ++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/modelopt/torch/fastgen/plugins/qwen_image_pdd.py b/modelopt/torch/fastgen/plugins/qwen_image_pdd.py index b8b512dde9a..ac88a4a66d0 100644 --- a/modelopt/torch/fastgen/plugins/qwen_image_pdd.py +++ b/modelopt/torch/fastgen/plugins/qwen_image_pdd.py @@ -40,6 +40,7 @@ from typing import Any import torch +from packaging.version import Version from torch import nn from ..config import PDDConfig @@ -224,8 +225,13 @@ def enable_qwen_image_pdd_forward(transformer: nn.Module) -> nn.Module: return transformer # Diffusers is an optional dependency used only by the Qwen example. + import diffusers from diffusers import QwenImageTransformer2DModel + # Older diffusers builds the joint mask in the root forward, which this forward replaces. + if Version(diffusers.__version__) < Version("0.39"): + raise RuntimeError(f"Qwen-Image PDD requires diffusers>=0.39, got {diffusers.__version__}.") + if not isinstance(transformer, QwenImageTransformer2DModel): raise TypeError( "Qwen PDD forward binding requires QwenImageTransformer2DModel, " diff --git a/tests/unit/torch/fastgen/test_qwen_image_pdd_plugin.py b/tests/unit/torch/fastgen/test_qwen_image_pdd_plugin.py index dc6a2bc89dd..4b5511e90e3 100644 --- a/tests/unit/torch/fastgen/test_qwen_image_pdd_plugin.py +++ b/tests/unit/torch/fastgen/test_qwen_image_pdd_plugin.py @@ -79,7 +79,7 @@ def _mr210_rollout_oracle( def _tiny_diffusers_qwen(): - diffusers = pytest.importorskip("diffusers") + diffusers = pytest.importorskip("diffusers", minversion="0.39") return diffusers.QwenImageTransformer2DModel( patch_size=2, in_channels=8, @@ -306,6 +306,13 @@ def test_qwen_pdd_forward_binding_preserves_root_state_and_deepcopy() -> None: enable_qwen_image_pdd_forward(conflicting) +def test_qwen_pdd_forward_binding_rejects_old_diffusers(monkeypatch) -> None: + model = _tiny_diffusers_qwen() + monkeypatch.setattr("diffusers.__version__", "0.38.0") + with pytest.raises(RuntimeError, match=r"diffusers>=0\.39"): + enable_qwen_image_pdd_forward(model) + + def test_mr210_qwen_conversion_preserves_every_initialized_head() -> None: base = _tiny_diffusers_qwen().eval().to(torch.bfloat16) student = copy.deepcopy(base) From 40981f876515d53ff11fe686b1e774a62f48be33 Mon Sep 17 00:00:00 2001 From: Keval Morabia <28916987+kevalmorabia97@users.noreply.github.com> Date: Wed, 28 Oct 2026 14:16:51 -0700 Subject: [PATCH 3/9] Bump transformers to >=5.8 and nltk to >=3.9 Drop the transformers<5.6 expert-parallel test skips and the pre-punkt_tab nltk path. Co-Authored-By: Claude Opus 5.5 Signed-off-by: Keval Morabia <28916987+kevalmorabia97@users.noreply.github.com> --- CHANGELOG.rst | 2 +- examples/llm_sparsity/weight_sparsity/eval.py | 6 +----- modelopt/torch/__init__.py | 2 +- .../calibration/ruler_dataset.py | 3 +-- modelopt/torch/utils/moe.py | 5 ++--- noxfile.py | 2 +- pyproject.toml | 4 ++-- tests/gpu/torch/utils/test_moe_a2a.py | 5 +---- tests/unit/torch/utils/test_moe_a2a.py | 17 ++++------------- 9 files changed, 14 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 8295ddb6788..8f135a88763 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -47,7 +47,7 @@ Changelog - ``export_mcore_gpt_to_hf`` given a Hugging Face Hub model ID no longer writes the tokenizer, processor, generation config or remote-code files. Pass a local directory instead, e.g. from ``modelopt.torch.export.ensure_local_checkpoint``. - Drop support for Python 3.10, which has reached end-of-life. ModelOpt now requires Python 3.11 or later. - Bump minimum required PyTorch version to 2.10. -- Bump minimum required ``diffusers`` version to 0.37 for the ``[hf]`` extra. +- Bump minimum required ``transformers`` version to 5.8, ``diffusers`` to 0.37 and ``nltk`` to 3.9 for the ``[hf]`` extra. - Diffusion HF exports now omit attention quantizer buffers by default, except for FLUX.1, which retains enabled Q/K/V ``_amax`` keys for runtime compatibility; other consumers of those keys can opt in with ``export_attention_quantizers=True`` (CLI: ``--extra-param export_attention_quantizers=true``). Disabled attention quantizers remain excluded, and diffusion components, including LTX-2, no longer emit KV-cache quantization metadata. - ``hf_quant_config.json`` of an INT4 or W4A8 AWQ checkpoint now sets ``pre_quant_scale`` per layer, only for layers that have the tensor. A model whose fused-MoE experts have none (AWQ never applies to them) is now exported as ``MIXED_PRECISION`` with per-layer entries instead of a uniform algorithm. - The Windows FVD example rejects I3D checkpoints requiring unrestricted pickle loading. Custom TorchScript archives now require ``--weights-sha256`` with a trusted checksum; tensor state dictionaries continue to load directly. diff --git a/examples/llm_sparsity/weight_sparsity/eval.py b/examples/llm_sparsity/weight_sparsity/eval.py index a5f2fb91b2d..3aea2104e2c 100644 --- a/examples/llm_sparsity/weight_sparsity/eval.py +++ b/examples/llm_sparsity/weight_sparsity/eval.py @@ -25,7 +25,6 @@ import torch import transformers from accelerate import Accelerator -from packaging.version import Version from torch.utils.data import DataLoader, Dataset from tqdm import tqdm from transformers import AutoModelForCausalLM, AutoTokenizer @@ -244,10 +243,7 @@ def main(): accelerator = Accelerator() with accelerator.main_process_first(): - if Version(nltk.__version__) < Version("3.8.2"): - nltk.download("punkt") - else: - nltk.download("punkt_tab") + nltk.download("punkt_tab") tokenizer = prepare_tokenizer(accelerator, args.model_dir, args.model_max_length) dataset = get_dataset(accelerator, args.data_path, tokenizer) diff --git a/modelopt/torch/__init__.py b/modelopt/torch/__init__.py index 1182304f541..1caebbfbe3c 100644 --- a/modelopt/torch/__init__.py +++ b/modelopt/torch/__init__.py @@ -48,7 +48,7 @@ try: from transformers import __version__ as _transformers_version - if _Version(_transformers_version) < _Version("5.5") or _Version( + if _Version(_transformers_version) < _Version("5.8") or _Version( _transformers_version ) >= _Version("5.19"): _warnings.warn( diff --git a/modelopt/torch/sparsity/attention_sparsity/calibration/ruler_dataset.py b/modelopt/torch/sparsity/attention_sparsity/calibration/ruler_dataset.py index abbbc399d6b..e3a0cb526b6 100644 --- a/modelopt/torch/sparsity/attention_sparsity/calibration/ruler_dataset.py +++ b/modelopt/torch/sparsity/attention_sparsity/calibration/ruler_dataset.py @@ -268,9 +268,8 @@ def generate_niah_sample( from nltk.tokenize import sent_tokenize try: - nltk.data.find("tokenizers/punkt") + nltk.data.find("tokenizers/punkt_tab") except LookupError: - nltk.download("punkt", quiet=True) nltk.download("punkt_tab", quiet=True) if random_seed is not None: diff --git a/modelopt/torch/utils/moe.py b/modelopt/torch/utils/moe.py index 80924b72f26..3351b68f7a1 100644 --- a/modelopt/torch/utils/moe.py +++ b/modelopt/torch/utils/moe.py @@ -291,9 +291,8 @@ def register_torch_a2a(): These two styles are functionally transformers' ``megamoe_router`` / ``megamoe_experts`` (added in 5.10.4 for DeepGEMM Mega-MoE), reimplemented here so the transport works below 5.10.4 too. - The base classes (``RouterParallel``, ``TensorParallelLayer``) are stock wherever the EP infra - exists (transformers >= 5.6, the framework floor enforced in ``load_and_prepare_ep``), so this - works on any such version; ``grouped_gemm`` (expert weight sharding) is stock too. + The base classes (``RouterParallel``, ``TensorParallelLayer``) are stock in every transformers + version ModelOpt supports; ``grouped_gemm`` (expert weight sharding) is stock too. """ global _TORCH_A2A_REGISTERED if _TORCH_A2A_REGISTERED: diff --git a/noxfile.py b/noxfile.py index 8528f7136e0..78627e773a6 100644 --- a/noxfile.py +++ b/noxfile.py @@ -48,7 +48,7 @@ # ``.[all,dev-test]`` install to constrain that env). TRANSFORMERS_VERSIONS = { "tf_latest": ("transformers~=5.18.0",), - "tf_min": ("transformers~=5.5.0",), + "tf_min": ("transformers~=5.8.0",), } diff --git a/pyproject.toml b/pyproject.toml index d911a6e2260..ae913c62772 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,11 +82,11 @@ hf = [ "diffusers>=0.37.0", "httpx>=0.23.0,<1", "huggingface_hub>=1.5.0", - "nltk", + "nltk>=3.9", "peft>=0.17.0", "sentencepiece>=0.2.1", # Also implicitly used in test_unified_export_megatron, test_vllm_fakequant_megatron_export "tiktoken", - "transformers>=5.5,<5.19", # Should match modelopt/torch/__init__.py and noxfile.py + "transformers>=5.8,<5.19", # Should match modelopt/torch/__init__.py and noxfile.py "wonderwords", ] diff --git a/tests/gpu/torch/utils/test_moe_a2a.py b/tests/gpu/torch/utils/test_moe_a2a.py index 9b8a3be53a4..ef85c0b9b44 100644 --- a/tests/gpu/torch/utils/test_moe_a2a.py +++ b/tests/gpu/torch/utils/test_moe_a2a.py @@ -20,7 +20,6 @@ import pytest import torch import torch.distributed as dist -from packaging.version import Version import modelopt.torch.utils.moe as moe @@ -67,7 +66,5 @@ def test_torch_a2a_gives_grouped_mms_result_bit_for_bit(dist_workers): """fp32 routing weights, as Mixtral's and DeepSeek-V3's routers return, make each token's top-k sum an fp32 one, whose bf16 result depends on the order of its adds. At EP=1 the torch_a2a forward adds them in grouped_mm's fixed order, not in the order of CUDA atomics.""" - transformers = pytest.importorskip("transformers") - if Version(transformers.__version__) < Version("5.6"): - pytest.skip("expert parallelism needs transformers >= 5.6") + pytest.importorskip("transformers") dist_workers.run(_grouped_mm_bit_for_bit_job) diff --git a/tests/unit/torch/utils/test_moe_a2a.py b/tests/unit/torch/utils/test_moe_a2a.py index a53c334ec51..2d79715a673 100644 --- a/tests/unit/torch/utils/test_moe_a2a.py +++ b/tests/unit/torch/utils/test_moe_a2a.py @@ -32,7 +32,6 @@ import pytest import torch import torch.distributed as dist -from packaging.version import Version import modelopt.torch.utils.moe as moe from modelopt.torch.utils.moe import a2a_moe @@ -217,9 +216,7 @@ def test_the_router_style_registers_under_transformers_5_16_0s_name(monkeypatch) def test_experts_whose_biases_the_plan_left_whole_are_refused(): """A plan that shards GPT-OSS's expert weights but not their biases leaves every rank all the experts' biases, which the forward indexes by local expert id; it refuses them instead.""" - transformers = pytest.importorskip("transformers") - if Version(transformers.__version__) < Version("5.6"): - pytest.skip("expert parallelism needs transformers >= 5.6") + pytest.importorskip("transformers") from transformers import GptOssConfig from transformers.models.gpt_oss.modeling_gpt_oss import GptOssExperts @@ -254,9 +251,7 @@ def _no_tokens_for_rank1_job(rank, size): def test_experts_that_receive_no_tokens_run_on_an_empty_batch(cpu_dist_workers): """Every token routed to rank 0's experts: rank 1 runs transformers' fused experts on an empty batch, and the forward and gradients still match the whole module's on every rank.""" - transformers = pytest.importorskip("transformers") - if Version(transformers.__version__) < Version("5.6"): - pytest.skip("expert parallelism needs transformers >= 5.6") + pytest.importorskip("transformers") cpu_dist_workers.run(_no_tokens_for_rank1_job) @@ -372,9 +367,7 @@ def test_fused_experts_backward_through_torch_a2a_matches_the_whole_module(cpu_d """transformers' fused experts, split over two ranks, train through the torch_a2a forward exactly as the whole module does on one process: same output, and same gradients for the hidden states, the routing weights and each rank's experts.""" - transformers = pytest.importorskip("transformers") - if Version(transformers.__version__) < Version("5.6"): - pytest.skip("expert parallelism needs transformers >= 5.6") + pytest.importorskip("transformers") cpu_dist_workers.run(_real_experts_backward_job) @@ -397,7 +390,5 @@ def _fp32_routing_weights_job(rank, size): def test_fp32_routing_weights_give_grouped_mms_result_at_ep1(cpu_dist_workers): """A router may return fp32 weights for bf16 experts, as Mixtral's does. grouped_mm weights the experts' results in fp32 then; at EP=1 the torch_a2a forward must give its result bit for bit.""" - transformers = pytest.importorskip("transformers") - if Version(transformers.__version__) < Version("5.6"): - pytest.skip("expert parallelism needs transformers >= 5.6") + pytest.importorskip("transformers") cpu_dist_workers.run(_fp32_routing_weights_job) From daec6dee4f17afc48df681b691e24e775b3fba1f Mon Sep 17 00:00:00 2001 From: Keval Morabia <28916987+kevalmorabia97@users.noreply.github.com> Date: Wed, 28 Oct 2026 14:48:04 -0700 Subject: [PATCH 4/9] Drop transformers 4.x compatibility leftovers Use AutoModelForImageTextToText directly, import native NemotronH, Qwen3.5 and conversion_mapping at module top, and remove test skips for APIs and models every transformers>=5.8 has. Co-Authored-By: Claude Opus 5.5 Signed-off-by: Keval Morabia <28916987+kevalmorabia97@users.noreply.github.com> --- modelopt/torch/models/nemotron_h/mtp.py | 15 +++----- modelopt/torch/speculative/utils.py | 8 +---- .../_test_utils/torch/transformers_models.py | 34 +++++-------------- .../export/test_quant_aware_conversion_gpu.py | 9 +---- .../export/test_qwen_vision_recipe_export.py | 3 -- tests/unit/recipe/test_qwen_vision_recipe.py | 3 -- .../torch/export/test_hf_spec_rope_export.py | 4 +-- .../export/test_quant_aware_conversion.py | 7 ---- .../torch/export/test_unified_export_hf.py | 4 +-- .../torch/utils/test_hf_checkpoint_utils.py | 3 +- 10 files changed, 19 insertions(+), 71 deletions(-) diff --git a/modelopt/torch/models/nemotron_h/mtp.py b/modelopt/torch/models/nemotron_h/mtp.py index 672aab1c324..6018d91aa2d 100644 --- a/modelopt/torch/models/nemotron_h/mtp.py +++ b/modelopt/torch/models/nemotron_h/mtp.py @@ -30,6 +30,11 @@ from transformers import AutoConfig from transformers.dynamic_module_utils import get_class_from_dynamic_module from transformers.masking_utils import create_causal_mask +from transformers.models.nemotron_h.modeling_nemotron_h import ( + MIXER_TYPES, + NemotronHBlock, + NemotronHRMSNorm, +) from modelopt.torch.quantization.model_calib import ( _needs_activation_forward_for_max_calib, @@ -84,9 +89,6 @@ def prefers_bundled_code(hf_config, checkpoint_path: str, config_kwargs: dict) - # Config-parsing incompatibilities only; e.g. an OSError from the Hub must propagate. return True # Parsing can succeed while model construction fails on a block type the built-in lacks. - # Native Nemotron-H is optional: older Transformers can still load unrelated models. - from transformers.models.nemotron_h.modeling_nemotron_h import MIXER_TYPES - return any(t not in MIXER_TYPES for t in getattr(builtin_config, "layers_block_type", ())) @@ -141,13 +143,6 @@ class _NemotronHMTP(torch.nn.Module): def __init__(self, config): super().__init__() - # Native Nemotron-H is optional: older Transformers can still load unrelated models. - from transformers.models.nemotron_h.modeling_nemotron_h import ( - MIXER_TYPES, - NemotronHBlock, - NemotronHRMSNorm, - ) - block_config = copy.deepcopy(config) # Some remote-code revisions drop this field while materializing ``llm_config``. # The detected tensor layout is the canonical attention + MoE MTP tail. diff --git a/modelopt/torch/speculative/utils.py b/modelopt/torch/speculative/utils.py index 910536fdb10..23c75203338 100644 --- a/modelopt/torch/speculative/utils.py +++ b/modelopt/torch/speculative/utils.py @@ -778,13 +778,7 @@ def _warn_overrides_on_fake_base(): return FakeBaseModel.from_source(model_name_or_path, trust_remote_code=trust_remote_code) if _is_vlm: - # Transformers 5 renamed AutoModelForVision2Seq to - # AutoModelForImageTextToText. Prefer the pre-5 name so this loader - # continues to support the Transformers 4 environments used by older - # speculative-decoding jobs. - model_cls = getattr(transformers, "AutoModelForVision2Seq", None) - if model_cls is None: - model_cls = transformers.AutoModelForImageTextToText + model_cls = transformers.AutoModelForImageTextToText else: model_cls = transformers.AutoModelForCausalLM diff --git a/tests/_test_utils/torch/transformers_models.py b/tests/_test_utils/torch/transformers_models.py index bdf910981d2..46543bab271 100644 --- a/tests/_test_utils/torch/transformers_models.py +++ b/tests/_test_utils/torch/transformers_models.py @@ -41,16 +41,22 @@ LlamaForSequenceClassification, MixtralConfig, NemotronConfig, + NemotronHConfig, PreTrainedModel, + Qwen3_5Config, + Qwen3_5MoeConfig, Qwen3Config, Qwen3MoeConfig, Qwen3VLConfig, - Qwen3VLTextConfig, T5Config, T5ForConditionalGeneration, ViTConfig, ViTImageProcessor, ) +from transformers.conversion_mapping import ( + get_checkpoint_conversion_mapping, + register_checkpoint_conversion_mapping, +) import modelopt.torch.opt as mto @@ -227,14 +233,7 @@ def get_tiny_qwen3vl(**config_kwargs) -> PreTrainedModel: "vocab_size": 32, "rope_scaling": {"rope_type": "default", "mrope_section": [1, 1, 2]}, } - # Transformers 4.x names this field rope_scaling; 5.x renamed it to rope_parameters. - # Supplying it avoids the 4.57 Qwen3-VL constructor dereferencing a None rope config. - rope_field = ( - "rope_parameters" - if "rope_parameters" in getattr(Qwen3VLTextConfig, "__annotations__", {}) - else "rope_scaling" - ) - text_kwargs[rope_field] = {"rope_type": "default", "mrope_section": [1, 1, 2]} + text_kwargs["rope_parameters"] = {"rope_type": "default", "mrope_section": [1, 1, 2]} text_kwargs.update(config_kwargs) # Pass as dicts — transformers 5.3.0 Qwen3VLConfig.__init__ only handles # vision_config/text_config when they are dicts or None, not instances. @@ -351,9 +350,6 @@ def __len__(self): def _get_tiny_qwen3_5_vl(moe: bool = False, *, tokenizer=None, **config_kwargs) -> PreTrainedModel: - # Lazy imports — Qwen3.5-VL requires a recent transformers version. - from transformers import Qwen3_5Config, Qwen3_5MoeConfig - set_seed(SEED) # Vocab + vision token ids normally derive from the reference tokenizer. Tests that do not @@ -529,16 +525,6 @@ def _match_released_nemotron_h_embedding_name() -> None: with every real checkpoint on one tensor. Dropped upstream in 5.16, where this is a no-op since there is no such rule left to remove. """ - # Optional dependency: conversion_mapping is transformers 5.x only, while NemotronH also - # exists on the 4.57 floor of the support matrix. - try: - from transformers.conversion_mapping import ( - get_checkpoint_conversion_mapping, - register_checkpoint_conversion_mapping, - ) - except ImportError: - return - mapping = get_checkpoint_conversion_mapping("nemotron_h") if mapping is None: return @@ -550,10 +536,6 @@ def _match_released_nemotron_h_embedding_name() -> None: def get_tiny_nemotron_h(**config_kwargs) -> PreTrainedModel: set_seed(SEED) - # Lazy import — NemotronHConfig only exists in newer transformers builds, and this - # module is imported broadly (including by the min-transformers CI job). - from transformers import NemotronHConfig - _match_released_nemotron_h_embedding_name() # Tiny NemotronH hybrid. hybrid_override_pattern letters: M=Mamba, E=MoE/FFN, *=Attention. diff --git a/tests/gpu/torch/export/test_quant_aware_conversion_gpu.py b/tests/gpu/torch/export/test_quant_aware_conversion_gpu.py index 32ffa5d2f1d..4d7c3d212c2 100644 --- a/tests/gpu/torch/export/test_quant_aware_conversion_gpu.py +++ b/tests/gpu/torch/export/test_quant_aware_conversion_gpu.py @@ -31,6 +31,7 @@ import torch from _test_utils.torch.transformers_models import get_tiny_mixtral from safetensors import safe_open +from transformers.core_model_loading import revert_weight_conversion import modelopt.torch.quantization as mtq from modelopt.torch.export import export_hf_checkpoint @@ -55,14 +56,6 @@ def _tiny_mixtral(): def test_export_tensor_names_match_hub_after_conversion_reverse(): - try: - from transformers.conversion_mapping import get_checkpoint_conversion_mapping - from transformers.core_model_loading import revert_weight_conversion - except ImportError: - pytest.skip("transformers build has no conversion_mapping API") - if not get_checkpoint_conversion_mapping("mixtral"): - pytest.skip("transformers build has no mixtral conversion_mapping") - # Canonical hub names: transformers' own reverse on the unquantized reference. ref = _tiny_mixtral() hub_names = set(revert_weight_conversion(ref, ref.state_dict()).keys()) diff --git a/tests/gpu/torch/export/test_qwen_vision_recipe_export.py b/tests/gpu/torch/export/test_qwen_vision_recipe_export.py index 389375428b1..53872377948 100644 --- a/tests/gpu/torch/export/test_qwen_vision_recipe_export.py +++ b/tests/gpu/torch/export/test_qwen_vision_recipe_export.py @@ -17,7 +17,6 @@ import pytest import torch -import transformers from _test_utils.torch import transformers_models from safetensors import safe_open @@ -32,8 +31,6 @@ def _get_tiny_qwen_vlm(model_type): if model_type == "qwen3_vl": return transformers_models.get_tiny_qwen3vl() - if not all(hasattr(transformers, name) for name in ("Qwen3_5Config", "Qwen3_5MoeConfig")): - pytest.skip("Qwen3.5-VL requires a newer Transformers release") return transformers_models.get_tiny_qwen3_5_vl_offline() diff --git a/tests/unit/recipe/test_qwen_vision_recipe.py b/tests/unit/recipe/test_qwen_vision_recipe.py index 264e3b73239..6de8403d840 100644 --- a/tests/unit/recipe/test_qwen_vision_recipe.py +++ b/tests/unit/recipe/test_qwen_vision_recipe.py @@ -14,7 +14,6 @@ # limitations under the License. import pytest -import transformers from _test_utils.torch import transformers_models import modelopt.torch.quantization as mtq @@ -27,8 +26,6 @@ def _get_tiny_qwen_vlm(model_type): if model_type == "qwen3_vl": return transformers_models.get_tiny_qwen3vl() - if not all(hasattr(transformers, name) for name in ("Qwen3_5Config", "Qwen3_5MoeConfig")): - pytest.skip("Qwen3.5-VL requires a newer Transformers release") return transformers_models.get_tiny_qwen3_5_vl_offline() diff --git a/tests/unit/torch/export/test_hf_spec_rope_export.py b/tests/unit/torch/export/test_hf_spec_rope_export.py index cceddce9c9c..737aaa46d82 100644 --- a/tests/unit/torch/export/test_hf_spec_rope_export.py +++ b/tests/unit/torch/export/test_hf_spec_rope_export.py @@ -216,8 +216,8 @@ def test_missing_everywhere_returns_the_default(self): def test_reads_a_real_config_whichever_layout_it_uses(self): """A real config resolves on every supported transformers version. - Asserts the outcome, not the layout: 5.12 keeps the value only in the dict while - the minimum supported version (4.57) has only the flat field and no dict at all. + Asserts the outcome, not the layout, which varies across supported releases (5.12 + keeps the value only in the dict). The layouts themselves are pinned above, built explicitly, so they stay covered where transformers is absent -- this is the only test here that needs it. """ diff --git a/tests/unit/torch/export/test_quant_aware_conversion.py b/tests/unit/torch/export/test_quant_aware_conversion.py index 1a798b2c1fd..ba86a58f736 100644 --- a/tests/unit/torch/export/test_quant_aware_conversion.py +++ b/tests/unit/torch/export/test_quant_aware_conversion.py @@ -638,13 +638,6 @@ def test_build_reverse_rules_from_mixtral_conversion_mapping_cpu(): pytest.importorskip("transformers") from _test_utils.torch.transformers_models import get_tiny_mixtral - try: - from transformers.conversion_mapping import get_checkpoint_conversion_mapping - except ImportError: - pytest.skip("transformers build has no conversion_mapping API") - if not get_checkpoint_conversion_mapping("mixtral"): - pytest.skip("transformers build has no mixtral conversion_mapping") - model = get_tiny_mixtral(**_MIXTRAL_KWARGS) p = "model.layers.0" diff --git a/tests/unit/torch/export/test_unified_export_hf.py b/tests/unit/torch/export/test_unified_export_hf.py index 57923dbadaf..1b8d55373fa 100644 --- a/tests/unit/torch/export/test_unified_export_hf.py +++ b/tests/unit/torch/export/test_unified_export_hf.py @@ -136,9 +136,7 @@ def test_hf_all_tied_weights_keys_contract(): (``tie_word_embeddings=True`` -> lm_head aliases the embedding). If transformers renames it or flips the direction, this breaks instead of silently skipping tied-weight dedup. """ - pytest.importorskip( - "transformers", minversion="5.0" - ) # attribute only exists on transformers>=5.0 + pytest.importorskip("transformers") from transformers import AutoModelForCausalLM, LlamaConfig cfg = LlamaConfig( diff --git a/tests/unit/torch/utils/test_hf_checkpoint_utils.py b/tests/unit/torch/utils/test_hf_checkpoint_utils.py index ef992a9f832..b14f26b4acb 100644 --- a/tests/unit/torch/utils/test_hf_checkpoint_utils.py +++ b/tests/unit/torch/utils/test_hf_checkpoint_utils.py @@ -228,8 +228,7 @@ def test_sanitize_hf_config_for_deployment_trims_nextn_layer_types(): @pytest.mark.parametrize("native", [False, True]) @pytest.mark.parametrize("keep_tokenizer", [False, True]) def test_export_config_reloads_nemotron_h_with_source_auto_map(tmp_path, native, keep_tokenizer): - # Native NemotronH is optional on the minimum-Transformers test lane. - transformers = pytest.importorskip("transformers", minversion="5.5") + transformers = pytest.importorskip("transformers") from modelopt.torch.export.unified_export_hf import ( _write_hf_export_config, save_non_weight_artifacts, From ecf455954bab2f28fe9124698b6e52c614c207fe Mon Sep 17 00:00:00 2001 From: Keval Morabia <28916987+kevalmorabia97@users.noreply.github.com> Date: Wed, 28 Oct 2026 23:41:36 -0700 Subject: [PATCH 5/9] Read NemotronH's mixer registry at call time A module-level MIXER_TYPES import kept a stale mapping once a test patched it. Co-Authored-By: Claude Opus 5.5 Signed-off-by: Keval Morabia <28916987+kevalmorabia97@users.noreply.github.com> --- modelopt/torch/models/nemotron_h/mtp.py | 30 ++++++++++++++++--------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/modelopt/torch/models/nemotron_h/mtp.py b/modelopt/torch/models/nemotron_h/mtp.py index 6018d91aa2d..2ac7d0509c3 100644 --- a/modelopt/torch/models/nemotron_h/mtp.py +++ b/modelopt/torch/models/nemotron_h/mtp.py @@ -30,11 +30,7 @@ from transformers import AutoConfig from transformers.dynamic_module_utils import get_class_from_dynamic_module from transformers.masking_utils import create_causal_mask -from transformers.models.nemotron_h.modeling_nemotron_h import ( - MIXER_TYPES, - NemotronHBlock, - NemotronHRMSNorm, -) +from transformers.models.nemotron_h import modeling_nemotron_h from modelopt.torch.quantization.model_calib import ( _needs_activation_forward_for_max_calib, @@ -89,7 +85,10 @@ def prefers_bundled_code(hf_config, checkpoint_path: str, config_kwargs: dict) - # Config-parsing incompatibilities only; e.g. an OSError from the Hub must propagate. return True # Parsing can succeed while model construction fails on a block type the built-in lacks. - return any(t not in MIXER_TYPES for t in getattr(builtin_config, "layers_block_type", ())) + return any( + t not in modeling_nemotron_h.MIXER_TYPES + for t in getattr(builtin_config, "layers_block_type", ()) + ) def _normalize_mtp_block_types(block_types, mixer_types): @@ -147,17 +146,26 @@ def __init__(self, config): # Some remote-code revisions drop this field while materializing ``llm_config``. # The detected tensor layout is the canonical attention + MoE MTP tail. block_types = list(getattr(config, "mtp_layers_block_type", None) or ("attention", "moe")) - block_config.layers_block_type = _normalize_mtp_block_types(block_types, MIXER_TYPES) + block_config.layers_block_type = _normalize_mtp_block_types( + block_types, modeling_nemotron_h.MIXER_TYPES + ) self.layers = torch.nn.ModuleList( - [NemotronHBlock(block_config, layer_idx) for layer_idx in range(len(block_types))] + [ + modeling_nemotron_h.NemotronHBlock(block_config, layer_idx) + for layer_idx in range(len(block_types)) + ] ) first_layer, last_layer = self.layers[0], self.layers[-1] - first_layer.enorm = NemotronHRMSNorm(config.hidden_size, eps=config.layer_norm_epsilon) - first_layer.hnorm = NemotronHRMSNorm(config.hidden_size, eps=config.layer_norm_epsilon) + first_layer.enorm = modeling_nemotron_h.NemotronHRMSNorm( + config.hidden_size, eps=config.layer_norm_epsilon + ) + first_layer.hnorm = modeling_nemotron_h.NemotronHRMSNorm( + config.hidden_size, eps=config.layer_norm_epsilon + ) first_layer.eh_proj = torch.nn.Linear( config.hidden_size * 2, config.hidden_size, bias=False ) - last_layer.final_layernorm = NemotronHRMSNorm( + last_layer.final_layernorm = modeling_nemotron_h.NemotronHRMSNorm( config.hidden_size, eps=config.layer_norm_epsilon ) From 1c5f591e9bf005bc7a96cbd443c3f1f54167cd7e Mon Sep 17 00:00:00 2001 From: Keval Morabia <28916987+kevalmorabia97@users.noreply.github.com> Date: Wed, 28 Oct 2026 23:50:26 -0700 Subject: [PATCH 6/9] Bump transformers to >=5.9 transformers 5.8.0 and 5.8.1 skip their own weight init for models built from a config, leaving every nn.Linear at PyTorch's default init. Co-Authored-By: Claude Opus 5.5 Signed-off-by: Keval Morabia <28916987+kevalmorabia97@users.noreply.github.com> --- CHANGELOG.rst | 2 +- modelopt/torch/__init__.py | 2 +- noxfile.py | 2 +- pyproject.toml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 8f135a88763..5bcdce2da0f 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -47,7 +47,7 @@ Changelog - ``export_mcore_gpt_to_hf`` given a Hugging Face Hub model ID no longer writes the tokenizer, processor, generation config or remote-code files. Pass a local directory instead, e.g. from ``modelopt.torch.export.ensure_local_checkpoint``. - Drop support for Python 3.10, which has reached end-of-life. ModelOpt now requires Python 3.11 or later. - Bump minimum required PyTorch version to 2.10. -- Bump minimum required ``transformers`` version to 5.8, ``diffusers`` to 0.37 and ``nltk`` to 3.9 for the ``[hf]`` extra. +- Bump minimum required ``transformers`` version to 5.9, ``diffusers`` to 0.37 and ``nltk`` to 3.9 for the ``[hf]`` extra. - Diffusion HF exports now omit attention quantizer buffers by default, except for FLUX.1, which retains enabled Q/K/V ``_amax`` keys for runtime compatibility; other consumers of those keys can opt in with ``export_attention_quantizers=True`` (CLI: ``--extra-param export_attention_quantizers=true``). Disabled attention quantizers remain excluded, and diffusion components, including LTX-2, no longer emit KV-cache quantization metadata. - ``hf_quant_config.json`` of an INT4 or W4A8 AWQ checkpoint now sets ``pre_quant_scale`` per layer, only for layers that have the tensor. A model whose fused-MoE experts have none (AWQ never applies to them) is now exported as ``MIXED_PRECISION`` with per-layer entries instead of a uniform algorithm. - The Windows FVD example rejects I3D checkpoints requiring unrestricted pickle loading. Custom TorchScript archives now require ``--weights-sha256`` with a trusted checksum; tensor state dictionaries continue to load directly. diff --git a/modelopt/torch/__init__.py b/modelopt/torch/__init__.py index 1caebbfbe3c..55975bb4ec0 100644 --- a/modelopt/torch/__init__.py +++ b/modelopt/torch/__init__.py @@ -48,7 +48,7 @@ try: from transformers import __version__ as _transformers_version - if _Version(_transformers_version) < _Version("5.8") or _Version( + if _Version(_transformers_version) < _Version("5.9") or _Version( _transformers_version ) >= _Version("5.19"): _warnings.warn( diff --git a/noxfile.py b/noxfile.py index 78627e773a6..ebda78425aa 100644 --- a/noxfile.py +++ b/noxfile.py @@ -48,7 +48,7 @@ # ``.[all,dev-test]`` install to constrain that env). TRANSFORMERS_VERSIONS = { "tf_latest": ("transformers~=5.18.0",), - "tf_min": ("transformers~=5.8.0",), + "tf_min": ("transformers~=5.9.0",), } diff --git a/pyproject.toml b/pyproject.toml index ae913c62772..8f38814a1fe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -86,7 +86,7 @@ hf = [ "peft>=0.17.0", "sentencepiece>=0.2.1", # Also implicitly used in test_unified_export_megatron, test_vllm_fakequant_megatron_export "tiktoken", - "transformers>=5.8,<5.19", # Should match modelopt/torch/__init__.py and noxfile.py + "transformers>=5.9,<5.19", # Should match modelopt/torch/__init__.py and noxfile.py "wonderwords", ] From 3fca1bf920e1debc7432191e0769183e49ccff20 Mon Sep 17 00:00:00 2001 From: Keval Morabia <28916987+kevalmorabia97@users.noreply.github.com> Date: Thu, 29 Oct 2026 00:05:34 -0700 Subject: [PATCH 7/9] Bump peft to >=0.19 and accelerate to >=1.6; trim redundant example pins transformers>=5.6 refuses peft<0.18.2 in add_adapter/enable_adapters, which the QAT trainer calls, and ModelOpt's HF trainers read Accelerator.is_fsdp2 (accelerate 1.6+). Drop example pins the new floors already cover. Co-Authored-By: Claude Opus 5.5 Signed-off-by: Keval Morabia <28916987+kevalmorabia97@users.noreply.github.com> --- CHANGELOG.rst | 2 +- examples/gpt-oss/requirements.txt | 2 -- examples/speculative_decoding/requirements.txt | 3 +-- examples/torch_trt/requirements.txt | 3 +-- pyproject.toml | 4 ++-- 5 files changed, 5 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 5bcdce2da0f..8a6715572d4 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -47,7 +47,7 @@ Changelog - ``export_mcore_gpt_to_hf`` given a Hugging Face Hub model ID no longer writes the tokenizer, processor, generation config or remote-code files. Pass a local directory instead, e.g. from ``modelopt.torch.export.ensure_local_checkpoint``. - Drop support for Python 3.10, which has reached end-of-life. ModelOpt now requires Python 3.11 or later. - Bump minimum required PyTorch version to 2.10. -- Bump minimum required ``transformers`` version to 5.9, ``diffusers`` to 0.37 and ``nltk`` to 3.9 for the ``[hf]`` extra. +- Bump minimum required ``transformers`` version to 5.9, ``diffusers`` to 0.37, ``peft`` to 0.19, ``accelerate`` to 1.6 and ``nltk`` to 3.9 for the ``[hf]`` extra. - Diffusion HF exports now omit attention quantizer buffers by default, except for FLUX.1, which retains enabled Q/K/V ``_amax`` keys for runtime compatibility; other consumers of those keys can opt in with ``export_attention_quantizers=True`` (CLI: ``--extra-param export_attention_quantizers=true``). Disabled attention quantizers remain excluded, and diffusion components, including LTX-2, no longer emit KV-cache quantization metadata. - ``hf_quant_config.json`` of an INT4 or W4A8 AWQ checkpoint now sets ``pre_quant_scale`` per layer, only for layers that have the tensor. A model whose fused-MoE experts have none (AWQ never applies to them) is now exported as ``MIXED_PRECISION`` with per-layer entries instead of a uniform algorithm. - The Windows FVD example rejects I3D checkpoints requiring unrestricted pickle loading. Custom TorchScript archives now require ``--weights-sha256`` with a trusted checksum; tensor state dictionaries continue to load directly. diff --git a/examples/gpt-oss/requirements.txt b/examples/gpt-oss/requirements.txt index 260f256e6e4..0ee4601c161 100644 --- a/examples/gpt-oss/requirements.txt +++ b/examples/gpt-oss/requirements.txt @@ -1,5 +1,3 @@ kernels>=0.9.0,<0.13 trackio>=0.41 -# transformers>=5.3 avoids CVE-2026-4372 (RCE via the `kernels` Hub download path, which this example installs) -transformers>=5.3 trl>=1.0,<1.15 diff --git a/examples/speculative_decoding/requirements.txt b/examples/speculative_decoding/requirements.txt index d333178fad9..3df5ac52fdf 100644 --- a/examples/speculative_decoding/requirements.txt +++ b/examples/speculative_decoding/requirements.txt @@ -1,4 +1,3 @@ accelerate>=1.12.0 -datasets peft==0.18.1 -transformers>=5.0,<5.13 +transformers<5.13 diff --git a/examples/torch_trt/requirements.txt b/examples/torch_trt/requirements.txt index 436366630e3..e2fc4c8095d 100644 --- a/examples/torch_trt/requirements.txt +++ b/examples/torch_trt/requirements.txt @@ -3,6 +3,5 @@ datasets>=2.14.4 # torch / torchvision / torch-tensorrt trio together: torchvision pins an exact # torch, so if the base install pulls a newer torch (e.g. 2.13) the torch-tensorrt # downgrade would otherwise leave torchvision mismatched and break `import`. -torch-tensorrt>=2.4.0,<2.13 +torch-tensorrt>=2.10,<2.13 torchvision<0.28 -transformers>=4.56 diff --git a/pyproject.toml b/pyproject.toml index 8f38814a1fe..bdeda676e27 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -76,14 +76,14 @@ onnx = [ "onnxruntime-ep-nv-tensorrt-rtx-cu13==0.4.0; platform_system == 'Windows' and platform_machine == 'ARM64'", ] hf = [ - "accelerate>=1.1.0", + "accelerate>=1.6.0", "datasets>=3.0.0", "deepspeed>=0.9.6; platform_system != 'Darwin' and platform_system != 'Windows'", "diffusers>=0.37.0", "httpx>=0.23.0,<1", "huggingface_hub>=1.5.0", "nltk>=3.9", - "peft>=0.17.0", + "peft>=0.19.0", "sentencepiece>=0.2.1", # Also implicitly used in test_unified_export_megatron, test_vllm_fakequant_megatron_export "tiktoken", "transformers>=5.9,<5.19", # Should match modelopt/torch/__init__.py and noxfile.py From f8ce66e19605f86bd6bb9a78aa9733ce656b10a5 Mon Sep 17 00:00:00 2001 From: Keval Morabia <28916987+kevalmorabia97@users.noreply.github.com> Date: Thu, 29 Oct 2026 00:24:23 -0700 Subject: [PATCH 8/9] Drop the speculative-decoding example's peft==0.18.1 pin It downgraded peft below the [hf] extra's new >=0.19 floor; the EAGLE LoRA tests pass on peft 0.18.1 through 0.21.2. Co-Authored-By: Claude Opus 5.5 Signed-off-by: Keval Morabia <28916987+kevalmorabia97@users.noreply.github.com> --- examples/speculative_decoding/requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/speculative_decoding/requirements.txt b/examples/speculative_decoding/requirements.txt index 3df5ac52fdf..b9068fd6f12 100644 --- a/examples/speculative_decoding/requirements.txt +++ b/examples/speculative_decoding/requirements.txt @@ -1,3 +1,2 @@ accelerate>=1.12.0 -peft==0.18.1 transformers<5.13 From c6b635662efb5dc37b76bb7c6c08560045db04f3 Mon Sep 17 00:00:00 2001 From: Keval Morabia <28916987+kevalmorabia97@users.noreply.github.com> Date: Thu, 29 Oct 2026 01:28:27 -0700 Subject: [PATCH 9/9] Keep the transformers floor at 5.5 and peft at 0.18.1 for TensorRT-LLM TensorRT-LLM 1.3.0 pins transformers==5.5.4 and peft<0.19, so higher floors upgrade them in its container and break TensorRT-LLM's own Qwen3-VL loading. Restore the transformers<5.6 expert-parallel test skips. Co-Authored-By: Claude Opus 5.5 Signed-off-by: Keval Morabia <28916987+kevalmorabia97@users.noreply.github.com> --- CHANGELOG.rst | 2 +- modelopt/torch/__init__.py | 2 +- modelopt/torch/utils/moe.py | 4 ++-- noxfile.py | 2 +- pyproject.toml | 5 +++-- tests/gpu/torch/utils/test_moe_a2a.py | 5 ++++- tests/unit/torch/utils/test_moe_a2a.py | 17 +++++++++++++---- 7 files changed, 25 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 8a6715572d4..5b8b221cca7 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -47,7 +47,7 @@ Changelog - ``export_mcore_gpt_to_hf`` given a Hugging Face Hub model ID no longer writes the tokenizer, processor, generation config or remote-code files. Pass a local directory instead, e.g. from ``modelopt.torch.export.ensure_local_checkpoint``. - Drop support for Python 3.10, which has reached end-of-life. ModelOpt now requires Python 3.11 or later. - Bump minimum required PyTorch version to 2.10. -- Bump minimum required ``transformers`` version to 5.9, ``diffusers`` to 0.37, ``peft`` to 0.19, ``accelerate`` to 1.6 and ``nltk`` to 3.9 for the ``[hf]`` extra. +- Bump minimum required ``diffusers`` version to 0.37, ``peft`` to 0.18.1, ``accelerate`` to 1.6 and ``nltk`` to 3.9 for the ``[hf]`` extra. - Diffusion HF exports now omit attention quantizer buffers by default, except for FLUX.1, which retains enabled Q/K/V ``_amax`` keys for runtime compatibility; other consumers of those keys can opt in with ``export_attention_quantizers=True`` (CLI: ``--extra-param export_attention_quantizers=true``). Disabled attention quantizers remain excluded, and diffusion components, including LTX-2, no longer emit KV-cache quantization metadata. - ``hf_quant_config.json`` of an INT4 or W4A8 AWQ checkpoint now sets ``pre_quant_scale`` per layer, only for layers that have the tensor. A model whose fused-MoE experts have none (AWQ never applies to them) is now exported as ``MIXED_PRECISION`` with per-layer entries instead of a uniform algorithm. - The Windows FVD example rejects I3D checkpoints requiring unrestricted pickle loading. Custom TorchScript archives now require ``--weights-sha256`` with a trusted checksum; tensor state dictionaries continue to load directly. diff --git a/modelopt/torch/__init__.py b/modelopt/torch/__init__.py index 55975bb4ec0..1182304f541 100644 --- a/modelopt/torch/__init__.py +++ b/modelopt/torch/__init__.py @@ -48,7 +48,7 @@ try: from transformers import __version__ as _transformers_version - if _Version(_transformers_version) < _Version("5.9") or _Version( + if _Version(_transformers_version) < _Version("5.5") or _Version( _transformers_version ) >= _Version("5.19"): _warnings.warn( diff --git a/modelopt/torch/utils/moe.py b/modelopt/torch/utils/moe.py index 3351b68f7a1..fc62fe9f71c 100644 --- a/modelopt/torch/utils/moe.py +++ b/modelopt/torch/utils/moe.py @@ -291,8 +291,8 @@ def register_torch_a2a(): These two styles are functionally transformers' ``megamoe_router`` / ``megamoe_experts`` (added in 5.10.4 for DeepGEMM Mega-MoE), reimplemented here so the transport works below 5.10.4 too. - The base classes (``RouterParallel``, ``TensorParallelLayer``) are stock in every transformers - version ModelOpt supports; ``grouped_gemm`` (expert weight sharding) is stock too. + The base classes (``RouterParallel``, ``TensorParallelLayer``) are stock wherever the EP infra + exists (transformers >= 5.6); ``grouped_gemm`` (expert weight sharding) is stock too. """ global _TORCH_A2A_REGISTERED if _TORCH_A2A_REGISTERED: diff --git a/noxfile.py b/noxfile.py index ebda78425aa..8528f7136e0 100644 --- a/noxfile.py +++ b/noxfile.py @@ -48,7 +48,7 @@ # ``.[all,dev-test]`` install to constrain that env). TRANSFORMERS_VERSIONS = { "tf_latest": ("transformers~=5.18.0",), - "tf_min": ("transformers~=5.9.0",), + "tf_min": ("transformers~=5.5.0",), } diff --git a/pyproject.toml b/pyproject.toml index bdeda676e27..ebbe3a79270 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -83,10 +83,11 @@ hf = [ "httpx>=0.23.0,<1", "huggingface_hub>=1.5.0", "nltk>=3.9", - "peft>=0.19.0", + "peft>=0.18.1", "sentencepiece>=0.2.1", # Also implicitly used in test_unified_export_megatron, test_vllm_fakequant_megatron_export "tiktoken", - "transformers>=5.9,<5.19", # Should match modelopt/torch/__init__.py and noxfile.py + # TensorRT-LLM 1.3.0 ships transformers 5.5; keep in sync with modelopt/torch/__init__.py and noxfile.py + "transformers>=5.5,<5.19", "wonderwords", ] diff --git a/tests/gpu/torch/utils/test_moe_a2a.py b/tests/gpu/torch/utils/test_moe_a2a.py index ef85c0b9b44..9b8a3be53a4 100644 --- a/tests/gpu/torch/utils/test_moe_a2a.py +++ b/tests/gpu/torch/utils/test_moe_a2a.py @@ -20,6 +20,7 @@ import pytest import torch import torch.distributed as dist +from packaging.version import Version import modelopt.torch.utils.moe as moe @@ -66,5 +67,7 @@ def test_torch_a2a_gives_grouped_mms_result_bit_for_bit(dist_workers): """fp32 routing weights, as Mixtral's and DeepSeek-V3's routers return, make each token's top-k sum an fp32 one, whose bf16 result depends on the order of its adds. At EP=1 the torch_a2a forward adds them in grouped_mm's fixed order, not in the order of CUDA atomics.""" - pytest.importorskip("transformers") + transformers = pytest.importorskip("transformers") + if Version(transformers.__version__) < Version("5.6"): + pytest.skip("expert parallelism needs transformers >= 5.6") dist_workers.run(_grouped_mm_bit_for_bit_job) diff --git a/tests/unit/torch/utils/test_moe_a2a.py b/tests/unit/torch/utils/test_moe_a2a.py index 2d79715a673..a53c334ec51 100644 --- a/tests/unit/torch/utils/test_moe_a2a.py +++ b/tests/unit/torch/utils/test_moe_a2a.py @@ -32,6 +32,7 @@ import pytest import torch import torch.distributed as dist +from packaging.version import Version import modelopt.torch.utils.moe as moe from modelopt.torch.utils.moe import a2a_moe @@ -216,7 +217,9 @@ def test_the_router_style_registers_under_transformers_5_16_0s_name(monkeypatch) def test_experts_whose_biases_the_plan_left_whole_are_refused(): """A plan that shards GPT-OSS's expert weights but not their biases leaves every rank all the experts' biases, which the forward indexes by local expert id; it refuses them instead.""" - pytest.importorskip("transformers") + transformers = pytest.importorskip("transformers") + if Version(transformers.__version__) < Version("5.6"): + pytest.skip("expert parallelism needs transformers >= 5.6") from transformers import GptOssConfig from transformers.models.gpt_oss.modeling_gpt_oss import GptOssExperts @@ -251,7 +254,9 @@ def _no_tokens_for_rank1_job(rank, size): def test_experts_that_receive_no_tokens_run_on_an_empty_batch(cpu_dist_workers): """Every token routed to rank 0's experts: rank 1 runs transformers' fused experts on an empty batch, and the forward and gradients still match the whole module's on every rank.""" - pytest.importorskip("transformers") + transformers = pytest.importorskip("transformers") + if Version(transformers.__version__) < Version("5.6"): + pytest.skip("expert parallelism needs transformers >= 5.6") cpu_dist_workers.run(_no_tokens_for_rank1_job) @@ -367,7 +372,9 @@ def test_fused_experts_backward_through_torch_a2a_matches_the_whole_module(cpu_d """transformers' fused experts, split over two ranks, train through the torch_a2a forward exactly as the whole module does on one process: same output, and same gradients for the hidden states, the routing weights and each rank's experts.""" - pytest.importorskip("transformers") + transformers = pytest.importorskip("transformers") + if Version(transformers.__version__) < Version("5.6"): + pytest.skip("expert parallelism needs transformers >= 5.6") cpu_dist_workers.run(_real_experts_backward_job) @@ -390,5 +397,7 @@ def _fp32_routing_weights_job(rank, size): def test_fp32_routing_weights_give_grouped_mms_result_at_ep1(cpu_dist_workers): """A router may return fp32 weights for bf16 experts, as Mixtral's does. grouped_mm weights the experts' results in fp32 then; at EP=1 the torch_a2a forward must give its result bit for bit.""" - pytest.importorskip("transformers") + transformers = pytest.importorskip("transformers") + if Version(transformers.__version__) < Version("5.6"): + pytest.skip("expert parallelism needs transformers >= 5.6") cpu_dist_workers.run(_fp32_routing_weights_job)