본문 바로가기
카테고리 없음

LLM Fine-Tuning: LoRA(Low-Rank Adaptation)와 QLoRA 적용 방식 비교

by 북더기 2025. 3. 21.

대규모 언어 모델(LLM)을 파인튜닝하려면 엄청난 계산 비용과 메모리 자원이 필요하게 됩니다. 회사에서 LLM을 활용하여 파인튜닝을 구현해본 경우, 정말 몸소 느끼게 되는 경험이었습니다. 특히, 기존의 풀 파인튜닝(Full Fine-Tuning) 방식은 모델 전체 가중치를 업데이트해야 하기 때문에 GPU 메모리 사용량이 많고 훈련 속도가 느려지는 문제가 있습니다. 이를 해결하기 위해 등장한 기술이 LoRA(Low-Rank Adaptation)와 QLoRA(Quantized LoRA)입니다. 두 가지 방법 모두 모델의 모든 가중치를 변경하지 않고, 일부를 효율적으로 업데이트하는 방식으로 메모리 사용량을 줄이면서도 높은 성능을 유지할 수 있습니다.

1. LoRA(Low-Rank Adaptation) 적용 방식

LoRA는 기존의 모델 가중치를 고정한 채, 가중치 변화량을 저차원 행렬(Low-Rank Matrix)로 근사하여 학습하는 방식입니다. 이렇게 하면 모델의 원래 파라미터를 수정하지 않으면서도 특정 태스크에 맞게 적응시킬 수 있습니다.

LoRA의 기본 개념은 아래와 같습니다.

  • 모델의 특정 레이어(보통 어텐션 가중치)에 대해, 작은 저차원 행렬(A, B)을 추가합니다.
  • 기존 가중치는 변경하지 않고, 추가된 저차원 행렬만 학습합니다.
  • 훈련이 끝난 후, LoRA 모듈을 기존 모델 위에 적용하여 인퍼런스를 수행합니다.

이 방식을 사용하면 전체 모델의 업데이트가 필요하지 않기 때문에 GPU 메모리 사용량이 크게 줄어들고, 훈련 속도가 빨라지게 됩니다.

Hugging Face의 `peft` 라이브러리를 활용한 LoRA 적용 예제를 한 번 살펴보겠습니다.


from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import LoraConfig, get_peft_model

# 모델 및 토크나이저 로드
model_name = "meta-llama/Llama-2-7b"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(model_name)

# LoRA 설정
lora_config = LoraConfig(
    r=8,  # Rank 크기
    lora_alpha=32,  # Scaling Factor
    target_modules=["q_proj", "v_proj"],  # LoRA를 적용할 레이어
    lora_dropout=0.1
)

# LoRA 모델 생성
lora_model = get_peft_model(model, lora_config)

# 모델 훈련 수행
lora_model.train()

LoRA의 장점은 훈련해야 할 파라미터 수가 크게 줄어들어 메모리를 절약할 수 있다는 점입니다. 또한, LoRA 모듈을 여러 개 저장하고 쉽게 교체할 수 있어, 같은 기본 모델을 사용하면서도 다양한 태스크에 빠르게 적용할 수 있습니다.

2. QLoRA(Quantized LoRA) 적용 방식

QLoRA는 LoRA를 기반으로 하지만, 추가적으로 모델 가중치를 4비트(4-bit)로 양자화(Quantization)하여 더 많은 메모리를 절약할 수 있도록 설계된 방식입니다. LoRA와 마찬가지로 모델의 원래 가중치는 변경하지 않으며, LoRA 모듈만 학습합니다. 하지만 QLoRA는 기존 LoRA보다 훨씬 적은 GPU 메모리를 사용하면서도 성능 저하를 최소화할 수 있습니다.

QLoRA의 핵심 개념은 아래와 같습니다.

  • 모델의 모든 가중치를 4비트 정밀도로 양자화하여 저장합니다.
  • 양자화된 모델 위에 LoRA 모듈을 추가하여 일부 가중치를 훈련합니다.
  • 훈련 중에 필요할 때만 가중치를 높은 정밀도로 복원하여 연산을 수행합니다.

QLoRA를 사용하면 기존 LoRA보다 더 작은 GPU 메모리로 훈련이 가능하며, 대규모 모델도 단일 GPU에서 파인튜닝할 수 있습니다.

Hugging Face의 `bitsandbytes` 라이브러리를 활용한 QLoRA 적용 예제 또한 아래 코드로 살펴보겠습니다.


import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
from peft import LoraConfig, get_peft_model

# 4-bit 양자화 설정
quantization_config = BitsAndBytesConfig(
    load_in_4bit=True,  # 4-bit 양자화 활성화
    bnb_4bit_compute_dtype=torch.float16  # 연산 시 사용할 데이터 타입
)

# 모델 및 토크나이저 로드 (4-bit 양자화 적용)
model_name = "meta-llama/Llama-2-7b"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(model_name, quantization_config=quantization_config)

# LoRA 설정
lora_config = LoraConfig(
    r=8,
    lora_alpha=32,
    target_modules=["q_proj", "v_proj"],
    lora_dropout=0.1
)

# QLoRA 모델 생성
qlora_model = get_peft_model(model, lora_config)

# 모델 훈련 수행
qlora_model.train()

QLoRA의 가장 큰 장점은 대규모 모델을 상대적으로 적은 리소스로 학습할 수 있다는 점입니다. 일반적으로 65억(6.5B) 파라미터 이상의 모델은 2~4개의 GPU가 필요하지만, QLoRA를 사용하면 24GB VRAM이 있는 단일 GPU에서도 학습이 가능합니다.

그렇다면 LoRA vs QLoRA, 어떤 방식이 더 적합할까?

두 방법 모두 기존의 풀 파인튜닝 방식보다 훨씬 적은 리소스로 훈련할 수 있도록 설계되었지만, 사용 목적과 환경에 따라 적절한 방식을 선택해야 합니다.

LoRA는 상대적으로 GPU 메모리가 충분한 환경에서 빠르게 모델을 튜닝할 때 적합합니다. 기존 모델의 원래 가중치를 변경하지 않고 특정 태스크에 적응시키는 것이 목적이라면 LoRA가 효과적입니다. 특히, 하나의 모델을 여러 용도로 활용해야 할 경우, LoRA 모듈을 변경하는 방식으로 쉽게 적용할 수 있습니다.

반면, QLoRA는 극한의 메모리 최적화가 필요한 환경에서 유리합니다. 4-bit 양자화를 적용함으로써 대규모 모델도 단일 GPU에서 훈련할 수 있으며, 비용 효율성이 뛰어납니다. 하지만 양자화 과정에서 약간의 정밀도 손실이 발생할 수 있어, 고정밀 연산이 필요한 경우에는 LoRA보다 성능이 낮아질 가능성이 있습니다.

결론적으로, 메모리와 연산 자원이 충분한 환경이라면 LoRA를, 제한된 GPU 자원에서 대규모 모델을 다뤄야 한다면 QLoRA를 선택하는 것이 적절하다고 생각합니다. 최근에는 두 가지의 기법을 혼합하여 LoRA의 장점과 QLoRA의 메모리 절약 기능을 동시에 활용하는 시도도 이루어지고 있습니다.