Skip to content

Commit c220ea9

Browse files
authored
Fix load_adapter OOM caused by full-model warmup sizing (#46145)
* Fix load_adapter OOM caused by full-model warmup sizing load_adapter passed every named parameter on the model, including the base model, as expected_keys to _load_pretrained_model. Downstream, caching_allocator_warmup summed those into a full base-model byte count and issued a single same-size allocation on top of the already-resident base model, OOMing whenever the base model occupies more than ~half of GPU memory. The file already defined an is_adapter_key helper for identifying parameters belonging to the freshly-injected adapter, but it was declared after the _load_pretrained_model call. Hoist the helper above the call and apply it to expected_keys. Adds a regression test that captures the device map passed to caching_allocator_warmup during load_adapter and asserts it contains only adapter-owned parameter names, not base-model names. * Address review: use unittest.mock.patch and expand test docstring
1 parent e1a37d2 commit c220ea9

2 files changed

Lines changed: 63 additions & 10 deletions

File tree

src/transformers/integrations/peft.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -583,6 +583,13 @@ def load_adapter(
583583
# Create and add fresh new adapters into the model, unless the weights are hotswapped
584584
inject_adapter_in_model(peft_config, self, adapter_name)
585585

586+
adapter_key_markers = {adapter_name}
587+
if peft_config is not None and getattr(peft_config, "peft_type", None) is not None:
588+
adapter_key_markers.add(peft_config.peft_type.value.lower())
589+
590+
def is_adapter_key(key: str) -> bool:
591+
return any(marker in key for marker in adapter_key_markers)
592+
586593
if not self._hf_peft_config_loaded:
587594
self._hf_peft_config_loaded = True
588595

@@ -670,9 +677,9 @@ def load_adapter(
670677
state_dict=adapter_state_dict,
671678
checkpoint_files=checkpoint_files,
672679
load_config=load_config,
673-
# pass expected keys explicitly, otherwise they are determined from the state_dict, which can contain
674-
# unexpected entries, like "layer.SCB" from a bnb layer.
675-
expected_keys=[n for n, _ in self.named_parameters()],
680+
# Pass expected keys explicitly while excluding non-adapter parameters.
681+
# Otherwise `caching_allocator_warmup` sizes for the full base model.
682+
expected_keys=[n for n, _ in self.named_parameters() if is_adapter_key(n)],
676683
)
677684

678685
if peft_config.inference_mode:
@@ -683,13 +690,6 @@ def load_adapter(
683690
if isinstance(module, BaseTunerLayer):
684691
module.requires_grad_(False)
685692

686-
adapter_key_markers = {adapter_name}
687-
if peft_config is not None and getattr(peft_config, "peft_type", None) is not None:
688-
adapter_key_markers.add(peft_config.peft_type.value.lower())
689-
690-
def is_adapter_key(key: str) -> bool:
691-
return any(marker in key for marker in adapter_key_markers)
692-
693693
loading_info.missing_keys = {k for k in loading_info.missing_keys if is_adapter_key(k)}
694694

695695
log_state_dict_report(

tests/peft_integration/test_peft_integration.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import tempfile
2020
import unittest
2121
from pathlib import Path
22+
from unittest.mock import patch
2223

2324
from datasets import Dataset, DatasetDict
2425
from huggingface_hub import hf_hub_download
@@ -694,6 +695,58 @@ def test_peft_add_adapter_with_state_dict_low_cpu_mem_usage(self):
694695
# after loading, no meta device should be remaining
695696
self.assertFalse(any((p.device.type == "meta") for p in model.parameters()))
696697

698+
def test_peft_load_adapter_warmup_uses_adapter_expected_keys(self):
699+
"""
700+
Check that adapter loading only warms up memory for adapter parameters by capturing the device map passed to
701+
`caching_allocator_warmup`.
702+
703+
Note: this test depends on `_load_pretrained_model` calling `caching_allocator_warmup`; if that internal
704+
contract changes, update the test to keep checking the keys used for warmup sizing.
705+
"""
706+
from peft import LoraConfig
707+
708+
import transformers.modeling_utils as modeling_utils
709+
710+
adapter_name = "warmup_test_adapter"
711+
adapter_key_markers = (adapter_name, "lora")
712+
713+
for model_id in self.transformers_test_model_ids:
714+
for transformers_class in self.transformers_test_model_classes:
715+
model = transformers_class.from_pretrained(model_id).to(torch_device)
716+
717+
peft_config = LoraConfig()
718+
template_model = transformers_class.from_pretrained(model_id)
719+
template_model.add_adapter(LoraConfig(), adapter_name=adapter_name)
720+
dummy_state_dict = {
721+
name: torch.zeros_like(param)
722+
for name, param in template_model.named_parameters()
723+
if any(marker in name for marker in adapter_key_markers)
724+
}
725+
del template_model
726+
self.assertTrue(dummy_state_dict)
727+
728+
captured_device_maps = []
729+
730+
def capture_warmup(model, expanded_device_map, hf_quantizer):
731+
captured_device_maps.append(dict(expanded_device_map))
732+
733+
with patch.object(modeling_utils, "caching_allocator_warmup", side_effect=capture_warmup):
734+
with CaptureLogger(logging.get_logger("transformers.integrations.peft")):
735+
model.load_adapter(
736+
adapter_state_dict=dummy_state_dict,
737+
adapter_name=adapter_name,
738+
peft_config=peft_config,
739+
)
740+
741+
self.assertTrue(captured_device_maps)
742+
warmed_keys = set().union(*(device_map.keys() for device_map in captured_device_maps))
743+
self.assertTrue(warmed_keys)
744+
745+
unexpected_base_keys = [
746+
key for key in warmed_keys if not any(marker in key for marker in adapter_key_markers)
747+
]
748+
self.assertEqual(unexpected_base_keys, [])
749+
697750
def test_peft_from_pretrained_hub_kwargs(self):
698751
"""
699752
Tests different combinations of PEFT model + from_pretrained + hub kwargs

0 commit comments

Comments
 (0)