8-Bit-Quantisierungsspezifikation für LiteRT

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

Verweise

arXiv:1712.05877