Skip to content

[OMNIML-6054] [6/21] Route load_pretrained by the distribution plan - #2779

Open
shengliangxu wants to merge 23 commits into
shengliangx/hfep-2-plan-decisionfrom
shengliangx/hfep-13b-load-routing
Open

shengliangxu wants to merge 23 commits into
shengliangx/hfep-2-plan-decisionfrom
shengliangx/hfep-13b-load-routing

Conversation

@shengliangxu

@shengliangxu shengliangxu commented Oct 13, 2026 •

Copy link
Copy Markdown
Collaborator

What does this PR do?

Type of change: New feature

load_pretrained (#2733) loads a model whole. A distributed load, such as parallel_load_and_prepare_fsdp2 (main), is a separate entry point, and every caller picks one itself, as hf_ptq.py --use_fsdp2 does. A caller that picks a loader also gets what that loader skips: a sharded load cannot run the model type's loading hook, so a Nemotron-H checkpoint's MTP head is not built and its tensors go to the export unquantized.

This PR makes load_pretrained the one loader, whole or distributed:

  • distribution is a DistributionPlan; by default it is the plan resolve_distribution_plan ([5/21]) decides.
    • dense="fsdp": parallel_load_and_prepare_fsdp2 over the process group. A single process has no group, so the load sets one up, of one rank, once nothing can refuse the load anymore. distributed_run ([4/21]) destroys it with the run.
    • Otherwise from_pretrained. On a distributed run every rank gets the whole model, on its own GPU.
    • A plan that shards the routed experts raises here. [12/21] adds that load, once the expert-parallel loader ([9/21]-[11/21]) is in.
  • The plan is recorded on the model and every submodule (set_distribution_plan, [4/21]), where calibration and export read it. load_pretrained records it exactly once, at the end of every load; the loaders it calls record none. fsdp2_wrap records nothing, as on main, so a caller that wraps or loads a model through it directly records the plan itself, as the error for a missing plan says.
  • fsdp2_wrap moves to model_load_utils, with the load that calls it and every other loader. modelopt.torch.utils.distributed.fsdp2_wrap forwards to it, so callers of the old path keep working, and the FSDP2 helpers it uses stay in distributed.
  • Every load makes a Hub ID local and records the unplaced source keys ([OMNIML-6080] Derive the unplaced source keys once, at load, through a core load_pretrained #2733).
  • Every rank loads the same plan. Handed a plan, every rank loads the one rank 0 was handed (broadcast_from, [4/21]), just as it would one it decided. Ranks that each loaded the plan they were handed could build mismatched meshes and hang. Rank 0 checks the plan before it sends it, so a plan it refuses raises on every rank instead of leaving the others waiting in the broadcast.
  • Refusals, before anything loads.
    • A sharded load cannot run the model type's loading hook, so it refuses a checkpoint the hook would build modules for: the MTP head above.
    • It refuses from_pretrained's placement arguments (device_map, max_memory, ...) and a model class it does not build, rather than ignore them.
    • The FSDP2 load refuses a quantized checkpoint, whose tensors it would read as stored.
    • A plan made for another world size is refused, and so is anything other than a plan.
    • hf_config without the plan it was resolved with is refused. A sharded load builds the model from it as given, so a plan decided here, from the checkpoint's own config, would not be the one it came with. config= is for a config the plan should be decided from.

Usage

# torchrun --nproc_per_node 8 quantize.py
from modelopt.torch.utils.distributed import distributed_run
from modelopt.torch.utils.plugins.model_load_utils import load_pretrained

with distributed_run():
    model = load_pretrained("meta-llama/Llama-3.1-8B")  # a whole copy on every rank's GPU
    # With MODELOPT_FSDP_SIZE=world: FSDP2-sharded over the 8 ranks instead.

Testing

  • New tests/unit/torch/utils/test_load_pretrained_distribution.py. It runs on CPU, and stand-in loaders record their calls. It covers:
    • each load: the arguments it gets and the plan it records;
    • a decided FSDP2 load, handed the config the plan was decided from instead of reading it again;
    • a single process's FSDP2 load setting up its group of one, and leaving none behind when the load is refused;
    • every refusal, raised before any loader runs, the expert-parallel plan's included;
    • on two real gloo ranks (cpu_dist_workers), rank 0 handed a replicated plan and rank 1 an FSDP2 one: both load whole and record rank 0's plan. A rank that loads the plan it was handed fails at once, on the placement refusal.
  • A single process's load, placed as from_pretrained places it, with the caller's arguments and a single process's plan: [OMNIML-6080] Derive the unplaced source keys once, at load, through a core load_pretrained #2733's real CPU load in tests/unit/torch/utils/test_unplaced_source_keys.py, which now also checks the plan.
  • fsdp2_wrap leaving the plan to the load: a CPU unit test in tests/unit/torch/utils/test_load_pretrained_distribution.py. [OMNIML-6080] Derive the unplaced source keys once, at load, through a core load_pretrained #2733's GPU tests that wrap or load through it directly (test_export_carry_over.py, test_model_load_utils.py) record the plan themselves. Its old path forwarding every argument: tests/unit/torch/utils/test_distributed_run.py.
  • Mutation-checked: 17 mutants, each failing a test. They drop the rank-GPU default, pin a single process's model, drop the config handoff or the world-size check, load an expert-parallel or FSDP2 plan whole, accept a string, let each rank load the plan it was handed, record no plan in the load or record one in fsdp2_wrap too, set no group up for a single process's FSDP2 load, set one up over a live group, or have the old fsdp2_wrap path drop an argument or the result.
  • New tests/gpu/torch/utils/test_load_pretrained_distribution.py runs load_pretrained left to decide, on every GPU of the node:
    • a dense model loads whole on each rank, and with MODELOPT_FSDP_SIZE=world FSDP2-sharded;
    • each rank's weights match the checkpoint;
    • the load records its unplaced keys and its plan.
    • Not run locally; CI's GPU workflow runs it.
  • tests/examples/hf_ptq/test_example_utils.py: get_model's stand-in model is a module now, since load_pretrained records its plan on it.
  • The load refusals are one parametrized test, and the single-process load test gives way to [OMNIML-6080] Derive the unplaced source keys once, at load, through a core load_pretrained #2733's real load. Each drop is checked by mutation.
  • Locally on this revision, the reordered series rebased on main 264fd26: the full unit suite on transformers 5.18, and on 5.5 apart from the tests whose ONNX, TensorRT or diffusers packages that environment lacks; CI's transformers-free unit lane; pre-commit over the whole PR against main; and the Sphinx docs build. The same at every PR of the stack.

Before your PR is "Ready for review"

  • Is this change backward compatible?: ✅ load_pretrained keeps its behavior in a single process, and distribution is new. fsdp2_wrap behaves as on main and records no plan; its old import path still works.
  • 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?: ✅ 0.49.0, Misc.
  • Did you get Claude approval on this PR?: ❌ Not yet.

Additional Information

Part 6 of 21 of expert-parallel PTQ for Hugging Face MoE models; merges after [5/21]. The expert-parallel half of this routing is [12/21].

Replaces #2774, which carried this routing whole as [19/20]. Moving its branch down the stack made the branch part of #2774's old base, so GitHub closed #2774 as merged into that base; nothing reached main.

Summary by CodeRabbit

  • New Features

    • Added GGML imatrix calibration with weighted search and per-expert statistics for fused MoE models.
    • Updated IQ recipes for combined GPTQ and imatrix calibration, and added a Nemotron 3.5 Super VL AutoQuantize recipe.
    • Added standalone draft model training and export for external-draft speculative decoding.
    • Distributed loading supports replicated or FSDP2-sharded models through configurable distribution plans, which are recorded on loaded models. Automatic plan selection and Hub authentication and caching options are supported.
  • Documentation

    • Added guidance for distributed loading and distribution-plan recording.

@copy-pr-bot

copy-pr-bot Bot commented Oct 13, 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 Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Review in 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

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
📥 Commits

Reviewing files that changed from the base of the PR and between 6150cee and fb8bf38.

📒 Files selected for processing: 4
  • CHANGELOG.rst
  • modelopt/torch/utils/plugins/model_load_utils.py
  • tests/unit/torch/utils/test_load_pretrained_distribution.py
  • tests/unit/torch/utils/test_unplaced_source_keys.py

Included review availability: This review used your included allowance. Your plan provides up to 12 included reviews per hour; 10 remain after this review.

📝 Walkthrough

load_pretrained now accepts or resolves a distribution plan and selects whole-model or FSDP2-sharded loading. It validates inputs, records load metadata, and provides FSDP2 wrapping through the model-loading plugin.

Changes: Distributed model loading

Layer / File(s) Summary
Plan-based load routing
modelopt/torch/utils/plugins/model_load_utils.py, tests/unit/torch/utils/test_load_pretrained_distribution.py, tests/gpu/torch/utils/test_load_pretrained_distribution.py, tests/examples/hf_ptq/test_example_utils.py, CHANGELOG.rst, tests/unit/torch/utils/test_unplaced_source_keys.py
load_pretrained resolves or accepts a distribution plan and selects whole-model or FSDP2-sharded loading. Sharded loads validate supported arguments and checkpoints. The loader records the plan and unplaced keys. Unit and GPU tests cover routing, validation, and placement.
FSDP2 wrapping and delegation
modelopt/torch/utils/plugins/model_load_utils.py, modelopt/torch/utils/distributed.py, tests/unit/torch/utils/test_distributed_run.py, tests/unit/torch/utils/test_load_pretrained_distribution.py
The plugin implements FSDP2 wrapping, and distributed.py delegates to it. Tests check argument forwarding and confirm that wrapping alone does not record a distribution plan.
Distribution metadata in distributed tests
tests/gpu/torch/export/test_export_carry_over.py, tests/gpu/torch/utils/test_model_load_utils.py
Distributed test models record an FSDP distribution plan before export-related checks.

Priority: ➖ Normal

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

Change: Feature · Severity of issue fixed: Medium

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant load_pretrained
  participant _route
  participant _load_sharded
  participant parallel_load_and_prepare_fsdp2
  participant from_pretrained
  Caller->>load_pretrained: Provide checkpoint and optional distribution plan
  load_pretrained->>_route: Validate plan and select load path
  alt Dense FSDP plan
    load_pretrained->>_load_sharded: Load checkpoint with config
    _load_sharded->>parallel_load_and_prepare_fsdp2: Load on local CUDA device
  else Whole-model plan
    load_pretrained->>from_pretrained: Load model with device map
  end
  load_pretrained-->>Caller: Return model with plan and unplaced keys recorded
Loading

Suggested reviewers: kevalmorabia97, sugunav14

Merge Risk: ⚪ Minimal · up to fb8bf

The documented distributed loading paths are ready for normal checks. Users selecting an unsupported expert-parallel plan must choose a supported loading option.

Pre-merge checks: Passed 5 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 68.42% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 57 functions across 10 files. (1 skipped:… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed 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 names the main change: load_pretrained now picks its loading route (whole-model or FSDP2) from a DistributionPlan. The changeset matches this. The title does not mention the moved fsdp2_wrap…
Linked Issues check Passed The only linked issue, #2774, is closed and completed. The issue states it is historical context only. No active directly linked target remains, so no linked-issue coding requirements apply to this pu…
Out of Scope Changes check Passed The whole-PR summary (mergeBase 4e10244 to currentHead fb8bf38) shows changes that match the PR description. In modelopt/torch/utils/plugins/model_load_utils.py, load_pretrained gains a distribution…
Security Anti-Patterns Passed No listed security anti-pattern is introduced by the pull request. In the changed modelopt Python files, torch.load uses weights_only=True; no added weights_only=False, allow_pickle=True, ha…
Full details: Title check

Explanation

The title names the main change: load_pretrained now picks its loading route (whole-model or FSDP2) from a DistributionPlan. The changeset matches this. The title does not mention the moved fsdp2_wrap or the new Hub options, but it does not need to cover every detail. The bracketed prefix [6/21] is a series marker and does not affect the assessment.

Full details: Linked Issues check

Explanation

The only linked issue, #2774, is closed and completed. The issue states it is historical context only. No active directly linked target remains, so no linked-issue coding requirements apply to this pull request. The prior PASS result still holds.

Full details: Out of Scope Changes check

Explanation

The whole-PR summary (mergeBase 4e10244 to currentHead fb8bf38) shows changes that match the PR description. In modelopt/torch/utils/plugins/model_load_utils.py, load_pretrained gains a distribution parameter, resolve_distribution_plan gains config and Hub access options, and fsdp2_wrap moves here. In modelopt/torch/utils/distributed.py, fsdp2_wrap forwards to the new location and its signature is unchanged. The Hub access options support the PR's Hub ID localization. The test changes cover the same functions: test_example_utils.py (FakeModel now a Module so the plan can be recorded), test_export_carry_over.py and test_model_load_utils.py (manual plan recording after fsdp2_wrap), test_distributed_run.py (forwarding), test_unplaced_source_keys.py (plan check), test_resolve_distribution_plan.py (Hub options), and the new unit and GPU test modules. The CHANGELOG.rst entry documents the same behavior. No file in the summary is shown to be unrelated. The assessment rests on the AI-generated summary; the source hunks were not read in this review.

Full details: Security Anti-Patterns

Explanation

No listed security anti-pattern is introduced by the pull request. In the changed modelopt Python files, torch.load uses weights_only=True; no added weights_only=False, allow_pickle=True, hardcoded trust_remote_code=True, eval()/exec() call, or # nosec comment exists. The pull request changes no pyproject.toml or requirements*.txt file, so it adds no dependency requiring license review.

Full details: Docstring Coverage

Explanation

Docstring coverage is 68.42% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 57 functions across 10 files. (1 skipped: 1 unsupported.)

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

✨ Finishing Touches: 💡 2
📝 Generate docstrings: 💡
  • Commit to this branch
  • Create a new PR
🛠️ Fix failing CI checks: 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

@codecov

codecov Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.87629% with 4 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (shengliangx/hfep-2-plan-decision@fb63124). Learn more about missing BASE report.

Files with missing lines Patch % Lines
modelopt/torch/utils/plugins/model_load_utils.py 95.74% 4 Missing ⚠️
Additional details and impacted files
@@                         Coverage Diff                         @@
##             shengliangx/hfep-2-plan-decision    #2779   +/-   ##
===================================================================
  Coverage                                    ?   61.36%           
===================================================================
  Files                                       ?      648           
  Lines                                       ?    71985           
  Branches                                    ?        0           
===================================================================
  Hits                                        ?    44173           
  Misses                                      ?    27812           
  Partials                                    ?        0           
Flag Coverage Δ
unit 61.36% <95.87%> (?)

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.

@shengliangxu

Copy link
Copy Markdown
Collaborator Author

/ok to test 3ac7c7b

@shengliangxu
shengliangxu removed this pull request from stack #2781 October 14, 2026 00:13
@shengliangxu
shengliangxu force-pushed the shengliangx/hfep-13b-load-routing branch from 3ac7c7b to df76b36 Compare October 14, 2026 00:14
@shengliangxu
shengliangxu added this pull request to stack #2786 October 14, 2026 00:14
@shengliangxu shengliangxu changed the title [OMNIML-6054] [3/21] Route load_pretrained by the distribution plan [OMNIML-6054] [4/22] Route load_pretrained by the distribution plan Oct 14, 2026
@shengliangxu

Copy link
Copy Markdown
Collaborator Author

/ok to test df76b36

@shengliangxu
shengliangxu force-pushed the shengliangx/hfep-13b-load-routing branch from df76b36 to aa3615e Compare October 14, 2026 01:47
@github-actions

github-actions Bot commented Oct 14, 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-2779/

Built to branch gh-pages at 2026-10-23 06:37 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

@shengliangxu

Copy link
Copy Markdown
Collaborator Author

/ok to test aa3615e

@shengliangxu
shengliangxu force-pushed the shengliangx/hfep-13b-load-routing branch 3 times, most recently from aa3615e to 168c90d Compare October 14, 2026 16:16
@shengliangxu

Copy link
Copy Markdown
Collaborator Author

/ok to test 168c90d

@shengliangxu
shengliangxu removed this pull request from stack #2786 October 14, 2026 18:13
@shengliangxu
shengliangxu force-pushed the shengliangx/hfep-13b-load-routing branch from 168c90d to 537ff8d Compare October 14, 2026 18:13
@shengliangxu
shengliangxu added this pull request to stack #2802 October 14, 2026 18:14
@shengliangxu shengliangxu changed the title [OMNIML-6054] [4/22] Route load_pretrained by the distribution plan [OMNIML-6054] [3/21] Route load_pretrained by the distribution plan Oct 14, 2026
@shengliangxu

Copy link
Copy Markdown
Collaborator Author

/ok to test 537ff8d

@shengliangxu
shengliangxu force-pushed the shengliangx/hfep-13b-load-routing branch 3 times, most recently from 465f1eb to aa2a065 Compare October 14, 2026 21:46
@shengliangxu

Copy link
Copy Markdown
Collaborator Author

/ok to test aa2a065

@shengliangxu

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@shengliangxu

Copy link
Copy Markdown
Collaborator Author

/ok to test ec4159a

@Fridah-nv

Copy link
Copy Markdown
Contributor

Question: does hf_config need to be a public load_pretrained argument?

It looks like it only lets a sharded load reuse the config resolve_distribution_plan already read, at the cost of two guard errors and a config → hf_config rewrite. Could load_pretrained pass that config to _load_sharded internally, so the signature stays config + distribution? If an outside caller needs it, a note on who would help.

@shengliangxu

Copy link
Copy Markdown
Collaborator Author

/ok to test 17cfb96

@shengliangxu

Copy link
Copy Markdown
Collaborator Author

@Fridah-nv, it lets a caller that decides the plan itself load by that plan without the config being prepared twice.

  • How hf_ptq.py uses it: it calls resolve_distribution_plan before loading, so it can refuse an FSDP2 plan without --use_fsdp2 and run validate_fsdp2_supported on the config. It then hands the plan to load_pretrained.
  • Why the load needs the prepared config: preparing a config through its model spec can patch the model's code, so the load must not prepare it again. It needs the prepared config the plan came with.
  • As of this push: hf_config without its plan is refused, and a config= passed with a handed-in plan is prepared once, on a copy.

Carrying the prepared config inside the plan would remove the argument. But a DistributionPlan is broadcast between ranks and recorded on the model, and the config would ride along with it, so we've kept the argument.

load_pretrained becomes the one loader for a model ModelOpt will optimize
and export, whole or distributed. A new distribution argument picks the
load: "sequential" (from_pretrained as before), "fsdp2"
(parallel_load_and_prepare_fsdp2), or a DistributionPlan -- FSDP2 over
the world, or a whole copy on every rank's own GPU. A plan that shards
the routed experts raises: no load here does that yet. Left at None,
the active ModelOptSession decides: a single process loads whole, a
distributed session decides the plan with the new
resolve_distribution_plan, through decide_distribution_plan with the
MODELOPT_EP_DEGREE and MODELOPT_FSDP_SIZE knobs
(modelopt.torch.utils.env). The plan a load runs is kept as the
session's synced plan, the same on every rank.

Every load makes a Hub ID local and records the unplaced source keys.
The FSDP2 load cannot run the model type's loading hook, so it refuses a
checkpoint the hook would build modules for (a Nemotron-H MTP head), and
it refuses from_pretrained arguments it would otherwise ignore, a
quantized checkpoint, whose tensors it would read as stored, and a
partial fsdp_size, which it cannot shard.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
load_pretrained kept a plan the caller passed as session.plan, which holds rank 0's, but routed and
loaded with this rank's own: ranks handed different plans built mismatched meshes and hung while
the session said they agreed. Every rank now loads the plan the session holds, once its own plan
passed the checks, so a refused plan still leaves the session as it was. A plan the session decides
itself is already held, and is no longer sent a second time.

The test that rank 0 alone works out the unplaced keys answers the plan the load now decides
first.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
Every rank loads the plan rank 0 was handed, now set with session.plan.decide(lambda: handed)
rather than an assignment, and read back with session.plan.value.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
Rank and world size come from the process group, and a plan handed to every rank goes out from
rank 0 through broadcast_from. Rank 0 now checks that plan before it sends, so a plan it refuses
raises on every rank instead of leaving the others in the broadcast. Nothing keeps the plan after
the load.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
load_pretrained's distribution is now a DistributionPlan or None, which decides through
resolve_distribution_plan; the 'sequential' and 'fsdp2' strings that picked a load without a plan
are gone. A single process loads by a single process's plan, so every load has one.

Whatever the route, the plan is recorded on the model and every submodule, where calibration and
export read it back instead of inspecting the model. parallel_load_and_prepare_fsdp2 records its
own plan as well, for a caller that uses it directly. The decision's copy here goes too: it lives
in resolve_distribution_plan now.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
fsdp2_wrap FSDP2-shards a model over the process group, so it records that plan on the model
itself. A caller that wraps a model with it -- the FSDP2 load among them, which no longer records
its own -- exports and calibrates it by the plan without a further call.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
…n on

load_pretrained records the plan on every module of the model it returns, so the stand-in model
these tests have from_pretrained return is a torch module now.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
FSDP2 shards through a process group even on one rank, and the FSDP2
load refused to start without one, leaving every caller to set it up
first. load_pretrained sets up the group of one itself, after anything
that could refuse the load, so a refused load leaves none behind; the
run (distributed_run) destroys it with the run.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
fsdp2_wrap shards a model as part of loading it, the one load that
calls it lives in model_load_utils, and so does every other loader.
It moves there; the FSDP2 helpers it uses stay in distributed.py, and
modelopt.torch.utils.distributed.fsdp2_wrap forwards to it for callers
of the old path.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
fsdp2_wrap recorded a plan it derived from the process group, and
load_pretrained then recorded the one it loaded by over it: two writers,
one silently replacing the other. load_pretrained is the one that knows
the plan, so it alone records it, once, at the end of every load.
fsdp2_wrap records nothing, as on main; the tests that wrap or load a
model through it directly record the plan themselves, as any caller
distributing a model itself must.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
main opened 0.49.0 for what merges after 0.48.0's cut, which this PR does.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
resolve_distribution_plan reads the checkpoint through the failure-carrying
all_reduce, which gathers from every rank of a launch. The run_as fixture
answers that gather as it answers the plan's broadcast: every rank read the
checkpoint as this one did.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
The test played rank 1 against a stand-in broadcast that answered with
rank 0's plan, and checked which stub loader ran. It now runs on the
session's two gloo workers: rank 0 is handed a replicated plan and rank
1 an FSDP2 one, both load a real checkpoint onto the CPU, and both must
load it whole and record rank 0's plan. A rank that loaded its own plan
would refuse the placement an FSDP2 load cannot honor, so a regression
fails at once instead of waiting in a collective.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
Fold near-duplicates into one test per behavior:
- test_a_load_refuses_what_it_cannot_load takes the another-world,
  quantized-checkpoint, not-a-plan and hf-config-on-a-whole-load
  refusals; each case still fails if its refusal goes.
- test_a_single_process_loads_the_model_whole_as_asked goes, and its
  plan check moves to test_unplaced_source_keys.py's real
  single-process load. That load fails if a single process is placed on
  the rank GPU, loses the caller's kwargs, records another plan or
  reaches the FSDP2 loader.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
test_fsdp2_wrap_leaves_the_plan_to_the_load imported it inside the test,
though it is neither optional nor circular. At module scope it sits after the
file's importorskip("transformers"), so the transformers-free lane still skips
the module first.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
load_pretrained now resolves the distribution plan before it loads, and
resolve_distribution_plan resolved the checkpoint and read its config
without token, cache_dir or local_files_only. That read reaches the Hub
for custom code an auto_map names in another repo, as the loads after it
do. resolve_distribution_plan takes the three options, and
load_pretrained passes on the ones it was given.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
The load_pretrained test cannot see whether the plan's read resolves the
checkpoint with token, cache_dir and local_files_only: load_pretrained has
made the checkpoint local by then. Drive resolve_distribution_plan with a
Hub ID and check that the resolution and the config read both get them.
(Test from the epdp port of this change.)

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
load_pretrained keeps token, cache_dir and local_files_only out of the
keywords it routes, and a sharded load given a plan but no config read
the config without them. That read reaches the Hub for custom code an
auto_map names in another repo, as the whole-model load's do.
_load_sharded now takes the Hub options for its config read.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
load_pretrained honored a config passed for the load only in the whole-model
from_pretrained: the plan was still decided from the checkpoint's
config.json, so a directory without one failed before loading, and a sharded
load refused config as an option it does not take.

resolve_distribution_plan takes the caller's config and decides from a copy
of it (deciding sets fields on it) without reading the checkpoint's;
load_pretrained passes it on, and a sharded load builds from it as its
hf_config. Passing both config and hf_config is refused.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
With a plan handed in, nothing is decided, so the model type comes from the
passed config itself rather than from the plan's copy of it. The test only
covered a plan load_pretrained decides, and a load that ignored the passed
config there (model type from hf_config alone) still passed it.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
A sharded load builds from hf_config as given. Without a plan handed
in, load_pretrained decided one from the checkpoint's own config and
then loaded the caller's hf_config, so the model loaded was not the one
the plan was decided for, nor one the model spec's checks had seen.
hf_config is the config resolve_distribution_plan returns with its plan;
without that plan it is now refused, pointing at config= for a config
the plan should be decided from.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
…oint

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
@shengliangxu

Copy link
Copy Markdown
Collaborator Author

/ok to test fb9d332

This branch has not been deployed

No deployments
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