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

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