caf324b09d
tests / check_code_quality (push) Waiting to run
tests / tests (ubuntu-latest, 3.10) (push) Blocked by required conditions
tests / tests (ubuntu-latest, 3.11) (push) Blocked by required conditions
Deploy "method_comparison" Gradio to Spaces / deploy (push) Waiting to run
Deploy "PEFT shop" Gradio app to Spaces / deploy (push) Waiting to run
tests on transformers main / tests (push) Waiting to run
tests / tests (ubuntu-latest, 3.12) (push) Blocked by required conditions
tests / tests (ubuntu-latest, 3.13) (push) Blocked by required conditions
tests / tests (windows-latest, 3.10) (push) Blocked by required conditions
tests / tests (windows-latest, 3.11) (push) Blocked by required conditions
tests / tests (windows-latest, 3.12) (push) Blocked by required conditions
tests / tests (windows-latest, 3.13) (push) Blocked by required conditions
Secret Leaks / trufflehog (push) Waiting to run
CI security linting / zizmor latest via Cargo (push) Waiting to run
Build documentation / build (push) Failing after 0s
1244 lines
51 KiB
Python
1244 lines
51 KiB
Python
# Copyright 2023-present the HuggingFace Inc. team.
|
|
#
|
|
# 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.
|
|
import json
|
|
import platform
|
|
import tempfile
|
|
from unittest.mock import Mock, call, patch
|
|
|
|
import pytest
|
|
import torch
|
|
from accelerate.test_utils.testing import get_backend
|
|
from safetensors.torch import load_file as safe_load_file
|
|
from transformers import (
|
|
AutoModelForCausalLM,
|
|
AutoTokenizer,
|
|
DataCollatorForLanguageModeling,
|
|
Trainer,
|
|
TrainingArguments,
|
|
)
|
|
from transformers.modeling_outputs import CausalLMOutputWithPast
|
|
|
|
from peft import (
|
|
AdaLoraConfig,
|
|
BeftConfig,
|
|
BOFTConfig,
|
|
C3AConfig,
|
|
CartridgeConfig,
|
|
CPTConfig,
|
|
DeftConfig,
|
|
DeloraConfig,
|
|
FourierFTConfig,
|
|
FrodConfig,
|
|
GloraConfig,
|
|
GraloraConfig,
|
|
HiraConfig,
|
|
HRAConfig,
|
|
IA3Config,
|
|
LoraConfig,
|
|
MissConfig,
|
|
OFTConfig,
|
|
OSFConfig,
|
|
PrefixTuningConfig,
|
|
PromptEmbedding,
|
|
PromptEncoderConfig,
|
|
PromptTuningConfig,
|
|
PromptTuningInit,
|
|
PsoftConfig,
|
|
PveraConfig,
|
|
RoadConfig,
|
|
ShiraConfig,
|
|
TaskType,
|
|
TinyLoraConfig,
|
|
UniLoraConfig,
|
|
VBLoRAConfig,
|
|
VeraConfig,
|
|
WaveFTConfig,
|
|
get_peft_model,
|
|
)
|
|
|
|
from .testing_common import PeftCommonTester
|
|
from .testing_utils import device_count, hub_online_once, load_dataset_english_quotes, set_init_weights_false
|
|
|
|
|
|
# Note: some models from peft-internal-testing are just the safetensors versions of hf-internal-testing
|
|
PEFT_DECODER_MODELS_TO_TEST = [
|
|
"peft-internal-testing/tiny-random-OPTForCausalLM",
|
|
"peft-internal-testing/tiny-random-GPT2LMHeadModel",
|
|
"peft-internal-testing/tiny-random-GPTJForCausalLM",
|
|
"trl-internal-testing/tiny-random-LlamaForCausalLM",
|
|
"peft-internal-testing/tiny-dummy-qwen2",
|
|
"hf-internal-testing/tiny-random-Gemma3ForCausalLM",
|
|
]
|
|
|
|
SMALL_GRID_MODELS = [
|
|
"hf-internal-testing/tiny-random-gpt2",
|
|
"peft-internal-testing/tiny-random-OPTForCausalLM",
|
|
"hf-internal-testing/tiny-random-MistralForCausalLM",
|
|
"peft-internal-testing/tiny-dummy-qwen2",
|
|
"trl-internal-testing/tiny-random-LlamaForCausalLM",
|
|
]
|
|
|
|
|
|
# TODO Missing from this list are LoKr, LoHa, LN Tuning, add them
|
|
# Note: If the PEFT method offers an initialization option to make it an identity transform (typically via the
|
|
# init_weights argument), then this option should be set here, if it's not already the default.
|
|
ALL_CONFIGS = [
|
|
(
|
|
AdaLoraConfig,
|
|
{
|
|
"task_type": "CAUSAL_LM",
|
|
"target_modules": None,
|
|
"total_step": 1,
|
|
},
|
|
),
|
|
(
|
|
BeftConfig,
|
|
{
|
|
"task_type": "CAUSAL_LM",
|
|
"target_modules": None,
|
|
},
|
|
),
|
|
(
|
|
BOFTConfig,
|
|
{
|
|
"task_type": "CAUSAL_LM",
|
|
"target_modules": None,
|
|
},
|
|
),
|
|
(
|
|
MissConfig,
|
|
{
|
|
"task_type": "CAUSAL_LM",
|
|
"target_modules": None,
|
|
"r": 2,
|
|
},
|
|
),
|
|
(
|
|
CPTConfig,
|
|
{
|
|
"task_type": "CAUSAL_LM",
|
|
"cpt_token_ids": [0, 1, 2, 3, 4, 5, 6, 7], # Example token IDs for testing
|
|
"cpt_mask": [1, 1, 1, 1, 1, 1, 1, 1],
|
|
"cpt_tokens_type_mask": [1, 2, 2, 2, 3, 3, 4, 4],
|
|
},
|
|
),
|
|
(
|
|
DeftConfig,
|
|
{
|
|
"task_type": "CAUSAL_LM",
|
|
"target_modules": None,
|
|
},
|
|
),
|
|
(
|
|
DeloraConfig,
|
|
{
|
|
"task_type": "CAUSAL_LM",
|
|
"target_modules": None,
|
|
"r": 2,
|
|
},
|
|
),
|
|
(
|
|
FourierFTConfig,
|
|
{
|
|
"task_type": "CAUSAL_LM",
|
|
"n_frequency": 10,
|
|
"target_modules": None,
|
|
},
|
|
),
|
|
(
|
|
FrodConfig,
|
|
{
|
|
"task_type": "CAUSAL_LM",
|
|
"target_modules": None,
|
|
"sparse_rate": 0.01,
|
|
},
|
|
),
|
|
(
|
|
GraloraConfig,
|
|
{
|
|
"task_type": "CAUSAL_LM",
|
|
"r": 8,
|
|
"alpha": 16,
|
|
"target_modules": None,
|
|
"gralora_dropout": 0.05,
|
|
"gralora_k": 2,
|
|
"hybrid_r": 0,
|
|
},
|
|
),
|
|
(
|
|
GraloraConfig,
|
|
{
|
|
"task_type": "CAUSAL_LM",
|
|
"r": 16,
|
|
"alpha": 32,
|
|
"target_modules": None,
|
|
"gralora_dropout": 0.05,
|
|
"gralora_k": 4,
|
|
"hybrid_r": 4,
|
|
},
|
|
),
|
|
(
|
|
GloraConfig,
|
|
{
|
|
"task_type": "CAUSAL_LM",
|
|
"target_modules": None,
|
|
"init_weights": True,
|
|
},
|
|
),
|
|
(
|
|
GloraConfig,
|
|
{
|
|
"task_type": "CAUSAL_LM",
|
|
"target_modules": None,
|
|
"init_weights": False,
|
|
},
|
|
),
|
|
(
|
|
HiraConfig,
|
|
{
|
|
"task_type": "CAUSAL_LM",
|
|
"target_modules": None,
|
|
},
|
|
),
|
|
(
|
|
HRAConfig,
|
|
{
|
|
"task_type": "CAUSAL_LM",
|
|
"target_modules": None,
|
|
},
|
|
),
|
|
(
|
|
IA3Config,
|
|
{
|
|
"task_type": "CAUSAL_LM",
|
|
"target_modules": None,
|
|
"feedforward_modules": None,
|
|
},
|
|
),
|
|
(
|
|
LoraConfig,
|
|
{
|
|
"task_type": "CAUSAL_LM",
|
|
"r": 8,
|
|
"lora_alpha": 32,
|
|
"target_modules": None,
|
|
"lora_dropout": 0.05,
|
|
"bias": "none",
|
|
},
|
|
),
|
|
# Activated LoRA (aLoRA)
|
|
(
|
|
LoraConfig,
|
|
{
|
|
"task_type": "CAUSAL_LM",
|
|
"r": 8,
|
|
"lora_alpha": 32,
|
|
"target_modules": None,
|
|
"lora_dropout": 0.05,
|
|
"bias": "none",
|
|
"alora_invocation_tokens": [1],
|
|
},
|
|
),
|
|
(
|
|
LoraConfig,
|
|
{
|
|
"task_type": "CAUSAL_LM",
|
|
"r": 8,
|
|
"lora_alpha": 32,
|
|
"target_modules": None,
|
|
"lora_dropout": 0.05,
|
|
"bias": "none",
|
|
# not one test input sequence will ever have this token, this should do nothing at all
|
|
"alora_invocation_tokens": [1000],
|
|
},
|
|
),
|
|
# LoRA + trainable tokens
|
|
(
|
|
LoraConfig,
|
|
{
|
|
"task_type": "CAUSAL_LM",
|
|
"r": 8,
|
|
"lora_alpha": 32,
|
|
"target_modules": None,
|
|
"lora_dropout": 0.05,
|
|
"bias": "none",
|
|
"trainable_token_indices": [0, 1, 3],
|
|
},
|
|
),
|
|
(
|
|
OFTConfig,
|
|
{
|
|
"task_type": "CAUSAL_LM",
|
|
"target_modules": None,
|
|
},
|
|
),
|
|
(
|
|
PrefixTuningConfig,
|
|
{
|
|
"task_type": "CAUSAL_LM",
|
|
"num_virtual_tokens": 10,
|
|
},
|
|
),
|
|
(
|
|
PrefixTuningConfig,
|
|
{
|
|
"task_type": "CAUSAL_LM",
|
|
"num_virtual_tokens": 10,
|
|
"init_weights": "zero",
|
|
},
|
|
),
|
|
(
|
|
PromptEncoderConfig,
|
|
{
|
|
"task_type": "CAUSAL_LM",
|
|
"num_virtual_tokens": 10,
|
|
"encoder_hidden_size": 32,
|
|
},
|
|
),
|
|
(
|
|
PromptTuningConfig,
|
|
{
|
|
"task_type": "CAUSAL_LM",
|
|
"num_virtual_tokens": 10,
|
|
},
|
|
),
|
|
(
|
|
RoadConfig,
|
|
{
|
|
"task_type": "CAUSAL_LM",
|
|
"variant": "road_1",
|
|
"group_size": 2,
|
|
},
|
|
),
|
|
(
|
|
ShiraConfig,
|
|
{
|
|
"r": 1,
|
|
"task_type": "CAUSAL_LM",
|
|
"target_modules": None,
|
|
"init_weights": False,
|
|
},
|
|
),
|
|
(
|
|
VBLoRAConfig,
|
|
{
|
|
"task_type": "CAUSAL_LM",
|
|
"target_modules": None,
|
|
"vblora_dropout": 0.05,
|
|
"vector_length": 1,
|
|
"num_vectors": 2,
|
|
},
|
|
),
|
|
(
|
|
VeraConfig,
|
|
{
|
|
"task_type": "CAUSAL_LM",
|
|
"r": 8,
|
|
"target_modules": None,
|
|
"vera_dropout": 0.05,
|
|
"projection_prng_key": 0xFF,
|
|
"d_initial": 0.1,
|
|
"save_projection": True,
|
|
"bias": "none",
|
|
},
|
|
),
|
|
(
|
|
UniLoraConfig,
|
|
{
|
|
"task_type": "CAUSAL_LM",
|
|
"target_modules": None,
|
|
"theta_d_length": 257,
|
|
},
|
|
),
|
|
(
|
|
TinyLoraConfig,
|
|
{
|
|
"task_type": "CAUSAL_LM",
|
|
"target_modules": None,
|
|
},
|
|
),
|
|
(
|
|
PveraConfig,
|
|
{
|
|
"r": 8,
|
|
"pvera_dropout": 0.05,
|
|
"task_type": "CAUSAL_LM",
|
|
},
|
|
),
|
|
(
|
|
C3AConfig,
|
|
{
|
|
"task_type": "CAUSAL_LM",
|
|
"block_size": 1, # Some test cases contain shapes of prime numbers where `block_size` must be 1
|
|
"target_modules": None,
|
|
},
|
|
),
|
|
(
|
|
WaveFTConfig,
|
|
{
|
|
"task_type": "CAUSAL_LM",
|
|
"n_frequency": 8,
|
|
"target_modules": None,
|
|
},
|
|
),
|
|
(
|
|
OSFConfig,
|
|
{
|
|
"task_type": "CAUSAL_LM",
|
|
},
|
|
),
|
|
(
|
|
PsoftConfig,
|
|
{
|
|
"task_type": "CAUSAL_LM",
|
|
"r": 4,
|
|
"psoft_alpha": 4,
|
|
},
|
|
),
|
|
]
|
|
|
|
|
|
def _skip_if_not_conv1d_supported(model_id, config_cls):
|
|
if "GPT2LMHeadModel" in model_id and config_cls in [
|
|
BeftConfig,
|
|
BOFTConfig,
|
|
GloraConfig,
|
|
HRAConfig,
|
|
OFTConfig,
|
|
OSFConfig,
|
|
RoadConfig,
|
|
ShiraConfig,
|
|
C3AConfig,
|
|
MissConfig,
|
|
DeloraConfig,
|
|
PsoftConfig,
|
|
]:
|
|
pytest.skip("Skipping Beft/BOFT/GLoRA/HRA/OFT/Road/SHiRA/C3A/MiSS/OSF/DeLoRA/PSOFT for GPT2LMHeadModel")
|
|
|
|
|
|
def _skip_alora_no_activation(config_cls, config_kwargs):
|
|
if config_cls is LoraConfig and config_kwargs.get("alora_invocation_tokens") == [1000]:
|
|
pytest.skip("Skipping aLoRA no-activation-case because the test expects changed output which there won't be.")
|
|
|
|
|
|
def _skip_osf_disable_adapter_test(config_cls):
|
|
if config_cls is OSFConfig:
|
|
pytest.skip(
|
|
"Skipping OSF for disable_adapter test because OSF uses exact SVD decomposition, so outputs are identical until training."
|
|
)
|
|
|
|
|
|
def check_beft_config(config_cls, model_id, config_kwargs):
|
|
if isinstance(config_cls, BeftConfig):
|
|
return
|
|
elif "gptj" in model_id.lower():
|
|
config_kwargs["target_modules"] = ["fc_out"]
|
|
elif "llama" in model_id.lower():
|
|
pytest.skip("Skip tests for Llama models because layers have no bias term")
|
|
elif "gemma3" in model_id.lower():
|
|
pytest.skip("Skip tests for Gemma3 models because layers have no bias term")
|
|
else:
|
|
return
|
|
|
|
|
|
class TestDecoderModels(PeftCommonTester):
|
|
transformers_class = AutoModelForCausalLM
|
|
|
|
def prepare_inputs_for_testing(self):
|
|
input_ids = torch.tensor([[1, 1, 1], [1, 2, 1]]).to(self.torch_device)
|
|
attention_mask = torch.tensor([[1, 1, 1], [1, 0, 1]]).to(self.torch_device)
|
|
return {"input_ids": input_ids, "attention_mask": attention_mask}
|
|
|
|
@pytest.mark.parametrize("model_id", PEFT_DECODER_MODELS_TO_TEST)
|
|
@pytest.mark.parametrize("config_cls,config_kwargs", ALL_CONFIGS)
|
|
def test_attributes_parametrized(self, model_id, config_cls, config_kwargs):
|
|
_skip_if_not_conv1d_supported(model_id, config_cls)
|
|
self._test_model_attr(model_id, config_cls, config_kwargs.copy())
|
|
|
|
@pytest.mark.parametrize("model_id", PEFT_DECODER_MODELS_TO_TEST)
|
|
@pytest.mark.parametrize("config_cls,config_kwargs", ALL_CONFIGS)
|
|
def test_adapter_name(self, model_id, config_cls, config_kwargs):
|
|
_skip_if_not_conv1d_supported(model_id, config_cls)
|
|
self._test_adapter_name(model_id, config_cls, config_kwargs.copy())
|
|
|
|
@pytest.mark.parametrize("model_id", PEFT_DECODER_MODELS_TO_TEST)
|
|
@pytest.mark.parametrize("config_cls,config_kwargs", ALL_CONFIGS)
|
|
def test_prepare_for_training_parametrized(self, model_id, config_cls, config_kwargs):
|
|
_skip_if_not_conv1d_supported(model_id, config_cls)
|
|
self._test_prepare_for_training(model_id, config_cls, config_kwargs.copy())
|
|
|
|
@pytest.mark.parametrize("model_id", PEFT_DECODER_MODELS_TO_TEST)
|
|
@pytest.mark.parametrize("config_cls,config_kwargs", ALL_CONFIGS)
|
|
def test_prompt_tuning_text_prepare_for_training(self, model_id, config_cls, config_kwargs):
|
|
if config_cls != PromptTuningConfig:
|
|
pytest.skip(f"This test does not apply to {config_cls}")
|
|
config_kwargs = config_kwargs.copy()
|
|
config_kwargs["prompt_tuning_init"] = PromptTuningInit.TEXT
|
|
config_kwargs["prompt_tuning_init_text"] = "This is a test prompt."
|
|
config_kwargs["tokenizer_name_or_path"] = model_id
|
|
self._test_prepare_for_training(model_id, config_cls, config_kwargs.copy())
|
|
|
|
def test_prompt_tuning_text_tokenizer_kwargs(self):
|
|
# Allow users to pass additional arguments to Tokenizer.from_pretrained
|
|
# Fix for #1032
|
|
mock = Mock()
|
|
orig_from_pretrained = AutoTokenizer.from_pretrained
|
|
|
|
def mock_autotokenizer_from_pretrained(*args, **kwargs):
|
|
mock(*args, **kwargs)
|
|
return orig_from_pretrained(config.tokenizer_name_or_path)
|
|
|
|
model_id = "peft-internal-testing/tiny-random-OPTForCausalLM"
|
|
config = PromptTuningConfig(
|
|
base_model_name_or_path=model_id,
|
|
tokenizer_name_or_path=model_id,
|
|
num_virtual_tokens=10,
|
|
prompt_tuning_init=PromptTuningInit.TEXT,
|
|
task_type="CAUSAL_LM",
|
|
prompt_tuning_init_text="This is a test prompt.",
|
|
tokenizer_kwargs={"cache_dir": "/tmp/somewhere", "foo": "bar"},
|
|
)
|
|
model = self.transformers_class.from_pretrained(model_id).to(self.torch_device)
|
|
with patch("transformers.AutoTokenizer.from_pretrained", mock_autotokenizer_from_pretrained):
|
|
_ = get_peft_model(model, config)
|
|
expected_call = call(model_id, cache_dir="/tmp/somewhere", foo="bar")
|
|
assert mock.call_args == expected_call
|
|
|
|
def test_prompt_tuning_trust_remote_code(self, tmp_path, monkeypatch):
|
|
# See #2888 for details
|
|
|
|
# This is a test for a hypothetical exploit that would enable trust_remote_code (and thus RCE) when a user loads
|
|
# a malicious prompt tuning model. This is because PEFT would just pass the on the tokenizer_kwargs defined in
|
|
# the prompt tuning config unsanitzed, which means that if the tokenizer is also malicious, the malicious code
|
|
# would be executed. For this exploit to work, a user cannot load a model using PeftModel.from_pretrained as
|
|
# normal, because the tokenizer is only loaded in training mode. Although the attacker could set
|
|
# inference_mode=True in the adapter_config.json, that would still not work because prompt tuning methods cannot
|
|
# be loaded in inference mode. Therefore, the only way for the exploit to work would be if the user manually
|
|
# loads the model, as is shown below.
|
|
|
|
model_id = "peft-internal-testing/tiny-random-OPTForCausalLM"
|
|
with hub_online_once(model_id):
|
|
# crafting the malicious checkpoint:
|
|
model = AutoModelForCausalLM.from_pretrained(model_id)
|
|
config = PromptTuningConfig(
|
|
num_virtual_tokens=10,
|
|
task_type=TaskType.CAUSAL_LM,
|
|
tokenizer_name_or_path=model_id,
|
|
prompt_tuning_init=PromptTuningInit.TEXT,
|
|
prompt_tuning_init_text="hello",
|
|
tokenizer_kwargs={"trust_remote_code": "foobar"},
|
|
)
|
|
model = get_peft_model(model, config)
|
|
model.save_pretrained(tmp_path)
|
|
|
|
with open(tmp_path / "adapter_config.json") as f:
|
|
config_dict = json.load(f)
|
|
# disable inference mode
|
|
config_dict["inference_mode"] = False
|
|
with open(tmp_path / "adapter_config.json", "w") as f:
|
|
json.dump(config_dict, f)
|
|
|
|
del model
|
|
|
|
# applying a mock to check the used parameters
|
|
used_args = []
|
|
used_kwargs = {}
|
|
|
|
orig_from_pretrained = AutoTokenizer.from_pretrained
|
|
|
|
def fake_from_pretrained(*args, **kwargs):
|
|
used_args.extend(args)
|
|
used_kwargs.update(kwargs)
|
|
return orig_from_pretrained(*args, **kwargs)
|
|
|
|
monkeypatch.setattr(AutoTokenizer, "from_pretrained", fake_from_pretrained)
|
|
|
|
# user code: loading the malicious checkpoint
|
|
model = AutoModelForCausalLM.from_pretrained(model_id)
|
|
config = PromptTuningConfig.from_pretrained(tmp_path)
|
|
PromptEmbedding(config, model.model.decoder.embed_tokens)
|
|
|
|
# check that neither args nor kwargs used trust_remote_code='foobar'
|
|
assert "foobar" not in used_args
|
|
assert used_kwargs.get("trust_remote_code") != "foobar"
|
|
|
|
@pytest.mark.parametrize("model_id", PEFT_DECODER_MODELS_TO_TEST)
|
|
@pytest.mark.parametrize("config_cls,config_kwargs", ALL_CONFIGS)
|
|
def test_prompt_tuning_sample_vocab_prepare_for_training(self, model_id, config_cls, config_kwargs):
|
|
if config_cls != PromptTuningConfig:
|
|
pytest.skip(f"This test does not apply to {config_cls}")
|
|
|
|
config_kwargs = config_kwargs.copy()
|
|
config_kwargs["prompt_tuning_init"] = PromptTuningInit.SAMPLE_VOCAB
|
|
config_kwargs["tokenizer_name_or_path"] = model_id
|
|
|
|
self._test_prepare_for_training(model_id, config_cls, config_kwargs.copy())
|
|
|
|
def test_prompt_tuning_config_invalid_args(self):
|
|
# Raise an error when tokenizer_kwargs is used with prompt_tuning_init!='TEXT', because this argument has no
|
|
# function in that case
|
|
model_id = "peft-internal-testing/tiny-random-OPTForCausalLM"
|
|
with pytest.raises(ValueError, match="tokenizer_kwargs only valid when using prompt_tuning_init='TEXT'."):
|
|
PromptTuningConfig(
|
|
base_model_name_or_path=model_id,
|
|
tokenizer_name_or_path=model_id,
|
|
num_virtual_tokens=10,
|
|
task_type="CAUSAL_LM",
|
|
prompt_tuning_init_text="This is a test prompt.",
|
|
prompt_tuning_init=PromptTuningInit.RANDOM, # <= should not be used together with tokenizer_kwargs
|
|
tokenizer_kwargs={"trust_remote_code": True, "foo": "bar"},
|
|
)
|
|
|
|
@pytest.mark.parametrize("model_id", PEFT_DECODER_MODELS_TO_TEST)
|
|
@pytest.mark.parametrize("config_cls,config_kwargs", ALL_CONFIGS)
|
|
def test_save_pretrained(self, model_id, config_cls, config_kwargs):
|
|
_skip_if_not_conv1d_supported(model_id, config_cls)
|
|
config_kwargs = set_init_weights_false(config_cls, config_kwargs)
|
|
self._test_save_pretrained(model_id, config_cls, config_kwargs.copy())
|
|
|
|
@pytest.mark.parametrize("model_id", PEFT_DECODER_MODELS_TO_TEST)
|
|
@pytest.mark.parametrize("config_cls,config_kwargs", ALL_CONFIGS)
|
|
def test_save_pretrained_pickle(self, model_id, config_cls, config_kwargs):
|
|
_skip_if_not_conv1d_supported(model_id, config_cls)
|
|
config_kwargs = set_init_weights_false(config_cls, config_kwargs)
|
|
self._test_save_pretrained(model_id, config_cls, config_kwargs.copy(), safe_serialization=False)
|
|
|
|
@pytest.mark.parametrize("model_id", PEFT_DECODER_MODELS_TO_TEST)
|
|
@pytest.mark.parametrize("config_cls,config_kwargs", ALL_CONFIGS)
|
|
def test_save_pretrained_selected_adapters(self, model_id, config_cls, config_kwargs):
|
|
_skip_if_not_conv1d_supported(model_id, config_cls)
|
|
config_kwargs = set_init_weights_false(config_cls, config_kwargs)
|
|
self._test_save_pretrained_selected_adapters(model_id, config_cls, config_kwargs.copy())
|
|
|
|
@pytest.mark.parametrize("model_id", PEFT_DECODER_MODELS_TO_TEST)
|
|
@pytest.mark.parametrize("config_cls,config_kwargs", ALL_CONFIGS)
|
|
def test_save_pretrained_selected_adapters_pickle(self, model_id, config_cls, config_kwargs):
|
|
_skip_if_not_conv1d_supported(model_id, config_cls)
|
|
config_kwargs = set_init_weights_false(config_cls, config_kwargs)
|
|
self._test_save_pretrained_selected_adapters(
|
|
model_id, config_cls, config_kwargs.copy(), safe_serialization=False
|
|
)
|
|
|
|
@pytest.mark.parametrize("model_id", PEFT_DECODER_MODELS_TO_TEST)
|
|
@pytest.mark.parametrize("config_cls,config_kwargs", ALL_CONFIGS)
|
|
def test_from_pretrained_config_construction(self, model_id, config_cls, config_kwargs):
|
|
_skip_if_not_conv1d_supported(model_id, config_cls)
|
|
self._test_from_pretrained_config_construction(model_id, config_cls, config_kwargs.copy())
|
|
|
|
@pytest.mark.parametrize("model_id", PEFT_DECODER_MODELS_TO_TEST)
|
|
@pytest.mark.parametrize("config_cls,config_kwargs", ALL_CONFIGS)
|
|
def test_merge_layers(self, model_id, config_cls, config_kwargs):
|
|
config_kwargs = set_init_weights_false(config_cls, config_kwargs)
|
|
check_beft_config(config_cls, model_id, config_kwargs)
|
|
self._test_merge_layers(model_id, config_cls, config_kwargs.copy())
|
|
|
|
@pytest.mark.parametrize("model_id", PEFT_DECODER_MODELS_TO_TEST)
|
|
@pytest.mark.parametrize("config_cls,config_kwargs", ALL_CONFIGS)
|
|
def test_merge_layers_multi(self, model_id, config_cls, config_kwargs):
|
|
_skip_if_not_conv1d_supported(model_id, config_cls)
|
|
config_kwargs = set_init_weights_false(config_cls, config_kwargs)
|
|
check_beft_config(config_cls, model_id, config_kwargs)
|
|
self._test_merge_layers_multi(model_id, config_cls, config_kwargs.copy())
|
|
|
|
@pytest.mark.parametrize("model_id", PEFT_DECODER_MODELS_TO_TEST)
|
|
@pytest.mark.parametrize("config_cls,config_kwargs", ALL_CONFIGS)
|
|
def test_merge_layers_nan(self, model_id, config_cls, config_kwargs):
|
|
config_kwargs = set_init_weights_false(config_cls, config_kwargs)
|
|
check_beft_config(config_cls, model_id, config_kwargs)
|
|
self._test_merge_layers_nan(model_id, config_cls, config_kwargs.copy())
|
|
|
|
@pytest.mark.parametrize("model_id", PEFT_DECODER_MODELS_TO_TEST)
|
|
@pytest.mark.parametrize("config_cls,config_kwargs", ALL_CONFIGS)
|
|
def test_mixed_adapter_batches(self, model_id, config_cls, config_kwargs):
|
|
if config_cls != LoraConfig:
|
|
pytest.skip("Mixed adapter batches not supported for this config.")
|
|
_skip_alora_no_activation(config_cls, config_kwargs)
|
|
config_kwargs = set_init_weights_false(config_cls, config_kwargs)
|
|
self._test_mixed_adapter_batches(model_id, config_cls, config_kwargs.copy())
|
|
|
|
@pytest.mark.parametrize("model_id", PEFT_DECODER_MODELS_TO_TEST)
|
|
@pytest.mark.parametrize("config_cls,config_kwargs", ALL_CONFIGS)
|
|
def test_generate_with_mixed_adapter_batches(self, model_id, config_cls, config_kwargs):
|
|
if config_cls != LoraConfig:
|
|
pytest.skip("Mixed adapter batches not supported for this config.")
|
|
config_kwargs = set_init_weights_false(config_cls, config_kwargs)
|
|
self._test_generate_with_mixed_adapter_batches_and_beam_search(model_id, config_cls, config_kwargs.copy())
|
|
|
|
@pytest.mark.parametrize("model_id", PEFT_DECODER_MODELS_TO_TEST)
|
|
@pytest.mark.parametrize("config_cls,config_kwargs", ALL_CONFIGS)
|
|
def test_generate(self, model_id, config_cls, config_kwargs):
|
|
_skip_if_not_conv1d_supported(model_id, config_cls)
|
|
self._test_generate(model_id, config_cls, config_kwargs.copy())
|
|
|
|
@pytest.mark.parametrize("model_id", PEFT_DECODER_MODELS_TO_TEST)
|
|
@pytest.mark.parametrize("config_cls,config_kwargs", ALL_CONFIGS)
|
|
def test_generate_pos_args(self, model_id, config_cls, config_kwargs):
|
|
_skip_if_not_conv1d_supported(model_id, config_cls)
|
|
self._test_generate_pos_args(model_id, config_cls, config_kwargs.copy(), raises_err=False)
|
|
|
|
@pytest.mark.parametrize("model_id", PEFT_DECODER_MODELS_TO_TEST)
|
|
@pytest.mark.parametrize("config_cls,config_kwargs", ALL_CONFIGS)
|
|
def test_merge_layers_fp16(self, model_id, config_cls, config_kwargs):
|
|
config_kwargs = config_kwargs.copy()
|
|
check_beft_config(config_cls, model_id, config_kwargs)
|
|
self._test_merge_layers_fp16(model_id, config_cls, config_kwargs.copy())
|
|
|
|
@pytest.mark.parametrize("model_id", PEFT_DECODER_MODELS_TO_TEST)
|
|
@pytest.mark.parametrize("config_cls,config_kwargs", ALL_CONFIGS)
|
|
def test_generate_half_prec(self, model_id, config_cls, config_kwargs):
|
|
self._test_generate_half_prec(model_id, config_cls, config_kwargs.copy())
|
|
|
|
@pytest.mark.parametrize("model_id", PEFT_DECODER_MODELS_TO_TEST)
|
|
@pytest.mark.parametrize("config_cls,config_kwargs", ALL_CONFIGS)
|
|
def test_training_decoders(self, model_id, config_cls, config_kwargs):
|
|
_skip_if_not_conv1d_supported(model_id, config_cls)
|
|
self._test_training(model_id, config_cls, config_kwargs.copy())
|
|
|
|
@pytest.mark.parametrize("model_id", PEFT_DECODER_MODELS_TO_TEST)
|
|
@pytest.mark.parametrize("config_cls,config_kwargs", ALL_CONFIGS)
|
|
def test_training_decoders_layer_indexing(self, model_id, config_cls, config_kwargs):
|
|
self._test_training_layer_indexing(model_id, config_cls, config_kwargs.copy())
|
|
|
|
@pytest.mark.parametrize("model_id", PEFT_DECODER_MODELS_TO_TEST)
|
|
@pytest.mark.parametrize("config_cls,config_kwargs", ALL_CONFIGS)
|
|
@pytest.mark.parametrize("use_reentrant", [True, False])
|
|
def test_training_decoders_gradient_checkpointing(self, model_id, config_cls, config_kwargs, use_reentrant):
|
|
_skip_if_not_conv1d_supported(model_id, config_cls)
|
|
self._test_training_gradient_checkpointing(
|
|
model_id, config_cls, config_kwargs.copy(), use_reentrant=use_reentrant
|
|
)
|
|
|
|
@pytest.mark.parametrize("model_id", PEFT_DECODER_MODELS_TO_TEST)
|
|
@pytest.mark.parametrize("config_cls,config_kwargs", ALL_CONFIGS)
|
|
def test_inference_safetensors(self, model_id, config_cls, config_kwargs):
|
|
_skip_if_not_conv1d_supported(model_id, config_cls)
|
|
self._test_inference_safetensors(model_id, config_cls, config_kwargs.copy())
|
|
|
|
@pytest.mark.parametrize("model_id", PEFT_DECODER_MODELS_TO_TEST)
|
|
@pytest.mark.parametrize("config_cls,config_kwargs", ALL_CONFIGS)
|
|
def test_peft_model_device_map(self, model_id, config_cls, config_kwargs):
|
|
self._test_peft_model_device_map(model_id, config_cls, config_kwargs.copy())
|
|
|
|
@pytest.mark.parametrize("model_id", PEFT_DECODER_MODELS_TO_TEST)
|
|
@pytest.mark.parametrize("config_cls,config_kwargs", ALL_CONFIGS)
|
|
def test_delete_adapter(self, model_id, config_cls, config_kwargs):
|
|
_skip_if_not_conv1d_supported(model_id, config_cls)
|
|
self._test_delete_adapter(model_id, config_cls, config_kwargs.copy())
|
|
|
|
@pytest.mark.parametrize("model_id", PEFT_DECODER_MODELS_TO_TEST)
|
|
@pytest.mark.parametrize("config_cls,config_kwargs", ALL_CONFIGS)
|
|
def test_delete_inactive_adapter(self, model_id, config_cls, config_kwargs):
|
|
_skip_if_not_conv1d_supported(model_id, config_cls)
|
|
self._test_delete_inactive_adapter(model_id, config_cls, config_kwargs.copy())
|
|
|
|
@pytest.mark.parametrize("model_id", PEFT_DECODER_MODELS_TO_TEST)
|
|
@pytest.mark.parametrize("config_cls,config_kwargs", ALL_CONFIGS)
|
|
def test_adding_multiple_adapters_with_bias_raises(self, model_id, config_cls, config_kwargs):
|
|
_skip_if_not_conv1d_supported(model_id, config_cls)
|
|
self._test_adding_multiple_adapters_with_bias_raises(model_id, config_cls, config_kwargs.copy())
|
|
|
|
@pytest.mark.parametrize("model_id", PEFT_DECODER_MODELS_TO_TEST)
|
|
@pytest.mark.parametrize("config_cls,config_kwargs", ALL_CONFIGS)
|
|
def test_unload_adapter(self, model_id, config_cls, config_kwargs):
|
|
_skip_if_not_conv1d_supported(model_id, config_cls)
|
|
_skip_alora_no_activation(config_cls, config_kwargs)
|
|
config_kwargs = set_init_weights_false(config_cls, config_kwargs)
|
|
self._test_unload_adapter(model_id, config_cls, config_kwargs.copy())
|
|
|
|
@pytest.mark.parametrize("model_id", PEFT_DECODER_MODELS_TO_TEST)
|
|
@pytest.mark.parametrize("config_cls,config_kwargs", ALL_CONFIGS)
|
|
def test_weighted_combination_of_adapters(self, model_id, config_cls, config_kwargs):
|
|
config_kwargs = set_init_weights_false(config_cls, config_kwargs)
|
|
self._test_weighted_combination_of_adapters(model_id, config_cls, config_kwargs.copy())
|
|
|
|
@pytest.mark.parametrize("model_id", PEFT_DECODER_MODELS_TO_TEST)
|
|
@pytest.mark.parametrize("config_cls,config_kwargs", ALL_CONFIGS)
|
|
def test_training_prompt_learning_tasks(self, model_id, config_cls, config_kwargs):
|
|
self._test_training_prompt_learning_tasks(model_id, config_cls, config_kwargs.copy())
|
|
|
|
@pytest.mark.parametrize("model_id", PEFT_DECODER_MODELS_TO_TEST)
|
|
@pytest.mark.parametrize("config_cls,config_kwargs", ALL_CONFIGS)
|
|
def test_disable_adapter(self, model_id, config_cls, config_kwargs):
|
|
_skip_if_not_conv1d_supported(model_id, config_cls)
|
|
_skip_alora_no_activation(config_cls, config_kwargs)
|
|
_skip_osf_disable_adapter_test(config_cls)
|
|
config_kwargs = set_init_weights_false(config_cls, config_kwargs)
|
|
self._test_disable_adapter(model_id, config_cls, config_kwargs.copy())
|
|
|
|
def test_generate_adalora_no_dropout(self):
|
|
# test for issue #730
|
|
model_id = "peft-internal-testing/tiny-random-OPTForCausalLM"
|
|
config_kwargs = {
|
|
"target_modules": None,
|
|
"task_type": "CAUSAL_LM",
|
|
"lora_dropout": 0.0,
|
|
"total_step": 1,
|
|
}
|
|
self._test_generate(model_id, AdaLoraConfig, config_kwargs.copy())
|
|
|
|
@pytest.mark.parametrize("model_id", PEFT_DECODER_MODELS_TO_TEST)
|
|
@pytest.mark.parametrize("config_cls,config_kwargs", ALL_CONFIGS)
|
|
def test_passing_input_embeds_works(self, model_id, config_cls, config_kwargs):
|
|
_skip_if_not_conv1d_supported(model_id, config_cls)
|
|
if (platform.system() == "Darwin") and (config_cls == PrefixTuningConfig):
|
|
# the error is:
|
|
# > RuntimeError: unsupported operation: more than one element of the written-to tensor refers to a single
|
|
# > memory location. Please clone() the tensor before performing the operation.
|
|
# in transformers sdpa_mask_older_torch. As we (currently) cannot upgrade PyTorch on MacOS GH runners, we're
|
|
# stuck with this error.
|
|
# TODO: remove if torch can be upgraded on MacOS or if MacOS CI is removed
|
|
pytest.skip("Prefix tuning fails on MacOS in this case, not worth fixing")
|
|
self._test_passing_input_embeds_works("", model_id, config_cls, config_kwargs.copy())
|
|
|
|
def test_lora_layer_replication(self):
|
|
model_id = "trl-internal-testing/tiny-random-LlamaForCausalLM"
|
|
config_kwargs = {
|
|
"target_modules": ["down_proj", "up_proj"],
|
|
"task_type": "CAUSAL_LM",
|
|
"lora_dropout": 0.0,
|
|
"layer_replication": [[0, 1], [0, 2], [1, 2]],
|
|
}
|
|
model = self.transformers_class.from_pretrained(model_id).to(self.torch_device)
|
|
config = LoraConfig(base_model_name_or_path=model_id, **config_kwargs)
|
|
|
|
assert len(model.model.layers) == 2, "Expected 2 layers in original model."
|
|
|
|
model = get_peft_model(model, config)
|
|
layers = model.base_model.model.model.layers
|
|
assert len(layers) == 4, "Expected 4 layers in adapted model."
|
|
assert (
|
|
layers[0].mlp.up_proj.base_layer.weight.data.storage().data_ptr()
|
|
== layers[1].mlp.up_proj.base_layer.weight.data.storage().data_ptr()
|
|
and layers[2].mlp.up_proj.base_layer.weight.data.storage().data_ptr()
|
|
== layers[3].mlp.up_proj.base_layer.weight.data.storage().data_ptr()
|
|
), "Expected layers 0-1 and 2-3 to share weights"
|
|
assert (
|
|
layers[0].mlp.up_proj.base_layer.weight.data.storage().data_ptr()
|
|
!= layers[2].mlp.up_proj.base_layer.weight.data.storage().data_ptr()
|
|
), "Expected layers 0 and 2 to have different weights"
|
|
assert (
|
|
layers[0].mlp.up_proj.lora_A.default.weight.data.storage().data_ptr()
|
|
!= layers[1].mlp.up_proj.lora_A.default.weight.data.storage().data_ptr()
|
|
and layers[2].mlp.up_proj.lora_A.default.weight.data.storage().data_ptr()
|
|
!= layers[3].mlp.up_proj.lora_A.default.weight.data.storage().data_ptr()
|
|
), "Expected all LoRA adapters to have distinct weights"
|
|
assert len([n for n, _ in model.named_parameters() if ".lora_A." in n]) == 8, (
|
|
"Expected 8 LoRA adapters since we are adding one each for up and down."
|
|
)
|
|
self._test_prepare_for_training(model_id, LoraConfig, config_kwargs.copy())
|
|
self._test_generate(model_id, LoraConfig, config_kwargs.copy())
|
|
|
|
def test_prefix_tuning_qwen2_with_grouped_query_attention(self):
|
|
# See 1901, fixes a bug with handling GQA
|
|
model_id = "peft-internal-testing/tiny-dummy-qwen2"
|
|
with hub_online_once(model_id):
|
|
base_model = AutoModelForCausalLM.from_pretrained(model_id)
|
|
peft_config = PrefixTuningConfig(num_virtual_tokens=10, task_type="CAUSAL_LM")
|
|
model = get_peft_model(base_model, peft_config)
|
|
x = torch.tensor([[1, 2, 3]])
|
|
# does not raise
|
|
model(x)
|
|
|
|
def test_prefix_tuning_qwen3_with_grouped_query_attention(self):
|
|
# See 2881, fixes a bug with handling GQA
|
|
model_id = "trl-internal-testing/tiny-Qwen3ForCausalLM"
|
|
with hub_online_once(model_id):
|
|
base_model = AutoModelForCausalLM.from_pretrained(model_id)
|
|
peft_config = PrefixTuningConfig(num_virtual_tokens=10, task_type="CAUSAL_LM")
|
|
model = get_peft_model(base_model, peft_config)
|
|
x = torch.tensor([[1, 2, 3]])
|
|
# does not raise
|
|
model(x)
|
|
|
|
def test_prefix_tuning_offsets_position_ids_in_forward(self, monkeypatch):
|
|
# Regression: RoPE models need position_ids offset for prefix tuning.
|
|
model_id = "trl-internal-testing/tiny-random-LlamaForCausalLM"
|
|
with hub_online_once(model_id):
|
|
base = AutoModelForCausalLM.from_pretrained(model_id)
|
|
peft_config = PrefixTuningConfig(num_virtual_tokens=4, task_type="CAUSAL_LM", prefix_projection=False)
|
|
model = get_peft_model(base, peft_config)
|
|
|
|
captured = {}
|
|
|
|
def fake_forward(*args, **kwargs):
|
|
captured["position_ids"] = kwargs.get("position_ids")
|
|
input_ids = kwargs.get("input_ids")
|
|
if input_ids is None and args:
|
|
input_ids = args[0]
|
|
batch, seq_len = input_ids.shape
|
|
logits = torch.zeros((batch, seq_len, base.config.vocab_size), device=input_ids.device)
|
|
return CausalLMOutputWithPast(logits=logits)
|
|
|
|
monkeypatch.setattr(model.base_model, "forward", fake_forward)
|
|
|
|
input_ids = torch.randint(0, base.config.vocab_size, (1, 3))
|
|
position_ids = torch.arange(input_ids.shape[1]).unsqueeze(0)
|
|
_ = model(input_ids=input_ids, position_ids=position_ids)
|
|
|
|
assert captured["position_ids"] is not None
|
|
assert torch.equal(captured["position_ids"], position_ids + peft_config.num_virtual_tokens)
|
|
|
|
def test_prefix_tuning_mistral(self):
|
|
# See issue 869, 1962
|
|
_, device_count, _ = get_backend()
|
|
if device_count > 1:
|
|
pytest.skip("PEFT Mistral training with DP does not work, skipping")
|
|
|
|
model_id = "hf-internal-testing/tiny-random-MistralForCausalLM"
|
|
base_model = AutoModelForCausalLM.from_pretrained(model_id)
|
|
peft_config = PrefixTuningConfig(num_virtual_tokens=10, task_type="CAUSAL_LM")
|
|
model = get_peft_model(base_model, peft_config)
|
|
|
|
tokenizer = AutoTokenizer.from_pretrained(model_id)
|
|
tokenizer.pad_token = tokenizer.eos_token
|
|
|
|
def process(samples):
|
|
tokenized = tokenizer(samples["quote"], truncation=True, max_length=128)
|
|
return tokenized
|
|
|
|
data = load_dataset_english_quotes()
|
|
data = data.map(process, batched=True)
|
|
|
|
with tempfile.TemporaryDirectory() as tmp_dirname:
|
|
trainer = Trainer(
|
|
model=model,
|
|
train_dataset=data["train"],
|
|
args=TrainingArguments(
|
|
num_train_epochs=1,
|
|
max_steps=5,
|
|
per_device_train_batch_size=4,
|
|
output_dir=tmp_dirname,
|
|
),
|
|
data_collator=DataCollatorForLanguageModeling(tokenizer, mlm=False),
|
|
)
|
|
trainer.train()
|
|
|
|
@pytest.mark.parametrize("model_id", SMALL_GRID_MODELS)
|
|
@pytest.mark.parametrize(
|
|
"config_cls,config_kwargs",
|
|
[
|
|
(
|
|
PromptTuningConfig,
|
|
{
|
|
"num_virtual_tokens": 10,
|
|
"task_type": "CAUSAL_LM",
|
|
},
|
|
),
|
|
(
|
|
PrefixTuningConfig,
|
|
{
|
|
"num_virtual_tokens": 10,
|
|
"task_type": "CAUSAL_LM",
|
|
},
|
|
),
|
|
(
|
|
CartridgeConfig,
|
|
{
|
|
"num_virtual_tokens": 10,
|
|
"num_frozen_tokens": 1,
|
|
"task_type": "CAUSAL_LM",
|
|
},
|
|
),
|
|
(
|
|
PromptEncoderConfig,
|
|
{
|
|
"num_virtual_tokens": 10,
|
|
"encoder_hidden_size": 32,
|
|
"task_type": "CAUSAL_LM",
|
|
},
|
|
),
|
|
(
|
|
CPTConfig,
|
|
{
|
|
"task_type": "CAUSAL_LM",
|
|
"cpt_token_ids": [0, 1, 2, 3, 4, 5, 6, 7], # Example token IDs for testing
|
|
"cpt_mask": [1, 1, 1, 1, 1, 1, 1, 1],
|
|
"cpt_tokens_type_mask": [1, 2, 2, 2, 3, 3, 4, 4],
|
|
},
|
|
),
|
|
],
|
|
)
|
|
def test_prompt_learning_with_gradient_checkpointing(self, model_id, config_cls, config_kwargs):
|
|
# See issue 869
|
|
# Test prompt learning methods with gradient checkpointing in a semi realistic setting.
|
|
# Prefix tuning does not work if the model uses the new caching implementation. In that case, a helpful error
|
|
# should be raised.
|
|
|
|
# skip if multi GPU, since this results in DataParallel usage by Trainer, which fails with "CUDA device
|
|
# assertion", breaking subsequent tests
|
|
if device_count > 1:
|
|
pytest.skip("Skip on multi-GPU setups")
|
|
peft_config = config_cls(base_model_name_or_path=model_id, **config_kwargs)
|
|
base_model = self.transformers_class.from_pretrained(model_id)
|
|
base_model.gradient_checkpointing_enable()
|
|
|
|
try:
|
|
model = get_peft_model(base_model, peft_config)
|
|
except ValueError as exc:
|
|
# Some methods will raise a helpful error. After this, exit the test, as training would fail.
|
|
assert config_cls in (PrefixTuningConfig, CartridgeConfig)
|
|
assert "does not work with gradient checkpointing" in str(exc)
|
|
return
|
|
|
|
tokenizer = AutoTokenizer.from_pretrained(model_id)
|
|
tokenizer.pad_token = tokenizer.eos_token
|
|
|
|
def process(samples):
|
|
tokenized = tokenizer(samples["quote"], truncation=True, max_length=128)
|
|
return tokenized
|
|
|
|
data = load_dataset_english_quotes()
|
|
data = data.map(process, batched=True)
|
|
|
|
with tempfile.TemporaryDirectory() as tmp_dirname:
|
|
trainer = Trainer(
|
|
model=model,
|
|
train_dataset=data["train"],
|
|
args=TrainingArguments(
|
|
num_train_epochs=1,
|
|
max_steps=3,
|
|
per_device_train_batch_size=4,
|
|
output_dir=tmp_dirname,
|
|
),
|
|
data_collator=DataCollatorForLanguageModeling(tokenizer, mlm=False),
|
|
)
|
|
trainer.train()
|
|
|
|
@pytest.mark.parametrize("save_embedding_layers", ["auto", True, False])
|
|
@pytest.mark.parametrize(
|
|
"peft_config",
|
|
[
|
|
(LoraConfig(target_modules=["lin0", "embed_tokens"], init_lora_weights=False)),
|
|
(LoraConfig(target_modules=r".*\.embed_tokens", init_lora_weights=False)),
|
|
],
|
|
)
|
|
def test_save_pretrained_targeting_lora_to_embedding_layer(self, save_embedding_layers, tmp_path, peft_config):
|
|
model_id = "trl-internal-testing/tiny-random-LlamaForCausalLM"
|
|
|
|
with hub_online_once(model_id):
|
|
model = AutoModelForCausalLM.from_pretrained(model_id)
|
|
model = get_peft_model(model, peft_config)
|
|
|
|
if save_embedding_layers == "auto":
|
|
# assert warning
|
|
msg_start = "Setting `save_embedding_layers` to `True` as embedding layers found in `target_modules`."
|
|
with pytest.warns(UserWarning, match=msg_start):
|
|
model.save_pretrained(tmp_path, save_embedding_layers=save_embedding_layers)
|
|
else:
|
|
model.save_pretrained(tmp_path, save_embedding_layers=save_embedding_layers)
|
|
|
|
state_dict = safe_load_file(tmp_path / "adapter_model.safetensors")
|
|
contains_embedding = "base_model.model.model.embed_tokens.base_layer.weight" in state_dict
|
|
|
|
if save_embedding_layers in ["auto", True]:
|
|
assert contains_embedding
|
|
assert torch.allclose(
|
|
model.base_model.model.model.embed_tokens.base_layer.weight,
|
|
state_dict["base_model.model.model.embed_tokens.base_layer.weight"],
|
|
)
|
|
else:
|
|
assert not contains_embedding
|
|
|
|
@pytest.mark.parametrize("use_dora", [False, True])
|
|
def test_lora_embed_scale_is_applied(self, use_dora):
|
|
"""Test that LoRA correctly handles embeddings with scaling (e.g., Gemma3)."""
|
|
model_id = "hf-internal-testing/tiny-random-Gemma3ForCausalLM"
|
|
with hub_online_once(model_id):
|
|
base_model = AutoModelForCausalLM.from_pretrained(model_id).to(self.torch_device)
|
|
orig_embedding = base_model.get_input_embeddings()
|
|
|
|
peft_config = LoraConfig(target_modules=["embed_tokens"], init_lora_weights=False, use_dora=use_dora)
|
|
peft_model = get_peft_model(base_model, peft_config)
|
|
|
|
x = torch.arange(10).to(self.torch_device)
|
|
peft_embedding = peft_model.base_model.model.get_input_embeddings()
|
|
embedding_output = peft_embedding(x)
|
|
max_embedding_output = embedding_output.abs().max(0)[0]
|
|
assert (max_embedding_output < 100.0).all()
|
|
peft_model.merge_adapter()
|
|
embedding_merged = peft_embedding(x)
|
|
assert torch.allclose(embedding_output, embedding_merged, atol=1e-5, rtol=1e-5)
|
|
peft_model.unmerge_adapter()
|
|
|
|
# set embed_scale to an absurdly high value, then check that the embedding output is also scaled to a high
|
|
# value
|
|
orig_embedding.embed_scale.fill_(10000.0)
|
|
max_embedding_output = peft_embedding(x).abs().max(0)[0]
|
|
assert (max_embedding_output > 100.0).all()
|
|
|
|
# set embed_scale to zero, then check that the embedding output is also zero
|
|
orig_embedding.embed_scale.fill_(0)
|
|
embedding_output = peft_embedding(x)
|
|
assert (embedding_output == 0.0).all()
|
|
|
|
def test_lora_embed_scale_is_applied_mixed_batch(self):
|
|
"""Test that LoRA correctly handles embeddings with scaling in mixed batch mode."""
|
|
model_id = "hf-internal-testing/tiny-random-Gemma3ForCausalLM"
|
|
with hub_online_once(model_id):
|
|
base_model = AutoModelForCausalLM.from_pretrained(model_id)
|
|
orig_embedding = base_model.get_input_embeddings()
|
|
|
|
peft_config = LoraConfig(target_modules=["embed_tokens"], init_lora_weights=False)
|
|
peft_model = get_peft_model(base_model, peft_config)
|
|
peft_model.add_adapter("adapter2", peft_config)
|
|
|
|
# sanity check: with the default embed_scale, the embedding output should be reasonably sized
|
|
peft_embedding = peft_model.base_model.model.get_input_embeddings()
|
|
input_ids = torch.arange(10).unsqueeze(0).repeat(2, 1)
|
|
adapter_names = ["default", "adapter2"]
|
|
max_embedding_output = peft_embedding(input_ids, adapter_names=adapter_names).abs().max()
|
|
assert max_embedding_output < 100.0
|
|
|
|
# set embed_scale to an absurdly high value, then check that the embedding output is also scaled to a high
|
|
# value
|
|
orig_embedding.embed_scale.fill_(10000.0)
|
|
max_embedding_output = peft_embedding(input_ids, adapter_names=adapter_names).abs().max()
|
|
assert max_embedding_output > 100.0
|
|
|
|
# set embed_scale to zero, then check that the embedding output is also zero
|
|
orig_embedding.embed_scale.fill_(0)
|
|
embedding_output = peft_embedding(input_ids, adapter_names=adapter_names)
|
|
assert (embedding_output == 0.0).all()
|
|
|
|
@pytest.mark.parametrize("config_cls,config_kwargs", ALL_CONFIGS)
|
|
def test_set_requires_grad_prompt_learning_raises(self, config_cls, config_kwargs):
|
|
# Test that for prompt learning, calling set_requires_grad raises an error with an appropriate error message.
|
|
# Note that for non-prompt learning methods, set_requires_grad is being tested for custom models, so there is no
|
|
# specific test here.
|
|
model_id = PEFT_DECODER_MODELS_TO_TEST[0] # it's enough to test this with one model
|
|
config = config_cls(
|
|
base_model_name_or_path=model_id,
|
|
**config_kwargs,
|
|
)
|
|
if not config.is_prompt_learning:
|
|
pytest.skip("This test is only for prompt learning methods.")
|
|
|
|
with hub_online_once(model_id + config_kwargs.get("tokenizer_name_or_path", "")):
|
|
model = self.transformers_class.from_pretrained(model_id).to(self.torch_device)
|
|
model = get_peft_model(model, config)
|
|
msg = "Setting `requires_grad` is not supported for prompt learning methods like"
|
|
with pytest.raises(TypeError, match=msg):
|
|
model.set_requires_grad(adapter_names="adpater0")
|
|
|
|
@pytest.mark.parametrize("model_id", PEFT_DECODER_MODELS_TO_TEST)
|
|
@pytest.mark.parametrize("config_cls,config_kwargs", ALL_CONFIGS)
|
|
def test_lora_conversion(self, model_id, config_cls, config_kwargs):
|
|
# Test for the ability to convert a PEFT adapter into a LoRA adapter (if the adapter supports it). It's not
|
|
# necessary to run this with all model types, only checking decoder models.
|
|
_skip_if_not_conv1d_supported(model_id, config_cls)
|
|
if config_kwargs.get("alora_invocation_tokens"):
|
|
# very large conversion error, not sure why
|
|
pytest.skip("Skipping LoRA conversion for aLoRA.")
|
|
|
|
config_kwargs = set_init_weights_false(config_cls, config_kwargs)
|
|
self._test_lora_conversion(model_id, config_cls, config_kwargs)
|
|
|
|
def test_merge_and_unload_fixes_tie_word_embeddings_config(self):
|
|
# See https://github.com/huggingface/transformers/issues/45127
|
|
model_id = "trl-internal-testing/tiny-random-LlamaForCausalLM"
|
|
with hub_online_once(model_id):
|
|
model = AutoModelForCausalLM.from_pretrained(model_id, tie_word_embeddings=True)
|
|
assert model.config.tie_word_embeddings
|
|
|
|
peft_model = get_peft_model(model, LoraConfig(target_modules=["embed_tokens"], init_lora_weights=False))
|
|
|
|
with pytest.warns(UserWarning, match="Setting.*tie_word_embeddings"):
|
|
merged = peft_model.merge_and_unload()
|
|
|
|
assert not merged.config.tie_word_embeddings
|
|
assert merged.lm_head.weight is not merged.model.embed_tokens.weight
|
|
assert merged.lm_head.weight.data_ptr() != merged.model.embed_tokens.weight.data_ptr()
|
|
|
|
def test_prefix_tuning_gemma4_works(self):
|
|
# see #3201
|
|
# The issue was that head dim differs depending on whether sliding window attention is being used or not:
|
|
# https://github.com/huggingface/transformers/blob/223fe5231b783fbfb25296bb0a243dad5d158cde/src/transformers/models/gemma4/modeling_gemma4.py#L1147
|
|
# Prefix tuning could deal with different sizes, resulting in a size error
|
|
|
|
model_id = "peft-internal-testing/tiny-random-gemma4-E2B"
|
|
with hub_online_once(model_id):
|
|
model = AutoModelForCausalLM.from_pretrained(
|
|
model_id,
|
|
dtype=torch.bfloat16,
|
|
).to(self.torch_device)
|
|
config = PrefixTuningConfig(
|
|
task_type=TaskType.CAUSAL_LM,
|
|
num_virtual_tokens=20,
|
|
prefix_projection=False,
|
|
)
|
|
model = get_peft_model(model, config)
|
|
|
|
inputs = torch.arange(10).view(1, -1).to(self.torch_device)
|
|
model(inputs) # does not raise
|
|
|
|
# do mini training run
|
|
torch.manual_seed(0)
|
|
labels = torch.ones_like(inputs)
|
|
optim = torch.optim.SGD(model.parameters(), lr=100.0)
|
|
losses = []
|
|
for _ in range(5):
|
|
optim.zero_grad()
|
|
outputs = model(inputs, labels=labels)
|
|
loss = outputs.loss
|
|
loss.backward()
|
|
optim.step()
|
|
losses.append(loss)
|
|
|
|
assert torch.isfinite(loss)
|
|
assert not torch.isclose(losses[0], losses[-1], atol=1e-6, rtol=1e-3)
|
|
|
|
def test_prefix_tuning_gemma4_warns_if_some_layers_skipped(self):
|
|
# See previous test_prefix_tuning_gemma4_works. When the embedding matrix is too small to fit any layer targeted
|
|
# by prefix tuning, raise an error
|
|
model_id = "peft-internal-testing/tiny-random-gemma4-E2B"
|
|
with hub_online_once(model_id):
|
|
model = AutoModelForCausalLM.from_pretrained(
|
|
model_id,
|
|
dtype=torch.bfloat16,
|
|
).to(self.torch_device)
|
|
config = PrefixTuningConfig(
|
|
task_type=TaskType.CAUSAL_LM,
|
|
num_virtual_tokens=20,
|
|
prefix_projection=False,
|
|
)
|
|
text_config = model.config.get_text_config()
|
|
text_config.num_kv_shared_layers = 1 # set to lower value (was 2)
|
|
model = get_peft_model(model, config)
|
|
|
|
inputs = torch.arange(10).view(1, -1).to(self.torch_device)
|
|
with pytest.warns(UserWarning, match=r"skipped \[.*\] due to KV shape"):
|
|
model(inputs)
|
|
|
|
def test_prefix_tuning_gemma4_raises_if_all_layers_skipped(self):
|
|
# See previous test_prefix_tuning_gemma4_works. When the embedding matrix is too small to fit any layer targeted
|
|
# by prefix tuning, raise an error
|
|
model_id = "peft-internal-testing/tiny-random-gemma4-E2B"
|
|
with hub_online_once(model_id):
|
|
model = AutoModelForCausalLM.from_pretrained(
|
|
model_id,
|
|
dtype=torch.bfloat16,
|
|
).to(self.torch_device)
|
|
config = PrefixTuningConfig(
|
|
task_type=TaskType.CAUSAL_LM,
|
|
num_virtual_tokens=20,
|
|
prefix_projection=False,
|
|
)
|
|
model = get_peft_model(model, config)
|
|
text_config = model.config.get_text_config()
|
|
text_config.num_key_value_heads = 999
|
|
|
|
inputs = torch.arange(10).view(1, -1).to(self.torch_device)
|
|
with pytest.raises(ValueError, match="skipped every layer"):
|
|
model(inputs)
|