NPU-Beschleunigung mit LiteRT

LiteRT bietet eine einheitliche Schnittstelle zur Verwendung von Neural Processing Units (NPUs), ohne dass Sie anbieterspezifische Compiler, Laufzeiten oder Bibliotheksabhängigkeiten verwenden müssen. Durch die Verwendung von LiteRT für die NPU-Beschleunigung wird die Leistung für die Echtzeit- und Large-Model-Inferenz gesteigert und die Anzahl der Speicherkopien durch die Verwendung von Hardware-Puffern ohne Kopieren minimiert.

Jetzt starten

Klassische ML-Modelle

Informationen zu klassischen ML-Modellen finden Sie in den folgenden Demoprogrammen.

GenAI-Modelle

Informationen zu GenAI-Modellen finden Sie in den folgenden Demos und im folgenden Leitfaden:

NPU-Anbieter

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

Google Tensor

  • Unterstützung der AOT-Ausführung über die CompiledModel API
  • Details zur Einrichtung finden Sie unter Google Tensor.

Qualcomm AI Engine Direct

MediaTek NeuroPilot

Intel OpenVino

  • Unterstützung der AOT- und On-Device-Kompilierung über die CompiledModel API
  • Details zur Einrichtung finden Sie unter Intel OpenVino.

AOT- und On-Device-Kompilierung

LiteRT NPU unterstützt sowohl die AOT- als auch die On-Device-Kompilierung, um Ihre spezifischen Bereitstellungsanforderungen zu erfüllen:

  • Offline-Kompilierung (AOT): Diese Option eignet sich am besten für große, komplexe Modelle , bei denen der Ziel-SoC bekannt ist. Durch die AOT-Kompilierung werden die Initialisierungskosten erheblich gesenkt und die Arbeitsspeichernutzung reduziert, wenn der Nutzer Ihre App startet.
  • Online-Kompilierung (auf dem Gerät): Auch als Just-in-time-Kompilierung bezeichnet. Diese Option ist ideal für die plattformunabhängige Bereitstellung kleiner Modelle. Das Modell wird während der Initialisierung auf dem Gerät des Nutzers kompiliert. Es ist kein zusätzlicher Vorbereitungsschritt erforderlich, aber die Kosten für die erste Ausführung sind höher.

So können Sie Ihr Modell mit beiden Optionen (AOT- oder On-Device-Kompilierung) bereitstellen:

Schritt 1: AOT-Kompilierung für die Ziel-SoCs der NPU

Mit dem LiteRT AOT-Compiler (Ahead-of-Time) können Sie Ihr .tflite-Modell für die unterstützten SoCs kompilieren. Sie können auch mehrere SoC-Anbieter und -Versionen gleichzeitig in einem einzigen Kompilierungsprozess verwenden. Weitere Informationen finden Sie in diesem LiteRT AOT-Kompilierungs-Notebook. Die AOT-Kompilierung ist zwar optional, wird aber für größere Modelle dringend empfohlen, um die Initialisierungszeit auf dem Gerät zu verkürzen. Dieser Schritt ist für die On-Device-Kompilierung nicht erforderlich.

Schritt 2: Bereitstellung mit Google Play, falls Android verwendet wird

Verwenden Sie unter Android Google Play für On-Device-KI (PODAI), um das Modell und die NPU-Laufzeitbibliotheken mit Ihrer App bereitzustellen.

  • Für Modelle der On-Device-Kompilierung: Fügen Sie die ursprüngliche .tflite-Modelldatei direkt in das Verzeichnis „assets/“ Ihrer App ein.
  • Für Modelle der AOT-Kompilierung: Exportieren Sie Ihre kompilierten Modelle mit LiteRT in ein einzelnes Google Play AI Pack. Anschließend laden Sie das AI Pack in Google Play hoch, um die richtigen kompilierten Modelle automatisch auf die Geräte der Nutzer zu übertragen.
  • Für NPU-Laufzeitbibliotheken verwenden Sie Play Feature Delivery, um die richtigen Laufzeitbibliotheken auf die Geräte der Nutzer zu übertragen.

In den folgenden Abschnitten erfahren Sie, wie Sie mit Play AI Pack und Play Feature Delivery bereitstellen.

AOT-Modelle mit Play AI Pack bereitstellen

Die folgenden Schritte führen Sie durch die Bereitstellung Ihrer AOT-kompilierten Modelle mit Play AI Packs.

AI Pack zum Projekt hinzufügen

Importieren Sie AI Packs in das Gradle-Projekt, indem Sie die AI Packs in das Stammverzeichnis des Gradle-Projekts kopieren. 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

AI Packs zur Gradle-Konfiguration hinzufügen

Kopieren Sie device_targeting_configuration.xml aus den generierten AI Packs in das Verzeichnis des Haupt-App-Moduls. Aktualisieren Sie dann settings.gradle.kts:

// my_app/setting.gradle.kts

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

Aktualisieren Sie 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 für die On-Demand-Bereitstellung konfigurieren

Mit der On-Demand-Bereitstellung können Sie das Modell zur Laufzeit anfordern. Das ist nützlich, wenn das Modell nur für bestimmte Nutzerabläufe erforderlich ist. Ihr Modell wird in den internen Speicher Ihrer App heruntergeladen. Prüfen Sie die Gerätefunktionen, wenn die Android AI Pack-Funktion in der Datei build.gradle.kts konfiguriert ist. Eine Anleitung zu Anweisungen für die Bereitstellung zur Installationszeit und Fast-Follow von PODAI finden Sie hier.

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

val cpuGpuModelProvider =
      ModelProvider.staticModel(
        ModelProvider.Type.ASSET,
        "model/my_model_cpu_gpu.tflite",
        if (accelerator != Accelerator.NPU) accelerator else Accelerator.CPU,
      )

val qualcommNpuModelProvider =
  AiPackModelProvider(context, "my_model", "model/my_model.tflite")
  {
    buildSet {
      if (
        accelerator == Accelerator.NPU && NpuCompatibilityChecker.Qualcomm.isDeviceSupported()
      )
        add(Accelerator.NPU)
    }
  }

val mtkNpuModelProvider =
  AiPackModelProvider(context, "my_model_mtk", "model/my_model.tflite")
  {
    buildSet {
      if (
        accelerator == Accelerator.NPU && NpuCompatibilityChecker.Mediatek.isDeviceSupported()
      )
        add(Accelerator.NPU)
    }
  }

val googleTensorTpuModelProvider =
  AiPackModelProvider(context, "my_model", "model/my_model.tflite")
  {
    buildSet {
      if (accelerator == Accelerator.NPU &&
          NpuCompatibilityChecker.GoogleTensor.isDeviceSupported()
      )
        add(Accelerator.NPU)
    }
  }

val aiPackModelProvider =
        ModelSelector(cpuGpuModelProvider, mtkNpuModelProvider, qualcommNpuModelProvider, googleTensorTpuModelProvider)
          .selectModel(env)

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

NPU-Laufzeitbibliotheken mit Play Feature Delivery bereitstellen

Play Feature Delivery unterstützt mehrere Bereitstellungsoptionen zur Optimierung der anfänglichen Downloadgröße, darunter die Bereitstellung zur Installationszeit, die On-Demand-Bereitstellung, die bedingte Bereitstellung und die sofortige Bereitstellung. Hier zeigen wir die grundlegende Anleitung zur Bereitstellung zur Installationszeit.

NPU-Laufzeitbibliotheken zum Projekt hinzufügen

Laden Sie litert_npu_runtime_libraries.zip aus der neuesten Version für die AOT-Kompilierung oder litert_npu_runtime_libraries_jit.zip aus der neuesten Version für die On-Device-Kompilierung herunter und entpacken Sie sie im Stammverzeichnis des Projekts:

my_app/
    ...
    litert_npu_runtime_libraries/
        google_tensor_runtime/...
        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

NPU-Laufzeitbibliotheken zur Gradle-Konfiguration hinzufügen

Kopieren Sie device_targeting_configuration.xml aus den generierten AI Packs in das Verzeichnis des Haupt-App-Moduls. Aktualisieren Sie dann settings.gradle.kts:

// my_app/setting.gradle.kts

...
// NPU runtime libraries
include(":litert_npu_runtime_libraries:runtime_strings")
include(":litert_npu_runtime_libraries:google_tensor_runtime")
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
      }
  }

  // NPU runtime libraries
  dynamicFeatures.add(":litert_npu_runtime_libraries:google_tensor_runtime")
  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"))
  ...
}

Schritt 3: Inferenz auf der NPU mit der LiteRT-Laufzeit

LiteRT abstrahiert die Komplexität der Entwicklung für bestimmte SoC-Versionen, sodass Sie Ihr Modell mit nur wenigen Codezeilen auf der NPU ausführen können. Außerdem bietet es einen robusten, integrierten Fallback-Mechanismus: Sie können CPU, GPU oder beides als Optionen angeben. LiteRT verwendet diese automatisch, wenn die NPU nicht verfügbar ist. Praktischerweise unterstützt die AOT-Kompilierung auch Fallbacks. Sie bietet eine teilweise Delegierung auf der NPU, wobei nicht unterstützte Untergraphen nahtlos auf der angegebenen CPU oder GPU ausgeführt werden.

In Kotlin ausführen

Eine Beispielimplementierung finden Sie in den folgenden Demoprogrammen:

Android-Abhängigkeiten hinzufügen

Sie können das neueste LiteRT-Maven-Paket zu Ihren build.gradle-Abhängigkeiten hinzufügen:

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

Laufzeitintegration

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

Plattformübergreifend in C++ ausführen

Eine Beispielimplementierung finden Sie in der asynchronen C++-App für die Segmentierung.

Bazel-Build-Abhängigkeiten

C++-Nutzer müssen die Abhängigkeiten der Anwendung mit der LiteRT-NPU-Beschleunigung erstellen. Für die cc_binary-Regel, die die Kernanwendungslogik (z.B. main.cc) verpackt, 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 gemeinsame Dispatch-Objekt für die NPU (//litert/vendors/qualcomm/dispatch:dispatch_api_so) enthalten.
  • NPU-spezifische Back-End-Bibliotheken: Zum Beispiel die Qualcomm AI RT-Bibliotheken (QAIRT) für den Android-Host (z. B. libQnnHtp.so, libQnnHtpPrepare.so) und die entsprechende Hexagon DSP-Bibliothek (libQnnHtpV79Skel.so). Dadurch wird sichergestellt, dass die LiteRT-Laufzeit Berechnungen an die NPU auslagern kann.
  • Attributabhängigkeiten: Das Attribut deps verweist auf wichtige Kompilierungszeitabhä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). So kann Ihr Anwendungscode über LiteRT mit der NPU interagieren.
  • Modelldateien und andere Assets: Über das Attribut data enthalten.

Mit dieser Einrichtung kann Ihre kompilierte Binärdatei die NPU dynamisch laden und für die beschleunigte Machine-Learning-Inferenz verwenden.

NPU-Umgebung einrichten

Für einige NPU-Back-Ends sind Laufzeitabhängigkeiten oder -bibliotheken erforderlich. Bei Verwendung der API für kompilierte Modelle organisiert LiteRT diese Anforderungen über ein Environment-Objekt. Verwenden Sie den folgenden Code, um die entsprechenden NPU-Bibliotheken oder -Treiber zu 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 grundlegende 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));

Kein Kopieren mit NPU-Beschleunigung

Durch die Verwendung von „Kein Kopieren“ 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-Speicher kopiert werden, kann die End-to-End-Latenz durch „Kein Kopieren“ erheblich reduziert werden.

Der folgende Code ist eine Beispielimplementierung von „Kein Kopieren“ für die NPU mit AHardwareBuffer, wobei Daten direkt an die NPU übergeben werden. Bei dieser Implementierung werden teure Roundtrips zum CPU-Speicher vermieden, wodurch der Inferenzaufwand 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-Inferenzen verketten

Für komplexe Pipelines können Sie mehrere NPU-Inferenzen verketten. Da jeder Schritt einen beschleunigerfreundlichen Puffer verwendet, verbleibt Ihre Pipeline größtenteils im von der 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);

Caching der On-Device-Kompilierung der NPU

LiteRT unterstützt die On-Device-Kompilierung (JIT) von .tflite-Modellen auf der NPU. Die Just-in-time-Kompilierung kann besonders nützlich sein, wenn eine Kompilierung des Modells im Voraus nicht möglich ist.

Die Just-in-time-Kompilierung kann jedoch mit einer gewissen Latenz und einem gewissen Speicheraufwand verbunden sein, um das vom Nutzer bereitgestellte Modell bei Bedarf in NPU-Bytecode-Anweisungen zu übersetzen. 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-Fingerabdruck hat sich geändert.
  • Das vom Nutzer bereitgestellte Modell hat sich geändert.
  • Die Kompilierungsoptionen haben sich geändert.

Wenn Sie das Caching der NPU-Kompilierung aktivieren möchten, geben Sie das Umgebungstag CompilerCacheDir in den Umgebungsoptionen 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));

Beispiele für Latenz- und Speichereinsparungen:

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

In der folgenden Tabelle werden die Initialisierungszeit der Laufzeit und der Speicherverbrauch verglichen, wenn eine NPU-Kompilierung erforderlich ist und wenn die Kompilierung aufgrund des Cachings übersprungen werden kann. Auf einem Beispielgerät erhalten wir Folgendes:

TFLite-Modell Modellinitialisierung mit NPU-Kompilierung Modellinitialisierung mit Kompilierung aus dem Cache Speicherbedarf bei der Initialisierung mit NPU-Kompilierung Speicherbedarf bei der Initialisierung mit Kompilierung aus dem Cache
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 Kompilierung aus dem Cache Speicherbedarf bei der Initialisierung mit NPU-Kompilierung Speicherbedarf bei der Initialisierung mit Kompilierung aus dem Cache
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