본문으로 건너뛰기

[llm-compressor] Transform Overview: 가중치 회전/변환 기반 Modifier 계열

들어가며

llm-compressor의 modifiers/transform/ 아래에는 네 가지 Modifier가 있다. QuIP, SpinQuant, iMatrix, 그리고 transform 버전의 SmoothQuant. 이들은 "가중치를 양자화 전에 변환(회전, 리스케일 등)해서 양자화 친화적인 분포로 만든다"는 공통 아이디어를 공유한다. AWQSmoothQuant 본체와 철학은 같지만, 구현이 별도의 Transform 계층으로 분리되어 있어 더 복잡한 변환(예: 랜덤 직교 회전)을 지원한다.

이 글은 src/llmcompressor/modifiers/transform/의 디렉토리 구조와 공통 패턴을 해부한다.

핵심 구조/코드 분석

디렉토리 레이아웃

modifiers/transform/
├── __init__.py
├── quip/                         # QuIP 랜덤 직교 회전
│   └── base.py
├── spinquant/                    # SpinQuant 학습된 회전
│   ├── base.py
│   ├── mappings.py               # 아키텍처별 변환 지점
│   └── norm_mappings.py          # RMSNorm 흡수 위치
├── imatrix/                      # iMatrix 리스케일
│   └── base.py
└── smoothquant/                  # SmoothQuant 의 transform 구현
    ├── base.py
    └── utils.py

각 하위 디렉토리는 base.py에 Modifier 클래스를 두고, 필요하면 보조 파일을 둔다. SpinQuant만 mappings.pynorm_mappings.py로 아키텍처 의존 정보를 분리했다.

공통 패턴: "Transform → Quantize"

모든 Transform Modifier는 다음 패턴을 따른다.

  1. 변환 계산. 가중치와 활성화 통계를 보고 변환 행렬 $R$(또는 스케일 $s$)을 계산한다.
  2. 변환 적용. $W \leftarrow W \cdot R$ 또는 $W \leftarrow W \cdot \text{diag}(s)$로 가중치를 변환한다. 수학적 등가성을 위해 반대편(입력 측)에 $R^{-1}$ 또는 $s^{-1}$를 흡수한다.
  3. 양자화 위임. 변환된 가중치를 QuantizationModifier에 넘긴다. 실제 양자화는 그곳에서 일어난다.

이 패턴은 "Transform Modifier는 양자화를 하지 않는다"는 것을 의미한다. 이들은 전처리 단계다. 사용자는 레시피에 Transform Modifier와 QuantizationModifier를 함께 선언한다.

transform_stage:
  transform_modifiers:
    QuIPModifier:
      rotation_size: 128

quant_stage:
  quantization_modifiers:
    QuantizationModifier:
      scheme: W4A16

이 레시피는 (1) QuIP의 랜덤 직교 회전을 적용한 뒤, (2) W4A16 양자화를 수행한다.

TransformBase: 공통 베이스 (없음)

흥미롭게도 llm-compressor는 Transform 모디파이어들의 공용 베이스를 두지 않는다. 각 Modifier가 독립적으로 Modifier를 상속한다. 이는 네 Transform 알고리즘의 수학적 성격이 너무 달라서 공통 추상화가 의미 없기 때문이다.

  • QuIP: 랜덤 직교 회전 (stateless, 사전 정의)
  • SpinQuant: 학습된 회전 (gradient descent로 최적화)
  • iMatrix: 입력 채널별 리스케일 (importance 기반)
  • SmoothQuant (transform): activation outlier를 가중치로 이동 (통계 기반)

각자 고유한 라이프사이클을 가지며, 공통점은 "가중치 변환 → 양자화" 고수준 패턴뿐이다.

Mapping 구조의 재사용

그래도 공통적으로 사용되는 것은 매핑 구조다. SmoothQuantSmoothMapping, AWQAWQMapping과 같은 패턴을 각 Transform Modifier가 내부적으로 쓴다.

# 추상 매핑 구조 (각 Modifier 가 자기 버전을 가짐)
@dataclass
class TransformMapping:
    source_layer: str              # 변환을 흡수할 레이어 (RMSNorm 등)
    target_layers: list[str]       # 변환이 곱해질 Linear 레이어들

SpinQuant의 mappings.py가 대표적이다. LLaMA의 경우 "어느 RMSNorm이 어느 projection에 회전을 흡수시키는가"를 하드코딩한다.

norm_mappings.py (SpinQuant 특유)

SpinQuant는 회전이 RMSNorm을 통과해야 한다는 특수 조건이 있다. RMSNorm은 per-element 연산이므로 직교 회전과 교환 법칙이 성립하지 않는다. 이를 해결하려면 RMSNorm의 weight를 회전 행렬의 대각화 버전으로 흡수해야 한다. norm_mappings.py가 이 관계를 정의한다.

LLAMA_NORM_MAPPINGS = [
    NormMapping(
        norm_layer="input_layernorm",
        following_layers=["self_attn.q_proj", "self_attn.k_proj", "self_attn.v_proj"],
    ),
    NormMapping(
        norm_layer="post_attention_layernorm",
        following_layers=["mlp.gate_proj", "mlp.up_proj"],
    ),
]

이 매핑은 "input_layernorm의 weight를 회전한 후 q/k/v projection에 회전을 곱하면 출력이 유지된다"는 수학적 관계를 표현한다. SpinQuant는 이 관계를 이용해 회전을 "모델 안에 흡수"시킨다.

왜 이 설계인가

1. Transform과 Quantization의 명확한 분리. Transform은 전처리이고, Quantization은 실행이다. 둘을 분리하면 "어떤 전처리와 어떤 양자화의 조합"을 자유롭게 탐색할 수 있다. 예를 들어 QuIP + GPTQ, SpinQuant + AWQ 같은 조합이 가능하다.

2. 베이스 클래스 없음. 네 Transform 알고리즘이 수학적으로 너무 달라서 공통 베이스가 의미 없다. 억지로 통일하면 가짜 추상화가 된다. 각자 Modifier만 상속하는 것이 유연하다.

3. Mapping 구조 재사용. AWQ/SmoothQuant와 같은 "smooth layer ↔ balance layers" 구조를 대부분의 Transform이 쓴다. 이름은 다르지만 본질은 같다.

4. SpinQuant의 norm_mappings 특수 처리. 직교 회전과 RMSNorm의 비교환성은 어려운 문제다. SpinQuant는 이를 정면으로 다루기 위해 별도 매핑 파일을 만들었다. 다른 Transform은 element-wise scale만 쓰므로 이런 문제가 없다.

5. 레시피 DSL에서 순서 강제. transform_stage를 먼저 쓰고 quant_stage를 나중에 쓰는 관습이 스테이지 이름으로 표현된다. Lifecycle이 스테이지 순서대로 실행하므로 "변환 먼저, 양자화 나중" 순서가 자연스럽게 유지된다.

마무리

Transform Overview는 네 구현체의 공통 철학을 보여준다. 다음 세 글에서는 각 Modifier의 구체적 구현을 차례로 본다: QuIP, SpinQuant, iMatrix Transform.

참고 자료

댓글

관련 포스트

llm-compressor 의 다른글