이번 글에서는 실제 Convolutional layer 하나를 처음부터 끝까지 완전히 양자화해보겠습니다. 단순히 몇 개의 숫자를 변환하는 것이 아니라, 수백 개의 가중치와 활성화 값을 다루면서 실전 감각을 익히는 것이 목표입니다.
1. 실전 예시: Conv 레이어 완전 분해
시나리오 설정
ResNet-50의 한 Conv 레이어를 양자화한다고 가정합니다.
주어진 정보:
- 레이어: Conv2D (3×3 커널, 64 채널)
- 가중치 개수: 3 × 3 × 64 × 64 = 36,864개
- 입력 이미지: 224×224×3
- 출력 Feature map: 112×112×64
우리는 이 레이어의 출력 활성화 값(activation values)을 양자화할 것입니다.
Step 1: 데이터 분포 파악
먼저 실제 활성화 값의 분포를 확인해야 합니다. 실제 추론을 한 번 실행하여 통계를 수집합니다.
수집된 통계 (calibration 과정):
- 최소값: $ x_{\min} = -2.47 $
- 최대값: $ x_{\max} = 4.83 $
- 평균: $ \mu = 0.68 $
- 표준편차: $ \sigma = 1.23 $
분포 특성:
대부분의 값이 [-1, 3] 사이에 몰려있음
범위가 비대칭적 (음수보다 양수 쪽으로 치우침)
→ Asymmetric quantization이 유리
[-2.47, -1.83, -0.92, -0.15, 0.00, 0.47, 1.25, 2.18, 3.56, 4.83]
이제 이 값들을 INT8로 양자화해보겠습니다.
Step 2: Scale과 Zero-point 계산
주어진 범위:
- $ x_{\min} = -2.47 $
- $ x_{\max} = 4.83 $
- 정수 범위: $ q_{\min} = -128 $, $ q_{\max} = 127 $
Scale 계산:
$$ s = \frac{x_{\max} - x_{\min}}{q_{\max} - q_{\min}} = \frac{4.83 - (-2.47)}{127 - (-128)} = \frac{7.30}{255} = 0.02863 $$
Zero-point 계산:
$$ z = q_{\min} - \text{round}\left(\frac{x_{\min}}{s}\right) = -128 - \text{round}\left(\frac{-2.47}{0.02863}\right) $$
$$= −128 − \text{round}(−86.26)=−128−(−86)=−42 $$
결과:
- Scale: $ s = 0.02863 $
- Zero-point: $ z = -42 $
의미 해석:
- 실수 0은 정수 -42로 매핑됨
- 실수 0.02863마다 정수 1칸 이동
- Asymmetric quantization (범위가 비대칭이므로)
Step 3: 샘플 값들을 실제로 양자화
이제 앞서 본 10개의 샘플 값을 INT8로 변환해봅시다.
양자화 수식
$$ x_q = \text{round}(x/s) + z $$
값 1: $ x = -2.47 $ (최소값)
$$ x_q = \text{round}(-2.47 / 0.02863) + (-42) = \text{round}(-86.26) - 42 = -86 - 42 = -128 $$
값 2: $ x = -1.83 $
$$ x_q = \text{round}(-1.83 / 0.02863) - 42 = \text{round}(-63.92) - 42 = -64 - 42 = -106 $$
값 3: $ x = -0.92 $
$$ x_q = \text{round}(-0.92 / 0.02863) - 42 = \text{round}(-32.13) - 42 = -32 - 42 = -74 $$
값 4: $ x = -0.15 $
$$ x_q = \text{round}(-0.15 / 0.02863) - 42 = \text{round}(-5.24) - 42 = -5 - 42 = -47 $$
값 5: $ x = 0.00 $
$$ x_q = \text{round}(0.00 / 0.02863) - 42 = 0 - 42 = -42 $$
값 6: $ x = 0.47 $
$$ x_q = \text{round}(0.47 / 0.02863) - 42 = \text{round}(16.41) - 42 = 16 - 42 = -26 $$
값 7: $ x = 1.25 $
$$ x_q = \text{round}(1.25 / 0.02863) - 42 = \text{round}(43.66) - 42 = 44 - 42 = 2 $$
값 8: $ x = 2.18 $
$$ x_q = \text{round}(2.18 / 0.02863) - 42 = \text{round}(76.14) - 42 = 76 - 42 = 34 $$
값 9: $ x = 3.56 $
$$ x_q = \text{round}(3.56 / 0.02863) - 42 = \text{round}(124.34) - 42 = 124 - 42 = 82 $$
값 10: $ x = 4.83 $ (최대값)
$$ x_q = \text{round}(4.83 / 0.02863) - 42 = \text{round}(168.70) - 42 = 169 - 42 = 127 $$
변환 결과 정리:
원본 FP32: [-2.47, -1.83, -0.92, -0.15, 0.00, 0.47, 1.25, 2.18, 3.56, 4.83]
양자화 INT8: [-128, -106, -74, -47, -42, -26, 2, 34, 82, 127]
관찰:
- 최소값/최대값이 정확히 -128/127로 매핑됨
- 실수 0이 정수 -42로 매핑됨
- 정수 범위 전체를 고르게 활용하고 있음
Step 4: Dequantization으로 복원
양자화된 INT8 값을 다시 FP32로 복원해봅시다. 이 과정을 Dequantization(역양자화)라고 합니다.
Dequantization 수식
$$ \hat{x} = (x_q - z) \times s $$
앞서 양자화한 10개 값을 복원해보겠습니다.
값 1: $ x_q = -128 $
$$ \hat{x} = (-128 - (-42)) \times 0.02863 = -86 \times 0.02863 = -2.462 $$
원본: $ -2.47 $, 복원: $ -2.462 $, 오차: $ 0.008 $
값 2: $ x_q = -106 $
$$ \hat{x} = (-106 - (-42)) \times 0.02863 = -64 \times 0.02863 = -1.832 $$
원본: $ -1.83 $, 복원: $ -1.832 $, 오차: $ 0.002 $
값 3: $ x_q = -74 $
$$ \hat{x} = (-74 - (-42)) \times 0.02863 = -32 \times 0.02863 = -0.916 $$
원본: $ -0.92 $, 복원: $ -0.916 $, 오차: $ 0.004 $
값 4: $ x_q = -47 $
$$ \hat{x} = (-47 - (-42)) \times 0.02863 = -5 \times 0.02863 = -0.143 $$
원본: $ -0.15 $, 복원: $ -0.143 $, 오차: $ 0.007 $
값 5: $ x_q = -42 $
$$ hat{x} = (-42 - (-42)) \times 0.02863 = 0 \times 0.02863 = 0.000 $$
원본: $ 0.00 $, 복원: $ 0.000 $, 오차: $ 0.000 $ (완벽 복원)
값 6: $ x_q = -26 $
$$ \hat{x} = (-26 - (-42)) \times 0.02863 = 16 \times 0.02863 = 0.458 $$
원본: $ 0.47 $, 복원: $ 0.458 $, 오차: $ 0.012 $
값 7: $ x_q = 2 $
$$ \hat{x} = (2 - (-42)) \times 0.02863 = 44 \times 0.02863 = 1.260 $$
원본: $ 1.25 $, 복원: $ 1.260 $, 오차: $ 0.010 $
값 8: $ x_q = 34 $
$$ \hat{x} = (34 - (-42)) \times 0.02863 = 76 \times 0.02863 = 2.176 $$
원본: $ 2.18 $, 복원: $ 2.176 $, 오차: $ 0.004 $
값 9: $ x_q = 82 $
$$ \hat{x} = (82 - (-42)) \times 0.02863 = 124 \times 0.02863 = 3.550 $$
원본: $ 3.56 $, 복원: $ 3.550 $, 오차: $ 0.010 $
값 10: $ x_q = 127 $
$$ \hat{x} = (127 - (-42)) \times 0.02863 = 169 \times 0.02863 = 4.838 $$
원본: $ 4.83 $, 복원: $ 4.838 $, 오차: $ 0.008 $
복원 결과 정리:
원본 FP32: [-2.470, -1.830, -0.920, -0.150, 0.000, 0.470, 1.250, 2.180, 3.560, 4.830]
양자화 INT8: [ -128, -106, -74, -47, -42, -26, 2, 34, 82, 127]
복원 FP32: [-2.462, -1.832, -0.916, -0.143, 0.000, 0.458, 1.260, 2.176, 3.550, 4.838]
절대 오차: [ 0.008, 0.002, 0.004, 0.007, 0.000, 0.012, 0.010, 0.004, 0.010, 0.008]
Step 5: 오차 분석
이제 양자화 오차를 정량적으로 분석해봅시다.
절대 오차 (Absolute Error):
$$ \epsilon_{abs} = |\hat{x} - x| $$
- 최소: $ 0.000 $ (실수 0)
- 최대: $ 0.012 $ (실수 0.47)
- 평균: $ 0.0065 $
상대 오차 (Relative Error)
$$ \epsilon_{rel} = \frac{|\hat{x} - x|}{|x|} \times 100\ $$
값 1: 0.008/2.47 = 0.32%
값 2: 0.002/1.83 = 0.11%
값 3: 0.004/0.92 = 0.43%
값 4: 0.007/0.15 = 4.67% ← 작은 값은 상대 오차 큼
값 5: 0.000/0.00 = N/A
값 6: 0.012/0.47 = 2.55%
값 7: 0.010/1.25 = 0.80%
값 8: 0.004/2.18 = 0.18%
값 9: 0.010/3.56 = 0.28%
값 10: 0.008/4.83 = 0.17%
관찰:
- 큰 값들: 상대 오차 0.1~0.5% (매우 작음)
- 작은 값들: 상대 오차 2~5% (상대적으로 큼)
- 하지만 작은 값들은 출력에 미치는 영향도 작음
최대 양자화 오차의 이론적 한계:
양자화는 반올림(round) 과정에서 최대 $ \pm 0.5 $ 정수 오차가 발생합니다.
실수로 환산하면:
$$ \epsilon_{max} = 0.5 \times s = 0.5 \times 0.02863 = 0.0143 $$
실제 최대 오차($ 0.012 $)가 이론적 한계($ 0.0143 $) 이내임을 확인
Signal-to-Quantization-Noise Ratio (SQNR)
신호의 품질을 측정하는 지표입니다
$$ \text{SQNR} = 10 \log_{10}\left(\frac{\sum x^2}{\sum (x - \hat{x})^2}\right) $$
우리 샘플에서:
- 신호 파워: $ \sum x^2 = 2.47^2 + 1.83^2 + … + 4.83^2 = 63.55 $
- 오차 파워: $ \sum \epsilon^2 = 0.008^2 + 0.002^2 + ... + 0.008^2 = 0.00046 $
$$ \text{SQNR} = 10 \log_{10}(63.55 / 0.00046) = 10 \log_{10}(138152) = 51.4 \text{ dB} $$
해석: 50dB 이상이면 매우 우수한 품질입니다. 원본 신호 대비 노이즈가 극히 작다는 의미죠.
핵심 통찰
- 오차는 예측 가능: 최대 오차는 $ 0.5 \times s $로 제한됨
- 상대 오차의 불균형: 작은 값은 상대 오차가 크지만, 출력 기여도도 작음
- 전체적으로 우수한 품질: SQNR 50dB 이상
- 정수 범위 완전 활용: Asymmetric quantization으로 [-128, 127] 모두 사용
지금까지 우리는 Scale과 Zero-point를 직접 계산하고, 실제 값들을 손으로 양자화/역양자화해봤습니다.
핵심 정리:
- Scale 계산: $ s = \frac{x_{\max} - x_{\min}}{q_{\max} - q_{\min}} $
- 실수 범위를 정수 범위에 맞추는 비율
- 정밀도와 범위의 트레이드오프 결정
- Zero-point 계산: $ z = q_{\min} - \text{round}(x_{\min}/s) $
- 실수 0이 정수 어디에 매핑되는지 결정
- 정수 범위 활용 효율성 결정
- 양자화: $ x_q = \text{round}(x/s) + z $
- FP32 → INT8 변환
- 역양자화: $ \hat{x} = (x_q - z) \times s $
- INT8 → FP32 복원
- 오차 분석:
- 최대 오차: $ 0.5 \times s $
- SQNR로 품질 측정 (50dB 이상이면 우수)
- Symmetric vs Asymmetric:
- 대칭 범위 → Symmetric ($ z=0 $), 계산 빠름
- 비대칭 범위 → Asymmetric ($ z \neq 0 $), 정수 범위 최대 활용
이제 이론과 계산 방법을 완전히 이해했습니다. 하지만 한 가지 의문이 남습니다:
"실제 현대 딥러닝 모델, 특히 Transformer에서는 어떻게 적용되는가?"
Conv 레이어는 상대적으로 단순합니다. 하지만 Transformer는 다릅니다:
- Multi-Head Attention: Q, K, V 행렬, Attention scores
- Feed-Forward Network: 거대한 Linear 레이어들
- Layer Normalization: 평균/분산 계산
- Residual Connection: Skip connection으로 값 더하기
각 컴포넌트는 서로 다른 값의 분포와 특성을 가지고 있습니다. 예를 들어:
- Attention scores는 [0, 1] 범위로 제한됨 (Softmax 출력)
- FFN 중간 활성화는 ReLU/GELU로 양수만 나올 수 있음
- Residual은 원본과 변환값을 더하므로 범위가 달라짐
다음 글에서는 BERT의 한 Transformer 블록을 처음부터 끝까지 양자화해보면서, 각 컴포넌트가 어떻게 다르게 처리되는지 완전히 이해해보겠습니다.
'Model Lightening > Model Compression' 카테고리의 다른 글
| AI 모델 양자화 기초 [6] - 실전: Transformer (0) | 2026.02.17 |
|---|---|
| AI 모델 양자화 기초 [4] - Scale과 Zero-point의 비밀 (0) | 2026.02.16 |
| AI 모델 양자화 기초 [3] - Affine Quantization의 수학적 원리 (0) | 2026.02.15 |
| AI 모델 양자화 기초 [2] - 양자화란 무엇인가? (0) | 2026.02.15 |
| AI 모델 양자화 기초 [1] - 왜 양자화가 필요한가 (0) | 2026.02.15 |