1# TensorFlow Lite operator versions 2 3This document describes TensorFlow Lite's op versioning schema. Op versioning 4enables developers to add new functionalities and parameters into existing ops. 5In addition, it guarantees the following: 6 7* Backward compatibility: New TensorFlow Lite implementation should handle an 8 old model file. 9* Forward compatibility: Old TensorFlow Lite implementation should handle a 10 new model file produced by new version of converter, as long as no new 11 features are used. 12* Forward in-compatibility detection: If an old TensorFlow Lite implementation 13 reads a new model that contains a new version of an op which isn't 14 supported, it should report the error. 15 16## Example: Adding dilation into depthwise convolution 17 18The remainder of this document explains op versioning in TFLite by showing how 19to add dilation parameters to the depthwise convolution operation. 20 21Knowledge of dilation is not required to understand this document. Note that: 22 23* 2 new integer parameters will be added: `dilation_width_factor` and 24 `dilation_height_factor`. 25* Old depthwise convolution kernels that don't support dilation are equivalent 26 to setting the dilation factors to 1. 27 28### Change FlatBuffer schema 29 30To add new parameters into an op, change the options table in 31`lite/schema/schema.fbs`. 32 33For example, the options table of depthwise convolution looks like this: 34 35``` 36table DepthwiseConv2DOptions { 37 padding:Padding; 38 stride_w:int; 39 stride_h:int; 40 depth_multiplier:int; 41 fused_activation_function:ActivationFunctionType; 42} 43``` 44 45When adding new parameters: 46 47* Add comments indicating which parameters are supported by which version. 48* When the new implementation gets the default values for newly added 49 parameters, it should work exactly the same as the old implementation. 50 51The table will be like this after the new parameters are added: 52 53``` 54table DepthwiseConv2DOptions { 55 // Parameters for DepthwiseConv version 1 or above. 56 padding:Padding; 57 stride_w:int; 58 stride_h:int; 59 depth_multiplier:int; 60 fused_activation_function:ActivationFunctionType; 61 // Parameters for DepthwiseConv version 2 or above. 62 dilation_w_factor:int = 1; 63 dilation_h_factor:int = 1; 64} 65``` 66 67The file `lite/schema/schema_generated.h` should be re-generated for the new 68schema. 69 70### Change C structures and kernel implementation 71 72In TensorFlow Lite, the kernel implementation is decoupled from FlatBuffer 73definition. The kernels read the parameter from C structures defined in 74`lite/c/builtin_op_data.h`. 75 76The original depthwise convolution parameter is as follows: 77 78``` 79typedef struct { 80 TfLitePadding padding; 81 int stride_width; 82 int stride_height; 83 int depth_multiplier; 84 TfLiteFusedActivation activation; 85} TfLiteDepthwiseConvParams; 86``` 87 88As with the FlatBuffer schema, add comments indicating which parameters are 89supported starting from which version. The result is seen below: 90 91``` 92typedef struct { 93 // Parameters for DepthwiseConv version 1 or above. 94 TfLitePadding padding; 95 int stride_width; 96 int stride_height; 97 int depth_multiplier; 98 TfLiteFusedActivation activation; 99 // Parameters for DepthwiseConv version 2 or above. 100 int dilation_width_factor; 101 int dilation_height_factor; 102} TfLiteDepthwiseConvParams; 103``` 104 105Please also change the kernel implementation to read the newly added parameters 106from the C structures. The details are omitted here. 107 108### Change the FlatBuffer reading code 109 110The logic to read FlatBuffer and produce C structure is in 111`lite/core/api/flatbuffer_conversions.cc`. 112 113Update the file to handle the new parameters, as shown below: 114 115``` 116TfLiteStatus ParseDepthwiseConv2D(const Operator* op, 117 ErrorReporter* error_reporter, 118 BuiltinDataAllocator* allocator, 119 void** builtin_data) { 120 CheckParsePointerParams(op, error_reporter, allocator, builtin_data); 121 122 SafeBuiltinDataAllocator safe_allocator(allocator); 123 124 std::unique_ptr<TfLiteDepthwiseConvParams, 125 SafeBuiltinDataAllocator::BuiltinDataDeleter> 126 params = safe_allocator.Allocate<TfLiteDepthwiseConvParams>(); 127 TF_LITE_ENSURE(error_reporter, params != nullptr); 128 129 const DepthwiseConv2DOptions* schema_params = 130 op->builtin_options_as_DepthwiseConv2DOptions(); 131 132 if (schema_params != nullptr) { 133 params->padding = ConvertPadding(schema_params->padding()); 134 params->stride_width = schema_params->stride_w(); 135 params->stride_height = schema_params->stride_h(); 136 params->depth_multiplier = schema_params->depth_multiplier(); 137 params->activation = 138 ConvertActivation(schema_params->fused_activation_function()); 139 140 params->dilation_width_factor = schema_params->dilation_w_factor(); 141 params->dilation_height_factor = schema_params->dilation_h_factor(); 142 } 143 144 *builtin_data = params.release(); 145 return kTfLiteOk; 146} 147``` 148 149It's not required to check the op version here. When the new implementation 150reads an old model file where dilation factors are missing, it will use 1 as the 151default value, and the new kernel will work consistently with the old kernel. 152 153### Change kernel registration 154 155The MutableOpResolver (defined in `lite/mutable_op_resolver.h`) provides a few 156functions to register op kernels. The minimum and maximum version are 1 by 157default: 158 159``` 160void AddBuiltin(tflite::BuiltinOperator op, TfLiteRegistration* registration, 161 int min_version = 1, int max_version = 1); 162void AddCustom(const char* name, TfLiteRegistration* registration, 163 int min_version = 1, int max_version = 1); 164``` 165 166The built-in ops are registered in `lite/kernels/register.cc`. In this example, 167we implemented a new op kernel which can handle `DepthwiseConv2D` version 1 and 1682, so we need to change this line: 169 170``` 171AddBuiltin(BuiltinOperator_DEPTHWISE_CONV_2D, Register_DEPTHWISE_CONV_2D()); 172``` 173 174to: 175 176``` 177AddBuiltin(BuiltinOperator_DEPTHWISE_CONV_2D, Register_DEPTHWISE_CONV_2D(), 178 /* min_version = */ 1, 179 /* max_version = */ 2); 180``` 181 182### Change TFLite op version 183 184The next step is to make TFLite populate the minimum version that's required to 185execute the op. In this example, it means: 186 187* Populate version=1 when dilation factors are all 1. 188* Populate version=2 otherwise. 189 190Modify `GetBuiltinOperatorVersion` function for the operator in 191`lite/tools/versioning/op_version.cc` by adding the new version to the case of 192`DepthwiseConv2D`: 193 194``` 195case BuiltinOperator_DEPTHWISE_CONV_2D: 196 auto depthwise_conv_params = 197 reinterpret_cast<TfLiteDepthwiseConvParams*>(op_sig.builtin_data); 198 TFLITE_DCHECK(depthwise_conv_params != nullptr); 199 if (depthwise_conv_params->dilation_width_factor != 1 || 200 depthwise_conv_params->dilation_height_factor != 1) { 201 return 2; 202 } 203 return 1; 204``` 205 206### Update the operator version map 207 208The last step is to add the new version info into the operator version map. This 209step is required because we need to generate the model's minimum required 210runtime version based on this version map. 211 212To do this, you need to add a new map entry in 213`lite/tools/versioning/runtime_version.cc`. 214 215In this example, you need to add the following entry into `op_version_map`: 216 217``` 218{{BuiltinOperator_DEPTHWISE_CONV_2D, 2}, %CURRENT_RUNTIME_VERSION%} 219``` 220 221where `%CURRENT_RUNTIME_VERSION%` corresponds to the current runtime version 222defined in [tensorflow/core/public/version.h](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/public/version.h). 223 224### Delegation implementation 225 226TensorFlow Lite provides a delegation API which enables delegating ops to 227hardware backends. In the delegate's `Prepare` function, check if the version is 228supported for every node in Delegation code. 229 230``` 231const int kMaxVersion = 1; 232TfLiteNode* node; 233TfLiteRegistration* registration = nullptr; 234TF_LITE_ENSURE_STATUS(context->GetNodeAndRegistration(context, node_index, &node, ®istration)); 235 236if (registration->version > kMaxVersion) { 237 // Reject the node if the version isn't supported. 238} 239``` 240 241This is required even if the delegation only supports version 1 ops, so the 242delegation can detect incompatibility when getting a higher version op. 243