제5부 추론·서빙(Inference/Serving)
5.2 양자화 — INT8·FP8·GPTQ·AWQ
앞 장이 남긴 문장은 하나였다 — 생성의 시간은 연산이 아니라 메모리에서 온다. 양자화는 그 문장의 가장 직접적인 귀결이다. decode 한 스텝의 시간은 대략 "읽어야 할 바이트 ÷ 메모리 대역폭"이고(5.1), 그 바이트의 대부분은 모델 가중치다. 그러니 가중치를 FP16 2바이트에서 INT8 1바이트로, 다시 INT4 0.5바이트로 줄이면 스텝마다 읽을 것이 절반·4분의 1로 줄고, decode가 그만큼 빨라진다. 정밀도는 하나의 조정 변수다. 이 장은 그 조정 변수를 어디까지·어떻게 조정할 수 있는지, 왜 실행하면 빨라지는지, 그리고 어디서 손이 나는지를 다룬다.
이 장에서 되풀이될 문장이 하나 있다. 큰 값 하나가 스케일을 지배하면 나머지 대다수가 구분되지 않는다. 양자화의 거의 모든 어려움 — 왜 활성이 가중치보다 어렵고, 왜 입도를 잘게 나누며, GPTQ·AWQ·SmoothQuant가 무엇을 서로 다르게 하는지 — 이 한 문장의 변주다. 그리고 정직하게 명시해 둘 것 하나. 이 분야에 "이 방법이 항상 최고"는 없다. 무엇이 나은지는 모델 크기·아키텍처·목표 비트·보정 데이터·태스크·하드웨어가 정하고, 조건을 바꾸면 순위가 뒤집힌다. 이 장의 비교와 수치는 방향을 주는 것이지, 특정 값을 외우라는 것이 아니다.
전제는 5.1이다 — prefill/decode, KV 캐시, 메모리 바운드, roofline이 여기서 그대로 쓰인다. 부동소수 포맷(FP16·BF16·FP8)의 비트 구조는 2.6에서 학습 관점으로 이미 다뤘으니 요점만 재사용한다. 그쪽은 그래디언트가 흘러야 해 제약이 다르고, 이 장은 학습이 끝난 모델을 배포용으로 압축하는 추론 양자화다.
5.2.1 정밀도를 낮추면 무엇이 사는가
양자화가 사는 것은 셋인데, 셋이 같은 근원에서 나온다 — 값 하나를 담는 바이트가 줄어든다.
첫째는 메모리 용량이다. FP16 가중치를 INT8로 바꾸면 모델이 절반, INT4면 4분의 1이 된다. 이것은 곧 "몇 장의 GPU가 필요한가"이고, 그것이 곧 비용이다(7.5). 70B급 모델은 FP16으로 약 140 GB라 80 GB GPU 한 장에 안 들어가 최소 두 장에 텐서 병렬로 분할해야 한다(2.4). INT8이면 약 70 GB로 한 장에 겨우 들어가고, INT4면 약 35 GB라 KV 캐시·활성까지 추가하고도 한 장에 여유가 생긴다. GPU를 두 장에서 한 장으로 내리는 것은 단순히 비용 절반이 아니다 — 매 스텝 GPU 사이를 오가던 통신(2.5)이 전부 사라져 지연 구조 자체가 바뀐다.
둘째는 메모리 대역폭, 즉 속도다. 이것이 양자화가 학습용 압축과 결정적으로 다른 지점이다. decode는 토큰마다 가중치 전부를 HBM에서 읽는 메모리 바운드이므로(5.1), 읽을 바이트가 절반이면 읽는 시간도 대략 절반이다. 5.1의 산식을 그대로 가져오면
$$t_{\text{decode}} \;\approx\; \frac{\text{모델 바이트}}{\text{메모리 대역폭}} \;=\; \frac{N_{\text{param}} \times (\text{bytes/param})}{\text{BW}}$$
이고, bytes/param을 2에서 1·0.5로 줄이면 이 시간이 그대로 준다. 숫자를 넣어 보자. 70B 모델을 대역폭 3.3 TB/s인 GPU(들)에서 배치 1로 디코드한다 치면, FP16은 스텝마다 140 GB를 읽어
$$t_{\text{FP16}} \approx \frac{140\ \text{GB}}{3.3\ \text{TB/s}} \approx 42\ \text{ms} \;\Rightarrow\; \text{약 } 24\ \text{tok/s}$$
INT4로 내리면 35 GB만 읽어
$$t_{\text{INT4}} \approx \frac{35\ \text{GB}}{3.3\ \text{TB/s}} \approx 10.6\ \text{ms} \;\Rightarrow\; \text{약 } 94\ \text{tok/s}$$
같은 하드웨어에서 토큰 속도가 약 네 배가 됐다. 계산을 빠르게 해서가 아니다 — 읽을 것을 줄여서다. 5.1에서 8B FP16이 200 tok/s였던 것과 같은 이유, 같은 산식이다.
셋째는 비용인데, 이것은 앞의 두 이득의 회계적 이면이다. 같은 GPU로 더 많은 요청을 처리하거나(용량), 요청당 더 빨리 끝내거나(대역폭), 아예 더 저렴한 GPU로 내려가면 토큰당 비용이 준다.
여기서 가장 흔한 오해를 처음부터 깨 두는 편이 낫다. "가중치를 4비트로 낮추면 무조건 빨라진다"는 틀리다. 위 계산은 배치가 작아 메모리 바운드인 decode에서만 성립한다. prefill나 큰 배치는 계산 바운드라(5.1의 roofline), 가중치가 작아져도 읽기가 병목이 아니다. 게다가 가중치만 양자화한 경우 GEMM 직전에 그 가중치를 다시 FP16으로 역양자화(dequantize)해서 곱하므로, 계산량 자체는 한 톨도 줄지 않고 오히려 역양자 오버헤드가 추가된다.
그래서 prefill·큰 배치에서 계산까지 빠르게 하려면 활성도 정수로 바꿔 정수 텐서 코어를 활용하거나(W8A8), FP8 GEMM을 써야 한다(5.2.8). 병목이 어디인지를 먼저 봐야 양자화가 실제로 어디서 이득인지 판단할 수 있다는 것 — 이것이 5.1을 앞에 둔 이유다.
5.2.2 실수를 격자에 옮기는 사상 — 스케일과 영점
양자화는 연속인 실수 $x$를 정해진 개수의 정수 격자점에 대응시키는 사상이다. $b$비트 정수면 격자점은 $2^b$개다(INT8이면 256개). 이 사상을 정하는 상수는 둘뿐이다. 하나는 스케일 $s$ — 정수 한 칸(=1)이 실수로 얼마인가, 곧 격자의 간격(step size). 다른 하나는 영점 $z$ — 실수 0이 어느 정수에 놓이는가, 곧 격자 원점의 이동.
가장 일반적인 형태는 아핀(affine), 흔히 비대칭(asymmetric)이라 부르는 것이다.
$$q = \operatorname{clip}\!\Big(\operatorname{round}\!\big(\tfrac{x}{s}\big) + z,\; q_{\min},\; q_{\max}\Big), \qquad \hat{x} = s\,(q - z)$$
앞 식이 양자화(실수 → 정수 $q$), 뒤 식이 역양자화(정수 → 근사 실수 $\hat{x}$)다. round는 반올림, clip은 표현 범위를 벗어난 값을 상·하한으로 포화(saturate)시킨다. 이 두 연산이 오차의 두 원천이다 — 범위 안에서는 반올림 오차, 범위 밖에서는 클리핑(포화) 오차.
비대칭 양자화는 실수 구간 $[x_{\min}, x_{\max}]$를 정수 구간 $[q_{\min}, q_{\max}]$에 전부 맞춘다. 두 구간의 폭 비율이 스케일이고, 실수 0이 어디로 가는지가 영점이다.
$$s = \frac{x_{\max} - x_{\min}}{q_{\max} - q_{\min}}, \qquad z = q_{\min} - \operatorname{round}\!\Big(\frac{x_{\min}}{s}\Big)$$
역양자화한 $\hat x$가 원래 $x$와 다르다는 사실이 양자화 전부의 단초다. 반올림에서 오차가 생기고, 스케일이 크면(격자가 성기면) 그 오차가 크다. 그리고 스케일을 무엇이 정하는가 — $x_{\max}-x_{\min}$, 즉 데이터의 폭이 정한다. 폭을 넓히는 값이 무엇이든(대개 이상치 하나), 그것이 스케일을 키워 나머지 값들의 해상도 전체를 저하시킨다. 이 인과가 5.2.4와 5.2.9에서 두 번 더 돌아온다.
5.2.3 대칭 vs 비대칭 — 그리고 왜 가중치는 대칭인가
영점을 0으로 고정하면($z=0$) 대칭(symmetric) 양자화다. 실수 0이 정수 0에 놓이고, 표현 범위를 $[-\alpha, +\alpha]$로 원점 대칭으로 설정한다($\alpha=\max|x|$).
$$s = \frac{\max|x|}{q_{\max}}, \qquad q = \operatorname{clip}\!\Big(\operatorname{round}\!\big(\tfrac{x}{s}\big),\, -q_{\max},\, q_{\max}\Big), \qquad \hat{x} = s\,q$$
INT8 대칭은 보통 $q_{\max}=127$을 써서 범위를 $[-127,127]$로 두고, 짝을 맞추려 $-128$ 한 칸을 버린다(대칭을 지키려는 대가로 격자점 하나를 포기).
대칭이냐 비대칭이냐를 취향으로 고를 문제가 아니다 — 정수 행렬곱의 커널 복잡도가 여기서 갈린다. 이것이 실무에서 대칭을 선호하는 실제 이유이므로, 왜 그런지를 수식으로 밟아 두자. 두 벡터의 내적을 양자화된 값으로 계산한다고 하자. 각각 가중치·활성이라 보면
$$\sum_i \hat{w}_i \hat{x}_i = \sum_i s_w(q^w_i - z_w)\, s_x(q^x_i - z_x) = s_w s_x \sum_i (q^w_i - z_w)(q^x_i - z_x)$$
곱을 전개하면 항이 넷 나온다.
$$= s_w s_x\Big[\underbrace{\textstyle\sum_i q^w_i q^x_i}_{\text{정수 내적}} - z_w\textstyle\sum_i q^x_i - z_x\textstyle\sum_i q^w_i + N z_w z_x\Big]$$
여기서 정수 텐서 코어가 빠르게 처리하는 것은 첫 항, 순수한 정수 내적뿐이다. 나머지 세 항 — 영점 교차항 — 은 활성 합·가중치 합을 따로 구해 보정해야 하고, 활성 합은 입력이 바뀔 때마다 다시 계산된다. 대칭이면 $z_w=z_x=0$이라 이 셋 전체가 사라져 $s_w s_x \sum q^w_i q^x_i$ 하나만 남는다. 명료한 정수 내적에 스케일 하나 곱하면 끝이다.
여기서 실무 조합이 나온다. 가중치는 학습이 끝나 고정돼 있고 대체로 0을 중심으로 대칭인 분포라, 대칭이 자연스럽고 정수 연산도 간단하다. 활성은 다르다 — ReLU·GELU·SiLU 같은 활성함수(1.5) 뒤라 한쪽으로 쏠려 있어, 비대칭이면 격자를 유휴 상태로 두지 않고 데이터가 실제로 사는 구간에 꽉 맞출 수 있다. 그래서 흔한 선택이 가중치 대칭 + 활성 비대칭, 또는 커널을 단순하게 두려고 둘 다 대칭이다.
| 구분 | 대칭 (symmetric) | 비대칭 (asymmetric·affine) |
|---|---|---|
| 영점 $z$ | 0 고정 | 데이터에 맞춰 이동 |
| 정수 GEMM | 교차항 없이 순수 정수 내적 | 영점 교차항 보정 필요 |
| 격자 활용 | 0 중심 대칭 분포에 알맞음 | 한쪽 치우친 분포에서 격자 안 놀림 |
| 전형적 대상 | 가중치(0 중심) | 활성(활성함수 뒤 비음수 치우침) |
5.2.4 오차의 크기 — s/2 경계, s²/12 잡음, 비트당 6 dB
양자화가 "얼마나 틀리나"를 짐작이 아니라 수식으로 확정해야 어디까지 낮춰도 되는지 판단할 수 있다. 세 개의 결과가 이 장 내내 쓰인다.
경계. 클리핑에 안 걸린 범위 안에서 오차는 순수한 반올림 오차다. 반올림은 가장 가까운 격자점으로 보내므로, 그 거리는 격자 간격의 절반을 넘을 수 없다.
$$|x - \hat{x}| \;\le\; \frac{s}{2} \qquad (\text{범위 안, round-to-nearest})$$
잡음 분산. 오차 $e = x-\hat x$가 $[-s/2,\, s/2]$ 위에서 균등하다고 근사하면(값들이 격자 간격에 비해 조밀하면 좋은 근사다), 그 분산을 적분으로 곧장 얻는다.
$$\sigma_q^2 = \int_{-s/2}^{s/2} e^2 \cdot \frac{1}{s}\, de = \frac{1}{s}\left[\frac{e^3}{3}\right]_{-s/2}^{s/2} = \frac{1}{s}\cdot\frac{2}{3}\left(\frac{s}{2}\right)^3 = \frac{s^2}{12}$$
이 $s^2/12$가 양자화 잡음의 표준 결과다. 신호에 이만한 분산의 잡음이 더해진 것으로 보면 된다.
비트당 SQNR. 비트를 하나 늘리면 격자점이 두 배가 돼 $s$가 절반이 되고, 잡음 분산은 $s^2/12$이니 4분의 1로 준다. 신호대양자화잡음비(SQNR)를 데시벨로 보면
$$\text{SQNR} = 10\log_{10}\frac{\sigma_x^2}{\sigma_q^2}, \qquad \Delta(\text{SQNR}) = 10\log_{10}4 \approx 6.02\ \text{dB / 비트}$$
구체적으로, 신호가 표현 범위를 꽉 채운 균등분포라면($\sigma_x^2 = \alpha^2/3$, $s=2\alpha/2^b$) 계산이 정확히 떨어져 $\text{SQNR} \approx 6.02\,b$ dB가 된다. INT8이면 약 48 dB, INT4면 약 24 dB, INT2면 약 12 dB다. 비트를 반으로(8→4) 줄이면 SQNR가 절반(48→24 dB)으로 떨어진다는 이 계산이, 뒤에서 "4비트 근처가 실용 경계이고 그 아래는 절벽"이라는 경험칙의 정량적 골격이다.
두 가지가 곧바로 따라 나온다. 첫째, 오차를 줄이는 정공법은 비트를 늘리는 것이지만 비용이 비트에 선형이라 곧 한계에 부딪힌다. 둘째 — 그리고 이것이 더 중요한데 — $s$는 $\max|x|$가 정하므로, 비트를 안 늘리고도 큰 값 하나를 격리해 $s$를 낮추면 나머지 전부의 오차가 준다. 입도(5.2.5)와 이상치 처리(5.2.9)가 전부 이 두 번째 수단을 쓰는 방법이다.
5.2.5 입도 — per-tensor·per-channel·group·per-token
스케일·영점을 하나만 두느냐 여럿 두느냐가 입도(granularity)다. 하나의 스케일이 덮는 값이 적을수록 그 안의 $\max|x|$ 편차가 작아 $s$가 데이터에 잘 맞고 오차가 준다. 대신 스케일을 여럿 저장하는 부기(bookkeeping) 오버헤드가 는다.
per-tensor는 가중치 행렬 전체에 스케일 하나다. 가장 저렴하고 빠르지만, 텐서 안 값 분포가 넓으면 한 스케일이 그 폭 전체를 감당하느라 거칠어진다. per-channel은 각 출력(또는 입력) 채널마다 스케일 하나를 둔다. 채널마다 크기대가 다른 것이 보통이라, 채널별로 스케일을 따로 설정하는 것만으로도 정확도가 크게 오른다 — 가중치 양자화의 사실상 기본이다. group-wise는 한 채널을 다시 $g$개 원소씩 그룹으로 분할해 그룹마다 스케일을 둔다. INT4 가중치에서 흔히 $g=128$(또는 64·32)을 쓴다.
스케일 저장은 무비용이 아니고, 그래서 "4비트"가 정확히 4비트가 아니다. INT4 + 그룹 128 + FP16 스케일이면, 가중치 128개(=512비트)마다 스케일 16비트가 붙으므로 원소당 유효 비트가
$$4 + \frac{16}{128} = 4.125\ \text{비트}$$
가 된다. 그룹을 32로 줄이면 $4 + 16/32 = 4.5$비트로 오버헤드가 세 배 이상 커진다. 정밀도(그룹을 잘게)와 저장(오버헤드)이 여기서 정면으로 맞선다. "4비트인데 저장이 왜 절반보다 크지?"의 답이 이 부기다.
활성에는 축이 하나 더 있다. per-token — 토큰마다 스케일을 두는 것이다. 활성은 토큰별로 크기대가 달라서, 배치 안 여러 토큰을 하나의 정적 스케일로 묶으면 손실이다. 토큰 축으로 스케일을 나누면 가중치의 per-channel과 짝을 이뤄, 가중치 채널 × 활성 토큰의 격자로 둘 다 잘 맞는다. K 캐시가 채널 이상치를, V 캐시가 토큰 편차를 갖는 KV 양자화(5.2.12)에서 이 축 선택이 다시 나온다.
5.2.6 정수 vs 부동소수 — INT8/INT4 vs FP8
같은 8비트라도 정수와 부동소수는 격자의 모양이 다르고, 그 모양이 어떤 데이터에 잘 맞는지를 정한다.
INT8은 격자가 균등하다 — 등간격이다. 스케일 하나로 $[-\alpha,\alpha]$를 256칸으로 고르게 나눈다. 값들이 좁은 범위에 모여 있고 스케일만 잘 맞으면, 앞 절의 $s^2/12$가 말하듯 같은 비트에서 SQNR가 유리하다. FP8은 격자가 비균등하다. 부동소수라 0 근처는 조밀하고 멀수록 성기다 — 지수 비트가 스케일을 값마다 다르게 붙이는 셈이다. 이 덕에 넓은 동적 범위를 스케일 튜닝 없이 자체적으로 담는다.
그래서 큰 값과 작은 값이 섞인 활성(이상치)에서는 FP8이 스케일 튜닝 없이도 덜 붕괴하고, 좁은 범위에 잘 모인 가중치에서는 잘 맞춘 INT8이 SQNR에서 앞선다. FP8의 비트 배분에는 두 갈래가 있다. E4M3(지수 4·가수 3, 최대 약 448)은 가수가 하나 더 많아 정밀도가 낫고, E5M2(지수 5·가수 2, 최대 약 57344)는 범위가 더 넓다. 추론에서는 값 범위가 학습만큼 튀지 않아 대개 정밀도가 나은 E4M3를 가중치·활성에 쓴다 — 넓은 범위가 필요한 학습 그래디언트에 E5M2를 쓰는 것과 대비된다(2.6).
| 축 | INT8/INT4 (균등) | FP8 (비균등) |
|---|---|---|
| 격자 간격 | 등간격 | 0 근처 조밀, 멀수록 성김 |
| 동적 범위 | 스케일이 정함(좁음) | 지수 비트가 넓게 담음 |
| 이상치 견딤 | 약함(스케일이 끌려감) | 상대적으로 강함 |
| 좁은 범위 정밀도 | 잘 맞추면 유리 | 상대적으로 불리 |
| 하드웨어 | INT8 텐서코어 널리 지원 | 최신 세대 GPU에서 지원(세대 확인 필요) |
"FP8이 INT8보다 낫다/못하다"는 조건에 달렸다. 이상치가 심한 활성에는 FP8의 넓은 범위가, 잘 스케일된 가중치에는 INT8의 조밀한 격자가 유리하다. 그래서 활성은 FP8, 가중치는 INT 그룹 같은 혼합도 쓴다. 무엇을 쓸지는 하드웨어 지원이 크게 좌우한다 — FP8 GEMM을 실행할 수 없는 GPU면 FP8은 애초에 논외다.
5.2.7 수계산 — INT8 대칭 양자화, 그리고 이상치가 스케일을 파괴하는 법
한 가중치 벡터를 실제로 양자화·역양자화해 오차를 눈으로 보자. 수치는 예시다. 가중치 $w = [\,0.02,\ -1.53,\ 0.87,\ 2.10,\ -0.44\,]$를 INT8 대칭($q_{\max}=127$)으로 per-channel 양자화한다(이 벡터가 한 채널).
스케일부터. $\max|w| = 2.10$이므로
$$s = \frac{\max|w|}{127} = \frac{2.10}{127} \approx 0.016535$$
각 원소를 $q=\operatorname{round}(w/s)$, $\hat w = s\,q$로 보낸다.
| $w$ | $w/s$ | $q$ | $\hat{w}=s\,q$ | 오차 $w-\hat{w}$ |
|---|---|---|---|---|
| 0.02 | 1.21 | 1 | 0.0165 | +0.0035 |
| −1.53 | −92.53 | −93 | −1.5378 | +0.0078 |
| 0.87 | 52.61 | 53 | 0.8764 | −0.0064 |
| 2.10 | 127.00 | 127 | 2.1000 | 0.0000 |
| −0.44 | −26.61 | −27 | −0.4465 | +0.0065 |
모든 오차의 절댓값이 $s/2 \approx 0.00827$ 이하다 — 5.2.4의 경계 그대로다. 최댓값 2.10은 격자 끝점에 정확히 놓여 오차가 0이다. 이 벡터는 값들이 0.02~2.10으로 비슷한 크기대라, INT8이면 사실상 무손실에 가깝다. 여기까지는 양자화가 왜 대체로 안전한지를 보여 준다.
이제 이상치 하나를 넣어 무엇이 깨지는지 본다. 같은 벡터에 값 20.0을 하나 붙인다: $w' = [\,0.02,\ -1.53,\ 0.87,\ 2.10,\ -0.44,\ 20.0\,]$. 이제 $\max|w'|=20.0$이라
$$s' = \frac{20.0}{127} \approx 0.1575$$
작은 값 0.02를 양자화하면 $\operatorname{round}(0.02/0.1575)=\operatorname{round}(0.127)=0$, 역양자화하면 0이다. 작은 값 전체가 0으로 구분되지 않게 됐다. 0.87은 $\operatorname{round}(5.52)=6 \to \hat w = 0.945$로 오차가 아까의 열 배가 됐다. 이상치 하나가 스케일을 12배로 키워 나머지 다섯 값의 해상도를 파괴한 것이다. 5.2.4의 두 번째 수단이 여기서 역방향으로 작동한다 — $\max|x|$가 커지자 나머지 전부의 오차가 커졌다.
여기서 두 갈래의 대응이 자연스럽게 보인다. 이상치를 다른 값들과 다른 스케일로 격리하거나(per-channel·group으로 잘게 분할해 이상치가 자기 그룹 안에만 갇히게), 아예 활성 이상치를 수학적으로 옮기거나 보호하는 것(5.2.9)이다. 그룹 크기를 128로 두면 이상치 하나는 자기가 속한 128개 그룹의 스케일만 키우고 나머지 그룹은 변경하지 않는다 — 피해가 국소화된다.
5.2.8 무엇을 양자화하나 — weight-only vs W8A8
무엇을 정수로 바꾸느냐로 방법이 크게 둘로 갈린다. 표기 관행은 W(가중치)비트 A(활성)비트다.
가중치만 양자화하는 쪽(W4A16·W8A16)은 가중치를 INT4/INT8로 저장하되 활성은 FP16/BF16 그대로 둔다. GEMM 직전에 가중치를 FP16으로 역양자화해 FP16으로 곱한다. 줄이는 것은 대역폭과 메모리 — decode를 빠르게 한다(5.2.1). 계산 자체는 여전히 FP16이다. GPTQ·AWQ·k-quant·NF4가 여기 속한다. 가중치+활성을 양자화하는 쪽(W8A8)은 둘 다 INT8로 바꿔 정수 텐서 코어로 정수 GEMM을 실행한다 — 대역폭뿐 아니라 계산까지 줄여 prefill·큰 배치(계산 바운드)에서 이득을 낸다. SmoothQuant·LLM.int8()이 여기 속한다.
이 둘의 선택은 취향이 아니라 병목 진단의 결과다. 소배치·decode 지연이 문제면 가중치만 양자화해도 충분하다 — 어차피 병목이 대역폭이라 계산을 줄여 봐야 소용없고, 역양자 오버헤드만 추가된다. prefill·큰 배치 처리량이 문제면 계산까지 줄여야 하니 W8A8나 FP8로 가야 한다. 그런데 W8A8은 무비용이 아니다. 활성을 정수로 바꾸는 순간 5.2.7에서 본 이상치 문제가 정면으로 발생한다. 그것이 다음 절이다.
5.2.9 왜 활성 양자화가 어렵나 — 이상치와 세 가지 대응
가중치는 학습이 끝나 고정돼 있고 대체로 0 중심의 좁은 분포라 양자화가 비교적 쉽다. 활성은 근본적으로 다르다. 큰 트랜스포머에서(경험적으로 수 B 파라미터를 넘어서면서) 특정 소수의 은닉 차원(채널)에 체계적으로 아주 큰 값이 나타난다는 관찰이 있다. 이 값들은 나머지의 수십~수백 배라, 활성을 per-tensor로 양자화하면 5.2.7의 이상치 실험이 매 층에서 벌어진다 — 스케일이 이상치에 끌려가 나머지 대다수 활성이 몇 칸으로 구분되지 않는다. 그래서 큰 모델에서 활성 INT8을 순진하게 하면 품질이 급락한다. 작은 모델에서는 이 현상이 약해 순진한 W8A8도 그럭저럭 되는데, 정작 압축이 절실한 큰 모델에서 문제가 심해진다는 것이 역설적이다.
이 이상치를 다루는 방식이 셋 있고, 이 셋이 뒤에 나올 방법들의 골격이다. 격리 — 이상치가 있는 소수 채널만 FP16으로 따로 계산하고 나머지를 INT8로 처리한다(LLM.int8()). 정확하지만 혼합 경로라 커널이 복잡하고 느려질 수 있다. 이동 — 활성의 이상치를 수학적으로 등가인 변형으로 가중치 쪽에 옮겨, 활성·가중치 둘 다 양자화하기 쉬운 상태로 만든다(SmoothQuant). 보호 — 이상치 활성이 지나가는 가중치 채널이 출력에 크게 기여한다고 보고, 그 채널을 스케일로 보호해 저비트에서도 살린다(AWQ, weight-only). 격리는 정확도를 지키되 속도를 희생하고, 이동은 W8A8의 정수 GEMM 이득을 여는 대신 등가 변형의 균형점을 잘 맞춰야 하며, 보호는 활성을 변경하지 않는 대신 활성 이상치 자체는 남겨 둔다. 무엇이 나은지는 목표가 대역폭이냐 계산이냐, 비트가 얼마냐에 달렸다.
5.2.10 언제 양자화하나 — PTQ vs QAT
양자화를 학습이 끝난 뒤에 추가하느냐, 학습 중에 미리 적응시키느냐로 또 한 번 갈린다.
PTQ(Post-Training Quantization)는 이미 학습된 모델에 사후로 양자화한다. 재학습 없이 보정 데이터(calibration) 수백~수천 샘플만으로 스케일과, 일부 방법은 가중치 보정까지 계산한다. 저렴하고 빠르며, LLM 배포의 사실상 표준이다 — 뒤에 나올 GPTQ·AWQ·SmoothQuant·k-quant가 전부 PTQ다.
QAT(Quantization-Aware Training)는 학습(또는 파인튜닝) 중에 양자화를 흉내 낸다. 순전파에 반올림을 삽입해(fake quantization) 모델이 저정밀도의 오차를 미리 겪게 하는데, 반올림은 미분이 불가능하므로 역전파에서는 STE(straight-through estimator) — 반올림의 도함수를 단순히 1로 취급해 그래디언트를 통과시키는 트릭 — 를 쓴다. 모델이 저정밀도에 적응해 같은 비트에서 정확도가 더 높을 수 있다. 대신 학습 비용·데이터·파이프라인이 든다.
수십~수백 B 규모 LLM을 재학습하는 비용이 크기 때문에, 현재 LLM 추론 양자화의 주류는 PTQ다. QAT는 매우 낮은 비트(2~3비트)나 엣지·온디바이스처럼 PTQ로 품질이 안 나올 때, 또는 어차피 파인튜닝을 할 때 고려한다. QLoRA(5.2.14)는 이 둘의 중간지대에 가깝다 — 4비트로 동결한 베이스는 PTQ된 상태이고 학습은 고정밀도 어댑터에서만 일어난다. 무엇이 나은지는 재학습 예산·목표 비트·품질 요구가 정한다.
5.2.11 방법들 — GPTQ·AWQ·SmoothQuant·k-quant·bitsandbytes
이제 실제로 쓰이는 방법들을 아이디어 중심으로 본다. 모두 PTQ이며, 이상치·오차를 다루는 전략이 다르다.
5.2.11.1 GPTQ — 2차 근사로 오차를 보정하며 양자화
핵심은 이렇다. 가중치를 한 열(또는 원소)씩 양자화하되, 양자화로 생긴 오차를 아직 양자화하지 않은 나머지 가중치로 보상한다. "이 가중치를 반올림했더니 층 출력이 틀어졌으니, 남은 가중치들을 조금 바꿔 그 틀어짐을 상쇄"하는 식이다. 이것을 정확히 하려면 층 출력 오차의 곡률, 즉 2차 정보(헤시안)가 필요하다. 한 층의 재구성 목표를 쓰면
$$\min_{\hat{W}}\ \big\lVert W X - \hat{W} X \big\rVert_2^2$$
인데(입력 $X$는 보정 데이터), 이는 각 행에 대해 분리되고 그 헤시안은 $H = 2XX^\top$로 나온다. Optimal Brain Surgeon 계보의 결과를 쓰면, 한 가중치 $w_q$를 양자화값으로 고정할 때 남은 가중치들의 최적 보정과 그로 인한 오차 증가가 헤시안 역행렬로 닫힌 형태로 나온다.
$$\delta = -\frac{w_q - \operatorname{quant}(w_q)}{[H^{-1}]_{qq}}\, H^{-1}_{:,q}, \qquad \Delta E = \frac{\big(w_q - \operatorname{quant}(w_q)\big)^2}{[H^{-1}]_{qq}}$$
GPTQ의 기여는 이 고전 알고리즘을 수십억 파라미터로 확장한 데 있다. 원소별로 오차가 최소인 것을 매번 고르는 대신 열을 왼쪽에서 오른쪽으로 고정된 순서로 처리해도 큰 모델에서는 품질이 비슷하다는 것, 그리고 헤시안 역행렬 갱신을 블록 단위 Cholesky 분해로 수치적으로 안정·고속화한 것이다. 성격은 weight-only(주로 INT4·INT3), per-channel + group이고, 헤시안을 다뤄 보정에 계산·메모리가 든다. 오차 보정 덕에 낮은 비트에서 정확도가 좋은 편이지만 보정 데이터에 민감하고, 큰 모델은 보정 시간이 길 수 있다. 활성은 변경하지 않으므로 활성 이상치 자체는 해결하지 않는다.
5.2.11.2 AWQ — 활성을 봐서 중요한 가중치를 보호
Activation-aware Weight Quantization의 관찰은 "모든 가중치가 똑같이 중요하진 않다"이다. 큰 활성이 지나가는 채널의 가중치가 출력에 더 크게 기여하므로, 그 중요(salient) 채널을 저비트 양자화로부터 보호한다. 방법은 그 채널의 가중치를 양자화 전에 per-channel 스케일 $s>1$로 키우고 대응 활성은 그만큼 나눠 곱을 보존하는 것이다. 가중치를 키우면 그 값이 격자 위에서 상대적으로 커져 반올림 상대오차가 줄어든다 — 큰 값일수록 $s/2$ 오차가 값에 비해 작으니까. 스케일을 헤시안 역행렬 없이 활성 크기 통계만으로 정하므로 절차가 GPTQ보다 경량이다.
성격은 weight-only(주로 INT4), per-channel/group이다. 역전파·헤시안이 불필요해 빠르고 구현이 단순하며, 보정 데이터에 덜 민감하다는 보고가 있다. GPTQ의 명시적 오차 보정은 없지만 "중요 채널 보호"라는 다른 축으로 품질을 지킨다. AWQ와 GPTQ의 우열은 모델·태스크·목표 비트에 따라 갈려 어느 쪽이 항상 낫다고 명시할 수 없다 — 둘 다 시도해 자기 태스크로 비교하는 것이 정직한 절차다.
5.2.11.3 SmoothQuant — 이상치를 활성에서 가중치로 이동
W8A8을 가능케 하려면 활성 이상치를 없애야 한다. SmoothQuant는 수학적으로 등가인 변형으로 활성의 크기를 가중치로 옮긴다. 채널별 평활 계수 $\operatorname{diag}(\mathbf{s})$를 사이에 삽입하면 곱이 보존된다.
$$Y = X W = \big(X\,\operatorname{diag}(\mathbf{s})^{-1}\big)\big(\operatorname{diag}(\mathbf{s})\,W\big) = \hat{X}\,\hat{W}$$
활성 $\hat X$는 이상치 채널을 $s_j$로 나눠 완만해지고, 그만큼 가중치 $\hat W$가 조금 험해진다. 양자화 난이도를 활성과 가중치가 나눠 지도록 평활 강도를 정하는데,
$$s_j = \frac{\big(\max|X_j|\big)^{\alpha}}{\big(\max|W_j|\big)^{1-\alpha}}$$
로 두고 $\alpha$(예: 0.5 근처)로 균형점을 찾는다. $\alpha$를 키우면 활성을 더 억제해 활성 양자화는 쉬워지지만 가중치가 험해지고, 줄이면 반대다. 성격은 활성 이상치 자체를 완화해 그 뒤에 W8A8을 추가하기 쉽게 만드는 전처리라, 종종 다른 양자화와 결합된다. 활성을 정수화해 계산 바운드(prefill·큰 배치)에서 속도 이득을 여는 것이 강점이고, $\alpha$·채널 통계 선택이 품질을 좌우한다.
5.2.11.4 GGUF / llama.cpp k-quant — CPU·소비자 GPU를 위한 블록 양자화
llama.cpp의 파일 포맷이 GGUF이고, 그 안의 대표 양자화가 k-quant(Q2_K·Q3_K·Q4_K·Q5_K·Q6_K 등)다. 가중치를 작은 블록(super-block과 그 안의 sub-block)으로 나눠 블록마다 스케일과 최소값을 두고, 가중치 종류·층별로 비트를 다르게 배분한다 — 어텐션이나 중요한 텐서에는 더 많은 비트를 준다. Q4_K_M 같은 접미어(S/M/L)가 이 혼합 배분의 프리셋이다. 최근에는 중요도 행렬(importance matrix)로 비트를 배분하는 I-quant(IQ2·IQ3 등)도 있다. 성격은 weight-only, 블록 단위 혼합 비트이며 CPU·소비자 GPU·로컬 실행(맥·PC)에 최적화돼 접근성이 큰 강점이다. 서버급 GPU 텐서 코어 활용을 극대화하는 vLLM/TensorRT 경로(5.4)와는 지향이 달라, 같은 "4비트"라도 방식이 달라 정확도·속도가 직접 비교되지 않는다.
5.2.11.5 bitsandbytes — LLM.int8()과 NF4
두 가지를 제공한다. 하나는 LLM.int8() — W8A8 계열이되 이상치 채널만 FP16으로 분리하고 나머지를 INT8로 처리하는 혼합 정밀도 분해(5.2.9의 격리)다. 다른 하나는 4비트 NF4(NormalFloat4)로, 가중치가 대략 정규분포라는 가정에서 정보이론적으로 최적화된 4비트 격자다 — 등간격 INT4와 달리 정규분포 분위수에 맞춰 격자점을 배치해 같은 4비트로 더 잘 맞춘다. 여기에 스케일 상수까지 다시 양자화하는 이중 양자화(double quantization)로 오버헤드를 더 줄인다.
이중 양자화가 유효 비트를 얼마나 아끼는지 직접 보자. NF4를 블록 64로 두고 블록마다 FP32 스케일을 붙이면 유효 비트가 $4 + 32/64 = 4.5$비트다. 이 스케일들을 다시 블록 256으로 묶어 8비트로 양자화하면(그 8비트 블록에 FP32 상수 하나) 유효 비트가
$$4 + \frac{8}{64} + \frac{32}{64 \times 256} \approx 4.127\ \text{비트}$$
로 내려온다 — 4.5에서 4.13으로, 파라미터 10억 개당 약 47 MB를 아끼는 셈이다. bitsandbytes는 Hugging Face 생태계에 깊이 통합돼 파인튜닝(QLoRA)·간편 로드에 강하다. LLM.int8()은 정확도 보존이 좋은 편이나 이상치 분리 경로 때문에 처리량이 최고는 아닐 수 있고, 최대 서빙 처리량이 목표면 GPTQ/AWQ/FP8 전용 커널이 더 빠를 수 있다.
5.2.11.6 한눈에 비교
| 방법 | 대상 | 전형 비트 | 이상치 전략 | 강점 | 유의 |
|---|---|---|---|---|---|
| GPTQ | weight-only | INT4/INT3 | (활성 미처리) | 2차 오차보정으로 저비트 품질 | 보정 계산·데이터 민감 |
| AWQ | weight-only | INT4 | 중요 채널 보호 | 부하가 작고 빠름, 보정 덜 민감 | 명시적 오차보정 없음 |
| SmoothQuant | W8A8 전처리 | INT8 | 활성→가중치 이동 | 계산 바운드 속도(정수 GEMM) | $\alpha$·통계 선택 민감 |
| LLM.int8() | W8A8 | INT8 | 이상치 채널 FP16 분리 | 정확도 보존, 간편 로드 | 혼합 경로로 처리량 제약 |
| NF4 (bnb) | weight-only | 4비트 | 분포 최적 격자 | QLoRA·파인튜닝 결합 | 서빙 처리량은 전용 커널만 못할 수 |
| k-quant (GGUF) | weight-only | 2~6비트 혼합 | 블록·종류별 비트 배분 | CPU·로컬·엣지 접근성 | 서버 GPU 최적 경로와 지향 다름 |
이 표의 강점·유의는 경향이다. 같은 방법도 구현·커널·버전에 따라 성능이 달라진다 — 예컨대 INT4 GEMM용 Marlin류 전용 커널이 있느냐 없느냐로 실측 속도가 갈리고, 정확도는 모델·보정·태스크로 갈린다. 벤치마크는 반드시 자기 모델·자기 태스크·자기 하드웨어로 다시 재는 것이 맞고, 논문·리드미의 숫자를 그대로 옮기는 것이 이 분야에서 가장 흔한 실수다.
5.2.12 KV 캐시 양자화
5.1에서 봤듯, 긴 컨텍스트·큰 배치에서는 KV 캐시가 가중치보다 더 큰 메모리·대역폭 압박이 된다. 캐시는 시퀀스 길이·배치·레이어·헤드에 비례해 자라고, decode마다 그 전체를 다시 읽어야 하기 때문이다. 그래서 가중치와 별개로 K·V를 8/4비트로 양자화한다. KV 바이트는
$$\text{KV 바이트} \;\propto\; 2 \times L \times n_{\text{layer}} \times n_{\text{kv-head}} \times d_{\text{head}} \times B \times (\text{bytes})$$
이고, bytes를 2(FP16)에서 1(INT8)·0.5(INT4)로 줄이면 KV 용량과 decode 대역폭이 그만큼 준다 — 더 긴 컨텍스트·더 큰 배치를 같은 GPU에서 처리한다. GQA/MQA(1.6)로 KV 헤드를 줄이는 것과 직교적으로 곱해지는 절감이다. 5.1의 8B MHA 예(컨텍스트 128K에 KV 64 GB)를 INT8로 내리면 KV가 32 GB로 반이 되고, 그만큼 스텝당 읽는 바이트가 줄어 TPOT가 회복된다.
문제는 K가 V보다 어렵다는 것이다. 둘은 성격이 다르다. K(키)는 RoPE(1.5) 등을 거쳐 채널 방향 이상치가 생기는 경향이라, per-token만으로는 거칠고 per-channel 스케일이 더 맞는다 — 그런데 어텐션 계산 구조상 per-channel K가 커널에 까다롭다. V(값)는 상대적으로 얌전해 per-token 양자화로도 잘 견딘다. 5.2.5의 축 선택이 여기서 그대로 나온다 — K는 채널 이상치라 채널 축, V는 토큰 편차라 토큰 축이다. 그래서 실무는 K는 더 조심(더 높은 비트 또는 채널별 스케일), V는 더 공격적으로 가는 비대칭 전략을 쓰기도 한다.
또 하나. KV 4비트는 긴 컨텍스트·정밀 태스크에서 품질 저하가 누적될 수 있다. 짧은 대화엔 무해해도 긴 문서 QA·다단계 추론에서는 티가 난다 — 작은 오차가 어텐션의 긴 사슬을 따라 증폭되기 때문이다(5.2.13). 그리고 KV 양자화는 PagedAttention·연속 배칭 같은 캐시 관리(5.3)와 결합해야 실이득이 난다. 압축했어도 캐시 단편화나 복사 오버헤드가 크면 상쇄되기 때문이다.
5.2.13 정확도 vs 압축 — 어디서 붕괴하나
양자화는 무비용이 아니다. 비트를 낮출수록 품질이 저하되는데, 중요한 건 선형으로 깎이지 않고 어느 지점에서 급락한다는 것이다. 5.2.4에서 SQNR가 비트당 6 dB로 떨어진다고 했으니 손실이 비트에 부드럽게 비례할 것 같지만, 모델의 최종 품질은 SQNR가 아니라 오차가 어텐션·MLP를 몇십 층 통과하며 어떻게 전파·증폭되는지가 정한다. 그래서 실제로는 4비트 근처까지 완만하다가 그 아래에서 절벽이 나온다.
| 대략 비트 | 전형적 품질 영향(경향) | 비고 |
|---|---|---|
| W8 (INT8 weight-only) | 거의 무손실에 가까움 | 안전한 기본 출발점 |
| W4 (GPTQ/AWQ, 그룹) | 작은 저하(대개 실용 허용) | 오늘날 LLM 서빙의 흔한 스윗스팟 |
| W3 | 눈에 띄는 저하, 방법·모델 의존 | 정교한 방법·QAT 필요할 수 있음 |
| W2 이하 | 급격한 저하, 특수 기법 없이 위험 | 활발한 연구 영역 |
"4비트 근처가 현재의 실용 경계"라는 것이 대체적 경험이다. 3비트 이하로 내려가면 방법 선택·보정·태스크에 따라 품질이 크게 변동해 무손실을 기대하기 어렵다. "비트를 반으로 줄였는데 점수는 조금밖에 안 떨어졌다"가 4비트까지의 흔한 관찰이지만, 그 아래는 절벽이다. 품질을 좌우하는 변수는 넷이다.
모델 크기 — 대체로 큰 모델이 양자화에 더 강건하다. 여유 표현력이 오차를 흡수하기 때문인데, 같은 4비트라도 작은 모델(수 B 이하)이 더 크게 다칠 수 있다. "작은 모델을 극단 양자화"가 가장 위험한 조합이다. 보정 데이터 — PTQ 방법 다수가 스케일·오차보정을 소량의 보정 샘플로 계산하는데, 이 데이터가 배포 분포와 동떨어지면 스케일이 부적절해져 실사용 품질이 나쁘다. 반대로 특정 셋에 과하게 맞추면 보정 과적합이다. 보정 데이터는 실제 서빙 입력을 대표해야 한다(도메인·언어·형식). 태스크 민감도 — 추론·수학·코드·긴 컨텍스트처럼 오차가 누적·전파되는 태스크가 더 민감하다. 짧은 잡담·분류에서는 티가 안 나도 다단계 추론에서는 작은 오차가 사슬을 따라 증폭된다. 평가 함정 — perplexity가 조금만 올랐다고 안심하면 안 된다. perplexity는 완만해도 형식 준수·긴 추론·드문 지식 같은 특정 능력이 붕괴할 수 있다. 생성 품질·태스크 정확도·안전성(4.3)까지 목표 태스크로 봐야 한다(4.1).
"양자화하면 무조건 성능이 크게 떨어진다"도, "4비트는 공짜 점심이다"도 둘 다 틀리다. INT8과 잘 된 W4는 많은 경우 실용상 거의 무해하지만, 모델이 작거나·비트가 3 이하거나·태스크가 민감하면 실질적 손실이 난다. 답은 늘 하나다 — 내 모델·내 태스크·내 하드웨어로 재 보라. 일반 벤치 점수 한두 개로 "무손실"을 선언하는 것이 이 분야의 가장 값비싼 착각이다.
5.2.14 QLoRA와 서빙 적용
이 장은 추론용 양자화지만, QLoRA(3.8)는 양자화를 파인튜닝에 쓰는 대표 사례라 한 번 확인하고 넘어가는 편이 낫다. 아이디어는 베이스 모델을 4비트(NF4)로 동결해 메모리에 적재하고, 그 위에 LoRA 어댑터를 고정밀(BF16 등)으로 학습하는 것이다. 순전파 때만 4비트 가중치를 역양자화해 쓰고, 그래디언트는 어댑터에만 흐른다.
이중 양자화(5.2.11.5)로 스케일 상수까지 줄이고 페이지드 옵티마이저로 메모리 스파이크를 흡수하면, 단일 GPU에서 큰 모델을 파인튜닝할 수 있다 — 4비트 베이스로 메모리를 줄이고, 학습되는 파라미터는 어댑터뿐이라 옵티마이저 상태도 작다. 여기 쓰인 NF4·이중 양자화·역양자화는 이 장에서 배운 그 양자화다. 차이는 목적이다 — "배포용 압축"이 아니라 "학습을 단일 GPU에 앉히기 위한 압축"이다. 상세는 3.8.
양자화된 모델을 실제로 운용하는 것은 서빙 프레임워크다(5.4). 실무 관점으로 네 가지를 정리하면 이렇다. 포맷과 엔진의 궁합 — vLLM·TGI·TensorRT-LLM·SGLang이 지원하는 양자화 포맷이 제각각이라(GPTQ·AWQ·FP8·INT8·k-quant 등), "이 엔진이 이 포맷을, 이 GPU에서, 빠른 커널로 지원하나"를 먼저 확인해야 한다. 지원해도 느린 폴백 커널이면 이득이 없다. 커널이 이득을 정한다 — 같은 W4라도 전용 저비트 GEMM 커널(예: Marlin류)이 있어야 decode가 실제로 빨라진다. 커널이 없으면 역양자 오버헤드로 오히려 느릴 수 있다(5.2.1). 병목으로 무엇을 줄일지 정하라 — decode·소배치 지연이면 가중치만 양자화(대역폭), prefill·큰 배치 처리량이면 W8A8/FP8(계산), 긴 컨텍스트 메모리면 KV 양자화를 겹친다. 마지막으로, 양자화는 다른 최적화와 곱해진다 — 연속 배칭·PagedAttention·speculative decoding(5.3)과 직교적으로 함께 이득을 낸다.
의사결정을 강요할 생각은 없지만, 어디서부터 시도할지의 순서는 대체로 일관된다. 품질이 최우선이고 메모리 여유가 있으면 INT8 weight-only(W8A16)부터 — 거의 무손실인 안전한 기본이다. 메모리·decode 지연이 급하고 품질을 조금 양보할 수 있으면 W4(GPTQ 또는 AWQ) + 그룹으로 — 오늘날 흔한 스윗스팟이니 둘 다 시도해 자기 태스크로 비교한다. prefill·큰 배치 처리량이 병목이면 W8A8(SmoothQuant)나 FP8로 계산까지 줄이되 하드웨어 지원을 확인한다. 긴 컨텍스트 메모리 압박이면 위에 KV 양자화를 겹치되 K를 조심한다. 단일 GPU에서 파인튜닝까지 하려면 QLoRA(NF4), 로컬·엣지·CPU면 GGUF k-quant다. 어느 경우든 마지막 판단은 자기 하드웨어의 실측이 내린다 — 이론은 어디를 볼지 알려 주고, 프로파일링이 값을 준다.
다음 장은 유휴 상태인 decode GPU를 연속 배칭으로 채우고, KV 단편화를 PagedAttention으로 없애며, 대역폭 장벽을 speculative decoding으로 우회하는 기법들로 간다(5.3). 양자화가 "읽을 것을 줄이는" 축이라면 그쪽은 "같은 하드웨어에 더 많은 일을 겹치는" 축이고, 둘은 곱해져 서빙 효율을 만든다. 서빙 프레임워크가 이 모두를 어떻게 묶는지는 5.4이다.