API شبکه های عصبی

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 نشان می دهد.

شکل 1. معماری سیستم برای API شبکه های عصبی اندروید

مدل برنامه نویسی API شبکه های عصبی

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

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

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

    برای اجرای ناهمزمان، چندین رشته می توانند در اجرای یکسان منتظر بمانند. پس از اتمام این اجرا، تمام رشته ها آزاد می شوند.

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

شکل 2 جریان اصلی برنامه نویسی را نشان می دهد.

شکل 2. جریان برنامه نویسی برای API شبکه های عصبی اندروید

بقیه این بخش مراحل راه اندازی مدل 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 مدلی را با دو عمل نشان می دهد: یک جمع و به دنبال آن یک ضرب. مدل یک تانسور ورودی می گیرد و یک تانسور خروجی تولید می کند.

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

مدل فوق دارای هفت عملوند است. این عملوندها به طور ضمنی با نمایه ترتیبی که به مدل اضافه می شوند، شناسایی می شوند. عملوند اول اضافه شده دارای اندیس 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 را ایجاد می کنیم.

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

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

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

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

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

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

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

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

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

    به عنوان پارامترهای این تماس، برنامه شما باید موارد زیر را ارائه کند:

    • نوع عملیات
    • تعداد مقادیر ورودی
    • آرایه شاخص برای عملوندهای ورودی
    • تعداد مقادیر خروجی
    • آرایه شاخص برای عملوندهای خروجی

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

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

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

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

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

    ANeuralNetworksModel_finish(model);

هنگامی که یک مدل ایجاد می کنید، می توانید آن را هر تعداد بار کامپایل کنید و هر کامپایل را هر چند بار اجرا کنید.

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

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

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

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

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

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

تالیف

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

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

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

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

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

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

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

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

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

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

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

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

    ANeuralNetworksCompilation_finish(compilation);

کشف و تخصیص دستگاه

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

کشف دستگاه

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

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

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

از 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 دستگاه تسریع شده است.

اعدام

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

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

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

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

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

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

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

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

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

اجرای همزمان

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

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

اعدام های انفجاری

در دستگاه‌های اندرویدی دارای Android 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 بر روی ۲ غیرفعال کرد.

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

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

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

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

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

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

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

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

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

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

    ANeuralNetworksMemoryDesc_free(desc);

مشتری فقط می‌تواند از شی ANeuralNetworksMemory ایجاد شده با ANeuralNetworksExecution_setInputFromMemory() یا ANeuralNetworksExecution_setOutputFromMemory() با توجه به نقش‌های مشخص شده در شی ANeuralNetworksMemoryDesc استفاده کند. آرگومان های 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 را با مراحل زیر تولید کنید:

  1. 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 به تعاملی است.

  1. پس از شروع جمع کننده Systrace ، برنامه خود را شروع کرده و تست معیار خود را اجرا کنید.

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

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

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

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

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

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

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

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

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

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

برنامه parse_systrace مبتنی بر قابلیت داخلی 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 List recognizeImage(final Bitmap bitmap) {

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

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

کیفیت خدمات

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

اولویت بار کاری را تعیین کنید

برای تعیین اولویت بار کاری NNAPI ، قبل از تماس با ANeuralNetworksCompilation_setPriority() ANeuralNetworksCompilation_finish() تماس بگیرید.

ضرب الاجل تعیین کنید

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

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

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

تنشور کمکی

تانسور کمکی یک روش جمع و جور برای نشان دادن یک آرایه 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_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 انجام می شود ، برای همان مدل ها و مجموعه داده ها مقایسه می کند.

برای استفاده از معیار ، موارد زیر را انجام دهید:

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

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

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

  4. دستورات زیر را اجرا کنید:

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

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

سیاهههای مربوط به NNAPI

NNAPI اطلاعات تشخیصی مفیدی را در سیاهههای مربوط به سیستم ایجاد می کند. برای تجزیه و تحلیل سیاههها ، از ابزار LogCat استفاده کنید.

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

  • model : ساختمان مدل
  • compilation : تولید برنامه اجرای مدل و گردآوری
  • execution : اجرای مدل
  • cpuexe : اجرای عملیات با استفاده از اجرای 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
  • مدیر
  • حافظه
  • حافظه
  • متامدل
  • مدلی
  • مدل ساز
  • کارگاههای عصبی
  • عملگر
  • عملیات
  • عملیات
  • PackageInfo
  • توکننده
  • خفه کننده
  • 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 تنظیم کنید.