# Custom operators Since the TensorFlow Lite builtin operator library only supports a limited number of TensorFlow operators, not every model is convertible. For details, refer to [operator compatibility](ops_compatibility.md). To allow conversion, users can provide their own custom implementation of an unsupported TensorFlow operator in TensorFlow Lite, known as a custom operator. *If instead, you wish to combine a series of unsupported (or supported) TensorFlow operators into a single fused optimized custom operator, refer to [operator fusing](https://www.tensorflow.org/lite/models/convert/operation_fusion).* Using custom operators consists of four steps. * [Create a TensorFlow Model.](#create-a-tensorflow-model) Make sure the Saved Model (or Graph Def) refers to the correctly named TensorFlow Lite operator. * [Convert to a TensorFlow Lite Model.](#convert-to-a-tensorflow-lite-model) Make sure you set the right TensorFlow Lite converter attribute in order to successfully convert the model. * [Create and register the operator.](#create-and-register-the-operator) This is so that the TensorFlow Lite runtime knows how to map your operator and parameters in your graph to executable C/C++ code. * [Test and profile your operator.](#test-and-profile-your-operator) If you wish to test just your custom operator, it is best to create a model with just your custom operator and use the [benchmark_model](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/tools/benchmark/benchmark_model.cc) program. Let’s walk through an end-to-end example of running a model with a custom operator `tf.atan` (named as `Atan`, refer to #create-a-tensorflow-model) which is supported in TensorFlow, but unsupported in TensorFlow Lite. Note: The `tf.atan` function is **not** a custom operator. It is a regular operator which is supported by both TensorFlow and TensorFlow Lite. But we **assume** that it is a custom operator in the following example in order to demonstrate a simple workflow. The TensorFlow Text operator is an example of a custom operator. See the Convert TF Text to TF Lite tutorial for a code example. ## Example: Custom `Atan` operator Let’s walk through an example of supporting a TensorFlow operator that TensorFlow Lite does not have. Assume we are using the `Atan` operator and that we are building a very simple model for a function `y = atan(x + offset)`, where `offset` is trainable. ### Create a TensorFlow Model The following code snippet trains a simple TensorFlow model. This model just contains a custom operator named `Atan`, which is a function `y = atan(x + offset)`, where `offset` is trainable. ```python import tensorflow as tf # Define training dataset and variables x = [-8, 0.5, 2, 2.2, 201] y = [-1.4288993, 0.98279375, 1.2490457, 1.2679114, 1.5658458] offset = tf.Variable(0.0) # Define a simple model which just contains a custom operator named `Atan` @tf.function(input_signature=[tf.TensorSpec.from_tensor(tf.constant(x))]) def atan(x): return tf.atan(x + offset, name="Atan") # Train model optimizer = tf.optimizers.Adam(0.01) def train(x, y): with tf.GradientTape() as t: predicted_y = atan(x) loss = tf.reduce_sum(tf.square(predicted_y - y)) grads = t.gradient(loss, [offset]) optimizer.apply_gradients(zip(grads, [offset])) for i in range(1000): train(x, y) print("The actual offset is: 1.0") print("The predicted offset is:", offset.numpy()) ``` ```python The actual offset is: 1.0 The predicted offset is: 0.99999905 ``` At this point, if you try to generate a TensorFlow Lite model with the default converter flags, you will get the following error message: ```none Error: error: 'tf.Atan' op is neither a custom op nor a flex op. ``` ### Convert to a TensorFlow Lite Model Create a TensorFlow Lite model with custom operators, by setting the converter attribute `allow_custom_ops` as shown below:
converter = tf.lite.TFLiteConverter.from_concrete_functions([atan.get_concrete_function()], atan) converter.allow_custom_ops = True tflite_model = converter.convert()At this point, if you run it with the default interpreter using commands such as follows: ```python interpreter = tf.lite.Interpreter(model_content=tflite_model) interpreter.allocate_tensors() ``` You will still get the error: ```none Encountered unresolved custom op: Atan. ``` ### Create and register the operator. ```c++ #include "tensorflow/lite/c/c_api.h" #include "tensorflow/lite/c/c_api_opaque.h" ``` TensorFlow Lite custom operators are defined using a simple pure-C API that consists of an opaque type (`TfLiteOperator`) and related functions. `TfLiteOperator` is an opaque type: ```c++ typedef struct TfLiteOperator TfLiteOperator; ``` `TfLiteOperator` stores the operator's identity and implementation. (Note that the operator is distinct from its operands, which are stored in the TF Lite graph nodes for nodes that call the operator.) Instances of this type are constructed with calls to `TfLiteOperatorCreate` and can be destroyed by calling `TfLiteOperatorDelete`. The operator's identity is set via the parameters to the constructor function `TfLiteOperatorCreate`: ```c++ TfLiteOperator* TfLiteOperatorCreate( TfLiteBuiltinOperator builtin_code, // Normally `TfLiteBuiltinCustom`. const char* custom_name, // The name of the custom op. int version // Normally `1` for the first version of a custom op. ); ``` The operator implementation can define "methods" with the following signatures. All of these methods are optional, but for an operator to be successfully evaluated, the operator implementation needs to define and set (using the setter functions) at least the `Prepare` and `Invoke` methods. ```c++ // Initializes the op from serialized data. void* Init(TfLiteOpaqueContext* context, const char* buffer, size_t length); // Deallocates the op. // The pointer `buffer` is the data previously returned by an Init invocation. void Free(TfLiteOpaqueContext* context, void* buffer); // Called when the inputs that this node depends on have been resized. TfLiteStatus Prepare(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node); // Called when the node is executed. (Should read node inputs and write to // node outputs). TfLiteStatus Invoke(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node); // Retrieves the async kernel. TfLiteAsyncKernel AsyncKernel(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node); ``` The function *names* (or namespace prefixes, for C++) in your op implementation don't have to match the function names in the above code snippet, since the TF Lite custom ops API will only use their addresses. Indeed we recommend that you declare them in an anonymous namespace or as static functions. But it is a good idea to include your operator name as a namespace or prefix on these function names:
namespace my_namespace::my_custom_op {
void* Init(TfLiteOpaqueContext* context,
const char* buffer, size_t length) { ... }
// ... plus definitions of Free, Prepare, and Invoke ...
}
void* MyCustomOpInit(TfLiteOpaqueContext* context,
const char* buffer, size_t length) { ... }
// ... plus definitions of MyCustomOpFree, MyCustomOpPrepare, and
// MyCustomOpInvoke.
namespace my_namespace::my_custom_op {
namespace {
void* Init(TfLiteOpaqueContext* context,
const char* buffer, size_t length) { ... }
void Free(TfLiteOpaqueContext* context, void* buffer) { ... }
TfLiteStatus Prepare(TfLiteOpaqueContext* context,
TfLiteOpaqueNode* node) { ... }
TfLiteStatus Invoke(TfLiteOpaqueContext* context,
TfLiteOpaqueNode* node) {... }
};
const TfLiteOperator* MyCustomOpRegistrationExternal() {
// Singleton instance, intentionally never destroyed.
static const TfLiteOperator* my_custom_op = ()[] {
TfLiteOperator* r =
TfLiteOperatorCreate(
kTfLiteBuiltinCustom, "MyCustomOp", /*version=*/ 1);
TfLiteOperatorSetInit(r, Init);
TfLiteOperatorSetFree(r, Free);
TfLiteOperatorSetPrepare(r, Prepare);
TfLiteOperatorSetInvoke(r, Eval);
return r;
};
return my_custom_op;
}
} // namespace my_namespace
static void* MyCustomOpInit(TfLiteOpaqueContext* context, const char* buffer,
size_t length) { ... }
static void MyCustomOpFree(TfLiteOpaqueContext* context, void* buffer) { ... }
static TfLiteStatus MyCustomOpPrepare(TfLiteOpaqueContext* context,
TfLiteOpaqueNode* node) { ... }
static TfLiteStatus MyCustomOpInvoke(TfLiteOpaqueContext* context,
TfLiteOpaqueNode* node) {... }
static TfLiteOperator* MyCustomOpCreate() {
const TfLiteOperator* r =
TfLiteOperatorCreate(
kTfLiteBuiltinCustom, "MyCustomOp", /*version=*/ 1);
TfLiteOperatorSetInit(r, MyCustomOpInit);
TfLiteOperatorSetFree(r, MyCustomOpFree);
TfLiteOperatorSetPrepare(r, MyCustomOpPrepare);
TfLiteOperatorSetInvoke(r, MyCustomOpEval);
return r;
}
const TfLiteOperator* MyCustomOpRegistrationExternal() {
// Singleton instance, intentionally never destroyed.
static const TfLiteOperator* my_custom_op = MyCustomOpCreate();
return my_custom_op;
}
namespace atan_op {
namespace {
TfLiteStatus AtanPrepare(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node) {
TF_LITE_OPAQUE_ENSURE_EQ(context, TfLiteOpaqueNodeNumInputs(node), 1);
TF_LITE_OPAQUE_ENSURE_EQ(context, TfLiteOpaqueNodeNumOutputs(node), 1);
const TfLiteOpaqueTensor* input = TfLiteOpaqueNodeGetInput(context, node, 0);
TfLiteOpaqueTensor* output = TfLiteOpaqueNodeGetOutput(context, node, 0);
int num_dims = TfLiteOpaqueTensorNumDimensions(input);
TfLiteIntArray* output_size = TfLiteIntArrayCreate(num_dims);
for (int i=0; i < num_dims; ++i) {
output_size->data[i] = input->dims->data[i];
}
return TfLiteOpaqueContextResizeTensor(context, output, output_size);
}
TfLiteStatus AtanEval(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node) {
const TfLiteOpaqueTensor* input = TfLiteOpaqueNodeGetInput(context, node, 0);
TfLiteOpaqueTensor* output = TfLiteOpaqueNodeGetOutput(context, node, 0);
float* input_data = static_cast(TfLiteOpaqueTensorData(input));
float* output_data = static_cast(TfLiteOpaqueTensorData(output));
size_t count = 1;
int num_dims = TfLiteOpaqueTensorNumDimensions(input);
for (int i = 0; i < num_dims; ++i) {
count *= input->dims->data[i];
}
for (size_t i = 0; i < count; ++i) {
output_data[i] = atan(input_data[i]);
}
return kTfLiteOk;
}
} // anonymous namespace
const TfLiteOperator* AtanOpRegistrationExternal() {
// Singleton instance, intentionally never destroyed.
static const TfLiteOperator* atan_op = ()[] {
auto* r = TfLiteOperatorCreate(
kTfLiteBuiltinCustom, "ATAN", /*version=*/ 1);
TfLiteOperatorSetPrepare(r, Prepare);
TfLiteOperatorSetInvoke(r, Eval);
return r;
};
return atan_op;
}
} // namespace atan_op
static TfLiteStatus AtanPrepare(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node) {
TF_LITE_OPAQUE_ENSURE_EQ(context, TfLiteOpaqueNodeNumInputs(node), 1);
TF_LITE_OPAQUE_ENSURE_EQ(context, TfLiteOpaqueNodeNumOutputs(node), 1);
const TfLiteOpaqueTensor* input = TfLiteOpaqueNodeGetInput(context, node, 0);
TfLiteOpaqueTensor* output = TfLiteOpaqueNodeGetOutput(context, node, 0);
int num_dims = TfLiteOpaqueTensorNumDimensions(input);
TfLiteIntArray* output_size = TfLiteIntArrayCreate(num_dims);
for (int i = 0; i < num_dims; ++i) {
output_size->data[i] = input->dims->data[i];
}
return TfLiteOpaqueContextResizeTensor(context, output, output_size);
}
static TfLiteStatus AtanEval(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node) {
const TfLiteOpaqueTensor* input = TfLiteOpaqueNodeGetInput(context, node, 0);
TfLiteOpaqueTensor* output = TfLiteOpaqueNodeGetOutput(context, node, 0);
float* input_data = static_cast(TfLiteOpaqueTensorData(input));
float* output_data = static_cast(TfLiteOpaqueTensorData(output));
size_t count = 1;
int num_dims = TfLiteOpaqueTensorNumDimensions(input);
for (int i = 0; i < num_dims; ++i) {
count *= input->dims->data[i];
}
for (size_t i = 0; i < count; ++i) {
output_data[i] = atan(input_data[i]);
}
return kTfLiteOk;
}
static const TfLiteOperator* AtanOpCreate() {
TfLiteOperator* r = TfLiteOperatorCreate(
kTfLiteBuiltinCustom, "ATAN", /*version=*/ 1);
TfLiteOperatorSetPrepare(r, Prepare);
TfLiteOperatorSetInvoke(r, Eval);
return r;
}
const TfLiteOperator* AtanOpRegistrationExternal() {
// Singleton instance, intentionally never destroyed.
static const TfLiteOperator* atan_op = AtanOpCreate();
return atan_op;
}