본문으로 이동
메뉴 여닫기
환경 설정 메뉴 여닫기
개인 메뉴 여닫기
로그인하지 않음
지금 편집한다면 당신의 IP 주소가 공개될 수 있습니다.
GPTQ
2차 정보(헤세 행렬)를 이용해 양자화 오차를 남은 가중치에 보정해 가며 한 열씩 양자화하는, 대형 언어 모델용 학습 후 가중치 양자화 기법

GPTQ는 2022년 10월 오스트리아 과학기술원(IST Austria)과 ETH 취리히 연구진(Elias Frantar, Saleh Ashkboos, Torsten Hoefler, Dan Alistarh)이 발표한 학습 후 양자화(PTQ) 기법이다. 재학습 없이 소량의 보정 데이터만으로 GPT류 모델의 가중치를 3~4비트로 줄인다. 1,750억 매개변수 모델을 A100 GPU 한 장으로 약 4시간에 3~4비트로 양자화하면서 퍼플렉시티 손실을 작게 유지했고, 1,750억 매개변수 모델을 GPU 한 장에서 생성 추론까지 돌린 첫 사례가 되었다. ICLR 2023에 실렸다.[1]

활성값은 FP16 그대로 두고 가중치만 낮은 비트로 저장하는 가중치 전용(weight-only, W4A16 등) 방식이다. 같은 가중치 전용 계열인 AWQ와 함께 vLLM, 허깅 페이스 Transformers 등 주요 추론 도구가 GPTQ 체크포인트를 읽는다. 양자화 일반은 신경망 양자화, 활성값까지 8비트로 줄이는 방식은 SmoothQuant 문서 참고.

GPTQ는 층 하나씩 따로 푼다. 선형 층의 가중치를 W(행 drow, 열 dcol), 보정 데이터 m개를 통과시켜 얻은 그 층의 입력을 X라 하면, 양자화 격자 위의 W^ 중 출력 오차를 최소로 하는 것을 찾는다.[1]

argminW^‖WX−W^X‖22

가장 단순한 반올림(RTN, round-to-nearest)은 가중치마다 가장 가까운 격자점으로 옮길 뿐 입력의 통계를 보지 않는다. GPTQ는 이 목적 함수가 이차식이라는 점을 이용해, 한 가중치를 반올림해서 생긴 오차를 아직 양자화하지 않은 다른 가중치를 조정해 상쇄한다.

OBQ에서 GPTQ로

편집 원본 편집

OBQ(Optimal Brain Quantization)

편집 원본 편집

GPTQ는 가지치기 기법 OBS(Optimal Brain Surgeon, 1993)를 양자화로 옮긴 OBQ(2022)를 대규모로 확장한 것이다. 목적 함수를 W의 행별 오차 합으로 쓰면, 행 w 하나에서 아직 양자화하지 않은 가중치 집합 F의 헤세 행렬은 HF=2XFXF⊤다. 다음에 양자화할 가중치 wq와 나머지 가중치의 최적 보정량 δF는 다음과 같다.[1]

wq=argminwq(quant⁡(wq)−wq)2[HF−1]qq,δF=−wq−quant⁡(wq)[HF−1]qq⋅(HF−1):,q

wq를 확정한 뒤에는 역행렬을 다시 구하지 않고 가우스 소거 한 번으로 q번째 행·열을 지운다.

H−q−1=(H−1−1[H−1]qqH:,q−1Hq,:−1)−q

OBQ는 행마다 오차가 가장 작은 가중치부터 탐욕적으로 고르므로 계산량이 O(drow⋅dcol3)이다. ResNet-50(2,500만 매개변수)은 GPU 1장에서 약 1시간이면 되지만 수십억 매개변수 모델에는 쓸 수 없었다.

GPTQ의 세 가지 변경

편집 원본 편집
단계 내용 효과
1. 임의 순서 탐욕 순서 대신 모든 행을 같은 열 순서로 양자화한다. 큰 층에서는 순서를 바꿔도 결과 차이가 작았다. 헤세 행렬은 입력 X에만 의존하므로 모든 행이 같은 HF−1를 공유한다 역행렬 갱신이 가중치마다가 아니라 열마다 한 번. 계산량이 O(max⁡{drowdcol2,dcol3})로 줄어든다
2. 지연 일괄 갱신 열 i의 반올림은 그 열에 대한 갱신에만 영향을 받는다. 그래서 B=128개 열 블록 안에서만 갱신하고, 블록이 끝나면 나머지 열을 행렬 곱 한 번으로 갱신한다 메모리 대역폭 병목을 피해 큰 모델에서 한 자릿수 이상 빨라짐
3. 촐레스키 분해 반복 갱신으로 HF−1가 수치적으로 깨져 큰 모델의 일부 층이 망가졌다. 필요한 정보는 각 행의 대각 이후 원소뿐이고, 이는 H−1의 촐레스키 분해와 같다. 미리 한 번 계산해 둔다. 대각에 평균 대각값의 1%를 더하는 감쇠(dampening)도 함께 쓴다 수천억 매개변수 모델에서도 안정

OBQ 대비 계산량이 1,000배 이상 줄었다고 논문은 밝힌다.[1]

논문의 알고리즘 1을 옮기면 다음과 같다. H−1=(2XX⊤+λI)−1, 블록 크기 B.

Q ← 0 (d_row × d_col),  E ← 0 (d_row × B)
H⁻¹ ← Cholesky(H⁻¹)ᵀ                      # 역헤세 정보를 한 번에 계산
for i = 0, B, 2B, ...:                      # 열 블록 단위
    for j = i, ..., i+B-1:
        Q[:, j] ← quant(W[:, j])              # 열 하나 양자화
        E[:, j-i] ← (W[:, j] - Q[:, j]) / H⁻¹[j, j]
        W[:, j:i+B] ← W[:, j:i+B] - E[:, j-i] · H⁻¹[j, j:i+B]    # 블록 안 보정
    W[:, i+B:] ← W[:, i+B:] - E · H⁻¹[i:i+B, i+B:]             # 블록 밖 일괄 보정

층 하나를 양자화하는 파이토치 예시다. 행마다 하나의 비대칭 4비트 격자를 쓰고 그룹화와 열 재정렬은 뺐다. 상관이 있는 무작위 입력(512차원, 2,048개)으로 돌려 보면 같은 격자의 RTN보다 출력 제곱 오차가 절반 이하로 줄어든다(파이토치 2.5.1에서 확인).

import torch

def quantize(w, scale, zero, maxq):
    q = torch.clamp(torch.round(w / scale) + zero, 0, maxq)
    return scale * (q - zero)

def gptq_layer(W, X, bits=4, blocksize=128, percdamp=0.01):
    """W: [d_out, d_in] 가중치, X: [n, d_in] 보정 입력"""
    W = W.clone().float()
    d_out, d_in = W.shape
    maxq = 2 ** bits - 1
    wmin = W.min(1, keepdim=True).values
    wmax = W.max(1, keepdim=True).values
    scale = (wmax - wmin).clamp(min=1e-8) / maxq
    zero = torch.round(-wmin / scale)

    H = 2 * X.T @ X                                           # 층 단위 헤세 행렬
    H += percdamp * torch.mean(torch.diag(H)) * torch.eye(d_in)   # 감쇠
    Hinv = torch.linalg.cholesky(
        torch.cholesky_inverse(torch.linalg.cholesky(H)), upper=True)

    Q = torch.zeros_like(W)
    for i1 in range(0, d_in, blocksize):
        i2 = min(i1 + blocksize, d_in)
        W1, Hinv1 = W[:, i1:i2].clone(), Hinv[i1:i2, i1:i2]
        Err1 = torch.zeros_like(W1)
        for j in range(i2 - i1):
            w, d = W1[:, j], Hinv1[j, j]
            q = quantize(w.unsqueeze(1), scale, zero, maxq).flatten()
            Q[:, i1 + j] = q
            err = (w - q) / d
            W1[:, j:] -= err.unsqueeze(1) @ Hinv1[j, j:].unsqueeze(0)  # 블록 안 보정
            Err1[:, j] = err
        W[:, i2:] -= Err1 @ Hinv[i1:i2, i2:]                      # 블록 밖 일괄 보정
    return Q

모델 전체는 트랜스포머 블록을 앞에서부터 하나씩 GPU에 올려, 보정 입력을 통과시켜 층마다 H를 누적하고 양자화한 뒤, 양자화된 블록의 출력을 다음 블록의 입력으로 넘기는 식으로 처리한다. 원 논문의 보정 데이터는 C4 데이터셋에서 무작위로 뽑은 2,048토큰 구간 128개였다.[1]

  • 그룹 양자화: 가중치를 연속된 g개씩 묶어 묶음마다 배율과 영점을 둔다. 그룹 크기 128이면 가중치당 약 0.15비트가 추가되고 정확도가 확실히 좋아진다. GPTQ는 그룹 매개변수를 양자화 도중 갱신된 최신 가중치로 정할 수 있어 그룹화와 잘 맞는다.[1] 실무 체크포인트는 대개 4비트·그룹 128(W4A16-G128)이다.
  • 활성값 순서(act-order): 원 논문은 임의 순서도 괜찮다고 했지만, 이후 구현은 헤세 대각 값이 큰(입력 활성값이 큰) 열부터 양자화하는 옵션(desc_act, act-order)을 두었다. 오차가 큰 열을 먼저 처리해 보정할 여지를 남긴다. 그룹화와 함께 쓰면 추론 때 열 재배열 비용이 생길 수 있다.
  • 보정 데이터: 배포 환경과 비슷한 데이터를 쓰는 것이 좋다. llm-compressor 예제는 512개 샘플, 길이 2,048에서 시작하고 정확도가 떨어지면 늘리라고 권한다.[2]
모델 FP16 RTN 4비트 GPTQ 4비트 RTN 3비트 GPTQ 3비트
OPT-175B 8.34 10.54 8.37 7,300 8.68
BLOOM-176B 8.11 8.37 8.21 571 8.64
OPT-13B 10.13 11.32 10.31 3,400 11.61

값은 WikiText-2 퍼플렉시티(낮을수록 좋음)이다.[1]

  • 3비트에서 RTN은 무너지고 GPTQ는 버틴다. 그룹 128을 더한 3비트 GPTQ는 OPT-175B에서 8.45였다.
  • 양자화 시간: OPT-175B, BLOOM-176B를 A100 80GB 1장에서 약 4시간.
  • 추론: 3비트 OPT-175B는 임베딩·출력 층(FP16 유지)을 포함해 약 63GB라 A100 80GB 1장에 들어간다. FP16은 80GB GPU 5장, LLM.int8()은 3장이 필요했다. 가중치를 필요할 때 풀어 쓰는 전용 커널로 토큰당 지연이 A100에서 FP16(5장) 대비 3.25배, A6000에서 FP16(8장) 대비 4.5배 줄었다.[1]
  • 그룹화를 쓰면 약 2.2비트(2비트, 그룹 128)까지 내려가도 퍼플렉시티 증가가 1.5 미만이었다.
항목 GPTQ AWQ
핵심 아이디어 헤세 행렬로 오차를 남은 가중치에 보정 활성값이 큰 채널의 가중치를 배율로 키워 보호
계산 층마다 XX⊤ 누적, 촐레스키, 열 단위 갱신 채널별 평균 활성값 크기, 배율 격자 탐색
보정 데이터 의존 상대적으로 크다. 보정·평가 분포가 다르면 손실이 커질 수 있다 작다. AWQ 논문은 GPTQ 대비 10분의 1 보정 데이터로 충분하다고 보고[3]
정확도 3~4비트에서 RTN보다 크게 좋음 AWQ 논문의 LLaMA·Llama-2 실험에서는 GPTQ보다 조금 나음
결합 서로 독립이라 AWQ로 배율을 맞춘 뒤 GPTQ를 적용할 수 있다. AWQ 논문은 2비트에서 두 방법을 합쳐 더 좋아졌다고 보고[3]

고르는 기준: 둘 다 같은 W4A16 형식이라 추론 속도는 커널이 좌우한다. 보정 데이터가 배포 분포와 잘 맞으면 GPTQ, 여러 도메인·멀티모달·지시 튜닝 모델처럼 보정 데이터에 과적합할 위험이 크면 AWQ가 무난하다. 실제로는 대상 모델로 두 가지를 다 만들어 작업 벤치마크로 비교하는 경우가 많다.

  • 원 구현: IST-DASLab/gptq(GitHub).
  • AutoGPTQ: 허깅 페이스 연동으로 쓰였으나 지금은 유지보수가 끊겨 저장소가 보관 처리되었고(마지막 갱신 2025년 4월), 저장소는 후속 도구로 GPTQModel을 권한다.[4]
  • llm-compressor: vLLM 프로젝트의 압축 도구. GPTQ, AWQ, FP8 등을 한 레시피 문법으로 적용하고 vLLM이 바로 읽는 형식으로 저장한다.[2]
  • 추론: vLLM, SGLang, 허깅 페이스 Transformers, TGI 등이 GPTQ 체크포인트를 읽는다. CPU·로컬 실행용 GGUF 포맷은 llama.cpp 고유의 양자화 형식이라 별개다.
# llm-compressor 예제 기준(2026-10). 4비트 가중치, 그룹 128
from transformers import AutoModelForCausalLM, AutoTokenizer
from llmcompressor import oneshot
from llmcompressor.modifiers.gptq import GPTQModifier

model_id = "meta-llama/Meta-Llama-3-8B-Instruct"
model = AutoModelForCausalLM.from_pretrained(model_id)
tokenizer = AutoTokenizer.from_pretrained(model_id)

recipe = GPTQModifier(targets="Linear", scheme="W4A16", ignore=["lm_head"])
oneshot(model=model, dataset=ds, recipe=recipe,          # ds: 토큰화한 보정 데이터셋
        max_seq_length=2048, num_calibration_samples=512)
model.save_pretrained("Meta-Llama-3-8B-Instruct-W4A16-G128", save_compressed=True)
  • 가중치 전용이라 메모리와 대역폭은 줄지만 행렬 곱 자체는 FP16으로 한다. 배치가 커서 연산에 묶이는 상황에서는 속도 이득이 줄어든다.
  • 보정 데이터가 바뀌면 결과도 바뀐다. 특정 언어나 도메인 데이터로 보정하면 다른 도메인 성능이 더 떨어질 수 있다.
  • 2비트 이하로 가면 그룹 크기를 줄여도 손실이 크다. 이 영역은 벡터 양자화 계열 기법이나 양자화 인식 학습이 더 낫다.
  1. ↑ 1.0 1.1 1.2 1.3 1.4 1.5 1.6 1.7 Elias Frantar, Saleh Ashkboos, Torsten Hoefler, Dan Alistarh, 〈GPTQ: Accurate Post-Training Quantization for Generative Pre-trained Transformers〉, ICLR 2023. arXiv:2210.17323
  2. ↑ 2.0 2.1 Quantizing to int4 using GPTQ, llm-compressor 예제, GitHub.
  3. ↑ 3.0 3.1 Ji Lin 외, 〈AWQ: Activation-aware Weight Quantization for LLM Compression and Acceleration〉, MLSys 2024. arXiv:2306.00978
  4. ↑ AutoGPTQ/AutoGPTQ, GitHub.