Especificación de cuantificación de 8 bits de TensorFlow Lite

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 extra 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\]