Android Neural Networks API (NNAPI) یک API Android C است که برای اجرای عملیات محاسباتی فشرده برای یادگیری ماشین در دستگاههای Android طراحی شده است. NNAPI برای ارائه یک لایه پایه از عملکرد برای چارچوبهای یادگیری ماشینی سطح بالاتر، مانند TensorFlow Lite و Caffe2، که شبکههای عصبی را میسازند و آموزش میدهند، طراحی شده است. API در همه دستگاههای اندرویدی دارای Android 8.1 (سطح API 27) یا بالاتر در دسترس است.
NNAPI از استنتاج با اعمال دادهها از دستگاههای Android به مدلهای آموزشدیده قبلی و تعریفشده توسط توسعهدهنده پشتیبانی میکند. نمونه هایی از استنباط عبارتند از طبقه بندی تصاویر، پیش بینی رفتار کاربر، و انتخاب پاسخ های مناسب به یک عبارت جستجو.
استنباط بر روی دستگاه مزایای زیادی دارد:
- تأخیر : نیازی به ارسال درخواست از طریق اتصال شبکه ندارید و منتظر پاسخ باشید. به عنوان مثال، این می تواند برای برنامه های ویدیویی که فریم های متوالی از یک دوربین را پردازش می کنند، بسیار مهم باشد.
- در دسترس بودن : برنامه حتی زمانی که خارج از پوشش شبکه است اجرا می شود.
- سرعت : سختافزار جدیدی که مختص پردازش شبکههای عصبی است، محاسبات بسیار سریعتری را نسبت به یک CPU همه منظوره به تنهایی فراهم میکند.
- حریم خصوصی : داده ها از دستگاه Android خارج نمی شوند.
- هزینه : وقتی همه محاسبات در دستگاه Android انجام می شود، به مزرعه سرور نیاز نیست.
همچنین معاوضه هایی وجود دارد که یک توسعه دهنده باید در نظر داشته باشد:
- استفاده از سیستم : ارزیابی شبکه های عصبی مستلزم محاسبات زیادی است که می تواند مصرف انرژی باتری را افزایش دهد. اگر این مورد برای برنامه شما نگران کننده است، به خصوص برای محاسبات طولانی مدت، باید سلامت باتری را بررسی کنید.
- اندازه برنامه : به اندازه مدل های خود توجه کنید. مدل ها ممکن است چندین مگابایت فضا را اشغال کنند. اگر بستهبندی مدلهای بزرگ در APK شما بهطور غیرمنطقی بر کاربران شما تأثیر میگذارد، ممکن است بخواهید مدلها را پس از نصب برنامه، استفاده از مدلهای کوچکتر یا اجرای محاسبات خود در فضای ابری دانلود کنید. NNAPI عملکردی را برای مدلهای در حال اجرا در فضای ابری ارائه نمیکند.
برای مشاهده نمونه ای از نحوه استفاده از NNAPI ، نمونه API شبکه های عصبی اندروید را ببینید.
زمان اجرای API شبکه های عصبی را درک کنید
NNAPI قرار است با کتابخانهها، چارچوبها و ابزارهای یادگیری ماشینی فراخوانی شود که به توسعهدهندگان اجازه میدهد مدلهای خود را خارج از دستگاه آموزش دهند و آنها را در دستگاههای Android استقرار دهند. برنامهها معمولاً مستقیماً از NNAPI استفاده نمیکنند، اما در عوض از چارچوبهای یادگیری ماشینی سطح بالاتر استفاده میکنند. این چارچوبها به نوبه خود میتوانند از NNAPI برای انجام عملیات استنتاج تسریعشده سختافزاری در دستگاههای پشتیبانیشده استفاده کنند.
بر اساس نیازهای یک برنامه و قابلیتهای سختافزاری در یک دستگاه اندرویدی، زمان اجرا شبکه عصبی اندروید میتواند به طور موثر حجم کار محاسباتی را در بین پردازندههای موجود روی دستگاه، از جمله سختافزار شبکه عصبی اختصاصی، واحدهای پردازش گرافیکی (GPU) و پردازندههای سیگنال دیجیتال (DSP) توزیع کند. ).
برای دستگاههای اندرویدی که فاقد درایور فروشنده تخصصی هستند، زمان اجرا NNAPI درخواستها را روی CPU اجرا میکند.
شکل 1 معماری سیستم سطح بالا را برای NNAPI نشان می دهد.
مدل برنامه نویسی API شبکه های عصبی
برای انجام محاسبات با استفاده از NNAPI، ابتدا باید یک نمودار جهت دار بسازید که محاسبات را برای انجام تعریف کند. این نمودار محاسباتی، همراه با دادههای ورودی شما (به عنوان مثال، وزنها و سوگیریهای منتقل شده از چارچوب یادگیری ماشین)، مدلی را برای ارزیابی زمان اجرا NNAPI تشکیل میدهد.
NNAPI از چهار انتزاع اصلی استفاده می کند:
- مدل : یک نمودار محاسباتی از عملیات ریاضی و مقادیر ثابتی که از طریق یک فرآیند آموزشی آموخته شده است. این عملیات مختص شبکه های عصبی است. آنها شامل کانولوشن دو بعدی (2 بعدی)، فعال سازی لجستیک ( سیگموئید )، فعال سازی خطی اصلاح شده (ReLU) و غیره هستند. ایجاد یک مدل یک عملیات همزمان است. پس از ایجاد موفقیت آمیز، می توان از آن در سراسر رشته ها و مجموعه ها استفاده مجدد کرد. در NNAPI، یک مدل به عنوان یک نمونه
ANeuralNetworksModel
نشان داده می شود. - کامپایل : پیکربندی را برای کامپایل کردن یک مدل NNAPI در کدهای سطح پایین نشان می دهد. ایجاد یک کامپایل یک عملیات همزمان است. پس از ایجاد موفقیت آمیز، می توان از آن در سراسر رشته ها و اجراها استفاده مجدد کرد. در NNAPI، هر کامپایل به عنوان یک نمونه
ANeuralNetworksCompilation
نشان داده می شود. - حافظه : نشان دهنده حافظه مشترک، فایل های نقشه برداری شده از حافظه و بافرهای حافظه مشابه است. استفاده از بافر حافظه به NNAPI اجازه میدهد تا دادهها را با کارایی بیشتری به درایورها منتقل کند. یک برنامه معمولاً یک بافر حافظه مشترک ایجاد می کند که حاوی هر تانسور مورد نیاز برای تعریف یک مدل است. همچنین می توانید از بافرهای حافظه برای ذخیره ورودی ها و خروجی ها برای یک نمونه اجرا استفاده کنید. در NNAPI، هر بافر حافظه به عنوان یک نمونه
ANeuralNetworksMemory
نشان داده می شود. اجرا : رابطی برای اعمال یک مدل NNAPI به مجموعهای از ورودیها و جمعآوری نتایج. اجرا می تواند به صورت همزمان یا ناهمزمان انجام شود.
برای اجرای ناهمزمان، چندین رشته می توانند در اجرای یکسان منتظر بمانند. پس از اتمام این اجرا، تمام رشته ها آزاد می شوند.
در NNAPI، هر اجرا به عنوان یک نمونه
ANeuralNetworksExecution
نشان داده می شود.
شکل 2 جریان اصلی برنامه نویسی را نشان می دهد.
بقیه این بخش مراحل راه اندازی مدل NNAPI را برای انجام محاسبات، کامپایل مدل و اجرای مدل کامپایل شده شرح می دهد.
دسترسی به داده های آموزشی را فراهم کنید
داده های وزن و سوگیری های آموزش دیده شما احتمالاً در یک فایل ذخیره می شوند. برای فراهم کردن زمان اجرا NNAPI با دسترسی کارآمد به این داده ها، با فراخوانی تابع ANeuralNetworksMemory_createFromFd()
و ارسال توصیفگر فایل فایل داده باز شده، یک نمونه ANeuralNetworksMemory
ایجاد کنید. شما همچنین پرچم های حفاظت از حافظه و یک آفست را مشخص می کنید که منطقه حافظه مشترک در فایل شروع می شود.
// 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 از مقادیر اسکالر در فرمت های بولی، 16 بیتی ممیز شناور، 32 بیتی ممیز شناور، عدد صحیح 32 بیتی و 32 بیتی بدون علامت پشتیبانی می کند.
اکثر عملیات در NNAPI شامل تانسورها هستند. تانسورها آرایه های n بعدی هستند. NNAPI از تانسورهایی با ممیز شناور 16 بیتی، ممیز شناور 32 بیتی، کوانتیزه 8 بیتی، کوانتیزه 16 بیتی، عدد صحیح 32 بیتی و مقادیر بولی 8 بیتی پشتیبانی می کند.
به عنوان مثال، شکل 3 مدلی را با دو عمل نشان می دهد: یک جمع و به دنبال آن یک ضرب. مدل یک تانسور ورودی می گیرد و یک تانسور خروجی تولید می کند.
مدل فوق دارای هفت عملوند است. این عملوندها به طور ضمنی با نمایه ترتیبی که به مدل اضافه می شوند، شناسایی می شوند. عملوند اول اضافه شده دارای اندیس 0، دومی شاخص 1 و غیره است. عملوندهای 1، 2، 3 و 5 عملوندهای ثابت هستند.
ترتیب اضافه کردن عملوندها مهم نیست. برای مثال، عملوند خروجی مدل می تواند اولین موردی باشد که اضافه می شود. بخش مهم این است که هنگام ارجاع به یک عملوند از مقدار شاخص صحیح استفاده کنید.
عملوندها انواع دارند. اینها زمانی که به مدل اضافه می شوند مشخص می شوند.
عملوند را نمی توان هم به عنوان ورودی و هم به عنوان خروجی یک مدل استفاده کرد.
هر عملوند یا باید یک ورودی مدل، یک ثابت یا عملوند خروجی دقیقاً یک عملیات باشد.
برای اطلاعات بیشتر در مورد استفاده از عملوندها، به اطلاعات بیشتر درباره عملوندها مراجعه کنید.
عملیات
یک عملیات محاسباتی را که باید انجام شود را مشخص می کند. هر عملیات از این عناصر تشکیل شده است:
- یک نوع عملیات (به عنوان مثال، جمع، ضرب، کانولوشن)،
- فهرستی از شاخص های عملوندهایی که عملیات برای ورودی استفاده می کند و
- لیستی از شاخص های عملوندهایی که عملیات برای خروجی استفاده می کند.
ترتیب در این لیست ها مهم است. برای ورودی ها و خروجی های مورد انتظار هر نوع عملیات ، مرجع NNAPI API را ببینید.
قبل از اضافه کردن عملیات باید عملوندهایی را که یک عملیات مصرف یا تولید می کند به مدل اضافه کنید.
ترتیب اضافه کردن عملیات مهم نیست. NNAPI برای تعیین ترتیب اجرای عملیات بر وابستگی های ایجاد شده توسط نمودار محاسباتی عملوندها و عملیات تکیه دارد.
عملیاتی که NNAPI پشتیبانی می کند در جدول زیر خلاصه شده است:
مشکل شناخته شده در سطح 28 API: هنگام انتقال تانسورهای ANEURALNETWORKS_TENSOR_QUANT8_ASYMM
به عملیات ANEURALNETWORKS_PAD
، که در Android 9 (سطح API 28) و بالاتر در دسترس است، ممکن است خروجی NNAPI با خروجی چارچوبهای یادگیری ماشینی سطح بالاتر، مانند TetLow، مطابقت نداشته باشد. . در عوض باید فقط ANEURALNETWORKS_TENSOR_FLOAT32
را پاس کنید. مشکل در اندروید 10 (سطح API 29) و بالاتر حل شده است.
ساخت مدل ها
در مثال زیر، مدل دو عملیاتی موجود در شکل 3 را ایجاد می کنیم.
برای ساخت مدل، مراحل زیر را دنبال کنید:
تابع
ANeuralNetworksModel_create()
را برای تعریف یک مدل خالی فراخوانی کنید.ANeuralNetworksModel* model = NULL; ANeuralNetworksModel_create(&model);
با فراخوانی
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برای عملوندهایی که مقادیر ثابتی دارند، مانند وزنها و بایاسهایی که برنامه شما از یک فرآیند آموزشی به دست میآورد، از توابع
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));برای هر عملیات در گراف جهت دار که می خواهید محاسبه کنید، عملیات را با فراخوانی تابع
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);با فراخوانی تابع
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);
در صورت تمایل، با فراخوانی
ANeuralNetworksModel_relaxComputationFloat32toFloat16()
مشخص کنید که آیاANEURALNETWORKS_TENSOR_FLOAT32
مجاز است با برد یا دقتی به اندازه فرمت ممیز شناور 16 بیتی IEEE 754 محاسبه شود.برای نهایی کردن تعریف مدل خود
ANeuralNetworksModel_finish()
را فراخوانی کنید. اگر خطایی وجود نداشته باشد، این تابع کد نتیجهANEURALNETWORKS_NO_ERROR
را برمیگرداند.ANeuralNetworksModel_finish(model);
هنگامی که یک مدل ایجاد می کنید، می توانید آن را هر تعداد بار کامپایل کنید و هر کامپایل را هر چند بار اجرا کنید.
جریان را کنترل کنید
برای گنجاندن جریان کنترل در یک مدل NNAPI، موارد زیر را انجام دهید:
زیرگراف های اجرایی مربوطه (
then
و زیرگراف هایelse
برای یک عبارتIF
،condition
و زیرگراف هایbody
برای حلقهWHILE
) به عنوان مدل هایANeuralNetworksModel*
مستقل بسازید:ANeuralNetworksModel* thenModel = makeThenModel(); ANeuralNetworksModel* elseModel = makeElseModel();
عملوندهایی ایجاد کنید که به آن مدل ها در مدل حاوی جریان کنترل ارجاع دهند:
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);
عملیات جریان کنترل را اضافه کنید:
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);
تالیف
مرحله کامپایل تعیین می کند که مدل شما روی کدام پردازنده ها اجرا شود و از درایورهای مربوطه می خواهد تا برای اجرای آن آماده شوند. این می تواند شامل تولید کد ماشین مخصوص پردازنده هایی باشد که مدل شما روی آنها اجرا می کند.
برای کامپایل یک مدل، مراحل زیر را دنبال کنید:
برای ایجاد یک نمونه کامپایل جدید، تابع
ANeuralNetworksCompilation_create()
را فراخوانی کنید.// Compile the model ANeuralNetworksCompilation* compilation; ANeuralNetworksCompilation_create(model, &compilation);
به صورت اختیاری، میتوانید از تخصیص دستگاه برای انتخاب صریح دستگاههایی برای اجرا استفاده کنید.
میتوانید بهصورت اختیاری بر نحوه تغییر زمان اجرا بین مصرف انرژی باتری و سرعت اجرا تأثیر بگذارید. می توانید این کار را با فراخوانی
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
: ترجیح میدهید توان پردازشی فریمهای متوالی را به حداکثر برسانید، مانند هنگام پردازش فریمهای متوالی از دوربین.
-
میتوانید بهصورت اختیاری ذخیرهسازی کامپایل را با تماس با
ANeuralNetworksCompilation_setCaching
تنظیم کنید.// Set up compilation caching ANeuralNetworksCompilation_setCaching(compilation, cacheDir, token);
از
getCodeCacheDir()
برایcacheDir
استفاده کنید.token
مشخص شده باید برای هر مدل در برنامه منحصر به فرد باشد.با فراخوانی
ANeuralNetworksCompilation_finish()
تعریف کامپایل را نهایی کنید. اگر خطایی وجود نداشته باشد، این تابع کد نتیجهANEURALNETWORKS_NO_ERROR
را برمیگرداند.ANeuralNetworksCompilation_finish(compilation);
کشف و تخصیص دستگاه
در دستگاههای Android دارای Android 10 (سطح API 29) و بالاتر، NNAPI عملکردهایی را ارائه میکند که به کتابخانهها و برنامههای چارچوب یادگیری ماشین اجازه میدهد اطلاعاتی درباره دستگاههای موجود دریافت کنند و دستگاههایی را برای اجرا مشخص کنند. ارائه اطلاعات در مورد دستگاه های موجود به برنامه ها اجازه می دهد تا نسخه دقیق درایورهای موجود در دستگاه را دریافت کنند تا از ناسازگاری های شناخته شده جلوگیری کنند. با دادن توانایی به برنامهها برای تعیین اینکه کدام دستگاهها بخشهای مختلف یک مدل را اجرا کنند، برنامهها را میتوان برای دستگاه اندرویدی که در آن مستقر هستند بهینه کرد.
کشف دستگاه
از ANeuralNetworks_getDeviceCount
برای دریافت تعداد دستگاه های موجود استفاده کنید. برای هر دستگاه، از ANeuralNetworks_getDevice
استفاده کنید تا یک نمونه ANeuralNetworksDevice
را به یک مرجع به آن دستگاه تنظیم کنید.
هنگامی که یک مرجع دستگاه دارید، می توانید با استفاده از عملکردهای زیر اطلاعات بیشتری در مورد آن دستگاه پیدا کنید:
-
ANeuralNetworksDevice_getFeatureLevel
-
ANeuralNetworksDevice_getName
-
ANeuralNetworksDevice_getType
-
ANeuralNetworksDevice_getVersion
واگذاری دستگاه
از ANeuralNetworksModel_getSupportedOperationsForDevices
برای کشف اینکه کدام عملیات یک مدل را می توان در دستگاه های خاص اجرا کرد، استفاده کنید.
برای کنترل اینکه کدام شتاب دهنده برای اجرا استفاده شود، به جای ANeuralNetworksCompilation_create
، ANeuralNetworksCompilation_createForDevices
را فراخوانی کنید. به طور معمول از شیء ANeuralNetworksCompilation
حاصل استفاده کنید. اگر مدل ارائه شده حاوی عملیاتی باشد که توسط دستگاه های انتخابی پشتیبانی نمی شود، تابع یک خطا برمی گرداند.
اگر چندین دستگاه مشخص شده باشد، زمان اجرا مسئول توزیع کار در بین دستگاه ها است.
مشابه سایر دستگاهها، پیادهسازی CPU NNAPI توسط یک 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
همه دستگاه های موجود از جمله CPU در نظر گرفته می شوند.
توزیع با انتخاب از لیست دستگاه های موجود، برای هر یک از عملیات های موجود در مدل، دستگاهی که از عملیات پشتیبانی می کند و بهترین عملکرد، یعنی سریع ترین زمان اجرا یا کمترین مصرف برق، بسته به اولویت اجرای مشخص شده، اعلام می شود، انجام می شود. توسط مشتری این الگوریتم پارتیشنبندی ناکارآمدیهای احتمالی ناشی از 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
تسریع شده است.
اعدام
مرحله اجرا، مدل را روی مجموعهای از ورودیها اعمال میکند و خروجیهای محاسباتی را در یک یا چند بافر کاربر یا فضای حافظهای که برنامه شما اختصاص داده ذخیره میکند.
برای اجرای یک مدل کامپایل شده، مراحل زیر را دنبال کنید:
تابع
ANeuralNetworksExecution_create()
را برای ایجاد یک نمونه اجرایی جدید فراخوانی کنید.// Run the compiled model against a set of inputs ANeuralNetworksExecution* run1 = NULL; ANeuralNetworksExecution_create(compilation, &run1);
محل خواندن مقادیر ورودی محاسبات توسط برنامه شما را مشخص کنید. برنامه شما می تواند مقادیر ورودی را از بافر کاربر یا فضای حافظه اختصاص داده شده با فراخوانی
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));
محل نوشتن مقادیر خروجی را مشخص کنید. برنامه شما می تواند مقادیر خروجی را در بافر کاربر یا فضای حافظه اختصاص داده شده، با فراخوانی
ANeuralNetworksExecution_setOutput()
یاANeuralNetworksExecution_setOutputFromMemory()
بنویسد.// Set the output float32 myOutput[3][4]; ANeuralNetworksExecution_setOutput(run1, 0, NULL, myOutput, sizeof(myOutput));
با فراخوانی تابع
ANeuralNetworksExecution_startCompute()
اجرا را برای شروع برنامه ریزی کنید. اگر خطایی وجود نداشته باشد، این تابع کد نتیجهANEURALNETWORKS_NO_ERROR
را برمیگرداند.// Starts the work. The work proceeds asynchronously ANeuralNetworksEvent* run1_end = NULL; ANeuralNetworksExecution_startCompute(run1, &run1_end);
تابع
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);
به صورت اختیاری، می توانید مجموعه متفاوتی از ورودی ها را با استفاده از همان نمونه کامپایل برای ایجاد یک نمونه
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 10 (سطح API 29) و بالاتر، NNAPI از اجرای انفجاری از طریق شی ANeuralNetworksBurst
پشتیبانی میکند. اجراهای پشت سر هم دنباله ای از اجراهای یکسان هستند که به صورت متوالی اتفاق می افتند، مانند مواردی که بر روی فریم های ضبط دوربین یا نمونه های صوتی متوالی عمل می کنند. استفاده از اشیاء ANeuralNetworksBurst
ممکن است منجر به اجرای سریعتر شود، زیرا به شتابدهندهها نشان میدهد که ممکن است منابع بین اجراها مجدداً استفاده شوند و شتابدهندهها باید در طول مدت انفجار در حالت عملکرد بالا باقی بمانند.
ANeuralNetworksBurst
تنها یک تغییر کوچک در مسیر اجرای عادی معرفی می کند. همانطور که در قطعه کد زیر نشان داده شده است، با استفاده از ANeuralNetworksBurst_create
یک شی burst ایجاد می کنید:
// 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);
صف های فرمان ناهمزمان و اجرای حصاردار
در اندروید 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
اگر در حین پارتیشن بندی خطایی رخ دهد، اگر یک درایور نتواند یک مدل (قطعه ای) را کامپایل کند، یا اگر یک درایور نتواند یک مدل کامپایل شده (قطعه a) را اجرا کند، NNAPI ممکن است به اجرای CPU خودش برگردد. یا عملیات بیشتر
اگر سرویس گیرنده NNAPI شامل نسخه های بهینه شده عملیات باشد (مثلاً TFLite)، ممکن است غیرفعال کردن بازگشت مجدد CPU و رسیدگی به خرابی ها با اجرای عملیات بهینه سازی شده توسط مشتری مفید باشد.
در اندروید 10، اگر کامپایل با استفاده از ANeuralNetworksCompilation_createForDevices
انجام شود، بازگشت مجدد CPU غیرفعال خواهد شد.
در اندروید P، در صورت عدم موفقیت در اجرای درایور، اجرای NNAPI به CPU برمی گردد. این در Android 10 نیز صادق است، زمانی که از ANeuralNetworksCompilation_create
به جای ANeuralNetworksCompilation_createForDevices
استفاده می شود.
اجرای اول برای آن پارتیشن منفرد برمی گردد، و اگر باز هم شکست خورد، کل مدل را روی CPU دوباره امتحان می کند.
اگر پارتیشن بندی یا کامپایل انجام نشد، کل مدل روی CPU امتحان می شود.
مواردی وجود دارد که برخی از عملیات ها در CPU پشتیبانی نمی شوند، و در چنین شرایطی، کامپایل یا اجرا به جای عقب افتادن با شکست مواجه می شود.
حتی پس از غیرفعال کردن بازگشت مجدد CPU، ممکن است هنوز عملیاتی در مدل وجود داشته باشد که بر روی CPU برنامه ریزی شده است. اگر CPU در لیست پردازندههای ارائهشده به ANeuralNetworksCompilation_createForDevices
باشد و یا تنها پردازندهای باشد که از این عملیات پشتیبانی میکند یا پردازندهای است که بهترین عملکرد را برای این عملیاتها دارد، بهعنوان مجری اصلی (غیر بازگشتی) انتخاب میشود.
برای اطمینان از عدم اجرای CPU، از ANeuralNetworksCompilation_createForDevices
استفاده کنید و در عین حال nnapi-reference
از لیست دستگاه ها حذف کنید. با شروع در اندروید P، میتوان در زمان اجرا در ساختهای DEBUG با تنظیم ویژگی debug.nn.partition
بر روی ۲ غیرفعال کرد.
دامنه های حافظه
در اندروید 11 و بالاتر، NNAPI از دامنههای حافظه پشتیبانی میکند که رابطهای تخصیصدهنده را برای حافظههای غیر شفاف فراهم میکنند. این به برنامهها اجازه میدهد تا حافظههای بومی دستگاه را در بین اجراها منتقل کنند، به طوری که NNAPI هنگام اجرای اجرای متوالی روی همان درایور، دادهها را کپی یا تبدیل غیرضروری نمیکند.
ویژگی دامنه حافظه برای تانسورهایی در نظر گرفته شده است که عمدتاً داخلی درایور هستند و نیازی به دسترسی مکرر به سمت کلاینت ندارند. نمونه هایی از این تانسورها عبارتند از تانسورهای حالت در مدل های دنباله ای. برای تانسورهایی که نیاز به دسترسی مکرر به CPU در سمت کلاینت دارند، به جای آن از استخرهای حافظه مشترک استفاده کنید.
برای تخصیص یک حافظه غیر شفاف، مراحل زیر را انجام دهید:
تابع
ANeuralNetworksMemoryDesc_create()
را برای ایجاد یک توصیفگر حافظه جدید فراخوانی کنید:// Create a memory descriptor ANeuralNetworksMemoryDesc* desc; ANeuralNetworksMemoryDesc_create(&desc);
تمام نقش های ورودی و خروجی مورد نظر را با فراخوانی
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);
در صورت تمایل، ابعاد حافظه را با فراخوانی
ANeuralNetworksMemoryDesc_setDimensions()
مشخص کنید.// Specify the memory dimensions uint32_t dims[] = {3, 4}; ANeuralNetworksMemoryDesc_setDimensions(desc, 2, dims);
تعریف توصیفگر را با فراخوانی
ANeuralNetworksMemoryDesc_finish()
نهایی کنید.ANeuralNetworksMemoryDesc_finish(desc);
با ارسال توصیفگر به
ANeuralNetworksMemory_createFromDesc()
هر تعداد حافظه را که نیاز دارید اختصاص دهید.// Allocate two opaque memories with the descriptor ANeuralNetworksMemory* opaqueMem; ANeuralNetworksMemory_createFromDesc(desc, &opaqueMem);
وقتی دیگر به آن نیاز ندارید، توصیفگر حافظه را آزاد کنید.
ANeuralNetworksMemoryDesc_free(desc);
مشتری فقط میتواند از شی ANeuralNetworksMemory
ایجاد شده با ANeuralNetworksExecution_setInputFromMemory()
یا ANeuralNetworksExecution_setOutputFromMemory()
با توجه به نقشهای مشخص شده در شی ANeuralNetworksMemoryDesc
استفاده کند. آرگومان های offset و length باید روی 0 تنظیم شوند که نشان دهنده استفاده از کل حافظه است. مشتری همچنین میتواند محتویات حافظه را با استفاده از ANeuralNetworksMemory_copy()
به صراحت تنظیم یا استخراج کند.
می توانید خاطرات مبهم با نقش هایی با ابعاد یا رتبه نامشخص ایجاد کنید. در آن صورت، ایجاد حافظه ممکن است با وضعیت ANEURALNETWORKS_OP_FAILED
شکست بخورد، اگر توسط درایور اصلی پشتیبانی نشود. مشتری تشویق می شود تا با اختصاص یک بافر به اندازه کافی بزرگ که توسط Ashmem یا AHardwareBuffer
حالت BLOB پشتیبانی می شود، منطق بازگشتی را پیاده سازی کند.
هنگامی که NNAPI دیگر نیازی به دسترسی به شی حافظه مات ندارد، نمونه ANeuralNetworksMemory
مربوطه را آزاد کنید:
ANeuralNetworksMemory_free(opaqueMem);
عملکرد را اندازه گیری کنید
میتوانید عملکرد برنامه خود را با اندازهگیری زمان اجرا یا با نمایهسازی ارزیابی کنید.
زمان اجرا
هنگامی که می خواهید کل زمان اجرا را از طریق زمان اجرا تعیین کنید، می توانید از API اجرای همزمان استفاده کنید و زمان صرف شده توسط تماس را اندازه گیری کنید. هنگامی که می خواهید کل زمان اجرا را از طریق یک سطح پایین تر از پشته نرم افزار تعیین کنید، می توانید از ANeuralNetworksExecution_setMeasureTiming
و ANeuralNetworksExecution_getDuration
استفاده کنید:
- زمان اجرا در یک شتاب دهنده (نه در درایور که روی پردازنده میزبان اجرا می شود).
- زمان اجرا در راننده، از جمله زمان روی پدال گاز.
زمان اجرا در درایور شامل سرباری مانند زمان اجرا و IPC مورد نیاز برای زمان اجرا برای برقراری ارتباط با درایور نمی شود.
این APIها به جای زمانی که یک راننده یا شتاب دهنده به انجام استنتاج اختصاص می دهد، مدت زمان بین کار ارسال شده و رویدادهای کار تکمیل شده را اندازه گیری می کند، که احتمالاً با تغییر زمینه قطع می شود.
به عنوان مثال، اگر استنتاج 1 شروع شود، سپس راننده کار را برای انجام استنتاج 2 متوقف کند، سپس آن را از سر گرفته و استنتاج 1 را تکمیل کند، زمان اجرای استنتاج 1 شامل زمانی است که کار برای انجام استنتاج 2 متوقف شده است.
این اطلاعات زمان ممکن است برای استقرار تولید یک برنامه کاربردی برای جمع آوری تله متری برای استفاده آفلاین مفید باشد. میتوانید از دادههای زمانبندی برای تغییر برنامه برای عملکرد بالاتر استفاده کنید.
هنگام استفاده از این قابلیت، موارد زیر را در نظر داشته باشید:
- جمع آوری اطلاعات زمان بندی ممکن است هزینه عملکردی داشته باشد.
- فقط یک راننده قادر است زمان صرف شده در خود یا روی شتاب دهنده را محاسبه کند، به استثنای زمان صرف شده در زمان اجرا NNAPI و IPC.
- شما میتوانید از این APIها فقط با
ANeuralNetworksExecution
استفاده کنید که باANeuralNetworksCompilation_createForDevices
باnumDevices = 1
ایجاد شده است. - هیچ راننده ای لازم نیست تا بتواند اطلاعات زمان بندی را گزارش کند.
برنامه خود را با Android Systrace نمایه کنید
با شروع Android 10، NNAPI بهطور خودکار رویدادهای systrace را تولید میکند که میتوانید از آنها برای نمایهسازی برنامهتان استفاده کنید.
منبع NNAPI با یک ابزار parse_systrace
برای پردازش وقایع systrace ایجاد شده توسط برنامه شما و ایجاد یک نمای جدول که زمان صرف شده در مراحل مختلف چرخه چرخه مدل (لحظه ای ، آماده سازی ، اجرای تدوین و خاتمه) و لایه های مختلف برنامه ها را نشان می دهد ، ارائه می شود. . لایه هایی که برنامه شما تقسیم شده است عبارتند از:
-
Application
: کد اصلی برنامه -
Runtime
: زمان اجرا NNAPI -
IPC
: ارتباط بین فرآیند بین زمان اجرا NNAPI و کد درایور -
Driver
: فرآیند درایور شتاب دهنده.
داده های Profiling Analysys را تولید کنید
با فرض اینکه درخت منبع AOSP را در $ android_build_top بررسی کرده اید و با استفاده از مثال طبقه بندی تصویر TFLITE به عنوان برنامه هدف ، می توانید داده های پروفایل NNAPI را با مراحل زیر تولید کنید:
- Android Systrace را با دستور زیر شروع کنید:
$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
دادن به تعاملی است.
- پس از شروع جمع کننده Systrace ، برنامه خود را شروع کرده و تست معیار خود را اجرا کنید.
در مورد ما می توانید اگر برنامه قبلاً نصب شده است ، برنامه طبقه بندی تصویر را از Android Studio یا مستقیماً از UI تلفن تست خود شروع کنید. برای تولید برخی از داده های NNAPI ، باید با انتخاب NNAPI به عنوان دستگاه هدف در گفتگوی پیکربندی برنامه ، برنامه را برای استفاده از NNAPI پیکربندی کنید.
پس از اتمام آزمون ، Systrace را با فشار دادن
enter
در ترمینال کنسول فعال از مرحله 1 خاتمه دهید.اجرای برنامه
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 مبتنی بر قابلیت داخلی Android Systrace است. با استفاده از API Systrace ( برای جاوا ، برای برنامه های بومی ) می توانید اثری را برای عملیات خاص در برنامه خود اضافه کنید.
برای مرتبط کردن رویدادهای سفارشی خود با فازهای چرخه عمر برنامه ، نام رویداد خود را با یکی از رشته های زیر آماده کنید:
-
[NN_LA_PI]
: رویداد سطح برنامه برای اولیه سازی -
[NN_LA_PP]
: رویداد سطح برنامه برای آماده سازی -
[NN_LA_PC]
: رویداد سطح برنامه برای تدوین -
[NN_LA_PE]
: رویداد سطح برنامه برای اجرای
در اینجا نمونه ای از چگونگی تغییر کد مثال طبقه بندی تصویر tflite با اضافه کردن یک بخش runInferenceModel
برای مرحله Execution
و لایه Application
حاوی بخش های دیگر preprocessBitmap
که در آثار NNAPI در نظر گرفته نمی شود ، آورده شده است. بخش runInferenceModel
بخشی از رویدادهای Systrace است که توسط تجزیه کننده NNAPI Systrace پردازش می شود:
کاتلین
/** 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 ListrecognizeImage(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 با اجازه دادن به یک برنامه کاربردی می تواند اولویت های نسبی مدل های خود ، حداکثر زمان انتظار برای تهیه یک مدل معین را نشان دهد ، و حداکثر مدت زمان انتظار برای تکمیل محاسبه با توجه Android 11 همچنین کدهای نتیجه NNAPI اضافی را معرفی می کند که برنامه ها را قادر می سازد تا خرابی هایی مانند مهلت اجرای از دست رفته را درک کنند.
اولویت بار کاری را تعیین کنید
برای تعیین اولویت بار کاری NNAPI ، قبل از تماس با ANeuralNetworksCompilation_setPriority()
ANeuralNetworksCompilation_finish()
تماس بگیرید.
ضرب الاجل تعیین کنید
برنامه ها می توانند مهلت هایی را برای تدوین مدل و استنتاج تعیین کنند.
- برای تنظیم زمان بندی تدوین ، قبل از تماس با
ANeuralNetworksCompilation_finish()
ANeuralNetworksCompilation_setTimeout()
بگیرید. - برای تنظیم زمان استنتاج ، قبل از شروع تدوین ، با
ANeuralNetworksExecution_setTimeout()
تماس بگیرید.
اطلاعات بیشتر در مورد اپراند
در بخش زیر موضوعات پیشرفته در مورد استفاده از اپراند وجود دارد.
تنشور کمکی
تانسور کمکی یک روش جمع و جور برای نشان دادن یک آرایه N بعدی از مقادیر نقطه شناور است.
NNAPI از تانسرهای کمکی نامتقارن 8 بیتی پشتیبانی می کند. برای این تانسرها ، مقدار هر سلول توسط یک عدد صحیح 8 بیتی نشان داده شده است. همراه با تانسور یک مقیاس و یک مقدار نقطه صفر است. اینها برای تبدیل عدد صحیح 8 بیتی به مقادیر نقطه شناور که در حال نمایش هستند استفاده می شود.
فرمول این است:
(cellValue - zeroPoint) * scale
در جایی که مقدار Zeropoint یک عدد صحیح 32 بیتی و مقیاس یک نقطه شناور 32 بیتی است.
در مقایسه با تانسور از مقادیر نقطه شناور 32 بیتی ، تنسورهای کمیت 8 بیتی دارای دو مزیت هستند:
- برنامه شما کوچکتر است ، زیرا وزن های آموزش دیده یک چهارم از اندازه تانسور 32 بیتی را می گیرند.
- محاسبات اغلب می توانند سریعتر اجرا شوند. این به دلیل کمتری از داده هایی است که باید از حافظه و کارآیی پردازنده هایی مانند DSP در انجام ریاضی عدد صحیح بدست آورند.
در حالی که امکان تبدیل یک مدل نقطه شناور به یک مدل کمیت امکان پذیر است ، تجربه ما نشان داده است که با آموزش یک مدل کمکی به طور مستقیم نتایج بهتری حاصل می شود. در واقع ، شبکه عصبی یاد می گیرد که افزایش دانه بندی هر مقدار را جبران کند. برای هر تانسور کمکی ، مقادیر مقیاس و Zeropoint در طی فرآیند آموزش تعیین می شود.
در NNAPI ، شما با تنظیم نوع قسمت نوع ساختار داده ANeuralNetworksOperandType
به ANEURALNETWORKS_TENSOR_QUANT8_ASYMM
، انواع تانسور کمیت را تعریف می کنید. شما همچنین مقیاس و مقدار ZerOpoint تانسور را در آن ساختار داده مشخص می کنید.
علاوه بر تانسرهای کمکی نامتقارن 8 بیتی ، NNAPI از موارد زیر پشتیبانی می کند:
-
ANEURALNETWORKS_TENSOR_QUANT8_SYMM_PER_CHANNEL
که می توانید برای نشان دادن وزنه ها به عملیاتCONV/DEPTHWISE_CONV/TRANSPOSED_CONV
استفاده کنید. -
ANEURALNETWORKS_TENSOR_QUANT16_ASYMM
که می توانید برای وضعیت داخلیQUANTIZED_16BIT_LSTM
استفاده کنید. -
ANEURALNETWORKS_TENSOR_QUANT8_SYMM
که می تواند ورودی بهANEURALNETWORKS_DEQUANTIZE
باشد.
عملیات اختیاری
چند عمل ، مانند ANEURALNETWORKS_LSH_PROJECTION
، عملیات های اختیاری را انجام دهید. برای نشان دادن این مدل که عملگر اختیاری حذف شده است ، با عملکرد ANeuralNetworksModel_setOperandValue()
تماس بگیرید ، عبور NULL
برای بافر و 0 برای طول.
اگر تصمیم در مورد اینکه آیا این عمل برای هر اجرا وجود دارد یا خیر ، متفاوت است ، شما نشان می دهید که عمل با استفاده از ANeuralNetworksExecution_setInput()
یا ANeuralNetworksExecution_setOutput()
حذف می شود ، عبور NULL
برای بافر و 0 برای طول.
تانسور از رتبه ناشناخته
Android 9 (سطح API 28) اپراند مدل از ابعاد ناشناخته اما رتبه شناخته شده (تعداد ابعاد) را معرفی کرد. Android 10 (API سطح 29) تانسرهای دارای رتبه ناشناخته را معرفی کرد ، همانطور که در AneuralNetworkSoperAndType نشان داده شده است.
معیار NNAPI
معیار NNAPI در AOSP در platform/test/mlts/benchmark
(برنامه معیار) و platform/test/mlts/models
(مدل ها و مجموعه داده ها) در دسترس است.
این معیار تأخیر و دقت را ارزیابی می کند و درایورها را با همان کارهایی که با استفاده از TensorFlow Lite در حال اجرا بر روی CPU انجام می شود ، برای همان مدل ها و مجموعه داده ها مقایسه می کند.
برای استفاده از معیار ، موارد زیر را انجام دهید:
یک دستگاه Android Target را به رایانه خود وصل کنید ، یک پنجره ترمینال را باز کنید و مطمئن شوید که دستگاه از طریق ADB قابل دسترسی است.
اگر بیش از یک دستگاه Android متصل است ، متغیر محیط
ANDROID_SERIAL
دستگاه هدف را صادر کنید.به فهرست منبع سطح بالا Android بروید.
دستورات زیر را اجرا کنید:
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
: اجرای عملیات با استفاده از اجرای CPU NNAPI -
manager
: برنامه های افزودنی NNAPI ، رابط های موجود و اطلاعات مربوط به قابلیت ها -
all
یا1
: تمام عناصر فوق
به عنوان مثال ، برای فعال کردن ورود به سیستم کامل Verbose ، از دستور adb shell setprop debug.nn.vlog all
استفاده کنید. برای غیرفعال کردن ورود به سیستم Verbose ، از دستور adb shell setprop debug.nn.vlog '""'
استفاده کنید.
پس از فعال شدن ، ورود به سیستم Verbose ورودی های ورود به سیستم را در سطح اطلاعات با یک برچسب تنظیم شده روی نام فاز یا مؤلفه ایجاد می کند.
در کنار پیام های کنترل شده debug.nn.vlog
، اجزای API NNAPI ورودی های دیگر را در سطوح مختلف ارائه می دهند که هر یک از آنها با استفاده از یک برچسب ورود به سیستم خاص استفاده می کنند.
برای به دست آوردن لیستی از مؤلفه ها ، با استفاده از عبارت زیر ، درخت منبع را جستجو کنید:
grep -R 'define LOG_TAG' | awk -F '"' '{print $2}' | sort -u | egrep -v "Sample|FileTag|test"
این عبارت در حال حاضر برچسب های زیر را برمی گرداند:
- بیدر
- تماس های تلفنی
- گرداننده
- CPUEXTORTOR
- اعدام کننده
- اعدام کننده
- اعدام
- برنامه اعدام
- فیبوناکسیور
- گرافیک
- indexedshapewrapper
- یون Watcher
- مدیر
- حافظه
- حافظه
- متامودل
- مدلی
- مدل ساز
- کارگاههای عصبی
- عملگر
- عملیات
- عملیات
- بسته بندی
- توکننده
- خفه کننده
- Utils
- معتبر
- نسخه های نسخه
برای کنترل سطح پیام های ورود به سیستم توسط 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
است. برای تولید اطلاعات ورود به سیستم Verbose ، هنوز هم باید debug.nn.vlog
را روی all
تنظیم کنید.
شبکه های عصبی Android API (NNAPI) A Android C API است که برای اجرای عملیات محاسباتی فشرده برای یادگیری ماشین در دستگاه های Android طراحی شده است. NNAPI به گونه ای طراحی شده است که یک لایه پایه از عملکرد برای چارچوب های یادگیری ماشین سطح بالاتر ، مانند Tensorflow Lite و Caffe2 ، که شبکه های عصبی را می سازند و آموزش می دهند ، طراحی شده است. API در تمام دستگاه های Android در حال اجرا Android 8.1 (API سطح 27) یا بالاتر است.
NNAPI با استفاده از داده های دستگاه های Android به مدل های قبلاً تعریف شده و تعریف شده ، از استنتاج پشتیبانی می کند. نمونه هایی از استنباط شامل طبقه بندی تصاویر ، پیش بینی رفتار کاربر و انتخاب پاسخ های مناسب به پرس و جو جستجو است.
استنباط در دستگاه فواید بسیاری دارد:
- تأخیر : نیازی به ارسال درخواست از طریق اتصال شبکه نیست و منتظر پاسخ است. به عنوان مثال ، این می تواند برای برنامه های ویدیویی که فریم های پی در پی از یک دوربین را پردازش می کنند ، بسیار مهم باشد.
- در دسترس بودن : برنامه حتی در خارج از پوشش شبکه اجرا می شود.
- سرعت : سخت افزار جدید که مختص پردازش شبکه عصبی است ، محاسبات بسیار سریعتر از یک پردازنده با هدف کلی را به تنهایی فراهم می کند.
- حریم خصوصی : داده ها دستگاه Android را ترک نمی کنند.
- هزینه : در صورت انجام همه محاسبات در دستگاه Android ، هیچ مزرعه سرور لازم نیست.
همچنین معامله هایی وجود دارد که یک توسعه دهنده باید در نظر داشته باشد:
- استفاده از سیستم : ارزیابی شبکه های عصبی شامل محاسبات زیادی است که می تواند میزان مصرف باتری را افزایش دهد. اگر این نگرانی برای برنامه شما است ، به خصوص برای محاسبات طولانی مدت ، باید نظارت بر سلامت باتری را در نظر بگیرید.
- اندازه برنامه : به اندازه مدل های خود توجه کنید. مدل ها ممکن است چندین مگابایت فضا را به خود اختصاص دهند. اگر بسته بندی مدل های بزرگ در APK شما به طور غیرقانونی بر کاربران شما تأثیر می گذارد ، ممکن است بخواهید بارگیری مدل ها پس از نصب برنامه ، استفاده از مدل های کوچکتر یا اجرای محاسبات خود در ابر را در نظر بگیرید. NNAPI عملکردی را برای مدل های در حال اجرا در ابر فراهم نمی کند.
برای دیدن نمونه ای از نحوه استفاده از NNAPI ، به نمونه API شبکه های عصبی Android مراجعه کنید.
زمان اجرا API شبکه های عصبی را درک کنید
به معنای NNAPI توسط کتابخانه های یادگیری ماشین ، چارچوب ها و ابزارهایی است که به توسعه دهندگان اجازه می دهد مدل های خود را از کار خود خارج کنند و آنها را در دستگاه های Android مستقر کنند. برنامه ها به طور معمول از NNAPI استفاده نمی کنند ، اما در عوض از چارچوب های یادگیری ماشین سطح بالاتر استفاده می کنند. این چارچوب ها به نوبه خود می توانند از NNAPI برای انجام عملیات استنتاج شتاب سخت افزاری در دستگاه های پشتیبانی شده استفاده کنند.
براساس نیازهای یک برنامه و قابلیت های سخت افزاری در یک دستگاه Android ، زمان اجرای شبکه عصبی Android می تواند بار کار محاسبات را در پردازنده های موجود در دستگاه های موجود ، از جمله سخت افزار شبکه عصبی اختصاصی ، واحدهای پردازش گرافیک (GPU) و پردازنده های سیگنال دیجیتال توزیع کند (DSPS ).
برای دستگاه های Android که فاقد درایور فروشنده تخصصی هستند ، NNAPI Runtime درخواست های CPU را اجرا می کند.
شکل 1 معماری سیستم سطح بالا برای NNAPI را نشان می دهد.
مدل برنامه نویسی API شبکه های عصبی
برای انجام محاسبات با استفاده از NNAPI ، ابتدا باید یک نمودار کارگردانی بسازید که محاسبات را برای انجام آن تعریف می کند. این نمودار محاسبه ، همراه با داده های ورودی شما (به عنوان مثال ، وزن و تعصب که از یک چارچوب یادگیری ماشین عبور می کنند) ، مدل را برای ارزیابی زمان اجرا NNAPI تشکیل می دهد.
NNAPI از چهار انتزاع اصلی استفاده می کند:
- مدل : نمودار محاسباتی از عملیات ریاضی و مقادیر ثابت آموخته شده از طریق یک فرآیند آموزش. این عملیات مخصوص شبکه های عصبی است. آنها شامل فعال سازی 2 بعدی (2D) ، فعال سازی لجستیک ( سیگموئید ) ، فعال سازی خطی اصلاح شده (RELU) و موارد دیگر هستند. ایجاد یک مدل یک عمل همزمان است. پس از ایجاد موفقیت آمیز ، می توان از آن در میان موضوعات و تالیف استفاده مجدد کرد. در NNAPI ، یک مدل به عنوان نمونه
ANeuralNetworksModel
نشان داده شده است. - گردآوری : پیکربندی برای تهیه یک مدل NNAPI در کد سطح پایین را نشان می دهد. ایجاد یک مجموعه یک عمل همزمان است. پس از ایجاد موفقیت آمیز ، می توان از آن در موضوعات و اعدام ها استفاده مجدد کرد. در NNAPI ، هر مجموعه به عنوان یک نمونه
ANeuralNetworksCompilation
نشان داده شده است. - حافظه : حافظه مشترک ، فایلهای نقشه برداری شده حافظه و بافرهای حافظه مشابه را نشان می دهد. استفاده از بافر حافظه به داده های انتقال زمان اجرا NNAPI به درایورها اجازه می دهد تا کارآمدتر شوند. یک برنامه به طور معمول یک بافر حافظه مشترک ایجاد می کند که شامل هر تانسور مورد نیاز برای تعریف یک مدل است. همچنین می توانید از بافرهای حافظه برای ذخیره ورودی ها و خروجی ها برای نمونه اجرا استفاده کنید. در NNAPI ، هر بافر حافظه به عنوان نمونه
ANeuralNetworksMemory
نشان داده شده است. اجرای : رابط برای استفاده از یک مدل NNAPI در مجموعه ای از ورودی ها و جمع آوری نتایج. اجرای می تواند به صورت همزمان یا ناهمزمان انجام شود.
برای اجرای ناهمزمان ، چندین موضوع می توانند در همان اجرای منتظر بمانند. پس از اتمام این اجرای ، همه موضوعات منتشر می شوند.
در NNAPI ، هر اعدام به عنوان نمونه
ANeuralNetworksExecution
ارائه می شود.
شکل 2 جریان برنامه نویسی اساسی را نشان می دهد.
بقیه این بخش مراحل تنظیم مدل NNAPI خود را برای انجام محاسبات ، تدوین مدل و اجرای مدل کامپایل شده توصیف می کند.
دسترسی به داده های آموزشی
وزن و داده های تعصب شما به احتمال زیاد در یک پرونده ذخیره می شوند. برای ارائه زمان اجرا NNAPI با دسترسی کارآمد به این داده ها ، با فراخوانی عملکرد ANeuralNetworksMemory_createFromFd()
و عبور در توصیف کننده پرونده پرونده داده باز شده ، یک نمونه ANeuralNetworksMemory
ایجاد کنید. همچنین پرچم های حفاظت از حافظه و یک افست را که در آن منطقه حافظه مشترک در پرونده شروع می شود ، مشخص می کنید.
// 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 است. هر مدل توسط یک یا چند عمل و عملیات تعریف می شود.
عملوندها
Operands اشیاء داده ای هستند که در تعریف نمودار استفاده می شوند. اینها شامل ورودی ها و خروجی های مدل ، گره های میانی است که حاوی داده هایی است که از یک عمل به عمل دیگر جریان می یابد و ثابت هایی که به این عملیات منتقل می شوند.
دو نوع اپراند وجود دارد که می توانند به مدل های NNAPI اضافه شوند: مقیاس و تنش .
یک مقیاس یک مقدار واحد را نشان می دهد. NNAPI از مقادیر مقیاس در Boolean ، نقطه شناور 16 بیتی ، نقطه شناور 32 بیتی ، عدد صحیح 32 بیتی و قالب های عدد صحیح 32 بیتی بدون امضا پشتیبانی می کند.
بیشتر عملیات در NNAPI شامل تانسور است. تنسورها آرایه های بعدی هستند. NNAPI از تانسور با نقطه شناور 16 بیتی ، نقطه شناور 32 بیتی ، 8 بیتی کمیت ، عدد 16 بیتی ، عدد صحیح 32 بیتی و مقادیر بولی 8 بیتی پشتیبانی می کند.
به عنوان مثال ، شکل 3 یک مدل با دو عمل را نشان می دهد: یک افزودنی و به دنبال آن ضرب. این مدل یک تانسور ورودی را می گیرد و یک تانسور خروجی تولید می کند.
مدل فوق دارای هفت عمل است. این عملیات ها به طور ضمنی توسط شاخص نظمی که در آن به مدل اضافه می شوند ، مشخص می شوند. اولین عمل اضافه شده دارای شاخص 0 ، دوم شاخص 1 و غیره است. عملیات 1 ، 2 ، 3 و 5 عملیات ثابت هستند.
ترتیب اضافه کردن عملگر مهم نیست. به عنوان مثال ، عملیات خروجی مدل می تواند اولین مورد اضافه شده باشد. بخش مهم استفاده از مقدار صحیح شاخص هنگام مراجعه به یک عملگر است.
اپل ها انواع دارند. اینها هنگام اضافه شدن به مدل مشخص می شوند.
از یک عملگر نمی توان به عنوان ورودی و خروجی یک مدل استفاده کرد.
هر عملگر باید یک ورودی مدل ، ثابت یا عملیاتی باشد که دقیقاً یک عملیات را انجام می دهد.
برای کسب اطلاعات بیشتر در مورد استفاده از OperAnds ، اطلاعات بیشتری در مورد Operands مشاهده کنید.
عملیات
عملیاتی محاسباتی را که باید انجام شود مشخص می کند. هر عملیات شامل این عناصر است:
- یک نوع عملیات (به عنوان مثال ، علاوه بر این ، ضرب ، نتیجه) ،
- لیستی از شاخص های عملیاتی که این عملیات برای ورودی استفاده می کند ، و
- لیستی از شاخص های عملیاتی که این عملیات برای خروجی استفاده می کند.
ترتیب در این لیست ها مهم است. برای ورودی ها و خروجی های مورد انتظار از هر نوع عمل ، به مرجع API NNAPI مراجعه کنید.
شما باید قبل از افزودن عملیات ، عملیاتی را که یک عملیات مصرف می کند یا به آن تولید می کند ، اضافه کنید.
ترتیب اضافه کردن عملیات مهم نیست. NNAPI برای تعیین ترتیب اجرای عملیات به وابستگی های ایجاد شده توسط نمودار محاسبه عملیات و عملیات متکی است.
عملیاتی که NNAPI از آنها پشتیبانی می کند در جدول زیر خلاصه شده است:
مسئله شناخته شده در سطح API 28: هنگام عبور از ANEURALNETWORKS_TENSOR_QUANT8_ASYMM
به عملیات ANEURALNETWORKS_PAD
، که در Android 9 (API سطح 28) موجود است ، خروجی از NNAPI ممکن است با خروجی از چارچوب های یادگیری دستگاه بالاتر مطابقت نداشته باشد ، مانند Tensorflow Lite Lite Lite . در عوض باید فقط ANEURALNETWORKS_TENSOR_FLOAT32
را پشت سر بگذارید. مسئله در اندروید 10 (سطح API 29) و بالاتر حل شده است.
ساخت مدل ها
در مثال زیر ، ما مدل دو عمل موجود در شکل 3 را ایجاد می کنیم.
برای ساخت مدل ، این مراحل را دنبال کنید:
برای تعریف یک مدل خالی ، با تابع
ANeuralNetworksModel_create()
تماس بگیرید.ANeuralNetworksModel* model = NULL; ANeuralNetworksModel_create(&model);
با فراخوانی
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برای عملكس هایی كه دارای مقادیر ثابت هستند ، مانند وزن و تعصب كه برنامه شما از یك فرآیند آموزش به دست می آورد ، از توابع
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));برای هر عملیاتی در نمودار کارگردانی که می خواهید محاسبه کنید ، با فراخوانی تابع
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);مشخص کنید که کدام مدل باید با فراخوانی عملکرد
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);
به صورت اختیاری ، مشخص کنید که آیا
ANEURALNETWORKS_TENSOR_FLOAT32
مجاز است با دامنه یا دقت به اندازه کمترین قالب شناور IEEE 754 با استفاده ازANeuralNetworksModel_relaxComputationFloat32toFloat16()
محاسبه شود.برای نهایی کردن تعریف مدل خود با
ANeuralNetworksModel_finish()
تماس بگیرید. اگر خطایی وجود نداشته باشد ، این عملکرد کد نتیجهANEURALNETWORKS_NO_ERROR
را برمی گرداند.ANeuralNetworksModel_finish(model);
پس از ایجاد یک مدل ، می توانید هر تعداد بار آن را کامپایل کنید و هر یک از بار دیگر را اجرا کنید.
جریان را کنترل کنید
برای ترکیب جریان کنترل در یک مدل NNAPI ، موارد زیر را انجام دهید:
زیرگرافهای اجرای مربوطه (
then
و زیرگرافهایelse
برای یک عبارتIF
،condition
و زیرگرافهایbody
برایWHILE
) را به عنوان مدل های مستقلANeuralNetworksModel*
بسازید:ANeuralNetworksModel* thenModel = makeThenModel(); ANeuralNetworksModel* elseModel = makeElseModel();
Operands ایجاد کنید که آن مدل ها را در مدل حاوی جریان کنترل ارجاع دهید:
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);
عملکرد جریان کنترل را اضافه کنید:
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);
تالیف
مرحله تدوین مشخص می کند که مدل شما کدام پردازنده ها را اجرا می کنند و از رانندگان مربوطه می خواهند که برای اجرای آن آماده شوند. این می تواند شامل تولید کد دستگاه خاص برای پردازنده هایی باشد که مدل شما روی آن اجرا می شود.
برای تهیه یک مدل ، این مراحل را دنبال کنید:
برای ایجاد یک نمونه تدوین جدید ، با عملکرد
ANeuralNetworksCompilation_create()
تماس بگیرید.// Compile the model ANeuralNetworksCompilation* compilation; ANeuralNetworksCompilation_create(model, &compilation);
به صورت اختیاری ، می توانید از Assignment دستگاه استفاده کنید تا صریحاً انتخاب کنید که چه دستگاه هایی را برای اجرای آن اجرا کنید.
شما می توانید به صورت اختیاری تأثیر بگذارد که زمان اجرا بین مصرف باتری و سرعت اجرای آن انجام می شود. شما می توانید این کار را با فراخوانی
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
: ترجیح می دهید حداکثر توان قاب های پی در پی ، مانند هنگام پردازش فریم های پی در پی از دوربین.
-
با فراخوانی
ANeuralNetworksCompilation_setCaching
می توانید به صورت اختیاری حافظه پنهان را تنظیم کنید.// Set up compilation caching ANeuralNetworksCompilation_setCaching(compilation, cacheDir, token);
از
getCodeCacheDir()
برایcacheDir
استفاده کنید.token
مشخص شده باید برای هر مدل موجود در برنامه منحصر به فرد باشد.تعریف تدوین را با فراخوانی
ANeuralNetworksCompilation_finish()
. اگر خطایی وجود نداشته باشد ، این عملکرد کد نتیجهANEURALNETWORKS_NO_ERROR
را برمی گرداند.ANeuralNetworksCompilation_finish(compilation);
کشف و واگذاری دستگاه
NNAPI در دستگاه های Android که Android 10 (API سطح 29) و بالاتر را اجرا می کنند ، توابع را ارائه می دهد که به کتابخانه ها و برنامه های چارچوب یادگیری ماشین اجازه می دهد تا اطلاعات مربوط به دستگاه های موجود را دریافت کنند و دستگاه هایی را برای اجرای استفاده کنند. ارائه اطلاعات در مورد دستگاه های موجود به برنامه ها اجازه می دهد تا نسخه دقیق درایورهای موجود در یک دستگاه را برای جلوگیری از ناسازگاری های شناخته شده دریافت کنند. با ارائه برنامه ها امکان مشخص کردن دستگاه ها برای اجرای بخش های مختلف یک مدل ، برنامه ها را می توان برای دستگاه Android که در آن مستقر هستند بهینه سازی کنند.
کشف دستگاه
برای به دست آوردن تعداد دستگاه های موجود ANeuralNetworks_getDeviceCount
استفاده کنید. برای هر دستگاه ، از ANeuralNetworks_getDevice
استفاده کنید تا یک نمونه ANeuralNetworksDevice
را برای مراجعه به آن دستگاه تنظیم کنید.
پس از مرجع دستگاه ، می توانید با استفاده از توابع زیر اطلاعات اضافی در مورد آن دستگاه کسب کنید:
-
ANeuralNetworksDevice_getFeatureLevel
-
ANeuralNetworksDevice_getName
-
ANeuralNetworksDevice_getType
-
ANeuralNetworksDevice_getVersion
واگذاری دستگاه
از ANeuralNetworksModel_getSupportedOperationsForDevices
استفاده کنید تا کشف کنید که کدام یک از یک مدل را می توان در دستگاه های خاص اجرا کرد.
برای کنترل استفاده از شتاب دهنده ها برای اجرای ، با ANeuralNetworksCompilation_createForDevices
به جای ANeuralNetworksCompilation_create
تماس بگیرید. از شیء ANeuralNetworksCompilation
حاصل به عنوان عادی استفاده کنید. اگر مدل ارائه شده حاوی عملیاتی باشد که توسط دستگاه های انتخاب شده پشتیبانی نمی شوند ، این عملکرد خطایی را برمی گرداند.
اگر چندین دستگاه مشخص شده باشد ، زمان اجرا وظیفه توزیع کار در دستگاه ها را بر عهده دارد.
مشابه سایر دستگاه ها ، اجرای CPU NNAPI توسط یک ANeuralNetworksDevice
با نام nnapi-reference
و نوع ANEURALNETWORKS_DEVICE_TYPE_CPU
نشان داده شده است. هنگام تماس با ANeuralNetworksCompilation_createForDevices
، از اجرای CPU برای رسیدگی به موارد خرابی برای تدوین و اجرای مدل استفاده نمی شود.
این یک مسئولیت برنامه برای تقسیم یک مدل به زیر مدل های زیر است که می توانند در دستگاه های مشخص شده اجرا شوند. برنامه هایی که نیازی به انجام پارتیشن بندی دستی ندارند ، باید همچنان با استفاده از همه دستگاه های موجود (از جمله CPU) برای تسریع در مدل ، به عنوان ساده تر ANeuralNetworksCompilation_create
تماس بگیرند. اگر این مدل توسط دستگاههایی که شما با استفاده از ANeuralNetworksCompilation_createForDevices
مشخص کرده اید ، به طور کامل پشتیبانی نمی شود ، ANEURALNETWORKS_BAD_DATA
بازگردانده می شود.
پارتیشن بندی مدل
هنگامی که چندین دستگاه برای مدل در دسترس است ، NNAPI Runtime کار را در دستگاه ها توزیع می کند. به عنوان مثال ، اگر بیش از یک دستگاه به ANeuralNetworksCompilation_createForDevices
ارائه شود ، تمام موارد مشخص شده هنگام اختصاص کار در نظر گرفته می شوند. توجه داشته باشید که اگر دستگاه CPU در لیست نباشد ، اجرای CPU غیرفعال خواهد شد. هنگام استفاده از ANeuralNetworksCompilation_create
تمام دستگاه های موجود از جمله CPU مورد توجه قرار می گیرند.
توزیع با انتخاب از لیست دستگاه های موجود ، برای هر یک از عملیات موجود در مدل ، دستگاهی که از عملیات پشتیبانی می کند و بهترین عملکرد را اعلام می کند ، یعنی سریعترین زمان اجرای یا کمترین مصرف برق ، بسته به ترجیح اجرا مشخص شده ، انجام می شود. توسط مشتری این الگوریتم پارتیشن بندی ناکارآمدی احتمالی ناشی از IO بین پردازنده های مختلف را به خود اختصاص نمی دهد ، بنابراین ، هنگام مشخص کردن چندین پردازنده (یا صریحاً هنگام استفاده از ANeuralNetworksCompilation_createForDevices
یا به طور صریح با استفاده از ANeuralNetworksCompilation_create
) مهم است که برنامه نتیجه را ارائه دهید.
برای درک چگونگی تقسیم مدل شما توسط NNAPI ، گزارش های Android را برای یک پیام (در سطح اطلاعات با Tag ExecutionPlan
) بررسی کنید:
ModelBuilder::findBestDeviceForEachOperation(op-name): device-index
op-name
نام توصیفی این عملیات در نمودار است و device-index
شاخص دستگاه نامزد در لیست دستگاه ها است. این لیست ورودی ارائه شده به ANeuralNetworksCompilation_createForDevices
است یا در صورت استفاده از ANeuralNetworksCompilation_createForDevices
، لیست دستگاه هایی که هنگام تکرار همه دستگاه ها با استفاده از ANeuralNetworks_getDeviceCount
و ANeuralNetworks_getDevice
بازگشتند.
پیام (در سطح اطلاعات با TAG ExecutionPlan
):
ModelBuilder::partitionTheWork: only one best device: device-name
این پیام نشان می دهد که کل نمودار در device-name
دستگاه تسریع شده است.
اعدام
مرحله اجرای مدل را برای مجموعه ای از ورودی ها اعمال می کند و خروجی های محاسباتی را در یک یا چند بافر کاربر یا فضای حافظه که برنامه شما اختصاص داده است ، ذخیره می کند.
برای اجرای یک مدل گردآوری ، این مراحل را دنبال کنید:
برای ایجاد یک نمونه اجرای جدید ، با عملکرد
ANeuralNetworksExecution_create()
تماس بگیرید.// Run the compiled model against a set of inputs ANeuralNetworksExecution* run1 = NULL; ANeuralNetworksExecution_create(compilation, &run1);
مشخص کنید که برنامه شما مقادیر ورودی را برای محاسبه می خواند. برنامه شما می تواند مقادیر ورودی را از یک بافر کاربر یا یک فضای حافظه اختصاص داده شده با فراخوانی
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));
مشخص کنید که برنامه شما مقادیر خروجی را در کجا می نویسد. برنامه شما می تواند مقادیر خروجی را به یک بافر کاربر یا یک فضای حافظه اختصاص داده شده ، با فراخوانی
ANeuralNetworksExecution_setOutput()
یاANeuralNetworksExecution_setOutputFromMemory()
بنویسد.// Set the output float32 myOutput[3][4]; ANeuralNetworksExecution_setOutput(run1, 0, NULL, myOutput, sizeof(myOutput));
Schedule the execution to start, by calling the
ANeuralNetworksExecution_startCompute()
function. If there are no errors, this function returns a result code ofANEURALNETWORKS_NO_ERROR
.// Starts the work. The work proceeds asynchronously ANeuralNetworksEvent* run1_end = NULL; ANeuralNetworksExecution_startCompute(run1, &run1_end);
Call the
ANeuralNetworksEvent_wait()
function to wait for the execution to complete. If the execution was successful, this function returns a result code ofANEURALNETWORKS_NO_ERROR
. Waiting can be done on a different thread than the one starting the execution.// 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);
Optionally, you can apply a different set of inputs to the compiled model by using the same compilation instance to create a new
ANeuralNetworksExecution
instance.// 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);
Synchronous execution
Asynchronous execution spends time to spawn and synchronize threads. Furthermore, the latency can be hugely variable, with the longest delays reaching up to 500 microseconds between the time a thread is notified or woken and the time it is eventually bound to a CPU core.
To improve latency, you can instead direct an application to make a synchronous inference call to the runtime. That call will return only once an inference has been completed rather than returning once an inference has been started. Instead of calling ANeuralNetworksExecution_startCompute
for an asynchronous inference call to the runtime, the application calls ANeuralNetworksExecution_compute
to make a synchronous call to the runtime. A call to ANeuralNetworksExecution_compute
does not take an ANeuralNetworksEvent
and is not paired with a call to ANeuralNetworksEvent_wait
.
Burst executions
On Android devices running Android 10 (API level 29) and higher, the NNAPI supports burst executions through the ANeuralNetworksBurst
object. Burst executions are a sequence of executions of the same compilation that occur in rapid succession, such as those operating on frames of a camera capture or successive audio samples. Using ANeuralNetworksBurst
objects may result in faster executions, as they indicate to accelerators that resources may be reused between executions and that accelerators should remain in a high-performance state for the duration of the burst.
ANeuralNetworksBurst
introduces only a small change in the normal execution path. You create a burst object using ANeuralNetworksBurst_create
, as shown in the following code snippet:
// Create burst object to be reused across a sequence of executions ANeuralNetworksBurst* burst = NULL; ANeuralNetworksBurst_create(compilation, &burst);
Burst executions are synchronous. However, instead of using ANeuralNetworksExecution_compute
to perform each inference, you pair the various ANeuralNetworksExecution
objects with the same ANeuralNetworksBurst
in calls to the function 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 // ...
Free the ANeuralNetworksBurst
object with ANeuralNetworksBurst_free
when it is no longer needed.
// Cleanup ANeuralNetworksBurst_free(burst);
Asynchronous command queues and fenced execution
In Android 11 and higher, NNAPI supports an additional way to schedule asynchronous execution through the ANeuralNetworksExecution_startComputeWithDependencies()
method. When you use this method, the execution waits for all of the depending events to be signaled before starting the evaluation. Once the execution has completed and the outputs are ready to be consumed, the returned event is signaled.
Depending on which devices handle the execution, the event might be backed by a sync fence . You must call ANeuralNetworksEvent_wait()
to wait for the event and recuperate the resources that the execution used. You can import sync fences to an event object using ANeuralNetworksEvent_createFromSyncFenceFd()
, and you can export sync fences from an event object using ANeuralNetworksEvent_getSyncFenceFd()
.
Dynamically sized outputs
To support models where the size of the output depends on the input data—that is, where the size cannot be determined at model execution time—use ANeuralNetworksExecution_getOutputOperandRank
and ANeuralNetworksExecution_getOutputOperandDimensions
.
The following code sample shows how to do this:
// 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());
پاکسازی
The cleanup step handles the freeing of internal resources used for your computation.
// Cleanup ANeuralNetworksCompilation_free(compilation); ANeuralNetworksModel_free(model); ANeuralNetworksMemory_free(mem1);
Error management and CPU fallback
If there is an error during partitioning, if a driver fails to compile a (piece of a) model, or if a driver fails to execute a compiled (piece of a) model, NNAPI might fall back to its own CPU implementation of the one or more operations.
If the NNAPI client contains optimized versions of the operation (as, for example, TFLite) it might be advantageous to disable the CPU fallback and handle the failures with the client's optimized operation implementation.
In Android 10, if compilation is performed using ANeuralNetworksCompilation_createForDevices
, then CPU fallback will be disabled.
In Android P, NNAPI execution falls back to the CPU if execution on the driver fails. This is also true on Android 10 when ANeuralNetworksCompilation_create
rather than ANeuralNetworksCompilation_createForDevices
is used.
First execution falls back for that single partition, and if that still fails, it retries the entire model on the CPU.
If partitioning or compilation fails, the entire model will be tried on CPU.
There are cases where some operations are not supported on CPU, and in such situations compilation or execution will fail rather than falling back.
Even after disabling CPU fallback, there may still be operations in the model that are scheduled on the CPU. If the CPU is in the list of processors supplied to ANeuralNetworksCompilation_createForDevices
, and is either the only processor that supports those operations or is the processor that claims best performance for those operations, it will be chosen as a primary (non-fallback) executor.
To ensure there is no CPU execution, use ANeuralNetworksCompilation_createForDevices
while excluding the nnapi-reference
from the list of devices. Starting in Android P, it is possible to disable fallback at execution time on DEBUG builds by setting the debug.nn.partition
property to 2.
Memory domains
In Android 11 and higher, NNAPI supports memory domains that provide allocator interfaces for opaque memories. This allows applications to pass device-native memories across executions, so that NNAPI does not copy or transform data unnecessarily when performing consecutive executions on the same driver.
The memory domain feature is intended for tensors that are mostly internal to the driver and that don't need frequent access to the client side. Examples of such tensors include the state tensors in sequence models. For tensors that need frequent CPU access on the client side, use shared memory pools instead.
To allocate an opaque memory, perform the following steps:
Call the
ANeuralNetworksMemoryDesc_create()
function to create a new memory descriptor:// Create a memory descriptor ANeuralNetworksMemoryDesc* desc; ANeuralNetworksMemoryDesc_create(&desc);
Specify all of the intended input and output roles by calling
ANeuralNetworksMemoryDesc_addInputRole()
andANeuralNetworksMemoryDesc_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);
Optionally, specify the memory dimensions by calling
ANeuralNetworksMemoryDesc_setDimensions()
.// Specify the memory dimensions uint32_t dims[] = {3, 4}; ANeuralNetworksMemoryDesc_setDimensions(desc, 2, dims);
Finalize the descriptor definition by calling
ANeuralNetworksMemoryDesc_finish()
.ANeuralNetworksMemoryDesc_finish(desc);
Allocate as many memories as you need by passing the descriptor to
ANeuralNetworksMemory_createFromDesc()
.// Allocate two opaque memories with the descriptor ANeuralNetworksMemory* opaqueMem; ANeuralNetworksMemory_createFromDesc(desc, &opaqueMem);
Free the memory descriptor when you no longer need it.
ANeuralNetworksMemoryDesc_free(desc);
The client may only use the created ANeuralNetworksMemory
object with ANeuralNetworksExecution_setInputFromMemory()
or ANeuralNetworksExecution_setOutputFromMemory()
according to the roles specified in the ANeuralNetworksMemoryDesc
object. The offset and length arguments must be set to 0, indicating that the whole memory is used. The client may also explicitly set or extract the contents of the memory by using ANeuralNetworksMemory_copy()
.
You can create opaque memories with roles of unspecified dimensions or rank. In that case, the memory creation might fail with the ANEURALNETWORKS_OP_FAILED
status if it is not supported by the underlying driver. The client is encouraged to implement fallback logic by allocating a large enough buffer backed by Ashmem or BLOB-mode AHardwareBuffer
.
When NNAPI no longer needs to access the opaque memory object, free the corresponding ANeuralNetworksMemory
instance:
ANeuralNetworksMemory_free(opaqueMem);
عملکرد را اندازه گیری کنید
You can evaluate your app's performance by measuring execution time or by profiling.
زمان اجرا
When you want to determine total execution time through the runtime, you can use the synchronous execution API and measure the time taken by the call. When you want to determine total execution time through a lower level of the software stack, you can use ANeuralNetworksExecution_setMeasureTiming
and ANeuralNetworksExecution_getDuration
to get:
- execution time on an accelerator (not in the driver, which runs on the host processor).
- execution time in the driver, including time on the accelerator.
The execution time in the driver excludes overhead such as that of the runtime itself and the IPC needed for the runtime to communicate with the driver.
These APIs measure duration between the work submitted and work completed events, rather than the time a driver or accelerator devotes to performing the inference, possibly interrupted by context switching.
For example, if inference 1 begins, then the driver stops work to perform inference 2, then it resumes and completes inference 1, the execution time for inference 1 will include the time when work was stopped to perform inference 2.
This timing information may be useful for a production deployment of an application to collect telemetry for offline use. You can use the timing data to modify the app for higher performance.
When using this functionality, bear in mind the following:
- Collecting timing information might have a performance cost.
- Only a driver is capable of computing the time spent in itself or on the accelerator, excluding time spent in NNAPI runtime and in IPC.
- You can use these APIs only with an
ANeuralNetworksExecution
that was created withANeuralNetworksCompilation_createForDevices
withnumDevices = 1
. - No driver is required to be able to report timing information.
Profile your application with Android Systrace
Starting with Android 10, NNAPI automatically generates systrace events that you can use to profile your application.
The NNAPI Source comes with a parse_systrace
utility to process the systrace events generated by your application and generate a table view showing the time spent in the different phases of the model lifecycle (Instantiation, Preparation, Compilation Execution and Termination) and different layers of the applications . The layers in which your application is split are:
-
Application
: the main application code -
Runtime
: NNAPI Runtime -
IPC
: The inter process communication between NNAPI Runtime and the Driver code -
Driver
: the accelerator driver process.
Generate the profiling analysys data
Assuming you checked out the AOSP source tree at $ANDROID_BUILD_TOP, and using the TFLite image classification example as target application, you can generate the NNAPI profiling data with the following steps:
- Start the Android systrace with the following command:
$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
The -o trace.html
parameter indicates that the traces will be written in the trace.html
. When profiling own application you will need to replace org.tensorflow.lite.examples.classification
with the process name specified in your app manifest.
This will keep one of your shell console busy, don't run the command in background since it is interactively waiting for an enter
to terminate.
- After the systrace collector is started, start your app and run your benchmark test.
In our case you can start the Image Classification app from Android Studio or directly from your test phone UI if the app has already been installed. To generate some NNAPI data you need to configure the app to use NNAPI by selecting NNAPI as target device in the app configuration dialog.
When the test completes, terminate the systrace by pressing
enter
on the console terminal active since step 1.Run the
systrace_parser
utility generate cumulative statistics:
$ANDROID_BUILD_TOP/frameworks/ml/nn/tools/systrace_parser/parse_systrace.py --total-times trace.html
The parser accepts the following parameters: - --total-times
: shows the total time spent in a layer including the time spent waiting for execution on a call to an underlying layer - --print-detail
: prints all the events that have been collected from systrace - --per-execution
: prints only the execution and its subphases (as per-execution times) instead of stats for all phases - --json
: produces the output in JSON format
An example of the output is shown below:
===========================================================================================================================================
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
The parser might fail if the collected events do not represent a complete application trace. In particular it might fail if systrace events generated to mark the end of a section are present in the trace without an associated section start event. This usually happens if some events from a previous profiling session are being generated when you start the systrace collector. In this case you would have to run your profiling again.
Add statistics for your application code to systrace_parser output
The parse_systrace application is based on the built-in Android systrace functionality. You can add traces for specific operations in your app using the systrace API ( for Java , for native applications ) with custom event names.
To associate your custom events with phases of the Application lifecycle, prepend your event name with one of the following strings:
-
[NN_LA_PI]
: Application level event for Initialization -
[NN_LA_PP]
: Application level event for Preparation -
[NN_LA_PC]
: Application level event for Compilation -
[NN_LA_PE]
: Application level event for Execution
Here is an example of how you can alter the TFLite image classification example code by adding a runInferenceModel
section for the Execution
phase and the Application
layer containing another other sections preprocessBitmap
that won't be considered in NNAPI traces. The runInferenceModel
section will be part of the systrace events processed by the nnapi systrace parser:
کاتلین
/** 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 ListrecognizeImage(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; }
کیفیت خدمات
In Android 11 and higher, NNAPI enables better quality of service (QoS) by allowing an application to indicate the relative priorities of its models, the maximum amount of time expected to prepare a given model, and the maximum amount of time expected to complete a given computation. Android 11 also introduces additional NNAPI result codes that enable applications to understand failures such as missed execution deadlines.
Set the priority of a workload
To set the priority of an NNAPI workload, call ANeuralNetworksCompilation_setPriority()
prior to calling ANeuralNetworksCompilation_finish()
.
ضرب الاجل تعیین کنید
Applications can set deadlines for both model compilation and inference.
- To set the compilation timeout, call
ANeuralNetworksCompilation_setTimeout()
prior to callingANeuralNetworksCompilation_finish()
. - To set the inference timeout, call
ANeuralNetworksExecution_setTimeout()
prior to starting the compilation .
More about operands
The following section covers advanced topics about using operands.
Quantized tensors
A quantized tensor is a compact way to represent an n-dimensional array of floating point values.
NNAPI supports 8-bit asymmetric quantized tensors. For these tensors, the value of each cell is represented by an 8-bit integer. Associated with the tensor is a scale and a zero point value. These are used to convert the 8-bit integers into the floating point values that are being represented.
فرمول این است:
(cellValue - zeroPoint) * scale
where the zeroPoint value is a 32-bit integer and the scale a 32-bit floating point value.
Compared to tensors of 32-bit floating point values, 8-bit quantized tensors have two advantages:
- Your application is smaller, as the trained weights take a quarter of the size of 32-bit tensors.
- Computations can often be executed faster. This is due to the smaller amount of data that needs to be fetched from memory and the efficiency of processors such as DSPs in doing integer math.
While it is possible to convert a floating point model to a quantized one, our experience has shown that better results are achieved by training a quantized model directly. In effect, the neural network learns to compensate for the increased granularity of each value. For each quantized tensor, the scale and zeroPoint values are determined during the training process.
In NNAPI, you define quantized tensor types by setting the type field of the ANeuralNetworksOperandType
data structure to ANEURALNETWORKS_TENSOR_QUANT8_ASYMM
. You also specify the scale and zeroPoint value of the tensor in that data structure.
In addition to 8-bit asymmetric quantized tensors, NNAPI supports the following:
-
ANEURALNETWORKS_TENSOR_QUANT8_SYMM_PER_CHANNEL
which you can use for representing weights toCONV/DEPTHWISE_CONV/TRANSPOSED_CONV
operations. -
ANEURALNETWORKS_TENSOR_QUANT16_ASYMM
which you can use for the internal state ofQUANTIZED_16BIT_LSTM
. -
ANEURALNETWORKS_TENSOR_QUANT8_SYMM
which can be an input toANEURALNETWORKS_DEQUANTIZE
.
Optional operands
A few operations, like ANEURALNETWORKS_LSH_PROJECTION
, take optional operands. To indicate in the model that the optional operand is omitted, call the ANeuralNetworksModel_setOperandValue()
function, passing NULL
for the buffer and 0 for the length.
If the decision on whether the operand is present or not varies for each execution, you indicate that the operand is omitted by using the ANeuralNetworksExecution_setInput()
or ANeuralNetworksExecution_setOutput()
functions, passing NULL
for the buffer and 0 for the length.
Tensors of unknown rank
Android 9 (API level 28) introduced model operands of unknown dimensions but known rank (the number of dimensions). Android 10 (API level 29) introduced tensors of unknown rank, as shown in ANeuralNetworksOperandType .
NNAPI benchmark
The NNAPI benchmark is available on AOSP in platform/test/mlts/benchmark
(benchmark app) and platform/test/mlts/models
(models and datasets).
The benchmark evaluates latency and accuracy and compares drivers to the same work done using Tensorflow Lite running on the CPU, for the same models and datasets.
To use the benchmark, do the following:
Connect a target Android device to your computer, open a terminal window, and make sure the device is reachable through adb.
If more than one Android device is connected, export the target device
ANDROID_SERIAL
environment variable.Navigate to the Android top-level source directory.
دستورات زیر را اجرا کنید:
lunch aosp_arm-userdebug # Or aosp_arm64-userdebug if available ./test/mlts/benchmark/build_and_run_benchmark.sh
At the end of a benchmark run, its results will be presented as an HTML page passed to
xdg-open
.
NNAPI logs
NNAPI generates useful diagnostic information in the system logs. To analyze the logs, use the logcat utility.
Enable verbose NNAPI logging for specific phases or components by setting the property debug.nn.vlog
(using adb shell
) to the following list of values, separated by space, colon, or comma:
-
model
: Model building -
compilation
: Generation of the model execution plan and compilation -
execution
: Model execution -
cpuexe
: Execution of operations using the NNAPI CPU implementation -
manager
: NNAPI extensions, available interfaces and capabilities related info -
all
or1
: All the elements above
For example, to enable full verbose logging use the command adb shell setprop debug.nn.vlog all
. To disable verbose logging, use the command adb shell setprop debug.nn.vlog '""'
.
Once enabled, verbose logging generates log entries at INFO level with a tag set to the phase or component name.
Beside the debug.nn.vlog
controlled messages, NNAPI API components provide other log entries at various levels, each one using a specific log tag.
To get a list of components, search the source tree using the following expression:
grep -R 'define LOG_TAG' | awk -F '"' '{print $2}' | sort -u | egrep -v "Sample|FileTag|test"
This expression currently returns the following tags:
- BurstBuilder
- تماس های تلفنی
- CompilationBuilder
- CpuExecutor
- ExecutionBuilder
- ExecutionBurstController
- ExecutionBurstServer
- ExecutionPlan
- FibonacciDriver
- GraphDump
- IndexedShapeWrapper
- IonWatcher
- مدیر
- حافظه
- MemoryUtils
- MetaModel
- ModelArgumentInfo
- ModelBuilder
- NeuralNetworks
- OperationResolver
- عملیات
- OperationsUtils
- PackageInfo
- TokenHasher
- TypeManager
- Utils
- ValidateHal
- VersionedInterfaces
To control the level of log messages shown by logcat
, use the environment variable ANDROID_LOG_TAGS
.
To show the full set of NNAPI log messages and disable any others, set ANDROID_LOG_TAGS
to the following:
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.
You can set ANDROID_LOG_TAGS
using the following command:
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')
Note that this is just a filter that applies to logcat
. You still need to set the property debug.nn.vlog
to all
to generate verbose log info.
The Android Neural Networks API (NNAPI) is an Android C API designed for running computationally intensive operations for machine learning on Android devices. NNAPI is designed to provide a base layer of functionality for higher-level machine learning frameworks, such as TensorFlow Lite and Caffe2, that build and train neural networks. The API is available on all Android devices running Android 8.1 (API level 27) or higher.
NNAPI supports inferencing by applying data from Android devices to previously trained, developer-defined models. Examples of inferencing include classifying images, predicting user behavior, and selecting appropriate responses to a search query.
On-device inferencing has many benefits:
- Latency : You don't need to send a request over a network connection and wait for a response. For example, this can be critical for video applications that process successive frames coming from a camera.
- Availability : The application runs even when outside of network coverage.
- Speed : New hardware that is specific to neural network processing provides significantly faster computation than a general-purpose CPU, alone.
- Privacy : The data does not leave the Android device.
- Cost : No server farm is needed when all the computations are performed on the Android device.
There are also trade-offs that a developer should keep in mind:
- System utilization : Evaluating neural networks involves a lot of computation, which could increase battery power usage. You should consider monitoring the battery health if this is a concern for your app, especially for long-running computations.
- Application size : Pay attention to the size of your models. Models may take up multiple megabytes of space. If bundling large models in your APK would unduly impact your users, you may want to consider downloading the models after app installation, using smaller models, or running your computations in the cloud. NNAPI does not provide functionality for running models in the cloud.
See the Android Neural Networks API sample to see one example of how to use NNAPI.
Understand the Neural Networks API runtime
NNAPI is meant to be called by machine learning libraries, frameworks, and tools that let developers train their models off-device and deploy them on Android devices. Apps typically would not use NNAPI directly, but would instead use higher-level machine learning frameworks. These frameworks in turn could use NNAPI to perform hardware-accelerated inference operations on supported devices.
Based on an app's requirements and the hardware capabilities on an Android device, Android's neural network runtime can efficiently distribute the computation workload across available on-device processors, including dedicated neural network hardware, graphics processing units (GPUs), and digital signal processors (DSPs ).
For Android devices that lack a specialized vendor driver, the NNAPI runtime executes the requests on the CPU.
Figure 1 shows the high-level system architecture for NNAPI.
Neural Networks API programming model
To perform computations using NNAPI, you first need to construct a directed graph that defines the computations to perform. This computation graph, combined with your input data (for example, the weights and biases passed down from a machine learning framework), forms the model for NNAPI runtime evaluation.
NNAPI uses four main abstractions:
- Model : A computation graph of mathematical operations and the constant values learned through a training process. These operations are specific to neural networks. They include 2-dimensional (2D) convolution , logistic ( sigmoid ) activation, rectified linear (ReLU) activation, and more. Creating a model is a synchronous operation. Once successfully created, it can be reused across threads and compilations. In NNAPI, a model is represented as an
ANeuralNetworksModel
instance. - Compilation : Represents a configuration for compiling an NNAPI model into lower-level code. Creating a compilation is a synchronous operation. Once successfully created, it can be reused across threads and executions. In NNAPI, each compilation is represented as an
ANeuralNetworksCompilation
instance. - Memory : Represents shared memory, memory mapped files, and similar memory buffers. Using a memory buffer lets the NNAPI runtime transfer data to drivers more efficiently. An app typically creates one shared memory buffer that contains every tensor needed to define a model. You can also use memory buffers to store the inputs and outputs for an execution instance. In NNAPI, each memory buffer is represented as an
ANeuralNetworksMemory
instance. Execution : Interface for applying an NNAPI model to a set of inputs and to gather the results. Execution can be performed synchronously or asynchronously.
For asynchronous execution, multiple threads can wait on the same execution. When this execution completes, all threads are released.
In NNAPI, each execution is represented as an
ANeuralNetworksExecution
instance.
Figure 2 shows the basic programming flow.
The rest of this section describes the steps to set up your NNAPI model to perform computation, compile the model, and execute the compiled model.
Provide access to training data
Your trained weights and biases data are likely stored in a file. To provide the NNAPI runtime with efficient access to this data, create an ANeuralNetworksMemory
instance by calling the ANeuralNetworksMemory_createFromFd()
function and passing in the file descriptor of the opened data file. You also specify memory protection flags and an offset where the shared memory region starts in the file.
// 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);
Although in this example we use only one ANeuralNetworksMemory
instance for all our weights, it's possible to use more than one ANeuralNetworksMemory
instance for multiple files.
Use native hardware buffers
You can use native hardware buffers for model inputs, outputs, and constant operand values. In certain cases, an NNAPI accelerator can access AHardwareBuffer
objects without the driver needing to copy the data. AHardwareBuffer
has many different configurations, and not every NNAPI accelerator may support all of these configurations. Because of this limitation, refer to the constraints listed in ANeuralNetworksMemory_createFromAHardwareBuffer
reference documentation and test ahead of time on target devices to ensure compilations and executions that use AHardwareBuffer
behave as expected, using device assignment to specify the accelerator.
To allow the NNAPI runtime to access an AHardwareBuffer
object, create an ANeuralNetworksMemory
instance by calling the ANeuralNetworksMemory_createFromAHardwareBuffer
function and passing in the AHardwareBuffer
object, as shown in the following code sample:
// 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);
When NNAPI no longer needs to access the AHardwareBuffer
object, free the corresponding ANeuralNetworksMemory
instance:
ANeuralNetworksMemory_free(mem2);
توجه:
- You can use
AHardwareBuffer
only for the whole buffer; you cannot use it with anARect
parameter. - The NNAPI runtime will not flush the buffer. You need to make sure that the input and output buffers are accessible before scheduling the execution.
- There is no support for sync fence file descriptors.
- For an
AHardwareBuffer
with vendor-specific formats and usage bits, it is up to the vendor implementation to determine whether the client or the driver is responsible for flushing the cache.
مدل
A model is the fundamental unit of computation in NNAPI. Each model is defined by one or more operands and operations.
عملوندها
Operands are data objects used in defining the graph. These include the inputs and outputs of the model, the intermediate nodes that contain the data that flows from one operation to another, and the constants that are passed to these operations.
There are two types of operands that can be added to NNAPI models: scalars and tensors .
A scalar represents a single value. NNAPI supports scalar values in boolean, 16-bit floating point, 32-bit floating point, 32-bit integer, and unsigned 32-bit integer formats.
Most operations in NNAPI involve tensors. Tensors are n-dimensional arrays. NNAPI supports tensors with 16-bit floating point, 32-bit floating point, 8-bit quantized , 16-bit quantized, 32-bit integer, and 8-bit boolean values.
For example, figure 3 represents a model with two operations: an addition followed by a multiplication. The model takes an input tensor and produces one output tensor.
The model above has seven operands. These operands are identified implicitly by the index of the order in which they are added to the model. The first operand added has an index of 0, the second an index of 1, and so on. Operands 1, 2, 3, and 5 are constant operands.
The order in which you add the operands does not matter. For example, the model output operand could be the first one added. The important part is to use the correct index value when referring to an operand.
Operands have types. These are specified when they are added to the model.
An operand cannot be used as both input and output of a model.
Every operand must either be a model input, a constant, or the output operand of exactly one operation.
For additional information on using operands, see More about operands .
عملیات
An operation specifies the computations to be performed. Each operation consists of these elements:
- an operation type (for example, addition, multiplication, convolution),
- a list of indexes of the operands that the operation uses for input, and
- a list of indexes of the operands that the operation uses for output.
The order in these lists matters; see the NNAPI API reference for the expected inputs and outputs of each operation type.
You must add the operands that an operation consumes or produces to the model before adding the operation.
The order in which you add operations does not matter. NNAPI relies on the dependencies established by the computation graph of operands and operations to determine the order in which operations are executed.
The operations that NNAPI supports are summarized in the table below:
Known issue in API level 28: When passing ANEURALNETWORKS_TENSOR_QUANT8_ASYMM
tensors to the ANEURALNETWORKS_PAD
operation, which is available on Android 9 (API level 28) and higher, the output from NNAPI may not match output from higher-level machine learning frameworks, such as TensorFlow Lite . You should instead pass only ANEURALNETWORKS_TENSOR_FLOAT32
. The issue is resolved in Android 10 (API level 29) and higher.
Build models
In the following example, we create the two-operation model found in figure 3 .
To build the model, follow these steps:
Call the
ANeuralNetworksModel_create()
function to define an empty model.ANeuralNetworksModel* model = NULL; ANeuralNetworksModel_create(&model);
Add the operands to your model by calling
ANeuralNetworks_addOperand()
. Their data types are defined using theANeuralNetworksOperandType
data structure.// 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 6For operands that have constant values, such as weights and biases that your app obtains from a training process, use the
ANeuralNetworksModel_setOperandValue()
andANeuralNetworksModel_setOperandValueFromMemory()
functions.In the following example, we set constant values from the training data file corresponding to the memory buffer we created in Provide access to training data .
// 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));For each operation in the directed graph you want to compute, add the operation to your model by calling the
ANeuralNetworksModel_addOperation()
function.As parameters to this call, your app must provide:
- the operation type
- the count of input values
- the array of the indexes for input operands
- the count of output values
- the array of the indexes for output operands
Note that an operand cannot be used for both input and output of the same operation.
// 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);Identify which operands the model should treat as its inputs and outputs by calling the
ANeuralNetworksModel_identifyInputsAndOutputs()
function.// 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);
Optionally, specify whether
ANEURALNETWORKS_TENSOR_FLOAT32
is allowed to be calculated with range or precision as low as that of the IEEE 754 16-bit floating-point format by callingANeuralNetworksModel_relaxComputationFloat32toFloat16()
.Call
ANeuralNetworksModel_finish()
to finalize the definition of your model. If there are no errors, this function returns a result code ofANEURALNETWORKS_NO_ERROR
.ANeuralNetworksModel_finish(model);
Once you create a model, you can compile it any number of times and execute each compilation any number of times.
جریان را کنترل کنید
To incorporate control flow in an NNAPI model, do the following:
Construct the corresponding execution subgraphs (
then
andelse
subgraphs for anIF
statement,condition
andbody
subgraphs for aWHILE
loop) as standaloneANeuralNetworksModel*
models:ANeuralNetworksModel* thenModel = makeThenModel(); ANeuralNetworksModel* elseModel = makeElseModel();
Create operands that reference those models within the model containing the control flow:
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);
Add the control flow operation:
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);
تالیف
The compilation step determines on which processors your model will be executed and asks the corresponding drivers to prepare for its execution. This could include the generation of machine code specific to the processors your model will run on.
To compile a model, follow these steps:
Call the
ANeuralNetworksCompilation_create()
function to create a new compilation instance.// Compile the model ANeuralNetworksCompilation* compilation; ANeuralNetworksCompilation_create(model, &compilation);
Optionally, you can use device assignment to explicitly choose what devices to execute on.
You can optionally influence how the runtime trades off between battery power usage and execution speed. You can do so by calling
ANeuralNetworksCompilation_setPreference()
.// Ask to optimize for low power consumption ANeuralNetworksCompilation_setPreference(compilation, ANEURALNETWORKS_PREFER_LOW_POWER);
The preferences you can specify include:
-
ANEURALNETWORKS_PREFER_LOW_POWER
: Prefer executing in a way that minimizes battery drain. This is desirable for compilations that are executed often. -
ANEURALNETWORKS_PREFER_FAST_SINGLE_ANSWER
: Prefer returning a single answer as fast as possible, even if this causes more power consumption. این پیش فرض است. -
ANEURALNETWORKS_PREFER_SUSTAINED_SPEED
: Prefer maximizing the throughput of successive frames, such as when processing successive frames coming from the camera.
-
You can optionally set up compilation caching by calling
ANeuralNetworksCompilation_setCaching
.// Set up compilation caching ANeuralNetworksCompilation_setCaching(compilation, cacheDir, token);
Use
getCodeCacheDir()
for thecacheDir
. Thetoken
specified must be unique to each model within the application.Finalize the compilation definition by calling
ANeuralNetworksCompilation_finish()
. If there are no errors, this function returns a result code ofANEURALNETWORKS_NO_ERROR
.ANeuralNetworksCompilation_finish(compilation);
Device discovery and assignment
On Android devices running Android 10 (API level 29) and higher, NNAPI provides functions that allow machine learning framework libraries and apps to get information about the devices available and specify devices to be used for execution. Providing information about the available devices allows apps to get the exact version of the drivers found on a device to avoid known incompatibilities. By giving apps the ability to specify which devices are to execute different sections of a model, apps can be optimized for the Android device on which they are deployed.
Device discovery
Use ANeuralNetworks_getDeviceCount
to get the number of available devices. For each device, use ANeuralNetworks_getDevice
to set an ANeuralNetworksDevice
instance to a reference to that device.
Once you have a device reference, you can find out additional information about that device using the following functions:
-
ANeuralNetworksDevice_getFeatureLevel
-
ANeuralNetworksDevice_getName
-
ANeuralNetworksDevice_getType
-
ANeuralNetworksDevice_getVersion
Device assignment
Use ANeuralNetworksModel_getSupportedOperationsForDevices
to discover which operations of a model can be run on specific devices.
To control which accelerators to use for execution, call ANeuralNetworksCompilation_createForDevices
in place of ANeuralNetworksCompilation_create
. Use the resulting ANeuralNetworksCompilation
object, as normal. The function returns an error if the provided model contains operations that are not supported by the selected devices.
If multiple devices are specified, the runtime is responsible for distributing the work across the devices.
Similar to other devices, the NNAPI CPU implementation is represented by an ANeuralNetworksDevice
with the name nnapi-reference
and the type ANEURALNETWORKS_DEVICE_TYPE_CPU
. When calling ANeuralNetworksCompilation_createForDevices
, the CPU implementation is not used to handle the failure cases for model compilation and execution.
It is an application's responsibility to partition a model into sub-models that can run on the specified devices. Applications that don't need to do manual partitioning should continue to call the simpler ANeuralNetworksCompilation_create
to use all available devices (including the CPU) to accelerate the model. If the model couldn't be fully supported by the devices you specified using ANeuralNetworksCompilation_createForDevices
, ANEURALNETWORKS_BAD_DATA
is returned.
Model partitioning
When multiple devices are available for the model, the NNAPI runtime distributes the work across the devices. For example, if more than one device was provided to ANeuralNetworksCompilation_createForDevices
, all the specified ones will be considered when allocating the work. Note that, if the CPU device is not in the list, CPU execution will be disabled. When using ANeuralNetworksCompilation_create
all available devices will be taken into account, including CPU.
The distribution is done by selecting from the list of available devices, for each of the operations in the model, the device supporting the operation and declaring the best performance, ie the fastest execution time or the lowest power consumption, depending on the execution preference specified by the client. This partitioning algorithm doesn't account for possible inefficiencies caused by the IO between the different processors so, when specifying multiple processors (either explicitly when using ANeuralNetworksCompilation_createForDevices
or implicitly by using ANeuralNetworksCompilation_create
) it's important to profile the resulting application.
To understand how your model has been partitioned by NNAPI, check the Android logs for a message (at INFO level with tag ExecutionPlan
):
ModelBuilder::findBestDeviceForEachOperation(op-name): device-index
op-name
is the descriptive name of the operation in the graph and device-index
is the index of the candidate device in the list of devices. This list is the input provided to ANeuralNetworksCompilation_createForDevices
or, if using ANeuralNetworksCompilation_createForDevices
, the list of devices returned when iterating over all devices using ANeuralNetworks_getDeviceCount
and ANeuralNetworks_getDevice
.
The message (at INFO level with tag ExecutionPlan
):
ModelBuilder::partitionTheWork: only one best device: device-name
This message indicates that the whole graph has been accelerated on the device device-name
.
اعدام
The execution step applies the model to a set of inputs and stores the computation outputs to one or more user buffers or memory spaces that your app allocated.
To execute a compiled model, follow these steps:
Call the
ANeuralNetworksExecution_create()
function to create a new execution instance.// Run the compiled model against a set of inputs ANeuralNetworksExecution* run1 = NULL; ANeuralNetworksExecution_create(compilation, &run1);
Specify where your app reads the input values for the computation. Your app can read input values from either a user buffer or an allocated memory space by calling
ANeuralNetworksExecution_setInput()
orANeuralNetworksExecution_setInputFromMemory()
respectively.// 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));
Specify where your app writes the output values. Your app can write output values to either a user buffer or an allocated memory space, by calling
ANeuralNetworksExecution_setOutput()
orANeuralNetworksExecution_setOutputFromMemory()
respectively.// Set the output float32 myOutput[3][4]; ANeuralNetworksExecution_setOutput(run1, 0, NULL, myOutput, sizeof(myOutput));
Schedule the execution to start, by calling the
ANeuralNetworksExecution_startCompute()
function. If there are no errors, this function returns a result code ofANEURALNETWORKS_NO_ERROR
.// Starts the work. The work proceeds asynchronously ANeuralNetworksEvent* run1_end = NULL; ANeuralNetworksExecution_startCompute(run1, &run1_end);
Call the
ANeuralNetworksEvent_wait()
function to wait for the execution to complete. If the execution was successful, this function returns a result code ofANEURALNETWORKS_NO_ERROR
. Waiting can be done on a different thread than the one starting the execution.// 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);
Optionally, you can apply a different set of inputs to the compiled model by using the same compilation instance to create a new
ANeuralNetworksExecution
instance.// 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);
Synchronous execution
Asynchronous execution spends time to spawn and synchronize threads. Furthermore, the latency can be hugely variable, with the longest delays reaching up to 500 microseconds between the time a thread is notified or woken and the time it is eventually bound to a CPU core.
To improve latency, you can instead direct an application to make a synchronous inference call to the runtime. That call will return only once an inference has been completed rather than returning once an inference has been started. Instead of calling ANeuralNetworksExecution_startCompute
for an asynchronous inference call to the runtime, the application calls ANeuralNetworksExecution_compute
to make a synchronous call to the runtime. A call to ANeuralNetworksExecution_compute
does not take an ANeuralNetworksEvent
and is not paired with a call to ANeuralNetworksEvent_wait
.
Burst executions
On Android devices running Android 10 (API level 29) and higher, the NNAPI supports burst executions through the ANeuralNetworksBurst
object. Burst executions are a sequence of executions of the same compilation that occur in rapid succession, such as those operating on frames of a camera capture or successive audio samples. Using ANeuralNetworksBurst
objects may result in faster executions, as they indicate to accelerators that resources may be reused between executions and that accelerators should remain in a high-performance state for the duration of the burst.
ANeuralNetworksBurst
introduces only a small change in the normal execution path. You create a burst object using ANeuralNetworksBurst_create
, as shown in the following code snippet:
// Create burst object to be reused across a sequence of executions ANeuralNetworksBurst* burst = NULL; ANeuralNetworksBurst_create(compilation, &burst);
Burst executions are synchronous. However, instead of using ANeuralNetworksExecution_compute
to perform each inference, you pair the various ANeuralNetworksExecution
objects with the same ANeuralNetworksBurst
in calls to the function 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 // ...
Free the ANeuralNetworksBurst
object with ANeuralNetworksBurst_free
when it is no longer needed.
// Cleanup ANeuralNetworksBurst_free(burst);
Asynchronous command queues and fenced execution
In Android 11 and higher, NNAPI supports an additional way to schedule asynchronous execution through the ANeuralNetworksExecution_startComputeWithDependencies()
method. When you use this method, the execution waits for all of the depending events to be signaled before starting the evaluation. Once the execution has completed and the outputs are ready to be consumed, the returned event is signaled.
Depending on which devices handle the execution, the event might be backed by a sync fence . You must call ANeuralNetworksEvent_wait()
to wait for the event and recuperate the resources that the execution used. You can import sync fences to an event object using ANeuralNetworksEvent_createFromSyncFenceFd()
, and you can export sync fences from an event object using ANeuralNetworksEvent_getSyncFenceFd()
.
Dynamically sized outputs
To support models where the size of the output depends on the input data—that is, where the size cannot be determined at model execution time—use ANeuralNetworksExecution_getOutputOperandRank
and ANeuralNetworksExecution_getOutputOperandDimensions
.
The following code sample shows how to do this:
// 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());
پاکسازی
The cleanup step handles the freeing of internal resources used for your computation.
// Cleanup ANeuralNetworksCompilation_free(compilation); ANeuralNetworksModel_free(model); ANeuralNetworksMemory_free(mem1);
Error management and CPU fallback
If there is an error during partitioning, if a driver fails to compile a (piece of a) model, or if a driver fails to execute a compiled (piece of a) model, NNAPI might fall back to its own CPU implementation of the one or more operations.
If the NNAPI client contains optimized versions of the operation (as, for example, TFLite) it might be advantageous to disable the CPU fallback and handle the failures with the client's optimized operation implementation.
In Android 10, if compilation is performed using ANeuralNetworksCompilation_createForDevices
, then CPU fallback will be disabled.
In Android P, NNAPI execution falls back to the CPU if execution on the driver fails. This is also true on Android 10 when ANeuralNetworksCompilation_create
rather than ANeuralNetworksCompilation_createForDevices
is used.
First execution falls back for that single partition, and if that still fails, it retries the entire model on the CPU.
If partitioning or compilation fails, the entire model will be tried on CPU.
There are cases where some operations are not supported on CPU, and in such situations compilation or execution will fail rather than falling back.
Even after disabling CPU fallback, there may still be operations in the model that are scheduled on the CPU. If the CPU is in the list of processors supplied to ANeuralNetworksCompilation_createForDevices
, and is either the only processor that supports those operations or is the processor that claims best performance for those operations, it will be chosen as a primary (non-fallback) executor.
To ensure there is no CPU execution, use ANeuralNetworksCompilation_createForDevices
while excluding the nnapi-reference
from the list of devices. Starting in Android P, it is possible to disable fallback at execution time on DEBUG builds by setting the debug.nn.partition
property to 2.
Memory domains
In Android 11 and higher, NNAPI supports memory domains that provide allocator interfaces for opaque memories. This allows applications to pass device-native memories across executions, so that NNAPI does not copy or transform data unnecessarily when performing consecutive executions on the same driver.
The memory domain feature is intended for tensors that are mostly internal to the driver and that don't need frequent access to the client side. Examples of such tensors include the state tensors in sequence models. For tensors that need frequent CPU access on the client side, use shared memory pools instead.
To allocate an opaque memory, perform the following steps:
Call the
ANeuralNetworksMemoryDesc_create()
function to create a new memory descriptor:// Create a memory descriptor ANeuralNetworksMemoryDesc* desc; ANeuralNetworksMemoryDesc_create(&desc);
Specify all of the intended input and output roles by calling
ANeuralNetworksMemoryDesc_addInputRole()
andANeuralNetworksMemoryDesc_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);
Optionally, specify the memory dimensions by calling
ANeuralNetworksMemoryDesc_setDimensions()
.// Specify the memory dimensions uint32_t dims[] = {3, 4}; ANeuralNetworksMemoryDesc_setDimensions(desc, 2, dims);
Finalize the descriptor definition by calling
ANeuralNetworksMemoryDesc_finish()
.ANeuralNetworksMemoryDesc_finish(desc);
Allocate as many memories as you need by passing the descriptor to
ANeuralNetworksMemory_createFromDesc()
.// Allocate two opaque memories with the descriptor ANeuralNetworksMemory* opaqueMem; ANeuralNetworksMemory_createFromDesc(desc, &opaqueMem);
Free the memory descriptor when you no longer need it.
ANeuralNetworksMemoryDesc_free(desc);
The client may only use the created ANeuralNetworksMemory
object with ANeuralNetworksExecution_setInputFromMemory()
or ANeuralNetworksExecution_setOutputFromMemory()
according to the roles specified in the ANeuralNetworksMemoryDesc
object. The offset and length arguments must be set to 0, indicating that the whole memory is used. The client may also explicitly set or extract the contents of the memory by using ANeuralNetworksMemory_copy()
.
You can create opaque memories with roles of unspecified dimensions or rank. In that case, the memory creation might fail with the ANEURALNETWORKS_OP_FAILED
status if it is not supported by the underlying driver. The client is encouraged to implement fallback logic by allocating a large enough buffer backed by Ashmem or BLOB-mode AHardwareBuffer
.
When NNAPI no longer needs to access the opaque memory object, free the corresponding ANeuralNetworksMemory
instance:
ANeuralNetworksMemory_free(opaqueMem);
عملکرد را اندازه گیری کنید
You can evaluate your app's performance by measuring execution time or by profiling.
زمان اجرا
When you want to determine total execution time through the runtime, you can use the synchronous execution API and measure the time taken by the call. When you want to determine total execution time through a lower level of the software stack, you can use ANeuralNetworksExecution_setMeasureTiming
and ANeuralNetworksExecution_getDuration
to get:
- execution time on an accelerator (not in the driver, which runs on the host processor).
- execution time in the driver, including time on the accelerator.
The execution time in the driver excludes overhead such as that of the runtime itself and the IPC needed for the runtime to communicate with the driver.
These APIs measure duration between the work submitted and work completed events, rather than the time a driver or accelerator devotes to performing the inference, possibly interrupted by context switching.
For example, if inference 1 begins, then the driver stops work to perform inference 2, then it resumes and completes inference 1, the execution time for inference 1 will include the time when work was stopped to perform inference 2.
This timing information may be useful for a production deployment of an application to collect telemetry for offline use. You can use the timing data to modify the app for higher performance.
When using this functionality, bear in mind the following:
- Collecting timing information might have a performance cost.
- Only a driver is capable of computing the time spent in itself or on the accelerator, excluding time spent in NNAPI runtime and in IPC.
- You can use these APIs only with an
ANeuralNetworksExecution
that was created withANeuralNetworksCompilation_createForDevices
withnumDevices = 1
. - No driver is required to be able to report timing information.
Profile your application with Android Systrace
Starting with Android 10, NNAPI automatically generates systrace events that you can use to profile your application.
The NNAPI Source comes with a parse_systrace
utility to process the systrace events generated by your application and generate a table view showing the time spent in the different phases of the model lifecycle (Instantiation, Preparation, Compilation Execution and Termination) and different layers of the applications . The layers in which your application is split are:
-
Application
: the main application code -
Runtime
: NNAPI Runtime -
IPC
: The inter process communication between NNAPI Runtime and the Driver code -
Driver
: the accelerator driver process.
Generate the profiling analysys data
Assuming you checked out the AOSP source tree at $ANDROID_BUILD_TOP, and using the TFLite image classification example as target application, you can generate the NNAPI profiling data with the following steps:
- Start the Android systrace with the following command:
$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
The -o trace.html
parameter indicates that the traces will be written in the trace.html
. When profiling own application you will need to replace org.tensorflow.lite.examples.classification
with the process name specified in your app manifest.
This will keep one of your shell console busy, don't run the command in background since it is interactively waiting for an enter
to terminate.
- After the systrace collector is started, start your app and run your benchmark test.
In our case you can start the Image Classification app from Android Studio or directly from your test phone UI if the app has already been installed. To generate some NNAPI data you need to configure the app to use NNAPI by selecting NNAPI as target device in the app configuration dialog.
When the test completes, terminate the systrace by pressing
enter
on the console terminal active since step 1.Run the
systrace_parser
utility generate cumulative statistics:
$ANDROID_BUILD_TOP/frameworks/ml/nn/tools/systrace_parser/parse_systrace.py --total-times trace.html
The parser accepts the following parameters: - --total-times
: shows the total time spent in a layer including the time spent waiting for execution on a call to an underlying layer - --print-detail
: prints all the events that have been collected from systrace - --per-execution
: prints only the execution and its subphases (as per-execution times) instead of stats for all phases - --json
: produces the output in JSON format
An example of the output is shown below:
===========================================================================================================================================
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
The parser might fail if the collected events do not represent a complete application trace. In particular it might fail if systrace events generated to mark the end of a section are present in the trace without an associated section start event. This usually happens if some events from a previous profiling session are being generated when you start the systrace collector. In this case you would have to run your profiling again.
Add statistics for your application code to systrace_parser output
The parse_systrace application is based on the built-in Android systrace functionality. You can add traces for specific operations in your app using the systrace API ( for Java , for native applications ) with custom event names.
To associate your custom events with phases of the Application lifecycle, prepend your event name with one of the following strings:
-
[NN_LA_PI]
: Application level event for Initialization -
[NN_LA_PP]
: Application level event for Preparation -
[NN_LA_PC]
: Application level event for Compilation -
[NN_LA_PE]
: Application level event for Execution
Here is an example of how you can alter the TFLite image classification example code by adding a runInferenceModel
section for the Execution
phase and the Application
layer containing another other sections preprocessBitmap
that won't be considered in NNAPI traces. The runInferenceModel
section will be part of the systrace events processed by the nnapi systrace parser:
کاتلین
/** 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 ListrecognizeImage(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; }
کیفیت خدمات
In Android 11 and higher, NNAPI enables better quality of service (QoS) by allowing an application to indicate the relative priorities of its models, the maximum amount of time expected to prepare a given model, and the maximum amount of time expected to complete a given computation. Android 11 also introduces additional NNAPI result codes that enable applications to understand failures such as missed execution deadlines.
Set the priority of a workload
To set the priority of an NNAPI workload, call ANeuralNetworksCompilation_setPriority()
prior to calling ANeuralNetworksCompilation_finish()
.
ضرب الاجل تعیین کنید
Applications can set deadlines for both model compilation and inference.
- To set the compilation timeout, call
ANeuralNetworksCompilation_setTimeout()
prior to callingANeuralNetworksCompilation_finish()
. - To set the inference timeout, call
ANeuralNetworksExecution_setTimeout()
prior to starting the compilation .
More about operands
The following section covers advanced topics about using operands.
Quantized tensors
A quantized tensor is a compact way to represent an n-dimensional array of floating point values.
NNAPI supports 8-bit asymmetric quantized tensors. For these tensors, the value of each cell is represented by an 8-bit integer. Associated with the tensor is a scale and a zero point value. These are used to convert the 8-bit integers into the floating point values that are being represented.
فرمول این است:
(cellValue - zeroPoint) * scale
where the zeroPoint value is a 32-bit integer and the scale a 32-bit floating point value.
Compared to tensors of 32-bit floating point values, 8-bit quantized tensors have two advantages:
- Your application is smaller, as the trained weights take a quarter of the size of 32-bit tensors.
- Computations can often be executed faster. This is due to the smaller amount of data that needs to be fetched from memory and the efficiency of processors such as DSPs in doing integer math.
While it is possible to convert a floating point model to a quantized one, our experience has shown that better results are achieved by training a quantized model directly. In effect, the neural network learns to compensate for the increased granularity of each value. For each quantized tensor, the scale and zeroPoint values are determined during the training process.
In NNAPI, you define quantized tensor types by setting the type field of the ANeuralNetworksOperandType
data structure to ANEURALNETWORKS_TENSOR_QUANT8_ASYMM
. You also specify the scale and zeroPoint value of the tensor in that data structure.
In addition to 8-bit asymmetric quantized tensors, NNAPI supports the following:
-
ANEURALNETWORKS_TENSOR_QUANT8_SYMM_PER_CHANNEL
which you can use for representing weights toCONV/DEPTHWISE_CONV/TRANSPOSED_CONV
operations. -
ANEURALNETWORKS_TENSOR_QUANT16_ASYMM
which you can use for the internal state ofQUANTIZED_16BIT_LSTM
. -
ANEURALNETWORKS_TENSOR_QUANT8_SYMM
which can be an input toANEURALNETWORKS_DEQUANTIZE
.
Optional operands
A few operations, like ANEURALNETWORKS_LSH_PROJECTION
, take optional operands. To indicate in the model that the optional operand is omitted, call the ANeuralNetworksModel_setOperandValue()
function, passing NULL
for the buffer and 0 for the length.
If the decision on whether the operand is present or not varies for each execution, you indicate that the operand is omitted by using the ANeuralNetworksExecution_setInput()
or ANeuralNetworksExecution_setOutput()
functions, passing NULL
for the buffer and 0 for the length.
Tensors of unknown rank
Android 9 (API level 28) introduced model operands of unknown dimensions but known rank (the number of dimensions). Android 10 (API level 29) introduced tensors of unknown rank, as shown in ANeuralNetworksOperandType .
NNAPI benchmark
The NNAPI benchmark is available on AOSP in platform/test/mlts/benchmark
(benchmark app) and platform/test/mlts/models
(models and datasets).
The benchmark evaluates latency and accuracy and compares drivers to the same work done using Tensorflow Lite running on the CPU, for the same models and datasets.
To use the benchmark, do the following:
Connect a target Android device to your computer, open a terminal window, and make sure the device is reachable through adb.
If more than one Android device is connected, export the target device
ANDROID_SERIAL
environment variable.Navigate to the Android top-level source directory.
دستورات زیر را اجرا کنید:
lunch aosp_arm-userdebug # Or aosp_arm64-userdebug if available ./test/mlts/benchmark/build_and_run_benchmark.sh
At the end of a benchmark run, its results will be presented as an HTML page passed to
xdg-open
.
NNAPI logs
NNAPI generates useful diagnostic information in the system logs. To analyze the logs, use the logcat utility.
Enable verbose NNAPI logging for specific phases or components by setting the property debug.nn.vlog
(using adb shell
) to the following list of values, separated by space, colon, or comma:
-
model
: Model building -
compilation
: Generation of the model execution plan and compilation -
execution
: Model execution -
cpuexe
: Execution of operations using the NNAPI CPU implementation -
manager
: NNAPI extensions, available interfaces and capabilities related info -
all
or1
: All the elements above
For example, to enable full verbose logging use the command adb shell setprop debug.nn.vlog all
. To disable verbose logging, use the command adb shell setprop debug.nn.vlog '""'
.
Once enabled, verbose logging generates log entries at INFO level with a tag set to the phase or component name.
Beside the debug.nn.vlog
controlled messages, NNAPI API components provide other log entries at various levels, each one using a specific log tag.
To get a list of components, search the source tree using the following expression:
grep -R 'define LOG_TAG' | awk -F '"' '{print $2}' | sort -u | egrep -v "Sample|FileTag|test"
This expression currently returns the following tags:
- BurstBuilder
- تماس های تلفنی
- CompilationBuilder
- CpuExecutor
- ExecutionBuilder
- ExecutionBurstController
- ExecutionBurstServer
- ExecutionPlan
- FibonacciDriver
- GraphDump
- IndexedShapeWrapper
- IonWatcher
- مدیر
- حافظه
- MemoryUtils
- MetaModel
- ModelArgumentInfo
- ModelBuilder
- NeuralNetworks
- OperationResolver
- عملیات
- OperationsUtils
- PackageInfo
- TokenHasher
- TypeManager
- Utils
- ValidateHal
- VersionedInterfaces
To control the level of log messages shown by logcat
, use the environment variable ANDROID_LOG_TAGS
.
To show the full set of NNAPI log messages and disable any others, set ANDROID_LOG_TAGS
to the following:
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.
You can set ANDROID_LOG_TAGS
using the following command:
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')
Note that this is just a filter that applies to logcat
. You still need to set the property debug.nn.vlog
to all
to generate verbose log info.