La HAL de Neural Networks 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 se producen en una sucesión rápida, por ejemplo, en los marcos de una captura de cámara o en ejemplos de audio sucesivos. Un objeto de ráfaga se usa para controlar un conjunto de ejecuciones de ráfaga y para conservar los recursos entre ejecuciones, lo que permite que las ejecuciones tengan una sobrecarga menor. Los objetos de ráfaga permiten tres optimizaciones:
- Se crea un objeto de ráfaga antes de una secuencia de ejecuciones y se libera cuando finaliza la secuencia. Por este motivo, la vida útil del objeto de ráfaga le indica a un controlador cuánto tiempo debe permanecer en un estado de alto rendimiento.
- Un objeto de ráfaga puede conservar 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 reutilizarla 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 ya no se requiere el recurso.
- Un objeto de ráfaga usa colas de mensajes rápidos (FMQ) para comunicarse entre los procesos de la app y del controlador. Esto puede reducir la latencia, ya que la FMQ omite HIDL y pasa datos directamente a otro proceso a través de una FIFO circular atómica en la memoria compartida. El proceso del consumidor sabe que debe quitar un elemento de la cola y comenzar a procesarlo, ya sea sondeando la cantidad de elementos en la FIFO o esperando la marca de evento de la FMQ, que indica el productor. Esta marca de evento es un mutex rápido del espacio del usuario (futex).
Una FMQ es una estructura de datos de bajo nivel que no ofrece garantías de vida útil entre procesos y no tiene un mecanismo integrado para determinar si el proceso en el otro extremo de la FMQ se ejecuta según lo esperado. En consecuencia, si el productor de la FMQ falla, el consumidor puede quedar atascado 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 finalizó la ejecución de la ráfaga.
Dado 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 de servicio de la NNAPI. Sin embargo, las FMQ solo pueden transferir tipos de datos simples. La transferencia de datos complejos se logra serializando y deserializando búferes anidados (tipos de vectores) directamente en las FMQ y usando objetos de devolución de llamada de HIDL para transferir identificadores de grupos de memoria a pedido. El lado del productor de la FMQ debe enviar los mensajes de solicitud o resultado al consumidor de forma atómica con MessageQueue::writeBlocking
si la cola es de bloqueo o con MessageQueue::write
si la cola no es de bloqueo.
Interfaces de ráfaga
Las interfaces de ráfaga 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 ráfaga en la capa del NDK, consulta frameworks/ml/nn/runtime/include/NeuralNetworks.h
.
types.hal
types.hal
define el tipo de datos que se envían a través de la FMQ.
FmqRequestDatum
: Es un solo elemento de una representación serializada de un objetoRequest
de ejecución y un valorMeasureTiming
, que se envía a través de la cola de mensajes rápida.FmqResultDatum
: Es un solo elemento de una representación serializada de los valores que se muestran a partir de una ejecución (ErrorStatus
,OutputShapes
yTiming
), que se muestra a través de la cola de mensajes rápida.
IBurstContext.hal
IBurstContext.hal
define el objeto de interfaz HIDL que reside en el servicio de Neural Networks.
IBurstContext
: Es el objeto de contexto para administrar 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 de Neural Networks lo usa para recuperar objetos hidl_memory
correspondientes a identificadores de ranuras.
- IBurstCallback: Objeto de devolución de llamada que usa un servicio para recuperar objetos de memoria.
IPreparedModel.hal
IPreparedModel.hal
se extiende 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 que se usa para ejecutar varias 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 de NNAPI de HIDL es usar la función de utilidad de ráfaga ::android::nn::ExecutionBurstServer::create
, que se encuentra en ExecutionBurstServer.h
y se empaqueta 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 usa el métodoexecuteSynchronously
en un objetoIPreparedModel
para ejecutar el modelo. - Una sobrecarga acepta un objeto
IBurstExecutorWithCache
personalizable, que se puede usar para almacenar en caché recursos (como asignaciones dehidl_memory
) que persisten en varias ejecuciones.
Cada sobrecarga devuelve un objeto IBurstContext
(que representa el objeto de ráfaga) que contiene y administra su propio subproceso de escucha dedicado. Este subproceso recibe solicitudes de la FMQ requestChannel
, realiza la inferencia y, luego, devuelve los resultados a través de la FMQ resultChannel
. Este subproceso y todos los demás recursos incluidos en el objeto IBurstContext
se liberan automáticamente cuando el cliente de la ráfaga 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 las FMQ requestChannel
y resultChannel
que se pasan 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);
A continuación, se incluye una implementación de referencia de una interfaz de ráfaga 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();
}