از اندروید ۱۰، رابط برنامهنویسی کاربردی شبکههای عصبی (NNAPI) توابعی را برای پشتیبانی از ذخیرهسازی مصنوعات کامپایل ارائه میدهد که زمان مورد استفاده برای کامپایل را هنگام شروع برنامه کاهش میدهد. با استفاده از این قابلیت ذخیرهسازی، درایور نیازی به مدیریت یا پاک کردن فایلهای ذخیره شده ندارد. این یک ویژگی اختیاری است که میتواند با NN HAL 1.2 پیادهسازی شود. برای اطلاعات بیشتر در مورد این تابع، به ANeuralNetworksCompilation_setCaching مراجعه کنید.
این درایور همچنین میتواند ذخیرهسازی تلفیقی را مستقل از NNAPI پیادهسازی کند. این قابلیت صرف نظر از استفاده یا عدم استفاده از ویژگیهای ذخیرهسازی NNAPI NDK و HAL قابل پیادهسازی است. AOSP یک کتابخانه کاربردی سطح پایین (یک موتور ذخیرهسازی) ارائه میدهد. برای اطلاعات بیشتر، به پیادهسازی یک موتور ذخیرهسازی مراجعه کنید.
مرور کلی گردش کار
این بخش، گردشهای کاری عمومی را با پیادهسازی ویژگی ذخیرهسازی تلفیقی شرح میدهد.
اطلاعات حافظه پنهان ارائه شده و ضربه به حافظه پنهان
- این برنامه یک دایرکتوری ذخیرهسازی و یک چکسام منحصر به فرد برای مدل ارسال میکند.
- زمان اجرای NNAPI بر اساس مجموع کنترلی، ترجیح اجرا و نتیجه پارتیشنبندی، به دنبال فایلهای کش میگردد و آنها را پیدا میکند.
- NNAPI فایلهای کش را باز میکند و با استفاده از
prepareModelFromCache، هندلها را به درایور ارسال میکند. - درایور، مدل را مستقیماً از فایلهای حافظه پنهان (cache) آماده میکند و مدل آمادهشده را برمیگرداند.
اطلاعات حافظه پنهان ارائه شده و خطای حافظه پنهان
- این برنامه یک چکسام منحصر به فرد برای مدل و یک دایرکتوری ذخیرهسازی ارسال میکند.
- زمان اجرای NNAPI بر اساس مجموع کنترلی، ترجیح اجرا و نتیجه پارتیشنبندی به دنبال فایلهای ذخیرهسازی میگردد و فایلهای ذخیرهسازی را پیدا نمیکند.
- NNAPI بر اساس مجموع کنترلی، اولویت اجرا و پارتیشنبندی، فایلهای کش خالی ایجاد میکند، فایلهای کش را باز میکند و هندلها و مدل را با استفاده از
prepareModel_1_2به درایور ارسال میکند. - درایور مدل را کامپایل میکند، اطلاعات ذخیرهسازی را در فایلهای حافظه پنهان مینویسد و مدل آمادهشده را برمیگرداند.
اطلاعات حافظه پنهان ارائه نشده است
- این برنامه بدون ارائه هیچ گونه اطلاعات ذخیره سازی، کامپایل را فراخوانی میکند.
- این برنامه هیچ چیزی مربوط به ذخیره سازی را منتقل نمیکند.
- زمان اجرای NNAPI مدل را با استفاده از
prepareModel_1_2به درایور منتقل میکند. - درایور مدل را کامپایل میکند و مدل آمادهشده را برمیگرداند.
اطلاعات حافظه پنهان
اطلاعات ذخیرهسازی که در اختیار درایور قرار میگیرد شامل یک توکن و شناسههای فایل کش است.
توکن
این توکن ، یک توکن ذخیرهسازی با طول Constant::BYTE_SIZE_OF_CACHE_TOKEN است که مدل آمادهشده را شناسایی میکند. همین توکن هنگام ذخیره فایلهای حافظه پنهان با prepareModel_1_2 و بازیابی مدل آمادهشده با prepareModelFromCache ارائه میشود. کلاینت درایور باید توکنی با نرخ برخورد پایین انتخاب کند. درایور نمیتواند برخورد توکن را تشخیص دهد. برخورد منجر به اجرای ناموفق یا اجرای موفقیتآمیزی میشود که مقادیر خروجی نادرستی تولید میکند.
مدیریت فایلهای کش (دو نوع فایل کش)
دو نوع فایل کش ، کش داده و کش مدل هستند.
- حافظه پنهان داده: برای ذخیره دادههای ثابت شامل بافرهای تانسور پیشپردازششده و تبدیلشده استفاده میشود. تغییر در حافظه پنهان داده نباید منجر به هیچ تأثیری بدتر از تولید مقادیر خروجی بد در زمان اجرا شود.
- حافظه پنهان مدل: برای ذخیره دادههای حساس به امنیت مانند کد اجرایی کامپایل شده ماشین در قالب دودویی بومی دستگاه استفاده میشود. تغییر در حافظه پنهان مدل ممکن است بر رفتار اجرای درایور تأثیر بگذارد و یک کلاینت مخرب میتواند از این برای اجرای فراتر از مجوز اعطا شده استفاده کند. بنابراین، درایور باید قبل از آمادهسازی مدل از حافظه پنهان، بررسی کند که آیا حافظه پنهان مدل خراب شده است یا خیر. برای اطلاعات بیشتر، به بخش امنیت مراجعه کنید.
درایور باید تصمیم بگیرد که اطلاعات حافظه پنهان چگونه بین دو نوع فایل حافظه پنهان توزیع شود و با استفاده از تابع getNumberOfCacheFilesNeeded گزارش دهد که برای هر نوع به چند فایل حافظه پنهان نیاز دارد.
زمان اجرای NNAPI همیشه فایلهای کش را با هر دو مجوز خواندن و نوشتن باز میکند.
امنیت
در ذخیرهسازی تلفیقی، حافظه پنهان مدل ممکن است حاوی دادههای حساس به امنیت مانند کد اجرایی کامپایل شده ماشین در قالب دودویی بومی دستگاه باشد. اگر به درستی محافظت نشود، تغییر در حافظه پنهان مدل ممکن است بر رفتار اجرای درایور تأثیر بگذارد. از آنجا که محتویات حافظه پنهان در دایرکتوری برنامه ذخیره میشوند، فایلهای حافظه پنهان توسط کلاینت قابل تغییر هستند. یک کلاینت دارای باگ ممکن است به طور تصادفی حافظه پنهان را خراب کند و یک کلاینت مخرب میتواند عمداً از این برای اجرای کد تأیید نشده در دستگاه استفاده کند. بسته به ویژگیهای دستگاه، این ممکن است یک مسئله امنیتی باشد. بنابراین، درایور باید قبل از آمادهسازی مدل از حافظه پنهان، بتواند خرابی احتمالی حافظه پنهان مدل را تشخیص دهد.
یک راه برای انجام این کار این است که درایور، نگاشتی از توکن به یک هش رمزنگاریشده از حافظه پنهان مدل را نگهداری کند. درایور میتواند توکن و هش حافظه پنهان مدل خود را هنگام ذخیره کامپایل در حافظه پنهان ذخیره کند. درایور هنگام بازیابی کامپایل از حافظه پنهان، هش جدید حافظه پنهان مدل را با جفت توکن و هش ثبتشده بررسی میکند. این نگاشت باید در طول راهاندازی مجدد سیستم پایدار باشد. درایور میتواند از سرویس فروشگاه کلید اندروید ، کتابخانه ابزار در framework/ml/nn/driver/cache یا هر مکانیسم مناسب دیگری برای پیادهسازی یک مدیر نگاشت استفاده کند. پس از بهروزرسانی درایور، این مدیر نگاشت باید دوباره مقداردهی اولیه شود تا از آمادهسازی فایلهای حافظه پنهان از نسخه قبلی جلوگیری شود.
برای جلوگیری از حملات زمان بررسی تا زمان استفاده (TOCTOU)، درایور باید هش ثبت شده را قبل از ذخیره در فایل محاسبه کند و هش جدید را پس از کپی کردن محتوای فایل در یک بافر داخلی محاسبه کند.
این نمونه کد نحوه پیادهسازی این منطق را نشان میدهد.
bool saveToCache(const sp<V1_2::IPreparedModel> preparedModel,
const hidl_vec<hidl_handle>& modelFds, const hidl_vec<hidl_handle>& dataFds,
const HidlToken& token) {
// Serialize the prepared model to internal buffers.
auto buffers = serialize(preparedModel);
// This implementation detail is important: the cache hash must be computed from internal
// buffers instead of cache files to prevent time-of-check to time-of-use (TOCTOU) attacks.
auto hash = computeHash(buffers);
// Store the {token, hash} pair to a mapping manager that is persistent across reboots.
CacheManager::get()->store(token, hash);
// Write the cache contents from internal buffers to cache files.
return writeToFds(buffers, modelFds, dataFds);
}
sp<V1_2::IPreparedModel> prepareFromCache(const hidl_vec<hidl_handle>& modelFds,
const hidl_vec<hidl_handle>& dataFds,
const HidlToken& token) {
// Copy the cache contents from cache files to internal buffers.
auto buffers = readFromFds(modelFds, dataFds);
// This implementation detail is important: the cache hash must be computed from internal
// buffers instead of cache files to prevent time-of-check to time-of-use (TOCTOU) attacks.
auto hash = computeHash(buffers);
// Validate the {token, hash} pair by a mapping manager that is persistent across reboots.
if (CacheManager::get()->validate(token, hash)) {
// Retrieve the prepared model from internal buffers.
return deserialize<V1_2::IPreparedModel>(buffers);
} else {
return nullptr;
}
}
موارد استفاده پیشرفته
در برخی موارد استفاده پیشرفته، یک درایور پس از فراخوانی کامپایل نیاز به دسترسی به محتوای حافظه پنهان (خواندن یا نوشتن) دارد. موارد استفاده مثال عبارتند از:
- کامپایل درجا (Just-in-time): کامپایل تا اولین اجرا به تعویق میافتد.
- کامپایل چند مرحلهای: در ابتدا یک کامپایل سریع انجام میشود و بسته به میزان استفاده، یک کامپایل بهینه اختیاری در زمان دیگری انجام میشود.
برای دسترسی به محتوای حافظه پنهان (خواندن یا نوشتن) پس از فراخوانی کامپایل، مطمئن شوید که درایور:
- در طول فراخوانی
prepareModel_1_2یاprepareModelFromCacheهندلهای فایل را کپی میکند و محتوای کش را بعداً میخواند/بهروزرسانی میکند. - منطق قفل کردن فایل را خارج از فراخوانی کامپایل معمولی پیادهسازی میکند تا از وقوع نوشتن همزمان با خواندن یا نوشتن دیگر جلوگیری کند.
پیادهسازی یک موتور ذخیرهسازی
علاوه بر رابط ذخیرهسازی تلفیقی NN HAL 1.2، میتوانید یک کتابخانه ابزار ذخیرهسازی را نیز در دایرکتوری frameworks/ml/nn/driver/cache پیدا کنید. زیرشاخه nnCache شامل کد ذخیرهسازی پایدار برای درایور است تا ذخیرهسازی تلفیقی را بدون استفاده از ویژگیهای ذخیرهسازی NNAPI پیادهسازی کند. این شکل از ذخیرهسازی تلفیقی را میتوان با هر نسخهای از NN HAL پیادهسازی کرد. اگر درایور پیادهسازی ذخیرهسازی جدا از رابط HAL را انتخاب کند، درایور مسئول آزادسازی مصنوعات ذخیرهسازی شده در زمانی است که دیگر نیازی به آنها نیست.