• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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, &registration));
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