TensorFlow Lite על GPU

TensorFlow Lite תומך במספר מאיצי חומרה. מסמך זה מתאר כיצד להשתמש ב-GPU backend באמצעות ממשקי API הנציגים של TensorFlow Lite באנדרואיד (דורש OpenCL או OpenGL ES 3.1 ומעלה) ו-iOS (דורש iOS 8 ואילך).

היתרונות של האצת GPU

מְהִירוּת

GPUs מתוכננים לקבל תפוקה גבוהה עבור עומסי עבודה הניתנים להקבלה מאסיבית. לפיכך, הם מתאימים היטב לרשתות עצביות עמוקות, המורכבות ממספר עצום של אופרטורים, כל אחד עובד על טנסור(ים) קלט, שניתן לחלק בקלות לעומסי עבודה קטנים יותר ולבצע במקביל. מקביליות זו גורמת בדרך כלל לאחביון נמוך יותר. בתרחיש הטוב ביותר, ההסקה על ה-GPU עשויה לפעול מהר מספיק כדי להתאים ליישומים בזמן אמת שלא היו אפשריים בעבר.

דיוק

מעבדי GPU עושים את החישוב שלהם עם מספרי נקודה צפה של 16 סיביות או 32 סיביות ו(בניגוד למעבדים) אינם דורשים קוונטיזציה לביצועים מיטביים. אם ירידה בדיוק הפכה את הקוונטיזציה לבלתי נסבלת עבור הדגמים שלך, הפעלת הרשת העצבית שלך על GPU עשויה לבטל את החשש הזה.

חסכון באנרגיה

יתרון נוסף שמגיע עם הסקת GPU הוא יעילות ההספק שלו. GPU מבצע חישובים בצורה מאוד יעילה ומוטבת, צורך פחות חשמל ומייצר פחות חום מאשר אותה משימה המופעלת על מעבד.

פעולות נתמכות

TensorFlow Lite ב-GPU תומך באופציות הבאות בדיוק ציפה של 16 סיביות ו-32 סיביות:

  • ADD
  • AVERAGE_POOL_2D
  • CONCATENATION
  • CONV_2D
  • DEPTHWISE_CONV_2D v1-2
  • EXP
  • FULLY_CONNECTED
  • LOGISTIC
  • LSTM v2 (Basic LSTM only)
  • MAX_POOL_2D
  • MAXIMUM
  • MINIMUM
  • MUL
  • PAD
  • PRELU
  • RELU
  • RELU6
  • RESHAPE
  • RESIZE_BILINEAR v1-3
  • SOFTMAX
  • STRIDED_SLICE
  • SUB
  • TRANSPOSE_CONV

כברירת מחדל, כל הפעולות נתמכות רק בגרסה 1. הפעלת תמיכת הקוונטיזציה הניסיונית מאפשרת את הגרסאות המתאימות; לדוגמה, ADD v2.

שימוש בסיסי

ישנן שתי דרכים להפעיל האצת מודל באנדרואיד, תלוי אם אתה משתמש ב- Android Studio ML Model Binding או TensorFlow Lite Interpreter.

אנדרואיד באמצעות מתורגמן TensorFlow Lite

הוסף את tensorflow-lite-gpu לצד חבילת tensorflow-lite הקיימת בבלוק dependencies הקיים.

dependencies {
    ...
    implementation 'org.tensorflow:tensorflow-lite:2.3.0'
    implementation 'org.tensorflow:tensorflow-lite-gpu:2.3.0'
}

לאחר מכן הפעל את TensorFlow Lite ב-GPU עם TfLiteDelegate . ב-Java, אתה יכול לציין את GpuDelegate דרך Interpreter.Options .

קוטלין

    import org.tensorflow.lite.Interpreter
    import org.tensorflow.lite.gpu.CompatibilityList
    import org.tensorflow.lite.gpu.GpuDelegate

    val compatList = CompatibilityList()

    val options = Interpreter.Options().apply{
        if(compatList.isDelegateSupportedOnThisDevice){
            // if the device has a supported GPU, add the GPU delegate
            val delegateOptions = compatList.bestOptionsForThisDevice
            this.addDelegate(GpuDelegate(delegateOptions))
        } else {
            // if the GPU is not supported, run on 4 threads
            this.setNumThreads(4)
        }
    }

    val interpreter = Interpreter(model, options)

    // Run inference
    writeToInput(input)
    interpreter.run(input, output)
    readFromOutput(output)
      

Java

    import org.tensorflow.lite.Interpreter;
    import org.tensorflow.lite.gpu.CompatibilityList;
    import org.tensorflow.lite.gpu.GpuDelegate;

    // Initialize interpreter with GPU delegate
    Interpreter.Options options = new Interpreter.Options();
    CompatibilityList compatList = CompatibilityList();

    if(compatList.isDelegateSupportedOnThisDevice()){
        // if the device has a supported GPU, add the GPU delegate
        GpuDelegate.Options delegateOptions = compatList.getBestOptionsForThisDevice();
        GpuDelegate gpuDelegate = new GpuDelegate(delegateOptions);
        options.addDelegate(gpuDelegate);
    } else {
        // if the GPU is not supported, run on 4 threads
        options.setNumThreads(4);
    }

    Interpreter interpreter = new Interpreter(model, options);

    // Run inference
    writeToInput(input);
    interpreter.run(input, output);
    readFromOutput(output);
      

אנדרואיד (C/C++)

עבור שימוש ב-C/C++ של TensorFlow Lite GPU ב-Android, ניתן ליצור את נציג ה-GPU עם TfLiteGpuDelegateV2Create() ולהשמיד אותו עם TfLiteGpuDelegateV2Delete() .

// Set up interpreter.
auto model = FlatBufferModel::BuildFromFile(model_path);
if (!model) return false;
ops::builtin::BuiltinOpResolver op_resolver;
std::unique_ptr<Interpreter> interpreter;
InterpreterBuilder(*model, op_resolver)(&interpreter);

// NEW: Prepare GPU delegate.
auto* delegate = TfLiteGpuDelegateV2Create(/*default options=*/nullptr);
if (interpreter->ModifyGraphWithDelegate(delegate) != kTfLiteOk) return false;

// Run inference.
WriteToInputTensor(interpreter->typed_input_tensor<float>(0));
if (interpreter->Invoke() != kTfLiteOk) return false;
ReadFromOutputTensor(interpreter->typed_output_tensor<float>(0));

// NEW: Clean up.
TfLiteGpuDelegateV2Delete(delegate);

עיין ב- TfLiteGpuDelegateOptionsV2 כדי ליצור מופע נציג עם אפשרויות מותאמות אישית. אתה יכול לאתחל את אפשרויות ברירת המחדל עם TfLiteGpuDelegateOptionsV2Default() ולאחר מכן לשנות אותן לפי הצורך.

TFLite GPU עבור Android C/C++ משתמש במערכת הבנייה של Bazel . ניתן לבנות את הנציג, למשל, באמצעות הפקודה הבאה:

bazel build -c opt --config android_arm64 tensorflow/lite/delegates/gpu:delegate                           # for static library
bazel build -c opt --config android_arm64 tensorflow/lite/delegates/gpu:libtensorflowlite_gpu_delegate.so  # for dynamic library

iOS (C++)

כדי להשתמש ב-TensorFlow Lite ב-GPU, קבל את נציג ה-GPU דרך TFLGpuDelegateCreate() ולאחר מכן העבר אותו ל- Interpreter::ModifyGraphWithDelegate() (במקום לקרוא ל-Interpreter::AllocateTensors( Interpreter::AllocateTensors() ).

// Set up interpreter.
auto model = FlatBufferModel::BuildFromFile(model_path);
if (!model) return false;
tflite::ops::builtin::BuiltinOpResolver op_resolver;
std::unique_ptr<Interpreter> interpreter;
InterpreterBuilder(*model, op_resolver)(&interpreter);

// NEW: Prepare GPU delegate.

auto* delegate = TFLGpuDelegateCreate(/*default options=*/nullptr);
if (interpreter->ModifyGraphWithDelegate(delegate) != kTfLiteOk) return false;

// Run inference.
WriteToInputTensor(interpreter->typed_input_tensor<float>(0));
if (interpreter->Invoke() != kTfLiteOk) return false;
ReadFromOutputTensor(interpreter->typed_output_tensor<float>(0));

// Clean up.
TFLGpuDelegateDelete(delegate);

שימוש מתקדם

אפשרויות האצלה עבור iOS

Constructor עבור נציג GPU מקבל struct של אפשרויות. ( Swift API , Objective-C API , C API )

העברת nullptr (C API) או כלום (Objective-C ו-Swift API) לאתחל מגדיר את אפשרויות ברירת המחדל (אשר מוסברות בדוגמה של שימוש בסיסי למעלה).

מָהִיר

    // THIS:
    var options = MetalDelegate.Options()
    options.isPrecisionLossAllowed = false
    options.waitType = .passive
    options.isQuantizationEnabled = true
    let delegate = MetalDelegate(options: options)

    // IS THE SAME AS THIS:
    let delegate = MetalDelegate()
      

Objective-C

    // THIS:
    TFLMetalDelegateOptions* options = [[TFLMetalDelegateOptions alloc] init];
    options.precisionLossAllowed = false;
    options.waitType = TFLMetalDelegateThreadWaitTypePassive;
    options.quantizationEnabled = true;

    TFLMetalDelegate* delegate = [[TFLMetalDelegate alloc] initWithOptions:options];

    // IS THE SAME AS THIS:
    TFLMetalDelegate* delegate = [[TFLMetalDelegate alloc] init];
      

ג

    // THIS:
    const TFLGpuDelegateOptions options = {
      .allow_precision_loss = false,
      .wait_type = TFLGpuDelegateWaitType::TFLGpuDelegateWaitTypePassive,
      .enable_quantization = true,
    };

    TfLiteDelegate* delegate = TFLGpuDelegateCreate(options);

    // IS THE SAME AS THIS:
    TfLiteDelegate* delegate = TFLGpuDelegateCreate(nullptr);
      

למרות שנוח להשתמש ב- nullptr או בבוני ברירת מחדל, אנו ממליצים להגדיר במפורש את האפשרויות, כדי למנוע כל התנהגות בלתי צפויה אם ערכי ברירת המחדל ישתנו בעתיד.

הפעלת דגמים כמותיים על GPU

סעיף זה מסביר כיצד נציג ה-GPU מאיץ מודלים של 8 סיביות. זה כולל את כל טעמי הקוונטיזציה, כולל:

כדי לייעל את הביצועים, השתמש במודלים עם טנסור קלט ופלט של נקודה צפה.

איך זה עובד?

מכיוון שה-GPU העורפי תומך רק בביצוע של נקודה צפה, אנו מפעילים מודלים כמותיים על ידי מתן 'תצוגה של נקודה צפה' של הדגם המקורי. ברמה גבוהה, הדבר כולל את השלבים הבאים:

  • טנסורים קבועים (כגון משקלים/הטיות) מסולקים פעם אחת לתוך זיכרון ה-GPU. זה קורה כאשר הנציג מוחל על המתורגמן TFLite.

  • כניסות ופלטים לתוכנית ה-GPU, אם 8-bit מכומדים, עוברים דה-quantized ו-quantized (בהתאמה) עבור כל הסקה. זה נעשה על ה-CPU באמצעות הגרעינים המותאמים של TFLite.

  • תוכנית ה-GPU שונתה כדי לחקות התנהגות כמותית על ידי הכנסת סימולטורי קוונטיזציה בין פעולות. זה הכרחי עבור מודלים שבהם המבצעים מצפים שהפעלות ילכו אחרי הגבולות שנלמדו במהלך הקוונטיזציה.

ניתן להפעיל תכונה זו באמצעות אפשרויות נציג באופן הבא:

דְמוּי אָדָם

ממשקי API של אנדרואיד תומכים בדגמים כמותיים כברירת מחדל. כדי להשבית, בצע את הפעולות הבאות:

C++ API

TfLiteGpuDelegateOptionsV2 options = TfLiteGpuDelegateOptionsV2Default();
options.experimental_flags = TFLITE_GPU_EXPERIMENTAL_FLAGS_NONE;

auto* delegate = TfLiteGpuDelegateV2Create(options);
if (interpreter->ModifyGraphWithDelegate(delegate) != kTfLiteOk) return false;

Java API

GpuDelegate delegate = new GpuDelegate(new GpuDelegate.Options().setQuantizedModelsAllowed(false));

Interpreter.Options options = (new Interpreter.Options()).addDelegate(delegate);

iOS

ממשקי API של iOS תומכים בדגמים כמותיים כברירת מחדל. כדי להשבית, בצע את הפעולות הבאות:

מָהִיר

    var options = MetalDelegate.Options()
    options.isQuantizationEnabled = false
    let delegate = MetalDelegate(options: options)
      

Objective-C

    TFLMetalDelegateOptions* options = [[TFLMetalDelegateOptions alloc] init];
    options.quantizationEnabled = false;
      

ג

    TFLGpuDelegateOptions options = TFLGpuDelegateOptionsDefault();
    options.enable_quantization = false;

    TfLiteDelegate* delegate = TFLGpuDelegateCreate(options);
      

מאגרי קלט/פלט (iOS, C++ API בלבד)

כדי לבצע חישוב על ה-GPU, הנתונים חייבים להיות זמינים ל-GPU. זה מצריך לעתים קרובות ביצוע העתקת זיכרון. רצוי לא לחצות את גבול זיכרון ה-CPU/GPU במידת האפשר, מכיוון שהדבר עלול לקחת פרק זמן משמעותי. בדרך כלל, מעבר כזה הוא בלתי נמנע, אך במקרים מיוחדים מסוימים ניתן לוותר על זה או אחר.

אם הקלט של הרשת הוא תמונה שכבר נטענה בזיכרון ה-GPU (לדוגמה, מרקם GPU המכיל את הזנת המצלמה) הוא יכול להישאר בזיכרון ה-GPU מבלי להיכנס לזיכרון ה-CPU. באופן דומה, אם הפלט של הרשת הוא בצורה של תמונה הניתנת לעיבוד (לדוגמה, העברת סגנון תמונה ) ניתן להציג אותה ישירות על המסך.

כדי להשיג את הביצועים הטובים ביותר, TensorFlow Lite מאפשר למשתמשים לקרוא ולכתוב ישירות ממאגר החומרה של TensorFlow ולעקוף עותקי זיכרון שניתן להימנע מהם.

בהנחה שקלט התמונה נמצא בזיכרון GPU, תחילה יש להמיר אותו לאובייקט MTLBuffer עבור Metal. אתה יכול לשייך TfLiteTensor ל- MTLBuffer שהוכן על ידי המשתמש עם TFLGpuDelegateBindMetalBufferToTensor() . שים לב ש- TFLGpuDelegateBindMetalBufferToTensor() חייב להיקרא אחרי Interpreter::ModifyGraphWithDelegate() . בנוסף, פלט ההסקה מועתק, כברירת מחדל, מזיכרון GPU לזיכרון CPU. ניתן לבטל התנהגות זו על ידי קריאה Interpreter::SetAllowBufferHandleOutput(true) במהלך האתחול.

#include "tensorflow/lite/delegates/gpu/metal_delegate.h"
#include "tensorflow/lite/delegates/gpu/metal_delegate_internal.h"

// ...

// Prepare GPU delegate.
auto* delegate = TFLGpuDelegateCreate(nullptr);

if (interpreter->ModifyGraphWithDelegate(delegate) != kTfLiteOk) return false;

interpreter->SetAllowBufferHandleOutput(true);  // disable default gpu->cpu copy
if (!TFLGpuDelegateBindMetalBufferToTensor(
        delegate, interpreter->inputs()[0], user_provided_input_buffer)) {
  return false;
}
if (!TFLGpuDelegateBindMetalBufferToTensor(
        delegate, interpreter->outputs()[0], user_provided_output_buffer)) {
  return false;
}

// Run inference.
if (interpreter->Invoke() != kTfLiteOk) return false;

סידור נציגי GPU

שימוש בסריאליזציה של קוד ליבת GPU ונתוני מודל מאתחולים קודמים יכול להפחית את זמן האחזור של האתחול של נציג GPU עד 90%. שיפור זה מושג על ידי החלפת שטח דיסק לחיסכון בזמן. אתה יכול להפעיל תכונה זו עם כמה אפשרויות תצורה, כפי שמוצג בדוגמאות הקוד הבאות:

C++

    TfLiteGpuDelegateOptionsV2 options = TfLiteGpuDelegateOptionsV2Default();
    options.experimental_flags |= TFLITE_GPU_EXPERIMENTAL_FLAGS_ENABLE_SERIALIZATION;
    options.serialization_dir = kTmpDir;
    options.model_token = kModelToken;

    auto* delegate = TfLiteGpuDelegateV2Create(options);
    if (interpreter->ModifyGraphWithDelegate(delegate) != kTfLiteOk) return false;
      

Java

    GpuDelegate delegate = new GpuDelegate(
      new GpuDelegate.Options().setSerializationParams(
        /* serializationDir= */ serializationDir,
        /* modelToken= */ modelToken));

    Interpreter.Options options = (new Interpreter.Options()).addDelegate(delegate);
      

בעת שימוש בתכונת הסידרה, ודא שהקוד שלך תואם לכללי היישום הבאים:

  • אחסן את נתוני ההסדרה בספרייה שאינה נגישה לאפליקציות אחרות. במכשירי אנדרואיד, השתמש ב- getCodeCacheDir() המצביע על מיקום פרטי לאפליקציה הנוכחית.
  • אסימון הדגם חייב להיות ייחודי למכשיר עבור הדגם הספציפי. אתה יכול לחשב אסימון מודל על ידי יצירת טביעת אצבע מנתוני המודל (למשל באמצעות farmhash::Fingerprint64 ).

טיפים וטריקים

  • חלק מהפעולות שהן טריוויאליות במעבד עשויות להיות בעלות גבוהה ב-GPU. מחלקה אחת של פעולה כזו כוללת צורות שונות של פעולות עיצוב מחדש (כולל BATCH_TO_SPACE , SPACE_TO_BATCH , SPACE_TO_DEPTH SPACE_TO_DEPTH דומה). אם פעולות אלו אינן נדרשות (לדוגמה, הן הוכנסו כדי לעזור לארכיטקט הרשת לנמק את המערכת אך אינן משפיעות אחרת על הפלט), כדאי להסיר אותן לביצועים.

  • ב-GPU, נתוני טנזור מחולקים ל-4 ערוצים. לפיכך, חישוב על טנזור של צורה [B, H, W, 5] יבצע בערך אותו דבר על טנזור של צורה [B, H, W, 8] , אך גרוע משמעותית מ- [B, H, W, 4] .

    • לדוגמה, אם חומרת המצלמה תומכת במסגרות תמונה ב-RGBA, הזנת קלט 4 ערוצים זו מהירה משמעותית, מכיוון שניתן להימנע מהעתקת זיכרון (מ-3 ערוצים RGB ל-4 ערוצים RGBX).
  • לקבלת הביצועים הטובים ביותר, אל תהסס להכשיר מחדש את המסווג שלך עם ארכיטקטורת רשת מותאמת לנייד. זהו חלק משמעותי באופטימיזציה להסקת הסקת מסקנות במכשיר.