מאחר שספריית המפעילים המובנית של LiteRT תומכת רק במספר מוגבל של יש מספר אופרטורים של TensorFlow, לא כל מודל ניתן להמרה. לפרטים, התייחסות לתאימות מפעילים.
כדי לאפשר המרה, המשתמשים יכולים לספק יישום מותאם אישית משלהם של אופרטור TensorFlow ב-LiteRT שלא נתמך, נקרא אופרטור מותאם אישית. אם במקום זאת, אתם רוצים לשלב סדרה של תכונות לא נתמכות (או נתמכות) אופרטורים של TensorFlow לאופרטור יחיד מותאם אישית שעבר אופטימיזציה, כמו fיזוז אופרטור.
השימוש באופרטורים מותאמים אישית כולל ארבעה שלבים.
יצירת מודל TensorFlow. ודא שהפריטים שנשמרו Model (או Graph Def) מתייחס לאופרטור LiteRT שקיבל את השם הנכון.
המרה למודל LiteRT. הקפידו להגדיר את המאפיין הנכון של ממיר LiteRT כדי להמיר את המודל בהצלחה.
יוצרים ורושמים את האופרטור. הזה היא כדי שזמן הריצה של LiteRT ילמד איך למפות את האופרטור בפרמטרים של התרשים לקוד C/C++ להפעלה.
בודקים את מפעיל השירות ומגדירים לו פרופיל. אם אם אתם רוצים לבדוק רק את האופרטור המותאם אישית, עדיף ליצור מודל רק את האופרטור המותאם אישית ולהשתמש benchmark_model בתוכנית.
בואו נראה דוגמה מקצה לקצה להרצת מודל עם מודל
אופרטור tf.atan
(בשם Atan
, מתייחס למאמר יצירת מודל TensorFlow).
נתמך ב-TensorFlow אבל לא נתמך ב-LiteRT.
האופרטור TensorFlow Text הוא דוגמה לאופרטור בהתאמה אישית. לצפייה מדריך בנושא המרת טקסט 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 מוגדרים באמצעות API פשוט pure-C
מורכב מסוג אטום (TfLiteRegistrationExternal
) ופונקציות קשורות.
TfLiteRegistrationExternal
הוא סוג אטום:
typedef struct TfLiteRegistrationExternal TfLiteRegistrationExternal;
TfLiteRegistrationExternal
שומר את הזהות וההטמעה של המפעיל.
(שימו לב שהאופרטור ייחודי מהאופרנדים שלו, השמורים בפונקציות
צמתים של תרשים LiteRT לצמתים שקוראים לאופרטור.)
מופעים מהסוג הזה נוצרים באמצעות קריאות ל-
TfLiteRegistrationExternalCreate
וניתן להשמיד אותו באמצעות התקשרות
TfLiteRegistrationExternalDelete
.
זהות האופרטור מוגדרת באמצעות הפרמטרים לפונקציית ה-constructor
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.
);
הטמעת האופרטורים יכולה להגדיר "methods" עם החתימות הבאות.
כל השיטות האלה הן אופציונליות, אבל כדי שהאופרטור יפעל בהצלחה
והטמעת האופרטור צריכה להגדיר ולהגדיר (באמצעות המרכיב
פונקציות) לפחות את 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 של הפעלה מותאמת אישית של 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 ... }
C
void* MyCustomOpInit(TfLiteOpaqueContext* context, const char* buffer, size_t length) { ... } // ... plus definitions of MyCustomOpFree, MyCustomOpPrepare, and // MyCustomOpInvoke.
מכיוון שמדובר ב-C API, ה-"methods" האלה מיושמות כמצביעים של פונקציית C
סוג TfLiteRegistrationExternal
, שמוגדר על ידי העברת הכתובות של
את פונקציות ההטמעה לפונקציות המגדיר המתאימות
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()
.
בכל פעם שמשנים את הגודל של רכיבי הקלט, המתרגם יעבור
תרשים שמיידע את ההטמעות של השינוי. כך הם יכולים
לשנות את הגודל של המאגר הפנימי שלהם, לבדוק את התקינות של צורות וסוגי קלט,
חשבו מחדש צורות פלט. כל זה מתבצע באמצעות ה-method Prepare()
.
יכולים לגשת למצב שלהם באמצעות
TfLiteOpaqueNodeGetUserData(node)
.
בסוף, בכל פעם שההסקה מופעלת, המתורגמן חוצה את הקריאה בגרף
השיטה Invoke()
, וגם כאן המצב זמין
TfLiteOpaqueNodeGetUserData(node)
.
ניתן ליישם פעולות מותאמות אישית על ידי הגדרת ה-method פונקציות שונות,
הגדרת פונקציה שמחזירה מופע של 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
) נמשך
על הרישום של מערכות מובנות, צריך לאסוף פעולות מותאמות אישית
ספריות מותאמות אישית נפרדות.
הגדרת הליבה (kernel) של זמן ריצה של 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
. מאחורי הקלעים, המתורגמן יטען ספרייה של
ליבות שיוקצו להפעלת כל אחד מהאופרטורים במודל.
ספריית ברירת המחדל מכילה רק ליבה (kernel) מובנית, אבל ניתן
להחליף או להגדיל אותם באופרטורים מותאמים אישית של הפעלת ספריות.
המחלקה 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 משלכם בקוד ה-JN הזה. באופן דומה, אם רוצים להגדיר את האופרטורים האלה שזמינים ב-Python, אפשר להציב את הרישומים שלכם ב-Python wrapper.
שימו לב: אפשר לבצע תהליך דומה כמו שמתואר למעלה כדי לתמוך בקבוצה
במקום אופרטור יחיד. פשוט מוסיפים כמה שיותר אופרטורים של AddCustom
לפי הצורך. בנוסף, אפשר להשתמש ב-MutableOpResolver
כדי לשנות מברירת המחדל
של הטמעות מובנות באמצעות AddBuiltin
.
בדיקה ופרופיל של המפעיל
כדי ליצור פרופיל של התפעול באמצעות כלי ההשוואה לשוק LiteRT, אפשר להשתמש
כלי לבניית מודלים של בנצ'מרק
ל-LiteRT. למטרות בדיקה, אפשר ליצור את ה-build המקומי של
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
, כלומר, יש להשתמש בפקודות המאקרו האלה לפני את כל המשאבים שיוקצו לדליפה.