본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2025. 12. 2. · 15 Views
LLM 파인튜닝 기법 완벽 가이드
대규모 언어 모델을 효율적으로 미세조정하는 다양한 기법을 알아봅니다. Full Fine-tuning부터 LoRA, QLoRA, Adapter, Prefix Tuning까지 실무에서 바로 활용할 수 있는 PEFT 기법들을 초급자도 이해할 수 있도록 쉽게 설명합니다.
목차
1. Full Fine-tuning의 한계
김개발 씨는 회사에서 GPT 기반 챗봇을 만들라는 업무를 받았습니다. "우리 회사 데이터로 모델을 학습시키면 되겠지?"라고 가볍게 생각했는데, 막상 시작하려니 GPU 메모리가 터져버렸습니다.
도대체 무엇이 문제일까요?
Full Fine-tuning은 사전 학습된 모델의 모든 파라미터를 업데이트하는 전통적인 방식입니다. 마치 집 전체를 리모델링하는 것과 같습니다.
확실한 효과를 볼 수 있지만, 엄청난 비용과 자원이 필요합니다. 70억 개의 파라미터를 가진 모델을 학습시키려면 수십 GB의 GPU 메모리가 필요합니다.
다음 코드를 살펴봅시다.
from transformers import AutoModelForCausalLM, Trainer, TrainingArguments
# 7B 모델 로드 - 이것만으로도 14GB+ 메모리 필요
model = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-2-7b-hf")
# 모든 파라미터를 학습 가능하게 설정
for param in model.parameters():
param.requires_grad = True # 7B개 파라미터 모두 업데이트
# 학습 설정 - 메모리 부족으로 batch_size를 1로 줄여야 함
training_args = TrainingArguments(
output_dir="./results",
per_device_train_batch_size=1, # 메모리 한계로 최소값
gradient_accumulation_steps=16, # 배치 크기 보완
)
김개발 씨는 입사 6개월 차 ML 엔지니어입니다. 어느 날 팀장님이 다가와 말했습니다.
"우리 고객 상담 데이터로 LLM을 학습시켜서 AI 상담사를 만들어 봐요." 김개발 씨는 자신 있게 대답했습니다. "네, Hugging Face에서 모델 받아서 학습시키면 되죠!" 하지만 현실은 녹록지 않았습니다.
먼저 Full Fine-tuning이 무엇인지 알아볼까요? Full Fine-tuning은 마치 아파트 전체를 리모델링하는 것과 같습니다.
거실, 주방, 욕실, 침실 모든 곳을 새로 꾸미는 거죠. 확실히 원하는 대로 바꿀 수 있지만, 비용이 어마어마합니다.
시간도 오래 걸리고, 전문 인력도 많이 필요합니다. LLM도 마찬가지입니다.
Llama-2-7B 모델은 약 70억 개의 파라미터를 가지고 있습니다. Full Fine-tuning은 이 70억 개의 숫자를 모두 조금씩 수정하는 작업입니다.
문제는 여기서 시작됩니다. 70억 개의 파라미터를 학습시키려면 각 파라미터마다 그래디언트와 옵티마이저 상태를 저장해야 합니다.
32비트 부동소수점 기준으로 계산하면, 모델 가중치 28GB, 그래디언트 28GB, Adam 옵티마이저 상태 56GB가 필요합니다. 총 112GB입니다.
A100 GPU 한 장이 80GB인데, 모델 하나 학습시키는 데 GPU가 두 장이나 필요한 셈입니다. 클라우드에서 A100을 빌리면 시간당 수만 원이 듭니다.
김개발 씨는 회사의 RTX 3090(24GB)으로 시도해 봤습니다. 당연히 CUDA Out of Memory 에러가 떴습니다.
배치 사이즈를 1로 줄여도 안 됐습니다. gradient checkpointing을 적용해도 간신히 돌아가는 수준이었습니다.
더 큰 문제는 과적합 위험입니다. 회사의 상담 데이터는 고작 수천 건입니다.
70억 개의 파라미터를 수천 건의 데이터로 학습시키면 어떻게 될까요? 모델이 학습 데이터를 외워버립니다.
새로운 질문에는 엉뚱한 답을 하게 됩니다. 또한 재앙적 망각 문제도 있습니다.
모든 파라미터를 수정하다 보면, 모델이 원래 알고 있던 일반적인 지식을 잊어버릴 수 있습니다. 상담은 잘하는데 기본적인 대화가 안 되는 이상한 모델이 만들어지는 거죠.
박시니어 씨가 김개발 씨에게 다가왔습니다. "Full Fine-tuning은 자원이 충분할 때나 쓰는 거야.
우리 같은 상황에선 다른 방법이 필요해." 결국 Full Fine-tuning은 이론적으로는 최고의 성능을 낼 수 있지만, 현실적인 제약이 너무 많습니다. 이런 한계를 극복하기 위해 등장한 것이 바로 Parameter-Efficient Fine-Tuning, 줄여서 PEFT입니다.
실전 팁
💡 - Full Fine-tuning은 데이터가 수십만 건 이상이고 GPU 자원이 충분할 때만 고려하세요
- 먼저 PEFT 기법으로 시작하고, 성능이 부족할 때 Full Fine-tuning을 검토하세요
2. LoRA 원리와 구현
박시니어 씨가 김개발 씨에게 논문 하나를 건넸습니다. "이거 읽어봐.
LoRA라는 건데, GPU 하나로도 대형 모델을 학습시킬 수 있어." 김개발 씨는 반신반의하며 논문을 펼쳤습니다. 정말 그런 마법 같은 기술이 있을까요?
**LoRA(Low-Rank Adaptation)**는 거대한 가중치 행렬을 직접 수정하는 대신, 작은 행렬 두 개를 추가로 학습시키는 기법입니다. 마치 건물 전체를 리모델링하는 대신 인테리어 소품만 바꾸는 것과 같습니다.
원래 모델의 0.1% 정도 파라미터만 학습하면서도 Full Fine-tuning에 준하는 성능을 낼 수 있습니다.
다음 코드를 살펴봅시다.
from peft import LoraConfig, get_peft_model
from transformers import AutoModelForCausalLM
# 기본 모델 로드
model = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-2-7b-hf")
# LoRA 설정 - 핵심 파라미터들
lora_config = LoraConfig(
r=8, # rank: 작을수록 효율적, 클수록 표현력 증가
lora_alpha=32, # 스케일링 팩터: 보통 r의 2~4배
target_modules=["q_proj", "v_proj"], # 적용할 레이어
lora_dropout=0.05, # 과적합 방지
bias="none", # bias는 학습하지 않음
)
# LoRA 적용 - 학습 가능 파라미터가 0.1%로 감소
model = get_peft_model(model, lora_config)
model.print_trainable_parameters()
# 출력: trainable params: 4,194,304 || all params: 6,742,609,920 || 0.06%
김개발 씨는 LoRA 논문을 읽기 시작했습니다. 처음에는 수학 공식이 어려워 보였지만, 박시니어 씨의 설명을 듣고 나니 이해가 됐습니다.
LoRA의 핵심 아이디어는 생각보다 단순합니다. "파인튜닝할 때 가중치의 변화량은 사실 저차원(low-rank)이다"라는 가설에서 출발합니다.
쉽게 비유해 볼까요? 1000x1000 크기의 거대한 퍼즐이 있다고 상상해 보세요.
이 퍼즐의 그림을 바꾸려면 백만 개의 조각을 모두 교체해야 합니다. 하지만 LoRA는 다르게 접근합니다.
"사실 이 그림의 핵심 패턴은 8개 정도의 기본 요소로 표현할 수 있어"라고 말하는 겁니다. 수학적으로 표현하면 이렇습니다.
원래 가중치 행렬 W가 있을 때, 변화량을 직접 계산하지 않고 두 개의 작은 행렬 A와 B의 곱으로 근사합니다. W_new = W + BA 형태가 되는 거죠.
예를 들어 4096x4096 크기의 행렬이 있다면, 원래는 약 1,600만 개의 파라미터를 학습해야 합니다. 하지만 LoRA를 적용하면 4096x8 크기의 A 행렬과 8x4096 크기의 B 행렬만 학습하면 됩니다.
총 65,536개의 파라미터만 학습하면 되는 거죠. 99.6%나 줄어듭니다.
코드를 살펴보겠습니다. r=8은 랭크(rank)를 의미합니다.
앞서 말한 "8개의 기본 요소"가 바로 이겁니다. 이 숫자가 작을수록 파라미터가 줄어들지만, 너무 작으면 표현력이 부족해집니다.
보통 4, 8, 16, 32 중에서 선택합니다. lora_alpha=32는 스케일링 팩터입니다.
LoRA의 출력을 얼마나 반영할지 결정합니다. 보통 r의 2~4배로 설정합니다.
alpha/r 비율이 실제 스케일링에 적용됩니다. target_modules는 LoRA를 적용할 레이어를 지정합니다.
Transformer 구조에서 가장 효과적인 곳은 Attention의 Query와 Value 프로젝션 레이어입니다. Key 프로젝션과 FFN에도 적용할 수 있지만, 성능 대비 효율이 떨어집니다.
김개발 씨는 LoRA를 적용하고 깜짝 놀랐습니다. RTX 3090 하나로 Llama-2-7B를 학습시킬 수 있었습니다.
학습 속도도 빨라졌습니다. 무엇보다 과적합 위험이 줄어들었습니다.
추론할 때는 더 좋은 점이 있습니다. 학습된 LoRA 가중치를 원래 모델에 합쳐버릴 수 있습니다.
그러면 추론 속도가 원래 모델과 동일해집니다. 추가적인 오버헤드가 없는 겁니다.
또한 여러 LoRA를 만들어두고 상황에 따라 교체할 수도 있습니다. 고객 상담용 LoRA, 기술 문서용 LoRA, 마케팅용 LoRA를 각각 만들어서 필요할 때 바꿔 끼우는 거죠.
원본 모델은 하나만 있으면 됩니다. 박시니어 씨가 덧붙였습니다.
"LoRA의 진짜 장점은 실험하기 쉽다는 거야. 다양한 설정을 빠르게 테스트해 볼 수 있거든." 김개발 씨는 고개를 끄덕였습니다.
실전 팁
💡 - r 값은 4부터 시작해서 성능이 부족하면 8, 16으로 늘려보세요
- target_modules에 더 많은 레이어를 추가하면 성능은 올라가지만 메모리도 증가합니다
3. QLoRA로 메모리 절약
LoRA로 학습이 가능해졌지만, 김개발 씨는 여전히 고민이 있었습니다. "13B 모델도 써보고 싶은데, 그건 여전히 메모리가 부족해요." 박시니어 씨가 미소를 지었습니다.
"QLoRA라는 게 있어. 4비트 양자화랑 LoRA를 결합한 기술이야."
**QLoRA(Quantized LoRA)**는 모델을 4비트로 양자화하고 LoRA를 적용하는 기법입니다. 마치 고해상도 이미지를 압축 저장해두고, 필요한 부분만 원본 품질로 수정하는 것과 같습니다.
65B 모델도 단일 48GB GPU에서 학습할 수 있게 해주며, 메모리 사용량을 75% 이상 절약합니다.
다음 코드를 살펴봅시다.
from transformers import AutoModelForCausalLM, BitsAndBytesConfig
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
import torch
# 4비트 양자화 설정
bnb_config = BitsAndBytesConfig(
load_in_4bit=True, # 4비트로 로드
bnb_4bit_quant_type="nf4", # NormalFloat4 - 정규분포에 최적화
bnb_4bit_compute_dtype=torch.bfloat16, # 연산은 bfloat16으로
bnb_4bit_use_double_quant=True, # 이중 양자화로 추가 절약
)
# 4비트로 모델 로드 - 7B 모델이 약 4GB만 차지
model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-2-7b-hf",
quantization_config=bnb_config,
device_map="auto",
)
# k-bit 학습을 위한 준비
model = prepare_model_for_kbit_training(model)
# LoRA 적용
model = get_peft_model(model, LoraConfig(r=8, lora_alpha=32, target_modules=["q_proj", "v_proj"]))
김개발 씨는 QLoRA라는 이름을 처음 들었습니다. "Q가 뭔가요?" 박시니어 씨가 설명했습니다.
"Quantization, 양자화라는 뜻이야." 양자화란 무엇일까요? 쉽게 말해 숫자를 더 작은 비트로 표현하는 것입니다.
비유하자면 이렇습니다. 당신이 그림을 그리는데, 원래는 1600만 가지 색상을 쓸 수 있었습니다.
하지만 스케치북이 너무 비싸서, 16가지 색상만 쓰는 스케치북으로 바꿨습니다. 물론 미세한 색 차이는 표현하기 어렵지만, 그림의 큰 형태는 충분히 그릴 수 있습니다.
일반적으로 딥러닝 모델은 32비트 부동소수점(FP32)을 사용합니다. 하나의 숫자를 저장하는 데 4바이트가 필요합니다.
7B 모델이라면 70억 x 4바이트 = 28GB입니다. QLoRA는 이를 4비트로 줄입니다.
하나의 숫자를 0.5바이트로 저장합니다. 7B 모델이 70억 x 0.5바이트 = 3.5GB가 됩니다.
8배나 줄어드는 겁니다. 하지만 여기서 의문이 생깁니다.
"그렇게 압축하면 성능이 떨어지지 않나요?" 바로 이것이 QLoRA의 핵심 혁신입니다. 세 가지 기술이 결합됩니다.
첫째, NF4(NormalFloat4) 양자화입니다. 일반적인 4비트 양자화가 아니라, 가중치가 정규분포를 따른다는 점을 활용한 최적화된 양자화입니다.
정보 손실을 최소화합니다. 둘째, **이중 양자화(Double Quantization)**입니다.
양자화 상수 자체도 양자화합니다. 메모리를 추가로 0.5GB 정도 절약할 수 있습니다.
셋째, Paged Optimizers입니다. GPU 메모리가 부족하면 자동으로 CPU 메모리를 활용합니다.
긴 시퀀스를 처리할 때 특히 유용합니다. 코드를 살펴보겠습니다.
load_in_4bit=True는 모델을 4비트로 로드하라는 뜻입니다. bnb_4bit_compute_dtype=torch.bfloat16은 연산할 때는 16비트로 변환해서 계산한다는 뜻입니다.
저장은 4비트로 하지만 계산은 정확하게 하는 겁니다. prepare_model_for_kbit_training 함수가 중요합니다.
양자화된 모델에서 LoRA가 제대로 작동하도록 준비해줍니다. 그래디언트 체크포인팅을 활성화하고, 레이어 정규화를 float32로 유지합니다.
김개발 씨는 QLoRA를 적용해 봤습니다. 놀랍게도 RTX 3090 하나로 Llama-2-13B를 학습시킬 수 있었습니다.
이전에는 꿈도 못 꿨던 일입니다. 성능은 어떨까요?
논문에 따르면 QLoRA로 학습한 모델은 Full Fine-tuning과 거의 동등한 성능을 보입니다. 메모리는 75% 절약하면서 성능 손실은 1% 미만입니다.
박시니어 씨가 조언했습니다. "QLoRA는 특히 개인 개발자나 작은 팀에게 게임 체인저야.
비싼 클라우드 GPU 없이도 최신 LLM을 파인튜닝할 수 있거든."
실전 팁
💡 - bitsandbytes 라이브러리 설치가 필요합니다: pip install bitsandbytes
- Windows에서는 호환성 이슈가 있을 수 있으니 Linux나 WSL2를 권장합니다
4. Adapter 방식
김개발 씨가 여러 LoRA 모델을 관리하다 보니 새로운 고민이 생겼습니다. "도메인마다 다른 모델을 만들어야 하는데, 더 깔끔하게 관리할 방법은 없을까요?" 박시니어 씨가 대답했습니다.
"Adapter 방식을 알아볼 때가 됐네."
Adapter는 Transformer 블록 사이에 작은 병목 레이어를 삽입하는 기법입니다. 마치 전자제품에 어댑터를 끼워서 다른 규격의 콘센트에서도 사용하게 하는 것처럼, 기존 모델에 작은 모듈을 끼워넣어 새로운 태스크에 적응시킵니다.
원본 모델은 그대로 두고 Adapter만 교체하면 다양한 작업을 수행할 수 있습니다.
다음 코드를 살펴봅시다.
from transformers import AutoModelForSequenceClassification
from peft import AdapterConfig, get_peft_model
# 기본 모델 로드
model = AutoModelForSequenceClassification.from_pretrained("bert-base-uncased")
# Adapter 설정
adapter_config = AdapterConfig(
adapter_type="bottleneck", # 병목 구조 어댑터
bottleneck_size=64, # 병목 차원 (작을수록 효율적)
non_linearity="relu", # 활성화 함수
reduction_factor=16, # 축소 비율
original_ln_before=True, # 어댑터 전에 레이어 정규화
original_ln_after=True, # 어댑터 후에도 레이어 정규화
)
# Adapter 적용
model = get_peft_model(model, adapter_config)
# 태스크별 어댑터 저장/로드
model.save_pretrained("./sentiment_adapter")
# 나중에: model.load_adapter("./sentiment_adapter")
Adapter는 LoRA보다 먼저 등장한 PEFT 기법입니다. 2019년 구글에서 발표한 논문에서 처음 소개되었습니다.
Adapter의 아이디어를 이해하기 위해 비유를 들어보겠습니다. 여러분이 해외여행을 갈 때 전압 어댑터를 챙기시죠?
한국 전자제품을 미국 콘센트에 꽂으려면 어댑터가 필요합니다. 전자제품 자체를 바꾸는 게 아니라, 작은 어댑터 하나로 호환성을 확보하는 겁니다.
Adapter 방식도 마찬가지입니다. 거대한 Transformer 모델 자체를 바꾸지 않습니다.
대신 Transformer 블록 사이사이에 작은 어댑터 모듈을 끼워넣습니다. 구조를 살펴보겠습니다.
일반적인 Transformer 블록은 Self-Attention과 Feed-Forward Network(FFN)로 구성됩니다. Adapter는 이 각 컴포넌트 뒤에 삽입됩니다.
Adapter 모듈의 내부 구조는 병목(bottleneck) 형태입니다. 먼저 입력을 작은 차원으로 다운 프로젝션합니다.
예를 들어 768차원을 64차원으로 줄입니다. 그다음 비선형 활성화 함수(ReLU 등)를 적용합니다.
마지막으로 다시 원래 차원으로 업 프로젝션합니다. 이렇게 하면 학습할 파라미터가 크게 줄어듭니다.
768 x 64 + 64 x 768 = 약 10만 개입니다. 각 레이어마다 이 정도이니, BERT 같은 12층 모델이라면 총 약 120만 개 파라미터만 학습하면 됩니다.
LoRA와 비교하면 어떨까요? 둘 다 파라미터 효율적이지만 접근 방식이 다릅니다.
LoRA는 기존 가중치에 병렬로 작은 행렬을 추가합니다. 추론할 때 원래 가중치에 합쳐버릴 수 있어서 추가 지연이 없습니다.
Adapter는 레이어 사이에 직렬로 모듈을 추가합니다. 추론할 때 추가 연산이 발생하지만, 모듈을 쉽게 교체할 수 있습니다.
실무에서 Adapter가 유용한 경우가 있습니다. 바로 다중 태스크 상황입니다.
김개발 씨의 회사를 예로 들어보겠습니다. 고객 상담팀용 모델, 기술 지원팀용 모델, 마케팅팀용 모델이 필요합니다.
기존 방식이라면 세 개의 모델을 따로 저장해야 합니다. 7B 모델이라면 총 84GB의 저장 공간이 필요합니다.
Adapter 방식을 쓰면 어떨까요? 기본 모델 하나(28GB)와 세 개의 어댑터(각 수 MB)만 있으면 됩니다.
서빙할 때도 기본 모델 하나만 메모리에 올려두고, 요청에 따라 어댑터만 교체하면 됩니다. 박시니어 씨가 말했습니다.
"Adapter는 요즘 LoRA에 비해 덜 쓰이지만, 다중 태스크나 모듈형 시스템에서는 여전히 강점이 있어."
실전 팁
💡 - 다중 태스크 시스템에서는 Adapter가 관리하기 편합니다
- bottleneck_size는 32-128 사이에서 시작해보세요
5. Prefix Tuning
어느 날 김개발 씨가 이상한 질문을 했습니다. "모델의 파라미터를 건드리지 않고도 행동을 바꿀 수 있는 방법은 없을까요?" 박시니어 씨가 눈을 빛냈습니다.
"Prefix Tuning이 바로 그거야. 입력 앞에 가상의 토큰을 붙이는 기법이지."
Prefix Tuning은 모델 파라미터는 완전히 고정하고, 입력 앞에 학습 가능한 "가상 토큰(virtual tokens)"을 추가하는 기법입니다. 마치 대화를 시작하기 전에 "당신은 전문 상담사입니다"라고 맥락을 주는 것처럼, 모델의 행동 방식을 조절하는 접두사를 학습합니다.
모델 자체는 그대로이면서 입력만으로 행동을 바꿀 수 있습니다.
다음 코드를 살펴봅시다.
from peft import PrefixTuningConfig, get_peft_model
from transformers import AutoModelForCausalLM
# 기본 모델 로드
model = AutoModelForCausalLM.from_pretrained("gpt2-medium")
# Prefix Tuning 설정
prefix_config = PrefixTuningConfig(
task_type="CAUSAL_LM",
num_virtual_tokens=20, # 가상 토큰 개수
prefix_projection=True, # MLP로 프로젝션 (성능 향상)
encoder_hidden_size=1024, # 프로젝션 히든 사이즈
)
# Prefix Tuning 적용 - 모델 파라미터는 완전 고정
model = get_peft_model(model, prefix_config)
model.print_trainable_parameters()
# 출력: trainable params: 983,040 || all params: 355,893,248 || 0.28%
# 실제 입력: "오늘 날씨 어때?"
# 모델이 보는 것: [v1][v2]...[v20] + "오늘 날씨 어때?"
# v1~v20은 학습된 가상 토큰
Prefix Tuning은 2021년 스탠포드에서 발표된 기법입니다. 다른 PEFT 기법들과는 조금 다른 철학을 가지고 있습니다.
비유를 들어보겠습니다. 여러분이 신입 직원에게 업무를 맡긴다고 생각해보세요.
신입 직원의 능력 자체를 바꿀 수는 없습니다. 하지만 업무를 시작하기 전에 상세한 지침을 줄 수 있습니다.
"고객에게 친절하게 응대하세요", "기술 용어는 쉽게 풀어서 설명하세요" 같은 지침이요. Prefix Tuning도 마찬가지입니다.
모델의 능력(파라미터)은 건드리지 않습니다. 대신 모든 입력 앞에 학습된 프롬프트를 붙입니다.
이 프롬프트가 모델의 행동을 원하는 방향으로 유도합니다. 여기서 핵심은 **가상 토큰(virtual tokens)**입니다.
실제 단어가 아니라, 모델의 임베딩 공간에서만 존재하는 연속적인 벡터입니다. 사람이 읽을 수는 없지만, 모델은 이해할 수 있습니다.
왜 실제 단어 대신 가상 토큰을 쓸까요? 실제 단어는 이산적입니다.
"친절하게"라는 단어의 임베딩은 고정되어 있습니다. 하지만 가상 토큰은 연속적으로 학습됩니다.
더 세밀하게 모델의 행동을 조절할 수 있습니다. 코드를 살펴보겠습니다.
num_virtual_tokens=20은 입력 앞에 20개의 가상 토큰을 붙인다는 뜻입니다. 이 숫자가 크면 표현력이 높아지지만, 입력 길이가 줄어드는 트레이드오프가 있습니다.
prefix_projection=True는 가상 토큰을 바로 쓰지 않고 MLP(다층 퍼셉트론)를 통과시킨다는 뜻입니다. 이렇게 하면 학습이 더 안정적이고 성능도 올라갑니다.
Prefix Tuning의 장점은 무엇일까요? 첫째, 모델 파라미터를 완전히 고정합니다.
LoRA나 Adapter도 효율적이지만, 기존 레이어에 무언가를 추가합니다. Prefix Tuning은 입력만 조작하므로 모델 구조 자체는 변하지 않습니다.
둘째, 모듈성이 뛰어납니다. 다른 태스크를 위해서는 다른 prefix만 학습하면 됩니다.
prefix를 바꿔 끼우는 것만으로 모델의 행동이 바뀝니다. 셋째, 프롬프트 엔지니어링의 자동화 버전이라고 볼 수 있습니다.
사람이 프롬프트를 직접 작성하는 대신, 데이터로부터 최적의 프롬프트를 학습합니다. 단점도 있습니다.
가상 토큰이 컨텍스트 길이를 차지합니다. 20개의 가상 토큰을 쓰면, 실제 입력에 쓸 수 있는 토큰이 20개 줄어듭니다.
긴 문서를 다루는 작업에서는 문제가 될 수 있습니다. 또한 LoRA에 비해 성능이 약간 떨어지는 경향이 있습니다.
특히 모델이 작을수록 그렇습니다. 요즘은 대부분 LoRA를 선호하지만, Prefix Tuning만의 장점이 필요한 경우도 있습니다.
김개발 씨가 물었습니다. "그럼 언제 Prefix Tuning을 쓰나요?" 박시니어 씨가 대답했습니다.
"모델을 API로만 접근할 수 있을 때 유용해. 파라미터 수정 없이 프롬프트만으로 조절할 수 있거든."
실전 팁
💡 - num_virtual_tokens는 10-30 사이에서 시작하세요
- prefix_projection=True를 권장합니다. 학습이 더 안정적입니다
6. PEFT 라이브러리 활용
김개발 씨는 지난 몇 주간 다양한 파인튜닝 기법을 배웠습니다. 이제 실제 프로젝트에 적용할 시간입니다.
박시니어 씨가 마지막 조언을 해줬습니다. "Hugging Face의 PEFT 라이브러리를 잘 활용하면 이 모든 기법을 쉽게 쓸 수 있어."
PEFT(Parameter-Efficient Fine-Tuning) 라이브러리는 Hugging Face에서 제공하는 통합 도구입니다. LoRA, QLoRA, Adapter, Prefix Tuning 등 모든 효율적 파인튜닝 기법을 일관된 API로 사용할 수 있습니다.
몇 줄의 코드만으로 어떤 기법이든 적용할 수 있어, 실험과 비교가 매우 편리합니다.
다음 코드를 살펴봅시다.
from peft import (
get_peft_model,
LoraConfig,
TaskType,
AutoPeftModelForCausalLM,
)
from transformers import AutoModelForCausalLM, AutoTokenizer, Trainer
# 1. 모델과 토크나이저 로드
model = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-2-7b-hf")
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-2-7b-hf")
# 2. PEFT 설정 (LoRA 예시)
peft_config = LoraConfig(
task_type=TaskType.CAUSAL_LM,
r=8, lora_alpha=32,
target_modules=["q_proj", "v_proj"],
)
# 3. PEFT 모델 생성
model = get_peft_model(model, peft_config)
# 4. 일반 Trainer로 학습 (동일한 방식)
trainer = Trainer(model=model, train_dataset=dataset, ...)
trainer.train()
# 5. 어댑터만 저장 (수 MB)
model.save_pretrained("./my_lora_adapter")
# 6. 나중에 로드
model = AutoPeftModelForCausalLM.from_pretrained("./my_lora_adapter")
PEFT 라이브러리는 Hugging Face 생태계의 일부입니다. Transformers, Datasets, Accelerate 등과 완벽하게 통합됩니다.
비유하자면 PEFT는 스위스 아미 나이프와 같습니다. 하나의 도구에 여러 기능이 들어있죠.
칼, 가위, 드라이버가 필요할 때 각각 따로 사지 않아도 됩니다. PEFT 하나면 LoRA, QLoRA, Adapter, Prefix Tuning, P-Tuning, IA3 등 모든 기법을 사용할 수 있습니다.
설치는 간단합니다. pip install peft만 하면 됩니다.
transformers와 accelerate도 함께 설치되어 있어야 합니다. 핵심 함수 get_peft_model을 알아보겠습니다.
이 함수는 기존 모델을 받아서 PEFT가 적용된 모델을 반환합니다. 어떤 PEFT 기법을 쓸지는 config 객체로 결정합니다.
LoRA를 쓰고 싶으면 LoraConfig를, Adapter를 쓰고 싶으면 AdapterConfig를, Prefix Tuning을 쓰고 싶으면 PrefixTuningConfig를 넘기면 됩니다. 나머지 코드는 동일합니다.
이 일관성이 PEFT의 큰 장점입니다. 실무에서 유용한 기능들을 살펴보겠습니다.
첫째, merge_and_unload입니다. LoRA 가중치를 원래 모델에 합쳐버리는 기능입니다.
추론할 때 LoRA 오버헤드 없이 원래 모델 속도 그대로 사용할 수 있습니다. 둘째, add_adapter입니다.
하나의 모델에 여러 어댑터를 추가할 수 있습니다. 상황에 따라 어댑터를 전환해서 사용합니다.
셋째, AutoPeftModel 클래스들입니다. 저장된 어댑터를 쉽게 로드할 수 있습니다.
AutoPeftModelForCausalLM, AutoPeftModelForSequenceClassification 등 태스크별로 준비되어 있습니다. 코드를 단계별로 살펴보겠습니다.
1단계에서 기본 모델을 로드합니다. 여기까지는 일반적인 Transformers 사용법과 동일합니다.
2단계에서 PEFT 설정을 정의합니다. task_type은 모델의 용도를 지정합니다.
CAUSAL_LM은 텍스트 생성, SEQ_CLS는 분류, SEQ_2_SEQ_LM은 번역 같은 시퀀스-투-시퀀스 작업입니다. 3단계에서 get_peft_model로 PEFT 모델을 생성합니다.
이제 model 객체의 대부분 파라미터는 고정되고, LoRA 파라미터만 학습됩니다. 4단계에서 일반 Trainer로 학습합니다.
PEFT를 적용했다고 해서 학습 코드가 달라지지 않습니다. 평소대로 Trainer를 사용하면 됩니다.
5단계에서 어댑터만 저장합니다. 7B 모델 전체가 아니라 수 MB 크기의 어댑터 파일만 저장됩니다.
버전 관리도 쉽고, 공유도 편리합니다. 6단계에서 저장된 어댑터를 로드합니다.
AutoPeftModelForCausalLM이 알아서 기본 모델을 찾아 로드하고 어댑터를 적용합니다. 김개발 씨는 PEFT를 사용해 프로젝트를 완성했습니다.
RTX 3090 하나로 Llama-2-7B를 회사 데이터에 맞게 파인튜닝했습니다. 학습에 4시간, 비용은 전기료 몇 천 원뿐이었습니다.
박시니어 씨가 최종 조언을 했습니다. "파인튜닝은 시작일 뿐이야.
이제 평가, 프롬프트 엔지니어링, 서빙까지 배워야 해." 김개발 씨는 고개를 끄덕였습니다. 배울 것이 많지만, 한 걸음씩 나아가면 된다는 것을 알았습니다.
실전 팁
💡 - 처음에는 LoRA로 시작하고, 필요하면 다른 기법을 실험해보세요
- Hugging Face Hub에서 다른 사람이 공유한 어댑터를 다운받아 쓸 수도 있습니다
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
Helm 마이크로서비스 패키징 완벽 가이드
Kubernetes 환경에서 마이크로서비스를 효율적으로 패키징하고 배포하는 Helm의 핵심 기능을 실무 중심으로 학습합니다. Chart 생성부터 릴리스 관리까지 체계적으로 다룹니다.
보안 아키텍처 구성 완벽 가이드
프로젝트의 보안을 처음부터 설계하는 방법을 배웁니다. AWS 환경에서 VPC부터 WAF, 암호화, 접근 제어까지 실무에서 바로 적용할 수 있는 보안 아키텍처를 단계별로 구성해봅니다.
AWS Organizations 완벽 가이드
여러 AWS 계정을 체계적으로 관리하고 통합 결제와 보안 정책을 적용하는 방법을 실무 스토리로 쉽게 배워봅니다. 초보 개발자도 바로 이해할 수 있는 친절한 설명과 실전 예제를 제공합니다.
AWS KMS 암호화 완벽 가이드
AWS KMS(Key Management Service)를 활용한 클라우드 데이터 암호화 방법을 초급 개발자를 위해 쉽게 설명합니다. CMK 생성부터 S3, EBS 암호화, 봉투 암호화까지 실무에 필요한 모든 내용을 담았습니다.
AWS Secrets Manager 완벽 가이드
AWS에서 데이터베이스 비밀번호, API 키 등 민감한 정보를 안전하게 관리하는 Secrets Manager의 핵심 개념과 실무 활용법을 배워봅니다. 초급 개발자도 쉽게 따라할 수 있도록 실전 예제와 함께 설명합니다.