| # Vendor Extensions |
| |
| Neural Networks API (NNAPI) vendor extensions, introduced in Android 10, are |
| collections of vendor-defined operations and data types. On devices running NN |
| HAL 1.2 or higher, drivers can provide custom hardware-accelerated operations by |
| supporting corresponding vendor extensions. Vendor extensions don't modify the |
| behavior of existing operations. |
| |
| Vendor extensions provide a more structured alternative to OEM operation and |
| data types, which were deprecated in Android 10. |
| For more information, see the last section. |
| |
| ## Extensions usage allowlist |
| |
| Vendor extensions can only be used by explicitly specified Android apps and |
| native binaries on the `/product`, `/vendor`, `/odm`, and `/data` partitions. |
| Apps and native binaries located on the `/system` partition can't use vendor |
| extensions. |
| |
| A list of Android apps and binaries permitted to use NNAPI vendor extensions is |
| stored in `/vendor/etc/nnapi_extensions_app_allowlist`. Each line of the file |
| contains a new entry. An entry can be a native binary path that is prefixed with |
| a slash (/), for example, `/data/foo`, or a name of an Android app package, for |
| example, `com.foo.bar`. |
| |
| The allowlist is enforced from the NNAPI runtime shared library. This library |
| protects against accidental usage but not against deliberate circumvention by |
| an app directly using the NNAPI driver HAL interface. |
| |
| ## Vendor extension definition |
| |
| The vendor creates and maintains a header file with the extension definition. A |
| complete example of an extension definition can be found in |
| `example/fibonacci/FibonacciExtension.h`. |
| |
| Each extension must have a unique name that starts with the reverse domain name |
| of the vendor. |
| |
| Note: Names of extension operation and operand types must be qualified with a |
| vendor name. The vendor name is represented by `EXAMPLE` in the code samples on |
| this page. |
| |
| ``` |
| const char EXAMPLE_EXTENSION_NAME[] = "com.example.my_extension"; |
| ``` |
| |
| The name acts as a namespace for operations and data types. The NNAPI uses this |
| name to distinguish between vendor extensions. |
| |
| Operations and data types are declared in a way similar to those in |
| `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, |
| }; |
| ``` |
| |
| An extension operation can use any operand type, including nonextension operand |
| types and operand types from other extensions. When using an operand type from |
| another extension, the driver must support the other extension. |
| |
| Extensions can also declare custom structures to accompany extension operands. |
| |
| ``` |
| /** |
| * Quantization parameters for {@link EXAMPLE_TENSOR}. |
| */ |
| typedef struct ExampleTensorParams { |
| double scale; |
| int64_t zeroPoint; |
| } ExampleTensorParams; |
| ``` |
| |
| ## Using extensions in NNAPI clients |
| |
| The `runtime/include/NeuralNetworksExtensions.h` (C API) file provides runtime |
| extension support. This section provides an overview of the C API. |
| |
| To check whether a device supports an extension, use |
| `ANeuralNetworksDevice_getExtensionSupport`. |
| |
| ``` |
| bool isExtensionSupported; |
| CHECK_EQ(ANeuralNetworksDevice_getExtensionSupport(device, EXAMPLE_EXTENSION_NAME, &isExtensionSupported), |
| ANEURALNETWORKS_NO_ERROR); |
| if (isExtensionSupported) { |
| // The device supports the extension. |
| ... |
| } |
| ``` |
| |
| To build a model with an extension operand, use `ANeuralNetworksModel_getExtensionOperandType` |
| to obtain the operand type and call `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); |
| ``` |
| |
| Optionally, use |
| `ANeuralNetworksModel_setOperandExtensionData` |
| to associate additional data with an extension operand. |
| |
| ``` |
| ExampleTensorParams params{ |
| .scale = 0.5, |
| .zeroPoint = 128, |
| }; |
| CHECK_EQ(ANeuralNetworksModel_setOperandExtensionData(model, operandIndex, ¶ms, sizeof(params)), |
| ANEURALNETWORKS_NO_ERROR); |
| ``` |
| |
| To build a model with an extension operation, use |
| `ANeuralNetworksModel_getExtensionOperationType` |
| to obtain the operation type and call |
| `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); |
| ``` |
| |
| ## Adding extension support to an NNAPI driver |
| |
| Drivers report supported extensions through the `IDevice::getSupportedExtensions` |
| method. The returned list must contain an entry describing each supported |
| extension. |
| |
| ``` |
| Extension { |
| .name = EXAMPLE_EXTENSION_NAME, |
| .operandTypes = { |
| { |
| .type = EXAMPLE_SCALAR, |
| .isTensor = false, |
| .byteSize = 8, |
| }, |
| { |
| .type = EXAMPLE_TENSOR, |
| .isTensor = true, |
| .byteSize = 8, |
| }, |
| }, |
| } |
| ``` |
| |
| Of the 32 bits used to identify types and operations, the high |
| `ExtensionTypeEncoding::HIGH_BITS_PREFIX` bits is the extension |
| _prefix_ and the low `ExtensionTypeEncoding::LOW_BITS_TYPE` |
| bits represents the type or operation of the extension. |
| |
| When handling an operation or operand type, the driver must check the extension |
| prefix. If the extension prefix has a nonzero value, the operation or operand |
| type is an extension type. If the value is `0`, the operation or operand type |
| isn't an extension type. |
| |
| To map the prefix to an extension name, look it up in `model.extensionNameToPrefix`. |
| The mapping from the prefix to the extension name is a one-to-one correspondence |
| (bijection) for a given model. Different prefix values might correspond to the |
| same extension name in different models. |
| |
| The driver must validate extension operations and data types because the NNAPI |
| runtime can't validate particular extension operations and data types. |
| |
| Extension operands can have associated data in `operand.extraParams.extension`, |
| which the runtime treats as a raw data blob of arbitrary size. |
| |
| ## OEM operation and data types |
| |
| Note: OEM operation and data types have been deprecated. For devices running |
| Android 10 or higher, use vendor extensions instead. |
| |
| NNAPI has an OEM operation and OEM data types to allow |
| device manufacturers to provide custom, driver-specific functionality. These |
| operation and data types are only used by OEM applications. The semantics of OEM |
| operation and data types are OEM-specific and can change at any time. The OEM |
| operation and data types are encoded using `OperationType::OEM_OPERATION`, |
| `OperandType::OEM`, and `OperandType::TENSOR_OEM_BYTE`. |