供应商扩展

Android 10 中引入了 Neural Networks API (NNAPI) 供应商扩展,它是供应商定义的运算和数据类型的集合。在运行 NN HAL 1.2 或更高版本的设备上,驱动程序可以通过支持相应的供应商扩展来提供自定义硬件加速运算。供应商扩展不会修改现有运算的行为。

供应商扩展为 OEM 运算和数据类型(在 Android 10 中已废弃)提供更加结构化的替代方案。如需了解详情,请参阅 OEM 运算和数据类型

允许使用供应商扩展的许可名单

供应商扩展只能由 /product/vendor/odm/data 分区上明确指定的 Android 应用和原生二进制文件使用。位于 /system 分区上的应用和原生二进制文件无法使用供应商扩展。

允许使用 NNAPI 供应商扩展的 Android 应用和二进制文件列表存储在 /vendor/etc/nnapi_extensions_app_allowlist 中。该文件的每一行都包含一个新条目。条目可以是以斜杠 (/) 为前缀的原生二进制文件路径,例如 /data/foo,也可以是 Android 应用软件包的名称,例如 com.foo.bar

许可名单会从 NNAPI 运行时共享库强制实施。此库可防止意外使用,但不会阻止直接使用 NNAPI 驱动程序 HAL 接口的应用故意规避。

供应商扩展定义

供应商会创建并维护一个包含扩展定义的头文件。您可以在 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_OPERATIONOperandType::OEMOperandType::TENSOR_OEM_BYTE 进行编码。