میانای برنامه‌سازی کاربردی شبکه‌های عصبی

Android Neural Networks API (NNAPI)‎ یک Android C API است که برای اجرای عملیات محاسباتی فشرده برای یادگیری ماشین در دستگاه‌های Android طراحی شده است. NNAPI برای ارائه لایه پایه عملکرد برای چارچوب‌های یادگیری ماشین سطح بالاتر، مانند TensorFlow Lite و Caffe2، که شبکه‌های عصبی را می‌سازند و آموزش می‌دهند، طراحی شده است. این میانای برنامه‌سازی کاربردی در همه دستگاه‌های Android دارای Android 8.1 (سطح میانای برنامه‌سازی کاربردی ۲۷) یا بالاتر دردسترس است، اما در Android 15 منسوخ شده است.

NNAPI با اعمال داده‌های دستگاه‌های Android بر مدل‌های ازپیش آموزش‌دیده و تعریف‌شده توسط توسعه‌دهنده، از استنتاج پشتیبانی می‌کند. نمونه‌هایی از استنتاج شامل طبقه‌بندی تصاویر، پیش‌بینی رفتار کاربر، و انتخاب پاسخ‌های مناسب برای پُرسمان جستجو است.

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

  • تأخیر: نیازی نیست درخواست را ازطریق اتصال شبکه ارسال کنید و منتظر پاسخ بمانید. برای مثال، این موضوع می‌تواند برای برنامه‌های ویدیویی که فریم‌های متوالی دریافتی از دوربین را پردازش می‌کنند بسیار مهم باشد.
  • دردسترس بودن: برنامه حتی زمانی که خارج از پوشش شبکه هستید اجرا می‌شود.
  • سرعت: سخت‌افزار جدیدی که مختص پردازش شبکه عصبی است، محاسبات را به‌طور قابل‌توجهی سریع‌تر از واحد پردازش مرکزی چندمنظوره به‌تنهایی انجام می‌دهد.
  • حریم خصوصی: داده‌ها از دستگاه Android خارج نمی‌شود.
  • هزینه: وقتی همه محاسبات در دستگاه Android انجام می‌شود، به مزرعه سرور نیازی نیست.

همچنین معاوضه‌هایی وجود دارد که توسعه‌دهنده باید درنظر داشته باشد:

  • استفاده از سیستم: ارزیابی شبکه‌های عصبی شامل محاسبات زیادی است که می‌تواند مصرف باتری را افزایش دهد. اگر این موضوع برای برنامه‌تان مهم است، به‌ویژه برای محاسبات طولانی‌مدت، باید پایش سلامت باتری را درنظر بگیرید.
  • اندازه برنامه: به اندازه مدل‌هایتان توجه کنید. مدل‌ها ممکن است چند مگابایت فضا اشغال کنند. اگر بسته‌بندی مدل‌های بزرگ در APK شما تأثیر نامطلوبی بر کاربران شما داشته باشد، ممکن است بخواهید بارگیری مدل‌ها را پس‌از نصب برنامه، استفاده از مدل‌های کوچک‌تر، یا اجرای محاسبات خود در فضای ابری درنظر بگیرید. NNAPI عملکردی برای اجرای مدل‌ها در فضای ابری ارائه نمی‌دهد.

برای دیدن نمونه‌ای از نحوه استفاده از NNAPI، نمونه Android Neural Networks API را ببینید.

آشنایی با زمان اجرای Neural Networks API

NNAPI برای فراخوانی توسط کتابخانه‌ها، چارچوب‌ها، و ابزارهای یادگیری ماشین طراحی شده است که به توسعه‌دهندگان امکان می‌دهد مدل‌های خود را خارج از دستگاه آموزش دهند و آن‌ها را در دستگاه‌های Android مستقر کنند. برنامه‌ها معمولاً مستقیماً از NNAPI استفاده نمی‌کنند، بلکه درعوض از چارچوب‌های یادگیری ماشین سطح بالاتر استفاده می‌کنند. این چارچوب‌ها به‌نوبه خود می‌توانند از NNAPI برای انجام عملیات استنتاج با شتاب سخت‌افزاری در دستگاه‌های پشتیبانی‌شده استفاده کنند.

براساس الزامات برنامه و قابلیت‌های سخت‌افزاری در دستگاه Android، زمان اجرای شبکه عصبی Android می‌تواند به‌طور کارآمدی بار کاری محاسباتی را در پردازنده‌های درون‌دستگاهی دردسترس، ازجمله سخت‌افزار شبکه عصبی اختصاصی، واحدهای پردازش گرافیکی (GPU)، و پردازشگرهای سیگنال دیجیتال (DSP) توزیع کند.

برای دستگاه‌های Android که فاقد درایور فروشنده تخصصی هستند، زمان اجرای NNAPI درخواست‌ها را در CPU اجرا می‌کند.

شکل ۱ معماری سیستم سطح بالا را برای NNAPI نشان می‌دهد.

شکل ۱. معماری سیستم برای میانای برنامه‌سازی کاربردی شبکه‌های عصبی Android

مدل برنامه‌نویسی Neural Networks API

برای انجام محاسبات بااستفاده از NNAPI، ابتدا باید یک گراف جهت‌دار بسازید که محاسبات موردنظر را برای اجرا تعریف کند. این نمودار محاسباتی، در ترکیب با داده‌های ورودی شما (برای مثال، وزن‌ها و سوگیری‌های منتقل‌شده از چارچوب یادگیری ماشین)، مدل را برای ارزیابی زمان اجرای NNAPI تشکیل می‌دهد.

NNAPI از چهار انتزاع اصلی استفاده می‌کند:

  • مدل: نمودار محاسباتی عملیات ریاضی و مقادیر ثابتی که ازطریق فرایند آموزش یاد گرفته شده‌اند. این عملیات‌ها مختص شبکه‌های عصبی است. این عملیات‌ها شامل پیچش دوبعدی (2D)، فعال‌سازی لجستیک (سیگموئید)، فعال‌سازی خطی اصلاح‌شده (ReLU)، و غیره می‌شود. ایجاد مدل عملیاتی همگام است. پس‌از ایجاد موفقیت‌آمیز، می‌توان از آن در رشته‌ها و مجموعه‌ها مجدداً استفاده کرد. در NNAPI، مدل به‌عنوان نمونه‌ای از ANeuralNetworksModel نمایش داده می‌شود.
  • Compilation: پیکربندی‌ای را برای کامپایل کردن مدل NNAPI به کد سطح پایین‌تر نشان می‌دهد. ایجاد مجموعه عملیاتی همگام است. پس‌از ایجاد موفقیت‌آمیز، می‌توان از آن در رشته‌ها و اجراهای مختلف استفاده مجدد کرد. در NNAPI، هر کامپایل به‌عنوان یک نمونه ANeuralNetworksCompilation نمایش داده می‌شود.
  • حافظه: نشان‌دهنده حافظه مشترک، فایل‌های نگاشت‌شده حافظه، و میان‌گیرهای حافظه مشابه است. استفاده از میان‌گیر حافظه به زمان اجرای NNAPI اجازه می‌دهد داده‌ها را به‌طور کارآمدتری به درایورها منتقل کند. برنامه معمولاً یک میان‌گیر حافظه مشترک ایجاد می‌کند که حاوی همه تانسورهای لازم برای تعریف مدل است. همچنین می‌توانید از میان‌گیرهای حافظه برای ذخیره کردن ورودی‌ها و خروجی‌های نمونه اجرا استفاده کنید. در NNAPI، هر میان‌گیر حافظه به‌عنوان یک نمونه ANeuralNetworksMemory نمایش داده می‌شود.
  • اجرا: میانایی برای اعمال مدل NNAPI به مجموعه‌ای از ورودی‌ها و جمع‌آوری نتایج. اجرا می‌تواند به‌صورت هم‌زمان یا ناهم‌زمان انجام شود.

    برای اجرای ناهمگام، چندین رشته می‌توانند در یک اجرا منتظر بمانند. وقتی این اجرا تکمیل شود، همه رشته‌ها منتشر می‌شوند.

    در NNAPI، هر اجرا به‌عنوان یک نمونه ANeuralNetworksExecution نمایش داده می‌شود.

شکل ۲ جریان برنامه‌نویسی پایه را نشان می‌دهد.

شکل ۲. جریان برنامه‌نویسی برای Android Neural Networks API

بقیه این بخش مراحل راه‌اندازی مدل NNAPI شما را برای انجام محاسبات، کامپایل کردن مدل، و اجرای مدل کامپایل‌شده شرح می‌دهد.

ارائه دسترسی به داده‌های آموزشی

داده‌های وزن‌ها و سوگیری‌های آموزش‌دیده شما احتمالاً در یک فایل ذخیره شده‌اند. برای ارائه زمان اجرای NNAPI با دسترسی کارآمد به این داده‌ها، یک نمونه ANeuralNetworksMemory با فراخوانی تابع ANeuralNetworksMemory_createFromFd() و ارسال توصیف‌گر فایل فایل داده بازشده ایجاد کنید. همچنین پرچم‌های محافظت از حافظه و افستی را که منطقه حافظه مشترک در فایل شروع می‌شود مشخص می‌کنید.

// Create a memory buffer from the file that contains the trained data
ANeuralNetworksMemory* mem1 = NULL;
int fd = open("training_data", O_RDONLY);
ANeuralNetworksMemory_createFromFd(file_size, PROT_READ, fd, 0, &mem1);

اگرچه در این مثال ما فقط از یک نمونه ANeuralNetworksMemory برای همه وزن‌هایمان استفاده می‌کنیم، اما می‌توان از بیش‌از یک نمونه ANeuralNetworksMemory برای چند فایل استفاده کرد.

استفاده از میان‌گیرهای سخت‌افزاری بومی

می‌توانید از میانگیرهای سخت‌افزاری بومی برای ورودی‌ها، خروجی‌ها، و مقادیر عملوند ثابت مدل استفاده کنید. در برخی موارد، شتاب‌دهنده NNAPI می‌تواند بدون نیاز به کپی کردن داده‌ها توسط درایور، به اشیاء AHardwareBuffer دسترسی پیدا کند. AHardwareBuffer پیکربندی‌های مختلف زیادی دارد و ممکن است همه شتاب‌دهنده‌های NNAPI از همه این پیکربندی‌ها پشتیبانی نکنند. به‌دلیل این محدودیت، به محدودیت‌های فهرست‌شده در سند مرجع ANeuralNetworksMemory_createFromAHardwareBuffer مراجعه کنید و پیش‌از موعد در دستگاه‌های هدف آزمایش کنید تا مطمئن شوید کامپایل‌ها و اجراهایی که از AHardwareBuffer استفاده می‌کنند، همان‌طور که انتظار می‌رود رفتار می‌کنند، و از اختصاص دستگاه برای مشخص کردن شتاب‌دهنده استفاده کنید.

برای اینکه زمان اجرای NNAPI بتواند به شیء AHardwareBuffer دسترسی پیدا کند، با فراخوانی تابع ANeuralNetworksMemory_createFromAHardwareBuffer و ارسال شیء AHardwareBuffer، نمونه ANeuralNetworksMemory ایجاد کنید، همان‌طور که در نمونه کد زیر نشان داده شده است:

// Configure and create AHardwareBuffer object
AHardwareBuffer_Desc desc = ...
AHardwareBuffer* ahwb = nullptr;
AHardwareBuffer_allocate(&desc, &ahwb);

// Create ANeuralNetworksMemory from AHardwareBuffer
ANeuralNetworksMemory* mem2 = NULL;
ANeuralNetworksMemory_createFromAHardwareBuffer(ahwb, &mem2);

وقتی NNAPI دیگر نیازی به دسترسی به شیء AHardwareBuffer ندارد، نمونه ANeuralNetworksMemory مربوطه را آزاد کنید:

ANeuralNetworksMemory_free(mem2);

توجه:

  • می‌توانید از AHardwareBuffer فقط برای کل میان‌گیر استفاده کنید؛ نمی‌توانید از آن با پارامتر ARect استفاده کنید.
  • زمان اجرای NNAPI میان‌گیر را پاک نخواهد کرد. باید مطمئن شوید که میان‌گیرهای ورودی و خروجی قبل‌از زمان‌بندی اجرا قابل‌دسترسی باشند.
  • از توصیف‌گرهای فایل حصار همگام‌سازی پشتیبانی نمی‌شود.
  • برای AHardwareBuffer با قالب‌های خاص فروشنده و بیت‌های استفاده، تعیین اینکه آیا کارخواه یا درایور مسئول پاک کردن حافظه نهان است یا نه، برعهده پیاده‌سازی فروشنده است.

مدل

مدل واحد اصلی محاسبات در NNAPI است. هر مدل با یک یا چند عملوند و عملگر تعریف می‌شود.

عملوندها

عملوندها اشیای داده‌ای هستند که در تعریف گراف استفاده می‌شوند. این شامل ورودی‌ها و خروجی‌های مدل، گره‌های واسطه‌ای که حاوی داده‌هایی هستند که از یک عملیات به عملیات دیگر جریان می‌یابند، و ثابت‌هایی که به این عملیات‌ها منتقل می‌شوند است.

دو نوع عملوند وجود دارد که می‌توان به مدل‌های NNAPI اضافه کرد: اسکالرها و تانسورها.

اسکالر نشان‌دهنده یک مقدار است. NNAPI از مقادیر اسکالر در قالب‌های بولی، ممیز شناور ۱۶ بیتی، ممیز شناور ۳۲ بیتی، عدد صحیح ۳۲ بیتی، و عدد صحیح ۳۲ بیتی بدون علامت پشتیبانی می‌کند.

اکثر عملیات‌ها در NNAPI شامل تانسورها می‌شود. تانسورها آرایه‌های n بعدی هستند. NNAPI از تانسورهایی با مقادیر ممیز شناور ۱۶ بیتی، ممیز شناور ۳۲ بیتی، کوانتیزه‌شده ۸ بیتی، کوانتیزه‌شده ۱۶ بیتی، عدد صحیح ۳۲ بیتی، و بولی ۸ بیتی پشتیبانی می‌کند.

برای مثال، شکل ۳ مدلی را با دو عملیات نشان می‌دهد: یک جمع و به‌دنبال آن یک ضرب. مدل یک تانسور ورودی می‌گیرد و یک تانسور خروجی تولید می‌کند.

شکل ۳. نمونه‌ای از عملوندها برای مدل NNAPI

مدل بالا هفت عملوند دارد. این عملوندها به‌طور ضمنی با نمایه ترتیبی که به مدل اضافه شده‌اند شناسایی می‌شوند. اولین عملوند اضافه‌شده دارای نمایه ۰، دومین عملوند دارای نمایه ۱، و غیره است. عملوندهای ۱، ۲، ۳، و ۵ عملوندهای ثابت هستند.

ترتیب افزودن عملوندها اهمیتی ندارد. برای مثال، عملوند خروجی مدل می‌تواند اولین عملوند اضافه‌شده باشد. بخش مهم این است که هنگام ارجاع به عملوند، از مقدار نمایه صحیح استفاده کنید.

عملوندها نوع دارند. این موارد هنگام اضافه شدن به مدل مشخص می‌شوند.

عملوند نمی‌تواند هم به‌عنوان ورودی و هم به‌عنوان خروجی مدل استفاده شود.

هر عملوند باید ورودی مدل، ثابت، یا عملوند خروجی دقیقاً یک عملیات باشد.

برای اطلاعات بیشتر درباره استفاده از عملوندها، اطلاعات بیشتر درباره عملوندها را ببینید.

عملیات

یک عملیات محاسباتی را که باید انجام شود مشخص می‌کند. هر عملیات شامل این عناصر است:

  • نوع عملیات (برای مثال، جمع، ضرب، پیچش)،
  • فهرستی از نمایه‌های عملوندهایی که عملیات برای ورودی استفاده می‌کند، و
  • فهرست نمایه‌های عملوندهایی که عملیات برای برونداد استفاده می‌کند.

ترتیب در این فهرست‌ها مهم است؛ برای ورودی‌ها و خروجی‌های موردانتظار هر نوع عملیات، مرجع NNAPI API را ببینید.

قبل‌از افزودن عملیات، باید عملوندهایی را که عملیات مصرف یا تولید می‌کند به مدل اضافه کنید.

ترتیب افزودن عملیات‌ها مهم نیست. NNAPI برای تعیین ترتیب اجرای عملیات‌ها به وابستگی‌های ایجادشده توسط نمودار محاسباتی عملوندها و عملیات‌ها متکی است.

عملیاتی که NNAPI پشتیبانی می‌کند در جدول زیر خلاصه شده است:

دسته عملیات
عملیات ریاضی عنصر-محور
دستکاری تانسور
عملیات تصویر
عملیات جستجو
عملیات نرمال‌سازی
عملیات پیچیدگی
عملیات ادغام
عملیات فعال‌سازی
عملیات دیگر

مشکل شناخته‌شده در سطح ای‌پی‌آی ۲۸: هنگام ارسال ANEURALNETWORKS_TENSOR_QUANT8_ASYMM تانسورها به ANEURALNETWORKS_PAD عملیات، که در Android 9 (سطح ای‌پی‌آی ۲۸) و بالاتر دردسترس است، خروجی از NNAPI ممکن است با خروجی از چارچوب‌های یادگیری ماشین سطح بالاتر، مانند TensorFlow Lite مطابقت نداشته باشد. درعوض باید فقط ANEURALNETWORKS_TENSOR_FLOAT32 را ارسال کنید. این مشکل در Android 10 (سطح میانای برنامه‌سازی کاربردی ۲۹) و بالاتر برطرف شده است.

ساختن مدل‌ها

در مثال زیر، مدل دو عملیاتی را که در شکل ۳ آمده است ایجاد می‌کنیم.

برای ساختن مدل، این مراحل را دنبال کنید:

  1. تابع ANeuralNetworksModel_create() را برای تعریف مدل خالی فراخوانی کنید.

    ANeuralNetworksModel* model = NULL;
    ANeuralNetworksModel_create(&model);
  2. عملوندها را با فراخوانی ANeuralNetworks_addOperand() به مدل خود اضافه کنید. انواع داده‌های آن‌ها بااستفاده از ساختار داده ANeuralNetworksOperandType تعریف می‌شود.

    // In our example, all our tensors are matrices of dimension [3][4]
    ANeuralNetworksOperandType tensor3x4Type;
    tensor3x4Type.type = ANEURALNETWORKS_TENSOR_FLOAT32;
    tensor3x4Type.scale = 0.f;    // These fields are used for quantized tensors
    tensor3x4Type.zeroPoint = 0;  // These fields are used for quantized tensors
    tensor3x4Type.dimensionCount = 2;
    uint32_t dims[2] = {3, 4};
    tensor3x4Type.dimensions = dims;

    // We also specify operands that are activation function specifiers ANeuralNetworksOperandType activationType; activationType.type = ANEURALNETWORKS_INT32; activationType.scale = 0.f; activationType.zeroPoint = 0; activationType.dimensionCount = 0; activationType.dimensions = NULL;

    // Now we add the seven operands, in the same order defined in the diagram ANeuralNetworksModel_addOperand(model, &tensor3x4Type); // operand 0 ANeuralNetworksModel_addOperand(model, &tensor3x4Type); // operand 1 ANeuralNetworksModel_addOperand(model, &activationType); // operand 2 ANeuralNetworksModel_addOperand(model, &tensor3x4Type); // operand 3 ANeuralNetworksModel_addOperand(model, &tensor3x4Type); // operand 4 ANeuralNetworksModel_addOperand(model, &activationType); // operand 5 ANeuralNetworksModel_addOperand(model, &tensor3x4Type); // operand 6
  3. برای عملوندهایی که مقادیر ثابت دارند، مانند وزن‌ها و سوگیری‌هایی که برنامه‌تان از فرایند آموزش به‌دست می‌آورد، از توابع ANeuralNetworksModel_setOperandValue() و ANeuralNetworksModel_setOperandValueFromMemory() استفاده کنید.

    در مثال زیر، مقادیر ثابتی را از فایل داده‌های آموزشی تنظیم می‌کنیم که با میان‌گیر حافظه‌ای که در ارائه دسترسی به داده‌های آموزشی ایجاد کردیم مطابقت دارد.

    // In our example, operands 1 and 3 are constant tensors whose values were
    // established during the training process
    const int sizeOfTensor = 3 * 4 * 4;    // The formula for size calculation is dim0 * dim1 * elementSize
    ANeuralNetworksModel_setOperandValueFromMemory(model, 1, mem1, 0, sizeOfTensor);
    ANeuralNetworksModel_setOperandValueFromMemory(model, 3, mem1, sizeOfTensor, sizeOfTensor);

    // We set the values of the activation operands, in our example operands 2 and 5 int32_t noneValue = ANEURALNETWORKS_FUSED_NONE; ANeuralNetworksModel_setOperandValue(model, 2, &noneValue, sizeof(noneValue)); ANeuralNetworksModel_setOperandValue(model, 5, &noneValue, sizeof(noneValue));
  4. برای هر عملیات در نمودار جهت‌دار که می‌خواهید محاسبه کنید، با فراخوانی تابع ANeuralNetworksModel_addOperation() عملیات را به مدل خود اضافه کنید.

    برنامه‌تان باید موارد زیر را به‌عنوان پارامترهای این فراخوانی ارائه دهد:

    • نوع عملیات
    • تعداد مقادیر ورودی
    • آرایه نمایه‌ها برای عملوندهای ورودی
    • تعداد مقادیر برونداد
    • آرایه نمایه‌ها برای عملوندهای برونداد

    توجه داشته باشید که نمی‌توان از عملوند هم برای ورودی و هم برای خروجی یک عملیات استفاده کرد.

    // We have two operations in our example
    // The first consumes operands 1, 0, 2, and produces operand 4
    uint32_t addInputIndexes[3] = {1, 0, 2};
    uint32_t addOutputIndexes[1] = {4};
    ANeuralNetworksModel_addOperation(model, ANEURALNETWORKS_ADD, 3, addInputIndexes, 1, addOutputIndexes);

    // The second consumes operands 3, 4, 5, and produces operand 6 uint32_t multInputIndexes[3] = {3, 4, 5}; uint32_t multOutputIndexes[1] = {6}; ANeuralNetworksModel_addOperation(model, ANEURALNETWORKS_MUL, 3, multInputIndexes, 1, multOutputIndexes);
  5. با فراخوانی تابع ANeuralNetworksModel_identifyInputsAndOutputs() مشخص کنید مدل باید کدام عملوندها را به‌عنوان ورودی و خروجی خود درنظر بگیرد.

    // Our model has one input (0) and one output (6)
    uint32_t modelInputIndexes[1] = {0};
    uint32_t modelOutputIndexes[1] = {6};
    ANeuralNetworksModel_identifyInputsAndOutputs(model, 1, modelInputIndexes, 1 modelOutputIndexes);
  6. درصورت تمایل، با فراخوانی ANeuralNetworksModel_relaxComputationFloat32toFloat16() مشخص کنید که آیا ANEURALNETWORKS_TENSOR_FLOAT32 می‌تواند با محدوده یا دقتی به پایینی قالب ممیز شناور ۱۶ بیتی IEEE 754 محاسبه شود یا نه.

  7. برای نهایی کردن تعریف مدل خود، ANeuralNetworksModel_finish() را فراخوانی کنید. اگر خطایی وجود نداشته باشد، این تابع کد نتیجه ANEURALNETWORKS_NO_ERROR را برمی‌گرداند.

    ANeuralNetworksModel_finish(model);

پس‌از ایجاد مدل، می‌توانید آن را هر چند بار که بخواهید کامپایل کنید و هر کامپایل را هر چند بار که بخواهید اجرا کنید.

کنترل جریان

برای گنجاندن جریان کنترل در مدل NNAPI، کارهای زیر را انجام دهید:

  1. زیرگراف‌های اجرای مربوطه را (زیرگراف‌های then و else برای عبارت IF، زیرگراف‌های condition و body برای حلقه WHILE) به‌عنوان مدل‌های مستقل ANeuralNetworksModel* بسازید:

    ANeuralNetworksModel* thenModel = makeThenModel();
    ANeuralNetworksModel* elseModel = makeElseModel();
  2. عملوندهایی ایجاد کنید که به آن مدل‌ها در مدل حاوی جریان کنترل ارجاع دهند:

    ANeuralNetworksOperandType modelType = {
        .type = ANEURALNETWORKS_MODEL,
    };
    ANeuralNetworksModel_addOperand(model, &modelType);  // kThenOperandIndex
    ANeuralNetworksModel_addOperand(model, &modelType);  // kElseOperandIndex
    ANeuralNetworksModel_setOperandValueFromModel(model, kThenOperandIndex, &thenModel);
    ANeuralNetworksModel_setOperandValueFromModel(model, kElseOperandIndex, &elseModel);
  3. عملیات جریان کنترل را اضافه کنید:

    uint32_t inputs[] = {kConditionOperandIndex,
                         kThenOperandIndex,
                         kElseOperandIndex,
                         kInput1, kInput2, kInput3};
    uint32_t outputs[] = {kOutput1, kOutput2};
    ANeuralNetworksModel_addOperation(model, ANEURALNETWORKS_IF,
                                      std::size(inputs), inputs,
                                      std::size(output), outputs);

گردآوری‌شده

مرحله کامپایل تعیین می‌کند که مدل شما روی کدام پردازنده‌ها اجرا خواهد شد و از درایورهای مربوطه می‌خواهد برای اجرای آن آماده شوند. این کار می‌تواند شامل تولید کد ماشینی مختص پردازنده‌هایی باشد که مدل شما روی آن‌ها اجرا خواهد شد.

برای کامپایل کردن مدل، این مراحل را دنبال کنید:

  1. تابع ANeuralNetworksCompilation_create() را برای ایجاد نمونه کامپایل جدید فراخوانی کنید.

    // Compile the model
    ANeuralNetworksCompilation* compilation;
    ANeuralNetworksCompilation_create(model, &compilation);

    درصورت تمایل، می‌توانید از واگذاری دستگاه استفاده کنید تا به‌طور صریح انتخاب کنید که روی کدام دستگاه‌ها اجرا شود.

  2. می‌توانید به‌طور اختیاری بر نحوه تعادل زمان اجرا بین مصرف شارژ باتری و سرعت اجرا تأثیر بگذارید. می‌توانید این کار را با تماس گرفتن با ANeuralNetworksCompilation_setPreference() انجام دهید.

    // Ask to optimize for low power consumption
    ANeuralNetworksCompilation_setPreference(compilation, ANEURALNETWORKS_PREFER_LOW_POWER);

    اولویت‌هایی که می‌توانید مشخص کنید عبارت‌اند از:

    • ANEURALNETWORKS_PREFER_LOW_POWER: ترجیحاً به‌گونه‌ای اجرا شود که خالی شدن شارژ باتری را به حداقل برساند. این امر برای مجموعه‌هایی که اغلب اجرا می‌شوند مطلوب است.
    • ANEURALNETWORKS_PREFER_FAST_SINGLE_ANSWER: ترجیحاً یک پاسخ را در سریع‌ترین زمان ممکن برگرداند، حتی اگر این کار باعث مصرف برق بیشتر شود. این گزینه پیش‌فرض است.
    • ANEURALNETWORKS_PREFER_SUSTAINED_SPEED: به حداکثر رساندن توان عملیاتی فریم‌های متوالی را ترجیح می‌دهد، مثلاً هنگام پردازش فریم‌های متوالی که از دوربین می‌آیند.
  3. می‌توانید به‌طور اختیاری با فراخوانی ANeuralNetworksCompilation_setCaching، حافظه نهان تلفیقی را راه‌اندازی کنید.

    // Set up compilation caching
    ANeuralNetworksCompilation_setCaching(compilation, cacheDir, token);

    از getCodeCacheDir() برای cacheDir استفاده کنید. token مشخص‌شده باید برای هر مدل در برنامه یکتا باشد.

  4. تعریف گردآوری را با فراخوانی ANeuralNetworksCompilation_finish() نهایی کنید. اگر خطایی وجود نداشته باشد، این تابع کد نتیجه ANEURALNETWORKS_NO_ERROR را برمی‌گرداند.

    ANeuralNetworksCompilation_finish(compilation);

کشف و واگذاری دستگاه

در دستگاه‌های Android دارای Android 10 (میانای برنامه‌سازی کاربردی سطح ۲۹) و بالاتر، NNAPI عملکردهایی ارائه می‌دهد که به کتابخانه‌ها و برنامه‌های چارچوب یادگیری ماشین اجازه می‌دهد اطلاعاتی درباره دستگاه‌های دردسترس دریافت کنند و دستگاه‌هایی را که باید برای اجرا استفاده شوند مشخص کنند. ارائه اطلاعات درباره دستگاه‌های دردسترس به برنامه‌ها امکان می‌دهد نسخه دقیق درایورهای موجود در دستگاه را دریافت کنند تا از ناسازگاری‌های شناخته‌شده جلوگیری شود. با دادن این توانایی به برنامه‌ها که مشخص کنند کدام دستگاه‌ها باید بخش‌های مختلف مدل را اجرا کنند، می‌توان برنامه‌ها را برای دستگاه Android که در آن مستقر شده‌اند بهینه‌سازی کرد.

یافتن دستگاه

از ANeuralNetworks_getDeviceCount برای دریافت تعداد دستگاه‌های دردسترس استفاده کنید. برای هر دستگاه، از ANeuralNetworks_getDevice استفاده کنید تا نمونه ANeuralNetworksDevice را روی مرجع آن دستگاه تنظیم کنید.

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

واگذاری دستگاه

از ANeuralNetworksModel_getSupportedOperationsForDevices استفاده کنید تا متوجه شوید کدام عملیات مدل را می‌توان در دستگاه‌های خاص اجرا کرد.

برای کنترل اینکه از کدام شتاب‌دهنده‌ها برای اجرا استفاده شود، ANeuralNetworksCompilation_createForDevices را به‌جای ANeuralNetworksCompilation_create فراخوانی کنید. از شیء ANeuralNetworksCompilation حاصل به‌طور معمول استفاده کنید. اگر مدل ارائه‌شده حاوی عملیاتی باشد که دستگاه‌های انتخاب‌شده از آن پشتیبانی نمی‌کنند، تابع خطا برمی‌گرداند.

اگر چند دستگاه مشخص شده باشد، زمان اجرا مسئول توزیع کار در دستگاه‌ها است.

مشابه دستگاه‌های دیگر، پیاده‌سازی NNAPI CPU با یک ANeuralNetworksDevice با نام nnapi-reference و نوع ANEURALNETWORKS_DEVICE_TYPE_CPU نشان داده می‌شود. هنگام فراخوانی ANeuralNetworksCompilation_createForDevices، از پیاده‌سازی CPU برای رسیدگی به موارد ناموفق در کامپایل و اجرای مدل استفاده نمی‌شود.

مسئولیت تقسیم مدل به زیرمدل‌هایی که بتوانند در دستگاه‌های مشخص‌شده اجرا شوند برعهده برنامه است. برنامه‌هایی که نیازی به پارتیشن‌بندی دستی ندارند باید همچنان ANeuralNetworksCompilation_create ساده‌تر را فراخوانی کنند تا از همه دستگاه‌های دردسترس (ازجمله CPU) برای شتاب‌دهی به مدل استفاده کنند. اگر مدل نتواند به‌طور کامل توسط دستگاه‌هایی که بااستفاده از ANeuralNetworksCompilation_createForDevices مشخص کرده‌اید پشتیبانی شود، ANEURALNETWORKS_BAD_DATA برگردانده می‌شود.

پارتیشن‌بندی مدل

وقتی چندین دستگاه برای مدل دردسترس باشد، زمان اجرای NNAPI کار را بین دستگاه‌ها توزیع می‌کند. برای مثال، اگر بیش‌از یک دستگاه به ANeuralNetworksCompilation_createForDevices ارائه شده باشد، همه دستگاه‌های مشخص‌شده هنگام تخصیص کار درنظر گرفته خواهند شد. توجه داشته باشید که اگر دستگاه CPU در فهرست نباشد، اجرای CPU غیرفعال خواهد شد. هنگام استفاده از ANeuralNetworksCompilation_create همه دستگاه‌های دردسترس، ازجمله واحد پردازش مرکزی، درنظر گرفته خواهند شد.

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

برای درک نحوه پارتیشن‌بندی مدل شما توسط NNAPI، گزارش‌های Android را برای پیامی (در سطح INFO با برچسب ExecutionPlan) بررسی کنید:

ModelBuilder::findBestDeviceForEachOperation(op-name): device-index

op-name نام توصیفی عملیات در نمودار است و device-index نمایه دستگاه نامزد در فهرست دستگاه‌ها است. این فهرست ورودی ارائه‌شده به ANeuralNetworksCompilation_createForDevices است یا، درصورت استفاده از ANeuralNetworksCompilation_createForDevices، فهرست دستگاه‌هایی است که هنگام تکرار روی همه دستگاه‌ها بااستفاده از ANeuralNetworks_getDeviceCount و ANeuralNetworks_getDevice برگردانده می‌شود.

پیام (در سطح INFO با برچسب ExecutionPlan):

ModelBuilder::partitionTheWork: only one best device: device-name

این پیام نشان می‌دهد که کل نمودار در دستگاه device-name شتاب گرفته است.

اجرا

مرحله اجرا مدل را روی مجموعه‌ای از ورودی‌ها اعمال می‌کند و خروجی‌های محاسباتی را در یک یا چند میان‌گیر کاربر یا فضای حافظه که برنامه‌تان اختصاص داده است ذخیره می‌کند.

برای اجرای مدل کامپایل‌شده، این مراحل را دنبال کنید:

  1. تابع ANeuralNetworksExecution_create() را فراخوانی کنید تا نمونه اجرای جدیدی ایجاد شود.

    // Run the compiled model against a set of inputs
    ANeuralNetworksExecution* run1 = NULL;
    ANeuralNetworksExecution_create(compilation, &run1);
  2. مشخص کنید برنامه‌تان مقادیر ورودی را برای محاسبه از کجا می‌خواند. برنامه شما می‌تواند مقادیر ورودی را از میان‌گیر کاربر یا فضای حافظه اختصاص‌داده‌شده با فراخوانی ANeuralNetworksExecution_setInput() یا ANeuralNetworksExecution_setInputFromMemory() به‌ترتیب بخواند.

    // Set the single input to our sample model. Since it is small, we won't use a memory buffer
    float32 myInput[3][4] = { ...the data... };
    ANeuralNetworksExecution_setInput(run1, 0, NULL, myInput, sizeof(myInput));
  3. مشخص کنید برنامه‌تان مقادیر خروجی را کجا می‌نویسد. برنامه شما می‌تواند با فراخوانی ANeuralNetworksExecution_setOutput() یا ANeuralNetworksExecution_setOutputFromMemory() به‌ترتیب، مقادیر خروجی را در میان‌گیر کاربر یا فضای حافظه اختصاص‌داده‌شده بنویسد.

    // Set the output
    float32 myOutput[3][4];
    ANeuralNetworksExecution_setOutput(run1, 0, NULL, myOutput, sizeof(myOutput));
  4. با فراخوانی تابع ANeuralNetworksExecution_startCompute() ، زمان شروع اجرا را زمان‌بندی کنید. اگر خطایی وجود نداشته باشد، این تابع کد نتیجه ANEURALNETWORKS_NO_ERROR را برمی‌گرداند.

    // Starts the work. The work proceeds asynchronously
    ANeuralNetworksEvent* run1_end = NULL;
    ANeuralNetworksExecution_startCompute(run1, &run1_end);
  5. تابع ANeuralNetworksEvent_wait() را فراخوانی کنید تا منتظر تکمیل اجرا بمانید. اگر اجرا موفقیت‌آمیز باشد، این تابع کد نتیجه ANEURALNETWORKS_NO_ERROR را برمی‌گرداند. انتظار می‌تواند در رشته‌ای متفاوت از رشته‌ای که اجرا را شروع می‌کند انجام شود.

    // For our example, we have no other work to do and will just wait for the completion
    ANeuralNetworksEvent_wait(run1_end);
    ANeuralNetworksEvent_free(run1_end);
    ANeuralNetworksExecution_free(run1);
  6. درصورت تمایل، می‌توانید مجموعه متفاوتی از ورودی‌ها را بااستفاده از همان نمونه کامپایل برای ایجاد نمونه جدید ANeuralNetworksExecution به مدل کامپایل‌شده اعمال کنید.

    // Apply the compiled model to a different set of inputs
    ANeuralNetworksExecution* run2;
    ANeuralNetworksExecution_create(compilation, &run2);
    ANeuralNetworksExecution_setInput(run2, ...);
    ANeuralNetworksExecution_setOutput(run2, ...);
    ANeuralNetworksEvent* run2_end = NULL;
    ANeuralNetworksExecution_startCompute(run2, &run2_end);
    ANeuralNetworksEvent_wait(run2_end);
    ANeuralNetworksEvent_free(run2_end);
    ANeuralNetworksExecution_free(run2);

اجرای همگام

اجرای غیرهمگام برای ایجاد و همگام‌سازی رشته‌ها زمان صرف می‌کند. علاوه‌براین، تأخیر می‌تواند بسیار متغیر باشد و طولانی‌ترین تأخیرها بین زمانی که به رشته اطلاع داده می‌شود یا بیدار می‌شود و زمانی که درنهایت به هسته CPU متصل می‌شود، به ۵۰۰ میکروثانیه می‌رسد.

برای بهبود تأخیر، می‌توانید به‌جای آن، برنامه‌ای را برای برقراری تماس استنتاج همگام با زمان اجرا هدایت کنید. این فراخوانی فقط زمانی برمی‌گردد که استنتاج تکمیل شده باشد، نه زمانی که استنتاج شروع شده باشد. به‌جای فراخوانی ANeuralNetworksExecution_startCompute برای فراخوانی استنتاج ناهمگام به زمان اجرا، برنامه ANeuralNetworksExecution_compute را فراخوانی می‌کند تا فراخوانی همگام به زمان اجرا انجام دهد. تماس با ANeuralNetworksExecution_compute، ANeuralNetworksEvent را نمی‌پذیرد و با تماس با ANeuralNetworksEvent_wait جفت نشده است.

اجراهای رگباری

در دستگاه‌های Android با Android نسخه ۱۰ (سطح میانای برنامه‌سازی کاربردی ۲۹) و بالاتر، NNAPI از اجرای انفجاری ازطریق شیء ANeuralNetworksBurst پشتیبانی می‌کند. اجراهای انفجاری توالی‌ای از اجراهای یک کامپایل یکسان هستند که به‌سرعت پشت‌سرهم اتفاق می‌افتند، مانند اجراهایی که روی فریم‌های ضبط دوربین یا نمونه‌های صوتی متوالی عمل می‌کنند. استفاده از اشیاء ANeuralNetworksBurst ممکن است منجر به اجرای سریع‌تر شود، زیرا به شتاب‌دهنده‌ها نشان می‌دهند که منابع ممکن است بین اجراها دوباره استفاده شوند و شتاب‌دهنده‌ها باید درطول مدت انفجار در حالت عملکرد بالا باقی بمانند.

ANeuralNetworksBurst فقط تغییر کوچکی در مسیر اجرای عادی ایجاد می‌کند. بااستفاده از ANeuralNetworksBurst_create یک شیء انفجاری ایجاد می‌کنید، همان‌طور که در تکه‌کد زیر نشان داده شده است:

// Create burst object to be reused across a sequence of executions
ANeuralNetworksBurst* burst = NULL;
ANeuralNetworksBurst_create(compilation, &burst);

اجراهای انفجاری همگام هستند. اما به‌جای استفاده از ANeuralNetworksExecution_compute برای انجام هر استنتاج، اشیای مختلف ANeuralNetworksExecution را با همان ANeuralNetworksBurst در فراخوانی‌های تابع ANeuralNetworksExecution_burstCompute جفت می‌کنید.

// Create and configure first execution object
// ...

// Execute using the burst object
ANeuralNetworksExecution_burstCompute(execution1, burst);

// Use results of first execution and free the execution object
// ...

// Create and configure second execution object
// ...

// Execute using the same burst object
ANeuralNetworksExecution_burstCompute(execution2, burst);

// Use results of second execution and free the execution object
// ...

وقتی دیگر به شیء ANeuralNetworksBurst نیاز ندارید، آن را با ANeuralNetworksBurst_free آزاد کنید.

// Cleanup
ANeuralNetworksBurst_free(burst);

صف‌های فرمان ناهمگام و اجرای حصارکشی‌شده

در Android 11 و بالاتر، NNAPI از روش دیگری برای زمان‌بندی اجرای ناهمگام ازطریق ANeuralNetworksExecution_startComputeWithDependencies() پشتیبانی می‌کند. وقتی از این روش استفاده می‌کنید، اجرا منتظر می‌ماند تا همه رویدادهای وابسته قبل‌از شروع ارزیابی علامت‌دهی شوند. پس‌از تکمیل اجرا و آماده شدن خروجی‌ها برای استفاده، رویداد برگردانده‌شده علامت‌گذاری می‌شود.

بسته به اینکه کدام دستگاه‌ها اجرا را مدیریت می‌کنند، ممکن است رویداد توسط یک حصار همگام‌سازی پشتیبانی شود. باید ANeuralNetworksEvent_wait() را فراخوانی کنید تا منتظر رویداد بمانید و منابعی را که اجرا استفاده کرده است بازیابی کنید. می‌توانید حصار همگام‌سازی را بااستفاده از ANeuralNetworksEvent_createFromSyncFenceFd() به شیء رویداد وارد کنید، و می‌توانید حصار همگام‌سازی را بااستفاده از ANeuralNetworksEvent_getSyncFenceFd() از شیء رویداد صادر کنید.

بروندادهای با اندازه پویا

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

نمونه کد زیر نحوه انجام این کار را نشان می‌دهد:

// Get the rank of the output
uint32_t myOutputRank = 0;
ANeuralNetworksExecution_getOutputOperandRank(run1, 0, &myOutputRank);

// Get the dimensions of the output
std::vector<uint32_t> myOutputDimensions(myOutputRank);
ANeuralNetworksExecution_getOutputOperandDimensions(run1, 0, myOutputDimensions.data());

پاک‌سازی

مرحله پاک‌سازی، آزادسازی منابع داخلی استفاده‌شده برای محاسبات شما را مدیریت می‌کند.

// Cleanup
ANeuralNetworksCompilation_free(compilation);
ANeuralNetworksModel_free(model);
ANeuralNetworksMemory_free(mem1);

مدیریت خطا و بازگشت به CPU

اگر درطول بخش‌بندی خطایی رخ دهد، اگر درایور نتواند (بخشی از) مدل را کامپایل کند، یا اگر درایور نتواند (بخشی از) مدل کامپایل‌شده را اجرا کند، NNAPI ممکن است به پیاده‌سازی CPU خود برای یک یا چند عملیات برگردد.

اگر کارخواه NNAPI حاوی نسخه‌های بهینه‌شده عملیات باشد (برای مثال، TFLite)، ممکن است غیرفعال کردن بازگشت CPU و رسیدگی به خطاها با پیاده‌سازی عملیات بهینه‌شده کارخواه مفید باشد.

در Android 10، اگر کامپایل بااستفاده از ANeuralNetworksCompilation_createForDevices انجام شود، جایگزین CPU غیرفعال خواهد شد.

در Android P، اگر اجرای NNAPI در درایور ناموفق باشد، اجرای آن به CPU واگذار می‌شود. این موضوع در Android 10 نیز صادق است، وقتی به‌جای ANeuralNetworksCompilation_createForDevices از ANeuralNetworksCompilation_create استفاده می‌شود.

اولین اجرا برای آن پارتیشن تکی به حالت قبلی برمی‌گردد و اگر همچنان ناموفق باشد، کل مدل را در CPU دوباره امتحان می‌کند.

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

در برخی موارد، بعضی‌از عملیات‌ها در CPU پشتیبانی نمی‌شوند و در چنین شرایطی، به‌جای بازگشت به حالت قبلی، کامپایل یا اجرا ناموفق خواهد بود.

حتی پس‌از غیرفعال کردن بازگشت به CPU، ممکن است همچنان عملیاتی در مدل وجود داشته باشد که در CPU زمان‌بندی شده است. اگر CPU در فهرست پردازنده‌های ارائه‌شده به ANeuralNetworksCompilation_createForDevices باشد و تنها پردازنده‌ای باشد که از آن عملیات پشتیبانی می‌کند یا پردازنده‌ای باشد که ادعا می‌کند بهترین عملکرد را برای آن عملیات دارد، به‌عنوان مجری اصلی (غیرجایگزین) انتخاب خواهد شد.

برای اطمینان از اینکه هیچ اجرای CPU وجود ندارد، از ANeuralNetworksCompilation_createForDevices استفاده کنید و درعین‌حال nnapi-reference را از فهرست دستگاه‌ها حذف کنید. از Android P به بعد، می‌توان با تنظیم کردن ویژگی debug.nn.partition روی ۲، بازگشت را در زمان اجرا در ساخت‌های DEBUG غیرفعال کرد.

دامنه‌های حافظه

در Android 11 و بالاتر، NNAPI از دامنه‌های حافظه که میاناهای تخصیص‌دهنده برای حافظه‌های مات ارائه می‌دهند پشتیبانی می‌کند. این کار به برنامه‌ها اجازه می‌دهد حافظه‌های بومی دستگاه را در سراسر اجراها منتقل کنند، به‌طوری‌که NNAPI هنگام انجام اجراهای متوالی در همان درایور، داده‌ها را به‌طور غیرضروری کپی یا تبدیل نکند.

ویژگی دامنه حافظه برای تانسورهایی درنظر گرفته شده است که عمدتاً برای درایور داخلی هستند و نیازی به دسترسی مکرر به سمت کارخواه ندارند. نمونه‌هایی از این تانسورها شامل تانسورهای حالت در مدل‌های توالی است. برای تانسورهایی که نیاز به دسترسی مکرر به CPU در سمت کارخواه دارند، به‌جای آن از استخرهای حافظه مشترک استفاده کنید.

برای اختصاص دادن حافظه مات، مراحل زیر را انجام دهید:

  1. تابع ANeuralNetworksMemoryDesc_create() را برای ایجاد توصیف‌گر حافظه جدید فراخوانی کنید:

    // Create a memory descriptor
    ANeuralNetworksMemoryDesc* desc;
    ANeuralNetworksMemoryDesc_create(&desc);
  2. همه نقش‌های ورودی و خروجی موردنظر را با فراخوانی ANeuralNetworksMemoryDesc_addInputRole() و ANeuralNetworksMemoryDesc_addOutputRole() مشخص کنید.

    // Specify that the memory may be used as the first input and the first output
    // of the compilation
    ANeuralNetworksMemoryDesc_addInputRole(desc, compilation, 0, 1.0f);
    ANeuralNetworksMemoryDesc_addOutputRole(desc, compilation, 0, 1.0f);
  3. درصورت تمایل، ابعاد حافظه را با فراخوانی ANeuralNetworksMemoryDesc_setDimensions() مشخص کنید.

    // Specify the memory dimensions
    uint32_t dims[] = {3, 4};
    ANeuralNetworksMemoryDesc_setDimensions(desc, 2, dims);
  4. تعریف توصیف‌گر را با فراخوانی ANeuralNetworksMemoryDesc_finish() نهایی کنید.

    ANeuralNetworksMemoryDesc_finish(desc);
  5. با ارسال توصیف‌گر به ANeuralNetworksMemory_createFromDesc()، هر تعداد حافظه که نیاز دارید اختصاص دهید.

    // Allocate two opaque memories with the descriptor
    ANeuralNetworksMemory* opaqueMem;
    ANeuralNetworksMemory_createFromDesc(desc, &opaqueMem);
  6. وقتی دیگر به توصیف‌گر حافظه نیاز ندارید، آن را آزاد کنید.

    ANeuralNetworksMemoryDesc_free(desc);

مشتری فقط می‌تواند از شیء ANeuralNetworksMemory ایجادشده با ANeuralNetworksExecution_setInputFromMemory() یا ANeuralNetworksExecution_setOutputFromMemory() طبق نقش‌های مشخص‌شده در شیء ANeuralNetworksMemoryDesc استفاده کند. آرگومان‌های انحراف و طول باید روی ۰ تنظیم شوند، که نشان می‌دهد از کل حافظه استفاده می‌شود. کارخواه همچنین می‌تواند بااستفاده از ANeuralNetworksMemory_copy() محتوای حافظه را به‌طور صریح تنظیم یا استخراج کند.

می‌توانید حافظه‌های مات با نقش‌هایی از ابعاد یا رتبه نامشخص ایجاد کنید. در این صورت، اگر درایور زیربنایی از ایجاد حافظه پشتیبانی نکند، ممکن است این فرایند با وضعیت ANEURALNETWORKS_OP_FAILED ناموفق باشد. به کارخواه توصیه می‌شود منطق بازگشت را با اختصاص دادن میان‌گیر به‌اندازه کافی بزرگ که توسط Ashmem یا AHardwareBuffer حالت BLOB پشتیبانی می‌شود پیاده‌سازی کند.

وقتی NNAPI دیگر نیازی به دسترسی به شیء حافظه مات ندارد، نمونه ANeuralNetworksMemory مربوطه را آزاد کنید:

ANeuralNetworksMemory_free(opaqueMem);

سنجش عملکرد

می‌توانید عملکرد برنامه‌تان را با اندازه‌گیری زمان اجرا یا با نمایه‌سازی ارزیابی کنید.

زمان اجرا

وقتی می‌خواهید کل زمان اجرا را ازطریق زمان اجرا تعیین کنید، می‌توانید از میانای برنامه‌سازی کاربردی اجرای همگام استفاده کنید و زمان صرف‌شده برای تماس را اندازه‌گیری کنید. وقتی می‌خواهید کل زمان اجرا را ازطریق سطح پایین‌تری از پشته نرم‌افزار تعیین کنید، می‌توانید از ANeuralNetworksExecution_setMeasureTiming و ANeuralNetworksExecution_getDuration برای دریافت موارد زیر استفاده کنید:

  • زمان اجرا در شتاب‌دهنده (نه در درایور که در پردازنده میزبان اجرا می‌شود).
  • زمان اجرا در درایور، ازجمله زمان صرف‌شده در شتاب‌دهنده.

زمان اجرا در درایور شامل سربار مانند زمان اجرای خود و IPC موردنیاز برای زمان اجرا برای برقراری ارتباط با درایور نمی‌شود.

این میاناهای برنامه‌سازی کاربردی مدت زمان بین رویدادهای ارسال کار و تکمیل کار را اندازه‌گیری می‌کنند، نه زمانی را که درایور یا شتاب‌دهنده به انجام استنتاج اختصاص می‌دهد، که ممکن است با تعویض بافت قطع شود.

برای مثال، اگر استنتاج ۱ شروع شود، سپس درایور کار را متوقف کند تا استنتاج ۲ را انجام دهد، سپس استنتاج ۱ را ازسر بگیرد و تکمیل کند، زمان اجرای استنتاج ۱ شامل زمانی خواهد بود که کار برای انجام استنتاج ۲ متوقف شده بود.

این اطلاعات زمان‌بندی ممکن است برای استقرار تولید یک برنامه کاربردی برای جمع‌آوری دورسنجی برای استفاده آفلاین مفید باشد. می‌توانید از داده‌های زمان‌بندی برای اصلاح برنامه جهت عملکرد بهتر استفاده کنید.

هنگام استفاده از این عملکرد، موارد زیر را درنظر داشته باشید:

  • جمع‌آوری اطلاعات زمان‌بندی ممکن است هزینه عملکرد داشته باشد.
  • فقط درایور می‌تواند زمان صرف‌شده در خودش یا در شتاب‌دهنده را محاسبه کند، به‌استثنای زمان صرف‌شده در زمان اجرای NNAPI و در IPC.
  • فقط با ANeuralNetworksExecution که با ANeuralNetworksCompilation_createForDevices با numDevices = 1 ایجاد شده است می‌توانید از این APIها استفاده کنید.
  • برای گزارش اطلاعات زمان‌بندی، به هیچ درایوری نیاز نیست.

نمایه‌سازی برنامه با Android Systrace

از Android 10، NNAPI به‌طور خودکار رویدادهای systrace را تولید می‌کند که می‌توانید از آن‌ها برای نمایه کردن برنامه‌تان استفاده کنید.

NNAPI Source با ابزار کمکی parse_systrace ارائه می‌شود تا رویدادهای systrace تولیدشده توسط برنامه شما را پردازش کند و نمای جدولی تولید کند که زمان صرف‌شده در فازهای مختلف چرخه حیات مدل (نمونه‌سازی، آماده‌سازی، اجرای کامپایل، و خاتمه) و لایه‌های مختلف برنامه‌ها را نشان می‌دهد. لایه‌هایی که برنامه شما به آن‌ها تقسیم می‌شود عبارت‌اند از:

  • Application: کد برنامه اصلی
  • ‫Runtime: زمان اجرای NNAPI
  • IPC: ارتباط بین فرایندی بین NNAPI Runtime و کد Driver
  • ‫Driver: فرایند درایور شتاب‌دهنده.

تولید داده‌های تحلیل نمایه‌سازی

با فرض اینکه درخت منبع AOSP را در $ANDROID_BUILD_TOP بررسی کرده‌اید و از نمونه طبقه‌بندی تصویر TFLite به‌عنوان برنامه هدف استفاده می‌کنید، می‌توانید داده‌های نمایه‌سازی NNAPI را با مراحل زیر تولید کنید:

  1. systrace Android را با فرمان زیر شروع کنید:
$ANDROID_BUILD_TOP/external/chromium-trace/systrace.py  -o trace.html -a org.tensorflow.lite.examples.classification nnapi hal freq sched idle load binder_driver

پارامتر -o trace.html نشان می‌دهد که ردگیری‌ها در trace.html نوشته خواهند شد. هنگام نمایه‌سازی برنامه خودتان، باید org.tensorflow.lite.examples.classification را با نام فرایند مشخص‌شده در مانیفست برنامه‌تان جایگزین کنید.

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

  1. پس‌از شروع جمع‌آوری‌کننده systrace، برنامه‌تان را شروع کنید و آزمون محک‌زنی را اجرا کنید.

در مورد ما، می‌توانید برنامه طبقه‌بندی تصویر را از Android Studio یا مستقیماً از میانای کاربری تلفن آزمایشی خود (اگر برنامه قبلاً نصب شده باشد) شروع کنید. برای تولید برخی‌از داده‌های NNAPI، باید برنامه را پیکربندی کنید تا از NNAPI استفاده کند. برای این کار، NNAPI را به‌عنوان دستگاه هدف در کادر گفتگوی پیکربندی برنامه انتخاب کنید.

  1. وقتی آزمایش تکمیل شد، systrace را با فشار دادن enter در پایانه کنسول که از مرحله ۱ فعال است خاتمه دهید.

  2. اجرای ابزار systrace_parser برای تولید آمار تجمعی:

$ANDROID_BUILD_TOP/frameworks/ml/nn/tools/systrace_parser/parse_systrace.py --total-times trace.html

تجزیه‌کننده پارامترهای زیر را می‌پذیرد: - --total-times: کل زمان صرف‌شده در یک لایه را نشان می‌دهد، ازجمله زمان صرف‌شده برای انتظار برای اجرا در فراخوانی به یک لایه زیرین - --print-detail: همه رویدادهایی را که از systrace جمع‌آوری شده‌اند چاپ می‌کند - --per-execution: فقط اجرا و زیرفازهای آن را چاپ می‌کند (به‌عنوان زمان‌های هر اجرا) به‌جای آمار برای همه فازها - --json: خروجی را در قالب JSON تولید می‌کند

نمونه‌ای از خروجی در زیر نشان داده شده است:

===========================================================================================================================================
NNAPI timing summary (total time, ms wall-clock)                                                      Execution
                                                           ----------------------------------------------------
              Initialization   Preparation   Compilation           I/O       Compute      Results     Ex. total   Termination        Total
              --------------   -----------   -----------   -----------  ------------  -----------   -----------   -----------   ----------
Application              n/a         19.06       1789.25           n/a           n/a         6.70         21.37           n/a      1831.17*
Runtime                    -         18.60       1787.48          2.93         11.37         0.12         14.42          1.32      1821.81
IPC                     1.77             -       1781.36          0.02          8.86            -          8.88             -      1792.01
Driver                  1.04             -       1779.21           n/a           n/a          n/a          7.70             -      1787.95

Total                   1.77*        19.06*      1789.25*         2.93*        11.74*        6.70*        21.37*         1.32*     1831.17*
===========================================================================================================================================
* This total ignores missing (n/a) values and thus is not necessarily consistent with the rest of the numbers

اگر رویدادهای جمع‌آوری‌شده نشان‌دهنده ردیابی کامل برنامه نباشد، ممکن است تجزیه‌کننده با خطا مواجه شود. به‌ویژه اگر رویدادهای systrace تولیدشده برای علامت‌گذاری پایان یک بخش در ردگیری وجود داشته باشند اما رویداد شروع بخش مرتبط وجود نداشته باشد، ممکن است با شکست مواجه شود. این اتفاق معمولاً زمانی می‌افتد که برخی‌از رویدادهای جلسه نمایه‌سازی قبلی درحال تولید شدن باشند و شما جمع‌آورنده systrace را شروع کنید. دراین‌صورت باید نمایه‌سازی را دوباره اجرا کنید.

افزودن آمار کد برنامه به خروجی systrace_parser

برنامه parse_systrace براساس عملکرد systrace داخلی Android است. می‌توانید بااستفاده از systrace API (برای Java ، برای برنامه‌های بومی ) با نام‌های رویداد سفارشی، برای عملیات خاص در برنامه‌تان ردیابی اضافه کنید.

برای مرتبط کردن رویدادهای سفارشی‌تان با فازهای چرخه حیات برنامه، یکی از رشته‌های زیر را به ابتدای نام رویدادتان اضافه کنید:

  • ‫[NN_LA_PI]: رویداد سطح برنامه برای مقداردهی اولیه
  • ‫[NN_LA_PP]: رویداد سطح برنامه برای آماده‌سازی
  • ‫[NN_LA_PC]: رویداد سطح برنامه برای گردآوری
  • ‫[NN_LA_PE]: رویداد سطح برنامه برای اجرا

در اینجا مثالی از نحوه تغییر کد نمونه طبقه‌بندی تصویر TFLite با افزودن بخش runInferenceModel برای فاز Execution و لایه Application حاوی بخش‌های دیگر preprocessBitmap که در ردیابی‌های NNAPI درنظر گرفته نخواهند شد آورده شده است. بخش runInferenceModel بخشی از رویدادهای systrace خواهد بود که توسط تجزیه‌کننده systrace در nnapi پردازش می‌شود:

کاتلین

/** Runs inference and returns the classification results. */
fun recognizeImage(bitmap: Bitmap): List {
   // This section won’t appear in the NNAPI systrace analysis
   Trace.beginSection("preprocessBitmap")
   convertBitmapToByteBuffer(bitmap)
   Trace.endSection()

   // Run the inference call.
   // Add this method in to NNAPI systrace analysis.
   Trace.beginSection("[NN_LA_PE]runInferenceModel")
   long startTime = SystemClock.uptimeMillis()
   runInference()
   long endTime = SystemClock.uptimeMillis()
   Trace.endSection()
    ...
   return recognitions
}

جاوا

/** Runs inference and returns the classification results. */
public List recognizeImage(final Bitmap bitmap) {

 // This section won’t appear in the NNAPI systrace analysis
 Trace.beginSection("preprocessBitmap");
 convertBitmapToByteBuffer(bitmap);
 Trace.endSection();

 // Run the inference call.
 // Add this method in to NNAPI systrace analysis.
 Trace.beginSection("[NN_LA_PE]runInferenceModel");
 long startTime = SystemClock.uptimeMillis();
 runInference();
 long endTime = SystemClock.uptimeMillis();
 Trace.endSection();
  ...
 Trace.endSection();
 return recognitions;
}

کیفیت خدمات

در Android 11 و بالاتر، NNAPI با اجازه دادن به برنامه برای نشان دادن اولویت‌های نسبی مدل‌هایش، حداکثر زمان موردانتظار برای آماده‌سازی مدل داده‌شده، و حداکثر زمان موردانتظار برای تکمیل محاسبات داده‌شده، کیفیت سرویس (QoS) بهتری را فراهم می‌کند. Android 11 همچنین کدهای نتیجه NNAPI بیشتری را معرفی می‌کند که به برنامه‌ها امکان می‌دهد خرابی‌هایی مانند ازدست رفتن مهلت‌های اجرای برنامه را درک کنند.

تنظیم اولویت حجم کاری

برای تنظیم اولویت حجم کاری NNAPI، ANeuralNetworksCompilation_setPriority() را قبل‌از فراخوانی ANeuralNetworksCompilation_finish() فراخوانی کنید.

تنظیم کردن مهلت

برنامه‌ها می‌توانند برای کامپایل و استنتاج مدل مهلت تعیین کنند.

اطلاعات بیشتر درباره عملوندها

بخش زیر موضوعات پیشرفته درباره استفاده از عملوندها را پوشش می‌دهد.

تانسورهای کوانتیده

تانسور کوانتیده روشی فشرده برای نمایش آرایه n-بعدی از مقادیر ممیز شناور است.

NNAPI از تانسورهای کوانتیزه‌شده نامتقارن ۸ بیتی پشتیبانی می‌کند. برای این تانسورها، مقدار هر سلول با یک عدد صحیح ۸ بیتی نشان داده می‌شود. یک مقیاس و یک مقدار نقطه صفر با تانسور مرتبط است. از این‌ها برای تبدیل اعداد صحیح ۸ بیتی به مقادیر نقطه شناور که نشان داده می‌شوند استفاده می‌شود.

فرمول:

(cellValue - zeroPoint) * scale

که در آن مقدار zeroPoint یک عدد صحیح ۳۲ بیتی و مقیاس یک مقدار ممیز شناور ۳۲ بیتی است.

درمقایسه با تانسورهای مقادیر ممیز شناور ۳۲ بیتی، تانسورهای کوانتیزه‌شده ۸ بیتی دو مزیت دارند:

  • برنامه شما کوچک‌تر است، زیرا وزن‌های آموزش‌دیده یک‌چهارم اندازه تانسورهای ۳۲ بیتی را اشغال می‌کنند.
  • محاسبات اغلب سریع‌تر اجرا می‌شوند. دلیل این امر مقدار کمتر داده‌ای است که باید از حافظه واکشی شود و همچنین کارایی پردازنده‌هایی مانند DSP در انجام محاسبات عدد صحیح است.

اگرچه امکان تبدیل مدل ممیز شناور به مدل کوانتیزه‌شده وجود دارد، اما تجربه ما نشان داده است که با آموزش مستقیم مدل کوانتیزه‌شده نتایج بهتری حاصل می‌شود. درواقع، شبکه عصبی یاد می‌گیرد که افزایش دانه‌بندی هر مقدار را جبران کند. برای هر تانسور کوانتیزه‌شده، مقیاس و مقادیر zeroPoint درطول فرایند آموزش تعیین می‌شود.

در NNAPI، انواع تانسور کوانتیزه‌شده را با تنظیم فیلد نوع ساختار داده ANeuralNetworksOperandType روی ANEURALNETWORKS_TENSOR_QUANT8_ASYMM تعریف می‌کنید. همچنین مقیاس و مقدار zeroPoint تانسور را در آن ساختار داده مشخص می‌کنید.

علاوه‌بر تانسورهای کوانتیزه‌شده نامتقارن ۸ بیتی، NNAPI از موارد زیر پشتیبانی می‌کند:

عملوندهای اختیاری

چند عملیات، مثل ANEURALNETWORKS_LSH_PROJECTION، عملوندهای اختیاری می‌گیرند. برای نشان دادن اینکه عملوند اختیاری در مدل حذف شده است، تابع ANeuralNetworksModel_setOperandValue() را فراخوانی کنید و NULL را برای میان‌گیر و 0 را برای طول ارسال کنید.

اگر تصمیم درباره اینکه عملوند وجود دارد یا نه برای هر اجرا متفاوت باشد، بااستفاده از توابع ANeuralNetworksExecution_setInput() یا ANeuralNetworksExecution_setOutput() و ارسال NULL برای میان‌گیر و 0 برای طول، نشان می‌دهید که عملوند حذف شده است.

تانسورهای رتبه نامشخص

Android 9 (سطح میانای برنامه‌سازی کاربردی ۲۸) عملوندهای مدل با ابعاد نامشخص اما رتبه مشخص (تعداد ابعاد) را معرفی کرد. Android 10 (سطح میانای برنامه‌سازی کاربردی ۲۹) تانسورهای رتبه ناشناخته را معرفی کرد، همان‌طور که در ANeuralNetworksOperandType نشان داده شده است.

محک NNAPI

معیار NNAPI در AOSP در platform/test/mlts/benchmark (برنامه معیار) و platform/test/mlts/models (مدل‌ها و مجموعه‌های داده) دردسترس است.

این محک تأخیر و دقت را ارزیابی می‌کند و درایورها را با همان کار انجام‌شده بااستفاده از Tensorflow Lite که روی CPU اجرا می‌شود، برای همان مدل‌ها و مجموعه‌داده‌ها مقایسه می‌کند.

برای استفاده از محک، کارهای زیر را انجام دهید:

  1. دستگاه Android هدف را به رایانه متصل کنید، پنجره ترمینال را باز کنید، و مطمئن شوید دستگاه ازطریق adb قابل‌دسترسی است.

  2. اگر بیش‌از یک دستگاه Android متصل است، متغیر محیطی دستگاه هدف ANDROID_SERIAL را صادر کنید.

  3. به دایرکتوری منبع سطح بالای Android پیمایش کنید.

  4. فرمان‌های زیر را اجرا کنید:

    lunch aosp_arm-userdebug # Or aosp_arm64-userdebug if available
    ./test/mlts/benchmark/build_and_run_benchmark.sh
    

    در پایان اجرای محک، نتایج آن به‌صورت صفحه HTML ارائه می‌شود که به xdg-open ارسال شده است.

گزارش‌های NNAPI

NNAPI اطلاعات عیب‌یابی مفیدی در گزارش‌های سیستم تولید می‌کند. برای تجزیه‌وتحلیل گزارش‌ها، از ابزار کمکی logcat استفاده کنید.

با تنظیم کردن ویژگی debug.nn.vlog (بااستفاده از adb shell) روی فهرست مقادیر زیر که با فاصله، دونقطه، یا ویرگول از هم جدا شده‌اند، ثبت NNAPI پرحرف را برای فازها یا مؤلفه‌های خاص فعال کنید:

  • model: ساختن مدل
  • ‫compilation: تولید طرح اجرای مدل و کامپایل کردن
  • ‫execution: اجرای مدل
  • cpuexe: اجرای عملیات بااستفاده از پیاده‌سازی NNAPI CPU
  • manager: افزونه‌های NNAPI، اطلاعات مربوط به میاناهای دردسترس و قابلیت‌ها
  • all یا 1: همه عناصر بالا

برای مثال، برای فعال کردن گزارش‌گیری مفصل کامل، از فرمان adb shell setprop debug.nn.vlog all استفاده کنید. برای غیرفعال کردن گزارش‌گیری مفصل، از فرمان adb shell setprop debug.nn.vlog '""' استفاده کنید.

پس‌از فعال شدن، ثبت گزارش پرجزئیات ورودی‌های گزارش را در سطح INFO با برچسبی که روی نام فاز یا مؤلفه تنظیم شده است تولید می‌کند.

علاوه‌بر پیام‌های کنترل‌شده debug.nn.vlog، مؤلفه‌های NNAPI API ورودی‌های گزارش دیگری را در سطوح مختلف ارائه می‌دهند که هرکدام از برچسب گزارش خاصی استفاده می‌کنند.

برای دریافت فهرست مؤلفه‌ها، درخت منبع را بااستفاده از عبارت زیر جستجو کنید:

grep -R 'define LOG_TAG' | awk -F '"' '{print $2}' | sort -u | egrep -v "Sample|FileTag|test"

این عبارت درحال‌حاضر برچسب‌های زیر را برمی‌گرداند:

  • BurstBuilder
  • بازخوانی‌ها
  • CompilationBuilder
  • CpuExecutor
  • ExecutionBuilder
  • ExecutionBurstController
  • ExecutionBurstServer
  • ExecutionPlan
  • FibonacciDriver
  • GraphDump
  • IndexedShapeWrapper
  • IonWatcher
  • مدیر
  • حافظه
  • MemoryUtils
  • MetaModel
  • ModelArgumentInfo
  • ModelBuilder
  • NeuralNetworks
  • OperationResolver
  • عملیات
  • OperationsUtils
  • PackageInfo
  • TokenHasher
  • TypeManager
  • Utils
  • ValidateHal
  • VersionedInterfaces

برای کنترل سطح پیام‌های گزارش نشان‌داده‌شده توسط logcat، از متغیر محیطی ANDROID_LOG_TAGS استفاده کنید.

برای نمایش مجموعه کامل پیام‌های گزارش NNAPI و غیرفعال کردن سایر پیام‌ها، ANDROID_LOG_TAGS را روی مقدار زیر تنظیم کنید:

BurstBuilder:V Callbacks:V CompilationBuilder:V CpuExecutor:V ExecutionBuilder:V ExecutionBurstController:V ExecutionBurstServer:V ExecutionPlan:V FibonacciDriver:V GraphDump:V IndexedShapeWrapper:V IonWatcher:V Manager:V MemoryUtils:V Memory:V MetaModel:V ModelArgumentInfo:V ModelBuilder:V NeuralNetworks:V OperationResolver:V OperationsUtils:V Operations:V PackageInfo:V TokenHasher:V TypeManager:V Utils:V ValidateHal:V VersionedInterfaces:V *:S.

می‌توانید ANDROID_LOG_TAGS را بااستفاده از فرمان زیر تنظیم کنید:

export ANDROID_LOG_TAGS=$(grep -R 'define LOG_TAG' | awk -F '"' '{ print $2 ":V" }' | sort -u | egrep -v "Sample|FileTag|test" | xargs echo -n; echo ' *:S')

توجه داشته باشید که این فقط فیلتری است که بر logcat اعمال می‌شود. همچنان باید ویژگی debug.nn.vlog را روی all تنظیم کنید تا اطلاعات گزارش پرجزئیات تولید شود.