एक साथ कई टास्क पूरा करना और मैसेज की तेज़ी से चलने वाली कतारें

Neural Networks HAL 1.2 में, एक साथ कई अनुरोधों को पूरा करने की सुविधा पेश की गई है. बर्स्ट एक्ज़ीक्यूशन, तैयार किए गए एक ही मॉडल के एक्ज़ीक्यूशन का एक क्रम होता है. ये एक्ज़ीक्यूशन, तेज़ी से एक के बाद एक होते हैं. जैसे, कैमरा कैप्चर के फ़्रेम या लगातार ऑडियो सैंपल पर काम करने वाले एक्ज़ीक्यूशन. बर्स्ट ऑब्जेक्ट का इस्तेमाल, बर्स्ट एक्ज़ीक्यूशन के सेट को कंट्रोल करने के लिए किया जाता है. साथ ही, इसका इस्तेमाल एक्ज़ीक्यूशन के बीच संसाधनों को सुरक्षित रखने के लिए किया जाता है. इससे एक्ज़ीक्यूशन का ओवरहेड कम हो जाता है. बर्स्ट ऑब्जेक्ट की मदद से, तीन तरह के ऑप्टिमाइज़ेशन किए जा सकते हैं:

  1. बर्स्ट ऑब्जेक्ट, एक्ज़ीक्यूशन के क्रम से पहले बनाया जाता है. साथ ही, क्रम खत्म होने पर इसे हटा दिया जाता है. इस वजह से, बर्स्ट ऑब्जेक्ट की लाइफ़टाइम वैल्यू से ड्राइवर को यह पता चलता है कि उसे कितनी देर तक हाई-परफ़ॉर्मेंस मोड में रहना चाहिए.
  2. बर्स्ट ऑब्जेक्ट, एक से ज़्यादा बार लागू किए जाने के बीच संसाधनों को सेव रख सकता है. उदाहरण के लिए, कोई ड्राइवर पहली बार मेमोरी ऑब्जेक्ट को मैप कर सकता है. साथ ही, बाद में इस्तेमाल करने के लिए, मैपिंग को बर्स्ट ऑब्जेक्ट में कैश मेमोरी में सेव कर सकता है. कैश किए गए किसी भी संसाधन को तब रिलीज़ किया जा सकता है, जब बर्स्ट ऑब्जेक्ट मिटा दिया जाता है या जब NNAPI रनटाइम, बर्स्ट ऑब्जेक्ट को सूचना देता है कि संसाधन की अब ज़रूरत नहीं है.
  3. बर्स्ट ऑब्जेक्ट, ऐप्लिकेशन और ड्राइवर प्रोसेस के बीच कम्यूनिकेट करने के लिए, फ़ास्ट मैसेज कतारों (एफ़एमक्यू) का इस्तेमाल करता है. इससे लेटेन्सी कम हो सकती है, क्योंकि FMQ, HIDL को बायपास करता है और शेयर की गई मेमोरी में एटॉमिक सर्कुलर FIFO के ज़रिए, डेटा को सीधे दूसरी प्रोसेस में भेजता है. उपयोगकर्ता प्रोसेस को किसी आइटम को डीक्यू करने और उसे प्रोसेस करने के बारे में पता होता है. इसके लिए, वह FIFO में मौजूद एलिमेंट की संख्या को पोल करती है या FMQ के इवेंट फ़्लैग का इंतज़ार करती है. इस फ़्लैग के बारे में प्रोड्यूसर सूचना देता है. यह इवेंट फ़्लैग, फ़ास्ट यूज़रस्पेस म्यूटेक्स (फ़्यूटेक्स) है.

एफ़एमक्यू, डेटा स्ट्रक्चर का एक बुनियादी लेवल है. यह सभी प्रोसेस में लाइफ़टाइम की गारंटी नहीं देता. साथ ही, इसमें यह पता लगाने के लिए कोई बिल्ट-इन मैकेनिज़्म नहीं होता कि एफ़एमक्यू के दूसरे छोर पर मौजूद प्रोसेस, उम्मीद के मुताबिक चल रही है या नहीं. इसलिए, अगर FMQ के लिए डेटा जनरेट करने वाला व्यक्ति मर जाता है, तो डेटा का इस्तेमाल करने वाला व्यक्ति ऐसे डेटा का इंतज़ार करता रह सकता है जो कभी नहीं आएगा. इस समस्या को हल करने का एक तरीका यह है कि ड्राइवर, FMQ को ज़्यादा लेवल वाले बर्स्ट ऑब्जेक्ट से जोड़ दे. इससे यह पता चल पाएगा कि बर्स्ट का एक्ज़ीक्यूशन कब खत्म हुआ.

बर्स्ट एक्ज़ीक्यूशन, एक ही आर्ग्युमेंट पर काम करते हैं और अन्य एक्ज़ीक्यूशन पाथ की तरह ही नतीजे देते हैं. इसलिए, बुनियादी FMQ को NNAPI सेवा ड्राइवर को और उससे एक जैसा डेटा पास करना होगा. हालांकि, FMQ सिर्फ़ सामान्य डेटा टाइप ट्रांसफ़र कर सकते हैं. जटिल डेटा को ट्रांसफ़र करने के लिए, FMQ में सीधे तौर पर नेस्ट किए गए बफ़र (वेक्टर टाइप) को क्रम से लगाया जाता है और क्रम से हटाया जाता है. साथ ही, ज़रूरत पड़ने पर मेमोरी पूल हैंडल ट्रांसफ़र करने के लिए, HIDL कॉलबैक ऑब्जेक्ट का इस्तेमाल किया जाता है. FMQ के प्रोड्यूसर को, अनुरोध या नतीजे के मैसेज को उपभोक्ता को एक साथ भेजना होगा. इसके लिए, अगर कतार ब्लॉक हो रही है, तो MessageQueue::writeBlocking का इस्तेमाल करें. अगर कतार ब्लॉक नहीं हो रही है, तो MessageQueue::write का इस्तेमाल करें.

बर्स्ट इंटरफ़ेस

न्यूरल नेटवर्क एचएएल के लिए बर्स्ट इंटरफ़ेस, hardware/interfaces/neuralnetworks/1.2/ में मिलते हैं. इनके बारे में यहां बताया गया है. एनडीके लेयर में बर्स्ट इंटरफ़ेस के बारे में ज़्यादा जानने के लिए, frameworks/ml/nn/runtime/include/NeuralNetworks.h देखें.

types.hal

types.hal इसकी मदद से, यह तय किया जाता है कि FMQ के ज़रिए किस तरह का डेटा भेजा जाएगा.

  • FmqRequestDatum: यह Request ऑब्जेक्ट और MeasureTiming वैल्यू के सीरियल किए गए प्रज़ेंटेशन का एक एलिमेंट है. इसे फ़ास्ट मैसेज क्यू में भेजा जाता है.
  • FmqResultDatum: यह किसी एक्ज़ीक्यूशन (ErrorStatus, OutputShapes, और Timing) से मिली वैल्यू के सीरियलाइज़ किए गए फ़ॉर्मैट का एक एलिमेंट है. इसे फ़ास्ट मैसेज क्यू के ज़रिए वापस भेजा जाता है.

IBurstContext.hal

IBurstContext.hal एचआईडीएल इंटरफ़ेस ऑब्जेक्ट को तय करता है, जो न्यूरल नेटवर्क सेवा में मौजूद होता है.

  • IBurstContext: यह कॉन्टेक्स्ट ऑब्जेक्ट, बर्स्ट के संसाधनों को मैनेज करता है.

IBurstCallback.hal

IBurstCallback.hal न्यूरल नेटवर्क रनटाइम से बनाए गए कॉलबैक के लिए, एचआईडीएल इंटरफ़ेस ऑब्जेक्ट को तय करता है. साथ ही, इसका इस्तेमाल न्यूरल नेटवर्क सेवा, स्लॉट आइडेंटिफ़ायर से जुड़े hidl_memory ऑब्जेक्ट को वापस पाने के लिए करती है.

  • IBurstCallback: यह एक कॉलबैक ऑब्जेक्ट है. इसका इस्तेमाल कोई सेवा, मेमोरी ऑब्जेक्ट को वापस पाने के लिए करती है.

IPreparedModel.hal

IPreparedModel.hal को HAL 1.2 में बढ़ाया गया है. इसमें तैयार किए गए मॉडल से IBurstContext ऑब्जेक्ट बनाने का तरीका शामिल है.

  • configureExecutionBurst: यह बस्ट ऑब्जेक्ट को कॉन्फ़िगर करता है. इसका इस्तेमाल, तैयार किए गए मॉडल पर एक के बाद एक कई अनुमान लगाने के लिए किया जाता है.

ड्राइवर में एक साथ कई कार्रवाइयाँ करने की सुविधा

HIDL NNAPI सेवा में बस्ट ऑब्जेक्ट को सपोर्ट करने का सबसे आसान तरीका है, बस्ट यूटिलिटी फ़ंक्शन ::android::nn::ExecutionBurstServer::create का इस्तेमाल करना. यह ExecutionBurstServer.h में मौजूद होता है. साथ ही, इसे libneuralnetworks_common और libneuralnetworks_util स्टैटिक लाइब्रेरी में पैकेज किया जाता है. इस फ़ैक्ट्री फ़ंक्शन के दो ओवरलोड हैं:

  • एक ओवरलोड, IPreparedModel ऑब्जेक्ट के पॉइंटर को स्वीकार करता है. यह यूटिलिटी फ़ंक्शन, मॉडल को लागू करने के लिए executeSynchronously ऑब्जेक्ट में executeSynchronously तरीके का इस्तेमाल करता है.IPreparedModel
  • एक ओवरलोड, पसंद के मुताबिक बनाए जा सकने वाले IBurstExecutorWithCache ऑब्जेक्ट को स्वीकार करता है. इसका इस्तेमाल, उन संसाधनों (जैसे कि hidl_memory मैपिंग) को कैश मेमोरी में सेव करने के लिए किया जा सकता है जो कई बार लागू होते हैं.

हर ओवरलोड, IBurstContext ऑब्जेक्ट दिखाता है. यह ऑब्जेक्ट, बर्स्ट ऑब्जेक्ट को दिखाता है. इसमें अपनी लिसनर थ्रेड होती है और यह उसे मैनेज करता है. इस थ्रेड को requestChannel FMQ से अनुरोध मिलते हैं. इसके बाद, यह थ्रेड अनुमान लगाता है और resultChannel FMQ के ज़रिए नतीजे दिखाता है. जब बर्स्ट का क्लाइंट, IBurstContext का रेफ़रंस खो देता है, तब यह थ्रेड और IBurstContext ऑब्जेक्ट में मौजूद अन्य सभी संसाधन अपने-आप रिलीज़ हो जाते हैं.

इसके अलावा, IBurstContext को लागू करने का अपना तरीका बनाया जा सकता है. इससे यह समझ पाएगा कि requestChannel और IPreparedModel::configureExecutionBurst को पास किए गए resultChannel FMQ के ज़रिए मैसेज कैसे भेजे और पाए जाते हैं.

बर्स्ट यूटिलिटी फ़ंक्शन, ExecutionBurstServer.h में मौजूद होते हैं.

/**
 * Create automated context to manage FMQ-based executions.
 *
 * This function is intended to be used by a service to automatically:
 * 1) Receive data from a provided FMQ
 * 2) Execute a model with the given information
 * 3) Send the result to the created FMQ
 *
 * @param callback Callback used to retrieve memories corresponding to
 *     unrecognized slots.
 * @param requestChannel Input FMQ channel through which the client passes the
 *     request to the service.
 * @param resultChannel Output FMQ channel from which the client can retrieve
 *     the result of the execution.
 * @param executorWithCache Object which maintains a local cache of the
 *     memory pools and executes using the cached memory pools.
 * @result IBurstContext Handle to the burst context.
 */
static sp<ExecutionBurstServer> create(
        const sp<IBurstCallback>& callback, const FmqRequestDescriptor& requestChannel,
        const FmqResultDescriptor& resultChannel,
        std::shared_ptr<IBurstExecutorWithCache> executorWithCache);

/**
 * Create automated context to manage FMQ-based executions.
 *
 * This function is intended to be used by a service to automatically:
 * 1) Receive data from a provided FMQ
 * 2) Execute a model with the given information
 * 3) Send the result to the created FMQ
 *
 * @param callback Callback used to retrieve memories corresponding to
 *     unrecognized slots.
 * @param requestChannel Input FMQ channel through which the client passes the
 *     request to the service.
 * @param resultChannel Output FMQ channel from which the client can retrieve
 *     the result of the execution.
 * @param preparedModel PreparedModel that the burst object was created from.
 *     IPreparedModel::executeSynchronously will be used to perform the
 *     execution.
 * @result IBurstContext Handle to the burst context.
 */
  static sp<ExecutionBurstServer> create(const sp<IBurstCallback>& callback,
                                         const FmqRequestDescriptor& requestChannel,
                                         const FmqResultDescriptor& resultChannel,
                                         IPreparedModel* preparedModel);

यहां frameworks/ml/nn/driver/sample/SampleDriver.cpp पर मौजूद न्यूरल नेटवर्क के सैंपल ड्राइवर में मिले बर्स्ट इंटरफ़ेस को लागू करने का रेफ़रंस दिया गया है.

Return<void> SamplePreparedModel::configureExecutionBurst(
        const sp<V1_2::IBurstCallback>& callback,
        const MQDescriptorSync<V1_2::FmqRequestDatum>& requestChannel,
        const MQDescriptorSync<V1_2::FmqResultDatum>& resultChannel,
        configureExecutionBurst_cb cb) {
    NNTRACE_FULL(NNTRACE_LAYER_DRIVER, NNTRACE_PHASE_EXECUTION,
                 "SampleDriver::configureExecutionBurst");
    // Alternatively, the burst could be configured via:
    // const sp<V1_2::IBurstContext> burst =
    //         ExecutionBurstServer::create(callback, requestChannel,
    //                                      resultChannel, this);
    //
    // However, this alternative representation does not include a memory map
    // caching optimization, and adds overhead.
    const std::shared_ptr<BurstExecutorWithCache> executorWithCache =
            std::make_shared<BurstExecutorWithCache>(mModel, mDriver, mPoolInfos);
    const sp<V1_2::IBurstContext> burst = ExecutionBurstServer::create(
            callback, requestChannel, resultChannel, executorWithCache);
    if (burst == nullptr) {
        cb(ErrorStatus::GENERAL_FAILURE, {});
    } else {
        cb(ErrorStatus::NONE, burst);
    }
    return Void();
}