از آنجایی که کتابخانه اپراتور داخلی LiteRT تنها از تعداد محدودی از اپراتورهای TensorFlow پشتیبانی می کند، هر مدلی قابل تبدیل نیست. برای جزئیات، به سازگاری اپراتور مراجعه کنید.
برای اجازه دادن به تبدیل، کاربران می توانند پیاده سازی سفارشی خود را از یک اپراتور TensorFlow پشتیبانی نشده در LiteRT، که به عنوان یک اپراتور سفارشی شناخته می شود، ارائه دهند. در عوض، اگر میخواهید یک سری از عملگرهای پشتیبانینشده (یا پشتیبانیشده) TensorFlow را در یک اپراتور سفارشی بهینهسازی شده واحد ترکیب کنید، به ترکیب عملگر مراجعه کنید.
استفاده از عملگرهای سفارشی شامل چهار مرحله است.
یک مدل TensorFlow ایجاد کنید. مطمئن شوید که مدل ذخیره شده (یا Graph Def) به عملگر 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 ساده را آموزش می دهد. این مدل فقط شامل یک عملگر سفارشی به نام Atan
است که یک تابع y = atan(x + offset)
است که در آن 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
در این مرحله، اگر بخواهید یک مدل LiteRT با پرچم های مبدل پیش فرض تولید کنید، با پیغام خطای زیر مواجه خواهید شد:
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 با استفاده از یک API خالص-C ساده که از نوع مات ( TfLiteOperator
) و توابع مرتبط تشکیل شده است، تعریف میشوند.
TfLiteOperator
یک نوع مات است:
typedef struct TfLiteOperator TfLiteOperator;
TfLiteOperator
هویت و پیاده سازی اپراتور را ذخیره می کند. (توجه داشته باشید که عملگر از عملوندهایش متمایز است که در گرههای گراف LiteRT برای گرههایی که اپراتور را فراخوانی میکنند، ذخیره میشوند.)
نمونه هایی از این نوع با فراخوانی به TfLiteOperatorCreate
ساخته می شوند و می توانند با فراخوانی TfLiteOperatorDelete
از بین بروند.
هویت اپراتور از طریق پارامترهای تابع سازنده TfLiteOperatorCreate
تنظیم می شود:
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.
);
پیاده سازی عملگر می تواند "روش ها" را با امضاهای زیر تعریف کند. همه این روشها اختیاری هستند، اما برای اینکه یک عملگر با موفقیت ارزیابی شود، پیادهسازی اپراتور باید حداقل متدهای 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);
نام توابع (یا پیشوندهای فضای نام، برای C++) در اجرای عملیات شما نباید با نام توابع در قطعه کد بالا مطابقت داشته باشند، زیرا API سفارشی عملیات TF Lite فقط از آدرسهای آنها استفاده میکند. در واقع ما توصیه می کنیم که آنها را در یک فضای نام ناشناس یا به عنوان توابع ثابت اعلام کنید.
اما ایده خوبی است که نام اپراتور خود را به عنوان یک فضای نام یا پیشوند در نام این توابع اضافه کنید:
C++
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.
از آنجایی که این یک C API است، این «روشها» بهعنوان نشانگرهای تابع C در نوع TfLiteOperator
پیادهسازی میشوند که با ارسال آدرسهای توابع پیادهسازی شما به توابع تنظیمکننده مربوطه TfLiteOperatorSet
MethodName تنظیم میشوند:
void TfLiteOperatorSetInit(
TfLiteOperator* operator,
void* (*init)(TfLiteOpaqueContext* context, const char* buffer,
size_t length));
void TfLiteOperatorSetFree(
TfLiteOperator* operator,
void (*free)(TfLiteOpaqueContext* context, void* data));
void TfLiteOperatorSetPrepare(
TfLiteOperator* operator,
TfLiteStatus (*prepare)(TfLiteOpaqueContext* context,
TfLiteOpaqueNode* node));
void TfLiteOperatorSetInvoke(
TfLiteOperator* operator,
TfLiteStatus (*invoke)(TfLiteOpaqueContext* context,
TfLiteOpaqueNode* node));
void TfLiteOperatorSetAsyncKernel(
TfLiteOperator* operator,
struct TfLiteAsyncKernel* (*async_kernel)(TfLiteOpaqueContext* context,
TfLiteOpaqueNode* node));
برای جزئیات در مورد TfLiteContext
و TfLiteNode
به common.h
مراجعه کنید. TfLiteContext
امکانات گزارش خطا و دسترسی به اشیاء سراسری، از جمله تمام تانسورها را فراهم می کند. TfLiteNode
به پیاده سازی اپراتورها اجازه می دهد تا به ورودی ها و خروجی های خود دسترسی داشته باشند.
هنگامی که مفسر یک مدل را بارگذاری می کند، متد Init()
را یک بار برای هر گره در گراف فراخوانی می کند. یک Init()
داده شده بیش از یک بار فراخوانی می شود اگر op چندین بار در نمودار استفاده شود. برای عملیات سفارشی یک بافر پیکربندی ارائه خواهد شد که حاوی یک فلکسبافر است که نام پارامترها را به مقادیر آنها نگاشت می کند. بافر برای عملیات داخلی خالی است زیرا مفسر قبلاً پارامترهای op را تجزیه کرده است. پیاده سازی های هسته ای که نیاز به حالت دارند باید آن را در اینجا مقداردهی اولیه کنند و مالکیت را به تماس گیرنده منتقل کنند. برای هر فراخوانی Init()
یک فراخوانی متناظر با Free()
وجود خواهد داشت که به پیاده سازی ها اجازه می دهد بافری را که ممکن است در Init()
تخصیص داده باشند، از بین ببرند.
هر زمان که اندازه تانسورهای ورودی تغییر کند، مفسر از طریق نمودار به اجرای تغییرات اطلاع می دهد. این به آنها این فرصت را می دهد که اندازه بافر داخلی خود را تغییر دهند، اعتبار اشکال و انواع ورودی را بررسی کنند و اشکال خروجی را دوباره محاسبه کنند. همه این کارها از طریق متد Prepare()
انجام می شود و پیاده سازی ها می توانند با استفاده از TfLiteOpaqueNodeGetUserData(node)
به وضعیت خود دسترسی داشته باشند.
در نهایت، هر بار که استنتاج اجرا میشود، مفسر گراف را طی میکند و متد Invoke()
را فراخوانی میکند و در اینجا نیز وضعیت بهعنوان TfLiteOpaqueNodeGetUserData(node)
در دسترس است.
عملیات سفارشی را می توان با تعریف آن توابع "روش"، و سپس تعریف تابعی که نمونه ای از TfLiteOperator
ساخته شده با فراخوانی TfLiteOperatorCreate
و سپس متدهای تنظیم کننده مربوطه را برمی گرداند، پیاده سازی کرد:
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 TfLiteOperator* MyCustomOperator() { // 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* MyCustomOperator() { // Singleton instance, intentionally never destroyed. static const TfLiteOperator* my_custom_op = MyCustomOpCreate(); return my_custom_op; }
توجه داشته باشید که ثبت نام خودکار نیست و باید یک تماس صریح با عملکرد MyCustomOperator
شما برقرار شود (به جزئیات زیر مراجعه کنید). در حالی که BuiltinOpResolver
استاندارد (موجود در هدف :builtin_ops
) از ثبت داخلی ها مراقبت می کند، عملیات های سفارشی باید در کتابخانه های سفارشی جداگانه جمع آوری شوند.
تعریف کرنل در زمان اجرا LiteRT
تنها کاری که برای استفاده از op در LiteRT باید انجام دهیم این است که دو تابع ( Prepare
و Eval
) و سومی برای ساخت یک TfLiteOperator
تعریف کنیم:
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 TfLiteOperator* AtanOperator() { // 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<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 TfLiteOperator* AtanOpCreate() { TfLiteOperator* r = TfLiteOperatorCreate( kTfLiteBuiltinCustom, "ATAN", /*version=*/ 1); TfLiteOperatorSetPrepare(r, Prepare); TfLiteOperatorSetInvoke(r, Eval); return r; } const TfLiteOperator* AtanOperator() { // Singleton instance, intentionally never destroyed. static const TfLiteOperator* atan_op = AtanOpCreate(); return atan_op; }
هنگام راه اندازی OpResolver
، عملیات سفارشی را به حل کننده اضافه کنید (برای مثال به زیر مراجعه کنید). این اپراتور را با LiteRT ثبت می کند تا LiteRT بتواند از پیاده سازی جدید استفاده کند.
اپراتور را با کتابخانه هسته ثبت کنید
اکنون باید اپراتور را با کتابخانه هسته ثبت کنیم. این کار با OpResolver
انجام می شود. در پشت صحنه، مفسر کتابخانه ای از هسته ها را بارگذاری می کند که برای اجرای هر یک از عملگرهای مدل تخصیص داده می شود. در حالی که کتابخانه پیشفرض فقط شامل هستههای داخلی است، میتوان آن را با عملگرهای عملیاتی کتابخانه سفارشی جایگزین/افزایش کرد.
کلاس OpResolver
که کدها و نامهای اپراتورها را به کد واقعی ترجمه میکند، به صورت زیر تعریف میشود:
class OpResolver {
public:
virtual TfLiteRegistration* FindOp(tflite::BuiltinOperator op) const = 0;
virtual TfLiteRegistration* FindOp(const char* op) const = 0;
...
};
توجه داشته باشید که برای سازگاری با عقب، این کلاس از نوع بتن قدیمی تر TfLiteRegistration
به جای TfLiteOperator
نوع مات استفاده می کند، اما ساختار TfLiteRegistration
حاوی یک فیلد registration_external
از نوع TfLiteOperator*
است.
کلاس های MutableOpResolver
و BuiltinOpResolver
از OpResolver
مشتق شده اند:
class MutableOpResolver : public OpResolver {
public:
MutableOpResolver(); // Constructs an initially empty op resolver.
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
استفاده کنید و tflite::AddOp
فراخوانی کنید (قبل از اینکه حلکننده را به InterpreterBuilder
ارسال کنید):
tflite::ops::builtin::MutableOpResolver resolver;
resolver.AddAll(tflite::ops::builtin::BuiltinOpResolver());
tflite::AddOp(&resolver, AtanOpRegistration());
اگر مجموعه عملیات داخلی بیش از حد بزرگ در نظر گرفته شود، یک OpResolver
جدید میتواند براساس زیرمجموعه معینی از عملیاتها، احتمالاً فقط موارد موجود در یک مدل خاص، کد تولید شود. این معادل ثبت انتخابی TensorFlow است (و یک نسخه ساده از آن در فهرست tools
موجود است).
اگر می خواهید اپراتورهای سفارشی خود را در جاوا تعریف کنید، در حال حاضر باید لایه JNI سفارشی خود را بسازید و AAR خود را در این کد jni کامپایل کنید. به طور مشابه، اگر میخواهید این عملگرها را در پایتون تعریف کنید، میتوانید ثبتهای خود را در کد پوشش پایتون قرار دهید.
توجه داشته باشید که فرآیند مشابهی مانند بالا را می توان برای پشتیبانی از مجموعه ای از عملیات به جای یک اپراتور منفرد دنبال کرد. فقط هر تعداد اپراتور AddCustom
که نیاز دارید اضافه کنید. علاوه بر این، MutableOpResolver
همچنین به شما اجازه می دهد تا با استفاده از AddBuiltin
، پیاده سازی های داخلی را نادیده بگیرید.
اپراتور خود را تست و مشخصات
برای نمایه کردن عملیات خود با ابزار معیار LiteRT، می توانید از ابزار مدل معیار برای LiteRT استفاده کنید. برای اهداف آزمایشی، میتوانید با افزودن تماس AddCustom
مناسب (همانطور که در بالا نشان داده شده است) به register.cc، ساخت محلی LiteRT خود را از عملیات سفارشی خود آگاه کنید.
بهترین شیوه ها
تخصیص حافظه و عدم تخصیص را با احتیاط بهینه کنید. تخصیص حافظه در
Prepare
کارآمدتر ازInvoke
است و تخصیص حافظه قبل از حلقه بهتر از هر تکرار است. از دادههای تانسور موقت بهجای اینکه خودتان را مخفی کنید، استفاده کنید (به مورد 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; }
اگر هزینه زیادی برای حافظه تلف نمی شود، ترجیح دهید از یک آرایه با اندازه ثابت ثابت (یا یک
std::vector
از پیش تخصیص داده شده درResize
) به جای استفاده ازstd::vector
اختصاص داده شده به صورت پویا در هر تکرار اجرا استفاده کنید.از نمونهسازی الگوهای استاندارد کانتینر کتابخانهای که قبلاً وجود ندارند، خودداری کنید، زیرا بر اندازه باینری تأثیر میگذارند. به عنوان مثال، اگر در عملیات خود به یک
std::map
نیاز دارید که در هسته های دیگر وجود ندارد، استفاده از یکstd::vector
با نگاشت نمایه سازی مستقیم می تواند در عین کوچک نگه داشتن اندازه باینری کار کند. ببینید هسته های دیگر از چه چیزهایی برای به دست آوردن بینش استفاده می کنند (یا بپرسید).نشانگر حافظه بازگشتی توسط
malloc
را بررسی کنید. اگر این اشاره گرnullptr
باشد، هیچ عملیاتی نباید با استفاده از آن نشانگر انجام شود. اگر در یک تابعmalloc
دارید و خطای خروجی دارید، قبل از خروج، حافظه را اختصاص دهید.TF_LITE_OPAQUE_ENSURE(context, condition)
برای بررسی یک وضعیت خاص استفاده کنید. هنگام استفاده ازTF_LITE_OPAQUE_ENSURE
، کد شما نباید حافظه را معلق بگذارد، به عنوان مثال، این ماکروها باید قبل از تخصیص هرگونه منبعی که نشت می کند استفاده شوند.