במסמך הזה מתוארת סכימת ניהול הגרסאות של LiteRT. הוספת גרסאות לאופרטורים מאפשרת למפתחים להוסיף פונקציות ופרמטרים חדשים לאופרטורים קיימים. בנוסף, היא מבטיחה את הדברים הבאים:
- תאימות לאחור: ההטמעה החדשה של LiteRT צריכה לטפל בקובץ מודל ישן.
- תאימות קדימה: הטמעה ישנה של LiteRT אמורה לטפל בקובץ מודל חדש שנוצר על ידי גרסה חדשה של הכלי להמרה, כל עוד לא נעשה שימוש בתכונות חדשות.
- זיהוי של אי-תאימות קדימה: אם הטמעה ישנה של LiteRT קוראת מודל חדש שמכיל גרסה חדשה של אופרטור שלא נתמך, היא צריכה לדווח על השגיאה.
דוגמה: הוספת הרחבה לקונבולוציה נפרדת
בהמשך המסמך מוסבר על יצירת גרסאות של אופרטורים ב-TFLite. לשם כך, נראה איך מוסיפים פרמטרים של הרחבה לפעולת הקונבולוציה הנפרדת.
לא צריך ידע בהרחבת אישונים כדי להבין את המסמך הזה. חשוב לדעת:
- יתווספו 2 פרמטרים חדשים של מספרים שלמים:
dilation_width_factorו-dilation_height_factor. - ליבות ישנות של קונבולוציה עומקית שלא תומכות בהרחבה שוות להגדרת גורמי ההרחבה ל-1.
שינוי סכימת FlatBuffer
כדי להוסיף פרמטרים חדשים לפעולה, משנים את טבלת האפשרויות ב-lite/schema/schema.fbs.
לדוגמה, טבלת האפשרויות של convolution עומק נראית כך:
table DepthwiseConv2DOptions {
padding:Padding;
stride_w:int;
stride_h:int;
depth_multiplier:int;
fused_activation_function:ActivationFunctionType;
}
כשמוסיפים פרמטרים חדשים:
- מוסיפים הערות שמציינות אילו פרמטרים נתמכים בכל גרסה.
- כשההטמעה החדשה מקבלת את ערכי ברירת המחדל של פרמטרים חדשים שנוספו, היא אמורה לפעול בדיוק כמו ההטמעה הישנה.
כך תיראה הטבלה אחרי הוספת הפרמטרים החדשים:
table DepthwiseConv2DOptions {
// Parameters for DepthwiseConv version 1 or above.
padding:Padding;
stride_w:int;
stride_h:int;
depth_multiplier:int;
fused_activation_function:ActivationFunctionType;
// Parameters for DepthwiseConv version 2 or above.
dilation_w_factor:int = 1;
dilation_h_factor:int = 1;
}
צריך ליצור מחדש את הקובץ lite/schema/schema_generated.h עבור הסכימה החדשה.
שינוי מבני C והטמעה של ליבת המערכת
ב-LiteRT, ההטמעה של ליבת המערכת מופרדת מההגדרה של FlatBuffer.
הליבות קוראות את הפרמטר ממבני C שמוגדרים ב-lite/c/builtin_op_data.h.
הפרמטר המקורי של קונבולוציה נפרדת לעומק הוא:
typedef struct {
TfLitePadding padding;
int stride_width;
int stride_height;
int depth_multiplier;
TfLiteFusedActivation activation;
} TfLiteDepthwiseConvParams;
בדומה לסכימת FlatBuffer, מוסיפים הערות שמציינות אילו פרמטרים נתמכים החל מאיזו גרסה. התוצאה מוצגת בהמשך:
typedef struct {
// Parameters for DepthwiseConv version 1 or above.
TfLitePadding padding;
int stride_width;
int stride_height;
int depth_multiplier;
TfLiteFusedActivation activation;
// Parameters for DepthwiseConv version 2 or above.
int dilation_width_factor;
int dilation_height_factor;
} TfLiteDepthwiseConvParams;
בנוסף, צריך לשנות את ההטמעה של ליבת המערכת כדי לקרוא את הפרמטרים החדשים שנוספו ממבני הנתונים בשפת C. הפרטים לא מופיעים כאן.
שינוי קוד הקריאה של FlatBuffer
הלוגיקה לקריאת FlatBuffer ויצירת מבנה C נמצאת ב-lite/core/api/flatbuffer_conversions.cc.
מעדכנים את הקובץ כדי לטפל בפרמטרים החדשים, כמו שמוצג בהמשך:
TfLiteStatus ParseDepthwiseConv2D(const Operator* op,
ErrorReporter* error_reporter,
BuiltinDataAllocator* allocator,
void** builtin_data) {
CheckParsePointerParams(op, error_reporter, allocator, builtin_data);
SafeBuiltinDataAllocator safe_allocator(allocator);
std::unique_ptr<TfLiteDepthwiseConvParams,
SafeBuiltinDataAllocator::BuiltinDataDeleter>
params = safe_allocator.Allocate<TfLiteDepthwiseConvParams>();
TF_LITE_ENSURE(error_reporter, params != nullptr);
const DepthwiseConv2DOptions* schema_params =
op->builtin_options_as_DepthwiseConv2DOptions();
if (schema_params != nullptr) {
params->padding = ConvertPadding(schema_params->padding());
params->stride_width = schema_params->stride_w();
params->stride_height = schema_params->stride_h();
params->depth_multiplier = schema_params->depth_multiplier();
params->activation =
ConvertActivation(schema_params->fused_activation_function());
params->dilation_width_factor = schema_params->dilation_w_factor();
params->dilation_height_factor = schema_params->dilation_h_factor();
}
*builtin_data = params.release();
return kTfLiteOk;
}
אין צורך לבדוק כאן את גרסת ה-op. כשיישום חדש קורא קובץ מודל ישן שחסרים בו גורמי הרחבה, הוא ישתמש ב-1 כערך ברירת המחדל, והליבה החדשה תפעל באופן עקבי עם הליבה הישנה.
שינוי רישום של ליבת מערכת ההפעלה
ה-MutableOpResolver (מוגדר ב-lite/mutable_op_resolver.h) מספק כמה פונקציות לרישום של ליבות אופרטורים. גרסת המינימום וגרסת המקסימום הן 1 כברירת מחדל:
void AddBuiltin(tflite::BuiltinOperator op, TfLiteRegistration* registration,
int min_version = 1, int max_version = 1);
void AddCustom(const char* name, TfLiteRegistration* registration,
int min_version = 1, int max_version = 1);
הפעולות המובנות רשומות ב-lite/kernels/register.cc. בדוגמה הזו,
הטמענו ליבת op חדשה שיכולה לטפל בגרסאות DepthwiseConv2D 1 ו-2, ולכן צריך לשנות את השורה הזו:
AddBuiltin(BuiltinOperator_DEPTHWISE_CONV_2D, Register_DEPTHWISE_CONV_2D());
אל:
AddBuiltin(BuiltinOperator_DEPTHWISE_CONV_2D, Register_DEPTHWISE_CONV_2D(),
/* min_version = */ 1,
/* max_version = */ 2);
שינוי גרסת האופרטור של TFLite
השלב הבא הוא לאכלס את הגרסה המינימלית שנדרשת להפעלת האופרטור ב-TFLite. בדוגמה הזו, המשמעות היא:
- מאכלסים את הערך version=1 כשכל גורמי ההרחבה הם 1.
- אחרת, מאכלסים את הערך version=2.
משנים את הפונקציה GetBuiltinOperatorVersion של האופרטור ב-lite/tools/versioning/op_version.cc על ידי הוספת הגרסה החדשה למקרה של DepthwiseConv2D:
case BuiltinOperator_DEPTHWISE_CONV_2D:
auto depthwise_conv_params =
reinterpret_cast<TfLiteDepthwiseConvParams*>(op_sig.builtin_data);
TFLITE_DCHECK(depthwise_conv_params != nullptr);
if (depthwise_conv_params->dilation_width_factor != 1 ||
depthwise_conv_params->dilation_height_factor != 1) {
return 2;
}
return 1;
עדכון המיפוי של גרסאות האופרטור
השלב האחרון הוא להוסיף את פרטי הגרסה החדשה למיפוי הגרסאות של האופרטור. השלב הזה נדרש כי אנחנו צריכים ליצור את גרסת זמן הריצה המינימלית הנדרשת של המודל על סמך מפת הגרסאות הזו.
כדי לעשות את זה, צריך להוסיף רשומה חדשה של מפה ב-lite/tools/versioning/runtime_version.cc.
בדוגמה הזו, צריך להוסיף את הרשומה הבאה אל op_version_map:
{ {BuiltinOperator_DEPTHWISE_CONV_2D, 2}, %CURRENT_RUNTIME_VERSION%}
כאשר %CURRENT_RUNTIME_VERSION% תואם לגרסת זמן הריצה הנוכחית שמוגדרת ב-release_version.h.
הטמעה של הקצאת הרשאות
LiteRT מספק API להעברת הרשאות שמאפשר להעביר פעולות אל קצה העורפי של החומרה. בפונקציה Prepare של הנציג, בודקים אם הגרסה נתמכת בכל צומת בקוד ההרשאה.
const int kMaxVersion = 1;
TfLiteNode* node;
TfLiteRegistration* registration = nullptr;
TF_LITE_ENSURE_STATUS(context->GetNodeAndRegistration(context, node_index, &node, ®istration));
if (registration->version > kMaxVersion) {
// Reject the node if the version isn't supported.
}
הדבר נדרש גם אם ההרשאה תומכת רק בפעולות מגרסה 1, כדי שההרשאה תוכל לזהות חוסר תאימות כשמתקבלת פעולה מגרסה גבוהה יותר.