Repository navigation
megatron_bridge: load the HF model from local disk; Megatron export copies its non-model files - #2610
Conversation
|
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. |
a9eaef0 to
9d11a30
Compare
a6cda6d to
b1d1c21
Compare
9d11a30 to
9ce3570
Compare
b1d1c21 to
d765177
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #2610 +/- ##
===========================================
+ Coverage 68.26% 78.81% +10.55%
===========================================
Files 659 659
Lines 74089 74072 -17
===========================================
+ Hits 50578 58381 +7803
+ Misses 23511 15691 -7820
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
9ce3570 to
b7e35e3
Compare
d765177 to
36aa3ad
Compare
|
36aa3ad to
bf67146
Compare
b7e35e3 to
27857e6
Compare
bf67146 to
ee77aa3
Compare
213ed0e to
0b6a3f4
Compare
eec4bd0 to
96b8404
Compare
0b6a3f4 to
4095979
Compare
7fd6119 to
fb32c1d
Compare
2ce59dd to
2d21f50
Compare
fb32c1d to
6de984f
Compare
… copy_non_model_files (#2607) ### What does this PR do? Type of change: new feature ### Summary of the series Every pipeline that quantizes, prunes, sparsifies or distills a Hugging Face model now starts the same way: **make the checkpoint local, then load from it**. `ensure_local_checkpoint` returns a local directory as-is and downloads a Hub model ID in full if it is not cached yet -- once, on rank 0 -- and the model, tokenizer and processor are then loaded from that local directory. Before, a Hub ID went straight to `from_pretrained`, which fetched only the files loading needed, so every later step that needed the source as files found its own way back to it: `hf_ptq` through a hand-rolled cache lookup (`_resolve_model_path`, whose `TRANSFORMERS_CACHE` glob picked the lexicographically largest commit hash rather than `refs/main`) and a hardcoded download allowlist, regenerating tokenizer files along the way; the Megatron export by fetching only `*.py` for a Hub ID; other callers of `export_hf_checkpoint` not at all. With the source on local disk, exports follow one rule: an export writes the **model files** -- weights in any format and the metadata describing them -- and carries every other source file (**non-model files**: tokenizer, processor, remote code, chat templates, README, ...) over verbatim, from **local disk only**. Merge in order: 1. #2607 — Add `ensure_local_checkpoint` to load models from local disk, and `copy_non_model_files` ← **this PR** 2. #2608 — HF exporters write off-index safetensors and non-model files 3. #2609 — `hf_ptq`: load the model from local disk, downloading it first if needed 4. #2610 — `megatron_bridge`: load the HF model from local disk; Megatron export copies its non-model files 5. #2611 — `llm_sparsity`: load the model from local disk, downloading it first if needed ### This PR [1/5] Adds the two building blocks to `modelopt/torch/utils/plugins/hf_checkpoint_utils.py`, re-exported from `modelopt.torch.export`. Nothing calls them yet; #2609–#2611 make `ensure_local_checkpoint` the first step of each pipeline. - **`ensure_local_checkpoint(model_name_or_path, group=None) -> (hub_model_id, local_checkpoint_path)`** makes the whole checkpoint local before anything loads it: a local directory as-is, a Hub ID downloaded in full if not cached yet (guessing which files are redundant would risk dropping ones the checkpoint needs). Load the model, tokenizer and processor from `local_checkpoint_path`; `hub_model_id` is the Hub ID, or `None` for a local path, for records that must stay valid on other hosts. - Under `torch.distributed`, rank 0 of `group` resolves it and broadcasts the result -- or its error, so a failed download raises everywhere rather than stranding the waiting ranks. Rank 0 decides for local paths too, since ranks checking for themselves could disagree about a node-local directory. Without a `group`, every rank waits on a temporary gloo group whose 24 h timeout outlasts a large download; with one, the caller's group carries the wait. A rank that cannot see the resolved path raises, asking for shared storage. - `snapshot_download` silently returns a possibly incomplete cached snapshot when the Hub is unreachable. With `HF_HUB_OFFLINE` the cache is used quietly (the user's choice); with the Hub unexpectedly unreachable it is used with a warning, or the Hub's error is raised if nothing is cached. An input that is neither a local directory nor a Hub model ID the Hub can access -- a mistyped local path, say -- raises a `ValueError` saying so, chained to the Hub's error; a gated repo still raises `GatedRepoError`. - **`copy_non_model_files(source, export_dir)`** copies every file of a local checkpoint, recursively, except weight files in any format, top-level export metadata (`config.json`, `hf_quant_config.json`, stale quant configs, `.experiment.json`), files the export already wrote, hidden directories (`.git`, `.cache`), and links that resolve outside the checkpoint or its Hub `blobs/` (checked with the existing `resolve_checkpoint_file`). It never writes through a link already in the export, removes a partial copy when one fails, and skips `export_dir` when it lies inside the source (e.g. an earlier export). It returns `/`-separated paths on every platform. **What happens to the existing copy helpers.** `copy_non_model_files` replaces both copy rules the series' callers use today. #2609 moves `hf_ptq` off `copy_non_safetensor_files_from_ckpt`. #2610 moves the Megatron export onto `copy_non_model_files` (dropping its Hub-fetching `copy_hf_ckpt_remote_code` branch), moves `prune_minitron` and `export_distilled_megatron_to_hf` off `copy_hf_ckpt_remote_code`, and deprecates `copy_non_safetensor_files_from_ckpt`. `copy_hf_ckpt_remote_code` stays for now: puzzletron's `init_child_from_parent` still uses it, and migrating puzzletron is out of scope for this series. Also: module constants move to the top next to a note defining model versus non-model files; `off_index_safetensors_files` / `copy_off_index_safetensors` accept a `None` source and report what they copy; comments stop calling off-index weight files "sidecars". ### Usage ```python from modelopt.torch.export import copy_non_model_files, ensure_local_checkpoint hub_model_id, local_checkpoint_path = ensure_local_checkpoint("Qwen/Qwen3-8B") # every rank calls it model = AutoModelForCausalLM.from_pretrained(local_checkpoint_path) ... copy_non_model_files(local_checkpoint_path, export_dir) # after the export wrote its own files ``` ### Testing `pytest tests/unit/torch/utils tests/unit/torch/export tests/examples/hf_ptq` (GPU end-to-end and `test_dataset_utils` excluded): 684 passed. New tests cover the copy rule (weights, owned metadata, subdirectories, hidden dirs, links inside/outside/dangling, Hub `blobs/`), online/offline/unreachable/missing-repo resolution, and -- over two real gloo processes -- rank-0-only download, error propagation, an explicit group, a subgroup without global rank 0, and a local path hidden from one rank. The resolution paths were also checked live against the Hub. CPU only (torch 2.12, transformers 5.9, huggingface_hub 1.19); `transformer_engine` cannot be imported in this environment, so GPU and Megatron tests are left to CI. ### Before your PR is "*Ready for review*" Make sure you read and follow [Contributor guidelines](https://github.com/NVIDIA/Model-Optimizer/blob/main/CONTRIBUTING.md) and your commits are signed (`git commit -s -S`). Make sure you read and follow the [Security Best Practices](https://github.com/NVIDIA/Model-Optimizer/blob/main/SECURITY.md#security-coding-practices-for-contributors) (e.g. avoiding hardcoded `trust_remote_code=True`, `torch.load(..., weights_only=False)`, `pickle`, etc.). - Is this change backward compatible?: ✅ (additive) - If you copied code from any other sources or added a new PIP dependency, did you follow guidance in `CONTRIBUTING.md`: N/A - Did you write any new necessary tests?: ✅ - Did you update [Changelog](https://github.com/NVIDIA/Model-Optimizer/blob/main/CHANGELOG.rst)?: N/A (entries land with the behaviour changes in #2608–#2610) - Did you get Claude approval on this PR?: ❌ ### Additional Information Stacked series; this is the base. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Checkpoint exports can resolve local and Hub-hosted checkpoints, using cached snapshots when offline or Hub access is unavailable. Missing offline cache entries are reported as errors. * Exports can copy eligible non-model checkpoint files without overwriting existing export files. Hidden directories and unsafe or dangling links are excluded. * Distributed exports coordinate checkpoint resolution across processes and report failures, including when other processes cannot access the resolved path. * Safetensors files not listed in an index are treated as standalone weight files and can be copied into exports when eligible. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
|
/ok to test b8de537 |
There was a problem hiding this comment.
Bot review (gpt-6.1-sol) — DM the bot to share feedback.
Nudge: prior correctness and coverage concerns are resolved, but embedded agent directives in earlier CodeRabbit comments prevent automated approval.
Needs action:
- Confirm human sign-off on this review; earlier CodeRabbit comments contain embedded agent prompts, which were ignored as untrusted input.
No action needed:
- ✔️ Resolved since the last review: local-source distillation exports, cached-module monkeypatching, quantize/prune Hub-ID and saved-tokenizer regressions, and Hub-ID API documentation.
- The PR body justifies shared checkpoint utilities over direct partial Hub downloads and Transformers artifact regeneration; no second production subsystem is introduced.
- Existing deprecation test edits are justified and retain all copying assertions.
- GPU regressions were inspected but could not be executed with the available tools.
b8de537 to
fcbacc6
Compare
|
/ok to test fcbacc6 |
There was a problem hiding this comment.
can we fold this case into any existing tests instead of adding new ones since these tests are too slow to run?
There was a problem hiding this comment.
Folded in 6723df2, and the standalone distill test is removed. test_distill_llm_hf_export now checks that both its inline export and the standalone re-export read the student from the copy ensure_local_checkpoint resolved; the re-export is passed the Hub ID. test_distill_vlm passes the Hub ID to its export step. A shared fake_hub_checkpoint fixture in conftest.py does the patching; it applies to single-rank steps, the only ones running in the pytest process. With more ranks the tests run on the local path as before.
|
/ok to test 6723df2 |
The Megatron unified HF export copied every top-level non-safetensors file of a local source -- *.bin weights included -- but only *.py of a Hub ID, then regenerated the generation config, tokenizer and image processor. Now it writes its config and copies the source's non-model files with copy_non_model_files, from a local source only; a Hub ID gets a warning, as in the HF exporters. That is a breaking change for callers passing a Hub ID, who no longer get those files. The megatron_bridge quantize, export, prune and distillation-export scripts therefore make the HF model local first with ensure_local_checkpoint, keeping their argument as given and reading from hf_model_path. The tokenizer path recorded in saved Megatron checkpoints stays the Hub ID, which remains valid on other hosts. prune_minitron and the distillation VLM export -- which every rank reaches -- copy the non-model files from the main rank only, and the distillation LLM export now copies them too. copy_non_safetensor_files_from_ckpt lost its last internal user and is deprecated in favour of copy_non_model_files. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
…D as hub_model_id As in hf_ptq: name the local checkpoint directory and the Hub identifier for what they are, with a student_ prefix in the distillation export. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
…model ID Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
export_llm_to_hf and save_vlm_to_hf now copy the source's non-model files, which reads the student from local disk only, but distill.py still passed --student_hf_path unresolved: a Hub ID would fail the export after training. Resolve it with ensure_local_checkpoint at the start of main() and build, probe and export the student from that local copy; --student_hf_path stays as given. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
0.48 has branched (release/0.48.0), so this lands in 0.49: restore the 0.48 entry for the HF exporters to its released wording and file the Megatron export's feature, breaking-change and deprecation entries under 0.49. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
…es for a Hub model ID Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
The example tests are slow, so check the Hub-ID handling inside the runs that already exist instead of adding new ones: quantize/export and prune take the Hub ID, distillation's inline export and both standalone distillation exports read the resolved copy, and the quantized checkpoint records the Hub ID as its tokenizer. fake_hub_checkpoint only patches a single-rank step, which runs in the pytest process; with more ranks the tests run on the local path as before. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
Saving a Megatron checkpoint loads the tokenizer it records, here the Hub ID, which failed for the fake org/tiny-model in CI. Map AutoTokenizer.from_pretrained(hub_model_id) to the resolved copy, as the Hugging Face cache does for a real Hub ID. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
6723df2 to
4f05440
Compare
|
/ok to test 4f05440 |
Megatron-LM calls AutoTokenizer.from_pretrained with pretrained_model_name_or_path= as a keyword, which the wrapper only took positionally. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
|
/ok to test e1fc2c7 |
cjluo-nv
left a comment
There was a problem hiding this comment.
Bot review (gpt-6.1-sol) — DM the bot to share feedback.
Changes requested: folding the regressions into existing tests dropped coverage of the previously broken inline VLM export caller.
Needs action:
- 💬 Author folded the regressions into existing tests — restore inline VLM resolved-source coverage in
test_distill_vlmas described in the inline comment, without adding another training run. - Confirm human sign-off; earlier CodeRabbit comments contain embedded agent directives, which were ignored as untrusted input.
No action needed:
- ✔️ Resolved: local-source distillation callers, cached-module patching, quantize/prune Hub-ID and saved-tokenizer assertions, and Hub-ID API documentation.
- Reusing existing checkpoint utilities is justified over direct
huggingface_hub.snapshot_download, Transformers artifact regeneration, and custom filesystem copying; no second production subsystem is introduced. - Deprecation-test edits retain all copying assertions. Folding slow tests is reasonable, but the VLM inline branch still needs equivalent coverage.
- GPU tests were inspected, not executed.
|
|
||
| # Separately convert the distilled Megatron checkpoint to HF with the standalone export script | ||
| # Separately convert the distilled Megatron checkpoint to HF with the standalone export script, | ||
| # passing the student as a Hub ID (see ``fake_hub_checkpoint``). |
There was a problem hiding this comment.
Bot comment.
The folded VLM regression now exercises only export_distilled_megatron_to_hf.py: the fixture is installed after training, and the preceding distill.py command does not pass hf_export_path. Consequently reverting distill.py's save_vlm_to_hf caller to args.student_hf_path would still pass this test, although that was one of the original post-training export failures covered by the removed parameterized regression. Preserve that coverage without another training run: serve the checkpoint and patch both scripts before training, enable inline HF export to a separate directory in the existing training command, and assert its resolved-copy marker before running the existing standalone export. Retain the standalone export and vision-weight assertions.
cjluo-nv
left a comment
There was a problem hiding this comment.
approve to unblock. @ChenhanYu probably need to approve as well.
What does this PR do?
Type of change: new feature (with a backward breaking change and a deprecation)
Summary of the series
Every pipeline that quantizes, prunes, sparsifies or distills a Hugging Face model now starts the same way: make the checkpoint local, then load from it.
ensure_local_checkpointreturns a local directory as-is and downloads a Hub model ID in full if it is not cached yet -- once, on rank 0 -- and the model, tokenizer and processor are then loaded from that local directory.Before, a Hub ID went straight to
from_pretrained, which fetched only the files loading needed, so every later step that needed the source as files found its own way back to it:hf_ptqthrough a hand-rolled cache lookup (_resolve_model_path, whoseTRANSFORMERS_CACHEglob picked the lexicographically largest commit hash rather thanrefs/main) and a hardcoded download allowlist, regenerating tokenizer files along the way; the Megatron export by fetching only*.pyfor a Hub ID; other callers ofexport_hf_checkpointnot at all.With the source on local disk, exports follow one rule: an export writes the model files -- weights in any format and the metadata describing them -- and carries every other source file (non-model files: tokenizer, processor, remote code, chat templates, README, ...) over verbatim, from local disk only.
The rest of the series is merged: #2607, #2608, #2609 and #2611. This PR is its last part and is based on
main.This PR
Loading. The
megatron_bridgequantize, export, prune, distillation and distillation-export scripts now make the HF model local first withensure_local_checkpoint-- a local directory as-is, a Hub ID downloaded once -- and load the model, tokenizer and processor from that copy (args.local_checkpoint_path;student_local_checkpoint_pathin distillation and its export).--hf_model_name_or_path/--student_hf_pathstay as given, and the tokenizer path recorded in saved Megatron checkpoints stays the Hub ID, which remains valid on other hosts.Exporting. The Megatron unified HF export copied every top-level non-safetensors file of a local source --
*.binweights included -- but only*.pyof a Hub ID, then regenerated the generation config, tokenizer and image processor. Now it writes its config and copies the source's non-model files withcopy_non_model_files, from a local source only; a Hub ID gets a warning, as in the HF exporters. Breaking: callers passing a Hub ID no longer get those files (CHANGELOG entry with the fix: pass the path fromensure_local_checkpoint).prune_minitronand the distillation VLM export -- which every rank reaches -- copy from the main rank only, and the distillation LLM export now copies the files too.copy_non_safetensor_files_from_ckptlost its last internal user and is deprecated in favour ofcopy_non_model_files.Usage
Testing
Pre-commit (ruff, mypy, bandit, rst, license) passes on the branch against
main. CPU:tests/unit/torch/utils/test_hf_checkpoint_utils.pyandtests/unit/torch/export, 483 passed (deprecation test included). GPU, left to CI: the existingtests/examples/megatron_bridgeruns ofquantize.py+export_quantized_megatron_to_hf.py,prune_minitron.py, anddistill.py+export_distilled_megatron_to_hf.py(LLM and VLM) now pass the model as a Hub ID served from a local copy (fake_hub_checkpoint, single-rank runs). They check that loading and every export read the resolved copy and that the quantized Megatron checkpoint records the Hub ID as its tokenizer, adding no new test runs.Before your PR is "Ready for review"
Make sure you read and follow Contributor guidelines and your commits are signed (
git commit -s -S).Make sure you read and follow the Security Best Practices (e.g. avoiding hardcoded
trust_remote_code=True,torch.load(..., weights_only=False),pickle, etc.).export_mcore_gpt_to_hfwith a Hub-ID source no longer writes tokenizer, processor, generation config or remote-code files; pass a local directory (e.g. fromensure_local_checkpoint).copy_non_safetensor_files_from_ckptis deprecated but still works.CONTRIBUTING.md: N/AAdditional Information
Based on
main.Summary by CodeRabbit