ベンダー拡張

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など)、または 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で検索します。プレフィックスから拡張子名へのマッピングは、特定のモデルに対して 1 対 1 対応 (全単射) です。異なるモデルでは、異なるプレフィックス値が同じ拡張名に対応する場合があります。

NNAPI ランタイムは特定の拡張機能の操作とデータ型を検証できないため、ドライバーは拡張機能の操作とデータ型を検証する必要があります。

拡張オペランドは、 operand.extraParams.extensionに関連するデータを持つことができ、ランタイムはこれを任意のサイズの生データ BLOB として扱います。

OEM の操作とデータの種類

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