버스트 실행 및 FMQ

Neural Networks HAL 1.2에는 버스트 실행의 개념이 도입됩니다. 버스트 실행은 급속한 연속(카메라 캡처 프레임 또는 연속적 오디오 샘플에서 작동하는 모델 등)에서 발생하는 동일한 준비된 모델의 실행 순서입니다. 버스트 개체는 버스트 실행 집합을 제어하고 실행 간의 리소스를 보존하여 실행의 오버헤드를 낮춰줍니다. 버스트 개체는 세 가지 최적화를 지원합니다.

  1. 버스트 개체는 실행 순서 이전에 생성되며 순서가 종료되면 방출됩니다. 따라서 버스트 개체의 전체 기간은 고성능 상태를 얼마나 오랫동안 유지해야 하는지를 드라이버에 시사합니다.
  2. 버스트 개체는 실행 간의 리소스를 보존할 수 있습니다. 예를 들어 드라이버는 첫 실행 시 메모리 개체를 매핑하고 매핑을 버스트 개체에 캐싱하여 후속 실행에서 다시 사용할 수 있도록 합니다. 모든 캐싱된 리소스는 버스트 개체가 파괴되거나 MNAPI 런타임이 리소스가 더 이상 필요없음을 버스트 개체에 알리면 방출할 수 있습니다.
  3. 버스트 개체는 FMQ(빠른 메시지 대기열)를 사용하여 앱과 드라이버 프로세스 간에 통신합니다. 그러면 FMQ가 HIDL을 우회화고 공유 메모리의 원자적 원형 FIFO를 통해 데이터를 다른 프로세스에 직접적으로 전달하기 때문에 지연 시간이 감소할 수 있습니다. 소비자 프로세스는 알아서 항목을 대기열에서 제외하고 FIFO에서 여러 요소를 폴링하거나 프로듀서에 의해 신호를 받는 FMQ의 이벤트 플래그를 기다리는 방식으로 처리 과정을 시작합니다. 이 이벤트 플래그는 futex(fast userspace mutex)입니다.

FMQ는 프로세스 전체에 걸쳐 평생 보증을 제공하지 않는 하위 수준의 데이터 구조이며, FMQ의 반대쪽 끝의 프로세스가 예상대로 실행되고 있는지를 파악하기 위한 메커니즘이 내장되어 있지 않습니다. 따라서 FMQ의 프로듀서가 죽으면 소비자가 도착하지 않을 데이터를 계속해서 기다려야 할 수도 있습니다. 이 문제에 대한 한 가지 해결책은 더 높은 수준의 버스트 개체를 가진 FMQ에 드라이버를 연결하여 버스트 실행이 종료된 시점을 감지하는 것입니다.

버스트 실행은 동일한 인수에 대해 작동하고 다른 실행 경로와 동일한 결과를 반환합니다. 따라서 기본 FMQ와 NNAPI 서비스 드라이버 간에 동일한 데이터가 오가야 합니다. 하지만 FMQ는 POD(plain-old-data) 유형만 전송합니다. 복합 데이터를 전송하려면 중첩된 버퍼(벡터 유형)를 FMQ에서 바로 직렬화하고 역직렬화해야 하며, 요청이 있을 경우 HIDL 콜백 개체를 사용하여 메모리 풀 핸들을 전송해야 합니다. FMQ의 프로듀서 측에서는 대기열이 차단된 경우 MessageQueue::writeBlocking을 사용하여 요청이나 결과 메시지를 소비자에게 원자적으로 전송해야 하며 대기열이 차단되지 않은 경우에는 MessageQueue::write를 사용해야 합니다.

버스트 인터페이스

Neural Networks HAL의 버스트 인터페이스는 hardware/interfaces/neuralnetworks/1.2/에 있으며 아래에 설명이 있습니다. NDK 계층의 버스트 인터페이스에 대한 자세한 내용은 frameworks/ml/nn/runtime/include/NeuralNetworks.h를 참조하세요.

types.hal

types.hal은 FMQ에 걸쳐 전송된 데이터 유형을 정의합니다.

  • FmqRequestDatum: FMQ 전체에 걸쳐 전송되는 실행 Request 개체 및 MeasureTiming 값의 일련화된 표현으로 이루어진 단일 요소입니다.
  • FmqResultDatum: 실행(ErrorStatus, OutputShapesTiming)에서 반환되고 FMQ를 통해 반환되는 값의 일련화된 표현으로 이루어진 단일 요소입니다.

IBurstContext.hal

IBurstContext.hal은 신경망 서비스에서 상주하는 HIDL 인터페이스 개체를 정의합니다.

  • IBurstContext: 버스트의 리소스 관리를 위한 컨텍스트 개체입니다.

IBurstCallback.hal

IBurstCallback.hal은 신경망 런타임에 의해 생성된 콜백의 HIDL 인터페이스 개체를 정의하며, 신경망 서비스에서 슬롯 식별자에 해당하는 hidl_memory 개체를 가져오는 데 사용됩니다.

  • IBurstCallback: 서비스에서 메모리 개체를 가져오기 위해 사용하는 콜백 개체입니다.

IPreparedModel.hal

IPreparedModel.hal은 메서드와 함께 HAL 1.2에서 확장되어 준비된 모델에서 IBurstContext 개체를 생성합니다.

  • configureExecutionBurst: 빠른 연속의 준비된 모델에 대한 여러 추론을 실행하는 데 사용되는 버스트 개체를 구성합니다.

드라이버에서 버스트 실행 지원

HIDL NNAPI 서비스에서 버스트 개체를 지원하기 위한 가장 간단한 방법은 ExecutionBurstServer.h에서 찾을 수 있고 libneuralnetworks_commonlibneuralnetworks_util 정적 라이브러리에서 패키징된 버스트 유틸리티 함수 ::android::nn::ExecutionBurstServer::create를 사용하는 것입니다. 이 공장 기능에는 두 개의 오버로드가 있습니다.

  • 한 오버로드는 IPreparedModel 개체에 대한 포인터를 수락합니다. 이 유틸리티 함수는 IPreparedModel 개체에 executeSynchronously 메서드를 사용하여 모델을 실행합니다.
  • 나머지 오버로드는 맞춤설정 가능한 IBurstExecutorWithCache 개체를 수락합니다. 이 개체는 여러 실행에서 지속되는 리소스(예: hidl_memory 매핑)를 캐싱하는 데 사용될 수 있습니다.

각 오버로드는 자체의 전용 리스너 스레드를 포함하고 관리하는 IBurstContext 개체(버스트 개체를 표현)를 반환합니다. 이 스레드는 requestChannel FMQ에서 요청을 수신하고 추론을 수행한 다음 resultChannel FMQ를 통해 결과를 반환합니다. 이 스레드와 IBurstContext 개체에 포함된 다른 모든 리소스는 버스트의 클라이언트가 IBurstContext에 대한 참조를 잃으면 자동으로 방출됩니다.

아니면 IPreparedModel::configureExecutionBurst에 전달된 requestChannelresultChannel FMQ를 통해 메시지를 전송하고 수신하는 방식을 이해하는 IBurstContext의 자체 구현을 생성할 수도 있습니다.

버스트 유틸리티 함수는 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();
    }