El siguiente documento describe la especificación del esquema de cuantificación de 8 bits de TensorFlow Lite. Esto tiene como objetivo 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 en bits. 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.
\[real\_value = (int8\_value - zero\_point) \times scale\]
Per-eje (aka por canal en ops Conv) o per-tensor pesos están representados por int8
de dos valores del complemento en el intervalo [-127, 127]
con punto cero igual a 0. activaciones Per-tensor / entradas están representados por int8
valores de complemento a dos en el intervalo [-128, 127]
, con un punto cero en el rango de [-128, 127]
.
Hay otras excepciones para operaciones particulares que se documentan a continuación.
Entero con signo frente a entero sin signo
TensorFlow Lite cuantización herramientas principalmente Priorizar y granos para int8
cuantificación para 8 bits. Esto es para la comodidad de cuantificación simétrica siendo representado por el punto cero igual a 0. Además muchos backends tener optimizaciones adicionales para int8xint8
acumulación.
Por eje vs por tensor
La cuantificación por tensor significa que habrá una escala y / o punto cero por tensor completo. Per ejes medios de cuantificación de que habrá una escala y / o zero_point
por rebanada en el 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 params de cuantificación: scale=[1.0, 2.0, 3.0]
, zero_point=[1, 2, 3]
, quantization_dimension=1
se cuantizará través 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, el quantized_dimension
es la output_channel
de los pesos de circunvoluciones, pero en teoría puede ser la dimensión que corresponde a cada punto-producto en la implementación del núcleo, lo que permite más 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
Activaciones son asimétricos: pueden tener su punto cero en cualquier lugar dentro de la firma int8
gama [-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 tiempo 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 los cálculos: esto es similar a la sección 2.3 en arXiv: 1712.05877 , con la diferencia de que permitimos que los valores de la escala a ser por eje. Esto se generaliza fácilmente, como sigue:
\(A\) es un \(m \times n\) matriz de activaciones cuantificados.
\(B\) es un \(n \times p\) matriz de pesos cuantificados.
Considere multiplicando el \(j\)º fila de \(A\), \(a_j\) por el \(k\)ésima columna de\(B\), \(b_k\), ambos de longitud \(n\). Los valores enteros cuantificados y cero puntos valores son \(q_a\), \(z_a\) y \(q_b\), \(z_b\) respectivamente.
\[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\]
El \(\sum_{i=0}^{n} q_{a}^{(i)} q_{b}^{(i)}\) plazo es inevitable ya que está realizando el producto escalar del valor de entrada y el valor de peso.
Los \(\sum_{i=0}^{n} q_{b}^{(i)} z_a\) y \(\sum_{i=0}^{n} z_a z_b\) términos se componen de constantes que siguen siendo los mismos por invocación inferencia, y por lo tanto puede ser pre-calculado.
El \(\sum_{i=0}^{n} q_{a}^{(i)} z_b\) término necesita ser computado 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 núcleos 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