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

Расширения поставщика 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 .