Quy cách lượng tử hoá 8 bit của TensorFlow Lite

Tài liệu sau đây trình bày quy cách cho lược đồ lượng tử 8 bit của TensorFlow Lite. Mục đích của tính năng này là hỗ trợ các nhà phát triển phần cứng trong việc hỗ trợ phần cứng cho việc dự đoán bằng các mô hình TensorFlow Lite lượng tử hoá.

Tóm tắt thông số kỹ thuật

Chúng tôi đang cung cấp thông số kỹ thuật và chỉ có thể đưa ra một số đảm bảo về hành vi nếu quy cách đó được tuân thủ. Chúng tôi cũng hiểu rằng mỗi phần cứng có thể có các lựa chọn ưu tiên và hạn chế có thể gây ra sai lệch nhỏ khi triển khai thông số kỹ thuật dẫn đến việc triển khai không chính xác bit. Mặc dù điều đó có thể chấp nhận được trong hầu hết các trường hợp (và chúng tôi sẽ cung cấp một bộ chương trình kiểm thử theo hiểu biết tốt nhất của mình bao gồm dung sai cho mỗi thao tác mà chúng tôi thu thập được từ một số mô hình), về bản chất của công nghệ học máy (và công nghệ học sâu trong trường hợp phổ biến nhất) khiến chúng tôi không thể đưa ra bất kỳ sự đảm bảo cứng nào.

Quá trình lượng tử hoá 8 bit ước tính các giá trị dấu phẩy động bằng công thức sau.

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

Trên mỗi trục (còn gọi là mỗi kênh trong Tỷ lệ chuyển đổi) hoặc trọng số trên mỗi tensor được biểu thị bằng int8 giá trị bổ sung của 2 trong phạm vi [-127, 127] với điểm 0 bằng 0. Các lệnh kích hoạt/đầu vào cho mỗi tensor được biểu thị bằng các giá trị bổ sung của int8 2 trong phạm vi [-128, 127], với điểm 0 trong phạm vi [-128, 127].

Dưới đây là các trường hợp ngoại lệ khác đối với những thao tác cụ thể.

Số nguyên đã ký và số nguyên chưa ký

Quá trình lượng tử TensorFlow Lite sẽ chủ yếu ưu tiên công cụ và hạt nhân để lượng tử hoá int8 cho 8 bit. Việc này để thuận tiện cho việc lượng tử đối xứng được biểu thị bằng điểm 0 bằng 0. Ngoài ra, nhiều phần phụ trợ có thêm tính năng tối ưu hoá để tích luỹ int8xint8.

Mỗi trục so với mỗi tensor

Việc lượng tử hoá trên mỗi tensor có nghĩa là sẽ có một thang đo và/hoặc một điểm 0 trên toàn bộ tensor. Việc lượng tử hoá trên mỗi trục có nghĩa là sẽ có một tỷ lệ và/hoặc zero_point cho mỗi lát cắt trong quantized_dimension. Phương diện lượng tử hoá chỉ định kích thước hình dạng của Tensor tương ứng với các tỷ lệ và điểm 0. Ví dụ: một tensor t, với dims=[4, 3, 2, 1] có các tham số lượng tử: scale=[1.0, 2.0, 3.0], zero_point=[1, 2, 3], quantization_dimension=1 sẽ được lượng tử hoá trên chiều thứ hai của 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

Thông thường, quantized_dimensionoutput_channel của trọng số của các phép tích chập, nhưng về mặt lý thuyết, giá trị này có thể là kích thước tương ứng với từng sản phẩm chấm trong quá trình triển khai hạt nhân, cho phép nhiều chi tiết lượng tử hoá hơn mà không ảnh hưởng đến hiệu suất. Tính năng này có những điểm cải tiến lớn về độ chính xác.

TFLite có khả năng hỗ trợ mỗi trục cho số lượng thao tác ngày càng tăng. Tại thời điểm viết tài liệu này, chúng tôi đã hỗ trợ cho Conv2d và DepthwiseConv2d.

Đối xứng và không đối xứng

Kích hoạt không đối xứng: chúng có thể có điểm 0 ở bất kỳ đâu trong phạm vi int8 đã ký [-128, 127]. Nhiều kích hoạt về bản chất là bất đối xứng và điểm 0 là một cách tương đối ít tốn kém để có được độ chính xác bổ sung một cách hiệu quả. Vì số lượt kích hoạt chỉ được nhân với trọng số không đổi, nên giá trị điểm 0 không đổi có thể được tối ưu hoá khá nhiều.

Trọng số có tính đối xứng: bắt buộc có điểm 0 bằng 0. Giá trị trọng số được nhân với giá trị đầu vào động và giá trị kích hoạt. Điều này có nghĩa là bạn không thể tránh khỏi một chi phí thời gian chạy khi nhân điểm 0 của trọng số với giá trị kích hoạt. Bằng cách thực thi điểm 0 đó bằng 0, chúng ta có thể tránh được chi phí này.

Giải thích về cách tính toán: điều này tương tự như phần 2.3 trong arXiv:1712.05877, ngoại trừ điểm khác biệt ở chỗ chúng tôi cho phép các giá trị tỷ lệ là trên mỗi trục. Điều này sẽ được khái quát hoá một cách dễ dàng như sau:

$A$ là ma trận $m \times n$ của các thao tác kích hoạt lượng tử hoá.
$B$ là ma trận $n \times p$ của các trọng số lượng tử hoá.
Hãy xem xét nhân hàng $j$của $A$, $a_j$ với cột $k$của $B$, $b_k$, cả hai độ dài $n$. Giá trị số nguyên lượng tử hoá và giá trị điểm 0 lần lượt là $q_a$, $z_a$ và $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\]

Không thể tránh khỏi \(\sum_{i=0}^{n} q_{a}^{(i)} q_{b}^{(i)}\) vì từ này đang thực hiện sản phẩm dấu chấm của giá trị đầu vào và giá trị trọng số.

Các thuật ngữ \(\sum_{i=0}^{n} q_{b}^{(i)} z_a\) và \(\sum_{i=0}^{n} z_a z_b\) được tạo thành từ các hằng số không thay đổi đối với mỗi lệnh gọi suy luận nên có thể được tính toán trước.

Thuật ngữ \(\sum_{i=0}^{n} q_{a}^{(i)} z_b\) cần được tính toán cho mọi lượt suy luận vì khi kích hoạt sẽ thay đổi mọi thông tin dự đoán. Bằng cách thực thi trọng số là đối xứng, chúng ta có thể loại bỏ chi phí của thuật ngữ này.

thông số kỹ thuật toán tử lượng tử hoá int8

Dưới đây chúng tôi mô tả các yêu cầu lượng hoá cho các hạt nhân 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

Tài liệu tham khảo

arXiv:1712.05877