Opérateurs personnalisés

Étant donné que la bibliothèque d'opérateurs intégrés TensorFlow Lite n'accepte qu'un nombre limité d'opérateurs TensorFlow, tous les modèles ne sont pas convertibles. Pour en savoir plus, consultez la section Compatibilité des opérateurs.

Pour permettre la conversion, les utilisateurs peuvent fournir leur propre implémentation personnalisée d'un opérateur TensorFlow non compatible dans TensorFlow Lite, appelé opérateur personnalisé. Si vous préférez combiner une série d'opérateurs TensorFlow non compatibles (ou compatibles) en un seul opérateur personnalisé optimisé et fusionné, reportez-vous à la section sur la fusion des opérateurs.

L'utilisation d'opérateurs personnalisés comprend quatre étapes.

Examinons un exemple de bout en bout d'exécution d'un modèle avec un opérateur personnalisé tf.atan (nommé Atan ; consultez la section Créer un modèle TensorFlow) compatible avec TensorFlow, mais pas dans TensorFlow Lite.

L'opérateur TensorFlow Text est un exemple d'opérateur personnalisé. Consultez le tutoriel Convertir TF Text en TF Lite pour obtenir un exemple de code.

Exemple: opérateur Atan personnalisé

Prenons l'exemple d'un opérateur TensorFlow dont TensorFlow Lite n'est pas compatible. Supposons que nous utilisions l'opérateur Atan et que nous créions un modèle très simple pour une fonction y = atan(x + offset), où offset peut être entraîné.

Créer un modèle TensorFlow

L'extrait de code suivant entraîne un modèle TensorFlow simple. Ce modèle contient simplement un opérateur personnalisé nommé Atan, qui est une fonction y = atan(x + offset), où offset peut être entraîné.

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())
The actual offset is: 1.0
The predicted offset is: 0.99999905

À ce stade, si vous essayez de générer un modèle TensorFlow Lite avec les indicateurs de conversion par défaut, le message d'erreur suivant s'affiche:

Error:
error: 'tf.Atan' op is neither a custom op nor a flex op.

Convertir en un modèle TensorFlow Lite

Créez un modèle TensorFlow Lite avec des opérateurs personnalisés en définissant l'attribut de conversion allow_custom_ops, comme indiqué ci-dessous:

converter = tf.lite.TFLiteConverter.from_concrete_functions([atan.get_concrete_function()], atan)
converter.allow_custom_ops = True
tflite_model = converter.convert()

À ce stade, si vous l'exécutez avec l'interpréteur par défaut à l'aide des commandes suivantes:

interpreter = tf.lite.Interpreter(model_content=tflite_model)
interpreter.allocate_tensors()

L'erreur suivante s'affichera toujours:

Encountered unresolved custom op: Atan.

Créez et enregistrez l'opérateur.

#include "third_party/tensorflow/lite/c/c_api.h"
#include "third_party/tensorflow/lite/c/c_api_opaque.h"

Les opérateurs personnalisés TensorFlow Lite sont définis à l'aide d'une API simple en C pur qui se compose d'un type opaque (TfLiteRegistrationExternal) et de fonctions associées.

TfLiteRegistrationExternal est un type opaque:

typedef struct TfLiteRegistrationExternal TfLiteRegistrationExternal;

TfLiteRegistrationExternal stocke l'identité et l'implémentation de l'opérateur. Notez que l'opérateur est différent de ses opérandes, qui sont stockés dans les nœuds du graphique TF Lite pour les nœuds qui l'appellent.

Les instances de ce type sont créées avec des appels à TfLiteRegistrationExternalCreate et peuvent être détruites en appelant TfLiteRegistrationExternalDelete.

L'identité de l'opérateur est définie via les paramètres de la fonction de constructeur TfLiteRegistrationExternalCreate:

TfLiteRegistrationExternal*
TfLiteRegistrationExternalCreate(
    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.
);

L'implémentation de l'opérateur peut définir des "méthodes" avec les signatures suivantes. Toutes ces méthodes sont facultatives, mais pour qu'un opérateur soit évalué, l'implémentation de l'opérateur doit définir et définir (à l'aide des fonctions setter) au moins les méthodes Prepare et Invoke.

// 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);

Les names de fonction (ou préfixes d'espace de noms, pour C++) dans votre implémentation d'opérations ne doivent pas nécessairement correspondre aux noms de fonction dans l'extrait de code ci-dessus, car l'API d'opérations personnalisées TF Lite n'utilisera que leurs adresses. D'ailleurs, nous vous recommandons de les déclarer dans un espace de noms anonyme ou en tant que fonctions statiques.

Toutefois, il est judicieux d'inclure votre nom d'opérateur en tant qu'espace de noms ou préfixe pour les noms de fonction suivants:

C++

namespace my_namespace::my_custom_op {
  void* Init(TfLiteOpaqueContext* context,
             const char* buffer, size_t length) { ... }
  // ... plus definitions of Free, Prepare, and Invoke ...
}
      

C

void* MyCustomOpInit(TfLiteOpaqueContext* context,
                     const char* buffer, size_t length) { ... }
// ... plus definitions of MyCustomOpFree, MyCustomOpPrepare, and
// MyCustomOpInvoke.
      

Comme il s'agit d'une API C, ces "méthodes" sont implémentées en tant que pointeurs de fonction C de type TfLiteRegistrationExternal, qui sont définis en transmettant les adresses de vos fonctions d'implémentation aux fonctions setter TfLiteRegistrationExternalSetMethodName correspondantes:

void TfLiteRegistrationExternalSetInit(
    TfLiteRegistrationExternal* registration,
    void* (*init)(TfLiteOpaqueContext* context, const char* buffer,
                  size_t length));
void TfLiteRegistrationExternalSetFree(
    TfLiteRegistrationExternal* registration,
    void (*free)(TfLiteOpaqueContext* context, void* data));
void TfLiteRegistrationExternalSetPrepare(
    TfLiteRegistrationExternal* registration,
    TfLiteStatus (*prepare)(TfLiteOpaqueContext* context,
                            TfLiteOpaqueNode* node));
void TfLiteRegistrationExternalSetInvoke(
    TfLiteRegistrationExternal* registration,
    TfLiteStatus (*invoke)(TfLiteOpaqueContext* context,
                           TfLiteOpaqueNode* node));
void TfLiteRegistrationExternalSetAsyncKernel(
    TfLiteRegistrationExternal* registration,
    struct TfLiteAsyncKernel* (*async_kernel)(TfLiteOpaqueContext* context,
                                              TfLiteOpaqueNode* node));

Pour en savoir plus sur TfLiteContext et TfLiteNode, consultez common.h. TfLiteContext fournit des installations Error Reporting et un accès aux objets globaux, y compris à tous les Tensors. TfLiteNode permet aux implémentations d'opérateur d'accéder à leurs entrées et sorties.

Lorsque l'interpréteur charge un modèle, il appelle la méthode Init() une fois pour chaque nœud du graphique. Un élément Init() donné est appelé plusieurs fois si l'opération est utilisée plusieurs fois dans le graphe. Pour les opérations personnalisées, un tampon de configuration sera fourni, contenant un flexbuffer qui mappe les noms des paramètres à leurs valeurs. Le tampon est vide pour les opérations intégrées, car l'interpréteur a déjà analysé les paramètres d'opération. Les implémentations de noyau qui nécessitent un état doivent l'initialiser ici et transférer la propriété à l'appelant. Pour chaque appel Init(), un appel correspondant à Free() est effectué, ce qui permet aux implémentations de supprimer le tampon qu'elles auraient pu attribuer dans Init().

Chaque fois que les Tensors d'entrée sont redimensionnés, l'interpréteur parcourt le graphe pour signaler les implémentations de la modification. Cela leur permet de redimensionner leur tampon interne, de vérifier la validité des formes et des types d'entrée et de recalculer les formes de sortie. Pour ce faire, vous devez utiliser la méthode Prepare(), et les implémentations peuvent accéder à leur état à l'aide de TfLiteOpaqueNodeGetUserData(node).

Enfin, chaque fois que l'inférence est exécutée, l'interpréteur parcourt le graphe en appelant la méthode Invoke(). Ici aussi, l'état est disponible en tant que TfLiteOpaqueNodeGetUserData(node).

Vous pouvez implémenter des opérations personnalisées en définissant ces fonctions "méthode", puis en définissant une fonction qui renvoie une instance de TfLiteRegistrationExternal construite en appelant TfLiteRegistrationExternalCreate, puis les méthodes setter pertinentes:

C++

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 TfLiteRegistrationExternal* MyCustomOpRegistrationExternal() {
    // Singleton instance, intentionally never destroyed.
    static const TfLiteRegistrationExternal* my_custom_op = ()[] {
        TfLiteRegistrationExternal* r =
            TfLiteRegistrationExternalCreate(
                kTfLiteBuiltinCustom, "MyCustomOp", /*version=*/ 1);
        TfLiteRegistrationExternalSetInit(r, Init);
        TfLiteRegistrationExternalSetFree(r, Free);
        TfLiteRegistrationExternalSetPrepare(r, Prepare);
        TfLiteRegistrationExternalSetInvoke(r, Eval);
        return r;
      };
    return my_custom_op;
  }

  const TfLiteRegistration* MyCustomOpRegistration() {
    static const TfLiteRegistration my_custom_op {
      .registration_external = MyCustomOpRegistrationExternal();
    };
    return my_custom_op;
  }
}  // namespace my_namespace
      

C

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 TfLiteRegistrationExternal* MyCustomOpCreate() {
  const TfLiteRegistrationExternal* r =
      TfLiteRegistrationExternalCreate(
          kTfLiteBuiltinCustom, "MyCustomOp", /*version=*/ 1);
  TfLiteRegistrationExternalSetInit(r, MyCustomOpInit);
  TfLiteRegistrationExternalSetFree(r, MyCustomOpFree);
  TfLiteRegistrationExternalSetPrepare(r, MyCustomOpPrepare);
  TfLiteRegistrationExternalSetInvoke(r, MyCustomOpEval);
  return r;
}

const TfLiteRegistrationExternal* MyCustomOpRegistrationExternal() {
  // Singleton instance, intentionally never destroyed.
  static const TfLiteRegistrationExternal* my_custom_op = MyCustomOpCreate();
  return my_custom_op;
}

const TfLiteRegistration MyCustomOpRegistration() {
  static const TfLiteRegistration my_custom_op {
    .registration_external = MyCustomOpRegistrationExternal();
  };
  return my_custom_op;
}
      

Notez que l'enregistrement n'est pas automatique et qu'un appel explicite à votre fonction MyCustomOpRegistration doit être effectué (voir les détails ci-dessous). Bien que la BuiltinOpResolver standard (disponible à partir de la cible :builtin_ops) s'occupe de l'enregistrement des éléments intégrés, les opérations personnalisées doivent être collectées dans des bibliothèques personnalisées distinctes.

Définir le noyau dans l'environnement d'exécution TensorFlow Lite

Pour utiliser l'opération dans TensorFlow Lite, il nous suffit de définir deux fonctions (Prepare et Eval) et une troisième pour construire un TfLiteRegistrationExternal:

C++

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 TfLiteRegistrationExternal* AtanOpRegistrationExternal() {
    // Singleton instance, intentionally never destroyed.
    static const TfLiteRegistrationExternal* atan_op = ()[] {
        auto* r = TfLiteRegistrationExternalCreate(
            kTfLiteBuiltinCustom, "ATAN", /*version=*/ 1);
        TfLiteRegistrationExternalSetPrepare(r, Prepare);
        TfLiteRegistrationExternalSetInvoke(r, Eval);
        return r;
      };
    return atan_op;
  }

  const TfLiteRegistration AtanOpRegistration() {
    static const TfLiteRegistration atan_op {
      .registration_external = AtanOpRegistrationExternal();
    };
    return atan_op;
  }
}  // namespace atan_op
      

C

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 TfLiteRegistrationExternal* AtanOpCreate() {
  TfLiteRegistrationExternal* r = TfLiteRegistrationExternalCreate(
          kTfLiteBuiltinCustom, "ATAN", /*version=*/ 1);
  TfLiteRegistrationExternalSetPrepare(r, Prepare);
  TfLiteRegistrationExternalSetInvoke(r, Eval);
  return r;
}

const TfLiteRegistrationExternal* AtanOpRegistrationExternal() {
  // Singleton instance, intentionally never destroyed.
  static const TfLiteRegistrationExternal* atan_op = AtanOpCreate();
  return atan_op;
}

const TfLiteRegistration AtanOpRegistration() {
  static const TfLiteRegistration atan_op {
    .registration_external = AtanOpRegistrationExternal();
  };
  return atan_op;
}
      

Lors de l'initialisation de OpResolver, ajoutez l'opération personnalisée dans le résolveur (voir l'exemple ci-dessous). L'opérateur est alors enregistré auprès de Tensorflow Lite afin que TensorFlow Lite puisse utiliser la nouvelle implémentation. Notez que les deux derniers arguments de TfLiteRegistration correspondent aux fonctions AtanPrepare et AtanEval que vous avez définies pour l'opération personnalisée. Si vous utilisiez les fonctions AtanInit et AtanFree pour initialiser les variables utilisées dans l'opération et pour libérer de l'espace, respectivement, elles seront ajoutées aux deux premiers arguments de TfLiteRegistration. Dans cet exemple, ces arguments sont définis sur nullptr.

Enregistrer l'opérateur auprès de la bibliothèque du noyau

Nous devons maintenant enregistrer l'opérateur dans la bibliothèque du noyau. Pour ce faire, vous devez utiliser un OpResolver. En arrière-plan, l'interpréteur charge une bibliothèque de noyaux qui seront chargés d'exécuter chacun des opérateurs du modèle. Bien que la bibliothèque par défaut ne contienne que des noyaux intégrés, il est possible de la remplacer ou de l'enrichir avec des opérateurs d'opérations de bibliothèque personnalisés.

La classe OpResolver, qui convertit les codes et noms d'opérateur en code réel, est définie comme suit:

class OpResolver {
 public:
  virtual TfLiteRegistration* FindOp(tflite::BuiltinOperator op) const = 0;
  virtual TfLiteRegistration* FindOp(const char* op) const = 0;
  ...
};

Notez que pour des raisons de rétrocompatibilité, cette classe utilise l'ancien type concret TfLiteRegistration au lieu du type opaque TfLiteRegistrationExternal, mais la structure TfLiteRegistration contient un champ registration_external de type TfLiteRegistrationExternal*.

Les classes MutableOpResolver et BuiltinOpResolver sont dérivées de OpResolver:

class MutableOpResolver : public OpResolver {
 public:
  MutableOpResolver();  // Constructs an initially empty op resolver.
  void AddBuiltin(tflite::BuiltinOperator op, const TfLiteRegistration* registration) = 0;
  void AddCustom(const char* op, const TfLiteRegistration* registration) = 0;
  void AddAll(const MutableOpResolver& other);
  ...
};

class BuiltinOpResolver : public MutableOpResolver {
 public:
  BuiltinOpResolver();  // Constructs an op resolver with all the builtin ops.
};

Une utilisation standard (sans opérations personnalisées) nécessite d'utiliser BuiltinOpResolver et d'écrire:

tflite::ops::builtin::BuiltinOpResolver resolver;

Pour ajouter l'opération personnalisée créée ci-dessus, vous pouvez utiliser un MutableOpResolver et appeler AddCustom (avant de transmettre le résolveur à InterpreterBuilder):

tflite::ops::builtin::MutableOpResolver resolver;
resolver.AddAll(tflite::ops::builtin::BuiltinOpResolver());
resolver.AddCustom("Atan", AtanOpRegistration());

Si l'ensemble d'opérations intégrées est jugé trop volumineux, un nouveau OpResolver peut être généré à partir d'un sous-ensemble d'opérations donné, éventuellement uniquement ceux contenus dans un modèle donné. Il s'agit de l'équivalent de l'enregistrement sélectif de TensorFlow (et d'une version simple de cet enregistrement est disponible dans le répertoire tools).

Si vous souhaitez définir vos opérateurs personnalisés en Java, vous devez actuellement créer votre propre couche JNI personnalisée et compiler votre propre AAR dans ce code jni. De même, si vous souhaitez définir ces opérateurs disponibles en Python, vous pouvez placer vos enregistrements dans le code du wrapper Python.

Notez qu'un processus semblable à celui ci-dessus peut être suivi pour prendre en charge un ensemble d'opérations au lieu d'un seul opérateur. Ajoutez simplement autant d'opérateurs AddCustom que nécessaire. En outre, MutableOpResolver vous permet également de remplacer les implémentations d'éléments intégrés à l'aide de AddBuiltin.

Tester et profiler votre opérateur

Pour profiler votre opération avec l'outil de benchmark TensorFlow Lite, vous pouvez utiliser l'outil de modèle d'analyse comparative pour TensorFlow Lite. À des fins de test, vous pouvez rendre votre build local TensorFlow Lite compatible avec votre opération personnalisée en ajoutant l'appel AddCustom approprié (comme indiqué ci-dessus) à register.cc.

Bonnes pratiques

  1. Optimisez avec précaution les allocations et désallocations de mémoire. L'allocation de mémoire dans Prepare est plus efficace que dans Invoke, et il est préférable d'allouer de la mémoire avant une boucle qu'à chaque itération. Utilisez des données de Tensors temporaires au lieu de vous transformer vous-même (voir le point 2). Utilisez des pointeurs/références au lieu de copier autant que possible.

  2. Si une structure de données persiste pendant toute l'opération, nous vous conseillons de pré-allouer la mémoire à l'aide de Tensors temporaires. Vous devrez peut-être utiliser une structure OpData pour référencer les index de Tensor dans d'autres fonctions. Consultez l'exemple dans le noyau pour la convolution. Vous trouverez ci-dessous un exemple d'extrait de code.

    struct MyOpData {
      int temp_tensor_index;
      ...
    };
    
    void* Init(TfLiteOpaqueContext* context,
        const char* buffer, size_t length) {
      auto* op_data = new MyOpData{};
      ...
      return op_data;
    }
    void Free(TfLiteOpaqueContext* context, void* buffer) {
      ...
      delete reinterpret_cast<MyOpData*>(buffer);
    }
    TfLiteStatus Prepare(TfLiteOpaqueContext* context,
                         TfLiteOpaqueNode* node) {
      ...
      auto* op_data =
          reinterpret_cast<MyOpData*>(TfLiteOpaqueNodeGetUserData(node));
      const int num_temporaries = 1;
      int temporary_tensor_indices[num_temporaries];
      TfLiteOpaqueTensorBuilder* builder = TfLiteOpaqueTensorBuilderCreate();
      TfLiteOpaqueTensorBuilderSetType(builder, kTfLiteFloat32);
      TfLiteOpaqueTensorBuilderSetAllocationType(builder, kTfLiteArenaRw);
      TfLiteOpaqueContextAddTensor(context, builder,
          &temporary_tensor_indices[0]);
      TfLiteOpaqueTensorBuilderDelete(builder);
      TfLiteOpaqueNodeSetTemporaries(node, temporary_tensor_indices,
          num_temporaries);
      op_data->temp_tensor_index = temporary_tensor_indices[0];
      ...
      return kTfLiteOk;
    }
    TfLiteStatus Invoke(TfLiteOpaqueContext* context,
                        TfLiteOpaqueNode* node) {
      ...
      auto* op_data = reinterpret_cast<MyOpData*>(
          TfLiteOpaqueNodeGetUserData(node));
      TfLiteOpaqueTensor* temp_tensor =
          TfLiteOpaqueContextGetOpaqueTensor(context,
              op_data->temp_tensor_index);
      TF_LITE_OPAQUE_ENSURE(context,
          TfLiteTensorType(temp_tensor) == kTfLiteFloat32);
      TF_LITE_OPAQUE_ENSURE(context,
          TfLiteTensorGetAllocationType(temp_Tensor) == kTfLiteArenaRw);
      void *temp_data = TfLiteTensorData(temp_tensor);
      TF_LITE_OPAQUE_ENSURE(context, temp_data != nullptr);
      ...
      return kTfLiteOk;
    }
    
  3. Si le gaspillage de mémoire n'est pas trop important, il est préférable d'utiliser un tableau statique de taille fixe (ou un tableau std::vector pré-alloué dans Resize) plutôt qu'un std::vector alloué de manière dynamique à chaque itération d'exécution.

  4. Évitez d'instancier des modèles de conteneur de bibliothèque standard qui n'existent pas encore, car ils affectent la taille binaire. Par exemple, si vous avez besoin dans votre opération d'un élément std::map qui n'existe pas dans les autres noyaux, l'utilisation d'un std::vector avec mappage d'indexation directe peut fonctionner tout en conservant une taille binaire faible. Découvrez ce que les autres noyaux utilisent pour obtenir des informations (ou demander).

  5. Vérifiez le pointeur vers la mémoire renvoyée par malloc. Si ce pointeur est nullptr, aucune opération ne doit être effectuée à l'aide de ce pointeur. Si vous exécutez la commande malloc dans une fonction et qu'une erreur se produit, libérez de la mémoire avant de quitter.

  6. Utilisez TF_LITE_OPAQUE_ENSURE(context, condition) pour vérifier une condition spécifique. Votre code ne doit pas laisser de mémoire en attente lorsque TF_LITE_OPAQUE_ENSURE est utilisé. Autrement dit, ces macros doivent être utilisées avant l'allocation de ressources susceptibles de fuir.