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 brindar soporte de hardware para la inferencia con modelos cuantificados de TensorFlow Lite.
Resumen de especificaciones
Proporcionamos una especificación y solo podemos ofrecer algunas garantías de comportamiento si se siguen las especificaciones. También entendemos que diferentes hardware pueden tener preferencias y restricciones que pueden causar ligeras desviaciones al implementar las especificaciones que resultan en 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, hasta donde sabemos, 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\]
Los pesos por eje (también conocido como por canal en operaciones Conv) o por tensor están representados por valores de complemento a dos 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]
.
Existen otras excepciones para operaciones particulares que se documentan a continuación.
Entero con signo versus entero sin signo
La cuantización de TensorFlow Lite priorizará principalmente las herramientas y los núcleos para la cuantización int8
para 8 bits. Esto es para la conveniencia de que la cuantificación simétrica se represente mediante un punto cero igual a 0. Además, muchos backends tienen optimizaciones adicionales para la acumulación int8xint8
.
Por eje versus por tensor
La cuantizació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 segmento en la quantized_dimension
. La dimensión cuantificada 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 quantized_dimension
es el output_channel
de los pesos de las convoluciones, pero en teoría puede ser la dimensión que corresponde a cada producto escalar en la implementación del núcleo, 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 redactar 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
con signo [-128, 127]
. Muchas activaciones son de naturaleza asimétrica y un punto cero es una forma relativamente económica de alcanzar efectivamente un bit binario adicional de precisión. Dado que las activaciones sólo se multiplican por pesos constantes, el valor de punto cero constante se puede optimizar bastante.
Los pesos son simétricos: se obliga a tener un punto cero igual a 0. Los valores de peso se multiplican por los valores de entrada y activación dinámicos. Esto significa que existe 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 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, de la siguiente manera:
\(A\) es un \(m \times n\) matriz de activaciones cuantificadas.
\(B\) es un \(n \times p\) matriz de pesos cuantificados.
Considere multiplicar el \(j\)ª fila de \(A\), \(a_j\) por el \(k\)ª columna de\(B\), \(b_k\), ambos de longitud \(n\). Los valores enteros cuantificados y los valores de punto cero 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\]