Specyfikacja kwantyzacji 8-bitowej TensorFlow Lite

Zadbaj o dobrą organizację dzięki kolekcji Zapisuj i kategoryzuj treści zgodnie ze swoimi preferencjami.

Poniższy dokument przedstawia specyfikację 8-bitowego schematu kwantyzacji TensorFlow Lite. Ma to na celu pomóc deweloperom sprzętu w zapewnianiu obsługi sprzętu w celu wnioskowania z skwantowanymi modelami TensorFlow Lite.

Podsumowanie specyfikacji

Dostarczamy specyfikację i możemy zapewnić tylko pewne gwarancje zachowania, jeśli specyfikacja jest przestrzegana. Rozumiemy również, że różne urządzenia mogą mieć preferencje i ograniczenia, które mogą powodować niewielkie odchylenia podczas implementacji specyfikacji, które skutkują implementacjami, które nie są dokładne w bitach. Chociaż może to być akceptowalne w większości przypadków (a my dostarczymy zestaw testów, które zgodnie z naszą najlepszą wiedzą obejmują tolerancje operacji, które zebraliśmy na podstawie kilku modeli), charakter uczenia maszynowego (i uczenia głębokiego w najczęstszych przypadku) uniemożliwia udzielenie twardych gwarancji.

Kwantyzacja 8-bitowa aproksymuje wartości zmiennoprzecinkowe za pomocą następującego wzoru.

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

Za osią (czyli na kanał OPS Conv) lub za-tensora wagi są przedstawione int8 dwójkowego wartości dopełniacza w przedziale [-127, 127] z punktu zerowego równe 0. per tensor aktywacji / wejścia są przedstawione int8 dwójkowego wartościami całkowitymi w zakresie [-128, 127] , z punktu zerowego w zakresie [-128, 127] .

Istnieją inne wyjątki dla poszczególnych operacji, które są udokumentowane poniżej.

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

TensorFlow Lite kwantyzacji będzie głównie Prioritize oprzyrządowanie i jądra dla int8 kwantyzacji dla 8-bitowych. Jest to dla wygody symetrycznego kwantyzacji jest reprezentowany przez punkt zerowy równa 0. Dodatkowo wielu backendów mają dodatkowe optymalizacje dla int8xint8 akumulacji.

Na oś vs na tensor

Kwantyzacja przypadająca na tensor oznacza, że ​​na cały tensor przypada jedna skala i/lub punkt zerowy. Na osi środki kwantowania, które nie będą skali, i / lub zero_point za plaster w quantized_dimension . Wymiar skwantowany określa wymiar kształtu tensora, któremu odpowiadają podziałki i punkty zerowe. Na przykład, tensora t , z dims=[4, 3, 2, 1] z params kwantyzacji: scale=[1.0, 2.0, 3.0] , zero_point=[1, 2, 3] , quantization_dimension=1 będzie kwantowane 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 jest output_channel od ciężarów zwojów, ale teoretycznie może być wymiar, który odpowiada każda kropka produktem w realizacji jądra, dzięki czemu więcej kwantyzacji ziarnistość bez konsekwencji wydajności. Ma to dużą poprawę dokładności.

TFLite obsługuje każdą oś dla rosnącej liczby operacji. W czasie tworzenia tego dokumentu istnieje wsparcie dla Conv2d i DepthwiseConv2d.

Symetryczny vs asymetryczny

Aktywacje są asymetryczne: mogą mieć ich punktu zerowego w dowolnym miejscu podpisanym int8 przedziale [-128, 127] . Wiele aktywacji ma charakter asymetryczny, a punkt zerowy jest stosunkowo niedrogim sposobem na skuteczne uzyskanie dodatkowego binarnego bitu 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 aktywacji. Oznacza to, że pomnożenie punktu zerowego wagi przez wartość aktywacji wiąże się z nieuniknionym kosztem działania. Wymuszając, że punkt zerowy wynosi 0, możemy uniknąć tego kosztu.

Wyjaśnienie matematyki: jest podobna do sekcji 2.3 w arXiv: 1712.05877 , poza różnicą, że pozwolimy wartości skali, aby być za osią. To łatwo uogólnia się w następujący sposób:

\(A\) jest \(m \times n\) matrycy skwantowanych aktywacji.
\(B\) jest \(n \times p\) matrycy skwantowanych wag.
Rozważmy pomnożenia \(j\)th rząd \(A\), \(a_j\) przez \(k\)tej kolumny\(B\), \(b_k\), oba o długości \(n\). Skwantowane wartości całkowite i zero punktów wartości są \(q_a\), \(z_a\) i \(q_b\), \(z_b\) odpowiednio.

\[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\]

\(\sum_{i=0}^{n} q_{a}^{(i)} q_{b}^{(i)}\) termin jest nieuniknione, ponieważ jest to wykonanie iloczynu skalarnego wartości wejściowej i wartości ciężaru.

W \(\sum_{i=0}^{n} q_{b}^{(i)} z_a\) i \(\sum_{i=0}^{n} z_a z_b\) warunki są wykonane stałych, które pozostają takie same, za wnioskowania wywołaniu, a zatem może być obliczony z wyprzedzeniem.

\(\sum_{i=0}^{n} q_{a}^{(i)} z_b\) termin musi być obliczane za każdym wnioskowanie ponieważ aktywacja zmienia się za każdym wnioskowanie. Wymuszając symetryczność wag, możemy usunąć koszt tego terminu.

int8 specyfikacje operatorów skwantowanych

Poniżej opisujemy wymagania 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