شتاب‌دهی NPU با LiteRT

LiteRT یک رابط یکپارچه برای استفاده از واحدهای پردازش عصبی (NPU) فراهم می‌کند، بدون اینکه شما را مجبور به پیمایش کامپایلرها، زمان‌های اجرا یا وابستگی‌های کتابخانه‌ای خاص فروشنده کند. استفاده از LiteRT برای شتاب‌دهی NPU، عملکرد را برای استنتاج بلادرنگ و مدل‌های بزرگ افزایش می‌دهد و کپی‌های حافظه را از طریق استفاده از بافر سخت‌افزاری بدون کپی به حداقل می‌رساند.

شروع کنید

برای شروع، به راهنمای کلی NPU مراجعه کنید:

برای مثال، پیاده‌سازی‌های LiteRT با پشتیبانی از NPU، به برنامه‌های نمایشی زیر مراجعه کنید:

فروشندگان NPU

LiteRT از شتاب‌دهی NPU با فروشندگان زیر پشتیبانی می‌کند:

موتور هوش مصنوعی کوالکام (Qualcomm AI Engine Direct)

  • مسیرهای اجرای کامپایل AOT و On-Device از طریق Compiled Model API پشتیبانی می‌شوند.
  • برای جزئیات تنظیمات، به Qualcomm AI Engine Direct مراجعه کنید.

مدیاتک نوروپایلوت

  • مسیرهای اجرای AOT و JIT از طریق رابط برنامه‌نویسی کاربردی مدل کامپایل‌شده پشتیبانی می‌شوند.
  • برای جزئیات تنظیمات به MediaTek NeuroPilot مراجعه کنید.

تبدیل و کامپایل مدل‌ها برای NPU

برای استفاده از شتاب‌دهی NPU با LiteRT، مدل‌ها باید به فرمت فایل LiteRT تبدیل شده و برای استفاده در NPU روی دستگاه کامپایل شوند. می‌توانید از کامپایلر LiteRT AOT (قبل از زمان) برای کامپایل مدل‌ها در یک بسته هوش مصنوعی استفاده کنید که مدل‌های کامپایل شده شما را با پیکربندی‌های هدف‌گیری دستگاه همراه می‌کند. این امر تأیید می‌کند که مدل‌ها بسته به اینکه آیا برای SoC های خاص مجهز یا بهینه شده‌اند، به درستی به دستگاه‌ها ارائه می‌شوند.

پس از تبدیل و کامپایل مدل‌ها، می‌توانید از Play for On-device AI (PODAI) برای آپلود مدل‌ها در Google Play و ارائه مدل‌ها به دستگاه‌ها از طریق چارچوب On-Demand AI استفاده کنید.

برای یک راهنمای جامع برای تبدیل و کامپایل مدل‌ها برای NPU، از دفترچه کامپایل LiteRT AOT استفاده کنید.

[فقط AOT] با Play AI Pack مستقر شوید

پس از تبدیل مدل و کامپایل کردن یک بسته هوش مصنوعی، از مراحل زیر برای استقرار بسته هوش مصنوعی با Google Play استفاده کنید.

وارد کردن بسته‌های هوش مصنوعی به پروژه Gradle

بسته‌های AI را در دایرکتوری ریشه پروژه Gradle کپی کنید. برای مثال:

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

هر بسته هوش مصنوعی را به پیکربندی Gradle build اضافه کنید:

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

کتابخانه‌های زمان اجرای NPU را به پروژه اضافه کنید

فایل litert_npu_runtime_libraries.zip را برای AOT یا litert_npu_runtime_libraries_jit.zip را برای JIT دانلود کنید و آن را در دایرکتوری ریشه پروژه از حالت فشرده خارج کنید:

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

اسکریپت را اجرا کنید تا کتابخانه‌های پشتیبانی NPU را دانلود کنید. برای مثال، دستور زیر را برای NPUهای کوالکام اجرا کنید:

$ ./litert_npu_runtime_libraries/fetch_qualcomm_library.sh

اضافه کردن بسته‌های هوش مصنوعی و کتابخانه‌های زمان اجرای NPU به پیکربندی 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"))
  ...
}

[فقط AOT] از استقرار بر اساس تقاضا استفاده کنید

با پیکربندی ویژگی Android AI Pack در فایل build.gradle.kts ، قابلیت‌های دستگاه را بررسی کنید و از NPU در دستگاه‌های دارای این قابلیت استفاده کنید و از GPU و CPU به عنوان پشتیبان استفاده کنید:

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

ایجاد CompiledModel برای حالت JIT

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

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

استنتاج روی NPU با استفاده از LiteRT در کاتلین

برای شروع استفاده از شتاب‌دهنده NPU، هنگام ایجاد مدل کامپایل‌شده ( CompiledModel ) پارامتر NPU را ارسال کنید.

قطعه کد زیر پیاده‌سازی اولیه کل فرآیند را در کاتلین نشان می‌دهد:

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

استنتاج روی NPU با استفاده از LiteRT در C++

وابستگی‌ها را بسازید

کاربران ++C باید وابستگی‌های برنامه را با شتاب‌دهی LiteRT NPU بسازند. قانون cc_binary که منطق اصلی برنامه (مثلاً main.cc ) را بسته‌بندی می‌کند، به اجزای زمان اجرای زیر نیاز دارد:

  • کتابخانه مشترک LiteRT C API : ویژگی data باید شامل کتابخانه مشترک LiteRT C API ( //litert/c:litert_runtime_c_api_shared_lib ) و شیء مشترک dispatch مختص فروشنده برای NPU ( //litert/vendors/qualcomm/dispatch:dispatch_api_so ) باشد.
  • کتابخانه‌های بک‌اند مخصوص NPU : برای مثال، کتابخانه‌های Qualcomm AI RT (QAIRT) برای میزبان اندروید (مانند libQnnHtp.so ، libQnnHtpPrepare.so ) و کتابخانه Hexagon DSP مربوطه ( libQnnHtpV79Skel.so ). این تضمین می‌کند که زمان اجرای LiteRT می‌تواند محاسبات را به NPU منتقل کند.
  • وابستگی‌های ویژگی : پیوندهای ویژگی deps به وابستگی‌های ضروری زمان کامپایل، مانند بافر تنسور LiteRT ( //litert/cc:litert_tensor_buffer ) و API برای لایه ارسال NPU ( //litert/vendors/qualcomm/dispatch:dispatch_api ). این امر کد برنامه شما را قادر می‌سازد تا از طریق LiteRT با NPU تعامل داشته باشد.
  • فایل‌های مدل و سایر دارایی‌ها : از طریق ویژگی data گنجانده شده‌اند.

این تنظیمات به باینری کامپایل شده شما اجازه می‌دهد تا به صورت پویا بارگذاری شود و از NPU برای استنتاج یادگیری ماشینی شتاب‌یافته استفاده کند.

راه‌اندازی یک محیط NPU

برخی از بک‌اندهای NPU به وابستگی‌های زمان اجرا یا کتابخانه‌ها نیاز دارند. هنگام استفاده از API مدل کامپایل‌شده، LiteRT این الزامات را از طریق یک شیء Environment سازماندهی می‌کند. از کد زیر برای یافتن کتابخانه‌ها یا درایورهای 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)));

ادغام زمان اجرا

قطعه کد زیر پیاده‌سازی اولیه کل فرآیند را در ++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));

کپی صفر با شتاب NPU

استفاده از کپی صفر، یک NPU را قادر می‌سازد تا بدون نیاز به کپی صریح داده‌ها توسط CPU، مستقیماً به داده‌ها در حافظه خود دسترسی پیدا کند. با عدم کپی کردن داده‌ها به و از حافظه CPU، کپی صفر می‌تواند تأخیر انتها به انتها را به میزان قابل توجهی کاهش دهد.

کد زیر نمونه‌ای از پیاده‌سازی واحد پردازش عصبی بدون کپی (Zero-Copy NPU) با AHardwareBuffer است که داده‌ها را مستقیماً به واحد پردازش عصبی (NPU) ارسال می‌کند. این پیاده‌سازی از رفت و برگشت‌های پرهزینه به حافظه CPU جلوگیری می‌کند و سربار استنتاج را به میزان قابل توجهی کاهش می‌دهد.

// 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 را زنجیره‌ای کنید

برای خطوط لوله پیچیده، می‌توانید چندین استنتاج NPU را به صورت زنجیره‌ای انجام دهید. از آنجایی که هر مرحله از یک بافر سازگار با شتاب‌دهنده استفاده می‌کند، خط لوله شما عمدتاً در حافظه مدیریت‌شده توسط 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);

ذخیره‌سازی همزمان کامپایل در NPU

LiteRT از کامپایل همزمان (JIT) مدل‌های .tflite با NPU پشتیبانی می‌کند. کامپایل JIT می‌تواند به ویژه در موقعیت‌هایی که کامپایل مدل از قبل امکان‌پذیر نیست، مفید باشد.

با این حال، کامپایل JIT می‌تواند با مقداری تأخیر و سربار حافظه برای ترجمه مدل ارائه شده توسط کاربر به دستورالعمل‌های بایت‌کد NPU در صورت تقاضا همراه باشد. برای به حداقل رساندن تأثیر بر عملکرد، می‌توان مصنوعات کامپایل NPU را ذخیره کرد.

وقتی ذخیره‌سازی فعال باشد، LiteRT فقط در صورت نیاز، کامپایل مجدد مدل را آغاز می‌کند، مثلاً:

  • نسخه افزونه کامپایلر NPU فروشنده تغییر کرد؛
  • اثر انگشت ساخت اندروید تغییر کرد؛
  • مدل ارائه شده توسط کاربر تغییر کرد؛
  • گزینه‌های کامپایل تغییر کردند.

برای فعال کردن ذخیره‌سازی تلفیقی NPU، برچسب محیط 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));

مثالی از تأخیر و صرفه‌جویی در حافظه:

زمان و حافظه مورد نیاز برای کامپایل NPU می‌تواند بر اساس عوامل مختلفی مانند تراشه NPU زیربنایی، پیچیدگی مدل ورودی و غیره متفاوت باشد.

جدول زیر زمان اولیه‌سازی اجرا و مصرف حافظه را زمانی که کامپایل NPU مورد نیاز است در مقابل زمانی که می‌توان به دلیل ذخیره‌سازی از کامپایل صرف‌نظر کرد، مقایسه می‌کند. در یک دستگاه نمونه، موارد زیر را به دست می‌آوریم:

مدل TFLite مدل اولیه با کامپایل NPU مدل اولیه با کامپایل ذخیره شده ردپای حافظه اولیه با کامپایل NPU حافظه اولیه با کامپایل ذخیره شده در حافظه پنهان
torchvision_resnet152.tflite ۷۴۶۵.۲۲ میلی‌ثانیه ۱۹۸.۳۴ میلی‌ثانیه ۱۵۲۵.۲۴ مگابایت ۳۵۵.۰۷ مگابایت
torchvision_lraspp_mobilenet_v3_large.tflite ۱۵۹۲.۵۴ میلی‌ثانیه ۱۶۶.۴۷ میلی‌ثانیه ۲۵۴.۹۰ مگابایت ۳۳.۷۸ مگابایت

در دستگاه دیگری موارد زیر را دریافت می‌کنیم:

مدل TFLite مدل اولیه با کامپایل NPU مدل اولیه با کامپایل ذخیره شده ردپای حافظه اولیه با کامپایل NPU حافظه اولیه با کامپایل ذخیره شده در حافظه پنهان
torchvision_resnet152.tflite ۲۷۶۶.۴۴ میلی‌ثانیه ۳۷۹.۸۶ میلی‌ثانیه ۶۵۳.۵۴ مگابایت ۵۰۱.۲۱ مگابایت
torchvision_lraspp_mobilenet_v3_large.tflite ۷۸۴.۱۴ میلی‌ثانیه ۲۳۱.۷۶ میلی‌ثانیه ۱۱۳.۱۴ مگابایت ۶۷.۴۹ مگابایت