W tym dokumencie znajdziesz specyfikację 8-bitowego schematu kwantyzacji TensorFlow Lite. Ta funkcja ma pomagać deweloperom sprzętu w oferowaniu sprzętowej obsługi wnioskowania za pomocą skwantyzowanych modeli TensorFlow Lite.
Podsumowanie specyfikacji
Udostępniamy specyfikację i możemy dać tylko pewne gwarancje dotyczące jej działania w przypadku jej zgodności z tą specyfikacją. Zdajemy sobie też sprawę, że w przypadku różnych urządzeń mogą występować preferencje i ograniczenia, które mogą powodować niewielkie odchylenia przy implementacji specyfikacji, która skutkuje niedokładnymi implementacjami. W większości przypadków takie rozwiązanie może być akceptowalne (a my dostarczymy zestaw testów, które zgodnie z naszą najlepszą wiedzą obejmują tolerancje dla poszczególnych operacji zebrane na podstawie kilku modeli), jednak charakter systemów uczących się (i w najczęstszym przypadku deep learning) nie pozwala na uzyskanie żadnych twardych gwarancji.
Kwantyzacja 8-bitowa zapewnia przybliżone wartości zmiennoprzecinkowe za pomocą poniższego wzoru.
\[real\_value = (int8\_value - zero\_point) \times scale\]
Wagi na oś (w operacjach konw.) lub wagi na intensywność parametru są reprezentowane przez int8
wartości uzupełniające w zakresie [-127, 127]
z punktem zerowym równym 0. Aktywacje/wejściowe dane na podstawie liczby agentów są reprezentowane przez wartości uzupełniające (int8
) z zakresu [-128, 127]
o punkcie zerowym w zakresie [-128, 127]
.
Występują też inne wyjątki dotyczące konkretnych operacji, które zostały opisane poniżej.
Podpisana liczba całkowita a nieoznaczona liczba całkowita
Kwantyzacja TensorFlow Lite traktuje priorytetowo narzędzia i jądra przy kwantyzacji int8
w przypadku wersji 8-bitowej. Ułatwia to kwantyzację symetryczną reprezentowaną przez punkt zerowy równy 0. Poza tym wiele backendów ma dodatkowe optymalizacje pod kątem akumulacji int8xint8
.
Na oś lub tenisor
Kwantyzacja na podstawie intensywności oznacza, że na cały tensor przypada jedna skala lub punkt 0 punktu. Kwantyzacja na osi oznacza, że występuje 1 skala lub zero_point
na wycinek w funkcji quantized_dimension
. Poddany kwantyzacji wymiar określa wymiar kształtu Tensora, któremu odpowiadają skale i punkty zerowe. Na przykład tensor t
z parametrem dims=[4, 3, 2, 1]
z parametrami kwantyzacji scale=[1.0, 2.0, 3.0]
, zero_point=[1, 2, 3]
i quantization_dimension=1
zostanie poddany kwantyzacji w drugim wymiarze obiektu 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 wartością output_channel
wagi splotów, ale teoretycznie może też odpowiadać każdemu produktowi punktowemu w implementacji jądra, co zapewnia większą szczegółowość kwantyzacji bez wpływu na wydajność. Pozwoli to znacznie zwiększyć dokładność.
TFLite obsługuje liczby operacji na jednej osi. W chwili obecnej tego dokumentu obsługiwane są programy Conv2d i DepthwiseConv2d.
Symetryczne czy asymetryczne
Aktywacje są asymetryczne: mogą mieć punkt zerowy w dowolnym miejscu w zakresie [-128, 127]
zawierającym znak int8
. Wiele aktywacji ma asymetryczny charakter, a punkt zerowy to stosunkowo tani sposób na uzyskanie nawet bardzo binarnej precyzji. Aktywacje są mnożone tylko przez stałą wagę, więc stałą wartość zerową można znacznie optymalizować.
Wagi są symetryczne: punkt zerowy ma wartość 0. Wartości wag są mnożone przez dynamiczne wartości wejściowe i aktywacyjne. Oznacza to, że mnożenie punktu 0 wagi przez wartość aktywacji jest nieuniknionym kosztem działania w czasie działania. Dzięki wyegzekwowaniu, że punkt zerowy ma wartość 0, możemy tego uniknąć.
Wyjaśnienie matematyki: działa podobnie do sekcji 2.3 w artykule arXiv:1712.05877, z tą różnicą, że dozwolone jest przypisywanie wartości skali na osi. Można to łatwo uogólnić w ten sposób:
$A$ to macierz $m \times n$ aktywacji kwantyzowanych.
$B$ to macierz $n \times p$ kwantyzowanych wag.
Rozważ pomnożenie $j$th wiersza $A$, $a_j$ przez kolumnę $k$th w kolumnie $B$ i $b_k$. Obie wartości w postaci liczb całkowitych $n$ to odpowiednio: $q_a$, $z_a$, $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\]
Nie można uniknąć \(\sum_{i=0}^{n} q_{a}^{(i)} q_{b}^{(i)}\) terminu, ponieważ zapewnia iloczyn skalarny wartości wejściowej i wagi.
Warunki \(\sum_{i=0}^{n} q_{b}^{(i)} z_a\) i \(\sum_{i=0}^{n} z_a z_b\) są składane ze stałych, które pozostają takie same zgodnie z wywołaniem wnioskowania, więc można je wstępnie obliczyć.
Termin \(\sum_{i=0}^{n} q_{a}^{(i)} z_b\) musi być obliczany przy każdym wnioskowaniu, ponieważ aktywacja zmienia się w każdym wnioskowaniu. Dzięki wyegzekwowaniu symetrycznego wag możemy zmniejszyć koszt tego terminu.
specyfikacja operatora skwantyzowanego int8
Poniżej opisujemy wymagania dotyczące kwantyzacji dla naszych jąder tflite int8:
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