メモリプール

このページでは、ドライバとフレームワークの間でオペランド バッファを効率的に通信するために使用する、データ構造とメソッドについて説明します。

モデルのコンパイル時に、フレームワークは定数オペランドの値をドライバに提供します。定数オペランドの存続期間に応じて、その値は HIDL ベクトルまたは共有メモリプールのいずれかに配置されます。

  • 存続期間が CONSTANT_COPY の場合、値はモデル構造の operandValues フィールドに配置されます。HIDL ベクトルの値はプロセス間通信(IPC)中にコピーされるため、通常はスカラー オペランド(たとえば ADD のスカラーの有効化)や小さなテンソル パラメータ(たとえば RESHAPE のシェイプ テンソル)など、少量のデータを保持するためにのみ使用されます。
  • 存続期間が CONSTANT_REFERENCE の場合、値はモデル構造の pools フィールドに配置されます。IPC 中は共有メモリプールのハンドルのみが複製されます。未加工の値はコピーされません。したがって、大量のデータ(畳み込みの重みパラメータなど)を保持する場合は、HIDL ベクトルを使用するより、共有メモリプールを使用した方が効率的です。

モデルの実行時に、フレームワークは入力オペランドと出力オペランドのバッファをドライバに提供します。HIDL ベクトルで送信されるコンパイル時定数とは異なり、実行の入力データと出力データは常にメモリプールのコレクションを介して通信されます。

HIDL データ型 hidl_memory は、コンパイルと実行の両方で使用され、マッピングされていない共有メモリプールを表します。ドライバは、hidl_memory データ型の名前に基づいて使用可能になるようにメモリをマッピングする必要があります。サポートされているメモリ名は次のとおりです。

  • ashmem: Android 共有メモリ。詳細については、メモリをご覧ください。
  • mmap_fd: mmap を介したファイル記述子に基づく共有メモリ。
  • hardware_buffer_blob: AHardwareBuffer に基づく、AHARDWARE_BUFFER_FORMAT_BLOB 形式の共有メモリ。ニューラル ネットワーク(NN)HAL 1.2 から利用できます。詳細については、AHardwareBuffer をご覧ください。
  • hardware_buffer: AHARDWARE_BUFFER_FORMAT_BLOB 形式を使用しない一般的な AHardwareBuffer に基づく共有メモリ。BLOB 以外のモードのハードウェア バッファは、モデルの実行でのみサポートされます。NN HAL 1.2 から利用できます。詳細については、AHardwareBuffer をご覧ください。

NN HAL 1.3 以降、NNAPI は、ドライバが管理するバッファのアロケータ インターフェースを提供するメモリドメインをサポートしています。ドライバが管理するバッファは、実行の入力または出力としても使用できます。詳細については、メモリドメインをご覧ください。

NNAPI ドライバは、メモリ名 ashmemmmap_fd のマッピングをサポートする必要があります。NN HAL 1.3 以降、ドライバは hardware_buffer_blob のマッピングもサポートする必要があります。一般的な BLOB 以外のモード hardware_buffer とメモリドメインのサポートは任意です。

AHardwareBuffer

AHardwareBuffer は、Gralloc バッファをラップする共有メモリの一種です。Android 10 では、ニューラル ネットワーク API(NNAPI)が AHardwareBuffer の使用をサポートし、データをコピーせずにドライバで実行できるため、アプリのパフォーマンスと消費電力が改善します。たとえば、カメラ HAL のスタックは、カメラ NDK の API とメディア NDK の API で生成された AHardwareBuffer ハンドルを使用して、AHardwareBuffer オブジェクトを機械学習ワークロードの NNAPI に渡すことができます。詳細については、ANeuralNetworksMemory_createFromAHardwareBuffer をご覧ください。

NNAPI で使用される AHardwareBuffer オブジェクトは、hardware_buffer または hardware_buffer_blob と名付けられた hidl_memory 構造体を介してドライバに渡されます。hidl_memory 構造体 hardware_buffer_blob は、AHARDWAREBUFFER_FORMAT_BLOB 形式の AHardwareBuffer オブジェクトのみを表します。

フレームワークで必要な情報は、hidl_memory 構造体の hidl_handle フィールドでエンコードされます。hidl_handle フィールドは、AHardwareBuffer または Gralloc バッファに関する必須メタデータをすべてエンコードする native_handle をラップします。

ドライバは、提供された hidl_handle フィールドを適切にデコードして、hidl_handle によって記述されたメモリにアクセスする必要があります。getSupportedOperations_1_2getSupportedOperations_1_1、または getSupportedOperations メソッドが呼び出されると、ドライバは、提供された hidl_handle をデコードして hidl_handle によって記述されたメモリにアクセスできるかどうかを見極めます。定数オペランドに使用する hidl_handle フィールドがサポートされていない場合、モデルの準備は必ず失敗します。実行の入力または出力オペランドに使用する hidl_handle フィールドがサポートされていない場合、その実行は必ず失敗します。モデルの準備や実行に失敗した場合、ドライバは GENERAL_FAILURE エラーコードを返すことが推奨されます。

メモリドメイン

Android 11 以降を搭載するデバイスの場合、ドライバが管理するバッファのアロケータ インターフェースを提供するメモリドメインが NNAPI でサポートされます。これにより、異なる実行間でデバイスのネイティブ メモリを受け渡しできるようになり、同じドライバの連続する実行間での不要なデータのコピーや変換が抑制されます。このフローを図 1 に示します。

バッファデータ フロー(メモリドメイン使用または不使用)

図 1. メモリドメインを使用したバッファデータ フロー

メモリドメイン機能は、主にクライアント側への頻繁なアクセスを必要としないドライバ内部のテンソル向けです。このようなテンソルの例には、シーケンス モデル内の状態テンソルがあります。クライアント側での CPU アクセスを頻繁に必要とするテンソルの場合は、共有メモリプールを使用することをおすすめします。

メモリドメイン機能をサポートするには、IDevice::allocate を実装して、ドライバが管理するバッファの割り当てをフレームワークがリクエストできるようにします。割り当ての際、フレームワークはバッファに次のプロパティと使用パターンを提供します。

  • BufferDesc は、バッファの必須プロパティを記述します。
  • BufferRole は、準備済みのモデルの入力または出力として、あり得るバッファの使用パターンを記述します。バッファ割り当ての際、複数のロールを指定できます。割り当てられたバッファは、指定されたロールとしてのみ使用できます。

割り当てられたバッファはドライバの内部にあります。ドライバは、任意のバッファの場所またはデータ レイアウトを選択できます。バッファが正常に割り当てられると、ドライバのクライアントは、返されたトークンまたは IBuffer オブジェクトを使用してバッファを参照または操作できます。

IDevice::allocate からのトークンは、実行の Request 構造体にある MemoryPool オブジェクトの一つとしてバッファを参照する際に提供されます。あるプロセスが別のプロセスに割り当てられたバッファにアクセスできないようにするには、ドライバがバッファを使用するたびに適切な検証を行う必要があります。ドライバは、バッファの使用状況が割り当て時に提供された BufferRole ロールの一つであることを検証し、使用状況が不正になった場合はすぐに実行を失敗させる必要があります。

IBuffer オブジェクトは、明示的なメモリコピーに使用されます。特定の状況では、ドライバのクライアントは、共有メモリプールからドライバが管理するバッファを初期化するか、バッファを共有メモリプールにコピーする必要があります。たとえば、次のような場合があります。

  • 状態テンソルの初期化
  • 中間結果のキャッシュ保存
  • CPU での代替実行

このようなユースケースをサポートするために、ドライバは ashmemmmap_fdhardware_buffer_blob を使用して IBuffer::copyToIBuffer::copyFrom を実装する必要があります(メモリドメイン割り当てをサポートしている場合)。ドライバで BLOB 以外のモード hardware_buffer をサポートすることは任意です。

バッファ割り当ての際、バッファのディメンションは、BufferRole によって指定されたすべてのロールに対応するモデル オペランドと、BufferDesc で提供されるディメンションから推測できます。すべてのディメンション情報を組み合わせると、バッファのディメンションまたはランクが不明になる可能性があります。このような場合は、バッファが柔軟な状態のときで、モデル入力として使用される際にディメンションが固定されます。モデル出力として使用される場合は、バッファが動的な状態のときです。同じバッファが異なる実行の異なる出力形状で使用されることもあるため、ドライバはバッファのサイズ変更を適切に処理する必要があります。

メモリドメインはオプション機能です。ドライバは、さまざまな理由で特定の割り当てリクエストをサポートできないと判断することがあります。次に例を示します。

  • リクエストされたバッファのサイズが動的である。
  • ドライバにメモリの制約があるため、大きなバッファを処理できない。

ドライバが管理するバッファから同時に複数のスレッドで読み取ることも可能です。書き込みまたは読み書きを想定したバッファへの同時アクセスについては未定義ですが、ドライバ サービスをクラッシュさせたり、呼び出し元を無期限にブロックしたりしてはなりません。ドライバでエラーが発生するか、バッファのコンテンツが不確定な状態になることも考えられます。