bitsandbytes 4비트 양자화, NF4·compute dtype·double quantization을 검증하는 법

AI를 활용하여 생성한 이미지입니다.
4비트 양자화는 모델 파일을 작게 만드는 작업으로만 설명하기 어렵습니다. 저장 dtype, 행렬 계산 dtype, 일부 모듈의 예외 처리, 장치 배치가 서로 다른 층에 있기 때문이에요. Transformers의 BitsAndBytesConfig는 4비트 로딩과 FP4·NF4를 설정할 수 있고, bitsandbytes는 제한된 메모리에서 LLM을 다루기 위한 양자화 기능을 제공합니다.[1][2][3]
먼저 저장과 계산을 분리해요
| 항목 | 무엇을 바꾸나 | 잘못 고르면 생기는 일 |
|---|---|---|
load_in_4bit |
Linear 계층의 가중치를 4비트 경로로 불러와요. | 모델 메모리는 줄지만 모든 연산이 4비트라는 뜻은 아니에요. |
bnb_4bit_quant_type |
FP4 또는 NF4 저장 형식을 고릅니다. | 학습·추론 목적과 모델 분포에 맞지 않을 수 있어요. |
bnb_4bit_compute_dtype |
계산 중 사용할 dtype을 정해요. | 하드웨어가 지원하지 않으면 오류나 성능 저하가 생길 수 있어요. |
bnb_4bit_use_double_quant |
이미 양자화한 가중치를 한 번 더 양자화해요. | 메모리는 더 줄지만 실제 모델별 품질·속도 검증이 필요해요. |
Hugging Face 문서는 4비트 경로에서 계산 dtype의 기본값이 float32이고, 필요하면 bfloat16으로 바꿀 수 있다고 설명해요.[1] 다만 bfloat16으로 바꾼다고 모든 GPU에서 자동으로 빨라지는 것은 아닙니다. 장치의 지원 여부와 모델의 실제 토큰 처리량을 함께 측정해야 해요.
NF4와 FP4는 같은 선택이 아니에요
NF4는 정규분포로 초기화된 가중치를 전제로 한 4비트 dtype이며, 공식 문서는 4비트 base model을 학습할 때 NF4를 사용하라고 안내합니다.[1] 따라서 QLoRA 학습을 시작하면서 “4비트니까 아무 형식이나 같다”고 두지 말고, 학습인지 추론인지부터 적어 두세요. 추론 결과가 같을 것이라는 보장은 모델과 프롬프트, 커널, 장치에 따라 달라집니다.
import torch
from transformers import AutoModelForCausalLM, BitsAndBytesConfig
config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.bfloat16,
bnb_4bit_use_double_quant=True,
)
model = AutoModelForCausalLM.from_pretrained(
"모델-경로",
device_map="auto",
quantization_config=config,
)
이 코드는 가능한 한 가지 출발점일 뿐이에요. 모델 저장소가 요구하는 Transformers·bitsandbytes·Accelerate 버전, CUDA와 장치 지원을 먼저 확인해야 하고, 지원되지 않는 조합을 코드만 바꿔 해결할 수는 없습니다. 공식 문서도 bitsandbytes의 하드웨어 지원 범위와 백엔드 상태를 별도로 확인하도록 안내합니다.[1][3]
double quantization은 언제 켤까?
중첩 양자화는 이미 양자화한 가중치를 다시 양자화해 추가 메모리를 줄이는 방식이에요. Hugging Face 문서는 추가 성능 비용 없이 파라미터당 0.4비트를 더 절약할 수 있다고 설명합니다.[1] 하지만 “0.4비트 절감”은 저장·구성에 대한 공식 설명이지, 여러분의 전체 VRAM 사용량이 정확히 같은 비율로 줄어든다는 뜻은 아니에요. KV 캐시, 임시 버퍼, 비양자화 모듈, 런타임 오버헤드는 별도로 남습니다.
그래서 double quantization은 메모리 한계에 걸린 모델에서 후보로 두고, 짧은 프롬프트와 긴 프롬프트를 각각 실행하세요. 로딩 성공만 보지 말고 첫 토큰 지연, 생성 속도, 최대 메모리, 출력 품질을 기록해야 합니다. 모델이 겨우 로드됐지만 긴 컨텍스트에서 KV 캐시 때문에 다시 OOM이 난다면 양자화 옵션만으로 해결되지 않아요.[1]
검증은 네 가지 조합으로 작게 시작해요
| 실험 | 고정할 값 | 비교할 값 |
|---|---|---|
| A | NF4, float32 계산 | 기준 출력·메모리·속도 |
| B | NF4, bfloat16 계산 | 지연과 출력 차이 |
| C | NF4, double quant on | 최대 메모리와 긴 입력 안정성 |
| D | FP4, 같은 프롬프트 | NF4 대비 출력·속도·호환성 |
각 실험에서 seed, 모델 revision, tokenizer, 입력 토큰 수를 고정하세요. 같은 질문에 대한 문장 하나만 보고 품질을 판단하지 말고, 구조화 출력·긴 문맥·코드·다국어처럼 실제 사용 패턴을 작은 회귀 세트로 나누는 편이 낫습니다.[1]
오류가 나면 양자화부터 의심하지 않아요
- 모델이 로드되지 않으면 Transformers·bitsandbytes·Accelerate 버전과 장치 백엔드 확인하기
- 계산 dtype 오류면 장치가 bfloat16을 지원하는지와 실제 선택 dtype 확인하기
- 로딩은 되지만 긴 입력에서 OOM이면 KV 캐시와 최대 입력 길이를 별도로 측정하기
- 출력 품질이 흔들리면 NF4·FP4, double quant, 모델 revision을 한 번에 하나씩 바꾸기
- dequantize를 시도할 때는 원래 정밀도의 모델을 담을 메모리가 있는지 먼저 확인하기
양자화 모델을 다시 원래 정밀도로 되돌리는 dequantize() 경로도 있지만, 공식 문서는 충분한 GPU 메모리가 필요하고 품질 손실이 생길 수 있다고 설명합니다.[1] 그래서 되돌리기는 운영 중 즉흥적으로 실행할 기능이 아니라, 별도 환경에서 결과를 보존한 뒤 결정할 작업에 가깝습니다.
메모리는 계층별로 측정해요
4비트 로딩을 비교할 때는 모델 파일 크기만 기록하지 마세요. 프로세스가 시작된 직후, 첫 입력을 넣은 뒤, 긴 입력을 처리하는 동안의 최대 GPU 메모리를 각각 저장해야 합니다. 모델 가중치가 줄어도 KV 캐시와 임시 버퍼는 입력 길이와 배치 크기에 따라 늘어날 수 있어요. 공식 문서의 양자화 설정은 Linear 계층을 중심으로 동작하므로, 모든 모듈이 같은 방식으로 축소된다고 가정하면 안 됩니다.[1][2]
import torch
print(torch.cuda.memory_allocated() / 1024**3)
print(torch.cuda.max_memory_allocated() / 1024**3)
# 실험 전 torch.cuda.reset_peak_memory_stats()를 호출해요.
이 값은 장치별 구현에 따라 해석이 달라질 수 있으니 절대적인 벤치마크로 쓰기보다 같은 환경 안에서 A/B 비교에 사용하세요. 모델 revision과 라이브러리 버전을 바꾸면 결과도 다시 측정해야 합니다.[1]
출력 검증과 되돌리기 절차
- 기준 정밀도 모델에서 짧은 질문·긴 문맥·코드 입력을 저장해요.
- NF4와 FP4를 같은 tokenizer와 seed로 실행해요.
- compute dtype만 바꿔 첫 토큰 지연과 생성 속도를 비교해요.
- double quantization을 켠 뒤 최대 메모리와 출력 차이를 기록해요.
- 문제가 생기면 마지막으로 통과한 설정 파일과 모델 revision으로 되돌려요.
정량 결과가 없는 상태에서 “NF4가 항상 더 정확하다”거나 “double quantization은 품질 손실이 없다”고 단정하면 안 됩니다. Hugging Face 문서가 설명하는 절감량과 권장 dtype은 출발점이고, 실제 선택은 모델과 하드웨어의 검증 결과로 정해야 해요.[1] 특히 생성 품질이 중요한 서비스라면 자동 회귀 테스트를 통과한 조합만 배포 목록에 남기세요.
로컬 AI 실행 주제는 AI·LLM 가이드 모음에서 관련 글을 함께 볼 수 있어요.