NPU-Beschleunigung mit LiteRT

LiteRT bietet eine einheitliche Schnittstelle für die Verwendung von Neural Processing Units (NPUs), ohne dass Sie sich mit anbieterspezifischen Compilern, Laufzeiten oder Bibliotheksabhängigkeiten auseinandersetzen müssen. Die Verwendung von LiteRT für die NPU-Beschleunigung steigert die Leistung für Echtzeit- und Large-Model-Inferenz und minimiert Speicherkopien durch die Verwendung von Hardwarepuffern ohne Kopieren.

Jetzt starten

Eine Einführung finden Sie im NPU-Übersichtsleitfaden:

  • Für klassische ML-Modelle finden Sie in den folgenden Abschnitten Informationen zu den Schritten für die Konvertierung, Kompilierung und Bereitstellung.
  • Für Large Language Models (LLMs) verwenden Sie unser LiteRT-LM-Framework:

Beispielimplementierungen von LiteRT mit NPU-Unterstützung finden Sie in den folgenden Demo-Apps:

NPU-Anbieter

LiteRT unterstützt die NPU-Beschleunigung mit den folgenden Anbietern:

Qualcomm AI Engine Direct

  • AOT- und On-Device-Kompilierungsausführungspfade werden über die Compiled Model API unterstützt.
  • Weitere Informationen zur Einrichtung finden Sie unter Qualcomm AI Engine Direct.

MediaTek NeuroPilot

  • AOT- und JIT-Ausführungspfade werden über die Compiled Model API unterstützt.
  • Weitere Informationen zur Einrichtung finden Sie unter MediaTek NeuroPilot.

Modelle für die NPU konvertieren und kompilieren

Damit die NPU-Beschleunigung mit LiteRT verwendet werden kann, müssen Modelle in das LiteRT-Dateiformat konvertiert und für die Verwendung auf dem Gerät kompiliert werden. Mit dem LiteRT-AOT-Compiler (Ahead-of-Time) können Sie Modelle in ein KI-Paket kompilieren, in dem Ihre kompilierten Modelle mit Konfigurationen für die Ausrichtung auf Geräte gebündelt werden. So wird überprüft, ob Modelle auf Geräten korrekt bereitgestellt werden, je nachdem, ob sie für bestimmte SoCs ausgestattet oder optimiert sind.

Nachdem Sie die Modelle konvertiert und kompiliert haben, können Sie Play for On-device AI (PODAI) verwenden, um Modelle bei Google Play hochzuladen und über das On-Demand AI-Framework auf Geräte zu übertragen.

Notebook zur AOT-Kompilierung von LiteRT

[AOT only] Mit Play AI Pack bereitstellen

Nachdem Sie das Modell konvertiert und ein KI-Paket kompiliert haben, führen Sie die folgenden Schritte aus, um das KI-Paket bei Google Play bereitzustellen.

KI-Pakete in das Gradle-Projekt importieren

Kopieren Sie die KI-Pakete in das Stammverzeichnis des Gradle-Projekts. Beispiel:

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

Fügen Sie jedes AI Pack der Gradle-Build-Konfiguration hinzu:

// 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-Laufzeitbibliotheken zum Projekt hinzufügen

Laden Sie litert_npu_runtime_libraries.zip für AOT oder litert_npu_runtime_libraries_jit.zip für JIT herunter und entpacken Sie die Datei im Stammverzeichnis des Projekts:

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

Führen Sie das Skript aus, um die NPU-Supportbibliotheken herunterzuladen. Führen Sie beispielsweise Folgendes für Qualcomm-NPUs aus:

$ ./litert_npu_runtime_libraries/fetch_qualcomm_library.sh

AI Packs und NPU-Laufzeitbibliotheken zur Gradle-Konfiguration hinzufügen

Kopieren Sie device_targeting_configuration.xml aus den generierten KI-Paketen in das Verzeichnis des Haupt-App-Moduls. Aktualisieren Sie dann 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")

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

[Nur AOT] On-Demand-Bereitstellung verwenden

Wenn die Funktion „Android AI Pack“ in der Datei build.gradle.kts konfiguriert ist, prüfen Sie die Gerätefunktionen und verwenden Sie die NPU auf kompatiblen Geräten. Verwenden Sie GPU und CPU als Fallback:

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

CompiledModel für den JIT-Modus erstellen

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

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

Inferenz auf der NPU mit LiteRT in Kotlin

Wenn Sie den NPU-Beschleuniger verwenden möchten, übergeben Sie beim Erstellen des kompilierten Modells (CompiledModel) den NPU-Parameter.

Das folgende Code-Snippet zeigt eine einfache Implementierung des gesamten Prozesses in 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()

Inferenz auf der NPU mit LiteRT in C++

Build-Abhängigkeiten

C++-Nutzer müssen die Abhängigkeiten der Anwendung mit LiteRT-NPU-Beschleunigung erstellen. Die cc_binary-Regel, die die Kernanwendungslogik (z.B. main.cc) sind die folgenden Laufzeitkomponenten erforderlich:

  • Gemeinsame Bibliothek der LiteRT C API: Das Attribut data muss die gemeinsame Bibliothek der LiteRT C API (//litert/c:litert_runtime_c_api_shared_lib) und das anbieterspezifische Dispatch-Shared-Object für die NPU (//litert/vendors/qualcomm/dispatch:dispatch_api_so) enthalten.
  • NPU-spezifische Backend-Bibliotheken: Zum Beispiel die Qualcomm AI RT (QAIRT)-Bibliotheken für den Android-Host (z. B. libQnnHtp.so, libQnnHtpPrepare.so) und die entsprechende Hexagon DSP-Bibliothek (libQnnHtpV79Skel.so). So kann die LiteRT-Laufzeit Berechnungen an die NPU auslagern.
  • Attributabhängigkeiten: Das Attribut deps verweist auf wichtige Compile-Zeit-Abhängigkeiten wie den Tensorpuffer von LiteRT (//litert/cc:litert_tensor_buffer) und die API für die NPU-Dispatch-Ebene (//litert/vendors/qualcomm/dispatch:dispatch_api). Dadurch kann Ihr Anwendungscode über LiteRT mit der NPU interagieren.
  • Modelldateien und andere Assets: Über das Attribut data eingebunden.

Mit dieser Einrichtung kann Ihre kompilierte Binärdatei die NPU dynamisch laden und für die beschleunigte Inferenz von maschinellem Lernen verwenden.

NPU-Umgebung einrichten

Für einige NPU-Back-Ends sind Laufzeitabhängigkeiten oder Bibliotheken erforderlich. Bei Verwendung der API für kompilierte Modelle werden diese Anforderungen von LiteRT über ein Environment-Objekt organisiert. Mit dem folgenden Code können Sie die entsprechenden NPU-Bibliotheken oder ‑Treiber finden:

// 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)));

Laufzeitintegration

Das folgende Code-Snippet zeigt eine einfache Implementierung des gesamten Prozesses in 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));

Zero-Copy mit NPU-Beschleunigung

Durch die Verwendung von Zero-Copy kann eine NPU direkt auf Daten im eigenen Speicher zugreifen, ohne dass die CPU diese Daten explizit kopieren muss. Da keine Daten in den und aus dem CPU-Arbeitsspeicher kopiert werden müssen, kann die End-to-End-Latenz durch Zero-Copy deutlich reduziert werden.

Der folgende Code ist eine Beispielimplementierung von Zero-Copy-NPU mit AHardwareBuffer, bei der Daten direkt an die NPU übergeben werden. Durch diese Implementierung werden teure Roundtrips zum CPU-Speicher vermieden, wodurch der Inferenz-Overhead erheblich reduziert wird.

// 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();

Mehrere NPU-Inferenzvorgänge verketten

Bei komplexen Pipelines können Sie mehrere NPU-Inferenzvorgänge verketten. Da in jedem Schritt ein accelerator-freundlicher Puffer verwendet wird, bleibt Ihre Pipeline größtenteils im NPU-verwalteten Speicher:

// 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);

Just-in-time-Kompilierungs-Caching für NPU

LiteRT unterstützt die NPU-Just-in-Time-Kompilierung (JIT) von .tflite-Modellen. Die JIT-Kompilierung kann besonders nützlich sein, wenn es nicht möglich ist, das Modell vorab zu kompilieren.

Die JIT-Kompilierung kann jedoch zu Wartezeiten und Speicher-Overhead führen, da das vom Nutzer bereitgestellte Modell bei Bedarf in NPU-Bytecode-Anweisungen übersetzt werden muss. Um die Auswirkungen auf die Leistung zu minimieren, können NPU-Kompilierungsartefakte im Cache gespeichert werden.

Wenn das Caching aktiviert ist, löst LiteRT die Neukompilierung des Modells nur bei Bedarf aus, z.B.:

  • Die Version des NPU-Compiler-Plug-ins des Anbieters hat sich geändert.
  • Der Android-Build-Fingerprint hat sich geändert.
  • Das vom Nutzer bereitgestellte Modell wurde geändert.
  • Die Kompilierungsoptionen wurden geändert.

Wenn Sie das NPU-Kompilierungs-Caching aktivieren möchten, geben Sie in den Umgebungsoptionen das Umgebungstag CompilerCacheDir an. Der Wert muss auf einen vorhandenen beschreibbaren Pfad der Anwendung festgelegt werden.

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

Beispiel für Latenz und Arbeitsspeichereinsparungen:

Die für die NPU-Kompilierung erforderliche Zeit und der erforderliche Arbeitsspeicher können je nach verschiedenen Faktoren variieren, z. B. dem zugrunde liegenden NPU-Chip und der Komplexität des Eingabemodells.

In der folgenden Tabelle werden die Laufzeitinitialisierungszeit und der Arbeitsspeicherverbrauch verglichen, wenn eine NPU-Kompilierung erforderlich ist und wenn die Kompilierung aufgrund von Caching übersprungen werden kann. Auf einem Beispielgerät erhalten wir Folgendes:

TFLite-Modell Modellinitialisierung mit NPU-Kompilierung Modellinitialisierung mit zwischengespeicherter Kompilierung Speicherbedarf bei der Initialisierung mit NPU-Kompilierung Arbeitsspeicher mit zwischengespeicherter Kompilierung initialisieren
torchvision_resnet152.tflite 7465,22 ms 198,34 ms 1525,24 MB 355,07 MB
torchvision_lraspp_mobilenet_v3_large.tflite 1592,54 ms 166,47 ms 254,90 MB 33,78 MB

Auf einem anderen Gerät erhalten wir Folgendes:

TFLite-Modell Modellinitialisierung mit NPU-Kompilierung Modellinitialisierung mit zwischengespeicherter Kompilierung Speicherbedarf bei der Initialisierung mit NPU-Kompilierung Arbeitsspeicher mit zwischengespeicherter Kompilierung initialisieren
torchvision_resnet152.tflite 2766,44 ms 379,86 ms 653,54 MB 501,21 MB
torchvision_lraspp_mobilenet_v3_large.tflite 784,14 ms 231,76 ms 113,14 MB 67,49 MB