Tài liệu sau đây trình bày quy cách cho lược đồ lượng tử hoá 8 bit của LiteRT. Mục đích của hướng dẫn này là hỗ trợ các nhà phát triển phần cứng cung cấp khả năng hỗ trợ phần cứng cho suy luận bằng các mô hình LiteRT được lượng tử hoá.
Tóm tắt quy cách
Chúng tôi đang cung cấp một quy cách và chỉ có thể đảm bảo một số hành vi nếu bạn tuân thủ quy cách này. Chúng tôi cũng hiểu rằng các phần cứng khác nhau có thể có các lựa chọn ưu tiên và hạn chế có thể gây ra những sai lệch nhỏ khi triển khai quy cách, dẫn đến việc triển khai không chính xác từng 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ộ kiểm thử theo hiểu biết tốt nhất của mình, bao gồm cả dung sai cho mỗi thao tác mà chúng tôi thu thập được từ một số mô hình), nhưng bản chất của học máy (và 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ỳ đảm bảo chắc chắn nào.
Lượng tử hoá 8 bit xấp xỉ 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ọng số trên mỗi trục (còn gọi là trọng số trên mỗi kênh trong Conv ops) hoặc trọng số trên mỗi tensor được biểu thị bằng các giá trị bù hai int8 trong phạm vi [-127, 127] với điểm 0 bằng 0. Các hoạt động/đầu vào trên mỗi tensor được biểu thị bằng các giá trị bù hai int8 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 một số hoạt động cụ thể.
Số nguyên có dấu so với số nguyên không dấu
Việc lượng tử hoá LiteRT sẽ chủ yếu ưu tiên các công cụ và hạt nhân để lượng tử hoá 8 bit.int8 Điều này là để thuận tiện cho việc lượng tử hoá đố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ó các chế độ tối ưu hoá bổ sung cho việc tích luỹ int8xint8.
Theo trục so với theo tensor
Lượng tử hoá trên mỗi tensor có nghĩa là sẽ có một số tỷ lệ và/hoặc điểm không cho mỗi tensor. Lượng tử hoá theo trục có nghĩa là sẽ có một thang đo và/hoặc zero_point cho mỗi lát trong quantized_dimension. Phương diện được lượng tử hoá chỉ định phương diện của hình dạng Tensor mà các tỷ lệ và điểm 0 tương ứng. Ví dụ: một tensor t, với dims=[4, 3, 2, 1] có các tham số định lượng: scale=[1.0, 2.0, 3.0], zero_point=[1, 2, 3], quantization_dimension=1 sẽ được định lượng trên phương diện 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_dimension là output_channel của trọng số tích chập, nhưng về lý thuyết, đây có thể là phương diện tương ứng với từng tích vô hướng trong quá trình triển khai nhân, cho phép tăng độ chi tiết của lượng tử hoá mà không ảnh hưởng đến hiệu suất. Điều này giúp cải thiện đáng kể độ chính xác.
TFLite có hỗ trợ theo trục cho số lượng ngày càng tăng các hoạt động. Tại thời điểm viết tài liệu này, Conv2d và DepthwiseConv2d được hỗ trợ.
Đối xứng và không đối xứng
Các hàm 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 có dấu [-128, 127]. Nhiều lượt kích hoạt có bản chất bất đối xứng và điểm 0 là một cách tương đối rẻ để tăng thêm một bit nhị phân độ chính xác một cách hiệu quả. Vì các lượt kích hoạt chỉ được nhân với các trọng số không đổi, nên giá trị điểm không không đổi có thể được tối ưu hoá khá nhiều.
Trọng số là đối xứng: buộc phải có điểm không bằng 0. Giá trị trọng số được nhân với giá trị đầu vào và giá trị kích hoạt linh động. Điều này có nghĩa là có một chi phí thời gian chạy không thể tránh khỏi khi nhân điểm 0 của trọng số với giá trị kích hoạt. Bằng cách bắt buộc điểm 0 là 0, chúng ta có thể tránh được chi phí này.
Giải thích về toán học: đ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 là chúng tôi cho phép các giá trị tỷ lệ theo từng trục. Điều này dễ dàng được khái quát hoá như sau:
$A$ là ma trận $m \times n$ gồm các giá trị kích hoạt được lượng tử hoá.
$B$ là ma trận $n \times p$ gồm các trọng số được lượng tử hoá.
Hãy xem xét việc nhân hàng thứ $j$của $A$, $a_j$ với cột thứ $k$của $B$, $b_k$, cả hai đều có độ dài $n$. Các giá trị số nguyên được lượng tử hoá và các 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 được thuật ngữ \(\sum_{i=0}^{n} q_{a}^{(i)} q_{b}^{(i)}\) vì thuật ngữ này đang thực hiện tích vô hướng 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\) bao gồm các hằng số vẫn giữ nguyên cho mỗi lệnh gọi suy luận, do đó có thể được tính toán trước.
Bạn cần tính toán thuật ngữ \(\sum_{i=0}^{n} q_{a}^{(i)} z_b\) cho mỗi suy luận vì quá trình kích hoạt thay đổi theo từng suy luận. Bằng cách thực thi các trọng số đố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 của toán tử được lượng tử hoá int8
Dưới đây, chúng tôi mô tả các yêu cầu về lượng tử hoá đối với 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