LiteRT による NPU アクセラレーション

LiteRT は、ベンダー固有のコンパイラ、ランタイム、ライブラリの依存関係を操作しなくても、ニューラル プロセッシング ユニット(NPU)を使用できる統合インターフェースを提供します。NPU アクセラレーションに LiteRT を使用すると、リアルタイム推論と大規模モデル推論のパフォーマンスが向上し、ゼロコピー ハードウェア バッファの使用によりメモリコピーが最小限に抑えられます。

使ってみる

NPU ベンダー

LiteRT は、次のベンダーの NPU アクセラレーションをサポートしています。

Qualcomm AI Engine Direct

MediaTek NeuroPilot

Google Tensor

Google Tensor SDK は試験運用版です。こちらでお申込み願います。

AOT とオンデバイス コンパイル

LiteRT NPU は、特定のデプロイ要件を満たすために、AOT とデバイス上でのコンパイルの両方をサポートしています。

  • オフライン(AOT)コンパイル: ターゲット SoC がわかっている大規模で複雑なモデルに最適です。事前コンパイルを行うと、初期化コストが大幅に削減され、ユーザーがアプリを起動したときのメモリ使用量が削減されます。
  • オンライン(デバイス上)コンパイル: JIT コンパイルとも呼ばれます。これは、小規模モデルのプラットフォームに依存しないモデル配布に最適です。モデルは初期化時にユーザーのデバイスでコンパイルされるため、追加の準備手順は必要ありませんが、初回実行時のコストが高くなります。

次のガイドでは、AOT コンパイルとオンデバイス コンパイルの両方でデプロイする方法を 3 つの手順で説明します。

ステップ 1: ターゲット NPU SoC の AOT コンパイル

LiteRT AOT(事前)コンパイラを使用して、.tflite モデルをサポートされている SoC にコンパイルできます。1 回のコンパイル プロセスで複数の SoC ベンダーとバージョンを同時にターゲットにすることもできます。詳細については、LiteRT AOT コンパイル ノートブックをご覧ください。AOT コンパイルは省略可能ですが、デバイス上の初期化時間を短縮するために、大規模なモデルでは強く推奨されます。この手順は、デバイス上のコンパイルでは必要ありません。

ステップ 2: Android の場合は Google Play でデプロイする

Android では、Google Play for On-device AI(PODAI)を使用して、モデルと NPU ランタイム ライブラリをアプリとともにデプロイします。

Play AI PackPlay Feature Delivery を使用してデプロイする方法については、以降のセクションをご覧ください。

Play AI Pack で AOT モデルをデプロイする

次の手順では、Play AI パックを使用して AOT コンパイル済みモデルをデプロイする方法について説明します。

プロジェクトに AI パックを追加する

AI パックを Gradle プロジェクトのルート ディレクトリにコピーして、AI パックを Gradle プロジェクトにインポートします。次に例を示します。

my_app/
    ...
    ai_packs/
        my_model/...
        my_model_mtk/...

各 AI パックを Gradle ビルド構成に追加します。

// my_app/ai_packs/my_model/build.gradle.kts

plugins { id("com.android.ai-pack") }

aiPack {
  packName = "my_model"  // ai pack dir name
  dynamicDelivery { deliveryType = "on-demand" }
}

// Add another build.gradle.kts for my_model_mtk/ as well

Gradle 構成に AI パックを追加する

生成された AI パックからメインアプリ モジュールのディレクトリに device_targeting_configuration.xml をコピーします。次に、settings.gradle.kts を更新します。

// my_app/setting.gradle.kts

...
// AI Packs
include(":ai_packs:my_model")
include(":ai_packs:my_model_mtk")

build.gradle.kts を更新します。

// my_app/build.gradle.kts

android {
 ...

 defaultConfig {
    ...
    // API level 31+ is required for NPU support.
    minSdk = 31
  }

  // AI Packs
  assetPacks.add(":ai_packs:my_model")
  assetPacks.add(":ai_packs:my_model_mtk")
}

オンデマンド配信用に AI Pack を構成する

オンデマンド配信では、実行時にモデルをリクエストできます。これは、特定のユーザーフローでのみモデルが必要な場合に便利です。モデルはアプリの内部ストレージ スペースにダウンロードされます。build.gradle.kts ファイルで Android AI Pack 機能が設定されている状態で、デバイスの機能を確認します。PODAI のインストール時の配信迅速なフォローアップ配信手順もご覧ください。

val env = Environment.create(BuiltinNpuAcceleratorProvider(context))

val modelProvider = AiPackModelProvider(
    context, "my_model", "model/my_model.tflite") {
    if (NpuCompatibilityChecker.Qualcomm.isDeviceSupported())
      setOf(Accelerator.NPU) else setOf(Accelerator.CPU, Accelerator.GPU)
}
val mtkModelProvider = AiPackModelProvider(
    context, "my_model_mtk", "model/my_model_mtk.tflite") {
    if (NpuCompatibilityChecker.Mediatek.isDeviceSupported())
      setOf(Accelerator.NPU) else setOf()
}
val modelSelector = ModelSelector(modelProvider, mtkModelProvider)
val model = modelSelector.selectModel(env)

val compiledModel = CompiledModel.create(
    model.getPath(),
    CompiledModel.Options(model.getCompatibleAccelerators()),
    env,
)

Play Feature Delivery を使用して NPU ランタイム ライブラリをデプロイする

Play Feature Delivery は、初回ダウンロード サイズを最適化するための複数の配信オプション(インストール時配信、オンデマンド配信、条件付き配信、Instant 配信など)をサポートしています。ここでは、基本的なインストール時の配信ガイドを示します。

プロジェクトに NPU ランタイム ライブラリを追加する

AOT コンパイル用の litert_npu_runtime_libraries.zip またはオンデバイス コンパイル用の litert_npu_runtime_libraries_jit.zip をダウンロードし、プロジェクトのルート ディレクトリに解凍します。

my_app/
    ...
    litert_npu_runtime_libraries/
        mediatek_runtime/...
        qualcomm_runtime_v69/...
        qualcomm_runtime_v73/...
        qualcomm_runtime_v75/...
        qualcomm_runtime_v79/...
        qualcomm_runtime_v81/...
        fetch_qualcomm_library.sh

スクリプトを実行して NPU サポート ライブラリをダウンロードします。たとえば、Qualcomm NPU の場合は次のコマンドを実行します。

$ ./litert_npu_runtime_libraries/fetch_qualcomm_library.sh

Gradle 構成に NPU ランタイム ライブラリを追加

生成された AI パックからメインアプリ モジュールのディレクトリに device_targeting_configuration.xml をコピーします。次に、settings.gradle.kts を更新します。

// my_app/setting.gradle.kts

...
// NPU runtime libraries
include(":litert_npu_runtime_libraries:runtime_strings")

include(":litert_npu_runtime_libraries:mediatek_runtime")
include(":litert_npu_runtime_libraries:qualcomm_runtime_v69")
include(":litert_npu_runtime_libraries:qualcomm_runtime_v73")
include(":litert_npu_runtime_libraries:qualcomm_runtime_v75")
include(":litert_npu_runtime_libraries:qualcomm_runtime_v79")
include(":litert_npu_runtime_libraries:qualcomm_runtime_v81")

build.gradle.kts を更新します。

// my_app/build.gradle.kts

android {
 ...
 defaultConfig {
    ...
    // API level 31+ is required for NPU support.
    minSdk = 31

    // NPU only supports arm64-v8a
    ndk { abiFilters.add("arm64-v8a") }
    // Needed for Qualcomm NPU runtime libraries
    packaging { jniLibs { useLegacyPackaging = true } }
  }

  // Device targeting
  bundle {
      deviceTargetingConfig = file("device_targeting_configuration.xml")
      deviceGroup {
        enableSplit = true // split bundle by #group
        defaultGroup = "other" // group used for standalone APKs
      }
  }

  // NPU runtime libraries
  dynamicFeatures.add(":litert_npu_runtime_libraries:mediatek_runtime")
  dynamicFeatures.add(":litert_npu_runtime_libraries:qualcomm_runtime_v69")
  dynamicFeatures.add(":litert_npu_runtime_libraries:qualcomm_runtime_v73")
  dynamicFeatures.add(":litert_npu_runtime_libraries:qualcomm_runtime_v75")
  dynamicFeatures.add(":litert_npu_runtime_libraries:qualcomm_runtime_v79")
  dynamicFeatures.add(":litert_npu_runtime_libraries:qualcomm_runtime_v81")
}

dependencies {
  // Dependencies for strings used in the runtime library modules.
  implementation(project(":litert_npu_runtime_libraries:runtime_strings"))
  ...
}

ステップ 3: LiteRT ランタイムを使用した NPU での推論

LiteRT は、特定の SoC バージョンに対する開発の複雑さを抽象化し、数行のコードだけで NPU でモデルを実行できるようにします。また、堅牢な組み込みフォールバック メカニズムも提供します。CPU、GPU、またはその両方をオプションとして指定できます。NPU が使用できない場合、LiteRT はそれらを自動的に使用します。AOT コンパイルはフォールバックもサポートしています。NPU での部分委任を提供します。サポートされていないサブグラフは、指定されたとおりに CPU または GPU でシームレスに実行されます。

Kotlin で実行

実装例については、次のデモアプリをご覧ください。

Android の依存関係を追加する

最新の LiteRT Maven パッケージを build.gradle の依存関係に追加できます。

dependencies {
  ...
  implementation("com.google.ai.edge.litert:litert:+")
}

ランタイム統合

// 1. Load model and initialize runtime.
// If NPU is unavailable, inference will fallback to GPU.
val model =
    CompiledModel.create(
        context.assets,
        "model/mymodel.tflite",
        CompiledModel.Options(Accelerator.NPU, Accelerator.GPU)
    )

// 2. Pre-allocate input/output buffers
val inputBuffers = model.createInputBuffers()
val outputBuffers = model.createOutputBuffers()

// 3. Fill the first input
inputBuffers[0].writeFloat(...)

// 4. Invoke
model.run(inputBuffers, outputBuffers)

// 5. Read the output
val outputFloatArray = outputBuffers[0].readFloat()

C++ クロスプラットフォームで実行する

実装例については、非同期セグメンテーション C++ アプリをご覧ください。

Bazel ビルドの依存関係

C++ ユーザーは、LiteRT NPU アクセラレーションを使用してアプリケーションの依存関係をビルドする必要があります。コア アプリケーション ロジック(cc_binarymain.cc)には、次のランタイム コンポーネントが必要です。

  • LiteRT C API 共有ライブラリ: data 属性には、LiteRT C API 共有ライブラリ(//litert/c:litert_runtime_c_api_shared_lib)と NPU のベンダー固有のディスパッチ共有オブジェクト(//litert/vendors/qualcomm/dispatch:dispatch_api_so)を含める必要があります。
  • NPU 固有のバックエンド ライブラリ: たとえば、Android ホスト(libQnnHtp.solibQnnHtpPrepare.so など)用の Qualcomm AI RT(QAIRT)ライブラリと、対応する Hexagon DSP ライブラリ(libQnnHtpV79Skel.so)です。これにより、LiteRT ランタイムが NPU に計算をオフロードできるようになります。
  • 属性の依存関係: deps 属性は、LiteRT のテンソル バッファ(//litert/cc:litert_tensor_buffer)や NPU ディスパッチ レイヤの API(//litert/vendors/qualcomm/dispatch:dispatch_api)などの重要なコンパイル時の依存関係にリンクします。これにより、アプリコードは LiteRT を介して NPU とやり取りできます。
  • モデルファイルとその他のアセット: data 属性を介して含まれます。

この設定により、コンパイルされたバイナリは NPU を動的に読み込んで使用し、機械学習推論を高速化できます。

NPU 環境を設定する

一部の NPU バックエンドでは、ランタイム依存関係またはライブラリが必要です。コンパイル済みモデル API を使用する場合、LiteRT は Environment オブジェクトを介してこれらの要件を整理します。適切な NPU ライブラリまたはドライバを見つけるには、次のコードを使用します。

// Provide a dispatch library directory (following is a hypothetical path) for the NPU
std::vector<Environment::Option> environment_options = {
    {
      Environment::OptionTag::DispatchLibraryDir,
      "/usr/lib64/npu_dispatch/"
    }
};

LITERT_ASSIGN_OR_RETURN(auto env, Environment::Create(absl::MakeConstSpan(environment_options)));

ランタイム統合

次のコード スニペットは、C++ でのプロセス全体の基本的な実装を示しています。

// 1. Load the model that has NPU-compatible ops
LITERT_ASSIGN_OR_RETURN(auto model, Model::Load("mymodel_npu.tflite"));

// 2. Create a compiled model with NPU acceleration
//    See following section on how to set up NPU environment
LITERT_ASSIGN_OR_RETURN(auto compiled_model,
  CompiledModel::Create(env, model, kLiteRtHwAcceleratorNpu));

// 3. Allocate I/O buffers
LITERT_ASSIGN_OR_RETURN(auto input_buffers, compiled_model.CreateInputBuffers());
LITERT_ASSIGN_OR_RETURN(auto output_buffers, compiled_model.CreateOutputBuffers());

// 4. Fill model inputs (CPU array -> NPU buffers)
float input_data[] = { /* your input data */ };
input_buffers[0].Write<float>(absl::MakeConstSpan(input_data, /*size*/));

// 5. Run inference
compiled_model.Run(input_buffers, output_buffers);

// 6. Access model output
std::vector<float> data(output_data_size);
output_buffers[0].Read<float>(absl::MakeSpan(data));

NPU アクセラレーションによるゼロコピー

ゼロコピーを使用すると、NPU は CPU がデータを明示的にコピーする必要なく、独自のメモリ内のデータに直接アクセスできます。ゼロコピーでは、CPU メモリとの間でデータをコピーしないため、エンドツーエンドのレイテンシを大幅に短縮できます。

次のコードは、AHardwareBuffer を使用して NPU にデータを直接渡すゼロコピー NPU の実装例です。この実装では、CPU メモリへの高コストのラウンド トリップを回避し、推論のオーバーヘッドを大幅に削減します。

// Suppose you have AHardwareBuffer* ahw_buffer

LITERT_ASSIGN_OR_RETURN(auto tensor_type, model.GetInputTensorType("input_tensor"));

LITERT_ASSIGN_OR_RETURN(auto npu_input_buffer, TensorBuffer::CreateFromAhwb(
    env,
    tensor_type,
    ahw_buffer,
    /* offset = */ 0
));

std::vector<TensorBuffer> input_buffers{npu_input_buffer};

LITERT_ASSIGN_OR_RETURN(auto output_buffers, compiled_model.CreateOutputBuffers());

// Execute the model
compiled_model.Run(input_buffers, output_buffers);

// Retrieve the output (possibly also an AHWB or other specialized buffer)
auto ahwb_output = output_buffers[0].GetAhwb();

複数の NPU 推論をチェーンする

複雑なパイプラインでは、複数の NPU 推論をチェーン状に接続できます。各ステップでアクセラレータ対応のバッファが使用されるため、パイプラインは主に NPU 管理のメモリに留まります。

// compiled_model1 outputs into an AHWB
compiled_model1.Run(input_buffers, intermediate_buffers);

// compiled_model2 consumes that same AHWB
compiled_model2.Run(intermediate_buffers, final_outputs);

NPU オンデバイス コンパイルのキャッシュ保存

LiteRT は、.tflite モデルの NPU オンデバイス(JIT)コンパイルをサポートしています。JIT コンパイルは、モデルを事前にコンパイルすることができない状況で特に役立ちます。

ただし、JIT コンパイルでは、ユーザーが提供したモデルを NPU バイトコード命令にオンデマンドで変換する際に、レイテンシとメモリ オーバーヘッドが発生する可能性があります。パフォーマンスへの影響を最小限に抑えるため、NPU コンパイル アーティファクトをキャッシュに保存できます。

キャッシュ保存が有効になっている場合、LiteRT は必要に応じてモデルの再コンパイルをトリガーします。例:

  • ベンダーの NPU コンパイラ プラグインのバージョンが変更された。
  • Android ビルドのフィンガープリントが変更された。
  • ユーザー提供のモデルが変更された。
  • コンパイル オプションが変更されました。

NPU コンパイル キャッシュを有効にするには、環境オプションで CompilerCacheDir 環境タグを指定します。この値は、アプリケーションの既存の書き込み可能なパスに設定する必要があります。

   const std::array environment_options = {
        litert::Environment::Option{
            /*.tag=*/litert::Environment::OptionTag::CompilerPluginLibraryDir,
            /*.value=*/kCompilerPluginLibSearchPath,
        },
        litert::Environment::Option{
            litert::Environment::OptionTag::DispatchLibraryDir,
            kDispatchLibraryDir,
        },
        // 'kCompilerCacheDir' will be used to store NPU-compiled model
        // artifacts.
        litert::Environment::Option{
            litert::Environment::OptionTag::CompilerCacheDir,
            kCompilerCacheDir,
        },
    };

    // Create an environment.
    LITERT_ASSERT_OK_AND_ASSIGN(
        auto environment, litert::Environment::Create(environment_options));

    // Load a model.
    auto model_path = litert::testing::GetTestFilePath(kModelFileName);
    LITERT_ASSERT_OK_AND_ASSIGN(auto model,
                                litert::Model::CreateFromFile(model_path));

    // Create a compiled model, which only triggers NPU compilation if
    // required.
    LITERT_ASSERT_OK_AND_ASSIGN(
        auto compiled_model, litert::CompiledModel::Create(
                                 environment, model, kLiteRtHwAcceleratorNpu));

レイテンシとメモリの節約の例:

NPU コンパイルに必要な時間とメモリは、基盤となる NPU チップ、入力モデルの複雑さなど、いくつかの要因によって異なります。

次の表は、NPU コンパイルが必要な場合と、キャッシュ保存によりコンパイルをスキップできる場合の、ランタイムの初期化時間とメモリ使用量を比較したものです。あるサンプル デバイスでは、次のようになります。

TFLite モデル NPU コンパイルによるモデルの初期化 キャッシュされたコンパイルによるモデルの初期化 NPU コンパイルによるメモリ使用量の初期化 キャッシュされたコンパイルでメモリを初期化
torchvision_resnet152.tflite 7465.22 ミリ秒 198.34 ミリ秒 1525.24 MB 355.07 MB
torchvision_lraspp_mobilenet_v3_large.tflite 1592.54 ミリ秒 166.47 ミリ秒 254.90 MB 33.78 MB

別のデバイスでは、次の情報を取得します。

TFLite モデル NPU コンパイルによるモデルの初期化 キャッシュされたコンパイルによるモデルの初期化 NPU コンパイルによるメモリ使用量の初期化 キャッシュされたコンパイルでメモリを初期化
torchvision_resnet152.tflite 2766.44 ミリ秒 379.86 ミリ秒 653.54 MB 501.21 MB
torchvision_lraspp_mobilenet_v3_large.tflite 784.14 ミリ秒 231.76 ミリ秒 113.14 MB 67.49 MB