ביצוע פרצים ותורי הודעות מהירים

Neural Networks HAL 1.2 מציג את הרעיון של ביצוע פרץ להורג. ביצוע פרץ הוא רצף של ביצועים מאותו דגם מוכן המתרחשות ברצף מהיר, כגון אלו הפועלות על פריימים של לכידת מצלמה או דגימות אודיו עוקבות. אובייקט פרץ משמש כדי לשלוט על קבוצה של ביצוע פרץ, ולשמור משאבים בין ביצועים להורג, מה שמאפשר לביצוע תקורה נמוכה יותר. אובייקטים מתפרצים מאפשרים שלוש אופטימיזציות:

  1. אובייקט פרץ נוצר לפני רצף של ביצועים, ומשוחרר כשהרצף הסתיים. בשל כך, משך החיים של האובייקט המתפרץ מרמז לנהג כמה זמן עליו להישאר במצב בעל ביצועים גבוהים.
  2. אובייקט מתפרץ יכול לשמר משאבים בין ביצועים להורג. לדוגמה, מנהל התקן יכול למפות אובייקט זיכרון בביצוע הראשון ולשמור את המיפוי באובייקט הפרץ לשימוש חוזר בביצועים הבאים. ניתן לשחרר כל משאב שמור במטמון כאשר אובייקט ה-Burst מושמד או כאשר זמן הריצה של NNAPI מודיע לאובייקט ה-Burst שהמשאב אינו נדרש עוד.
  3. אובייקט מתפרץ משתמש בתורי הודעות מהירים (FMQs) כדי לתקשר בין תהליכי האפליקציה והנהגים. זה יכול להפחית את השהיה מכיוון שה-FMQ עוקף את HIDL ומעביר נתונים ישירות לתהליך אחר דרך FIFO מעגלי אטומי בזיכרון משותף. תהליך הצרכן יודע להעמיד פריט בתור ולהתחיל בעיבוד או על ידי סקר מספר האלמנטים ב-FIFO או על ידי המתנה לדגל האירוע של ה-FMQ, אשר מסומן על ידי המפיק. דגל האירוע הזה הוא mutex מהיר של מרחב משתמש (futex).

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

מכיוון שביצועי פרץ פועלים על אותם ארגומנטים ומחזירים את אותן תוצאות כמו נתיבי ביצוע אחרים, ה-FMQs הבסיסיים חייבים להעביר את אותם נתונים אל מנהלי ההתקן של שירות NNAPI וממנו. עם זאת, FMQs יכולים להעביר רק סוגי נתונים רגילים-ישנים. העברת נתונים מורכבים מתבצעת על ידי הסדרת וסידריאליזציה של מאגרים מקוננים (סוגי וקטור) ישירות ב-FMQs, ושימוש באובייקטי התקשרות HIDL להעברת נקודות אחיזה של מאגר זיכרון לפי דרישה. צד המפיק של ה-FMQ חייב לשלוח את הודעות הבקשה או התוצאה לצרכן בצורה אטומית באמצעות MessageQueue::writeBlocking אם התור חוסם, או באמצעות MessageQueue::write אם התור אינו חוסם.

ממשקי פרץ

ממשקי התפרצות עבור ה-Neural Networks HAL נמצאים hardware/interfaces/neuralnetworks/1.2/ ומתוארים להלן. למידע נוסף על ממשקי פרץ בשכבת NDK, ראה frameworks/ml/nn/runtime/include/NeuralNetworks.h .

types.hal

types.hal מגדיר את סוג הנתונים שנשלחים על פני ה-FMQ.

  • FmqRequestDatum : אלמנט בודד של ייצוג סדרתי של אובייקט Request לביצוע וערך MeasureTiming , שנשלח על פני תור ההודעות המהיר.
  • FmqResultDatum : אלמנט בודד של ייצוג סדרתי של הערכים המוחזרים מביצוע ( ErrorStatus , OutputShapes Timing ), המוחזר דרך תור ההודעות המהיר.

IBurstContext.hal

IBurstContext.hal מגדיר את אובייקט ממשק HIDL שחי בשירות Neural Networks.

  • IBurstContext : אובייקט הקשר לניהול המשאבים של פרץ.

IBurstCallback.hal

IBurstCallback.hal מגדיר את אובייקט ממשק HIDL עבור התקשרות חוזרת שנוצרה על ידי זמן הריצה של Neural Networks ומשמש את שירות Neural Networks כדי לאחזר אובייקטי hidl_memory התואמים למזהי חריץ.

  • IBurstCallback : אובייקט Callback המשמש שירות לאחזור אובייקטי זיכרון.

IPreparedModel.hal

IPreparedModel.hal מורחב ב-HAL 1.2 עם שיטה ליצירת אובייקט IBurstContext ממודל מוכן.

  • configureExecutionBurst : מגדיר אובייקט פרץ המשמש לביצוע מסקנות מרובות על מודל מוכן ברצף מהיר.

תמיכה בהוצאה להורג מתפרצת במנהל ההתקן

הדרך הפשוטה ביותר לתמוך באובייקטים מתפרצים בשירות HIDL NNAPI היא להשתמש בפונקציית השירות burst ::android::nn::ExecutionBurstServer::create , שנמצאת ב- ExecutionBurstServer.h וארוזה ב- libneuralnetworks_common ו- libneuralnetworks_util static. לפונקציית היצרן הזו יש שני עומסים:

  • עומס יתר אחד מקבל מצביע לאובייקט IPreparedModel . פונקציית עזר זו משתמשת בשיטת executeSynchronously באובייקט IPreparedModel כדי להפעיל את המודל.
  • עומס יתר אחד מקבל אובייקט IBurstExecutorWithCache הניתן להתאמה אישית, שניתן להשתמש בו כדי לשמור משאבים במטמון (כגון מיפוי hidl_memory ) הנמשכים לאורך מספר ביצועים.

כל עומס יתר מחזיר אובייקט IBurstContext (המייצג את אובייקט ה-burst) המכיל ומנהל פתיל מאזין ייעודי משלו. השרשור הזה מקבל בקשות מה- requestChannel FMQ, מבצע את ההסקה, ואז מחזיר את התוצאות דרך ה- resultChannel FMQ. שרשור זה וכל שאר המשאבים הכלולים באובייקט IBurstContext משוחררים אוטומטית כאשר הלקוח של הפרץ מאבד את ההתייחסות שלו ל- IBurstContext .

לחלופין, אתה יכול ליצור יישום משלך של IBurstContext שמבין כיצד לשלוח ולקבל הודעות על פני requestChannel ו- resultChannel FMQs שהועברו ל- IPreparedModel::configureExecutionBurst .

פונקציות השירות Burst נמצאות ב- ExecutionBurstServer.h .

/**
 * Create automated context to manage FMQ-based executions.
 *
 * This function is intended to be used by a service to automatically:
 * 1) Receive data from a provided FMQ
 * 2) Execute a model with the given information
 * 3) Send the result to the created FMQ
 *
 * @param callback Callback used to retrieve memories corresponding to
 *     unrecognized slots.
 * @param requestChannel Input FMQ channel through which the client passes the
 *     request to the service.
 * @param resultChannel Output FMQ channel from which the client can retrieve
 *     the result of the execution.
 * @param executorWithCache Object which maintains a local cache of the
 *     memory pools and executes using the cached memory pools.
 * @result IBurstContext Handle to the burst context.
 */
static sp<ExecutionBurstServer> create(
        const sp<IBurstCallback>& callback, const FmqRequestDescriptor& requestChannel,
        const FmqResultDescriptor& resultChannel,
        std::shared_ptr<IBurstExecutorWithCache> executorWithCache);

/**
 * Create automated context to manage FMQ-based executions.
 *
 * This function is intended to be used by a service to automatically:
 * 1) Receive data from a provided FMQ
 * 2) Execute a model with the given information
 * 3) Send the result to the created FMQ
 *
 * @param callback Callback used to retrieve memories corresponding to
 *     unrecognized slots.
 * @param requestChannel Input FMQ channel through which the client passes the
 *     request to the service.
 * @param resultChannel Output FMQ channel from which the client can retrieve
 *     the result of the execution.
 * @param preparedModel PreparedModel that the burst object was created from.
 *     IPreparedModel::executeSynchronously will be used to perform the
 *     execution.
 * @result IBurstContext Handle to the burst context.
 */
  static sp<ExecutionBurstServer> create(const sp<IBurstCallback>& callback,
                                         const FmqRequestDescriptor& requestChannel,
                                         const FmqResultDescriptor& resultChannel,
                                         IPreparedModel* preparedModel);

להלן יישום ייחוס של ממשק פרץ שנמצא במנהל ההתקן לדוגמה של Neural Networks ב- frameworks/ml/nn/driver/sample/SampleDriver.cpp .

Return<void> SamplePreparedModel::configureExecutionBurst(
        const sp<V1_2::IBurstCallback>& callback,
        const MQDescriptorSync<V1_2::FmqRequestDatum>& requestChannel,
        const MQDescriptorSync<V1_2::FmqResultDatum>& resultChannel,
        configureExecutionBurst_cb cb) {
    NNTRACE_FULL(NNTRACE_LAYER_DRIVER, NNTRACE_PHASE_EXECUTION,
                 "SampleDriver::configureExecutionBurst");
    // Alternatively, the burst could be configured via:
    // const sp<V1_2::IBurstContext> burst =
    //         ExecutionBurstServer::create(callback, requestChannel,
    //                                      resultChannel, this);
    //
    // However, this alternative representation does not include a memory map
    // caching optimization, and adds overhead.
    const std::shared_ptr<BurstExecutorWithCache> executorWithCache =
            std::make_shared<BurstExecutorWithCache>(mModel, mDriver, mPoolInfos);
    const sp<V1_2::IBurstContext> burst = ExecutionBurstServer::create(
            callback, requestChannel, resultChannel, executorWithCache);
    if (burst == nullptr) {
        cb(ErrorStatus::GENERAL_FAILURE, {});
    } else {
        cb(ErrorStatus::NONE, burst);
    }
    return Void();
}