次のドキュメントでは、LiteRT の 8 ビット量子化スキームの仕様について説明します。これは、ハードウェア デベロッパーが量子化された LiteRT モデルでの推論のハードウェア サポートを提供できるようにすることを目的としています。
仕様の概要
仕様を提供しており、仕様に準拠している場合にのみ動作を保証できます。また、ハードウェアによって設定や制限が異なるため、仕様を実装する際にわずかなずれが生じ、ビット単位で正確な実装にならない場合があることも理解しています。ほとんどの場合、これは許容されます(Google は、複数のモデルから収集したオペレーションごとの許容範囲を含む、既知の最良のテストスイートを提供します)。しかし、機械学習(最も一般的なケースではディープ ラーニング)の性質上、確実な保証を提供することはできません。
8 ビット量子化では、次の式を使用して浮動小数点値を近似します。
\[real\_value = (int8\_value - zero\_point) \times scale\]
軸ごと(Conv オペレーションではチャネルごと)またはテンソルごとの重みは、ゼロ点が 0 の [-127, 127] の範囲の int8 2 の補数値で表されます。テンソルごとのアクティベーション/入力は、範囲 [-128, 127] の int8 2 の補数値で表され、ゼロ点は範囲 [-128, 127] にあります。
特定のオペレーションには、以下で説明する例外もあります。
符号付き整数と符号なし整数
LiteRT 量子化では、主に 8 ビットの int8 量子化のツールとカーネルが優先されます。これは、ゼロポイントが 0 に等しい対称量子化を表現するのに便利です。また、多くのバックエンドには int8xint8 累積の追加の最適化があります。
軸単位とテンソル単位
テンソルごとの量子化とは、テンソル全体に対して 1 つのスケールとゼロポイントが設定されることを意味します。軸ごとの量子化とは、quantized_dimension のスライスごとに 1 つのスケールと zero_point が存在することを意味します。量子化されたディメンションは、スケールとゼロ点が対応する Tensor の形状のディメンションを指定します。たとえば、量子化パラメータ scale=[1.0, 2.0, 3.0]、zero_point=[1, 2, 3]、quantization_dimension=1 を含む dims=[4, 3, 2, 1] を持つテンソル t は、t の 2 番目のディメンション全体で量子化されます。
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
多くの場合、quantized_dimension は畳み込みの重みの output_channel ですが、理論的には、カーネル実装の各ドット積に対応するディメンションにすることもできます。これにより、パフォーマンスに影響を与えることなく、量子化の粒度を上げることができます。これにより、精度が大幅に向上します。
TFLite は、増加するオペレーションに対して軸ごとのサポートを提供します。このドキュメントの執筆時点では、Conv2d と DepthwiseConv2d がサポートされています。
対称と非対称
アクティベーションは非対称です。ゼロ点は符号付き int8 範囲 [-128, 127] 内の任意の場所に設定できます。多くのアクティベーションは非対称であり、ゼロポイントは、最大 1 ビットの精度を効果的に得るための比較的安価な方法です。アクティベーションは定数の重みでのみ乗算されるため、定数のゼロポイント値をかなり最適化できます。
重みは対称です。ゼロポイントが 0 になるように強制されます。重み値は、動的な入力値とアクティベーション値で乗算されます。つまり、重みのゼロポイントとアクティベーション値を乗算するランタイム コストは避けられません。ゼロポイントを 0 に強制することで、このコストを回避できます。
数式の説明: これは arXiv:1712.05877 のセクション 2.3 と似ていますが、スケール値を軸ごとに設定できる点が異なります。これは次のように簡単に一般化できます。
$A$ は量子化されたアクティベーションの $m \times n$ 行列です。
$B$ は量子化された重みの $n \times p$ 行列です。
長さ $n$の $A$ の $j$ 行 $a_j$と $B$ の $k$ 列 $b_k$ の乗算を検討します。量子化された整数値とゼロポイント値は、それぞれ $q_a$、$z_a$、$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\]
\(\sum_{i=0}^{n} q_{a}^{(i)} q_{b}^{(i)}\) という用語は、入力値と重み値の内積を計算するため、避けることはできません。
\(\sum_{i=0}^{n} q_{b}^{(i)} z_a\) と \(\sum_{i=0}^{n} z_a z_b\) の項は、推論呼び出しごとに同じ定数で構成されているため、事前に計算できます。
アクティベーションは推論ごとに変化するため、 \(\sum_{i=0}^{n} q_{a}^{(i)} z_b\) 項は推論ごとに計算する必要があります。重みを対称にすることで、この項のコストを削除できます。
int8 量子化演算子の仕様
以下に、int8 tflite カーネルの量子化要件を示します。
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