Skip to content

[Fix] Resolve deprecated pipeline modules in from_pretrained - #14026

Open
Nas01010101 wants to merge 3 commits into
huggingface:mainfrom
Nas01010101:fix/load-deprecated-pipelines
Open

Nas01010101 wants to merge 3 commits into
huggingface:mainfrom
Nas01010101:fix/load-deprecated-pipelines

Conversation

@Nas01010101

Copy link
Copy Markdown

What does this PR do?

Fixes #14008

The bug

DiffusionPipeline.from_pretrained raises a spurious ValueError when loading any pipeline that was relocated under diffusers.pipelines.deprecated (for example Wuerstchen, moved in #13157):

from diffusers import DiffusionPipeline
DiffusionPipeline.from_pretrained("warp-ai/wuerstchen-prior")
ValueError: prior/wuerstchen.py as defined in `model_index.json` does not exist in
warp-ai/wuerstchen-prior and is not a module in 'diffusers/pipelines'.

Root cause

This is a bug in the deprecation mechanism itself, not in the deprecated pipeline.

_get_custom_components_and_folders decides whether a component refers to a built-in module via hasattr(diffusers.pipelines, module_candidate). When a pipeline is moved to diffusers.pipelines.deprecated, its module name is no longer a top-level attribute of diffusers.pipelines (deprecated modules are nested under the deprecated key in _import_structure). So hasattr returns False, the component is mistaken for a missing custom module, and loading fails.

The fix suggested in the issue (hasattr(diffusers.pipelines.deprecated, module_candidate)) does not work: diffusers.pipelines.deprecated is a _LazyModule, so the module name (wuerstchen) is not an attribute until it is imported; only the lazily exposed class names are. hasattr therefore still returns False.

Fix

Resolve the module spec under the deprecated namespace without importing it, via importlib.util.find_spec("diffusers.pipelines.deprecated.<module>"). This is import side-effect free (no heavy import is triggered) and correctly recognizes relocated modules. Genuinely missing modules still raise, since find_spec returns None for them.

Tests

Added GetCustomComponentsAndFoldersTests in tests/pipelines/test_pipeline_utils.py (the existing home for pipeline_loading_utils unit tests). All are function level and require no network or GPU.

$ python -m pytest tests/pipelines/test_pipeline_utils.py::GetCustomComponentsAndFoldersTests -q
4 passed, 2 warnings in 2.28s

Reverting only the production change reproduces the issue (from_pretrained raises the ValueError above) and makes the new tests fail, confirming they guard the bug. ruff check and ruff format --check pass on the changed files.

Before submitting

  • Did you use an AI agent to help with this PR? Yes, Claude Opus 4.8. The diff was self-reviewed against .ai/review-rules.md, and I understand and take responsibility for the change.
  • Did you read the contributor guideline?
  • Did you write any new necessary tests?

@dxqb

dxqb commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

Claude: Thanks for this — the _is_deprecated_pipeline_module check does get past the early ValueError in _get_custom_components_and_folders when loading e.g. warp-ai/wuerstchen-prior.

One thing to flag: with this PR applied, loading a deprecated pipeline still doesn't complete. get_class_obj_and_candidates later does importlib.import_module(library_name) with the bare module name, so it then raises:

ModuleNotFoundError: No module named 'wuerstchen'

(pipeline_loading_utils.py, in the else: # else we just import it from the library. branch.) The module resolves only under diffusers.pipelines.deprecated.<library_name>, so that import path may need the same deprecated-namespace fallback for the fix to make these pipelines actually loadable end-to-end.

Drafted by Claude.

Nas01010101 pushed a commit to Nas01010101/diffusers that referenced this pull request Jun 28, 2026
Follow-up to the review feedback on huggingface#14026: the earlier fix gets past the
spurious ValueError in _get_custom_components_and_folders, but loading a
deprecated pipeline still failed end-to-end.

get_class_obj_and_candidates received is_pipeline_module=False for relocated
modules (hasattr(pipelines, library_name) is False), so it fell to the bare
importlib.import_module(library_name) branch and raised
ModuleNotFoundError: No module named 'wuerstchen'.

Even resolving the import there is not enough: that branch builds
class_candidates via getattr(library, c, None), and a relocated module does not
expose the base classes in importable_classes, so no load method would resolve
and load_sub_model would then raise 'cannot be loaded ...'. Deprecated modules
must be handled with pipeline-module semantics. Add an explicit branch that
imports diffusers.pipelines.deprecated.<library_name> and resolves the class via
dict.fromkeys, matching the is_pipeline_module path. Genuinely missing modules
still raise ModuleNotFoundError.

Adds GetClassObjAndCandidatesTests covering the wuerstchen prior component
(class + from_pretrained load method) and the missing-module case.
@myselfhimself

Copy link
Copy Markdown

For me the code changes by the 2 contributors of this PR fix the problem as tested on my side yesterday. I have not tried to run the related patched automated test collections.
Thank you for the fix!

Anas added 3 commits August 19, 2026 20:47
Pipelines relocated to `diffusers.pipelines.deprecated` (e.g. Wuerstchen via
`hasattr(pipelines, module_candidate)` check in
`_get_custom_components_and_folders` mistook them for missing custom modules and
raised a spurious ValueError when loading repos such as
`warp-ai/wuerstchen-prior`.

Also check the deprecated namespace (via `importlib.util.find_spec`, no import
side effects) before treating a component as a missing custom module. Genuinely
missing modules still raise.

Fixes huggingface#14008
Follow-up to the review feedback on huggingface#14026: the earlier fix gets past the
spurious ValueError in _get_custom_components_and_folders, but loading a
deprecated pipeline still failed end-to-end.

get_class_obj_and_candidates received is_pipeline_module=False for relocated
modules (hasattr(pipelines, library_name) is False), so it fell to the bare
importlib.import_module(library_name) branch and raised
ModuleNotFoundError: No module named 'wuerstchen'.

Even resolving the import there is not enough: that branch builds
class_candidates via getattr(library, c, None), and a relocated module does not
expose the base classes in importable_classes, so no load method would resolve
and load_sub_model would then raise 'cannot be loaded ...'. Deprecated modules
must be handled with pipeline-module semantics. Add an explicit branch that
imports diffusers.pipelines.deprecated.<library_name> and resolves the class via
dict.fromkeys, matching the is_pipeline_module path. Genuinely missing modules
still raise ModuleNotFoundError.

Adds GetClassObjAndCandidatesTests covering the wuerstchen prior component
(class + from_pretrained load method) and the missing-module case.
Gap 1 — _fetch_class_library_tuple: deprecated modules (e.g. Wuerstchen)
live under diffusers.pipelines.deprecated, so hasattr(pipelines, pipeline_dir)
returns False and the function fell back to returning ("diffusers", class_name).
That breaks save_pretrained because the written model_index.json entry ("diffusers",
"WuerstchenPrior") is not resolvable on reload.  Fix: promote the pipeline_dir to
is_pipeline_module=True when _is_deprecated_pipeline_module(pipeline_dir) is True,
so the returned library is the subpackage name (e.g. "wuerstchen").

Gap 2 — maybe_raise_or_warn: the guard was `if not is_pipeline_module`, which caused
deprecated-library callers to enter the type-check path, import the non-existent
top-level module, and eventually hit issubclass(model_cls, None) (TypeError) once
expected_class_obj stayed None.  Fix: extend the guard to also skip the type-check
path for deprecated modules (`not _is_deprecated_pipeline_module(library_name)`),
mirroring the existing pipeline-module behaviour.

Gap 3 — simple_get_class_obj: the same is_pipeline_module=False path caused
importlib.import_module("wuerstchen") to raise ModuleNotFoundError.  Fix: add an
elif branch that imports from diffusers.pipelines.deprecated.<library_name>,
mirroring the fix already present in get_class_obj_and_candidates.

Tests added for all three gaps (FetchClassLibraryTupleTests,
MaybeRaiseOrWarnTests, SimpleGetClassObjTests), including a roundtrip assertion
that proves the tuple written by _fetch_class_library_tuple is resolvable through
get_class_obj_and_candidates.
@Nas01010101
Nas01010101 force-pushed the fix/load-deprecated-pipelines branch from aadc755 to 8a0866e Compare August 20, 2026 01:05
@github-actions github-actions Bot added size/L PR with diff > 200 LOC and removed size/M PR with diff < 200 LOC labels Aug 20, 2026

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

from_pretrained fails loading deprecated pipelines

3 participants