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

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

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

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

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

อินเทอร์เฟซแบบระเบิด

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

ประเภท.hal

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

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

IBurstContext.hal

IBurstContext.hal กำหนดวัตถุอินเทอร์เฟซ HIDL ที่อยู่ในบริการ Neural Networks

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

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 ฟังก์ชันโรงงานนี้มีโอเวอร์โหลดสองตัว:

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

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

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

ฟังก์ชันยูทิลิตี้ burst มีอยู่ใน 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);

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