ونظرًا لأن مكتبة مشغّلات LiteRT المضمنة تدعم فقط واجهة عدد عوامل تشغيل TensorFlow، وليس كل نموذج قابل للتحويل. لمزيد من التفاصيل، راجِع التوافق مع مشغِّلي شبكات الجوّال.
للسماح بالإحالة الناجحة، يمكن للمستخدمين تقديم تنفيذ مخصص لهم عامل تشغيل TensorFlow غير متوافق في LiteRT، ويُعرف باسم عامل تشغيل مخصّص. إذا كنت تريد بدلاً من ذلك دمج سلسلة من الأدوات غير المتوافقة (أو المتاحة) عوامل التشغيل TensorFlow في عامل تشغيل مخصّص واحد مدمج ومحسّن، ارجع إلى دمج مشغِّل شبكة الجوّال
يتكون استخدام عوامل التشغيل المخصصة من أربع خطوات.
أنشئ نموذج TensorFlow. تأكد من حفظ يشير النموذج (أو Graph Def) إلى عامل التشغيل LiteRT الذي تمت تسميته بشكل صحيح.
التحويل إلى نموذج LiteRT: تأكَّد من ضبط سمة محوّل LiteRT الصحيحة لكي في تحويل النموذج بنجاح.
أنشِئ عامل التشغيل وسجِّله. هذا النمط حتى يعرف وقت تشغيل LiteRT كيفية تعيين المشغل المعلَمات في الرسم البياني إلى رمز C/C++ التنفيذي.
اختبِر منظِّم الجولات وأنشِئ ملفًا شخصيًا له: إذا كنت ترغب في اختبار عامل التشغيل المخصص فقط، فمن الأفضل إنشاء نموذج يحتوي على فقط عامل التشغيل المخصص الخاص بك واستخدام benchmark_model البرنامج.
لنلقِ نظرة على مثال شامل لتشغيل نموذج يحتوي على
العامل tf.atan
(المسمى Atan
، ويمكنك الرجوع إلى إنشاء نموذج TensorFlow) الذي
يتوفّر في TensorFlow، ولكنه غير متاح في LiteRT.
عامل النص TensorFlow هو مثال على عامل تشغيل مخصص. يمكنك الاطّلاع على برنامج تعليمي حول تحويل نص TF إلى 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 باستخدام واجهة برمجة تطبيقات بسيطة pure-C
يتكون من نوع غامض (TfLiteRegistrationExternal
) ودوال ذات صلة.
TfLiteRegistrationExternal
هو نوع مبهم:
typedef struct TfLiteRegistrationExternal TfLiteRegistrationExternal;
يخزِّن TfLiteRegistrationExternal
هوية عامل التشغيل وتنفيذه.
(لاحظ أن عامل التشغيل يختلف عن معاملاته، والتي يتم تخزينها في
عُقد الرسم البياني LiteRT للعُقد التي تستدعي العامل).
يتم إنشاء مثيلات من هذا النوع بواسطة استدعاءات
TfLiteRegistrationExternalCreate
ويمكن أن يتعرض للتلف من خلال إجراء
TfLiteRegistrationExternalDelete
يتم تعيين هوية العامل عن طريق المعلمات إلى الدالة الإنشائية
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++) في تنفيذ الأمر مع أسماء الدوال في مقتطف الرمز أعلاه، حيث إن TF وستستخدم واجهة برمجة التطبيقات Lite custom ops 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.
ونظرًا لأن هذه "الطرق" هي واجهة برمجة تطبيقات من Google يتم تنفيذها كمؤشرات للدالة 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()
محدد أكثر من مرة إذا كانت العملية
عدة مرات في الرسم البياني. بالنسبة للعمليات المخصصة، سيتم تخزين المخزن المؤقت للتهيئة
يحتوي على مخزن مؤقت مرن يحدد أسماء المعلمات إلى قيمها. تشير رسالة الأشكال البيانية
يكون المخزن المؤقت فارغًا للعمليات المضمنة لأن المُترجم قد حلل بالفعل
. يجب أن تتم عمليات تنفيذ النواة (Kernel) التي تتطلب حالة تهيئة
هنا ونقل الملكية إلى المتصل. بالنسبة إلى كل مكالمة Init()
، سيكون هناك
استدعاء مطابق إلى Free()
، مما يسمح لعمليات التنفيذ بالتخلص من
المخزن المؤقت الذي قد يكون خصصه في Init()
.
عند تغيير حجم مُدخلات الإدخال، سينتقل المُترجم الفوري إلى
رسم بياني يُعلم تنفيذات التغيير. هذا يعطيه الفرصة
وتغيير حجم المخزن المؤقت الداخلي، والتحقق من صحة أشكال وأنواع الإدخال،
لإعادة حساب أشكال الإخراج. ويتم كل ذلك من خلال الطريقة Prepare()
،
عمليات التنفيذ الوصول إلى حالتها باستخدام
TfLiteOpaqueNodeGetUserData(node)
وأخيرًا، في كل مرة يتم فيها إجراء استنتاج، يجتاز المترجم الفوري الرسم البياني الذي يستدعي
طريقة Invoke()
، وهنا أيضًا تكون الحالة متاحة
TfLiteOpaqueNodeGetUserData(node)
يمكن تنفيذ العمليات المخصصة من خلال تحديد تلك "الطريقة" الدوال، ثم
تحديد دالة تقوم بإرجاع مثيل لـ TfLiteRegistrationExternal
تم إنشاؤها عن طريق استدعاء TfLiteRegistrationExternalCreate
ثم
طرق الإعداد:
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
في هذا المثال.
تسجيل المشغّل في مكتبة النواة
نحتاج الآن إلى تسجيل المشغل في مكتبة kernel. يتم ذلك باستخدام
OpResolver
خلف الكواليس، سيحمّل المترجم الفوري مكتبة من
النواة التي سيتم تعيينها لتنفيذ كل عامل من المشغلات في النموذج.
بينما تحتوي المكتبة الافتراضية على نواة مدمجة فقط، من الممكن
واستبداله أو زيادته بمشغلات عمليات مكتبة مخصصة.
الفئة OpResolver
، التي تترجم رموز عامل التشغيل وأسمائه إلى قيم فعلية
التعليمة البرمجية على النحو التالي:
class OpResolver {
public:
virtual TfLiteRegistration* FindOp(tflite::BuiltinOperator op) const = 0;
virtual TfLiteRegistration* FindOp(const char* op) const = 0;
...
};
تجدر الإشارة إلى أنّه من أجل التوافق مع الأنظمة القديمة، تستخدم هذه الفئة نوع الخرسانة القديمة.
TfLiteRegistration
بدلاً من النوع المعتم TfLiteRegistrationExternal
،
لكن بنية 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.
لاحظ أنه يمكن اتباع عملية مماثلة كما هو موضح أعلاه لدعم مجموعة من
العمليات بدلاً من عامل واحد. ما عليك سوى إضافة أكبر عدد ممكن من عوامل التشغيل AddCustom
.
وفقًا لاحتياجاتك. فضلاً عن ذلك، يسمح لك MutableOpResolver
بإلغاء
تطبيقات العناصر المضمَّنة باستخدام AddBuiltin
.
اختبار منظِّم الجولات وتعريفه
للاطّلاع على تفاصيل نشاطك باستخدام أداة قياس الأداء في LiteRT، يمكنك استخدام
أداة نموذج قياس الأداء
لـ LiteRT. لأغراض الاختبار، يمكنك جعل إصدار محلي من
LiteRT على علم بالعملية المخصّصة من خلال إضافة AddCustom
المناسبة.
(كما هو موضح أعلاه)
register.cc
أفضل الممارسات
عليك توخّي الحذر عند تحسين عمليات تخصيص الذاكرة وإلغاء تخصيصها. جارٍ تخصيص ذكرى في "
Prepare
" أكثر كفاءة منInvoke
، ويخصّص ذاكرة قبل التكرار الحلقي أفضل من كل تكرار. استخدام بيانات التوترات المؤقتة بدلاً من إرباك نفسك (راجع البند 2). استخدام المؤشرات/المراجع بدلاً من ذلك للنسخ قدر الإمكان.في حالة استمرار بنية البيانات خلال العملية بأكملها، فإننا ننصح تخصيص الذاكرة بشكل مسبق باستخدام عوامل تشغيل مؤقتة قد تحتاج إلى استخدام بنية OpData للإشارة إلى فهارس الكثير في الدوال الأخرى. يمكنك الاطّلاع على المثال في قسم النواة (kernel) للالتفاف. في ما يلي نموذج مقتطف الرمز.
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
، أي أنه يجب استخدام وحدات الماكرو هذه قبل أي موارد مخصصة سيتم تسربها.