Akselerasi NPU dengan LiteRT

LiteRT menyediakan antarmuka terpadu untuk menggunakan Unit Pemrosesan Neural (NPU) tanpa mengharuskan Anda menavigasi compiler, runtime, atau dependensi library spesifik 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

Untuk memulai, lihat panduan ringkasan NPU:

Untuk contoh penerapan LiteRT dengan dukungan NPU, lihat aplikasi demo berikut:

Vendor NPU

LiteRT mendukung akselerasi NPU dengan vendor berikut:

Qualcomm AI Engine Direct

  • Jalur eksekusi kompilasi AOT dan On-Device didukung melalui Compiled Model API.
  • Lihat Qualcomm AI Engine Direct untuk mengetahui detail penyiapan.

MediaTek NeuroPilot

  • Jalur eksekusi AOT dan JIT didukung melalui Compiled Model API.
  • Lihat MediaTek NeuroPilot untuk mengetahui detail penyiapan.

Mengonversi dan mengompilasi model untuk NPU

Untuk menggunakan akselerasi NPU dengan LiteRT, model harus dikonversi ke format file LiteRT dan dikompilasi untuk penggunaan NPU di perangkat. Anda dapat menggunakan Compiler AOT (ahead of time) LiteRT untuk mengompilasi model menjadi Paket AI, yang menggabungkan model yang dikompilasi dengan konfigurasi penargetan perangkat. Hal ini memverifikasi bahwa model ditayangkan dengan benar ke perangkat, bergantung pada apakah model tersebut dilengkapi atau dioptimalkan untuk SoC tertentu.

Setelah mengonversi dan mengompilasi model, Anda dapat menggunakan Play untuk AI di Perangkat (PODAI) untuk mengupload model ke Google Play dan mengirimkan model ke perangkat melalui framework AI Sesuai Permintaan.

Gunakan notebook kompilasi AOT LiteRT untuk panduan menyeluruh tentang mengonversi dan mengompilasi model untuk NPU.

[Khusus AOT] Men-deploy dengan Paket AI Play

Setelah mengonversi model dan mengompilasi Paket AI, gunakan langkah-langkah berikut untuk men-deploy Paket AI dengan Google Play.

Mengimpor Paket AI ke project Gradle

Salin 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 library runtime NPU ke project

Download litert_npu_runtime_libraries.zip untuk AOT atau litert_npu_runtime_libraries_jit.zip untuk JIT, 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 dan 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

...

// [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")

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

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

[Khusus AOT] Menggunakan deployment on-demand

Dengan fitur Android AI Pack yang dikonfigurasi dalam file build.gradle.kts, periksa kemampuan perangkat dan gunakan NPU di perangkat yang kompatibel, menggunakan GPU dan CPU sebagai penggantian:

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

Membuat CompiledModel untuk mode JIT

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

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

Inferensi di NPU menggunakan LiteRT di Kotlin

Untuk mulai menggunakan akselerator NPU, teruskan parameter NPU saat membuat Model yang Dikompilasi (CompiledModel).

Cuplikan kode berikut menunjukkan implementasi dasar seluruh proses di 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()

Inferensi di NPU menggunakan LiteRT di C++

Membuat dependensi

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 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 implementasi NPU Tanpa Penyalinan 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);

Caching kompilasi tepat waktu NPU

LiteRT mendukung kompilasi tepat waktu (JIT) NPU untuk model .tflite. 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 menjadi instruksi 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 versus 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, 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 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