En este documento, se describe el esquema de control de versiones de operaciones de LiteRT. El control de versiones de las operaciones permite a los desarrolladores agregar funciones y parámetros nuevos a las operaciones existentes. Además, garantiza lo siguiente:
- Retrocompatibilidad: La nueva implementación de LiteRT debe controlar un archivo de modelo anterior.
- Retrocompatibilidad: La implementación anterior de LiteRT debe controlar un archivo de modelo nuevo producido por la nueva versión del convertidor, siempre y cuando no se usen funciones nuevas.
- Detección de compatibilidad directa: Si una implementación anterior de LiteRT lee un modelo nuevo que contiene una versión nueva de una operación que no se admite, debe informar el error.
Ejemplo: Cómo agregar dilatación a la convolución en profundidad
En el resto de este documento, se explica el control de versiones de operaciones en TFLite, y se muestra cómo agregar parámetros de dilatación a la operación de convolución en profundidad.
No es necesario tener conocimientos sobre la dilatación para comprender este documento. Ten en cuenta lo siguiente:
- Se agregarán 2 parámetros enteros nuevos:
dilation_width_factor
ydilation_height_factor
. - Los kernels de convolución por profundidad anteriores que no admiten la dilatación equivalen a establecer los factores de dilatación en 1.
Cambia el esquema de FlatBuffer
Para agregar parámetros nuevos a una operación, cambia la tabla de opciones en lite/schema/schema.fbs
.
Por ejemplo, la tabla de opciones de la convolución en profundidad se ve de la siguiente manera:
table DepthwiseConv2DOptions {
padding:Padding;
stride_w:int;
stride_h:int;
depth_multiplier:int;
fused_activation_function:ActivationFunctionType;
}
Cuando agregues parámetros nuevos, ten en cuenta lo siguiente:
- Agrega comentarios que indiquen qué parámetros son compatibles con cada versión.
- Cuando la implementación nueva obtenga los valores predeterminados para los parámetros agregados recientemente, debería funcionar exactamente igual que la implementación anterior.
La tabla se verá de la siguiente manera después de agregar los parámetros nuevos:
table DepthwiseConv2DOptions {
// Parameters for DepthwiseConv version 1 or above.
padding:Padding;
stride_w:int;
stride_h:int;
depth_multiplier:int;
fused_activation_function:ActivationFunctionType;
// Parameters for DepthwiseConv version 2 or above.
dilation_w_factor:int = 1;
dilation_h_factor:int = 1;
}
El archivo lite/schema/schema_generated.h
se debe volver a generar para el nuevo esquema.
Cambia las estructuras C y la implementación del kernel
En LiteRT, la implementación del kernel está desacoplada de la definición de FlatBuffer. Los kernels leen el parámetro de las estructuras C definidas en lite/c/builtin_op_data.h
.
El parámetro de convolución en profundidad original es el siguiente:
typedef struct {
TfLitePadding padding;
int stride_width;
int stride_height;
int depth_multiplier;
TfLiteFusedActivation activation;
} TfLiteDepthwiseConvParams;
Al igual que con el esquema de FlatBuffer, agrega comentarios que indiquen qué parámetros se admiten a partir de qué versión. El resultado se muestra a continuación:
typedef struct {
// Parameters for DepthwiseConv version 1 or above.
TfLitePadding padding;
int stride_width;
int stride_height;
int depth_multiplier;
TfLiteFusedActivation activation;
// Parameters for DepthwiseConv version 2 or above.
int dilation_width_factor;
int dilation_height_factor;
} TfLiteDepthwiseConvParams;
También cambia la implementación del kernel para leer los parámetros agregados recientemente de las estructuras C. Aquí se omiten los detalles.
Cambia el código de lectura de FlatBuffer
La lógica para leer FlatBuffer y producir la estructura C se encuentra en lite/core/api/flatbuffer_conversions.cc
.
Actualiza el archivo para controlar los parámetros nuevos, como se muestra a continuación:
TfLiteStatus ParseDepthwiseConv2D(const Operator* op,
ErrorReporter* error_reporter,
BuiltinDataAllocator* allocator,
void** builtin_data) {
CheckParsePointerParams(op, error_reporter, allocator, builtin_data);
SafeBuiltinDataAllocator safe_allocator(allocator);
std::unique_ptr<TfLiteDepthwiseConvParams,
SafeBuiltinDataAllocator::BuiltinDataDeleter>
params = safe_allocator.Allocate<TfLiteDepthwiseConvParams>();
TF_LITE_ENSURE(error_reporter, params != nullptr);
const DepthwiseConv2DOptions* schema_params =
op->builtin_options_as_DepthwiseConv2DOptions();
if (schema_params != nullptr) {
params->padding = ConvertPadding(schema_params->padding());
params->stride_width = schema_params->stride_w();
params->stride_height = schema_params->stride_h();
params->depth_multiplier = schema_params->depth_multiplier();
params->activation =
ConvertActivation(schema_params->fused_activation_function());
params->dilation_width_factor = schema_params->dilation_w_factor();
params->dilation_height_factor = schema_params->dilation_h_factor();
}
*builtin_data = params.release();
return kTfLiteOk;
}
No es necesario verificar la versión de la operación aquí. Cuando la nueva implementación lea un archivo de modelo anterior en el que faltan factores de dilatación, usará 1 como valor predeterminado, y el nuevo kernel funcionará de forma coherente con el anterior.
Cambia el registro del kernel
MutableOpResolver (definido en lite/mutable_op_resolver.h
) proporciona algunas funciones para registrar kernels de op. La versión mínima y máxima son 1 de forma predeterminada:
void AddBuiltin(tflite::BuiltinOperator op, TfLiteRegistration* registration,
int min_version = 1, int max_version = 1);
void AddCustom(const char* name, TfLiteRegistration* registration,
int min_version = 1, int max_version = 1);
Las operaciones integradas se registran en lite/kernels/register.cc
. En este ejemplo, implementamos un nuevo kernel de operaciones que puede controlar la versión 1 y 2 de DepthwiseConv2D
, por lo que debemos cambiar esta línea:
AddBuiltin(BuiltinOperator_DEPTHWISE_CONV_2D, Register_DEPTHWISE_CONV_2D());
a:
AddBuiltin(BuiltinOperator_DEPTHWISE_CONV_2D, Register_DEPTHWISE_CONV_2D(),
/* min_version = */ 1,
/* max_version = */ 2);
Cambia la versión de la operación de TFLite
El siguiente paso es hacer que TFLite propague la versión mínima que se requiere para ejecutar la operación. En este ejemplo, significa lo siguiente:
- Propaga version=1 cuando los factores de dilatación sean todos 1.
- De lo contrario, propaga version=2.
Modifica la función GetBuiltinOperatorVersion
para el operador en lite/tools/versioning/op_version.cc
agregando la versión nueva al caso de DepthwiseConv2D
:
case BuiltinOperator_DEPTHWISE_CONV_2D:
auto depthwise_conv_params =
reinterpret_cast<TfLiteDepthwiseConvParams*>(op_sig.builtin_data);
TFLITE_DCHECK(depthwise_conv_params != nullptr);
if (depthwise_conv_params->dilation_width_factor != 1 ||
depthwise_conv_params->dilation_height_factor != 1) {
return 2;
}
return 1;
Actualiza el mapa de versiones del operador
El último paso es agregar la información de la versión nueva al mapa de versiones del operador. Este paso es obligatorio porque necesitamos generar la versión mínima del entorno de ejecución requerida del modelo según este mapa de versiones.
Para ello, debes agregar una nueva entrada de mapa en lite/tools/versioning/runtime_version.cc
.
En este ejemplo, debes agregar la siguiente entrada a op_version_map
:
{ {BuiltinOperator_DEPTHWISE_CONV_2D, 2}, %CURRENT_RUNTIME_VERSION%}
donde %CURRENT_RUNTIME_VERSION%
corresponde a la versión actual del entorno de ejecución definida en release_version.h.
Implementación de la delegación
LiteRT proporciona una API de delegación que permite delegar operaciones a backends de hardware. En la función Prepare
del delegado, verifica si la versión es compatible con cada nodo en el código de delegación.
const int kMaxVersion = 1;
TfLiteNode* node;
TfLiteRegistration* registration = nullptr;
TF_LITE_ENSURE_STATUS(context->GetNodeAndRegistration(context, node_index, &node, ®istration));
if (registration->version > kMaxVersion) {
// Reject the node if the version isn't supported.
}
Esto es obligatorio, incluso si la delegación solo admite operaciones de versión 1, de modo que la delegación pueda detectar incompatibilidad cuando obtenga una operación de versión superior.