대칭 vs 비대칭 선형 양자화: 핵심 개념과 실무적 고찰

신경망 양자화 연구 계획에 이어, 이 글에서는 대칭 및 비대칭 선형 양자화의 개념을 한층 더 깊이 있게 다뤄보겠습니다.

아직 대규모 실험이 진행되지는 않았기에, 이 글은 다음과 같은 내용을 중심으로 합니다.

따라서 이 글은 개념과 실험 계획 중심의 글입니다. 실제 벤치마크 결과와 레이어별 수치는, 실험을 마치는 대로 별도의 “실험 결과” 편에서 상세히 다룰 예정입니다.


1. 숫자 표현 방식 간단 복습

양자화를 제대로 이해하려면, 먼저 하드웨어에서 숫자를 어떻게 표현하는지 짚고 넘어가는 것이 좋습니다.

FP32 (32비트 부동소수점)

INT8 (8비트 부호 있는 정수)

결국 양자화의 핵심 질문은 이렇게 요약할 수 있습니다.

연속적인 FP32 값을, 제한된 개수의 INT8 값으로 변환하면서 우리가 관심 있는 성능(정확도) 손실을 얼마나 최소화할 수 있을까?


2. 대칭 선형 양자화

2.1. 기본 아이디어

대칭(symmetric) 양자화는 FP32의 0을 INT8의 0에 정확히 대응시키고, 값의 분포가 “0을 중심으로 좌우 대칭”이라고 가정합니다.

텐서 x(가중치 또는 활성화)에 대해, 비트 수를 b라고 하면 (예: INT8은 b = 8) 양수 쪽에서 사용할 수 있는 정수 레벨의 수는 다음과 같습니다.

n_levels = 2^(b - 1) - 1

INT8의 경우 n_levels는 127이 됩니다.

개념적인 구현은 다음과 같이 표현할 수 있습니다.

def quantize_symmetric(x, bits=8):
    n_levels = 2 ** (bits - 1) - 1  # INT8의 경우 127
    max_abs = torch.max(torch.abs(x))
    scale = max_abs / n_levels      # Δ (스케일)

    x_int = torch.round(x / scale)
    x_int = torch.clamp(x_int, -n_levels - 1, n_levels)
    return x_int, scale

def dequantize_symmetric(x_int, scale):
    return x_int * scale

여기서 scale(Δ)은 “정수 1스텝(step)당 실제 값이 얼마나 변하는지”를 나타내는 척도입니다.

2.2. 토이 예시 (가중치)

가상의 예로, [512, 512] 형태의 가중치 텐서가 있고, 그 값의 범위가

라고 가정해 보겠습니다. 이 텐서를 INT8 대칭 양자화하면:

n_levels = 127
max_abs = 0.18
Δ = max_abs / n_levels = 0.18 / 127 ≈ 0.00142

이 경우:

이는 실제 실험 데이터가 아닌, 스케일 인자에 대한 감을 잡기 위한 단순 예시입니다.

2.3. 대칭 양자화가 잘 맞는 경우

대칭 양자화는 일반적으로 다음과 같은 경우에 효과적입니다.

  1. 데이터가 0을 기준으로 비교적 고르게 퍼져 있을 때 (예: BatchNorm / LayerNorm 후의 출력값)
  2. 분포가 대략 대칭을 이룰 때 (적절히 초기화된 대부분의 레이어 가중치)
  3. 구현의 단순성을 우선할 때 (Zero-point를 별도로 저장하거나 계산할 필요가 없음)

실제 적용 사례로는 다음과 같은 경우가 자연스럽습니다.


3. 활성화가 더 까다로운 이유 (특히 ReLU)

이처럼 깔끔한 “대칭” 가정은 ReLU 활성화처럼 항상 0 이상의 값만 나오는 분포에서는 깨지게 됩니다.

예를 들어, 어떤 ReLU의 출력이 다음과 같다고 가정해 봅시다.

이 상황에 INT8 대칭 양자화를 그대로 적용하면,

결과적으로:

이러한 문제를 해결하기 위해 비대칭(asymmetric) 양자화가 필요합니다.


4. 비대칭 선형 양자화

4.1. 기본 아이디어

비대칭 양자화는 실제 값의 범위를 정수 범위에 효과적으로 매핑하기 위해 zero-point라는 개념을 도입합니다.

를 정하고, 개념적인 매핑 수식은 다음과 같습니다.

q  = round(x / Δ) + z
x̂ = (q - z) * Δ

파이토치 스타일의 예시 코드는 다음과 같습니다.

def quantize_asymmetric(x, bits=8):
    n_levels = 2 ** bits        # INT8의 경우 256
    x_min, x_max = torch.min(x), torch.max(x)

    if x_max == x_min:
        return torch.zeros_like(x, dtype=torch.int32), 1.0, 0

    scale = (x_max - x_min) / (n_levels - 1)
    zero_point = torch.round(-x_min / scale)

    x_int = torch.round(x / scale) + zero_point
    x_int = torch.clamp(x_int, 0, n_levels - 1)

    return x_int, scale, zero_point

def dequantize_asymmetric(x_int, scale, zero_point):
    return (x_int - zero_point) * scale

4.2. 토이 예시 (ReLU 출력)

앞서 살펴본 가상의 ReLU 출력:

에 대해 INT8 비대칭 양자화를 적용하면 다음과 같습니다.

n_levels = 256
Δ = (3.24 - 0.0) / (256 - 1) ≈ 0.0127
z ≈ 0

대칭 방식과 비교하면:

이 역시 직관적인 이해를 돕기 위한 구성 예시이며, 실제 측정값은 아닙니다.


5. 앞으로의 실험 설계 방향

아직 ResNet-50 같은 대형 모델 기준의 정량적인 벤치마크는 진행하지 않았으며, 현재까지는 이론과 기존 연구들을 바탕으로 방향성을 수립하는 단계입니다. 실험은 대략 다음 순서로 진행할 계획입니다.

  1. 대칭 INT8부터 시작 — 가중치와 일부 활성화(정규화 후 출력 등)에 대칭 양자화 우선 적용. ResNet-50 및 더 작은 모델에서 벤치마크 수행.
  2. 명백히 비대칭인 활성화에 비대칭 양자화 추가 적용 — ReLU 이후 활성화, Attention의 Softmax 출력, [0, 1] 구간에 분포하는 각종 확률 값.
  3. 핵심 비교 지표 — FP32 대비 Top-1 / Top-5 정확도, 추론 지연시간, 모델 크기, 레이어별 민감도.

예상되는 패턴은 다음과 같습니다.

실험이 완료되면, 이 섹션은 실제 수치와 그래프, 레이어별 심층 분석 결과로 대체될 예정입니다.


6. 현재까지의 실무적 권장 사항

아직 저희의 실험 결과는 없지만, 이론과 기존 문헌을 토대로 다음과 같은 기본 전략을 고려해볼 수 있습니다.

6.1. 대칭 양자화를 추천하는 경우

6.2. 비대칭 양자화를 추천하는 경우

6.3. 현실적인 혼합 전략

  1. 가중치: 대칭 INT8 (per-tensor)
  2. 정규화된 pre-activation: 대칭 INT8
  3. ReLU 이후 / Softmax / 확률 출력: 비대칭 INT8
  4. 특히 민감한 레이어(예: 마지막 분류기)는 필요시 FP16/FP32로 유지

7. 구현 시 고민할 지점들

  1. 캘리브레이션 (Calibration) — min/max 값 추정을 위해 얼마나 많은 샘플이 필요한가? 백분위수 클리핑 vs 단순 min-max?
  2. Per-tensor vs Per-channel — 채널별 분포 편차가 큰 경우의 정확도 차이.
  3. PTQ vs QAT — PTQ를 우선 시도하고, 정확도 손실이 크면 QAT로 확장.

구체적인 설정과 벤치마크 결과는 실험 완료 후 별도의 글에서 정리할 예정입니다.


전체적인 연구 방향과 동기, 타임라인이 궁금하다면 신경망 양자화 연구 계획 글을 참고하시면 됩니다.

↑↓ 이동 · enter 열기 · esc 닫기