32비트짜리 숫자가 정말 다 필요할까

지난 글에서 데이터타입을 뜯어보며 한 가지를 관찰했다. 신경망의 가중치는 대개 0 근처에 촘촘히 몰려 있고 꼬리가 길다. 그렇다면 이런 질문이 자연스럽다.

서로 비슷한 값을 가진 가중치가 이렇게 많은데, 정말 하나하나를 32비트 실수로 따로 저장해야 할까?

비슷한 값들을 한 덩어리로 묶어서 대표값 하나로 퉁치면 어떨까? 예를 들어 2.09, 2.12, 1.92, 1.87은 전부 “대략 2.0"이다. 이 넷을 그냥 2.00 하나로 대체하고, 각 자리에는 “몇 번 대표값을 쓸지” 인덱스만 적어두는 것이다.

이게 K-Means 기반 weight quantization(가중치 양자화) 의 핵심 아이디어다. 2016년 Deep Compression 논문1이 제안했고, 정확도를 거의 잃지 않으면서 모델을 수십 배로 줄였다.


아이디어: 클러스터링으로 가중치 공유하기

절차는 세 단계다.

  1. 클러스터링 — 모든 가중치를 $k$개의 그룹으로 K-Means 클러스터링한다.
  2. 코드북(codebook) — 각 그룹의 중심값(centroid)을 대표값으로 저장한다. 이 대표값 목록이 코드북이다.
  3. 인덱스 — 각 가중치 자리에는 32비트 실수 대신 “몇 번 클러스터에 속하는지"를 가리키는 작은 정수 인덱스만 저장한다.

$k=4$개 클러스터라면 인덱스는 2비트($\log_2 4$)면 충분하다. 원래 32비트짜리 숫자들이 2비트 인덱스 + 작은 코드북으로 줄어든다.

아래 위젯에서 직접 돌려보자. 4×4 가중치 행렬이 있고, run을 누르면 K-Means가 반복될 때마다 중심값(코드북)이 수렴하고, 각 셀이 자기가 속한 클러스터 색으로 칠해진다. bits를 바꾸면 클러스터 개수가 달라지고, 아래에 양자화 오차(MSE)와 저장 용량, 압축비가 실시간으로 갱신된다.

기본 상태(2-bit)에서 run을 끝까지 돌리면 코드북이 대략 -1.00, 0.00, 1.50, 2.00으로 수렴한다. 이제 각 가중치는 이 네 값 중 하나로 복원(reconstruct)된다. 원본과 복원값의 차이가 곧 양자화 오차다.

  • bits3으로 올려보자 → 클러스터가 8개로 늘어 오차가 줄지만, 인덱스가 커져 저장 용량은 늘어난다.
  • bits1로 내려보자 → 대표값 2개로 뭉뚱그려져 오차가 커진다.

정밀도(오차)와 크기(저장) 사이의 트레이드오프가 손에 잡힌다.


얼마나 줄어드나

방금 위젯의 숫자를 따라가 보자. 4×4 = 16개 가중치, 2-bit 양자화:

  • 원본: $16 \times 32\text{bit} = 512\text{bit} = 64\text{B}$
  • 압축: 인덱스 $16 \times 2\text{bit} = 32\text{bit} = 4\text{B}$  +  코드북 $4 \times 32\text{bit} = 128\text{bit} = 16\text{B}$  =  $20\text{B}$
  • $64 / 20 = \textbf{3.2배}$ 작아졌다.

이 예제는 파라미터가 16개뿐이라 코드북(16B)이 상대적으로 크게 잡힌다. 하지만 실제 신경망은 파라미터 수 $M$이 수백만–수십억이라 $M \gg 2^N$이고, 이때 코드북 비용은 무시할 만해진다. 일반화하면:

$$\text{원본} = 32M \text{ bit}, \qquad \text{압축} = \underbrace{N \cdot M}_{\text{인덱스}} + \underbrace{32 \cdot 2^N}_{\text{코드북}} \text{ bit}$$

$M \gg 2^N$일 때 코드북 항은 사라지고 압축비는 $32M / NM = \mathbf{32/N}$ 배로 수렴한다. 즉 2비트면 약 16배, 4비트면 8배다. 아래에서 파라미터 수 $M$과 비트 수 $N$을 움직여보면 코드북이 언제 무시할 만해지는지 보인다.


정확도는? — 코드북을 재학습한다

가중치를 대표값으로 뭉치면 당연히 오차가 생긴다. 그냥 두면 정확도가 떨어진다. Deep Compression의 영리한 점은, 양자화한 코드북 자체를 다시 학습(fine-tuning) 시킨다는 것이다.

방법은 이렇다. 역전파로 각 가중치의 그래디언트를 구한 뒤,

  1. 같은 클러스터에 속한 가중치들의 그래디언트를 모아서(group by index),
  2. 더한 다음(reduce),
  3. 학습률을 곱해 그 클러스터의 중심값(centroid)을 업데이트한다.

개별 가중치가 아니라 대표값 몇 개만 학습하는 셈이다. 이렇게 하면 양자화로 생긴 오차를 상당 부분 되돌릴 수 있다.

가중치 분포로 보면 직관적이다. 양자화 전에는 연속적인 종 모양 분포였다가($\to$ 이산적인 몇 개의 스파이크로 바뀌고), 재학습을 거치면 그 스파이크들이 데이터에 맞게 미세 조정된다. 대표값의 “위치"가 손실을 줄이는 쪽으로 옮겨가는 것이다.

그 결과, 몇 비트까지 내려도 정확도가 유지되는가에 대한 답이 꽤 놀랍다. Deep Compression 실험에서 AlexNet 기준으로:

  • 합성곱(Conv) 층: 4비트까지 정확도 손실 없음
  • 완전연결(FC) 층: 2비트까지 정확도 손실 없음

즉 층마다 필요한 비트 수가 다르고, 파라미터가 많은 FC 층일수록 더 공격적으로 줄일 수 있다.


마지막 한 방울: 허프만 코딩

양자화까지 마치면 각 가중치는 몇 개의 인덱스(예: 2비트라면 0–3) 중 하나가 된다. 그런데 이 인덱스들은 골고루 나오지 않는다. 가중치가 0 근처에 몰려 있으니 0에 해당하는 인덱스는 아주 자주 나오고, 양 끝 인덱스는 드물게 나온다. 실제 신경망의 인덱스 히스토그램을 그려보면 한쪽으로 심하게 쏠려 있다.

여기서 무손실 압축을 한 번 더 짜낼 수 있다. 허프만 코딩(Huffman coding) 은 심볼의 빈도에 따라 비트 길이를 다르게 배정한다.

  • 자주 나오는 값 → 짧은 코드
  • 드물게 나오는 값 → 긴 코드

모든 인덱스에 똑같이 2비트를 쓰는 대신 빈도에 맞춰 코드 길이를 나누면, 평균 비트 수가 줄어든다. 예를 들어 분포가 한쪽으로 쏠린 경우:

대표값빈도고정 2비트허프만 코드
0.0050%010 (1비트)
−1.0025%0010 (2비트)
1.5015%10110 (3비트)
2.0010%11111 (3비트)

이때 평균 비트 수는 $0.5\times1 + 0.25\times2 + 0.15\times3 + 0.1\times3 = 1.75$비트로, 고정 2비트보다 짧다. 정보 이론적으로 최적 코드 길이는 분포의 엔트로피에 가까워지므로, 쏠린 분포일수록 이득이 커진다.

핵심은 무손실이라는 점이다. 값을 하나도 바꾸지 않고 순전히 표현의 낭비만 걷어낸다. Deep Compression에서는 이 단계가 파이프라인 맨 끝에 붙어 압축비를 한 번 더 끌어올린다(양자화까지 27–31배 → 허프만까지 35–49배).


중요한 한계: 이건 “저장"만 줄인다

여기서 반드시 짚어야 할 점이 있다. K-Means 양자화가 줄이는 것은 디스크·메모리에 저장되는 모델 크기뿐이다.

추론(inference) 시점에는 어떻게 될까? 저장된 것은 정수 인덱스 + 실수 코드북이다. 실제로 곱셈·덧셈을 하려면 인덱스를 코드북으로 디코드해서 원래 실수 가중치를 복원해야 한다. 즉:

  • 저장: 정수 인덱스 (작다) ✓
  • 연산: 복원된 32비트 실수로 여전히 floating-point 연산

메모리 대역폭은 아꼈지만, 계산 자체는 하나도 빨라지지 않았다. 곱셈기(MAC)는 여전히 float를 돌린다.

진짜로 정수 연산으로 추론까지 빠르게 하려면 다른 접근이 필요하다. 가중치를 정수로 저장하고 정수 산술로 곱셈까지 수행하는 Linear Quantization(선형 양자화) 이 그것이다. 그 이야기는 다음 글에서.


정리

  • 아이디어: 비슷한 가중치를 K-Means로 묶어 대표값(코드북)을 공유하고, 각 자리엔 작은 인덱스만 저장한다.
  • 압축: 32비트 → $N$비트 인덱스. $M \gg 2^N$이면 약 $32/N$배 작아진다.
  • 정확도: 코드북(centroid)을 재학습해 오차를 되돌린다. Conv 4비트·FC 2비트까지 무손실.
  • 추가 압축: 인덱스 분포가 쏠려 있어 허프만 코딩으로 무손실 압축을 한 번 더 짜낼 수 있다.
  • 한계: 저장만 줄고, 추론 연산은 여전히 float. 이게 Deep Compression 파이프라인(Pruning → Quantization → Huffman)의 가운데 조각이고, 정수 연산까지 가속하려면 Linear Quantization이 필요하다.

참고로 원 논문의 Deep Compression은 여기에 가지치기(pruning) 까지 더한 3단계 파이프라인(가지치기 → 양자화 → 허프만)으로 AlexNet을 35배, VGG를 49배까지 줄였다(정확도 유지). SqueezeNet에 적용하면 0.47MB, 510배 압축까지 갔다. 가지치기는 별도로 다룰 주제다.


  1. Han, Mao, Dally. Deep Compression: Compressing Deep Neural Networks with Pruning, Trained Quantization and Huffman Coding. ICLR 2016. ↩︎