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