LoRA·QLoRA로 LLM 파인튜닝하기: GPU 메모리 절약 원리부터 Hugging Face PEFT 학습 파이프라인까지
핵심 요약
이 글에서 확인할 내용
파인튜닝은 하고 싶은데 GPU가 없다는 딜레마 사내 도메인 특화 LLM 도입을 검토하던 2025년 말, 팀이 처음 맞닥뜨린 벽은 "7B 모델 풀 파인튜닝에 H100 4장이 필요하다"는 사실이었습니다. 클라우드 GPU 비용을 계산하는 스프레드시트가 점점 붉게 물들었고, "그냥 RAG로 갈까요?"라는 말이 슬랙에 올라왔습니다. 하지만 RAG가 만능은 아닙니다.
- 1. 왜 Full Fine-tuning이 아니라 LoRA인가 — VRAM 사용량 비교
- 2. LoRA 수학적 원리: 저랭크 분해 직관과 rank·alpha 설정 기준
- 3. QLoRA: 4비트 NF4 양자화 + Double Quantization + Paged Optimizer
파인튜닝은 하고 싶은데 GPU가 없다는 딜레마
사내 도메인 특화 LLM 도입을 검토하던 2025년 말, 팀이 처음 맞닥뜨린 벽은 "7B 모델 풀 파인튜닝에 H100 4장이 필요하다"는 사실이었습니다. 클라우드 GPU 비용을 계산하는 스프레드시트가 점점 붉게 물들었고, "그냥 RAG로 갈까요?"라는 말이 슬랙에 올라왔습니다. 하지만 RAG가 만능은 아닙니다. 모델 자체의 추론 스타일, 답변 형식, 도메인 어휘를 내재화하려면 가중치 수준의 학습이 필요합니다. RAG 기반 AI 에이전트 가이드와 파인튜닝은 경쟁 관계가 아니라 상호 보완적입니다.
LoRA와 QLoRA는 그 딜레마를 실용적으로 해결해줍니다. 7B 모델을 RTX 4090 한 장(24 GB VRAM)에서, 13B 모델을 A100 40 GB 한 장에서 학습할 수 있게 됩니다. 이 글은 GPU 메모리를 어떻게 아끼는지 원리부터 파고들고, Hugging Face PEFT·bitsandbytes를 이용한 실전 학습 코드를 전체 공개합니다. 데이터셋 구성, 학습 후 평가와 병합, 그리고 2026년 현재 LoRA 변형 기법들의 선택 기준까지 다룹니다.
1. 왜 Full Fine-tuning이 아니라 LoRA인가 — VRAM 사용량 비교
Full Fine-tuning의 메모리 소비가 얼마나 큰지 수치로 먼저 짚어봅니다. 트랜스포머 모델을 학습할 때 GPU에 올라가는 데이터는 크게 네 가지입니다. 모델 가중치 자체, 역전파를 위한 기울기(gradient), Adam 옵티마이저의 1·2차 모멘텀 상태, 그리고 혼합 정밀도(mixed precision) 학습 시 FP32로 보관하는 마스터 가중치 사본입니다.
BF16 혼합 정밀도 기준으로 파라미터 하나에는 2 바이트가 필요합니다. 기울기도 동일하게 2 바이트, Adam 모멘텀 상태는 FP32이므로 파라미터당 4 바이트 × 2 = 8 바이트입니다. 마스터 가중치 사본까지 포함하면 파라미터 하나당 약 16 바이트가 필요합니다. Mistral 7B의 파라미터 수는 약 7.24 × 10⁹개이므로 7.24B × 16 B ≒ 115 GB의 VRAM이 필요합니다. 여기에 활성화 메모리가 더해지면 실제 요구치는 140~160 GB에 달합니다.
LoRA는 이 방정식을 근본적으로 바꿉니다. 사전학습 가중치 행렬 W를 완전히 동결(freeze)하고, 그 대신 두 개의 저랭크 행렬 A와 B만 학습합니다. A와 B의 곱 ΔW = BA는 W와 같은 모양이지만 rank r이 훨씬 작습니다. rank가 8일 때 Mistral 7B에서 학습 가능한 파라미터 수는 전체의 0.06~0.1% 수준으로 줄어듭니다.
아래 표는 Mistral 7B 기준으로 학습 방식별 대략적인 VRAM 요구치를 비교합니다. 실제 수치는 배치 크기·시퀀스 길이에 따라 달라집니다.
| 학습 방식 | 학습 파라미터 수 | 최소 VRAM (배치 1) | 권장 GPU |
|---|---|---|---|
| Full Fine-tuning (BF16) | 7.24B (100%) | ~115 GB | H100 2장 이상 |
| LoRA r=8 (BF16) | ~4.2M (0.058%) | ~18 GB | RTX 3090 / A10 |
| LoRA r=16 (BF16) | ~8.4M (0.116%) | ~20 GB | RTX 3090 / A10 |
| LoRA r=32 (BF16) | ~16.8M (0.232%) | ~22 GB | RTX 4090 |
| QLoRA r=8 (4-bit NF4) | ~4.2M (0.058%) | ~8 GB | RTX 3080 / V100 16G |
| QLoRA r=16 (4-bit NF4) | ~8.4M (0.116%) | ~10 GB | RTX 3090 / A10 |
학습 파라미터가 줄면 기울기 텐서와 옵티마이저 상태가 함께 줄기 때문에 절감 폭이 단순 계산보다 크게 나타납니다. 동결된 가중치는 기울기를 쌓을 필요가 없어서 기울기 메모리 자체가 거의 0에 수렴합니다.
Full Fine-tuning 대비 LoRA/QLoRA를 선택해야 할 기준은 명확합니다. 학습 데이터가 수백~수천 개 예시이고 GPU 예산이 제한적이라면 LoRA가 훨씬 합리적입니다. 반대로 100만 개 이상의 대규모 데이터로 모델의 세계 지식 자체를 교정해야 하고, GPU 클러스터 비용을 감당할 수 있다면 Full Fine-tuning이 정답일 수 있습니다. 현실에서 대부분의 도메인 적응·지시 학습 태스크는 LoRA/QLoRA로 충분합니다.
2. LoRA 수학적 원리: 저랭크 분해 직관과 rank·alpha 설정 기준
LoRA의 핵심 아이디어는 Hu et al. 2021, "LoRA: Low-Rank Adaptation of Large Language Models"에 담겨 있습니다. 논문의 핵심 가설은 "사전학습 모델의 가중치 업데이트는 본질적으로 낮은 내재 랭크(intrinsic rank)를 가진다"는 것입니다. 풀어서 말하면, 수십억 개 파라미터를 모두 바꿀 필요 없이 소수의 중요한 방향만 조정해도 태스크 적응이 가능하다는 얘기입니다.
수식으로는 다음과 같이 표현됩니다. 사전학습 가중치 행렬 W₀ ∈ ℝ^(d×k)에 대해, 순전파 출력은 다음과 같이 정의됩니다.
h = W₀x + ΔWx = W₀x + BAx
여기서 B ∈ ℝ^(d×r), A ∈ ℝ^(r×k)이고 r ≪ min(d, k)입니다. 초기화 시 A는 가우시안 분포로, B는 0으로 초기화됩니다. 덕분에 학습 시작 시 ΔW = BA = 0이고, 모델은 사전학습 가중치 그대로 동작합니다.
실제 가중치 업데이트가 적용될 때는 스케일링 계수 α/r이 곱해집니다.
h = W₀x + (α/r) · BAx
rank와 alpha를 어떻게 설정하는가
rank(r) 는 학습 가능한 파라미터 수와 표현력을 결정합니다. rank가 클수록 더 복잡한 변환을 표현할 수 있지만, VRAM 사용량과 과적합 위험이 늘어납니다.
- r = 4: 텍스트 스타일 변환처럼 변화의 폭이 좁은 태스크. 데이터가 1,000개 미만일 때.
- r = 8: 지시 학습(instruction tuning) 범용 설정. 대부분의 7B 모델에서 안전한 출발점.
- r = 16: 코드 생성, 수학적 추론처럼 복잡한 패턴 학습이 필요한 경우.
- r = 32: 도메인 전문 지식이 깊고 데이터가 충분할 때. Full Fine-tuning과의 품질 격차를 좁히고 싶을 때.
- r = 64 이상: 일반적으로 과적합 위험이 높고 실익이 적습니다. 데이터가 수십만 개 이상일 때 선택적으로 고려합니다.
alpha(α) 는 LoRA 업데이트의 강도를 결정하는 스케일링 계수입니다. 실효 학습률은 (α/r) × lr이 됩니다. 가장 안전한 설정은 α = 2r 또는 α = r입니다.
- α = r 설정 시 스케일링 계수가 1이 됩니다. 학습률을 그대로 쓰는 것과 동일한 효과입니다.
- α = 2r이 업계에서 가장 널리 쓰이는 경험적 설정입니다. rank를 바꾸더라도 α/r 비율이 2로 고정되므로 일관된 학습 강도를 유지합니다.
- α를 rank보다 지나치게 크게 설정(예: r=8, α=64)하면 학습 초기 기울기 폭발(gradient explosion)이 발생할 수 있습니다.
target_modules 설정도 중요합니다. 기본값은 어텐션 레이어의 q_proj와 v_proj에만 LoRA를 적용하지만, QLoRA 논문은 모든 선형 레이어로 확장하면 Full Fine-tuning에 가까운 품질을 낼 수 있다고 보고합니다.
3. QLoRA: 4비트 NF4 양자화 + Double Quantization + Paged Optimizer
QLoRA는 Dettmers et al. 2023, "QLoRA: Efficient Finetuning of Quantized LLMs"(arXiv:2305.14314)에서 제안한 기법으로, 세 가지 핵심 기술의 조합입니다.
NF4 (NormalFloat4) 양자화
NF4는 딥러닝 모델 가중치가 평균 0, 표준편차 σ의 정규분포를 따른다는 관찰에 기반합니다. 기존 INT4는 균등하게 분포된 4비트 코드북을 사용하지만, NF4는 정규분포의 분위수(quantile)에 맞춰 코드북 간격을 조정합니다. 분포 중심부(0 근방)의 정밀도가 높아지고, 거의 쓰이지 않는 극단값에는 더 거친 해상도가 할당됩니다. 같은 4비트라도 정보 손실이 적습니다.
Double Quantization
NF4로 양자화할 때 필요한 스케일(normalization constant) 자체도 메모리를 차지합니다. Double Quantization은 이 스케일 값들을 한 번 더 양자화합니다. 파라미터당 약 0.5비트를 추가로 절약할 수 있습니다.
Paged Optimizer
학습 도중 GPU 메모리 사용량은 일정하지 않습니다. 긴 시퀀스나 큰 배치에서 일시적인 스파이크가 발생합니다. Paged Optimizer는 NVIDIA의 통합 메모리(Unified Memory) 기능을 활용해 옵티마이저 상태를 CPU RAM으로 "페이징"합니다. GPU 메모리가 넘칠 것 같을 때 자동으로 슬롯을 비우고, 업데이트가 필요할 때 다시 GPU로 불러옵니다. Out-of-Memory 오류 없이 학습을 완주하게 해주는 안전망입니다.
세 기술의 합산 효과로 QLoRA는 Mistral 7B를 8~12 GB VRAM에서 학습할 수 있게 합니다. RTX 3080 한 장으로도 충분한 수준입니다.
4. Hugging Face PEFT + bitsandbytes + transformers 실전 파인튜닝 코드
아래 코드는 Mistral 7B를 QLoRA로 지시 학습하는 전체 파이프라인입니다. trl의 SFTTrainer를 사용하고, 4비트 NF4 양자화와 Paged AdamW를 적용합니다.
버전 참고: trl v1.0 이후
SFTTrainer의max_seq_length,dataset_text_field등 일부 인자가SFTConfig(또는TrainingArguments)로 이동했습니다. 아래 코드는 trl v1.x 기준이며, 구버전(v0.x)을 쓰고 있다면 해당 인자들을SFTTrainer에 직접 전달해야 합니다.
pip install torch transformers accelerate peft bitsandbytes trl datasets
import torch
from datasets import load_dataset
from transformers import (
AutoModelForCausalLM,
AutoTokenizer,
BitsAndBytesConfig,
)
from peft import LoraConfig, TaskType, get_peft_model, prepare_model_for_kbit_training
from trl import SFTTrainer, SFTConfig, DataCollatorForCompletionOnlyLM
# 1. 4비트 NF4 양자화 설정
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4", # NormalFloat4 사용
bnb_4bit_compute_dtype=torch.bfloat16, # 연산은 BF16으로
bnb_4bit_use_double_quant=True, # Double Quantization 활성화
)
MODEL_ID = "mistralai/Mistral-7B-v0.3"
tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
tokenizer.pad_token = tokenizer.eos_token
tokenizer.padding_side = "right"
model = AutoModelForCausalLM.from_pretrained(
MODEL_ID,
quantization_config=bnb_config,
device_map="auto",
)
# 2. kbit 학습을 위한 모델 준비 (gradient checkpointing 등)
model = prepare_model_for_kbit_training(model, use_gradient_checkpointing=True)
# 3. LoRA 어댑터 설정
lora_config = LoraConfig(
task_type=TaskType.CAUSAL_LM,
r=16, # rank
lora_alpha=32, # alpha = 2 * r (권장 비율)
target_modules="all-linear", # 모든 선형 레이어에 적용 (QLoRA 스타일)
lora_dropout=0.05,
bias="none",
)
model = get_peft_model(model, lora_config)
model.print_trainable_parameters()
# 출력 예시: trainable params: 약 1.1% || trainable%: 1.14
# 4. 데이터셋 로드 및 Alpaca 포맷 적용
dataset = load_dataset("tatsu-lab/alpaca", split="train")
ALPACA_TEMPLATE = (
"### Instruction:
{instruction}
"
"### Input:
{input}
"
"### Response:
{output}"
)
def format_alpaca(example):
return {
"text": ALPACA_TEMPLATE.format(
instruction=example["instruction"],
input=example.get("input", ""),
output=example["output"],
)
}
dataset = dataset.map(format_alpaca, remove_columns=dataset.column_names)
# 응답 부분만 loss 계산하도록 DataCollator 설정
response_template = "### Response:
"
collator = DataCollatorForCompletionOnlyLM(
response_template=response_template,
tokenizer=tokenizer,
)
# 5. 학습 설정 (trl v1.x: SFTConfig 사용)
training_args = SFTConfig(
output_dir="./mistral-7b-qlora-alpaca",
num_train_epochs=3,
per_device_train_batch_size=4,
gradient_accumulation_steps=4, # 유효 배치 = 16
learning_rate=2e-4,
lr_scheduler_type="cosine",
warmup_ratio=0.05,
bf16=True,
logging_steps=25,
save_strategy="steps",
save_steps=200,
optim="paged_adamw_32bit", # Paged Optimizer 활성화
report_to="none",
group_by_length=True, # 비슷한 길이끼리 묶어 패딩 최소화
gradient_checkpointing=True,
gradient_checkpointing_kwargs={"use_reentrant": False},
max_seq_length=2048, # trl v1.x: SFTConfig 인자
dataset_text_field="text", # trl v1.x: SFTConfig 인자
)
# 6. SFTTrainer 실행
trainer = SFTTrainer(
model=model,
args=training_args,
train_dataset=dataset,
data_collator=collator,
)
trainer.train()
trainer.save_model("./mistral-7b-qlora-alpaca/final")
몇 가지 실무 포인트를 짚어둡니다. gradient_accumulation_steps=4와 per_device_train_batch_size=4의 조합으로 유효 배치 크기를 16으로 설정했습니다. QLoRA 환경에서는 배치 크기를 키우는 것보다 gradient accumulation으로 보완하는 편이 VRAM 안전성에 유리합니다. group_by_length=True는 길이가 비슷한 샘플끼리 묶어 패딩 토큰을 줄여주므로, 실효 토큰 처리량을 높이는 효과가 있습니다. DataCollatorForCompletionOnlyLM은 응답(### Response: 이후) 부분에 대해서만 cross-entropy loss를 계산합니다. 지시 텍스트까지 loss에 포함하면 학습 시그널이 희석됩니다.

5. 학습 데이터셋 구성 전략: Instruction Tuning 포맷과 데이터 품질
LoRA/QLoRA 학습에서 rank 설정보다 훨씬 강력한 레버가 있습니다. 바로 데이터 품질입니다. 저품질 데이터 10만 개로 학습한 모델이 고품질 데이터 1,000개로 학습한 모델보다 실제 성능이 낮은 경우를 반복적으로 경험했습니다.
Instruction Tuning 포맷: Alpaca vs ShareGPT
두 가지 주요 포맷의 특징을 이해하고 목적에 맞게 선택해야 합니다.
Alpaca 포맷은 Instruction, Input, Output 세 필드로 구성됩니다. 단일 턴 지시 응답에 적합하고 데이터 생성이 단순합니다. 내부 도메인 FAQ, 분류, 요약 태스크에 잘 맞습니다.
ShareGPT 포맷은 conversations 배열로 멀티턴 대화를 표현합니다. Human/Assistant 롤이 교대로 등장하며, 챗봇·에이전트처럼 맥락을 이어가야 하는 태스크에 필수입니다. trl의 SFTTrainer는 ShareGPT 포맷을 chat template으로 자동 변환하는 기능을 내장합니다.
데이터 품질 필터링 체크리스트
현장에서 실제 적용하는 데이터 품질 기준입니다.
- 지시 명확성: 하나의 예시가 하나의 의도만 가르쳐야 합니다. 지시와 출력이 일관성을 잃은 데이터는 제거합니다.
- 응답 길이 분포: 너무 짧은 응답(20토큰 미만)과 지나치게 긴 응답(1,500토큰 초과)을 걸러냅니다. 양극단은 학습 시그널을 왜곡합니다.
- 중복 제거: MinHash 또는 단순 cosine similarity로 거의 동일한 예시를 제거합니다. 중복 데이터는 해당 표현에 대한 과적합을 유발합니다.
- 도메인 비율: 도메인 특화 데이터에 일반 지시 데이터를 10~20% 혼합하면 범용 능력이 퇴화(catastrophic forgetting)하는 것을 완화합니다.
- 언어 일관성: 한국어 모델을 학습할 때 영어 예시가 무분별하게 섞이면 모델이 한국어 쿼리에도 영어로 답하는 경향이 생깁니다.
파인튜닝이 유리한 경우 vs RAG가 유리한 경우
파인튜닝과 RAG 중 어느 것을 선택할지는 실무에서 가장 많이 받는 질문 중 하나입니다.
| 상황 | 파인튜닝 | RAG |
|---|---|---|
| 새로운 지식을 추가해야 함 | 부적합 (지식 주입 불안정) | 적합 |
| 응답 형식·스타일을 바꿔야 함 | 적합 | 비효율적 |
| 도메인 어휘·전문 용어 이해 | 적합 | 보조 수단 |
| 최신 데이터 반영 필요 | 부적합 (재학습 필요) | 적합 |
| 특정 추론 패턴 강화 | 적합 | 불가 |
| 지식 출처 인용 필요 | 부적합 | 적합 |
두 기법을 함께 쓰는 것이 실무에서 가장 효과적입니다. 파인튜닝으로 스타일과 추론 패턴을 최적화하고, RAG로 최신 지식과 인용 근거를 제공합니다. LLM 추론 비용 최적화 가이드에서 다루는 모델 라우팅 전략과 함께 설계하면 비용까지 제어할 수 있습니다.
6. 학습 후 평가: loss curve 해석, merge_and_unload, 추론 트레이드오프
Training Loss Curve 읽는 법
Loss가 지속적으로 감소하다가 평탄해지는 패턴이 이상적입니다. 실무에서 자주 마주치는 문제 패턴 세 가지를 정리합니다.
- Loss가 초반에 급격히 떨어지고 이후 거의 변화 없음: 대개 학습률이 너무 높거나 데이터셋이 지나치게 단순할 때 나타납니다. cosine scheduler의 warmup 비율을 0.1~0.15로 높이거나 학습률을 절반으로 줄여봅니다.
- Loss가 계속 감소하지만 검증 loss가 상승: 과적합의 전형적인 신호입니다. lora_dropout을 0.1~0.15로 높이거나 데이터에 다양성을 추가합니다.
- Loss 스파이크(갑작스러운 급등): 배치 안에 비정상적으로 긴 시퀀스가 포함됐거나 NaN 기울기가 발생했을 가능성입니다. max_seq_length 필터링과 gradient clipping 적용을 확인합니다.
LoRA 가중치 병합: merge_and_unload
학습이 끝난 LoRA 어댑터는 기반 모델과 병합해 추론 오버헤드를 없앨 수 있습니다. 병합 후에는 추론 속도가 기반 모델과 동일하고, 별도 PEFT 라이브러리 없이 일반 transformers만으로 서빙할 수 있습니다.
from peft import PeftModel
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
BASE_MODEL_ID = "mistralai/Mistral-7B-v0.3"
ADAPTER_PATH = "./mistral-7b-qlora-alpaca/final"
MERGED_OUTPUT = "./mistral-7b-merged"
# 병합 시에는 BF16으로 기반 모델을 로드 (4비트 양자화 없이)
tokenizer = AutoTokenizer.from_pretrained(BASE_MODEL_ID)
base_model = AutoModelForCausalLM.from_pretrained(
BASE_MODEL_ID,
torch_dtype=torch.bfloat16,
device_map="auto",
)
# LoRA 어댑터 로드 후 병합
merged_model = PeftModel.from_pretrained(base_model, ADAPTER_PATH)
merged_model = merged_model.merge_and_unload()
# 병합된 모델 저장 (safetensors 형식 권장)
merged_model.save_pretrained(MERGED_OUTPUT, safe_serialization=True)
tokenizer.save_pretrained(MERGED_OUTPUT)
print("병합 완료:", MERGED_OUTPUT)
병합 vs 어댑터 분리 서빙 트레이드오프
| 항목 | merge_and_unload | 어댑터 분리 서빙 |
|---|---|---|
| 추론 속도 | 기반 모델과 동일 | 어댑터 연산 추가 (소폭 오버헤드) |
| VRAM | 기반 모델 전체 필요 | 4비트 양자화 유지 가능 |
| 어댑터 교체 유연성 | 불가 (재병합 필요) | 런타임 스와핑 가능 |
| 적합한 상황 | 단일 태스크 프로덕션 서빙 | 멀티 태스크 A/B 테스트, 실험 단계 |
프로덕션에서 태스크가 고정됐다면 병합 후 GGUF 양자화(llama.cpp 호환)나 AWQ 변환을 거쳐 더 경량화된 형태로 서빙하는 것이 일반적입니다. LLM 평가 파이프라인 CI 통합과 함께 구성하면 병합 전·후 품질 회귀를 자동으로 검증할 수 있습니다.
7. LoRA vs QLoRA vs DoRA vs LoRA+: 2026 PEFT 변형 기법 선택 기준
2025~2026년에 걸쳐 LoRA 변형 기법들이 빠르게 발전했습니다. 논문 수가 많아 어느 것을 선택해야 할지 혼란스러울 수 있습니다. 먼저 주요 기법들을 비교 표로 정리합니다.
| 기법 | 학습 파라미터 수 | 추론 오버헤드 | 주요 강점 | 주요 약점 | 권장 상황 |
|---|---|---|---|---|---|
| LoRA | 낮음 | 없음 (병합 후) | 범용성, 안정성, 생태계 | 표현력 한계 | 범용 지시 학습, 빠른 실험 |
| QLoRA | 낮음 (4bit) | 없음 (병합 후) | 메모리 절약 | 학습 속도 소폭 저하 | GPU 메모리 제약 환경 |
| DoRA | 낮음~중간 | 없음 (병합 후) | 낮은 rank에서도 높은 품질 | 구현 복잡성, 다소 느린 학습 | 품질 우선 태스크, 낮은 rank 선호 시 |
| QDoRA | 낮음 (4bit) | 없음 (병합 후) | QLoRA + DoRA의 장점 조합 | 최신 기법, 레퍼런스 부족 | 저메모리 + 고품질이 동시에 필요할 때 |
| LoRA+ | 낮음 | 없음 (병합 후) | A/B 행렬 다른 학습률 적용 | 튜닝 파라미터 증가 | 수렴 속도 개선이 필요할 때 |
| PiSSA | 낮음 | 없음 (병합 후) | SVD 초기화로 빠른 수렴 | 초기화 비용 | 학습 에폭 제한, 빠른 수렴 필요 |
실무에서의 선택 기준
GPU 메모리가 16 GB 이하 — QLoRA r=8~16이 사실상 유일한 선택입니다. NF4 + Double Quantization으로 운신의 폭을 확보합니다.
GPU 메모리가 24~40 GB — LoRA BF16 r=16~32가 가장 안정적입니다. 품질을 우선한다면 DoRA r=16을 시도할 가치가 있습니다.
데이터셋이 1만 개 이상이고 품질을 극대화해야 함 — DoRA 또는 QDoRA를 고려합니다. DoRA는 낮은 rank에서 LoRA보다 높은 품질을 보고하는 연구들이 있습니다.
수렴 속도가 중요하고 학습 에폭을 줄이고 싶음 — PiSSA를 시도합니다. SVD 초기화로 LoRA보다 빠른 초기 수렴을 보입니다.
전반적으로 기법 선택보다 학습률·데이터 품질이 훨씬 강력한 레버입니다. 새로운 기법을 도입하기 전에 기본 LoRA에서 학습률과 데이터 정제를 충분히 튜닝하는 것이 우선입니다.
DoRA를 PEFT에서 활성화하는 방법
DoRA는 PEFT 라이브러리 최신 버전에서 LoraConfig에 use_dora=True만 추가하면 됩니다. 현재 선형 레이어와 Conv2D 레이어를 지원합니다.
from peft import LoraConfig, TaskType
dora_config = LoraConfig(
task_type=TaskType.CAUSAL_LM,
r=16,
lora_alpha=32,
target_modules="all-linear",
lora_dropout=0.05,
bias="none",
use_dora=True, # DoRA 활성화
)
QDoRA는 BitsAndBytesConfig로 4비트 양자화를 적용한 상태에서 use_dora=True를 함께 설정하면 됩니다. Hugging Face PEFT 공식 문서에서 최신 파라미터 목록을 확인할 것을 권장합니다.
8. 결론: LoRA/QLoRA 파인튜닝 실무 체크리스트
LoRA와 QLoRA는 "GPU 없이는 LLM 파인튜닝 불가"라는 진입 장벽을 현실적인 수준으로 낮춰줬습니다. 7B 모델을 소비자용 GPU 한 장에서 수 시간 안에 학습할 수 있고, 어댑터 파일 크기는 수십~수백 MB에 불과해 배포와 버전 관리도 간편합니다.
그러나 LoRA가 만능 해결책은 아닙니다. 적용해서는 안 되는 상황도 분명합니다. 모델에 완전히 새로운 사실 지식을 주입하려는 목적이라면 파인튜닝보다 RAG가 더 신뢰할 수 있는 선택입니다. 가중치에 지식을 새기는 작업은 hallucination을 줄이기보다 특정 방향으로 편향을 강화할 위험이 있습니다.
아래는 프로젝트를 시작하기 전 반드시 확인하는 실무 체크리스트입니다.
- 목적 명확화: 응답 형식·스타일·추론 패턴 교정이 목적이면 LoRA/QLoRA. 최신 지식 주입이 주목적이면 RAG를 우선 고려합니다.
- rank 설정: 소규모 데이터(1,000개 미만)에는 r=8, 충분한 데이터(1만 개 이상)에는 r=16~32로 시작하고, alpha는 2r로 고정합니다.
- 응답 전용 loss: DataCollatorForCompletionOnlyLM으로 Instruction 부분에는 loss가 계산되지 않도록 설정합니다.
- 데이터 정제: 중복 제거, 응답 길이 필터(20~1,500토큰), 일반 지시 데이터 10~20% 혼합으로 catastrophic forgetting을 예방합니다.
- 병합 전 검증: merge_and_unload 전 자동 평가 파이프라인으로 병합 전후 품질을 비교하고, 기반 모델 대비 성능 회귀가 없음을 확인합니다.