WarpQuant: 하다마르 회전과 Output-Fisher 기반의 듀얼 도메인 LLM 양자화
이 글에서는 LLM 추론의 세 가지 메모리 경로인 가중치, KV Cache, Activation을 어떻게 함께 줄였는지 설명합니다. 가중치는 Hadamard 회전과 block-GPTQ로 압축하고, Output-Fisher가 민감하다고 판단한 column만 고정밀도로 복원했습니다. KV Cache에는 TurboQuant 기반의 저비트 저장 방식을, Activation에는 token별 INT8 양자화를 적용했습니다.
들어가며: 왜 세 가지 메모리를 따로 줄여야 하는가
LLM이 token 하나를 생성할 때마다 전체 가중치를 다시 읽습니다. KV Cache는 대화가 길어질수록 계속 쌓이고, Activation은 각 linear 연산을 통과할 때마다 새로 만들어집니다. 세 데이터는 크기가 커지는 방식도, 값의 분포도, 메모리에 머무는 시간도 다릅니다. WarpQuant는 이 차이를 무시하고 하나의 비트폭을 적용하는 대신, 각 경로에 맞는 양자화 방식을 사용합니다.
회전 영역에서 압축하고, 원본 영역에서 복원합니다
먼저 가중치 행렬 W에 결정론적으로 만든 부호 벡터 D와 Hadamard 행렬 H를 곱해 R=HD를 구성합니다. 이 회전은 일부 channel에 몰린 큰 값을 전체 차원으로 고르게 펼칩니다. 회전된 가중치는 128개 column 단위로 묶어 Gaussian Lloyd-Max 3-bit codebook과 FP16 scale로 양자화하고, block-GPTQ가 앞쪽 group에서 생긴 오차를 뒤쪽 group에 전달해 보정합니다. [1][4][5]
- 1:
Construct the signed Hadamard rotation R ← HD
- 2:
Wr ← WRT, Xr ← XRT
- 3:
(Ŵr, codes, scales) ← BlockGPTQQ3,G128(Wr, Xr)
- 4:
W̃ ← ŴrR, E ← W − W̃
- 5:
sc ← HX,ccE:,cTdiag(HG)E:,c / (16dout + 32)
- 6:
Select columns C by descending sc under the global budget B
- 7:
return codes, scales, C, E:,C
R16E4H4라는 이름은 실제 저장 형식을 뜻합니다. R16은 선택된 weak column을 BF16으로 복원하고, E4와 H4는 token embedding과 lm_head를 group size 128의 INT4로 저장합니다. Projection layer만 3.55 bpw로 줄이는 데서 끝나지 않고, embedding과 head까지 포함한 전체 텍스트 모델의 용량을 3.6 bpw대로 맞추기 위한 구성입니다.
어떤 Column을 복원할 것인가
양자화 오차가 크다고 해서 반드시 모델 성능에 중요한 column은 아닙니다. 실제 입력에서 거의 사용되지 않는 column이라면 오차가 커도 최종 출력에는 영향이 작을 수 있습니다. 그래서 입력 Activation의 에너지 H_X, 양자화 잔차 E, 출력 NLL gradient의 제곱 평균 H_G를 함께 사용해 각 column의 오차가 next-token loss에 얼마나 크게 전달되는지 계산합니다. [8]
여기서 W_base는 회전 영역에서 block-GPTQ를 적용한 뒤 원본 좌표계로 되돌린 가중치입니다. 실제 구현에서는 아래 PyTorch 함수로 column별 점수를 계산합니다.
import torch
def output_fisher_score(W, W_base, X, H_G, index_bits=32):
assert W.ndim == W_base.ndim == 2
assert X.shape[-1] == W.shape[1]
assert H_G.shape == (W.shape[0],)
E = W.float() - W_base.float()
H_X = X.float().square().mean(dim=0)
loss_weighted_error = (
H_G.float()[:, None] * E.square()
).sum(dim=0)
cost_bits = 16 * W.shape[0] + index_bits
return H_X * loss_weighted_error / cost_bits 이 방식은 모든 출력 방향을 같은 중요도로 보지 않습니다. 실제 loss에 더 민감한 v_proj와 down_proj에 복원 예산이 자연스럽게 더 배분됩니다. Llama 3 8B에서는 같은 용량에서 기존 선택 방식의 WikiText-2 PPL 7.3953을 7.3446으로 낮췄습니다.
Qwen3.8-27B에서의 결과
모든 비트율과 용량은 vision tower와 MTP를 제외한 26,895,998,464개의 텍스트 생성 파라미터를 기준으로 계산했습니다. Commonsense 점수는 HellaSwag, WinoGrande, PIQA에서 각각 같은 1,000문항을 평가한 뒤 평균을 낸 값입니다.
| Format | Text bpw | Payload | WT2 PPL ↓ | ARC-299 ↑ | MMLU-13,943 ↑ | Commonsense ↑ | GSM8K-500 flex ↑ |
|---|---|---|---|---|---|---|---|
| BF16 | 16.00 | 50.11 GiB | 6.9548 | 52.17 | 43.07 | 79.23 | 70.40 |
| Q4_K_M | 4.92 | 15.41 GiB | 6.9656 | 50.84 | 42.90 | 79.23 | 75.20 |
| IQ3_S | 3.6940 | 11.57 GiB | 7.1820 | 52.17 | 42.97 | 78.83 | 59.40 |
| WarpQuant R16E4H4 | 3.6165 | 11.32 GiB | 7.4737 | 56.86 | 42.72 | 78.83 | 61.00 |
PPL, ARC, MMLU는 같은 llama.cpp 평가 경로에서 측정했습니다. GSM8K는 네 모델에 동일한 첫 500문항과 5-shot 설정을 적용했으며, lm-evaluation-harness의 flexible-extract 정확도를 표시했습니다.
다른 모델에서도 같은 방식을 적용했습니다
Qwen3.5-4B에서 WarpQuant는 IQ3_M보다 243.6 MB 작으면서 PPL, ARC, MMLU가 모두 높았습니다. Llama 3 8B에서는 IQ3_S보다 43.0 MB 작은 모델에서 ARC는 높았고, PPL과 MMLU는 IQ3_S가 높았습니다.
| Format | Text bpw | Payload | WT2 PPL ↓ | ARC-299 ↑ | MMLU ↑ |
|---|---|---|---|---|---|
| BF16 | 16.00 | 7.846 GiB | 8.3885 | 45.82 | 39.58 |
| Q4_K_M | 5.13 | 2.523 GiB | 8.5472 | 48.83 | 39.48 |
| IQ3_M | 4.09 | 2.015 GiB | 10.6976 | 42.81 | 37.41 |
| WarpQuant Fisher R16E4 | 3.6514 | 1.788 GiB | 9.2494 | 46.15 | 38.13 |
| Format | Text bpw | Payload | WT2 PPL ↓ | ARC-299 ↑ | MMLU ↑ |
|---|---|---|---|---|---|
| BF16 | 16.00 | 14.965 GiB | 6.2559 | 50.50 | 41.04 |
| Q4_K_M | 4.89 | 4.583 GiB | 6.4359 | 50.84 | 40.67 |
| IQ3_S + imatrix | 3.66 | 3.429 GiB | 6.9929 | 44.15 | 39.87 |
| WarpQuant Fisher R16E4H4 | 3.6256 | 3.389 GiB | 7.3446 | 45.49 | 38.99 |
KV Cache와 Activation 양자화
Qwen3.8-27B에는 16개의 full-attention layer가 있습니다. 이 layer에서는 가장 최근의 128개 token을 BF16으로 유지하고, 그보다 오래된 key는 random rotation 뒤 3-bit MSE code와 1-bit QJL residual로 저장합니다. Value는 token별 group-32 INT4로 저장합니다. 새 token은 높은 정밀도로 처리하면서, 길어진 context의 대부분은 저비트로 보관하는 구조입니다. [9][10][11][12]
Activation은 496개 decoder linear module의 입력마다 token별 절댓값 최댓값을 구해 INT8로 변환합니다. 아래 표는 같은 Qwen3.8-27B R16E4H4 checkpoint와 4,088개의 WikiText-2 validation token에서 가중치만 양자화한 경우, KV Cache를 추가한 경우, A8을 추가한 경우, 두 방식을 함께 적용한 경우를 비교합니다. [13][14]
| 구성 | PPL ↓ | Δ PPL | Top-1 | KV @ 512 |
|---|---|---|---|---|
| WarpQuant weight-only | 6.6468 | — | reference | 1.00× |
| + K4/V4/R128 | 6.6495 | +0.0027 | 97.65% | 2.14× |
| + Dynamic A8 | 6.7139 | +0.0671 | 92.10% | 1.00× |
| + K4/V4/R128 + A8 | 6.6945 | +0.0477 | 92.47% | 2.14× |
평가 방법
GGUF 대조군은 실제 파일에서 텍스트 모델이 차지하는 비트율과 용량을 사용했습니다. WarpQuant의 용량은 3-bit code, FP16 scale, weak-column index와 residual, embedding과 head의 group scale을 모두 합산해 계산했습니다. 평가는 같은 양자화 값을 사용하는 checkpoint로 진행했습니다.
어떤 연구를 바탕으로 만들었는가
References
[3]Sehoon Kim et al. “SqueezeLLM: Dense-and-Sparse Quantization.” ICML, 2024.
[11]Zirui Liu et al. “KIVI: A Tuning-Free Asymmetric 2bit Quantization for KV Cache.” ICML, 2024.
Citation
@misc{choi2026warpquant,
author = {Harim Choi},
title = {WarpQuant: Dual-Domain LLM Quantization via Hadamard Rotation and Output-Fisher Sensitivity},
year = {2026},
url = {https://harimxchoi.github.io/projects/warpquant}
}