קוונטיזציה אחרי אימון

קוונטיזציה של מהירות לאחר האימון היא שיטת המרה שיכולה להקטין את גודל המודל וגם לשפר את זמן האחזור של המעבד (CPU) מאיץ החומרה, עם ירידה קטנה בדיוק של המודל. אפשר לכמת מודל צף מסוג TensorFlow כשממירים אותו לפורמט TensorFlow Lite באמצעות הממיר של TensorFlow Lite.

שיטות אופטימיזציה

יש כמה אפשרויות לכימות לאחר האימון לבחירה. לפניכם טבלת סיכום של האפשרויות הזמינות והיתרונות שהן מספקות:

שיטה יתרונות חומרה
כימות טווח דינמי פי 4 קטן יותר, מהירות פי 2 עד 3 (CPU) מעבד
כימות של מספרים שלמים מלאים קטן פי 4, האצה של פי 3 ומעלה מעבד (CPU), Edge TPU, מיקרו-בקרים
כימות Float16 פי 2 קטן יותר, האצת GPU מעבד (CPU), GPU

עץ ההחלטות הבא יכול לעזור לקבוע איזו שיטת כימות אחרי האימון הכי מתאימה לתרחיש שלכם לדוגמה:

אפשרויות אופטימיזציה אחרי האימון

ללא כימות

נקודת ההתחלה המומלצת היא המרה למודל TFLite ללא קוונטיזציה. הפעולה הזו תיצור מודל TFLite מסוג צף.

import tensorflow as tf
converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_dir)
tflite_quant_model = converter.convert()

מומלץ לעשות את זה כשלב ראשוני כדי לוודא שהאופרטורים של מודל ה-TF המקורי תואמים ל-TFLite, ואפשר גם להשתמש בהם כבסיס לניפוי באגים בשגיאות קוונטיזציה שנוצרות עקב שיטות הקוונטיזציה שנוצרות אחרי האימון. לדוגמה, אם מודל TFLite שניתן לכמת מניב תוצאות בלתי צפויות, ומודל ה-TFLite הצף מדויק, אנחנו יכולים לצמצם את הבעיה לשגיאות שקיימות בגרסה הכמותית של אופרטורים של TFLite.

כימות טווח דינמי

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

import tensorflow as tf
converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_dir)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
tflite_quant_model = converter.convert()

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

כימות מספרים שלמים מלאים

אם תוודאו שכל המודלים המתמטיים,

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

החל מגרסה 2.7 של TensorFlow אפשר לציין את מערך הנתונים הייצוגי באמצעות חתימה כמו בדוגמה הבאה:

def representative_dataset():
  for data in dataset:
    yield {
      "image": data.image,
      "bias": data.bias,
    }

אם יש יותר מחתימה אחת במודל TensorFlow הנתון, אפשר לציין את מערך הנתונים המרובים על ידי ציון מפתחות החתימה:

def representative_dataset():
  # Feed data set for the "encode" signature.
  for data in encode_signature_dataset:
    yield (
      "encode", {
        "image": data.image,
        "bias": data.bias,
      }
    )

  # Feed data set for the "decode" signature.
  for data in decode_signature_dataset:
    yield (
      "decode", {
        "image": data.image,
        "hint": data.hint,
      },
    )

אפשר ליצור את מערך הנתונים הייצוגי על ידי מתן רשימה של img_tensor.

def representative_dataset():
  for data in tf.data.Dataset.from_tensor_slices((images)).batch(1).take(100):
    yield [tf.dtypes.cast(data, tf.float32)]

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

למטרות בדיקה, ניתן להשתמש במערך נתונים מדומה באופן הבא:

def representative_dataset():
    for _ in range(100):
      data = np.random.rand(1, 244, 244, 3)
      yield [data.astype(np.float32)]
 

מספר שלם עם חלופה צפה (באמצעות קלט/פלט צף שמוגדר כברירת מחדל)

כדי לכמת במודל מספר שלם מלא, אבל להשתמש באופרטורים צפים כאשר אין להם הטמעה של מספר שלם (כדי להבטיח שההמרה תתבצע בצורה חלקה), תוכלו לבצע את השלבים הבאים:

import tensorflow as tf
converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_dir)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.representative_dataset = representative_dataset
tflite_quant_model = converter.convert()

מספר שלם בלבד

יצירת מודלים של מספרים שלמים בלבד היא תרחיש לדוגמה נפוץ ב-TensorFlow Lite for Microcontrollers וב-Coral Edge TPU.

כמו כן, כדי להבטיח תאימות למכשירים עם מספרים שלמים בלבד (כמו מיקרו-בקרים של 8 ביט) ולמאיצים (כמו Coral Edge TPU), אפשר לאכוף כימות מלא של מספרים שלמים בכל הפעולות, כולל הקלט והפלט, באמצעות השלבים הבאים:

import tensorflow as tf
converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_dir)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.representative_dataset = representative_dataset
converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8]
converter.inference_input_type = tf.int8  # or tf.uint8
converter.inference_output_type = tf.int8  # or tf.uint8
tflite_quant_model = converter.convert()

כימות Float16

אפשר להקטין את גודל המודל של נקודה צפה (floating-point) על ידי כימת המשקולות ל-float16, תקן IEEE למספרים עם נקודה צפה (floating-point) של 16 ביט. כדי להפעיל כימות משקולות מסוג float16:

import tensorflow as tf
converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_dir)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.target_spec.supported_types = [tf.float16]
tflite_quant_model = converter.convert()

אלה היתרונות של כימות float16:

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

אלה החסרונות של כימות float16:

  • הוא לא מפחית את זמן האחזור באותה מידה כמו קוונטיזציה של מתמטיקה עם נקודות קבועות.
  • כברירת מחדל, מודל כמותי מסוג float16 'מפענח' את ערכי המשקולות ל-float32 כשמפעילים אותו במעבד. (שימו לב שההאציל של ה-GPU לא יבצע את ביטול הקונטיינר הזה כי הוא יכול לפעול על נתונים של float16).

מספר שלם בלבד: הפעלות של 16 ביט עם משקולות של 8 ביט (ניסיוני)

זוהי סכמת קוונטיזציה ניסיונית. היא דומה לסכימה 'שלם בלבד', אבל מכמתת את ההפעלות לפי הטווח שלהן עד 16 ביט, את המשקולות יוצרים כמות של מספר שלם של 8 ביט וההטיה מחושבת למספר שלם של 64 ביט. נקרא גם 'קוונטיזציה של 16x8'.

היתרון העיקרי של הקונטיזציה הזו הוא שהיא יכולה לשפר את הדיוק באופן משמעותי, אבל רק מעט להגדיל את גודל המודל.

import tensorflow as tf
converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_dir)
converter.representative_dataset = representative_dataset
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.target_spec.supported_ops = [tf.lite.OpsSet.EXPERIMENTAL_TFLITE_BUILTINS_ACTIVATIONS_INT16_WEIGHTS_INT8]
tflite_quant_model = converter.convert()

אם באופרטורים מסוימים אין תמיכה בכימות 16x8, עדיין אפשר לכמת את המודל, אבל אופרטורים שלא נתמכים יישארו במצב צף. כדי לאפשר זאת, צריך להוסיף ל- target_spec את האפשרות הבאה.

import tensorflow as tf
converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_dir)
converter.representative_dataset = representative_dataset
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.target_spec.supported_ops = [tf.lite.OpsSet.EXPERIMENTAL_TFLITE_BUILTINS_ACTIVATIONS_INT16_WEIGHTS_INT8,
tf.lite.OpsSet.TFLITE_BUILTINS]
tflite_quant_model = converter.convert()

דוגמאות לתרחישים לדוגמה שבהם סכמת הקונטיזציה הזו מספקת שיפורים ברמת הדיוק:

  • super-resolution,
  • עיבוד אותות אודיו, כמו סינון רעשים ותכנון אלומה,
  • הסרת רעשי תמונות,
  • שחזור HDR מתמונה אחת.

החיסרון של הקוונטיזציה הזו הוא:

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

מדריך למצב הקוונטיזציה הזה זמין כאן.

דיוק המודל

מכיוון שמשקולות נמדדות לאחר אימון, עלולה להיות ירידה ברמת הדיוק, במיוחד ברשתות קטנות יותר. מודלים כמותיים שעברו אימון מראש מסופקים לרשתות ספציפיות ב-TensorFlow Hub. חשוב לבדוק את הדיוק של המודל המספרי כדי לוודא שכל ירידה ברמת הדיוק עומדת במגבלות המקובלות. יש כלים להערכת הדיוק של המודל TensorFlow Lite.

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

ייצוג של טנסיטורים כמותיים

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

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

הייצוג כולל שני חלקים עיקריים:

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

  • ערכי השלמה של אינט8 (int8) בטווח [128-127], עם נקודת אפס בטווח [ -128, 127].

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