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