ニューラル ネットワーク API

Android Neural Networks API(NNAPI)は、Android デバイス上で演算負荷の高い機械学習処理を実行するために設計された Android C API です。NNAPI は、ニューラル ネットワークの構築とトレーニングを行う、高レベルの機械学習フレームワーク(TensorFlow Lite、Caffe2 など)に機能ベースレイヤを提供することを目的としています。この API は Android 8.1(API レベル 27)以降を搭載したすべての Android デバイスで利用できます。

NNAPI は、デベロッパーが定義したトレーニング済みのモデルに Android デバイスのデータを適用することで、推論処理をサポートします。この推論処理には、画像の分類、ユーザー行動の予測、検索クエリに対する最適な回答の選択などが含まれます。

Android バイス上で推論を実行することには、次のような多くのメリットがあります。

  • レイテンシ: ネットワーク経由でリクエストを送信して応答を待つ必要がありません。これはカメラからの連続フレームを処理する動画アプリにおいて、非常に重要な点です。
  • 可用性: ネットワークがつながらない状況でもアプリケーションが動作します。
  • 速度: ニューラル ネットワーク処理に特化した新しいハードウェアによって、汎用 CPU のみを使う場合に比べて計算速度が格段に速くなります。
  • プライバシー: データが Android デバイスの外部に流出しません。
  • 費用: Android デバイス上ですべての計算が実行される場合、サーバー ファームは必要ありません。

デベロッパーが留意すべきデメリットもあります。

  • システムの使用率: ニューラル ネットワークの評価時は計算量が多くなるため、電池の消費量が増えます。特に計算が長時間におよぶアプリなどで、この点が懸念される場合は、電池の状態をモニタリングすることをおすすめします。
  • アプリケーションのサイズ: モデルのサイズに注意してください。モデルは何メガバイトもの容量を占有する場合があります。APK に含まれるモデルの容量が大きく、ユーザーに悪影響がおよぶ可能性がある場合は、アプリのインストール後にモデルをダウンロードする、より小さなモデルを使用する、クラウド上で計算を実行するなどの対策を検討してください。なお、クラウド上でモデルを実行する機能は NNAPI にはありません。

Android Neural Networks API のサンプルで、NNAPI の使用方法の例をご覧ください。

Neural Networks API ランタイムとは

NNAPI は機械学習のライブラリ、フレームワーク、ツール(デベロッパーによるモデルのトレーニング、および Android デバイスへのモデルのデプロイに使用するツール)によって呼び出されることを想定して設計されています。通常、アプリが直接使用するのは NNAPI ではなく、高レベルの機械学習フレームワークです。そのフレームワークが NNAPI を使用することで、ハードウェア アクセラレーションによる推論処理がサポート デバイス上で実行されます。

Android のニューラル ネットワーク ランタイムは、アプリの要件や Android デバイスのハードウェア性能に応じて、そのデバイスで利用可能なプロセッサに効率的に計算負荷を分散させます。具体的には、専用のニューラル ネットワーク ハードウェア、グラフィックス プロセッシング ユニット(GPU)、デジタル シグナル プロセッサ(DSP)などに分散させます。

専用のベンダー ドライバがないデバイスの場合、NNAPI ランタイムは CPU 上でリクエストを実行します。

図 1 は NNAPI のハイレベル システム アーキテクチャを示しています。

図 1. Android Neural Networks API のシステム アーキテクチャ

Neural Networks API プログラミング モデル

NNAPI を使用して計算を実行するには、まずその計算を定義する有向グラフを作成する必要があります。この計算グラフと入力データ(機械学習フレームワークから渡される重みやバイアスなど)とが組み合わされて、NNAPI ランタイム評価のモデルが形成されます。

NNAPI には 4 つの主な抽象概念があります。

  • モデル: 算術演算の計算グラフと、トレーニング プロセスで学習した定数値です。これらの演算はニューラル ネットワークごとに固有です。たとえば 2 次元(2D)の畳み込み、ロジスティック(シグモイド)活性化関数、正規化線形(ReLU)活性化関数などが含まれます。モデルの作成は同期的な処理です。いったん正常に作成できると、さまざまなスレッドやコンパイルで再利用できます。NNAPI では、モデルは ANeuralNetworksModel インスタンスとして表されます。
  • コンパイル: NNAPI モデルを低レベルのコードにコンパイルするための構成を表します。コンパイルの作成は同期的な処理です。いったん正常に作成できると、さまざまなスレッドや実行で再利用できます。NNAPI では、各コンパイルは ANeuralNetworksCompilation インスタンスとして表されます。
  • メモリ: 共有メモリ、メモリマップ ファイル、および同様のメモリバッファを表します。メモリバッファを使うと、NNAPI ランタイムはデータをより効率的にドライバへ転送できます。一般的にアプリは、モデルの定義に必要なテンソルをすべて含む共有メモリバッファを 1 つ作成します。メモリバッファを使用して、実行インスタンスの入出力データを保存することもできます。NNAPI では、各メモリバッファは ANeuralNetworksMemory インスタンスとして表されます。
  • 実行: NNAPI モデルを入力データのセットに適用して結果を収集するインターフェースです。実行は同期的にも非同期的にも行えます。

    1 件の実行において複数のスレッドが待機できます。この実行が完了すると、すべてのスレッドが解放されます。

    NNAPI では、各実行は ANeuralNetworksExecution インスタンスとして表されます。

図 2 は基本的なプログラミング フローを示しています。

図 2. Android Neural Networks API のプログラミング フロー

このセクションの残りのパートでは、NNAPI モデルをセットアップして、モデルの計算とコンパイルを行い、コンパイルしたモデルを実行する手順を説明します。

トレーニング データへのアクセスを可能にする

通常、トレーニング済みの重みやバイアスのデータはファイルに保存してあります。このデータに NNAPI ランタイムが効率よくアクセスできるようにするには、ANeuralNetworksMemory_createFromFd() 関数を呼び出して ANeuralNetworksMemory インスタンスを作成し、開いたデータファイルのファイル ディスクリプタを渡します。メモリ保護フラグや、ファイル内の共有メモリ領域の開始点となるオフセットも指定します。

// Create a memory buffer from the file that contains the trained data
    ANeuralNetworksMemory* mem1 = NULL;
    int fd = open("training_data", O_RDONLY);
    ANeuralNetworksMemory_createFromFd(file_size, PROT_READ, fd, 0, &mem1);
    

この例では、すべての重みに対して 1 つの ANeuralNetworksMemory インスタンスのみを使用していますが、複数のファイルに対して複数の ANeuralNetworksMemory インスタンスを使用することもできます。

ネイティブ ハードウェア バッファの使用

モデル入力、モデル出力、定数オペランド値に対してネイティブ ハードウェア バッファを使用できます。NNAPI アクセラレータが、ドライバでデータをコピーすることなく、AHardwareBuffer オブジェクトにアクセスできる場合があります。AHardwareBuffer には多数の構成があり、すべての NNAPI アクセラレータがこれらの構成のすべてをサポートしているわけではありません。この制限があるため、AHardwareBuffer を使用するコンパイルと実行が期待どおりの挙動をするか確認してください。そのために、ANeuralNetworksMemory_createFromAHardwareBuffer リファレンス ドキュメントに記載されている制約を確認するとともに、デバイス割り当てを使ってアクセラレータを指定し、ターゲット デバイスで事前にテストしてください。

AHardwareBuffer オブジェクトに NNAPI ランタイムがアクセスできるようにするには、以下のコードサンプルで示すように、ANeuralNetworksMemory_createFromAHardwareBuffer 関数を呼び出して AHardwareBuffer オブジェクトを渡し、ANeuralNetworksMemory のインスタンスを作成します。

    // Configure and create AHardwareBuffer object
    AHardwareBuffer_Desc desc = ...
    AHardwareBuffer* awhb = nullptr;
    AHardwareBuffer_allocate(&desc, &awhb);

    // Create ANeuralNetworksMemory from AHardwareBuffer
    ANeuralNetworksMemory* mem2 = NULL;
    ANeuralNetworksMemory_createFromAHardwareBuffer(ahwb, &mem2);
    

NNAPI が AHardwareBuffer オブジェクトにアクセスする必要がなくなったら、対応する ANeuralNetworksMemory のインスタンスを解放します。

    ANeuralNetworksMemory_free(mem2);
    

注:

  • AHardwareBuffer はバッファ全体に対してのみ使用できます。ARect パラメータとともに使用することはできません。
  • NNAPI ランタイムはバッファをフラッシュしません。実行をスケジューリングする前に、入力バッファと出力バッファにアクセスできることを確認する必要があります。
  • ファイル ディスクリプタの sync fence には対応していません。
  • AHardwareBuffer にベンダー固有のフォーマットと使用ビットがある場合、クライアントとドライバのどちらにキャッシュのフラッシュをする責任があるかはベンダーの実装次第です。

モデル

NNAPI の計算において、モデルは基本構成要素です。各モデルは 1 つ以上のオペランドと演算で定義されます。

オペランド

オペランドはグラフの定義に使われるデータ オブジェクトです。オペランドには、モデルの入力データと出力データ、ある演算から別の演算に渡されるデータを含む中間ノード、それらの演算に渡される定数が含まれます。

NNAPI モデルに追加できるオペランドのタイプは、「スカラー」と「テンソル」の 2 種類です。

スカラーは単一の値を表します。NNAPI では、ブール値、16 ビット浮動小数点数、32 ビット浮動小数点数、32 ビット整数、符号なし 32 ビット整数の形式のスカラー値をサポートしています。

NNAPI での演算の多くには、テンソルが含まれます。テンソルとは N 次元の配列です。NNAPI では 16 ビット浮動小数点数、32 ビット浮動小数点数、8 ビットの量子化された値、16 ビットの量子化された値、32 ビット整数、8 ビットブール値をサポートしています。

たとえば図 3 は、2 つの演算(加算の後に乗算)からなるモデルを表しています。このモデルは 1 つの入力テンソルを取り込み、1 つの出力テンソルを算出します。

図 3. NNAPI モデルのオペランドの例

上記のモデルには 7 つのオペランドがあります。これらのオペランドは、モデルに追加する順番を示したインデックスにより、間接的に特定されます。最初に追加するオペランドのインデックスは 0、2 番目のオペランドのインデックスは 1 となり、それ以降も同様にインデックスが付けられます。オペランド 1、2、3、5 は定数オペランドです。

オペランドを追加する順序に意味はありません。たとえば、モデルの出力オペランドを最初に追加してもかまいません。重要なのは、オペランドを参照するときに正しいインデックスを使用することです。

オペランドにはタイプがあり、モデルに追加されるときに指定されます。

1 つのオペランドをモデルの入力と出力の両方に使用することはできません。

すべてのオペランドは必ず、モデル入力、定数、1 つの演算の出力オペランドのいずれかです。

オペランドの使用方法について詳しくは、オペランドの詳細をご覧ください。

演算

演算は実行する計算を指定するもので、次の 3 つの要素で構成されます。

  • 演算タイプ(加算、乗算、畳み込みなど)
  • 演算の入力に使用するオペランドのインデックス リスト
  • 演算の出力に使用するオペランドのインデックス リスト

これらのリスト内の順番には意味があります。想定される入出力データの各演算については、NNAPI API リファレンスをご覧ください。

演算を追加する前に、演算で使用または生成するオペランドをモデルに追加する必要があります。

演算の追加順には意味がありません。NNAPI は、オペランドと演算の計算グラフに規定された依存関係に基づいて演算の実行順序を決定します。

下記の表は、NNAPI がサポートする演算をまとめたものです。

カテゴリ 演算
要素ごとの算術演算
テンソル操作
画像演算
検索演算
正規化演算
畳み込み演算
プーリング演算
活性化演算
その他の演算

API レベル 28 での既知の問題: ANEURALNETWORKS_TENSOR_QUANT8_ASYMM テンソルを ANEURALNETWORKS_PAD 演算(Android 9、API レベル 28 以降で利用可能)に渡す場合、NNAPI を使用した場合の出力と、高レベルの機械学習フレームワーク(TensorFlow Lite など)を使用した場合の出力が一致しない場合があります。代わりに ANEURALNETWORKS_TENSOR_FLOAT32 のみを渡す必要があります。この問題は、Android 10(API レベル 29)以降で解決されています。

モデルの作成

以下の例では、図 3 に示した 2 つの演算を含むモデルを作成します。

モデルの作成手順は次のとおりです。

  1. ANeuralNetworksModel_create() 関数を呼び出して、空のモデルを定義します。

        ANeuralNetworksModel* model = NULL;
        ANeuralNetworksModel_create(&model);
        
  2. ANeuralNetworks_addOperand() を呼び出して、オペランドをモデルに追加します。データ型は、ANeuralNetworksOperandType データ構造を使用して定義します。

        // In our example, all our tensors are matrices of dimension [3][4]
        ANeuralNetworksOperandType tensor3x4Type;
        tensor3x4Type.type = ANEURALNETWORKS_TENSOR_FLOAT32;
        tensor3x4Type.scale = 0.f;    // These fields are used for quantized tensors
        tensor3x4Type.zeroPoint = 0;  // These fields are used for quantized tensors
        tensor3x4Type.dimensionCount = 2;
        uint32_t dims[2] = {3, 4};
        tensor3x4Type.dimensions = dims;

    // We also specify operands that are activation function specifiers ANeuralNetworksOperandType activationType; activationType.type = ANEURALNETWORKS_INT32; activationType.scale = 0.f; activationType.zeroPoint = 0; activationType.dimensionCount = 0; activationType.dimensions = NULL;

    // Now we add the seven operands, in the same order defined in the diagram ANeuralNetworksModel_addOperand(model, &tensor3x4Type); // operand 0 ANeuralNetworksModel_addOperand(model, &tensor3x4Type); // operand 1 ANeuralNetworksModel_addOperand(model, &activationType); // operand 2 ANeuralNetworksModel_addOperand(model, &tensor3x4Type); // operand 3 ANeuralNetworksModel_addOperand(model, &tensor3x4Type); // operand 4 ANeuralNetworksModel_addOperand(model, &activationType); // operand 5 ANeuralNetworksModel_addOperand(model, &tensor3x4Type); // operand 6
  3. トレーニング プロセスでアプリが取得した重みやバイアスなどの定数を含むオペランドには、ANeuralNetworksModel_setOperandValue() 関数と ANeuralNetworksModel_setOperandValueFromMemory() 関数を使用します。

    以下の例では、先ほど作成したメモリバッファに対応するトレーニング データのファイルにある定数値を設定しています。

        // In our example, operands 1 and 3 are constant tensors whose values were
        // established during the training process
        const int sizeOfTensor = 3 * 4 * 4;    // The formula for size calculation is dim0 * dim1 * elementSize
        ANeuralNetworksModel_setOperandValueFromMemory(model, 1, mem1, 0, sizeOfTensor);
        ANeuralNetworksModel_setOperandValueFromMemory(model, 3, mem1, sizeOfTensor, sizeOfTensor);

    // We set the values of the activation operands, in our example operands 2 and 5 int32_t noneValue = ANEURALNETWORKS_FUSED_NONE; ANeuralNetworksModel_setOperandValue(model, 2, &noneValue, sizeof(noneValue)); ANeuralNetworksModel_setOperandValue(model, 5, &noneValue, sizeof(noneValue));
  4. 計算したい有向グラフ内の各演算について、ANeuralNetworksModel_addOperation() 関数を呼び出して演算をモデルに追加します。

    この呼び出しの際に、アプリは次のパラメータを指定する必要があります。

    • 演算タイプ
    • 入力値の総数
    • 入力オペランドのインデックス配列
    • 出力値の総数
    • 出力オペランドのインデックス配列

    同一の演算で、1 つのオペランドを入力と出力の両方に使用することはできません。

        // We have two operations in our example
        // The first consumes operands 1, 0, 2, and produces operand 4
        uint32_t addInputIndexes[3] = {1, 0, 2};
        uint32_t addOutputIndexes[1] = {4};
        ANeuralNetworksModel_addOperation(model, ANEURALNETWORKS_ADD, 3, addInputIndexes, 1, addOutputIndexes);

    // The second consumes operands 3, 4, 5, and produces operand 6 uint32_t multInputIndexes[3] = {3, 4, 5}; uint32_t multOutputIndexes[1] = {6}; ANeuralNetworksModel_addOperation(model, ANEURALNETWORKS_MUL, 3, multInputIndexes, 1, multOutputIndexes);
  5. ANeuralNetworksModel_identifyInputsAndOutputs() 関数を呼び出して、モデルで入力データとして扱うオペランドと出力データとして扱うオペランドを指定します。

        // Our model has one input (0) and one output (6)
        uint32_t modelInputIndexes[1] = {0};
        uint32_t modelOutputIndexes[1] = {6};
        ANeuralNetworksModel_identifyInputsAndOutputs(model, 1, modelInputIndexes, 1 modelOutputIndexes);
        
  6. 必要に応じて ANeuralNetworksModel_relaxComputationFloat32toFloat16() を呼び出し、IEEE 754 16 ビット浮動小数点形式と同程度の範囲あるいは精度で ANEURALNETWORKS_TENSOR_FLOAT32 を計算することが許容されるかどうかを指定します。

  7. ANeuralNetworksModel_finish() を呼び出して、モデルの定義を完了します。エラーがなければ、この関数は結果コード ANEURALNETWORKS_NO_ERROR を返します。

        ANeuralNetworksModel_finish(model);
        

作成したモデルは何度でもコンパイルでき、コンパイルしたものは何度でも実行できます。

コンパイル

コンパイルの手順では、モデルを実行するプロセッサを決定し、対応するドライバで実行の準備をします。この手順には、モデルを実行するプロセッサに固有のマシンコードの生成が含まれる場合があります。

モデルをコンパイルする手順は次のとおりです。

  1. ANeuralNetworksCompilation_create() 関数を呼び出して、コンパイル インスタンスを新規作成します。

        // Compile the model
        ANeuralNetworksCompilation* compilation;
        ANeuralNetworksCompilation_create(model, &compilation);
        

    必要に応じて、デバイス割り当てを使用して、どのデバイスで実行するかを明示的に選択できます。

  2. 必要に応じて、ランタイムに電池節約と実行スピードのどちらを優先するかを指定できます。そのためには ANeuralNetworksCompilation_setPreference() を呼び出します。

        // Ask to optimize for low power consumption
        ANeuralNetworksCompilation_setPreference(compilation, ANEURALNETWORKS_PREFER_LOW_POWER);
        

    指定できるプリファレンスは次のとおりです。

  3. 必要に応じて、ANeuralNetworksCompilation_setCaching を呼び出してコンパイルのキャッシュへの保存をセットアップできます。

        // Set up compilation caching
        ANeuralNetworksCompilation_setCaching(compilation, cacheDir, token);
        

    cacheDir には getCodeCacheDir() を使用します。指定された token は、アプリケーション内の各モデルに一意である必要があります。

  4. ANeuralNetworksCompilation_finish() を呼び出して、コンパイルの定義を完了します。エラーがなければ、この関数は結果コード ANEURALNETWORKS_NO_ERROR を返します。

        ANeuralNetworksCompilation_finish(compilation);
        

デバイスの検出と割り当て

Android 10(API レベル 29)以降を搭載した Android デバイスでは、機械学習フレームワーク ライブラリとアプリで NNAPI の関数を使うことにより、使用可能なデバイスに関する情報を取得して、実行に使用するデバイスを指定できます。アプリは、使用可能なデバイスに関する情報を得ることにより、デバイスで見つかったドライバの正確なバージョンから、既知の非互換性の問題を回避できるようになります。アプリは、実行するデバイスをモデルのセクションごとに指定できるようになるため、デプロイする Android デバイス用に最適化できます。

デバイス検出

ANeuralNetworks_getDeviceCount を使用して使用可能なデバイスの数を取得します。各デバイスに対して、ANeuralNetworks_getDevice を使用して、ANeuralNetworksDevice のインスタンスをそのデバイスへの参照に設定します。

デバイス参照を取得したら、次の関数を使用してそのデバイスに関する追加情報を得ることができます。

デバイス割り当て

ANeuralNetworksModel_getSupportedOperationsForDevices を使用して、特定のデバイスで実行できるモデルの演算を調べます。

実行に使用するアクセラレータを制御するには、ANeuralNetworksCompilation_create の代わりに ANeuralNetworksCompilation_createForDevices を呼び出します。通常は、結果の ANeuralNetworksCompilation オブジェクトを使用します。与えられたモデルが選択されたデバイスでサポートされていない演算を含んでいる場合、この関数はエラーを返します。

複数のデバイスが指定されている場合、デバイス間で処理を分散する責任はランタイムにあります。

他のデバイスと同様に、NNAPI CPU 実装は、名前 nnapi-reference とタイプ ANEURALNETWORKS_DEVICE_TYPE_CPU を持った ANeuralNetworksDevice で表されます。ANeuralNetworksCompilation_createForDevices を呼び出すとき、モデルのコンパイルと実行が失敗した場合の処理に CPU 実装は使用されません。

モデルを特定のデバイスで実行できるようにサブモデルにパーティショニングするのはアプリケーションの責任です。手動のパーティショニングが不要なアプリケーションは、モデルのアクセラレーションに使用可能なすべてのデバイス(CPU を含む)を使用するために、引き続きより単純な ANeuralNetworksCompilation_create を呼び出す必要があります。ANeuralNetworksCompilation_createForDevices を使用して指定されたデバイスがモデルを完全にサポートできない場合、ANEURALNETWORKS_BAD_DATA が返されます。

実行

実行の手順では、モデルを入力データのセットに適用し、計算結果を 1 つ以上のユーザー バッファまたはアプリが割り当てたメモリ領域に保存します。

コンパイル済みのモデルを実行する手順は次のとおりです。

  1. ANeuralNetworksExecution_create() 関数を呼び出して、実行インスタンスを新規作成します。

        // Run the compiled model against a set of inputs
        ANeuralNetworksExecution* run1 = NULL;
        ANeuralNetworksExecution_create(compilation, &run1);
        
  2. 計算の入力値をアプリが読み取る場所を指定します。アプリは、ANeuralNetworksExecution_setInput() または ANeuralNetworksExecution_setInputFromMemory() を呼び出すことによって、それぞれユーザー バッファまたは割り当てられたメモリ領域から入力値を読み取ることができます。

        // Set the single input to our sample model. Since it is small, we won't use a memory buffer
        float32 myInput[3][4] = { ...the data... };
        ANeuralNetworksExecution_setInput(run1, 0, NULL, myInput, sizeof(myInput));
        
  3. 出力値をアプリが書き出す場所を指定します。アプリは、ANeuralNetworksExecution_setOutput() または ANeuralNetworksExecution_setOutputFromMemory() を呼び出すことによって、それぞれユーザー バッファまたは割り当てられたメモリ領域に出力値を書き出すことができます。

        // Set the output
        float32 myOutput[3][4];
        ANeuralNetworksExecution_setOutput(run1, 0, NULL, myOutput, sizeof(myOutput));
        
  4. ANeuralNetworksExecution_startCompute() 関数を呼び出して、実行を開始するようにスケジューリングします。エラーがなければ、この関数は結果コード ANEURALNETWORKS_NO_ERROR を返します。

        // Starts the work. The work proceeds asynchronously
        ANeuralNetworksEvent* run1_end = NULL;
        ANeuralNetworksExecution_startCompute(run1, &run1_end);
        
  5. ANeuralNetworksEvent_wait() 関数を呼び出して、実行が完了するのを待ちます。正常に実行が完了すると、この関数は結果コード ANEURALNETWORKS_NO_ERROR を返します。実行を開始したスレッドとは別のスレッドで完了を待つことができます。

        // For our example, we have no other work to do and will just wait for the completion
        ANeuralNetworksEvent_wait(run1_end);
        ANeuralNetworksEvent_free(run1_end);
        ANeuralNetworksExecution_free(run1);
        
  6. 必要に応じて、同じコンパイル インスタンスを使用して新しい ANeuralNetworksExecution インスタンスを作成して、コンパイル済みモデルに異なる入力セットを適用できます。

        // Apply the compiled model to a different set of inputs
        ANeuralNetworksExecution* run2;
        ANeuralNetworksExecution_create(compilation, &run2);
        ANeuralNetworksExecution_setInput(run2, ...);
        ANeuralNetworksExecution_setOutput(run2, ...);
        ANeuralNetworksEvent* run2_end = NULL;
        ANeuralNetworksExecution_startCompute(run2, &run2_end);
        ANeuralNetworksEvent_wait(run2_end);
        ANeuralNetworksEvent_free(run2_end);
        ANeuralNetworksExecution_free(run2);
        

同期実行

非同期実行では、スレッドの生成と同期に時間がかかります。さらに、レイテンシには非常に大きなばらつきがあり、スレッドが通知または起動されてから最終的に CPU コアにバインドされるまでに最長 500 マイクロ秒かかる場合があります。

遅延を改善するために、アプリケーションに対して、ランタイムへの同期推論呼び出しを行うように指示できます。この呼び出しは、推論の開始時に戻るのではなく、完了時に戻ります。ランタイムへの非同期推論呼び出しのために ANeuralNetworksExecution_startCompute を呼び出す代わりに、アプリケーションは ANeuralNetworksExecution_compute を呼び出してランタイムへの同期呼び出しを行います。ANeuralNetworksExecution_compute への呼び出しは ANeuralNetworksEvent を受け取らず、ANeuralNetworksEvent_wait への呼び出しと対になりません。

バースト実行

Android 10(API レベル 29)以降を搭載した Android デバイスの NNAPI では、ANeuralNetworksBurst オブジェクトを介したバースト実行がサポートされています。バースト実行とは、カメラ キャプチャのフレームや連続するオーディオ サンプルなど、同じコンパイルが途切れずに発生する実行シーケンスです。ANeuralNetworksBurst オブジェクトを使用すると、実行間でリソースの再利用ができることと、バーストの期間中に高パフォーマンス状態を維持する必要があることがアクセラレータに示されるため、実行が高速になる場合があります。

ANeuralNetworksBurst では、通常の実行パスに対するわずかな変更が行われるだけです。以下のコード スニペットのように、ANeuralNetworksBurst_create オブジェクトを使用してバースト オブジェクトを作成します。

    // Create burst object to be reused across a sequence of executions
    ANeuralNetworksBurst* burst = NULL;
    ANeuralNetworksBurst_create(compilation, &burst);
    

ANeuralNetworksExecution が使用前に構成されて、実行が通常どおりに行われ、完了時に通常どおりに消費されます。ただし、ANeuralNetworksExecution_startCompute あるいは ANeuralNetworksExecution_compute を使用して推論を実行する代わりに、ANeuralNetworksExecution_burstCompute 関数への呼び出しの中で各 ANeuralNetworksExecution オブジェクトを同じ ANeuralNetworksBurst と対にします。

    // Create and configure first execution object
    // ...

    // Execute using the burst object
    ANeuralNetworksExecution_burstCompute(execution1, burst);

    // Use results of first execution and free the execution object
    // ...

    // Create and configure second execution object
    // ...

    // Execute using the same burst object
    ANeuralNetworksExecution_burstCompute(execution2, burst);

    // Use results of second execution and free the execution object
    // ...
    

ANeuralNetworksBurst オブジェクトが不要になったら ANeuralNetworksBurst_free で解放します。

    // Cleanup
    ANeuralNetworksBurst_free(burst);
    

動的にサイズ変更される出力

出力のサイズが入力データに依存するモデル、つまり、サイズがモデル実行時に決定できないモデルをサポートするには、ANeuralNetworksExecution_getOutputOperandRankANeuralNetworksExecution_getOutputOperandDimensions を使用します。

これを行う方法を、次のコードサンプルで示します。

    // Get the rank of the output
    uint32_t myOutputRank = 0;
    ANeuralNetworksExecution_getOutputOperandRank(run1, 0, &myOutputRank);

    // Get the dimensions of the output
    std::vector<uint32_t> myOutputDimensions(myOutputRank);
    ANeuralNetworksExecution_getOutputOperandDimensions(run1, 0, myOutputDimensions.data());
    

パフォーマンスの測定

ランタイム全体の合計実行時間を調べるには、同期実行 API を使用して、呼び出しにかかった時間を測定します。下位レベルのソフトウェア スタック全体の合計実行時間を調べるには、ANeuralNetworksExecution_setMeasureTimingANeuralNetworksExecution_getDuration を使用して次を取得します。

  • アクセラレータでの実行時間(ホスト プロセッサで実行されるドライバは含まない)
  • ドライバでの実行時間(アクセラレータでの時間を含む)

ドライバでの実行時間には、ランタイム自体のオーバーヘッドや、ランタイムがドライバと通信するために必要な IPC のオーバーヘッドなどは含まれません。

これらの API は、処理の送信から完了までの時間を測定します。これは、ドライバまたはアクセラレータが推論を実行していた時間だけを測定すると、コンテキストの切り替えによって阻害される可能性があるためです。

たとえば、推論 1 が始まってから、ドライバが推論 2 を実行するために処理を停止して、その後で推論 1 を再開して完了した場合、推論 1 の実行時間には、推論 2 を実行するために処理が停止していた時間が含まれます。

このタイミング情報は、オフラインでの使用のためにテレメトリーを収集するアプリケーションを本番環境にデプロイするときに役立つ場合があります。タイミング データを使用して、アプリにパフォーマンス改善のための修正を加えることが可能です。

この機能を使用するときは、次のことに注意してください。

  • タイミング情報の収集によってパフォーマンスが低下することがあります。
  • NNAPI ランタイムと IPC にかかった時間を除いて、ドライバ自身あるいはアクセラレータでかかった時間を算出できるのは、ドライバだけです。
  • この API に使用できるのは、numDevices = 1 として ANeuralNetworksCompilation_createForDevices で作成された ANeuralNetworksExecution だけです。
  • タイミング情報をレポートできるようにするのに、ドライバは必要ありません。

クリーンアップ

クリーンアップの手順では、計算に使用した内部リソースを解放します。

    // Cleanup
    ANeuralNetworksCompilation_free(compilation);
    ANeuralNetworksModel_free(model);
    ANeuralNetworksMemory_free(mem1);
    

オペランドの詳細

以下のセクションでは、オペランドの使用に関する高度なトピックを扱います。

量子化テンソル

量子化テンソルは、浮動小数点数値の N 次元配列を簡潔に表現する方法です。

NNAPI は 8 ビットの非対称な量子化テンソルをサポートします。このテンソルでは、各セルの値は 8 ビットの整数で表現されます。テンソルと関連づけられているのがスケールと零点値で、これらは 8 ビットの整数を浮動小数点数値に変換して表現するために使用されます。

式は次のとおりです。

    (cellValue - zeroPoint) * scale
    

ここで、zeroPoint 値は 32 ビット整数で、scale は 32 ビット浮動小数点数値です。

32 ビット浮動小数点数値のテンソルと比較して、8 ビットの量子化テンソルには次の 2 つのメリットがあります。

  • トレーニング済みの重みのサイズが 32 ビットテンソルの 4 分の 1 になるので、アプリケーションのサイズが小さくなります。
  • 一般的に計算の実行速度が上がります。これは、メモリから取得する必要があるデータ量が減り、整数値の計算中に DSP などのプロセッサ効率が上がるためです。

浮動小数点数モデルを量子化モデルに変換することは可能ですが、量子化モデルを直接トレーニングしたほうが良い結果が得られることがこれまでの経験からわかっています。実際にニューラル ネットワークは、各値の粒度の粗さを補うように学習します。各量子化テンソルの scale と zeroPoint 値はトレーニング プロセスで決定されます。

NNAPI では、ANeuralNetworksOperandType データ構造のタイプ フィールドを ANEURALNETWORKS_TENSOR_QUANT8_ASYMM に設定することで、量子化テンソルのタイプを定義します。そのデータ構造内でテンソルの scale と zeroPoint 値も指定できます。

NNAPI では、8 ビットの非対称な量子化テンソルに加えて、次をサポートします。

オプションのオペランド

ANEURALNETWORKS_LSH_PROJECTION のようないくつかの演算は、オプションのオペランドを取ります。モデル内でオプションのオペランドが省略されていることを示すには、ANeuralNetworksModel_setOperandValue() 関数を呼び出して、バッファに NULL を、長さに 0 を渡します。

オペランドが存在するかどうかの決定が実行のたびに異なる場合は、ANeuralNetworksExecution_setInput() または ANeuralNetworksExecution_setOutput() 関数を使用し、バッファに NULL を、長さに 0 を指定して、そのオペランドが省略されていることを示します。

階数が未知のテンソル

Android 9(API レベル 28)では、次元は未知ですが、階数(次元数)が既知であるモデル演算が導入されました。Android 10(API レベル 29)では、ANeuralNetworksOperandType に記載されているように、階数が未知のテンソルを導入しました。

NNAPI ベンチマーク

The NNAPI ベンチマークは、ASOP の platform/test/mlts/benchmark(ベンチマーク アプリ)と platform/test/mlts/models(モデルとデータセット)から入手可能です。

ベンチマークでは、レイテンシと精度を評価し、ドライバごとに比較します。CPU 上で Tensorflow Lite を実行し、同じモデルとデータセットに対して同じ処理を行います。

ベンチマークは次の手順で使用します。

  1. ターゲットの Android デバイスをパソコンに接続し、ターミナル ウィンドウを開き、adb からデバイスにアクセスできることを確認します。

  2. 複数の Android デバイスが接続されている場合、ターゲット デバイスの ANDROID_SERIAL 環境変数をエクスポートします。

  3. Android のソース ディレクトリのトップに移動します。

  4. 次のコマンドを実行します。

        lunch aosp_arm-userdebug # Or aosp_arm64-userdebug if available
        ./test/mlts/benchmark/build_and_run_benchmark.sh
        

    ベンチマークの終了時に、結果が HTML ページとして xdg-open に渡されます。