트랜스포머·PEFT 2일 과정8장 · PEFT와 LoRA 학습6 / 18← 이전목차다음 →MetaContents

8장. PEFT와 LoRA 학습

LoRA 의 원리

벽에 걸린 큰 지도를 고친다고 해 봅시다. 지도에 바로 그리면 되돌릴 수 없습니다. 그래서 투명 필름을 한 장 덮고 그 위에만 새 길을 그립니다. 필름을 떼면 원래 지도가 그대로 나오고, 필름을 바꿔 끼우면 다른 용도의 지도가 됩니다. LoRA(Low-Rank Adaptation)가 이 필름입니다. 큰 가중치 W 는 그대로 두고, 학습으로 생기는 변화량만 작은 행렬 두 개(A, B)로 그립니다.

기존 가중치 W 옆에 A 와 B 두 사다리꼴을 붙여 입력 x 를 두 길로 보낸 뒤 출력 h 에서 더하는 강사자료 LoRA 구조 그림

강사자료 그림을 아래에서 위로 읽습니다. 맨 아래 입력 x(1×4096)가 두 길로 갈라집니다. 왼쪽 파랑 상자가 원래 가중치 W(4096×4096)입니다. 오른쪽의 두 사다리꼴이 LoRA 입니다. 아래 사다리꼴 A 가 4096 칸을 r = 8 칸으로 줄이고, 위 사다리꼴 B 가 8 칸을 다시 4096 칸으로 늘립니다. 두 길의 결과를 맨 위에서 더한 것이 출력 h 입니다. 그림에서 B = 0 으로 적힌 것은 학습을 시작할 때 B 를 0 으로 채운다는 뜻이고, A 는 작은 무작위 값으로 채웁니다.

원래 길 x×W, LoRA 길 x×A×B, 두 결과 합치기의 세 걸음을 이은 흐름도

같은 흐름을 글자로 쓰면 이렇습니다. 노트북은 입력을 가로줄(행 벡터)로 놓고 씁니다.

y = x·W + (alpha / r) · x·A·B

기호와 코드

기호뜻크기(Qwen 의 q_proj)학습하나코드
W기존 가중치896 × 896동결기반 모델의 q_proj
A입력 칸 수를 r 로 줄이는 행렬896 × 16학습lora_A
Br 을 출력 칸 수로 늘리는 행렬16 × 896학습lora_B
r중간 차원(랭크)16정하는 값r
alphaLoRA 길의 배율을 정하는 값32 → 배율 32 ÷ 16 = 2정하는 값lora_alpha

파라미터 수 손계산

LoRA 의 파라미터 수는 r × (입력 칸 수 + 출력 칸 수) 입니다.

경우원래 WA + B비율
강사자료 그림(4096×4096, r = 8)4096 × 4096 = 16,777,2164096 × 8 + 8 × 4096 = 65,536약 0.39%
Qwen 의 q_proj(896×896, r = 16)896 × 896 = 802,816896 × 16 + 16 × 896 = 28,672약 3.6%

LoRA 의 좋은 성질

성질까닭지도로 치면
시작할 때 원래 모델과 똑같다B = 0 이므로 A·B = 0아무것도 안 그린 빈 필름
병합하면 추론 속도가 그대로다W' = W + (alpha/r)·A·B 로 한 장이 된다필름을 지도에 코팅
어댑터를 바꿔 끼울 수 있다병합하지 않으면 A·B 만 따로 있다용도별 필름 여러 장

짧은 예시

Python
import numpy as np

rng = np.random.default_rng(42)       # 씨앗을 고정한 난수 생성기
d, r, alpha = 6, 2, 4                 # 작은 층: 입력·출력 6칸, 랭크 2, 배율 4/2 = 2
x = rng.normal(size=(1, d))           # 입력 한 줄(1×6)
W = rng.normal(size=(d, d))           # 얼려 둔 원래 가중치(6×6)
A = rng.normal(scale=0.01, size=(d, r))  # A: 작은 무작위 값(6×2)
B = np.zeros((r, d))                  # B: 0 으로 시작(2×6)

y_base = x @ W                                # 원래 길
y_lora = x @ W + (alpha / r) * (x @ A @ B)    # 원래 길 + LoRA 길
print(np.allclose(y_base, y_lora))            # 결과: True  (B = 0 이라 똑같다)

B = rng.normal(size=(r, d))                   # 학습으로 B 가 바뀌었다고 치자
y_lora = x @ W + (alpha / r) * (x @ A @ B)
print(np.allclose(y_base, y_lora))            # 결과: False (이제 답이 달라진다)

# 병합: W' = W + (alpha/r)·A·B 한 장으로 계산해도 같은 답
W_merged = W + (alpha / r) * (A @ B)
print(np.allclose(x @ W_merged, y_lora))      # 결과: True

# 파라미터 수 손계산
print(896 * 896, 896 * 16 + 16 * 896)         # 결과: 802816 28672
print(f"{(896 * 16 + 16 * 896) / (896 * 896):.1%}")  # 결과: 3.6%

이 코드가 뜻하는 것

6×6 짜리 작은 지도에 두께 2짜리 필름을 덮어 본 코드입니다. 빈 필름(B = 0)일 때는 원래 지도와 같고, 필름에 무언가를 그리면(B 가 바뀌면) 답이 달라지며, 필름을 코팅해 붙여도(병합) 답은 같습니다. 여기서 지도가 W, 필름이 A @ B 입니다.

줄하는 일쉽게 말하면
B = np.zeros((r, d))B 를 0 으로 채운다빈 필름
x @ W + (alpha / r) * (x @ A @ B)두 길을 더한다지도 + 필름
W + (alpha / r) * (A @ B)필름을 W 에 더해 한 장으로코팅
896 * 16 + 16 * 896A 와 B 의 칸 수를 더한다필름 두 장의 넓이

@ 는 행렬곱입니다. x @ A 가 (1×6)·(6×2) = 1×2 로 줄었다가, @ B 에서 (1×2)·(2×6) = 1×6 으로 다시 늘어납니다. 1장의 shape 규칙 그대로입니다.

주의할 점

  • A 와 B 를 둘 다 0 으로 시작하면 그래디언트도 0 이 되어 학습이 되지 않습니다. 그래서 A 는 작은 무작위 값, B 는 0 으로 시작합니다.
  • 노트북의 식은 입력을 가로줄로 놓은 x·A·B 꼴입니다. 다른 글에서는 세로줄로 놓아 W + BA 로 쓰기도 합니다. 같은 내용을 쓰는 방향만 다릅니다. PyTorch 의 lora_A 는 Linear(in_features=896, out_features=16) 처럼 찍힙니다.
  • r 이 작을수록 변화량을 단순하게만 그릴 수 있습니다. 너무 작으면 배워야 할 것을 다 담지 못합니다.
← 이전 절8장 · PEFT 기법 한눈에 보기다음 절 →8장 · Qwen 에 LoRA 붙이기와 하이퍼파라미터