Im folgenden Dokument wird die Spezifikation für die 8-Bit-Version von LiteRT beschrieben. Quantisierungsschema. Dies soll Hardwareentwicklern helfen, Hardware-Unterstützung für Inferenz mit quantisierten LiteRT-Modellen.
Zusammenfassung der Spezifikation
Wir stellen eine Spezifikation bereit und können Verhalten zu erkennen, wenn die Spezifikation eingehalten wird. Uns ist auch bewusst, dass unterschiedliche Hardware Präferenzen und Einschränkungen haben, die zu geringfügigen Abweichungen führen können, Implementierung der Spezifikation, die zu nicht bitgenauen Implementierungen führt. Das kann in den meisten Fällen akzeptabel sein. Wir stellen Ihnen eine Reihe von Tests, dass nach unserem Wissen Toleranzen pro Vorgang enthalten sind, die aus verschiedenen Modellen stammen, die Art des maschinellen Lernens (und Deep Learning) im häufigsten Fall) machen es unmöglich, feste Garantien zu geben.
Bei der 8-Bit-Quantisierung werden Gleitkommawerte mithilfe von aus.
\[real\_value = (int8\_value - zero\_point) \times scale\]
Gewichtungen pro Achse (bzw. pro Kanal in Conv.-Vorgängen) oder pro Tensor werden durch
int8
: Komplementärwerte von zwei im Bereich [-127, 127]
, wobei Nullpunkt gleich ist
auf 0 gesetzt. Aktivierungen/Eingaben pro Tensor werden durch das Zweierkomplement von int8
dargestellt
Werte im Bereich [-128, 127]
, mit einem Nullpunkt im Bereich [-128, 127]
Es gibt noch weitere Ausnahmen für bestimmte Vorgänge, die im Folgenden beschrieben werden.
Vorzeichenbehaftete Ganzzahl im Vergleich zu vorzeichenloser Ganzzahl
Bei der LiteRT-Quantisierung werden Tools und Kernel für
int8
-Quantisierung für 8-Bit. Dies geschieht aus Gründen der symmetrischen
Quantisierung durch einen Nullpunkt, der gleich 0 ist. Außerdem viele
Back-Ends haben zusätzliche Optimierungen für die int8xint8
-Akkumulation.
Pro Achse vs. pro Tensor
Die Quantisierung pro Tensor bedeutet, dass es eine Skala und/oder einen Nullpunkt pro
ganzen Tensors. Die Quantisierung pro Achse bedeutet, dass es eine Skala und/oder
zero_point
pro Segment in der quantized_dimension
. Die quantisierte Dimension
gibt die Abmessung der Tensor-Form an, die durch die Skalierung und Nullpunkte
die dem jeweiligen Produkt
entsprechen. Zum Beispiel hat ein Tensor t
mit dims=[4, 3, 2, 1]
mit
Quantisierungsparameter: scale=[1.0, 2.0, 3.0]
, zero_point=[1, 2, 3]
,
quantization_dimension=1
wird für die zweite Dimension von t
quantisiert:
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
Häufig ist der quantized_dimension
der output_channel
der Gewichtungen von
aber theoretisch kann es sich um die Dimension handeln,
Punkt-product in der Kernel-Implementierung für eine höhere Quantisierungsgenauigkeit
ohne Auswirkungen auf die Leistung. Dadurch wurde die Genauigkeit erheblich verbessert.
TFLite bietet pro Achsen-Unterstützung für eine wachsende Anzahl von Vorgängen. Zum Zeitpunkt des In diesem Dokument werden Conv2d und DepthwiseConv2d unterstützt.
Symmetrische vs. asymmetrische
Aktivierungen sind asymmetrisch: Der Nullpunkt kann an einer beliebigen Stelle im
Vorzeichen int8
, Bereich [-128, 127]
. Viele Aktivierungen sind asymmetrischer Natur und
ist ein Nullpunkt eine relativ kostengünstige Möglichkeit,
Binärbit der Präzision. Da Aktivierungen nur mit Konstanten multipliziert werden,
kann der konstante Nullpunktwert ziemlich stark optimiert werden.
Gewichtungen sind symmetrisch, d. h., der Nullpunkt muss gleich 0 sein. Gewichtungswerte sind multipliziert mit den dynamischen Eingabe- und Aktivierungswerten. Das bedeutet, dass es eine Laufzeitkosten bei der Multiplikation des Nullpunkts der Gewichtung mit der Aktivierungswerts. Durch das Erzwingen von Nullpunkt 0 können wir diese Kosten vermeiden.
Erklärung der Berechnung: Dies ähnelt Abschnitt 2.3 der arXiv:1712.05877 mit Ausnahme des Unterschieds legen wir fest, dass die Skalierungswerte pro Achse sind. Das lässt sich leicht verallgemeinern, folgt:
$A$ ist eine $m \times n$-Matrix quantisierter Aktivierungen.
$B$ ist eine $n \times p$-Matrix quantisierter Gewichtungen.
Multiplizieren Sie die $j$. Zeile von $A$, $a_j$ mit der $k$th Spalte von
$B$, $b_k$, beide der Länge $n$. Die quantisierten ganzzahligen Werte
Nullpunkte sind jeweils $q_a$, $z_a$ und $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\]
fehl.Der Begriff \(\sum_{i=0}^{n} q_{a}^{(i)} q_{b}^{(i)}\) ist unvermeidbar, da er das Skalarprodukt aus Eingabewert und Gewichtung berechnen.
Die Nutzungsbedingungen \(\sum_{i=0}^{n} q_{b}^{(i)} z_a\) und \(\sum_{i=0}^{n} z_a z_b\) sind aus Konstanten bestehen, die pro Inferenzaufruf gleich bleiben und somit vorab berechnet werden.
Der Begriff \(\sum_{i=0}^{n} q_{a}^{(i)} z_b\) muss für jede Inferenz berechnet werden da die Aktivierung jede Inferenz ändert. Indem er auf Gewichtungen setzt, können wir die Kosten für diesen Begriff entfernen.
Spezifikationen für quantisierte Int8-Operatoren
Im Folgenden werden die Quantisierungsanforderungen für unsere int8-tflite-Kernel beschrieben:
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