Il seguente documento descrive le specifiche per lo schema di quantizzazione a 8 bit di TensorFlow Lite. È pensata per aiutare gli sviluppatori hardware a fornire supporto hardware per l'inferenza con i modelli quantizzati TensorFlow Lite.
Riepilogo delle specifiche
Stiamo fornendo una specifica e possiamo fornire alcune garanzie sul comportamento solo se le specifiche vengono rispettate. Comprendiamo inoltre che diversi hardware potrebbero avere preferenze e restrizioni che potrebbero causare lievi deviazioni durante l'implementazione delle specifiche che generano implementazioni non precise in base al bit. Mentre ciò può essere accettabile nella maggior parte dei casi (e forniremo una serie di test che, per quanto è in nostro possesso, includono le tolleranze per operazione che abbiamo raccolto da diversi modelli), la natura del machine learning (e del deep learning nel caso più comune) rende impossibile fornire garanzie difficili.
La quantizzazione a 8 bit approssima i valori in virgola mobile utilizzando la seguente formula.
\[real\_value = (int8\_value - zero\_point) \times scale\]
Le ponderazioni per asse (noto anche per canale nelle operazioni di conversione) o per tensore sono rappresentate da
int8
valori complementari di due nell'intervallo [-127, 127]
con punto zero uguale
a 0. Gli input/attivazioni per tensore sono rappresentati dai valori di int8
in complemento a due nell'intervallo [-128, 127]
, con un punto zero nell'intervallo [-128, 127]
.
Esistono altre eccezioni per operazioni particolari documentate di seguito.
Numero intero firmato e numero intero senza segno
La quantizzazione di TensorFlow Lite darà principalmente la priorità a strumenti e kernel per la quantizzazione di int8
a 8 bit. per comodità della quantizzazione simmetrica
rappresentata da un punto zero uguale a 0. Inoltre, molti backend presentano ottimizzazioni aggiuntive per l'accumulo di int8xint8
.
Confronto per asse e per tensore
La quantizzazione per tensore indica che ci sarà una scala e/o un punto zero per
intero tensore. La quantizzazione per asse significa che ci sarà una scala e/o zero_point
per sezione in quantized_dimension
. La dimensione quantizzata specifica la dimensione della forma di Tensor a cui corrispondono le scale e i punti zero. Ad esempio, un tensore t
, con dims=[4, 3, 2, 1]
con
parametri di quantizzazione: scale=[1.0, 2.0, 3.0]
, zero_point=[1, 2, 3]
,
quantization_dimension=1
verrà quantizzato nella seconda dimensione di 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
Spesso, quantized_dimension
è il output_channel
delle ponderazioni delle
convoluzioni, ma in teoria può essere la dimensione che corrisponde a ogni
prodotto punto nell'implementazione del kernel, consentendo una maggiore granularità della quantizzazione
senza implicazioni per le prestazioni. Questo aspetto ha notevolmente migliorato l'accuratezza.
TFLite offre il supporto per asse per un numero crescente di operazioni. Al momento della pubblicazione di questo documento, esiste un supporto per Conv2d e DepthwiseConv2d.
Simmetrica e asimmetrica
Le attivazioni sono asimmetriche: possono avere il punto zero ovunque all'interno dell'intervallo int8
indicato [-128, 127]
. Molte attivazioni sono asimmetriche e il punto zero è un modo relativamente economico per ottenere in modo efficace un bit di precisione extra binario. Poiché le attivazioni vengono moltiplicate solo per le ponderazioni
costanti, il valore del punto zero costante può essere ottimizzato in modo significativo.
Le ponderazioni sono simmetriche: il punto zero è forzato pari a 0. I valori di ponderazione sono moltiplicati per valori di input dinamico e di attivazione. Ciò significa che è previsto un costo di runtime inevitabile moltiplicando il punto zero della ponderazione per il valore di attivazione. Se applichi il valore del punto zero pari a 0, possiamo evitare questo costo.
Spiegazione del calcolo: è simile alla sezione 2.3 in arXiv:1712.05877, fatta eccezione per la differenza per cui i valori di scala sono consentiti per asse. Questa procedura è facilmente generale, come segue:
$A$ è una matrice $m \times n$ di attivazioni quantificate.
$B$ è una matrice $n \times p$ di pesi quantiizzati.
Valuta la possibilità di moltiplicare la riga $j$th di $A$, $a_j$ per la colonna $k$th di
$B$, $b_k$, entrambi con lunghezza $n$. I valori interi quantizzati e i valori a punto zero sono rispettivamente $q_a$, $z_a$ e $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\]
Il \(\sum_{i=0}^{n} q_{a}^{(i)} q_{b}^{(i)}\) termine è inevitabile poiché esegue il prodotto scalare del valore di input e il valore ponderato.
\(\sum_{i=0}^{n} q_{b}^{(i)} z_a\) e \(\sum_{i=0}^{n} z_a z_b\) i termini sono costituiti da costanti che rimangono invariate per ogni chiamata di inferenza, pertanto possono essere precalcolate.
Il \(\sum_{i=0}^{n} q_{a}^{(i)} z_b\) termine deve essere calcolato ogni inferenza poiché l'attivazione cambia ogni inferenza. Se le ponderazioni sono simmetriche, possiamo rimuovere il costo di questo termine.
Specifiche dell'operatore quantizzato int8
Di seguito descriviamo i requisiti di quantizzazione per i nostri kernel 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