Dokumen berikut menguraikan spesifikasi untuk skema kuantisasi 8-bit LiteRT. Tujuannya adalah untuk membantu developer hardware dalam menyediakan dukungan hardware untuk inferensi dengan model LiteRT terkuantisasi.
Ringkasan spesifikasi
Kami memberikan spesifikasi, dan kami hanya dapat memberikan beberapa jaminan terkait perilaku jika spesifikasi diikuti. Kami juga memahami bahwa hardware yang berbeda mungkin memiliki preferensi dan batasan yang dapat menyebabkan sedikit penyimpangan saat menerapkan spesifikasi yang menghasilkan implementasi yang tidak bit-exact. Meskipun hal itu dapat diterima dalam sebagian besar kasus (dan kami akan menyediakan serangkaian pengujian yang menurut pengetahuan terbaik kami mencakup toleransi per operasi yang kami kumpulkan dari beberapa model), sifat machine learning (dan deep learning dalam kasus yang paling umum) membuat kami tidak dapat memberikan jaminan yang pasti.
Kuantisasi 8-bit memperkirakan nilai floating point menggunakan formula berikut.
\[real\_value = (int8\_value - zero\_point) \times scale\]
Bobot per-sumbu (alias per-saluran dalam operasi Conv) atau per-tensor diwakili oleh nilai komplemen dua int8 dalam rentang [-127, 127] dengan titik nol sama dengan 0. Aktivasi/input per-tensor diwakili oleh nilai komplemen dua int8 dalam rentang [-128, 127], dengan titik nol dalam rentang [-128, 127].
Ada pengecualian lain untuk operasi tertentu yang didokumentasikan di bawah.
Bilangan bulat bertanda vs. bilangan bulat tidak bertanda
Kuantisasi LiteRT terutama akan memprioritaskan alat dan kernel untuk int8
kuantisasi untuk 8-bit. Hal ini untuk memudahkan kuantisasi simetris
yang diwakili oleh 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 akan ada satu skala dan/atau titik nol per seluruh tensor. Kuantisasi per sumbu berarti akan ada satu skala dan/atau
zero_point per irisan dalam quantized_dimension. Dimensi terkuantisasi
menentukan dimensi bentuk Tensor yang sesuai dengan skala dan titik nol. 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
Biasanya, quantized_dimension adalah output_channel bobot
konvolusi, tetapi secara teori dapat berupa dimensi yang sesuai dengan setiap
produk titik dalam penerapan kernel, sehingga memungkinkan perincian kuantisasi yang lebih besar
tanpa implikasi performa. Hal ini memberikan peningkatan akurasi yang besar.
TFLite memiliki dukungan per-sumbu untuk sejumlah operasi yang terus bertambah. Pada saat dokumen ini dibuat, dukungan tersedia untuk Conv2d dan DepthwiseConv2d.
Simetris vs. asimetris
Pengaktifan bersifat asimetris: titik nolnya dapat berada di mana saja dalam rentang int8bertanda [-128, 127]. Banyak aktivasi bersifat asimetris dan
titik nol adalah cara yang relatif murah untuk mendapatkan presisi hingga satu bit biner tambahan secara efektif. Karena aktivasi hanya dikalikan dengan bobot
konstan, nilai titik nol konstan dapat dioptimalkan dengan cukup berat.
Bobot simetris: dipaksa memiliki titik nol yang sama dengan 0. Nilai bobot dikalikan dengan nilai input dinamis dan aktivasi. Artinya, ada biaya runtime yang tidak dapat dihindari untuk mengalikan titik nol bobot dengan nilai aktivasi. Dengan memastikan bahwa titik nol adalah 0, kita dapat menghindari biaya ini.
Penjelasan matematika: ini mirip dengan bagian 2.3 dalam arXiv:1712.05877, kecuali perbedaannya adalah kami mengizinkan nilai skala per sumbu. Hal ini dapat digeneralisasi dengan mudah, sebagai berikut:
$A$ adalah matriks $m \times n$ aktivasi terkuantisasi.
$B$ adalah matriks $n \times p$ dari bobot terkuantisasi.
Pertimbangkan untuk mengalikan baris ke-$j$dari $A$, $a_j$ dengan kolom ke-$k$dari
$B$, $b_k$, yang keduanya memiliki panjang $n$. Nilai bilangan bulat yang dikuantisasi dan
nilai titik nol 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 melakukan produk titik dari nilai input dan nilai bobot.
Istilah \(\sum_{i=0}^{n} q_{b}^{(i)} z_a\) dan \(\sum_{i=0}^{n} z_a z_b\) terdiri dari konstanta yang tetap sama per pemanggilan inferensi, sehingga dapat dihitung sebelumnya.
Istilah \(\sum_{i=0}^{n} q_{a}^{(i)} z_b\) perlu dihitung setiap inferensi karena aktivasi berubah setiap inferensi. Dengan menerapkan bobot agar simetris, kita dapat menghilangkan biaya istilah ini.
spesifikasi operator yang dikuantisasi int8
Di bawah ini kami menjelaskan persyaratan kuantisasi untuk kernel tflite int8 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