chore: import upstream snapshot with attribution
Build documentation / build (push) Failing after 0s
Deploy "method_comparison" Gradio to Spaces / deploy (push) Has been cancelled
Deploy "PEFT shop" Gradio app to Spaces / deploy (push) Has been cancelled
tests on transformers main / tests (push) Has been cancelled
tests / check_code_quality (push) Has been cancelled
tests / tests (ubuntu-latest, 3.10) (push) Has been cancelled
tests / tests (ubuntu-latest, 3.11) (push) Has been cancelled
tests / tests (ubuntu-latest, 3.12) (push) Has been cancelled
tests / tests (ubuntu-latest, 3.13) (push) Has been cancelled
tests / tests (windows-latest, 3.10) (push) Has been cancelled
tests / tests (windows-latest, 3.11) (push) Has been cancelled
tests / tests (windows-latest, 3.12) (push) Has been cancelled
tests / tests (windows-latest, 3.13) (push) Has been cancelled
Secret Leaks / trufflehog (push) Has been cancelled
CI security linting / zizmor latest via Cargo (push) Has been cancelled
Build documentation / build (push) Failing after 0s
Deploy "method_comparison" Gradio to Spaces / deploy (push) Has been cancelled
Deploy "PEFT shop" Gradio app to Spaces / deploy (push) Has been cancelled
tests on transformers main / tests (push) Has been cancelled
tests / check_code_quality (push) Has been cancelled
tests / tests (ubuntu-latest, 3.10) (push) Has been cancelled
tests / tests (ubuntu-latest, 3.11) (push) Has been cancelled
tests / tests (ubuntu-latest, 3.12) (push) Has been cancelled
tests / tests (ubuntu-latest, 3.13) (push) Has been cancelled
tests / tests (windows-latest, 3.10) (push) Has been cancelled
tests / tests (windows-latest, 3.11) (push) Has been cancelled
tests / tests (windows-latest, 3.12) (push) Has been cancelled
tests / tests (windows-latest, 3.13) (push) Has been cancelled
Secret Leaks / trufflehog (push) Has been cancelled
CI security linting / zizmor latest via Cargo (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,244 @@
|
||||
<!--Copyright 2024 The HuggingFace Team. All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
||||
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations under the License.
|
||||
|
||||
⚠️ Note that this file is in Markdown but contain specific syntax for our doc-builder (similar to MDX) that may not be
|
||||
rendered properly in your Markdown viewer.
|
||||
|
||||
-->
|
||||
|
||||
# PEFT checkpoint format
|
||||
|
||||
This document describes how PEFT's checkpoint files are structured and how to convert between the PEFT format and other formats.
|
||||
|
||||
## PEFT files
|
||||
|
||||
PEFT (parameter-efficient fine-tuning) methods only update a small subset of a model's parameters rather than all of them. This is nice because checkpoint files can generally be much smaller than the original model files and are easier to store and share. However, this also means that to load a PEFT model, you need to have the original model available as well.
|
||||
|
||||
When you call [`~PeftModel.save_pretrained`] on a PEFT model, the PEFT model saves three files, described below:
|
||||
|
||||
1. `adapter_model.safetensors` or `adapter_model.bin`
|
||||
|
||||
By default, the model is saved in the `safetensors` format, a secure alternative to the `bin` format, which is known to be susceptible to [security vulnerabilities](https://huggingface.co/docs/hub/security-pickle) because it uses the pickle utility under the hood. Both formats store the same `state_dict` though, and are interchangeable.
|
||||
|
||||
The `state_dict` only contains the parameters of the adapter module, not the base model. To illustrate the difference in size, a normal BERT model requires ~420MB of disk space, whereas an IA³ adapter on top of this BERT model only requires ~260KB.
|
||||
|
||||
2. `adapter_config.json`
|
||||
|
||||
The `adapter_config.json` file contains the configuration of the adapter module, which is necessary to load the model. Below is an example of an `adapter_config.json` for an IA³ adapter with standard settings applied to a BERT model:
|
||||
|
||||
```json
|
||||
{
|
||||
"auto_mapping": {
|
||||
"base_model_class": "BertModel",
|
||||
"parent_library": "transformers.models.bert.modeling_bert"
|
||||
},
|
||||
"base_model_name_or_path": "bert-base-uncased",
|
||||
"fan_in_fan_out": false,
|
||||
"feedforward_modules": [
|
||||
"output.dense"
|
||||
],
|
||||
"inference_mode": true,
|
||||
"init_ia3_weights": true,
|
||||
"modules_to_save": null,
|
||||
"peft_type": "IA3",
|
||||
"revision": null,
|
||||
"target_modules": [
|
||||
"key",
|
||||
"value",
|
||||
"output.dense"
|
||||
],
|
||||
"task_type": null
|
||||
}
|
||||
```
|
||||
|
||||
The configuration file contains:
|
||||
|
||||
- the adapter module type stored, `"peft_type": "IA3"`
|
||||
- information about the base model like `"base_model_name_or_path": "bert-base-uncased"`
|
||||
- the revision of the model (if any), `"revision": null`
|
||||
|
||||
If the base model is not a pretrained Transformers model, the latter two entries will be `null`. Other than that, the settings are all related to the specific IA³ adapter that was used to fine-tune the model.
|
||||
|
||||
3. `README.md`
|
||||
|
||||
The generated `README.md` is the model card of a PEFT model and contains a few pre-filled entries. The intent of this is to make it easier to share the model with others and to provide some basic information about the model. This file is not needed to load the model.
|
||||
|
||||
## Convert to PEFT format
|
||||
|
||||
When converting from another format to the PEFT format, we require both the `adapter_model.safetensors` (or `adapter_model.bin`) file and the `adapter_config.json` file.
|
||||
|
||||
### adapter_model
|
||||
|
||||
For the model weights, it is important to use the correct mapping from parameter name to value for PEFT to load the file. Getting this mapping right is an exercise in checking the implementation details, as there is no generally agreed upon format for PEFT adapters.
|
||||
|
||||
Fortunately, figuring out this mapping is not overly complicated for common base cases. Let's look at a concrete example, the [`LoraLayer`](https://github.com/huggingface/peft/blob/main/src/peft/tuners/lora/layer.py):
|
||||
|
||||
```python
|
||||
# showing only part of the code
|
||||
|
||||
class LoraLayer(BaseTunerLayer):
|
||||
# All names of layers that may contain (trainable) adapter weights
|
||||
adapter_layer_names = ("lora_A", "lora_B", "lora_embedding_A", "lora_embedding_B")
|
||||
# All names of other parameters that may contain adapter-related parameters
|
||||
other_param_names = ("r", "lora_alpha", "scaling", "lora_dropout")
|
||||
|
||||
def __init__(self, base_layer: nn.Module, **kwargs) -> None:
|
||||
self.base_layer = base_layer
|
||||
self.r = {}
|
||||
self.lora_alpha = {}
|
||||
self.scaling = {}
|
||||
self.lora_dropout = nn.ModuleDict({})
|
||||
self.lora_A = nn.ModuleDict({})
|
||||
self.lora_B = nn.ModuleDict({})
|
||||
# For Embedding layer
|
||||
self.lora_embedding_A = nn.ParameterDict({})
|
||||
self.lora_embedding_B = nn.ParameterDict({})
|
||||
# Mark the weight as unmerged
|
||||
self._disable_adapters = False
|
||||
self.merged_adapters = []
|
||||
self.use_dora: dict[str, bool] = {}
|
||||
self.lora_magnitude_vector: Optional[torch.nn.ParameterDict] = None # for DoRA
|
||||
self._caches: dict[str, Any] = {}
|
||||
self.kwargs = kwargs
|
||||
```
|
||||
|
||||
In the `__init__` code used by all `LoraLayer` classes in PEFT, there are a bunch of parameters used to initialize the model, but only a few are relevant for the checkpoint file: `lora_A`, `lora_B`, `lora_embedding_A`, and `lora_embedding_B`. These parameters are listed in the class attribute `adapter_layer_names` and contain the learnable parameters, so they must be included in the checkpoint file. All the other parameters, like the rank `r`, are derived from the `adapter_config.json` and must be included there (unless the default value is used).
|
||||
|
||||
Let's check the `state_dict` of a PEFT LoRA model applied to BERT. When printing the first five keys using the default LoRA settings (the remaining keys are the same, just with different layer numbers), we get:
|
||||
|
||||
- `base_model.model.encoder.layer.0.attention.self.query.lora_A.weight`
|
||||
- `base_model.model.encoder.layer.0.attention.self.query.lora_B.weight`
|
||||
- `base_model.model.encoder.layer.0.attention.self.value.lora_A.weight`
|
||||
- `base_model.model.encoder.layer.0.attention.self.value.lora_B.weight`
|
||||
- `base_model.model.encoder.layer.1.attention.self.query.lora_A.weight`
|
||||
- etc.
|
||||
|
||||
Let's break this down:
|
||||
|
||||
- By default, for BERT models, LoRA is applied to the `query` and `value` layers of the attention module. This is why you see `attention.self.query` and `attention.self.value` in the key names for each layer.
|
||||
- LoRA decomposes the weights into two low-rank matrices, `lora_A` and `lora_B`. This is where `lora_A` and `lora_B` come from in the key names.
|
||||
- These LoRA matrices are implemented as `nn.Linear` layers, so the parameters are stored in the `.weight` attribute (`lora_A.weight`, `lora_B.weight`).
|
||||
- By default, LoRA isn't applied to BERT's embedding layer, so there are _no entries_ for `lora_A_embedding` and `lora_B_embedding`.
|
||||
- The keys of the `state_dict` always start with `"base_model.model."`. The reason is that, in PEFT, we wrap the base model inside a tuner-specific model (`LoraModel` in this case), which itself is wrapped in a general PEFT model (`PeftModel`). For this reason, these two prefixes are added to the keys. When converting to the PEFT format, it is required to add these prefixes.
|
||||
|
||||
> [!TIP]
|
||||
> This last point is not true for prefix tuning techniques like prompt tuning. There, the extra embeddings are directly stored in the `state_dict` without any prefixes added to the keys.
|
||||
|
||||
When inspecting the parameter names in the loaded model, you might be surprised to find that they look a bit different, e.g. `base_model.model.encoder.layer.0.attention.self.query.lora_A.default.weight`. The difference is the *`.default`* part in the second to last segment. This part exists because PEFT generally allows the addition of multiple adapters at once (using an `nn.ModuleDict` or `nn.ParameterDict` to store them). For example, if you add another adapter called "other", the key for that adapter would be `base_model.model.encoder.layer.0.attention.self.query.lora_A.other.weight`.
|
||||
|
||||
When you call [`~PeftModel.save_pretrained`], the adapter name is stripped from the keys. The reason is that the adapter name is not an important part of the model architecture; it is just an arbitrary name. When loading the adapter, you could choose a totally different name, and the model would still work the same way. This is why the adapter name is not stored in the checkpoint file.
|
||||
|
||||
> [!TIP]
|
||||
> If you call `save_pretrained("some/path")` and the adapter name is not `"default"`, the adapter is stored in a sub-directory with the same name as the adapter. So if the name is "other", it would be stored inside of `some/path/other`.
|
||||
|
||||
In some circumstances, deciding which values to add to the checkpoint file can become a bit more complicated. For example, in PEFT, DoRA is implemented as a special case of LoRA. If you want to convert a DoRA model to PEFT, you should create a LoRA checkpoint with extra entries for DoRA. You can see this in the `__init__` of the previous `LoraLayer` code:
|
||||
|
||||
```python
|
||||
self.lora_magnitude_vector: Optional[torch.nn.ParameterDict] = None # for DoRA
|
||||
```
|
||||
|
||||
This indicates that there is an optional extra parameter per layer for DoRA.
|
||||
|
||||
### adapter_config
|
||||
|
||||
All the other information needed to load a PEFT model is contained in the `adapter_config.json` file. Let's check this file for a LoRA model applied to BERT:
|
||||
|
||||
```json
|
||||
{
|
||||
"alpha_pattern": {},
|
||||
"auto_mapping": {
|
||||
"base_model_class": "BertModel",
|
||||
"parent_library": "transformers.models.bert.modeling_bert"
|
||||
},
|
||||
"base_model_name_or_path": "bert-base-uncased",
|
||||
"bias": "none",
|
||||
"fan_in_fan_out": false,
|
||||
"inference_mode": true,
|
||||
"init_lora_weights": true,
|
||||
"layer_replication": null,
|
||||
"layers_pattern": null,
|
||||
"layers_to_transform": null,
|
||||
"loftq_config": {},
|
||||
"lora_alpha": 8,
|
||||
"lora_dropout": 0.0,
|
||||
"megatron_config": null,
|
||||
"megatron_core": "megatron.core",
|
||||
"modules_to_save": null,
|
||||
"peft_type": "LORA",
|
||||
"r": 8,
|
||||
"rank_pattern": {},
|
||||
"revision": null,
|
||||
"target_modules": [
|
||||
"query",
|
||||
"value"
|
||||
],
|
||||
"task_type": null,
|
||||
"use_dora": false,
|
||||
"use_rslora": false
|
||||
}
|
||||
```
|
||||
|
||||
This contains a lot of entries, and at first glance, it could feel overwhelming to figure out all the right values to put in there. However, most of the entries are not necessary to load the model. This is either because they use the default values and don't need to be added or because they only affect the initialization of the LoRA weights, which is irrelevant when it comes to loading the model. If you find that you don't know what a specific parameter does, e.g., `"use_rslora",` don't add it, and you should be fine. Also note that as more options are added, this file will get more entries in the future, but it should be backward compatible.
|
||||
|
||||
At the minimum, you should include the following entries:
|
||||
|
||||
```json
|
||||
{
|
||||
"target_modules": ["query", "value"],
|
||||
"peft_type": "LORA"
|
||||
}
|
||||
```
|
||||
|
||||
However, adding as many entries as possible, like the rank `r` or the `base_model_name_or_path` (if it's a Transformers model) is recommended. This information can help others understand the model better and share it more easily. To check which keys and values are expected, check out the [config.py](https://github.com/huggingface/peft/blob/main/src/peft/tuners/lora/config.py) file (as an example, this is the config file for LoRA) in the PEFT source code.
|
||||
|
||||
## Model storage
|
||||
|
||||
In some circumstances, you might want to store the whole PEFT model, including the base weights. This can be necessary if, for instance, the base model is not available to the users trying to load the PEFT model. You can merge the weights first or convert it into a Transformer model.
|
||||
|
||||
### Merge the weights
|
||||
|
||||
The most straightforward way to store the whole PEFT model is to merge the adapter weights into the base weights:
|
||||
|
||||
```python
|
||||
merged_model = model.merge_and_unload()
|
||||
merged_model.save_pretrained(...)
|
||||
```
|
||||
|
||||
There are some disadvantages to this approach, though:
|
||||
|
||||
- Once [`~LoraModel.merge_and_unload`] is called, you get a basic model without any PEFT-specific functionality. This means you can't use any of the PEFT-specific methods anymore.
|
||||
- You cannot unmerge the weights, load multiple adapters at once, disable the adapter, etc.
|
||||
- Not all PEFT methods support merging weights.
|
||||
- Some PEFT methods may generally allow merging, but not with specific settings (e.g. when using certain quantization techniques).
|
||||
- The whole model will be much larger than the PEFT model, as it will contain all the base weights as well.
|
||||
|
||||
But inference with a merged model should be a bit faster.
|
||||
|
||||
### Convert to a Transformers model
|
||||
|
||||
Another way to save the whole model, assuming the base model is a Transformers model, is to use this hacky approach to directly insert the PEFT weights into the base model and save it, which only works if you "trick" Transformers into believing the PEFT model is not a PEFT model. This only works with LoRA because other adapters are not implemented in Transformers.
|
||||
|
||||
```python
|
||||
model = ... # the PEFT model
|
||||
...
|
||||
# after you finish training the model, save it in a temporary location
|
||||
model.save_pretrained(<temp_location>)
|
||||
# now load this model directly into a transformers model, without the PEFT wrapper
|
||||
# the PEFT weights are directly injected into the base model
|
||||
model_loaded = AutoModel.from_pretrained(<temp_location>)
|
||||
# now make the loaded model believe that it is _not_ a PEFT model
|
||||
model_loaded._hf_peft_config_loaded = False
|
||||
# now when we save it, it will save the whole model
|
||||
model_loaded.save_pretrained(<final_location>)
|
||||
# or upload to Hugging Face Hub
|
||||
model_loaded.push_to_hub(<final_location>)
|
||||
```
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
<!--Copyright 2023 The HuggingFace Team. All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
||||
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations under the License.
|
||||
|
||||
⚠️ Note that this file is in Markdown but contain specific syntax for our doc-builder (similar to MDX) that may not be
|
||||
rendered properly in your Markdown viewer.
|
||||
|
||||
-->
|
||||
|
||||
# Contribute to PEFT
|
||||
|
||||
We are happy to accept contributions to PEFT. If you plan to contribute, please read this to make the process as smooth as possible.
|
||||
|
||||
## Installation
|
||||
|
||||
Follow these steps to start contributing:
|
||||
|
||||
1. Fork the [repository](https://github.com/huggingface/peft) by clicking on the 'Fork' button on the repository's page. This creates a copy of the code under your GitHub user account.
|
||||
|
||||
2. Clone your fork to your local disk, and add the base repository as a remote. The following command assumes you have your public SSH key uploaded to GitHub. See the following guide for more [information](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository).
|
||||
|
||||
```bash
|
||||
git clone git@github.com:<your Github handle>/peft.git
|
||||
cd peft
|
||||
git remote add upstream https://github.com/huggingface/peft.git
|
||||
```
|
||||
|
||||
3. Create a new branch to hold your development changes, and do this for every new PR you work on.
|
||||
|
||||
Start by synchronizing your `main` branch with the `upstream/main` branch (more details in the [GitHub Docs](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/syncing-a-fork)):
|
||||
|
||||
```bash
|
||||
git checkout main
|
||||
git fetch upstream
|
||||
git merge upstream/main
|
||||
```
|
||||
|
||||
Once your `main` branch is synchronized, create a new branch from it:
|
||||
|
||||
```bash
|
||||
git checkout -b a-descriptive-name-for-my-changes
|
||||
```
|
||||
|
||||
**Do not** work on the `main` branch.
|
||||
|
||||
4. Set up a development environment by running the following command in a conda or a virtual environment you've created for working on this library:
|
||||
|
||||
```bash
|
||||
pip install -e ".[test]"
|
||||
```
|
||||
|
||||
(If PEFT was already installed in the virtual environment, remove it with `pip uninstall peft` before reinstalling it.)
|
||||
|
||||
If you are new to creating a pull request, follow the [Creating a pull request](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request) guide by GitHub.
|
||||
|
||||
## Tests and code quality checks
|
||||
|
||||
Regardless of the contribution type (unless it’s only about the docs), you should run tests and code quality checks before creating a PR to ensure your contribution doesn’t break anything and follows the project standards.
|
||||
|
||||
We provide a Makefile to execute the necessary tests. Run the code below for the unit test:
|
||||
|
||||
```sh
|
||||
make test
|
||||
```
|
||||
|
||||
Run one of the following to either only check or check and fix code quality and style:
|
||||
|
||||
```sh
|
||||
make quality # just check
|
||||
make style # check and fix
|
||||
```
|
||||
|
||||
You can also set up [`pre-commit`](https://pre-commit.com/) to run these fixes
|
||||
automatically as Git commit hooks.
|
||||
|
||||
```bash
|
||||
$ pip install pre-commit
|
||||
$ pre-commit install
|
||||
```
|
||||
|
||||
Running all the tests can take a while, so during development it can be more efficient to only [run tests specific to your change](https://docs.pytest.org/en/6.2.x/usage.html#specifying-tests-selecting-tests), e.g. via:
|
||||
|
||||
```sh
|
||||
pytest tests/<test-file-name> -k <name-of-test>
|
||||
```
|
||||
|
||||
This should finish much quicker and allow for faster iteration.
|
||||
|
||||
If your change is specific to a hardware setting (e.g., it requires CUDA), take a look at [`tests/test_gpu_examples.py`](https://github.com/huggingface/peft/blob/1c1c7fdaa6e6abaa53939b865dee1eded82ad032/tests/test_gpu_examples.py) and [`tests/test_common_gpu.py`](https://github.com/huggingface/peft/blob/1c1c7fdaa6e6abaa53939b865dee1eded82ad032/tests/test_common_gpu.py) to see if it makes sense to add tests there. If your change could have an effect on saving and loading models, please run the tests with the `--regression` flag to trigger regression tests.
|
||||
|
||||
It can happen that while you’re working on your PR, the underlying code base changes due to other changes being merged. If that happens – especially when there is a merge conflict – please update your branch with the latest changes. This can be a merge or a rebase, and we'll squash and merge the PR once it’s ready. If possible, **avoid force pushes** to make reviews easier.
|
||||
|
||||
## PR description
|
||||
|
||||
When opening a PR, please provide a nice description of the change you're proposing. If it relates to other issues or PRs, please reference them. Providing a good description not only helps the reviewers review your code better and faster, it can also be used later (as a basis) for the commit message which helps with long term maintenance of the project.
|
||||
|
||||
If your code makes some non-trivial changes, it may also be a good idea to add comments to the code to explain those changes. For example, if you had to iterate on your implementation multiple times because the most obvious way didn’t work, it’s a good indication that a code comment is needed.
|
||||
|
||||
## Bugfixes
|
||||
|
||||
Please give a description of the circumstances that led to the bug. If there is an existing issue, please link to it (e.g., “Resolves #12345”).
|
||||
|
||||
Ideally when a bugfix is provided, it should be accompanied by a test for the bug. The test should fail with the current code and pass with the bugfix. Add a comment to the test that references the issue or PR. Without a test, it is more difficult to prevent regressions in the future.
|
||||
|
||||
## Documentation improvements
|
||||
|
||||
We are happy to have fixes for broken links and missing or unclear documentation. Taking care of examples, making sure that they are up-to-date and running fine in this fast moving environment is also highly appreciated.
|
||||
|
||||
Please refrain from sending pull requests that *only* correct typing errors as these generally create more work than they safe. Such changes are better combined with more substantial fixes (such as fixing broken links or extending/updating documentation).
|
||||
|
||||
## Add a new PEFT fine-tuning method
|
||||
|
||||
New parameter-efficient fine-tuning methods are developed all the time. If you would like to add a new and promising method to PEFT, please follow these steps.
|
||||
|
||||
1. If you're _not_ an author of the original paper, check for existing implementations and double check with the authors that they don't plan to submit a PR themselves.
|
||||
2. Start with the core integration work listed below.
|
||||
3. Check recent commits for new PEFT methods being added to take as inspiration.
|
||||
4. It can be useful to open a draft PR early once the method basically works and first tests pass, then ask for feedback.
|
||||
5. After working through reviewer feedback, ping the reviewer so that they know the PR is ready to review.
|
||||
|
||||
### Core integration of a new PEFT method
|
||||
|
||||
- [ ] Open a proposal issue on `huggingface/peft` before investing too much work.
|
||||
- [ ] Link the source of the method, usually the final paper or another stable primary reference. We want to avoid work that is still under review, as the implementation should be stable.
|
||||
- [ ] Add a new `PeftType` entry in `src/peft/utils/peft_types.py`.
|
||||
- [ ] Create a new tuner package under `src/peft/tuners/` with the files your method needs (typically: `config.py`, `model.py`, `layer.py`, and `__init__.py`).
|
||||
- [ ] Register the method in the tuner `__init__.py` with `register_peft_method(...)`.
|
||||
- [ ] Export the new config/model from `src/peft/tuners/__init__.py` and `src/peft/__init__.py`.
|
||||
- [ ] If the method needs default target modules for Transformers models, add the mapping in `src/peft/utils/constants.py`.
|
||||
- [ ] Add the method to the test matrix in `tests/test_custom_models.py` as these are the broadest and quickest tests. Check that the tests pass with `pytest tests/test_custom_models.py -k <method-name> -v`, fix failures if any.
|
||||
- [ ] Run style/quality checks with `make style` before pushing.
|
||||
- [ ] In the PR description, explain the method, link the paper, summarize tradeoffs, and list what was added.
|
||||
|
||||
### Full PR to add a new PEFT method
|
||||
|
||||
- [ ] Ensure that the configuration arguments that are specific to the method are well named and explained, don't assume that the user knows the paper inside out.
|
||||
- [ ] Follow the naming and coding conventions of PEFT.
|
||||
- [ ] Ensure that you didn't accidentally check in unrelated changes, e.g. the code formatter changing unrelated files.
|
||||
- [ ] If some implementation choices are non-trivial, document them with a code comment.
|
||||
- [ ] Complete the full test suite (`test_config.py`, `test_decoder_models.py`, etc.) by adding the PEFT method to the test matrix. Ensure that the tests pass.
|
||||
- [ ] Add docs in `docs/source/package_reference/` with a short explanation, paper link, usage snippet, and autodoc blocks. Explain the pros and cons compared to other methods like LoRA. Register that doc page in `docs/source/_toctree.yml`.
|
||||
- [ ] Add a runnable example under `examples/` (can be a copy of an existing example), with a short `README.md`.
|
||||
- [ ] Check the benchmarks in `method_comparison/` and add experiment settings for your new method. This is a good place to sanity check that the PEFT method trains as expected. Include one or two reasonable benchmark configurations (one default, one optimized for the benchmark).
|
||||
- [ ] Recommended: Add generic quantization support. Instead of having to explicitly add quantization layer types for each quantization method, support generic quantization. As an example, check how it's implemented in [BOFT](https://github.com/huggingface/peft/tree/main/src/peft/tuners/boft). Extend https://github.com/huggingface/peft/blob/main/tests/test_quantization.py by adding your PEFT method there. Ask maintainers for help if needed.
|
||||
|
||||
## Add other features
|
||||
|
||||
It is best if you first open an issue on GitHub with a proposal to add the new feature. This way, you can discuss with the maintainers if it makes sense to add the feature before spending too much time on implementing it.
|
||||
|
||||
New features should generally be accompanied by tests and documentation or examples. Without the latter, users will have a hard time discovering your cool new feature.
|
||||
|
||||
Changes to the code should be implemented in a backward-compatible way. For example, existing code should continue to work the same way after the feature is merged.
|
||||
@@ -0,0 +1,304 @@
|
||||
<!--Copyright 2023 The HuggingFace Team. All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
||||
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations under the License.
|
||||
|
||||
⚠️ Note that this file is in Markdown but contain specific syntax for our doc-builder (similar to MDX) that may not be
|
||||
rendered properly in your Markdown viewer.
|
||||
|
||||
-->
|
||||
|
||||
# Custom models
|
||||
|
||||
Some fine-tuning techniques, such as prompt tuning, are specific to language models. That means in 🤗 PEFT, it is
|
||||
assumed a 🤗 Transformers model is being used. However, other fine-tuning techniques - like
|
||||
[LoRA](../package_reference/lora) - are not restricted to specific model types.
|
||||
|
||||
In this guide, we will see how LoRA can be applied to a multilayer perceptron, a computer vision model from the [timm](https://huggingface.co/docs/timm/index) library, or a new 🤗 Transformers architecture.
|
||||
|
||||
## Multilayer perceptron
|
||||
|
||||
Let's assume that we want to fine-tune a multilayer perceptron with LoRA. Here is the definition:
|
||||
|
||||
```python
|
||||
from torch import nn
|
||||
|
||||
|
||||
class MLP(nn.Module):
|
||||
def __init__(self, num_units_hidden=2000):
|
||||
super().__init__()
|
||||
self.seq = nn.Sequential(
|
||||
nn.Linear(20, num_units_hidden),
|
||||
nn.ReLU(),
|
||||
nn.Linear(num_units_hidden, num_units_hidden),
|
||||
nn.ReLU(),
|
||||
nn.Linear(num_units_hidden, 2),
|
||||
nn.LogSoftmax(dim=-1),
|
||||
)
|
||||
|
||||
def forward(self, X):
|
||||
return self.seq(X)
|
||||
```
|
||||
|
||||
This is a straightforward multilayer perceptron with an input layer, a hidden layer, and an output layer.
|
||||
|
||||
> [!TIP]
|
||||
> For this toy example, we choose an exceedingly large number of hidden units to highlight the efficiency gains
|
||||
> from PEFT, but those gains are in line with more realistic examples.
|
||||
|
||||
There are a few linear layers in this model that could be tuned with LoRA. When working with common 🤗 Transformers
|
||||
models, PEFT will know which layers to apply LoRA to, but in this case, it is up to us as a user to choose the layers.
|
||||
To determine the names of the layers to tune:
|
||||
|
||||
```python
|
||||
print([(n, type(m)) for n, m in MLP().named_modules()])
|
||||
```
|
||||
|
||||
This should print:
|
||||
|
||||
```
|
||||
[('', __main__.MLP),
|
||||
('seq', torch.nn.modules.container.Sequential),
|
||||
('seq.0', torch.nn.modules.linear.Linear),
|
||||
('seq.1', torch.nn.modules.activation.ReLU),
|
||||
('seq.2', torch.nn.modules.linear.Linear),
|
||||
('seq.3', torch.nn.modules.activation.ReLU),
|
||||
('seq.4', torch.nn.modules.linear.Linear),
|
||||
('seq.5', torch.nn.modules.activation.LogSoftmax)]
|
||||
```
|
||||
|
||||
Let's say we want to apply LoRA to the input layer and to the hidden layer, those are `'seq.0'` and `'seq.2'`. Moreover,
|
||||
let's assume we want to update the output layer without LoRA, that would be `'seq.4'`. The corresponding config would
|
||||
be:
|
||||
|
||||
```python
|
||||
from peft import LoraConfig
|
||||
|
||||
config = LoraConfig(
|
||||
target_modules=["seq.0", "seq.2"],
|
||||
modules_to_save=["seq.4"],
|
||||
)
|
||||
```
|
||||
|
||||
With that, we can create our PEFT model and check the fraction of parameters trained:
|
||||
|
||||
```python
|
||||
from peft import get_peft_model
|
||||
|
||||
model = MLP()
|
||||
peft_model = get_peft_model(model, config)
|
||||
peft_model.print_trainable_parameters()
|
||||
# prints trainable params: 56,164 || all params: 4,100,164 || trainable%: 1.369798866581922
|
||||
```
|
||||
|
||||
Finally, we can use any training framework we like, or write our own fit loop, to train the `peft_model`.
|
||||
|
||||
For a complete example, check out [this notebook](https://github.com/huggingface/peft/blob/main/examples/multilayer_perceptron/multilayer_perceptron_lora.ipynb).
|
||||
|
||||
## timm models
|
||||
|
||||
The [timm](https://huggingface.co/docs/timm/index) library contains a large number of pretrained computer vision models.
|
||||
Those can also be fine-tuned with PEFT. Let's check out how this works in practice.
|
||||
|
||||
To start, ensure that timm is installed in the Python environment:
|
||||
|
||||
```bash
|
||||
python -m pip install -U timm
|
||||
```
|
||||
|
||||
Next we load a timm model for an image classification task:
|
||||
|
||||
```python
|
||||
import timm
|
||||
|
||||
num_classes = ...
|
||||
model_id = "timm/poolformer_m36.sail_in1k"
|
||||
model = timm.create_model(model_id, pretrained=True, num_classes=num_classes)
|
||||
```
|
||||
|
||||
Again, we need to make a decision about what layers to apply LoRA to. Since LoRA supports 2D conv layers, and since
|
||||
those are a major building block of this model, we should apply LoRA to the 2D conv layers. To identify the names of
|
||||
those layers, let's look at all the layer names:
|
||||
|
||||
```python
|
||||
print([(n, type(m)) for n, m in model.named_modules()])
|
||||
```
|
||||
|
||||
This will print a very long list, we'll only show the first few:
|
||||
|
||||
```
|
||||
[('', timm.models.metaformer.MetaFormer),
|
||||
('stem', timm.models.metaformer.Stem),
|
||||
('stem.conv', torch.nn.modules.conv.Conv2d),
|
||||
('stem.norm', torch.nn.modules.linear.Identity),
|
||||
('stages', torch.nn.modules.container.Sequential),
|
||||
('stages.0', timm.models.metaformer.MetaFormerStage),
|
||||
('stages.0.downsample', torch.nn.modules.linear.Identity),
|
||||
('stages.0.blocks', torch.nn.modules.container.Sequential),
|
||||
('stages.0.blocks.0', timm.models.metaformer.MetaFormerBlock),
|
||||
('stages.0.blocks.0.norm1', timm.layers.norm.GroupNorm1),
|
||||
('stages.0.blocks.0.token_mixer', timm.models.metaformer.Pooling),
|
||||
('stages.0.blocks.0.token_mixer.pool', torch.nn.modules.pooling.AvgPool2d),
|
||||
('stages.0.blocks.0.drop_path1', torch.nn.modules.linear.Identity),
|
||||
('stages.0.blocks.0.layer_scale1', timm.models.metaformer.Scale),
|
||||
('stages.0.blocks.0.res_scale1', torch.nn.modules.linear.Identity),
|
||||
('stages.0.blocks.0.norm2', timm.layers.norm.GroupNorm1),
|
||||
('stages.0.blocks.0.mlp', timm.layers.mlp.Mlp),
|
||||
('stages.0.blocks.0.mlp.fc1', torch.nn.modules.conv.Conv2d),
|
||||
('stages.0.blocks.0.mlp.act', torch.nn.modules.activation.GELU),
|
||||
('stages.0.blocks.0.mlp.drop1', torch.nn.modules.dropout.Dropout),
|
||||
('stages.0.blocks.0.mlp.norm', torch.nn.modules.linear.Identity),
|
||||
('stages.0.blocks.0.mlp.fc2', torch.nn.modules.conv.Conv2d),
|
||||
('stages.0.blocks.0.mlp.drop2', torch.nn.modules.dropout.Dropout),
|
||||
('stages.0.blocks.0.drop_path2', torch.nn.modules.linear.Identity),
|
||||
('stages.0.blocks.0.layer_scale2', timm.models.metaformer.Scale),
|
||||
('stages.0.blocks.0.res_scale2', torch.nn.modules.linear.Identity),
|
||||
('stages.0.blocks.1', timm.models.metaformer.MetaFormerBlock),
|
||||
('stages.0.blocks.1.norm1', timm.layers.norm.GroupNorm1),
|
||||
('stages.0.blocks.1.token_mixer', timm.models.metaformer.Pooling),
|
||||
('stages.0.blocks.1.token_mixer.pool', torch.nn.modules.pooling.AvgPool2d),
|
||||
...
|
||||
('head.global_pool.flatten', torch.nn.modules.linear.Identity),
|
||||
('head.norm', timm.layers.norm.LayerNorm2d),
|
||||
('head.flatten', torch.nn.modules.flatten.Flatten),
|
||||
('head.drop', torch.nn.modules.linear.Identity),
|
||||
('head.fc', torch.nn.modules.linear.Linear)]
|
||||
]
|
||||
```
|
||||
|
||||
Upon closer inspection, we see that the 2D conv layers have names such as `"stages.0.blocks.0.mlp.fc1"` and
|
||||
`"stages.0.blocks.0.mlp.fc2"`. How can we match those layer names specifically? You can write a [regular
|
||||
expressions](https://docs.python.org/3/library/re.html) to match the layer names. For our case, the regex
|
||||
`r".*\.mlp\.fc\d"` should do the job.
|
||||
|
||||
Furthermore, as in the first example, we should ensure that the output layer, in this case the classification head, is
|
||||
also updated. Looking at the end of the list printed above, we can see that it's named `'head.fc'`. With that in mind,
|
||||
here is our LoRA config:
|
||||
|
||||
```python
|
||||
config = LoraConfig(target_modules=r".*\.mlp\.fc\d", modules_to_save=["head.fc"])
|
||||
```
|
||||
|
||||
Then we only need to create the PEFT model by passing our base model and the config to `get_peft_model`:
|
||||
|
||||
```python
|
||||
peft_model = get_peft_model(model, config)
|
||||
peft_model.print_trainable_parameters()
|
||||
# prints trainable params: 1,064,454 || all params: 56,467,974 || trainable%: 1.88505789139876
|
||||
```
|
||||
|
||||
This shows us that we only need to train less than 2% of all parameters, which is a huge efficiency gain.
|
||||
|
||||
For a complete example, check out [this notebook](https://github.com/huggingface/peft/blob/main/examples/image_classification/image_classification_timm_peft_lora.ipynb).
|
||||
|
||||
## New transformers architectures
|
||||
|
||||
When new popular transformers architectures are released, we do our best to quickly add them to PEFT. If you come across a transformers model that is not supported out of the box, don't worry, it will most likely still work if the config is set correctly. Specifically, you have to identify the layers that should be adapted and set them correctly when initializing the corresponding config class, e.g. `LoraConfig`. Here are some tips to help with this.
|
||||
|
||||
As a first step, it is a good idea to check the existing models for inspiration. You can find them inside of [constants.py](https://github.com/huggingface/peft/blob/main/src/peft/utils/constants.py) in the PEFT repository. Often, you'll find a similar architecture that uses the same names. For example, if the new model architecture is a variation of the "mistral" model and you want to apply LoRA, you can see that the entry for "mistral" in `TRANSFORMERS_MODELS_TO_LORA_TARGET_MODULES_MAPPING` contains `["q_proj", "v_proj"]`. This tells you that for "mistral" models, the `target_modules` for LoRA should be `["q_proj", "v_proj"]`:
|
||||
|
||||
```python
|
||||
from peft import LoraConfig, get_peft_model
|
||||
|
||||
my_mistral_model = ...
|
||||
config = LoraConfig(
|
||||
target_modules=["q_proj", "v_proj"],
|
||||
..., # other LoRA arguments
|
||||
)
|
||||
peft_model = get_peft_model(my_mistral_model, config)
|
||||
```
|
||||
|
||||
If that doesn't help, check the existing modules in your model architecture with the `named_modules` method and try to identify the attention layers, especially the key, query, and value layers. Those will often have names such as `c_attn`, `query`, `q_proj`, etc. The key layer is not always adapted, and ideally, you should check whether including it results in better performance.
|
||||
|
||||
Additionally, linear layers are common targets to be adapted (e.g. in [QLoRA paper](https://huggingface.co/papers/2305.14314), authors suggest to adapt them as well). Their names will often contain the strings `fc` or `dense`.
|
||||
|
||||
If you want to add a new model to PEFT, please create an entry in [constants.py](https://github.com/huggingface/peft/blob/main/src/peft/utils/constants.py) and open a pull request on the [repository](https://github.com/huggingface/peft/pulls). Don't forget to update the [README](https://github.com/huggingface/peft#models-support-matrix) as well.
|
||||
|
||||
## Verify parameters and layers
|
||||
|
||||
You can verify whether you've correctly applied a PEFT method to your model in a few ways.
|
||||
|
||||
* Check the fraction of parameters that are trainable with the [`~PeftModel.print_trainable_parameters`] method. If this number is lower or higher than expected, check the model `repr` by printing the model. This shows the names of all the layer types in the model. Ensure that only the intended target layers are replaced by the adapter layers. For example, if LoRA is applied to `nn.Linear` layers, then you should only see `lora.Linear` layers being used.
|
||||
|
||||
```py
|
||||
peft_model.print_trainable_parameters()
|
||||
```
|
||||
|
||||
* Another way you can view the adapted layers is to use the `targeted_module_names` attribute to list the name of each module that was adapted.
|
||||
|
||||
```python
|
||||
print(peft_model.targeted_module_names)
|
||||
```
|
||||
|
||||
## Unsupported module types
|
||||
|
||||
Methods like LoRA only work if the target modules are supported by PEFT. For example, it's possible to apply LoRA to `nn.Linear` and `nn.Conv2d` layers, but not, for instance, to `nn.LSTM`. If you find a layer class you want to apply PEFT to is not supported, you can:
|
||||
|
||||
- define a custom mapping to dynamically dispatch custom modules in LoRA
|
||||
- open an [issue](https://github.com/huggingface/peft/issues) and request the feature where maintainers will implement it or guide you on how to implement it yourself if demand for this module type is sufficiently high
|
||||
|
||||
### Experimental support for dynamic dispatch of custom modules in LoRA
|
||||
|
||||
> [!WARNING]
|
||||
> This feature is experimental and subject to change, depending on its reception by the community. We will introduce a public and stable API if there is significant demand for it.
|
||||
|
||||
PEFT supports an experimental API for custom module types for LoRA. Let's assume you have a LoRA implementation for LSTMs. Normally, you would not be able to tell PEFT to use it, even if it would theoretically work with PEFT. However, this is possible with dynamic dispatch of custom layers.
|
||||
|
||||
The experimental API currently looks like this:
|
||||
|
||||
```python
|
||||
class MyLoraLSTMLayer:
|
||||
...
|
||||
|
||||
base_model = ... # load the base model that uses LSTMs
|
||||
|
||||
# add the LSTM layer names to target_modules
|
||||
config = LoraConfig(..., target_modules=["lstm"])
|
||||
# define a mapping from base layer type to LoRA layer type
|
||||
custom_module_mapping = {nn.LSTM: MyLoraLSTMLayer}
|
||||
# register the new mapping
|
||||
config._register_custom_module(custom_module_mapping)
|
||||
# after registration, create the PEFT model
|
||||
peft_model = get_peft_model(base_model, config)
|
||||
# do training
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
> When you call [`get_peft_model`], you will see a warning because PEFT does not recognize the targeted module type. In this case, you can ignore this warning.
|
||||
|
||||
By supplying a custom mapping, PEFT first checks the base model's layers against the custom mapping and dispatches to the custom LoRA layer type if there is a match. If there is no match, PEFT checks the built-in LoRA layer types for a match.
|
||||
|
||||
Therefore, this feature can also be used to override existing dispatch logic, e.g. if you want to use your own LoRA layer for `nn.Linear` instead of using the one provided by PEFT.
|
||||
|
||||
When creating your custom LoRA module, please follow the same rules as the [existing LoRA modules](https://github.com/huggingface/peft/blob/main/src/peft/tuners/lora/layer.py). Some important constraints to consider:
|
||||
|
||||
- The custom module should inherit from `nn.Module` and `peft.tuners.lora.layer.LoraLayer`.
|
||||
- The `__init__` method of the custom module should have the positional arguments `base_layer` and `adapter_name`. After this, there are additional `**kwargs` that you are free to use or ignore.
|
||||
- The learnable parameters should be stored in an `nn.ModuleDict` or `nn.ParameterDict`, where the key corresponds to the name of the specific adapter (remember that a model can have more than one adapter at a time).
|
||||
- The name of these learnable parameter attributes should start with `"lora_"`, e.g. `self.lora_new_param = ...`.
|
||||
- Some methods are optional, e.g. you only need to implement `merge` and `unmerge` if you want to support weight merging.
|
||||
|
||||
Currently, the information about the custom module does not persist when you save the model. When loading the model, you have to register the custom modules again.
|
||||
|
||||
```python
|
||||
# saving works as always and includes the parameters of the custom modules
|
||||
peft_model.save_pretrained(<model-path>)
|
||||
|
||||
# loading the model later:
|
||||
base_model = ...
|
||||
# load the LoRA config that you saved earlier
|
||||
config = LoraConfig.from_pretrained(<model-path>)
|
||||
# register the custom module again, the same way as the first time
|
||||
custom_module_mapping = {nn.LSTM: MyLoraLSTMLayer}
|
||||
config._register_custom_module(custom_module_mapping)
|
||||
# pass the config instance to from_pretrained:
|
||||
peft_model = PeftModel.from_pretrained(model, tmp_path / "lora-custom-module", config=config)
|
||||
```
|
||||
|
||||
If you use this feature and find it useful, or if you encounter problems, let us know by creating an issue or a discussion on GitHub. This allows us to estimate the demand for this feature and add a public API if it is sufficiently high.
|
||||
@@ -0,0 +1,148 @@
|
||||
<!--Copyright 2023 The HuggingFace Team. All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
||||
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations under the License.
|
||||
|
||||
⚠️ Note that this file is in Markdown but contain specific syntax for our doc-builder (similar to MDX) that may not be
|
||||
rendered properly in your Markdown viewer.
|
||||
|
||||
-->
|
||||
|
||||
# Adapter injection
|
||||
|
||||
With PEFT, you can inject trainable adapters into any `torch` module which allows you to use adapter methods without relying on the modeling classes in PEFT. This works for all adapters except for those based on prompt learning (e.g. prefix tuning or p-tuning).
|
||||
|
||||
Check the table below to see when you should inject adapters.
|
||||
|
||||
| Pros | Cons |
|
||||
|---|---|
|
||||
| the model is modified inplace, keeping all the original attributes and methods | manually write the `from_pretrained` and `save_pretrained` utility functions from Hugging Face to save and load adapters |
|
||||
| works for any `torch` module and modality | doesn't work with any of the utility methods provided by `PeftModel` such as disabling and merging adapters |
|
||||
|
||||
## Creating a new PEFT model
|
||||
|
||||
To perform the adapter injection, use the [`inject_adapter_in_model`] method. This method takes 3 arguments, the PEFT config, the model, and an optional adapter name. You can also attach multiple adapters to the model if you call [`inject_adapter_in_model`] multiple times with different adapter names.
|
||||
|
||||
For example, to inject LoRA adapters into the `linear` submodule of the `DummyModel` module:
|
||||
|
||||
```python
|
||||
import torch
|
||||
from peft import inject_adapter_in_model, LoraConfig
|
||||
|
||||
class DummyModel(torch.nn.Module):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.embedding = torch.nn.Embedding(10, 10)
|
||||
self.linear = torch.nn.Linear(10, 10)
|
||||
self.lm_head = torch.nn.Linear(10, 10)
|
||||
|
||||
def forward(self, input_ids):
|
||||
x = self.embedding(input_ids)
|
||||
x = self.linear(x)
|
||||
x = self.lm_head(x)
|
||||
return x
|
||||
|
||||
|
||||
lora_config = LoraConfig(
|
||||
lora_alpha=16,
|
||||
lora_dropout=0.1,
|
||||
r=64,
|
||||
bias="none",
|
||||
target_modules=["linear"],
|
||||
)
|
||||
|
||||
model = DummyModel()
|
||||
model = inject_adapter_in_model(lora_config, model)
|
||||
|
||||
dummy_inputs = torch.LongTensor([[0, 1, 2, 3, 4, 5, 6, 7]])
|
||||
dummy_outputs = model(dummy_inputs)
|
||||
```
|
||||
|
||||
Print the model to see that the adapters have been correctly injected.
|
||||
|
||||
```bash
|
||||
DummyModel(
|
||||
(embedding): Embedding(10, 10)
|
||||
(linear): Linear(
|
||||
in_features=10, out_features=10, bias=True
|
||||
(lora_dropout): ModuleDict(
|
||||
(default): Dropout(p=0.1, inplace=False)
|
||||
)
|
||||
(lora_A): ModuleDict(
|
||||
(default): Linear(in_features=10, out_features=64, bias=False)
|
||||
)
|
||||
(lora_B): ModuleDict(
|
||||
(default): Linear(in_features=64, out_features=10, bias=False)
|
||||
)
|
||||
(lora_embedding_A): ParameterDict()
|
||||
(lora_embedding_B): ParameterDict()
|
||||
)
|
||||
(lm_head): Linear(in_features=10, out_features=10, bias=True)
|
||||
)
|
||||
```
|
||||
|
||||
### Injection based on a `state_dict`
|
||||
|
||||
Sometimes, it is possible that there is a PEFT adapter checkpoint but the corresponding PEFT config is not known for whatever reason. To inject the PEFT layers for this checkpoint, you would usually have to reverse-engineer the corresponding PEFT config, most notably the `target_modules` argument, based on the `state_dict` from the checkpoint. This can be cumbersome and error prone. To avoid this, it is also possible to call [`inject_adapter_in_model`] and pass the loaded `state_dict` as an argument:
|
||||
|
||||
```python
|
||||
from safetensors.torch import load_file
|
||||
|
||||
model = ...
|
||||
state_dict = load_file(<path-to-safetensors-file>)
|
||||
lora_config = LoraConfig(...)
|
||||
model = inject_adapter_in_model(lora_config, model, state_dict=state_dict)
|
||||
```
|
||||
|
||||
In this case, PEFT will use the `state_dict` as reference for which layers to target instead of using the PEFT config. As a user, you don't have to set the exact `target_modules` of the PEFT config for this to work. However, you should still pass a PEFT config of the right type, in this example `LoraConfig`, you can leave the `target_modules` as `None`.
|
||||
|
||||
Be aware that this still only creates the uninitialized PEFT layers, the values from the `state_dict` are not used to populate the model weights. To populate the weights, proceed with calling [`set_peft_model_state_dict`] as described below.
|
||||
|
||||
⚠️ Note that if there is a mismatch between what is configured in the PEFT config and what is found in the `state_dict`, PEFT will warn you about this. You can ignore the warning if you know that the PEFT config is not correctly specified.
|
||||
|
||||
> [!WARNING]
|
||||
> If the original PEFT adapters was using `target_parameters` instead of `target_modules`, injecting from a `state_dict` will not work correctly. In this case, it is mandatory to use the correct PEFT config for injection.
|
||||
|
||||
## Saving the model
|
||||
|
||||
To only save the adapter, use the [`get_peft_model_state_dict`] function:
|
||||
|
||||
```python
|
||||
from peft import get_peft_model_state_dict
|
||||
|
||||
peft_state_dict = get_peft_model_state_dict(model)
|
||||
print(peft_state_dict)
|
||||
```
|
||||
|
||||
Otherwise, `model.state_dict()` returns the full state dict of the model.
|
||||
|
||||
## Loading the model
|
||||
|
||||
After loading the saved `state_dict`, it can be applied using the [`set_peft_model_state_dict`] function:
|
||||
|
||||
```python
|
||||
from peft import set_peft_model_state_dict
|
||||
|
||||
model = DummyModel()
|
||||
model = inject_adapter_in_model(lora_config, model)
|
||||
outcome = set_peft_model_state_dict(model, peft_state_dict)
|
||||
# check that there were no wrong keys
|
||||
print(outcome.unexpected_keys)
|
||||
```
|
||||
|
||||
If injecting the adapter is slow or you need to load a large number of adapters, you may use an optimization that allows to create an "empty" adapter on meta device and only fills the weights with real weights when the [`set_peft_model_state_dict`] is called. To do this, pass `low_cpu_mem_usage=True` to both [`inject_adapter_in_model`] and [`set_peft_model_state_dict`].
|
||||
|
||||
```python
|
||||
model = DummyModel()
|
||||
model = inject_adapter_in_model(lora_config, model, low_cpu_mem_usage=True)
|
||||
|
||||
print(model.linear.lora_A["default"].weight.device.type == "meta") # should be True
|
||||
set_peft_model_state_dict(model, peft_state_dict, low_cpu_mem_usage=True)
|
||||
print(model.linear.lora_A["default"].weight.device.type == "cpu") # should be True
|
||||
```
|
||||
@@ -0,0 +1,63 @@
|
||||
<!--Copyright 2026-present The HuggingFace Team. All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
||||
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations under the License.
|
||||
|
||||
⚠️ Note that this file is in Markdown but contain specific syntax for our doc-builder (similar to MDX) that may not be
|
||||
rendered properly in your Markdown viewer.
|
||||
|
||||
-->
|
||||
|
||||
# Memory Efficient Training
|
||||
|
||||
🤗 PEFT makes fine-tuning parameter efficient, but not automatically memory efficient. This overview collects tips for cutting training memory and links to the detailed guides.
|
||||
|
||||
> [!TIP]
|
||||
> Always consider the basics of choosing a smaller base model, smaller batch size or shorter sequence length to lower your memory usage.
|
||||
|
||||
## Training memory overview
|
||||
|
||||
Let's dissect the distribution of training memory so we can reason about potential countermeasures. We will use a large language-model trained with the Adam optimizer as an example. When doing full fine-tuning, we will have the following positions taking up memory:
|
||||
|
||||
1. base model parameters: the memory consumption highly depends on the chosen `dtype`. The less bits per parameter (16 for float16), the less memory this will take up. A 1B model in float16 (16 bit/2 byte per parameter) will roughly take `1e9 × 2 byte = 1.863 GiB` of memory.
|
||||
2. all trainable base model parameters ×3 for gradients (1×) and Adam optimizer states (2×), therefore 5.59GB of memory
|
||||
3. memory for the intermediate activations between layers, these are hard to predict but mostly depend on the used compute dtype and sequence length / batch size.
|
||||
|
||||
A smaller base model or a using a smaller compute dtype will reduce all points while using shorter sequences or smaller batches mainly affects gradients and activation memory. Employing PEFT methods will reduce the number of trainable parameters and therefore significantly reduce both gradients and optimizer state, saving a lot of memory.
|
||||
|
||||
## Choosing the right method
|
||||
|
||||
Not every PEFT method is built equally and some formulations are easier to build in a memory efficient manner. If you are on a memory budget it makes sense to check out the [PEFT method comparison suite](https://huggingface.co/spaces/peft-internal-testing/PEFT-method-comparison) and filter for **maximum** accelerator memory usage. Average accelerator memory usage can be fairly equal across methods but not every method scales equally with activations and sequence length; some methods are more prone to memory spikes than others.
|
||||
|
||||
Consider [using trainable tokens](troubleshooting#using-trainable-tokens) when targeting large layers like language modeling heads or embedding layers to fine-tune specific tokens.
|
||||
|
||||
## Quantization
|
||||
|
||||
Quantization is one of the best ways to reduce memory consumption *of the base model* and will, depending on the employed quantization, also reduce activation memory. Since the PEFT methods will only take up a small portion of the total number of parameters, PEFT defaults to use a higher precision than the base model. This can also have the effect that adapters can mitigate some of the quality loss incurred by quantization methods. Read the [PEFT quantization guide](quantization).
|
||||
|
||||
## Compilation
|
||||
|
||||
The models we train are composed of operations like matrix multiplications, sums and assignments where each operation produces a new result and, subsequently, needs to take up memory. If those intermediate results are not needed we can fuse these operations and save up on memory. This is just one of many optimizations that `torch.compile` can do for you, so check out the [PEFT torch.compile guide](torch_compile).
|
||||
|
||||
## Gradient Checkpointing
|
||||
|
||||
You can trade memory with computation by only saving every nth gradient between layers and computing the rest on the fly. Check out the [gradient checkpointing](https://huggingface.co/docs/transformers/grad_checkpointing) documentation of Transformers to learn more.
|
||||
|
||||
> [!NOTE]
|
||||
> When not using Diffusers or Transformers you may need to implement your own gradient checkpointing logic, depending on the training framework that you are using.
|
||||
|
||||
## Chunked NLL loss
|
||||
|
||||
Using [`NLLLoss`](https://docs.pytorch.org/docs/stable/generated/torch.nn.NLLLoss.html) is very common when training language models (or classification tasks). You allocate a matrix of size `batch × sequence × vocabulary`. With particularly long sequences or vocabularies this can get expensive fast.
|
||||
|
||||
When using [TRL](https://huggingface.co/docs/trl) you can either use the [Liger kernel integration](https://huggingface.co/docs/trl/liger_kernel_integration) or use [Chunked NLLLoss](https://huggingface.co/docs/trl/v1.5.1/en/reducing_memory_usage#chunked-cross-entropy-for-reducing-peak-memory-usage). The latter will split the sequence in chunks of size 256 to keep the maximum memory consumption constant.
|
||||
|
||||

|
||||
|
||||
In case the default chunk size is not optimal for your setting, look in the [original TRL PR](https://github.com/huggingface/trl/pull/5575) for more information on how to tune the chunk size.
|
||||
@@ -0,0 +1,37 @@
|
||||
<!--Copyright 2023 The HuggingFace Team. All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
||||
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations under the License.
|
||||
-->
|
||||
|
||||
# Mixed adapter types
|
||||
|
||||
Normally, it isn't possible to mix different adapter types in 🤗 PEFT. You can create a PEFT model with two different LoRA adapters (which can have different config options), but it is not possible to combine a LoRA and LoHa adapter. With [`PeftMixedModel`] however, this works as long as the adapter types are compatible. The main purpose of allowing mixed adapter types is to combine trained adapters for inference. While it is possible to train a mixed adapter model, this has not been tested and is not recommended.
|
||||
|
||||
To load different adapter types into a PEFT model, use [`PeftMixedModel`] instead of [`PeftModel`]:
|
||||
|
||||
```py
|
||||
from peft import PeftMixedModel
|
||||
|
||||
base_model = ... # load the base model, e.g. from transformers
|
||||
# load first adapter, which will be called "default"
|
||||
peft_model = PeftMixedModel.from_pretrained(base_model, <path_to_adapter1>)
|
||||
peft_model.load_adapter(<path_to_adapter2>, adapter_name="other")
|
||||
peft_model.set_adapter(["default", "other"])
|
||||
```
|
||||
|
||||
The [`~PeftMixedModel.set_adapter`] method is necessary to activate both adapters, otherwise only the first adapter would be active. You can keep adding more adapters by calling [`~PeftModel.add_adapter`] repeatedly.
|
||||
|
||||
[`PeftMixedModel`] does not support saving and loading mixed adapters. The adapters should already be trained, and loading the model requires a script to be run each time.
|
||||
|
||||
## Tips
|
||||
|
||||
- Not all adapter types can be combined. See [`peft.tuners.mixed.COMPATIBLE_TUNER_TYPES`](https://github.com/huggingface/peft/blob/1c1c7fdaa6e6abaa53939b865dee1eded82ad032/src/peft/tuners/mixed/model.py#L35) for a list of compatible types. An error will be raised if you try to combine incompatible adapter types.
|
||||
- It is possible to mix multiple adapters of the same type which can be useful for combining adapters with very different configs.
|
||||
- If you want to combine a lot of different adapters, the most performant way to do it is to consecutively add the same adapter types. For example, add LoRA1, LoRA2, LoHa1, LoHa2 in this order, instead of LoRA1, LoHa1, LoRA2, and LoHa2. While the order can affect the output, there is no inherently *best* order, so it is best to choose the fastest one.
|
||||
@@ -0,0 +1,164 @@
|
||||
<!--Copyright 2024 The HuggingFace Team. All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
||||
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations under the License.
|
||||
|
||||
⚠️ Note that this file is in Markdown but contain specific syntax for our doc-builder (similar to MDX) that may not be
|
||||
rendered properly in your Markdown viewer.
|
||||
|
||||
-->
|
||||
|
||||
# Model merging
|
||||
|
||||
Training a model for each task can be costly, take up storage space, and the models aren't able to learn new information to improve their performance. Multitask learning can overcome some of these limitations by training a model to learn several tasks, but it is expensive to train and designing a dataset for it is challenging. *Model merging* offers a solution to these challenges by combining multiple pretrained models into one model, giving it the combined abilities of each individual model without any additional training.
|
||||
|
||||
PEFT provides several methods for merging models like a linear or SVD combination. This guide focuses on two methods that are more efficient for merging LoRA adapters by eliminating redundant parameters:
|
||||
|
||||
* [TIES](https://hf.co/papers/2306.01708) - TrIm, Elect, and Merge (TIES) is a three-step method for merging models. First, redundant parameters are trimmed, then conflicting signs are resolved into an aggregated vector, and finally the parameters whose signs are the same as the aggregate sign are averaged. This method takes into account that some values (redundant and sign disagreement) can degrade performance in the merged model.
|
||||
* [DARE](https://hf.co/papers/2311.03099) - Drop And REscale is a method that can be used to prepare for other model merging methods like TIES. It works by randomly dropping parameters according to a drop rate and rescaling the remaining parameters. This helps to reduce the number of redundant and potentially interfering parameters among multiple models.
|
||||
|
||||
Models are merged with the [`~LoraModel.add_weighted_adapter`] method, and the specific model merging method is specified in the `combination_type` parameter.
|
||||
|
||||
## Merge method
|
||||
|
||||
With TIES and DARE, merging is enabled by setting `combination_type` and `density` to a value of the weights to keep from the individual models. For example, let's merge three finetuned [TinyLlama/TinyLlama-1.1B-intermediate-step-1431k-3T](https://huggingface.co/TinyLlama/TinyLlama-1.1B-intermediate-step-1431k-3T) models: [tinyllama_lora_nobots](https://huggingface.co/smangrul/tinyllama_lora_norobots), [tinyllama_lora_sql](https://huggingface.co/smangrul/tinyllama_lora_sql), and [tinyllama_lora_adcopy](https://huggingface.co/smangrul/tinyllama_lora_adcopy).
|
||||
|
||||
<Tip warning={true}>
|
||||
|
||||
When you're attempting to merge fully trained models with TIES, you should be aware of any special tokens each model may have added to the embedding layer which are not a part of the original checkpoint's vocabulary. This may cause an issue because each model may have added a special token to the same embedding position. If this is the case, you should use the [`~transformers.PreTrainedModel.resize_token_embeddings`] method to avoid merging the special tokens at the same embedding index.
|
||||
|
||||
<br>
|
||||
|
||||
This shouldn't be an issue if you're only merging LoRA adapters trained from the same base model.
|
||||
|
||||
</Tip>
|
||||
|
||||
Load a base model and can use the [`~PeftModel.load_adapter`] method to load and assign each adapter a name:
|
||||
|
||||
```py
|
||||
from peft import PeftConfig, PeftModel
|
||||
from transformers import AutoModelForCausalLM, AutoTokenizer
|
||||
import torch
|
||||
|
||||
config = PeftConfig.from_pretrained("smangrul/tinyllama_lora_norobots")
|
||||
model = AutoModelForCausalLM.from_pretrained(config.base_model_name_or_path, load_in_4bit=True, device_map="auto").eval()
|
||||
tokenizer = AutoTokenizer.from_pretrained("smangrul/tinyllama_lora_norobots")
|
||||
|
||||
model.config.vocab_size = 32005
|
||||
model.resize_token_embeddings(32005)
|
||||
|
||||
model = PeftModel.from_pretrained(model, "smangrul/tinyllama_lora_norobots", adapter_name="norobots")
|
||||
_ = model.load_adapter("smangrul/tinyllama_lora_sql", adapter_name="sql")
|
||||
_ = model.load_adapter("smangrul/tinyllama_lora_adcopy", adapter_name="adcopy")
|
||||
```
|
||||
|
||||
Set the adapters, weights, `adapter_name`, `combination_type`, and `density` with the [`~LoraModel.add_weighted_adapter`] method.
|
||||
|
||||
<hfoptions id="merge-method">
|
||||
<hfoption id="TIES">
|
||||
|
||||
Weight values greater than `1.0` typically produce better results because they preserve the correct scale. A good default starting value for the weights is to set all values to `1.0`.
|
||||
|
||||
```py
|
||||
adapters = ["norobots", "adcopy", "sql"]
|
||||
weights = [2.0, 1.0, 1.0]
|
||||
adapter_name = "merge"
|
||||
density = 0.2
|
||||
model.add_weighted_adapter(adapters, weights, adapter_name, combination_type="ties", density=density)
|
||||
```
|
||||
|
||||
</hfoption>
|
||||
<hfoption id="DARE">
|
||||
|
||||
```py
|
||||
adapters = ["norobots", "adcopy", "sql"]
|
||||
weights = [2.0, 0.3, 0.7]
|
||||
adapter_name = "merge"
|
||||
density = 0.2
|
||||
model.add_weighted_adapter(adapters, weights, adapter_name, combination_type="dare_ties", density=density)
|
||||
```
|
||||
|
||||
</hfoption>
|
||||
</hfoptions>
|
||||
|
||||
Set the newly merged model as the active model with the [`~LoraModel.set_adapter`] method.
|
||||
|
||||
```py
|
||||
model.set_adapter("merge")
|
||||
```
|
||||
|
||||
Now you can use the merged model as an instruction-tuned model to write ad copy or SQL queries!
|
||||
|
||||
<hfoptions id="ties">
|
||||
<hfoption id="instruct">
|
||||
|
||||
```py
|
||||
device = torch.accelerator.current_accelerator().type if hasattr(torch, "accelerator") else "cuda"
|
||||
messages = [
|
||||
{"role": "user", "content": "Write an essay about Generative AI."},
|
||||
]
|
||||
text = tokenizer.apply_chat_template(messages, add_generation_prompt=True, tokenize=False)
|
||||
inputs = tokenizer(text, return_tensors="pt")
|
||||
inputs = {k: v.to(device) for k, v in inputs.items()}
|
||||
outputs = model.generate(**inputs, max_new_tokens=256, do_sample=True, top_p=0.95, temperature=0.2, repetition_penalty=1.2, eos_token_id=tokenizer.eos_token_id)
|
||||
print(tokenizer.decode(outputs[0]))
|
||||
```
|
||||
|
||||
</hfoption>
|
||||
<hfoption id="ad copy">
|
||||
|
||||
```py
|
||||
device = torch.accelerator.current_accelerator().type if hasattr(torch, "accelerator") else "cuda"
|
||||
messages = [
|
||||
{"role": "system", "content": "Create a text ad given the following product and description."},
|
||||
{"role": "user", "content": "Product: Sony PS5 PlayStation Console\nDescription: The PS5 console unleashes new gaming possibilities that you never anticipated."},
|
||||
]
|
||||
text = tokenizer.apply_chat_template(messages, add_generation_prompt=True, tokenize=False)
|
||||
inputs = tokenizer(text, return_tensors="pt")
|
||||
inputs = {k: v.to(device) for k, v in inputs.items()}
|
||||
outputs = model.generate(**inputs, max_new_tokens=128, do_sample=True, top_p=0.95, temperature=0.2, repetition_penalty=1.2, eos_token_id=tokenizer.eos_token_id)
|
||||
print(tokenizer.decode(outputs[0]))
|
||||
```
|
||||
|
||||
</hfoption>
|
||||
<hfoption id="SQL">
|
||||
|
||||
```py
|
||||
device = torch.accelerator.current_accelerator().type if hasattr(torch, "accelerator") else "cuda"
|
||||
|
||||
text = """Table: 2-11365528-2
|
||||
Columns: ['Team', 'Head Coach', 'President', 'Home Ground', 'Location']
|
||||
Natural Query: Who is the Head Coach of the team whose President is Mario Volarevic?
|
||||
SQL Query:"""
|
||||
|
||||
inputs = tokenizer(text, return_tensors="pt")
|
||||
inputs = {k: v.to(device) for k, v in inputs.items()}
|
||||
outputs = model.generate(**inputs, max_new_tokens=64, repetition_penalty=1.1, eos_token_id=tokenizer("</s>").input_ids[-1])
|
||||
print(tokenizer.decode(outputs[0]))
|
||||
```
|
||||
|
||||
</hfoption>
|
||||
</hfoptions>
|
||||
|
||||
|
||||
## Merging (IA)³ Models
|
||||
The (IA)³ models facilitate linear merging of adapters. To merge adapters in an (IA)³ model, utilize the `add_weighted_adapter` method from the `IA3Model` class. This method is analogous to the `add_weighted_adapter` method used in `LoraModel`, with the key difference being the absence of the `combination_type` parameter. For example, to merge three (IA)³ adapters into a PEFT model, you would proceed as follows:
|
||||
|
||||
```py
|
||||
adapters = ["adapter1", "adapter2", "adapter3"]
|
||||
weights = [0.4, 0.3, 0.3]
|
||||
adapter_name = "merge"
|
||||
model.add_weighted_adapter(adapters, weights, adapter_name)
|
||||
```
|
||||
|
||||
It is recommended that the weights sum to 1.0 to preserve the scale of the model. The merged model can then be set as the active model using the `set_adapter` method:
|
||||
|
||||
```py
|
||||
model.set_adapter("merge")
|
||||
```
|
||||
@@ -0,0 +1,356 @@
|
||||
<!--Copyright 2023 The HuggingFace Team. All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
||||
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations under the License.
|
||||
|
||||
⚠️ Note that this file is in Markdown but contain specific syntax for our doc-builder (similar to MDX) that may not be
|
||||
rendered properly in your Markdown viewer.
|
||||
|
||||
-->
|
||||
|
||||
# Quantization
|
||||
|
||||
Quantization represents data with fewer bits, making it a useful technique for reducing memory-usage and accelerating inference especially when it comes to large language models (LLMs). There are several ways to quantize a model including:
|
||||
|
||||
* optimizing which model weights are quantized with the [AWQ](https://hf.co/papers/2306.00978) algorithm
|
||||
* independently quantizing each row of a weight matrix with the [GPTQ](https://hf.co/papers/2210.17323) algorithm
|
||||
* quantizing to 8-bit and 4-bit precision with the [bitsandbytes](https://github.com/TimDettmers/bitsandbytes) library
|
||||
* quantizing to as low as 2-bit precision with the [AQLM](https://huggingface.co/papers/2401.06118) algorithm
|
||||
|
||||
However, after a model is quantized it isn't typically further trained for downstream tasks because training can be unstable due to the lower precision of the weights and activations. But since PEFT methods only add *extra* trainable parameters, this allows you to train a quantized model with a PEFT adapter on top! Combining quantization with PEFT can be a good strategy for training even the largest models on a single GPU. For example, [QLoRA](https://hf.co/papers/2305.14314) is a method that quantizes a model to 4-bits and then trains it with LoRA. This method allows you to finetune a 65B parameter model on a single 48GB GPU!
|
||||
|
||||
In this guide, you'll see how to quantize a model to 4-bits and train it with LoRA.
|
||||
|
||||
## Quantize a model
|
||||
|
||||
[bitsandbytes](https://github.com/TimDettmers/bitsandbytes) is a quantization library with a Transformers integration. With this integration, you can quantize a model to 8 or 4-bits and enable many other options by configuring the [`~transformers.BitsAndBytesConfig`] class. For example, you can:
|
||||
|
||||
* set `load_in_4bit=True` to quantize the model to 4-bits when you load it
|
||||
* set `bnb_4bit_quant_type="nf4"` to use a special 4-bit data type for weights initialized from a normal distribution
|
||||
* set `bnb_4bit_use_double_quant=True` to use a nested quantization scheme to quantize the already quantized weights
|
||||
* set `bnb_4bit_compute_dtype=torch.bfloat16` to use bfloat16 for faster computation
|
||||
|
||||
```py
|
||||
import torch
|
||||
from transformers import BitsAndBytesConfig
|
||||
|
||||
config = BitsAndBytesConfig(
|
||||
load_in_4bit=True,
|
||||
bnb_4bit_quant_type="nf4",
|
||||
bnb_4bit_use_double_quant=True,
|
||||
bnb_4bit_compute_dtype=torch.bfloat16,
|
||||
)
|
||||
```
|
||||
|
||||
Pass the `config` to the [`~transformers.AutoModelForCausalLM.from_pretrained`] method.
|
||||
|
||||
```py
|
||||
from transformers import AutoModelForCausalLM
|
||||
|
||||
model = AutoModelForCausalLM.from_pretrained("mistralai/Mistral-7B-v0.1", quantization_config=config)
|
||||
```
|
||||
|
||||
Next, you should call the [`~peft.utils.prepare_model_for_kbit_training`] function to preprocess the quantized model for training.
|
||||
|
||||
```py
|
||||
from peft import prepare_model_for_kbit_training
|
||||
|
||||
model = prepare_model_for_kbit_training(model)
|
||||
```
|
||||
|
||||
Now that the quantized model is ready, let's set up a configuration.
|
||||
|
||||
## LoraConfig
|
||||
|
||||
Create a [`LoraConfig`] with the following parameters (or choose your own):
|
||||
|
||||
```py
|
||||
from peft import LoraConfig
|
||||
|
||||
config = LoraConfig(
|
||||
r=16,
|
||||
lora_alpha=8,
|
||||
target_modules=["q_proj", "k_proj", "v_proj", "o_proj"],
|
||||
lora_dropout=0.05,
|
||||
bias="none",
|
||||
task_type="CAUSAL_LM"
|
||||
)
|
||||
```
|
||||
|
||||
Then use the [`get_peft_model`] function to create a [`PeftModel`] from the quantized model and configuration.
|
||||
|
||||
```py
|
||||
from peft import get_peft_model
|
||||
|
||||
model = get_peft_model(model, config)
|
||||
```
|
||||
|
||||
You're all set for training with whichever training method you prefer!
|
||||
|
||||
### LoftQ initialization
|
||||
|
||||
[LoftQ](https://hf.co/papers/2310.08659) initializes LoRA weights such that the quantization error is minimized, and it can improve performance when training quantized models. To get started, follow [these instructions](https://github.com/huggingface/peft/tree/main/examples/loftq_finetuning).
|
||||
|
||||
In general, for LoftQ to work best, it is recommended to target as many layers with LoRA as possible, since those not targeted cannot have LoftQ applied. This means that passing `LoraConfig(..., target_modules="all-linear")` will most likely give the best results. Also, you should use `nf4` as quant type in your quantization config when using 4bit quantization, i.e. `BitsAndBytesConfig(load_in_4bit=True, bnb_4bit_quant_type="nf4")`.
|
||||
|
||||
In general, for LoftQ to work best, it is recommended to target as many layers with LoRA as possible, since those not targeted cannot have LoftQ applied. This means that passing `LoraConfig(..., target_modules="all-linear")` will most likely give the best results. Also, you should use `nf4` as quant type in your quantization config when using 4bit quantization, i.e. `BitsAndBytesConfig(load_in_4bit=True, bnb_4bit_quant_type="nf4")`.
|
||||
|
||||
There are currently two intended ways of applying LoftQ:
|
||||
|
||||
1. load the non-quantized base model, apply LoftQ with your intended quantization level (e.g., `bits=4` for nf4) and
|
||||
save the resulting adapter. Then quantize the base model using, for example,
|
||||
`BitsAndBytesConfig(load_in_4bit=True, bnb_4bit_quant_type="nf4")` and load the LoftQ-initialized adapter on top
|
||||
2. load the quantized base model, initialize a PEFT model with LoRA (no loftq) and use `replace_lora_weights_loftq` to
|
||||
apply LoftQ initialization by streaming the reference weights of the non-quantized base model from the weights file
|
||||
(explained further in the next section)
|
||||
|
||||
#### Using `replace_lora_weights_loftq` for on-the-fly LoftQ application
|
||||
|
||||
An easier but more limited way to apply LoftQ initialization is to use the convenience function `replace_lora_weights_loftq`. This takes the quantized PEFT model as input and replaces the LoRA weights in-place with their LoftQ-initialized counterparts.
|
||||
|
||||
```python
|
||||
from peft import replace_lora_weights_loftq
|
||||
from transformers import BitsAndBytesConfig
|
||||
|
||||
bnb_config = BitsAndBytesConfig(load_in_4bit=True, ...)
|
||||
base_model = AutoModelForCausalLM.from_pretrained(..., quantization_config=bnb_config)
|
||||
# note: don't pass init_lora_weights="loftq" or loftq_config!
|
||||
lora_config = LoraConfig(task_type="CAUSAL_LM")
|
||||
peft_model = get_peft_model(base_model, lora_config)
|
||||
replace_lora_weights_loftq(peft_model)
|
||||
```
|
||||
|
||||
`replace_lora_weights_loftq` also allows you to pass a `callback` argument to give you more control over which layers should be modified or not, which empirically can improve the results quite a lot. To see a more elaborate example of this, check out [this notebook](https://github.com/huggingface/peft/blob/main/examples/loftq_finetuning/LoftQ_weight_replacement.ipynb).
|
||||
|
||||
`replace_lora_weights_loftq` implements only one iteration step of LoftQ. This means that only the LoRA weights are updated, instead of iteratively updating LoRA weights and quantized base model weights. This may lead to lower performance but has the advantage that we can use the original quantized weights derived from the base model, instead of having to keep an extra copy of modified quantized weights. Whether this tradeoff is worthwhile depends on the use case.
|
||||
|
||||
At the moment, `replace_lora_weights_loftq` has these additional limitations:
|
||||
|
||||
- Model files must be stored as a `safetensors` file.
|
||||
- Only bitsandbytes 4bit quantization is supported.
|
||||
|
||||
### QLoRA-style training
|
||||
|
||||
QLoRA adds trainable weights to all the linear layers in the transformer architecture. Since the attribute names for these linear layers can vary across architectures, set `target_modules` to `"all-linear"` to add LoRA to all the linear layers:
|
||||
|
||||
```py
|
||||
config = LoraConfig(target_modules="all-linear", ...)
|
||||
```
|
||||
|
||||
## GPTQ quantization
|
||||
|
||||
You can learn more about GPTQ-based `[2, 3, 4, 8]` bit quantization at [GPT-QModel](https://github.com/ModelCloud/GPTQModel) and in the Transformers [GPTQ](https://huggingface.co/docs/transformers/quantization/gptq) documentation. PEFT supports GPTQ post-training through GPT-QModel.
|
||||
|
||||
```bash
|
||||
# GPT-QModel install
|
||||
pip install "gptqmodel>=7.0.0"
|
||||
```
|
||||
|
||||
```py
|
||||
from transformers import AutoModelForCausalLM, AutoTokenizer, GPTQConfig
|
||||
|
||||
model_id = "facebook/opt-125m"
|
||||
tokenizer = AutoTokenizer.from_pretrained(model_id)
|
||||
|
||||
gptq_config = GPTQConfig(bits=4, group_size=128, dataset="wikitext2", tokenizer=tokenizer)
|
||||
|
||||
quantized_model = AutoModelForCausalLM.from_pretrained(model_id, device_map="auto", quantization_config=gptq_config)
|
||||
|
||||
# save quantized model
|
||||
quantized_model.save_pretrained("./opt-125m-gptq")
|
||||
tokenizer.save_pretrained("./opt-125m-gptq")
|
||||
```
|
||||
|
||||
Once quantized, you can post-train GPTQ models with PEFT APIs.
|
||||
|
||||
## AQLM quantization
|
||||
|
||||
Additive Quantization of Language Models ([AQLM](https://huggingface.co/papers/2401.06118)) is a Large Language Models compression method. It quantizes multiple weights together and takes advantage of interdependencies between them. AQLM represents groups of 8-16 weights as a sum of multiple vector codes. This allows it to compress models down to as low as 2-bit with considerably low accuracy losses.
|
||||
|
||||
Since the AQLM quantization process is computationally expensive, the use of prequantized models is recommended. A partial list of available models can be found in the official aqlm [repository](https://github.com/Vahe1994/AQLM).
|
||||
|
||||
The models support LoRA adapter tuning. To tune the quantized model you'll need to install the `aqlm` inference library: `pip install aqlm>=1.0.2`. Finetuned LoRA adapters shall be saved separately, as merging them with AQLM quantized weights is not possible.
|
||||
|
||||
```py
|
||||
quantized_model = AutoModelForCausalLM.from_pretrained(
|
||||
"BlackSamorez/Mixtral-8x7b-AQLM-2Bit-1x16-hf-test-dispatch",
|
||||
dtype="auto", device_map="auto", low_cpu_mem_usage=True,
|
||||
)
|
||||
|
||||
peft_config = LoraConfig(...)
|
||||
|
||||
quantized_model = get_peft_model(quantized_model, peft_config)
|
||||
```
|
||||
|
||||
You can refer to the [Google Colab](https://colab.research.google.com/drive/12GTp1FCj5_0SnnNQH18h_2XFh9vS_guX?usp=sharing) example for an overview of AQLM+LoRA finetuning.
|
||||
|
||||
## EETQ quantization
|
||||
|
||||
You can also perform LoRA fine-tuning on EETQ quantized models. [EETQ](https://github.com/NetEase-FuXi/EETQ) package offers simple and efficient way to perform 8-bit quantization, which is claimed to be faster than the `LLM.int8()` algorithm. First, make sure that you have a transformers version that is compatible with EETQ (e.g. by installing it from latest pypi or from source).
|
||||
|
||||
```py
|
||||
import torch
|
||||
from transformers import EetqConfig
|
||||
|
||||
config = EetqConfig("int8")
|
||||
```
|
||||
|
||||
Pass the `config` to the [`~transformers.AutoModelForCausalLM.from_pretrained`] method.
|
||||
|
||||
```py
|
||||
from transformers import AutoModelForCausalLM
|
||||
|
||||
model = AutoModelForCausalLM.from_pretrained("mistralai/Mistral-7B-v0.1", quantization_config=config)
|
||||
```
|
||||
|
||||
and create a `LoraConfig` and pass it to `get_peft_model`:
|
||||
|
||||
```py
|
||||
from peft import LoraConfig, get_peft_model
|
||||
|
||||
config = LoraConfig(
|
||||
r=16,
|
||||
lora_alpha=8,
|
||||
target_modules=["q_proj", "k_proj", "v_proj", "o_proj"],
|
||||
lora_dropout=0.05,
|
||||
bias="none",
|
||||
task_type="CAUSAL_LM"
|
||||
)
|
||||
|
||||
model = get_peft_model(model, config)
|
||||
```
|
||||
|
||||
## HQQ quantization
|
||||
|
||||
The models that are quantized using Half-Quadratic Quantization of Large Machine Learning Models ([HQQ](https://mobiusml.github.io/hqq_blog/)) support LoRA adapter tuning. To tune the quantized model, you'll need to install the `hqq` library with: `pip install hqq`.
|
||||
|
||||
```python
|
||||
from hqq.engine.hf import HQQModelForCausalLM
|
||||
|
||||
device = torch.accelerator.current_accelerator().type if hasattr(torch, "accelerator") else "cuda"
|
||||
|
||||
quantized_model = HQQModelForCausalLM.from_quantized(save_dir_or_hfhub, device=device)
|
||||
peft_config = LoraConfig(...)
|
||||
quantized_model = get_peft_model(quantized_model, peft_config)
|
||||
```
|
||||
|
||||
Or using transformers version that is compatible with HQQ (e.g. by installing it from latest pypi or from source).
|
||||
|
||||
```python
|
||||
from transformers import HqqConfig, AutoModelForCausalLM
|
||||
|
||||
quant_config = HqqConfig(nbits=4, group_size=64)
|
||||
quantized_model = AutoModelForCausalLM.from_pretrained(save_dir_or_hfhub, device_map=device_map, quantization_config=quant_config)
|
||||
peft_config = LoraConfig(...)
|
||||
quantized_model = get_peft_model(quantized_model, peft_config)
|
||||
```
|
||||
|
||||
## torchao (PyTorch Architecture Optimization)
|
||||
|
||||
PEFT supports models quantized with [torchao](https://github.com/pytorch/ao) ("ao") for int8 quantization.
|
||||
|
||||
```python
|
||||
from peft import LoraConfig, get_peft_model
|
||||
from transformers import AutoModelForCausalLM, TorchAoConfig
|
||||
from torchao.quantization import Int8WeightOnlyConfig
|
||||
|
||||
model_id = ...
|
||||
quantization_config = TorchAoConfig(quant_type=Int8WeightOnlyConfig())
|
||||
base_model = AutoModelForCausalLM.from_pretrained(model_id, quantization_config=quantization_config)
|
||||
peft_config = LoraConfig(...)
|
||||
model = get_peft_model(base_model, peft_config)
|
||||
```
|
||||
|
||||
### Caveats:
|
||||
|
||||
- Use the most recent versions of torchao (>= v0.4.0) and transformers (> 4.42).
|
||||
- Only linear layers are currently supported.
|
||||
- `quant_type = "int4_weight_only"` is currently not supported.
|
||||
- `NF4` is not implemented in transformers as of yet and is thus also not supported.
|
||||
- DoRA only works with `quant_type = "int8_weight_only"` at the moment.
|
||||
- There is explicit support for torchao when used with LoRA. However, when torchao quantizes a layer, its class does not change, only the type of the underlying tensor. For this reason, PEFT methods other than LoRA will generally also work with torchao, even if not explicitly supported. Be aware, however, that **merging only works correctly with LoRA and with `quant_type = "int8_weight_only"`**. If you use a different PEFT method or dtype, merging will likely result in an error, and even it doesn't, the results will still be incorrect.
|
||||
|
||||
## INC quantization
|
||||
|
||||
Intel Neural Compressor ([INC](https://github.com/intel/neural-compressor)) enables model quantization for various devices,
|
||||
including Intel Gaudi accelerators (also known as HPU devices). You can perform LoRA fine-tuning on models that have been
|
||||
quantized using INC. To use INC with PyTorch models, install the library with: `pip install neural-compressor[pt]`.
|
||||
Quantizing a model to FP8 precision for HPU devices can be done with the following single-step quantization workflow:
|
||||
|
||||
```python
|
||||
import torch
|
||||
from neural_compressor.torch.quantization import FP8Config, convert, finalize_calibration, prepare
|
||||
quant_configs = {
|
||||
...
|
||||
}
|
||||
config = FP8Config(**quant_configs)
|
||||
```
|
||||
|
||||
Pass the config to the `prepare` method, run inference to gather calibration stats, and call `finalize_calibration`
|
||||
and `convert` methods to quantize model to FP8 precision:
|
||||
|
||||
```python
|
||||
model = prepare(model, config)
|
||||
# Run inference to collect calibration statistics
|
||||
...
|
||||
# Finalize calibration and convert the model to FP8 precision
|
||||
finalize_calibration(model)
|
||||
model = convert(model)
|
||||
# Load PEFT LoRA adapter as usual
|
||||
...
|
||||
```
|
||||
|
||||
An example demonstrating how to load a PEFT LoRA adapter into an INC-quantized FLUX text-to-image model for HPU
|
||||
devices is provided [here](https://github.com/huggingface/peft/blob/main/examples/stable_diffusion/inc_flux_lora_hpu.py).
|
||||
|
||||
|
||||
### Caveats:
|
||||
|
||||
- `merge()` and `unmerge()` methods are currently not supported for INC-quantized models.
|
||||
- Currently, only **Linear** INC-quantized layers are supported when loading PEFT adapters.
|
||||
|
||||
## Other Supported PEFT Methods
|
||||
|
||||
Besides LoRA, the following PEFT methods also support quantization:
|
||||
|
||||
- **VeRA** (supports bitsandbytes quantization)
|
||||
- **AdaLoRA** (supports both bitsandbytes and GPTQ quantization)
|
||||
- **(IA)³** (supports bitsandbytes quantization)
|
||||
|
||||
## Transformer Engine (TE) LoRA
|
||||
|
||||
PEFT supports LoRA adapters on top of [NVIDIA Transformer Engine](https://docs.nvidia.com/deeplearning/transformer-engine/) layers (`te.pytorch.Linear`, `te.pytorch.LayerNormLinear`, and `te.pytorch.LayerNormMLP`). TE layers use FP8 and fused kernels to accelerate transformer training, and attaching LoRA adapters lets you parameter-efficiently fine-tune TE-accelerated models.
|
||||
|
||||
```python
|
||||
from peft import LoraConfig, get_peft_model
|
||||
|
||||
config = LoraConfig(
|
||||
r=8,
|
||||
lora_alpha=16,
|
||||
target_modules=["q_proj", "v_proj"],
|
||||
task_type="TOKEN_CLS",
|
||||
)
|
||||
model = get_peft_model(te_model, config)
|
||||
```
|
||||
|
||||
When Transformer Engine is installed, PEFT automatically dispatches matching layers to the `TeLinear` adapter — no extra configuration is needed.
|
||||
|
||||
### Caveats
|
||||
|
||||
- `merge()` and `unmerge()` are not yet supported for TE layers.
|
||||
- DoRA is not supported with TE layers.
|
||||
|
||||
A complete ESM2 token-classification example is available under `examples/lora_finetuning_transformer_engine/`.
|
||||
|
||||
## Next steps
|
||||
|
||||
If you're interested in learning more about quantization, the following may be helpful:
|
||||
|
||||
* Learn more details about QLoRA and check out some benchmarks on its impact in the [Making LLMs even more accessible with bitsandbytes, 4-bit quantization and QLoRA](https://huggingface.co/blog/4bit-transformers-bitsandbytes) blog post.
|
||||
* Read more about different quantization schemes in the Transformers [Quantization](https://hf.co/docs/transformers/main/quantization) guide.
|
||||
@@ -0,0 +1,70 @@
|
||||
<!--Copyright 2023 The HuggingFace Team. All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
||||
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations under the License.
|
||||
|
||||
⚠️ Note that this file is in Markdown but contain specific syntax for our doc-builder (similar to MDX) that may not be
|
||||
rendered properly in your Markdown viewer.
|
||||
|
||||
-->
|
||||
|
||||
# torch.compile
|
||||
|
||||
In PEFT, [torch.compile](https://pytorch.org/tutorials/intermediate/torch_compile_tutorial.html) works for some but not all features. The reason why it won't always work is because PEFT is highly dynamic in certain places (loading and switching between multiple adapters, for instance), which can cause trouble for `torch.compile`. In other places, `torch.compile` may work, but won't be as fast as expected because of graph breaks.
|
||||
|
||||
If you don't see an error, it doesn't necessarily mean that `torch.compile` worked correctly. It might give you an output, but the output is incorrect. This guide describes what works with `torch.compile` and what doesn't. For your own testing, we recommend using the latest PyTorch version, as `torch.compile` is constantly being improved.
|
||||
|
||||
> [!TIP]
|
||||
> Unless indicated otherwise, the default `torch.compile` settings were used.
|
||||
|
||||
## Training and inference with `torch.compile`
|
||||
|
||||
These features **work** with `torch.compile`. Everything listed below was tested with a causal LM:
|
||||
|
||||
- Training with `Trainer` from 🤗 transformers
|
||||
- Training with a custom PyTorch loop
|
||||
- Inference
|
||||
- Generation
|
||||
|
||||
The following adapters were tested successfully:
|
||||
|
||||
- AdaLoRA
|
||||
- BOFT
|
||||
- IA³
|
||||
- Layer Norm Tuning
|
||||
- LoHa
|
||||
- LoKr
|
||||
- LoRA
|
||||
- LoRA + DoRA
|
||||
- LoRA applied to embedding layers
|
||||
- OFT
|
||||
- VeRA
|
||||
- HRA
|
||||
|
||||
## Advanced PEFT features with `torch.compile`
|
||||
|
||||
Below are some of the more advanced PEFT features that **work**. They were all tested with LoRA.
|
||||
|
||||
- `modules_to_save` (i.e. `config = LoraConfig(..., modules_to_save=...)`)
|
||||
- Merging adapters (one or multiple)
|
||||
- Merging multiple adapters into one adapter (i.e. calling `model.add_weighted_adapter(...)`)
|
||||
- Using PEFT adapters with quantization (bitsandbytes)
|
||||
- Disabling adapters (i.e. using `with model.disable_adapter()`)
|
||||
- Unloading (i.e. calling `model.merge_and_unload()`)
|
||||
- Mixed adapter batches (i.e. calling `model(batch, adapter_names=["__base__", "default", "other", ...])`)
|
||||
- Inference with multiple adapters (i.e. using `model.add_adapter` or `model.load_adapter` to load more than 1 adapter); for this, only call `torch.compile` _after_ loading all adapters
|
||||
|
||||
Generally, we can expect that if a feature works correctly with LoRA and is also supported by other adapter types, it should also work for that adapter type.
|
||||
|
||||
## Test cases
|
||||
|
||||
All the use cases listed above are tested inside of [`peft/tests/test_torch_compile.py`](https://github.com/huggingface/peft/blob/main/tests/test_torch_compile.py). If you want to check in more detail how we tested a certain feature, please go to that file and check the test that corresponds to your use case.
|
||||
|
||||
> [!TIP]
|
||||
> If you have another use case where you know that `torch.compile` does or does not work with PEFT, please contribute by letting us know or by opening a PR to add this use case to the covered test cases.
|
||||
@@ -0,0 +1,458 @@
|
||||
<!--Copyright 2023 The HuggingFace Team. All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
||||
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations under the License.
|
||||
|
||||
⚠️ Note that this file is in Markdown but contain specific syntax for our doc-builder (similar to MDX) that may not be
|
||||
rendered properly in your Markdown viewer.
|
||||
|
||||
-->
|
||||
|
||||
# Troubleshooting
|
||||
|
||||
If you encounter any issue when using PEFT, please check the following list of common issues and their solutions.
|
||||
|
||||
## Examples don't work
|
||||
|
||||
Examples often rely on the most recent package versions, so please ensure they're up-to-date. In particular, check the following package versions:
|
||||
|
||||
- `peft`
|
||||
- `transformers`
|
||||
- `accelerate`
|
||||
- `torch`
|
||||
|
||||
In general, you can update the package version by running this command inside your Python environment:
|
||||
|
||||
```bash
|
||||
python -m pip install -U <package_name>
|
||||
```
|
||||
|
||||
Installing PEFT from source is useful for keeping up with the latest developments:
|
||||
|
||||
```bash
|
||||
python -m pip install git+https://github.com/huggingface/peft
|
||||
```
|
||||
|
||||
## Dtype-related issues
|
||||
|
||||
### ValueError: Attempting to unscale FP16 gradients
|
||||
|
||||
This error probably occurred because the model was loaded with `dtype=torch.float16` and then used in an automatic mixed precision (AMP) context, e.g. by setting `fp16=True` in the [`~transformers.Trainer`] class from 🤗 Transformers. The reason is that when using AMP, trainable weights should never use fp16. To make this work without loading the whole model in fp32, add the following to your code:
|
||||
|
||||
```python
|
||||
peft_model = get_peft_model(...)
|
||||
|
||||
# add this:
|
||||
for param in model.parameters():
|
||||
if param.requires_grad:
|
||||
param.data = param.data.float()
|
||||
|
||||
# proceed as usual
|
||||
trainer = Trainer(model=peft_model, fp16=True, ...)
|
||||
trainer.train()
|
||||
```
|
||||
|
||||
Alternatively, you can use the [`~utils.cast_mixed_precision_params`] function to correctly cast the weights:
|
||||
|
||||
```python
|
||||
from peft import cast_mixed_precision_params
|
||||
|
||||
peft_model = get_peft_model(...)
|
||||
cast_mixed_precision_params(peft_model, dtype=torch.float16)
|
||||
|
||||
# proceed as usual
|
||||
trainer = Trainer(model=peft_model, fp16=True, ...)
|
||||
trainer.train()
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
> Starting from PEFT version v0.12.0, PEFT automatically promotes the dtype of adapter weights from `torch.float16` and `torch.bfloat16` to `torch.float32` where appropriate. To _prevent_ this behavior, you can pass `autocast_adapter_dtype=False` to [`~get_peft_model`], to [`~PeftModel.from_pretrained`], and to [`~PeftModel.load_adapter`].
|
||||
|
||||
### Selecting the dtype of the adapter
|
||||
|
||||
Most PEFT methods, like LoRA, work by adding trainable adapter weights. By default, those weights are stored in float32 dtype (fp32), i.e. at a relatively high precision. Therefore, even if the base model is loaded in float16 (fp16) or bfloat16 (bf16), the adapter weights are float32. When the adapter results are calculated during the forward pass, the input will typically be in the dtype of the base model, thus it will be upcast to float32 if necessary, then cast back to the original dtype.
|
||||
|
||||
If you prefer to have the adapter weights in the lower precision of the base model, i.e. in float16 or bfloat16, you can pass `autocast_adapter_dtype=False` when creating the model ([`~get_peft_model`]) or loading the model ([`~PeftModel.from_pretrained`]). There are some advantages and disadvantages to this:
|
||||
|
||||
Advantages of half precision adapter:
|
||||
- computation slightly faster
|
||||
- slightly less memory
|
||||
- smaller file size of checkpoint (half the size)
|
||||
|
||||
Disadvantages of half precision adapter:
|
||||
- slightly worse loss
|
||||
- higher risk of overflow or underflow
|
||||
|
||||
Note that for most use cases, overall runtime and memory cost will be determined by the size of the base model and by the dataset, while the dtype of the PEFT adapter will only have a small impact.
|
||||
|
||||
## Bad results from a loaded PEFT model
|
||||
|
||||
There can be several reasons for getting a poor result from a loaded PEFT model which are listed below. If you're still unable to troubleshoot the problem, see if anyone else had a similar [issue](https://github.com/huggingface/peft/issues) on GitHub, and if you can't find any, open a new issue.
|
||||
|
||||
When opening an issue, it helps a lot if you provide a minimal code example that reproduces the issue. Also, please report if the loaded model performs at the same level as the model did before fine-tuning, if it performs at a random level, or if it is only slightly worse than expected. This information helps us identify the problem more quickly.
|
||||
|
||||
### Random deviations
|
||||
|
||||
If your model outputs are not exactly the same as previous runs, there could be an issue with random elements. For example:
|
||||
|
||||
1. please ensure it is in `.eval()` mode, which is important, for instance, if the model uses dropout
|
||||
2. if you use [`~transformers.GenerationMixin.generate`] on a language model, there could be random sampling, so obtaining the same result requires setting a random seed
|
||||
3. if you used quantization and merged the weights, small deviations are expected due to rounding errors
|
||||
|
||||
### Incorrectly loaded model
|
||||
|
||||
Please ensure that you load the model correctly. A common error is trying to load a _trained_ model with [`get_peft_model`] which is incorrect. Instead, the loading code should look like this:
|
||||
|
||||
```python
|
||||
from peft import PeftModel, PeftConfig
|
||||
|
||||
base_model = ... # to load the base model, use the same code as when you trained it
|
||||
config = PeftConfig.from_pretrained(peft_model_id)
|
||||
peft_model = PeftModel.from_pretrained(base_model, peft_model_id)
|
||||
```
|
||||
|
||||
### Randomly initialized layers
|
||||
|
||||
For some tasks, it is important to correctly configure `modules_to_save` in the config to account for randomly initialized layers.
|
||||
|
||||
As an example, this is necessary if you use LoRA to fine-tune a language model for sequence classification because 🤗 Transformers adds a randomly initialized classification head on top of the model. If you do not add this layer to `modules_to_save`, the classification head won't be saved. The next time you load the model, you'll get a _different_ randomly initialized classification head, resulting in completely different results.
|
||||
|
||||
PEFT tries to correctly guess the `modules_to_save` if you provide the `task_type` argument in the config. This should work for transformers models that follow the standard naming scheme. It is always a good idea to double check though because we can't guarantee all models follow the naming scheme.
|
||||
|
||||
When you load a transformers model that has randomly initialized layers, you should see a warning along the lines of:
|
||||
|
||||
```
|
||||
Some weights of <MODEL> were not initialized from the model checkpoint at <ID> and are newly initialized: [<LAYER_NAMES>].
|
||||
You should probably TRAIN this model on a down-stream task to be able to use it for predictions and inference.
|
||||
```
|
||||
|
||||
The mentioned layers should be added to `modules_to_save` in the config to avoid the described problem.
|
||||
|
||||
> [!TIP]
|
||||
> As an example, when loading a model that is using the DeBERTa architecture for sequence classification, you'll see a warning that the following weights are newly initialized: `['classifier.bias', 'classifier.weight', 'pooler.dense.bias', 'pooler.dense.weight']`. From this, it follows that the `classifier` and `pooler` layers should be added to: `modules_to_save=["classifier", "pooler"]`.
|
||||
|
||||
### Extending the vocabulary
|
||||
|
||||
For many language fine-tuning tasks, extending the model's vocabulary is necessary since new tokens are being introduced. This requires extending the embedding layer to account for the new tokens and, depending on the fine-tuning method, also storing the embedding layer in addition to the adapter weights when saving the adapter. There are a few ways of achieving this ordered by parameter effectiveness:
|
||||
|
||||
- [trainable tokens](../package_reference/trainable_tokens), train only the specified tokens, optionally store only the updated values
|
||||
- training an adapter on the embedding matrix, optionally store only the updated values
|
||||
- full-finetuning of the embedding layer
|
||||
|
||||
#### Using trainable tokens
|
||||
|
||||
Let's start with trainable tokens, in this case its [LoRA integration](../package_reference/lora#efficiently-train-tokens-alongside-lora). If you're interested in only training the new embeddings and nothing else, refer to the [standalone documentation](../package_reference/trainable_tokens).
|
||||
|
||||
To enable selective token training of the embedding layer, you'll need to supply the token ids of your newly added tokens via the `trainable_token_indices` parameter. Optionally you can specify which layer to target if there is more than one embedding layer. For a Mistral model this could look like this:
|
||||
|
||||
```python
|
||||
new_tokens = ['<think>', '</think>']
|
||||
tokenizer.add_tokens(new_tokens)
|
||||
base_model.resize_token_embeddings(len(tokenizer))
|
||||
|
||||
lora_config = LoraConfig(
|
||||
...,
|
||||
trainable_token_indices={'embed_tokens': tokenizer.convert_tokens_to_ids(new_tokens)},
|
||||
)
|
||||
```
|
||||
|
||||
If your model uses tied weights (such as the `lm_head`), trainable tokens will try to resolve those and keep them updated as well, so in that case there should be no need for adding `modules_to_save=["lm_head"]`. This only works if the model uses the Transformers convention for tying weights.
|
||||
|
||||
Saving the model with `model.save_pretrained` may save the full embedding matrix instead of
|
||||
only the difference as a precaution because the embedding matrix was resized. To save space you can disable this behavior by setting `save_embedding_layers=False` when calling `save_pretrained`. This is safe to do as long as you don't modify the embedding matrix through other means as well, as such changes will be not tracked by trainable tokens.
|
||||
|
||||
#### Using an adapter, e.g. LoRA
|
||||
|
||||
Prepare the embedding layer by adding it to the `target_modules` of your adapter config. For example, the Mistral config could look like this:
|
||||
|
||||
```python
|
||||
config = LoraConfig(..., target_modules=["embed_tokens", "lm_head", "q_proj", "v_proj"])
|
||||
```
|
||||
|
||||
Once added to `target_modules`, PEFT automatically stores the embedding layer when saving the adapter if the model has the [`~transformers.PreTrainedModel.get_input_embeddings`] and [`~transformers.PreTrainedModel.get_output_embeddings`]. This is generally the case for Transformers models.
|
||||
|
||||
If the model's embedding layer doesn't follow the Transformer's naming scheme but nevertheless implements `get_input_embeddings`, you can still save it by manually passing `save_embedding_layers=True` when saving the adapter:
|
||||
|
||||
```python
|
||||
model = get_peft_model(...)
|
||||
# train the model
|
||||
model.save_pretrained("my_adapter", save_embedding_layers=True)
|
||||
```
|
||||
|
||||
For inference, load the base model first and resize it the same way you did before you trained the model. After you've resized the base model, you can load the PEFT checkpoint.
|
||||
|
||||
For a complete example, please check out [this notebook](https://github.com/huggingface/peft/blob/main/examples/causal_language_modeling/peft_lora_clm_with_additional_tokens.ipynb).
|
||||
|
||||
#### Full fine-tuning
|
||||
|
||||
Full fine-tuning is more costly in terms of VRAM or storage space but if all else fails, you can fall back to this and see if it works for you. Achieve it by adding the name of the embedding layer to `modules_to_save`. Note that you need to add tied layers as well, e.g. `lm_head`. Example for a Mistral model with LoRA:
|
||||
|
||||
```python
|
||||
config = LoraConfig(..., modules_to_save=["embed_tokens", "lm_head"], target_modules=["q_proj", "v_proj"])
|
||||
```
|
||||
|
||||
### Getting a warning about "weights not being initialized from the model checkpoint"
|
||||
|
||||
When you load your PEFT model which has been trained on a task (for example, classification), you may get a warning like:
|
||||
|
||||
> Some weights of LlamaForSequenceClassification were not initialized from the model checkpoint at meta-llama/Llama-3.2-1B and are newly initialized: ['score.weight']. You should probably TRAIN this model on a down-stream task to be able to use it for predictions and inference.
|
||||
|
||||
Although this looks scary, it is most likely nothing to worry about. This warning comes from Transformers, and it isn't a PEFT specific warning. It lets you know that a randomly initialized classification head (`score`) is attached to the base model, and the head must be trained to produce sensible predictions.
|
||||
|
||||
When you get this warning _before_ training the model, PEFT automatically takes care of making the classification head trainable if you correctly passed the `task_type` argument to the PEFT config.
|
||||
|
||||
```python
|
||||
from peft import LoraConfig, TaskType
|
||||
|
||||
lora_config = LoraConfig(..., task_type=TaskType.SEQ_CLS)
|
||||
```
|
||||
|
||||
If your classification head does not follow the usual naming conventions from Transformers (which is rare), you have to explicitly tell PEFT the name of the head in `modules_to_save`.
|
||||
|
||||
```python
|
||||
lora_config = LoraConfig(..., modules_to_save=["name-of-classification-head"])
|
||||
```
|
||||
|
||||
To check the name of the classification head, print the model and it should be the last module.
|
||||
|
||||
If you get this warning from your inference code, i.e. _after_ training the model, when you load the PEFT model, you always have to load the Transformers model first. Since Transformers does not know that you will load PEFT weights afterwards, it still gives the warning.
|
||||
|
||||
As always, it is best practice to ensure the model works correctly for inference by running some validation on it.
|
||||
|
||||
### Check layer and model status
|
||||
|
||||
Sometimes a PEFT model can end up in a bad state, especially when handling multiple adapters. There can be some confusion around what adapters exist, which one is active, which one is merged, etc. To help investigate this issue, call the [`~peft.PeftModel.get_layer_status`] and the [`~peft.PeftModel.get_model_status`] methods.
|
||||
|
||||
The [`~peft.PeftModel.get_layer_status`] method gives you a detailed overview of each targeted layer's active, merged, and available adapters.
|
||||
|
||||
```python
|
||||
>>> from transformers import AutoModel
|
||||
>>> from peft import get_peft_model, LoraConfig
|
||||
|
||||
>>> model_id = "google/flan-t5-small"
|
||||
>>> model = AutoModel.from_pretrained(model_id)
|
||||
>>> model = get_peft_model(model, LoraConfig())
|
||||
|
||||
>>> model.get_layer_status()
|
||||
[TunerLayerStatus(name='model.encoder.block.0.layer.0.SelfAttention.q',
|
||||
module_type='lora.Linear',
|
||||
enabled=True,
|
||||
active_adapters=['default'],
|
||||
merged_adapters=[],
|
||||
requires_grad={'default': True},
|
||||
available_adapters=['default']),
|
||||
TunerLayerStatus(name='model.encoder.block.0.layer.0.SelfAttention.v',
|
||||
module_type='lora.Linear',
|
||||
enabled=True,
|
||||
active_adapters=['default'],
|
||||
merged_adapters=[],
|
||||
requires_grad={'default': True},
|
||||
available_adapters=['default']),
|
||||
...]
|
||||
|
||||
>>> model.get_model_status()
|
||||
TunerModelStatus(
|
||||
base_model_type='T5Model',
|
||||
adapter_model_type='LoraModel',
|
||||
peft_types={'default': 'LORA'},
|
||||
trainable_params=344064,
|
||||
total_params=60855680,
|
||||
num_adapter_layers=48,
|
||||
enabled=True,
|
||||
active_adapters=['default'],
|
||||
merged_adapters=[],
|
||||
requires_grad={'default': True},
|
||||
available_adapters=['default'],
|
||||
)
|
||||
```
|
||||
|
||||
In the model state output, you should look out for entries that say `"irregular"`. This means PEFT detected an inconsistent state in the model. For instance, if `merged_adapters="irregular"`, it means that for at least one adapter, it was merged on some target modules but not on others. The inference results will most likely be incorrect as a result.
|
||||
|
||||
The best way to resolve this issue is to reload the whole model and adapter checkpoint(s). Ensure that you don't perform any incorrect operations on the model, e.g. manually merging adapters on some modules but not others.
|
||||
|
||||
Convert the layer status into a pandas `DataFrame` for an easier visual inspection.
|
||||
|
||||
```python
|
||||
from dataclasses import asdict
|
||||
import pandas as pd
|
||||
|
||||
df = pd.DataFrame(asdict(layer) for layer in model.get_layer_status())
|
||||
```
|
||||
|
||||
It is possible to get this information for non-PEFT models if they are using PEFT layers under the hood, but some information like the `base_model_type` or the `peft_types` cannot be determined in that case. As an example, you can call this on a [diffusers](https://huggingface.co/docs/diffusers/index) model like so:
|
||||
|
||||
```python
|
||||
>>> import torch
|
||||
>>> from diffusers import StableDiffusionPipeline
|
||||
>>> from peft import get_model_status, get_layer_status
|
||||
|
||||
>>> path = "runwayml/stable-diffusion-v1-5"
|
||||
>>> lora_id = "takuma104/lora-test-text-encoder-lora-target"
|
||||
>>> pipe = StableDiffusionPipeline.from_pretrained(path, dtype=torch.float16)
|
||||
>>> pipe.load_lora_weights(lora_id, adapter_name="adapter-1")
|
||||
>>> pipe.load_lora_weights(lora_id, adapter_name="adapter-2")
|
||||
>>> pipe.set_lora_device(["adapter-2"], "cuda")
|
||||
>>> get_layer_status(pipe.text_encoder)
|
||||
[TunerLayerStatus(name='text_model.encoder.layers.0.self_attn.k_proj',
|
||||
module_type='lora.Linear',
|
||||
enabled=True,
|
||||
active_adapters=['adapter-2'],
|
||||
merged_adapters=[],
|
||||
requires_grad={'adapter-1': False, 'adapter-2': True},
|
||||
available_adapters=['adapter-1', 'adapter-2'],
|
||||
devices={'adapter-1': ['cpu'], 'adapter-2': ['cuda']}),
|
||||
TunerLayerStatus(name='text_model.encoder.layers.0.self_attn.v_proj',
|
||||
module_type='lora.Linear',
|
||||
enabled=True,
|
||||
active_adapters=['adapter-2'],
|
||||
merged_adapters=[],
|
||||
requires_grad={'adapter-1': False, 'adapter-2': True},
|
||||
devices={'adapter-1': ['cpu'], 'adapter-2': ['cuda']}),
|
||||
...]
|
||||
|
||||
>>> get_model_status(pipe.unet)
|
||||
TunerModelStatus(
|
||||
base_model_type='other',
|
||||
adapter_model_type='None',
|
||||
peft_types={},
|
||||
trainable_params=797184,
|
||||
total_params=861115332,
|
||||
num_adapter_layers=128,
|
||||
enabled=True,
|
||||
active_adapters=['adapter-2'],
|
||||
merged_adapters=[],
|
||||
requires_grad={'adapter-1': False, 'adapter-2': True},
|
||||
available_adapters=['adapter-1', 'adapter-2'],
|
||||
devices={'adapter-1': ['cpu'], 'adapter-2': ['cuda']},
|
||||
)
|
||||
```
|
||||
|
||||
## Speed
|
||||
|
||||
### Loading adapter weights is slow
|
||||
|
||||
Loading adapters like LoRA weights should generally be fast compared to loading the base model. However, there can be use cases where the adapter weights are quite large or where users need to load a large number of adapters -- the loading time can add up in this case. The reason for this is that the adapter weights are first initialized and then overridden by the loaded weights, which is wasteful. To speed up the loading time, you can pass the `low_cpu_mem_usage=True` argument to [`~PeftModel.from_pretrained`] and [`~PeftModel.load_adapter`].
|
||||
|
||||
> [!TIP]
|
||||
> If this option works well across different use cases, it may become the default for adapter loading in the future.
|
||||
|
||||
|
||||
## Reproducibility
|
||||
|
||||
### Models using batch norm
|
||||
|
||||
When loading a trained PEFT model where the base model uses batch norm (e.g. `torch.nn.BatchNorm1d` or `torch.nn.BatchNorm2d`), you may find that you cannot reproduce the exact same outputs. This is because the batch norm layers keep track of running stats during training, but these stats are not part of the PEFT checkpoint. Therefore, when you load the PEFT model, the running stats of the base model will be used (i.e. from before training with PEFT).
|
||||
|
||||
Depending on your use case, this may not be a big deal. If, however, you need your outputs to be 100% reproducible, you can achieve this by adding the batch norm layers to `modules_to_save`. Below is an example of this using resnet and LoRA. Notice that we set `modules_to_save=["classifier", "normalization"]`. We need the `"classifier"` argument because our task is image classification, and we add the `"normalization"` argument to ensure that the batch norm layers are saved in the PEFT checkpoint.
|
||||
|
||||
```python
|
||||
from transformers import AutoModelForImageClassification
|
||||
from peft import LoraConfig, get_peft_model
|
||||
|
||||
model_id = "microsoft/resnet-18"
|
||||
base_model = AutoModelForImageClassification.from_pretrained(self.model_id)
|
||||
config = LoraConfig(
|
||||
target_modules=["convolution"],
|
||||
modules_to_save=["classifier", "normalization"],
|
||||
),
|
||||
```
|
||||
|
||||
Depending on the type of model you use, the batch norm layers could have different names than `"normalization"`, so please ensure that the name matches your model architecture.
|
||||
|
||||
## Version mismatch
|
||||
|
||||
### Error while loading the config because of an unexpected keyword argument
|
||||
|
||||
When you encounter an error like the one shown below, it means the adapter you're trying to load was trained with a more recent version of PEFT than the version you have installed on your system.
|
||||
|
||||
```
|
||||
TypeError: LoraConfig.__init__() got an unexpected keyword argument <argument-name>
|
||||
```
|
||||
|
||||
The best way to resolve this issue is to install the latest PEFT version:
|
||||
|
||||
```sh
|
||||
python -m pip install -U PEFT
|
||||
```
|
||||
|
||||
If the adapter was trained from a source install of PEFT (an unreleased version of PEFT), then you also need to install PEFT from source.
|
||||
|
||||
```sh
|
||||
python -m pip install -U git+https://github.com/huggingface/peft.git
|
||||
```
|
||||
|
||||
If it is not possible for you to upgrade PEFT, there is a workaround you can try.
|
||||
|
||||
Assume the error message says that the unknown keyword argument is named `foobar`. Search inside the `adapter_config.json` of this PEFT adapter for the `foobar` entry and delete it from the file. Then save the file and try loading the model again.
|
||||
|
||||
This solution works most of the time. As long as it is the default value for `foobar`, it can be ignored. However, when it is set to some other value, you will get incorrect results. Upgrading PEFT is the recommended solution.
|
||||
|
||||
## Adapter handling
|
||||
|
||||
### Using multiple adapters at the same time
|
||||
|
||||
PEFT allows you to create more than one adapter on the same model. This can be useful in many situations. For example, for inference, you may want to serve two fine-tuned models from the same base model instead of loading the base model once for each fine-tuned model, which would cost more memory. However, multiple adapters can be activated at the same time. This way, the model may leverage the learnings from all those adapters at the same time. As an example, if you have a diffusion model, you may want to use one LoRA adapter to change the style and a different one to change the subject.
|
||||
|
||||
Activating multiple adapters at the same time is generally possible on all PEFT methods (LoRA, LoHa, IA³, etc.) except for prompt learning methods (p-tuning, prefix tuning, etc.). The following example illustrates how to achieve this:
|
||||
|
||||
```python
|
||||
from transformers import AutoModelForCausalLM
|
||||
from peft import PeftModel
|
||||
|
||||
model_id = ...
|
||||
base_model = AutoModelForCausalLM.from_pretrained(model_id)
|
||||
model = PeftModel.from_pretrained(base_model, lora_path_0) # default adapter_name is 'default'
|
||||
model.load_adapter(lora_path_1, adapter_name="other")
|
||||
# the 'other' adapter was loaded but it's not active yet, so to activate both adapters:
|
||||
model.base_model.set_adapter(["default", "other"])
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
> In the example above, you can see that we need to call `model.base_model.set_adapter(["default", "other"])`. Why can we not call `model.set_adapter(["default", "other"])`? This is unfortunately not possible because, as explained earlier, some PEFT methods don't support activating more than one adapter at a time.
|
||||
|
||||
It is also possible to train two adapters at the same time, but you should be careful to ensure that the weights of both adapters are known to the optimizer. Otherwise, only one adapter will receive updates.
|
||||
|
||||
```python
|
||||
from transformers import AutoModelForCausalLM
|
||||
from peft import LoraConfig, get_peft_model
|
||||
|
||||
model_id = ...
|
||||
base_model = AutoModelForCausalLM.from_pretrained(model_id)
|
||||
lora_config_0 = LoraConfig(...)
|
||||
lora_config_1 = LoraConfig(...)
|
||||
model = get_peft_model(base_model, lora_config_0)
|
||||
model.add_adapter(adapter_name="other", peft_config=lora_config_1)
|
||||
```
|
||||
|
||||
If we would now call:
|
||||
|
||||
```python
|
||||
from transformers import Trainer
|
||||
|
||||
trainer = Trainer(model=model, ...)
|
||||
trainer.train()
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```python
|
||||
optimizer = torch.optim.AdamW([param for param in model.parameters() if param.requires_grad], ...)
|
||||
```
|
||||
|
||||
then the second LoRA adapter (`"other"`) would not be trained. This is because it is inactive at this moment, which means the `requires_grad` attribute on its parameters is set to `False` and the optimizer will ignore it. Therefore, make sure to activate all adapters that should be trained _before_ initializing the optimizer:
|
||||
|
||||
```python
|
||||
# activate all adapters
|
||||
model.base_model.set_adapter(["default", "other"])
|
||||
trainer = Trainer(model=model, ...)
|
||||
trainer.train()
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
> This section deals with using multiple adapters _of the same type_ on the same model, for example, using multiple LoRA adapters at the same time. It does not apply to using _different types_ of adapters on the same model, for example one LoRA adapter and one LoHa adapter. For this, please check [`PeftMixedModel`](https://huggingface.co/docs/peft/developer_guides/mixed_models).
|
||||
Reference in New Issue
Block a user