使用 LiteRT 加速 NPU

LiteRT 提供統一介面,可使用神經處理單元 (NPU),不必強制導覽至供應商專用的編譯器、執行階段或程式庫依附元件。使用 LiteRT 進行 NPU 加速,可提升即時和大型模型推論的效能,並透過零複製硬體緩衝區的使用,盡量減少記憶體副本。

開始使用

如要開始使用,請參閱 NPU 總覽指南:

如需支援 NPU 的 LiteRT 實作範例,請參閱下列試用版應用程式:

NPU 供應商

LiteRT 支援下列供應商的 NPU 加速功能:

Qualcomm AI Engine Direct

  • 透過編譯模型 API,即可支援 AOT 和裝置端編譯執行路徑。
  • 如需設定詳情,請參閱「Qualcomm AI Engine Direct」。

MediaTek NeuroPilot

  • 透過編譯模型 API,即可支援 AOT 和 JIT 執行路徑。
  • 如需設定詳情,請參閱「MediaTek NeuroPilot」。

轉換及編譯 NPU 適用的模型

如要搭配 LiteRT 使用 NPU 加速功能,模型必須轉換為 LiteRT 檔案格式,並編譯為裝置端 NPU 使用。您可以使用 LiteRT AOT (預先) 編譯器將模型編譯為 AI Pack,其中會將編譯的模型與裝置指定設定綁在一起。這項測試會驗證模型是否正確提供給裝置,取決於裝置是否配備或最佳化特定 SoC。

轉換及編譯模型後,您可以使用 Play On-device AI (PODAI) 將模型上傳至 Google Play,並透過 On-Demand AI 架構將模型傳送至裝置。

如需將模型轉換及編譯為 NPU 適用的端對端指南,請參閱 LiteRT AOT 編譯筆記本

[僅限 AOT] 使用 Play AI Pack 部署

轉換模型並編譯 AI Pack 後,請按照下列步驟,透過 Google Play 部署 AI Pack。

將 AI Pack 匯入 Gradle 專案

將 AI Pack 複製到 Gradle 專案的根目錄。例如:

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

將每個 AI Pack 新增至 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 Pack 和 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_binary 規則,可封裝核心應用程式邏輯 (例如 main.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 主機的 Qualcomm AI RT (QAIRT) 程式庫 (如 libQnnHtp.solibQnnHtpPrepare.so),以及對應的 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 記憶體,以及從 CPU 記憶體複製資料,因此能大幅縮短端對端延遲時間。

以下程式碼是 Zero-Copy NPU 的實作範例,可搭配 AHardwareBuffer 使用,直接將資料傳遞至 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