Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

[Hub] feat: explicitly tag to diffusers when using push_to_hub #6678

Merged
merged 25 commits into from
Jan 26, 2024
Merged
Changes from 7 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
d5f2cf6
feat: explicitly tag to diffusers when using push_to_hub
sayakpaul Jan 23, 2024
dc7f55d
remove tags.
sayakpaul Jan 23, 2024
91b26ff
reset repo.
sayakpaul Jan 23, 2024
156586f
Merge branch 'main' into add-diffusers-tag
sayakpaul Jan 23, 2024
03a704a
Apply suggestions from code review
sayakpaul Jan 23, 2024
d33ed6c
fix: tests
sayakpaul Jan 23, 2024
2baf0d2
fix: push_to_hub behaviour for tagging from save_pretrained
sayakpaul Jan 23, 2024
5d9e664
Apply suggestions from code review
sayakpaul Jan 23, 2024
62ddbb8
Apply suggestions from code review
sayakpaul Jan 23, 2024
0d73555
import fixes.
sayakpaul Jan 23, 2024
5297ad4
add library name to existing model card.
sayakpaul Jan 23, 2024
99ce47c
add: standalone test for generate_model_card
sayakpaul Jan 23, 2024
19d26da
Merge branch 'main' into add-diffusers-tag
sayakpaul Jan 23, 2024
2b93dcc
fix tests for standalone method
sayakpaul Jan 23, 2024
0f31032
moved library_name to a better place.
sayakpaul Jan 23, 2024
987178b
merge create_model_card and generate_model_card.
sayakpaul Jan 23, 2024
5bd864c
fix test
sayakpaul Jan 23, 2024
33e2d91
address lucain's comments
sayakpaul Jan 23, 2024
322c0e1
fix return identation
sayakpaul Jan 23, 2024
73ea51d
Apply suggestions from code review
sayakpaul Jan 23, 2024
e32e5e1
address further comments.
sayakpaul Jan 23, 2024
6b36050
Merge branch 'main' into add-diffusers-tag
sayakpaul Jan 23, 2024
ffc3845
Update src/diffusers/pipelines/pipeline_utils.py
sayakpaul Jan 23, 2024
183fd65
Merge branch 'main' into add-diffusers-tag
sayakpaul Jan 23, 2024
2d5c555
Merge branch 'main' into add-diffusers-tag
sayakpaul Jan 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/diffusers/models/modeling_utils.py
Original file line number Diff line number Diff line change
@@ -41,7 +41,7 @@
is_torch_version,
logging,
)
from ..utils.hub_utils import PushToHubMixin
from ..utils.hub_utils import PushToHubMixin, create_and_tag_model_card


logger = logging.get_logger(__name__)
@@ -375,6 +375,11 @@ def save_pretrained(
logger.info(f"Model weights saved in {os.path.join(save_directory, weights_name)}")

if push_to_hub:
# Create a new empty model card and eventually tag it
model_card = create_and_tag_model_card(repo_id, token=token)
# Update model card if needed:
model_card.save(os.path.join(save_directory, "README.md"))

self._upload_folder(
save_directory,
repo_id,
6 changes: 6 additions & 0 deletions src/diffusers/pipelines/pipeline_utils.py
Original file line number Diff line number Diff line change
@@ -60,6 +60,7 @@
logging,
numpy_to_pil,
)
from ..utils.hub_utils import create_and_tag_model_card
from ..utils.torch_utils import is_compiled_module


@@ -720,6 +721,11 @@ def is_saveable_module(name, value):
self.save_config(save_directory)

if push_to_hub:
# Create a new empty model card and eventually tag it
model_card = create_and_tag_model_card(repo_id, token=token, is_pipeline=True)
# Update model card if needed:
model_card.save(os.path.join(save_directory, "README.md"))

self._upload_folder(
save_directory,
repo_id,
32 changes: 32 additions & 0 deletions src/diffusers/utils/hub_utils.py
Original file line number Diff line number Diff line change
@@ -144,6 +144,32 @@ def create_model_card(args, model_name):
model_card.save(card_path)


# Taken from `transformers`
def create_and_tag_model_card(repo_id: str, token: Optional[str] = None, is_pipeline=False):
"""
Creates or loads an existing model card and tags it with the `library_name`.

Args:
repo_id (`str`):
The repo_id where to look for the model card.
token (`str`, *optional*):
Authentication token, obtained with `huggingface_hub.HfApi.login` method. Will default to the stored token.
is_pipeline (`bool`, *optional*):
Boolean to indicate if we're adding tag to a [`DiffusionPipeline`].
"""
try:
# Check if the model card is present on the remote repo
model_card = ModelCard.load(repo_id, token=token, ignore_metadata_errors=False)
except EntryNotFoundError:
# Otherwise create a simple model card from template
component = "pipeline" if is_pipeline else "model"
model_description = f"This is the model card of a 🧨 diffusers {component} that has been pushed on the Hub. This model card has been automatically generated."
card_data = ModelCardData(library_name="diffusers")
model_card = ModelCard.from_template(card_data, model_description=model_description)

return model_card


def extract_commit_hash(resolved_file: Optional[str], commit_hash: Optional[str] = None):
"""
Extracts the commit hash from a resolved filename toward a cache file.
@@ -435,6 +461,9 @@ def push_to_hub(
"""
repo_id = create_repo(repo_id, private=private, token=token, exist_ok=True).repo_id

# Create a new empty model card and eventually tag it
model_card = create_and_tag_model_card(repo_id, token=token)

# Save all files.
save_kwargs = {"safe_serialization": safe_serialization}
if "Scheduler" not in self.__class__.__name__:
@@ -443,6 +472,9 @@ def push_to_hub(
with tempfile.TemporaryDirectory() as tmpdir:
self.save_pretrained(tmpdir, **save_kwargs)

# Update model card if needed:
model_card.save(os.path.join(tmpdir, "README.md"))

return self._upload_folder(
tmpdir,
repo_id,
26 changes: 25 additions & 1 deletion tests/models/test_modeling_common.py
Original file line number Diff line number Diff line change
@@ -24,7 +24,8 @@
import numpy as np
import requests_mock
import torch
from huggingface_hub import delete_repo
from huggingface_hub import ModelCard, delete_repo
from huggingface_hub.utils import is_jinja_available
from requests.exceptions import HTTPError

from diffusers.models import UNet2DConditionModel
@@ -732,3 +733,26 @@ def test_push_to_hub_in_organization(self):

# Reset repo
delete_repo(self.org_repo_id, token=TOKEN)

@unittest.skipIf(
not is_jinja_available(),
reason="Model card tests cannot be performed with Jinja installed.",
)
def test_push_to_hub_library_name(self):
Copy link
Collaborator

Choose a reason for hiding this comment

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

(nit) I would add an explicit test both for when the model card doesn't exist yet and for when the model card already exists. Maybe not needed to test the full push_to_hub method but simply the create_and_tag_model_card helper (or whatever its name :) )

Copy link
Member Author

Choose a reason for hiding this comment

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

Does 99ce47c work for you?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Oh, I didn't notice that there is a generate_model_card and a create_model_card in the hub utils. Should we merge them since they seem to do closely related things? (the difference is generating for a training or generating from anywhere, right?). Naming is misleading in that case (sorry, didn't notice before when I suggest generate_model_card).

Regarding the test, yes it looks good to me :)

Copy link
Member Author

Choose a reason for hiding this comment

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

Sorry, but it won't work since tmpdir is local. What's the best way to test here?

Copy link
Member Author

Choose a reason for hiding this comment

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

@Wauplin I merged them. However, I am not sure about the test since tmpdir is local and ModelCard.load() would fail there.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think tests are good. I've added a comment below to test from existing file with existing library_name that is not diffusers.

But what I meant above is that with this PR the hub utils feels clunky. We now have:

  • create_model_card that creates a model card for a training. The method looks outdated and not used anywhere. However it introduces a template that is nice.
  • generate_model_card that either loads an existing model card or create a new one (from a different template) and add library_name: diffusers to it. This method is used in the codebase.

Maybe what I would do to solve this (and sorry if it's a revamp of the PR):

  • deprecate create_model_card (or even remove it completely) if it's not used
  • add in hub_utils.py a load_or_create_model_card helper that returns a ModelCard object without modifying anything. It's similar to the try: (ModelCard.load) except EntryNotFound: (ModelCard.from_template) part
  • add in hub_utils.py a populate_model_card that takes as input a ModelCard object and add library_name: diffusers if doesn't exist yet.
  • then in the codebase (for example in pipeline_utils.py), you would do
model_card = load_or_create_model_card(repo_id, token=token, is_pipeline=True)
populate_model_card(model_card)
model_card.save(os.path.join(save_directory, "README.md"))

WDTY?

(to take with a grain of salt, I'm not expert in diffusers codebase so I might be missing some parts)

Copy link
Collaborator

Choose a reason for hiding this comment

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

(sorry @sayakpaul I didn't see your comment while posting this message)

Copy link
Member Author

Choose a reason for hiding this comment

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

Makes sense. The latest commit should be have reflected these. Let me know if that makes sense.

I have opted to remove create_model_card() and the related test as it's not really used.

model = UNet2DConditionModel(
block_out_channels=(32, 64),
layers_per_block=2,
sample_size=32,
in_channels=4,
out_channels=4,
down_block_types=("DownBlock2D", "CrossAttnDownBlock2D"),
up_block_types=("CrossAttnUpBlock2D", "UpBlock2D"),
cross_attention_dim=32,
)
model.push_to_hub(self.repo_id, token=TOKEN)

model_card = ModelCard.load(f"{USER}/{self.repo_id}", token=TOKEN).data
assert model_card.library_name == "diffusers"

# Reset repo
delete_repo(self.repo_id, token=TOKEN)
18 changes: 17 additions & 1 deletion tests/pipelines/test_pipelines_common.py
Original file line number Diff line number Diff line change
@@ -13,7 +13,8 @@
import numpy as np
import PIL.Image
import torch
from huggingface_hub import delete_repo
from huggingface_hub import ModelCard, delete_repo
from huggingface_hub.utils import is_jinja_available
from transformers import CLIPTextConfig, CLIPTextModel, CLIPTokenizer

import diffusers
@@ -1142,6 +1143,21 @@ def test_push_to_hub_in_organization(self):
# Reset repo
delete_repo(self.org_repo_id, token=TOKEN)

@unittest.skipIf(
not is_jinja_available(),
reason="Model card tests cannot be performed with Jinja installed.",
)
def test_push_to_hub_library_name(self):
components = self.get_pipeline_components()
pipeline = StableDiffusionPipeline(**components)
pipeline.push_to_hub(self.repo_id, token=TOKEN)

model_card = ModelCard.load(f"{USER}/{self.repo_id}", token=TOKEN).data
assert model_card.library_name == "diffusers"

# Reset repo
delete_repo(self.repo_id, token=TOKEN)


# For SDXL and its derivative pipelines (such as ControlNet), we have the text encoders
# and the tokenizers as optional components. So, we need to override the `test_save_load_optional_components()`