مواصفات تحديد حجم 8 بت في TensorFlow Lite

يوضح المستند التالي مواصفات نظام تحديد الكمي الـ 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

المراجع

arXiv:1712.05877