LoRA(Low-Rank Adaptation)의 수학적 원리를 이해하고, 타겟 레이어 선택부터 하이퍼파라미터 튜닝까지 실전 적용법을 다룹니다.
Full Fine-Tuning은 모델의 모든 파라미터를 업데이트합니다. 70억 파라미터 모델 기준으로 가중치(14GB), 그래디언트(14GB), 옵티마이저 상태(28GB)를 합치면 최소 56GB 이상의 GPU 메모리가 필요합니다. 이는 대부분의 연구자와 엔지니어에게 현실적인 장벽입니다.
LoRA(Low-Rank Adaptation of Large Language Models)는 2021년 Microsoft Research에서 발표한 논문에서 제안된 방법으로, 이 문제를 우아하게 해결합니다. 핵심 아이디어는 간단합니다. 파인튜닝 과정에서 실제로 변경되는 가중치의 변화량은 저랭크(Low-Rank) 행렬로 충분히 근사할 수 있다는 것입니다.
Transformer 모델의 각 레이어에는 큰 가중치 행렬 W가 존재합니다. 예를 들어, 어텐션 레이어의 Query 행렬 W_q의 크기는 d x d입니다 (d는 모델의 hidden dimension). Llama 3.1 8B 모델의 경우 d = 4096이므로, 하나의 행렬에만 약 1,600만 개의 파라미터가 있습니다.
Full Fine-Tuning에서는 이 행렬 전체를 업데이트합니다.
W' = W + Delta_W
여기서:
W: 원본 가중치 행렬 (d x d)
Delta_W: 학습된 변화량 (d x d)
W': 업데이트된 가중치 (d x d)LoRA의 핵심 통찰은 Delta_W가 실제로는 저랭크(Low-Rank)라는 것입니다. 즉, 전체 d x d 행렬을 직접 학습하는 대신, 두 개의 작은 행렬 A와 B의 곱으로 분해할 수 있습니다.
Delta_W = B x A
여기서:
A: d x r 행렬 (다운 프로젝션)
B: r x d 행렬 (업 프로젝션)
r: 랭크 (rank), 보통 4 ~ 64
파라미터 수 비교 (d = 4096, r = 16):
Full: 4096 x 4096 = 16,777,216
LoRA: 4096 x 16 + 16 x 4096 = 131,072
비율: 약 0.78%이 분해를 통해 학습해야 할 파라미터 수가 원본의 1% 미만으로 줄어듭니다. 원본 가중치 W는 고정(Freeze)하고 A와 B만 학습하므로, 메모리 사용량이 극적으로 감소합니다.
LoRA의 두 행렬은 특별한 방식으로 초기화됩니다. 행렬 A는 가우시안 분포로 초기화하고, 행렬 B는 영행렬(Zero Matrix)로 초기화합니다. 이렇게 하면 학습 시작 시 Delta_W = B x A = 0이 되어, 파인튜닝 이전의 원본 모델과 동일하게 동작합니다.
초기화:
A ~ N(0, sigma^2) (가우시안 랜덤)
B = 0 (영행렬)
Delta_W = B x A = 0 x A = 0
W' = W + 0 = W (원본 모델과 동일)LoRA에는 alpha라는 스케일링 하이퍼파라미터가 있습니다. 실제 가중치 업데이트는 다음과 같이 적용됩니다.
W' = W + (alpha / r) x B x A
alpha/r은 스케일링 팩터로, 학습률(Learning Rate)과 함께
LoRA 업데이트의 크기를 조절합니다.
일반적인 설정:
alpha = 2 x r (예: r=16이면 alpha=32)
이 경우 스케일링 팩터 = 32/16 = 2LoRA를 모델의 어떤 레이어에 적용할지 결정하는 것은 성능에 직접적인 영향을 미칩니다. Transformer 모델의 주요 가중치 행렬은 다음과 같습니다.
Transformer 레이어의 가중치 행렬:
Self-Attention:
q_proj: Query 프로젝션
k_proj: Key 프로젝션
v_proj: Value 프로젝션
o_proj: Output 프로젝션
Feed-Forward Network (MLP):
gate_proj: 게이트 프로젝션 (SwiGLU 활성화)
up_proj: 업 프로젝션
down_proj: 다운 프로젝션원본 LoRA 논문에서는 주로 Query와 Value 프로젝션에 LoRA를 적용했습니다. 그러나 후속 연구들은 더 많은 레이어에 적용할수록 성능이 향상됨을 보여주었습니다.
타겟 레이어 설정별 성능 비교 (경험적):
설정 상대 성능 학습 파라미터
q_proj, v_proj만 기준선 가장 적음
q_proj, k_proj, v_proj +2~3% 약간 증가
모든 Attention 레이어 +3~5% 중간
Attention + MLP 전체 +5~8% 가장 많음실무에서는 모든 선형 레이어(Attention + MLP)에 LoRA를 적용하는 것이 가장 좋은 결과를 보이는 경우가 많습니다. PEFT 라이브러리에서는 target_modules="all-linear"로 간편하게 설정할 수 있습니다.
Hugging Face의 PEFT 라이브러리를 사용하여 LoRA를 적용하는 전체 과정을 살펴봅니다.
from peft import LoraConfig, TaskType, get_peft_model
from transformers import AutoModelForCausalLM, AutoTokenizer
# 베이스 모델 로드
model_name = "meta-llama/Llama-3.1-8B-Instruct"
model = AutoModelForCausalLM.from_pretrained(
model_name,
torch_dtype="auto",
device_map="auto"
)
tokenizer = AutoTokenizer.from_pretrained(model_name)
# LoRA 설정
lora_config = LoraConfig(
task_type=TaskType.CAUSAL_LM,
r=16, # 랭크
lora_alpha=32, # 스케일링 팩터
lora_dropout=0.05, # 드롭아웃
target_modules=[ # 타겟 레이어
"q_proj", "k_proj", "v_proj", "o_proj",
"gate_proj", "up_proj", "down_proj"
],
bias="none", # 바이어스 학습 여부
)
# LoRA 적용
model = get_peft_model(model, lora_config)
# 학습 파라미터 수 확인
model.print_trainable_parameters()
# 출력 예시:
# trainable params: 83,886,080 || all params: 8,114,212,864
# || trainable%: 1.0335각 하이퍼파라미터의 역할과 권장 값을 정리합니다.
LoRA 하이퍼파라미터 가이드:
r (랭크):
역할: LoRA 행렬의 차원, 모델 용량을 결정
범위: 4 ~ 256
권장: 16 ~ 64 (작업 복잡도에 비례)
팁: 작은 r에서 시작하여 성능이 부족하면 늘림
lora_alpha (스케일링):
역할: LoRA 업데이트의 크기를 조절
범위: r ~ 4*r
권장: 2 * r (r=16이면 alpha=32)
팁: alpha/r 비율이 중요, 보통 1~2 사이
lora_dropout:
역할: 과적합 방지를 위한 드롭아웃
범위: 0.0 ~ 0.2
권장: 0.05 ~ 0.1
팁: 데이터가 적을수록 높게 설정
target_modules:
역할: LoRA를 적용할 레이어 지정
권장: "all-linear" 또는 주요 프로젝션 레이어 목록
팁: 더 많은 레이어 = 더 높은 성능, 더 많은 메모리
bias:
역할: 바이어스 파라미터 학습 여부
옵션: "none", "all", "lora_only"
권장: "none" (대부분의 경우)from transformers import TrainingArguments
from trl import SFTTrainer
from datasets import load_dataset
# 데이터셋 로드
dataset = load_dataset("json", data_files="training_data.jsonl")
# 학습 설정
training_args = TrainingArguments(
output_dir="./lora-output",
num_train_epochs=3,
per_device_train_batch_size=4,
gradient_accumulation_steps=4,
learning_rate=2e-4,
lr_scheduler_type="cosine",
warmup_ratio=0.1,
logging_steps=10,
save_strategy="epoch",
evaluation_strategy="epoch",
fp16=True,
report_to="wandb",
)
# SFTTrainer로 학습
trainer = SFTTrainer(
model=model,
args=training_args,
train_dataset=dataset["train"],
eval_dataset=dataset.get("validation"),
tokenizer=tokenizer,
max_seq_length=2048,
)
# 학습 시작
trainer.train()
# LoRA 어댑터 저장
trainer.save_model("./lora-adapter")LoRA의 큰 장점 중 하나는 어댑터가 원본 모델과 분리되어 있다는 것입니다. 이를 통해 여러 용도의 어댑터를 효율적으로 관리할 수 있습니다.
from peft import PeftModel
from transformers import AutoModelForCausalLM, AutoTokenizer
# 베이스 모델 로드
base_model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-3.1-8B-Instruct",
torch_dtype="auto",
device_map="auto"
)
# LoRA 어댑터 적용
model = PeftModel.from_pretrained(base_model, "./lora-adapter")
# 추론
tokenizer = AutoTokenizer.from_pretrained(
"meta-llama/Llama-3.1-8B-Instruct"
)
messages = [
{"role": "user", "content": "Python의 GIL에 대해 설명해 주세요."}
]
inputs = tokenizer.apply_chat_template(
messages, return_tensors="pt", add_generation_prompt=True
)
outputs = model.generate(
inputs.to(model.device),
max_new_tokens=512,
temperature=0.7
)
print(tokenizer.decode(outputs[0], skip_special_tokens=True))배포 시에는 LoRA 어댑터를 베이스 모델에 병합하여 추론 오버헤드를 제거할 수 있습니다.
# 어댑터를 베이스 모델에 병합
merged_model = model.merge_and_unload()
# 병합된 모델 저장
merged_model.save_pretrained("./merged-model")
tokenizer.save_pretrained("./merged-model")어댑터 병합의 장단점:
장점:
- 추론 시 추가 연산 없음 (LoRA 연산 제거)
- 배포 간소화 (단일 모델 파일)
- 추론 속도 미세하게 향상
단점:
- 병합 후에는 어댑터 교체 불가
- 원본 모델 + 어댑터 조합의 유연성 상실
- 모델 전체 크기의 저장 공간 필요하나의 베이스 모델에 여러 LoRA 어댑터를 동적으로 전환할 수 있습니다. 이는 멀티 태넌트 환경에서 유용합니다.
from peft import PeftModel
# 베이스 모델에 첫 번째 어댑터 로드
model = PeftModel.from_pretrained(
base_model,
"./adapters/code-review",
adapter_name="code_review"
)
# 두 번째 어댑터 추가
model.load_adapter("./adapters/tech-writing", adapter_name="tech_writing")
# 어댑터 전환
model.set_adapter("code_review") # 코드 리뷰 모드
# model.set_adapter("tech_writing") # 기술 문서 모드
# 어댑터 비활성화 (베이스 모델로 복귀)
model.disable_adapter_layers()LoRA의 성공 이후 다양한 변형 기법이 등장했습니다.
DoRA는 가중치를 크기(Magnitude)와 방향(Direction)으로 분해한 후, 방향 성분에만 LoRA를 적용합니다. 이를 통해 Full Fine-Tuning에 더 가까운 학습 역학(Learning Dynamics)을 달성합니다.
from peft import LoraConfig
# DoRA 설정 (PEFT 라이브러리 지원)
dora_config = LoraConfig(
r=16,
lora_alpha=32,
target_modules="all-linear",
use_dora=True, # DoRA 활성화
)rsLoRA는 스케일링 팩터를 alpha/r 대신 alpha/sqrt(r)로 변경하여, 높은 랭크에서도 안정적인 학습을 가능하게 합니다.
# rsLoRA 설정
rslora_config = LoraConfig(
r=64,
lora_alpha=128,
target_modules="all-linear",
use_rslora=True, # rsLoRA 활성화
)이번 장에서는 LoRA의 수학적 원리와 실전 적용 방법을 깊이 있게 다루었습니다.
다음 장에서는 QLoRA를 사용하여 16GB 이하의 소비자용 GPU에서 대규모 모델을 파인튜닝하는 방법을 실습합니다. 4비트 양자화의 원리와 실전 구현을 상세하게 다룹니다.
이 글이 도움이 되셨나요?
관련 주제 더 보기
4비트 양자화와 LoRA를 결합한 QLoRA의 원리를 이해하고, 단일 소비자 GPU에서 대규모 모델을 파인튜닝하는 실전 방법을 다룹니다.
파인튜닝 학습 데이터의 정제, 중복 제거, 토큰화, 패딩 전략 등 실전 전처리 파이프라인을 구축하는 방법을 다룹니다.
파인튜닝 학습 파이프라인의 전체 구조를 설계하고, 학습률, 배치 크기, 스케줄링 등 핵심 하이퍼파라미터를 최적화하는 전략을 다룹니다.