Spesifikasi kuantisasi TensorFlow Lite 8-bit

Dokumen berikut menguraikan spesifikasi skema kuantisasi 8-bit TensorFlow Lite. Hal ini dimaksudkan untuk membantu pengembang perangkat keras dalam memberikan dukungan perangkat keras untuk inferensi dengan model TensorFlow Lite yang terkuantisasi.

Ringkasan spesifikasi

Kami memberikan spesifikasi, dan kami hanya dapat memberikan jaminan tertentu mengenai perilaku jika spesifikasi tersebut diikuti. Kami juga memahami bahwa perangkat keras yang berbeda mungkin memiliki preferensi dan batasan yang dapat menyebabkan sedikit penyimpangan ketika menerapkan spesifikasi yang mengakibatkan implementasi yang tidak tepat. Meskipun hal tersebut mungkin dapat diterima dalam sebagian besar kasus (dan kami akan memberikan serangkaian pengujian yang sejauh pengetahuan kami mencakup toleransi per operasi yang kami kumpulkan dari beberapa model), sifat pembelajaran mesin (dan pembelajaran mendalam pada model yang paling umum) kasus) tidak memungkinkan untuk memberikan jaminan yang pasti.

Kuantisasi 8-bit memperkirakan nilai floating point menggunakan rumus berikut.

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

Per-sumbu (alias per saluran dalam operasi Konv) atau bobot per-tensor diwakili oleh nilai komplemen dua int8 dalam rentang [-127, 127] dengan titik nol sama dengan 0. Aktivasi/input per-tensor diwakili oleh int8 nilai komplemen dua dalam rentang [-128, 127] , dengan titik nol dalam rentang [-128, 127] .

Ada pengecualian lain untuk operasi tertentu yang didokumentasikan di bawah ini.

Bilangan bulat yang ditandatangani vs bilangan bulat yang tidak ditandatangani

Kuantisasi TensorFlow Lite terutama akan memprioritaskan perkakas dan kernel untuk kuantisasi int8 untuk 8-bit. Hal ini demi kenyamanan kuantisasi simetris yang diwakili oleh titik nol 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 untuk 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 seluruhnya dimensi kedua dari 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

Seringkali, quantized_dimension adalah output_channel dari bobot konvolusi, namun secara teori ini bisa menjadi dimensi yang sesuai dengan setiap produk titik dalam implementasi kernel, sehingga memungkinkan lebih banyak perincian kuantisasi tanpa implikasi kinerja. Ini memberikan peningkatan besar pada akurasi.

TFLite memiliki dukungan per sumbu untuk semakin banyak operasi. Pada saat dokumen ini dibuat, terdapat dukungan untuk Conv2d dan DepthwiseConv2d.

Simetris vs asimetris

Aktivasi bersifat asimetris: titik nolnya dapat berada di mana saja dalam rentang int8 yang ditandatangani [-128, 127] . Banyak aktivasi bersifat asimetris dan titik nol adalah cara yang relatif murah untuk secara efektif mencapai presisi biner ekstra. Karena aktivasi hanya dikalikan dengan bobot konstan, nilai titik nol konstan dapat dioptimalkan secara signifikan.

Bobot bersifat simetris: dipaksa memiliki titik nol sama dengan 0. Nilai bobot dikalikan dengan input dinamis dan nilai aktivasi. Artinya, terdapat biaya runtime yang tidak dapat dihindari dalam mengalikan titik nol bobot dengan nilai aktivasi. Dengan menerapkan titik nol adalah 0 kita dapat menghindari biaya ini.

Penjelasan matematikanya: ini mirip dengan bagian 2.3 di arXiv:1712.05877 , kecuali perbedaannya adalah kami mengizinkan nilai skala per sumbu. Ini dapat digeneralisasikan dengan mudah, sebagai berikut:

\(A\) adalah matriks \(m \times n\) dari aktivasi terkuantisasi.
\(B\) adalah matriks \(n \times p\) dengan bobot terkuantisasi.
Pertimbangkan mengalikan \(j\)baris \(A\), \(a_j\) dengan \(k\)kolom\(B\), \(b_k\), keduanya panjangnya \(n\). Nilai bilangan bulat terkuantisasi dan nilai titik 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 merupakan perkalian titik dari nilai masukan dan nilai bobot.

Suku \(\sum_{i=0}^{n} q_{b}^{(i)} z_a\) dan \(\sum_{i=0}^{n} z_a z_b\) terdiri dari konstanta yang tetap sama setiap pemanggilan inferensi, sehingga dapat dihitung sebelumnya.

Suku \(\sum_{i=0}^{n} q_{a}^{(i)} z_b\) perlu dihitung setiap inferensi karena aktivasi mengubah setiap inferensi. Dengan menerapkan bobot menjadi simetris, kita dapat menghilangkan 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

Referensi

arXiv:1712.05877