ממשק ה-API של רשתות נוירונים של Android (NNAPI) הוא ממשק API של Android C שמיועד להרצה. פעולות עתירות חישוב של למידת מכונה במכשירי Android. NNAPI נועד לספק שכבת בסיס של פונקציונליות ברמה גבוהה יותר מסגרות של למידת מכונה, TensorFlow Lite ו-Caffe2, שבונים ומאמנים רשתות נוירונים. ה-API זמין בכל מכשירי Android שבהם פועלת גרסת Android 8.1 (רמת API 27) ומעלה.
NNAPI תומך בהסקת מסקנות על ידי החלת נתונים ממכשירי Android מאומנים שהוגדרו על ידי המפתחים. דוגמאות להסקת מסקנות כוללות סיווג תמונות, לחזות את התנהגות המשתמשים ולבחור תגובות מתאימות שאילתת החיפוש.
יש הרבה יתרונות להסקת המסקנות במכשיר:
- זמן אחזור: לא צריך לשלוח בקשה דרך חיבור לרשת. עליך להמתין לתגובה. לדוגמה, השלב הזה יכול להיות חיוני באפליקציות וידאו שמעבדות פריימים רציפים שמגיעים ממצלמה.
- זמינות: האפליקציה פועלת גם מחוץ לכיסוי הרשת.
- מהירות: חומרה חדשה שספציפית לעיבוד של רשתות נוירונים מספק חישוב מהיר יותר באופן משמעותי מאשר מעבד לשימוש כללי, לבדו.
- פרטיות: הנתונים לא נשלחים ממכשיר Android.
- עלות: אין צורך בחוות שרתים כאשר כל החישובים מבוצעים מכשיר ה-Android.
יש גם יתרונות וחסרונות שמפתח צריך להביא בחשבון:
- שימוש במערכת: כדי להעריך רשתות נוירונים, צריך עשויים להגדיל את השימוש בסוללה. כדאי לך לשקול מעקב אחר תקינות הסוללה, במקרה של חששות בנוגע לאפליקציה, במיוחד לביצוע חישובים ממושכים.
- גודל האפליקציה: חשוב לשים לב לגודל המודלים. מודלים יכולים תופסות כמה מגה-בייט של שטח. אם קיבוץ מודלים גדולים ב-APK עשויה להשפיע על המשתמשים שלך ללא רשות, אולי כדאי להוריד את אחרי התקנת האפליקציה, באמצעות מודלים קטנים יותר או ובענן. NNAPI לא מספק פונקציונליות להרצה שבענן.
לצפייה דוגמה של Android Neural Networks API כדי לראות דוגמה אחת לשימוש ב-NNAPI.
הסבר על זמן הריצה של Neural Networks API
אפשר לקרוא ל-NNAPI באמצעות ספריות, frameworks וכלים של למידת מכונה שמאפשרות למפתחים לאמן את המודלים שלהם מחוץ למכשירים ולפרוס אותם ב-Android מכשירים. אפליקציות בדרך כלל לא ישתמשו ישירות ב-NNAPI, אלא ישתמשו במקום זאת להשתמש ב-frameworks של למידת מכונה ברמה גבוהה יותר. המסגרות האלה בתורן יכולות להשתמש NNAPI לביצוע פעולות הסקת מסקנות עם האצת חומרה במכשירים נתמכים.
על סמך דרישות האפליקציה ויכולות החומרה במכשיר Android זמן הריצה ברשת הנוירונים של Android יכול להפיץ עומס העבודה של החישובים במעבדים הזמינים במכשיר, כולל חומרת רשת נוירונים, יחידות עיבוד גרפיות (GPU) ואות דיגיטלי מעבדים (DSP).
במכשירי Android ללא מנהל התקן של ספק מיוחד, זמן הריצה של NNAPI מפעיל את הבקשות במעבד (CPU).
איור 1 מציג את ארכיטקטורת המערכת ברמה הכללית עבור NNAPI.
מודל תכנות API של רשתות נוירונים
כדי לבצע חישובים באמצעות NNAPI, קודם צריך ליצור שמגדיר את החישובים לביצוע. גרף החישוב הזה, בנתוני הקלט (לדוגמה, המשקולות וההטיות שמועברות מסגרת למידת מכונה), יוצרת את המודל להערכת זמן ריצה של NNAPI.
ב-NNAPI משתמשים בארבעה הפשטות עיקריות:
- מודל: תרשים חישוב של פעולות מתמטיות והקבוע
והערכים שנלמדו בתהליך האימון. הפעולות האלה ספציפיות
נוירונים מלאכותיות. הם כוללים תמונה דו-ממדית (דו-ממד)
convolution,
לוגיסטי
(sigmoid)
הפעלה,
ליניארית מתוקנת
הפעלה של ReLU ועוד. יצירת מודל היא פעולה סנכרונית.
אחרי שתסיימו ליצור אותו, תוכלו להשתמש בו שוב בשרשורים ובאוספים.
ב-NNAPI, מודל מיוצג
ANeuralNetworksModel
מכונה. - הידור: מייצג הגדרה להידור של מודל NNAPI ל-
ברמה נמוכה יותר. יצירת הידור היא פעולה סנכרונית. פעם אחת
הוא נוצר בהצלחה, ואפשר להשתמש בו שוב בשרשורים ובהפעלות. לחשבון
ב-NNAPI, כל הידור מיוצג
ANeuralNetworksCompilation
מכונה. - זיכרון: מייצג זיכרון משותף, קבצים ממופים לזיכרון וזיכרון דומה
בתהליך אגירת נתונים. שימוש במאגר נתונים זמני של זיכרון מאפשר ל-NNAPI להעביר נתונים בזמן הריצה למנהלי התקנים
בצורה יעילה יותר. אפליקציה בדרך כלל יוצרת מאגר אחסון משותף אחד
מכילה את כל הטנזור שנדרש כדי להגדיר מודל. אפשר גם להשתמש בזיכרון
מאגרי נתונים זמניים לצורך אחסון קלט ופלט במכונת ביצוע. ב-NNAPI,
כל מאגר נתונים זמני מיוצג
ANeuralNetworksMemory
מכונה. ביצוע: ממשק להחלת מודל NNAPI על קבוצת קלטים לאסוף את התוצאות. אפשר לבצע את הפעולה באופן סינכרוני או אסינכרוני.
להפעלה אסינכרונית, ניתן להשתמש ב-threads מרובים יכולים להמתין עם אותה הפעלה. בסיום ההרצה, כל השרשורים שוחרר.
ב-NNAPI, כל הפעלה מיוצגת
ANeuralNetworksExecution
מכונה.
איור 2 מציג את זרימת התכנות הבסיסית.
שאר החלק הזה מתאר את השלבים להגדרת מודל ה-NNAPI לבצע חישוב, להדר את המודל ולהריץ את המודל שעבר הידור.
מתן גישה לנתוני האימון
נתוני המשקולות וההטיות שאומנו מאוחסנים כנראה בקובץ. כדי לספק את
זמן ריצה של NNAPI עם גישה יעילה לנתונים האלה,
ANeuralNetworksMemory
באמצעות קריאה
ANeuralNetworksMemory_createFromFd()
הפונקציה ומעבירה את מתאר הקובץ של קובץ הנתונים הפתוח. בנוסף,
להגדיר דגלי הגנת זיכרון והיסט שבו אזור הזיכרון המשותף
מתחיל בקובץ.
// Create a memory buffer from the file that contains the trained data
ANeuralNetworksMemory* mem1 = NULL;
int fd = open("training_data", O_RDONLY);
ANeuralNetworksMemory_createFromFd(file_size, PROT_READ, fd, 0, &mem1);
למרות שבדוגמה הזאת אנחנו משתמשים רק
ANeuralNetworksMemory
לדוגמה של כל המשקולות שלנו, אפשר להשתמש ביותר
מופע ANeuralNetworksMemory
למספר קבצים.
שימוש במאגרי נתונים זמניים של חומרה
אפשר להשתמש במאגרי נתונים זמניים של חומרה
עבור קלט של מודל, פלט וערכי אופרנד קבועים. במקרים מסוימים,
מאיץ NNAPI יכול לגשת
AHardwareBuffer
אובייקטים בלי שהנהג יצטרך להעתיק את הנתונים. ב-AHardwareBuffer
יש הרבה
לא כל מאיץ NNAPI תומך בכל
את ההגדרות האישיות האלה. לכן, עיינו במגבלות
רשום ב
מאמרי עזרה של ANeuralNetworksMemory_createFromAHardwareBuffer
ולבצע בדיקות מראש במכשירי יעד כדי להבטיח הידור וביצוע של פעולות.
שמשתמשים ב-AHardwareBuffer
מתנהגים כצפוי,
הקצאת מכשיר כדי לציין את המאיץ.
כדי לאפשר לסביבת זמן הריצה של NNAPI לגשת לאובייקט AHardwareBuffer
, צריך ליצור
ANeuralNetworksMemory
באמצעות קריאה
ANeuralNetworksMemory_createFromAHardwareBuffer
ומעבירים את הפונקציה
אובייקט AHardwareBuffer
, כפי שמוצג בדוגמת הקוד הבאה:
// Configure and create AHardwareBuffer object AHardwareBuffer_Desc desc = ... AHardwareBuffer* ahwb = nullptr; AHardwareBuffer_allocate(&desc, &ahwb); // Create ANeuralNetworksMemory from AHardwareBuffer ANeuralNetworksMemory* mem2 = NULL; ANeuralNetworksMemory_createFromAHardwareBuffer(ahwb, &mem2);
כשב-NNAPI לא נדרשת יותר גישה לאובייקט AHardwareBuffer
, צריך לשחרר את ה-
מופע ANeuralNetworksMemory
תואם:
ANeuralNetworksMemory_free(mem2);
הערה:
- אפשר להשתמש
AHardwareBuffer
רק לכל מאגר הנתונים הזמני. אי אפשר להשתמש בו עם פרמטרARect
. - זמן הריצה של NNAPI לא יאופס את מאגר הנתונים הזמני. צריך לוודא שמאגרי הנתונים הזמניים של הקלט והפלט יהיה נגיש לפני תזמון הביצוע.
- אין תמיכה ב- מתארי קבצים של גדר סנכרון.
- עבור
AHardwareBuffer
עם פורמטים ספציפיים לספק וביטים ספציפיים לשימוש, תלוי בהטמעה של הספק כדי לקבוע אם הלקוח או הנהג אחראים לשטיפת השקפים של Google.
דגם
מודל הוא יחידת החישוב הבסיסית ב-NNAPI. כל מודל מוגדר אופרנד אחד או יותר או פעולה אחת או יותר.
מפרסמים
רכיבים של נתונים משמשים להגדרת הגרף. אלה כוללים את מקורות הקלט והפלט של המודל, צומתי הביניים שמכילים את הנתונים עובר מפעולה אחת לאחרת, והקבועים שמועברים אל לביצוע הפעולות האלה.
יש שני סוגי אופרנדים שניתן להוסיף למודלים של NNAPI: סקלריים ו tensors.
סקלר מייצג ערך יחיד. NNAPI תומך בערכים סקלריים בערכים בוליאניים, נקודה צפה (floating-point), נקודה צפה של 32 ביט, מספר שלם בגרסת 32 ביט, ללא חתימה פורמטים של מספרים שלמים 32 ביט.
רוב הפעולות ב-NNAPI כוללות מעבדי tensor. טנזורים הם מערכים של n. NNAPI תומך בטכנולוגיית tensors עם נקודה צפה (floating-point) של 16 ביט, נקודה צפה (float) של 32 ביט, 8 ביט כמותית, כמות נתונים של 16 ביט, מספר שלם בגרסת 32 ביט ו-8 ביט ערכים בוליאניים.
לדוגמה, צורה 3 מייצגת מודל עם שתי פעולות: ואחריו יש כפל. המודל לוקח טנזור קלט ומפיק אחד את Tensor לפלט.
למודל שלמעלה יש שבעה אופרנדים. האופרנדים האלה מזוהים באופן מרומז על ידי האינדקס של הסדר שבו הן נוספו למודל. האופרנד הראשון להוסיף את האינדקס 0, השנייה אינדקס 1, וכן הלאה. Operands 1, 2, 3, ו-5 הם אופרנדים קבועים.
הסדר שבו מוסיפים את האופרנדים לא משנה. לדוגמה, המודל אופרנד פלט יכול להיות הראשון שנוסף. החלק החשוב הוא להשתמש את ערך האינדקס הנכון כשמפנות לאופרנד.
יש סוגים של פריטי אופציה. הפרמטרים האלה מציינים כשהם נוספים למודל.
לא ניתן להשתמש באופרנד גם כקלט וגם כפלט של מודל.
כל אופרנד חייב להיות קלט של מודל, קבוע או אופרנד פלט פעולה אחת בלבד.
למידע נוסף על שימוש באופרנדים, אפשר לעיין במאמר למידע נוסף על אופרנדים
תפעול
פעולה מציינת את החישובים שיש לבצע. כל פעולה מורכבת של הרכיבים האלה:
- סוג פעולה (לדוגמה: חיבור, כפל, קונבולציה),
- רשימה של אינדקסים של האופרנדים שבהם הפעולה משתמשת לקלט,
- רשימה של אינדקסים של האופרנדים שבהם הפעולה משתמשת לפלט.
הסדר ברשימות האלה חשוב; לראות את הפניית NNAPI API לערכי הקלט הצפויים ופלט של כל סוג פעולה.
עליך להוסיף למודל את האופרנדים שפעולה צורכת או מייצרת לפני הוספת הפעולה.
הסדר שבו אתם מוסיפים פעולות לא משנה. NNAPI מסתמך על של יחסי התלות שנוצרו על ידי תרשים החישוב של אופרנדים ופעולות, לקבוע את הסדר שבו הפעולות מבוצעות.
הפעולות שנתמכות ב-NNAPI מסוכמות בטבלה הבאה:
בעיה ידועה ברמת API 28: בעת העברה
ANEURALNETWORKS_TENSOR_QUANT8_ASYMM
את Tensor
ANEURALNETWORKS_PAD
שזמינה ב-Android מגרסה 9 (API ברמה 28) ואילך,
פלט מ-NNAPI לא יהיה תואם לפלט מלמידת מכונה ברמה גבוהה יותר
מסגרות, כמו
TensorFlow Lite. שלך
במקום זאת עובר רק
ANEURALNETWORKS_TENSOR_FLOAT32
הבעיה נפתרה ב-Android 10 (רמת API 29) ואילך.
יצירת מודלים
בדוגמה הבאה אנחנו יוצרים את מודל שתי הפעולות שנמצא איור 3.
כדי ליצור את המודל:
קוראים לפונקציה
ANeuralNetworksModel_create()
כדי להגדיר מודל ריק.ANeuralNetworksModel* model = NULL; ANeuralNetworksModel_create(&model);
הוסיפו את האופרנדים למודל באמצעות קריאה
ANeuralNetworks_addOperand()
סוגי הנתונים שלהם מוגדרים באמצעותANeuralNetworksOperandType
של מבנה הנתונים.// In our example, all our tensors are matrices of dimension [3][4] ANeuralNetworksOperandType tensor3x4Type; tensor3x4Type.type = ANEURALNETWORKS_TENSOR_FLOAT32; tensor3x4Type.scale = 0.f; // These fields are used for quantized tensors tensor3x4Type.zeroPoint = 0; // These fields are used for quantized tensors tensor3x4Type.dimensionCount = 2; uint32_t dims[2] = {3, 4}; tensor3x4Type.dimensions = dims;
// We also specify operands that are activation function specifiers ANeuralNetworksOperandType activationType; activationType.type = ANEURALNETWORKS_INT32; activationType.scale = 0.f; activationType.zeroPoint = 0; activationType.dimensionCount = 0; activationType.dimensions = NULL;
// Now we add the seven operands, in the same order defined in the diagram ANeuralNetworksModel_addOperand(model, &tensor3x4Type); // operand 0 ANeuralNetworksModel_addOperand(model, &tensor3x4Type); // operand 1 ANeuralNetworksModel_addOperand(model, &activationType); // operand 2 ANeuralNetworksModel_addOperand(model, &tensor3x4Type); // operand 3 ANeuralNetworksModel_addOperand(model, &tensor3x4Type); // operand 4 ANeuralNetworksModel_addOperand(model, &activationType); // operand 5 ANeuralNetworksModel_addOperand(model, &tensor3x4Type); // operand 6לאופרנדים שיש להם ערכים קבועים, כמו משקולות והטיות האפליקציה מקבלת מתהליך אימון,
ANeuralNetworksModel_setOperandValue()
וגםANeuralNetworksModel_setOperandValueFromMemory()
למשימות ספציפיות.בדוגמה הבאה מגדירים ערכים קבועים מקובץ נתוני האימון. שתואם למאגר האחסון הזמני שיצרנו בקטע יש לספק גישה אל נתוני אימון.
// In our example, operands 1 and 3 are constant tensors whose values were // established during the training process const int sizeOfTensor = 3 * 4 * 4; // The formula for size calculation is dim0 * dim1 * elementSize ANeuralNetworksModel_setOperandValueFromMemory(model, 1, mem1, 0, sizeOfTensor); ANeuralNetworksModel_setOperandValueFromMemory(model, 3, mem1, sizeOfTensor, sizeOfTensor);
// We set the values of the activation operands, in our example operands 2 and 5 int32_t noneValue = ANEURALNETWORKS_FUSED_NONE; ANeuralNetworksModel_setOperandValue(model, 2, &noneValue, sizeof(noneValue)); ANeuralNetworksModel_setOperandValue(model, 5, &noneValue, sizeof(noneValue));עבור כל פעולה בתרשים המכוון שרוצים לחשב, מוסיפים את הפעולה במודל שלך באמצעות קריאה
ANeuralNetworksModel_addOperation()
מותאמת אישית.כפרמטרים של השיחה הזו, האפליקציה צריכה לספק:
- סוג הפעולה
- מספר ערכי הקלט
- מערך האינדקסים לאופרנדים של קלט
- את הספירה של ערכי הפלט
- מערך המדדים לאופרנדים של פלט
שימו לב שלא ניתן להשתמש באופרנד גם לקלט וגם לפלט של אותה הגדרה פעולה.
// We have two operations in our example // The first consumes operands 1, 0, 2, and produces operand 4 uint32_t addInputIndexes[3] = {1, 0, 2}; uint32_t addOutputIndexes[1] = {4}; ANeuralNetworksModel_addOperation(model, ANEURALNETWORKS_ADD, 3, addInputIndexes, 1, addOutputIndexes);
// The second consumes operands 3, 4, 5, and produces operand 6 uint32_t multInputIndexes[3] = {3, 4, 5}; uint32_t multOutputIndexes[1] = {6}; ANeuralNetworksModel_addOperation(model, ANEURALNETWORKS_MUL, 3, multInputIndexes, 1, multOutputIndexes);לזהות לאילו אופרנדים המודל צריך להתייחס בתור הקלט והפלט שלו באמצעות קוראים לפונקציה
ANeuralNetworksModel_identifyInputsAndOutputs()
מותאמת אישית.// Our model has one input (0) and one output (6) uint32_t modelInputIndexes[1] = {0}; uint32_t modelOutputIndexes[1] = {6}; ANeuralNetworksModel_identifyInputsAndOutputs(model, 1, modelInputIndexes, 1 modelOutputIndexes);
אופציונלי: אפשר לציין
ANEURALNETWORKS_TENSOR_FLOAT32
לחישוב בטווח או ברמת דיוק נמוכה כמו פורמט IEEE 754 עם נקודה צפה (floating-point) של 16 ביט באמצעות התקשרותANeuralNetworksModel_relaxComputationFloat32toFloat16()
חיוג אל
ANeuralNetworksModel_finish()
כדי להשלים את ההגדרה של המודל שלכם. אם לא יהיו שגיאות, האפשרות הפונקציה מחזירה קוד תוצאהANEURALNETWORKS_NO_ERROR
ANeuralNetworksModel_finish(model);
אחרי שיוצרים מודל, אפשר להדר אותו כמה פעמים ולהריץ כל מודל או הידור כמה פעמים.
זרימת בקרה
כדי לשלב תהליך בקרה במודל NNAPI, צריך לבצע את הפעולות הבאות:
בנייה של תתי הביצוע התואמים (
then
ו-else
תת-גרפים) להצהרהIF
,condition
ו-body
תת-גרפים עבור לולאתWHILE
) כמודלים עצמאיים שלANeuralNetworksModel*
:ANeuralNetworksModel* thenModel = makeThenModel(); ANeuralNetworksModel* elseModel = makeElseModel();
ליצור אופרנדים שמפנה למודלים האלה בתוך המודל שמכיל את זרימת בקרה:
ANeuralNetworksOperandType modelType = { .type = ANEURALNETWORKS_MODEL, }; ANeuralNetworksModel_addOperand(model, &modelType); // kThenOperandIndex ANeuralNetworksModel_addOperand(model, &modelType); // kElseOperandIndex ANeuralNetworksModel_setOperandValueFromModel(model, kThenOperandIndex, &thenModel); ANeuralNetworksModel_setOperandValueFromModel(model, kElseOperandIndex, &elseModel);
מוסיפים את הפעולה של תהליך הבקרה:
uint32_t inputs[] = {kConditionOperandIndex, kThenOperandIndex, kElseOperandIndex, kInput1, kInput2, kInput3}; uint32_t outputs[] = {kOutput1, kOutput2}; ANeuralNetworksModel_addOperation(model, ANEURALNETWORKS_IF, std::size(inputs), inputs, std::size(output), outputs);
קומפילציה
שלב ההידור קובע באילו מעבדים המודל יופעל ומבקש מהנהגים המתאימים להתכונן לביצוע הפעולה. הפעולה הזו יכולה כוללים את היצירה של קוד מכונה שספציפי למעבדי המודל יפעל בתאריך.
כדי להדר מודל, מבצעים את השלבים הבאים:
קוראים לפונקציה
ANeuralNetworksCompilation_create()
כדי ליצור מכונת הידור (compilation) חדשה.// Compile the model ANeuralNetworksCompilation* compilation; ANeuralNetworksCompilation_create(model, &compilation);
לחלופין, אפשר להשתמש בהקצאת מכשירים כדי להגדיר במפורש לבחור באילו מכשירים יופעל.
אפשר לבחור להשפיע על האופן שבו זמן הריצה מתבטל בין ההספק של הסוללה על מהירות השימוש והביצוע. אפשר לעשות את זה בטלפון
ANeuralNetworksCompilation_setPreference()
// Ask to optimize for low power consumption ANeuralNetworksCompilation_setPreference(compilation, ANEURALNETWORKS_PREFER_LOW_POWER);
ההעדפות שאפשר להגדיר כוללות:
ANEURALNETWORKS_PREFER_LOW_POWER
: מומלץ לפעול באופן שמצמצם את התרוקנות הסוללה. זה רצוי של אוספים שמופעלים לעיתים קרובות.ANEURALNETWORKS_PREFER_FAST_SINGLE_ANSWER
: כדאי להחזיר תשובה יחידה במהירות האפשרית, גם אם זו הסיבה צריכת חשמל גבוהה יותר. (זוהי ברירת המחדל)ANEURALNETWORKS_PREFER_SUSTAINED_SPEED
: העדפת התפוקה המקסימלית של פריימים עוקבים, למשל בעיבוד פריימים רצופים שמגיעים מהמצלמה.
אם רוצים, אפשר להגדיר שמירה במטמון של אוסף באמצעות קריאה
ANeuralNetworksCompilation_setCaching
// Set up compilation caching ANeuralNetworksCompilation_setCaching(compilation, cacheDir, token);
שימוש ב-
getCodeCacheDir()
לcacheDir
. הערךtoken
שצוין חייב להיות ייחודי לכל מודל בדומיין את האפליקציה.השלמת הגדרת האוסף באמצעות קריאה
ANeuralNetworksCompilation_finish()
אם אין שגיאות, הפונקציה הזו מחזירה קוד תוצאה שלANEURALNETWORKS_NO_ERROR
ANeuralNetworksCompilation_finish(compilation);
גילוי והקצאה של מכשירים
במכשירי Android עם Android 10 (API ברמה 29) ואילך, NNAPI מספק שמאפשרות לספריות ולאפליקציות של למידת מכונה לקבל מידע על המכשירים הזמינים ומציינים את המכשירים שישמשו להגדיר. ציון מידע על המכשירים הזמינים מאפשר לאפליקציות לקבל את הגרסה המדויקת של מנהלי ההתקנים שנמצאה במכשיר כדי להימנע של חוסר תאימות. מתן אפשרות לאפליקציות לציין את המכשירים להפעיל קטעים שונים של המודל, לבצע אופטימיזציה של אפליקציות במכשיר שבו הן נפרסות.
גילוי מכשירים
כדאי להשתמש
ANeuralNetworks_getDeviceCount
כדי לקבל את מספר המכשירים הזמינים. לכל מכשיר, משתמשים
ANeuralNetworks_getDevice
כדי להגדיר מופע ANeuralNetworksDevice
שמפנה למכשיר הזה.
ברגע שיש לכם הפניה למכשיר, אתם יכולים לקבל מידע נוסף על באותו מכשיר באמצעות הפונקציות הבאות:
ANeuralNetworksDevice_getFeatureLevel
ANeuralNetworksDevice_getName
ANeuralNetworksDevice_getType
ANeuralNetworksDevice_getVersion
הקצאת מכשירים
כדאי להשתמש
ANeuralNetworksModel_getSupportedOperationsForDevices
כדי לגלות אילו פעולות של מודל אפשר להריץ במכשירים ספציפיים.
כדי לקבוע באילו מאיצים להשתמש לביצוע, צריך לבצע קריאה
ANeuralNetworksCompilation_createForDevices
במקום ANeuralNetworksCompilation_create
.
משתמשים באובייקט ANeuralNetworksCompilation
שמתקבל, כרגיל.
הפונקציה מחזירה שגיאה אם המודל שצוין מכיל פעולות
לא נתמך על ידי המכשירים שנבחרו.
אם מציינים כמה מכשירים, סביבת זמן הריצה היא האחראית להפצה את העבודה בכל המכשירים.
בדומה למכשירים אחרים, הטמעת מעבד NNAPI מיוצגת על ידי
ANeuralNetworksDevice
בשם nnapi-reference
ובסוג
ANEURALNETWORKS_DEVICE_TYPE_CPU
. בשיחה
ANeuralNetworksCompilation_createForDevices
, ההטמעה של המעבד (CPU) לא
שמשמש לטיפול במקרים של כשלים בהידור ובביצוע של מודלים.
באחריות האפליקציה לחלק את המודל למודלים משנה
יכול לפעול במכשירים שצוינו. אפליקציות שלא צריך לבצע ידנית
תמשיך להיות חלוקה למחיצות
ANeuralNetworksCompilation_create
להשתמש בכל המכשירים הזמינים (כולל המעבד) כדי להאיץ
מודל טרנספורמר. אם המכשירים שציינת לא יכולים לתמוך באופן מלא במודל
באמצעות ANeuralNetworksCompilation_createForDevices
,
ANEURALNETWORKS_BAD_DATA
מוחזר.
חלוקה למחיצות (partitioning) של מודל
כשיש כמה מכשירים זמינים למודל, זמן הריצה של NNAPI
ומפיץ את העבודה בין המכשירים השונים. לדוגמה, אם יותר ממכשיר אחד
סופקו ל-ANeuralNetworksCompilation_createForDevices
, כל הערכים שצוינו
המערכת תתחשב בהם בזמן הקצאת העבודה. שימו לב שאם מכשיר ה-CPU
לא מופיע ברשימה, הביצוע של המעבד (CPU) יושבת. כשמשתמשים ב-ANeuralNetworksCompilation_create
כל המכשירים הזמינים יובאו בחשבון, כולל מעבד (CPU).
ההפצה מתבצעת על ידי בחירה מתוך רשימת המכשירים הזמינים, עבור כל אחד
הפעולות בדגם, המכשיר שתומך בפעולה
את הצהרה על הביצועים הטובים ביותר, כלומר, זמן הביצוע המהיר ביותר
צריכת החשמל הנמוכה ביותר, בהתאם להעדפת הביצוע שצוינה
עם הלקוח. אלגוריתם החלוקה למחיצות (partitioning) לא מביא בחשבון אפשרויות
של חוסר היעילות שנגרמו על ידי ה-IO בין המעבדים השונים. לכן, כש
ציון מעבדים מרובים (באופן מפורש כאשר משתמשים
ANeuralNetworksCompilation_createForDevices
או באופן מרומז באמצעות באמצעות
ANeuralNetworksCompilation_create
) חשוב ליצור פרופיל לתוצאה
תרגום מכונה.
כדי להבין איך המודל מחולק למחיצות על ידי NNAPI, צריך לבדוק את
יומני Android עבור הודעה (ברמת INFO עם התג ExecutionPlan
):
ModelBuilder::findBestDeviceForEachOperation(op-name): device-index
op-name
הוא השם התיאורי של הפעולה בתרשים.
device-index
הוא האינדקס של המכשיר המועמד ברשימת המכשירים.
הרשימה הזו היא הקלט שסופק ל-ANeuralNetworksCompilation_createForDevices
או, אם משתמשים ב-ANeuralNetworksCompilation_createForDevices
, את רשימת המכשירים
מוחזרת בעת ניסיון חוזר בכל המכשירים באמצעות ANeuralNetworks_getDeviceCount
ו
ANeuralNetworks_getDevice
.
ההודעה (ברמת INFO עם התג ExecutionPlan
):
ModelBuilder::partitionTheWork: only one best device: device-name
ההודעה הזו מציינת שההצגה של כל התרשים הואצה במכשיר
device-name
ביצוע
שלב הביצוע מחיל את המודל על קבוצה של קלטים ושומר את פלט החישוב מתבצע למאגר נתונים זמני אחד או יותר או למרחב זיכרון שהאפליקציה שלכם שהוקצו.
כדי להפעיל מודל שעבר הידור, מבצעים את השלבים הבאים:
קוראים לפונקציה
ANeuralNetworksExecution_create()
כדי ליצור מכונת הפעלה חדשה.// Run the compiled model against a set of inputs ANeuralNetworksExecution* run1 = NULL; ANeuralNetworksExecution_create(compilation, &run1);
יש לציין איפה האפליקציה תקרא את ערכי הקלט של החישוב. האפליקציה שלך יכול לקרוא ערכי קלט ממאגר נתונים זמני של משתמש או משטח זיכרון שהוקצה על ידי התקשרות
ANeuralNetworksExecution_setInput()
אוANeuralNetworksExecution_setInputFromMemory()
בהתאמה.// Set the single input to our sample model. Since it is small, we won't use a memory buffer float32 myInput[3][4] = { ...the data... }; ANeuralNetworksExecution_setInput(run1, 0, NULL, myInput, sizeof(myInput));
יש לציין איפה האפליקציה כותבת את ערכי הפלט. האפליקציה שלך יכולה לכתוב ערכי פלט למאגר הנתונים הזמני של המשתמשים או לשטח זיכרון ייעודי, על ידי קריאה
ANeuralNetworksExecution_setOutput()
אוANeuralNetworksExecution_setOutputFromMemory()
בהתאמה.// Set the output float32 myOutput[3][4]; ANeuralNetworksExecution_setOutput(run1, 0, NULL, myOutput, sizeof(myOutput));
כדי לתזמן את הביצוע, קוראים לפונקציה
ANeuralNetworksExecution_startCompute()
מותאמת אישית. אם אין שגיאות, הפונקציה הזו מחזירה קוד תוצאה שלANEURALNETWORKS_NO_ERROR
// Starts the work. The work proceeds asynchronously ANeuralNetworksEvent* run1_end = NULL; ANeuralNetworksExecution_startCompute(run1, &run1_end);
קוראים ל
ANeuralNetworksEvent_wait()
. כדי להמתין שהביצוע יסתיים. אם הביצוע היה הפונקציה הזאת מחזירה קוד תוצאה שלANEURALNETWORKS_NO_ERROR
אפשר להמתין בשרשור אחר מזה שהתחיל את הביצוע.// For our example, we have no other work to do and will just wait for the completion ANeuralNetworksEvent_wait(run1_end); ANeuralNetworksEvent_free(run1_end); ANeuralNetworksExecution_free(run1);
לחלופין, אפשר להחיל קבוצה שונה של קלט על המודל שעבר הידור באמצעות להשתמש באותו מכונת הידור כדי ליצור
ANeuralNetworksExecution
מכונה.// Apply the compiled model to a different set of inputs ANeuralNetworksExecution* run2; ANeuralNetworksExecution_create(compilation, &run2); ANeuralNetworksExecution_setInput(run2, ...); ANeuralNetworksExecution_setOutput(run2, ...); ANeuralNetworksEvent* run2_end = NULL; ANeuralNetworksExecution_startCompute(run2, &run2_end); ANeuralNetworksEvent_wait(run2_end); ANeuralNetworksEvent_free(run2_end); ANeuralNetworksExecution_free(run2);
ביצוע סינכרוני
ביצוע אסינכרוני משקיע זמן כדי להריץ ולסנכרן שרשורים. בנוסף, זמן האחזור יכול להיות שונה מאוד, של עד 500 מיקרו-שניות בין מועד קבלת ההתראה שרשור, או והזמן שבו הוא קשור בסופו של דבר לליבת המעבד (CPU).
כדי לשפר את זמן האחזור, ניתן במקום זאת לנתב אפליקציה ליצירת קישור
בזמן הריצה. הקריאה הזו תוחזר רק אחרי שההסקה
שהושלמו, ולא לחזור לאחר תחילת ההסקה. במקום זאת
של התקשרות
ANeuralNetworksExecution_startCompute
לקריאת הסקת מסקנות אסינכרונית בזמן הריצה, האפליקציה קוראת
ANeuralNetworksExecution_compute
כדי לבצע קריאה מסונכרנת לסביבת זמן הריצה. קריאה אל
ANeuralNetworksExecution_compute
לא לוקח ANeuralNetworksEvent
ו
לא מותאם לקריאה אל ANeuralNetworksEvent_wait
.
הפעלות רציפות
במכשירי Android עם Android 10 (API ברמה 29) ואילך, NNAPI תומך ברצף
מופעלות באמצעות
ANeuralNetworksBurst
לאובייקט. הפעלות רצף הן רצף של פעולות של אותו אוסף
שמתרחשות ברצף מהיר, למשל כאלה שפועלות על פריימים של מצלמה
להקליט או ברצף דגימות אודיו. שימוש ב-ANeuralNetworksBurst
אובייקטים עלול
יובילו לפעולות מהירות יותר, כי הם מציינים מאיצים שמשאבים
לעשות בו שימוש חוזר בין ביצוע פעולות, ושמאיצים צריכים להישאר
רמת ביצועים גבוהה למשך הזמן לפיצוץ.
ANeuralNetworksBurst
כולל שינוי קטן בלבד בביצוע הרגיל
נתיב. אתם יוצרים אובייקט רצף באמצעות
ANeuralNetworksBurst_create
כפי שמוצג בקטע הקוד הבא:
// Create burst object to be reused across a sequence of executions ANeuralNetworksBurst* burst = NULL; ANeuralNetworksBurst_create(compilation, &burst);
הפעלות של רצף רצף הן סינכרוניות. עם זאת, במקום להשתמש
ANeuralNetworksExecution_compute
כדי לבצע כל מסקנות,
ANeuralNetworksExecution
אובייקטים עם אותו ANeuralNetworksBurst
בקריאות לפונקציה
ANeuralNetworksExecution_burstCompute
.
// Create and configure first execution object // ... // Execute using the burst object ANeuralNetworksExecution_burstCompute(execution1, burst); // Use results of first execution and free the execution object // ... // Create and configure second execution object // ... // Execute using the same burst object ANeuralNetworksExecution_burstCompute(execution2, burst); // Use results of second execution and free the execution object // ...
משחררים את האובייקט ANeuralNetworksBurst
באמצעות
ANeuralNetworksBurst_free
כאשר אין בו יותר צורך.
// Cleanup ANeuralNetworksBurst_free(burst);
תורי פקודות אסינכרוניים וביצוע גידור
ב-Android מגרסה 11 ואילך, NNAPI תומך בדרך נוספת ללוח זמנים
ביצוע אסינכרוני באמצעות
ANeuralNetworksExecution_startComputeWithDependencies()
. כשמשתמשים בשיטה הזו, ההרצה ממתינה
אירועים שצריך לסמן לפני התחלת ההערכה. לאחר שהביצוע
והפלט מוכן לשימוש, האירוע המוחזר
סומן.
בהתאם למכשירים שמנהלים את הביצוע, יכול להיות שהאירוע מגובה על ידי
גדר סנכרון. שלך
חייב להתקשר
ANeuralNetworksEvent_wait()
להמתין לאירוע ולהחזיר את המשאבים שבהם הביצוע השתמש. שלך
יכול לייבא גדרות סנכרון לאובייקט אירוע באמצעות
ANeuralNetworksEvent_createFromSyncFenceFd()
ואפשר לייצא גדרות סנכרון מאובייקט אירוע באמצעות
ANeuralNetworksEvent_getSyncFenceFd()
פלט בגודל דינמי
לתמוך במודלים שבהם גודל הפלט תלוי בקלט
נתונים - כלומר, לא ניתן לקבוע את גודלם בזמן הפעלת המודל
זמן – השתמשו
ANeuralNetworksExecution_getOutputOperandRank
וגם
ANeuralNetworksExecution_getOutputOperandDimensions
דוגמת הקוד הבאה מראה איך לעשות זאת:
// Get the rank of the output uint32_t myOutputRank = 0; ANeuralNetworksExecution_getOutputOperandRank(run1, 0, &myOutputRank); // Get the dimensions of the output std::vector<uint32_t> myOutputDimensions(myOutputRank); ANeuralNetworksExecution_getOutputOperandDimensions(run1, 0, myOutputDimensions.data());
ניקוי
שלב הניקוי מטפל בפינוי משאבים פנימיים שמשמשים את שהוכפל.
// Cleanup ANeuralNetworksCompilation_free(compilation); ANeuralNetworksModel_free(model); ANeuralNetworksMemory_free(mem1);
ניהול שגיאות וחלופה למעבד (CPU)
אם מתרחשת שגיאה במהלך החלוקה למחיצות (partitioning), אם מנהל התקן לא מצליח להדר (חלק של א) מודל, או אם נהג לא מצליח להפעיל מודל (חלק של א) שעבר הידור, יכול להיות ש-NNAPI יחזור להטמעת המעבד (CPU) שלו של הטמעה אחת או יותר. ב-AI.
אם לקוח ה-NNAPI מכיל גרסאות שעברו אופטימיזציה של הפעולה (כמו, למשל, TFLite) כדאי להשבית את החלופה של ה-CPU, טיפול בכשלים בהטמעת פעולה אופטימלית של הלקוח.
ב-Android 10, אם ההידור מבוצע באמצעות
ANeuralNetworksCompilation_createForDevices
, ואז החלופה למעבד (CPU) תושבת.
ב-Android P, ביצוע NNAPI חוזר למעבד (CPU) אם הביצוע במנהל התקן נכשל.
הדבר נכון גם ב-Android 10 כאשר ANeuralNetworksCompilation_create
מ-ANeuralNetworksCompilation_createForDevices
בשימוש.
הביצוע הראשון חוזר למחיצה היחידה, ואם היא עדיין נכשל, הוא מבצע ניסיון חוזר של המודל כולו במעבד.
אם חלוקה למחיצות (partitioning) או הידור (compilation) נכשלות, המערכת תנסה להשתמש במודל כולו ב-CPU.
יש מקרים שבהם פעולות מסוימות לא נתמכות ב-CPU, כאשר הידור או הביצוע ייכשלו, במקום לנסות להיכשל.
גם אחרי השבתת החלופה של המעבד (CPU), יכול להיות שעדיין יהיו פעולות במודל
שנקבעו במעבד. אם המעבד (CPU) מופיע ברשימת המעבדים
אל ANeuralNetworksCompilation_createForDevices
, והוא היחיד
או מעבד מידע שתומך בפעולות אלה
ביצועים של הפעולות האלה, הוא ייבחר כראשית (ללא גיבוי)
מבצע/ת.
כדי לוודא שלא תתבצע הפעלה של המעבד (CPU), משתמשים ב-ANeuralNetworksCompilation_createForDevices
בזמן ההחרגה של nnapi-reference
ברשימת המכשירים.
החל מ-Android P, אפשר להשבית את החלופה בזמן הביצוע ב-
DEBUG יוצר את גרסת ה-build על ידי הגדרת הנכס debug.nn.partition
ל-2.
דומיינים של זיכרון
ב-Android מגרסה 11 ואילך, NNAPI תומך בדומיינים של זיכרון שמספקים הקצאה וממשקים לזיכרונות אטומים. כך אפליקציות יכולות לעבור של זכרונות בכל הפעלות, כך ש-NNAPI לא יעתיק או יבצע טרנספורמציה של נתונים כשמבצעים הפעלות ברצף על אותו מנהל התקן, שלא לצורך.
תכונת דומיין הזיכרון מיועדת לטינוטורים שהם בעיקר פנימיים את הנהג/ת ולא צריכים גישה לעיתים קרובות לצד הלקוח. דוגמאות של כוללים את Tensor המצבים במודלים של רצף. לטינזורים שצריכים בגישה למעבד (CPU) לעיתים קרובות בצד הלקוח, השתמשו במקום זאת במאגרי זיכרון משותפים.
כדי להקצות זיכרון אטום, מבצעים את השלבים הבאים:
קוראים לפונקציה
ANeuralNetworksMemoryDesc_create()
כדי ליצור מתאר זיכרון חדש:// Create a memory descriptor ANeuralNetworksMemoryDesc* desc; ANeuralNetworksMemoryDesc_create(&desc);
ציון כל תפקידי הקלט והפלט המיועדים באמצעות קריאה
ANeuralNetworksMemoryDesc_addInputRole()
וגםANeuralNetworksMemoryDesc_addOutputRole()
// Specify that the memory may be used as the first input and the first output // of the compilation ANeuralNetworksMemoryDesc_addInputRole(desc, compilation, 0, 1.0f); ANeuralNetworksMemoryDesc_addOutputRole(desc, compilation, 0, 1.0f);
אפשר לציין את מידות הזיכרון בקריאה
ANeuralNetworksMemoryDesc_setDimensions()
// Specify the memory dimensions uint32_t dims[] = {3, 4}; ANeuralNetworksMemoryDesc_setDimensions(desc, 2, dims);
השלמת הגדרת התיאור באמצעות קריאה
ANeuralNetworksMemoryDesc_finish()
ANeuralNetworksMemoryDesc_finish(desc);
אפשר להקצות כמה זיכרונות שרוצים על ידי העברת המתאר אל
ANeuralNetworksMemory_createFromDesc()
// Allocate two opaque memories with the descriptor ANeuralNetworksMemory* opaqueMem; ANeuralNetworksMemory_createFromDesc(desc, &opaqueMem);
שחרר את מתאר הזיכרון כשכבר אין לך צורך בו.
ANeuralNetworksMemoryDesc_free(desc);
הלקוח יכול להשתמש רק באובייקט ANeuralNetworksMemory
שנוצר עם
ANeuralNetworksExecution_setInputFromMemory()
או
ANeuralNetworksExecution_setOutputFromMemory()
בהתאם לתפקידים
שצוינו באובייקט ANeuralNetworksMemoryDesc
. ההיסט והאורך
ערך הארגומנטים צריך להיות 0, מה שמעיד על כך שנעשה שימוש בכל הזיכרון. הלקוח/ה
עשוי גם להגדיר באופן מפורש את התוכן של הזיכרון או לחלץ אותו באמצעות
ANeuralNetworksMemory_copy()
אתם יכולים ליצור זיכרונות אטומים עם תפקידים או עם תפקידים שלא צוינו.
במקרה כזה, יצירת הזיכרון עלולה להיכשל עם
הסטטוס ANEURALNETWORKS_OP_FAILED
אם הוא לא נתמך על ידי
לנהגים. מומלץ ללקוח ליישם לוגיקת חלופי על ידי הקצאת
מאגר נתונים זמני גדול מספיק שמגובה על ידי Ashmem או BLOB במצב AHardwareBuffer
.
כשכבר לא נדרשת ל-NNAPI גישה לאובייקט הזיכרון האטום,
מופע ANeuralNetworksMemory
התואם:
ANeuralNetworksMemory_free(opaqueMem);
מדידת ביצועים
אפשר להעריך את ביצועי האפליקציה על ידי מדידת זמן הביצוע או לפי הפרופיילינג.
זמן הביצוע
כשרוצים לקבוע את הזמן הביצוע הכולל לאורך זמן הריצה, אפשר להשתמש
את ממשק ה-API הסינכרוני של הפעלה ומודדים את הזמן שהוקדש לקריאה. אחרי ש
שרוצים לקבוע את זמן הביצוע הכולל דרך רמה נמוכה יותר של התוכנה
סטאק, אפשר להשתמש
ANeuralNetworksExecution_setMeasureTiming
וגם
ANeuralNetworksExecution_getDuration
כדי לקבל:
- את זמן הביצוע במאוץ (לא במנהל התקן, שפועל על המארח מעבד מידע).
- בזמן הביצוע של הנהג, כולל זמן על המאיץ.
זמן הביצוע במנהל התקן לא כולל תקורה, כמו זו של זמן הריצה עצמה ואת ה-IPC שנדרש כדי שזמן הריצה יוכל לתקשר עם הנהג.
ממשקי ה-API האלה מודדים את משך הזמן בין העבודה שהוגשה לבין העבודה שהושלמה האירועים, במקום הזמן שנהג או מאיץ מקדישים לביצוע הסקת מסקנות, שאולי נקטעה על ידי שינוי ההקשר.
לדוגמה, אם הסקה 1 מתחילה, הנהג מפסיק לעבוד כדי לבצע את ההסקה 2, ואז מחדשים את ההסקה 1, את זמן הביצוע מסקנות 1 תכלול את הזמן שבו העבודה הופסקה כדי לבצע את ההסקה 2.
פרטי התזמון האלה יכולים להיות שימושיים לפריסת ייצור של לאיסוף נתוני טלמטריה לשימוש במצב אופליין. אפשר להשתמש בנתוני התזמון כדי לשנות את האפליקציה כדי לשפר את הביצועים.
כשמשתמשים בפונקציונליות הזו, חשוב לזכור את הדברים הבאים:
- איסוף פרטי התזמון עשוי להיות כרוך בעלות ביצועים.
- רק נהג יכול לחשב את הזמן שבלה בעצמו או מאיץ, לא כולל הזמן שהוקדש לזמן ריצה של NNAPI וב-IPC.
- אפשר להשתמש בממשקי ה-API האלה רק עם
ANeuralNetworksExecution
נוצר באמצעותANeuralNetworksCompilation_createForDevices
עםnumDevices = 1
. - אף נהג לא צריך את האפשרות לדווח על פרטי התזמון.
יצירת פרופיל לאפליקציה באמצעות Android Systrace
החל מ-Android 10, מערכת NNAPI יוצרת באופן אוטומטי אירועי systrace שאפשר להשתמש בהם כדי ליצור פרופיל לקידום האפליקציה.
מקור ה-NNAPI מגיע עם כלי עזר parse_systrace
לעיבוד
אירועי systrace שנוצרו על ידי האפליקציה שלכם וצרו תצוגת טבלה שמציגה
הזמן שהוקדש לשלבים השונים של מחזור החיים של המודל (יצירת שילובים,
הכנה, ביצוע הידור וסיום) ושכבות שונות של
תרגום מכונה. השכבות שבהן האפליקציה מחולקת הן:
Application
: קוד האפליקציה הראשיRuntime
: זמן ריצה ל-NNAPIIPC
: התקשורת בין התהליכים בין זמן הריצה של NNAPI לבין הנהג קודDriver
: התהליך של מנהל המאיץ.
יצירת נתוני הניתוח של הפרופיילינג
בהנחה שבדקתם את עץ המקור של AOSP ב-$ANDROID_BUILD_TOP באמצעות הדוגמה לסיווג תמונות של TFLite. בתור אפליקציית יעד, אפשר ליצור את נתוני הפרופיילינג של ה-NNAPI עם את השלבים הבאים:
- מפעילים את ה-systrace של Android באמצעות הפקודה הבאה:
$ANDROID_BUILD_TOP/external/chromium-trace/systrace.py -o trace.html -a org.tensorflow.lite.examples.classification nnapi hal freq sched idle load binder_driver
הפרמטר -o trace.html
מציין שהמעקבים יהיו
שכתוב בtrace.html
. כאשר יוצרים פרופיל של אפליקציה מסוימת,
מחליפים את org.tensorflow.lite.examples.classification
בשם התהליך
שצוין בקובץ המניפסט של האפליקציה.
אחד ממסוף המעטפת יישאר עמוס, ולא מריצים את הפקודה
כי הוא ממתין באופן אינטראקטיבי לסיום של enter
.
- לאחר התחלת איסוף ה-systrace, מפעילים את האפליקציה ומריצים את של ההשוואה לשוק.
במקרה שלנו, אפשר להפעיל את האפליקציה סיווג תמונות ב-Android Studio או ישירות מממשק המשתמש של הטלפון לבדיקה, אם האפליקציה כבר הותקנה. כדי ליצור נתוני NNAPI מסוימים, צריך להגדיר את האפליקציה לשימוש ב-NNAPI באמצעות בחירת NNAPI כמכשיר יעד בתיבת הדו-שיח של הגדרת האפליקציה.
בסיום הבדיקה, סיים את המערכת על ידי לחיצה על
enter
הטרמינל פעיל החל משלב 1.מריצים את הכלי
systrace_parser
כדי ליצור נתונים סטטיסטיים מצטברים:
$ANDROID_BUILD_TOP/frameworks/ml/nn/tools/systrace_parser/parse_systrace.py --total-times trace.html
המנתח מקבל את הפרמטרים הבאים:
- --total-times
: הצגת משך הזמן הכולל בשכבה, כולל הזמן
בהמתנה לביצוע קריאה לשכבה בסיסית
- --print-detail
: מדפיסה את כל האירועים שנאספו מ-systrace
- --per-execution
: מדפיס רק את הביצוע ואת תתי השלבים שלו
(כזמנים לכל ביצוע) במקום נתונים סטטיסטיים לכל השלבים
- --json
: יפיק את הפלט בפורמט JSON
כך אפשר לראות דוגמה לפלט:
===========================================================================================================================================
NNAPI timing summary (total time, ms wall-clock) Execution
----------------------------------------------------
Initialization Preparation Compilation I/O Compute Results Ex. total Termination Total
-------------- ----------- ----------- ----------- ------------ ----------- ----------- ----------- ----------
Application n/a 19.06 1789.25 n/a n/a 6.70 21.37 n/a 1831.17*
Runtime - 18.60 1787.48 2.93 11.37 0.12 14.42 1.32 1821.81
IPC 1.77 - 1781.36 0.02 8.86 - 8.88 - 1792.01
Driver 1.04 - 1779.21 n/a n/a n/a 7.70 - 1787.95
Total 1.77* 19.06* 1789.25* 2.93* 11.74* 6.70* 21.37* 1.32* 1831.17*
===========================================================================================================================================
* This total ignores missing (n/a) values and thus is not necessarily consistent with the rest of the numbers
המנתח עלול להיכשל אם האירועים שנאספו לא מייצגים נתונים מלאים מעקב אחר נתוני אפליקציות. באופן ספציפי, היא עלולה להיכשל אם ייווצרו אירועי systrace כדי לסמן את סוף הקטע נמצאים במעקב ללא אירוע התחלת הקטע. זה קורה בדרך כלל אם חלק מהאירועים מקודמים נוצרים סשן של יצירת פרופילים כשמתחילים את איסוף המערכת. במקרה כזה, יהיה עליך להפעיל שוב את הפרופיל.
הוספת נתונים סטטיסטיים עבור קוד האפליקציה לפלט systrace_parser
האפליקציה parse_systrace מבוססת על מערכת Android המובנית החדשה. אפשר להוסיף עקבות לפעולות ספציפיות באפליקציה באמצעות ממשק API של systrace (ב-Java , לאפליקציות מקוריות ) עם שמות של אירועים בהתאמה אישית.
כדי לשייך את האירועים בהתאמה אישית לשלבים במחזור החיים של האפליקציה, מוסיפים את שם האירוע לאחת מהמחרוזות הבאות:
[NN_LA_PI]
: אירוע ברמת האפליקציה לאתחול[NN_LA_PP]
: אירוע ברמת האפליקציה להכנה[NN_LA_PC]
: אירוע ברמת האפליקציה עבור הידור[NN_LA_PE]
: אירוע ברמת האפליקציה לביצוע
זאת דוגמה לאופן שבו אפשר לשנות את סיווג התמונות בפורמט TFLite
באמצעות הוספת קטע runInferenceModel
לשלב Execution
בשכבה Application
שמכילה קטעים אחרים preprocessBitmap
לא ייכללו במעקבי NNAPI. הקטע runInferenceModel
יהיה
חלק מאירועי ה-systrace שעובדו על ידי מנתח ה-nnapi systrace:
Kotlin
/** Runs inference and returns the classification results. */ fun recognizeImage(bitmap: Bitmap): List{ // This section won’t appear in the NNAPI systrace analysis Trace.beginSection("preprocessBitmap") convertBitmapToByteBuffer(bitmap) Trace.endSection() // Run the inference call. // Add this method in to NNAPI systrace analysis. Trace.beginSection("[NN_LA_PE]runInferenceModel") long startTime = SystemClock.uptimeMillis() runInference() long endTime = SystemClock.uptimeMillis() Trace.endSection() ... return recognitions }
Java
/** Runs inference and returns the classification results. */ public ListrecognizeImage(final Bitmap bitmap) { // This section won’t appear in the NNAPI systrace analysis Trace.beginSection("preprocessBitmap"); convertBitmapToByteBuffer(bitmap); Trace.endSection(); // Run the inference call. // Add this method in to NNAPI systrace analysis. Trace.beginSection("[NN_LA_PE]runInferenceModel"); long startTime = SystemClock.uptimeMillis(); runInference(); long endTime = SystemClock.uptimeMillis(); Trace.endSection(); ... Trace.endSection(); return recognitions; }
איכות השירות
ב-Android מגרסה 11 ואילך, NNAPI מאפשר איכות שירות טובה יותר (QoS) ומאפשרת לאפליקציה לציין את העדיפויות היחסיות של המודלים שלה, משך הזמן המקסימלי הצפוי להכנת מודל נתון, והסכום המקסימלי של הזמן הצפוי להשלים חישוב נתון. גם Android 11 כולל קודי תוצאות נוספים של NNAPI שמאפשרות לאפליקציות להבין כשלים כמו ביצוע חסר ולוחות זמנים פעילים.
הגדרת העדיפות של עומס העבודה
כדי להגדיר את העדיפות של עומס עבודה מסוג NNAPI, יש להפעיל
ANeuralNetworksCompilation_setPriority()
לפני השיחה אל ANeuralNetworksCompilation_finish()
.
הגדרת מועדים אחרונים
אפליקציות יכולות להגדיר מועדים אחרונים להידור של המודלים וגם להסקת המסקנות.
- כדי להגדיר את הזמן הקצוב לתפוגה של הידור, צריך להפעיל
ANeuralNetworksCompilation_setTimeout()
לפני השיחה אלANeuralNetworksCompilation_finish()
. - כדי להגדיר את הזמן הקצוב להסקת מסקנות, צריך להפעיל
ANeuralNetworksExecution_setTimeout()
לפני התחלת האוסף.
מידע נוסף על אופרנדים
בקטע הבא מתוארים נושאים מתקדמים שקשורים לשימוש באופרנדים.
טנסטורים בכמות גדולה
טנזור כמותי הוא דרך קומפקטית לייצג מערך n-ממדי של של נקודה צפה (floating-point).
NNAPI תומך במגעים כמותיים אסימטריים ב-8 ביט. עבור הפרמטרים האלה, של כל תא מיוצג על ידי מספר שלם של 8 ביט. משויך אל tensor הוא סולם וערך של אפס נקודות. הם משמשים להמרת 8-bit שלמים לערכי הנקודה הצפה שמיוצגים.
הנוסחה היא:
(cellValue - zeroPoint) * scale
כאשר הערך של ZeroPoint הוא מספר שלם של 32 ביט והקנה מידה של ערך צף של 32 ביט כערך הנקודות.
בהשוואה לטינורים של ערכי נקודה צפה (floating-point) של 32 ביט, tenors כמותיים של 8 ביט יש שני יתרונות:
- האפליקציה קטנה יותר, מאחר שהמשקולות המאומנות תופסות רבע מהגודל של img_tensors 32-bit.
- לעיתים קרובות ניתן לבצע את החישובים מהר יותר. הסיבה לכך היא הכמות הקטנה יותר של נתונים שצריך לאחזר מהזיכרון והיעילות של המעבדים כמו DSP במתמטיקה של מספרים שלמים.
למרות שאפשר להמיר מודל נקודה צפה למודל כמותי, ראינו שתוצאות טובות יותר מתקבלות על ידי אימון של מודלים מודל טרנספורמר באופן ישיר. למעשה, רשת הנוירונים לומדת לפצות על רמת פירוט גבוהה יותר של כל ערך. לכל טנזור כמותי, ערכי אפס הנקודות נקבעים במהלך האימון.
ב-NNAPI, מגדירים סוגים של רכיב img_tensor
ANeuralNetworksOperandType
של מבנה הנתונים
ANEURALNETWORKS_TENSOR_QUANT8_ASYMM
צריך גם לציין את קנה המידה ואת ערך אפסי של ה-tensor בנתונים האלה
שלנו.
בנוסף למכסי img_ quantor_ עדינים א-סימטריים של 8 ביט, NNAPI תומך בדברים הבאים:
ANEURALNETWORKS_TENSOR_QUANT8_SYMM_PER_CHANNEL
אפשר להשתמש בו כדי לייצג משקולות פעולות שלCONV/DEPTHWISE_CONV/TRANSPOSED_CONV
.ANEURALNETWORKS_TENSOR_QUANT16_ASYMM
אפשר להשתמש בה במצב הפנימי שלQUANTIZED_16BIT_LSTM
ANEURALNETWORKS_TENSOR_QUANT8_SYMM
שיכול להיות קלטANEURALNETWORKS_DEQUANTIZE
אופרנדים אופציונליים
ביצוע כמה פעולות, כמו
ANEURALNETWORKS_LSH_PROJECTION
להשתמש באופרנדים אופציונליים. כדי לציין במודל שהאופרנד האופציונלי הוא
שהושמט, מפעילים את הפונקציה
ANeuralNetworksModel_setOperandValue()
פונקציה, העברת NULL
למאגר הנתונים הזמני ו-0 לאורך האורך.
אם ההחלטה אם האופרנד קיים או לא משתנה בכל אחד מהמצבים האלה
מציינים שהאופרנד מושמט באמצעות הפקודה
ANeuralNetworksExecution_setInput()
או
ANeuralNetworksExecution_setOutput()
הפונקציה מעבירה את NULL
בשביל מאגר הנתונים הזמני ו-0 בשביל האורך.
Tensors בדרגה לא ידועה
ב-Android 9 (רמת API 28) נוספו אופרנדים למודל של מימדים לא ידועים, אבל דירוג ידוע (מספר המאפיינים). הוספה של Android 10 (רמת API 29) Tensors בעלי דירוג לא ידוע, כפי שמוצג ANeuralNetworksOperandType.
בנצ'מרק של NNAPI
נקודת ההשוואה של NNAPI זמינה ב-AOSP בplatform/test/mlts/benchmark
(אפליקציית השוואה לשוק) ו-platform/test/mlts/models
(מודלים ומערכי נתונים).
נקודת ההשוואה בודקת את זמן האחזור והדיוק ומשווה בין הנהגים שנעשתה באמצעות Tensorflow Lite שפועלת על המעבד (CPU), עבור אותם דגמים של מערכי נתונים.
כדי להשתמש בנתוני ההשוואה לשוק:
לחבר מכשיר Android יעד למחשב שלך, לפתוח חלון טרמינל, מוודאים שניתן להגיע למכשיר דרך adb.
אם יש כמה מכשירי Android מחוברים, צריך לייצא את מכשיר היעד משתנה סביבה
ANDROID_SERIAL
.מנווטים לספריית המקור ברמה העליונה של Android.
מריצים את הפקודות הבאות:
lunch aosp_arm-userdebug # Or aosp_arm64-userdebug if available ./test/mlts/benchmark/build_and_run_benchmark.sh
בסיום הפעלה של נקודת השוואה, התוצאות שלה יוצגו כדף HTML הועברה אל
xdg-open
.
יומני NNAPI
NNAPI יוצר מידע אבחון שימושי ביומני המערכת. כדי לנתח את היומנים, צריך להשתמש ב-logcat של Google.
הפעלת רישום מפורט ביומן NNAPI לשלבים או לרכיבים ספציפיים על ידי הגדרת
המאפיין debug.nn.vlog
(באמצעות adb shell
) לרשימת הערכים הבאה,
שמופרדות באמצעות רווח, נקודתיים או פסיק:
model
: בניית דגמיםcompilation
: יצירת תוכנית הביצוע וההידור של המודלexecution
: הפעלת המודלcpuexe
: ביצוע פעולות באמצעות הטמעת מעבד (CPU) של NNAPImanager
: תוספי NNAPI, ממשקים זמינים ויכולות שקשורות ליכולותall
או1
: כל הרכיבים שלמעלה
לדוגמה, כדי להפעיל רישום מפורט מלא ביומן, צריך להשתמש בפקודה
adb shell setprop debug.nn.vlog all
כדי להשבית רישום מפורט ביומן, משתמשים בפקודה
adb shell setprop debug.nn.vlog '""'
לאחר ההפעלה, הרישום המפורט ביומן יוצר רשומות ביומן ברמת INFO בתג שמוגדר לשם השלב או הרכיב.
לצד debug.nn.vlog
ההודעות המבוקרות, רכיבי ה-API של NNAPI מספקים
רשומות אחרות ביומן ברמות שונות, כשכל אחת מהן משתמשת בתג יומן ספציפי.
כדי לקבל רשימה של רכיבים, מחפשים בעץ המקור באמצעות את הביטוי הבא:
grep -R 'define LOG_TAG' | awk -F '"' '{print $2}' | sort -u | egrep -v "Sample|FileTag|test"
הביטוי מחזיר כרגע את התגים הבאים:
- BurstBuilder
- התקשרות חזרה
- CompilationBuilder
- מעבד (CPU)
- הכלי ליצירת פעולות
- ExecutionBurstController
- ExecutionBurstServer
- תוכנית הפעלה
- פיבונאצ'ידרייבר
- רפליקה של תרשים
- IndexedFormWrapper
- IonWatcher
- מנהל
- זיכרון
- קובצי MemoryUtils
- מטא-מודל
- ModelArgumentInfo
- בונה מודלים
- רשתות נוירונים
- מעבד מידע
- תפעול
- כלי תפעול
- פרטי חבילה
- כלי גיבוב (TokenHasher)
- TypeManager (מנהל סוג)
- עזר
- אימותHal
- VersionedInterfaces
כדי לקבוע את רמת ההודעות ביומן שיוצגו על ידי logcat
, צריך להשתמש ב-
משתנה הסביבה ANDROID_LOG_TAGS
.
כדי להציג את הרשימה המלאה של הודעות יומן NNAPI ולהשבית הודעות אחרות, צריך להגדיר את ANDROID_LOG_TAGS
לערך
הבאים:
BurstBuilder:V Callbacks:V CompilationBuilder:V CpuExecutor:V ExecutionBuilder:V ExecutionBurstController:V ExecutionBurstServer:V ExecutionPlan:V FibonacciDriver:V GraphDump:V IndexedShapeWrapper:V IonWatcher:V Manager:V MemoryUtils:V Memory:V MetaModel:V ModelArgumentInfo:V ModelBuilder:V NeuralNetworks:V OperationResolver:V OperationsUtils:V Operations:V PackageInfo:V TokenHasher:V TypeManager:V Utils:V ValidateHal:V VersionedInterfaces:V *:S.
אפשר להגדיר את ANDROID_LOG_TAGS
באמצעות הפקודה הבאה:
export ANDROID_LOG_TAGS=$(grep -R 'define LOG_TAG' | awk -F '"' '{ print $2 ":V" }' | sort -u | egrep -v "Sample|FileTag|test" | xargs echo -n; echo ' *:S')
חשוב לזכור שמדובר רק במסנן שחל על logcat
. עדיין צריך
מגדירים את המאפיין debug.nn.vlog
לערך all
כדי ליצור פרטי יומן מפורטים.