Skip to content
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] Add hub utils #39

Merged
merged 7 commits into from Jan 30, 2023
Merged

Conversation

younesbelkada
Copy link
Collaborator

@younesbelkada younesbelkada commented Jan 25, 2023

What does this PR do?

This PR adds some enhancements on peft by being able to share trained LoRA weights and configs. With this PR the API looks like the following:

from transformers import AutoModelForCausalLM
from peft import LoraConfig, LoraModel

model_id = "facebook/opt-350m"
lora_model_id = "./temp-lora"

# Create a config
config = LoraConfig(
    r=16,
    lora_alpha=32,
    target_modules=["q_proj", "v_proj"],
    lora_dropout=0.05,
    bias="none",
)
config.save_pretrained(lora_model_id)

# Load the config
config = LoraConfig.from_pretrained(lora_model_id)
model = AutoModelForCausalLM.from_pretrained(model_id)

# Load and save the model
model = LoraModel(config, model)
# save the adapters only --> here only 6MB
model.save_pretrained(lora_model_id)


# Load from saved model
model = AutoModelForCausalLM.from_pretrained(model_id)
model = LoraModel.from_pretrained(model, lora_model_id)

This PR adds:

  • from_pretrained support for xxConfig and LoraModel
  • save_pretrained support for xxConfig and LoraModel

TODOs:

  • push_to_hub support
  • add tests
  • add nice docstrings

cc @pacman100

- from_pretrained support for config
- from_pretrained support for loramodel
- todo: tests
- todo: push_to_hub
@sayakpaul
Copy link
Member

@younesbelkada would it make more sense to retrieve the base model from the LoRA model id?

Something like so?

from huggingface_hub.repocard import RepoCard

card = RepoCard.load(lora_model_id)
base_model_id = card.data.to_dict()["base_model"]

model = AutoModelForCausalLM.from_pretrained(base_model_id)
model = LoraModel.from_pretrained(model, lora_model_id)

A full-fledged example is here: https://huggingface.co/docs/diffusers/main/en/training/lora

@younesbelkada
Copy link
Collaborator Author

Yes makes totally sense, will work on that!

- push to hub method works
- add tests
- add config super class
- add Lora support for `from_pretrained`
@younesbelkada
Copy link
Collaborator Author

younesbelkada commented Jan 26, 2023

I have now a working v1 that includes also the feature you have asked @sayakpaul so that it can be adapted / used in diffusers.
Now you can safely push / save adapters locally and / or the Hub. You can also push adapter_config easily on the Hub for all supported configurations. However, I tried to have a look if I can refactor the code to support from_pretrained & push_to_hub for all adapter models but this seemed to imply a lot of code changes that I didn't wanted to address now and prefer to let them in a follow up PR.
In terms of structure, whenever you push adapters on the Hub, it will push 3 files:

  • adapter_config.json
  • adapter_model.bin
  • README.md (to retrieve the base_model_id - maybe we should find a safer way here since it'll overrwrite previous README.md files)

The latest changes adds also nice tests, for config and adapter models

Here is an example of pushed adapter weights of facebook/opt-350m that I have pushed on the Hub: https://huggingface.co/ybelkada/test-opt-lora/tree/main and you can load within this PR with the following:

from transformers import AutoModelForCausalLM
from peft import LoraConfig, LoraModel

from huggingface_hub.repocard import RepoCard

lora_model_id = "ybelkada/test-opt-lora"
card = RepoCard.load(lora_model_id)
model_id = card.data.to_dict()["base_model"]

# Load the config & model
config = LoraConfig.from_pretrained(lora_model_id)
model = AutoModelForCausalLM.from_pretrained(model_id)

# Load the Lora model
model = LoraModel.from_pretrained(model, lora_model_id)

cc @sayakpaul @pacman100 this PR is now ready for review

@younesbelkada younesbelkada marked this pull request as ready for review January 26, 2023 11:21
@sayakpaul
Copy link
Member

sayakpaul commented Jan 26, 2023

Love it. Thank you, Younes!

@@ -72,7 +86,7 @@ def __post_init__(self):
self.peft_type = PeftType.LORA


class LoraModel(torch.nn.Module):
class LoraModel(PushToHubMixin, torch.nn.Module):
Copy link
Member

Choose a reason for hiding this comment

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

is there any specific advantage to using transformers.PushToHubMixin vs huggingface_hub's? the latter means you only have to implement a single _save_pretrained() method, but perhaps you need more flexibility

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I was not aware that the class you propose is simpler, I will have a look now and potentially replace it !

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I tried it and in both cases I needed to re-implement from_pretrained method, also it seems that push_to_hub from PushToHubMixin first clones a repo with the weights before pushing on the Hub, maybe I'll just stick on transformers.PushToHubMixin for now :D

@sayakpaul
Copy link
Member

@younesbelkada I spun up this Colab Notebook quickly to showcase LoRA (from PEFT) for image classification and wow: https://colab.research.google.com/drive/1GtKXiVyINz2rRnd5FSMh2WnTjt7nOTEn?usp=sharing ❤️

I guess after this PR is merged, we should incorporate Hub-related utilities in the Notebook. WDYT?

Thanks to @pacman100 for his guidance and help.

@younesbelkada
Copy link
Collaborator Author

Thanks a lot for sharing the notebook @sayakpaul !
Yes let's wait for this PR to be merged and we can add how to share/load lora weights on the Hub to the notebook!

@sayakpaul
Copy link
Member

Cool, I am looking forward to it. Next, I will try SegFormer.

My plan is to come up with the faithful benchmarks that users can refer to while using PEFT for computer vision to really see the benefits.

src/peft/tuners/lora.py Outdated Show resolved Hide resolved
src/peft/utils/config.py Show resolved Hide resolved
peft_type: Union[str, PeftType] = field(default=None, metadata={"help": "Peft type"})
task_type: Union[str, TaskType] = field(default=None, metadata={"help": "Task type"})
inference_mode: bool = field(default=False, metadata={"help": "Whether to use inference mode"})


@dataclass
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is required else similar error as seen in the previous comment

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thanks! In any case I added tests to test that the script you shared above will not fail

@pacman100
Copy link
Collaborator

pacman100 commented Jan 29, 2023

Hello @younesbelkada, Thanks a lot for adding hf hub utils and test 🤗. This would help the users a lot by being able to load and save peft modules to HF Hub 🚀. Few general comments:

  1. Instead of having a base model name in the README, let's add base_model_name_or_path to the PeftConfig class. So, the saved peft_config can look like this:
{
  "base_model_name_or_path": "facebook/opt-350m",
  "bias": "none",
  "enable_lora": null,
  "fan_in_fan_out": false,
  "inference_mode": false,
  "lora_alpha": 32,
  "lora_dropout": 0.05,
  "merge_weights": false,
  "peft_type": "LORA",
  "r": 16,
  "target_modules": [
    "q_proj",
    "v_proj"
  ],
  "task_type": "SEQ_CLS"
}

With this one can load the model:

# Load the config & model
config = LoraConfig.from_pretrained(lora_model_id)
model = AutoModelForCausalLM.from_pretrained(config.base_model_name_or_path)

# Load the Lora model
model = LoraModel.from_pretrained(model, lora_model_id)
  1. I have tried extending this PR to add save and load utils for all Peft model tasks covering available adapters in this PR: [Don't Merge] sample pr for changes to hub utils support #43.
    Could you please look at that, port relevant parts and make necessary updates? This would enable support for all adapters, which would be great. Below is an example of the prefix-tuning PEFT method on the seq_cls task:

Saving

# save the config and model
peft_model_id = "./temp-peft-prefix-tuning"
model.save_pretrained(peft_model_id)

Loading

# Load the config & model
from peft import PeftModel, PeftConfig
config = PeftConfig.from_pretrained(peft_model_id)
model = AutoModelForSequenceClassification.from_pretrained(config.base_model_name_or_path,  return_dict=True)

# Load the Lora model
model = PeftModel.from_pretrained(model, peft_model_id)
  1. Could you please run make style and make quality to fix code style and quality?

Left a few other suggestions and comments.

younesbelkada and others added 3 commits January 29, 2023 11:41
Co-authored-by: Sourab Mangrulkar <13534540+pacman100@users.noreply.github.com>
- remove `README`
- inherit from `dataclass`
- add new test
@younesbelkada
Copy link
Collaborator Author

Thanks a lot for the feedback @pacman100 !
I should have addressed your comments now :D
However regarding tests, they are failing for other peft methods since I am not aware of the canonical way of loading a PeftModel using these classes. Let me know what I can do next!

@pacman100
Copy link
Collaborator

Hello @younesbelkada, Thank You for reiterating! 🤗

Wrt tests for remaining peft methods, I will address them in a follow up PR. This PR is good to go 🚀.

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.

None yet

4 participants