맞춤 연산자

LiteRT 내장 연산자 라이브러리는 제한된 TensorFlow 연산자 수가 많으며 모든 모델을 변환할 수 있는 것은 아닙니다. 자세한 내용은 연산자 호환성을 참조하세요.

전환을 허용하기 위해 사용자는 광고의 자체 맞춤 구현을 제공할 수 있습니다. LiteRT에서 지원되지 않는 TensorFlow 연산자(커스텀 연산자라고 함) 대신 미지원 (또는 지원됨) TensorFlow 연산자를 하나의 융합된 최적화된 커스텀 연산자로 만들 수 있습니다. 자세한 내용은 다음을 참고하세요. 연산자 융합

커스텀 연산자 사용은 네 단계로 이루어집니다.

커스텀 예측 인덱스를 사용하여 모델을 실행하는 연산자 tf.atan (이름: Atan, TensorFlow 모델 만들기 참고)은 다음과 같습니다. TensorFlow에서는 지원되지만 LiteRT에서는 지원되지 않습니다.

TensorFlow Text 연산자는 커스텀 연산자의 예입니다. 자세한 내용은 코드 예에 대한 TF Text를 LiteRT로 변환 튜토리얼을 확인하세요.

예: 맞춤 Atan 연산자

이번에는 TensorFlow 연산자 지원 예시를 살펴보겠습니다. LiteRT에는 포함되지 않습니다. Atan 연산자를 사용한다고 가정해 보겠습니다. 함수 y = atan(x + offset)을 위한 매우 간단한 모델을 빌드합니다. offset는 학습 가능합니다.

TensorFlow 모델 만들기

다음 코드 스니펫은 간단한 TensorFlow 모델을 학습시킵니다. 이 모델은 y = atan(x + offset) 함수인 Atan라는 커스텀 연산자가 포함되어 있습니다. 여기서 offset는 학습이 가능합니다.

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

이 시점에서 기본값인 Cloud Build를 사용하여 변환기 플래그를 사용하면 다음과 같은 오류 메시지가 표시됩니다.

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

LiteRT 모델로 변환

변환기를 설정하여 커스텀 연산자로 LiteRT 모델 만들기 다음과 같이 allow_custom_ops 속성을 지정해야 합니다.

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

이 시점에서 다음과 같은 명령어를 사용하여 기본 인터프리터로 실행하면 다음과 같습니다.

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

다음 오류가 계속 표시됩니다.

Encountered unresolved custom op: Atan.

연산자를 만들고 등록합니다.

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

LiteRT 커스텀 연산자는 불투명 유형 (TfLiteRegistrationExternal)과 관련 함수로 구성됩니다.

TfLiteRegistrationExternal는 불투명 유형입니다.

typedef struct TfLiteRegistrationExternal TfLiteRegistrationExternal;

TfLiteRegistrationExternal는 연산자의 ID와 구현을 저장합니다. 연산자는 연산자를 호출하는 노드의 LiteRT 그래프 노드입니다.)

이 유형의 인스턴스는 TfLiteRegistrationExternalCreate를 포함하고 다음을 호출하여 소멸할 수 있습니다. TfLiteRegistrationExternalDelete

연산자의 ID는 생성자 함수의 매개변수를 통해 설정됩니다. 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.
);

연산자 구현은 '메서드'를 정의할 수 있음 사용할 수 있습니다 이러한 메서드는 모두 선택사항이지만 운영자가 성공적으로 평가가 필요한 경우 연산자 구현은 (setter를 사용하여) 정의하고 함수) 적어도 PrepareInvoke 메서드를 사용해야 합니다.

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

작업 구현의 함수 names (또는 C++의 경우 네임스페이스 접두사) 위의 코드 스니펫에 있는 함수 이름과 일치하지 않아도 됩니다. 라이트 커스텀 작업 API는 주소만 사용합니다. 실제로 익명 네임스페이스에서 또는 정적 함수로 선언할 수 있습니다.

하지만 연산자 이름을 다음 함수 이름:

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.
      

이것은 C API이기 때문에 이러한 "메서드"는 C 함수 포인터로 구현되며 TfLiteRegistrationExternal 유형: 해당하는 setter 함수에 대한 구현 함수 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));

다음을 참고하세요. common.h 드림 TfLiteContextTfLiteNode에 대한 세부정보를 확인하세요. TfLiteContext에서 오류 발생 보고 기능 및 모든 텐서를 포함한 전역 객체에 대한 액세스. TfLiteNode를 사용하면 운영자 구현이 입력 및 출력에 액세스할 수 있습니다.

인터프리터는 모델을 로드할 때 모델마다 Init() 메서드를 한 번씩 호출합니다. 노드입니다. 작업이 다음과 같은 경우 지정된 Init()가 두 번 이상 호출됩니다. 그래프에서 여러 번 사용될 수 있습니다. 맞춤 작업의 경우 구성 버퍼는 매개변수 이름을 값에 매핑하는 flexbuffer가 포함되어 있습니다. 이 인터프리터가 이미 op 매개변수를 사용합니다. 상태가 필요한 커널 구현은 상태를 초기화해야 합니다. 발신자에게 소유권을 이전하세요. 각 Init() 호출마다 다음을 할 수 있습니다. 상응하는 Free() 호출이며, 이를 통해 구현은 다음을 삭제할 수 있습니다. Init()에 할당했을 수 있는 버퍼입니다.

입력 텐서의 크기가 조절될 때마다 인터프리터는 구현에 변경사항을 알리는 그래프입니다. 이렇게 하면 내부 버퍼의 크기를 조절하고, 입력 형태와 유형의 유효성을 확인하며, 출력 셰이프를 다시 계산합니다. 이 작업은 모두 Prepare() 메서드를 통해 실행됩니다. 구현은 다음을 사용하여 상태에 액세스할 수 있고 TfLiteOpaqueNodeGetUserData(node)

마지막으로 추론이 실행될 때마다 인터프리터는 Invoke() 메서드에서도 상태를 다음과 같이 사용할 수 있습니다. TfLiteOpaqueNodeGetUserData(node)

커스텀 작업은 이러한 '메서드'를 정의하여 구현할 수 있음 함수에 대해 알아본 다음 TfLiteRegistrationExternal의 인스턴스를 반환하는 함수 정의 TfLiteRegistrationExternalCreate를 호출하고 관련 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;
}
      

등록은 자동으로 이루어지지 않으며 MyCustomOpRegistration 함수를 만들어야 합니다 (아래 세부정보 참고). 반면 표준 BuiltinOpResolver (:builtin_ops 타겟에서 사용 가능) 커스텀 작업은 사용할 수 있습니다.

LiteRT 런타임에서 커널 정의

LiteRT에서 작업을 사용하기 위해서는 두 가지 함수만 정의하면 됩니다. (PrepareEval), 세 번째는 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;
}
      

OpResolver를 초기화할 때 맞춤 작업을 리졸버에 추가합니다. 예를 참조하세요. 이렇게 하면 LiteRT에 운영자가 등록되므로 LiteRT에서 새 구현을 사용할 수 있습니다. 마지막 두 개는 TfLiteRegistration의 인수는 AtanPrepareAtanEval에 상응합니다. 함수를 정의합니다. AtanInitAtanFree를 사용한 경우 함수를 사용하여 연산에 사용되는 변수를 초기화하고 두 개의 인수에 각각 TfLiteRegistration 이 예에서 해당 인수는 nullptr로 설정됩니다.

커널 라이브러리에 연산자 등록

이제 연산자를 커널 라이브러리에 등록해야 합니다. 이 작업은 OpResolver 인터프리터는 백그라운드에서 모델의 각 연산자를 실행하기 위해 할당될 커널입니다. 기본 라이브러리에는 내장 커널만 포함되어 있지만 맞춤 라이브러리 작업 연산자로 대체/보강할 수 있습니다.

OpResolver 클래스: 연산자 코드와 이름을 실제 다음과 같이 정의됩니다.

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

이전 버전과의 호환성을 위해 이 클래스는 이전 구체적인 유형을 사용합니다. 불투명 유형 TfLiteRegistrationExternal가 아닌 TfLiteRegistration TfLiteRegistration 구조체에는 registration_external 필드가 포함되어 있습니다. TfLiteRegistrationExternal*를 입력합니다.

MutableOpResolverBuiltinOpResolver 클래스는 다음에서 파생됩니다. 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.
};

(맞춤 작업 없이) 정기적으로 사용하려면 BuiltinOpResolver를 사용해야 합니다. 그리고 다음과 같이 작성합니다.

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

위에서 만든 맞춤 작업을 추가하려면 대신 MutableOpResolver를 사용하면 됩니다. AddCustom을 호출하세요 (리졸버를 InterpreterBuilder).

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

기본 제공 작업 세트가 너무 크다고 판단되면 새 OpResolver를 생성된 모든 작업(작업에 포함된 작업만 가능)에 따라 예측하도록 하는 것입니다. 이는 TensorFlow의 선택적 등록과 간단한 버전은 tools 디렉터리에서 사용할 수 있습니다.

Java에서 맞춤 연산자를 정의하려면 현재 자체 맞춤 JNI 레이어를 빌드하고 자체 AAR을 컴파일 이 jni 코드에 포함되어 있습니다. 마찬가지로 Python에서 사용할 수 있는 이러한 연산자를 정의하려면 등록 정보를 Python 래퍼 코드.

이와 유사한 과정을 통해 작업을 사용할 수 있습니다. AddCustom 연산자를 최대한 많이 추가하면 됩니다. 추가할 수 있습니다 또한 MutableOpResolver를 사용하면 AddBuiltin를 사용하여 내장 기능을 구현합니다.

통신사 테스트 및 프로파일링

LiteRT 벤치마크 도구로 작업을 프로파일링하려면 다음을 사용하면 됩니다. 벤치마크 모델 도구 (LiteRT)입니다 테스트 목적으로는 LiteRT에서 적절한 AddCustom를 추가하여 맞춤 작업을 인식 (위 그림 참조) register.cc

권장사항

  1. 메모리 할당 및 할당 해제를 신중하게 최적화합니다. 메모리 할당 중 PrepareInvoke보다 더 효율적이며 메모리를 할당합니다. 하는 것이 모든 반복보다 낫습니다. 임시 텐서 데이터 사용 2번 항목을 참조하세요 (항목 2 참조). 대신 포인터/참조 사용 최대한 복제하는 것이 바람직합니다.

  2. 데이터 구조가 전체 작업 중에 유지된다면 임시 텐서를 사용하여 메모리를 사전 할당합니다. 다음을 사용해야 할 수도 있습니다. 다른 함수에서 텐서 색인을 참조하는 OpData 구조체입니다. 자세한 내용은 예시: 컨볼루션용 커널. 샘플 코드 스니펫은 아래와 같습니다.

    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. 메모리 낭비가 많이 발생하지 않는다면 고정된 고정 크기를 사용하는 것이 좋습니다. 배열 (또는 Resize에 사전 할당된 std::vector)을 실행이 반복될 때마다 std::vector를 동적으로 할당합니다.

  4. 아직 구현하지 않은 표준 라이브러리 컨테이너 템플릿은 인스턴스화하지 마세요. 바이너리 크기에 영향을 미치기 때문입니다. 예를 들어 다음을 사용하여 다른 커널에 없는 std::map를 작업에 포함시킵니다. 직접 색인 매핑을 사용한 std::vector가 작동할 수 있지만 바이너리 크기가 작습니다. 다른 커널이 통찰력을 얻기 위해 무엇을 사용하는지 확인하거나 질문합니다.

  5. malloc에서 반환한 메모리의 포인터를 확인합니다. 이 포인터가 nullptr: 이 포인터를 사용하여 작업을 실행하면 안 됩니다. 만약 malloc 함수를 실행하고 오류가 발생하면 먼저 메모리를 할당 해제합니다. 있습니다.

  6. TF_LITE_OPAQUE_ENSURE(context, condition)를 사용하여 특정 있습니다. 코드 실행 시 메모리가 중단되어서는 안 됨 TF_LITE_OPAQUE_ENSURE가 사용됩니다. 즉, 이 매크로는 누출될 모든 리소스가 할당됩니다