Следующий документ описывает спецификацию 8-битной схемы квантования TensorFlow Lite. Это предназначено для помощи разработчикам оборудования в обеспечении аппаратной поддержки для логического вывода с квантованными моделями TensorFlow Lite.
Краткое описание спецификации
Мы предоставляем спецификацию, и мы можем предоставить некоторые гарантии поведения только в том случае, если она соблюдается. Мы также понимаем, что различное оборудование может иметь настройки и ограничения, которые могут вызывать небольшие отклонения при реализации спецификации, что приводит к реализациям, которые не являются точными по размеру битов. Хотя это может быть приемлемо в большинстве случаев (и мы предоставим набор тестов, которые, насколько нам известно, включают допуски для каждой операции, которые мы собрали из нескольких моделей), природа машинного обучения (и глубокое обучение в наиболее распространенных case) делает невозможным предоставление каких-либо жестких гарантий.
8-битное квантование приближает значения с плавающей запятой по следующей формуле.
\[real\_value = (int8\_value - zero\_point) \times scale\]
Пер-оси (он же для каждого канала в Conv OPS) или за тензор вес представлены int8
дополнением до двух значений в диапазоне [-127, 127]
с нулевой точкой равен 0. Пер-тензор активаций / входы представлены int8
два значение в дополнении в диапазоне [-128, 127]
, с нулевой точкой в диапазоне [-128, 127]
.
Существуют и другие исключения для определенных операций, которые описаны ниже.
Целое число со знаком против целого числа без знака
TensorFlow Lite квантования , в первую очередь будет Приоритетность оснастки и ядра для int8
квантования для 8-битных. Это для удобства симметричного квантования представлены нулевой точки , равными 0. Кроме того многие движки имеют дополнительные оптимизации для int8xint8
накопления.
По оси против тензора
Пертензорное квантование означает, что будет одна шкала и / или нулевая точка на весь тензор. Пер-оси средства квантования , что будет один масштаб и / или zero_point
за срез в quantized_dimension
. Квантованное измерение определяет размерность формы Тензор, которой соответствуют шкалы и нулевые точки. Так , например, тензорной t
, с dims=[4, 3, 2, 1]
с квантования Params: scale=[1.0, 2.0, 3.0]
, zero_point=[1, 2, 3]
, quantization_dimension=1
будет квантуется по второе измерение 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, мы можем избежать этой стоимости.
Объяснение математики: это похоже на раздел 2.3 в Arxiv: 1712.05877 , за ту разницу , что мы допускаем значение масштаба , чтобы быть за исключение оси. Это легко обобщается следующим образом:
\(A\) является \(m \times n\) матрица квантованных активаций.
\(B\) является \(n \times p\) матрица квантованных весов.
Рассмотрим умножения \(j\)- й ряд \(A\), \(a_j\) по \(k\)- й столбец\(B\), \(b_k\), обе длины \(n\). Квантованные целые значения и нулевые точки значения \(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-tensor
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