PEFT documentation
Adapter 주입
Adapter 주입
PEFT를 사용하면 torch 모듈에 학습 가능한 어댑터를 삽입할 수 있으며, 이를 통해 PEFT의 모델링 클래스에 의존하지 않고도 어댑터 방법을 사용할 수 있습니다. 이는 프롬프트 학습 기반(예: prefix tuning 또는 p-tuning)인 어댑터와 여러 대상 레이어 간에 상태를 공유하는 어댑터를 제외한 모든 어댑터에 적용됩니다.
어떤 시점에 어댑터를 주입해야 하는지 확인하려면 아래 표를 참조하세요.
| 장점 | 단점 |
|---|---|
| inplace로 모델이 수정되며, 모든 원래 속성과 메서드가 유지됩니다 | Hugging Face의 from_pretrained와 save_pretrained 유틸리티 함수를 수동으로 작성하여 어댑터를 저장하고 로드합니다 |
모든 torch 모듈과 모달리티에 적용됩니다 | PeftModel에서 제공하는 어댑터 비활성화 및 병합과 같은 유틸리티 메서드와 함께 작동하지 않습니다 |
inject_adapter_in_model은 여러 대상 레이어 간에 어댑터 상태를 공유하는 PEFT 메서드를 지원하지 않습니다. 현재 TinyLoRA, UniLoRA, VeRA, PVeRA, VBLoRA, FRoD가 해당됩니다. 이러한 메서드의 경우 대신 get_peft_model()을 사용하세요.
새 PEFT 모델 만들기
어댑터 주입을 수행하려면 inject_adapter_in_model() 메서드를 사용합니다. 이 메서드는 PEFT 설정, 모델, 선택적 어댑터 이름이라는 3개의 인수를 받습니다. 서로 다른 어댑터 이름으로 inject_adapter_in_model()을 여러 번 호출하면 모델에 여러 어댑터를 연결할 수도 있습니다.
예를 들어, DummyModel 모듈의 linear 서브모듈에 LoRA 어댑터를 주입하려면:
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)…
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)
)state_dict 기반의 주입
가끔 PEFT 어댑터 체크포인트는 존재하지만, 그 이유를 불문하고 해당 PEFT 설정이 알려지지 않은 경우가 있습니다. 이 체크포인트에 PEFT 레이어를 주입하려면 일반적으로 체크포인트의 state_dict를 기반으로, 특히 target_modules 인수를 포함하여, 해당 PEFT 설정을 역설계해야 합니다. 이는 번거롭고 오류가 발생하기 쉽습니다. 이를 피하기 위해 inject_adapter_in_model()을 호출하고 로드된 state_dict를 인수로 전달하는 것도 가능합니다:
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)이 경우 PEFT는 PEFT config 대신 state_dict를 참조하여 대상 레이어를 결정합니다. 사용자는 이 기능이 작동하도록 PEFT config의 정확한 target_modules를 설정할 필요가 없습니다. 그러나 여전히 올바른 유형의 PEFT config, 이 예시에서는 LoraConfig를 전달해야 하며, target_modules는 None으로 두면 됩니다.
이 과정은 초기화되지 않은 PEFT 레이어만 생성할 뿐, state_dict의 값으로 모델 가중치를 채우지 않습니다. 가중치를 채우려면 아래 설명에 따라 set_peft_model_state_dict()을 호출하세요.
⚠️ PEFT 설정에 구성된 내용과 state_dict에서 발견된 내용이 일치하지 않으면 PEFT가 이를 경고합니다. PEFT 설정이 올바르게 지정되지 않은 것을 알고 있다면 이 경고를 무시할 수 있습니다.
원본 PEFT 어댑터가
target_modules대신target_parameters를 사용했다면,state_dict에서 주입하는 것은 올바르게 작동하지 않습니다. 이 경우 주입에 올바른 PEFT 설정을 사용하는 것이 필수적입니다.
모델 저장
어댑터만 저장하려면 get_peft_model_state_dict() 함수를 사용하세요:
from peft import get_peft_model_state_dict
peft_state_dict = get_peft_model_state_dict(model)
print(peft_state_dict)그렇지 않은 경우, model.state_dict()는 모델의 전체 state dict를 반환합니다.
모델 로드
저장된 state_dict를 로드한 후, set_peft_model_state_dict() 함수를 사용하여 적용할 수 있습니다:
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)어댑터 주입이 느리거나 대량의 어댑터를 로드해야 하는 경우, 메타 디바이스에서 “빈” 어댑터를 생성하고 set_peft_model_state_dict()이 호출될 때만 실제 가중치로 가중치를 채우는 최적화를 사용할 수 있습니다. 이를 위해 low_cpu_mem_usage=True를 inject_adapter_in_model()과 set_peft_model_state_dict() 양쪽에 전달하세요.
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 TrueHub에서 가중치를 로드하려면 저수준 load_peft_weights() 함수가 있습니다:
state_dict = load_peft_weights("my-account/my-adapter-repo")기본 가중치 설정 및 로드
위 함수들은 adapter의 state dict를 다룹니다. base model의 가중치를 PEFT 래퍼를 통해 읽거나 작성해야 하는 상황도 있습니다. PEFT가 대상 모듈의 매개변수를 이름 변경(예: q_proj.weight이 q_proj.base_layer.weight으로 변경)하고 base model에 없는 adapter 매개변수를 추가하기 때문에 이 작업은 완전히 자명하지 않습니다. 두 명명 규칙 사이를 변환하려면 get_base_model_state_dict()와 set_base_model_state_dict()를 사용하세요:
from peft import LoraConfig, get_peft_model, get_base_model_state_dict, set_base_model_state_dict
from transformers import AutoModelForCausalLM
model = AutoModelForCausalLM.from_pretrained("facebook/opt-125m")
model = get_peft_model(model, LoraConfig(target_modules="all-linear"))
# state dict with the original (pre-PEFT) key names, adapter parameters excluded
base_state_dict = get_base_model_state_dict(model)
# load a state dict with original key names into the PEFT-wrapped model
outcome = set_base_model_state_dict(model, base_state_dict, strict=False)
# check that there were no wrong keys
print(outcome.missing_keys, outcome.unexpected_keys)이 기능의 주요 사용 사례는 모델이 이미 PEFT로 래핑된 이후에 기본 가중치를 로드하는 것입니다. 예를 들어, TorchTitan과 같은 FSDP 학습 설정은 모델을 meta device에서 초기화하고, PEFT를 적용한 후, 그 결과를 샤딩합니다. 실제 메모리는 샤딩 이후에만 존재하므로, 원래 이름을 키로 사용하는 체크포인트는 이미 래핑된 모델에 로드되어야 합니다. 참고로 get_base_model_state_dict()는 모델의 라이브 텐서를 반환하며, 복사본을 반환하지 않습니다.