모델 양자화(Quantization)은 모델의 가중치(weight)와 활성화 함수(activation function) 출력 값을 낮은 정밀도(precision)으로 변환하여 메모리를 줄이고 연산을 빠르게 하는 기법이다. 모델 가중치 값 등을 실수형(floating-point) 변수에서 정수형(integer or fixed point) 변수로 변환하여 모델의 성능은 보존하되 사이즈를 줄여 모델의 실행과 배포를 효율적으로 하는 것이다.
낮은 정밀도(precision)으로 변환한다는 것은 다음과 같다. 먼저 데이터를 표현하는 부동 소수점(Floating Point)는 정규화를 통해 더 많은 수를 적은 메모리로 표현하기 위한 것이다.
데이터를 "부호 x 가수 x 밑수^(지수)" 형태로 표현하게 된다. 예를 들어 123.45라는 데이터가 있다면 12.345 x 10^1 로 표현하게 되는데 지수가 0이면 123.45 -1이면 1234.5가 된다. 소수점이 떠다니는 것처럼 변한다고 해서 floating point라고 부른다. 데이터를 정규화하여 가수가 밑수보다 더 작게 만들게 된다.
ex) 123.75 → 1.2375 * 10^2, 12.375가 있을 때
가수부의 정수부 12을 2진수로 바꾸면 1100
가수부의 실수부 0.375를 2진수로 바꾸면 0.011
(1100.011)2 가 되고 정규화 를 거치면 > (1.100011)2 * 2^3
정규화를 하면 정수부는 언제나 1이므로 생략하고, ‘가수부’ 와 ‘지수부’로 표현한다.
precision이 낮을 수록 표현 가능 범위를 벗어난 값들은 버려지게 된다. 123456.7890 이 있으면 정규화를 하면 1.234567890 * 10^5가 되는데 가수부 자릿수가 5인 경우 1.23456 * 10^5 으로 표현된다. 가수부가 클수록 원래 값이 잘 유지된다. = 정밀도(precision)가 올라간다.
모델 학습시 가중치는 보통 fp32 데이터 타입으로 표현하는데, 연산이 오래걸리고 메모리를 많이 사용하게 된다. fp32의 절반으로 데이터를 표현하는 타입은 bfloat16이다. fp16은 half-precision 16-bit 부동 소수점 형식으로 표현한다는 것을 의미하며 주로 predict에 많이 사용한다. 32bit 부동 소수점을 8bit integer 타입으로 변환하게 되면 3412.2345 라는 데이터가 3412로 변환된다.
양자화 라이브러리 중 GGUF라는 것이 있는데 딥러닝 모델 저장 용도의 단일 파일 포맷이다. LLM 추론에 많이 사용되는데 fp16을 넘어 8,6,5,4,3-bit 그리고 2-bit 양자 텐서타입까지 지원한다. 로컬 LLM 추론기로 유명한 llama.cpp에는 다양한 모델을 양자화 변환해 GGUF 파일로 공유한다.
데이터의 정밀도를 낮추게 되면 저장 값의 용량을 줄일 수 있는 것 뿐 아니라 계산 복잡도도 줄일 수 있다. 곱셈의 경우 데이터 사이즈를 N배 줄이면 연산 복잡도를 N*N배로 줄일 수 있다. fp32대신 int8로 데이터를 표현할 경우 모델 사이즈는 약 1/4가 되고 추론 속도는 4배정도 빨라지고 메모리 대역폭도 2-4배 정도 작아진다.
✲양자화의 작동 방식
양자화는 필연적으로 데이터 표현 범위와 정확도가 바뀌기 때문에 손실과 성능 저하가 있을 수 있다. 따라서 단순히 표현 범위를 벗어난 값을 버리는 것이 아니라 추론 손실을 떨어트리지 않게 양자화하는 법이 필요하고 이에 대한 연구가 많이 이루어지고 있다.
기본적으로 고정밀 값을 낮은 정밀도 값으로 매핑하는 과정이 필요한데, 고정밀도를 가진 값들의 범위에 비해 int8, int4와 같은 정수형의 범위는 적기 때문에 복잡하다. 양자화에는 입력과 출력 매핑 사이의 간격이 일정하도록 하는 uniform/symmetric 방법이 있고 불균형하도록 하는 non-uniform/asymmetric 방법이 있다.
‣ symmetric quantization
x_q = round(r/S)
- x_q : 양자화된 int 타입의 값
- S : fp의 scaling factor
- round : 결과 값을 가장 가까운 정수로 반올림
fp 값의 [min, max] 범위를 찾기 위해서 먼저 더 작은 데이터로 모델을 교정해야 한다. 일반적으로 최대, 최소는 여러 가지 방법으로 결정될 수 있고 일반적인 방법은 관찰된 최소, 최대 값으로 설정하는 것이다. 결과적으로 범위를 벗어나는 모든 값을 각각 최소, 최대 값에 매핑되는 클리핑을 하게 된다. fp16을 int8로 변환하는 경우 범위은 -127~127이고 입력의 0이 대칭 매핑으로 이어지는 출력의 0에 매핑되도록 보장한다.
‣ asymmetric quantization
x_q = round(r/S) + Z
- Z : fp에서 0에 해당하는 int의 값
반면, 0의 양쪽 값이 대칭되지 않는 경우나 입력의 0을 출력의 0이 아닌 값에 매핑하는 경우를 비대칭 양자화(asymmetric quantization)이라 한다. 출력에서 0 값이 이동되었으므로 수식에서 0 요소 Z를 포함해 방정식을 계산해야 한다.
scaling factor s와 영점을 선택하는 방법은 위 그림과 같다. s는 본질적으로 최소값 r_min에서 최대값 r_max까지 전체 범위를 균일하게 나눈다. 하지만 음수 값인 경우 알파, 양수 값인 경우 베타 등 어느 시점에서 이 입력을 클리핑하도록 선택할 수 있다. 알파 베타 이외 값은 알파와 동일한 출력에 매핑된다. 클리핑 값과 알파, 베타를 선택하고 클리핑 범위를 선택하는 프로세스를 "calibration"이라고 한다.
과도한 클리핑을 방지하기 위한 가장 쉬운 옵션은 알파를 r_min으로 설정하고 베타를 r_max로 설정하는 것이다. 그리고 r_min, r_max를 사용해 s를 계산할 수 있고 이로 인해 출력이 비대칭이 될 수 있다.
✲ 양자화 종류
양자화 종류에는 학습된 모델을 양자화 하는 기법(PTQ, Post-training Quantization)과 학습하면서 양자화하는 기법(QAT, Quantization-Aware Training)이 있다.
‣ PTQ, Post-training Quantization
이미 학습된 LLM을 양자화하는 기술을 의미하고 GAT보다 구현하기는 쉽지만 가중치 값의 정밀도 손실로 인해 모델 정확도가 감소할 수 있다. 학습이 완료된 모델을 통계적으로 분석해 알파를 구하게 된다.
‣ QAT, Quantization-Aware Training
학습시 양자화를 하는 기법으로 성능이 보존되도록 데이터를 미세조정하게 된다. 학습 단계에서 보정, 범위 추정, 클리핑, 반올림 등의 가중치 변환 프로세스를 진행한다. 이로 인해 PTQ에 비해 모델 성능감소를 완화할 수 있지만 더 많은 연산이 필요하다. QAT는 학습을 통해 알파를 구하게 되고 학습이 완료된 모델에 양자화를 위한 레이어를 추가한 후 다시 파인튜닝하여 적절한 알파를 구한다.
QAT는 처음에 사전 학습된 모델 또는 PTQ 모델로 시작한다. PTQ 모델을 사용한 경우 발생한 손실을 복구화 한다.
양자화하는 레이어에 fake quantizatio module을 배치해서 양자화할 때 성능에 어떤 영향을 미치는지 확인하게 된다. forward pass시에 weight를 양자화하고 backpropagation 때 양자화된 weight에 대해 gradient를 계산한다. 이때 STE(Straight Trough Estimator)를 통해서 backpropagation 때는 양자화값에 영향을 받지 않도록 한다. STE는 양자화된 영역을 다시 원래대로 만들고 실제 weight 반영은 양자화 값 Q가 아닌 r에 대해 진행할 수 있도록 하여 모델이 양자화에 의한 오류를 이해할 수 있도록 한다.
그리고 AWQ(Activation-Aware Quantization)이라는 방법이 있는데 이는 모든 가중치를 양자화하지 않는 방법이다. 중요한 가중치를 확대하고 그렇지 않은 가중치의 정밀도를 낮춤으로써 양자화로 인핸 성능 저하를 최소화 할 수 있다.