कंपाइलेशन कैश मेमोरी

Android 10 से, Neural Networks API (NNAPI) की मदद से कंपाइलेशन आर्टफ़ैक्ट को कैश मेमोरी में सेव करने की सुविधा दी जाती है. इससे, ऐप्लिकेशन के शुरू होने पर, कंपाइलेशन में लगने वाला समय कम होता है. कैश मेमोरी में सेव करने की इस सुविधा का इस्तेमाल करने पर, ड्राइवर को कैश मेमोरी में सेव की गई फ़ाइलों को मैनेज करने या हटाने की ज़रूरत नहीं होती. यह एक वैकल्पिक सुविधा है, जिसे NN HAL 1.2 के साथ लागू किया जा सकता है. इस फ़ंक्शन के बारे में ज़्यादा जानकारी के लिए, ANeuralNetworksCompilation_setCaching देखें.

ड्राइवर, एनएनएपीआई के अलावा किसी दूसरे तरीके से भी कंपाइलेशन कैश मेमोरी लागू कर सकता है. चाहे NNAPI NDK और HAL की कैश मेमोरी की सुविधाओं का इस्तेमाल किया गया हो या नहीं, इसे लागू किया जा सकता है. AOSP, एक लो-लेवल यूटिलिटी लाइब्रेरी (कैश मेमोरी का इंजन) उपलब्ध कराता है. ज़्यादा जानकारी के लिए, कैशिंग इंजन लागू करना देखें.

वर्कफ़्लो के बारे में खास जानकारी

इस सेक्शन में, कंपाइलेशन कैश मेमोरी की सुविधा के साथ लागू किए गए सामान्य वर्कफ़्लो के बारे में बताया गया है.

कैश मेमोरी में सेव किए गए डेटा की जानकारी और कैश मेमोरी में डेटा का हिट

  1. ऐप्लिकेशन, कैश मेमोरी में सेव करने की डायरेक्ट्री और मॉडल के लिए यूनीक चेकसम पास करता हो.
  2. NNAPI रनटाइम, चेकसम, एक्ज़ीक्यूशन की प्राथमिकता, और बंटवारे के नतीजे के आधार पर कैश फ़ाइलों को खोजता है और फ़ाइलों को ढूंढता है.
  3. NNAPI, कैश फ़ाइलों को खोलता है और हैंडल को prepareModelFromCache के साथ ड्राइवर को भेजता है.
  4. ड्राइवर, कैश मेमोरी फ़ाइलों से सीधे मॉडल तैयार करता है और तैयार किया गया मॉडल दिखाता है.

कैश मेमोरी की जानकारी दी गई है और कैश मेमोरी में सेव की गई जानकारी मौजूद नहीं है

  1. ऐप्लिकेशन, मॉडल और कैश मेमोरी में सेव किए जाने वाले डायरेक्ट्री के लिए यूनीक चेकसम पास करता है.
  2. NNAPI रनटाइम, चेकसम, एक्ज़ीक्यूशन की प्राथमिकता, और पार्टीशन के नतीजे के आधार पर कैश मेमोरी में सेव की गई फ़ाइलों को खोजता है. हालांकि, इसमें कैश फ़ाइलें नहीं मिलती हैं.
  3. NNAPI, चेकसम, प्रोसेस करने के तरीके, और पार्टिशन के आधार पर खाली कैश मेमोरी फ़ाइलें बनाता है. साथ ही, कैश मेमोरी फ़ाइलें खोलता है और prepareModel_1_2 के साथ हैंडल और मॉडल को ड्राइवर को पास करता है.
  4. ड्राइवर मॉडल को कंपाइल करता है, कैश मेमोरी वाली फ़ाइलों में कैश मेमोरी में सेव होने वाली जानकारी लिखता है, और तैयार किए गए मॉडल को दिखाता है.

कैश मेमोरी की जानकारी नहीं दी गई

  1. ऐप्लिकेशन, कैश मेमोरी की जानकारी दिए बिना ही कंपाइलेशन की सुविधा शुरू करता है.
  2. ऐप्लिकेशन, कैश मेमोरी से जुड़ी कोई जानकारी पास नहीं करता.
  3. NNAPI रनटाइम, मॉडल को ड्राइवर को prepareModel_1_2 के साथ पास करता है.
  4. ड्राइवर, मॉडल को कंपाइल करता है और तैयार किया गया मॉडल दिखाता है.

कैश मेमोरी की जानकारी

ड्राइवर को कैश मेमोरी में सेव करने की जानकारी में, टोकन और कैश मेमोरी में सेव की गई फ़ाइल के हैंडल शामिल होते हैं.

टोकन

टोकन, Constant::BYTE_SIZE_OF_CACHE_TOKEN लंबाई का कैश मेमोरी में सेव किया जाने वाला टोकन होता है. इससे, तैयार किए गए मॉडल की पहचान की जाती है. prepareModel_1_2 की मदद से कैश मेमोरी फ़ाइलें सेव करने और prepareModelFromCache की मदद से तैयार किया गया मॉडल वापस पाने पर, एक ही टोकन दिया जाता है. ड्राइवर के क्लाइंट को एक ऐसा टोकन चुनना होगा जिसकी एक ही दर में टकराव होने की दर कम हो. ड्राइवर को टोकन के आपस में टकराने का पता नहीं चलता. टक्कर लगने की वजह से, एक्ज़ीक्यूट नहीं किया जा सका या सही तरीके से काम किया जा सकता है. इससे आउटपुट की गलत वैल्यू जनरेट होती हैं.

कैश फ़ाइल हैंडल (दो तरह की कैश फ़ाइलें)

कैश मेमोरी में सेव की जाने वाली फ़ाइलें दो तरह की होती हैं: डेटा कैश मेमोरी और मॉडल कैश मेमोरी.

  • डेटा कैश मेमोरी: इसका इस्तेमाल, पहले से प्रोसेस किए गए और बदले गए टेंसर बफ़र के डेटा को कैश मेमोरी में सेव करने के लिए किया जाता है. डेटा कैश मेमोरी में बदलाव करने से, प्रोग्राम के रन होने के समय खराब आउटपुट वैल्यू जनरेट होने के अलावा कोई और बुरा असर नहीं पड़ना चाहिए.
  • मॉडल कैश मेमोरी: इसका इस्तेमाल, सुरक्षा से जुड़े संवेदनशील डेटा को कैश मेमोरी में सेव करने के लिए किया जाता है. जैसे, डिवाइस के नेटिव बाइनरी फ़ॉर्मैट में, इकट्ठा किए गए और चलाए जा सकने वाले मशीन कोड को कैश मेमोरी में सेव करना. मॉडल कैश में बदलाव करने से ड्राइवर के एक्ज़ीक्यूशन के व्यवहार पर असर पड़ सकता है. साथ ही, नुकसान पहुंचाने वाला क्लाइंट, दी गई अनुमति के अलावा अन्य काम करने के लिए इसका इस्तेमाल कर सकता है. इसलिए, कैश मेमोरी से मॉडल तैयार करने से पहले, ड्राइवर को यह देखना चाहिए कि मॉडल का कैश मेमोरी ठीक है या नहीं. ज़्यादा जानकारी के लिए, सुरक्षा देखें.

ड्राइवर को यह तय करना होगा कि दो तरह की कैश फ़ाइलों के बीच, कैश की जानकारी को कैसे बांटा जाए. साथ ही, ड्राइवर को getNumberOfCacheFilesNeeded के साथ यह भी बताना होगा कि हर टाइप के लिए कितनी कैश फ़ाइलों की ज़रूरत है.

NNAPI रनटाइम हमेशा कैश फ़ाइल के हैंडल को पढ़ने और लिखने, दोनों की अनुमति से खोलता है.

सुरक्षा

कंपाइलेशन कैश मेमोरी में, कैश मेमोरी में सेव किए गए मॉडल में सुरक्षा के लिए संवेदनशील डेटा हो सकता है. जैसे, डिवाइस के नेटिव बाइनरी फ़ॉर्मैट में, इकट्ठा किया जाने वाला एक्ज़ीक्यूटेबल मशीन कोड. अगर मॉडल कैश मेमोरी को सही तरीके से सुरक्षित नहीं किया जाता है, तो उसमें बदलाव करने से ड्राइवर के काम करने के तरीके पर असर पड़ सकता है. कैश मेमोरी में सेव किया गया कॉन्टेंट, ऐप्लिकेशन डायरेक्ट्री में सेव होता है. इसलिए, क्लाइंट कैश फ़ाइलों में बदलाव कर सकता है. कोई गड़बड़ी वाला क्लाइंट अनजाने में कैश मेमोरी को खराब कर सकता है और नुकसान पहुंचाने वाला क्लाइंट, डिवाइस पर बिना पुष्टि वाले कोड को लागू करने के लिए जान-बूझकर इसका इस्तेमाल कर सकता है. डिवाइस की विशेषताओं के आधार पर, यह सुरक्षा से जुड़ी समस्या हो सकती है. इसलिए, कैश मेमोरी से मॉडल तैयार करने से पहले, ड्राइवर को ऐसे संभावित मॉडल कैश के खराब होने का पता चलना चाहिए.

ऐसा करने का एक तरीका यह है कि ड्राइवर, मॉडल कैश मेमोरी के क्रिप्टोग्राफ़िक हैश से टोकन तक का मैप बनाए रखे. कंपाइलेशन को कैश मेमोरी में सेव करते समय, ड्राइवर अपने मॉडल कैश मेमोरी का टोकन और हैश सेव कर सकता है. कैश मेमोरी से कंपाइलेशन को वापस लाने के दौरान, ड्राइवर, मॉडल कैश मेमोरी के नए हैश की जांच, रिकॉर्ड किए गए टोकन और हैश पेयर के साथ करता है. यह मैपिंग, सिस्टम को फिर से चालू करने के दौरान स्थायी होनी चाहिए. ड्राइवर, मैपिंग मैनेजर को लागू करने के लिए, Android कीस्टोर सेवा, 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;
    }
}

बेहतर इस्तेमाल के उदाहरण

इस्तेमाल के कुछ बेहतर मामलों में, कंपाइलेशन कॉल के बाद ड्राइवर को कैश मेमोरी में सेव किए गए कॉन्टेंट (पढ़ने या लिखने) के ऐक्सेस की ज़रूरत होती है. इस्तेमाल के उदाहरणों में ये शामिल हैं:

  • सटीक समय पर कंपाइलेशन: कंपाइलेशन को पहली बार एक्ज़ीक्यूट होने में देरी होती है.
  • कई स्टेज वाला कंपाइलेशन: शुरुआत में तेज़ी से कंपाइल किया जाता है और इस्तेमाल की फ़्रीक्वेंसी के आधार पर, वैकल्पिक तौर पर ऑप्टिमाइज़ किया गया कंपाइलेशन बाद में किया जाता है.

कंपाइलेशन कॉल के बाद कैश मेमोरी में मौजूद कॉन्टेंट को पढ़ने या उसमें बदलाव करने के लिए, पक्का करें कि ड्राइवर:

  • prepareModel_1_2 या prepareModelFromCache को शुरू करने के दौरान, फ़ाइल हैंडल का डुप्लीकेट बनाता है और बाद में कैश मेमोरी में मौजूद कॉन्टेंट को पढ़ता/अपडेट करता है.
  • सामान्य कंपाइलेशन कॉल के अलावा, फ़ाइल लॉक करने वाला लॉजिक लागू करता है, ताकि किसी रीड या अन्य राइट के साथ लिखना रोका जा सके.

कैश मेमोरी में सेव करने के लिए इंजन लागू करना

NN HAL 1.2 कंपाइलेशन कैश मेमोरी इंटरफ़ेस के अलावा, आपको frameworks/ml/nn/driver/cache डायरेक्ट्री में कैश मेमोरी की लाइब्रेरी भी मिल सकती है. nnCache सबडायरेक्ट्री में, ड्राइवर के लिए पर्सिस्टेंट स्टोरेज कोड होता है. इससे, NNAPI की कैश मेमोरी की सुविधाओं का इस्तेमाल किए बिना, कंपाइलेशन कैश मेमोरी लागू की जा सकती है. कंपाइलेशन कैश मेमोरी के इस तरीके को, NN HAL के किसी भी वर्शन के साथ लागू किया जा सकता है. अगर ड्राइवर, एचएएल इंटरफ़ेस से डिसकनेक्ट किए गए कैश मेमोरी का इस्तेमाल करने का विकल्प चुनता है, तो कैश मेमोरी में सेव किए गए आर्टफ़ैक्ट की जगह खाली करने की ज़िम्मेदारी ड्राइवर की होती है.