LiteRT 8비트 양자화 사양

다음 문서에서는 LiteRT의 8비트 양자화 스키마 사양을 간략하게 설명합니다. 이는 하드웨어 개발자가 양자화된 LiteRT 모델을 사용한 추론을 위한 하드웨어 지원을 제공하는 데 도움을 주기 위한 것입니다.

사양 요약

사양을 제공하며 사양을 따르는 경우에만 동작에 관한 보증을 제공할 수 있습니다. 또한 다양한 하드웨어에는 사양을 구현할 때 약간의 편차를 일으켜 비트 정확하지 않은 구현이 발생할 수 있는 환경설정과 제한사항이 있을 수 있습니다. 대부분의 경우 허용될 수 있지만 (Google에서는 여러 모델에서 수집한 작업별 허용 오차를 포함하는 테스트 모음을 제공할 예정임) 머신러닝 (가장 일반적인 경우 딥러닝)의 특성상 확실한 보장을 제공할 수는 없습니다.

8비트 양자화는 다음 공식을 사용하여 부동 소수점 값을 근사합니다.

\[real\_value = (int8\_value - zero\_point) \times scale\]

축별 (컨볼루션 작업의 채널별) 또는 텐서별 가중치는 0과 동일한 영점과 함께 [-127, 127] 범위의 int8 2의 보수 값으로 표현됩니다. 텐서별 활성화/입력은 [-128, 127] 범위의 2의 보수 값으로 표현되며, 0점은 [-128, 127] 범위에 있습니다.int8

아래에 설명된 특정 작업에 대한 다른 예외도 있습니다.

부호 있는 정수와 부호 없는 정수 비교

LiteRT 양자화는 주로 8비트의 int8 양자화를 위한 도구와 커널에 우선순위를 둡니다. 이는 0과 같은 0점으로 표현되는 대칭 양자화의 편의를 위한 것입니다. 또한 많은 백엔드에는 int8xint8 누적을 위한 추가 최적화가 있습니다.

축별 vs 텐서별

텐서별 양자화는 전체 텐서당 하나의 스케일 또는 제로 포인트가 있음을 의미합니다. 축당 양자화는 quantized_dimension의 슬라이스당 하나의 스케일 또는 zero_point가 있음을 의미합니다. 양자화된 차원은 스케일과 0점이 해당하는 텐서 모양의 차원을 지정합니다. 예를 들어 양자화 매개변수 scale=[1.0, 2.0, 3.0], zero_point=[1, 2, 3], quantization_dimension=1이 있는 dims=[4, 3, 2, 1]이 있는 텐서 tt의 두 번째 차원에서 양자화됩니다.

t[:, 0, :, :] will have scale[0]=1.0, zero_point[0]=1
t[:, 1, :, :] will have scale[1]=2.0, zero_point[1]=2
t[:, 2, :, :] will have scale[2]=3.0, zero_point[2]=3

일반적으로 quantized_dimension는 컨볼루션 가중치의 output_channel이지만 이론적으로는 커널 구현의 각 점곱에 해당하는 차원일 수 있으므로 성능에 영향을 주지 않고 더 많은 양자화 세부사항을 허용합니다. 이렇게 하면 정확도가 크게 향상됩니다.

TFLite는 증가하는 수의 작업에 대해 축별 지원을 제공합니다. 이 문서를 작성하는 시점에는 Conv2d와 DepthwiseConv2d가 지원됩니다.

대칭과 비대칭

활성화는 비대칭입니다. 활성화의 0점은 부호가 있는 int8 범위 [-128, 127] 내에 있을 수 있습니다. 많은 활성화는 본질적으로 비대칭이며 0점은 최대 1비트의 정밀도를 효과적으로 얻을 수 있는 비교적 저렴한 방법입니다. 활성화는 상수 가중치로만 곱해지므로 상수 0점 값을 상당히 많이 최적화할 수 있습니다.

가중치는 대칭입니다. 0과 동일한 0점이 강제됩니다. 가중치 값은 동적 입력 및 활성화 값과 곱해집니다. 이는 가중치의 0점을 활성화 값과 곱하는 런타임 비용이 불가피하다는 의미입니다. 영점을 0으로 강제하면 이 비용을 방지할 수 있습니다.

수학적 설명: 이는 arXiv:1712.05877의 섹션 2.3과 유사하지만, 스케일 값이 축별로 허용된다는 점이 다릅니다. 다음과 같이 쉽게 일반화할 수 있습니다.

$A$ 는 양자화된 활성화의 $m \times n$ 행렬입니다.
$B$ 는 양자화된 가중치의 $n \times p$ 행렬입니다.
길이가 $n$인 $A$의 $j$ 번째 행 $a_j$에 $B$의 $k$번째 열 $b_k$를 곱한다고 가정해 보겠습니다. 양자화된 정수 값과 0점 값은 각각 $q_a$, $z_a$ 와 $q_b$, $z_b$ 입니다.

\[a_j \cdot b_k = \sum_{i=0}^{n} a_{j}^{(i)} b_{k}^{(i)} = \sum_{i=0}^{n} (q_{a}^{(i)} - z_a) (q_{b}^{(i)} - z_b) = \sum_{i=0}^{n} q_{a}^{(i)} q_{b}^{(i)} - \sum_{i=0}^{n} q_{a}^{(i)} z_b - \sum_{i=0}^{n} q_{b}^{(i)} z_a + \sum_{i=0}^{n} z_a z_b\]

\(\sum_{i=0}^{n} q_{a}^{(i)} q_{b}^{(i)}\) 항은 입력 값과 가중치 값의 점곱을 수행하므로 피할 수 없습니다.

\(\sum_{i=0}^{n} q_{b}^{(i)} z_a\) 및 \(\sum_{i=0}^{n} z_a z_b\) 항은 추론 호출마다 동일하게 유지되는 상수로 구성되므로 미리 계산할 수 있습니다.

활성화는 추론마다 변경되므로 \(\sum_{i=0}^{n} q_{a}^{(i)} z_b\) 항목은 추론마다 계산해야 합니다. 가중치가 대칭이 되도록 강제하면 이 항의 비용을 삭제할 수 있습니다.

int8 양자화 연산자 사양

아래에서는 int8 tflite 커널의 양자화 요구사항을 설명합니다.

ADD
  Input 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
  Input 1:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
  Output 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor

AVERAGE_POOL_2D
  Input 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
  Output 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
  restriction: Input and outputs must all have same scale/zero_point

CONCATENATION
  Input ...:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
  Output 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
  restriction: Input and outputs must all have same scale/zero_point

CONV_2D
  Input 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
  Input 1 (Weight):
    data_type  : int8
    range      : [-127, 127]
    granularity: per-axis (dim = 0)
    restriction: zero_point = 0
  Input 2 (Bias):
    data_type  : int32
    range      : [int32_min, int32_max]
    granularity: per-axis
    restriction: (scale, zero_point) = (input0_scale * input1_scale[...], 0)
  Output 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor

DEPTHWISE_CONV_2D
  Input 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
  Input 1 (Weight):
    data_type  : int8
    range      : [-127, 127]
    granularity: per-axis (dim = 3)
    restriction: zero_point = 0
  Input 2 (Bias):
    data_type  : int32
    range      : [int32_min, int32_max]
    granularity: per-axis
    restriction: (scale, zero_point) = (input0_scale * input1_scale[...], 0)
  Output 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor

FULLY_CONNECTED
  Input 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
  Input 1 (Weight):
    data_type  : int8
    range      : [-127, 127]
    granularity: per-axis (dim = 0)
    restriction: zero_point = 0
  Input 2 (Bias):
    data_type  : int32
    range      : [int32_min, int32_max]
    granularity: per-tensor
    restriction: (scale, zero_point) = (input0_scale * input1_scale[...], 0)
  Output 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor

L2_NORMALIZATION
  Input 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
  Output 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
    restriction: (scale, zero_point) = (1.0 / 128.0, 0)

LOGISTIC
  Input 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
  Output 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
    restriction: (scale, zero_point) = (1.0 / 256.0, -128)

MAX_POOL_2D
  Input 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
  Output 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
  restriction: Input and outputs must all have same scale/zero_point

MUL
  Input 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
  Input 1:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
  Output 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor

RESHAPE
  Input 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
  Output 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
  restriction: Input and outputs must all have same scale/zero_point

RESIZE_BILINEAR
  Input 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
  Output 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
  restriction: Input and outputs must all have same scale/zero_point

SOFTMAX
  Input 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
  Output 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
    restriction: (scale, zero_point) = (1.0 / 256.0, -128)

SPACE_TO_DEPTH
  Input 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
  Output 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
  restriction: Input and outputs must all have same scale/zero_point

TANH
  Input 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
  Output 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
    restriction: (scale, zero_point) = (1.0 / 128.0, 0)

PAD
  Input 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
  Output 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
  restriction: Input and outputs must all have same scale/zero_point

GATHER
  Input 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
  Output 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
  restriction: Input and outputs must all have same scale/zero_point

BATCH_TO_SPACE_ND
  Input 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
  Output 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
  restriction: Input and outputs must all have same scale/zero_point

SPACE_TO_BATCH_ND
  Input 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
  Output 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
  restriction: Input and outputs must all have same scale/zero_point

TRANSPOSE
  Input 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
  Output 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
  restriction: Input and outputs must all have same scale/zero_point

MEAN
  Input 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
  Output 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor

SUB
  Input 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
  Input 1:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
  Output 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor

SUM
  Input 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
  Output 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor

SQUEEZE
  Input 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
  Output 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
  restriction: Input and outputs must all have same scale/zero_point

LOG_SOFTMAX
  Input 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
  Output 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
    restriction: (scale, zero_point) = (16.0 / 256.0, 127)

MAXIMUM
  Input 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
  Output 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
  restriction: Input and outputs must all have same scale/zero_point

ARG_MAX
  Input 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor

MINIMUM
  Input 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
  Output 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
  restriction: Input and outputs must all have same scale/zero_point

LESS
  Input 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
  Input 1:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor

PADV2
  Input 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
  Output 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
  restriction: Input and outputs must all have same scale/zero_point

GREATER
  Input 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
  Input 1:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor

GREATER_EQUAL
  Input 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
  Input 1:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor

LESS_EQUAL
  Input 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
  Input 1:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor

SLICE
  Input 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
  Output 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
  restriction: Input and outputs must all have same scale/zero_point

EQUAL
  Input 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
  Input 1:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor

NOT_EQUAL
  Input 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
  Input 1:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor

SHAPE
  Input 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor

QUANTIZE (Requantization)
  Input 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor
  Output 0:
    data_type  : int8
    range      : [-128, 127]
    granularity: per-tensor

참조

arXiv:1712.05877