-
Notifications
You must be signed in to change notification settings - Fork 27.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[core
/ FEAT] Add the possibility to push custom tags using PreTrainedModel
itself
#28405
[core
/ FEAT] Add the possibility to push custom tags using PreTrainedModel
itself
#28405
Conversation
core
/ FETA] Add the possibility to push custom tags using PreTrainedModel
itselfcore
/ FEAT] Add the possibility to push custom tags using PreTrainedModel
itself
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. |
I made some modification after some offline discussion, an exmaple model pushed can be found here: https://huggingface.co/ybelkada/test-tags-model lmk wdyt @amyeroberts @ArthurZucker @Wauplin |
src/transformers/modeling_utils.py
Outdated
elif "tags" in kwargs and self._model_tags is not None: | ||
logger.warning( | ||
"You manually passed `tags` to `push_to_hub` method and the model has already some tags set, we will use the tags that you passed." | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we use both instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 on merging both lists if they exist (for flexibility)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense, done !
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for adding this! Looking good 🤗
Two comments:
- I'd have some way for the user to inspect the current tags e.g.
get_model_tags
or just using a non-private attributemodel_tags
. Otherwise I'm blindly modifying the tags without knowing the current state. - It should be possible to reset or remove tags, rather than just add them
src/transformers/utils/hub.py
Outdated
card_data = ModelCardData(language="en", tags=[]) | ||
model_card = ModelCard.from_template(card_data) | ||
|
||
if model_card is not None and tags is not None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why would model_card
be None
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah nice catch, yes it should never be None here
src/transformers/modeling_utils.py
Outdated
for tag in tags: | ||
if tag not in self._model_tags: | ||
self._model_tags.append(tag) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we want an append behaviour here - more of an overwrite. Otherwise, if I accidentally add a tag there's no way to ever remove it. I'd suggest a set_model_tags
and add_model_tags
method
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd also be in favor of switching to add_model_tags
if we are appending tags to a list.
Don't know if adding support for removing tags if really necessary (but I don't have full context).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah makes sense, I changed that method to add_tags
and added a new method set_tags
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for implementing this @younesbelkada!
I left some comments. Some might be out of scope for this PR so feel free to do as you prefer :)
src/transformers/modeling_utils.py
Outdated
for tag in tags: | ||
if tag not in self._model_tags: | ||
self._model_tags.append(tag) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd also be in favor of switching to add_model_tags
if we are appending tags to a list.
Don't know if adding support for removing tags if really necessary (but I don't have full context).
src/transformers/modeling_utils.py
Outdated
elif "tags" in kwargs and self._model_tags is not None: | ||
logger.warning( | ||
"You manually passed `tags` to `push_to_hub` method and the model has already some tags set, we will use the tags that you passed." | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 on merging both lists if they exist (for flexibility)
src/transformers/modeling_utils.py
Outdated
if "tags" not in kwargs: | ||
kwargs["tags"] = self._model_tags | ||
elif "tags" in kwargs and self._model_tags is not None: | ||
logger.warning( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(nit) I think using warnings.warn
would be more appropriate here. I usually tend to follow the "rule" from https://docs.python.org/3/howto/logging.html:
warnings.warn() in library code if the issue is avoidable and the client application should be modified to eliminate the warning
logging.warning() if there is nothing the client application can do about the situation, but the event should still be noted
Here the problem is most likely avoidable by the user.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! I think that transformers fully switched to using logger.warning
(I got told that in a PR review from @LysandreJik but I don't remember which one 😢 ) let me try to find that and link the comment here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No you can keep using warnings.warn
! see #26527
Co-authored-by: amyeroberts <[email protected]>
Co-authored-by: Lucain <[email protected]>
Co-authored-by: Lucain <[email protected]>
@amyeroberts @Wauplin I think that I have addressed most of your comments now, the API is much more flexible and less "agressive" thanks to your suggestions from transformers import AutoModelForCausalLM, TrainingArguments
from trl import SFTTrainer
from datasets import load_dataset
model_name = "HuggingFaceM4/tiny-random-LlamaForCausalLM"
model = AutoModelForCausalLM.from_pretrained(model_name)
model.add_model_tags(["tag-test"])
train_data = load_dataset("imdb", split="train")
trainer = SFTTrainer(
model=model,
args=TrainingArguments(output_dir="test-tag-already-tagged"),
train_dataset=train_data,
dataset_text_field="text"
)
trainer.push_to_hub("ybelkada/test-tag-already-tagged") The example repo is here: https://huggingface.co/ybelkada/test-tag-already-tagged cc @osanseviero as well |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good! Thanks for making the changes :)
Regarding set_model_tags
, reset_model_tags
and remove_model_tags
, I feel that their behavior might be misleading/non-explicit. For example remove_model_tags
will remove tags from the model object (AutoModelForCausalLM.model_tags
in your example) but not from the model card on the Hub. I think some users can expect model.remove_model_tags("sft")
to remove the "sft"
tag from the existing model card. Maybe it's safer not to implement those three methods to avoid questions. (Also I'm not sure they would be really used -only my 2c-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for iterating - LGTM!
Agree with @Wauplin re other methods. I realise I asked for set_model_tags
but realise wow model_tags
are public, we probably don't need it as the user can just do it themselves directly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @amyeroberts @Wauplin , this is ready for a final review, I tested it on different scenarios and added some docs with a simple code snippet, let me know wdyt !
repo_id: str, | ||
tags: Optional[List[str]] = None, | ||
token: Optional[str] = None, | ||
ignore_metadata_errors: bool = False, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Wauplin @amyeroberts fine to assume that repo_type
will always be set to "model"
in ModelCard.load()
(inspecting at that methods signature)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes definitely, repo_type="model"
is implicit when using ModelCard
.
(the parameter exists because ModelCard
inherits from the generic RepoCard
. Not a perfect design-choice but it's fine :) )
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok perfect! let's keep it like that then
token (`str`, *optional*): | ||
Authentication token, obtained with `huggingface_hub.HfApi.login` method. Will default to the stored token. | ||
ignore_metadata_errors (`str`): | ||
If True, errors while parsing the metadata section will be ignored. Some information might be lost during | ||
the process. Use it at your own risk. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I copied this from ModelCard.load_card docstring
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks cool!
Co-authored-by: Omar Sanseviero <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me! I left some minor comments but feel free to ignore.
src/transformers/modeling_utils.py
Outdated
@@ -1253,6 +1255,38 @@ def _backward_compatibility_gradient_checkpointing(self): | |||
# Remove the attribute now that is has been consumed, so it's no saved in the config. | |||
delattr(self.config, "gradient_checkpointing") | |||
|
|||
def add_model_tags(self, tags: Union[List[str], str]) -> None: | |||
r""" | |||
Add custom tags into the model that gets pushed in 🤗 Hub. Will |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(nit)
Add custom tags into the model that gets pushed in 🤗 Hub. Will | |
Add custom tags into the model that gets pushed to the Hugging Face Hub. Will |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah thanks! I think it has been partially fixed by the suggestion of Omar above :D
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let me push your fix as well
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep sorry I was already reviewing when Omar posted his comment :)
repo_id: str, | ||
tags: Optional[List[str]] = None, | ||
token: Optional[str] = None, | ||
ignore_metadata_errors: bool = False, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes definitely, repo_type="model"
is implicit when using ModelCard
.
(the parameter exists because ModelCard
inherits from the generic RepoCard
. Not a perfect design-choice but it's fine :) )
|
||
self.assertTrue(model.model_tags == new_tags) | ||
|
||
model.push_to_hub("test-dynamic-model-with-tags", token=self._token) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't it problematic to have a fixed name for the repo_id? This could lead to conflicts if several CIs are ran in parallel. Using a unique id (uuid) would avoid that.
(not an expert of transformers
's CI though. It's just that I've experienced similar problem in huggingface_hub
's CI in the past)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm good point, we do the same things for all tests above so perhaps it is fine, I will let @ydshieh comment on this if he has more insights than me
Co-authored-by: Lucain <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for iterating!
Just a final comment / question on the push_to_hub logic
src/transformers/modeling_utils.py
Outdated
if "tags" not in kwargs: | ||
kwargs["tags"] = self.model_tags | ||
elif "tags" in kwargs and self.model_tags is not None: | ||
for model_tag in self.model_tags: | ||
# merge the tags together | ||
if model_tag not in kwargs["tags"]: | ||
kwargs["tags"].append(model_tag) | ||
return super().push_to_hub(*args, **kwargs) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The current if/lse logic means:
- if
self.model_tags
isNone
andtags
aren't passed inkwargs['tags']
is set toNone
. Do we want this or notags
kwarg at all?
*- just realised I'm wrong as kwargs are passed otherwise 🙃tags
are only used ifself.model_tags
is notNone
.
Another Q
- Is it possible
kwargs["tags"]
is a string here too?
if "tags" not in kwargs: | |
kwargs["tags"] = self.model_tags | |
elif "tags" in kwargs and self.model_tags is not None: | |
for model_tag in self.model_tags: | |
# merge the tags together | |
if model_tag not in kwargs["tags"]: | |
kwargs["tags"].append(model_tag) | |
return super().push_to_hub(*args, **kwargs) | |
tags = self.model_tags if self.model_tags is not None else [] | |
for tag in kwargs.get("tags", []): | |
if tag not in tags: | |
tags.append(tag) | |
if tags: | |
kwargs["tags"] = tags | |
return super().push_to_hub(*args, **kwargs) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think your suggestion sounds great, also
Is it possible kwargs["tags"] is a string here too?
It can be strings, let me adapt a bit the logic after accepting your suggestion
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just took care of the str case in 1e3fc1e
Co-authored-by: amyeroberts <[email protected]>
Thanks for handling this @younesbelkada |
@@ -3581,6 +3581,15 @@ def create_model_card( | |||
library_name = ModelCard.load(model_card_filepath).data.get("library_name") | |||
is_peft_library = library_name == "peft" | |||
|
|||
# Append existing tags in `tags` | |||
existing_tags = ModelCard.load(model_card_filepath).data.tags |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This handles a corner case I just found out for Trainers, if you push into a repo that has already some tags, it will overwrite the existing tags with new tags. This block simply cirumvents this by instead of overwriting new tags it will append new tags from existing tags
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let me know if this is ok
ModelCard.load(model_card_filepath).data.tags
can give None
if the model card is empty
from huggingface_hub import ModelCard
ModelCard.load("ybelkada/test-empty-model-case").data.tags
>>> None
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Look OK to me - only comment is that existing_tags
might be None
, so you'll need to convert it to a list before adding tags
if ModelCard.load(model_card_filepath).data.tags
doesn't have anything set
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I pushed in 59738c6 a simple workaround, I think that way we don't need to convert it to a list as we care about that corner case only if tags
are explicilty passed + there are some existing tags already
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for adding this and iterating - looks great!
…nedModel` itself (huggingface#28405) * v1 tags * remove unneeded conversion * v2 * rm unneeded warning * add more utility methods * Update src/transformers/utils/hub.py Co-authored-by: amyeroberts <[email protected]> * Update src/transformers/utils/hub.py Co-authored-by: Lucain <[email protected]> * Update src/transformers/utils/hub.py Co-authored-by: Lucain <[email protected]> * more enhancements * oops * merge tags * clean up * revert unneeded change * add extensive docs * more docs * more kwargs * add test * oops * fix test * Update src/transformers/modeling_utils.py Co-authored-by: Omar Sanseviero <[email protected]> * Update src/transformers/utils/hub.py Co-authored-by: Lucain <[email protected]> * Update src/transformers/modeling_utils.py * Update src/transformers/trainer.py Co-authored-by: amyeroberts <[email protected]> * Update src/transformers/modeling_utils.py Co-authored-by: amyeroberts <[email protected]> * add more conditions * more logic --------- Co-authored-by: amyeroberts <[email protected]> Co-authored-by: Lucain <[email protected]> Co-authored-by: Omar Sanseviero <[email protected]>
…nedModel` itself (huggingface#28405) * v1 tags * remove unneeded conversion * v2 * rm unneeded warning * add more utility methods * Update src/transformers/utils/hub.py Co-authored-by: amyeroberts <[email protected]> * Update src/transformers/utils/hub.py Co-authored-by: Lucain <[email protected]> * Update src/transformers/utils/hub.py Co-authored-by: Lucain <[email protected]> * more enhancements * oops * merge tags * clean up * revert unneeded change * add extensive docs * more docs * more kwargs * add test * oops * fix test * Update src/transformers/modeling_utils.py Co-authored-by: Omar Sanseviero <[email protected]> * Update src/transformers/utils/hub.py Co-authored-by: Lucain <[email protected]> * Update src/transformers/modeling_utils.py * Update src/transformers/trainer.py Co-authored-by: amyeroberts <[email protected]> * Update src/transformers/modeling_utils.py Co-authored-by: amyeroberts <[email protected]> * add more conditions * more logic --------- Co-authored-by: amyeroberts <[email protected]> Co-authored-by: Lucain <[email protected]> Co-authored-by: Omar Sanseviero <[email protected]>
…nedModel` itself (huggingface#28405) * v1 tags * remove unneeded conversion * v2 * rm unneeded warning * add more utility methods * Update src/transformers/utils/hub.py Co-authored-by: amyeroberts <[email protected]> * Update src/transformers/utils/hub.py Co-authored-by: Lucain <[email protected]> * Update src/transformers/utils/hub.py Co-authored-by: Lucain <[email protected]> * more enhancements * oops * merge tags * clean up * revert unneeded change * add extensive docs * more docs * more kwargs * add test * oops * fix test * Update src/transformers/modeling_utils.py Co-authored-by: Omar Sanseviero <[email protected]> * Update src/transformers/utils/hub.py Co-authored-by: Lucain <[email protected]> * Update src/transformers/modeling_utils.py * Update src/transformers/trainer.py Co-authored-by: amyeroberts <[email protected]> * Update src/transformers/modeling_utils.py Co-authored-by: amyeroberts <[email protected]> * add more conditions * more logic --------- Co-authored-by: amyeroberts <[email protected]> Co-authored-by: Lucain <[email protected]> Co-authored-by: Omar Sanseviero <[email protected]>
What does this PR do?
From an idea we discussed internally. This PR introduces a new API to inject custom tags in the model card
From a community perspective it will make easier to push custom tags as for now it is only limited to trainers.
Below I demonstrate the simplicity of the API
cc @osanseviero @Narsil @julien-c
Note with the current design each time a user calls
push_to_hub
, it will create a model card template if no model card is present on the hub