Skip to content
Merged
Show file tree
Hide file tree
Changes from 53 commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
676eac4
MSE & mixed precision in mcore
jenchen13 May 22, 2026
9143e02
lint
jenchen13 May 22, 2026
57e5f26
MSE calibration: universal MseCalibrator fallback for non-NVFP4 quant…
jenchen13 May 26, 2026
2dea94a
update mse test for mixed precision
jenchen13 May 26, 2026
918ed6a
fix gpu test, no duplicate backend registration
jenchen13 May 26, 2026
05a436f
Merge branch 'main' into feature/mcore_mse_mixed_precision
jenchen13 May 27, 2026
ff20eca
fix unit tests
jenchen13 May 27, 2026
985da85
lazy init autoquant register
jenchen13 May 27, 2026
d88e54a
revert breaking change on MSECalibrator
jenchen13 May 27, 2026
5f291a6
revert autoquant and gptq changes
jenchen13 May 27, 2026
5c9cd43
fallback to copy HF remote code if no dir
jenchen13 May 27, 2026
c5c7a2e
Merge branch 'main' into feature/mcore_mse_mixed_precision
jenchen13 May 28, 2026
d63bf70
fix if else
jenchen13 May 28, 2026
2ba29fd
autoquant and gptq in mcore
jenchen13 May 28, 2026
e14fa62
fix logic again
jenchen13 May 29, 2026
e985e93
Merge branch 'main' into feature/mcore_mse_mixed_precision
jenchen13 May 29, 2026
9088966
Merge branch 'feature/mcore_mse_mixed_precision' into jennifchen/mcor…
jenchen13 Jun 2, 2026
68e6837
revert tensor quantizer changes
jenchen13 Jun 2, 2026
967d5ef
Fix MCore layerwise discovery mutation
jenchen13 Jun 15, 2026
5b141b5
Merge branch 'main' into jennifchen/mcore_autoquant_gptq
jenchen13 Jun 16, 2026
55ed710
Fix AutoQuant budget denominator for restored stats
jenchen13 Jun 16, 2026
2acbc03
Update changelog for MCore quantization support
jenchen13 Jun 16, 2026
f40849e
Clean up Megatron parallel state in layerwise test
jenchen13 Jun 22, 2026
a59eecd
Merge branch 'main' into jennifchen/mcore_autoquant_gptq
jenchen13 Jun 24, 2026
f68bab8
fix: sum AutoQuantize cost/score across EP for MCore MoE experts
jenchen13 Jun 25, 2026
8f94cf8
test: strengthen GPTQ mamba hybrid assertions and increase timeout
jenchen13 Jun 25, 2026
fbaa4a3
test: move update_hessian zero-token noop test to CPU unit tests
jenchen13 Jun 25, 2026
d4d91c8
separate megatron calib dataloader out into helper
jenchen13 Jun 25, 2026
bbf7f35
increase timeout for onnx test
jenchen13 Jun 25, 2026
fe80756
test: raise unit test timeout to 120 s on Windows
jenchen13 Jun 25, 2026
6c2d446
update comment
jenchen13 Jun 25, 2026
29475fe
move cpu only gptq tests to cpu unit tests
jenchen13 Jun 26, 2026
850799b
move autoquant registration to plugins/megatron.py; fix circular impo…
jenchen13 Jun 26, 2026
470b48f
remove padding hardcode
jenchen13 Jun 26, 2026
b9a1a6b
Merge branch 'main' into jennifchen/mcore_autoquant_gptq
jenchen13 Jun 26, 2026
0608daf
Increase timeout for Megatron sharded state dict test
jenchen13 Jun 26, 2026
289dbdc
Increase GPU Megatron CI timeout
jenchen13 Jun 29, 2026
35bd94f
Adjust GPU Megatron CI timeout
jenchen13 Jun 29, 2026
f0d1c55
Speed up Mamba GPU quantization tests
jenchen13 Jun 29, 2026
351c6c6
test: tighten timeouts for mamba-hybrid quant tests
jenchen13 Jun 29, 2026
14cd2df
test: prebuild quant CUDA extensions in a session fixture
jenchen13 Jun 29, 2026
9749a49
test: drop redundant quant CUDA extension prebuild fixture
jenchen13 Jun 29, 2026
20a888d
Fix gpu_megatron quant extension timeout
jenchen13 Jun 30, 2026
d6b01ac
test: prebuild quant CUDA extensions via session fixture
jenchen13 Jul 1, 2026
6d75cbc
Merge branch 'main' into jennifchen/mcore_autoquant_gptq
jenchen13 Jul 1, 2026
51ba351
Reduce compressed gpu_megatron state dict matrix
jenchen13 Jul 1, 2026
f518c1a
Address docs and gpu_megatron feedback
jenchen13 Jul 1, 2026
848c317
Add Nemotron Nano AutoQuantize launcher example
jenchen13 Jul 1, 2026
c976a23
Restore quantization docs cross references
jenchen13 Jul 1, 2026
485750b
Update launcher Megatron submodule for AutoQuantize
jenchen13 Jul 1, 2026
8c307de
bring gpu_megatron tests back to 60min
jenchen13 Jul 1, 2026
eccabc8
respond to comments fix tests
jenchen13 Jul 1, 2026
961abac
explain conftest change
jenchen13 Jul 1, 2026
2e5100c
rename example
jenchen13 Jul 1, 2026
054e353
refactor: drop dead AutoQuantize denominator fallback path
jenchen13 Jul 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/gpu_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ jobs:
timeout: 60
container_image: nvcr.io/nvidia/pytorch:26.05-py3
- example: gpu_megatron
timeout: 60
timeout: 75
container_image: nvcr.io/nvidia/nemo:26.06
- example: gpu_trtllm
timeout: 15
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.rst
Comment thread
kevalmorabia97 marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ Changelog
- Add shared Megatron-Core calibration forward loop: ``modelopt.torch.utils.plugins.megatron_calibration.get_megatron_calibration_forward_loop`` produces the ``forward_loop`` callable expected by ``mtq.quantize`` / ``mtp.prune``. Replaces the bespoke calibration loops in Megatron-LM and Megatron-Bridge for quantization and pruning with a single canonical implementation.
- Support Megatron-Core checkpoint restore and export for MSE ``NVFP4StaticQuantizer``.
- Add mixed-precision FP8 + NVFP4 export for Megatron-Core: per-layer ``quant_algo`` recorded under ``quantized_layers`` in ``hf_quant_config.json``, PP-aware ``kv_cache_dtype`` gather, fused-QKV exclude split into per-HF-name ``q/k/v_proj`` entries.
- Add AutoQuant and GPTQ support for Megatron-Core models, including MCore-specific AutoQuant hooks and decoder-layer discovery for GPTQ layerwise calibration.
- Add support for ``active_params`` (for MoE models) and ``memory_mb`` constraints in Minitron pruning on top of existing ``params`` constraint. You can also provide multiple constraints. See `examples/pruning/README.md <https://github.com/NVIDIA/Model-Optimizer/tree/main/examples/pruning>`_ for more details. The underlying utility functions ``mcore_param_count``, ``mcore_memory_footprint_mb``, and ``print_mcore_model_stats`` in ``modelopt.torch.nas.plugins.megatron_model_stats`` are also available for standalone use to compute parameter counts and memory footprints (weights + KV-cache + Mamba state) for any Megatron-Core model.
- Add Minitron pruning support for Megatron-Bridge Gemma3 models.
- Add end-to-end optimization tutorial for Minitron pruning + two-phase distillation (80B @ 8K + 20B @ 32K long-context = 100B tokens) + FP8 PTQ + vLLM deployment for Nemotron-3-Nano-30B-A3B-BF16 (MoE + Mamba-Transformer hybrid) → Pruned 22B/A3.0B active params, along with data blend preparation steps (with tool-calling data) and detailed pruning / data-blend / long-context ablations. See `examples/megatron_bridge/tutorials/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16/README.md <https://github.com/NVIDIA/Model-Optimizer/tree/main/examples/megatron_bridge/tutorials/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16/>`_ for details.
Expand Down
50 changes: 35 additions & 15 deletions modelopt/torch/quantization/algorithms.py
Comment thread
realAsma marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -378,13 +378,14 @@ def get_score(self, recipe: QuantRecipe) -> float:
total_score += importance.cpu().item()
continue

if parallel_state.expert_model_parallel_group.is_initialized():
# TODO: Support expert model parallelism for score estimation
warnings.warn("AutoQuantize does not support expert model parallelism yet.")
importance = importance.cpu()
importance = DistributedProcessGroup.get_dist_syncd_obj(
importance,
[parallel_state.tensor_parallel_group, parallel_state.data_parallel_group],
[
parallel_state.tensor_parallel_group,
parallel_state.data_parallel_group,
parallel_state.expert_model_parallel_group,
],
sum,
)
Comment thread
jenchen13 marked this conversation as resolved.
total_score += importance.item()
Expand All @@ -408,13 +409,12 @@ def get_cost(self, recipe: QuantRecipe, cost_weight: float | None = None) -> flo
cost += weight_size * recipe.compression
continue

if parallel_state.expert_model_parallel_group.is_initialized():
# TODO: Support expert model parallelism
warnings.warn("AutoQuantize does not support expert model parallelism yet.")

weight_size = DistributedProcessGroup.get_dist_syncd_obj(
weight_size,
[parallel_state.tensor_parallel_group],
[
parallel_state.tensor_parallel_group,
parallel_state.expert_model_parallel_group,
],
sum,
)
Comment thread
jenchen13 marked this conversation as resolved.

Expand Down Expand Up @@ -466,6 +466,8 @@ class _AutoQuantizeBaseSearcher(BaseSearcher, ABC):
# gate_proj, up_proj, down_proj for Qwen3 like MoE models
r"^(.*?\.mlp\.experts)\.\d+\.(gate_proj|up_proj|down_proj)$",
r"^(.*?\.mixer\.experts)\.\d+\.(up_proj|down_proj)$", # NemotronH MoE experts
# NemotronH MoE experts in MCore naming (linear_fc1=gate+up fused, linear_fc2=down)
r"^(.*?\.mlp\.experts\.local_experts)\.\d+\.(linear_fc1|linear_fc2)$",
r"^(.*?)\.(gate_proj|up_proj)$", # gate_proj, up_proj for llama like models
r"^(.*?)\.(\d+\.(w1|w2|w3))$", # mixtral experts
r"^(.*?)\.((w1_linear|w2_linear|w3_linear)\.\d+)$", # dbrx experts
Expand Down Expand Up @@ -875,6 +877,15 @@ def _get_total_weight_size(modules):
for module in modules
)

@staticmethod
def _get_total_weight_size_from_candidate_stats(candidate_stats):
no_quant_recipe = QuantRecipe(quant_cfg=None)
total_weight_size = 0
for candidate_stat in candidate_stats.values():
no_quant_idx = candidate_stat["formats"].index(no_quant_recipe)
total_weight_size += candidate_stat["costs"][no_quant_idx]
return total_weight_size

def _get_constraints_for_search(self, max_weight_size, lower_bound=None):
constraints = {
"weight_size_after_compression": (
Expand Down Expand Up @@ -905,9 +916,14 @@ def run_search(self):
)

compression = self._get_formatted_weight_compression_constraint()
total_weight_size = self._cost_model.total_weight_size(
self.model.named_modules(), self._is_auto_quantize_module, self.config["cost"]
)
if self.candidate_stats:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jenchen13 why do we need the else branch here? self.candidate_stats is always set in before_search which gets called before run_search

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The else branch is never used.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm I think i kept it for backward compatibility

total_weight_size = self._get_total_weight_size_from_candidate_stats(
self.candidate_stats
)
else:
total_weight_size = self._cost_model.total_weight_size(
self.model.named_modules(), self._is_auto_quantize_module, self.config["cost"]
)
Comment thread
realAsma marked this conversation as resolved.
Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if self.candidate_stats:
total_weight_size = self._get_total_weight_size_from_candidate_stats(
self.candidate_stats
)
else:
total_weight_size = self._cost_model.total_weight_size(
self.model.named_modules(), self._is_auto_quantize_module, self.config["cost"]
)
total_weight_size = self._get_total_weight_size_from_candidate_stats(
self.candidate_stats
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this work?

self.cost_denominator = total_weight_size
max_weight_size = total_weight_size * compression
if verbose:
Expand All @@ -928,12 +944,16 @@ def run_search(self):
best_recipe = {}
best_constraints, best_scores = 0, 0
for name, best_hparam_recipe_info in best_recipe_info.items():
# Solvers could give different solutions for the same layer across DP/TP groups even though
# the scores and costs are the same. Lets make sure the same recipe is selected across DP/TP
# Solvers could give different solutions for the same layer across DP/TP/EP groups even though
# the scores and costs are the same. Lets make sure the same recipe is selected across DP/TP/EP
_ps = self.model.get_submodule(name.split(".quant_recipe")[0]).parallel_state
best_format = DistributedProcessGroup.get_dist_syncd_obj(
best_hparam_recipe_info["format"],
[_ps.data_parallel_group, _ps.tensor_parallel_group],
[
_ps.data_parallel_group,
_ps.tensor_parallel_group,
_ps.expert_model_parallel_group,
],
lambda a: a[0],
)

Expand Down
15 changes: 6 additions & 9 deletions modelopt/torch/quantization/model_calib.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,6 @@
)
from .utils.calib_utils import _GPTQ_HELPER_REGISTRY, GPTQHelper

try:
from .plugins.megatron import _check_nvfp4_static_tp_supported
except ImportError:

def _check_nvfp4_static_tp_supported(model: nn.Module) -> None: # no-op without megatron
return


__all__ = [
"CalibratorFactory",
"awq",
Expand Down Expand Up @@ -305,7 +297,12 @@ def max_calibrate(
module.layer_sync_moe_local_experts_amax(sync_weight_amax=sync_expert_weight_amax)

# Fail fast on NVFP4 static-block with TP>1 (sharded_state_dict treats _amax as replicated).
_check_nvfp4_static_tp_supported(model)
try:
from .plugins.megatron import _check_nvfp4_static_tp_supported
except ImportError:
pass
else:
_check_nvfp4_static_tp_supported(model)

if not distributed_sync:
# Single-process: _amax is final.
Expand Down
57 changes: 51 additions & 6 deletions modelopt/torch/quantization/plugins/megatron.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"""Support quantization for megatron linear layers."""

import types
from contextlib import contextmanager
from typing import Any

import megatron.core.parallel_state as mcore_parallel
Expand All @@ -39,11 +40,13 @@
from modelopt.torch.utils import warn_rank_0
from modelopt.torch.utils.distributed import ParallelState

from ..algorithms import AutoQuantizeGradientSearcher
from ..conversion import maybe_promote_nvfp4_static_quantizer
from ..nn import QuantModule, QuantModuleRegistry, SequentialQuantizer, TensorQuantizer
from ..nn.modules.quant_linear import RealQuantLinear
from ..qtensor import QTensorWrapper
from ..utils import sync_moe_expert_amax
from ..utils.layerwise_calib import LayerActivationCollector
from .custom import CUSTOM_MODEL_PLUGINS, _ParallelLinear

try:
Expand Down Expand Up @@ -612,10 +615,11 @@ def _setup(self):
expert_model_parallel_group=mcore_parallel.get_expert_model_parallel_group(),
)

# Initialize parallel state for submodules local_experts.*.linear_fc1 and local_experts.*.linear_fc2
# These child linears are still native MCore modules here. Seed `_parallel_state`
# directly so the later QuantModule conversion sees the intended parallel state.
for expert in self.local_experts:
expert.linear_fc1.parallel_state = self.parallel_state
expert.linear_fc2.parallel_state = self.parallel_state
expert.linear_fc1._parallel_state = self.parallel_state
expert.linear_fc2._parallel_state = self.parallel_state

def layer_sync_moe_local_experts_amax(self, sync_weight_amax=False):
"""Sync quantizer amax across local experts in a SequentialMLP.
Expand Down Expand Up @@ -721,9 +725,10 @@ def _setup(self):
tensor_parallel_group=mcore_parallel.get_expert_tensor_parallel_group(),
expert_model_parallel_group=mcore_parallel.get_expert_model_parallel_group(),
)
# initialize parallel state for submodules linear_fc1 and linear_fc2
self.linear_fc1.parallel_state = self.parallel_state
self.linear_fc2.parallel_state = self.parallel_state
# These child linears are still native MCore modules here. Seed `_parallel_state`
# directly so the later QuantModule conversion sees the intended parallel state.
self.linear_fc1._parallel_state = self.parallel_state
self.linear_fc2._parallel_state = self.parallel_state

@QuantModuleRegistry.register({TEDotProductAttention: "TEDotProductAttention"})
class _QuantTEDotProductAttention(QuantModule):
Expand Down Expand Up @@ -779,3 +784,43 @@ def sharded_state_dict(self, prefix="", sharded_offsets=(), metadata=None):
# Affine KVCache Quant bias vector.
state_dict = self.state_dict(prefix="", keep_vars=True)
return make_sharded_tensors_for_checkpoint(state_dict, prefix, {}, sharded_offsets)


def _is_supported_megatron_model(model: torch.nn.Module) -> bool:
return isinstance(model, MegatronModule)


@contextmanager
def _megatron_grad_ckpt_context(model: torch.nn.Module):
# Megatron configures activation recompute at model build time via TransformerConfig,
# so there is no runtime flag to flip here.
yield


def _is_param_grad_enabled_for_megatron(pname: str, model: torch.nn.Module) -> bool:
return "weight" in pname

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[IMPORTANT Performance] _is_param_grad_enabled_for_megatron enables requires_grad on every weight in the model ("weight" in pname matches essentially all linear/layernorm weights).

The whole point of the is_param_grad_enabled hook (see the register_custom_support docstring in algorithms.py) is the opposite: AutoQuantize only needs activation gradients, so the hook should enable grad on the minimal set of params required to trigger activation-grad flow — the HF analog (_is_param_grad_enabled_for_auto_quantize) enables only "embed" in pname.

Why it matters: with requires_grad=True on all weights, autograd computes a full weight gradient (inputᵀ @ grad_output) for every linear during each score-estimation backward pass — roughly doubling backward FLOPs over a multi-iteration calibration loop. The create_param_grad_clear_hook only bounds stored-grad memory, not the compute. For large MoE models this is a real regression.

Suggestion: enable grad more selectively. If the concern is pipeline parallelism (where embedding lives only on the first PP stage, so "embed" alone would leave later stages with no grad seed), prefer seeding the first parameter of each PP stage / first layer input rather than every weight. If the broad selection is in fact intentional for PP correctness, a short comment explaining the tradeoff would help.



AutoQuantizeGradientSearcher.register_custom_support(
_is_supported_megatron_model,
_megatron_grad_ckpt_context,
_is_param_grad_enabled_for_megatron,
)


def get_mcore_layerwise_calibration_layers(
model: torch.nn.Module,
) -> list[torch.nn.Module] | torch.nn.ModuleList | None:
if not hasattr(model, "decoder") or not hasattr(model.decoder, "layers"):
return None
decoder_layers = model.decoder.layers
if getattr(model, "output_layer", None) is None:
return decoder_layers
layers = list(decoder_layers)
layers.append(model.output_layer)
return layers
Comment on lines +819 to +821

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[IMPORTANT Algorithm] When output_layer is present, this returns a plain Python list (list(decoder_layers) + append), whereas the no-output-layer branch and all HF discoverers (get_homogeneous_hf_decoder_layers, get_nemotron_h_decoder_layers) return the model's live nn.ModuleList.

LayerActivationCollector._swap_to_dummy and _cleanup_layers do self._decoder_layers[idx] = _SkipLayer(layer) / = layer._original. When _decoder_layers is the live ModuleList, that swap mutates the real model so framework hooks (accelerate/FSDP2) see a parameter-free stand-in — the stated purpose of _SkipLayer. When it's a detached list, the assignment mutates only the copy: the model's decoder.layers still holds the original layer, so the _SkipLayer memory/hook optimization silently never engages for Megatron models with an output layer.

Correctness is preserved (the patched forward handles skip mode on the original layer), but the optimization the author likely intends is disabled, and behavior diverges between the two branches of this same function.

Suggestion: if output_layer must be included, consider returning a structure the collector can mutate in place (or have the collector support a heterogeneous sequence and write back to both decoder.layers and output_layer). At minimum, document that the swap-to-dummy optimization does not apply when the output layer is appended.



LayerActivationCollector.register_decoder_layer_support(
_is_supported_megatron_model, get_mcore_layerwise_calibration_layers
)
3 changes: 3 additions & 0 deletions modelopt/torch/quantization/utils/calib_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ def update_hessian(input, hessian, n_samples):
input_flat = input.reshape(-1, input.shape[-1]).t().float()
batch_size = input_flat.shape[1]

if batch_size == 0: # in MOEs some experts receive no tokens
return hessian, n_samples

# Incremental averaging: scale down old hessian
hessian *= n_samples / (n_samples + batch_size)
n_samples += batch_size
Expand Down
55 changes: 44 additions & 11 deletions modelopt/torch/utils/plugins/megatron_calibration.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,13 @@
from transformers import PreTrainedTokenizerBase, ProcessorMixin

__all__ = [
"get_megatron_calibration_dataloader",
"get_megatron_calibration_forward_loop",
"get_megatron_vlm_calibration_forward_loop",
]


def get_megatron_calibration_forward_loop(
def get_megatron_calibration_dataloader(
tokenizer: "PreTrainedTokenizerBase",
*,
dataset_name: str | list[str] = "cnn_dailymail",
Expand All @@ -49,17 +50,15 @@ def get_megatron_calibration_forward_loop(
device: torch.device | str | None = "cuda",
apply_chat_template: bool = True,
pack: bool = False,
) -> Callable[[torch.nn.Module], None]:
"""Build a Megatron-Core calibration ``forward_loop(model)``.
) -> torch.utils.data.DataLoader:
"""Build a DP-sharded calibration dataloader for Megatron-Core models.

Iterates a packed dataloader built via ``get_dataset_dataloader(pack=True)``
and drives a logits-free prefill pass through the model so activation hooks
fire on every layer. All kwargs except ``seq_length`` are forwarded
1:1 — see :func:`get_dataset_dataloader` for their semantics. ``seq_length``
maps to that function's ``max_sample_length``.
Each batch is a dict with at least ``input_ids`` and ``attention_mask`` tensors
on ``device``. The dataloader is suitable as the ``data_loader`` argument to
``mtq.auto_quantize`` or any other API that iterates batches directly.

Returns:
A ``forward_loop(model)`` callable to pass into ``mtq.quantize``, ``mtp.prune``, or other such APIs.
All kwargs are forwarded to :func:`get_dataset_dataloader`; ``seq_length``
maps to that function's ``max_sample_length``.
"""
# Deepcopy before mutating pad_token so the caller's tokenizer isn't silently changed.
if getattr(tokenizer, "pad_token", None) is None:
Expand All @@ -68,7 +67,7 @@ def get_megatron_calibration_forward_loop(

# Shard calibration data across DP ranks; amax is max-reduced across DP inside ``mtq``.
dp_size = mpu.get_data_parallel_world_size()
dataloader = get_dataset_dataloader(
return get_dataset_dataloader(
dataset_name=dataset_name,
tokenizer=tokenizer,
batch_size=batch_size,
Expand All @@ -85,6 +84,40 @@ def get_megatron_calibration_forward_loop(
},
)


def get_megatron_calibration_forward_loop(
tokenizer: "PreTrainedTokenizerBase",
*,
dataset_name: str | list[str] = "cnn_dailymail",
batch_size: int = 1,
num_samples: int | list[int] = 512,
seq_length: int = 512,
device: torch.device | str | None = "cuda",
apply_chat_template: bool = True,
pack: bool = False,
) -> Callable[[torch.nn.Module], None]:
"""Build a Megatron-Core calibration ``forward_loop(model)``.

Iterates a dataloader built via :func:`get_megatron_calibration_dataloader`
and drives a logits-free prefill pass through the model so activation hooks
fire on every layer. All kwargs are forwarded 1:1 — see
:func:`get_megatron_calibration_dataloader` for their semantics.

Returns:
A ``forward_loop(model)`` callable to pass into ``mtq.quantize``,
``mtp.prune``, or other such APIs.
"""
dataloader = get_megatron_calibration_dataloader(
tokenizer,
dataset_name=dataset_name,
batch_size=batch_size,
num_samples=num_samples,
seq_length=seq_length,
device=device,
apply_chat_template=apply_chat_template,
pack=pack,
)

def _forward_loop(model: torch.nn.Module) -> None:
cp_size = mpu.get_context_parallel_world_size()
cp_group = mpu.get_context_parallel_group()
Expand Down
2 changes: 1 addition & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def pytest_addoption(parser):
"gpu_trtllm": 60,
"gpu_vllm": 60,
"regression": 180,
"unit": 60,
"unit": 120 if platform.system() == "Windows" else 60,
}


Expand Down
Loading
Loading