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

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

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

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

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

อินเทอร์เฟซการถ่ายภาพต่อเนื่อง

อินเทอร์เฟซการระเบิดสำหรับ 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 ที่อยู่ในบริการ Neural Networks

  • IBurstContext: ออบเจ็กต์บริบทเพื่อจัดการทรัพยากรของ Burst

IBurstCallback.hal

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

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

IPreparedModel.hal

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

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

รองรับการดำเนินการแบบกลุ่มในไดรเวอร์

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

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

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

หรือคุณจะสร้างการติดตั้งใช้งาน IBurstContext ของคุณเองก็ได้ ซึ่งจะ เข้าใจวิธีส่งและรับข้อความผ่าน requestChannel และ resultChannel FMQ ที่ส่งไปยัง 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);

ต่อไปนี้คือการใช้งานอ้างอิงของอินเทอร์เฟซแบบกลุ่มที่พบใน ไดรเวอร์ตัวอย่างของเครือข่ายประสาทที่ 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();
}