תוספי ספקים

תוספי ספקים של Neural Networks API (NNAPI), שהוצגו ב-Android 10, הם אוספים של פעולות וסוגי נתונים שהוגדרו על ידי הספק. במכשירים עם NN HAL 1.2 ואילך, הנהגים יכולים לספק פעולות בהתאמה אישית שמואצלות בחומרה על ידי תמיכה בתוספים התואמים של הספק. תוספי ספקים לא משנים את ההתנהגות של פעולות קיימות.

תוספים של ספקים מספקים חלופה מובנית יותר לסוגי הנתונים ולפעולות של יצרני ציוד מקורי, שהוצאו משימוש ב-Android 10. מידע נוסף זמין במאמר פעולות וסוגי נתונים של ה-OEM.

רשימת ההיתרים לשימוש בתוספים

רק אפליקציות Android וקבצים בינאריים מקומיים שצוינו במפורש יכולים להשתמש בתוספים של ספקים במחיצות /product,‏ /vendor,‏ /odm ו-/data. אפליקציות וקבצים בינאריים מקומיים שנמצאים במחיצה /system לא יכולים להשתמש בתוספים של ספקים.

רשימה של אפליקציות וקבצים בינאריים ל-Android שמותר להם להשתמש בתוספים של ספקי NNAPI מאוחסנת ב-/vendor/etc/nnapi_extensions_app_allowlist. כל שורה בקובץ מכילה רשומה חדשה. רשומה יכולה להיות נתיב בינארי מקורי עם קידומת של קו נטוי (/), לדוגמה /data/foo, או שם של חבילת אפליקציה ל-Android, לדוגמה com.foo.bar.

אכיפת רשימת ההיתרים מתבצעת מהספרייה המשותפת של NNAPI בסביבת זמן הריצה. הספרייה הזו מגינה מפני שימוש לא מכוון, אבל לא מפני עקיפה מכוונת על ידי אפליקציה שמשתמשת ישירות בממשק HAL של הנהג NNAPI.

הגדרת תוסף של ספק

הספק יוצר קובץ כותרת עם הגדרת התוסף ומנהל אותו. דוגמה מלאה להגדרת תוסף מופיעה ב-example/fibonacci/FibonacciExtension.h.

לכל תוסף צריך להיות שם ייחודי שמתחיל בשם הדומיין ההפוך של הספק.

const char EXAMPLE_EXTENSION_NAME[] = "com.example.my_extension";

השם משמש כמרחב שמות לפעולות ולסוגי נתונים. NNAPI משתמש בשם הזה כדי להבדיל בין התוספים של הספקים.

ההצהרה על פעולות ועל סוגי נתונים דומה להצהרה עליהם ב-runtime/include/NeuralNetworks.h.

enum {
    /**
     * A custom scalar type.
     */
    EXAMPLE_SCALAR = 0,

    /**
     * A custom tensor type.
     *
     * Attached to this tensor is {@link ExampleTensorParams}.
     */
    EXAMPLE_TENSOR = 1,
};

enum {
    /**
     * Computes example function.
     *
     * Inputs:
     * * 0: A scalar of {@link EXAMPLE_SCALAR}.
     *
     * Outputs:
     * * 0: A tensor of {@link EXAMPLE_TENSOR}.
     */
    EXAMPLE_FUNCTION = 0,
};

אפשר להשתמש בכל סוג של אופרנד בפעולה של תוסף, כולל סוגי אופרנדים שאינם תוספים וסוגים של אופרנדים מתוספים אחרים. כשמשתמשים בסוג אופרנד מתוסף אחר, הנהג צריך לתמוך בתוסף השני.

תוספים יכולים גם להצהיר על מבנים מותאמים אישית שיתלוו לאופרטורים של התוספים.

/**
 * Quantization parameters for {@link EXAMPLE_TENSOR}.
 */
typedef struct ExampleTensorParams {
    double scale;
    int64_t zeroPoint;
} ExampleTensorParams;

שימוש בתוספים בלקוחות NNAPI

הקובץ runtime/include/NeuralNetworksExtensions.h (C API) מספק תמיכה בתוספים בסביבת זמן הריצה. בקטע הזה מופיעה סקירה כללית על C API.

כדי לבדוק אם מכשיר תומך בתוסף, משתמשים ב-ANeuralNetworksDevice_getExtensionSupport.

bool isExtensionSupported;
CHECK_EQ(ANeuralNetworksDevice_getExtensionSupport(device, EXAMPLE_EXTENSION_NAME,
                                                   &isExtensionSupported),
         ANEURALNETWORKS_NO_ERROR);
if (isExtensionSupported) {
    // The device supports the extension.
    ...
}

כדי ליצור מודל עם אופרטור של תוסף, משתמשים ב-ANeuralNetworksModel_getExtensionOperandType כדי לקבל את סוג האופרטור ומפעילים את ANeuralNetworksModel_addOperand.

int32_t type;
CHECK_EQ(ANeuralNetworksModel_getExtensionOperandType(model, EXAMPLE_EXTENSION_NAME, EXAMPLE_TENSOR, &type),
         ANEURALNETWORKS_NO_ERROR);
ANeuralNetworksOperandType operandType{
        .type = type,
        .dimensionCount = dimensionCount,
        .dimensions = dimensions,
};
CHECK_EQ(ANeuralNetworksModel_addOperand(model, &operandType), ANEURALNETWORKS_NO_ERROR);

אפשר גם להשתמש ב-ANeuralNetworksModel_setOperandExtensionData כדי לשייך נתונים נוספים לאופרטור של תוסף.

ExampleTensorParams params{
        .scale = 0.5,
        .zeroPoint = 128,
};
CHECK_EQ(ANeuralNetworksModel_setOperandExtensionData(model, operandIndex, &params, sizeof(params)),
         ANEURALNETWORKS_NO_ERROR);

כדי לבנות מודל עם פעולת תוסף, משתמשים ב-ANeuralNetworksModel_getExtensionOperationType כדי לקבל את סוג הפעולה ולקרוא ל-ANeuralNetworksModel_addOperation.

ANeuralNetworksOperationType type;
CHECK_EQ(ANeuralNetworksModel_getExtensionOperationType(model, EXAMPLE_EXTENSION_NAME, EXAMPLE_FUNCTION,
                                                        &type),
         ANEURALNETWORKS_NO_ERROR);
CHECK_EQ(ANeuralNetworksModel_addOperation(model, type, inputCount, inputs, outputCount, outputs),
         ANEURALNETWORKS_NO_ERROR);

הוספת תמיכה בתוספים למנהל NNAPI

הנהגים מדווחים על התוספים הנתמכים באמצעות השיטה IDevice::getSupportedExtensions. הרשימה שתוחזר חייבת לכלול רשומה שמתארת כל תוסף נתמך.

Extension {
    .name = EXAMPLE_EXTENSION_NAME,
    .operandTypes = {
        {
            .type = EXAMPLE_SCALAR,
            .isTensor = false,
            .byteSize = 8,
        },
        {
            .type = EXAMPLE_TENSOR,
            .isTensor = true,
            .byteSize = 8,
        },
    },
}

מתוך 32 הביטים שמשמשים לזיהוי סוגים ופעולות, הביטים העליונים Model::ExtensionTypeEncoding::HIGH_BITS_PREFIX הם התחילית של התוסף, והביטים התחתונים Model::ExtensionTypeEncoding::LOW_BITS_TYPE מייצגים את הסוג או הפעולה של התוסף.

כשמטפלים בסוג של פעולה או של אופרנד, הנהג צריך לבדוק את הקידומת של התוסף. אם לתחילית התוסף יש ערך שאינו אפס, סוג הפעולה או האופרנד הוא סוג תוסף. אם הערך הוא 0, סוג הפעולה או האופרנד הוא לא סוג תוסף.

כדי למפות את התחילית לשם של תוסף, צריך לחפש אותה ב-model.extensionNameToPrefix. המיפוי מהקידומת לשם הסיומת הוא התאמה חד-ל-חד (ביקציה) במודל נתון. ערכי קידומת שונים עשויים להתאים לאותו שם תוסף במודלים שונים.

מנהל ההתקן צריך לאמת את הפעולות וסוגי הנתונים של התוסף, כי זמן הריצה של NNAPI לא יכול לאמת פעולות מסוימות של תוסף וסוגי נתונים מסוימים.

לאופרטורים של התוספים יכולים להיות נתונים משויכים ב-operand.extraParams.extension, שזמן הריצה מתייחס אליהם כאל blob של נתונים גולמיים בגודל שרירותי.

סוגי הנתונים והפעולות של OEM

ל-NNAPI יש פעולה של OEM וסוגי נתונים של OEM, כדי לאפשר ליצרני המכשירים לספק פונקציונליות מותאמת אישית וספציפית לנהג. סוגי הפעולות והנתונים האלה משמשים רק אפליקציות של יצרני ציוד מקורי (OEM). הסמנטיקה של הפעולות וסוגי הנתונים של ה-OEM היא ספציפית ל-OEM, והיא עשויה להשתנות בכל שלב. פעולות ה-OEM וסוגים של נתונים מקודדים באמצעות OperationType::OEM_OPERATION,‏ OperandType::OEM ו-OperandType::TENSOR_OEM_BYTE.