TensorFlow Lite 8비트 양자화 사양

다음 문서에서는 TensorFlow Lite의 8비트 양자화 체계에 관한 사양을 간략하게 설명합니다. 이는 하드웨어 개발자가 양자화된 TensorFlow Lite 모델을 사용한 추론을 위한 하드웨어 지원을 제공하도록 지원하기 위한 것입니다.

사양 요약

Google은 사양을 제공하고 있으며, 사양을 준수하는 경우에만 동작을 일부 보장할 수 있습니다. 또한 Google은 다양한 하드웨어에 사양을 구현할 때 약간의 편차를 일으킬 수 있는 환경설정과 제한사항이 있을 수 있으며, 이로 인해 비트 정밀하지 않은 구현을 초래할 수 있음을 잘 알고 있습니다. 대부분의 경우 허용될 수 있지만 (Google이 아는 한도 내에서 여러 모델에서 수집한 작업당 허용 오차를 포함하는 일련의 테스트를 제공할 예정임) 머신러닝 (가장 일반적인 경우 딥 러닝)의 특성으로 인해 확실한 보장을 제공할 수 없습니다.

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

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

축당 (전환 작업에서 채널당이라고도 함) 또는 텐서당 가중치는 0포인트가 0인 범위 [-127, 127]에 있는 int8 2의 보수 값으로 표현됩니다. 텐서당 활성화/입력은 [-128, 127] 범위에서 int8 2의 보수 값으로 표현되며 범위 [-128, 127]에 0점이 있습니다.

아래에 설명된 특정 작업에는 그 밖의 예외가 있습니다.

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

TensorFlow Lite 양자화는 주로 8비트용 int8 양자화를 위한 도구와 커널에 우선순위를 둡니다. 이는 대칭 양자화를 0과 동일한 0포인트로 표현하기 위한 편의를 위한 것입니다. 또한 많은 백엔드에는 int8xint8 누적을 위한 추가 최적화가 있습니다.

축당과 텐서당 비교

텐서당 양자화는 전체 텐서당 하나의 확장 또는 제로포인트가 있음을 의미합니다. 축당 양자화는 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가 지원됩니다.

대칭 대 비대칭

활성화는 비대칭입니다. 즉, 부호 있는 int8 범위 [-128, 127] 내 어느 곳에서나 0점을 가질 수 있습니다. 많은 활성화가 본질적으로 비대칭적이며 제로포인트는 비교적 저렴하게 추가적인 바이너리의 정밀도를 얻을 수 있는 방법입니다. 활성화는 일정한 가중치로만 곱하기 때문에 상수 0점 값은 상당히 많이 최적화할 수 있습니다.

가중치가 대칭입니다. 0포인트가 0과 같아야 합니다. 가중치 값에는 동적 입력 값과 활성화 값이 곱해집니다. 즉, 가중치의 0포인트에 활성화 값을 곱하는 데 불가피한 런타임 비용이 발생합니다. 0포인트를 0이 되도록 적용하면 이 비용을 방지할 수 있습니다.

수학 설명: 배율 값이 축별로 허용된다는 점을 제외하면 arXiv:1712.05877의 섹션 2.3과 유사합니다. 이는 다음과 같이 쉽게 일반화됩니다.

$A$ 는 양자화 활성화의 $m \times n$ 행렬입니다.
$B$ 는 양자화 가중치의 $n \times p$ 행렬입니다.
$A$, $a_j$의 $j$ 번째 행에 길이 $n$의 $k$번째 열($B$, $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