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

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

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

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

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

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

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

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

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

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

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

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

פרטי הקאשינג שמועברים לדרייבר כוללים טוקן ומטפלים בקובץ קאש.

אסימון

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

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

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

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

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

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

אבטחה

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

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

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

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

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

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

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

הטמעה של מנוע אחסון במטמון

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