diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 90aa9a89b4c..5b8b221cca7 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, ``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/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/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/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/examples/speculative_decoding/requirements.txt b/examples/speculative_decoding/requirements.txt index d333178fad9..b9068fd6f12 100644 --- a/examples/speculative_decoding/requirements.txt +++ b/examples/speculative_decoding/requirements.txt @@ -1,4 +1,2 @@ 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/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/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/modelopt/torch/models/nemotron_h/mtp.py b/modelopt/torch/models/nemotron_h/mtp.py index 672aab1c324..2ac7d0509c3 100644 --- a/modelopt/torch/models/nemotron_h/mtp.py +++ b/modelopt/torch/models/nemotron_h/mtp.py @@ -30,6 +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 import modeling_nemotron_h from modelopt.torch.quantization.model_calib import ( _needs_activation_forward_for_max_calib, @@ -84,10 +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. - # 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", ())) + 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): @@ -141,28 +142,30 @@ 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. 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 ) 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/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/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/modelopt/torch/utils/moe.py b/modelopt/torch/utils/moe.py index 80924b72f26..fc62fe9f71c 100644 --- a/modelopt/torch/utils/moe.py +++ b/modelopt/torch/utils/moe.py @@ -292,8 +292,7 @@ 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. + exists (transformers >= 5.6); ``grouped_gemm`` (expert weight sharding) is stock too. """ global _TORCH_A2A_REGISTERED if _TORCH_A2A_REGISTERED: diff --git a/pyproject.toml b/pyproject.toml index 3c07d934ea1..ebbe3a79270 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -76,17 +76,18 @@ onnx = [ "onnxruntime-ep-nv-tensorrt-rtx-cu13==0.4.0; platform_system == 'Windows' and platform_machine == 'ARM64'", ] hf = [ - "accelerate>=1.0.0", + "accelerate>=1.6.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", - "nltk", - "peft>=0.17.0", + "huggingface_hub>=1.5.0", + "nltk>=3.9", + "peft>=0.18.1", "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 + # 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/_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") 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/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) 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,