תוספי ספקים

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

תוספים של ספקים מספקים חלופה מובנית יותר לפעולות ולסוגי נתונים של יצרני ציוד מקורי (OEM), שהוצאו משימוש ב-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.