Skip to content

fix(diffusion): reuse warm HF cache instead of re-downloading models - #2747

Merged
akoumpa merged 2 commits into
mainfrom
pranav/diffusion_hf_cache_fix
Jun 24, 2026
Merged

fix(diffusion): reuse warm HF cache instead of re-downloading models#2747
akoumpa merged 2 commits into
mainfrom
pranav/diffusion_hf_cache_fix

Conversation

@pthombre

Copy link
Copy Markdown
Contributor

What does this PR do ?

Stops the diffusion recipes (and their preprocessing step) from re-fetching models from the HuggingFace Hub on every run even when the model is already cached in HF_HOME.

The issue

The diffusers bridge and the preprocessing processors pass a bare repo id straight to diffusers' from_pretrained:

  • NeMoAutoDiffusionPipeline.from_pretrained / from_configDiffusionPipeline.from_pretrained(model_id, ...)
  • tools/diffusion/processors/{flux,flux2,hunyuan,qwen_image,wan}.py<Pipeline>.from_pretrained(model_name, ...)

With a bare repo id, huggingface_hub resolves in online mode by default: for every file it issues a network request to the Hub to revalidate the commit hash / ETag before deciding whether to reuse the cache. So even a warm HF_HOME is re-validated over the network, and any ETag drift, partial cache, or unreachable-but-not-offline state turns into a fresh download. A diffusion pipeline loads ~6 sub-components (VAE, two text encoders, two tokenizers, transformer) in one call, which multiplies the round-trips.

The behavior depended entirely on the ambient HF_HUB_OFFLINE env var being set in every stage/subprocess. The diffusion launcher never sets it itself — it only relies on the outer CI template — so any context where it isn't propagated (local dev, a runner without it, a subprocess that drops it) re-downloads.

The transformers bridge does not have this problem: it pre-resolves the repo to a local snapshot directory in code (_resolve_model_dirsnapshot_download(..., local_files_only=True) in nemo_automodel/_transformers/model_init.py, plus an explicit HF_HUB_OFFLINE short-circuit) and hands that local path to HF — which then performs zero network I/O. The diffusers bridge is a separate integration layer and never went through those helpers, so the cache discipline was simply missing on the diffusion path.

The fix

Port the same discipline to the diffusion path. Once HF from_pretrained receives a local directory, it does no network I/O regardless of env-var propagation, which is what removes the per-run re-download.

Changelog

  • Add nemo_automodel/_diffusers/_hf_cache.py with resolve_diffusion_model_dir(): returns local paths unchanged; for repo ids it downloads the snapshot once only when the cache is cold and the process is online (HF_HUB_OFFLINE unset), then resolves the local snapshot with local_files_only=True so the subsequent from_pretrained performs no network I/O. Guarded with safe_import('huggingface_hub') and passes through unchanged when the lib is unavailable.
  • Use the helper in NeMoAutoDiffusionPipeline.from_pretrained (finetune) and from_config (pretrain).
  • Use the helper in the flux, flux2, hunyuan, qwen_image, and wan preprocessing processors (wan resolves once and reuses the dir across its three subfolder= loads).
  • Add unit tests in tests/unit_tests/_diffusers/test_hf_cache.py covering local-path passthrough, offline (single cache-only call), online (download-then-resolve), and hub-unavailable passthrough.
Cache state HF_HUB_OFFLINE Behavior after fix
Warm unset local dir resolved, no network, no re-download
Warm set local dir resolved, no network
Cold unset download once, then resolve local dir
Cold set clean "not in cache" error

Before your PR is "Ready for review"

Pre checks:

  • Make sure you read and followed Contributor guidelines
  • Did you write any new necessary tests?
  • Did you add or update any necessary documentation?

Additional Information

  • Independent of the flux nightly CI fixes in fix(diffusion): resolve flux nightly CI failures #2529.
  • Not addressed here: the video-dataset snapshot_download('modal-labs/dissolve') in diffusion_finetune_launcher.sh has the same online-by-default gap, but only runs in CI where HF_HUB_OFFLINE is already set. Can harden separately if desired.

🤖 Generated with Claude Code

The diffusers bridge and preprocessing processors passed bare repo ids to
diffusers' from_pretrained, which resolves in online mode by default and
re-validates (and can re-download) a warm HF_HOME over the network on every
run. The transformers bridge avoids this by pre-resolving the repo to a local
snapshot dir; port the same discipline to the diffusion path.

- Add resolve_diffusion_model_dir() helper: returns local paths unchanged,
  downloads once only on a cold cache when online, then resolves the local
  snapshot with local_files_only=True so from_pretrained does no network I/O.
- Use it in NeMoAutoDiffusionPipeline.from_pretrained and from_config.
- Use it in the flux, flux2, hunyuan, qwen_image, and wan processors.
- Add unit tests for the helper.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Pranav Prashant Thombre <pthombre@nvidia.com>
@pthombre
pthombre requested a review from a team as a code owner June 23, 2026 21:19
@copy-pr-bot

copy-pr-bot Bot commented Jun 23, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@pthombre

Copy link
Copy Markdown
Contributor Author

/ok to test f6a139a

@pthombre

Copy link
Copy Markdown
Contributor Author

/claude review

# Online: fetch once (cold cache), then resolve the local dir without revalidation.
assert mock_sd.call_count == 2
assert mock_sd.call_args_list[0].args == ("some/repo-id",)
assert mock_sd.call_args_list[1].kwargs == {"local_files_only": True}

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.

nit: This assertion verifies the kwargs of the second snapshot_download call but doesn't check that "some/repo-id" was also passed as the positional arg. If someone accidentally dropped the model_id argument from the second call, this test would still pass.

Suggested change
assert mock_sd.call_args_list[1].kwargs == {"local_files_only": True}
assert mock_sd.call_args_list[1] == (("some/repo-id",), {"local_files_only": True})

…sts offline

The cache-resolution helper imported snapshot_download inside the function
body, so test_hf_cache.py's patch of the module-level name failed at setup.
Bind it at module scope (guarded by the optional-dep check) so it is
patchable, with identical runtime behavior.

The auto_diffusion_pipeline from_pretrained/from_config tests mock
DiffusionPipeline but not the newly added resolve_diffusion_model_dir call,
so they reached the Hub for fake repo ids (401). Add an autouse fixture that
resolves repo ids to a passthrough, keeping the unit tests offline.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Pranav Prashant Thombre <pthombre@nvidia.com>
@pthombre

Copy link
Copy Markdown
Contributor Author

/ok to test a75a683

@akoumpa akoumpa added the r0.5.0 Auto-cherrypick to release branch. Apply before merge; cherrypick happens after merge. label Jun 24, 2026
@akoumpa
akoumpa merged commit 98d39fe into main Jun 24, 2026
80 checks passed
@akoumpa
akoumpa deleted the pranav/diffusion_hf_cache_fix branch June 24, 2026 06:56
akoumpa pushed a commit that referenced this pull request Jun 24, 2026
…dels (2747)` into `r0.5.0` (#2754)

fix(diffusion): reuse warm HF cache instead of re-downloading models (#2747)

* fix(diffusion): reuse warm HF cache instead of re-downloading models

The diffusers bridge and preprocessing processors passed bare repo ids to
diffusers' from_pretrained, which resolves in online mode by default and
re-validates (and can re-download) a warm HF_HOME over the network on every
run. The transformers bridge avoids this by pre-resolving the repo to a local
snapshot dir; port the same discipline to the diffusion path.

- Add resolve_diffusion_model_dir() helper: returns local paths unchanged,
  downloads once only on a cold cache when online, then resolves the local
  snapshot with local_files_only=True so from_pretrained does no network I/O.
- Use it in NeMoAutoDiffusionPipeline.from_pretrained and from_config.
- Use it in the flux, flux2, hunyuan, qwen_image, and wan processors.
- Add unit tests for the helper.




* fix(diffusion): make HF cache resolver patchable and keep diffuser tests offline

The cache-resolution helper imported snapshot_download inside the function
body, so test_hf_cache.py's patch of the module-level name failed at setup.
Bind it at module scope (guarded by the optional-dep check) so it is
patchable, with identical runtime behavior.

The auto_diffusion_pipeline from_pretrained/from_config tests mock
DiffusionPipeline but not the newly added resolve_diffusion_model_dir call,
so they reached the Hub for fake repo ids (401). Add an autouse fixture that
resolves repo ids to a passthrough, keeping the unit tests offline.




---------

Signed-off-by: Pranav Prashant Thombre <pthombre@nvidia.com>
Signed-off-by: NeMo Bot <nemo-bot@nvidia.com>
Co-authored-by: Pranav Thombre <pthombre@nvidia.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

r0.5.0 Auto-cherrypick to release branch. Apply before merge; cherrypick happens after merge.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants