Skip to content
Merged
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
14 changes: 2 additions & 12 deletions examples/diffusers/quantization/models_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion examples/diffusers/quantization/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 0 additions & 2 deletions examples/gpt-oss/requirements.txt
Original file line number Diff line number Diff line change
@@ -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
6 changes: 1 addition & 5 deletions examples/llm_sparsity/weight_sparsity/eval.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
4 changes: 1 addition & 3 deletions examples/speculative_decoding/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,2 @@
accelerate>=1.12.0
datasets
peft==0.18.1
transformers>=5.0,<5.13
transformers<5.13
3 changes: 1 addition & 2 deletions examples/torch_trt/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion modelopt/torch/export/unified_export_hf.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 6 additions & 0 deletions modelopt/torch/fastgen/plugins/qwen_image_pdd.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
from typing import Any

import torch
from packaging.version import Version
from torch import nn

from ..config import PDDConfig
Expand Down Expand Up @@ -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, "
Expand Down
35 changes: 19 additions & 16 deletions modelopt/torch/models/nemotron_h/mtp.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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
)

Expand Down
55 changes: 20 additions & 35 deletions modelopt/torch/quantization/plugins/diffusion/diffusers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
Comment thread
kevalmorabia97 marked this conversation as resolved.
Resolved
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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
8 changes: 1 addition & 7 deletions modelopt/torch/speculative/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
3 changes: 1 addition & 2 deletions modelopt/torch/utils/moe.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
13 changes: 7 additions & 6 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Comment thread
kevalmorabia97 marked this conversation as resolved.
Resolved
"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",
]

Expand Down
17 changes: 0 additions & 17 deletions tests/_test_utils/import_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down Expand Up @@ -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}"
)
Loading
Loading