2-4. 양자화: 큰 모델을 작은 그래픽카드에 넣기
모델 파일은 숫자 수십억 개다. 숫자 하나를 몇 비트로 적느냐에 따라 파일 크기와 필요한 메모리가 달라진다. 비트를 줄여 적는 것이 양자화다. 파일 이름에 붙은 fp8, Q4, nvfp4 같은 말이 전부 이것이다.
숫자를 적는 정밀도
1-8에서 어림했다.
필요한 메모리 ≈ 파라미터 수 × 파라미터 하나의 바이트 수
파라미터 하나는 소수점 있는 숫자다. 이것을 몇 비트로 적을지가 정밀도다.
| 이름 | 비트 | 120억 파라미터 모델의 크기 | 비고 |
|---|---|---|---|
| fp32 | 32 | 48GB | 학습할 때의 원래 정밀도 |
| fp16, bf16 | 16 | 24GB | 사실상 손실이 없다. 배포의 기준 |
| fp8 | 8 | 12GB | 조금 손실 |
| 4비트 계열 | 4 안팎 | 약 7GB | 눈에 보이는 손실이 생기기 시작 |
fp16과 bf16은 둘 다 16비트다. 숫자를 적는 방식이 조금 다르다. bf16이 큰 값을 다루는 데 안정적이라서 최신 모델은 주로 bf16으로 나온다.
왜 줄여도 되나
신경망은 숫자 하나하나의 정확한 값에 민감하지 않다. 수십억 개가 함께 일하기 때문에 각각이 조금씩 틀려도 전체 결과는 비슷하다. 0-2에서 본 평균의 힘이다.
다만 한계가 있다. 줄일수록 오차가 쌓인다. 처음에는 디테일이 조금 달라지고, 더 줄이면 질감이 뭉개지고, 더 줄이면 구도가 흐트러진다.
양자화 형식
같은 8비트, 4비트라도 줄이는 방법이 여럿이다.
| 형식 | 생각 | 특징 |
|---|---|---|
| fp8 | 16비트 소수를 8비트 소수로 그냥 줄인다 | 단순하다. 오차가 좀 있다 |
| fp8 scaled | 층마다 숫자의 범위를 재서, 그 범위에 맞춰 8비트를 쓴다 | fp8보다 정확하다 |
| GGUF (Q8, Q6, Q5, Q4…) | 숫자를 묶음으로 나눠, 묶음마다 범위를 따로 잡는다 | 숫자가 작을수록 작고 거칠다. Q8은 거의 손실이 없다. 속도는 조금 느리다 |
| int8 | 8비트 정수로 바꾼다 | 방법에 따라 fp8보다 품질이 좋다 |
| nvfp4 | 4비트 소수. 최신 그래픽카드가 하드웨어로 직접 계산한다 | 지원하는 카드에서 가장 빠르다 |
GGUF의 숫자는 대략 비트 수다. Q8은 8비트, Q4는 4비트 근처다. 뒤에 붙는 _K_M, _K_S 같은 것은 묶는 방법의 세부 차이다.
형식에 따라 속도도 달라진다는 점이 중요하다.
- 그래픽카드가 그 형식을 직접 계산할 수 있으면 빠르다.
- 못 하면 계산할 때마다 16비트로 풀어야 해서 느리다. 메모리만 아끼고 속도는 손해 본다.
그래서 "내 그래픽카드에 맞는 형식"이 따로 있다. 모델 제작사가 형식별 파일과 권장 카드를 함께 안내한다.
무엇을 고를까
원칙은 단순하다. 메모리에 들어가는 것 중 가장 정밀한 것.
| 상황 | 고를 것 |
|---|---|
| 메모리가 넉넉하다 | bf16 원본. 고민할 것이 없다 |
| 조금 모자란다 | fp8 scaled, int8, GGUF Q8 |
| 많이 모자란다 | GGUF Q6~Q4, 4비트 계열 |
| 4비트로도 안 된다 | 더 작은 모델을 찾는다. 그 아래는 품질이 무너진다 |
큰 모델을 심하게 줄인 것과 작은 모델의 원본 중에서는 대개 후자가 낫다.
텍스트 인코더도 줄일 수 있다
최신 모델은 텍스트 인코더가 디노이저만큼 크다. 이것도 양자화한 판이 나온다.
텍스트 인코더는 그림 한 장에 한 번만 돈다. 그래서 그래픽카드에 올리지 않고 일반 메모리에서 천천히 돌리는 방법도 있다. 프롬프트가 바뀌지 않으면 결과를 다시 쓸 수도 있다.
텍스트 인코더를 너무 줄이면 프롬프트 이해가 나빠진다. 그림의 질감은 멀쩡한데 지시를 놓치는 식으로 나타난다.
메모리가 모자랄 때 도구가 하는 일
양자화 말고도 방법이 있다. ComfyUI가 자동으로 해 준다.
- 차례로 올리고 내린다. 텍스트 인코더를 돌린 뒤 내리고, 디노이저를 올린다. 세 덩어리를 동시에 올리지 않는다.
- 일부만 그래픽카드에 올린다. 디노이저의 블록 일부를 일반 메모리에 두고, 필요할 때마다 옮겨 온다. 블록 스왑이라고 한다. 돌아가긴 하지만 느려진다.
- VAE를 조각내 푼다. 큰 그림을 한 번에 풀지 않고 타일로 나눠 푼다.
"메모리 부족으로 죽지는 않는데 유난히 느리다"면 블록의 일부가 일반 메모리로 밀려난 것이다. 더 작은 양자화 판을 쓰면 오히려 빨라진다.
LoRA와 양자화
LoRA는 원본 가중치에 더하는 값이다(4단계). 양자화한 모델에도 붙일 수 있다. 다만 형식에 따라 제약이 있다.
- 붙일 때마다 계산을 한 번 더 해야 해서 느려지는 형식이 있다.
- 심하게 줄인 모델에서는 LoRA의 효과가 원본과 조금 다르게 나온다.
LoRA를 학습할 때는 이야기가 다르다. 학습은 원본 정밀도에 가까운 모델로 해야 한다. 그래서 학습에는 추론보다 메모리가 훨씬 많이 든다.
품질을 직접 비교하는 법
형식의 우열은 모델마다 다르다. 남의 비교표보다 직접 보는 것이 정확하다.
- 프롬프트, 시드, 설정을 전부 고정한다.
- 원본과 양자화 판으로 각각 생성한다.
- 두 그림을 나란히 놓고 본다. 구도, 얼굴, 손, 작은 글자, 머리카락 같은 가는 선.
구도가 같고 디테일만 조금 다르면 쓸 만한 것이다. 구도가 달라지면 오차가 큰 것이다.
확인 문제
- 파라미터 70억 개인 모델의 bf16 크기와 fp8 크기는?
- 같은 8비트인데 fp8보다 fp8 scaled가 정확한 이유는?
- 그래픽카드 메모리가 12GB일 때, 120억 모델의 Q4 판과 60억 모델의 원본 중 무엇을 먼저 시험해 볼 만한가?
풀이
- 14GB와 7GB다. 70억 × 2바이트, 70억 × 1바이트.
- 층마다 숫자의 실제 범위를 재서 그 범위에 8비트를 다 쓰기 때문이다. 안 쓰는 범위에 비트를 낭비하지 않는다.
- 60억 모델의 원본(12GB)이다. 빠듯하게 들어가고 손실이 없다. 큰 모델을 4비트까지 줄이면 손실이 눈에 보인다. 다만 모델 자체의 능력 차이가 크면 결과는 달라질 수 있으니, 같은 프롬프트로 직접 비교한다.