모든 이야기

정수 누산값을 다음 layer의 scale로 바꾸는 조건

정수 MAC이 끝난 뒤 몇 bit를 오른쪽으로 옮기면 다음 layer의 입력을 만들 수 있을까. 어떤 양자화에서는 가능하지만, scale이 임의의 값이거나 정수 0이 실제 0을 뜻하지 않으면 부족하다. quantization rescaling은 큰 누산값을 작은 정수로 바꾸는 동작과, 그 정수가 뜻하는 실제 값의 기준을 맞추는 동작을 함께 다룬다.

정수 코드에는 scale과 zero-point가 붙는다

affine 양자화의 표현을 x=sx(qx−zx)로 쓰자. qx는 저장한 정수 코드, zx는 실제 0을 표현하는 코드, sx는 양의 scale이다. weight도 w=sw(qw−zw)로 표현한다. 같은 dot product의 reduction 안에서 두 scale이 일정하다면 bias가 없는 출력은 다음과 같다.

S = Σk (qx[k]−zx)(qw[k]−zw)
y = sx × sw × S
qy = clip(round((sx×sw/sy)×S) + zy)

마지막 식은 출력 scale sy와 zero-point zy를 가진 정수 코드로 바꾸는 표현이다. 여기서 clip 범위와 rounding 규칙은 따로 정해야 한다. 이 식이 복원하는 것은 입력 양자화 표현으로 계산한 값이며, 원래 float 모델과 완전히 동일한 값이라는 보증은 아니다. Jacob 등의 원 논문은 affine 표현과 행렬 곱의 multiplier를 이 관계로 전개한다. 원 논문, PDF 3쪽 식 1–5

zero-point를 생략하면 곱 자체가 달라진다

unsigned 8bit를 쓰는 자체 예를 정하자. activation 코드는 [12,8], zx=10, sx=0.1이고 weight 코드는 [7,9], zw=5, sw=0.2다. 실제로 나타낸 두 activation은 [0.2,−0.2], weight는 [0.4,0.8]이다.

보정한 정수 dot product는 2×2+(−2)×4=−4다. 실제 값은 0.1×0.2×(−4)=−0.08이다. 출력 sy=0.05, zy=100, nearest-even rounding과 [0,255] clip을 정하면 qy=round(−1.6)+100=98이다. 복원 값은 0.05×(98−100)=−0.10이다. 원래 −0.08과의 차이 0.02는 이 예에서 마지막 rounding으로 생겼다.

반대로 저장 코드끼리만 곱하면 12×7+8×9=156이다. scale만 곱해도 3.12가 되어 원하는 −0.08과 전혀 다르다. zero-point는 결과 뒤에서 적당한 상수를 빼면 된다는 뜻이 아니다. 전개하면 row·column의 합에 붙은 보정 항이 나오고, 그 값은 두 피연산자의 정의에 달려 있다.

이 unsigned 예는 현재 LiteRT int8 weight 규칙을 재현한 것이 아니다. LiteRT 문서는 activation의 비대칭 zero-point와, weight의 zero-point를 0으로 제한하는 대칭 규칙을 구분한다. 후자는 동적인 activation 합에 곱해야 하는 weight zero-point 항을 제거한다. LiteRT, Symmetric vs asymmetric

scale을 밖으로 뺄 수 있는 범위를 확인한다

output channel마다 weight scale이 다른 경우에도, 한 output의 reduction 안에서는 해당 channel의 scale이 일정할 수 있다. 이때 channel별 multiplier sx×sw[c]/sy를 마지막에 적용할 수 있다. 모든 channel에 multiplier 하나를 공유해도 된다는 뜻은 아니다.

scale이 reduction의 k마다 바뀌면 상황이 다르다. zero-point가 0이고 정수 곱 두 항이 각각 1인데, 항의 scale 곱이 1과 2라면 실제 합은 1+2=3이다. 정수 곱을 [2,0]으로 바꾸면 정수 합은 똑같이 2지만 실제 합은 2다. 같은 정수 누산값을 보고 공통 multiplier 하나로 두 답을 구분할 수 없다. 단일 공통 scale로 모든 입력의 dot product를 나타내려면 항별 scale이 일정하거나 먼저 같은 기준으로 변환되는 조건이 필요하다.

bias를 정수 누산값에 더하려면 bias의 scale도 누산값의 sx×sw에 맞아야 한다. Jacob 등의 구현 설명과 LiteRT의 convolution bias 규칙이 이 연결을 명시한다. 원 논문, PDF 4쪽 2.4절

shift를 고르기 전에 수치 계약을 고른다

이번 multiplier는 0.4다. 오른쪽 shift만으로 표현하는 1/2, 1/4 등의 값과 같지 않다. 정수 multiplier와 shift로 근사하는 구현은 multiplier의 정밀도와 부호 있는 값의 rounding을 정의한다. −1.6을 0 쪽으로 잘라 −1로 만드는 규칙은 앞에서 정한 nearest-even 결과 −2와 다르다. tie 처리도 실제 reference kernel과 맞추어야 한다.

누산 폭과 saturation을 적용하는 시점도 확인한다. 중간 누산에서 잘라 버린 정보를 마지막 rescaling이 되살릴 수는 없다. scaling 전 충분한 범위를 유지하는 조건과 최종 output 범위를 넘었을 때의 clip을 구분한다. 모든 작은 입력이 같은 부호로 더해지는 경계도 포함해 범위를 계산한다.

rescaling을 검토할 때는 정수 bit 수만 적지 않는다. zero-point 보정, scale이 일정한 reduction 범위, bias의 기준과 rounding·clip 순서를 같이 기록한다. 이 계약을 식에서 정수 코드까지 따라가면 마지막 scale 적용이 유효한 조건과 단순 shift로 대신할 수 없는 조건을 정확하게 나눌 수 있다.