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

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

使ってみる

開始するには、NPU の概要ガイドをご覧ください。

NPU サポート付きの LiteRT の実装例については、次のデモアプリを参照してください。

NPU ベンダー

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

Qualcomm AI Engine Direct

  • AOT とオンデバイス コンパイルの実行パスは、コンパイル済みモデル API を通じてサポートされています。
  • 設定の詳細については、Qualcomm AI Engine Direct をご覧ください。

MediaTek NeuroPilot

  • AOT と JIT の実行パスは、コンパイル済みモデル API を通じてサポートされています。
  • 設定の詳細については、MediaTek NeuroPilot をご覧ください。

NPU 用にモデルを変換してコンパイルする

LiteRT で NPU アクセラレーションを使用するには、モデルを LiteRT ファイル形式に変換し、デバイス上の NPU で使用できるようにコンパイルする必要があります。LiteRT AOT(事前)コンパイラを使用して、モデルを AI パックにコンパイルできます。AI パックには、コンパイルされたモデルとデバイス ターゲティング構成がバンドルされます。これにより、特定の SoC を搭載しているか、特定の SoC 向けに最適化されているかに応じて、モデルがデバイスに正しく提供されることが検証されます。

モデルを変換してコンパイルしたら、オンデバイス AI 用 Play(PODAI)を使用して、モデルを Google Play にアップロードし、オンデマンド AI フレームワークを通じてデバイスに配信できます。

NPU 用のモデルの変換とコンパイルに関するエンドツーエンドのガイドについては、LiteRT AOT コンパイル ノートブックをご覧ください。

[AOT のみ] Play AI パックでデプロイする

モデルを変換して AI パックをコンパイルしたら、次の手順に沿って Google Play で 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

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

AOT の場合は litert_npu_runtime_libraries.zip、JIT の場合は 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

AI パックと NPU ランタイム ライブラリを Gradle 構成に追加

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

// my_app/setting.gradle.kts

...

// [AOT only]
// AI Packs
include(":ai_packs:my_model")
include(":ai_packs:my_model_mtk")

// 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
      }
  }

  // [AOT Only]
  // AI Packs
  assetPacks.add(":ai_packs:my_model")
  assetPacks.add(":ai_packs:my_model_mtk")

  // 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"))
  ...
}

[AOT のみ] オンデマンド デプロイを使用する

build.gradle.kts ファイルで Android AI Pack 機能を構成したら、デバイスの機能をチェックし、対応するデバイスで NPU を使用します。GPU と CPU はフォールバックとして使用します。

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,
)

JIT モード用の CompiledModel を作成

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

val compiledModel = CompiledModel.create(
    "model/my_model.tflite",
    CompiledModel.Options(Accelerator.NPU),
    env,
)

Kotlin で LiteRT を使用した NPU での推論

NPU アクセラレータの使用を開始するには、コンパイル済みモデル(CompiledModel)の作成時に NPU パラメータを渡します。

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

val inputBuffers = model.createInputBuffers()
val outputBuffers = model.createOutputBuffers()

inputBuffers[0].writeFloat(FloatArray(data_size) { data_value })
model.run(inputBuffers, outputBuffers)
val outputFloatArray = outputBuffers[0].readFloat()

inputBuffers.forEach { it.close() }
outputBuffers.forEach { it.close() }
model.close()

C++ の LiteRT を使用した NPU での推論

ビルド依存関係

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