Opérateurs personnalisés

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

Pour autoriser la conversion, les utilisateurs peuvent fournir leur propre implémentation personnalisée d'un d'opérateur TensorFlow non pris en charge dans LiteRT, appelé opérateur personnalisé. Si vous préférez, vous pouvez combiner une série les opérateurs TensorFlow en un seul opérateur personnalisé optimisé. Pour en savoir plus, fusion des opérateurs.

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

Examinons un exemple de bout en bout illustrant l'exécution d'un modèle avec un l'opérateur tf.atan (nommé Atan, reportez-vous à la section Créer un modèle TensorFlow). est compatible avec TensorFlow, mais pas avec LiteRT.

L'opérateur TensorFlow Text est un exemple d'opérateur personnalisé. Consultez le Convert TF Text to LiteRT (Convertir TF Text en LiteRT) pour obtenir un exemple de code

Exemple: opérateur Atan personnalisé

Prenons l'exemple d'un opérateur TensorFlow compatible ce n'est pas le cas de LiteRT. Supposons que nous utilisons l'opérateur Atan et que nous construisons un modèle très simple pour une fonction y = atan(x + offset), où offset est apte à l'entraînement.

Créer un modèle TensorFlow

L'extrait de code suivant entraîne un modèle TensorFlow simple. Ce modèle vient contient 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 LiteRT avec vous obtenez le message d'erreur suivant:

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

Convertir en modèle LiteRT

Créer un modèle LiteRT avec des opérateurs personnalisés, en définissant le convertisseur l'attribut 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 de commandes telles que ce qui suit:

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

Vous obtiendrez toujours l'erreur suivante:

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 LiteRT sont définis à l'aide d'une simple API en C pur qui est constitué 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 la Nœuds de graphe LiteRT pour les nœuds qui appellent l'opérateur)

Les instances de ce type sont construites avec des appels vers TfLiteRegistrationExternalCreate et peut être détruite en appelant TfLiteRegistrationExternalDelete

L'identité de l'opérateur est définie via les paramètres de la fonction 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 puisse évalué, l'implémentation de l'opérateur doit définir et régler (à l'aide du setter fonctions) 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 noms de fonctions (ou préfixes d'espace de noms, pour C++) dans la mise en œuvre de votre opération ne doivent pas nécessairement correspondre aux noms de fonction dans l'extrait de code ci-dessus, car le fichier L'API Lite custom ops n'utilisera que leurs adresses. En effet, nous vous recommandons Déclarez-les dans un espace de noms anonyme ou en tant que fonctions statiques.

Mais c'est une bonne idée d'inclure votre nom d'opérateur en tant qu’espace de noms ou préfixe sur ces noms de fonction:

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.
      

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

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

Consultez common.h pour en savoir plus sur TfLiteContext et TfLiteNode. TfLiteContext renvoie une erreur de création de rapports et l'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 dans le graphique. Une valeur Init() donnée sera appelée plusieurs fois si l'opération est utilisées plusieurs fois dans le graphique. Pour les opérations personnalisées, un tampon de configuration contenant un tampon flexible qui mappe les noms de paramètres à leurs valeurs. La est vide pour les opérations intégrées, car l'interpréteur a déjà analysé op. Les implémentations du noyau qui nécessitent un état doivent l'initialiser et transférer la propriété à l'appelant. Pour chaque appel Init(), vous aurez un appel correspondant à Free(), ce qui permet aux implémentations de supprimer qu'ils ont peut-être alloué dans Init().

Chaque fois que les Tensors d'entrée sont redimensionnés, l'interpréteur passe par les graphique signalant les implémentations de la modification. Cela leur donne la possibilité de redimensionner leur tampon interne, vérifier la validité des formes et types de saisie ; pour recalculer les formes de sortie. Tout cela s'effectue via la méthode Prepare(). les implémentations peuvent accéder à leur état TfLiteOpaqueNodeGetUserData(node)

Enfin, à chaque exécution d'une inférence, l'interpréteur parcourt le graphique, 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 "méthodes" fonctions, puis définir une fonction qui renvoie une instance de TfLiteRegistrationExternal ; créée en appelant TfLiteRegistrationExternalCreate, puis les valeurs méthodes setter:

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'inscription n'est pas automatique et ne constitue pas un appel explicite à votre La fonction MyCustomOpRegistration doit être créée (voir les détails ci-dessous). Alors que le standard BuiltinOpResolver (disponible depuis la cible :builtin_ops) prend de l'enregistrement des builds intégrés, les opérations personnalisées devront être collectées bibliothèques personnalisées distinctes.

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

Pour utiliser l'opération dans LiteRT, il suffit de définir deux fonctions (Prepare et Eval) et un 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<float*>(TfLiteOpaqueTensorData(input));
      float* output_data = static_cast<float*>(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<float*>(TfLiteOpaqueTensorData(input));
  float* output_data = static_cast<float*>(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 ci-dessous). L'opérateur sera alors enregistré dans LiteRT. que LiteRT puisse utiliser la nouvelle implémentation. Notez que les deux derniers arguments dans TfLiteRegistration correspondent à AtanPrepare et AtanEval que vous avez définies pour l'opération personnalisée. Si vous avez utilisé 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; ces arguments sont définis sur nullptr dans cet exemple.

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

Nous devons maintenant enregistrer l'opérateur auprès de la bibliothèque du noyau. Cela se fait avec un OpResolver. En arrière-plan, l'interprète charge une bibliothèque de noyaux qui seront affectés pour 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 remplacez/enrichissez-le par des opérateurs d'opérations de bibliothèque personnalisés.

La classe OpResolver, qui traduit les codes et les noms des opérateurs en est définie comme ceci:

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

Notez que pour assurer la 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 saisissez 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 l'utilisation de BuiltinOpResolver. et écrire:

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

Pour ajouter l'opération personnalisée créée ci-dessus, vous pouvez utiliser à la place un MutableOpResolver, et appelez AddCustom (avant de transmettre le résolveur au 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 considéré comme trop volumineux, une nouvelle OpResolver peut être du code généré à partir d'un sous-ensemble donné d'opérations, peut-être uniquement ceux contenus dans un modèle donné. Il s'agit de l'équivalent de l'enregistrement sélectif de TensorFlow. (et une version simple 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 placez vos inscriptions Code wrapper Python.

Notez qu'un processus similaire à celui présenté ci-dessus peut être suivi pour prendre en charge un ensemble de des opérations au lieu d'un seul opérateur. Ajoutez simplement autant d'opérateurs AddCustom selon vos besoins. De plus, MutableOpResolver vous permet aussi d'ignorer les implémentations de "builtins" à l'aide de AddBuiltin.

Tester et profiler votre opérateur

Pour profiler votre opération avec l'outil d'analyse comparative LiteRT, vous pouvez utiliser outil de modélisation de modèles de benchmark pour LiteRT. À des fins de test, vous pouvez créer votre build local Vous n'avez pas besoin de vous informer facilement de votre opération personnalisée en ajoutant le AddCustom approprié. (comme indiqué ci-dessus) au register.cc

Bonnes pratiques

  1. Optimisez les allocations et désallocations de mémoire avec prudence. Allouer de la mémoire est plus efficace dans Prepare que dans Invoke, et l'allocation de mémoire avant une boucle est préférable à chaque itération. Utiliser des données de Tensors temporaires plutôt que de vous espionner vous-même (voir le point 2). Utilisez plutôt des pointeurs/références de copier autant que possible.

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

    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. S'il ne coûte pas trop de mémoire gaspillée, utilisez une taille fixe et statique. (ou un std::vector préalloué dans Resize) plutôt que d'utiliser 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 ne car elles affectent la taille binaire. Par exemple, si vous avez besoin d'un std::map dans votre opération qui n'existe pas dans d'autres noyaux, à l'aide d'un std::vector avec mappage d'indexation directe pourrait fonctionner tout en gardant le la taille binaire la plus petite. 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 malloc dans une fonction et si une sortie d'erreur s'affiche, libérez de la mémoire avant de .

  6. Utilisez TF_LITE_OPAQUE_ENSURE(context, condition) pour rechercher une . Votre code ne doit pas laisser de mémoire en attente lorsque TF_LITE_OPAQUE_ENSURE est utilisé, c'est-à-dire que ces macros doivent être utilisées avant toutes les ressources qui risquent d'être divulguées sont allouées.