본문 바로가기
Deep Learning

Quantization 정리

by 아르카눔 2025. 5. 20.

 

Quantization 양자화는 모델 경량화 방법 중 하나다.

 

FP32 형식의 데이터를 INT 8처럼 더 작은 비트수의 정수 데이터 형식으로 근사하여 모델의 크기를 줄이면서도 정확도를 최대한 유지한다.

 

이를 통해 더 작은 모델로 더 작은 리소스를 사용해서 비슷한 결과를 달성할 수 있게 된다.

 

Quantization 예시. From FP32 To INT 8 

 

Nvidia의 블로그 (링크)를 보면 32 bit FP를 8 bit INT로 근사하는 과정을 보여준다.

 

아래 Figure 1이 그 과정을 보여준다.

Figure 1

 

우선 FP32는 [ $-3.4e38, 3.40e38$ ]의 범위를 가진다.

 

반면에 INT8은 [ $-128, 127$ ]의 범위를 가진다.

 

이때 표현 가능한 숫자의 구간을 동적 범위 dynamic-range 라고 한다. 그리고 인접한 두개의 표현 가능한 숫자 사이의 거리는 precision of prepresentation 표현의 정밀도라고 한다.

 

다시 돌아와서 양자화란 결국 FP32의 약 40억 개의 숫자를 고작 256개의 정수로 매핑하는 과정이 된다. 

 

Figure 1을 보면 알겠지만 FP32의 경우 $-1$과 1사이에 표현 가능한 부동소수점 값들이 절반 가까이 몰려있다.

 

이 구간은 [1, 2] 구간의 표현 가능한 숫자보다 precision이 더 높다.

 

반면에 INT8의 경우 28개의 서로 다른 값들만 표현할 수 있다.

 

이제 이 256개의 값을 균등하게 분포하게 만들지, 비균등하게 분포하게 만들지를 결정해야 한다.

 

FP32 값을 $x_f$로 INT8을 $x_q$로 표기하면 다음과 같이 양자화 할 수 있다.

 

$x_q = Clip( Round(x_f / scale))$ 

 

scale은 FP를 [$-128, 127$]의 범위로 매핑하는데 사용된다. 

 

동적 범위가 원점을 중심으로 대칭이기 때문에 symmetric quatization 대칭 양자화다.

 

Round를 통해서 유리수를 정수로 반올림하며, Clip은 [$-128, 127$]의 범위를 벗어나는 outlier 이상치를 잘라내는 함수다.

 

양수인데 127보다 크면 127로, 음수인데 $-128$ 보다 절대값이 크면 $-128$로 매핑한다. 

 

Figure 1의 amax는 표현할 절대값의 가장 큰 값이다. Scale의 계산에 이 amax를 이용한다.

 

$amax = maxx(abs(x_f))$ 이고  

 

$scale = (2 * amax ) / 256$  

 

scale의 분모는 INT8의 범위라서 256 (음수 128개, 0 1개, 양수 127개)이다. 

 

Asymmetric quatization 비대칭 양자화는 zero point 원점을 새로 설정한다.

 

A Visual Guide To Quantization (링크)를 그대로 따라서 살펴보면 [ $-7.4, 10.8$ ]은 원점인 0에 대해서 대칭이 아니다.

 

따라서 대칭이 되는 point인 zero point를 계산하고 이를 통해서 모든 값들을 shift 한 다음 scaling을 하고 round를 취한다.

 

 

 

Quantization 종류

 

양자화는 크게 2가지로 구분 가능하며, 첫 번째는 학습하는 와중에 양자화를 적용하는 quantization-aware training (QAT), 그리고 두 번째로는 학습을 끝마친 다음에 수행하는 post-training quantization (PTQ)이 있다.

 

 

Tenforflow에서는 아래 Figure 2 (링크)에 나와있듯이 양자화의 적용에 대한 가이드라인을 제공한다. 

 

Figure 2

 

 

명확한 출처를 못 찾았는데 아래 Figure 3와 Table 1을 보면 어느 상황에서 어떤 하드웨어를 사용하는지에 따라서, 어떤 양자화를 수행하면 좋을지에 대한 가이드라인도 존재한다.

 

Figure 3

 

Table 1

 

 

QAT, Quantization-Aware Training

 

 

 

Figure 4

 

 

Quantization aware training layers를 추가하여 양자화된 모델을 양자화 되지 않은 모델화 동시에 학습하는 방법이다.

 

추가 정리 필요 

 

 

 

PTQ, Post-Training Quantization 

학습을 끝마친 모델에 양자화를 수행하는 방법이다.

 

다음과 같은 다양한 프레임워크를 통해서 구현할 수 있다. 

 

  1. TensorFlow Lite (TFLite): 링크
  2. PyTorch Quantization Toolkit: 링크
  3. NVIDIA TensorRT: 링크
  4. GGML: 링크 
  5. llama.cpp: 링크 
  6. bitsandbytes: 링크
  7. GPTQ: 링크

 

 

이 글 (링크)에서는 LLM에서 쓰이는 양자화 방법들을 다음의 Figure 5처럼 분류하고 있다. 

 

Figure 5

 

 

 

GGML, llama.cpp과 Q4_K_M 양식 

GGML은 Georgi Gerganov Machine Learning의 약자로 C/C+로 작성하여 저사양 기기에서도 동작하도록 만드는 프레임워크다.

 

llama.cpp은 GGML을 LLM에 적용하는 프레임워크다.

 

일단 llama.cpp은 K-Quants로 링크 1링크 2이 출처인 것으로 알고 있는데 블록별로 다르게 양자화 한다고 한다.

 

이는 Q4_K_M과 같은 형식인데 LocalLLaMA 레딧 (링크)에서 보면 그 의미는 다음과 같다고 한다.

 

K quantization options are labeled "S", "M", and "L" and stand for small, medium, and large model sizes, respectively. Option "0" represents baseline quantization without extra calibration. In terms of quality and speed: 0 (lowest quality, fastest speed) < S < M < L (highest quality, slowest speed).

 

보통은 Q4_K_M을 가장 많이 쓰는 것 같다.

 

보다 자세한 내용은 뒤의 K-Quants에서 설명한다. 

 

 

GGUF

 

모델을 빠르게 저장하고 불러오기 위한 파일 포맷이다.

 

Figure 6

 

 

K-Quants

 

A Visual Guide To Quantization (링크)와 LLMs quantization naming explained (링크)를 통해서 파악한 내용을 정리한다.

 

Q4_K_M의 형식을 살펴본다.

 

우선 첫번째 자리의 4는 bit의 수다. Q4면 4 bit의 정수가 가지는 범위로 매핑한다는 뜻이다. 

Bit의 수는 2, 3, 4, 5, 6, 8이 가능하다.

 

2번째 자리에는 0, 1, K가 올 수 있다.

 

0과 1은 uniform quantization으로 range를 균등하게 자른다. 

 

0은 0을 원점으로하는 양자화 방식이고, 1은 새로 zero point를 계산하는 양자화 방식이다.  

 

Figure 7

 

 

K는 메모리 사용의 최적화를 위해서 range를 불균등하게 자른다.

 

Figure 8

 

3번째 자리S, M, L은 메모리 블록의 사이즈다.

크기가 작을 수록 메모리를 더 적게 사용하지만 반대로 정밀도가 낮다.

 

  • S (Small): Indicates small-sized blocks are used for quantization.
  • M (Medium): Indicates medium-sized blocks are used.
  • L (Large): Indicates large-sized blocks are used.

 

LLMs quantization naming explained의 저자는 다음과 같이 이름을 통해서 의미를 파악하게 알려준다. 

 

So, here’s a result test overview on understanding the naming convention:

  • llama-2–13b-chat.ggmlv3.q4_0.bin: Uses q4 quantization with simple uniform quantization (divided into equal parts).
  • llama-2–13b-chat.ggmlv3.q2_K.bin: Uses q2 quantization with k-quant (advanced, not equal parts) system quantization.
  • llama-2–13b-chat.ggmlv3.q3_K_M.bin: Uses q3 quantization with k-quant system and medium blocks (smaller than the normal q3_K).
  • llama-2–13b-chat.ggmlv3.q5_K_S.bin: Uses q5 quantization with k-quant system and small blocks (the smallest possible q5_K).

 

 

 

 

구체적으로 블록에 대해서 어떻게 계산하는지를 알아본다.

 

Operations in Block

Figure 9

 

우선 주어진 레이어에서의 weights를 super block과 sub block으로 구성한다.

Super block은 sub block의 집합을 가진다.

 

본래 absmax 방식에서는 Equation 1에서처럼 scale factor인 S를 값 X에 곱하게 된다. 

Equation 1

 

반면에 K-Quants에서는 sub block의 scale factor를 그대로 사용하는게 아니라, super block의 scale factor를 이용해서 양자화한다. 

한마디로, Weights와 sub blocks의 scale factors 둘 다 양자화한다. 

또한 super blocks도 각각이 고유한 scale factor를 지닌다. 

 

Equation 2

 

 

아래 Table 2에 Super block과 sub block의 예시가 나와있다. 

 

Table 2

 

 

 

 

 

GPTQ

 

A Visual Guide To Quantization (링크)의 설명을 따른다. 

 

기본적으로 layer-wise quantization이다. 

Figure 10

 

 

먼저 weights를 Inverse Hessian으로 변경한다.

Hessian matrix는 2nd order derivatives of loss function이고,

Inverse Hessian은 이 Hessian matrix의 역행렬이다.

 

Figure 11

 

 

구체적인 계산은 다음 Equation 1, 2, Figure 9에 표기된 방식으로 수행한다.

 

Equation 3

 

 

Equation 4

 

 

 

Figure 12

 

Inverse Hessian을 구하는 것 자체도 하나의 큰 주제일텐데 이에 대한 내용은 원래 GPTQ 논문 (링크)을 봐야 알 수 있을듯하다. 

 

 

bitsandbytes

bitsandbytes는 Huggingface와 연동해서 사전 학습된 모델의 양자화가 가능하다.

 

LLM.int8(): 8-bit Matrix Multiplication for Transformers at Scale 논문 (링크)의 방식을 따라서 outlier가 있는 경우에는 그 값만을 FP16의 형식으로 반환한다.

 

Figure 13

 

 

또한 llm_int8_skip_modules 같은 arguments를 통해서 양자화할 레이어를 건너뛸 수도 있다.

 

또한 dequntization 비양자화도 가능하다. 

 

아래 코드처럼 간편하게 사용할 수 있다. 

from transformers import AutoModelForCausalLM, BitsAndBytesConfig

quantization_config = BitsAndBytesConfig(llm_int8_enable_fp32_cpu_offload=True)


model_id = "google/gemma-2-2b-it"

model_8bit = AutoModelForCausalLM.from_pretrained(
    model_id,
    device_map='auto',
    quantization_config=quantization_config,
)

 

아래 Figure 14의 Gemma2 2B Instruct의 weight를 살펴보면 8비트의 정수 형태로 적재되었음을 알 수 있다. 

 

Figure 14

 

 

원래 모델 카드에 있는 Gemma2 2B Instruct는 2.6B의 파라미터의 모델이며 데이터 타입이 BF16이므로 원래는 약 5GB를 차지 하게 된다. (이때 dtype을 torch.bf16으로 정해줘야 한다. 아니면 FP32으로 자동으로 업스케일링하는지 10GB를 차지한다. 관련 링크 참조)

 

하지만 Figure 15에서 알 수 있듯이 디폴트로 잡힌 약 1.7GB의 램을 제외하면 bitsandbytes를 적용한 모델이 메모리를 차지하는 크기는 Figure 16에 나온대로 약 3.5 기가다. 

Figure 15

 

Figure 16

 

 

BF16으로 2.6 GB의 모델을 불러오면 약 5.2 GB가 된다.

from transformers import AutoModelForCausalLM

model_id = "google/gemma-2-2b-it"

model = AutoModelForCausalLM.from_pretrained(
    model_id,
    device_map='auto',
    torch_dtype=torch.bfloat16,
)

 

마찬가지로 Figure 15의 디폴트 램 점유 값인 1.7 GB를 제외하면 약 6.3 GB의 용량을 차지함을 Figure 17에서 알 수 있다.

 

오버헤드를 감안하면 계산한 결과와 유사하게 메모리 용량을 차지한다. 

 

Figure 17

 

 

PyTorch Quantization

PyTorch의 양자화에 대한 블로그 (링)를 참조했다 .

 

Dynamic Quantization

 

여기서 말하는 activations은 LLM을 거치면서 계속해서 업데이트 되는 input을 말한다.  

동적 양자화는 activations를 하나의 레이어를 통과할 때 마다 zero point와 scaling factor를 계산하고 이를 이용하여 양자화한다.

동적 양자화의 경우 가중치를 int8로 변환하는 것뿐만 아니라, 계산 직전에 activations도 int8로 변환한다.

하지만 activations는 메모리에 읽고 쓸 때 FP를 사용한다. 

 

양자화된 모델은 가중치를 찍어보면 실수가 나온다. 

int_repr()로 실행해야 양자화된 값을 확인할 수 있다. 

# Post Training Dynamic Quantization

import torch

# define a floating point model
class M(torch.nn.Module):
    def __init__(self):
        super().__init__()
        self.fc = torch.nn.Linear(4, 4)

    def forward(self, x):
        x = self.fc(x)
        return x

# create a model instance
model_fp32 = M()
# create a quantized model instance
model_int8 = torch.ao.quantization.quantize_dynamic(
    model_fp32,  # the original model
    {torch.nn.Linear},  # a set of layers to dynamically quantize
    dtype=torch.qint8)  # the target dtype for quantized weights

model_int8.fc.weight()
>> 
tensor([[-0.2456,  0.1209, -0.0756, -0.2721],
        [-0.0416, -0.3741, -0.4799, -0.4837],
        [-0.4421, -0.3779,  0.1889, -0.3627],
        [-0.4005, -0.3816,  0.0718,  0.2494]], size=(4, 4), dtype=torch.qint8,
       quantization_scheme=torch.per_tensor_affine, scale=0.003778624115511775,
       zero_point=0)
       
# 양자화된 가중치 가져오기
quantized_weight = model_int8.fc.weight()

# qint8로 저장된 정수값 확인
qint8_values = quantized_weight.int_repr()
print("qint8 값:", qint8_values)

# 스케일(scale)과 제로포인트(zero_point) 확인
scale = quantized_weight.q_scale()
zero_point = quantized_weight.q_zero_point()
print("스케일:", scale)
print("제로포인트:", zero_point)

>>
qint8 값: tensor([[  30,   89,  -78, -122],
        [ -26,  -71, -109,   83],
        [  11,   50,   19, -127],
        [ -44,  103,  -59,  -69]], dtype=torch.int8)
스케일: 0.0036430195905268192
제로포인트: 0

 

 

Static Quantization

 

정적 양자화는 inference 추론 단계에서 zero point와 scaling factor를 계산하고 이를 이용하여 양자화한다.

PyTorch의 정적 양자화에서는 네트워크에 데이터 배치를 먼저 공급하고 다양한 activations 값의 결과 분포를 계산하는 추가 단계를 수행한다. (구체적으로는 이러한 분포를 기록하는 여러 지점에 "Observer 관찰자" 모듈을 삽입하여 수행한다.) 양자화된 값을 부동 소수점으로 변환한 다음 다시 정수로 변환하는 대신, 연산 간에 양자화된 값을 전달할 수 있어 속도가 크게 향상된다. 

 

 

PyTorch의 모델 종류와 권장 양자화 모듈

Table 3

 

RNN, LSTM, BERT, Transformer 계열은 Dynamic을,

 

CNN 계열은 Static와 Quantization Aware Training을 권장한다.

 

 

PyTorch의 연산 종류와 적용 가능 양자화 방법

 

Operator 레벨로 정리한 표도 있다.

 

Table 4

 

 

 

 

 

Dequntization

 

말 그대로 양자화한 내용을 다시 역으로 되돌리는 과정이다.

 

Quantization과 dequantization 과정을 링크에 나온 예시로 알아본다.

 

 

1. Here the step-by-step recipe for quantization:

 

  1. We find the absolute maximum value of the vector: [3, 1, 2, 3] -> 3
  2. Then we divide by that value: [3, 1, 2, 3] -> [1, 0.33, 0.66, 1.0]
  3. And now we multiple by the range of the target data type I3, which is 4: [1, 0.33, 0.66, 1.0] -> [4.0, 1.33, 2.66, 4.0]
  4. Now we round to the nearest value: [4.0, 1.33, 2.66, 4.0] -> [4, 0, 2, 4]

 

We now converted [3, 1, 2, 4] in I5 to [4, 0, 2, 4] in I3. 

 

 

2. To dequantize, we reverse this process.

  1. Divide by 4: [4, 0, 2, 4] -> [1.0, 0.0, 0.5, 1.0]
  2. Multiply by the absolute maximum: [1.0, 0.0, 0.5, 1.0] -> [3.0, 0.0, 1.5, 3.0]
  3. Now we round again: [3.0, 0.0, 1.5, 3.0] -> [3, 0, 2, 3]

 

양자화에서 3으로 나누고, 4를 곱해준 과정을 역으로 해서,

4로 나눠주고, 3을 곱한다. 정수 형태를 만들기 위해서 이를 다시 반올림한다.

 

 

 

 

 

 

 

 

References:

https://velog.io/@jooh95/%EB%94%A5%EB%9F%AC%EB%8B%9D-Quantization%EC%96%91%EC%9E%90%ED%99%94-%EC%A0%95%EB%A6%AC

https://re-code-cord.tistory.com/entry/Quantization%EC%9D%80-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C

https://www.ibm.com/kr-ko/think/topics/quantization

https://myunghaklee.github.io/quantization/quantization_method/

https://data-newbie.tistory.com/992

https://velog.io/@ganta/Quantization

https://docs.pytorch.org/docs/stable/quantization.html

https://huggingface.co/blog/merve/quantization

https://www.tensorflow.org/lite/performance/model_optimization?hl=ko

https://docs.nvidia.com/deeplearning/tensorrt/latest/inference-library/work-quantized-types.html

https://developer.nvidia.com/blog/achieving-fp32-accuracy-for-int8-inference-using-quantization-aware-training-with-tensorrt/

https://medium.com/@siddharth.vij10/llm-quantization-gptq-qat-awq-gguf-ggml-ptq-2e172cd1b3b5

https://www.tensorops.ai/post/what-are-quantized-llms

https://n-brogrammer.tistory.com/m/135

https://gaussian37.github.io/dl-concept-quantization/

https://pytorch.org/blog/quantization-aware-training/

https://github.com/matlab-deep-learning/quantization-aware-training/blob/main/README.md

https://huggingface.co/blog/4bit-transformers-bitsandbytes

https://huggingface.co/blog/hf-bitsandbytes-integration

https://timdettmers.com/2022/08/17/llm-int8-and-emergent-features/

https://github.com/ggml-org/llama.cpp/pull/1684

https://github.com/ggml-org/llama.cpp/blob/master/tools/quantize/README.md

https://www.reddit.com/r/LocalLLaMA/comments/1ba55rj/overview_of_gguf_quantization_methods/

https://huggingface.co/docs/transformers/v4.51.3/ko/quantization/bitsandbytes

https://docs.pytorch.org/docs/stable/quantization-support.html

https://pytorch.org/blog/introduction-to-quantization-on-pytorch/

https://docs.pytorch.org/docs/stable/quantization.html#quantization-api-summary

https://velog.io/@woojinn8/LightWeight-Deep-Learning-2.-Quantization

https://leimao.github.io/article/Neural-Networks-Quantization/

https://www.ibm.com/kr-ko/think/topics/gguf-versus-ggml

https://andreshat.medium.com/llm-quantization-naming-explained-bedde33f7192

https://newsletter.maartengrootendorst.com/p/a-visual-guide-to-quantization

https://hayunjong83.tistory.com/59

https://github.com/ggml-org/llama.cpp/pull/4861

https://github.com/byroneverson/llm.cpp/blob/master/k_quants.c

https://github.com/byroneverson/llm.cpp/blob/master/k_quants.h

https://news.ycombinator.com/item?id=36577898