במסמך הבא מפורט המפרט של גרסת 8 הביטים של LiteRT סכמת קוונטיזציה. היא נועדה לעזור למפתחי חומרה לספק תמיכה בחומרה לצורך הסקת מסקנות במודלים של LiteRT באמצעות כמות נתונים.
סיכום המפרט
אנחנו מספקים מפרט, ואנחנו יכולים לספק רק חלק מהאחריות התנהגות מסוימת אם פועלים לפי המפרט. אנחנו גם מבינים שחומרה שונה יש העדפות והגבלות שעלולות לגרום לסטיות קלות כאשר להטמיע את המפרט שמובילים להטמעות לא מדויקות ברמת הביט. הוא עשוי להיות מקובל ברוב המקרים (ואנחנו נספק למיטב ידיעתנו, בדיקות שכוללות סבילות לכל פעולה שנאספו מכמה מודלים), האופי של למידת המכונה (והלמידה העמוקה) במקרה הנפוץ ביותר) בלתי אפשרי לספק אחריות קשיחה.
קוונטיזציה של 8 ביט מבצעת הערכה של ערכי נקודה צפה (floating-point) באמצעות בנוסחה.
\[real\_value = (int8\_value - zero\_point) \times scale\]
לפי ציר (נקרא גם 'לכל ערוץ בפעולות המרה') או משקולות לפי טנזור מיוצגים באמצעות
int8
הערכים המשלימים של שני הערכים בטווח [-127, 127]
עם ערך שווה של אפס נקודות
ל-0. הפעלות/קלט של כל-כלי מיוצגות על ידי השילוב של int8
בטווח [-128, 127]
, עם אפס נקודות בטווח [-128, 127]
.
יש מקרים חריגים נוספים לגבי פעולות מסוימות, שמפורטות בהמשך.
מספר שלם חתום לעומת מספר שלם לא חתום
כימות LiteRT ייתן עדיפות בעיקר לכלים ולליבה עבור
קוונטיזציה של int8
ל-8 ביט. זאת מטעמי נוחות
קוונטיזציה מיוצגת על ידי נקודת אפס ששווה ל-0. בנוסף, רבות
בקצוות העורפיים יש אופטימיזציות נוספות לצבירת int8xint8
.
לפי ציר לעומת טנזור
קוונטיזציה לפי טנזור פירושה שיהיה סולם אחד ו/או אפס נקודה
את כל tensor. כימות לפי ציר פירושו שיהיה סולם אחד ו/או
zero_point
לכל פרוסה ב-quantized_dimension
. המאפיין הכמותי
מציין את הממדים של צורת ה-Tensor שהגודל ואפס הנקודות
תואמים. לדוגמה, t e n s o r 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
של המשקולות של
אבל בתיאוריה זה יכול להיות המימד שמתאים
נקודה-product בהטמעת הליבה, וכך לאפשר רמת פירוט גבוהה יותר של קונטיינרים
ללא השלכות על הביצועים. בתהליך הזה יש שיפורים משמעותיים ברמת הדיוק.
ל-TFLite יש תמיכה בכל ציר זמן במספר הולך וגדל של פעולות. בזמן במסמך זה, קיימת תמיכה עבור Conv2d ו-DepthwiseConv2d.
סימטרי לעומת אסימטרי
הפעלות הן אסימטריות: הן יכולות להופיע בכל מקום
חתמת על הטווח int8
[-128, 127]
. הפעלות רבות הן אסימטריות מטבען
נקודת הצירוף היא דרך לא יקרה יחסית
של דיוק בינארי. מכיוון שההפעלות מוכפלות רק בקבוע
את הערך הקבוע של אפס נקודות ניתן לבצע אופטימיזציה די גדולה.
המשקולות הן סימטריות: התוצאה של אפס נקודות שווה ל-0. ערכי המשקל הם כפול ערכים של קלט והפעלה דינמיים. המשמעות היא שיש עלות זמן ריצה בלתי נמנעת של הכפלת נקודת האפס של המשקל ערך הפעלה. אם לא קובעים שנקודת האפס היא 0, אנחנו יכולים להימנע מהעלות הזו.
הסבר לחשבון: דומה לסעיף 2.3 ב arXiv:1712.05877, מלבד ההפרש שאנחנו מאפשרים לערכים של קנה המידה להיות לכל ציר. זה הופך את זה לכללי באופן כללי, ככה:
$A$ היא מטריצה של $m \times 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
בהמשך נתאר את דרישות הקוונטיזציה לליבות ה-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