Dokumen berikut menguraikan spesifikasi untuk 8-bit LiteRT skema kuantisasi. Hal ini dimaksudkan untuk membantu pengembang perangkat keras dalam menyediakan dukungan hardware untuk inferensi dengan model LiteRT terkuantisasi.
Ringkasan spesifikasi
Kami menyediakan spesifikasi, dan kami hanya dapat memberikan beberapa jaminan pada jika spesifikasi diikuti. Kami juga memahami bahwa perangkat keras yang berbeda mungkin memiliki preferensi dan batasan yang dapat menyebabkan sedikit penyimpangan saat menerapkan spesifikasi yang menghasilkan implementasi yang tidak bit-exact. Sementara itu mungkin dapat diterima dalam kebanyakan kasus (dan kami akan memberikan serangkaian pengujian yang sebaik mungkin mencakup toleransi per operasi yang yang dikumpulkan dari beberapa model), sifat machine learning (dan deep learning dalam kasus yang paling umum) membuatnya tidak mungkin memberikan jaminan pasti.
Kuantisasi 8-bit mendekati nilai floating point menggunakan formula.
\[real\_value = (int8\_value - zero\_point) \times scale\]
Bobot per-sumbu (alias per-saluran dalam Operasi Konv.) atau per-tensor diwakili oleh
int8
nilai pelengkap dua dalam rentang [-127, 127]
dengan titik nol sama
menjadi 0. Aktivasi/input per-tensor diwakili oleh komplemen dua int8
nilai dalam rentang [-128, 127]
, dengan titik nol dalam rentang [-128, 127]
.
Ada pengecualian lain untuk operasi tertentu yang didokumentasikan di bawah.
Bilangan bulat bertanda tangan vs bilangan bulat tidak bertanda tangan
Kuantisasi LiteRT terutama akan memprioritaskan
alat dan {i>kernel<i} untuk
Kuantisasi int8
untuk 8-bit. Ini untuk kenyamanan simetris
kuantisasi yang dinyatakan dengan
titik nol yang sama dengan 0. Selain itu, banyak
backend memiliki pengoptimalan tambahan untuk akumulasi int8xint8
.
Per sumbu vs per-tensor
Kuantisasi per-tensor berarti bahwa akan ada satu skala dan/atau titik nol per
seluruh tensor. Kuantisasi per sumbu berarti bahwa akan ada satu skala dan/atau
zero_point
per bagian dalam quantized_dimension
. Dimensi terkuantisasi
menentukan dimensi bentuk Tensor yang skalanya dan titik nol
yang sesuai. Misalnya, tensor t
, dengan dims=[4, 3, 2, 1]
dengan
parameter kuantisasi: scale=[1.0, 2.0, 3.0]
, zero_point=[1, 2, 3]
,
quantization_dimension=1
akan dikuantisasi di seluruh dimensi kedua 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
Sering kali, quantized_dimension
adalah output_channel
dari bobot
konvolusi, tetapi secara teori bisa merupakan dimensi yang sesuai dengan
dot-product dalam implementasi {i>kernel<i}, memungkinkan perincian kuantisasi yang lebih besar
tanpa implikasi performa. Fitur ini memiliki peningkatan besar pada akurasi.
TFLite memiliki dukungan per sumbu untuk operasi yang semakin banyak. Pada saat dokumen ini, dukungan tersedia untuk Conv2d dan DepthwiseConv2d.
Simetris vs asimetris
Aktivasi bersifat asimetris: mereka dapat memiliki titik nol di mana pun dalam
rentang int8
yang ditandatangani [-128, 127]
. Banyak aktivasi
bersifat asimetris dan
titik nol merupakan cara yang relatif murah
untuk secara efektif mendapatkan
bit biner presisi. Karena aktivasi hanya
dikalikan dengan konstanta
bobotnya, nilai titik nol yang konstan
dapat banyak dioptimalkan.
Bobot simetris: dipaksa memiliki titik nol yang sama dengan 0. Nilai bobot adalah dikalikan dengan input dinamis dan nilai aktivasi. Ini berarti bahwa terdapat biaya runtime yang tidak dapat dihindari dari mengalikan titik nol bobot dengan nilai aktivasi. Dengan menerapkan bahwa titik nol adalah 0, kita dapat menghindari biaya ini.
Penjelasan matematika: ini mirip dengan bagian 2.3 di arXiv:1712.05877, kecuali perbedaannya yang kita izinkan adalah nilai skala per sumbu. Hal ini dapat diterapkan dengan mudah, karena berikut ini:
$A$ adalah matriks $m \times n$ dari aktivasi terkuantisasi.
$B$ adalah matriks $n \times p$ dari bobot terkuantisasi.
Pertimbangkan untuk mengalikan baris $j$th dari $A$, $a_j$ dengan kolom $k$th dari
$B$, $b_k$, panjangnya $n$. Nilai-nilai bilangan bulat terkuantisasi dan
nilai poin nol masing-masing adalah $q_a$, $z_a$, dan $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\]
Istilah \(\sum_{i=0}^{n} q_{a}^{(i)} q_{b}^{(i)}\) tidak dapat dihindari karena menjalankan perkalian titik dari nilai input dan nilai bobot.
\(\sum_{i=0}^{n} q_{b}^{(i)} z_a\) dan \(\sum_{i=0}^{n} z_a z_b\) Persyaratan terdiri dari konstanta yang tetap sama per pemanggilan inferensi, dan dengan demikian bisa telah dihitung sebelumnya.
Istilah \(\sum_{i=0}^{n} q_{a}^{(i)} z_b\) harus dihitung pada setiap inferensi karena aktivasi mengubah setiap inferensi. Dengan menerapkan bobot agar simetris, kita dapat menghapus biaya istilah ini.
spesifikasi operator terkuantisasi int8
Di bawah ini kami menjelaskan persyaratan kuantisasi untuk kernel int8 tflite kami:
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