Расширения поставщика

Расширения вендоров 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 , которые среда выполнения обрабатывает как необработанный блок данных произвольного размера.

Операции OEM и типы данных

В NNAPI есть OEM-операции и OEM-типы данных, позволяющие производителям устройств предоставлять специализированные функции, специфичные для драйверов. Эти операции и типы данных используются только OEM-приложениями. Семантика OEM-операций и типов данных специфична для OEM-производителей и может измениться в любое время. OEM-операции и типы данных кодируются с помощью OperationType::OEM_OPERATION , OperandType::OEM и OperandType::TENSOR_OEM_BYTE .