Repository: huggingface/peft

Issue:
'NoneType' object has no attribute 'shape'
### System Info

peft==0.5.0.dev0   python=3.8

### Who can help?

@pacman100  
在peft_model.py的1094行的代码中
batch_size = input_ids.shape[0]

如果用户直接输入inputs_embeds，而不是 input_ids，就会报错。建议改成
 batch_size = input_ids.shape[0] if input_ids else inputs_embeds.shape[0]

### Information

- [ ] The official example scripts
- [ ] My own modified scripts

### Tasks

- [ ] An officially supported task in the `examples` folder
- [ ] My own task or dataset (give details below)

### Reproduction

```python
import torch
from peft import LoraConfig, TaskType
from peft import PromptTuningConfig, PrefixTuningConfig, AdaptionPromptConfig
from peft import IA3Config, IA3Model
from peft import get_peft_model
from transformers import BertModel,BertConfig
config = BertConfig()
bert = BertModel(config)

peft_config = PrefixTuningConfig(task_type=TaskType.SEQ_2_SEQ_LM, inference_mode=False, num_virtual_tokens=20)

model = get_peft_model(bert, peft_config)
model.print_trainable_parameters()

inputs = torch.randn([1,5,768])
output = model(inputs_embeds=inputs)
```

### Expected behavior

 Just like transformers, I recommend that either input_ids or inputs_embeds should work，as follow:

batch_size = input_ids.shape[0] if input_ids else inputs_embeds.shape[0]


Discussion:

Resolving patch:
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
@@ -53,6 +53,7 @@
     PeftType,
     PromptLearningConfig,
     TaskType,
+    _get_batch_size,
     _prepare_prompt_learning_config,
     _set_adapter,
     _set_trainable,
@@ -769,7 +770,7 @@ def forward(
                 **kwargs,
             )
 
-        batch_size = input_ids.shape[0]
+        batch_size = _get_batch_size(input_ids, inputs_embeds)
         if attention_mask is not None:
             # concat prompt attention mask
             prefix_attention_mask = torch.ones(batch_size, peft_config.num_virtual_tokens).to(attention_mask.device)
@@ -816,7 +817,7 @@ def _prefix_tuning_forward(
         return_dict=None,
         **kwargs,
     ):
-        batch_size = input_ids.shape[0]
+        batch_size = _get_batch_size(input_ids, inputs_embeds)
         past_key_values = self.get_prompt(batch_size)
         fwd_params = list(inspect.signature(self.base_model.forward).parameters.keys())
         kwargs.update(
@@ -955,7 +956,7 @@ def forward(
                 **kwargs,
             )
 
-        batch_size = input_ids.shape[0]
+        batch_size = _get_batch_size(input_ids, inputs_embeds)
         if attention_mask is not None:
             # concat prompt attention mask
             prefix_attention_mask = torch.ones(batch_size, peft_config.num_virtual_tokens).to(attention_mask.device)
@@ -979,7 +980,9 @@ def forward(
 
         if peft_config.peft_type == PeftType.PREFIX_TUNING:
             past_key_values = self.get_prompt(batch_size)
-            return self.base_model(input_ids=input_ids, past_key_values=past_key_values, **kwargs)
+            return self.base_model(
+                input_ids=input_ids, inputs_embeds=inputs_embeds, past_key_values=past_key_values, **kwargs
+            )
         else:
             if inputs_embeds is None:
                 inputs_embeds = self.word_embeddings(input_ids)
@@ -1116,7 +1119,7 @@ def forward(
                 **kwargs,
             )
 
-        batch_size = input_ids.shape[0]
+        batch_size = _get_batch_size(input_ids, inputs_embeds)
         if decoder_attention_mask is not None:
             # concat prompt attention mask
             prefix_attention_mask = torch.ones(batch_size, peft_config.num_virtual_tokens).to(
@@ -1363,7 +1366,7 @@ def forward(
                 **kwargs,
             )
 
-        batch_size = input_ids.shape[0]
+        batch_size = _get_batch_size(input_ids, inputs_embeds)
         if attention_mask is not None:
             # concat prompt attention mask
             prefix_attention_mask = torch.ones(batch_size, peft_config.num_virtual_tokens).to(attention_mask.device)
@@ -1410,7 +1413,7 @@ def _prefix_tuning_forward(
         return_dict=None,
         **kwargs,
     ):
-        batch_size = input_ids.shape[0]
+        batch_size = _get_batch_size(input_ids, inputs_embeds)
         past_key_values = self.get_prompt(batch_size)
         fwd_params = list(inspect.signature(self.base_model.forward).parameters.keys())
         kwargs.update(
@@ -1537,7 +1540,7 @@ def forward(
                 **kwargs,
             )
 
-        batch_size = input_ids.shape[0]
+        batch_size = _get_batch_size(input_ids, inputs_embeds)
         if attention_mask is not None:
             # concat prompt attention mask
             prefix_attention_mask = torch.ones(batch_size, peft_config.num_virtual_tokens).to(attention_mask.device)
@@ -1586,7 +1589,7 @@ def _prefix_tuning_forward(
         return_dict=None,
         **kwargs,
     ):
-        batch_size = input_ids.shape[0]
+        batch_size = _get_batch_size(input_ids, inputs_embeds)
         past_key_values = self.get_prompt(batch_size)
         fwd_params = list(inspect.signature(self.base_model.forward).parameters.keys())
         kwargs.update(
@@ -1706,7 +1709,7 @@ def forward(
                 **kwargs,
             )
 
-        batch_size = input_ids.shape[0]
+        batch_size = _get_batch_size(input_ids, inputs_embeds)
         if attention_mask is not None:
             # concat prompt attention mask
             prefix_attention_mask = torch.ones(batch_size, peft_config.num_virtual_tokens).to(attention_mask.device)
diff --git a/src/peft/utils/__init__.py b/src/peft/utils/__init__.py
--- a/src/peft/utils/__init__.py
+++ b/src/peft/utils/__init__.py
@@ -36,6 +36,7 @@
     prepare_model_for_kbit_training,
     shift_tokens_right,
     transpose,
+    _get_batch_size,
     _get_submodules,
     _set_adapter,
     _freeze_adapter,
diff --git a/src/peft/utils/other.py b/src/peft/utils/other.py
--- a/src/peft/utils/other.py
+++ b/src/peft/utils/other.py
@@ -16,6 +16,7 @@
 import inspect
 import os
 import warnings
+from typing import Optional
 
 import accelerate
 import torch
@@ -296,6 +297,22 @@ def transpose(weight, fan_in_fan_out):
     return weight.T if fan_in_fan_out else weight
 
 
+def _get_batch_size(input_ids: Optional[torch.Tensor], inputs_embeds: Optional[torch.Tensor]) -> int:
+    """Get the batch size based on either input_ids or input_embeds
+
+    Raises an ValueError if both are None.
+
+    """
+    if (input_ids is None) and (inputs_embeds is None):
+        raise ValueError("You have to provide either input_ids or inputs_embeds")
+
+    if input_ids is not None:
+        batch_size = input_ids.shape[0]
+    else:
+        batch_size = inputs_embeds.shape[0]
+    return batch_size
+
+
 TRANSFORMERS_MODELS_TO_LORA_TARGET_MODULES_MAPPING = {
     "t5": ["q", "v"],
     "mt5": ["q", "v"],


Test patch:
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
@@ -198,3 +198,7 @@ def test_generate_adalora_no_dropout(self):
             "lora_dropout": 0.0,
         }
         self._test_generate(model_id, AdaLoraConfig, config_kwargs)
+
+    @parameterized.expand(PeftTestConfigManager.get_grid_parameters(FULL_GRID, filter_params_func=skip_non_pt_mqa))
+    def test_passing_input_embeds_works(self, test_name, model_id, config_cls, config_kwargs):
+        self._test_passing_input_embeds_works(test_name, model_id, config_cls, config_kwargs)
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
@@ -18,6 +18,8 @@
 from parameterized import parameterized
 from transformers import AutoModel
 
+from peft import PrefixTuningConfig, PromptLearningConfig
+
 from .testing_common import PeftCommonTester, PeftTestConfigManager
 
 
@@ -34,6 +36,13 @@
 }
 
 
+def skip_non_prompt_tuning(test_list):
+    """Skip tests that are not prompt tuning"""
+    return [
+        test for test in test_list if issubclass(test[2], PromptLearningConfig) and (test[2] != PrefixTuningConfig)
+    ]
+
+
 def skip_deberta_lora_tests(test_list):
     r"""
     Skip tests that are checkpointing with lora/ia3 tests for Deberta models (couldn't find much info on the error)
@@ -161,3 +170,9 @@ def test_unload_adapter(self, test_name, model_id, config_cls, config_kwargs):
     )
     def test_weighted_combination_of_adapters(self, test_name, model_id, config_cls, config_kwargs):
         self._test_weighted_combination_of_adapters(model_id, config_cls, config_kwargs)
+
+    @parameterized.expand(
+        PeftTestConfigManager.get_grid_parameters(FULL_GRID, filter_params_func=skip_non_prompt_tuning)
+    )
+    def test_passing_input_embeds_works(self, test_name, model_id, config_cls, config_kwargs):
+        self._test_passing_input_embeds_works(test_name, model_id, config_cls, config_kwargs)
diff --git a/tests/testing_common.py b/tests/testing_common.py
--- a/tests/testing_common.py
+++ b/tests/testing_common.py
@@ -853,3 +853,16 @@ def get_output(model):
             self.assertTrue(torch.allclose(output_before, output_peft_disabled, atol=1e-6, rtol=1e-6))
 
         # TODO: add tests to check if disabling adapters works after calling merge_adapter
+
+    def _test_passing_input_embeds_works(self, test_name, model_id, config_cls, config_kwargs):
+        # https://github.com/huggingface/peft/issues/727
+        model = self.transformers_class.from_pretrained(model_id)
+        config = config_cls(
+            base_model_name_or_path=model_id,
+            **config_kwargs,
+        )
+        model = get_peft_model(model, config, adapter_name="test-adapter").to(self.torch_device)
+        dummy_input = self.prepare_inputs_for_testing()
+        inputs_embeds = model.get_input_embeddings()(dummy_input["input_ids"])
+        # just check that no error is raised
+        model.forward(inputs_embeds=inputs_embeds)