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

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

ड्राइवर, NNAPI से अलग कंपाइलेशन कैश मेमोरी को भी लागू कर सकता है. इसे तब भी लागू किया जा सकता है, जब 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 में मौजूद यूटिलिटी लाइब्रेरी या किसी अन्य सही तरीके का इस्तेमाल कर सकता है. ड्राइवर को अपडेट करने के बाद, इस मैपिंग मैनेजर को फिर से शुरू किया जाना चाहिए. इससे, कैश मेमोरी वाली फ़ाइलों को पिछले वर्शन से तैयार होने से रोका जा सकेगा.

टाइम-ऑफ़-चेक से टाइम-ऑफ़-यूज़ (टीओसीटीओयू) हमलों को रोकने के लिए, ड्राइवर को फ़ाइल में सेव करने से पहले रिकॉर्ड किए गए हैश का हिसाब लगाना होगा. साथ ही, फ़ाइल के कॉन्टेंट को इंटरनल बफ़र में कॉपी करने के बाद, नए हैश का हिसाब लगाना होगा.

इस सैंपल कोड में, इस लॉजिक को लागू करने का तरीका बताया गया है.

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 के किसी भी वर्शन के साथ लागू किया जा सकता है. अगर ड्राइवर, HAL इंटरफ़ेस से डिसकनेक्ट की गई कैश मेमोरी को लागू करने का विकल्प चुनता है, तो ड्राइवर की यह ज़िम्मेदारी है कि जब कैश किए गए आर्टफ़ैक्ट की ज़रूरत न हो, तो उन्हें हटा दिया जाए.