การดำเนินการแบบต่อเนื่องและคิวข้อความที่รวดเร็ว

Neural Networks HAL 1.2 แนะนำแนวคิดของการดำเนินการแบบระเบิด การดำเนินการแบบระเบิดคือลําดับการดำเนินการของโมเดลที่เตรียมไว้แบบเดียวกันซึ่งเกิดขึ้นอย่างต่อเนื่องอย่างรวดเร็ว เช่น การดำเนินการกับเฟรมของภาพจากกล้องหรือตัวอย่างเสียงที่ต่อเนื่องกัน ออบเจ็กต์การระเบิดใช้เพื่อควบคุมชุดการดําเนินการแบบระเบิด และเพื่อรักษาทรัพยากรระหว่างการดําเนินการ ซึ่งช่วยให้การดําเนินการมีค่าใช้จ่ายเพิ่มเติมน้อยลง วัตถุภาพหลายภาพช่วยให้คุณเพิ่มประสิทธิภาพได้ 3 วิธีดังนี้

  1. ระบบจะสร้างออบเจ็กต์การถ่ายต่อเนื่องก่อนการเรียกใช้ตามลําดับ และระบบจะปล่อยออบเจ็กต์นั้นเมื่อการเรียกใช้ตามลําดับสิ้นสุดลง ด้วยเหตุนี้ อายุของออบเจ็กต์การระเบิดจึงบอกเป็นนัยแก่ไดรเวอร์ว่าควรอยู่ในสถานะประสิทธิภาพสูงนานเท่าใด
  2. ออบเจ็กต์การถ่ายต่อเนื่องจะเก็บรักษาทรัพยากรไว้ระหว่างการเรียกใช้ ตัวอย่างเช่น โปรแกรมควบคุมสามารถแมปออบเจ็กต์หน่วยความจำในการเรียกใช้ครั้งแรกและแคชการแมปนั้นไว้ในออบเจ็กต์ Burst เพื่อใช้ซ้ำในการเรียกใช้ครั้งต่อๆ ไป ทรัพยากรที่แคชไว้จะปล่อยได้เมื่อระบบทำลายออบเจ็กต์ Burst หรือเมื่อรันไทม์ NNAPI แจ้งให้ออบเจ็กต์ Burst ทราบว่าไม่จําเป็นต้องใช้ทรัพยากรนั้นอีกต่อไป
  3. ออบเจ็กต์การระเบิดใช้คิวข้อความด่วน (FMQ) เพื่อสื่อสารระหว่างแอปกับกระบวนการของไดรเวอร์ ซึ่งช่วยลดเวลาในการตอบสนองได้ เนื่องจาก FMQ จะข้าม HIDL และส่งข้อมูลไปยังกระบวนการอื่นโดยตรงผ่าน FIFO แบบวนซ้ำแบบอะตอมในหน่วยความจำที่ใช้ร่วมกัน กระบวนการของผู้บริโภคจะรู้ว่าต้องนำรายการออกจากคิวและเริ่มประมวลผลโดยตรวจสอบจำนวนองค์ประกอบใน FIFO หรือรอสัญญาณ Flag เหตุการณ์ของ FMQ ซึ่งผู้ผลิตส่งสัญญาณ Flag เหตุการณ์นี้เป็นมิวเทคส์ (futex) ในพื้นที่ผู้ใช้ที่รวดเร็ว

FMQ เป็นโครงสร้างข้อมูลที่ระดับล่างซึ่งไม่มีการรับประกันตลอดอายุการใช้งานสำหรับกระบวนการต่างๆ และไม่มีกลไกในตัวสำหรับพิจารณาว่ากระบวนการที่อีกฝั่งหนึ่งของ FMQ ทำงานตามที่คาดไว้หรือไม่ ดังนั้น หากตัวสร้าง FMQ หยุดทำงาน ผู้บริโภคอาจต้องรอข้อมูลที่ไม่มีวันมาถึง วิธีแก้ปัญหาอย่างหนึ่งคือให้ไดรเวอร์เชื่อมโยง FMQ กับออบเจ็กต์การระเบิดระดับที่สูงขึ้นเพื่อตรวจหาเมื่อการเรียกใช้การระเบิดสิ้นสุดลง

เนื่องจากการดำเนินการแบบระเบิดจะทำงานกับอาร์กิวเมนต์เดียวกันและแสดงผลลัพธ์เดียวกันกับเส้นทางการดําเนินการอื่นๆ FMQ พื้นฐานจึงต้องส่งข้อมูลเดียวกันไปยังและจากไดรเวอร์บริการ NNAPI อย่างไรก็ตาม FMQ จะโอนได้เฉพาะประเภทข้อมูลแบบเดิมเท่านั้น การโอนข้อมูลที่ซับซ้อนทำได้โดยการทำให้เป็นอนุกรมและถอดรหัสบัฟเฟอร์ที่ฝังอยู่ (ประเภทเวกเตอร์) ใน FMQ โดยตรง และใช้ออบเจ็กต์การเรียกกลับ HIDL เพื่อโอนแฮนเดิลพูลหน่วยความจำตามต้องการ ฝั่งผู้ผลิตของ FMQ ต้องส่งข้อความคำขอหรือผลลัพธ์ไปยังฝั่งผู้บริโภคแบบเป็นกลุ่มโดยใช้ MessageQueue::writeBlocking หากคิวเป็นแบบบล็อก หรือใช้ MessageQueue::write หากคิวเป็นแบบไม่บล็อก

อินเทอร์เฟซของภาพถ่ายแบบ Burst

อินเทอร์เฟซ Burst สําหรับ HAL ของโครงข่ายประสาทเทียมจะอยู่ใน hardware/interfaces/neuralnetworks/1.2/ และอธิบายไว้ด้านล่าง ดูข้อมูลเพิ่มเติมเกี่ยวกับอินเทอร์เฟซการถ่ายต่อเนื่องในเลเยอร์ NDK ได้ที่ frameworks/ml/nn/runtime/include/NeuralNetworks.h

types.hal

types.hal กำหนดประเภทข้อมูลที่ส่งผ่าน FMQ

  • FmqRequestDatum: องค์ประกอบเดียวของการแสดงผลที่แปลงเป็นอนุกรมของออบเจ็กต์ Request ที่ใช้ดำเนินการและค่า MeasureTiming ซึ่งส่งผ่านคิวข้อความด่วน
  • FmqResultDatum: องค์ประกอบเดียวของการแสดงผลที่แปลงเป็นอนุกรมของค่าที่แสดงผลจากการดำเนินการ (ErrorStatus, OutputShapes และ Timing) ซึ่งแสดงผลผ่านคิวข้อความด่วน

IBurstContext.hal

IBurstContext.hal กำหนดออบเจ็กต์อินเทอร์เฟซ HIDL ที่อยู่ในบริการเครือข่ายประสาทเทียม

  • IBurstContext: ออบเจ็กต์บริบทสำหรับจัดการทรัพยากรของพุล

IBurstCallback.hal

IBurstCallback.hal กำหนดออบเจ็กต์อินเทอร์เฟซ HIDL สำหรับ Callback ที่สร้างขึ้นโดยรันไทม์ของ Neural Networks และบริการ Neural Networks จะใช้เพื่อดึงข้อมูลออบเจ็กต์ hidl_memory ที่สอดคล้องกับตัวระบุช่อง

  • IBurstCallback: ออบเจ็กต์การเรียกกลับที่บริการใช้เพื่อเรียกข้อมูลออบเจ็กต์หน่วยความจำ

IPreparedModel.hal

IPreparedModel.hal ได้รับการขยายใน HAL 1.2 ด้วยเมธอดในการสร้างออบเจ็กต์ IBurstContext จากโมเดลที่เตรียมไว้

  • configureExecutionBurst: กำหนดค่าออบเจ็กต์ Burst ที่ใช้เพื่อดำเนินการอนุมานหลายรายการในโมเดลที่เตรียมไว้อย่างรวดเร็ว

รองรับการดำเนินการแบบต่อเนื่องในไดรเวอร์

วิธีที่ง่ายที่สุดในการรองรับออบเจ็กต์ภาพหลายเฟรมในบริการ HIDL NNAPI คือการใช้ฟังก์ชันยูทิลิตีภาพหลายเฟรม ::android::nn::ExecutionBurstServer::create ซึ่งอยู่ใน ExecutionBurstServer.h และแพ็กเกจในไลบรารีแบบคงที่ libneuralnetworks_common และ libneuralnetworks_util ฟังก์ชันแฟกทอรีนี้มีโอเวอร์โหลด 2 รายการดังนี้

  • การโอเวอร์โหลดรายการหนึ่งยอมรับพอยน์เตอร์ไปยังออบเจ็กต์ IPreparedModel ฟังก์ชันยูทิลิตีนี้ใช้เมธอด executeSynchronously ในออบเจ็กต์ IPreparedModel เพื่อเรียกใช้โมเดล
  • การโอเวอร์โหลดรายการหนึ่งยอมรับออบเจ็กต์ IBurstExecutorWithCache ที่ปรับแต่งได้ ซึ่งสามารถใช้เพื่อแคชทรัพยากร (เช่น การแมป hidl_memory) ที่จะคงอยู่ตลอดการเรียกใช้หลายครั้ง

การโอเวอร์โหลดแต่ละรายการจะแสดงผลออบเจ็กต์ IBurstContext (ซึ่งแสดงออบเจ็กต์ Burst) ที่มีและจัดการเธรดผู้ฟังเฉพาะของตนเอง เทรดนี้รับคําขอจาก FMQ requestChannel ดําเนินการอนุมาน แล้วแสดงผลลัพธ์ผ่าน FMQ resultChannel ระบบจะปล่อยชุดข้อความนี้และทรัพยากรอื่นๆ ทั้งหมดที่อยู่ในออบเจ็กต์ IBurstContext โดยอัตโนมัติเมื่อไคลเอ็นต์ของพุลส์สูญเสียการอ้างอิงถึง IBurstContext

หรือคุณจะสร้างการใช้งาน IBurstContext ของคุณเองก็ได้ ซึ่งจะเข้าใจวิธีส่งและรับข้อความผ่าน FMQ ของ requestChannel และ resultChannel ที่ส่งไปยัง IPreparedModel::configureExecutionBurst

ฟังก์ชันยูทิลิตีของภาพถ่ายอัจฉริยะจะอยู่ใน 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);

ต่อไปนี้เป็นการใช้งานอ้างอิงสำหรับอินเทอร์เฟซ Burst ที่พบในไดรเวอร์ตัวอย่างของ Neural Networks ที่ 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();
}