Përshpejtimi i NPU-së me LiteRT

LiteRT ofron një ndërfaqe të unifikuar për të përdorur Njësitë e Përpunimit Neural (NPU) pa ju kërkuar të lundroni nëpër kompilues, kohë ekzekutimi ose varësi të bibliotekave specifike të shitësit. Përdorimi i LiteRT për përshpejtimin e NPU rrit performancën për inferencën në kohë reale dhe të modelit të madh dhe minimizon kopjet e memories përmes përdorimit të buffer-it të harduerit me kopje zero.

Filloni

Shitësit e NPU-së

LiteRT mbështet përshpejtimin e NPU me shitësit e mëposhtëm:

Qualcomm AI Engine Direct

MediaTek NeuroPilot

Tensor i Google-it

Google Tensor SDK është në qasje eksperimentale. Regjistrohuni këtu .

AOT dhe përpilimi në pajisje

LiteRT NPU mbështet si AOT ashtu edhe kompilimin në pajisje për të përmbushur kërkesat tuaja specifike të vendosjes:

  • Kompilimi jashtë linje (AOT) : Ky është më i përshtatshmi për modele të mëdha dhe komplekse ku dihet SoC-ja e synuar. Kompilimi paraprak ul ndjeshëm kostot e inicializimit dhe përdorimin e memories kur përdoruesi hap aplikacionin tuaj.
  • Kompilimi online (në pajisje) : I njohur edhe si kompilimi JIT. Ky është ideal për shpërndarjen e modeleve të vogla që nuk varen nga platforma. Modeli kompilohet në pajisjen e përdoruesit gjatë inicializimit, duke mos kërkuar asnjë hap shtesë përgatitjeje, por duke shkaktuar një kosto më të lartë të ekzekutimit të parë.

Udhëzuesi i mëposhtëm tregon se si të shpërndahet si për AOT ashtu edhe për kompilimin në pajisje në tre hapa.

Hapi 1: Përpilimi i AOT për SoC-të e synuara të NPU-së

Mund të përdorni Kompiluesin LiteRT AOT (para kohe) për të kompiluar modelin tuaj .tflite në SoC-të e mbështetura. Gjithashtu mund të synoni shitës dhe versione të shumëfishta SoC njëkohësisht brenda një procesi të vetëm kompilimi. Shihni më shumë detaje në këtë fletore të Kompilimit LiteRT AOT . Ndërsa është opsionale, kompilimi AOT rekomandohet shumë për modelet më të mëdha për të zvogëluar kohën e inicializimit në pajisje. Ky hap nuk kërkohet për kompilimin në pajisje.

Hapi 2: Vendose me Google Play nëse je në Android

Në Android, përdorni Google Play për IA në pajisje (PODAI) për të vendosur modelin dhe bibliotekat e kohës së ekzekutimit të NPU me aplikacionin tuaj.

Shihni seksionet e mëposhtme rreth mënyrës së shpërndarjes me Play AI Pack dhe Play Feature Delivery .

Vendosni modelet AOT me Play AI Pack

Hapat e mëposhtëm ju udhëzojnë përmes vendosjes së modeleve tuaja të kompiluara AOT duke përdorur Play AI Packs.

Shtoni AI Pack në projekt

Importoni Paketat AI në projektin Gradle duke kopjuar Paketat AI në direktorinë rrënjë të projektit Gradle. Për shembull:

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

Shtoni çdo Pako AI në konfigurimin e ndërtimit të 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

Shtoni Paketat e AI në konfigurimin e Gradle

Kopjoni device_targeting_configuration.xml nga Paketat AI të gjeneruara në direktorinë e modulit kryesor të aplikacionit. Pastaj përditësoni settings.gradle.kts :

// my_app/setting.gradle.kts

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

Përditëso 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")
}

Konfiguro AI Pack për dërgesë sipas kërkesës

Dorëzimi sipas kërkesës ju lejon të kërkoni modelin në kohën e ekzekutimit, gjë që është e dobishme nëse modeli kërkohet vetëm për rrjedha të caktuara të përdoruesit. Modeli juaj do të shkarkohet në hapësirën e brendshme të ruajtjes së aplikacionit tuaj. Me veçorinë Android AI Pack të konfiguruar në skedarin build.gradle.kts , kontrolloni aftësitë e pajisjes. Shihni gjithashtu udhëzimet për dorëzimin në kohën e instalimit dhe dorëzimin me ndjekje të shpejtë nga 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,
)

Vendosni bibliotekat e kohës së ekzekutimit të NPU-së me Play Feature Delivery

Shpërndarja e Funksioneve të Luajtura mbështet opsione të shumta shpërndarjeje për të optimizuar madhësinë fillestare të shkarkimit, duke përfshirë shpërndarjen në kohën e instalimit, shpërndarjen sipas kërkesës, shpërndarjen me kusht dhe shpërndarjen e menjëhershme. Këtu, ne tregojmë udhëzuesin bazë të shpërndarjes në kohën e instalimit.

Shtoni bibliotekat e kohës së ekzekutimit NPU në projekt

Shkarkoni littert_npu_runtime_libraries.zip për kompilimin AOT ose littert_npu_runtime_libraries_jit.zip për kompilimin në pajisje dhe çpaketojeni atë në direktorinë rrënjë të projektit:

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

Ekzekutoni skriptin për të shkarkuar bibliotekat e mbështetjes së NPU-ve. Për shembull, ekzekutoni sa vijon për NPU-të e Qualcomm:

$ ./litert_npu_runtime_libraries/fetch_qualcomm_library.sh

Shtoni bibliotekat e kohës së ekzekutimit të NPU-së në konfigurimin e Gradle

Kopjoni device_targeting_configuration.xml nga Paketat AI të gjeneruara në direktorinë e modulit kryesor të aplikacionit. Pastaj përditësoni 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")

Përditëso 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"))
  ...
}

Hapi 3: Nxjerrja e përfundimeve mbi NPU-në duke përdorur LiteRT Runtime

LiteRT eliminon kompleksitetin e zhvillimit kundrejt versioneve specifike të SoC, duke ju lejuar të ekzekutoni modelin tuaj në NPU me vetëm disa rreshta kodi. Ai gjithashtu ofron një mekanizëm rezervë të fuqishëm dhe të integruar: ju mund të specifikoni CPU, GPU ose të dyja si opsione, dhe LiteRT do t'i përdorë automatikisht ato nëse NPU nuk është e disponueshme. Përshtatshëm, përpilimi AOT gjithashtu mbështet rezervën. Ai ofron delegim të pjesshëm në NPU ku nëngrafet e pambështetura funksionojnë pa probleme në CPU ose GPU siç specifikohet.

Vraponi në Kotlin

Shihni shembullin e implementimit në aplikacionet demo të mëposhtme:

Shto varësitë e Android

Mund të shtoni paketën më të fundit LiteRT Maven në varësitë tuaja build.gradle:

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

Integrimi në kohën e ekzekutimit

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

Ekzekutoni në C++ ndërplatformë

Shih shembullin e implementimit në aplikacionin C++ të segmentimit asinkron .

Varësitë e Bazel Build

Përdoruesit e C++ duhet të ndërtojnë varësitë e aplikacionit me përshpejtimin LiteRT NPU. Rregulli cc_binary që paketon logjikën thelbësore të aplikacionit (p.sh., main.cc ) kërkon komponentët e mëposhtëm të kohës së ekzekutimit:

  • Biblioteka e përbashkët e LiteRT C API : atributi i data duhet të përfshijë bibliotekën e përbashkët të LiteRT C API ( //litert/c:litert_runtime_c_api_shared_lib ) dhe objektin e përbashkët të dërgimit specifik të shitësit për NPU-në ( //litert/vendors/qualcomm/dispatch:dispatch_api_so ).
  • Bibliotekat e backend-it specifik për NPU-në : Për shembull, bibliotekat Qualcomm AI RT (QAIRT) për hostin Android (si libQnnHtp.so , libQnnHtpPrepare.so ) dhe biblioteka përkatëse Hexagon DSP ( libQnnHtpV79Skel.so ). Kjo siguron që koha e ekzekutimit LiteRT mund t'i transferojë llogaritjet në NPU.
  • Varësitë e atributeve : lidhjet e atributeve deps kundrejt varësive thelbësore të kohës së kompajlimit, siç është buferi tensor i LiteRT ( //litert/cc:litert_tensor_buffer ) dhe API për shtresën e shpërndarjes së NPU ( //litert/vendors/qualcomm/dispatch:dispatch_api ). Kjo i mundëson kodit të aplikacionit tuaj të bashkëveprojë me NPU-në përmes LiteRT.
  • Skedarët e modelit dhe asete të tjera : Përfshihen përmes atributit të data .

Ky konfigurim lejon që skedari juaj binar i kompiluar të ngarkohet në mënyrë dinamike dhe të përdorë NPU-në për inferencë të përshpejtuar të të mësuarit automatik.

Konfiguro një mjedis NPU

Disa backend-e NPU kërkojnë varësi ose biblioteka në kohën e ekzekutimit. Kur përdoret API-ja e modelit të kompiluar, LiteRT i organizon këto kërkesa përmes një objekti Environment . Përdorni kodin e mëposhtëm për të gjetur bibliotekat ose drajverët e duhur 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)));

Integrimi në kohën e ekzekutimit

Fragmenti i mëposhtëm i kodit tregon një implementim bazë të të gjithë procesit në 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-kopje me përshpejtim NPU

Përdorimi i kopjes zero i mundëson një NPU-je të hyjë në të dhëna direkt në memorien e vet pa pasur nevojë që CPU-ja t'i kopjojë ato të dhëna në mënyrë të qartë. Duke mos kopjuar të dhëna nga dhe në memorien e CPU-së, kopja zero mund ta zvogëlojë ndjeshëm vonesën nga fillimi në fund.

Kodi i mëposhtëm është një shembull i implementimit të NPU-së Zero-Copy me AHardwareBuffer , duke kaluar të dhënat direkt te NPU-ja. Ky implementim shmang udhëtimet e kushtueshme vajtje-ardhje në memorien e CPU-së, duke ulur ndjeshëm mbingarkesën e inferencës.

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

Konkluzionet e shumëfishta të NPU-së në zinxhir

Për tubacione komplekse, mund të lidhni zinxhir disa përfundime të NPU-së. Meqenëse çdo hap përdor një buffer të përshtatshëm për përshpejtuesin, tubacioni juaj qëndron kryesisht në memorien e menaxhuar nga 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);

Ruajtja në memorien e përpilimit të NPU-së në pajisje

LiteRT mbështet kompilimin NPU në pajisje (i njohur si JIT) të modeleve .tflite . Kompilimi JIT mund të jetë veçanërisht i dobishëm në situata kur kompilimi i modelit paraprakisht nuk është i realizueshëm.

Megjithatë, kompilimi JIT mund të ketë disa vonesa dhe mbingarkesë memorieje për të përkthyer modelin e ofruar nga përdoruesi në udhëzime bajtkodi NPU sipas kërkesës. Për të minimizuar ndikimin në performancë, artefaktet e kompilimit NPU mund të ruhen në memorien specifike.

Kur ruajtja në memorje është e aktivizuar, LiteRT do të aktivizojë rikompilimin e modelit vetëm kur është e nevojshme, p.sh.:

  • Versioni i plugin-it të kompiluesit NPU të shitësit ndryshoi;
  • Gjurmët e gishtave të versionit të Android ndryshuan;
  • Modeli i ofruar nga përdoruesi ndryshoi;
  • Opsionet e kompilimit ndryshuan.

Për të aktivizuar ruajtjen në memorje të kompilimit NPU, specifikoni etiketën e mjedisit CompilerCacheDir në opsionet e mjedisit. Vlera duhet të caktohet në një shteg ekzistues të shkrueshëm të aplikacionit.

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

Shembull i latencës dhe kursimit të kujtesës:

Koha dhe memoria e nevojshme për përpilimin e NPU-së mund të ndryshojnë në bazë të disa faktorëve, si çipi themelor i NPU-së, kompleksiteti i modelit të hyrjes etj.

Tabela e mëposhtme krahason kohën e inicializimit në kohën e ekzekutimit dhe konsumin e memories kur kërkohet kompilimi i NPU-së kundrejt kur kompilimi mund të anashkalohet për shkak të ruajtjes në memorje. Në një pajisje shembullore marrim sa vijon:

Modeli TFLite inicializimi i modelit me kompilimin NPU inicialet e modelit me kompilim të ruajtur në memorje gjurmë memorieje init me kompilim NPU memorie init me kompilim të ruajtur në memorje
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

Në një pajisje tjetër marrim sa vijon:

Modeli TFLite inicializimi i modelit me kompilimin NPU inicialet e modelit me kompilim të ruajtur në memorje gjurmë memorieje init me kompilim NPU memorie init me kompilim të ruajtur në memorje
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