ב-Neural Networks HAL 1.2 מוצג המושג של ביצועים מהירים. ביצועים מהירים הם רצף של ביצועים של אותו מודל מוכן שמתרחשים ברצף מהיר, כמו ביצועים שפועלים על פריימים של צילום במצלמה או על דגימות אודיו עוקבות. אובייקט של הפעלה מהירה משמש לשליטה בסדרה של הפעלות מהירות, ולשמירה על משאבים בין ההפעלות, כך שהתקורה של ההפעלות נמוכה יותר. אובייקטים של Burst מאפשרים שלוש אופטימיזציות:
- אובייקט burst נוצר לפני רצף של ביצועים, ומשוחרר כשהרצף מסתיים. לכן, משך החיים של רמזים לאובייקט של פרץ מציין לדרייבר כמה זמן הוא צריך להישאר במצב של ביצועים גבוהים.
- אובייקט burst יכול לשמור משאבים בין הרצות. לדוגמה, מנהל התקן יכול למפות אובייקט זיכרון בהפעלה הראשונה ולשמור את המיפוי במטמון באובייקט burst לשימוש חוזר בהפעלות הבאות. אפשר לשחרר כל משאב שנשמר במטמון כשאובייקט ה-burst מושמד או כשזמן הריצה של NNAPI מודיע לאובייקט ה-burst שהמשאב כבר לא נדרש.
- אובייקט burst משתמש בתורים מהירים של הודעות (FMQ) כדי לתקשר בין תהליכי האפליקציה והדרייבר. הפעולה הזו יכולה לצמצם את זמן האחזור כי ה-FMQ עוקף את ה-HIDL ומעביר נתונים ישירות לתהליך אחר דרך FIFO מעגלי אטומי בזיכרון משותף. תהליך הצרכן יודע להוציא פריט מהתור ולהתחיל לעבד אותו על ידי בדיקה של מספר הרכיבים בתור FIFO או על ידי המתנה לדגל האירוע של FMQ, שמסומן על ידי היצרן. דגל האירוע הזה הוא mutex מהיר במרחב המשתמש (futex).
תור FMQ הוא מבנה נתונים ברמה נמוכה שלא מציע ערבויות לגבי משך החיים שלו בתהליכים, ואין לו מנגנון מובנה לקביעה אם התהליך בקצה השני של תור ה-FMQ פועל כמצופה. לכן, אם היצרן של תור ההודעות מת, הצרכן עלול להיתקע בהמתנה לנתונים שלא יגיעו לעולם. פתרון אחד לבעיה הזו הוא שהדרייבר ישייך את התורים להודעות מהירות (FMQ) לאובייקט של פרץ ברמה גבוהה יותר, כדי לזהות מתי הסתיים הביצוע של הפרץ.
מכיוון שהפעלות של פרץ פועלות על אותם ארגומנטים ומחזירות את אותן תוצאות כמו נתיבי הפעלה אחרים, מנהלי ההתקנים של שירות NNAPI הבסיסיים חייבים להעביר את אותם נתונים אל ומתוך מנהלי ההתקנים של שירות NNAPI. עם זאת, FMQ יכול להעביר רק סוגים של נתונים פשוטים. העברת נתונים מורכבים מתבצעת על ידי סריאליזציה ודה-סריאליזציה של מאגרי נתונים מוטמעים (סוגי וקטורים) ישירות ב-FMQ, ושימוש באובייקטים של HIDL callback להעברת ידיות של מאגרי זיכרון לפי דרישה. הצד של היצרן ב-FMQ צריך לשלוח את הבקשה או את הודעות התוצאה אל הצרכן באופן אטומי באמצעות MessageQueue::writeBlocking אם התור חוסם, או באמצעות MessageQueue::write אם התור לא חוסם.
ממשקי תמונות ברצף
ממשקי ה-burst של Neural Networks HAL נמצאים ב-hardware/interfaces/neuralnetworks/1.2/ והם מתוארים בהמשך. מידע נוסף על ממשקי burst בשכבת 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: אובייקט של קריאה חוזרת שמשמש שירות לאחזור אובייקטים של זיכרון.
IPreparedModel.hal
IPreparedModel.hal
מורחב ב-HAL 1.2 עם שיטה ליצירת אובייקט IBurstContext ממודל מוכן.
-
configureExecutionBurst: הגדרת אובייקט של פרץ, שמשמש להפעלת כמה מסקנות ברצף מהיר במודל מוכן.
תמיכה בהפעלות של פרצי נתונים בדרייבר
הדרך הפשוטה ביותר לתמוך באובייקטים של פרץ בשירות HIDL NNAPI היא באמצעות פונקציית השירות burst ::android::nn::ExecutionBurstServer::create, שנמצאת ב-ExecutionBurstServer.h ונארזת בספריות הסטטיות libneuralnetworks_common ו-libneuralnetworks_util. לפונקציית היצירה הזו יש שני עומסים:
- עומס יתר אחד מקבל מצביע לאובייקט
IPreparedModel. פונקציית השירות הזו משתמשת בשיטהexecuteSynchronouslyבאובייקטIPreparedModelכדי להפעיל את המודל. - עומס יתר אחד מקבל אובייקט
IBurstExecutorWithCacheשניתן להתאמה אישית, שאפשר להשתמש בו כדי לשמור במטמון משאבים (כמו מיפוייhidl_memory) שנשמרים בכמה הפעלות.
כל עומס יתר מחזיר אובייקט IBurstContext (שמייצג את אובייקט ההתפרצות) שמכיל ומנהל את השרשור הייעודי שלו להאזנה. השרשור הזה מקבל בקשות מ-requestChannel FMQ, מבצע את ההסקה ואז מחזיר את התוצאות דרך resultChannel FMQ. השרשור הזה וכל המשאבים האחרים שנכללים באובייקט 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();
}