TensorFlow Lite 8-Bit-Quantisierungsspezifikation

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 bereit 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 unserem besten Wissen Toleranzen pro Operation enthalten, die wir aus mehreren Modellen zusammengetragen haben), ist die Art des maschinellen Lernens (und am häufigsten das tiefe Lernen) die Art des maschinellen Lernens Fall) macht es unmöglich, irgendwelche harten Garantien zu geben.

Die 8-Bit-Quantisierung approximiert Gleitkommawerte unter Verwendung der folgenden Formel.

$$real\_value = (int8\_value - zero\_point) \times scale$$

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 von 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 hauptsächlich 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 haben viele Backends zusätzliche Optimierungen für die int8xint8 Akkumulation.

Pro Achse gegen Pro 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.

Erklärung 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 $.

$$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$$

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

$$\sum_{i=0}^{n} q_{b}^{(i)} z_a$$

und

$$\sum_{i=0}^{n} z_a z_b$$

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. Indem wir die Symmetrie der Symmetrien erzwingen, können wir die Kosten für diesen Begriff senken.

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

Verweise

arXiv: 1712.05877