ベンダー拡張

Android 10 で導入された Neural Networks API(NNAPI)のベンダー拡張機能は、ベンダーが定義した演算とデータタイプを組み合わせたものです。NN HAL 1.2 以降を実行するデバイスでは、対応するベンダー拡張機能をドライバに組み込むことで、ハードウェア アクセラレーションされたカスタムの演算をサポートできます。ベンダー拡張機能は、既存の演算の動作を変更しません。

ベンダー拡張機能は、Android 10 でサポートが終了した OEM 演算およびデータタイプに代わる、より構造化された手段を提供します。詳しくは、OEM 演算およびデータタイプをご覧ください。

拡張機能の使用許可リスト

ベンダー拡張機能は、/product/vendor/odm、および /data パーティションの明示的に指定された Android アプリとネイティブ バイナリでのみ使用できます。/system パーティションにあるアプリとネイティブ バイナリはベンダー拡張機能を使用できません。

NNAPI のベンダー拡張機能の使用を許可されている Android アプリとバイナリのリストは、/vendor/etc/nnapi_extensions_app_allowlist に格納されています。ファイルの各行に新しいエントリが含まれています。エントリは、/data/foo のようなスラッシュ(/)で始まるネイティブ バイナリのパス、または com.foo.bar のような Android アプリ パッケージの名前になります。

許可リストは、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 以外の場合、その演算またはオペランド タイプは拡張機能のタイプです。値が 0 の場合、その演算またはオペランド タイプは拡張機能のタイプではありません。

プレフィックスを拡張機能の名前にマッピングするには、model.extensionNameToPrefix で検出します。プレフィックスから拡張機能の名前へのマッピングは、特定のモデルの 1 対 1 の対応関係(全単射)です。拡張機能の名前は同じでも、対応するプレフィックスの値はモデルごとに異なります。

NNAPI ランタイムは特定の拡張演算とデータタイプを検証できないため、ドライバが担う必要があります。

拡張オペランドは operand.extraParams.extension に関連付けられたデータを格納でき、ランタイムが任意のサイズの元データ blob として処理します。

OEM 演算およびデータタイプ

NNAPI には OEM 演算と OEM データタイプがあり、デバイスのメーカーはドライバ固有のカスタム機能を提供できます。これらの演算とデータタイプは、OEM アプリケーションでのみ使用されます。OEM 演算およびデータタイプのセマンティクスは OEM 固有であり、いつでも変更できます。OEM 演算およびデータタイプは、OperationType::OEM_OPERATIONOperandType::OEMOperandType::TENSOR_OEM_BYTE を使用してエンコードされます。