Das folgende Dokument beschreibt die Spezifikation für das 8-Bit-Quantisierungsschema von TensorFlow Lite. Dies soll Hardwareentwicklern helfen, Hardwareunterstützung für Inferenzen mit quantisierten TensorFlow Lite-Modellen bereitzustellen.
Spezifikationszusammenfassung
Wir stellen eine Spezifikation zur Verfügung und können nur dann einige Garantien für das Verhalten geben, wenn die Spezifikation eingehalten wird. Wir wissen auch, dass unterschiedliche Hardware Einstellungen und Einschränkungen aufweisen kann, die bei der Implementierung der Spezifikation zu geringfügigen Abweichungen führen können, die zu Implementierungen führen, die nicht bitgenau sind. Während dies in den meisten Fällen akzeptabel sein kann (und wir werden eine Reihe von Tests bereitstellen, die nach bestem Wissen Toleranzen pro Operation enthalten, die wir aus mehreren Modellen zusammengetragen haben), ist die Art des maschinellen Lernens (und des Deep Learning am häufigsten) Fall) macht es unmöglich, irgendwelche harten Garantien zu geben.
Die 8-Bit-Quantisierung approximiert Gleitkommawerte unter Verwendung der folgenden Formel.
Pro-Achse (in Conv ops auch pro Kanal genannt) oder Pro-Tensor-Gewichte werden durch die Komplementwerte von int8
two im Bereich [-127, 127]
mit einem Nullpunkt gleich 0 dargestellt. Pro-Tensor-Aktivierungen / Eingaben werden durch dargestellt Komplementwerte von int8
two im Bereich [-128, 127]
mit einem Nullpunkt im Bereich [-128, 127]
.
Es gibt andere Ausnahmen für bestimmte Vorgänge, die unten dokumentiert sind.
Ganzzahl mit Vorzeichen und Ganzzahl ohne Vorzeichen
Bei der TensorFlow Lite-Quantisierung werden in erster Linie Werkzeuge und Kernel für die int8
Quantisierung für 8-Bit priorisiert. Dies dient der Bequemlichkeit, dass die symmetrische Quantisierung durch einen Nullpunkt gleich 0 dargestellt wird. Zusätzlich verfügen viele Backends über zusätzliche Optimierungen für int8xint8
Akkumulation von int8xint8
.
Pro Achse gegen Tensor
Pro-Tensor-Quantisierung bedeutet, dass es eine Skala und / oder einen Nullpunkt pro gesamtem Tensor gibt. Quantisierung pro Achse bedeutet, dass es in der quantized_dimension
zero_point
eine Skala und / oder einen zero_point
pro zero_point
. Die quantisierte Dimension gibt die Dimension der Tensorform an, der die Skalen und Nullpunkte entsprechen. Zum Beispiel wird ein Tensor t
mit dims=[4, 3, 2, 1]
mit Quantisierungsparametern: scale=[1.0, 2.0, 3.0]
, zero_point=[1, 2, 3]
, quantization_dimension=1
quer quantisiert die zweite Dimension von 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
Oft ist die quantized_dimension
output_channel
der output_channel
der Gewichte von Faltungen, aber theoretisch kann es die Dimension sein, die jedem Punktprodukt in der Kernel-Implementierung entspricht, was eine größere Quantisierungsgranularität ohne Auswirkungen auf die Leistung ermöglicht. Dies hat große Verbesserungen der Genauigkeit zur Folge.
TFLite unterstützt eine wachsende Anzahl von Vorgängen pro Achse. Zum Zeitpunkt dieses Dokuments besteht Unterstützung für Conv2d und DepthwiseConv2d.
Symmetrisch gegen asymmetrisch
Aktivierungen sind asymmetrisch: Sie können ihren Nullpunkt überall innerhalb des vorzeichenbehafteten int8
Bereichs haben [-128, 127]
. Viele Aktivierungen sind asymmetrischer Natur und ein Nullpunkt ist ein relativ kostengünstiger Weg, um effektiv ein zusätzliches binäres Bit an Präzision zu erreichen. Da Aktivierungen nur mit konstanten Gewichten multipliziert werden, kann der konstante Nullpunktwert ziemlich stark optimiert werden.
Gewichte sind symmetrisch: Der Nullpunkt muss gleich 0 sein. Die Gewichtswerte werden mit den dynamischen Eingabe- und Aktivierungswerten multipliziert. Dies bedeutet, dass es unvermeidbare Laufzeitkosten gibt, wenn der Nullpunkt des Gewichts mit dem Aktivierungswert multipliziert wird. Indem wir erzwingen, dass der Nullpunkt 0 ist, können wir diese Kosten vermeiden.
Erläuterung der Mathematik: Dies ähnelt Abschnitt 2.3 in arXiv: 1712.05877 , mit Ausnahme des Unterschieds, dass die Skalierungswerte pro Achse sein dürfen. Dies verallgemeinert sich leicht wie folgt:
$ A $ ist eine $ m \ times n $ -Matrix quantisierter Aktivierungen.
$ B $ ist eine $ n \ times p $ -Matrix quantisierter Gewichte.
Erwägen Sie, die $ j $ -te Zeile von $ A $, $ a_j $ mit der $ k $ -ten Spalte von $ B $, $ b_k $ zu multiplizieren, die beide die Länge $ n $ haben. Die quantisierten ganzzahligen Werte und Nullpunktwerte sind $ q_a $, $ z_a $ bzw. $ q_b $, $ z_b $.
Der Begriff \(\sum_{i=0}^{n} q_{a}^{(i)} q_{b}^{(i)}\) ist unvermeidlich, da er das Punktprodukt aus dem Eingabewert und dem Gewichtswert ausführt.
Das
und
Terme bestehen aus Konstanten, die pro Inferenzaufruf gleich bleiben und daher vorberechnet werden können.
Der \(\sum_{i=0}^{n} q_{a}^{(i)} z_b\)-Term muss für jede Inferenz berechnet werden, da die Aktivierung jede Inferenz ändert. Durch Erzwingen, dass Gewichte symmetrisch sind, können wir die Kosten für diesen Begriff entfernen.
int8 quantisierte Operator-Spezifikationen
Nachfolgend beschreiben wir die Quantisierungsanforderungen für unsere int8-tflite-Kernel:
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