LoRA와 QLoRA - 적은 자원으로 LLM 파인튜닝하기
LLM을 우리 도메인에 맞게 길들이고 싶을 때 가장 먼저 떠오르는 건 파인튜닝입니다. 그런데 수십억 개 파라미터를 통째로 다시 학습하려면 GPU가 몇 장씩 필요합니다. 이 부담을 확 낮춘 기법이 LoRA이고, 거기서 메모리를 한 번 더 줄인 것이 QLoRA입니다.
이 글에서는 두 기법이 왜 나왔고, 원리가 무엇이며, 실무에서 언제 쓰는지를 정리합니다.
1. 풀 파인튜닝의 부담부터 보기
풀 파인튜닝(full fine-tuning)은 모델의 모든 가중치를 다시 학습합니다. 이게 왜 무거운지 감을 잡아봅시다.
70억(7B) 파라미터 모델을 예로 들면, 학습에 필요한 메모리는 파라미터 자체만이 아닙니다.
| 항목 | 대략 필요량(7B 기준) | 설명 |
|---|---|---|
| 모델 가중치 | ~14GB | fp16 기준 파라미터당 2바이트 |
| 그래디언트 | ~14GB | 가중치와 같은 크기 |
| 옵티마이저 상태 | ~28GB | Adam은 파라미터당 상태 2개 |
합치면 7B 모델 하나 학습에 50GB 이상이 듭니다. 여기에 활성값(activation)까지 더하면 소비자용 GPU 한 장으론 어림도 없습니다. 게다가 학습 결과물은 원본과 똑같은 크기의 새 모델 파일이 통째로 나옵니다. 도메인별로 여러 개 만들면 저장·배포 비용도 부담입니다.
핵심 문제: "성능은 원하지만, 모든 파라미터를 건드리는 건 너무 비싸다."
2. LoRA - 저랭크 어댑터라는 아이디어
LoRA(Low-Rank Adaptation) 의 발상은 단순합니다.
"원본 가중치는 얼어붙게(freeze) 두고, 그 옆에 작은 행렬 두 개만 새로 학습하자."
2-1. 저랭크 분해
파인튜닝이란 결국 원본 가중치 W에 변화량 ΔW를 더하는 일입니다.
새 가중치 = W(원본, 동결) + ΔW(학습으로 얻는 변화량)
LoRA는 이 ΔW를 통째로 학습하는 대신, 두 개의 작은 행렬 곱으로 근사합니다.
ΔW ≈ B × A
- W : (d × d) 큰 행렬 → 동결(학습 안 함)
- A : (r × d) 작은 행렬 → 학습
- B : (d × r) 작은 행렬 → 학습
- r : 랭크(rank), 보통 8 / 16 / 32 처럼 아주 작음
여기서 r이 랭크입니다. d가 4096이고 r이 8이라면, 원래 4096 × 4096 ≈ 1,600만 개를 학습할 것을 2 × (8 × 4096) ≈ 6.5만 개만 학습합니다. 학습 파라미터가 수백 분의 1로 줄어듭니다.
2-2. 왜 이게 통할까
"변화량을 저랭크로 근사해도 되나?" 하는 의문이 생깁니다. LoRA 논문의 관찰은, 파인튜닝으로 생기는 가중치 변화가 실제로는 낮은 랭크 구조를 가진다는 것입니다. 즉 큰 행렬을 다 흔들 필요 없이, 몇 개의 방향만 조정해도 도메인 적응이 된다는 뜻입니다.
# 개념 코드: 선형 계층에 LoRA를 얹는 모습
class LoRALinear:
def __init__():
.base = base_linear
.base.weight.requires_grad =
d_out, d_in = base_linear.weight.shape
.A = zeros(r, d_in, requires_grad=)
.B = zeros(d_out, r, requires_grad=)
.scale = alpha / r
():
.base(x) + (x @ .A.T @ .B.T) * .scale