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

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

شروع کنید

فروشندگان NPU

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

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

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

گوگل تنسور

کیت توسعه نرم‌افزار گوگل تنسور (Google Tensor SDK) در مرحله آزمایشی است. برای ثبت نام اینجا کلیک کنید.

AOT و کامپایل روی دستگاه

LiteRT NPU از هر دو کامپایل AOT و کامپایل روی دستگاه پشتیبانی می‌کند تا نیازهای خاص استقرار شما را برآورده سازد:

  • کامپایل آفلاین (AOT) : این روش برای مدل‌های بزرگ و پیچیده که SoC هدف مشخص است، مناسب‌تر است. کامپایل از قبل، هزینه‌های اولیه‌سازی را به میزان قابل توجهی کاهش می‌دهد و میزان استفاده از حافظه را هنگام اجرای برنامه توسط کاربر، کاهش می‌دهد.
  • کامپایل آنلاین (روی دستگاه) : همچنین به عنوان کامپایل JIT شناخته می‌شود. این روش برای توزیع مدل مستقل از پلتفرم مدل‌های کوچک ایده‌آل است. مدل در هنگام مقداردهی اولیه روی دستگاه کاربر کامپایل می‌شود و نیازی به مرحله آماده‌سازی اضافی ندارد، اما هزینه اجرای اولیه بالاتری را متحمل می‌شود.

راهنمای زیر نحوه‌ی استقرار برای AOT و کامپایل روی دستگاه را در سه مرحله نشان می‌دهد.

مرحله ۱: کامپایل AOT برای NPU SoC های هدف

شما می‌توانید از کامپایلر LiteRT AOT (ahead of time) برای کامپایل مدل .tflite خود به SoC های پشتیبانی شده استفاده کنید. همچنین می‌توانید چندین فروشنده و نسخه SoC را به طور همزمان در یک فرآیند کامپایل هدف قرار دهید. جزئیات بیشتر را در این دفترچه کامپایل LiteRT AOT ببینید. اگرچه اختیاری است، اما کامپایل AOT برای مدل‌های بزرگتر به شدت توصیه می‌شود تا زمان اولیه‌سازی روی دستگاه را کاهش دهد. این مرحله برای کامپایل روی دستگاه الزامی نیست.

مرحله ۲: اگر روی اندروید هستید، با Google Play مستقر کنید

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

  • برای مدل‌های کامپایل شده روی دستگاه : فایل مدل اصلی .tflite را مستقیماً به پوشه assets/ برنامه خود اضافه کنید.
  • برای مدل‌های کامپایل شده AOT : از LiteRT برای خروجی گرفتن از مدل‌های کامپایل شده خود به یک بسته هوش مصنوعی گوگل پلی استفاده کنید. سپس بسته هوش مصنوعی را در گوگل پلی آپلود می‌کنید تا مدل‌های کامپایل شده صحیح به طور خودکار به دستگاه‌های کاربران تحویل داده شوند.
  • برای کتابخانه‌های زمان اجرای NPU ، از Play Feature Delivery برای توزیع کتابخانه‌های زمان اجرای صحیح در دستگاه‌های کاربران استفاده کنید.

بخش‌های زیر را در مورد نحوه‌ی استقرار با Play AI Pack و Play Feature Delivery مشاهده کنید.

مدل‌های AOT را با Play AI Pack مستقر کنید

مراحل زیر شما را در استقرار مدل‌های کامپایل‌شده AOT خود با استفاده از Play AI Packs راهنمایی می‌کند.

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

با کپی کردن بسته‌های AI به دایرکتوری ریشه پروژه 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

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

device_targeting_configuration.xml را از بسته‌های هوش مصنوعی تولید شده در دایرکتوری ماژول اصلی برنامه کپی کنید. سپس settings.gradle.kts را به‌روزرسانی کنید:

// my_app/setting.gradle.kts

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

به‌روزرسانی 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")
}

پیکربندی بسته هوش مصنوعی برای تحویل در صورت تقاضا

تحویل بر اساس تقاضا به شما امکان می‌دهد مدل را در زمان اجرا درخواست کنید، که در صورتی مفید است که مدل فقط برای جریان‌های کاربری خاصی مورد نیاز باشد. مدل شما در فضای ذخیره‌سازی داخلی برنامه شما دانلود خواهد شد. با پیکربندی ویژگی Android AI Pack در فایل build.gradle.kts ، قابلیت‌های دستگاه را بررسی کنید. همچنین به دستورالعمل‌های تحویل در زمان نصب و تحویل سریع از 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,
)

کتابخانه‌های زمان اجرای NPU را با Play Feature Delivery مستقر کنید

Play Feature Delivery از گزینه‌های تحویل چندگانه برای بهینه‌سازی حجم اولیه دانلود، از جمله تحویل در زمان نصب، تحویل در صورت تقاضا، تحویل مشروط و تحویل فوری، پشتیبانی می‌کند. در اینجا، راهنمای اولیه تحویل در زمان نصب را نشان می‌دهیم.

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

برای کامپایل AOT، فایل litert_npu_runtime_libraries.zip و برای کامپایل روی دستگاه، فایل 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

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

$ ./litert_npu_runtime_libraries/fetch_qualcomm_library.sh

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

device_targeting_configuration.xml را از بسته‌های هوش مصنوعی تولید شده در دایرکتوری ماژول اصلی برنامه کپی کنید. سپس 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")

به‌روزرسانی 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"))
  ...
}

مرحله ۳: استنتاج روی NPU با استفاده از LiteRT Runtime

LiteRT پیچیدگی توسعه در برابر نسخه‌های خاص SoC را حذف می‌کند و به شما امکان می‌دهد مدل خود را تنها با چند خط کد روی NPU اجرا کنید. همچنین یک مکانیسم پشتیبان داخلی و قوی ارائه می‌دهد: می‌توانید CPU، GPU یا هر دو را به عنوان گزینه مشخص کنید و LiteRT در صورت عدم دسترسی NPU به طور خودکار از آنها استفاده می‌کند. به راحتی، کامپایل AOT از پشتیبان نیز پشتیبانی می‌کند. این روش، واگذاری جزئی را در NPU فراهم می‌کند که در آن زیرگراف‌های پشتیبانی نشده به طور یکپارچه روی CPU یا GPU طبق مشخصات مشخص شده اجرا می‌شوند.

اجرا در کاتلین

نمونه پیاده‌سازی را در برنامه‌های نمایشی زیر مشاهده کنید:

اضافه کردن وابستگی‌های اندروید

می‌توانید آخرین بسته LiteRT Maven را به وابستگی‌های build.gradle خود اضافه کنید:

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

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

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

اجرا در سی پلاس پلاس به صورت چند پلتفرمی

مثال پیاده‌سازی را در برنامه‌ی C++ برای تقسیم‌بندی ناهمگام ببینید.

وابستگی‌های Bazel Build

کاربران ++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 از کامپایل مدل‌های .tflite توسط NPU روی دستگاه (معروف به JIT) پشتیبانی می‌کند. کامپایل 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 ۷۸۴.۱۴ میلی‌ثانیه ۲۳۱.۷۶ میلی‌ثانیه ۱۱۳.۱۴ مگابایت ۶۷.۴۹ مگابایت