Skip to content

Load local ModularPipeline snapshots when modular_model_index.json still names Hub ids - #14645

Open
22elix3r wants to merge 1 commit into
huggingface:mainfrom
22elix3r:fix/modular-local-snapshot-hub-ids
Open

Load local ModularPipeline snapshots when modular_model_index.json still names Hub ids#14645
22elix3r wants to merge 1 commit into
huggingface:mainfrom
22elix3r:fix/modular-local-snapshot-hub-ids

Conversation

@22elix3r

Copy link
Copy Markdown

What does this PR do?

Fixes #14640.

Motivation

ModularPipeline.from_pretrained(local_dir) / blocks.init_pipeline(local_dir) followed by load_components() still resolved components against the Hub id stored in modular_model_index.json. A complete local snapshot of MiniMax-H3 therefore re-downloaded ~134 GB (or left components as None offline), even though the files were already on disk.

This is the model_index.json vs modular_model_index.json asymmetry: the standard-repo fallback already binds repo to the local path; the modular index is taken verbatim, including published Hub ids.

Root cause

ModularPipeline.__init__ copies each from_pretrained spec from modular_model_index.json as-is. Published checkpoints such as MiniMax-H3 write "pretrained_model_name_or_path": "MiniMaxAI/MiniMax-H3" into every entry. When the caller passed a local directory, those Hub ids still won, so ComponentSpec.load() contacted the Hub.

The model_index.json fallback already does the right thing:

component_spec_dict = {
    "repo": pretrained_model_name_or_path,  # the local directory
    "subfolder": name,
    ...
}

The invariant that was missing: if the pipeline path is a local directory and <local_dir>/<subfolder> exists, load that component from the directory; otherwise keep the Hub id.

That second clause is required. Modular repos are allowed to be pointer repos (index only, weights elsewhere). Blindly rewriting every spec to the local directory would break those.

Solution

After specs are applied from either index, if pretrained_model_name_or_path is a local directory, rewrite a from_pretrained spec onto that directory only when:

  • the spec is not already a local path (explicit local overrides stay put, including stale-path recovery when the recorded path no longer exists)
  • the spec has a non-empty subfolder
  • <local_dir>/<subfolder> exists on disk

Missing subfolders keep their Hub id (pointer repos / pruned snapshots). Hub from_pretrained("org/model") is unchanged because os.path.isdir("org/model") is false.

Not done: wrapping pin_memory/from_pretrained in try/except, MiniMax-specific branches, or changing how published indexes are written. save_pretrained(..., overwrite_modular_index=True) remains the way to persist local references.

Testing

Synthetic snapshot (no MiniMax weights, no GPU):

PYTHONPATH=src python -m pytest tests/modular_pipelines/test_modular_pipeline_loading.py::TestLocalModularSnapshotLoading -q
5 passed in 1.40s

Without the __init__ change the three remapping tests fail; the two "leave Hub id / leave existing local path" tests already passed on main.

PYTHONPATH=src python -m pytest tests/modular_pipelines/test_modular_pipeline_loading.py -q
17 passed in 111.45s
PYTHONPATH=src python -m pytest tests/modular_pipelines/test_modular_pipelines_custom_blocks.py -q -k "not slow and not nightly"
18 passed, 2 skipped in 9.63s
ruff check src/diffusers/modular_pipelines/modular_pipeline.py tests/modular_pipelines/test_modular_pipeline_loading.py
ruff format --check src/diffusers/modular_pipelines/modular_pipeline.py tests/modular_pipelines/test_modular_pipeline_loading.py
All checks passed; 2 files already formatted

New coverage:

  • init_pipeline(local_dir) with Hub ids in the index and a present scheduler/ subfolder loads offline
  • constructor path, same contract
  • missing subfolder keeps the Hub id
  • mixed snapshot rewrites only present components
  • an existing local spec path is not overwritten by the snapshot directory

Performance

No extra copies and no extra Hub traffic on the common path. The new branch is an os.path.isdir scan of already-loaded specs, only when the pipeline path is a local directory. Hub loads are unchanged. Local snapshots avoid a second download.

Self-review

  • Blocking: none.
  • Left for review: English docs only (docs/source/en/modular_diffusers/modular_pipeline.md); the zh page was not updated. Specs remapped at init also flow into register_components / saved config — same as passing pretrained_model_name_or_path into load_components() today. overwrite_modular_index=True is still the way to retarget a save.
  • Verdict: READY.

Related issue

Fixes #14640

Coordination: #14640 (open, unassigned, no linked PR at the time of this change). Investigation comment: #14640 (comment)

Before submitting

Who can review?

cc @yiyixuxu @apolinario

…ndex.json

When ModularPipeline is initialized from a local directory, component specs
copied from modular_model_index.json still pointed at Hub repo ids. load_components()
then re-downloaded (or failed offline) even if the snapshot already contained the
subfolders. Bind those specs to the local directory when the subfolder exists, and
leave Hub ids in place for pointer repos and pruned snapshots.
Copilot AI lite review requested due to automatic review settings August 28, 2026 23:58

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions github-actions Bot added size/M PR with diff < 200 LOC fixes-issue documentation Improvements or additions to documentation tests modular-pipelines and removed size/M PR with diff < 200 LOC labels Aug 28, 2026
@4suino

4suino commented Aug 29, 2026

Copy link
Copy Markdown

Verified on the setup #14640 was reported from (MacBook Pro M5 Max, 128 GB, macOS 26.6.2, torch 2.14 nightly, diffusers 0.40.0.dev snapshot of 2026-08-08): applying this PR's modular_pipeline.py hunk to a copy of the installed package and re-running the zero-download reproducer from the issue (empty HF_HOME, HF_HUB_OFFLINE=1, a full local MiniMax-H3 snapshot with the original index files naming MiniMaxAI/MiniMax-H3):

with this PR:  pipe.load_components(names=["scheduler"]) -> MiniMaxH3Scheduler, spec rebound to the local directory
without it:    -> None (hub lookup, as in the issue)

A snapshot whose index was already rewritten to the local path keeps working unchanged. The "keep the Hub id when the subfolder is missing" clause is the right call for pointer repos. Thanks for picking this up so quickly.

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

Labels

documentation Improvements or additions to documentation fixes-issue modular-pipelines tests

Projects

None yet

3 participants