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

Neural Networks HAL 1.2 introduce el concepto de ejecuciones en ráfaga. Las ejecuciones en ráfaga son una secuencia de ejecuciones del mismo modelo preparado que ocurren en rápida sucesión, como aquellas que operan en fotogramas de una captura de cámara o muestras de audio sucesivas. Un objeto de ráfaga se utiliza para controlar un conjunto de ejecuciones de ráfaga y para preservar recursos entre ejecuciones, lo que permite que las ejecuciones tengan una sobrecarga menor. Los objetos de ráfaga permiten tres optimizaciones:

  1. Un objeto de ráfaga se crea antes de una secuencia de ejecuciones y se libera cuando la secuencia finaliza. Debido a esto, la vida útil del objeto en explosión le indica al conductor cuánto tiempo debe permanecer en un estado de alto rendimiento.
  2. Un objeto en ráfaga puede preservar recursos entre ejecuciones. Por ejemplo, un 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 su reutilización en ejecuciones posteriores. Cualquier recurso almacenado en caché se puede liberar cuando se destruye el objeto de ráfaga o cuando el tiempo de ejecución de NNAPI notifica al objeto de ráfaga que el recurso ya no es necesario.
  3. Un objeto de ráfaga utiliza colas de mensajes rápidos (FMQ) para comunicarse entre los procesos de la aplicación y el controlador. Esto puede reducir la latencia porque FMQ omite HIDL y pasa datos directamente a otro proceso a través de un FIFO circular atómico en la memoria compartida. El proceso del consumidor sabe cómo retirar un artículo de la cola y comenzar a procesarlo, ya sea sondeando el número de elementos en el FIFO o esperando el indicador de evento de FMQ, que es señalado por el productor. Este indicador de evento es un mutex rápido en el espacio de usuario (futex).

Una FMQ es una estructura de datos de bajo nivel que no ofrece garantías de por vida en todos los procesos y no tiene ningún mecanismo incorporado para determinar si el proceso en el otro extremo de la FMQ se está ejecutando como se esperaba. En consecuencia, si el productor del FMQ muere, el consumidor puede quedarse atrapado esperando datos que nunca llegan. Una solución a este problema es que el controlador asocie las FMQ con el objeto de ráfaga de nivel superior para detectar cuándo ha finalizado la ejecución de la ráfaga.

Debido a que las ejecuciones en ráfaga operan con los mismos argumentos y devuelven los mismos resultados que otras rutas de ejecución, las FMQ subyacentes deben pasar los mismos datos hacia y desde los controladores del servicio NNAPI. Sin embargo, las FMQ solo pueden transferir tipos de datos antiguos. La transferencia de datos complejos se logra serializando y deserializando buffers anidados (tipos vectoriales) directamente en las FMQ y usando objetos de devolución de llamada HIDL para transferir identificadores del grupo de memoria a pedido. El lado productor de FMQ debe enviar los mensajes de solicitud o resultado al consumidor de forma atómica usando MessageQueue::writeBlocking si la cola está bloqueando, o usando MessageQueue::write si la cola no está bloqueando.

Interfaces de ráfaga

Las interfaces de ráfaga para Neural Networks HAL se encuentran en hardware/interfaces/neuralnetworks/1.2/ y se describen a continuación. Para obtener más información sobre las interfaces de ráfaga en la capa NDK, consulte frameworks/ml/nn/runtime/include/NeuralNetworks.h .

tipos.hal

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

  • FmqRequestDatum : un elemento único de una representación serializada de un objeto Request de ejecución y un valor MeasureTiming , que se envía a través de la cola de mensajes rápidos.
  • FmqResultDatum : un elemento único de una representación serializada de los valores devueltos por una ejecución ( ErrorStatus , OutputShapes y Timing ), que se devuelve a través de la cola de mensajes rápidos.

IBurstContext.hal

IBurstContext.hal define el objeto de interfaz HIDL que reside en el servicio Neural Networks.

  • IBurstContext : Objeto de contexto para gestionar los recursos de una ráfaga.

IBurstCallback.hal

IBurstCallback.hal define el objeto de interfaz HIDL para una devolución de llamada creada por el tiempo de ejecución de Neural Networks y el servicio Neural Networks lo utiliza para recuperar objetos hidl_memory correspondientes a identificadores de ranura.

  • IBurstCallback : objeto de devolución de llamada utilizado por un servicio para recuperar objetos de memoria.

IPreparedModel.hal

IPreparedModel.hal se amplía en HAL 1.2 con un método para crear un objeto IBurstContext a partir de un modelo preparado.

  • configureExecutionBurst : configura un objeto de ráfaga utilizado para ejecutar múltiples inferencias en un modelo preparado en rápida sucesión.

Admite ejecuciones en ráfaga en un controlador.

La forma más sencilla de admitir objetos de ráfaga en un servicio HIDL NNAPI es utilizar la función de utilidad de ráfaga ::android::nn::ExecutionBurstServer::create , que se encuentra en ExecutionBurstServer.h y está empaquetada en las bibliotecas estáticas libneuralnetworks_common y libneuralnetworks_util . Esta función de fábrica tiene dos sobrecargas:

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

Cada sobrecarga devuelve un objeto IBurstContext (que representa el objeto de ráfaga) que contiene y administra su propio hilo de escucha dedicado. Este hilo recibe solicitudes de requestChannel FMQ, realiza la inferencia y luego devuelve los resultados a través de resultChannel FMQ. Este hilo y todos los demás recursos contenidos en el objeto IBurstContext se liberan automáticamente cuando el cliente de la ráfaga pierde su referencia a IBurstContext .

Como alternativa, puede crear su propia implementación de IBurstContext que comprenda cómo enviar y recibir mensajes a través de las FMQ requestChannel y resultChannel pasadas a IPreparedModel::configureExecutionBurst .

Las funciones de la 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 ráfaga que se encuentra en el controlador de muestra de Neural Networks 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();
}