LiteRT के साथ NPU ऐक्सेलरेट करना

LiteRT, न्यूरल प्रोसेसिंग यूनिट (एनपीयू) का इस्तेमाल करने के लिए एक यूनीफ़ाइड इंटरफ़ेस उपलब्ध कराता है. इसके लिए, आपको वेंडर के हिसाब से कंपाइलर, रनटाइम या लाइब्रेरी की डिपेंडेंसी पर जाने की ज़रूरत नहीं होती. एनपीयू की मदद से LiteRT का इस्तेमाल करने पर, रीयल-टाइम और बड़े मॉडल के इन्फ़रेंस की परफ़ॉर्मेंस बेहतर होती है. साथ ही, ज़ीरो-कॉपी हार्डवेयर बफ़र का इस्तेमाल करके, मेमोरी कॉपी को कम किया जाता है.

शुरू करें

शुरू करने के लिए, एनपीयू के बारे में खास जानकारी देने वाली गाइड देखें:

एनपीयू के साथ काम करने वाले LiteRT को लागू करने के उदाहरणों के लिए, यहां दिए गए डेमो ऐप्लिकेशन देखें:

एनपीयू वेंडर

LiteRT, इन वेंडर के साथ एनपीयू ऐक्सेलरेट करने की सुविधा देता है:

Qualcomm AI Engine Direct

  • Compiled Model API की मदद से, AOT और डिवाइस पर कंपाइल किए गए मॉडल के एक्ज़ीक्यूशन पाथ इस्तेमाल किए जा सकते हैं.
  • सेटअप के बारे में जानकारी के लिए, Qualcomm AI Engine Direct देखें.

MediaTek NeuroPilot

  • Compiled Model API की मदद से, AOT और JIT के एक्ज़ीक्यूशन पाथ इस्तेमाल किए जा सकते हैं.
  • सेटअप की जानकारी के लिए, MediaTek NeuroPilot देखें.

एनपीयू के लिए मॉडल को बदलना और कंपाइल करना

LiteRT के साथ NPU ऐक्सलरेशन का इस्तेमाल करने के लिए, मॉडल को LiteRT फ़ाइल फ़ॉर्मैट में बदलना होगा. साथ ही, डिवाइस पर NPU के इस्तेमाल के लिए कंपाइल करना होगा. मॉडल को एआई पैक में कंपाइल करने के लिए, LiteRTAOT (ऐडवांस) कंपाइलर का इस्तेमाल किया जा सकता है. यह कंपाइलर, आपके कंपाइल किए गए मॉडल को डिवाइस टारगेटिंग कॉन्फ़िगरेशन के साथ बंडल करता है. इससे यह पुष्टि की जाती है कि डिवाइसों को मॉडल सही तरीके से दिखाए जा रहे हैं. यह इस बात पर निर्भर करता है कि वे किसी खास एसओसी के लिए तैयार किए गए हैं या उन्हें ऑप्टिमाइज़ किया गया है.

मॉडल को बदलने और कंपाइल करने के बाद, Play for On-device AI (PODAI) का इस्तेमाल करके, Google Play पर मॉडल अपलोड किए जा सकते हैं. साथ ही, On-Demand AI फ़्रेमवर्क के ज़रिए डिवाइसों पर मॉडल डिलीवर किए जा सकते हैं.

एनपीयू के लिए मॉडल को बदलने और कंपाइल करने के बारे में पूरी जानकारी पाने के लिए, LiteRT AOT कंपाइलेशन नोटबुक का इस्तेमाल करें.

[सिर्फ़ एओटी] Play के एआई पैक के साथ ऐप्लिकेशन को डिप्लॉय करना

मॉडल को बदलने और एआई पैक को कंपाइल करने के बाद, Google Play के साथ एआई पैक को डिप्लॉय करने के लिए, यह तरीका अपनाएं.

Gradle प्रोजेक्ट में एआई पैक इंपोर्ट करना

एआई पैक को Gradle प्रोजेक्ट की रूट डायरेक्ट्री में कॉपी करें. उदाहरण के लिए:

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

हर एआई पैक को 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

प्रोजेक्ट में एनपीयू रनटाइम लाइब्रेरी जोड़ना

AOT के लिए litert_npu_runtime_libraries.zip या JIT के लिए litert_npu_runtime_libraries_jit.zip डाउनलोड करें. इसके बाद, इसे प्रोजेक्ट की रूट डायरेक्ट्री में अनपैक करें:

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

एनपीयू सपोर्ट लाइब्रेरी डाउनलोड करने के लिए, स्क्रिप्ट चलाएं. उदाहरण के लिए, Qualcomm NPUs के लिए यह कमांड चलाएं:

$ ./litert_npu_runtime_libraries/fetch_qualcomm_library.sh

Gradle कॉन्फ़िगरेशन में एआई पैक और एनपीयू रनटाइम लाइब्रेरी जोड़ना

जनरेट किए गए एआई पैक से device_targeting_configuration.xml को मुख्य ऐप्लिकेशन मॉड्यूल की डायरेक्ट्री में कॉपी करें. इसके बाद, 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")

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

[सिर्फ़ एओटी] ऑन-डिमांड डिप्लॉयमेंट का इस्तेमाल करना

build.gradle.kts फ़ाइल में कॉन्फ़िगर की गई Android AI Pack सुविधा की मदद से, डिवाइस की क्षमताओं की जांच करें. साथ ही, ज़रूरी क्षमता वाले डिवाइसों पर एनपीयू का इस्तेमाल करें. इसके लिए, जीपीयू और सीपीयू को फ़ॉलबैक के तौर पर इस्तेमाल करें:

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

JIT मोड के लिए CompiledModel बनाना

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

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

Kotlin में LiteRT का इस्तेमाल करके, NPU पर अनुमान लगाना

एनपीयू ऐक्सलरेटर का इस्तेमाल शुरू करने के लिए, कंपाइल किया गया मॉडल (CompiledModel) बनाते समय एनपीयू पैरामीटर पास करें.

यहां दिए गए कोड स्निपेट में, 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()

C++ में LiteRT का इस्तेमाल करके, एनपीयू पर अनुमान लगाना

डिपेंडेंसी बनाना

C++ का इस्तेमाल करने वाले लोगों को, ऐप्लिकेशन की डिपेंडेंसी को LiteRT NPU ऐक्सलरेशन के साथ बनाना होगा. cc_binary नियम, ऐप्लिकेशन के मुख्य लॉजिक को पैकेज करता है. उदाहरण के लिए, main.cc) को रनटाइम के इन कॉम्पोनेंट की ज़रूरत होती है:

  • LiteRT C API की शेयर की गई लाइब्रेरी: data एट्रिब्यूट में, LiteRT C API की शेयर की गई लाइब्रेरी (//litert/c:litert_runtime_c_api_shared_lib) और एनपीयू के लिए वेंडर के हिसाब से डिस्पैच किया गया शेयर किया गया ऑब्जेक्ट (//litert/vendors/qualcomm/dispatch:dispatch_api_so) शामिल होना चाहिए.
  • एनपीयू के लिए खास तौर पर बनाई गई बैकएंड लाइब्रेरी: उदाहरण के लिए, Android होस्ट (जैसे कि libQnnHtp.so, libQnnHtpPrepare.so) के लिए Qualcomm AI RT (QAIRT) लाइब्रेरी और इससे जुड़ी Hexagon DSP लाइब्रेरी (libQnnHtpV79Skel.so). इससे यह पक्का होता है कि LiteRT रनटाइम, कंप्यूटेशन को एनपीयू पर ऑफ़लोड कर सकता है.
  • एट्रिब्यूट की डिपेंडेंसी: deps एट्रिब्यूट, कंपाइल-टाइम की ज़रूरी डिपेंडेंसी से लिंक होता है. जैसे, LiteRT का टेंसर बफ़र (//litert/cc:litert_tensor_buffer) और एनपीयू डिस्पैच लेयर (//litert/vendors/qualcomm/dispatch:dispatch_api) के लिए एपीआई. इससे आपका ऐप्लिकेशन कोड, LiteRT के ज़रिए एनपीयू के साथ इंटरैक्ट कर पाता है.
  • मॉडल फ़ाइलें और अन्य ऐसेट: इन्हें data एट्रिब्यूट के ज़रिए शामिल किया जाता है.

इस सेटअप की मदद से, कंपाइल किए गए बाइनरी को डाइनैमिक तरीके से लोड किया जा सकता है. साथ ही, मशीन लर्निंग के अनुमान को बेहतर बनाने के लिए, NPU का इस्तेमाल किया जा सकता है.

एनपीयू एनवायरमेंट सेट अप करना

कुछ एनपीयू बैकएंड के लिए, रनटाइम डिपेंडेंसी या लाइब्रेरी की ज़रूरत होती है. compiled model API का इस्तेमाल करते समय, LiteRT इन ज़रूरी शर्तों को Environment ऑब्जेक्ट के ज़रिए व्यवस्थित करता है. सही एनपीयू लाइब्रेरी या ड्राइवर ढूंढने के लिए, इस कोड का इस्तेमाल करें:

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

रनटाइम इंटिग्रेशन

नीचे दिया गया कोड स्निपेट, 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));

एनपीयू की मदद से, बिना कॉपी किए डेटा को तेज़ी से प्रोसेस करना

ज़ेरो-कॉपी का इस्तेमाल करने पर, एनपीयू अपने डेटा को सीधे तौर पर अपनी मेमोरी में ऐक्सेस कर सकता है. इसके लिए, सीपीयू को उस डेटा को साफ़ तौर पर कॉपी करने की ज़रूरत नहीं होती. डेटा को सीपीयू मेमोरी में कॉपी न करने की वजह से, ज़ीरो-कॉपी से एंड-टू-एंड लेटेन्सी को काफ़ी हद तक कम किया जा सकता है.

यहां दिए गए कोड में, AHardwareBuffer के साथ ज़ीरो-कॉपी एनपीयू को लागू करने का उदाहरण दिया गया है. इसमें डेटा को सीधे एनपीयू को पास किया जाता है. इस तरीके से, सीपीयू मेमोरी में महंगे राउंड-ट्रिप से बचा जा सकता है. इससे अनुमान लगाने में लगने वाला समय काफ़ी कम हो जाता है.

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

एक से ज़्यादा एनपीयू इन्फ़रेंस को चेन करना

ज़्यादा जटिल पाइपलाइन के लिए, एक से ज़्यादा एनपीयू इन्फ़रेंस को एक साथ जोड़ा जा सकता है. हर चरण में, ऐक्सलरेटर के साथ काम करने वाला बफ़र इस्तेमाल किया जाता है. इसलिए, आपकी पाइपलाइन ज़्यादातर 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);

एनपीयू के लिए, ज़रूरत के हिसाब से कंपाइल करने की सुविधा के लिए कैश मेमोरी

LiteRT, .tflite मॉडल के एनपीयू जस्ट-इन-टाइम (JIT) कंपाइलेशन के साथ काम करता है. JIT कंपाइलेशन, खास तौर पर उन स्थितियों में फ़ायदेमंद हो सकता है जहां मॉडल को पहले से कंपाइल करना मुमकिन नहीं होता.

हालांकि, जेआईटी कंपाइलेशन में कुछ समय लग सकता है. साथ ही, उपयोगकर्ता के दिए गए मॉडल को ज़रूरत के हिसाब से एनपीयू बाइटकोड निर्देशों में बदलने के लिए, मेमोरी ओवरहेड भी हो सकता है. एनपीयू कंपाइलेशन आर्टफ़ैक्ट को कैश मेमोरी में सेव किया जा सकता है, ताकि परफ़ॉर्मेंस पर कम से कम असर पड़े.

कैशिंग की सुविधा चालू होने पर, LiteRT सिर्फ़ तब मॉडल को फिर से कंपाइल करेगा, जब इसकी ज़रूरत होगी. जैसे:

  • वेंडर के NPU कंपाइलर प्लगिन का वर्शन बदल गया हो;
  • Android बिल्ड फ़िंगरप्रिंट बदल गया है;
  • उपयोगकर्ता ने मॉडल बदल दिया है;
  • कंपाइल करने के विकल्प बदल गए हैं.

एनपीयू कंपाइलेशन की कैश मेमोरी को चालू करने के लिए, एनवायरमेंट के विकल्पों में CompilerCacheDir एनवायरमेंट टैग डालें. वैल्यू को ऐप्लिकेशन के ऐसे पाथ पर सेट किया जाना चाहिए जिसमें बदलाव किया जा सकता हो.

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

विलंबता और मेमोरी सेविंग का उदाहरण:

एनपीयू कंपाइलेशन के लिए ज़रूरी समय और मेमोरी, कई बातों पर निर्भर करती है. जैसे, एनपीयू चिप, इनपुट मॉडल की जटिलता वगैरह.

नीचे दी गई टेबल में, रनटाइम के दौरान शुरू होने में लगने वाले समय और मेमोरी की खपत की तुलना की गई है. इसमें यह बताया गया है कि एनपीयू कंपाइलेशन कब ज़रूरी होता है और कब कैश मेमोरी की वजह से कंपाइलेशन को स्किप किया जा सकता है. हमें एक सैंपल डिवाइस पर यह जानकारी मिली:

TFLite मॉडल एनपीयू कंपाइलेशन के साथ मॉडल शुरू करना कैश किए गए कंपाइलेशन के साथ मॉडल को शुरू करना एनपीयू कंपाइलेशन के साथ मेमोरी फ़ुटप्रिंट शुरू करें कैश किए गए कंपाइलेशन के साथ मेमोरी शुरू करें
torchvision_resnet152.tflite 7465.22 मि॰से॰ 198.34 मि॰से॰ 1525.24 एमबी 355.07 एमबी
torchvision_lraspp_mobilenet_v3_large.tflite 1592.54 मि॰से॰ 166.47 मि॰से॰ 254.90 एमबी 33.78 एमबी

हम किसी दूसरे डिवाइस पर यह जानकारी इकट्ठा करते हैं:

TFLite मॉडल एनपीयू कंपाइलेशन के साथ मॉडल शुरू करना कैश किए गए कंपाइलेशन के साथ मॉडल को शुरू करना एनपीयू कंपाइलेशन के साथ मेमोरी फ़ुटप्रिंट शुरू करें कैश किए गए कंपाइलेशन के साथ मेमोरी शुरू करें
torchvision_resnet152.tflite 2766.44 मि॰से॰ 379.86 मि॰से॰ 653.54 एमबी 501.21 एमबी
torchvision_lraspp_mobilenet_v3_large.tflite 784.14 मि॰से॰ 231.76 मि॰से॰ 113.14 एमबी 67.49 एमबी