본문 바로가기
Model Lightening/Model Compression

AI 모델 양자화 기초 [5] - 실전: Convolutional Layer

by Sangwoo Seo 2026. 2. 16.

이번 글에서는 실제 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 이상이면 매우 우수한 품질입니다. 원본 신호 대비 노이즈가 극히 작다는 의미죠.

핵심 통찰

  1. 오차는 예측 가능: 최대 오차는 $ 0.5 \times s $로 제한됨
  2. 상대 오차의 불균형: 작은 값은 상대 오차가 크지만, 출력 기여도도 작음
  3. 전체적으로 우수한 품질: SQNR 50dB 이상
  4. 정수 범위 완전 활용: Asymmetric quantization으로 [-128, 127] 모두 사용

지금까지 우리는 Scale과 Zero-point를 직접 계산하고, 실제 값들을 손으로 양자화/역양자화해봤습니다.

핵심 정리:

  1. Scale 계산: $ s = \frac{x_{\max} - x_{\min}}{q_{\max} - q_{\min}} $
    1. 실수 범위를 정수 범위에 맞추는 비율
    2. 정밀도와 범위의 트레이드오프 결정
  2. Zero-point 계산: $ z = q_{\min} - \text{round}(x_{\min}/s) $
    1. 실수 0이 정수 어디에 매핑되는지 결정
    2. 정수 범위 활용 효율성 결정
  3. 양자화: $ x_q = \text{round}(x/s) + z $
    1. FP32 → INT8 변환
  4. 역양자화: $ \hat{x} = (x_q - z) \times s $
    1. INT8 → FP32 복원
  5. 오차 분석:
    1. 최대 오차: $ 0.5 \times s $
    2. SQNR로 품질 측정 (50dB 이상이면 우수)
  6. Symmetric vs Asymmetric:
    1. 대칭 범위 → Symmetric ($ z=0 $), 계산 빠름
    2. 비대칭 범위 → 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 블록을 처음부터 끝까지 양자화해보면서, 각 컴포넌트가 어떻게 다르게 처리되는지 완전히 이해해보겠습니다.