מפרט קוונטיזציה של TensorFlow Lite 8 סיביות

המסמך הבא מתאר את המפרט עבור ערכת הקוונטיזציה של 8 סיביות של TensorFlow Lite. זה נועד לסייע למפתחי חומרה לספק תמיכת חומרה להסקת מסקנות עם דגמי TensorFlow Lite כמותיים.

סיכום מפרט

אנו מספקים מפרט, ואנחנו יכולים לספק כמה הבטחות לגבי התנהגות רק אם מקיימים את המפרט. אנו גם מבינים שחומרה שונה עשויה להיות בעלת העדפות והגבלות שעלולות לגרום לסטיות קלות בעת יישום המפרט, וכתוצאה מכך יישומים שאינם ביט מדויקים. בעוד שזה עשוי להיות מקובל ברוב המקרים (ואנחנו נספק חבילה של מבחנים שלמיטב ידיעתנו כוללים סובלנות לכל פעולה שאספנו מכמה דגמים), אופי למידת מכונה (ולמידה עמוקה בנפוצים ביותר מקרה) לא מאפשר לספק ערבויות קשות.

קוונטיזציה של 8 סיביות מקרבת ערכי נקודה צפה באמצעות הנוסחה הבאה.

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

פר-ציר (המכונה פר-ערוץ ב-Conv ops) או משקלות פר-טנסור מיוצגים על ידי ערכי המשלים של int8 two בטווח [-127, 127] עם נקודת אפס שווה ל-0. הפעלות/כניסות לכל טנסור מיוצגות על ידי ערכי המשלים של int8 two בטווח [-128, 127] , עם נקודת אפס בטווח [-128, 127] .

ישנם חריגים נוספים עבור פעולות מסוימות המתועדות להלן.

מספר שלם חתום מול מספר שלם ללא סימן

קוונטיזציה של TensorFlow Lite תעדוף בעיקר כלי עבודה וקרנלים לכימות int8 עבור 8 סיביות. זה נועד לנוחות של קוונטיזציה סימטרית המיוצגת על ידי נקודת אפס השווה ל-0. בנוסף לחלקים אחוריים רבים יש אופטימיזציות נוספות לצבירה של int8xint8 .

פר-ציר לעומת פר-טנסור

קוונטיזציה לפי טנזור פירושה שיהיה סולם אחד ו/או נקודת אפס לכל טנזור שלם. פירוש כימות לפי ציר יהיה סולם אחד ו/או zero_point לכל פרוסה ב- quantized_dimension . הממד המכומתי מציין את הממד של צורת הטנזור שאליה תואמים הקשקשים ונקודות האפס. לדוגמה, טנסור 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. ערכי המשקל מוכפלים בערכי קלט דינמיים והפעלה. המשמעות היא שישנה עלות בלתי נמנעת של זמן ריצה של הכפלת נקודת האפס של המשקל עם ערך ההפעלה. על ידי אכיפה שנקודת האפס היא 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