Il seguente documento illustra le specifiche per l'architettura a 8 bit di LiteRT schema di quantizzazione. Ha lo scopo di aiutare gli sviluppatori di hardware a fornire il supporto hardware per l'inferenza con i modelli LiteRT quantizzati.
Riepilogo delle specifiche
Stiamo fornendo una specifica e possiamo fornire solo alcune garanzie sui il comportamento corretto se viene rispettata la specifica. Siamo consapevoli, inoltre, che diversi hardware possono hanno preferenze e restrizioni che possono causare lievi scostamenti implementare le specifiche che si traducono in implementazioni non precise. Nella maggior parte dei casi potrebbe essere accettabile (e forniremo una serie di che, per quanto ci risulta, includono le tolleranze per ciascuna operazione che raccolti da diversi modelli), la natura del machine learning (e del deep learning) nel caso più comune) non permette di fornire garanzie reali.
La quantizzazione a 8 bit approssima i valori in virgola mobile utilizzando: formula.
\[real\_value = (int8\_value - zero\_point) \times scale\]
Le ponderazioni per asse (noto anche come per canale nelle operazioni di conversione) o per tensore sono rappresentate da
int8
valori di complemento di due nell'intervallo [-127, 127]
con punto zero uguale
a 0. Le attivazioni/gli input per tensore sono rappresentati dal complemento a due di int8
valori nell'intervallo [-128, 127]
, con un punto zero nell'intervallo [-128, 127]
.
Esistono altre eccezioni per determinate operazioni, documentate di seguito.
Numero intero firmato e numero intero senza segno
La quantizzazione LiteRT darà principalmente la priorità agli strumenti e ai kernel per
quantizzazione di int8
per 8 bit. Questo è per la comodità della simmetria
la quantizzazione è rappresentata da un punto zero uguale a 0. Inoltre, molti
i backend hanno ottimizzazioni aggiuntive per l'accumulo di int8xint8
.
Per asse e per tensore
La quantizzazione per tensore significa che ci sarà una scala e/o un punto zero per
l'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 del tensore che le scale e i punti zero
a cui corrispondono. 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
dei pesi di
di convoluzioni, ma in teoria può essere la dimensione che corrisponde a
dot-product nell'implementazione del kernel, che consente una maggiore granularità di quantizzazione
senza implicazioni in termini di prestazioni. Questo comporta notevoli miglioramenti in termini di accuratezza.
TFLite supporta un numero crescente di operazioni per asse. Al momento della questo documento, esiste il supporto per Conv2d e DepthwiseConv2d.
Simmetrico e asimmetrico
Le attivazioni sono asimmetriche: possono avere il loro zero-point in qualsiasi punto all'interno
int8
intervallo [-128, 127]
firmato. Molte attivazioni sono asimmetriche e
un punto zero è un modo relativamente economico per ottenere in modo efficace
bit binario di precisione. Poiché le attivazioni vengono moltiplicate solo per la costante
ponderate, il valore costante di zero può essere ottimizzato abbastanza.
I pesi sono simmetrici: devono avere un punto zero uguale a 0. I valori del peso sono moltiplicato per l'input dinamico e i valori di attivazione. Ciò significa che c'è il costo di runtime inevitabile moltiplicando il punto zero della ponderazione valore di attivazione. Se imposti il valore zero-point è pari a 0, possiamo evitare questo costo.
Spiegazione matematica: è simile alla sezione 2.3 della arXiv:1712.05877, fatta eccezione per la differenza che i valori della scala sono per asse. Questo viene generalizzato rapidamente, che segue:
$A$ è una matrice $m \times n$ di attivazioni quantificate.
$B$ è una matrice $n \times p$ di pesi quantizzati.
Potresti moltiplicare la riga $j$th di $A$, $a_j$ per la colonna $k$th di $A$
$B$, $b_k$, entrambi di lunghezza $n$. I valori interi quantizzati e
i valori di zero punti 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 che esegue il prodotto scalare del valore di input e del valore di ponderazione.
I \(\sum_{i=0}^{n} q_{b}^{(i)} z_a\) e \(\sum_{i=0}^{n} z_a z_b\) i termini costituita da costanti che rimangono le stesse per ogni chiamata di inferenza, e quindi possono essere precalcolate.
Il \(\sum_{i=0}^{n} q_{a}^{(i)} z_b\) termine deve essere calcolato a ogni inferenza poiché l'attivazione cambia ogni inferenza. Se applichi le ponderazioni simmetrico 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