로컬 LLM 파일을 보다 보면 Q4, 4-bit, INT4 같은 말이 계속 나온다.

전에는 그냥 숫자가 작을수록 파일도 작아지는 건가 보다 정도로 넘겼다.

근데 생각해보니 이상했다.

모델 안에는 학습하면서 만들어진 수많은 숫자가 들어 있다. 보통 이런 숫자를 가중치라고 부른다.

그 숫자를 16비트에서 4비트로 확 줄이면 뭔가를 버리는 거 아닌가?

그러면 결국 AI도 같이 멍청해지는 거 아닐까.

처음에는 이것부터 이해해보기로 했다.

4비트라는 게 숫자를 4씩 띄운다는 뜻은 아니었다

처음 떠올린 건 이런 그림이었다.

1~100 사이에 원래 여러 숫자가 있는데, 4비트로 줄이면 4, 8, 12, 16 ... 96, 100 같은 대표 숫자만 남겨두는 건가 싶었다.

방향은 꽤 비슷했다.

다만 4비트에서 4는 간격이 아니라, 숫자 하나를 표현할 때 쓰는 비트 수였다.

4개의 비트는 각각 0 또는 1이니까 만들 수 있는 조합은 2⁴ = 16개다.

TEXT
0000
0001
0010
...
1110
1111

= 16가지 코드

NVIDIA TensorRT의 INT4 설명을 보니 실제로 4비트 정수 예시는 -8부터 7까지, 정확히 16개의 값을 사용한다.

“x_q is a quantized INT4 value in the range [-8, 7].”

출처: NVIDIA TensorRT — Quantization Schemes

내가 이해한 건

원래 숫자가 아주 촘촘하게 있었는데, 양자화하면 사용할 수 있는 값의 종류를 확 줄이고 원래 숫자를 그중 가까운 값에 맞추는 느낌이다. 다만 INT4, FP4, NF4처럼 방식이 여러 개라서 실제로 어떤 16개 값을 쓰는지는 항상 똑같지 않다.

그러니까 처음 생각했던 1~100 → 몇 개의 대표 숫자로 뭉뚱그린다는 감은 맞았다.

4비트 = 4씩 띄운다가 아니라 4비트 = 16개의 코드로 표현한다가 더 정확했다.

그러면 원래 숫자와 달라지는 건 맞네

이 부분은 피할 수 없었다.

예를 들어 원래 값이 24.6인데 사용할 수 있는 대표값 중 가까운 게 25라면 결국 24.6 → 25처럼 바뀐다.

실제 양자화는 그냥 정수로 반올림하는 것보다 훨씬 복잡하다. 어느 범위를 몇 칸으로 나눌지 정하는 기준도 같이 쓴다.

그래도 원래 숫자를 더 적은 종류의 값으로 표현하면서 오차가 생긴다는 핵심은 같다.

NVIDIA는 이런 오차를 크게 두 가지로 설명한다.

  • 가장 가까운 값으로 맞추면서 생기는 오차 — 반올림 오차
  • 표현할 수 있는 범위를 벗어난 값을 끝값에 붙이면서 생기는 오차 — 클램핑 오차

출처: NVIDIA TensorRT — Accuracy Considerations

그러면 처음 질문으로 다시 돌아온다.

정보를 버리는 게 맞으면 AI가 멍청해지는 것도 맞는 거 아닌가?

그런데 생각보다 품질이 덜 무너지는 방법들이 있었다

찾아보니 양자화 연구의 상당 부분이 바로 이 문제를 다루고 있었다.

그냥 모델 안 숫자를 전부 똑같이 줄이는 게 아니라, 어떤 숫자를 어떻게 줄여야 오차가 덜 생기는지를 찾는 쪽이었다.

예를 들어 GPTQ 논문은 최대 175B 모델의 가중치, 그러니까 모델 안에 저장된 숫자들을 3~4비트까지 줄였을 때 실험에서 원본 대비 정확도 저하가 매우 작았다고 보고했다.

출처: GPTQ 원 논문

AWQ라는 방식은 한 단계 더 재미있었다.

논문에서는 모델 안 숫자가 전부 똑같이 중요한 게 아니라고 봤다. 특히 영향이 큰 일부를 더 잘 보존하는 쪽으로 줄이면 오차를 크게 낮출 수 있다고 설명한다.

출처: AWQ 원 논문

여기서 조심할 것

GPTQ나 AWQ 결과가 “4비트면 언제나 원본과 똑같다”는 뜻은 아니다. 모델, 줄이는 방식, 무엇을 평가했는지에 따라 손실은 달라질 수 있다. 다만 4비트로 줄이면 무조건 크게 망가진다고 생각한 것도 맞지는 않았다.

처음에는 숫자를 버린다 → 지능도 바로 크게 버린다 정도로 생각했는데, 실제로는 어떤 숫자를 어떤 방식으로 줄이느냐가 꽤 중요했다.

그래도 메모리가 줄어드는 양은 엄청 컸다

70B 모델 안의 숫자들만 저장한다고 아주 단순하게 계산해봤다.

추가 메모리나 실행 중 필요한 공간은 전부 빼고, 정말 700억 개 × 비트 수만 계산한 값이다.

16-bit · ████████████████약 130.4 GiB
8-bit · ████████약 65.2 GiB
4-bit · ████약 32.6 GiB
3-bit · ███약 24.4 GiB

16비트에서 4비트로 내려가면 모델 안 숫자들을 저장하는 공간만 단순 계산했을 때 4분의 1이 된다.

이제 왜 로컬 LLM에서 Q4가 그렇게 자주 보이는지는 좀 알 것 같았다.

조금의 정확도를 포기하는 대신, 원래는 아예 메모리에 못 들어가던 모델을 올릴 수 있게 만드는 것 자체가 엄청 큰 이득이었다.

물론 실제 파일과 실행 메모리는 위 숫자와 정확히 같지 않다. 줄인 숫자를 다시 해석하기 위한 기준값도 저장해야 하고, 실행 중에는 다른 메모리도 필요하다.

여기서 또 헷갈렸다: 4비트로 저장하면 4비트로 계산하는 거 아닌가?

처음에는 당연히 그렇게 생각했다.

4비트 모델이면 저장도 4비트, 계산도 4비트일 줄 알았다.

그런데 Hugging Face의 bitsandbytes 설정을 보니 모델을 4비트로 불러오면서 실제 계산에 쓰는 숫자 형식은 16비트 계열인 BF16으로 따로 지정할 수 있었다.

PYTHON
BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_compute_dtype=torch.bfloat16,
)

출처: Hugging Face Transformers — bitsandbytes 4-bit 설정

이걸 보고 나서야 작게 저장하는 방식과 실제 계산할 때 쓰는 방식이 꼭 같은 건 아니구나가 이해됐다.

NVIDIA TensorRT의 INT4 설명은 더 직접적이었다.

“INT4 is used for weight-only-quantization. Requires dequantization before computing is performed.”

출처: NVIDIA TensorRT — Capabilities

이 문장을 내가 이해한 방식으로 풀면 이렇다.

TensorRT의 이 INT4 방식에서는 모델 안 숫자를 4비트로 작게 저장해두고, 메모리에서 읽은 다음 계산하기 좋은 형태로 다시 풀어서 연산한다. 이 다시 푸는 과정을 dequantize라고 부른다.

01 · SAVE

4비트로 저장

짐을 작게 포장한다.

02 · MOVE

메모리에서 읽기

옮겨야 할 데이터 양이 줄어든다.

03 · UNPACK

계산하기 좋은 형태로 풀기

TensorRT의 이 INT4 방식에서는 여기서 dequantize 과정이 들어간다.

04 · COMPUTE

실제 계산

더 높은 정밀도의 숫자 형식으로 계산할 수도 있다.

아까 창고 비유로 다시 생각하면 이렇다.

작은 박스라서 창고 문을 통과할 때는 유리한데, 작업대에서 쓰기 전에 포장을 다시 풀 수도 있다.

그래서 파일이 4분의 1이 됐으니 속도도 정확히 4배겠네라고 바로 계산하면 안 됐다.

그러면 이건 그래픽카드가 4비트를 지원하냐는 문제인가

이 부분도 절반은 맞고 절반은 아니었다.

GPU가 적은 비트로 표현된 숫자를 직접 얼마나 잘 계산할 수 있느냐는 분명 중요하다.

NVIDIA GPU 안에는 AI 계산을 빠르게 처리하는 Tensor Core라는 장치가 있다. NVIDIA가 공개한 지원 표를 보면 Blackwell은 FP4를 지원하지만, Hopper의 지원 목록에는 FP4가 없다.

출처: NVIDIA — Tensor Cores

그래서 이 그래픽카드가 FP4 같은 낮은 정밀도 연산을 지원하냐는 질문 자체는 의미가 있었다.

다만 그것만으로 끝나지는 않았다.

같은 4비트라도 INT4와 FP4처럼 숫자를 적는 규칙 자체가 다를 수 있다. 여기에 모델을 어떤 방식으로 줄였는지, 실행 프로그램이 GPU에 계산을 어떤 순서와 묶음으로 시키는지, GPU가 그 숫자 형식을 얼마나 잘 처리하는지까지 같이 맞아야 한다.

원문에서는 이런 GPU용 계산 명령을 커널(kernel)이라고 부른다. 이 글에서는 그냥 GPU에 일을 시키는 계산 방식 정도로 이해하기로 했다.

AWQ 논문에서 함께 만든 TinyChat도 단순히 모델만 4비트로 줄인 게 아니었다.

여러 계산 단계를 따로따로 시키지 않고 묶어서 처리하고, 모델 안 숫자도 하드웨어가 읽기 좋은 순서로 정리하는 최적화를 같이 썼다. 논문에서는 테스트한 데스크톱과 모바일 GPU에서 Hugging Face의 16비트(FP16) 방식보다 3배 넘는 속도 향상을 보고했다.

출처: AWQ 원 논문

이걸 보고 나니 아까 헷갈렸던 부분이 좀 정리됐다.

내가 이해한 건

GPU가 4비트를 “지원하냐 마냐” 하나로 속도가 정해지는 건 아니다. 작게 줄인 모델 숫자를 읽어서 얻는 이득, 다시 풀어 계산하는 비용, GPU가 어떤 숫자 형식을 잘 처리하는지, 실행 프로그램이 GPU에 일을 얼마나 효율적으로 시키는지가 같이 붙어서 실제 속도가 나온다.

그래서 작아지면 빨라지는 건 맞나

대체로 빨라질 조건은 좋아진다. 하지만 용량이 줄어든 비율만큼 속도가 자동으로 빨라지는 건 아니다.

NVIDIA NeMo 문서에서는 양자화의 주요 이점으로 이런 내용을 적고 있다.

  • 모델이 차지하는 메모리가 줄어든다.
  • 메모리에서 옮겨야 할 데이터 양이 줄어든다.
  • 같은 시간에 처리할 수 있는 양이 늘어날 수 있다.

출처: NVIDIA NeMo — Quantization

앞 글에서 썼던 창고 비유가 또 들어맞았다.

16비트 → 4비트로 줄이면 같은 모델을 옮길 때 창고 문을 통과해야 하는 짐 자체가 훨씬 작아진다.

그런데 실제 작업 속도는 문을 통과한 뒤 어떤 기계로 어떻게 계산하느냐까지 포함해서 결정된다.

그러니까 이렇게 보는 게 제일 덜 헷갈렸다.

작게 저장한다 = 옮길 짐을 줄인다.
빨리 계산한다 = 그 작은 짐을 잘 처리하는 하드웨어와 프로그램까지 필요하다.

이제 Q4라는 글자가 조금 다르게 보인다

처음에는 Q4 = 품질을 엄청 깎은 싸구려 버전 정도로 생각했다.

지금은 조금 다르게 보인다.

  1. 모델 안 숫자를 더 적은 종류의 값으로 표현해서 저장 공간을 줄인다.
  2. 그래서 원래 숫자와 달라지는 오차는 생긴다.
  3. 좋은 양자화 방식은 그 오차를 최대한 줄이려고 한다.
  4. 저장은 4비트여도 실제 계산은 더 높은 정밀도로 할 수 있다.
  5. 실제 속도는 GPU와 실행 프로그램까지 봐야 한다.

그리고 가장 마음에 든 이해 방식은 이거였다.

지금까지의 임시 결론

양자화는 AI의 뇌 일부를 통째로 잘라내는 것보다는, AI 안에 들어 있는 숫자를 더 거친 눈금으로 다시 적는 것에 가깝다. 당연히 오차는 생기지만, 잘 줄이면 생각보다 많은 걸 유지하면서 메모리는 크게 아낄 수 있다.

아직 남은 궁금증도 있다.

같은 모델을 Q8, Q6, Q4, Q3로 하나씩 내려가면 실제 답변은 어느 지점부터 눈에 띄게 나빠질까?

이건 논문 숫자만 보는 것보다 나중에 같은 모델을 직접 돌려놓고 질문 몇 개를 똑같이 던져보는 게 훨씬 재밌을 것 같다.

지금은 여기까지 이해했다.

찾아본 순서

  1. NVIDIA TensorRT — Quantization Schemes
  2. NVIDIA TensorRT — Accuracy Considerations
  3. GPTQ — Accurate Post-Training Quantization for Generative Pre-trained Transformers
  4. AWQ — Activation-aware Weight Quantization for LLM Compression and Acceleration
  5. Hugging Face Transformers — bitsandbytes
  6. NVIDIA TensorRT — Capabilities
  7. NVIDIA — Tensor Cores
  8. NVIDIA NeMo — Quantization