يوضح المستند التالي مواصفات نظام تحديد الكمي الـ 8 بت في TensorFlow Lite. الهدف من ذلك هو مساعدة مطوري الأجهزة على توفير دعم الأجهزة للاستنتاج باستخدام نماذج TensorFlow Lite الكمية.
ملخّص المواصفات
نحن نقدّم مواصفات، ولا يمكننا تقديم بعض الضمانات بشأن السلوك إلا في حال اتّباع المواصفات. ندرك أيضًا أنّ الأجهزة المختلفة قد تتضمّن خيارات مفضّلة وقيودًا قد تؤدي إلى انحرافات طفيفة عند تنفيذ المواصفات التي تؤدي إلى عمليات تنفيذ ليست دقيقة البت. على الرغم من أنّ ذلك قد يكون مقبولاً في معظم الحالات (وسنقدم مجموعة من الاختبارات التي تتضمن، حسب ما نعرفه، قيم التفاوت في أداء العمليات التي جمعناها من نماذج متعدّدة)، إلا أنّ طبيعة التعلم الآلي (والتعلم المعمّق في الحالات الأكثر شيوعًا) تجعل من المستحيل تقديم أي ضمانات صارمة.
يعمل تحديد كميّات 8 بت على تقريب قيم النقطة العائمة باستخدام الصيغة التالية.
\[real\_value = (int8\_value - zero\_point) \times scale\]
يتم تمثيل القيم التقديرية لكل محور (المعروفة أيضًا باسم كل قناة في عمليات الإحالات الناجحة) أو القيم التقديرية لكل منظّم بقيم int8
اثنان في النطاق [-127, 127]
بقيمة صفرية تساوي 0. يتم تمثيل عمليات التفعيل/الإدخالات لكل مستوٍ بقيم int8
اثنان
مُكمِّلة في النطاق [-128, 127]
، مع وجود نقطة صفر في النطاق [-128, 127]
.
هناك استثناءات أخرى لعمليات محدّدة تم توثيقها أدناه.
العدد الصحيح الموقَّع مقابل العدد الصحيح غير الموقَّع
إنّ تحديد كمية TensorFlow Lite هو إعطاء الأولوية بشكل أساسي للأدوات والنواة
لتحديد كميّات 8 بت باستخدام الترميز int8
. هذا من أجل تسهيل عملية القياس المتماثل
التي يتم تمثيلها بنقطة صفرية تساوي 0. بالإضافة إلى ذلك، تحتوي العديد من الخلفيات على
تحسينات إضافية لتجميع int8xint8
.
لكل محور مقابل كل موتر
يعني تحديد الكم لكل متوتر أنه سيكون هناك مقياس واحد و/أو نقطة صفرية لكل متوتر كله. عند تحديد كميّات لكل محور، سيكون هناك مقياس واحد و/أو
zero_point
لكل شريحة في quantized_dimension
. يحدد البعد الكمي بُعد شكل Tensor الذي تتجاوب معه المقاييس والنقاط الصفرية. على سبيل المثال، الموتر t
مع dims=[4, 3, 2, 1]
مع
مَعلمات تحديد الكم: scale=[1.0, 2.0, 3.0]
وzero_point=[1, 2, 3]
وquantization_dimension=1
سيتم قياسه على مستوى البُعد الثاني في 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
غالبًا ما تكون السمة quantized_dimension
هي output_channel
لترجيحات الالتفاف، ولكن من الناحية النظرية يمكن أن تمثّل البُعد الذي يتوافق مع كل منتج نقطة عند تنفيذ النواة، ما يتيح زيادة دقة تحديد الكمية بدون أي تداعيات على الأداء. يؤدي ذلك إلى تحسينات كبيرة على مستوى الدقة.
يدعم TFLite كل محور عددًا متزايدًا من العمليات. في وقت هذا المستند، يوجد الدعم لـ Send2d وDepthwise Accept2d.
المتماثل مقابل غير المتماثل
عمليات التفعيل غير متماثلة: يمكن أن تحتوي على نقطة الصفر في أي مكان ضمن نطاق int8
الموقَّع [-128, 127]
. العديد من عمليات التفعيل غير متماثلة بطبيعتها،
وتُعد النقطة الصفرية طريقة غير مكلفة نسبيًا للوصول بفعالية إلى شيء ثنائي إضافي من الدقة. ونظرًا لأن عمليات التفعيل يتم ضربها فقط في الأوزان الثابتة، يمكن تحسين القيمة الثابتة صفرية بشكل كبير.
الأوزان متماثلة: يتم فرض أن تكون نقطة صفر تساوي 0. ويتم ضرب قيم الوزن في الإدخال الديناميكي وقيم التفعيل. وهذا يعني أنّ هناك تكلفة لا مفر منها في وقت التشغيل لضرب النقطة الصفرية للوزن بقيمة التفعيل. بفرض أن النقطة الصفرية هي 0، يمكننا تجنُّب هذه التكلفة.
شرح الرياضيات: يشبه هذا القسم 2.3 في arXiv:1712.05877، باستثناء الفرق الذي أننا نسمح بأن تكون قيم المقياس لكل محور. وهذا يعمّّم بسهولة، كما يلي:
$A$ عبارة عن مصفوفة $m مقدَّمة n$ لعمليات التفعيل الكمية.
$B$ عبارة عن مصفوفة $n\times p$ للأوزان الكمية.
يمكنك ضرب الصف $j$th في $A$ و$a_j$ في العمود $k$th لـ $B$ و$b_k$ وكليهما بالطول $n$. قيم الأعداد الصحيحة الكمية والقيم الصفرية هي $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