Il seguente documento delinea le specifiche per lo schema di quantizzazione a 8 bit di TensorFlow Lite. Questo ha lo scopo di aiutare gli sviluppatori hardware a fornire supporto hardware per l'inferenza con i modelli TensorFlow Lite quantizzati.
Riepilogo delle specifiche
Stiamo fornendo una specifica e possiamo fornire alcune garanzie sul comportamento solo se le specifiche vengono seguite. Comprendiamo anche che hardware diverso potrebbe avere preferenze e restrizioni che potrebbero causare lievi deviazioni durante l'implementazione delle specifiche che si traducono in implementazioni che non sono esatte in bit. Considerando che ciò può essere accettabile nella maggior parte dei casi (e forniremo una serie di test che per quanto a nostra conoscenza includono tolleranze per operazione che abbiamo raccolto da diversi modelli), la natura dell'apprendimento automatico (e l'apprendimento profondo nei più comuni caso) rende impossibile fornire garanzie reali.
La quantizzazione a 8 bit approssima i valori in virgola mobile utilizzando la formula seguente.
\[real\_value = (int8\_value - zero\_point) \times scale\]
Per-asse (alias per canale in ops Conv) o per-tensore pesi sono rappresentati da int8
due valori di complemento nell'intervallo [-127, 127]
con punto zero uguale a 0. Per attivazioni-tensore / ingressi sono rappresentati da int8
due valori di complemento nell'intervallo [-128, 127]
, con un punto zero nell'intervallo [-128, 127]
.
Esistono altre eccezioni per operazioni particolari che sono documentate di seguito.
Intero con segno vs intero senza segno
Tensorflow Lite quantizzazione volontà utensili in primo luogo la priorità e kernel per int8
quantizzazione per 8-bit. Questo è per la comodità di quantizzazione simmetrica rappresentata dal punto zero uguale a 0. Inoltre molti backends avere un'ulteriore ottimizzazione delle int8xint8
accumulo.
Per asse vs per tensore
Quantizzazione per tensore significa che ci sarà una scala e/o un punto zero per l'intero tensore. Per assi mezzi di quantizzazione che ci sarà una bilancia e / o zero_point
per fetta nel quantized_dimension
. La dimensione quantizzata specifica la dimensione della forma del tensore a cui corrispondono le scale ei punti zero. Ad esempio, un tensore t
, con dims=[4, 3, 2, 1]
con params quantizzazione: scale=[1.0, 2.0, 3.0]
, zero_point=[1, 2, 3]
, quantization_dimension=1
sono quantizzati tutti la 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, il quantized_dimension
è output_channel
dei pesi dei circonvoluzioni, ma in teoria può essere la dimensione che corrisponde a ciascun dot-prodotto per l'attuazione del kernel, consentendo una maggiore granularità quantizzazione, senza implicazioni sulle prestazioni. Questo ha grandi miglioramenti alla precisione.
TFLite dispone del supporto per asse per un numero crescente di operazioni. Al momento della stesura di questo documento, esiste il supporto per Conv2d e DepthwiseConv2d.
Simmetrico vs asimmetrico
Attivazioni sono asimmetrici: possono avere il loro ovunque punto zero all'interno del sottoscritto int8
gamma [-128, 127]
. Molte attivazioni sono di natura asimmetrica e un punto zero è un modo relativamente economico per ottenere efficacemente un bit binario extra di precisione. Poiché le attivazioni vengono moltiplicate solo per pesi costanti, il valore del punto zero costante può essere ottimizzato piuttosto pesantemente.
I pesi sono simmetrici: forzati ad avere punto zero uguale a 0. I valori di peso vengono moltiplicati per l'ingresso dinamico e i valori di attivazione. Ciò significa che la moltiplicazione del punto zero del peso per il valore di attivazione comporta un inevitabile costo di esecuzione. Imponendo che il punto zero sia 0 possiamo evitare questo costo.
Spiegazione della matematica: questo è simile alla sezione 2.3 in arXiv: 1.712,05,877 mila , tranne che per la differenza che ci consentono i valori di scala per essere per-asse. Questo si generalizza facilmente, come segue:
\(A\) è un \(m \times n\) matrice di attivazioni quantizzati.
\(B\) è un \(n \times p\) matrice dei pesi quantizzati.
Considerare moltiplicando il \(j\)esima riga di \(A\), \(a_j\) dal \(k\)esima colonna della\(B\), \(b_k\), sia di lunghezza \(n\). I valori interi quantizzati e zero punti sono valori \(q_a\), \(z_a\) e \(q_b\), \(z_b\) rispettivamente.
\[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é è eseguire il prodotto scalare del valore di ingresso e il valore di peso.
I \(\sum_{i=0}^{n} q_{b}^{(i)} z_a\) e \(\sum_{i=0}^{n} z_a z_b\) termini sono costituiti da costanti che rimangono gli stessi per inferenza invocazione, e quindi può essere pre-calcolato.
Il \(\sum_{i=0}^{n} q_{a}^{(i)} z_b\) termine deve essere calcolato ogni inferenza dall'attivazione cambia ogni inferenza. Imponendo che i pesi siano simmetrici possiamo rimuovere il costo di questo termine.
specifiche dell'operatore quantizzato int8
Di seguito descriviamo i requisiti di quantizzazione per i nostri kernel int8 tflite:
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