다음 문서에서는 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]
가 있는 텐서 t
는 t
의 두 번째 차원에서 양자화됩니다.
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