مواصفات قياس الكمي في LiteRT 8 بت

يوضّح المستند التالي مواصفات نظام التكميم 8 بت في LiteRT. يهدف ذلك إلى مساعدة مطوّري الأجهزة في توفير دعم للأجهزة من أجل الاستدلال باستخدام نماذج LiteRT المكمَّمة.

ملخّص المواصفات

نحن نقدّم مواصفات، ولا يمكننا تقديم بعض الضمانات بشأن السلوك إلا إذا تم اتّباع المواصفات. ندرك أيضًا أنّ الأجهزة المختلفة قد تتضمّن إعدادات مفضّلة وقيودًا قد تؤدي إلى حدوث انحرافات طفيفة عند تنفيذ المواصفات، ما يؤدي إلى عمليات تنفيذ غير متطابقة تمامًا. مع أنّ ذلك قد يكون مقبولاً في معظم الحالات (وسنقدّم مجموعة من الاختبارات التي تتضمّن، حسب علمنا، حدودًا مسموحًا بها لكل عملية جمعناها من عدّة نماذج)، إلا أنّ طبيعة تعلُّم الآلة (والتعلُّم العميق في معظم الحالات) تجعل من المستحيل تقديم أي ضمانات قاطعة.

يتم تقريب قيم النقطة العائمة باستخدام صيغة التكميم ذات 8 بتات التالية.

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

يتم تمثيل الأوزان لكل محور (المعروف أيضًا باسم لكل قناة في عمليات Conv) أو لكل موتر بقيم int8 المكمل الثنائي في النطاق [-127, 127] مع نقطة صفرية تساوي 0. يتم تمثيل عمليات التنشيط/الإدخال لكل موتر باستخدام قيم int8 المكمل الثنائي في النطاق [-128, 127]، مع نقطة صفرية في النطاق [-128, 127].

تتوفّر استثناءات أخرى لعمليات معيّنة موضّحة أدناه.

العدد الصحيح الموقّع مقابل العدد الصحيح غير الموقّع

ستمنح عملية تحديد الكميات في LiteRT الأولوية بشكل أساسي للأدوات والنواة من أجل int8 تحديد الكميات بمقدار 8 بت. هذا لتسهيل تمثيل التكميم المتماثل بنقطة صفر تساوي 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 مع عدد متزايد من العمليات لكل محور. في وقت كتابة هذا المستند، تتوفّر إمكانية استخدام Conv2d وDepthwiseConv2d.

متماثل مقابل غير متماثل

عمليات التفعيل غير متماثلة: يمكن أن يكون لها نقطة صفرية في أي مكان ضمن النطاق int8 الموقّع [-128, 127]. تتسم العديد من عمليات التنشيط بعدم التماثل، كما أنّ نقطة الصفر هي طريقة غير مكلفة نسبيًا للحصول على دقة إضافية تصل إلى بت ثنائي واحد. بما أنّ عمليات التنشيط يتم ضربها فقط في أوزان ثابتة، يمكن تحسين قيمة النقطة الثابتة بشكل كبير.

الأوزان متماثلة: يجب أن تكون نقطة الصفر مساوية للصفر. يتم ضرب قيم الأهمية في قيم الإدخال والتفعيل الديناميكية. وهذا يعني أنّ هناك تكلفة وقت تشغيل لا يمكن تجنُّبها لضرب نقطة الصفر الخاصة بالوزن في قيمة التنشيط. ومن خلال فرض أن تكون نقطة الصفر هي 0، يمكننا تجنُّب هذه التكلفة.

توضيح للعمليات الحسابية: هذا الإجراء مشابه للفقرة 2.3 في arXiv:1712.05877، باستثناء الاختلاف في أنّنا نسمح بأن تكون قيم المقياس لكل محور. يمكن تعميم ذلك بسهولة على النحو التالي:

‫$A$ هي مصفوفة $m \times n$ من عمليات التنشيط الكمّية.
$B$ هي مصفوفة $n \times p$ من الأوزان الكمية.
لنفترض أنّنا نضرب الصف رقم $j$من $A$، أي $a_j$، في العمود رقم $k$من $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