QLoRA란? 4비트 양자화로 LLM 미세 조정 메모리를 줄이는 원리
QLoRA란? 4비트 양자화로 LLM 미세 조정 메모리를 줄이는 원리#
QLoRA는 대규모 언어 모델의 기본 가중치를 4비트로 양자화해 보관하고, 기본 모델은 고정한 채 LoRA 어댑터만 학습하는 미세 조정 방법입니다.
LoRA를 이해했다면 이런 질문이 생길 수 있습니다.
“학습할 파라미터는 크게 줄었는데, 왜 여전히 모델을 GPU 메모리에 올리기 어려울까?”
LoRA는 기본 모델의 가중치를 학습하지 않도록 하지만, 계산에 사용하려면 기본 모델 자체는 메모리에 있어야 합니다. QLoRA는 이 기본 모델을 더 적은 비트로 저장해 메모리 부담을 줄입니다.
두 기술의 역할을 한 문장씩 정리하면 다음과 같습니다.
- LoRA: 학습할 가중치의 수를 줄인다.
- QLoRA: 기본 모델의 저장 메모리까지 줄이면서 LoRA를 학습한다.
이 글은 QLoRA의 개념을 설명한 뒤 NF4, 이중 양자화, 학습 준비, 실제 설정 순서로 살펴봅니다.
먼저 알아둘 용어#
| 용어 | 뜻 |
|---|---|
| GPU 메모리 또는 VRAM | GPU가 모델과 학습 중간 결과를 보관하는 공간 |
| 양자화 | 숫자를 더 적은 비트로 표현해 저장 공간을 줄이는 방법 |
| FP16·BF16 | 각각 16비트 부동소수점 수 표현 방식 |
| NF4 | QLoRA에서 사용하는 4비트 양자화 형식 |
| LoRA 어댑터 | 고정된 기본 모델에 추가해 학습하는 작은 가중치 |
| 활성화 값 | 순전파 중 생성되며 역전파 계산에 필요한 중간 결과 |
| 옵티마이저 상태 | 학습할 가중치를 갱신하기 위해 옵티마이저가 보관하는 값 |
LoRA만으로는 왜 GPU 메모리 문제가 남을까?#
파라미터가 약 70억 개인 모델을 단순화해 생각해 보겠습니다. 가중치 하나를 16비트, 즉 2바이트로 저장한다면 가중치만 다음 정도의 공간이 필요합니다.
70억 × 2바이트 ≈ 140억 바이트 ≈ 14GB이는 가중치만 계산한 이론적인 대략값입니다. 실제 실행에는 일부 비양자화 계층, 활성화 값, 학습용 어댑터와 옵티마이저 상태, 임시 버퍼 등도 필요합니다.
따라서 “LoRA 어댑터가 작으니 7B 모델을 12GB GPU에서 반드시 학습할 수 있다”라고 판단할 수 없습니다. 먼저 기본 모델이 어느 정도 메모리를 차지하는지 확인해야 합니다.
학습할 때 메모리에 들어가는 것은 가중치뿐일까?#
아닙니다. 학습 설정에 따라 다음 요소들이 메모리를 사용합니다.
| 메모리 항목 | 하는 일 |
|---|---|
| 기본 모델 가중치 | 입력을 처리해 출력을 계산 |
| LoRA 가중치 | 학습할 변화를 표현 |
| 기울기 | LoRA 가중치를 어떻게 바꿀지 계산 |
| 옵티마이저 상태 | 가중치 업데이트에 필요한 정보 유지 |
| 활성화 값 | 역전파 계산에 필요한 중간 결과 저장 |
| 임시 버퍼 | 연산·데이터 이동 등에 사용 |
LoRA는 학습 대상과 그에 딸린 상태를 크게 줄입니다. QLoRA는 여기에 기본 모델 가중치의 저장 비용을 줄이는 방법을 더합니다.
QLoRA는 어떻게 동작할까?#
QLoRA의 흐름은 다음과 같습니다.
- 사전 학습 모델의 기본 가중치를 4비트 형식으로 불러옵니다.
- 기본 가중치는 고정해 직접 업데이트하지 않습니다.
- 선택한 계층에 LoRA 어댑터를 추가합니다.
- 순전파에서는 양자화된 기본 모델과 LoRA 어댑터를 함께 사용합니다.
- 역전파에서 학습에 필요한 신호가 흐르고 어댑터 가중치가 갱신됩니다.
4비트로 저장한다는 말과 모든 계산을 4비트로 수행한다는 말은 다릅니다. 기본 가중치는 압축된 형태로 보관하되, 실제 연산에는 별도로 지정한 계산 자료형이 사용됩니다. 예를 들어 설정에서 bnb_4bit_compute_dtype=torch.bfloat16을 지정할 수 있습니다.
비전공자를 위한 비유#
두꺼운 참고서를 매번 새로 쓰지 않고, 압축해 보관한 원본을 펼쳐 보면서 작은 수정 메모만 학습한다고 생각해 보세요.
- 압축해 보관한 참고서: 4비트 기본 모델
- 새 작업에 맞춘 수정 메모: LoRA 어댑터
- 원본을 보며 답과 오차를 계산하는 과정: 순전파와 역전파
비유의 한계도 있습니다. 기본 모델이 사라지는 것은 아닙니다. 압축된 상태로 보관하고 필요할 때 계산에 사용합니다.
NF4란 무엇인가?#
NF4는 NormalFloat 4비트 형식입니다. QLoRA 논문은 신경망 가중치의 분포 특성을 고려해 NF4를 설계하고, 비교한 4비트 정수·부동소수점 형식보다 좋은 실험 결과를 제시했습니다.
양자화를 먼저 이해하기#
원래 숫자를 촘촘하게 표현하던 저장 공간을 줄이면, 표현 가능한 값의 종류도 줄어듭니다.
예를 들어 아주 단순화하면 다음과 같습니다.
원래 값: 0.13, 0.17, 0.22, 0.28 ...
저장 가능한 대표값이 적은 형식:
0.10, 0.20, 0.30 ...원래 값은 가까운 대표값으로 옮겨 저장됩니다. 메모리는 줄지만 정보의 일부가 달라질 수 있습니다. 이를 양자화 오차라고 생각하면 됩니다.
NF4는 4비트로 표현할 값의 위치를 신경망 가중치의 분포에 맞춰 배치하는 방식입니다. 자주 등장하는 구간에는 표현값을 더 적절히 배분할 수 있습니다.
NF4는 모델 품질을 항상 보존할까?#
그렇지는 않습니다. 모델의 작업, 학습 데이터, 평가 방법에 따라 양자화의 영향은 다릅니다.
원문의 “FP16 대비 성능 손실 1% 미만”은 모든 QLoRA 실험에 적용되는 수치가 아닙니다. 특정 모델이나 평가 결과를 제시하지 않은 채 일반 법칙처럼 쓰면 안 됩니다.
정확한 판단은 같은 평가 데이터에서 다음을 비교해야 합니다.
- 원본 모델
- LoRA로 학습한 모델
- QLoRA로 학습한 모델
특히 형식 준수율, 사실 오류, 긴 문서 처리처럼 실제로 사용할 작업의 지표를 확인해야 합니다.
이중 양자화는 무엇을 더 줄일까?#
4비트 양자화에서는 가중치 자체 외에도 원래 숫자를 복원·해석하는 데 필요한 양자화 상수를 보관합니다. 이 상수에도 메모리가 듭니다.
이중 양자화는 그 상수들까지 다시 양자화해 메모리를 조금 더 절약합니다.
첫 번째 양자화: 모델 가중치를 낮은 비트로 표현
두 번째 양자화: 첫 번째 양자화에 필요한 상수도 압축QLoRA 논문은 평균적으로 파라미터당 약 0.37비트를 추가로 절약한다고 설명합니다. 이를 단순 계산하면 다음과 같습니다.
7B 모델:
70억 × 0.37비트 ÷ 8
≈ 3억 2,375만 바이트
≈ 0.32GB
65B 모델:
650억 × 0.37비트 ÷ 8
≈ 30억 바이트
≈ 3GB따라서 원문의 “7B에서 약 3GB 추가 절감”은 모델 크기를 잘못 적용한 값입니다. 논문에서 말하는 약 3GB 절감은 65B 모델 기준입니다.
이 역시 논문의 평균 절감량을 파라미터 수에 곱한 설명용 계산입니다. 실제 GPU 메모리 사용량 전체가 정확히 그만큼 줄어든다고 보장하는 값은 아닙니다.
7B 모델의 메모리는 얼마가 필요할까?#
70억 파라미터를 가진 모델의 가중치 부분만 단순 계산하면 다음과 같습니다.
| 저장 형식 | 가중치 1개당 이론적인 비트 수 | 7B 가중치의 단순 계산 |
|---|---|---|
| FP16 또는 BF16 | 16비트 | 약 14GB |
| 4비트 | 4비트 | 약 3.5GB |
이 표만 보고 “QLoRA 학습은 3.5GB GPU에서 가능하다”라고 결론 내리면 안 됩니다. 4비트 양자화에도 메타데이터가 필요하고, 모든 계층이 동일하게 4비트로 저장되는 것도 아닙니다. 학습할 때는 활성화 값과 LoRA 관련 상태도 추가됩니다.
실제 GPU 사용량은 특히 다음에 좌우됩니다.
- 기본 모델의 정확한 구조와 파라미터 수
- 최대 입력 길이
- 한 번에 처리하는 샘플 수
- LoRA를 적용한 계층과 랭크
- 기울기 체크포인팅 사용 여부
- 옵티마이저 종류
- 라이브러리와 하드웨어의 지원 방식
따라서 원문의 “7B는 언제나 총 6~8GB면 충분하다” 또는 “8GB GPU에서 반드시 학습할 수 있다”는 표현은 사용할 수 없습니다. 가중치의 이론적 크기와 전체 학습 메모리를 구분해야 합니다.
BitsAndBytesConfig로 4비트 모델 불러오기#
Hugging Face Transformers와 bitsandbytes를 사용하는 환경에서는 다음처럼 4비트 양자화를 설정할 수 있습니다.
import torch
from transformers import AutoModelForCausalLM, BitsAndBytesConfig
model_id = "Qwen/Qwen2.5-7B-Instruct"
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_use_double_quant=True,
bnb_4bit_compute_dtype=torch.bfloat16,
)
base_model = AutoModelForCausalLM.from_pretrained(
model_id,
quantization_config=bnb_config,
device_map="auto",
torch_dtype=torch.bfloat16,
)이 코드는 모델 로드 단계의 예시입니다. 실행하려면 해당 모델을 받을 수 있어야 하고, 설치된 PyTorch·Transformers·bitsandbytes 버전과 장치가 이 설정을 지원해야 합니다. device_map="auto"는 사용 가능한 장치에 모델을 배치하려는 설정이지, 부족한 GPU 메모리를 없애 주는 옵션은 아닙니다.
| 설정 | 역할 |
|---|---|
load_in_4bit=True |
지원되는 가중치를 4비트로 불러오기 |
bnb_4bit_quant_type="nf4" |
NF4 형식 사용 |
bnb_4bit_use_double_quant=True |
이중 양자화 사용 |
bnb_4bit_compute_dtype=torch.bfloat16 |
4비트 계층의 계산 자료형 지정 |
device_map="auto" |
실행 장치 배치 자동화 |
torch_dtype처럼 모델을 불러올 때 사용하는 인자의 지원 형태는 라이브러리 버전에 따라 달라질 수 있습니다. 실제 프로젝트에서는 설치된 버전의 공식 문서를 확인해야 합니다.
BF16을 지원하지 않는 환경이라면?#
GPU가 BF16 계산을 지원하는지 확인할 수 있습니다.
import torch
print(torch.cuda.is_available())
if torch.cuda.is_available():
print(torch.cuda.is_bf16_supported())False가 나온다면 BF16을 강제로 적용하지 말고 해당 GPU와 라이브러리가 지원하는 계산 자료형을 검토해야 합니다. 하드웨어 이름만 보고 무조건 BF16을 설정하는 방식은 피하는 편이 정확합니다.
prepare_model_for_kbit_training()은 왜 사용할까?#
양자화 모델을 학습용 PEFT 설정에 연결할 때는 일반적으로 학습 준비 단계가 필요합니다.
from peft import prepare_model_for_kbit_training
base_model = prepare_model_for_kbit_training(base_model)이 함수는 Transformers 모델을 저비트 학습에 사용할 수 있도록 준비합니다. PEFT 문서에는 기본 모델 가중치 고정, 일부 계층의 정밀도 처리, 기울기 계산과 관련된 준비 과정이 설명되어 있습니다.
원문의 설명처럼 이 함수를 **“4비트 Linear 계층을 PEFT가 인식하도록 단순히 래핑하는 함수”**로 이해하면 역할을 놓칩니다. 또한 호출하지 않으면 모든 환경에서 반드시 오류가 난다고 단정할 수도 없습니다.
실무에서 기억할 순서는 다음과 같습니다.
기본 모델을 4비트로 로드
→ 저비트 학습 준비
→ LoRA 어댑터 연결
→ 학습 데이터와 학습 설정 구성
→ 학습 및 평가LoRA 어댑터 연결하기#
from peft import LoraConfig, TaskType, get_peft_model
lora_config = LoraConfig(
r=16,
lora_alpha=32,
target_modules="all-linear",
lora_dropout=0.05,
bias="none",
task_type=TaskType.CAUSAL_LM,
)
model = get_peft_model(base_model, lora_config)
model.print_trainable_parameters()target_modules="all-linear"는 지원되는 선형 계층에 LoRA를 적용하는 PEFT의 설정 예입니다. 적용 대상이 넓으면 학습 파라미터와 메모리 사용도 달라집니다. 더 작은 범위로 시작하려면 대상 모델에 실제로 존재하는 모듈 이름을 확인해 선택할 수 있습니다.
위 코드는 학습 가능한 모델을 준비하는 부분입니다. 이 코드만으로 데이터가 학습되지는 않습니다. 토크나이저, 학습 데이터, 학습 루프 또는 트레이너, 평가 절차가 별도로 필요합니다.
메모리를 더 줄일 때 사용하는 두 가지 설정#
QLoRA를 사용해도 입력이 길거나 배치가 크면 메모리가 부족할 수 있습니다. 이때 자주 검토하는 방법이 기울기 체크포인팅과 기울기 누적입니다.
기울기 체크포인팅: 중간 결과를 덜 저장하기#
학습에는 순전파 중간 결과인 활성화 값이 필요합니다. 기울기 체크포인팅은 일부 중간 결과를 계속 보관하는 대신, 역전파 때 필요한 계산을 다시 수행합니다.
GPU 메모리 사용 감소
↔ 일부 계산을 다시 수행해 학습 시간이 늘어날 수 있음메모리를 아끼는 대신 계산을 더 하는 선택입니다.
기울기 누적: 작은 배치를 여러 번 모으기#
한 번에 많은 샘플을 GPU에 올리기 어렵다면 작은 배치로 여러 번 계산한 기울기를 모아 업데이트할 수 있습니다.
한 번에 1개 샘플 처리
× 4회 기울기 누적
→ 업데이트 1회이는 메모리 제약 속에서 더 큰 유효 배치를 구성하는 방법입니다. 다만 기울기 누적을 32로 설정한다고 한 번에 처리하는 샘플의 메모리가 32분의 1로 줄어드는 것은 아닙니다. 긴 입력 한 건 자체가 메모리에 들어가지 않는 문제는 해결하지 못할 수 있습니다.
입력 길이가 왜 중요한가?#
텍스트를 더 길게 입력하면 모델이 처리할 토큰이 늘어납니다. 이 과정의 중간 결과에도 메모리가 필요합니다.
따라서 같은 7B 모델이라도 짧은 문답 데이터를 학습하는 경우와 긴 문서를 학습하는 경우의 메모리 요구량은 다릅니다. GPU 용량만 보고 가능 여부를 판단하기 어려운 이유입니다.
FP16과 BF16은 무엇이 다를까?#
둘 다 16비트 부동소수점 형식이지만, 비트를 숫자의 범위와 정밀도에 배분하는 방식이 다릅니다.
| 형식 | 전체 비트 | 지수 비트 | 가수 비트 | 특징 |
|---|---|---|---|---|
| FP16 | 16 | 5 | 10 | 상대적으로 세밀하지만 표현 가능한 값의 범위가 좁음 |
| BF16 | 16 | 8 | 7 | FP32와 비슷한 넓은 지수 범위를 가짐 |
큰 수와 작은 수를 폭넓게 다뤄야 할 때 BF16이 유리한 경우가 있습니다. 반면 가수 비트는 FP16보다 적습니다. 따라서 BF16을 “모든 계산에서 FP16보다 정확하다”라고 설명해서는 안 됩니다.
혼합 정밀도 학습도 “모든 순전파와 역전파는 FP16/BF16, 모든 가중치 업데이트는 FP32”라는 하나의 고정된 공식으로 설명하기 어렵습니다. 어떤 값을 어떤 형식으로 저장하고 계산하는지는 프레임워크, 옵티마이저와 학습 설정에 따라 다릅니다.
QLoRA에서 특히 구분해야 할 것은 다음입니다.
기본 가중치를 저장하는 형식: 예를 들어 NF4
연산에 사용하는 형식: 예를 들어 BF16
LoRA 어댑터와 다른 계층의 형식: 설정과 구현에 따라 다름원문의 “A100·H100·RTX 4090에서는 무조건 BF16”이라는 표현 대신, 지원 여부를 확인하고 해당 학습 설정에서 안정성과 성능을 비교하는 기준을 사용해야 합니다.
Full FT·LoRA·QLoRA는 어떻게 선택할까?#
원문의 7B·70B 메모리, 학습 시간, 품질을 고정된 수치로 나열한 표는 모델과 설정이 정의되지 않아 신뢰할 수 있는 비교표가 아닙니다. 먼저 어떤 자원을 줄이는 방식인가를 비교하는 편이 유용합니다.
| 방식 | 기본 모델 가중치 | 학습 대상 | 주된 이점 | 주요 제약 |
|---|---|---|---|---|
| 전체 미세 조정 | 일반적으로 직접 학습 | 선택한 원본 파라미터 전반 | 변경 가능한 범위가 넓음 | 큰 학습 메모리와 저장 비용 |
| LoRA | 고정 | 추가 어댑터 | 학습 파라미터 축소 | 기본 모델을 저장·계산할 메모리는 필요 |
| QLoRA | 4비트로 양자화하고 고정 | 추가 어댑터 | 기본 모델 메모리와 학습 파라미터 부담 완화 | 양자화 지원 환경과 품질 검증 필요 |
GPU 메모리가 넉넉할 때#
먼저 LoRA로 기준 결과를 만들고, 필요하면 전체 미세 조정과 비교할 수 있습니다. 자원이 충분하다는 이유만으로 랭크를 무조건 크게 하거나 추가 기법을 모두 켤 필요는 없습니다.
GPU 메모리가 부족할 때#
QLoRA를 검토하고 다음을 함께 조절합니다.
- 실제 모델 크기
- 입력 길이
- 한 번에 처리할 샘플 수
- 적용 계층과 랭크
- 기울기 체크포인팅
- 옵티마이저의 메모리 사용
8GB나 12GB라는 용량만으로 특정 7B 모델의 학습 성공을 보장할 수는 없습니다. 실제 모델을 로드하고 짧은 학습 단계까지 실행해 최대 GPU 메모리 사용량을 측정해야 합니다.
GPU 메모리가 매우 작을 때#
더 작은 기본 모델로 데이터 형식과 학습 코드가 제대로 동작하는지 먼저 검증하는 편이 현실적입니다. 4~6GB 환경에서 랭크와 입력 길이만 낮추면 어떤 7B 모델이든 학습된다고 기대해서는 안 됩니다.
FSDP와 DeepSpeed ZeRO는 QLoRA와 어떤 관계인가?#
단일 GPU의 메모리를 줄여도 목표 모델이나 작업이 들어가지 않을 수 있습니다. 여러 GPU를 사용할 수 있다면 분산 학습도 검토합니다.
FSDP#
PyTorch의 FSDP(Fully Sharded Data Parallel)는 모델 파라미터, 기울기, 옵티마이저 상태 등을 여러 작업자에 나눠 보관하는 방식입니다. 각 GPU가 모든 학습 상태를 동일하게 복제해 갖는 부담을 줄일 수 있습니다.
DeepSpeed ZeRO-3#
DeepSpeed ZeRO-3도 파라미터, 기울기, 옵티마이저 상태를 분할해 메모리 부담을 낮추는 방법입니다. 설정에 따라 일부 상태를 CPU 메모리나 저장 장치로 옮기는 방식도 지원합니다.
둘 다 “GPU 여러 개를 쓰면 학습이 무조건 빨라진다”는 뜻은 아닙니다. GPU 사이의 통신과 데이터 이동 비용이 들고, CPU로 옮긴 상태를 다시 가져오는 데에도 시간이 필요합니다.
또 FSDP와 QLoRA 또는 ZeRO-3와 QLoRA를 결합하는 방법은 저장 형식, 라이브러리와 실행 환경의 호환성을 확인해야 합니다. 입문 단계에서는 먼저 단일 GPU에서 작은 모델로 QLoRA의 구조를 이해한 뒤 분산 학습을 별도 주제로 다루는 편이 좋습니다.
실제 학습 전 확인할 질문#
QLoRA 실습을 시작하기 전에 다음 질문에 답해 보세요.
- 기본 모델의 크기와 가중치 형식은 무엇인가?
- 실제 GPU가 4비트 로드와 선택한 계산 자료형을 지원하는가?
- 입력 데이터는 얼마나 길고, 한 번에 몇 건을 처리하는가?
- LoRA를 어느 계층에 적용할 것인가?
- 기본 모델과 비교할 평가 자료를 따로 준비했는가?
- 학습 중 최대 메모리 사용량과 작업 품질을 함께 측정할 것인가?
학습이 완료되었다는 사실만으로 미세 조정의 성공을 판단할 수는 없습니다. 학습 전과 후를 같은 평가 조건에서 비교하고, 잘못된 답변 유형까지 확인해야 합니다.
정리#
QLoRA는 양자화된 기본 모델을 고정하고 LoRA 어댑터를 학습합니다. LoRA가 학습 파라미터를 줄였다면, QLoRA는 기본 모델의 가중치를 보관하는 데 필요한 메모리도 줄이려는 방법입니다.
- NF4는 신경망 가중치의 분포를 고려한 4비트 양자화 형식입니다.
- 이중 양자화는 양자화 상수의 저장 비용을 더 줄입니다.
- BF16 같은 계산 자료형은 가중치 저장 형식인 NF4와 구별해야 합니다.
- 기울기 체크포인팅과 누적은 각기 다른 방식으로 메모리 제약에 대응합니다.
- 학습에 필요한 총 GPU 메모리는 가중치의 4비트 크기만으로 결정되지 않습니다.
QLoRA를 이해하는 가장 중요한 문장은 다음과 같습니다.
기본 모델은 작게 저장하고 고정한 채 계산에 사용하며, 새 작업에 필요한 변화는 LoRA 어댑터에 학습한다.