LiteRT 내장 연산자 라이브러리는 제한된 TensorFlow 연산자 수가 많으며 모든 모델을 변환할 수 있는 것은 아닙니다. 자세한 내용은 연산자 호환성을 참조하세요.
전환을 허용하기 위해 사용자는 광고의 자체 맞춤 구현을 제공할 수 있습니다. LiteRT에서 지원되지 않는 TensorFlow 연산자(커스텀 연산자라고 함) 대신 미지원 (또는 지원됨) TensorFlow 연산자를 하나의 융합된 최적화된 커스텀 연산자로 만들 수 있습니다. 자세한 내용은 다음을 참고하세요. 연산자 융합
커스텀 연산자 사용은 네 단계로 이루어집니다.
TensorFlow 모델 만들기 저장된 항목 모델 (또는 그래프 데프)은 올바르게 이름이 지정된 LiteRT 연산자를 나타냅니다.
LiteRT 모델로 변환합니다. 올바른 LiteRT 변환기 속성을 설정해야 모델 변환이 완료됩니다
연산자를 만들고 등록합니다. 이 LiteRT 런타임이 연산자와 실행 가능한 C/C++ 코드로 변환할 수 있습니다.
연산자를 테스트하고 프로파일링합니다. 만약 커스텀 연산자만 테스트하려면 해당 연산자가 포함된 모델을 커스텀 연산자만 사용하고 benchmark_model 있습니다.
커스텀 예측 인덱스를 사용하여 모델을 실행하는
연산자 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를 사용하여) 정의하고
함수) 적어도 Prepare
및 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);
작업 구현의 함수 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 함수에 대한 구현 함수
TfLiteRegistrationExternalSet
MethodName:
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
드림
TfLiteContext
및 TfLiteNode
에 대한 세부정보를 확인하세요. 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에서 작업을 사용하기 위해서는 두 가지 함수만 정의하면 됩니다.
(Prepare
및 Eval
), 세 번째는 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
의 인수는 AtanPrepare
및 AtanEval
에 상응합니다.
함수를 정의합니다. AtanInit
및 AtanFree
를 사용한 경우
함수를 사용하여 연산에 사용되는 변수를 초기화하고
두 개의 인수에 각각
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*
를 입력합니다.
MutableOpResolver
및 BuiltinOpResolver
클래스는 다음에서 파생됩니다.
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
권장사항
메모리 할당 및 할당 해제를 신중하게 최적화합니다. 메모리 할당 중
Prepare
가Invoke
보다 더 효율적이며 메모리를 할당합니다. 하는 것이 모든 반복보다 낫습니다. 임시 텐서 데이터 사용 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; }
메모리 낭비가 많이 발생하지 않는다면 고정된 고정 크기를 사용하는 것이 좋습니다. 배열 (또는
Resize
에 사전 할당된std::vector
)을 실행이 반복될 때마다std::vector
를 동적으로 할당합니다.아직 구현하지 않은 표준 라이브러리 컨테이너 템플릿은 인스턴스화하지 마세요. 바이너리 크기에 영향을 미치기 때문입니다. 예를 들어 다음을 사용하여 다른 커널에 없는
std::map
를 작업에 포함시킵니다. 직접 색인 매핑을 사용한std::vector
가 작동할 수 있지만 바이너리 크기가 작습니다. 다른 커널이 통찰력을 얻기 위해 무엇을 사용하는지 확인하거나 질문합니다.malloc
에서 반환한 메모리의 포인터를 확인합니다. 이 포인터가nullptr
: 이 포인터를 사용하여 작업을 실행하면 안 됩니다. 만약malloc
함수를 실행하고 오류가 발생하면 먼저 메모리를 할당 해제합니다. 있습니다.TF_LITE_OPAQUE_ENSURE(context, condition)
를 사용하여 특정 있습니다. 코드 실행 시 메모리가 중단되어서는 안 됨TF_LITE_OPAQUE_ENSURE
가 사용됩니다. 즉, 이 매크로는 누출될 모든 리소스가 할당됩니다