Skip to content

[Modular] Save Modular Pipeline weights to Hub#13168

Open
DN6 wants to merge 5 commits intomainfrom
modular-save-pretrained-weights
Open

[Modular] Save Modular Pipeline weights to Hub#13168
DN6 wants to merge 5 commits intomainfrom
modular-save-pretrained-weights

Conversation

@DN6
Copy link
Collaborator

@DN6 DN6 commented Feb 20, 2026

What does this PR do?

Enable modular pipelines to save model weights to the Hub. Previously, only configs were saved. Note that if individual components are loaded from external repos, their model configs will continue to reference those repos. I've added an optional overwrite_modular_index flag to rewrite component configs so they point to the destination repo instead.

Fixes # (issue)

Before submitting

Who can review?

Anyone in the community is free to review the PR once the tests have passed. Feel free to tag
members/contributors who may be interested in your PR.

@DN6 DN6 requested a review from yiyixuxu February 20, 2026 14:02
@HuggingFaceDocBuilderDev

The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update.

Copy link
Member

@sayakpaul sayakpaul left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left some comments. Let's also add some simple tests:

  • Checking if the params are being serialized (just checking if the checkpoint files exist is enough)?
  • modular_model_index.json reflects the repository locations properly?

If expressed as an integer, the unit is bytes.
push_to_hub (`bool`, *optional*, defaults to `False`):
Whether to push the pipeline to the Hugging Face model hub after saving it.
**kwargs: Additional keyword arguments passed along to the push to hub method.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we document what is allowed in the kwargs? overwrite_modular_index deserves some documentation IMO.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will add the necessary doc.

Comment on lines 1917 to 1918
if component_spec.default_creation_method != "from_pretrained":
continue
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you explain what this is doing?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When saving a Modular Pipeline, its components in modular_model_index.json may reference repos different from the destination repo.

e.g. The text_encoder component in this model repo points to OzzyGT/Qwen3-4B-bnb-4bit while the other components point to black-forest-labs/FLUX.2-klein-4B".
https://huggingface.co/diffusers/FLUX.2-klein-4B-modular/blob/main/modular_model_index.json

None of these components actually point to the reference repo diffusers/FLUX.2-klein-4B-modular. The overwrite_modular_index parameter updates the modular index file so that all component references point to the repo specified by repo_id.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood but I don't find any relation of this to the condition if component_spec.default_creation_method != "from_pretrained":. For a remote repo ID, we would still rely on from_pretrained() no?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The overwrite_modular_index block only touches from_pretrained components because only they have pretrained_model_name_or_path entries in the config pointing to external Hub repos. Anything that's not created with from_pretrained is skipped.


# Create a new empty model card and eventually tag it
if push_to_hub:
card_content = generate_modular_model_card_content(self.blocks)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this conditioned on the above changes? If not, maybe we can keep it in the earlier position?

repo_id = kwargs.pop("repo_id", save_directory.split(os.path.sep)[-1])

for component_name, component_spec in self._component_specs.items():
sub_model = getattr(self, component_name, None)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(nit):

Suggested change
sub_model = getattr(self, component_name, None)
component = getattr(self, component_name, None)

Not all components need to models.

if variant is not None and "variant" in component_spec_dict:
component_spec_dict["variant"] = variant

self.register_to_config(**{component_name: (library, class_name, component_spec_dict)})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not too sure about the objective of this block. What happens if its corresponding model_cls doesn't have the save method we support through LOADABLE_CLASSES?

Or is this unrelated?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator

@yiyixuxu yiyixuxu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks!
i left one comment!


# Generate modular pipeline card content
card_content = generate_modular_model_card_content(self.blocks)
if overwrite_modular_index:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

very nice!
can we handle the case for custom/local models in this PR as well? I think it fits naturally in this PR

Basically, if users update a pipeline with a custom model (e.g. one loaded from a local path, or a custom model that they would want to host in the same repo with trust_remote_code like https://huggingface.co/krea/krea-realtime-video/tree/main/transformer) and they should be able save it to the pipeline repo even when overwrite_modular_index= False :

# load a custom model from local path
custom_model = AutoModel.from_pretrained("/path/to/your/local/model")
pipe.update_components(custom_model=custom_model)
pipe.save_pretrained("my-repo", push_to_hub=True)

in the modular_model_index, the pretrained_model_name_or_path should refer to my-repo

For components that don't have a _diffusers_load_id (i.e. no Hub reference), save_pretrained should automatically:

  1. Save the weights to the repo (you already did this)
  2. Update the modular_model_index.json to point locally (this need to be implemented even when overewrite_modular_index=False

additionally, I think we could use "." as the convention for local references in modular_model_index.json (huggingface/blog#3278 (comment)) but that should be a follow-up PR if we decided to do it!

and then, we can remove the warning in update_components for missing _diffusers_load_id should be removed: https://github.com/huggingface/diffusers/blob/main/src/diffusers/modular_pipelines/modular_pipeline.py#L2150

logger.warning(
    f"ModularPipeline.update_components: {name} has no valid _diffusers_load_id. "
    f"This will result in empty loading spec, use ComponentSpec.load() for proper specs"
)

because this now become a completely normal workflow (load custom model → update pipeline → save)!

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.

4 participants