שמירת הידור במטמון

החל מ-Android 10, ‏Neural Networks API‏ (NNAPI) מספק פונקציות שתומכות בשמירת ארטיפקטים של הידור במטמון, וכך מפחיתות את הזמן הנדרש להידור כשאפליקציה מופעלת. בעזרת פונקציונליות האחסון במטמון, הנהג לא צריך לנהל או לנקות את הקבצים ששמורים במטמון. זוהי תכונה אופציונלית שאפשר להטמיע באמצעות NN HAL 1.2. מידע נוסף על הפונקציה הזו זמין במאמר ANeuralNetworksCompilation_setCaching.

הנהג יכול גם להטמיע מטמון של הידור בנפרד מ-NNAPI. אפשר להטמיע את התכונה הזו גם אם משתמשים בתכונות של NNAPI NDK ו-HAL caching וגם אם לא. AOSP מספקת ספריית שירותים ברמה נמוכה (מנוע אחסון במטמון). למידע נוסף, ראו הטמעת מנוע שמירת מטמון.

סקירה כללית של תהליך העבודה

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

מידע שסופק מהמטמון והצלחה במטמון

  1. האפליקציה מעבירה ספריית מטמון ותוצאת סיכום ייחודית למודל.
  2. סביבת זמן הריצה של NNAPI מחפשת את קובצי המטמון על סמך סיכום הביקורות, העדפת הביצוע והתוצאה של חלוקת המחיצות, ומוצאת את הקבצים.
  3. NNAPI פותח את קובצי המטמון ומעביר את הלחצנים לנהג באמצעות prepareModelFromCache.
  4. הנהג מכין את המודל ישירות מקובצי המטמון ומחזיר את המודל המוכנה.

מידע שסופק במטמון והחמצת אחסון במטמון

  1. האפליקציה מעבירה סיכום ביקורת (checksum) ייחודי למודל ספרייה של מטמון.
  2. סביבת זמן הריצה של NNAPI מחפשת את קובצי המטמון על סמך סיכום הביקורות, העדפת הביצוע והתוצאה של חלוקת המחיצות, ולא מוצאת את קובצי המטמון.
  3. NNAPI יוצר קובצי מטמון ריקים על סמך סיכום הביקורת, העדפת הביצוע והחלוקה, פותח את קובצי המטמון ומעביר את הלחצנים והמודל לנהג באמצעות prepareModel_1_2.
  4. הנהג מקמפל את המודל, כותב את פרטי האחסון במטמון לקובצי המטמון ומחזיר את המודל המוכשר.

לא סופק מידע על המטמון

  1. האפליקציה מפעילה את הידור הקוד בלי לספק מידע על שמירת נתונים במטמון.
  2. האפליקציה לא מעבירה שום דבר שקשור לאחסון במטמון.
  3. סביבת זמן הריצה של NNAPI מעבירה את המודל לנהג באמצעות prepareModel_1_2.
  4. מנהל ההתקן יוצר את המודל ומחזיר את המודל המוגדר.

מידע על המטמון

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

אסימון

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

מפתחות של קובצי מטמון (שני סוגים של קובצי מטמון)

יש שני סוגים של קובצי מטמון: מטמון נתונים ומטמון מודלים.

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

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

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

אבטחה

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

אחת הדרכים לעשות זאת היא שהנהג ישמור מפה מהאסימון ל-hash קריפטוגרפית של מטמון המודל. הנהג יכול לאחסן את האסימון ואת הגיבוב של מטמון המודל שלו כששומר את הידור הקוד במטמון. במהלך אחזור הידור מהמטמון, הנהג בודק את הגיבוב החדש של מטמון המודל באמצעות הטוקן המוקלט וזוג הגיבוב. המיפוי הזה אמור להישאר קבוע גם אחרי הפעלות מחדש של המערכת. הנהג יכול להשתמש בשירות של מאגר המפתחות של Android, בספריית התשתית ב-framework/ml/nn/driver/cache או בכל מנגנון מתאים אחר כדי להטמיע מנהל מיפויים. אחרי עדכון מנהל ההתקן, צריך לאפס מחדש את מנהל המיפוי כדי למנוע הכנה של קובצי מטמון מגרסה קודמת.

כדי למנוע התקפות time-of-check to time-of-use (TOCTOU), הנהג צריך לחשב את הגיבוב המוקלט לפני השמירה בקובץ, ולחשב את הגיבוב החדש אחרי העתקת תוכן הקובץ למאגר פנימי.

הקוד לדוגמה הזה ממחיש איך ליישם את הלוגיקה הזו.

bool saveToCache(const sp<V1_2::IPreparedModel> preparedModel,
                 const hidl_vec<hidl_handle>& modelFds, const hidl_vec<hidl_handle>& dataFds,
                 const HidlToken& token) {
    // Serialize the prepared model to internal buffers.
    auto buffers = serialize(preparedModel);

    // This implementation detail is important: the cache hash must be computed from internal
    // buffers instead of cache files to prevent time-of-check to time-of-use (TOCTOU) attacks.
    auto hash = computeHash(buffers);

    // Store the {token, hash} pair to a mapping manager that is persistent across reboots.
    CacheManager::get()->store(token, hash);

    // Write the cache contents from internal buffers to cache files.
    return writeToFds(buffers, modelFds, dataFds);
}

sp<V1_2::IPreparedModel> prepareFromCache(const hidl_vec<hidl_handle>& modelFds,
                                          const hidl_vec<hidl_handle>& dataFds,
                                          const HidlToken& token) {
    // Copy the cache contents from cache files to internal buffers.
    auto buffers = readFromFds(modelFds, dataFds);

    // This implementation detail is important: the cache hash must be computed from internal
    // buffers instead of cache files to prevent time-of-check to time-of-use (TOCTOU) attacks.
    auto hash = computeHash(buffers);

    // Validate the {token, hash} pair by a mapping manager that is persistent across reboots.
    if (CacheManager::get()->validate(token, hash)) {
        // Retrieve the prepared model from internal buffers.
        return deserialize<V1_2::IPreparedModel>(buffers);
    } else {
        return nullptr;
    }
}

תרחישים מתקדמים לדוגמה

בתרחישי שימוש מתקדמים מסוימים, הנהג דורש גישה לתוכן המטמון (קריאה או כתיבה) אחרי קריאת ה-compile. תרחישים לדוגמה:

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

כדי לגשת לתוכן המטמון (לקריאה או לכתיבה) אחרי קריאת ה-compilation, צריך לוודא שהדרייבר:

  • המערכת מעתיקה את הטיפול בקבצים במהלך ההפעלה של prepareModel_1_2 או prepareModelFromCache, וקוראת או מעדכנת את תוכן המטמון בשלב מאוחר יותר.
  • הטמעת לוגיקה של נעילת קבצים מחוץ לקריאה הרגילה של הידור, כדי למנוע כתיבת בו-זמנית עם קריאה או כתיבת אחרת.

הטמעת מנוע מטמון

בנוסף לממשק של NN HAL 1.2 ל-compilation caching, תוכלו למצוא גם ספריית שירותי אחסון במטמון בספרייה frameworks/ml/nn/driver/cache. ספריית המשנה nnCache מכילה קוד אחסון מתמיד שמאפשר לנהג להטמיע מטמון של הידור בלי להשתמש בתכונות המטמון של NNAPI. אפשר להטמיע את סוג האחסון במטמון של הידור בכל גרסה של NN HAL. אם הנהג בוחר להטמיע מטמון שמנותק מממשק ה-HAL, הנהג אחראי לפנות את הארטיפקטים ששמורים במטמון כשאין בהם יותר צורך.