Le document suivant décrit les spécifications de la version 8 bits de LiteRT schéma de quantification. L'objectif est d'aider les développeurs de matériel à fournir support matériel pour l'inférence avec des modèles LiteRT quantifiés
Résumé des spécifications
Nous fournissons des spécifications et ne pouvons garantir que certaines de sécurité si la spécification est respectée. Nous sommes aussi conscients que différents matériels peuvent présentent des préférences et des restrictions qui peuvent entraîner de légers écarts la spécification qui entraîne des implémentations qui ne sont pas exactes. Toutefois, cela peut être acceptable dans la plupart des cas (et nous vous fournirons des tests qui, à notre connaissance, incluent des tolérances par opération que nous collectées à partir de plusieurs modèles), la nature du machine learning (et du deep learning dans le cas le plus courant), il est impossible de fournir des garanties strictes.
La quantification sur 8 bits se rapproche des valeurs à virgule flottante à l'aide des éléments suivants : formule.
\[real\_value = (int8\_value - zero\_point) \times scale\]
Les pondérations par axe (ou canal dans les opérations de conversion) ou par Tensor sont représentées par
int8
valeurs du complément de deux dans la plage [-127, 127]
avec un point zéro égal à
à 0. Les activations/entrées par Tensor sont représentées par le complément int8
de deux
des valeurs comprises dans l'intervalle [-128, 127]
et le point zéro dans l'intervalle [-128, 127]
.
D'autres exceptions sont prévues pour des opérations particulières, qui sont décrites ci-dessous.
Entier signé et entier non signé
La quantification LiteRT priorisera principalement les outils et les noyaux pour
Quantification int8
pour 8 bits. Ceci est pour la commodité
de l’utilisation
quantification représentée par un point zéro égal à 0. En outre, de nombreux
les backends comportent des optimisations supplémentaires pour l'accumulation de int8xint8
.
Par axe ou par Tensor
La quantification par Tensor signifie qu'il y a une échelle et/ou un point zéro
l'intégralité du Tensor. La quantification par axe signifie qu'il y aura une échelle et/ou
zero_point
par tranche dans quantized_dimension
. La dimension quantifiée
spécifie la dimension de la forme du Tensor que les échelles et les points zéro
correspondent. Par exemple, un Tensor t
, avec dims=[4, 3, 2, 1]
avec
paramètres de quantification: scale=[1.0, 2.0, 3.0]
, zero_point=[1, 2, 3]
,
quantization_dimension=1
sera quantifié pour la deuxième 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, quantized_dimension
correspond à l'output_channel
des pondérations de
mais en théorie, il peut s'agir de la dimension correspondant à chaque
produit scalaire dans l'implémentation du noyau, ce qui permet une plus grande précision de quantification
sans incidence sur les performances. Cela permet d'améliorer considérablement la précision.
TFLite est compatible avec un nombre croissant d'opérations. Au moment de ce document, Conv2d et DepthwiseConv2d sont acceptés.
Symétrique et asymétrique
Les activations sont asymétriques: elles peuvent avoir leur point zéro n'importe où dans le
int8
(plage [-128, 127]
) signée. De nombreuses activations sont de nature asymétrique
Le point 0 est un moyen relativement peu coûteux d'obtenir efficacement
bit binaire de précision. Comme les activations ne sont multipliées que par une constante
la valeur constante du point zéro peut
être optimisée de façon assez poussée.
Les pondérations sont symétriques: le point 0 est forcé et égal à 0. Les valeurs de pondération sont multipliée par les valeurs d'entrée dynamique et d'activation. Cela signifie qu'il existe coût d'exécution inévitable de la multiplication du point 0 de la pondération par le la valeur d'activation. En exigeant que le point zéro soit égal à 0, nous pouvons éviter ce coût.
Explication du calcul: elle est similaire à la section 2.3 arXiv:1712.05877, à l'exception de la différence que nous autorisons les valeurs d'échelle par axe. Cela se généralise facilement, comme ce qui suit:
$A$ est une matrice $m \times n$ d'activations quantifiées.
$B$ est une matrice $n \times p$ de pondérations quantifiées.
En multipliant la $j$th ligne de $A$, $a_j$ par la $k$th colonne de
$B$, $b_k$, les deux de longueur $n$. Les valeurs entières quantifiées
les valeurs zéro point sont respectivement $q_a$, $z_a$ et $q_b$, $z_b$.
\[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 puisqu'il est en effectuant le produit scalaire de la valeur d'entrée et de la valeur de pondération.
Les \(\sum_{i=0}^{n} q_{b}^{(i)} z_a\) et \(\sum_{i=0}^{n} z_a z_b\) Conditions d'utilisation sont constitué de constantes qui restent identiques par appel d'inférence, et peuvent donc être précalculées.
Le \(\sum_{i=0}^{n} q_{a}^{(i)} z_b\) terme doit être calculé à chaque inférence puisque l'activation change chaque inférence. En faisant en sorte que les pondérations symétrique, nous pouvons supprimer le coût de ce terme.
spécifications d'opérateur quantifiée int8
Nous décrivons ci-dessous 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-axis (dim = 0)
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