Post

[경량화] PEFT (Parameter-Efficient Fine-Tuning)

[경량화] PEFT (Parameter-Efficient Fine-Tuning)

PEFT (Parameter-Efficient Fine-Tuning)

GPT-3, LLaMA, Stable Diffusion과 같은 수십억 개 이상의 파라미터를 가진 사전 학습 모델을 Full Fine-Tuning하려면, 막대한 GPU 메모리와 긴 학습 시간이 필요하다.

또한, 이미 학습된 모델을 새로운 task에 맞춰 파인튜닝하는 과정에서 이전에 학습했던 지식을 잊어버리는 치명적 망각 (Catastrophic Forgetting) 현상이 발생할 수도 있다.

이러한 문제를 해결하기 위해, 특정 task에 맞게 파인튜닝할 때 모델 전체가 아닌 일부 파라미터만 수정하거나 소수의 새로운 파라미터를 추가하여 훈련하는 기법들이 등장했다. 이를 통칭하여 PEFT라고 부른다.

아래는 PEFT 기법을 접근 방식에 따라 크게 3가지 분류 (taxonomy)한 그림이다.

fig1 출처: Scaling Down to Scale Up: A Guide to Parameter-Efficient Fine-Tuning

  • Addition-based methods: 기존 LLM의 가중치는 완전히 동결시키고, 학습 가능한 새로운 파라미터나 모듈을 추가하여 이 추가된 부분만 학습하는 방식
  • Selection-based methods: 새로운 파라미터를 추가하지 않고, 모델이 원래 가지고 있던 수많은 파라미터 중 일부를 선택하여 해당 부분만 업데이트하는 방식
  • Reparametrization-based methods: 기존 가중치의 변화량을 직접 학습하지 않고, 이를 더 적은 수의 파라미터로 효율적이게 표현 (재매개변수화)하여 학습하는 방식

Adapter

Adapter 기법은 거대한 원본 모델의 가중치는 그대로 동결하고, 모델의 layer 중간에 Adapter라고 불리는 작은 plug-in 모듈을 삽입하여 이 모듈만 학습하는 방식이다.

학습 파라미터 수를 줄여 효율을 극대화하기 위해 Bottleneck 구조를 사용하였으며, Down-projection → Non-linearity → Up-projection의 단계로 이루어져 있다.

이후 Residual Connection을 통해 Adapter를 통과하기 전의 입력값 $x$를 Adapter를 통과한 출력에 더하여, 원본 모델의 지식을 보존한다.

$f$를 비선형 함수라고 할때, Adapter 모듈은 아래와 같이 표현할 수 있다.

\[\text{Adapter}(x)=\text{Up}\left(f\left(\text{Down}(x)\right)\right) \tag{1}\]

최종적으로 Adapter가 들어간 네트워크 구조를 아래와 같이 표현할 수 있다.

\[y=x+\text{Adapter}(x) \tag{2}\]

하지만 추론 시 삽입된 Adapter 모듈을 반드시 거쳐가야 하기 때문에, 추론 지연 (latency)이 발생한다.

fig2 [출처]

LoRA (Low-Rank Adaptation)

LoRA: Low-Rank Adaptation of Large Language Models 연구에서 제안한 방법으로, LLM을 효율적으로 파인튜닝하기 위한 기법이다.

사전 학습된 가중치를 $W_0$라고 표현할 때, 파인튜닝 과정은 기존 가중치에 새로운 변화량 $\Delta W$를 더하는 형태로 표현할 수 있다.

\[W_0+\Delta W \tag{3}\]

만약 모델 전체를 업데이트하는 Full Fine-Tuning을 한다면, $\Delta W$는 $W_0$와 동일한 크기의 거대한 행렬이 되어 계산량과 메모리 사용량이 매우 커진다.

LoRA는 파인튜닝할 때 업데이트되는 가중치 변화량 $\Delta W$가 실제로는 복잡하지 않고 매우 단순한 Low-Rank 구조를 가질 것이라는 가정에서 출발하였다.
이러한 가정은 LLM이 이미 방대한 언어 지식을 보유하고 있고, 파인튜닝은 새로운 지식을 다시 학습하는 것이 아니라 기존의 지식을 특정 목적에 맞게 조율만하는 과정이라는 아이디에서 기반하였다.

LoRA는 $\Delta W$를 직접 학습하는 대신, 이를 두 개의 훨씬 작은 Low-Rank 행렬 $B$와 $A$의 곱으로 쪼개서 학습한다.

\[\Delta W=BA,\quad\text{where } W_0\in\mathbb{R}^{d\times k},~B\in\mathbb{R}^{d\times r},~A\in\mathbb{R}^{r\times k} \tag{4}\]

이때 $r$은 $r\leq\min(d,k)$를 만족하는 매우 작은 값으로 설정한다.

행렬 곱에 대해 $\text{rank}(BA)\leq\min(\text{rank}(B),\text{rank}(A))$라는 성질이 성립하므로, $\Delta W$의 Rank 또한 $r$ 이하가 된다.

따라서 파인튜닝 수식이 다음과 같이 변환된다.

\[W_0+\Delta W=W_0+BA \tag{5}\]

파인튜닝 과정에서 원본 가중치 $W_0$는 동결하고 $A$와 $B$ 행렬만 학습하기 때문에, 학습 파라미터 수가 $d\times k$개에서 $r(d+k)$개로 줄어든다.

해당 연구에서는 학습 안정성을 위해 $A$는 가우시안 분포로, $B$는 0으로 초기화하여 학습 초기에는 $\Delta W = 0$이 되도록 하였다.

또한 추론 시에는 $W’ = W_0 + BA$ 연산을 통해 두 가중치를 미리 하나의 행렬로 병합할 수 있기 때문에, 원본 모델 대비 추가적인 추론 지연 (latency)이 발생하지 않는다.

fig3 출처: LoRA: Low-Rank Adaptation of Large Language Models

QLoRA (Quantized LoRA)

QLoRA: Efficient Finetuning of Quantized LLMs 연구에서 제안한 방법으로, LoRA에서 메모리를 효율화시킨 기법이다.

파인튜닝을 하기위해서는 먼저 원본 모델의 가중치 전체가 VRAM에 로드되어야 한다. LoRA는 학습해야 할 파라미터의 수는 줄였지만, 학습 시 VRAM에 올려야 하는 원본 모델의 용량 문제는 해결하지 못했다.

이를 위해 4-bit NormalFloat, Double Quantization, Paged Optimizer 기법을 도입하였다.

fig4 출처: QLoRA: Efficient Finetuning of Quantized LLMs

4-bit NormalFloat (NF4)

NF4는 AI 모델 가중치에 최적화된 4bit data type이다.

사전 학습된 AI 모델의 가중치는 $0$을 중심으로 한 정규분포를 따르는 특별한 성질이 있다. 이는 $0$ 근처에서 대부분의 값이 모여있고, $0$에서 멀어질수록 값이 드물게 존재한다는 뜻이다.

FP4Int4와 같은 기존의 4bit data type은 이를 고려하지 않고, 16개의 값을 균등한 간격으로 배치한다. 이로 인해, $0$ 근처의 데이터 밀집 구간에서는 많은 원본 값들이 하나의 점으로 뭉개져서 정밀도가 떨어지게 된다.

NF4는 가중치의 정규분포 특성에 맞춰 4bit의 16개 값을 불균등하게 배치한다. $0$ 근처의 데이터 밀집 구간에 대부분의 점을 배치하여 촘촘하게 배치하고, 외곽 지역에는 점을 듬성듬성 배치하여 정밀도를 극대화한다.

fig5 [출처]

Double Quantization (DQ)

DQ는 4bit 양자화를 수행할 때 발생하는 추가적인 양자화 상수까지 다시 양자화하여 메모리를 절약하는 방식이다.

양자화는 모델 전체를 한 번에 처리하는 것이 아니라, 여러 블록 단위로 묶어서 진행한다. 이는 모델의 가중치가 모든 영역에서 동일한 값의 범위를 갖지 않기 때문이다.

예를 들어, 블록 A의 가중치 값들은 $-0.1\sim0.1$ 사이에 촘촘히 모여있을 수 있지만, 블록 B의 가중치 값들은 $-10\sim10$까지 넓게 퍼져있을 수 있다. 만약 이 둘을 하나의 블록으로 보고 블록 B 기준으로 압축하면, 블록 A의 값들은 모두 $0$ 근처의 하나의 값으로 뭉개져 정보 손실이 발생하게 된다.

이 때문에 가중치를 여러 블록으로 나누고, 각 블록마다 고유한 양자화 상수를 계산한다. 하지만 이 양자화 상수들 또한 VRAM을 차지하는 메타데이터가 된다. 따라서 DQ는 이 양자화 상수들 자체에 대해 다시 양자화를 진행한다.

  • 1차 양자화: 16bit의 원본 가중치 → 4bit로 압축된 가중치 & 16bit의 1차 상수
  • 2차 양자화: 16bit의 1차 상수 → 8bit로 압축된 1차 상수 & 32bit의 2차 상수

Paged Optimizer

Paged Optimizer는 VRAM보다 훨씬 용량이 큰 CPU RAM을 GPU의 가상 메모리처럼 활용하여, 학습 중 OOM (Out of Memory)로 학습이 중단되는 것을 막는 기법이다.

LoRA나 QLoRA를 사용하면 학습할 파라미터 자체는 적어지지만, 학습 과정에서 Adam의 모멘텀과 같은 옵티마이저 상태 (Optimizer States)를 저장할 VRAM은 여전히 필요하다.

특히 Gradient Checkpointing과 같은 VRAM 절약 기법을 함께 사용하면, Backward 연산이 실행되는 특정 순간에 VRAM 사용량이 급증하는 Memory Spike가 발생하여 OOM 에러가 발생하기 쉽다.

Paged Optimizer는 이러한 문제를 OS의 가상 메모리 (Virtual Memory) 및 페이징 (Paging) 기법을 흉내 내어 해결한다.

1. 할당 (Allocation)

가장 큰 VRAM을 차지하는 optimizer state를 처음부터 VRAM이 아닌 CPU RAM 영역에 Paged Memory로 할당한다.

2. 동적 페이징 (Paging / Swapping)

학습 중 필요에 따라 CPU와 GPU 간에 데이터를 동적으로 주고받는다.

  • Page-in: 학습 중 GPU가 특정 파라미터의 optimizer state 값을 계산해야 하는 순간이 오면, CPU RAM에 있던 해당 데이터를 VRAM으로 가져온다.
  • Page-out: VRAM이 꽉 찼는데 새로운 데이터를 가져와야 한다면, VRAM에 있는 데이터 중 당장 사용하지 않는 데이터를 CPU RAM으로 다시 밀어낸다.
This post is licensed under CC BY 4.0 by the author.