- LoRA(Low-Rank Adaptation) 는 LLM 전체를 다시 학습하지 않고 작은 저랭크 행렬만 업데이트해 파인튜닝 비용을 줄이는 기법이며, 이 Studio는 LoRA 레이어를 직접 구현해 동작을 확인함
- 핵심은 일반 파인튜닝의 가중치 변화 ΔW를 두 작은 행렬
A, B의 곱으로 근사하는 것이며, 랭크 r이 작을수록 학습 파라미터와 표현 용량이 함께 줄어듦
- 5,000×10,000 가중치 행렬은 5,000만 파라미터를 갖지만,
r=8 LoRA는 B 5,000×8과 A 8×10,000만 추가해 12만 파라미터로 400배 작아짐
- DistilBERT 기반 IMDb 감성 분류에서 기본 LoRA는 Test acc 89.44% 를 기록해 마지막 두 레이어만 학습한 86.22%보다 높고, 전체 파인튜닝의 92.31%보다는 낮았음
- 하이퍼파라미터 탐색 후 LoRA는 약 50만 학습 파라미터로 Val acc 92.96%, Test acc 92.39%를 기록해, 6,695만 5,010개 파라미터를 학습한 전체 파인튜닝보다 약간 높은 정확도를 냄
LoRA가 줄이는 파인튜닝 비용
- LoRA는 Low-Rank Adaptation의 약자로, LLM을 더 효율적으로 파인튜닝하기 위한 기법임
- 일반 파인튜닝은 딥러닝 모델의 모든 파라미터를 조정하지만, LoRA는 작은 저랭크 행렬 집합만 업데이트함
- 사전학습 LLM은 여러 작업에 쓸 수 있으나, 특정 데이터셋이나 작업에 맞추려면 파인튜닝이 유용함
- 모델이 커질수록 모든 레이어를 업데이트하는 방식은 계산 비용 부담이 커짐
ΔW를 작은 행렬 곱으로 근사
- 일반 파인튜닝에서는 가중치 행렬
W의 업데이트를 ΔW로 계산함
- LoRA는
ΔW를 두 작은 행렬 A와 B의 곱으로 근사함
- PCA나 SVD에 익숙하다면
ΔW를 A와 B로 분해하는 방식에 가깝게 볼 수 있음
- 랭크
r은 LoRA의 하이퍼파라미터임
- 더 작은
r은 학습 파라미터 수를 줄이고 학습을 빠르게 하며 계산 요구량을 낮출 수 있음
- 동시에 작업별 정보를 포착하는 저랭크 행렬의 용량도 낮아짐
- 5,000×10,000 가중치 행렬 예시:
- 일반 업데이트
ΔW: 총 5,000만 파라미터
r=8 LoRA: B 5,000×8, A 8×10,000
- 추가 파라미터: 80,000 + 40,000 = 120,000개
- 일반 파인튜닝 대비 400배 작음
- 실제 사용에서는 성능과 비용의 균형을 찾기 위해 여러
r 값을 실험해야 함
PyTorch로 LoRA 레이어 구현
- 기본
LoRALayer는 입력 차원, 출력 차원, 랭크, 스케일링 계수 alpha를 받음
class LoRALayer(torch.nn.Module):
def __init__(self, in_dim, out_dim, rank, alpha):
super().__init__()
std_dev = 1 / torch.sqrt(torch.tensor(rank).float())
self.A = torch.nn.Parameter(torch.randn(in_dim, rank) * std_dev)
self.B = torch.nn.Parameter(torch.zeros(rank, out_dim))
self.alpha = alpha
def forward(self, x):
x = self.alpha * (x @ self.A @ self.B)
return x
in_dim은 LoRA를 적용할 레이어의 입력 차원이고, out_dim은 출력 차원임
rank는 A, B 행렬의 복잡도와 LoRA가 추가하는 파라미터 수를 제어함
alpha는 기존 모델 가중치에 LoRA가 주는 변화의 크기를 결정함
alpha가 높으면 모델 동작을 더 크게 조정함
alpha가 낮으면 더 미세한 변화가 됨
A는 작은 난수로 초기화하고 표준편차는 랭크의 제곱근으로 정함
- 초기
A 값이 너무 커지지 않게 하기 위한 선택임
B는 0으로 초기화함
- 학습 시작 전에는
B=0이므로 AB=0
- 역전파로
A와 B가 업데이트되기 전에는 LoRALayer가 원래 가중치에 영향을 주지 않음
Linear 레이어를 LinearWithLoRA로 교체
- LoRA는 보통 신경망의 Linear/피드포워드 레이어에 적용됨
- 기존 forward가 두 Linear 레이어를 순서대로 호출한다면, LoRA 적용 후에는 각 Linear 출력에 LoRA 출력을 더함
def forward(self, x):
x = self.linear_1(x) + self.lora_1(x)
x = F.relu(x)
x = self.linear_2(x) + self.lora_2(x)
return logits
- 기존 PyTorch 모델을 수정할 때는 각
Linear 레이어를 LinearWithLoRA로 바꾸는 방식이 단순함
class LinearWithLoRA(torch.nn.Module):
def __init__(self, linear, rank, alpha):
super().__init__()
self.linear = linear
self.lora = LoRALayer(
linear.in_features, linear.out_features, rank, alpha
)
def forward(self, x):
return self.linear(x) + self.lora(x)
LinearWithLoRA는 원래 Linear 레이어와 새 LoRALayer를 함께 보유함
- 사전학습 모델의
Linear 레이어를 LinearWithLoRA로 교체하면 LoRA를 장착한 뒤 파인튜닝할 수 있음
DistilBERT로 IMDb 분류 실험
from transformers import AutoModelForSequenceClassification
model = AutoModelForSequenceClassification.from_pretrained(
"distilbert-base-uncased", num_labels=2)
- 새 LoRA 가중치만 학습하기 위해 모든 모델 파라미터의
requires_grad를 False로 설정함
for param in model.parameters():
param.requires_grad = False
- DistilBERT에는 6개 Transformer 레이어가 있으며, 각 레이어 안에 Linear 레이어가 있음
- attention에는
q_lin, k_lin, v_lin, out_lin이 있음
- FFN에는
lin1, lin2가 있음
- 출력 쪽에는
pre_classifier, classifier 두 Linear 레이어가 있음
선택적으로 LoRA를 적용하는 설정
- 기본 LoRA 설정은 attention의 query와 value 가중치 행렬에만 LoRA를 적용함
lora_r = 8
lora_alpha = 16
lora_dropout = 0.05
lora_query = True
lora_key = False
lora_value = True
lora_projection = False
lora_mlp = False
lora_head = False
- 루프를 돌며 DistilBERT의 각 Transformer 레이어에서 선택된 Linear 레이어를
LinearWithLoRA로 교체함
lora_query=True이면 q_lin 교체
lora_key=True이면 k_lin 교체
lora_value=True이면 v_lin 교체
lora_projection=True이면 out_lin 교체
lora_mlp=True이면 ffn.lin1, ffn.lin2 교체
lora_head=True이면 pre_classifier, classifier 교체
- 교체 후 모델 출력에서
q_lin과 v_lin 등이 LinearWithLoRA로 바뀐 것을 확인할 수 있음
기본 LoRA와 일반 파인튜닝 비교
- 기본 LoRA 설정으로 IMDb Movie Reviews 분류를 학습한 결과:
- Train acc: 92.15%
- Val acc: 89.98%
- Test acc: 89.44%
- 마지막 두 출력 레이어만 파인튜닝한 결과:
- Train acc: 86.68%
- Val acc: 87.26%
- Test acc: 86.22%
- 학습 파라미터 수: 592,130개
- 기본 LoRA는 마지막 두 레이어만 학습하는 방식보다 Test acc가 높았고, 학습 파라미터 수는 147,456개로 더 적음
- 전체 레이어를 전통적인 방식으로 파인튜닝한 결과:
- Train acc: 96.41%
- Val acc: 92.80%
- Test acc: 92.31%
- 학습 파라미터 수: 66,955,010개
- 전체 파인튜닝은 기본 LoRA보다 Test acc가 약 2% 높지만, LoRA 설정보다 약 450배 많은 파라미터를 업데이트함
LoRA 하이퍼파라미터 탐색
- LoRA 성능은
lora_r, lora_alpha, 적용 대상 레이어 설정에 따라 달라질 수 있음
03_finetune-lora.py는 하이퍼파라미터를 명령줄 인자로 받음
python 03_finetune-lora.py --lora_alpha 32 --lora_r 16
- 다른 LoRA 적용 대상을 함께 켤 수도 있음
python 03_finetune-lora.py \
--lora_alpha 32 \
--lora_r 16 \
--lora_query True \
--lora_key True \
--lora_value True \
--lora_projection True \
--lora_mlp True \
--lora_head True
03_gridsearch.py는 사용 가능한 모든 GPU에서 다음 그리드를 실행함
alpha_values = [1, 4, 8, 16, 32, 64]
rank_values = [1, 2, 4, 8, 16, 32]
lora_query = ["True"]
lora_key = ["False", "True"]
lora_value = ["True"]
lora_projection = ["False", "True"]
lora_mlp = ["False", "True"]
lora_head = ["False", "True"]
- 스크립트는 Visual Studio Code, 명령줄 터미널, Job으로 실행할 수 있으며, Job은 완료 후 자동 종료됨
- 결과는
results.txt에 저장됨
그리드 탐색에서 나온 최고 설정
results.txt 기준 가장 좋은 하이퍼파라미터 구성은 다음과 같음
lora_r: 8
lora_alpha: 1
lora_query: True
lora_key: False
lora_value: True
lora_projection: False
lora_mlp: True
lora_head: False
- 이 설정의 결과:
- Val acc: 92.96%
- Test acc: 92.39%
- 이 LoRA 설정은 학습 파라미터가 약 500k이며, 전체 파인튜닝의 66M 파라미터보다 훨씬 적음
- 정확도는 전체 파인튜닝의 Val acc 92.80%, Test acc 92.31%보다 약간 높음
실행 환경과 추가 자료
- Studio 상단의 Run을 클릭하면 코드를 포함한 환경을 복제할 수 있음
- Studio 복제 후에는 추가 설치, 다운로드, 설정 단계 없이 코드 파일을 실행할 수 있음
- 관련 노트북과 스크립트:
00_lora-layer.ipynb: LoRA 레이어 구현
01_finetune-last-layers.ipynb: 마지막 레이어 파인튜닝
02_finetune-with-lora.ipynb: LoRA 파인튜닝
03_finetune-lora.py: LoRA 하이퍼파라미터 인자 실행
03_gridsearch.py: LoRA 하이퍼파라미터 그리드 탐색
04_finetune-all-layers.ipynb: 전체 레이어 파인튜닝
- 추가 자료: