El siguiente documento describe la especificación del esquema de cuantificación de 8 bits de TensorFlow Lite. Esto está destinado a ayudar a los desarrolladores de hardware a proporcionar soporte de hardware para inferencia con modelos cuantificados de TensorFlow Lite.
Resumen de especificaciones
Estamos proporcionando una especificación y solo podemos ofrecer algunas garantías de comportamiento si se siguen las especificaciones. También entendemos que el hardware diferente puede tener preferencias y restricciones que pueden causar ligeras desviaciones al implementar la especificación que dan como resultado implementaciones que no son exactas. Si bien eso puede ser aceptable en la mayoría de los casos (y proporcionaremos un conjunto de pruebas que, a nuestro leal saber y entender, incluyen tolerancias por operación que recopilamos de varios modelos), la naturaleza del aprendizaje automático (y el aprendizaje profundo en los casos más comunes). caso) hace imposible ofrecer garantías concretas.
La cuantificación de 8 bits se aproxima a los valores de punto flotante utilizando la siguiente fórmula.
Los pesos por eje (también conocido como por canal en operaciones de conv.) O por tensor están representados por int8
de complemento a dos de int8
en el rango [-127, 127]
con punto cero igual a 0. Las activaciones / entradas por tensor están representadas por int8
Valores de complemento a dos en el rango [-128, 127]
, con un punto cero en el rango [-128, 127]
.
Hay otras excepciones para operaciones particulares que se documentan a continuación.
Entero con signo frente a entero sin signo
La cuantificación de TensorFlow Lite priorizará principalmente las herramientas y los kernels para la cuantificación int8
para 8 bits. Esto es por la conveniencia de que la cuantificación simétrica se represente por un punto cero igual a 0. Además, muchos backends tienen optimizaciones adicionales para la acumulación de int8xint8
.
Por eje frente a por tensor
La cuantificación por tensor significa que habrá una escala y / o punto cero por tensor completo. La cuantificación por eje significa que habrá una escala y / o zero_point
por rebanada en la zero_point
quantized_dimension
. La dimensión cuantizada especifica la dimensión de la forma del tensor a la que corresponden las escalas y los puntos cero. Por ejemplo, un tensor t
, con dims=[4, 3, 2, 1]
con parámetros de cuantificación: scale=[1.0, 2.0, 3.0]
, zero_point=[1, 2, 3]
, quantization_dimension=1
se cuantificará en la segunda dimensión de 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
A menudo, la output_channel
quantized_dimension
es el output_channel
de output_channel
de los pesos de las convoluciones, pero en teoría puede ser la dimensión que corresponde a cada producto punto en la implementación del kernel, lo que permite una mayor granularidad de cuantificación sin implicaciones de rendimiento. Esto tiene grandes mejoras en la precisión.
TFLite tiene soporte por eje para un número creciente de operaciones. En el momento de este documento, existe soporte para Conv2d y DepthwiseConv2d.
Simétrico vs asimétrico
Las activaciones son asimétricas: pueden tener su punto cero en cualquier lugar dentro del rango int8
[-128, 127]
. Muchas activaciones son de naturaleza asimétrica y un punto cero es una forma relativamente económica de obtener efectivamente un bit binario adicional de precisión. Dado que las activaciones solo se multiplican por pesos constantes, el valor constante del punto cero se puede optimizar bastante.
Los pesos son simétricos: obligados a tener un punto cero igual a 0. Los valores de peso se multiplican por la entrada dinámica y los valores de activación. Esto significa que hay un costo de ejecución inevitable al multiplicar el punto cero del peso por el valor de activación. Al hacer cumplir que el punto cero es 0, podemos evitar este costo.
Explicación de las matemáticas: esto es similar a la sección 2.3 en arXiv: 1712.05877 , excepto por la diferencia de que permitimos que los valores de escala sean por eje. Esto se generaliza fácilmente, como sigue:
$ A $ es una matriz de $ m \ times n $ de activaciones cuantificadas.
$ B $ es una matriz de pesos cuantificados de $ n \ veces p $.
Considere la posibilidad de multiplicar la fila $ j $ ésima de $ A $, $ a_j $ por la columna $ k $ ésima de $ B $, $ b_k $, ambas de longitud $ n $. Los valores enteros cuantificados y los valores de punto cero son $ q_a $, $ z_a $ y $ q_b $, $ z_b $ respectivamente.
El término \(\sum_{i=0}^{n} q_{a}^{(i)} q_{b}^{(i)}\) es inevitable ya que realiza el producto escalar del valor de entrada y el valor de peso.
los
y
los términos se componen de constantes que permanecen iguales por invocación de inferencia y, por lo tanto, pueden precalcularse.
El término \(\sum_{i=0}^{n} q_{a}^{(i)} z_b\) debe calcularse en cada inferencia, ya que la activación cambia cada inferencia. Al hacer que los pesos sean simétricos, podemos eliminar el costo de este término.
int8 especificaciones cuantificadas del operador
A continuación, describimos los requisitos de cuantificación para nuestros kernels 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