Skip to content

refactor(export): split unified_export_hf into layered modules - #2088

Open
Fridah-nv wants to merge 10 commits into
mainfrom
fridah/export-module-split
Open

refactor(export): split unified_export_hf into layered modules#2088
Fridah-nv wants to merge 10 commits into
mainfrom
fridah/export-module-split

Conversation

@Fridah-nv

@Fridah-nv Fridah-nv commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Type of change: Refactor (no functional change)

unified_export_hf.py had grown to 1685 lines and was the largest file in modelopt/torch/export/. More importantly, it mixed four unrelated jobs — dispatch, the resident exporter, model-level preparation, and per-module weight packing — which forced the other exporters to import their shared helpers back from the module that dispatches to them. That cycle is why several function-local imports exist today.

Splitting by layer rather than by size makes the package a DAG:

hf_export_prep, hf_weight_export   ->  (nothing else in the package)
unified_export_hf_streaming        ->  prep
unified_export_diffusers           ->  prep, weight
unified_export_hf                  ->  the three exporters, prep, weight

Four commits, each independently green so they can be reviewed one at a time:

Commit Change
602879b280 diffusers exporter → unified_export_diffusers.py (498 lines)
b3037f8910 model-level preparation → hf_export_prep.py (364 lines)
04a7382b10 per-module weight export → hf_weight_export.py (349 lines)
04c5904396 remove the four lazy imports the layering made unnecessary

Resulting layout:

Module Lines Responsibility
unified_export_hf.py 373 (was 1685) entry point, dispatch, resident exporter
unified_export_diffusers.py 574 diffusers checkpoint export
hf_export_prep.py 455 dtype/MoE/MTP prep, resmooth + shared-input fusion
unified_export_hf_streaming.py 445 offloaded streaming export (unchanged, from #2008)
hf_weight_export.py 415 packing one module's weight + registry dispatch

The payoff is the last commit. Three of the four removed lazy imports predate this work: moe_utils.py and hf_export_handlers.py reached _export_quantized_weight through function-local imports purely to dodge the cycle, and #2008 added a third for the streaming dispatch with a comment saying it could go once the shared helpers moved. This is that.

Usage

No API change. export_hf_checkpoint is unaffected and still dispatches to the right exporter:

from modelopt.torch.export import export_hf_checkpoint

# resident, offloaded, and diffusers models all go through the same entry point;
# the dispatch now lives in a 373-line module instead of a 1685-line one.
export_hf_checkpoint(model, dtype=torch.bfloat16, export_dir="./exported")

Testing

Run after each commit, not just at the end:

  • tests/unit3130 passed, 15 skipped
  • tests/gpu/torch/export/ + tests/gpu/torch/quantization/test_gptq.py123 passed, 2 skipped (both pre-existing: sm90 requirement, INT4_AWQ_CFG on Qwen3 MoE)
  • ruff / ruff-format / mypy / bandit — clean
  • Import-order check: each of the 8 modules imported first, plus the package — confirms the DAG has no cycle

tests/gpu/torch/export/test_export_diffusers.py was excluded from the local GPU run because it exceeds our relay's time limit; its unit-test counterpart passes, and CI covers it.

Two review notes, both consequences of the mechanics rather than incidental:

  1. Commit 3 repoints 13 filesmoe_utils.py, hf_export_handlers.py, plugins/vllm_fakequant_hf.py, the other two exporters, and 9 test modules. Imports are updated rather than shimmed, so no symbol ends up with two addresses.
  2. Commit 4 changes name binding. Hoisting a lazy import means moe_utils now holds a module-scope reference, so patching _export_quantized_weight where it is defined no longer intercepts it. The spies in test_fused_experts.py move to patching where it is used (moe_utils._export_quantized_weight). Same reason test_export_diffusers.py's monkeypatches move in commit 1.

Before your PR is "Ready for review"

  • Is this change backward compatible?: ✅ — modelopt.torch.export.__all__ is unchanged (export_hf_checkpoint, export_speculative_decoding), and both stay in unified_export_hf. Worth flagging one caveat: deep imports of two non-underscore internals, requantize_resmooth_fused_llm_layers and collect_shared_input_modules, now resolve from hf_export_prep. They were never in __all__, and every in-repo caller is updated, but out-of-tree code importing them directly from unified_export_hf would need a one-line change. Happy to add re-export shims if reviewers would rather not break that.
  • If you copied code from any other sources or added a new PIP dependency, did you follow guidance in CONTRIBUTING.md: N/A — no new dependencies; all code is moved verbatim within the repo.
  • Did you write any new necessary tests?: N/A — pure code movement with no behavior change. Existing coverage is retained; test imports and monkeypatch targets are updated to follow the symbols.
  • Did you update Changelog?: N/A — internal refactor, no user-facing API or behavior change.
  • Did you get Claude approval on this PR?: ❌ — not yet run.

Additional Information

Follow-up to #2008. The extraction was suggested there by @Edwardf0t1, who scoped the diffusers block and _export_quantized_weight as separate work; this PR does both plus the preparation layer, because splitting all three is what actually removes the cycles rather than relocating them.

Deliberately left alone: layer_utils.py (1991 lines) and quant_utils.py (1664), which are now the two largest files in the package. Both are worth a look, but neither is entangled with the exporter layering this PR is fixing.

Summary by CodeRabbit

  • New Features

    • Added unified export support for Hugging Face and Diffusers models, including quantized and non-quantized checkpoints.
    • Improved handling of sharded safetensors, fused QKV weights, compressed NVFP4 scales, MoE experts, tied weights, and SVDQuant data.
    • Preserved model configuration and pipeline metadata during Diffusers exports.
  • Bug Fixes

    • Improved export reliability for encoder-decoder, Whisper, speculative-decoding, and vision-language models.
    • Added validation and cleanup for temporary export data and inconsistent quantization settings.
  • Refactor

    • Consolidated export preparation and quantized-weight processing into dedicated components while preserving existing behavior.

Fridah-nv and others added 4 commits August 5, 2026 22:03
The transformers and diffusers export paths shared a file but almost no code:
they meet only at the dispatch in export_hf_checkpoint. Move the diffusers
half -- _export_diffusers_checkpoint, _postprocess_safetensors,
_fuse_qkv_linears_diffusion and four helpers, 498 lines -- to
unified_export_diffusers.py.

unified_export_hf.py goes 1685 -> 1187. The diffusers-only imports
(generate_diffusion_dummy_forward_fn, get_diffusion_components,
merge_diffusion_checkpoint and the rest) leave with it; only
is_diffusers_object, is_qkv_projection and get_qkv_group_key stay, for the
dispatch check and the shared QKV fusion.

The dispatch imports _export_diffusers_checkpoint lazily for now, because the
diffusers module still imports the module-walking helpers back from here.
The following commits move those out and the lazy import goes away.

Two test files imported _postprocess_safetensors from the old location and are
updated rather than shimmed; test_export_diffusers.py's monkeypatches move to
the new module, since a `from X import Y` binding is not affected by patching
Y on X.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Fridah-nv <201670829+Fridah-nv@users.noreply.github.com>
Every unified HF exporter runs the same preparation before packing a single
weight: resolve the dtype, prepare MoE input quantizers, resmooth and fuse
shared-input modules, adjust the quant config, and patch transformers while
artifacts are written. That code sat in unified_export_hf.py, so the exporters
had to import it back from the module that dispatches to them -- which is the
only reason the lazy imports exist.

Move those 13 symbols (364 lines) to hf_export_prep.py. It imports nothing
else from the export package, so it sits at the bottom of the graph and the
three exporters can depend on it without a cycle.

unified_export_hf.py goes 1187 -> 823. The QKV fusion helpers travel with
_fuse_shared_input_modules, so only is_diffusers_object remains of the
diffusers imports here.

External importers are repointed rather than shimmed: plugins/vllm_fakequant_hf.py
for collect_shared_input_modules, and tests/gpu/.../test_fsdp2_export.py for
requantize_resmooth_fused_llm_layers.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Fridah-nv <201670829+Fridah-nv@users.noreply.github.com>
_export_quantized_weight is the leaf of the pipeline -- it packs one module's
weight and registers the scale buffers beside it -- but it lived in the same
file as the exporters that call it, so moe_utils.py and hf_export_handlers.py
had to reach it through function-local imports to dodge the cycle.

Move it, _compressed_per_block_scale, _dispatch_export_handler and
_process_quantized_modules (349 lines) to hf_weight_export.py. Like
hf_export_prep, it imports nothing else from the export package.

unified_export_hf.py goes 823 -> 474.

Thirteen files are repointed rather than shimmed: moe_utils.py,
hf_export_handlers.py, the two other exporters, and nine test modules. The
patch targets in test_fused_experts.py move too, since patching a name on the
old module no longer reaches the callers' bindings.

The lazy imports in moe_utils.py and hf_export_handlers.py still point at the
new module; hoisting them is the next commit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Fridah-nv <201670829+Fridah-nv@users.noreply.github.com>
With preparation and weight packing in their own modules, the export package is
a DAG:

    hf_export_prep, hf_weight_export  ->  (nothing in the package)
    unified_export_hf_streaming       ->  prep
    unified_export_diffusers          ->  prep, weight
    unified_export_hf                 ->  the three exporters, prep, weight

so the four function-local imports that existed only to dodge a cycle become
ordinary module-scope ones:

- moe_utils.py and hf_export_handlers.py reach _export_quantized_weight
  directly. These predate this work -- they were dodging the cycle through
  unified_export_hf.
- export_hf_checkpoint imports both the diffusers and streaming exporters at
  module scope. The streaming one was added in #2008 with a comment saying it
  could go once the shared helpers moved; this is that.

Verified by importing each of the eight modules first, and the package.

One test consequence, since hoisting changes name binding: the spies in
test_fused_experts.py patched _export_quantized_weight on the module that
defines it, which worked while moe_utils imported it lazily. Now that
moe_utils holds a module-scope reference, the patch has to target
moe_utils._export_quantized_weight instead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Fridah-nv <201670829+Fridah-nv@users.noreply.github.com>
@copy-pr-bot

copy-pr-bot Bot commented Aug 6, 2026

Copy link
Copy Markdown

Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually.

Contributors can view more details about this message here.

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The change extracts Hugging Face preparation, quantized weight export, and Diffusers serialization into dedicated modules. Unified exporters, plugins, MoE helpers, tests, documentation, and import-layering checks now use the new module boundaries.

Changes

Export pipeline modularization

Layer / File(s) Summary
Hugging Face export preparation
modelopt/torch/export/hf_export_prep.py, modelopt/torch/export/plugins/vllm_fakequant_hf.py, modelopt/torch/export/model_utils.py, .agents/skills/ptq/references/unsupported-models.md, tests/_test_utils/...
Adds shared-input collection and fusion, MoE preparation, resmoothing, dtype handling, Transformers patching, generation-config sanitization, and updated references.
Quantized weight export
modelopt/torch/export/hf_weight_export.py, modelopt/torch/export/hf_export_handlers.py, modelopt/torch/export/moe_utils.py, modelopt/torch/export/registry.py, tests/gpu/..., tests/unit/...
Adds format-specific weight and scale export, packed-weight handling, registry dispatch, FSDP resharding, and updated MoE integration and tests.
Diffusers checkpoint export
modelopt/torch/export/unified_export_diffusers.py, tests/unit/torch/export/test_export_diffusers.py, tests/unit/torch/export/test_nvfp4_utils.py
Adds component and pipeline serialization, QKV fusion, safetensors post-processing, quantization metadata, temporary tensor promotion, and pipeline metadata handling.
Exporter wiring and validation
modelopt/torch/export/unified_export_hf.py, modelopt/torch/export/unified_export_hf_streaming.py, tests/unit/torch/export/test_export_import_layering.py
Replaces removed local helpers with imports from the extracted modules and validates the intended acyclic import structure.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant unified_export_hf
  participant hf_export_prep
  participant hf_weight_export
  participant ExportRegistry
  Caller->>unified_export_hf: start checkpoint export
  unified_export_hf->>hf_export_prep: prepare model and quantizers
  hf_export_prep-->>unified_export_hf: prepared model
  unified_export_hf->>hf_weight_export: process quantized modules
  hf_weight_export->>ExportRegistry: dispatch export handlers
  ExportRegistry-->>hf_weight_export: exported weights and scales
  hf_weight_export-->>unified_export_hf: processed checkpoint
  unified_export_hf-->>Caller: saved checkpoint
Loading

Suggested reviewers: jingyu-ml, meenchen, shengliangxu

🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: splitting unified_export_hf into layered export modules.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Security Anti-Patterns ✅ Passed The PR adds no unsafe torch.load, allow_pickle=True, trust_remote_code=True, dynamic eval/exec, or # nosec patterns; no dependency manifests changed. The eval match is a module.eval() call.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fridah/export-module-split

Comment @coderabbitai help to get the list of available commands.

@Fridah-nv
Fridah-nv marked this pull request as ready for review August 6, 2026 03:16
@Fridah-nv
Fridah-nv requested review from a team as code owners August 6, 2026 03:16
@Fridah-nv
Fridah-nv requested a review from jingyu-ml August 6, 2026 03:16
@Fridah-nv

Copy link
Copy Markdown
Contributor Author

/claude review

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://NVIDIA.github.io/Model-Optimizer/pr-preview/pr-2088/

Built to branch gh-pages at 2026-08-11 00:29 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

@coderabbitai coderabbitai Bot left a comment

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.

Warning

CodeRabbit couldn't request changes on this pull request because it doesn't have sufficient GitHub permissions.

Please grant CodeRabbit Pull requests: Read and write permission and re-run the review.

👉 Steps to fix this

Actionable comments posted: 1

🧹 Nitpick comments (4)
modelopt/torch/export/hf_export_prep.py (2)

411-423: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Move import importlib to module scope.

importlib is a lightweight standard-library module. It is not an optional dependency and it creates no circular import. The coding guidelines require module-scope imports unless one of those justifications applies.

♻️ Proposed fix
+import importlib
 import re
 import warnings
 def _try_patch_module(mod_path: str) -> tuple[Any, Any] | None:
     """Try to patch revert_weight_conversion in a single module."""
-    import importlib
-
     try:

Based on coding guidelines: "Keep imports at the top of Python source and test files; use local imports only for justified circular dependencies, optional dependencies, or unusually heavy imports, with a brief explanatory comment."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modelopt/torch/export/hf_export_prep.py` around lines 411 - 423, Move the
importlib import from inside _try_patch_module to module scope with the other
standard-library imports, then keep _try_patch_module’s importlib.import_module
usage unchanged.

Source: Coding guidelines


26-30: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add __all__ to the new module.

This new module exports collect_shared_input_modules and requantize_resmooth_fused_llm_layers to other packages. The coding guidelines require each module to declare its public API.

♻️ Proposed addition
 from .registry import ExportContext, PrepareMoEInputsRegistry
 
+__all__ = ["collect_shared_input_modules", "requantize_resmooth_fused_llm_layers"]
+
 try:

Based on coding guidelines: "Define each module's public API with __all__ = [...]."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modelopt/torch/export/hf_export_prep.py` around lines 26 - 30, Add a
module-level __all__ declaration in hf_export_prep.py listing the public
functions collect_shared_input_modules and requantize_resmooth_fused_llm_layers,
so the module explicitly defines its exported API.

Source: Coding guidelines

modelopt/torch/export/unified_export_diffusers.py (1)

334-346: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

_remove_promoted_quantizer_tensors deletes buffers it did not create.

_promote_quantizer_tensors_to_module registers buffers only on modules where is_quantlinear(sub_module) is true. _remove_promoted_quantizer_tensors deletes the three buffer names from every submodule, without that filter and without tracking what was promoted.

Two consequences follow. A non-quantlinear submodule that legitimately owns a buffer named pre_quant_scale, svdquant_lora_a, or svdquant_lora_b loses it after export. A quantlinear that already owned pre_quant_scale has it overwritten at line 324 and then deleted, so the original value is lost. Both contradict the docstring claim that the live module is unchanged after export.

Track the promoted (module, buffer_name) pairs and remove only those.

♻️ Proposed refactor
-def _promote_quantizer_tensors_to_module(component: nn.Module) -> None:
+def _promote_quantizer_tensors_to_module(component: nn.Module) -> None:
@@
+    promoted: list[tuple[nn.Module, str]] = []
     for _, sub_module in component.named_modules():
         if not is_quantlinear(sub_module):
             continue
@@
         if pre_quant_scale is not None:
             sub_module.register_buffer("pre_quant_scale", pre_quant_scale.detach().clone())
+            promoted.append((sub_module, "pre_quant_scale"))
@@
         if lora_a is not None and lora_b is not None:
             sub_module.register_buffer("svdquant_lora_a", lora_a.detach().clone())
             sub_module.register_buffer("svdquant_lora_b", lora_b.detach().clone())
+            promoted.append((sub_module, "svdquant_lora_a"))
+            promoted.append((sub_module, "svdquant_lora_b"))
+    component._modelopt_promoted_export_buffers = promoted
-    for _, sub_module in component.named_modules():
-        for buffer_name in ("svdquant_lora_a", "svdquant_lora_b", "pre_quant_scale"):
-            if buffer_name in getattr(sub_module, "_buffers", {}):
-                del sub_module._buffers[buffer_name]
+    promoted = getattr(component, "_modelopt_promoted_export_buffers", [])
+    for sub_module, buffer_name in promoted:
+        sub_module._buffers.pop(buffer_name, None)
+    if hasattr(component, "_modelopt_promoted_export_buffers"):
+        del component._modelopt_promoted_export_buffers
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modelopt/torch/export/unified_export_diffusers.py` around lines 334 - 346,
Update _promote_quantizer_tensors_to_module and
_remove_promoted_quantizer_tensors to track each (module, buffer_name) pair
actually registered or overwritten during promotion, and remove only those
tracked buffers during cleanup. Preserve any pre-existing buffers, including on
quantlinear modules, and avoid deleting same-named buffers from non-quantlinear
submodules while maintaining repeated-export module reuse.
modelopt/torch/export/hf_export_handlers.py (1)

45-48: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove the obsolete lazy-import comment.

The comment still describes a lazy import that this PR removed. _export_quantized_weight now resolves from the module-level import at line 25. The stale text tells the next reader that a cycle still forces a function-local import, which is the opposite of the dependency structure this PR establishes.

♻️ Proposed cleanup
 def _export_weight(
     module: nn.Module,
     ctx: ExportContext,
     weight_name: str = "weight",
 ) -> None:
-    # Imported lazily to avoid a cycle: unified_export_hf imports this module to
-    # install the built-in handlers while retaining this legacy helper's import path.
-
     _export_quantized_weight(module, ctx.dtype, weight_name, _tied_cache=ctx.tied_cache)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modelopt/torch/export/hf_export_handlers.py` around lines 45 - 48, Remove the
obsolete lazy-import comment immediately above the _export_quantized_weight
call; the function now uses the module-level import, so leave the call and its
arguments unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@modelopt/torch/export/hf_export_prep.py`:
- Around line 251-254: Update the condition guarding the MoE quantization path
near is_moe(module) to handle a None quantization_format before performing the
substring check, and replace the fragile identity comparison against
QUANTIZATION_NONE with value inequality consistent with the existing usage.
Preserve the current AWQ and NVFP4_SVDQUANT selection behavior for non-None
formats.

---

Nitpick comments:
In `@modelopt/torch/export/hf_export_handlers.py`:
- Around line 45-48: Remove the obsolete lazy-import comment immediately above
the _export_quantized_weight call; the function now uses the module-level
import, so leave the call and its arguments unchanged.

In `@modelopt/torch/export/hf_export_prep.py`:
- Around line 411-423: Move the importlib import from inside _try_patch_module
to module scope with the other standard-library imports, then keep
_try_patch_module’s importlib.import_module usage unchanged.
- Around line 26-30: Add a module-level __all__ declaration in hf_export_prep.py
listing the public functions collect_shared_input_modules and
requantize_resmooth_fused_llm_layers, so the module explicitly defines its
exported API.

In `@modelopt/torch/export/unified_export_diffusers.py`:
- Around line 334-346: Update _promote_quantizer_tensors_to_module and
_remove_promoted_quantizer_tensors to track each (module, buffer_name) pair
actually registered or overwritten during promotion, and remove only those
tracked buffers during cleanup. Preserve any pre-existing buffers, including on
quantlinear modules, and avoid deleting same-named buffers from non-quantlinear
submodules while maintaining repeated-export module reuse.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 9046bcc1-c082-4c2a-b75b-ad9b86d70998

📥 Commits

Reviewing files that changed from the base of the PR and between 6a81025 and 04c5904.

📒 Files selected for processing (20)
  • modelopt/torch/export/hf_export_handlers.py
  • modelopt/torch/export/hf_export_prep.py
  • modelopt/torch/export/hf_weight_export.py
  • modelopt/torch/export/moe_utils.py
  • modelopt/torch/export/plugins/vllm_fakequant_hf.py
  • modelopt/torch/export/unified_export_diffusers.py
  • modelopt/torch/export/unified_export_hf.py
  • modelopt/torch/export/unified_export_hf_streaming.py
  • tests/gpu/torch/export/test_export_embedding.py
  • tests/gpu/torch/export/test_export_weight_gpu.py
  • tests/gpu/torch/export/test_fsdp2_export.py
  • tests/gpu/torch/quantization/test_gptq.py
  • tests/gpu_trtllm/torch/export/test_export_compressed_nvfp4.py
  • tests/unit/torch/export/test_export_diffusers.py
  • tests/unit/torch/export/test_export_registry.py
  • tests/unit/torch/export/test_export_weight.py
  • tests/unit/torch/export/test_nvfp4_utils.py
  • tests/unit/torch/export/test_offload_export.py
  • tests/unit/torch/export/test_unified_export_hf.py
  • tests/unit/torch/quantization/plugins/test_fused_experts.py

Comment thread modelopt/torch/export/hf_export_prep.py
@codecov

codecov Bot commented Aug 6, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 83.33333% with 89 lines in your changes missing coverage. Please review.
✅ Project coverage is 76.28%. Comparing base (6a81025) to head (22d56e1).
⚠️ Report is 20 commits behind head on main.

Files with missing lines Patch % Lines
modelopt/torch/export/hf_export_prep.py 77.77% 42 Missing ⚠️
modelopt/torch/export/unified_export_diffusers.py 84.36% 33 Missing ⚠️
modelopt/torch/export/hf_weight_export.py 88.03% 14 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2088      +/-   ##
==========================================
- Coverage   78.72%   76.28%   -2.44%     
==========================================
  Files         522      525       +3     
  Lines       60129    63407    +3278     
==========================================
+ Hits        47335    48369    +1034     
- Misses      12794    15038    +2244     
Flag Coverage Δ
examples 42.97% <75.04%> (-0.13%) ⬇️
gpu 58.69% <67.16%> (-0.61%) ⬇️
regression 15.02% <16.88%> (+0.16%) ⬆️
unit 55.31% <47.84%> (-0.06%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread modelopt/torch/export/hf_export_handlers.py Outdated
Comment thread modelopt/torch/export/unified_export_hf.py Outdated
Comment thread modelopt/torch/export/unified_export_diffusers.py Outdated
@claude

claude Bot commented Aug 6, 2026

Copy link
Copy Markdown

Claude review

Scope: Full review. Trigger comment carried no scoping instructions. 20 files changed (1493+/1361-); I reviewed all 8 modelopt/ files and all 12 test files.

Verification method

Because this PR claims moved-verbatim, the highest-value check was proving that rather than re-reading the logic. I hashed every relocated block against origin/main:unified_export_hf.py and confirmed byte-for-byte identity for all of it:

Block origin/main lines New location Result
_is_enabled_quantizer 123-131 hf_export_prep.py:68-76 identical
collect_shared_input_modules .. requantize_resmooth_fused_llm_layers 280-544 hf_export_prep.py:78-342 identical
_resolve_export_dtype .. _warn_on_unsynced_moe_gate_up 866-927 hf_export_prep.py:343-404 identical
_revert_weight_conversion_noop .. _sanitize_generation_config_for_save 1432-1481 hf_export_prep.py:406-455 identical
_compressed_per_block_scale .. _dispatch_export_handler 545-863 hf_weight_export.py:61-379 identical
_process_quantized_modules 929-964 hf_weight_export.py:382-415 identical
_save_component_state_dict_safetensors .. _postprocess_safetensors 133-279 unified_export_diffusers.py:65-211 identical
_fuse_qkv_linears_diffusion .. _export_diffusers_checkpoint 1062-1425 unified_export_diffusers.py:212-574 identical
_export_transformers_checkpoint 965-1060 unified_export_hf.py:72-167 identical

No function from the original file is missing, and no new function appeared. Also confirmed via grep that zero references to the old addresses remain anywhere in modelopt/, tests/, examples/, or docs/ — the 13-file repoint is complete.

The layering claim holds

The stated DAG checks out. hf_export_prep.py and hf_weight_export.py import only layer_utils / model_config / model_utils / quant_utils / registry and nothing from the exporters, so they are genuine leaves. All four lazy imports are gone and each removal is backed by a real edge deletion, not a relocated cycle.

Two things I specifically checked and found not to be problems:

  • Registry installation still guaranteed. _process_quantized_modules moved into hf_weight_export.py, which does not import hf_export_handlers. That looked like it could leave ExportModuleRegistry empty for the 4 tests that now import the function directly — a silent no-op export rather than a failure. It is safe: Python executes modelopt/torch/export/__init__.py before any submodule, and that runs from .unified_export_hf import *, which installs the handlers at line 45.
  • The test_fused_experts.py monkeypatch retarget is correct and still meaningful. Hoisting the import into moe_utils module scope does break patching at the definition site, and repointing to modelopt.torch.export.moe_utils._export_quantized_weight is the right fix. The spy is invoked from moe_utils.py:275, inside _export_fused_experts, which is what the test calls directly — so the interception point is unchanged in practice.

Findings

CRITICAL: 0 / IMPORTANT: 0 / SUGGESTION: 3

All three are comment/docstring residue from the split, not logic:

  1. hf_export_handlers.py:45-46 — the imported-lazily-to-avoid-a-cycle comment outlived the import it explained, and now asserts a cycle that this PR removed.
  2. unified_export_hf.py:169-174 — the transformers-5.12.0 revert_weight_conversion TODO stayed behind while the four functions it documents moved to hf_export_prep.py. It is the only record of the workarounds removal condition, and it is now attached to unrelated code.
  3. unified_export_diffusers.py:36-57HAS_DIFFUSERS is now derived twice from two different import sets, which can drift. Non-blocking; the unguarded line 36 is inherited verbatim and stays safe because diffusers_utils guards internally.

One more, outside the diff: .agents/skills/ptq/references/unsupported-models.md:227 still locates requantize_resmooth_fused_llm_layers in unified_export_hf.py. A one-word path fix.

On the flagged compatibility caveat

The PR body raises that out-of-tree deep imports of requantize_resmooth_fused_llm_layers and collect_shared_input_modules from unified_export_hf will break, and offers re-export shims. I agree with the authors own read that shims are not required: CONTRIBUTING.md defines the public surface as __all__, __init__.py re-exports via from .unified_export_hf import *, and neither symbol was ever in that __all__ (which remains exactly export_hf_checkpoint, export_speculative_decoding). Adding shims would give both symbols two addresses, which is the drift the PR is trying to avoid. Adding them is a maintainer call, not a correctness one.

Risk assessment

Low. A refactor whose every moved byte is verifiably unchanged, whose call sites are all repointed with no shims left to drift, and which deletes four real import cycles rather than moving them. The one behavioral change (name binding on the hoisted import) is understood, documented in the PR body, and correctly handled in the two affected tests. The only follow-up worth doing is carrying the two orphaned comments to where their code went.

No blocking issues.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Claude review passed — no blocking issues found. LGTM

Fridah-nv and others added 2 commits August 6, 2026 03:50
…split

Review findings on the module split.

HAS_DIFFUSERS was the real one. Replacing the `import diffusers` probe with an
import from .diffusers_utils changed behavior: that module catches its own
diffusers ImportError and still imports cleanly, so the except branch could
never fire and the flag was unconditionally True. Verified: without diffusers
it read True here while unified_export_diffusers read False -- two identically
named flags disagreeing. Both now read diffusers_utils._HAS_DIFFUSERS, so there
is one probe. unified_export_diffusers keeps a use-site `import diffusers` for
the one place it needs __version__.

Also from the split:

- hf_export_prep wrapped the QKV helpers in an `except ImportError` that could
  not fire, whose None fallback would have turned a missing dependency into
  `TypeError: 'NoneType' object is not callable` at the call site. Removed.
- Both new module docstrings claimed to depend on nothing else in the export
  package; each imports several leaf modules. They now state the real
  invariant: leaf helpers only, never an exporter.
- hf_export_handlers kept the comment explaining a lazy import that commit
  04c5904 deleted, describing a cycle that no longer exists.
- registry.py, model_utils.py and the ptq skill reference still pointed at
  unified_export_hf.py for code that moved.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Fridah-nv <201670829+Fridah-nv@users.noreply.github.com>
…it documents

Commit b3037f8 moved _revert_weight_conversion_noop and
_patch_revert_weight_conversion to hf_export_prep.py but left the TODO
explaining them behind in unified_export_hf.py, where it dangled between
_export_transformers_checkpoint and export_speculative_decoding -- two
functions it has nothing to do with.

That note is the only record of the transformers 5.12.0 0-d-scalar bug and the
condition for dropping the workaround, so detached it left the patch helpers
with no rationale and pointed anyone revisiting them at the wrong file.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Fridah-nv <201670829+Fridah-nv@users.noreply.github.com>

@Edwardf0t1 Edwardf0t1 left a comment

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.

Reviewed as a refactor-correctness question rather than a re-read of the logic, since the PR claims pure movement.

Verification

  • AST-level identity: parsed every top-level def/class in main:unified_export_hf.py and in the union of the five resulting modules. All 40 symbols present, none duplicated across modules, and every body identical except the two intentional deltas (_export_diffusers_checkpoint's use-site import diffusers, export_hf_checkpoint's dropped lazy import).
  • Comments too — AST unparse drops them, so I diffed comment lines separately: exactly 2 lost across the whole split, both from the deleted lazy import. The transformers-5.12.0 TODO and every other inline note survived.
  • No module-level mutable state moved: zero global statements, no module-scope constants beyond __all__ and the two import guards.
  • DAG holds: imported each of the 9 export modules first in a fresh interpreter, plus the package and plugins/vllm_fakequant_hf — clean under every order. Also confirmed the handler-registration side effect survives: hf_weight_export no longer transitively imports hf_export_handlers, but export/__init__.py runs before any submodule, so ExportModuleRegistry is populated (5 entries) even when only hf_weight_export is imported.
  • Tests: tests/unit/torch (excl. puzzletron, which fails to collect locally on unrelated missing deps) — 2256 passed, 0 failed. Import targets of all five changed GPU test files resolve against the new layout; the test_fused_experts.py retargets are correct, moe_utils._export_quantized_weight is the used-site binding the spies need.

LGTM. Four non-blocking notes inline.

On the shim question in the description: I'd skip them. requantize_resmooth_fused_llm_layers and collect_shared_input_modules are absent from __all__ and from docs/source; the only documented entry points (export_hf_checkpoint, plus _export_transformers_checkpoint as used by examples/llm_qat/export.py) all stayed put. Shims would reintroduce exactly the two-addresses-per-symbol problem the PR removes.

Minor: the line-count table in the description drifted after ee7f050/c927f89 — actual is 363/571/457/416, not 373/574/455/415.

Comment thread modelopt/torch/export/unified_export_hf.py Outdated
Comment thread modelopt/torch/export/unified_export_hf.py
Comment thread modelopt/torch/export/hf_weight_export.py
Comment thread .agents/skills/ptq/references/unsupported-models.md
Fridah-nv and others added 2 commits August 10, 2026 23:54
…module APIs

Review follow-ups on the export split:

- `unified_export_hf` no longer aliases `diffusers_utils._HAS_DIFFUSERS`. The
  guard was load-bearing only while `is_diffusers_object` could be undefined;
  now that the import is unconditional, that helper already early-returns
  `False` when diffusers is missing, so the dispatch collapses to a single
  call and `unified_export_diffusers` holds the sole remaining alias.
- Declare `__all__` on the three modules the split added, per the coding
  standards.
- Hoist `import importlib` in `hf_export_prep` to module scope; it is neither
  optional, heavy, nor circular.
- Repoint the last stale `unified_export_hf.py` pointer, in the
  `tied_modules` test helper, at `hf_export_prep.py`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Fridah-nv <201670829+Fridah-nv@users.noreply.github.com>
`export/__init__.py` always pulls `unified_export_hf` in first, so a
reintroduced cycle resolves under that one "good" order and stays invisible to
every existing test. It would surface only for someone importing a submodule
while the package init is partially executed.

Import each export module first in its own interpreter, which is what actually
exercises the DAG the split established.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Fridah-nv <201670829+Fridah-nv@users.noreply.github.com>

@coderabbitai coderabbitai Bot left a comment

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.

Warning

CodeRabbit couldn't request changes on this pull request because it doesn't have sufficient GitHub permissions.

Please grant CodeRabbit Pull requests: Read and write permission and re-run the review.

👉 Steps to fix this

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/unit/torch/export/test_export_import_layering.py (1)

47-51: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Bound the child import.

If an import stalls, subprocess.run waits indefinitely. Pass a finite timeout shorter than the tests/unit limit and include module in the timeout failure.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/torch/export/test_export_import_layering.py` around lines 47 - 51,
Update the subprocess.run call in the import test to use a finite timeout
shorter than the tests/unit limit, and catch the timeout failure so the
resulting assertion or error includes the module name. Preserve the existing
import command and captured output behavior.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/unit/torch/export/test_export_import_layering.py`:
- Around line 47-48: Update the subprocess invocation in the export import test
to bootstrap the package path without executing
modelopt.torch.export.__init__.py, then import the requested module via
sys.argv[1]. Pass module as a subprocess argument and add a timeout so hung
imports fail promptly, preserving the existing import validation behavior.

---

Nitpick comments:
In `@tests/unit/torch/export/test_export_import_layering.py`:
- Around line 47-51: Update the subprocess.run call in the import test to use a
finite timeout shorter than the tests/unit limit, and catch the timeout failure
so the resulting assertion or error includes the module name. Preserve the
existing import command and captured output behavior.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: e70c29e8-ee1d-4064-b030-d4e0f8003818

📥 Commits

Reviewing files that changed from the base of the PR and between c927f89 and 9bb8e58.

📒 Files selected for processing (6)
  • modelopt/torch/export/hf_export_prep.py
  • modelopt/torch/export/hf_weight_export.py
  • modelopt/torch/export/unified_export_diffusers.py
  • modelopt/torch/export/unified_export_hf.py
  • tests/_test_utils/torch/quantization/tied_modules.py
  • tests/unit/torch/export/test_export_import_layering.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • modelopt/torch/export/hf_weight_export.py
  • modelopt/torch/export/unified_export_diffusers.py
  • modelopt/torch/export/hf_export_prep.py

Comment thread tests/unit/torch/export/test_export_import_layering.py Outdated
…ng it

The subprocess-per-module test did not test what it claimed. Importing
`modelopt.torch.export.<submodule>` runs the package initializer first, and
`export/__init__.py` imports its submodules in one fixed order, so all nine
parametrizations replayed the identical import sequence — the requested module
was never the first one loaded. It also cost ~2 min of wall clock for that.

Read the graph instead of running it: parse the module-scope intra-package
imports out of each source file, assert the result is acyclic, and assert the
two leaves import no exporter at all. Only module-scope imports can form an
import-time cycle, which is exactly what the parse sees, and the result is
order-independent rather than dependent on whichever order `__init__` happens
to use. `plugins` is folded in as one node so a cycle routed through a plugin
still shows up.

Verified against injected regressions: a module-scope `hf_export_prep` ->
`unified_export_hf` edge is reported as
`hf_export_prep -> unified_export_hf -> hf_export_prep`, and a function-local
exporter import in a leaf — invisible to the cycle check, since it is the
cycle-dodging shape the split removed rather than an import-time cycle — is
caught by the leaf assertion. Runs in 0.3s.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Fridah-nv <201670829+Fridah-nv@users.noreply.github.com>

@coderabbitai coderabbitai Bot left a comment

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.

Warning

CodeRabbit couldn't request changes on this pull request because it doesn't have sufficient GitHub permissions.

Please grant CodeRabbit Pull requests: Read and write permission and re-run the review.

👉 Steps to fix this

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/unit/torch/export/test_export_import_layering.py`:
- Around line 65-70: The _relative_imports helper currently misses absolute
imports within modelopt.torch.export, allowing dependency cycles to evade the
graph tests. Update _relative_imports to parse ast.Import targets and level-zero
ast.ImportFrom modules beginning with modelopt.torch.export, recording the
relevant top-level imported module names consistently with relative imports; add
regression cases covering both import modelopt.torch.export.unified_export_hf
and from modelopt.torch.export import unified_export_hf.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 9f8a382f-45e7-4c90-b19d-f509a5639016

📥 Commits

Reviewing files that changed from the base of the PR and between 9bb8e58 and 772f67e.

📒 Files selected for processing (1)
  • tests/unit/torch/export/test_export_import_layering.py

Comment thread tests/unit/torch/export/test_export_import_layering.py Outdated
…elative

The parser only recognized relative `ImportFrom` nodes, so `import
modelopt.torch.export.x` and `from modelopt.torch.export[.x] import ...` were
invisible — an exporter dependency could be added in either spelling and both
graph tests would still pass. Not hypothetical: `plugins/vllm_fakequant_megatron.py`
already imports absolutely, so real edges were being dropped.

Parse `ast.Import` and level-zero `ast.ImportFrom` targets under the export
package alongside the relative forms, and cover all five spellings with parser
regression cases.

Widening the parse surfaced a pre-existing module-scope cycle it had been
hiding: `plugins` -> `unified_export_megatron` -> `plugins`. It predates the HF
export split, sits entirely in the megatron half, and resolves today only
because `export/__init__.py` imports `.plugins` first and what
`unified_export_megatron` needs back are `plugins` submodules, which resolve
against a partially initialized package. Left alone as out of scope here, but
recorded as an explicit `KNOWN_CYCLE_EDGES` entry rather than silently excluded,
so a new cycle still fails and the entry is asserted to still exist.

Verified: all three absolute spellings of a leaf -> exporter edge are now
reported by both the cycle check and the leaf assertion.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Fridah-nv <201670829+Fridah-nv@users.noreply.github.com>

@coderabbitai coderabbitai Bot left a comment

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.

Warning

CodeRabbit couldn't request changes on this pull request because it doesn't have sufficient GitHub permissions.

Please grant CodeRabbit Pull requests: Read and write permission and re-run the review.

👉 Steps to fix this

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
tests/unit/torch/export/test_export_import_layering.py (2)

76-92: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Traverse class bodies during import-scope analysis.

At Lines [89-92], ClassDef causes the walker to skip the complete class body. An import inside a class body runs when the containing module is imported. The graph can therefore miss an import cycle. Skip only function bodies and continue traversing ClassDef nodes. Update the docstring at Lines [76-77].

Proposed fix
-        if module_scope_only and isinstance(
-            node, ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef
-        ):
+        if module_scope_only and isinstance(node, ast.FunctionDef | ast.AsyncFunctionDef):
             continue

As per path instructions, tests must exercise the behavior they claim to validate.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/torch/export/test_export_import_layering.py` around lines 76 - 92,
Update the module-scope AST traversal to skip only FunctionDef and
AsyncFunctionDef nodes, while continuing into ClassDef bodies so imports
executed during class definition are analyzed. Revise the surrounding docstring
to state that function bodies are skipped but class bodies are traversed, and
add or update tests covering imports inside class bodies.

Source: Path instructions


147-172: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Fix import-graph false negatives.

  • _find_cycle handles self-cycles and disconnected components. The structural checks cover the listed leaves, exporters, and dispatcher dependencies.
  • _intra_package_imports(..., module_scope_only=True) skips ClassDef bodies, although class-body imports execute during module import. Include class-body imports in cycle detection while continuing to exclude function-local imports.
  • KNOWN_CYCLE_EDGES are removed before cycle detection. A new cycle that reuses an excluded edge can remain hidden. Validate that the exclusion covers only the documented cycle.
  • Add a parser regression test for level-2 relative imports because plugin parsing uses level=2.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/torch/export/test_export_import_layering.py` around lines 147 -
172, Update _intra_package_imports with module_scope_only=True to traverse
ClassDef bodies while continuing to skip function-local imports, and add
coverage for level-2 relative imports using the parser’s plugin configuration.
Revise KNOWN_CYCLE_EDGES handling and its validation so excluded edges cannot
hide any cycle beyond the documented one, while preserving _find_cycle behavior
for self-cycles and disconnected components.
🧹 Nitpick comments (1)
tests/unit/torch/export/test_export_import_layering.py (1)

175-196: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add regression coverage for plugin-relative imports.

At Lines [175-196], the test always passes level=1. The graph uses level=2 for files under plugins at Lines [136-139]. Add cases for from ..unified_export_hf import ... and from .. import unified_export_hf, and pass the parameterized level to _intra_package_imports.

Proposed test adjustment
-    "source",
+    ("source", "level"),
     [
-        pytest.param("from .unified_export_hf import export_hf_checkpoint", id="relative"),
+        pytest.param(
+            "from .unified_export_hf import export_hf_checkpoint", 1, id="relative"
+        ),
+        pytest.param(
+            "from ..unified_export_hf import export_hf_checkpoint", 2, id="plugin-relative"
+        ),
+        pytest.param("from .. import unified_export_hf", 2, id="plugin-relative-bare"),
     ],
 )
-def test_every_import_spelling_is_parsed(source):
+def test_every_import_spelling_is_parsed(source, level):
-    found = _intra_package_imports(ast.parse(source), 1, module_scope_only=True)
+    found = _intra_package_imports(ast.parse(source), level, module_scope_only=True)

As per path instructions, tests must exercise the behavior they claim to validate.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/torch/export/test_export_import_layering.py` around lines 175 -
196, Extend test_every_import_spelling_is_parsed with parameterized cases for
parent-relative imports using “from ..unified_export_hf” and “from .. import
unified_export_hf”, and parameterize the expected import level alongside each
source. Pass that level instead of the hardcoded 1 to _intra_package_imports so
the test covers both regular package imports and plugin-relative level-2
imports.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/unit/torch/export/test_export_import_layering.py`:
- Around line 141-143: Update _import_graph so _find_cycle analyzes the complete
graph instead of deleting KNOWN_CYCLE_EDGES beforehand. Permit only the
documented exact two-node cycle between plugins and unified_export_megatron, and
reject cycles that add intermediate nodes or alternate paths; retain the
existing edge-presence validation where applicable.

---

Outside diff comments:
In `@tests/unit/torch/export/test_export_import_layering.py`:
- Around line 76-92: Update the module-scope AST traversal to skip only
FunctionDef and AsyncFunctionDef nodes, while continuing into ClassDef bodies so
imports executed during class definition are analyzed. Revise the surrounding
docstring to state that function bodies are skipped but class bodies are
traversed, and add or update tests covering imports inside class bodies.
- Around line 147-172: Update _intra_package_imports with module_scope_only=True
to traverse ClassDef bodies while continuing to skip function-local imports, and
add coverage for level-2 relative imports using the parser’s plugin
configuration. Revise KNOWN_CYCLE_EDGES handling and its validation so excluded
edges cannot hide any cycle beyond the documented one, while preserving
_find_cycle behavior for self-cycles and disconnected components.

---

Nitpick comments:
In `@tests/unit/torch/export/test_export_import_layering.py`:
- Around line 175-196: Extend test_every_import_spelling_is_parsed with
parameterized cases for parent-relative imports using “from ..unified_export_hf”
and “from .. import unified_export_hf”, and parameterize the expected import
level alongside each source. Pass that level instead of the hardcoded 1 to
_intra_package_imports so the test covers both regular package imports and
plugin-relative level-2 imports.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: ad323521-8539-450e-b3cc-b279ccfa11a1

📥 Commits

Reviewing files that changed from the base of the PR and between 772f67e and 22d56e1.

📒 Files selected for processing (1)
  • tests/unit/torch/export/test_export_import_layering.py

Comment on lines +141 to +143
if drop_known_cycles:
for src, dst in KNOWN_CYCLE_EDGES:
graph.get(src, set()).discard(dst)

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.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Keep the known-cycle exception narrow.

At Lines [141-143], _import_graph deletes plugins -> unified_export_megatron before _find_cycle runs. This hides any new cycle that uses that edge. For example, plugins -> unified_export_megatron -> new_exporter -> plugins passes after the deletion. The check at Lines [205-211] only confirms that the edge still exists.

Run cycle analysis on the full graph. Allow only the exact documented two-node cycle. Alternatively, remove both known cycle edges and assert that no alternate path from unified_export_megatron to plugins exists.

Based on the provided import-graph implementation, the exception must cover only the pre-existing cycle.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/torch/export/test_export_import_layering.py` around lines 141 -
143, Update _import_graph so _find_cycle analyzes the complete graph instead of
deleting KNOWN_CYCLE_EDGES beforehand. Permit only the documented exact two-node
cycle between plugins and unified_export_megatron, and reject cycles that add
intermediate nodes or alternate paths; retain the existing edge-presence
validation where applicable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants