Akselerasi NPU dengan LiteRT

LiteRT menyediakan antarmuka terpadu untuk menggunakan Unit Pemrosesan Neural (NPU) tanpa mengharuskan Anda menavigasi compiler, runtime, atau dependensi library khusus vendor. Penggunaan LiteRT untuk akselerasi NPU meningkatkan performa untuk inferensi real-time dan model besar serta meminimalkan salinan memori melalui penggunaan buffer hardware tanpa salinan.

Mulai

Vendor NPU

LiteRT mendukung akselerasi NPU dengan vendor berikut:

Qualcomm AI Engine Direct

MediaTek NeuroPilot

Google Tensor

Google Tensor SDK dalam akses eksperimental. Daftar di sini.

Kompilasi AOT dan di perangkat

NPU LiteRT mendukung kompilasi AOT dan di perangkat untuk memenuhi persyaratan deployment spesifik Anda:

  • Kompilasi offline (AOT): Ini paling cocok untuk model besar dan kompleks yang SoC targetnya diketahui. Mengompilasi ahead-of-time secara signifikan mengurangi biaya inisialisasi dan menurunkan penggunaan memori saat pengguna meluncurkan aplikasi Anda.
  • Kompilasi online (di perangkat): Juga dikenal sebagai kompilasi JIT. Opsi ini ideal untuk distribusi model kecil yang tidak bergantung pada platform. Model dikompilasi di perangkat pengguna selama inisialisasi, tidak memerlukan langkah persiapan tambahan, tetapi menimbulkan biaya run pertama yang lebih tinggi.

Panduan berikut menunjukkan cara men-deploy untuk kompilasi AOT dan dalam perangkat dalam tiga langkah.

Langkah 1: Kompilasi AOT untuk SoC NPU target

Anda dapat menggunakan Compiler AOT (ahead of time) LiteRT untuk mengompilasi model .tflite ke SoC yang didukung. Anda juga dapat menargetkan beberapa vendor dan versi SoC secara bersamaan dalam satu proses kompilasi. Lihat detail selengkapnya di notebook Kompilasi AOT LiteRT ini. Meskipun bersifat opsional, kompilasi AOT sangat direkomendasikan untuk model yang lebih besar guna mengurangi waktu inisialisasi di perangkat. Langkah ini tidak diperlukan untuk kompilasi di perangkat.

Langkah 2: Deploy dengan Google Play jika menggunakan Android

Di Android, gunakan Play untuk AI di Perangkat (PODAI) Google untuk men-deploy model dan library runtime NPU dengan aplikasi Anda.

  • Untuk model kompilasi di perangkat: tambahkan file model .tflite asli langsung ke direktori aset/ aplikasi Anda.
  • Untuk model kompilasi AOT: gunakan LiteRT untuk mengekspor model yang dikompilasi ke dalam satu Paket AI Play Google. Kemudian, Anda mengupload paket AI ke Google Play untuk otomatis mengirimkan model yang dikompilasi dengan benar ke perangkat pengguna.
  • Untuk library runtime NPU, gunakan Play Feature Delivery untuk mendistribusikan library runtime yang benar ke perangkat pengguna.

Lihat bagian berikut tentang cara men-deploy dengan Paket AI Play dan Play Feature Delivery.

Men-deploy model AOT dengan Play AI Pack

Langkah-langkah berikut akan memandu Anda men-deploy model yang dikompilasi AOT menggunakan Paket AI Play.

Menambahkan Paket AI ke project

Impor Paket AI ke project Gradle dengan menyalin Paket AI ke direktori root project Gradle. Contoh:

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

Tambahkan setiap Paket AI ke konfigurasi build 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

Menambahkan Paket AI ke konfigurasi Gradle

Salin device_targeting_configuration.xml dari Paket AI yang dihasilkan ke direktori modul aplikasi utama. Kemudian, perbarui settings.gradle.kts:

// my_app/setting.gradle.kts

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

Perbarui 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")
}

Mengonfigurasi Paket AI untuk pengiriman on demand

Pengiriman on-demand memungkinkan Anda meminta model saat runtime, yang berguna jika model hanya diperlukan untuk alur penggunaan tertentu. Model Anda akan didownload ke ruang penyimpanan internal aplikasi Anda. Dengan fitur Paket AI Android yang dikonfigurasi dalam file build.gradle.kts, periksa kemampuan perangkat. Lihat juga petunjuk untuk pengiriman saat penginstalan dan pengiriman fast-follow dari 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,
)

Men-deploy library runtime NPU dengan Play Feature Delivery

Play Feature Delivery mendukung beberapa opsi pengiriman untuk mengoptimalkan ukuran download awal, termasuk pengiriman saat penginstalan, pengiriman on-demand, pengiriman bersyarat, dan pengiriman instan. Di sini, kami menunjukkan panduan penayangan waktu penginstalan dasar.

Menambahkan library runtime NPU ke project

Download litert_npu_runtime_libraries.zip untuk kompilasi AOT atau litert_npu_runtime_libraries_jit.zip untuk kompilasi di perangkat, lalu ekstrak di direktori root project:

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

Jalankan skrip untuk mendownload library dukungan NPU. Misalnya, jalankan perintah berikut untuk NPU Qualcomm:

$ ./litert_npu_runtime_libraries/fetch_qualcomm_library.sh

Menambahkan library runtime NPU ke konfigurasi Gradle

Salin device_targeting_configuration.xml dari Paket AI yang dihasilkan ke direktori modul aplikasi utama. Kemudian, perbarui 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")

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

Langkah 3: Inferensi di NPU menggunakan Runtime LiteRT

LiteRT menyederhanakan kompleksitas pengembangan terhadap versi SoC tertentu, sehingga Anda dapat menjalankan model di NPU hanya dengan beberapa baris kode. LiteRT juga menyediakan mekanisme penggantian bawaan yang andal: Anda dapat menentukan CPU, GPU, atau keduanya sebagai opsi, dan LiteRT akan otomatis menggunakannya jika NPU tidak tersedia. Hebatnya, kompilasi AOT juga mendukung penggantian. Delegasi parsial pada NPU memungkinkan subgraf yang tidak didukung berjalan dengan lancar di CPU atau GPU seperti yang ditentukan.

Menjalankan di Kotlin

Lihat contoh penerapan di aplikasi demo berikut:

Menambahkan dependensi Android

Anda dapat menambahkan paket Maven LiteRT terbaru ke dependensi build.gradle:

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

Integrasi runtime

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

Menjalankan di C++ lintas platform

Lihat contoh implementasi di Aplikasi C++ segmentasi asinkron.

Dependensi Build Bazel

Pengguna C++ harus membuat dependensi aplikasi dengan akselerasi NPU LiteRT. Aturan cc_binary yang mengemas logika aplikasi inti (misalnya, main.cc) memerlukan komponen runtime berikut:

  • Library bersama LiteRT C API: atribut data harus menyertakan library bersama LiteRT C API (//litert/c:litert_runtime_c_api_shared_lib) dan objek bersama pengiriman khusus vendor untuk NPU (//litert/vendors/qualcomm/dispatch:dispatch_api_so).
  • Library backend khusus NPU: Misalnya, library Qualcomm AI RT (QAIRT) untuk host Android (seperti libQnnHtp.so, libQnnHtpPrepare.so) dan library Hexagon DSP yang sesuai (libQnnHtpV79Skel.so). Hal ini memastikan bahwa runtime LiteRT dapat memindahkan komputasi ke NPU.
  • Dependensi atribut: atribut deps ditautkan dengan dependensi waktu kompilasi penting, seperti buffer tensor LiteRT (//litert/cc:litert_tensor_buffer) dan API untuk lapisan pengiriman NPU (//litert/vendors/qualcomm/dispatch:dispatch_api). Hal ini memungkinkan kode aplikasi Anda berinteraksi dengan NPU melalui LiteRT.
  • File model dan aset lainnya: Disertakan melalui atribut data.

Penyiapan ini memungkinkan biner yang dikompilasi memuat dan menggunakan NPU secara dinamis untuk inferensi machine learning yang dipercepat.

Menyiapkan lingkungan NPU

Beberapa backend NPU memerlukan dependensi atau library runtime. Saat menggunakan API model yang dikompilasi, LiteRT mengatur persyaratan ini melalui objek Environment. Gunakan kode berikut untuk menemukan library atau driver NPU yang sesuai:

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

Integrasi runtime

Cuplikan kode berikut menunjukkan implementasi dasar seluruh proses di 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 dengan akselerasi NPU

Penggunaan zero-copy memungkinkan NPU mengakses data secara langsung di memorinya sendiri tanpa perlu CPU menyalin data tersebut secara eksplisit. Dengan tidak menyalin data ke dan dari memori CPU, salinan nol dapat mengurangi latensi end-to-end secara signifikan.

Kode berikut adalah contoh penerapan NPU Zero-Copy dengan AHardwareBuffer, yang meneruskan data langsung ke NPU. Implementasi ini menghindari perjalanan pulang pergi yang mahal ke memori CPU, sehingga mengurangi overhead inferensi secara signifikan.

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

Merangkai beberapa inferensi NPU

Untuk pipeline yang kompleks, Anda dapat merangkai beberapa inferensi NPU. Karena setiap langkah menggunakan buffer yang kompatibel dengan akselerator, pipeline Anda sebagian besar tetap berada di memori yang dikelola 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);

Penyimpanan dalam cache kompilasi di perangkat NPU

LiteRT mendukung kompilasi .tflite model di perangkat NPU (dikenal sebagai JIT). Kompilasi JIT dapat sangat berguna dalam situasi ketika mengompilasi model sebelumnya tidak memungkinkan.

Namun, kompilasi JIT dapat menimbulkan beberapa latensi dan overhead memori untuk menerjemahkan model yang disediakan pengguna ke dalam petunjuk bytecode NPU sesuai permintaan. Untuk meminimalkan dampak performa, artefak kompilasi NPU dapat di-cache.

Jika caching diaktifkan, LiteRT hanya akan memicu kompilasi ulang model jika diperlukan, misalnya:

  • Versi plugin compiler NPU vendor berubah;
  • Sidik jari build Android berubah;
  • Model yang disediakan pengguna berubah;
  • Opsi kompilasi berubah.

Untuk mengaktifkan penyimpanan ke dalam cache kompilasi NPU, tentukan tag lingkungan CompilerCacheDir di opsi lingkungan. Nilai harus ditetapkan ke jalur aplikasi yang dapat ditulis yang ada.

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

Contoh penghematan latensi dan memori:

Waktu dan memori yang diperlukan untuk kompilasi NPU dapat bervariasi berdasarkan beberapa faktor, seperti chip NPU yang mendasarinya, kompleksitas model input, dll.

Tabel berikut membandingkan waktu inisialisasi runtime dan konsumsi memori saat kompilasi NPU diperlukan dan saat kompilasi dapat dilewati karena adanya caching. Di salah satu perangkat contoh, kita mendapatkan hal berikut:

Model TFLite inisialisasi model dengan kompilasi NPU inisialisasi model dengan kompilasi yang di-cache jejak memori init dengan kompilasi NPU menginisialisasi memori dengan kompilasi yang di-cache
torchvision_resnet152.tflite 7465,22 md 198,34 md 1525,24 MB 355,07 MB
torchvision_lraspp_mobilenet_v3_large.tflite 1.592,54 md 166,47 md 254,90 MB 33,78 MB

Di perangkat lain, kami mendapatkan hal berikut:

Model TFLite inisialisasi model dengan kompilasi NPU inisialisasi model dengan kompilasi yang di-cache jejak memori init dengan kompilasi NPU menginisialisasi memori dengan kompilasi yang di-cache
torchvision_resnet152.tflite 2766,44 md 379,86 md 653,54 MB 501,21 MB
torchvision_lraspp_mobilenet_v3_large.tflite 784,14 md 231,76 md 113,14 MB 67,49 MB