ביצועים מרובים בבת אחת ותורים מהירים להודעות

ב-Neural Networks HAL 1.2 מוצג הקונספט של ביצועים רצופיים. פעולות רצף הן רצף של פעולות של אותו מודל מוכן שמתרחשות ברצף מהיר, כמו פעולות שפועלות על פריימים של צילום במצלמה או על דגימות אודיו רצופות. אובייקט רצף (bursts) משמש לשליטה בקבוצה של הרצת רצף רצף. לשמר משאבים בין הפעלות, וכך לאפשר לפעולות בתקורה. אובייקטים של Burst מאפשרים שלוש אופטימיזציות:

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

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

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

ממשקי Burst

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

segments.hal

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

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

IBurstContext.hal

IBurstContext.hal מגדיר את אובייקט הממשק של HIDL שנמצא בשירות Neural Networks.

  • IBurstContext אובייקט הקשר לניהול המשאבים של רצף נתונים.

IBurstCallback.hal

IBurstCallback.hal מגדיר את אובייקט הממשק של HIDL לקריאה חוזרת (callback) שנוצרה על ידי סביבת זמן הריצה של רשתות העצבים, ומשמש את שירות רשתות העצבים לאחזור אובייקטים מסוג hidl_memory שתואמים למזהי חריצי זיכרון.

  • IBurstCallback: אובייקט קריאה חוזרת (callback) ששירות משתמש בו כדי לאחזר אובייקטים בזיכרון.

IpredModel.hal

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

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

תמיכה בביצוע רצפים במנהל התקן

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

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

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

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

פונקציות השירות של רצף התמונות נמצאות ב-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);

הדוגמה הבאה היא הטמעת הפניה של ממשק רצף, שנמצא ב- מנהל התקן לדוגמה של רשתות נוירונים ב- 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();
}