파라미터 효율 미세조정

파라미터 효율 미세조정 (Parameter-efficient fine-tuning)

파라미터 효율 미세조정(PEFT) 메서드는 사전 학습된 모델 위에 아주 작은 수의 추가 모델 파라미터(어댑터)만 미세조정해요. 어댑터 파라미터만 갱신되므로 옵티마이저가 추적하는 그래디언트와 상태가 훨씬 적어져 메모리 사용량이 크게 줄어들어요. 어댑터는 가볍기 때문에 공유·저장·로드가 편리해요.

Transformers는 모든 [PreTrainedModel] 클래스에 추가된 [~integrations.PeftAdapterMixin]을 통해 PEFT 라이브러리와 직접 통합돼요. 모델을 별도의 [~peft.PeftModel]로 감싸지 않고도 어댑터를 로드·추가·훈련·전환·삭제할 수 있어요. 모든 비-프롬프트-러닝 PEFT 메서드(LoRA, IA3, AdaLoRA)가 지원돼요. 프롬프트 튜닝·프리픽스 튜닝 같은 프롬프트 기반 메서드는 PEFT 라이브러리를 직접 사용해야 해요.

시작하려면 PEFT를 설치해요. 통합에는 peft >= 0.19.1이 필요해요.

pip install -U peft

어댑터 추가 (Add an adapter)

[~peft.LoraConfig] 같은 PEFT 설정을 만들고 [~integrations.PeftAdapterMixin.add_adapter]로 모델에 부착해요.

from peft import LoraConfig, TaskType
from transformers import AutoModelForCausalLM

model = AutoModelForCausalLM.from_pretrained("google/gemma-2-2b")

lora_config = LoraConfig(
    task_type=TaskType.CAUSAL_LM,
    inference_mode=False,
    r=8,
    lora_alpha=32,
    lora_dropout=0.1,
)

model.add_adapter(lora_config, adapter_name="my_adapter")

특정 레이어 완전 미세조정

어댑터와 함께 추가 모듈(예: 언어 모델 헤드)을 훈련하려면 modules_to_save에 지정해요. modules_to_save는 어댑터와 함께 완전히 미세조정되는 레이어를 지정하므로 모든 파라미터가 갱신돼요. 이는 인과 LM을 시퀀스 분류에 적용할 때 언어 모델 헤드(lm_head) 같은 특정 레이어가 갱신이 필요할 때 유용해요.

lora_config = LoraConfig(
    modules_to_save=["lm_head"],
    ...
)
model.add_adapter(lora_config)

적용할 레이어 선택

공통 아키텍처(Llama, Gemma, Qwen 등)의 경우 PEFT에는 미리 정의된 기본 대상(q_proj, v_proj 같은)이 있어 target_modules를 지정할 필요가 없어요. 다른 레이어를 대상으로 하거나, 모델에 미리 정의된 대상이 없다면 target_modules에 모듈 이름 리스트나 정규식 패턴을 명시적으로 넘겨요.

lora_config = LoraConfig(
    target_modules=["q_proj", "k_proj"],
    ...
)
model.add_adapter(lora_config)

훈련 (Training)

어댑터가 부착된 모델을 [Trainer]에 넘기고 [~Trainer.train]을 호출해요. [Trainer]는 베이스 모델이 동결돼 있으므로 어댑터 파라미터(즉 requires_grad=True인 것들)만 갱신해요.

from transformers import Trainer, TrainingArguments

training_args = TrainingArguments(
    output_dir="./output",
    num_train_epochs=3,
    per_device_train_batch_size=4,
)

trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=dataset,
)

trainer.train()

훈련 중에 [Trainer] 체크포인트에는 어댑터 가중치(adapter_model.safetensors)와 설정(adapter_config.json)만 담겨 체크포인트가 작게 유지돼요. 베이스 모델은 포함되지 않아요.

훈련 후에 최종 어댑터를 [~PreTrainedModel.save_pretrained]으로 저장해요.

model.save_pretrained("./my_adapter")

체크포인트에서 재개

[Trainer]는 재개할 때 어댑터 체크포인트를 자동 감지해요. [Trainer]는 체크포인트 디렉토리에서 어댑터 가중치를 담은 하위 디렉토리를 스캔하고, 각 어댑터를 올바른 학습 가능 상태로 다시 로드해요.

trainer.train(resume_from_checkpoint="./output/checkpoint-1000")

분산 훈련

PEFT 어댑터는 분산 훈련과 즉시 작동해요.

ZeRO-3에서는 [Trainer]가 PEFT 모델로 체크포인트를 저장할 때 exclude_frozen_parameters=True를 넘겨요. 동결된 베이스 모델 가중치는 건너뛰고, 학습 가능한 어댑터 파라미터만 저장돼 체크포인트 크기와 저장 시간이 줄어요.

FSDP에서는 [Trainer]가 FSDP auto-wrap 정책을 갱신해 LoRA 레이어를 올바르게 처리해요. QLoRA(양자화된 베이스 모델 + LoRA)에서는 [Trainer]가 양자화 저장 dtype에 맞게 혼합 정밀도 정책도 조정해요.

어댑터 로드 (Loading an adapter)

어댑터를 로드하려면 Hub 저장소나 로컬 디렉토리에 adapter_config.json 파일과 어댑터 가중치가 있어야 해요.

from_pretrained: [~PreTrainedModel.from_pretrained]은 어댑터를 자동 감지해요. adapter_config.json을 찾으면 base_model_name_or_path 필드를 읽어 올바른 베이스 모델을 로드한 뒤 그 위에 어댑터를 로드해요.

from transformers import AutoModelForCausalLM

# 베이스 모델을 자동 로드하고 어댑터를 부착
model = AutoModelForCausalLM.from_pretrained("klcsp/gemma7b-lora-alpaca-11-v1")

load_adapter: 기존 모델에 어댑터를 로드하려면 [~integrations.PeftAdapterMixin.load_adapter]를 사용해요.

from transformers import AutoModelForCausalLM

model = AutoModelForCausalLM.from_pretrained("google/gemma-7b")
model.load_adapter("klcsp/gemma7b-lora-alpaca-11-v1")

대형 모델은 bitsandbytes로 8비트나 4비트 정밀도의 양자화 버전을 로드해 메모리를 절약해요. device_map="auto"를 추가해 모델을 사용 가능한 하드웨어에 분배해요.

from transformers import AutoModelForCausalLM, BitsAndBytesConfig

model = AutoModelForCausalLM.from_pretrained(
    "klcsp/gemma7b-lora-alpaca-11-v1",
    quantization_config=BitsAndBytesConfig(load_in_8bit=True),
    device_map="auto",
)

여러 어댑터 관리 (Managing multiple adapters)

모델은 동시에 여러 어댑터를 보유할 수 있어요. 고유한 이름으로 어댑터를 추가하고 필요에 따라 전환해요.

from peft import LoraConfig

model.add_adapter(LoraConfig(r=8, lora_alpha=32), adapter_name="adapter_1")
model.add_adapter(LoraConfig(r=16, lora_alpha=64), adapter_name="adapter_2")

[~integrations.PeftAdapterMixin.set_adapter]로 특정 어댑터를 활성화해요. 다른 어댑터는 비활성화되지만 메모리에는 남아 있어요.

model.set_adapter("adapter_2")

[~integrations.PeftAdapterMixin.enable_adapters]는 부착된 모든 어댑터를 활성화하고, [~integrations.PeftAdapterMixin.disable_adapters]는 전부 비활성화해요.

# 베이스 모델 추론을 위해 모든 어댑터 비활성화
model.disable_adapters()

# 모든 어댑터 다시 활성화
model.enable_adapters()

[~integrations.PeftAdapterMixin.active_adapters]로 현재 활성 어댑터를 확인해요.

model.active_adapters()
# ["adapter_1"]

더 이상 필요 없는 어댑터는 [~integrations.PeftAdapterMixin.delete_adapter]로 제거해 메모리를 해제해요.

model.delete_adapter("adapter_1")

어댑터 핫스와핑 (Hotswapping adapters)

요청을 서빙할 때마다 새 어댑터를 로드하면 새 메모리를 할당해요. 모델이 torch.compile로 컴파일됐다면 각 새 어댑터가 재컴파일을 유발해요. 핫스와핑이 어댑터 가중치를 제자리에서 교체해 두 문제를 모두 피해요. LoRA 어댑터만 지원돼요.

LoRA 어댑터를 로드할 때 hotswap=True를 넘기면 그 가중치를 기존 어댑터 슬롯에 교체해 넣어요. adapter_name을 교체할 어댑터 이름으로 설정해요("default"가 기본 어댑터 이름).

model = AutoModel.from_pretrained(...)
# 첫 번째 어댑터를 일반적으로 로드
model.load_adapter(adapter_path_1)
# 어댑터 1로 출력 생성
...
# 두 번째 어댑터를 제자리에서 핫스왑
model.load_adapter(adapter_path_2, hotswap=True, adapter_name="default")
# 어댑터 2로 출력 생성

torch.compile

컴파일된 모델은 첫 번째 어댑터를 로드하기 전에 그리고 컴파일하기 전에 [~integrations.peft.PeftAdapterMixin.enable_peft_hotswap]을 호출해요.

model = AutoModel.from_pretrained(...)
max_rank = ...  # 로드할 모든 LoRA 중 최고 rank
model.enable_peft_hotswap(target_rank=max_rank)
model.load_adapter(adapter_path_1, adapter_name="default")
model = torch.compile(model, ...)
output_1 = model(...)

# 재컴파일 없이 핫스왑
model.load_adapter(adapter_path_2, adapter_name="default")
output_2 = model(...)

target_rank 인자는 로드할 모든 LoRA 어댑터 중 최고 rank를 설정해요. rank 8과 rank 16 어댑터가 있다면 target_rank=16을 넘기세요. 기본값은 128이에요.

enable_peft_hotswap 호출 후에는 이후 모든 load_adapter 호출이 기본적으로 핫스왑해요. 핫스와핑을 끄려면 hotswap=False를 명시적으로 넘겨요.

핫스왑된 어댑터가 초기 어댑터보다 더 많은 레이어를 대상으로 하면 재컴파일이 여전히 일어날 수 있어요. 재컴파일을 피하려면 가장 많은 레이어를 대상으로 하는 어댑터를 먼저 로드해요.

[!TIP] 예상치 못한 재컴파일을 감지하려면 코드를 with torch._dynamo.config.patch(error_on_recompile=True)로 감싸세요. 위 단계를 따랐는데도 재컴파일이 감지되면, 재현 가능한 예시와 함께 PEFT에 이슈를 여세요.

다음 단계

  • PEFT 문서는 PEFT 메서드와 옵션의 전체 범위를 다뤄요.
  • PEFT 핫스와핑 레퍼런스는 제한 사항과 엣지 케이스를 자세히 설명해요.
  • 블로그 포스트에서 torch.compile + 핫스와핑이 런타임을 어떻게 개선하는지 벤치마크해요.

출처: Hugging Face Transformers — Parameter-efficient fine-tuning

더 알아보기 (Learn more)

  • PEFT 라이브러리 — PEFT 메서드 전체 문서
  • Quantization — 양자화 백엔드와 bitsandbytes
  • [Trainer] — 어댑터 훈련용 훈련·평가 루프