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

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

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

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

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

סופקו פרטי המטמון וההיט של המטמון

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

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

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

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

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

פרטי המטמון

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

אסימון

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

נקודות אחיזה לקבצים של המטמון (שני סוגים של קובצי מטמון)

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

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

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

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

אבטחה

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

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

כדי למנוע התקפות של זמן הבדיקה לזמן השימוש (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;
    }
}

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

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

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

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

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

הטמעת מנוע שמירה במטמון

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