Skip to content
Open

Awq #10

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
__pycache__/
*.pyc
.DS_Store
outputs/
84 changes: 84 additions & 0 deletions qwen3-awq/AWQ_USAGE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# AWQ 양자화 모듈 사용 가이드

AWQ INT4 양자화를 직접 구현한 모듈. `AutoModelForCausalLM`으로 로드되는 모든 HF 모델에
적용 가능하며, gptqmodel/vLLM이 바로 로드하는 AWQ 표준 포맷으로 저장합니다.

## 빠른 시작

```bash
python run_awq.py --model Qwen/Qwen3-4B --calib-data pileval

# 다른 모델도 동일한 커맨드 (Llama, Mistral, Phi, TinyLlama 등)
python run_awq.py --model meta-llama/Llama-3.2-3B --calib-data wikitext2
python run_awq.py --model mistralai/Mistral-7B-v0.3 --calib-data c4
python run_awq.py --model TinyLlama/TinyLlama-1.1B-Chat-v1.0 --calib-data wikitext2

# 한국어 calibration
python run_awq.py --model Qwen/Qwen3-4B --calib-data kowikitext
```

```python
# Python API
from awq import AWQQuantizer
quantizer = AWQQuantizer("Qwen/Qwen3-4B", w_bit=4, group_size=128)
quantizer.quantize(calib_data="pileval", output_dir="./outputs/qwen3-4b-awq")
```

파이프라인: **calibration**(activation 통계 수집, hook 기반) → **scale 탐색**(grid search)
→ **INT4 양자화** → **export**(safetensors). `lm_head`는 자동 제외.

## 주요 옵션

| 인자 | 기본값 | 설명 |
|------|--------|------|
| `--model` | `Qwen/Qwen3-4B` | HF 모델 ID / 로컬 경로 |
| `--calib-data` | `pileval` | `pileval` / `wikitext2` / `kowikitext`(한국어) / `c4` |
| `--n-samples` / `--seq-len` | 128 / 512 | calibration 규모 (코드 검증만 할 땐 16 / 256) |
| `--w-bit` / `--group-size` | 4 / 128 | 양자화 설정 |
| `--skip-layers` | `lm_head` | 제외 레이어 (정확한 모듈 이름) |

## 대상 모델 요건

- `in_features % group_size == 0` — 안 맞는 레이어는 자동 스킵(FP16 유지)
- `out_features % 8 == 0` — INT4 8개 → INT32 패킹 단위
- gated repo(Gemma, Llama 등)는 HF 접근 승인 + 로그인 필요

커스텀 calibration 데이터는 `awq/calibration.py`의 `get_calib_dataset()`에 분기 추가
(텍스트 리스트만 만들면 토크나이즈/분할은 공통 처리).

## 출력 포맷 (AWQ GEMM 표준)

| 텐서 | Shape |
|------|-------|
| `qweight` | `[in_features, out_features // 8]` INT32 |
| `scales` | `[n_groups, out_features]` FP16 |
| `qzeros` | `[n_groups, out_features // 8]` INT32 |

INT32 내부 패킹은 **인터리브 순서 `[0, 2, 4, 6, 1, 3, 5, 7]`** (순차로 하면 출력이 깨짐).

로드 (GPU 필요):

```python
# transformers + gptqmodel — config의 quantization_config로 자동 인식
model = AutoModelForCausalLM.from_pretrained(path, torch_dtype="float16", device_map="auto")

# vLLM
llm = LLM(model=path, quantization="awq")
```

## 검증 절차

1. 작은 모델로 스모크 테스트: `--model TinyLlama/TinyLlama-1.1B-Chat-v1.0 --n-samples 16 --seq-len 256`
2. generate 테스트 — 깨진 토큰 반복이면 패킹 문제, 품질만 낮으면 calibration 문제
3. `lm_eval` 벤치마크 — 랜덤 수준(~25%)이면 export 버그 의심:
```bash
python -m lm_eval --model hf \
--model_args pretrained=<output_dir>,dtype=float16,device_map=auto \
--tasks kmmlu --batch_size 16
```

## 자주 겪는 문제

- **`autoawq` 이름 충돌**: `run_awq.py`가 importlib로 로컬 `awq/`를 명시적 로드해 회피
- **Colab pip install 후 ImportError**: 설치 후 반드시 런타임 재시작
- **출력이 깨진 토큰 반복**: 패킹 인터리브 순서 확인
97 changes: 97 additions & 0 deletions qwen3-awq/DESIGN_NOTES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# 직접 구현 AWQ vs 공식 AutoAWQ

## 결과 (Qwen3-4B, KMMLU)

| 모델 | 탐색 목적함수 | clip | KMMLU acc | FP16 대비 |
|------|------|------|-----------|-----------|
| FP16 baseline | — | — | 45.81% | — |
| 공식 AutoAWQ (INT4, pileval) | 출력 오차 | ✓ | 44.11% | −1.70%p |
| **직접 구현 (kowikitext, 출력 오차)** | 출력 오차 | ✗ | **42.98%** | **−2.83%p** |
| 직접 구현 (kowikitext, 출력 오차 + clip) | 출력 오차 | ✓ | 41.53% | −4.28%p |
| 직접 구현 (kowikitext, weight 오차) | weight 오차 | ✗ | 41.73% | −4.08%p |
| 직접 구현 (kowikitext, weight 오차 + clip) | weight 오차 | ✓ | 38.51% | −7.30%p |
| 직접 구현 (pileval 영어, weight 오차) | weight 오차 | ✗ | 40.34% | −5.47%p |
| 직접 구현 (wikitext2 영어, weight 오차) | weight 오차 | ✗ | 39.14% | −6.67%p |

*(RunPod 환경 측정. kowikitext weight-오차 결과는 Colab에서 41.61%로 재현성 확인됨)*

- **로드맵 #1(출력-오차 탐색) 적용으로 공식과의 격차가 −4.08%p → −1.13%p로 좁혀짐.**
남은 격차는 대부분 아래 차이점 #1(이중 양자화)에서 기인
- INT4 로딩(40.34%)과 FP16 dequant 시뮬레이션(40.33%)이 일치 → **export 파이프라인 정상**
- **한국어 calibration(kowikitext)이 pileval보다 +1.39%p, wikitext2보다 +2.59%p** —
한국어 벤치마크에는 한국어 calibration이 유리함을 확인. 특히 HUMSS(인문사회)에서 효과가 큼

### 압축 효과 (VRAM, 추론 로드 기준)

| | 경량화 전 (FP16) | 경량화 후 (INT4) | 압축률 |
|---|---|---|---|
| 모델 크기 | 8.04 GB | 2.67 GB | 3.0× |
| 파라미터 수 | 4.02B | 4.02B (비트수만 16→4) | — |

## 차이점과 이유

### 1. Scale folding 없음 → 이중 양자화 (격차의 주원인)

AWQ는 `W·s`를 양자화하면 입력 쪽에 `1/s` 보정이 필요합니다.

- **공식**: `1/s`를 이전 연산(LayerNorm, 앞단 Linear)의 weight에 접음(fold) → 양자화 1회
- **우리**: `dequant(quant(W·s)) / s`를 만든 뒤 이를 **다시 INT4로 양자화** → 오차 2회 누적

**이유**: fold는 아키텍처별 레이어 연결 매핑이 필요합니다 (AutoAWQ가 모델마다 전용
클래스를 두는 이유). "아무 HF 모델에나 적용"이라는 범용성 목표를 위해 unfold를 택했습니다.

### 2. 탐색 목적함수: weight 오차 vs 출력 오차 (로드맵 #1로 해소됨 ✓)

- **공식**: calibration 입력 X를 캐시해 `‖Q(W·s)·(X/s) − W·X‖` (출력 오차) 최소화
— activation이 큰 채널의 오차에 자동으로 가중치가 실림
- **초기 우리**: activation 통계는 탐색 후보(`s = act_scales^α`)에만 쓰고,
선택은 `‖dequant(quant(W·s)) − W·s‖` (weight 오차)로 함

**해소**: 로드맵 #1에서 hook에 레이어별 입력 서브샘플(512행, fp16/CPU, 전체 ~1GB)을
캐시하는 경로를 추가하고, `--search-mode output`으로 목적함수를 공식과 동일한 출력 오차로
교체함. 블록 단위 순차 실행 인프라 없이도 서브샘플만으로 근사가 가능했고,
결과적으로 **41.73% → 42.98% (+1.25%p)** 상승.

### 3. Weight clipping 탐색 (구현 완료, 실험으로 목적함수 종속성 규명 ✓)

공식은 그룹 max를 얼마나 잘라낼지도 grid search (outlier로 인한 해상도 낭비 방지).
구현 후 실험한 결과, **clip은 어떤 목적함수 위에서 도느냐에 성패가 갈림**:

| clip 탐색 기준 | KMMLU | baseline 대비 |
|---|---|---|
| weight 오차 위에 clip | 38.51% | **−3.22%p (역효과)** |
| 출력 오차 위에 clip | 41.53% | −1.45%p |

- **weight-오차 기준 clip이 역효과인 이유**: weight 재구성 오차만 보면 그룹 내 소수의
큰 weight(outlier)를 잘라 나머지 해상도를 높이는 게 항상 이득처럼 보인다. 그런데
그 outlier가 바로 AWQ가 보호하려는 salient weight(큰 activation과 곱해지는 채널)라서,
잘라내면 출력이 망가진다. 공식이 clip도 **출력 오차** 기준으로 탐색하는 이유가 이것.
- **단, 출력-오차 탐색이 이미 salient weight를 잘 보호하므로**, 그 위에 clip을 더해도
이 케이스에선 소폭 손해(42.98% → 41.53%). **최선 조합은 "출력-오차 탐색 + clip 없음".**



## 공식과 동일한 부분

INT4 group-wise asymmetric 양자화(group 128), alpha grid search(0~1, 20 grid),
AWQ GEMM export 포맷(인터리브 패킹 `[0,2,4,6,1,3,5,7]`, gptqmodel/vLLM 호환), lm_head 제외.

## 개선 로드맵

1. ✅ **입력 서브샘플 캐시 + 출력 오차 탐색** (완료) — hook에서 레이어당 512행 저장(~1GB),
범용성 유지하며 목적함수를 공식과 동일하게. `--search-mode output`. **+1.25%p**
2. ✅ **clipping 탐색** (완료) — 구현 후 실험으로 "목적함수 종속" 규명 (위 차이점 #3 참조).
출력-오차 탐색이 선행돼야 의미가 있으며, 이 케이스에선 clip 없는 쪽이 최선.
3. ⬜ **LayerNorm fold** — Llama-계열 표준 구조 한정 지원 + 미지원 모델은 폴백.
이중 양자화 제거로 남은 격차(−1.13%p)의 대부분 해소 예상, 대신 아키텍처 의존성 발생.
**현재 남은 유일한 주요 개선 항목.**

## 실험 로그 요약

- Colab → RunPod로 실행 환경 이전 (HF 다운로드 스톨/환경 리셋 대응). 코드는 GitHub로 동기화.
- fp16 overflow 버그 수정(`ec22cef`): wikitext2 calibration의 activation outlier가
`w·act_scales^α`를 fp16에서 inf로 만들어 scales에 NaN 전파 → KMMLU 9.87%(랜덤 이하)로 붕괴.
양자화 계산을 fp32로, 저장만 fp16으로 바꿔 해소 (wikitext2 39.14%로 정상화).
- 로드맵 #1(출력-오차 탐색, `5703ff8`) 적용: kowikitext 42.98%로 최고 성능 달성,
공식 AutoAWQ(44.11%)와 −1.13%p까지 근접.

19 changes: 19 additions & 0 deletions qwen3-awq/awq/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"""
awq — Activation-aware Weight Quantization

HuggingFace CausalLM 모델에 대한 AWQ INT4 양자화 파이프라인.

사용법:
from awq import AWQQuantizer

quantizer = AWQQuantizer(
model_name="Qwen/Qwen3-4B",
w_bit=4,
group_size=128,
)
quantizer.quantize(calib_data="pileval", output_dir="./outputs/qwen3-4b-awq")
"""

from .pipeline import AWQQuantizer

__all__ = ["AWQQuantizer"]
Loading