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

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

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

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

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

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

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()

מספר שלם בלבד

יצירת מודלים של מספרים שלמים בלבד היא תרחיש לדוגמה נפוץ ב-LiteRT עבור מיקרו-בקרים וקורל מכשירי TPU של Edge.

כמו כן, כדי להבטיח תאימות למכשירים עם מספרים שלמים בלבד (כמו 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 "מפענח" את ערכי המשקולות כדי לצוף 32 כשמריצים על המעבד (CPU). (חשוב לשים לב שהמנוי ל-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()

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

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

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

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

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

דיוק המודל

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

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

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

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

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

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

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

  • הפעלות/קלט של Tensor שמיוצגים על ידי ערכי ה-Int8 של שני הערכים המשלימים הטווח [-128, 127], עם אפס נקודה בטווח [-128, 127].

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