Ejecuciones en ráfaga y colas de mensajes rápidas

La HAL 1.2 de redes neuronales presenta el concepto de ejecuciones en aumento de actividad. Ráfaga las ejecuciones son una secuencia de ejecuciones del mismo modelo preparado que ocurre en en una sucesión rápida, como las que se realizan en marcos de una captura de cámara o audio sucesivo de muestra. Un objeto de ráfaga se usa para controlar un conjunto de ejecuciones de ráfaga recursos entre ejecuciones, lo que permite que estas tengan menor la sobrecarga. Los objetos de ráfaga habilitan tres optimizaciones:

  1. Se crea un objeto de ráfaga antes de una secuencia de ejecuciones y se libera cuando la secuencia haya finalizado. Debido a esto, la vida útil de la ráfaga sugiere a un controlador cuánto tiempo debe permanecer en un entorno para cada estado.
  2. Un objeto de ráfaga puede conservar recursos entre ejecuciones. Por ejemplo, un El controlador puede asignar un objeto de memoria en la primera ejecución y almacenar en caché la asignación. en el objeto de ráfaga para volver a usarlo en ejecuciones posteriores. Cualquier recurso almacenado en caché se puede liberar cuando se destruye el objeto de ráfaga o cuando la NNAPI el entorno de ejecución notifica al objeto de ráfaga que el recurso ya no es necesario.
  3. Un objeto de ráfaga usa colas de mensajes rápidas (FMQ) para la comunicación entre los procesos de la app y los controladores. Esto puede reducir la latencia porque el FMQ omite el HIDL y pasa los datos directamente otro proceso a través de un FIFO atómico circular en una memoria compartida. El consumidor sabe que debe retirar un elemento de la cola y comenzar a procesarse, ya sea sondeando el número de elementos en el FIFO o esperando un evento de FMQ marca, que indica el productor. La marca de este evento es rápida exclusión mutua de espacio de usuario (futex)

Una FMQ es una estructura de datos de bajo nivel que no ofrece garantías de por vida en procesos y no tiene un mecanismo integrado para determinar si el proceso en la de la FMQ se esté ejecutando como se espera. Por lo tanto, si el productor muere, el consumidor puede quedarse atascado esperando datos que nunca llegan. Uno a este problema es que el conductor asocie las FMQ con el un objeto de ráfaga de nivel superior para detectar cuándo finalizó la ejecución de la ráfaga.

Debido a que las ejecuciones en ráfaga operan en los mismos argumentos y muestran el mismo como otras rutas de ejecución, las FMQ subyacentes deben pasar los mismos datos y de los controladores del servicio de NNAPI. Sin embargo, las FMQ solo pueden transferir tipos de datos sin formato. La transferencia de datos complejos se realiza mediante la serialización y deserializar búferes anidados (tipos de vectores) directamente en las FMQ, y con Objetos de devolución de llamada HIDL para transferir controladores de grupos de memoria a pedido. El productor de la FMQ debe enviar los mensajes de solicitud o resultados al consumidor de forma atómica con MessageQueue::writeBlocking si la cola está bloqueando a través de MessageQueue::write si la cola no tiene bloqueo

Interfaces de ráfaga

Las interfaces de aumento de actividad para la HAL de redes neuronales se encuentran en hardware/interfaces/neuralnetworks/1.2/ y se describen a continuación. Para obtener más información sobre las interfaces de aumento de actividad en el NDK consulta, consulta frameworks/ml/nn/runtime/include/NeuralNetworks.h

tipos.hal

types.hal define el tipo de datos que se envían a través de FMQ.

  • FmqRequestDatum: Un solo elemento de una representación serializada de una ejecución Request y un valor MeasureTiming, que se envía en el mensaje rápido en la fila.
  • FmqResultDatum: Un único elemento de una representación serializada de los valores que se muestran una ejecución (ErrorStatus, OutputShapes y Timing), que es se devuelven a través de la cola de mensajes rápidos.

IBurstContext.hal

IBurstContext.hal define el objeto de la interfaz HIDL que reside en el servicio de redes neuronales.

  • IBurstContext: Objeto de contexto para administrar los recursos de un aumento de actividad.

IBurstCallback.hal

IBurstCallback.hal define el objeto de la interfaz HIDL para una devolución de llamada creada por las redes neuronales tiempo de ejecución y el servicio de redes neuronales la usa para recuperar hidl_memory objetos correspondientes a los identificadores de ranuras.

  • IBurstCallback: Es un objeto de devolución de llamada que usa un servicio para recuperar objetos de memoria.

Modelo_Preparado.hal

IPreparedModel.hal se extiende en HAL 1.2 con un método para crear un objeto IBurstContext a partir de un previamente entrenado.

  • configureExecutionBurst: Configura un objeto de ráfaga que se usa para ejecutar múltiples inferencias en un experimento modelo en una sucesión rápida.

Admite ejecuciones en ráfaga en un controlador

La manera más simple de admitir objetos de ráfaga en un servicio de NNAPI HIDL es usar la función de utilidad de ráfaga ::android::nn::ExecutionBurstServer::create, que es se encuentran en ExecutionBurstServer.h y empaquetado en libneuralnetworks_common y libneuralnetworks_util estáticas. Esta función de fábrica tiene dos sobrecargas:

  • Una sobrecarga acepta un puntero a un objeto IPreparedModel. Esta función de utilidad usa el método executeSynchronously en un Un objeto IPreparedModel para ejecutar el modelo.
  • Una sobrecarga acepta un objeto IBurstExecutorWithCache personalizable, que se puede usar para almacenar en caché los recursos (como las asignaciones hidl_memory) que persisten en múltiples ejecuciones.

Cada sobrecarga muestra un objeto IBurstContext (que representa el aumento de actividad). que contiene y administra su propio subproceso de objeto de escucha dedicado. Esta conversación recibe solicitudes de la FMQ requestChannel, realiza la inferencia y, luego, devuelve los resultados a través de la FMQ resultChannel. Esta conversación y todas las demás Los recursos contenidos en el objeto IBurstContext se liberan automáticamente Cuando el cliente del aumento de actividad pierde su referencia a IBurstContext.

Como alternativa, puedes crear tu propia implementación de IBurstContext, que comprenda cómo enviar y recibir mensajes a través de requestChannel y Se pasaron resultChannel FMQ a IPreparedModel::configureExecutionBurst.

Las funciones de utilidad de ráfaga se encuentran en 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);

La siguiente es una implementación de referencia de una interfaz de aumento de actividad que se encuentra en el Controlador de muestra de redes neuronales en 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();
}