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

Për të filluar, shihni udhëzuesin e përgjithshëm të NPU-së:

Për shembull, implementimet e LiteRT me mbështetje për NPU, referojuni aplikacioneve demo të mëposhtme:

Shitësit e NPU-së

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

Qualcomm AI Engine Direct

  • Shtigjet e ekzekutimit të kompilimit AOT dhe On-Device mbështeten përmes API-t të Modelit të Kompiluar.
  • Shihni Qualcomm AI Engine Direct për detajet e konfigurimit.

MediaTek NeuroPilot

  • Shtigjet e ekzekutimit AOT dhe JIT mbështeten përmes API-t të Modelit të Kompiluar.
  • Shihni MediaTek NeuroPilot për detajet e konfigurimit.

Konvertoni dhe kompiloni modele për NPU

Për të përdorur përshpejtimin e NPU me LiteRT, modelet duhet të konvertohen në formatin e skedarit LiteRT dhe të kompilohen për përdorim të NPU në pajisje. Ju mund të përdorni Kompiluesin LiteRT AOT (para kohe) për të kompiluar modelet në një Paketë AI, e cila i bashkon modelet tuaja të kompiluara me konfigurime të synuara për pajisjen. Kjo verifikon që modelet u shërbehen saktë pajisjeve në varësi të faktit nëse ato janë të pajisura ose të optimizuara për SoC të caktuara.

Pas konvertimit dhe kompilimit të modeleve, mund të përdorni Play for On-device AI (PODAI) për të ngarkuar modele në Google Play dhe për të dërguar modele në pajisje përmes kornizës On-Demand AI.

Përdorni fletoren e përpilimit LiteRT AOT për një udhëzues të plotë për konvertimin dhe përpilimin e modeleve për NPU.

[Vetëm AOT] Vendose me Play AI Pack

Pas konvertimit të modelit dhe përpilimit të një Paketë AI, përdorni hapat e mëposhtëm për të vendosur Paketën AI me Google Play.

Importoni Paketat e IA-së në projektin Gradle

Kopjoni paketën(et) 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 bibliotekat e kohës së ekzekutimit NPU në projekt

Shkarkoni litert_npu_runtime_libraries.zip për AOT ose litert_npu_runtime_libraries_jit.zip për JIT 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 Paketat AI dhe bibliotekat e kohës së ekzekutimit NPU 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

...

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

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

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

[Vetëm AOT] Përdorni vendosjen sipas kërkesës

Me veçorinë Android AI Pack të konfiguruar në skedarin build.gradle.kts , kontrolloni aftësitë e pajisjes dhe përdorni NPU në pajisjet e afta, duke përdorur GPU-në dhe CPU-në si rezervë:

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

Krijo CompiledModel për modalitetin JIT

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

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

Konkluzioni mbi NPU-në duke përdorur LiteRT në Kotlin

Për të filluar përdorimin e përshpejtuesit NPU, kaloni parametrin NPU kur krijoni Modelin e Kompiluar ( CompiledModel ).

Fragmenti i mëposhtëm i kodit tregon një implementim bazë të të gjithë procesit në 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()

Konkluzioni mbi NPU-në duke përdorur LiteRT në C++

Ndërtoni varësi

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ë memorje e përpilimit në kohë të NPU-së

LiteRT mbështet kompilimin JIT (just-in-time) të modeleve .tflite nga NPU. 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