Repository: huggingface/peft

Issue:
GPT2 Models Merge and Unload
Hello again, do you think about merging for gpt2 models? It would be great if you could do it. When we can't test new models (Alpaca etc), we have to use the old ones (GPT-2). Do you have another method ? I wish you a good work

```

from peft import PeftModel, PeftConfig

peft_model_id = "/content/PEFT/V_1.0/"

config = PeftConfig.from_pretrained(peft_model_id)

tokenizer = AutoTokenizer.from_pretrained(config.base_model_name_or_path)

model = AutoModelWithLMHead.from_pretrained(
    config.base_model_name_or_path, 
    #torchscript = True,
    #load_in_8bit=True,
    #device_map = "auto",
    use_cache = False
)
print("Base Model : ", model.device)

device = "cuda:0" if torch.cuda.is_available() else "cpu"
device = "cpu"

model = PeftModel.from_pretrained(model, peft_model_id)
print("Peft Model : " ,model.device)

if torch.__version__.startswith("2.0"): 
    print("Model Compile Using...")   
    model_comp = torch.compile(model)

model = model.merge_and_unload()

│ in <cell line: 1>:1                                                                              │
│                                                                                                  │
│ /usr/local/lib/python3.9/dist-packages/peft/tuners/lora.py:305 in merge_and_unload               │
│                                                                                                  │
│   302 │   │   as a standalone model.                                                             │
│   303 │   │   """                                                                                │
│   304 │   │   if getattr(self.config, "model_type", None) == "gpt2":                             │
│ ❱ 305 │   │   │   raise ValueError("GPT2 models are not supported for merging LORA layers")      │
│   306 │   │                                                                                      │
│   307 │   │   if getattr(self.model, "is_loaded_in_8bit", False):                                │
│   308 │   │   │   raise ValueError("Cannot merge LORA layers when the model is loaded in 8-bit   │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
ValueError: GPT2 models are not supported for merging LORA layers


Discussion:
This issue has been automatically marked as stale because it has not had recent activity. If you think this still needs to be addressed please comment on this thread.


Hi,

Can we have an update on this topic ?
At least some informations about how to implement it.

Thanks
can you please tell the estimated timelines for this?
This issue has been automatically marked as stale because it has not had recent activity. If you think this still needs to be addressed please comment on this thread.


Would be great with an update 👍 
I would appreciate this functionality as well. 

Resolving patch:
diff --git a/src/peft/mapping.py b/src/peft/mapping.py
--- a/src/peft/mapping.py
+++ b/src/peft/mapping.py
@@ -83,7 +83,10 @@ def get_peft_model(model: PreTrainedModel, peft_config: PeftConfig, adapter_name
         model ([`transformers.PreTrainedModel`]): Model to be wrapped.
         peft_config ([`PeftConfig`]): Configuration object containing the parameters of the Peft model.
     """
-    model_config = model.config.to_dict() if hasattr(model.config, "to_dict") else model.config
+    model_config = getattr(model, "config", {"model_type": "custom"})
+    if hasattr(model_config, "to_dict"):
+        model_config = model_config.to_dict()
+
     peft_config.base_model_name_or_path = model.__dict__.get("name_or_path", None)
 
     if peft_config.task_type not in MODEL_TYPE_TO_PEFT_MODEL_MAPPING.keys() and not isinstance(
diff --git a/src/peft/peft_model.py b/src/peft/peft_model.py
--- a/src/peft/peft_model.py
+++ b/src/peft/peft_model.py
@@ -102,7 +102,7 @@ class PeftModel(PushToHubMixin, torch.nn.Module):
     def __init__(self, model: PreTrainedModel, peft_config: PeftConfig, adapter_name: str = "default"):
         super().__init__()
         self.base_model = model
-        self.config = self.base_model.config
+        self.config = getattr(self.base_model, "config", {"model_type": "custom"})
         self.modules_to_save = None
         self.peft_config = {}
         self.active_adapter = adapter_name
@@ -302,7 +302,7 @@ def _prepare_model_for_gradient_checkpointing(self, model: PreTrainedModel):
         if not (getattr(model, "is_loaded_in_8bit", False) or getattr(model, "is_loaded_in_4bit", False)):
             if hasattr(model, "enable_input_require_grads"):
                 model.enable_input_require_grads()
-            else:
+            elif hasattr(model, "get_input_embeddings"):
 
                 def make_inputs_require_grad(module, input, output):
                     output.requires_grad_(True)
diff --git a/src/peft/tuners/lora.py b/src/peft/tuners/lora.py
--- a/src/peft/tuners/lora.py
+++ b/src/peft/tuners/lora.py
@@ -179,9 +179,16 @@ def __init__(self, model, config, adapter_name):
         self.peft_config = config
         self.add_adapter(adapter_name, self.peft_config[adapter_name])
 
+        # transformers models have a .config attribute, whose presence is assumed later on
+        if not hasattr(self, "config"):
+            self.config = {"model_type": "custom"}
+
     def add_adapter(self, adapter_name, config=None):
         if config is not None:
-            model_config = self.model.config.to_dict() if hasattr(self.model.config, "to_dict") else self.model.config
+            model_config = getattr(self.model, "config", {"model_type": "custom"})
+            if hasattr(model_config, "to_dict"):
+                model_config = model_config.to_dict()
+
             config = self._prepare_lora_config(config, model_config)
             self.peft_config[adapter_name] = config
         self._find_and_replace(adapter_name)
@@ -287,6 +294,7 @@ def _create_new_module(self, lora_config, adapter_name, target):
                 in_features, out_features = (
                     target.weight.ds_shape if hasattr(target.weight, "ds_shape") else target.weight.shape
                 )
+                kwargs["is_target_conv_1d_layer"] = True
                 if not kwargs["fan_in_fan_out"]:
                     warnings.warn(
                         "fan_in_fan_out is set to False but the target module is `Conv1D`. "
@@ -428,9 +436,6 @@ def _prepare_lora_config(peft_config, model_config):
         return peft_config
 
     def _unload_and_optionally_merge(self, merge=True):
-        if getattr(self.config, "model_type", None) == "gpt2":
-            raise ValueError("GPT2 models are not supported for merging LORA layers")
-
         if getattr(self.model, "is_loaded_in_8bit", False) or getattr(self.model, "is_loaded_in_4bit", False):
             raise ValueError("Cannot merge LORA layers when the model is loaded in 8-bit mode")
 
@@ -454,7 +459,10 @@ def _unload_and_optionally_merge(self, merge=True):
                     )
                 else:
                     bias = target.bias is not None
-                    new_module = torch.nn.Linear(target.in_features, target.out_features, bias=bias)
+                    if getattr(target, "is_target_conv_1d_layer", False):
+                        new_module = Conv1D(target.out_features, target.in_features)
+                    else:
+                        new_module = torch.nn.Linear(target.in_features, target.out_features, bias=bias)
                 if merge:
                     target.merge()
                 self._replace_module(parent, target_name, new_module, target)
@@ -749,6 +757,7 @@ def __init__(
         lora_alpha: int = 1,
         lora_dropout: float = 0.0,
         fan_in_fan_out: bool = False,  # Set this to True if the layer to replace stores weight like (fan_in, fan_out)
+        is_target_conv_1d_layer: bool = False,
         **kwargs,
     ):
         init_lora_weights = kwargs.pop("init_lora_weights", True)
@@ -765,6 +774,7 @@ def __init__(
         nn.Linear.reset_parameters(self)
         self.update_layer(adapter_name, r, lora_alpha, lora_dropout, init_lora_weights)
         self.active_adapter = adapter_name
+        self.is_target_conv_1d_layer = is_target_conv_1d_layer
 
     def merge(self):
         if self.active_adapter not in self.lora_A.keys():


Test patch:
diff --git a/tests/test_custom_models.py b/tests/test_custom_models.py
new file mode 100644
--- /dev/null
+++ b/tests/test_custom_models.py
@@ -0,0 +1,266 @@
+#!/usr/bin/env python3
+
+# coding=utf-8
+# 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 copy
+import unittest
+
+import torch
+from parameterized import parameterized
+from torch import nn
+from transformers.pytorch_utils import Conv1D
+
+from peft import LoraConfig, get_peft_model
+
+from .testing_common import PeftCommonTester
+
+
+# MLP is a vanilla FF network with only linear layers
+# EmbConv1D has an embedding and a Conv1D layer
+# Conv2D has a Conv2D layer
+TEST_CASES = [
+    ("Vanilla MLP 1", "MLP", LoraConfig, {"target_modules": "lin0"}),
+    ("Vanilla MLP 2", "MLP", LoraConfig, {"target_modules": ["lin0"]}),
+    ("Vanilla MLP 3", "MLP", LoraConfig, {"target_modules": ["lin1"]}),
+    ("Vanilla MLP 4", "MLP", LoraConfig, {"target_modules": ["lin0", "lin1"]}),
+    ("Embedding + transformers Conv1D 1", "EmbConv1D", LoraConfig, {"target_modules": ["conv1d"]}),
+    ("Embedding + transformers Conv1D 2", "EmbConv1D", LoraConfig, {"target_modules": ["emb"]}),
+    ("Embedding + transformers Conv1D 3", "EmbConv1D", LoraConfig, {"target_modules": ["emb", "conv1d"]}),
+    ("Conv2d 1", "Conv2d", LoraConfig, {"target_modules": ["conv2d"]}),
+    ("Conv2d 2", "Conv2d", LoraConfig, {"target_modules": ["conv2d", "lin0"]}),
+]
+
+
+class MLP(nn.Module):
+    def __init__(self):
+        super().__init__()
+        self.lin0 = nn.Linear(10, 20)
+        self.relu = nn.ReLU()
+        self.drop = nn.Dropout(0.5)
+        self.lin1 = nn.Linear(20, 2)
+        self.sm = nn.LogSoftmax(dim=-1)
+
+    def forward(self, X):
+        X = X.float()
+        X = self.lin0(X)
+        X = self.relu(X)
+        X = self.drop(X)
+        X = self.lin1(X)
+        X = self.sm(X)
+        return X
+
+
+class ModelEmbConv1D(nn.Module):
+    def __init__(self):
+        super().__init__()
+        self.emb = nn.Embedding(100, 5)
+        self.conv1d = Conv1D(1, 5)
+        self.relu = nn.ReLU()
+        self.flat = nn.Flatten()
+        self.lin0 = nn.Linear(10, 2)
+
+    def forward(self, X):
+        X = self.emb(X)
+        X = self.conv1d(X)
+        X = self.relu(X)
+        X = self.flat(X)
+        X = self.lin0(X)
+        return X
+
+
+class ModelConv2D(nn.Module):
+    def __init__(self):
+        super().__init__()
+        self.conv2d = nn.Conv2d(5, 10, 3)
+        self.relu = nn.ReLU()
+        self.flat = nn.Flatten()
+        self.lin0 = nn.Linear(10, 2)
+
+    def forward(self, X):
+        X = X.float().reshape(2, 5, 3, 3)
+        X = self.conv2d(X)
+        X = self.relu(X)
+        X = self.flat(X)
+        X = self.lin0(X)
+        return X
+
+
+class MockTransformerWrapper:
+    """Mock class to behave like a transformers model.
+
+    This is needed because the tests initialize the model by calling transformers_class.from_pretrained.
+
+    """
+
+    @classmethod
+    def from_pretrained(cls, model_id):
+        # set the seed so that from_pretrained always returns the same model
+        torch.manual_seed(0)
+
+        if model_id == "MLP":
+            return MLP()
+
+        if model_id == "EmbConv1D":
+            return ModelEmbConv1D()
+
+        if model_id == "Conv2d":
+            return ModelConv2D()
+
+        raise ValueError(f"model_id {model_id} not implemented")
+
+
+class PeftCustomModelTester(unittest.TestCase, PeftCommonTester):
+    """TODO"""
+
+    transformers_class = MockTransformerWrapper
+
+    def prepare_inputs_for_testing(self):
+        X = torch.arange(90).view(9, 10).to(self.torch_device)
+        return {"X": X}
+
+    @parameterized.expand(TEST_CASES)
+    def test_attributes_parametrized(self, test_name, model_id, config_cls, config_kwargs):
+        self._test_model_attr(model_id, config_cls, config_kwargs)
+
+    @parameterized.expand(TEST_CASES)
+    def test_adapter_name(self, test_name, model_id, config_cls, config_kwargs):
+        self._test_adapter_name(model_id, config_cls, config_kwargs)
+
+    @parameterized.expand(TEST_CASES)
+    def test_prepare_for_training_parametrized(self, test_name, model_id, config_cls, config_kwargs):
+        # This test does not work with custom models because it assumes that
+        # there is always a method get_input_embeddings that returns a layer
+        # which does not need updates. Instead, a new test is added below that
+        # checks that LoRA works as expected.
+        pass
+
+    @parameterized.expand(TEST_CASES)
+    def test_save_pretrained(self, test_name, model_id, config_cls, config_kwargs):
+        self._test_save_pretrained(model_id, config_cls, config_kwargs)
+
+    @parameterized.expand(TEST_CASES)
+    def test_from_pretrained_config_construction(self, test_name, model_id, config_cls, config_kwargs):
+        self._test_from_pretrained_config_construction(model_id, config_cls, config_kwargs)
+
+    @parameterized.expand(TEST_CASES)
+    def test_merge_layers(self, test_name, model_id, config_cls, config_kwargs):
+        # for embeddings, even with init_lora_weights=False, the LoRA embeddings weights are still initialized to
+        # perform the identity transform, thus the test would fail.
+        if config_kwargs["target_modules"] == ["emb"]:
+            return
+
+        config_kwargs = config_kwargs.copy()
+        config_kwargs["init_lora_weights"] = False
+        self._test_merge_layers(model_id, config_cls, config_kwargs)
+
+    @parameterized.expand(TEST_CASES)
+    def test_generate(self, test_name, model_id, config_cls, config_kwargs):
+        # Custom models do not (necessarily) have a generate method, so this test is not performed
+        pass
+
+    @parameterized.expand(TEST_CASES)
+    def test_generate_half_prec(self, test_name, model_id, config_cls, config_kwargs):
+        # Custom models do not (necessarily) have a generate method, so this test is not performed
+        pass
+
+    @parameterized.expand(TEST_CASES)
+    def test_training_customs(self, test_name, model_id, config_cls, config_kwargs):
+        self._test_training(model_id, config_cls, config_kwargs)
+
+    @parameterized.expand(TEST_CASES)
+    def test_training_customs_layer_indexing(self, test_name, model_id, config_cls, config_kwargs):
+        # At the moment, layer indexing only works when layer names conform to a specific pattern, which is not
+        # guaranteed here. Therefore, this test is not performed.
+        pass
+
+    @parameterized.expand(TEST_CASES)
+    def test_training_customs_gradient_checkpointing(self, test_name, model_id, config_cls, config_kwargs):
+        self._test_training_gradient_checkpointing(model_id, config_cls, config_kwargs)
+
+    @parameterized.expand(TEST_CASES)
+    def test_inference_safetensors(self, test_name, model_id, config_cls, config_kwargs):
+        self._test_inference_safetensors(model_id, config_cls, config_kwargs)
+
+    @parameterized.expand(TEST_CASES)
+    def test_peft_model_device_map(self, test_name, model_id, config_cls, config_kwargs):
+        self._test_peft_model_device_map(model_id, config_cls, config_kwargs)
+
+    @parameterized.expand(TEST_CASES)
+    def test_only_params_are_updated(self, test_name, model_id, config_cls, config_kwargs):
+        # An explicit test that when using LoRA on a custom model, only the LoRA parameters are updated during training
+        X = self.prepare_inputs_for_testing()
+        model = self.transformers_class.from_pretrained(model_id).to(self.torch_device)
+        config = config_cls(
+            base_model_name_or_path=model_id,
+            **config_kwargs,
+        )
+        model = get_peft_model(model, config)
+        model_before = copy.deepcopy(model)
+
+        model.train()
+        optimizer = torch.optim.SGD(model.parameters(), lr=0.5)
+
+        # train at least 3 steps for all parameters to be updated (probably this is required because of symmetry
+        # breaking of some LoRA layers that are initialized with constants)
+        for _ in range(3):
+            optimizer.zero_grad()
+            y_pred = model(**X)
+            loss = y_pred.sum()
+            loss.backward()
+            optimizer.step()
+
+        tol = 1e-4
+        params_before = dict(model_before.named_parameters())
+        params_after = dict(model.named_parameters())
+        self.assertEqual(params_before.keys(), params_after.keys())
+        for name, param_before in params_before.items():
+            param_after = params_after[name]
+            if "lora_" in name:
+                self.assertFalse(torch.allclose(param_before, param_after, atol=tol, rtol=tol))
+            else:
+                self.assertTrue(torch.allclose(param_before, param_after, atol=tol, rtol=tol))
+
+    @parameterized.expand(TEST_CASES)
+    def test_disable_adapters(self, test_name, model_id, config_cls, config_kwargs):
+        X = self.prepare_inputs_for_testing()
+        model = self.transformers_class.from_pretrained(model_id).to(self.torch_device)
+        config = config_cls(
+            base_model_name_or_path=model_id,
+            **config_kwargs,
+        )
+        model = get_peft_model(model, config)
+        model.eval()
+        outputs_before = model(**X)
+
+        model.train()
+        optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
+
+        # train at least 3 steps for all parameters to be updated (probably this is required because of symmetry
+        # breaking of some LoRA layers that are initialized with constants)
+        for _ in range(3):
+            optimizer.zero_grad()
+            y_pred = model(**X)
+            loss = y_pred.sum()
+            loss.backward()
+            optimizer.step()
+
+        model.eval()
+        outputs_after = model(**X)
+
+        with model.disable_adapter():
+            outputs_disabled = model(**X)
+
+        self.assertFalse(torch.allclose(outputs_before, outputs_after))
+        self.assertTrue(torch.allclose(outputs_before, outputs_disabled))
diff --git a/tests/test_decoder_models.py b/tests/test_decoder_models.py
--- a/tests/test_decoder_models.py
+++ b/tests/test_decoder_models.py
@@ -94,6 +94,7 @@ def test_from_pretrained_config_construction(self, test_name, model_id, config_c
             {
                 "model_ids": PEFT_DECODER_MODELS_TO_TEST,
                 "lora_kwargs": {"init_lora_weights": [False]},
+                "ia3_kwargs": {"init_ia3_weights": [False]},
                 "task_type": "CAUSAL_LM",
             },
         )
diff --git a/tests/test_encoder_decoder_models.py b/tests/test_encoder_decoder_models.py
--- a/tests/test_encoder_decoder_models.py
+++ b/tests/test_encoder_decoder_models.py
@@ -80,6 +80,7 @@ def test_from_pretrained_config_construction(self, test_name, model_id, config_c
             {
                 "model_ids": PEFT_ENCODER_DECODER_MODELS_TO_TEST,
                 "lora_kwargs": {"init_lora_weights": [False]},
+                "ia3_kwargs": {"init_ia3_weights": [False]},
                 "task_type": "SEQ_2_SEQ_LM",
             },
         )
diff --git a/tests/test_feature_extraction_models.py b/tests/test_feature_extraction_models.py
--- a/tests/test_feature_extraction_models.py
+++ b/tests/test_feature_extraction_models.py
@@ -97,6 +97,7 @@ def test_from_pretrained_config_construction(self, test_name, model_id, config_c
             {
                 "model_ids": PEFT_FEATURE_EXTRACTION_MODELS_TO_TEST,
                 "lora_kwargs": {"init_lora_weights": [False]},
+                "ia3_kwargs": {"init_ia3_weights": [False]},
                 "task_type": "FEATURE_EXTRACTION",
             },
         )
diff --git a/tests/testing_common.py b/tests/testing_common.py
--- a/tests/testing_common.py
+++ b/tests/testing_common.py
@@ -13,6 +13,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 import os
+import pickle
 import tempfile
 from collections import OrderedDict
 from dataclasses import replace
@@ -345,6 +346,12 @@ def _test_from_pretrained_config_construction(self, model_id, config_cls, config
             self.assertIs(model_from_pretrained.peft_config["default"], config)
 
     def _test_merge_layers(self, model_id, config_cls, config_kwargs):
+        if config_cls not in (LoraConfig, IA3Config):
+            # Merge layers only supported for LoRA and IA³
+            return
+        if ("gpt2" in model_id.lower()) and (config_cls != LoraConfig):
+            self.skipTest("Merging GPT2 adapters not supported for IA³ (yet)")
+
         model = self.transformers_class.from_pretrained(model_id)
         config = config_cls(
             base_model_name_or_path=model_id,
@@ -356,34 +363,34 @@ def _test_merge_layers(self, model_id, config_cls, config_kwargs):
         if config.peft_type not in ("IA3", "LORA"):
             with self.assertRaises(AttributeError):
                 model = model.merge_and_unload()
-        elif model.config.model_type == "gpt2":
-            with self.assertRaises(ValueError):
-                model = model.merge_and_unload()
-        else:
-            dummy_input = self.prepare_inputs_for_testing()
-            model.eval()
-            logits_unmerged = model(**dummy_input)[0]
 
-            model = model.merge_and_unload()
+        dummy_input = self.prepare_inputs_for_testing()
+        model.eval()
+        logits_unmerged = model(**dummy_input)[0]
 
-            logits_merged = model(**dummy_input)[0]
+        model = model.merge_and_unload()
+        logits_merged = model(**dummy_input)[0]
 
-            transformers_model = self.transformers_class.from_pretrained(model_id).to(self.torch_device)
+        self.assertTrue(torch.allclose(logits_unmerged, logits_merged, atol=1e-4, rtol=1e-4))
 
-            logits_transformers = transformers_model(**dummy_input)[0]
-
-            self.assertTrue(torch.allclose(logits_unmerged, logits_merged, atol=1e-4, rtol=1e-4))
-            if config_cls == LoraConfig:  # merge does not change logits for IA3
-                self.assertFalse(torch.allclose(logits_merged, logits_transformers, atol=1e-10, rtol=1e-10))
+        # For this test to work, init_lora_weights must be False. This ensures that weights are not initialized to
+        # the identity transform.
+        transformers_model = self.transformers_class.from_pretrained(model_id).to(self.torch_device)
+        logits_transformers = transformers_model(**dummy_input)[0]
+        self.assertFalse(torch.allclose(logits_merged, logits_transformers, atol=1e-10, rtol=1e-10))
 
+        # test that the logits are identical after a save-load-roundtrip
+        if hasattr(model, "save_pretrained"):
+            # model is a transformers model
             with tempfile.TemporaryDirectory() as tmp_dirname:
                 model.save_pretrained(tmp_dirname)
-
                 model_from_pretrained = self.transformers_class.from_pretrained(tmp_dirname).to(self.torch_device)
+        else:
+            # model is not a transformers model
+            model_from_pretrained = pickle.loads(pickle.dumps(model))
 
-                logits_merged_from_pretrained = model_from_pretrained(**dummy_input)[0]
-
-                self.assertTrue(torch.allclose(logits_merged, logits_merged_from_pretrained, atol=1e-4, rtol=1e-4))
+        logits_merged_from_pretrained = model_from_pretrained(**dummy_input)[0]
+        self.assertTrue(torch.allclose(logits_merged, logits_merged_from_pretrained, atol=1e-4, rtol=1e-4))
 
     def _test_generate(self, model_id, config_cls, config_kwargs):
         model = self.transformers_class.from_pretrained(model_id)
@@ -471,7 +478,6 @@ def _test_inference_safetensors(self, model_id, config_cls, config_kwargs):
 
         config = config_cls(
             base_model_name_or_path=model_id,
-            layers_to_transform=[0],
             **config_kwargs,
         )
         model = self.transformers_class.from_pretrained(model_id)
@@ -487,6 +493,10 @@ def _test_inference_safetensors(self, model_id, config_cls, config_kwargs):
         loss = output.sum()
         loss.backward()
 
+        # set to eval mode, since things like dropout can affect the output otherwise
+        model.eval()
+        logits = model(**inputs)[0][0]
+
         with tempfile.TemporaryDirectory() as tmp_dirname:
             model.save_pretrained(tmp_dirname, safe_serialization=True)
             self.assertTrue("adapter_model.safetensors" in os.listdir(tmp_dirname))
@@ -558,7 +568,7 @@ def _test_training_gradient_checkpointing(self, model_id, config_cls, config_kwa
 
         model = self.transformers_class.from_pretrained(model_id)
 
-        if not model.supports_gradient_checkpointing:
+        if not getattr(model, "supports_gradient_checkpointing", False):
             return
 
         model.gradient_checkpointing_enable()
@@ -675,9 +685,6 @@ def _test_unload_adapter(self, model_id, config_cls, config_kwargs):
         if config.peft_type not in ("LORA"):
             with self.assertRaises(AttributeError):
                 model = model.unload()
-        elif model.config.model_type == "gpt2":
-            with self.assertRaises(ValueError):
-                model = model.unload()
         else:
             dummy_input = self.prepare_inputs_for_testing()
             logits_with_lora = model(**dummy_input)[0]