Le document suivant décrit les spécifications du schéma de quantification 8 bits de TensorFlow Lite. Ceci est destiné à aider les développeurs de matériel à fournir une prise en charge matérielle pour l'inférence avec les modèles TensorFlow Lite quantifiés.
Résumé des spécifications
Nous fournissons une spécification et nous ne pouvons fournir des garanties de comportement que si la spécification est suivie. Nous comprenons également que différents matériels peuvent avoir des préférences et des restrictions qui peuvent entraîner de légers écarts lors de l'implémentation de la spécification, ce qui entraîne des implémentations qui ne sont pas exactes au bit près. Alors que cela peut être acceptable dans la plupart des cas (et nous fournirons une série de tests qui, à notre connaissance, incluent les tolérances par opération que nous avons recueillies à partir de plusieurs modèles), la nature de l'apprentissage automatique (et l'apprentissage en profondeur dans les cas les plus courants cas) rend impossible toute garantie ferme.
La quantification sur 8 bits se rapproche des valeurs à virgule flottante à l'aide de la formule suivante.
\[real\_value = (int8\_value - zero\_point) \times scale\]
Par axe (aka par canal dans ops Conv) ou par des poids tenseur sont représentés par int8
valeurs complément dans l'intervalle de deux [-127, 127]
avec point zéro égal à 0. activations par tenseur / entrées sont représentées par int8
deux des valeurs du complément dans l'intervalle [-128, 127]
, avec un point zéro dans la gamme [-128, 127]
.
Il existe d'autres exceptions pour des opérations particulières qui sont documentées ci-dessous.
Entier signé vs entier non signé
Tensorflow quantification Lite principalement Prioriser l' outillage et les noyaux pour int8
quantification pour 8 bits. Ceci est pour la commodité de quantification symétrique étant représentée par point zéro égal à 0. De plus nombreux backends ont des optimisations supplémentaires pour int8xint8
accumulation.
Par axe vs par tenseur
La quantification par tenseur signifie qu'il y aura une échelle et/ou un point zéro par tenseur entier. Par axe moyen de quantification qu'il y aura une échelle et / ou zero_point
par tranche dans le quantized_dimension
. La dimension quantifiée spécifie la dimension de la forme du Tensor à laquelle correspondent les échelles et les points zéro. Par exemple, un tenseur t
, avec dims=[4, 3, 2, 1]
avec params de quantification: scale=[1.0, 2.0, 3.0]
, zero_point=[1, 2, 3]
, quantization_dimension=1
seront quantifiés à travers la seconde dimension 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
Souvent, le quantized_dimension
est le output_channel
des poids des circonvolutions, mais en théorie , il peut être la dimension qui correspond à chaque point produit dans la mise en œuvre du noyau, ce qui permet une granularité plus de quantification sans impact sur les performances. Cela a de grandes améliorations à la précision.
TFLite prend en charge par axe un nombre croissant d'opérations. Au moment de la rédaction de ce document, il existe un support pour Conv2d et DepthwiseConv2d.
Symétrique vs asymétrique
Activations sont asymétriques: ils peuvent avoir leur point zéro partout dans le signé int8
gamme [-128, 127]
. De nombreuses activations sont de nature asymétrique et un point zéro est un moyen relativement peu coûteux d'atteindre efficacement un bit binaire supplémentaire de précision. Étant donné que les activations ne sont multipliées que par des poids constants, la valeur constante du point zéro peut être optimisée assez fortement.
Les poids sont symétriques : forcés d'avoir un point zéro égal à 0. Les valeurs de poids sont multipliées par les valeurs d'entrée et d'activation dynamiques. Cela signifie qu'il y a un coût d'exécution inévitable pour multiplier le point zéro du poids par la valeur d'activation. En imposant que le point zéro est 0, nous pouvons éviter ce coût.
Explication des mathématiques: ce qui est similaire à la section 2.3 arXiv: 1712,05877 , à l' exception de la différence que nous permettons aux valeurs d'échelle pour être par axe. Cela se généralise facilement, comme suit :
\(A\) est un \(m \times n\) matrice d'activations quantifiées.
\(B\) est un \(n \times p\) matrice de poids quantifiés.
Considérons multipliant le \(j\)ième ligne de \(A\), \(a_j\) par le \(k\)ième colonne de\(B\), \(b_k\), à la fois de la longueur \(n\). Les valeurs entières et quantifiées points zéro valeurs sont \(q_a\), \(z_a\) et \(q_b\), \(z_b\) respectivement.
\[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\]
Le \(\sum_{i=0}^{n} q_{a}^{(i)} q_{b}^{(i)}\) terme est inévitable car il est d' effectuer le produit scalaire de la valeur d'entrée et la valeur de poids.
Les \(\sum_{i=0}^{n} q_{b}^{(i)} z_a\) et \(\sum_{i=0}^{n} z_a z_b\) termes sont composées de constantes qui restent les mêmes par appel d'inférence, et peut donc être pré-calculée.
Le \(\sum_{i=0}^{n} q_{a}^{(i)} z_b\) doit être calculée chaque inférence terme , puisque l'activation change chaque inférence. En imposant des poids symétriques, nous pouvons supprimer le coût de ce terme.
Spécifications de l'opérateur quantifié int8
Ci-dessous, nous décrivons les exigences de quantification pour nos noyaux tflite int8 :
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