בניית TensorFlow ModelServer סטנדרטי

מדריך זה מראה לך כיצד להשתמש ברכיבי TensorFlow Serving כדי לבנות את TensorFlow ModelServer הסטנדרטי שמגלה באופן דינמי ומשרת גרסאות חדשות של מודל TensorFlow מאומן. אם אתה רק רוצה להשתמש בשרת הסטנדרטי כדי לשרת את הדגמים שלך, ראה מדריך בסיסי של TensorFlow Serving .

מדריך זה משתמש במודל הפשוט של Softmax Regression שהוצג במדריך TensorFlow לסיווג תמונה בכתב יד (נתוני MNIST). אם אינך יודע מה זה TensorFlow או MNIST, עיין במדריך MNIST למתחילים ב-ML .

הקוד של הדרכה זו מורכב משני חלקים:

  • קובץ Python mnist_saved_model.py שמאמן ומייצא גרסאות מרובות של המודל.

  • קובץ C++ main.cc שהוא TensorFlow ModelServer הסטנדרטי שמגלה דגמים מיוצאים חדשים ומפעיל שירות gRPC להגשתם.

מדריך זה מוביל את המשימות הבאות:

  1. אימון וייצוא של מודל TensorFlow.
  2. נהל ניהול גרסאות של מודל עם TensorFlow Serving ServerCore .
  3. הגדר אצווה באמצעות SavedModelBundleSourceAdapterConfig .
  4. בקשת הגשה עם TensorFlow Serving ServerCore .
  5. הפעל ובדוק את השירות.

לפני שמתחילים, התקן תחילה את Docker

אימון וייצוא מודל TensorFlow

ראשית, אם עדיין לא עשית זאת, שכפל את המאגר הזה למחשב המקומי שלך:

git clone https://github.com/tensorflow/serving.git
cd serving

נקה את ספריית הייצוא אם היא כבר קיימת:

rm -rf /tmp/models

התאמן (עם 100 איטרציות) וייצא את הגרסה הראשונה של הדגם:

tools/run_in_docker.sh python tensorflow_serving/example/mnist_saved_model.py \
  --training_iteration=100 --model_version=1 /tmp/mnist

רכב (עם 2000 איטרציות) וייצא את הגרסה השנייה של הדגם:

tools/run_in_docker.sh python tensorflow_serving/example/mnist_saved_model.py \
  --training_iteration=2000 --model_version=2 /tmp/mnist

כפי שאתה יכול לראות ב- mnist_saved_model.py , ההדרכה והייצוא נעשים באותו אופן שבו הם מתבצעים במדריך הבסיסי של TensorFlow Serving . למטרות הדגמה, אתה מחייג בכוונה את איטרציות האימון לריצה הראשונה ומייצא אותו כ-v1, תוך כדי אימון רגיל לריצה השנייה ומייצא אותו כ-v2 לאותה ספריית אב -- כפי שאנו מצפים שהאחרון ישיג. דיוק סיווג טוב יותר בגלל אימון אינטנסיבי יותר. אתה אמור לראות נתוני אימון עבור כל ריצת אימון בספריית /tmp/mnist שלך:

$ ls /tmp/mnist
1  2

ServerCore

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

TensorFlow Serving Manager עושה בדיוק את זה. הוא מטפל במחזור החיים המלא של דגמי TensorFlow כולל טעינה, הגשה ופריקה שלהם וכן מעברי גרסאות. במדריך זה, תבנה את השרת שלך על גבי TensorFlow Serving ServerCore , אשר עוטף באופן פנימי AspiredVersionsManager .

int main(int argc, char** argv) {
  ...

  ServerCore::Options options;
  options.model_server_config = model_server_config;
  options.servable_state_monitor_creator = &CreateServableStateMonitor;
  options.custom_model_config_loader = &LoadCustomModelConfig;

  ::google::protobuf::Any source_adapter_config;
  SavedModelBundleSourceAdapterConfig
      saved_model_bundle_source_adapter_config;
  source_adapter_config.PackFrom(saved_model_bundle_source_adapter_config);
  (*(*options.platform_config_map.mutable_platform_configs())
      [kTensorFlowModelPlatform].mutable_source_adapter_config()) =
      source_adapter_config;

  std::unique_ptr<ServerCore> core;
  TF_CHECK_OK(ServerCore::Create(options, &core));
  RunServer(port, std::move(core));

  return 0;
}

ServerCore::Create() לוקח פרמטר ServerCore::Options. להלן מספר אפשרויות נפוצות:

  • ModelServerConfig המציין דגמים לטעינה. מודלים מוצהרים או דרך model_config_list , שמצהירה על רשימה סטטית של מודלים, או דרך custom_model_config , המגדירה דרך מותאמת אישית להכריז על רשימת דגמים שעשויים להתעדכן בזמן הריצה.
  • PlatformConfigMap שממפה משם הפלטפורמה (כגון tensorflow ) ל- PlatformConfig , המשמש ליצירת ה- SourceAdapter . SourceAdapter מתאים את StoragePath (הנתיב שבו מתגלה גרסת דגם) ל-Model Loader (טוען את גרסת הדגם מנתיב האחסון ומספק ממשקי מעבר מצב Manager ). אם PlatformConfig מכיל SavedModelBundleSourceAdapterConfig , יווצר SavedModelBundleSourceAdapter , אותו נסביר בהמשך.

SavedModelBundle הוא מרכיב מרכזי בשרת TensorFlow. הוא מייצג מודל TensorFlow שנטען מנתיב נתון ומספק את אותו ממשק Session::Run כמו TensorFlow כדי להפעיל הסקה. SavedModelBundleSourceAdapter מתאים את נתיב האחסון ל- Loader<SavedModelBundle> כך שניתן לנהל את חיי המודל על ידי Manager . לידיעתך, SavedModelBundle הוא היורש של SessionBundle שהוצא משימוש. מומלץ למשתמשים להשתמש SavedModelBundle שכן התמיכה ב- SessionBundle תוסר בקרוב.

עם כל אלה, ServerCore עושה באופן פנימי את הדברים הבאים:

  • יוצר קובץ FileSystemStoragePathSource שמנטר נתיבי ייצוא של מודלים המוצהרים ב- model_config_list .
  • מפעיל SourceAdapter באמצעות PlatformConfigMap עם פלטפורמת המודל המוצהרת ב- model_config_list ומחבר אליו את FileSystemStoragePathSource . בדרך זו, בכל פעם שמתגלה גרסת דגם חדשה מתחת לנתיב הייצוא, SavedModelBundleSourceAdapter מתאים אותה ל- Loader<SavedModelBundle> .
  • יוצר יישום ספציפי של Manager בשם AspiredVersionsManager שמנהל את כל מופעי Loader כאלה שנוצרו על ידי SavedModelBundleSourceAdapter . ServerCore מייצא את ממשק Manager על ידי האצלת הקריאות ל- AspiredVersionsManager .

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

ראוי להזכיר כי TensorFlow Serving תוכנן מאפס להיות מאוד גמיש וניתן להרחבה. אתה יכול לבנות תוספים שונים כדי להתאים אישית את התנהגות המערכת, תוך ניצול רכיבי ליבה גנריים כמו ServerCore ו- AspiredVersionsManager . לדוגמה, אתה יכול לבנות תוסף מקור נתונים שמנטר אחסון בענן במקום אחסון מקומי, או שאתה יכול לבנות תוסף מדיניות גרסה שעושה מעבר גרסה בצורה אחרת -- למעשה, אתה יכול אפילו לבנות תוסף דגם מותאם אישית שמשרת דגמים שאינם TensorFlow. נושאים אלה אינם בהיקף של הדרכה זו. עם זאת, אתה יכול לעיין במקור המותאם אישית ובמדריכים המותאמים להגשה לקבלת מידע נוסף.

אצווה

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

ניתן להפעיל אצווה על ידי אספקת SessionBundleConfig המתאימה בעת יצירת SavedModelBundleSourceAdapter . במקרה זה אנו מגדירים את BatchingParameters עם ערכי ברירת מחדל בערך. ניתן לכוונן אצווה על ידי הגדרת ערכי זמן קצוב מותאם אישית, batch_size וכו'. לפרטים, עיין ב- BatchingParameters .

SessionBundleConfig session_bundle_config;
// Batching config
if (enable_batching) {
  BatchingParameters* batching_parameters =
      session_bundle_config.mutable_batching_parameters();
  batching_parameters->mutable_thread_pool_name()->set_value(
      "model_server_batch_threads");
}
*saved_model_bundle_source_adapter_config.mutable_legacy_config() =
    session_bundle_config;

עם הגעת אצווה מלאה, בקשות הסקת מסקנות מתמזגות באופן פנימי לבקשה אחת גדולה (tensor), ו- tensorflow::Session::Run() מופעל (שממנו מגיע רווח היעילות בפועל ב-GPUs).

מגישים עם מנהל

כפי שהוזכר לעיל, TensorFlow Serving Manager נועד להיות רכיב גנרי שיכול להתמודד עם טעינה, הגשה, פריקה ומעבר גרסאות של מודלים שנוצרו על ידי מערכות למידת מכונה שרירותיות. ממשקי ה-API שלו בנויים סביב מושגי המפתח הבאים:

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

  • גרסה ניתנת להגשה : גירסה ניתנת להגשה, ו- TensorFlow Serving Manager יכול לנהל גרסה אחת או יותר של גרסה ניתנת להגשה. גירסאות מאפשרות לטעון יותר מגרסה אחת של קובץ הגשה בו-זמנית, התומכת בהשקה הדרגתית ובניסויים.

  • זרם ניתן להגשה : זרם שניתן להגשה הוא רצף הגרסאות של זרם שניתן להגשה, עם מספרי גרסאות הולכים וגדלים.

  • דגם : דגם שנלמד על ידי מכונה מיוצג על ידי מוצר אחד או יותר שניתן להגיש. דוגמאות להגשה הן:

    • הפעלה של TensorFlow או עטיפות סביבם, כגון SavedModelBundle .
    • סוגים אחרים של מודלים שנלמדו על ידי מכונה.
    • טבלאות חיפוש אוצר מילים.
    • הטמעת טבלאות חיפוש.

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

כדי לשים את כל אלה בהקשר של הדרכה זו:

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

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

  • AspiredVersionsManager מפקח על זרם הייצוא ומנהל את מחזור החיים של כל רכיבי השרת SavedModelBundle באופן דינמי.

TensorflowPredictImpl::Predict אז רק:

  • מבקש SavedModelBundle מהמנהל (דרך ServerCore).
  • משתמש generic signatures כדי למפות שמות טנסור לוגיים ב- PredictRequest לשמות טנסור אמיתיים ולקשור ערכים לטנזורים.
  • מפעיל מסקנות.

בדוק והפעל את השרת

העתק את הגרסה הראשונה של הייצוא לתיקיה המנוטרת:

mkdir /tmp/monitored
cp -r /tmp/mnist/1 /tmp/monitored

ואז הפעל את השרת:

docker run -p 8500:8500 \
  --mount type=bind,source=/tmp/monitored,target=/models/mnist \
  -t --entrypoint=tensorflow_model_server tensorflow/serving --enable_batching \
  --port=8500 --model_name=mnist --model_base_path=/models/mnist &

השרת ישלח הודעות יומן בכל שנייה אחת שאומרות "גרסה שואפת להגשה...", כלומר הוא מצא את הייצוא ועוקב אחר המשך קיומו.

בוא נריץ את הלקוח עם --concurrency=10 . זה ישלח בקשות במקביל לשרת ובכך יפעיל את היגיון האצווה שלך.

tools/run_in_docker.sh python tensorflow_serving/example/mnist_client.py \
  --num_tests=1000 --server=127.0.0.1:8500 --concurrency=10

מה שמביא לפלט שנראה כך:

...
Inference error rate: 13.1%

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

cp -r /tmp/mnist/2 /tmp/monitored
tools/run_in_docker.sh python tensorflow_serving/example/mnist_client.py \
  --num_tests=1000 --server=127.0.0.1:8500 --concurrency=10

מה שמביא לפלט שנראה כך:

...
Inference error rate: 9.5%

זה מאשר שהשרת שלך מגלה אוטומטית את הגרסה החדשה ומשתמש בה להגשה!