Odpowiedz już dziś na lokalne wydarzenie TensorFlow Everywhere!
Ta strona została przetłumaczona przez Cloud Translation API.
Switch to English

Specyfikacja 8-bitowej kwantyzacji TensorFlow Lite

Poniższy dokument przedstawia specyfikację 8-bitowego schematu kwantyzacji TensorFlow Lite. Ma to na celu pomóc twórcom sprzętu w zapewnianiu wsparcia sprzętowego do wnioskowania w skwantyzowanych modelach TensorFlow Lite.

Podsumowanie specyfikacji

Udostępniamy specyfikację i możemy zapewnić pewne gwarancje dotyczące zachowania tylko wtedy, gdy jest ona przestrzegana. Rozumiemy również, że inny sprzęt może mieć preferencje i ograniczenia, które mogą powodować niewielkie odchylenia podczas implementacji specyfikacji, co skutkuje implementacjami, które nie są dokładne bitowo. Podczas gdy w większości przypadków może to być akceptowalne (i zapewnimy zestaw testów, które zgodnie z naszą najlepszą wiedzą obejmują tolerancje poszczególnych operacji, które zebraliśmy z kilku modeli), natura uczenia maszynowego (i uczenia głębokiego w większości przypadku) uniemożliwia udzielenie twardych gwarancji.

8-bitowa kwantyzacja aproksymuje wartości zmiennoprzecinkowe przy użyciu następującego wzoru.

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

Wagi na oś (aka na kanał w operacjach Conv) lub wagi na tensor są reprezentowane przez wartości uzupełnień do dwóch int8 w zakresie [-127, 127] z punktem zerowym równym 0. Aktywacje / wejścia na tensor są reprezentowane przez int8 wartości uzupełnienia do dwóch z zakresu [-128, 127] , z punktem zerowym w zakresie [-128, 127] .

Istnieją inne wyjątki dla określonych operacji, które opisano poniżej.

Liczba całkowita ze znakiem a liczba całkowita bez znaku

Kwantyzacja TensorFlow Lite będzie przede wszystkim nadawać priorytet narzędziom i int8 dla kwantyzacji int8 dla 8-bitów. Wynika to z wygody symetrycznej kwantyzacji reprezentowanej przez punkt zerowy równy 0. Dodatkowo wiele backendów ma dodatkowe optymalizacje dla akumulacji int8xint8 .

Na oś a na tensor

Kwantyzacja per-tensorowa oznacza, że ​​na cały tensor przypada jedna skala i / lub punkt zerowy. Kwantyzacja na osi oznacza, że ​​będzie jedna skala i / lub zero_point na wycinek w zero_point quantized_dimension . Wymiar skwantowany określa wymiar kształtu Tensora, któremu odpowiadają skale i punkty zerowe. Na przykład tensor t z dims=[4, 3, 2, 1] z parametrami kwantyzacji: scale=[1.0, 2.0, 3.0] , zero_point=[1, 2, 3] , quantization_dimension=1 zostanie skwantyzowany w poprzek drugi wymiar 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

Często quantized_dimension output_channel jest output_channel wag splotów, ale teoretycznie może to być wymiar, który odpowiada każdemu output_channel w implementacji jądra, umożliwiając większą ziarnistość kwantyzacji bez wpływu na wydajność. Ma to dużą poprawę dokładności.

TFLite ma wsparcie dla każdej osi dla rosnącej liczby operacji. W chwili wydania tego dokumentu istnieje wsparcie dla Conv2d i DepthwiseConv2d.

Symetryczny vs asymetryczny

Aktywacje są asymetryczne: mogą mieć punkt zerowy w dowolnym miejscu z zakresu int8 [-128, 127] . Wiele aktywacji ma charakter asymetryczny, a punkt zerowy to stosunkowo niedrogi sposób na skuteczne uzyskanie dodatkowej binarnej precyzji. Ponieważ aktywacje są mnożone tylko przez stałe wagi, stałą wartość punktu zerowego można dość mocno zoptymalizować.

Wagi są symetryczne: wymuszone, aby punkt zerowy był równy 0. Wartości wag są mnożone przez dynamiczne wartości wejściowe i wartości aktywacji. Oznacza to, że mnożenie punktu zerowego ciężaru przez wartość aktywacji wiąże się z nieuniknionym kosztem czasu pracy. Wymuszając, że punkt zerowy wynosi 0, możemy uniknąć tego kosztu.

Wyjaśnienie matematyki: jest to podobne do sekcji 2.3 w arXiv: 1712.05877 , z wyjątkiem różnicy polegającej na tym, że pozwalamy, aby wartości skali były przypadające na oś. Uogólnia to łatwo, jak następuje:

$ A $ to $ m \ razy n $ macierz skwantyzowanych aktywacji.
$ B $ to $ n \ razy p $ macierz skwantyzowanych wag.
Rozważ pomnożenie $ j $ -tego wiersza $ A $, $ a_j $ przez $ k $-tej kolumny $ B $, $ b_k $, obie długości $ n $. Kwantowane wartości całkowite i wartości punktów zerowych to odpowiednio $ q_a $, $ z_a $ i $ 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$$

Termin \(\sum_{i=0}^{n} q_{a}^{(i)} q_{b}^{(i)}\) jest nieunikniony, ponieważ wykonuje iloczyn skalarny wartości wejściowej i wartości wagi.

Plik

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

i

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

terminy składają się ze stałych, które pozostają takie same przy wywołaniu wnioskowania, a zatem mogą być wstępnie obliczone.

Termin \(\sum_{i=0}^{n} q_{a}^{(i)} z_b\) musi być obliczany przy każdym wnioskowaniu, ponieważ aktywacja zmienia każde wnioskowanie. Wymuszając symetryczność wag, możemy wyeliminować koszt tego terminu.

kwantyzowane specyfikacje operatora int8

Poniżej opisujemy wymagania dotyczące kwantyzacji dla naszych jąder 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

Bibliografia

arXiv: 1712.05877