كيف قمنا بزيادة إنتاجية Tensorflow بنسبة 70٪

أصبح Tensorflow منصة قياسية للتعلم الآلي (ML) ، شعبية في كل من الصناعة والبحث. تم إنشاء العديد من المكتبات والأدوات والأطر المجانية لتدريب وصيانة نماذج ML. يساعد مشروع Tensorflow Serving في الحفاظ على نماذج ML في بيئة الإنتاج الموزعة.

تستخدم خدمة Mux الخاصة بنا خدمة Tensorflow في عدة أجزاء من البنية التحتية ، وقد ناقشنا بالفعل استخدام خدمة Tensorflow في ترميز عناوين الفيديو. سنركز اليوم على الأساليب التي تعمل على تحسين زمن الوصول من خلال تحسين كل من خادم التنبؤ والعميل. عادة ما تكون التنبؤات النموذجية عبارة عن عمليات "عبر الإنترنت" (على المسار الحرج لطلب تقديم طلب) ، وبالتالي ، فإن الأهداف الرئيسية للتحسين هي معالجة كميات كبيرة من الطلبات بأقل تأخير ممكن.

ما هي خدمة Tensorflow؟


يوفر Tensorflow Serving بنية خادم مرنة لنشر وصيانة نماذج ML. بمجرد تدريب النموذج وجاهزة للاستخدام للتنبؤ ، يتطلب Tensorflow Serving تصديره إلى تنسيق متوافق (قابل للتنفيذ).

Servable عبارة عن تجريد مركزي يلف كائنات Tensorflow. على سبيل المثال ، يمكن تمثيل نموذج ككائن واحد أو أكثر من الكائنات القابلة للخدمة. وبالتالي ، Servable هي الكائنات الأساسية التي يستخدمها العميل لتنفيذ العمليات الحسابية. أهمية الحجم القابل للتطبيق: تشغل الموديلات الأصغر مساحة أقل وتستخدم ذاكرة أقل وتحمّل بشكل أسرع. لتنزيل وصيانة واجهة برمجة تطبيقات Predict ، يجب أن تكون الطرز في تنسيق SavedModel.



يجمع Tensorflow Serving بين المكونات الأساسية لإنشاء خادم gRPC / HTTP الذي يخدم العديد من طرز ML (أو عدة إصدارات) ، ويوفر مكونات المراقبة وهيكل مخصص.

Tensorflow خدمة مع عامل الميناء


دعنا نلقي نظرة على المقاييس الأساسية للكمون في التنبؤ بالأداء من خلال إعدادات خدمة Tensorflow القياسية (بدون تحسين وحدة المعالجة المركزية).

أولاً ، قم بتنزيل أحدث صورة من مركز TensorFlow Docker:

docker pull tensorflow/serving:latest 

في هذه المقالة ، تعمل جميع الحاويات على مضيف به أربعة نوى ، 15 جيجابايت ، أوبونتو 16.04.

تصدير نموذج Tensorflow إلى SavedModel


عند تدريب نموذج باستخدام Tensorflow ، يمكن حفظ الإخراج كنقاط تحكم متغيرة (ملفات على القرص). يتم تنفيذ الإخراج مباشرة من خلال استعادة نقاط التحكم في النموذج أو في تنسيق الرسم البياني المجمدة المجمدة (ملف ثنائي).

بالنسبة إلى Tensorflow Serving ، يجب تصدير هذا الرسم البياني المجمد إلى تنسيق SavedModel. تحتوي وثائق Tensorflow على أمثلة لتصدير النماذج المدربة إلى تنسيق SavedModel.

يوفر Tensorflow أيضًا العديد من النماذج الرسمية والبحثية كنقطة انطلاق للتجريب أو البحث أو الإنتاج.

على سبيل المثال ، سوف نستخدم نموذج الشبكة العصبية العميقة (ResNet) لتصنيف مجموعة بيانات ImageNet من 1000 فئة. قم بتنزيل ResNet-50 v2 ، وتحديداً خيار Channels_last (NHWC) في SavedModel : كقاعدة عامة ، يعمل بشكل أفضل على وحدة المعالجة المركزية.

انسخ دليل طراز RestNet إلى الهيكل التالي:

 models/ 1/ saved_model.pb variables/ variables.data-00000-of-00001 variables.index 

تتوقع خدمة Tensorflow بنية دليل مرتبة عدديًا للإصدار. في حالتنا ، يتوافق الدليل 1/ مع طراز الإصدار 1 ، الذي يحتوي على بنية طراز saved_model.pb مع لقطة من أوزان النموذج (المتغيرات).

تحميل ومعالجة SavedModel


يبدأ الأمر التالي في تشغيل خادم طراز Tensorflow Serving في حاوية Docker. لتحميل SavedModel ، يجب عليك تحميل دليل الطراز في دليل الحاوية المتوقع.

 docker run -d -p 9000:8500 \ -v $(pwd)/models:/models/resnet -e MODEL_NAME=resnet \ -t tensorflow/serving:latest 

يوضح التحقق من سجلات الحاوية أن ModelServer قيد التشغيل للتعامل مع طلبات الإخراج لطراز resnet في نقطتي gRPC و HTTP:

 ... I tensorflow_serving/core/loader_harness.cc:86] Successfully loaded servable version {name: resnet version: 1} I tensorflow_serving/model_servers/server.cc:286] Running gRPC ModelServer at 0.0.0.0:8500 ... I tensorflow_serving/model_servers/server.cc:302] Exporting HTTP/REST API at:localhost:8501 ... 

التنبؤ العميل


يحدد Tensorflow Serving مخطط API في تنسيق مخازن بروتوكول (protobufs). يتم حزم تطبيقات عميل GRPC لواجهة برمجة التطبيقات للتنبؤ كحزمة Python tensorflow_serving.apis . سنحتاج إلى حزمة tensorflow حزمة Python tensorflow المرافق.

تثبيت التبعيات لإنشاء عميل بسيط:

 virtualenv .env && source .env/bin/activate && \ pip install numpy grpcio opencv-python tensorflow tensorflow-serving-api 

يتوقع نموذج ResNet-50 v2 مدخلات ResNet-50 v2 العائمة في بنية بيانات التنسيق (NHWC) المنسقة. لذلك ، تتم قراءة صورة الإدخال باستخدام opencv-python وتحميلها في صفيف numpy (ارتفاع × عرض × قنوات) كنوع بيانات float32. يقوم البرنامج النصي أدناه بإنشاء كعب عميل تنبؤ وتحميل بيانات JPEG في صفيف numpy ، ويقوم بتحويلها إلى tensor_proto لتقديم طلب تنبؤ لـ gRPC:

 #!/usr/bin/env python from __future__ import print_function import argparse import numpy as np import time tt = time.time() import cv2 import tensorflow as tf from grpc.beta import implementations from tensorflow_serving.apis import predict_pb2 from tensorflow_serving.apis import prediction_service_pb2 parser = argparse.ArgumentParser(description='incetion grpc client flags.') parser.add_argument('--host', default='0.0.0.0', help='inception serving host') parser.add_argument('--port', default='9000', help='inception serving port') parser.add_argument('--image', default='', help='path to JPEG image file') FLAGS = parser.parse_args() def main(): # create prediction service client stub channel = implementations.insecure_channel(FLAGS.host, int(FLAGS.port)) stub = prediction_service_pb2.beta_create_PredictionService_stub(channel) # create request request = predict_pb2.PredictRequest() request.model_spec.name = 'resnet' request.model_spec.signature_name = 'serving_default' # read image into numpy array img = cv2.imread(FLAGS.image).astype(np.float32) # convert to tensor proto and make request # shape is in NHWC (num_samples x height x width x channels) format tensor = tf.contrib.util.make_tensor_proto(img, shape=[1]+list(img.shape)) request.inputs['input'].CopyFrom(tensor) resp = stub.Predict(request, 30.0) print('total time: {}s'.format(time.time() - tt)) if __name__ == '__main__': main() 

بعد تلقي إدخال JPEG ، سينتج عن عميل عامل النتيجة التالية:

 python tf_serving_client.py --image=images/pupper.jpg total time: 2.56152906418s 

يحتوي الموتر الناتج على تنبؤ في شكل قيمة عددية واحتمال وجود علامات.

 outputs { key: "classes" value { dtype: DT_INT64 tensor_shape { dim { size: 1 } } int64_val: 238 } } outputs { key: "probabilities" ... 

لطلب واحد ، مثل هذا التأخير غير مقبول. لكن ليس من المستغرب أن يتم تصميم Tensorflow Serving binary افتراضيًا لأوسع مجموعة من المعدات لمعظم حالات الاستخدام. ربما لاحظت الأسطر التالية في سجلات حاوية Tensorflow Serving القياسية:

 I external/org_tensorflow/tensorflow/core/platform/cpu_feature_guard.cc:141] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2 FMA 

يشير هذا إلى وجود TensorFlow Serving ثنائي يعمل على منصة وحدة المعالجة المركزية والتي لم يتم تحسينها.

بناء ثنائي الأمثل


وفقًا لوثائق Tensorflow ، يوصى بترجمة Tensorflow من المصدر مع جميع التحسينات المتاحة لوحدة المعالجة المركزية على المضيف حيث سيعمل الثنائي. عند التجميع ، تتيح العلامات الخاصة تنشيط مجموعات تعليمات وحدة المعالجة المركزية لمنصة معينة:

مجموعة التعليماتالأعلام
AVX- تبني = -mavx
AVX2- تبني = - mxx2
FMA- قبطي = -mfma
SSE 4.1- تبني = -msse4.1
SSE 4.2- تبني = -msse4.2
جميع تدعمها المعالج- تبن = مارس = مواطن

استنساخ Tensorflow تقديم إصدار محدد. في حالتنا ، هذا هو 1.13 (الأخير في وقت نشر هذه المقالة):

 USER=$1 TAG=$2 TF_SERVING_VERSION_GIT_BRANCH="r1.13" git clone --branch="$TF_SERVING_VERSION_GIT_BRANCH" https://github.com/tensorflow/serving 

تستخدم صورة dev Tensorflow Serving أداة بازل للبناء. نحن تكوينه لمجموعات محددة من تعليمات وحدة المعالجة المركزية:

 TF_SERVING_BUILD_OPTIONS="--copt=-mavx --copt=-mavx2 --copt=-mfma --copt=-msse4.1 --copt=-msse4.2" 

إذا لم تكن هناك ذاكرة كافية ، --local_resources=2048,.5,1.0 استهلاك الذاكرة أثناء عملية --local_resources=2048,.5,1.0 باستخدام العلامة --local_resources=2048,.5,1.0 . للحصول على معلومات حول العلامات ، راجع مساعدة Tensorflow Serving and Docker ، وكذلك وثائق Bazel .

قم بإنشاء صورة عمل بناءً على الصورة الحالية:

 #!/bin/bash USER=$1 TAG=$2 TF_SERVING_VERSION_GIT_BRANCH="r1.13" git clone --branch="${TF_SERVING_VERSION_GIT_BRANCH}" https://github.com/tensorflow/serving TF_SERVING_BUILD_OPTIONS="--copt=-mavx --copt=-mavx2 --copt=-mfma --copt=-msse4.1 --copt=-msse4.2" cd serving && \ docker build --pull -t $USER/tensorflow-serving-devel:$TAG \ --build-arg TF_SERVING_VERSION_GIT_BRANCH="${TF_SERVING_VERSION_GIT_BRANCH}" \ --build-arg TF_SERVING_BUILD_OPTIONS="${TF_SERVING_BUILD_OPTIONS}" \ -f tensorflow_serving/tools/docker/Dockerfile.devel . cd serving && \ docker build -t $USER/tensorflow-serving:$TAG \ --build-arg TF_SERVING_BUILD_IMAGE=$USER/tensorflow-serving-devel:$TAG \ -f tensorflow_serving/tools/docker/Dockerfile . 

تم تكوين ModelServer باستخدام إشارات TensorFlow لدعم التزامن. الخيارات التالية تكوين تجمعات مؤشر ترابط اثنين لعملية متوازية:

 intra_op_parallelism_threads 

  • يتحكم في الحد الأقصى لعدد مؤشرات الترابط للتنفيذ المتوازي لعملية واحدة ؛
  • تستخدم لموازنة العمليات التي لها عمليات فرعية مستقلة في الطبيعة.

 inter_op_parallelism_threads 

  • يتحكم في الحد الأقصى لعدد مؤشرات الترابط للتنفيذ المتوازي للعمليات المستقلة ؛
  • عمليات Tensorflow Graph ، والتي تكون مستقلة عن بعضها البعض ، وبالتالي ، يمكن تنفيذها في خيوط مختلفة.

افتراضيًا ، يتم تعيين المعلمتين على 0 . هذا يعني أن النظام نفسه يختار الرقم المناسب ، والذي يعني غالبًا خيط واحد لكل نواة. ومع ذلك ، يمكن تغيير المعلمة يدوياً من أجل التزامن متعدد النواة.

ثم قم بتشغيل الحاوية Serving بنفس طريقة الحاوية السابقة ، هذه المرة مع صورة Docker تم تجميعها من المصادر ومع إشارات Tensorflow المحسنة لمعالج محدد:

 docker run -d -p 9000:8500 \ -v $(pwd)/models:/models/resnet -e MODEL_NAME=resnet \ -t $USER/tensorflow-serving:$TAG \ --tensorflow_intra_op_parallelism=4 \ --tensorflow_inter_op_parallelism=4 

سجلات الحاويات يجب أن لا تظهر التحذيرات حول وحدة المعالجة المركزية غير محددة. بدون تغيير الكود بناءً على طلب التوقع نفسه ، يتم تقليل التأخير بحوالي 35.8٪:

 python tf_serving_client.py --image=images/pupper.jpg total time: 1.64234706879s 

زيادة السرعة في التنبؤ العميل


هل لا يزال من الممكن تسريع؟ لقد قمنا بتحسين جانب الخادم لوحدة المعالجة المركزية الخاصة بنا ، ولكن لا يزال تأخير أكثر من ثانية كبيرًا جدًا.

حدث أن تحميل مكتبات tensorflow و tensorflow يُسهم بشكل كبير في التأخير. يضيف كل مكالمة غير ضرورية إلى tf.contrib.util.make_tensor_proto أيضًا تقسيم ثانية.

قد تسأل: "ألا نحتاج إلى حزم TensorFlow Python لتقديم طلبات التنبؤ بالفعل إلى خادم Tensorflow؟" في الواقع ، ليست هناك حاجة حقيقية tensorflow_serving و tensorflow .

كما ذكر سابقًا ، يتم تعريف واجهات برمجة التطبيقات للتنبؤ Tensorflow على أنها مخازن مؤقتة. لذلك ، يمكن استبدال اثنين من التبعيات الخارجية tensorflow و tensorflow المقابلة - ومن ثم لن تحتاج إلى سحب مكتبة Tensorflow (الثقيلة) بالكامل على العميل.

أولاً ، تخلص من tensorflow و tensorflow_serving وأضف حزمة grpcio-tools .

 pip uninstall tensorflow tensorflow-serving-api && \ pip install grpcio-tools==1.0.0 

قم tensorflow/tensorflow و tensorflow/serving المستودعات ونسخ ملفات protobuf التالية إلى مشروع العميل:

 tensorflow/serving/ tensorflow_serving/apis/model.proto tensorflow_serving/apis/predict.proto tensorflow_serving/apis/prediction_service.proto tensorflow/tensorflow/ tensorflow/core/framework/resource_handle.proto tensorflow/core/framework/tensor_shape.proto tensorflow/core/framework/tensor.proto tensorflow/core/framework/types.proto 

انسخ ملفات protobuf هذه إلى protos/ directory مع الحفاظ على المسارات الأصلية:

 protos/ tensorflow_serving/ apis/ *.proto tensorflow/ core/ framework/ *.proto 

من أجل البساطة ، يمكن تبسيط prediction_service.proto لتنفيذ Predict RPC فقط حتى لا يتم تنزيل التبعيات المتداخلة لـ RPC الأخرى المحددة في الخدمة. فيما يلي مثال على prediction_service. المبسطة.

إنشاء تطبيقات PyRon gRPC باستخدام grpcio.tools.protoc :

 PROTOC_OUT=protos/ PROTOS=$(find . | grep "\.proto$") for p in $PROTOS; do python -m grpc.tools.protoc -I . --python_out=$PROTOC_OUT --grpc_python_out=$PROTOC_OUT $p done 

الآن يمكن إزالة وحدة tensorflow_serving بأكملها:

 from tensorflow_serving.apis import predict_pb2 from tensorflow_serving.apis import prediction_service_pb2 

... واستبدالها باستخدام protobuffers التي تم إنشاؤها من protos/tensorflow_serving/apis :

 from protos.tensorflow_serving.apis import predict_pb2 from protos.tensorflow_serving.apis import prediction_service_pb2 

يتم استيراد مكتبة Tensorflow لاستخدام وظيفة المساعد make_tensor_proto ، وهي ضرورية للالتفاف على كائن python / numpy ككائن TensorProto.

وبالتالي ، يمكننا استبدال جزء التبعية والرمز التالي:

 import tensorflow as tf ... tensor = tf.contrib.util.make_tensor_proto(features) request.inputs['inputs'].CopyFrom(tensor) 

استيراد protobuffers وبناء كائن TensorProto:

 from protos.tensorflow.core.framework import tensor_pb2 from protos.tensorflow.core.framework import tensor_shape_pb2 from protos.tensorflow.core.framework import types_pb2 ... # ensure NHWC shape and build tensor proto tensor_shape = [1]+list(img.shape) dims = [tensor_shape_pb2.TensorShapeProto.Dim(size=dim) for dim in tensor_shape] tensor_shape = tensor_shape_pb2.TensorShapeProto(dim=dims) tensor = tensor_pb2.TensorProto( dtype=types_pb2.DT_FLOAT, tensor_shape=tensor_shape, float_val=list(img.reshape(-1))) request.inputs['inputs'].CopyFrom(tensor) 

النص الكامل لبيثون هنا . قم بتشغيل عميل بدء تشغيل محدث يقدم طلب تنبؤ لخدمة Tensorflow المحسنة:

 python tf_inception_grpc_client.py --image=images/pupper.jpg total time: 0.58314920859s 

يوضح المخطط التالي وقت التنفيذ المتوقع في الإصدار الأمثل من Tensorflow Serving مقارنةً بالمعيار الذي يزيد عن 10 مرات:



انخفض متوسط ​​التأخير بنحو 3.38 مرة.

عرض النطاق الترددي الأمثل


يمكن تكوين خدمة Tensorflow للتعامل مع كميات كبيرة من البيانات. عادةً ما يتم إجراء تحسين عرض النطاق الترددي لمعالجة الدُفعات "المستقلة" ، حيث لا تكون حدود زمن الوصول الضيق متطلبًا صارمًا.

خادم الجانب تجهيز الدفعات


كما هو مذكور في الوثائق ، يتم دعم معالجة الدُفعات من جانب الخادم أصلاً في خدمة Tensorflow.

يتم تحديد المفاضلة بين الكمون والإنتاجية بواسطة معلمات معالجة الدُفعات. إنها تسمح لك بتحقيق الحد الأقصى من الإنتاجية التي تكون مسرعات الأجهزة قادرة عليها.

لتمكين التعبئة ، اضبط - --batching_parameters_file و - --batching_parameters_file . يتم تعيين المعلمات وفقًا SessionBundleConfig . بالنسبة للأنظمة على وحدة المعالجة المركزية ، قم بتعيين num_batch_threads على عدد النوى المتاحة. ل GPU ، راجع المعلمات المناسبة هنا .

بعد ملء الحزمة بأكملها على جانب الخادم ، يتم دمج طلبات الإصدار في طلب واحد كبير (tensor) ، وإرسالها إلى جلسة Tensorflow مع طلب مشترك. في هذه الحالة ، تشارك التوازي وحدة المعالجة المركزية / GPU حقا.

بعض الاستخدامات الشائعة لمعالجة الدفعات Tensorflow:

  • استخدام طلبات العميل غير المتزامنة لتعبئة الحزم من جانب الخادم
  • معالجة دفعية أسرع عن طريق نقل مكونات الرسم البياني النموذجي إلى وحدة المعالجة المركزية / وحدة معالجة الرسومات
  • خدمة الطلبات من نماذج متعددة من خادم واحد
  • يوصى بشدة بمعالجة الدُفعات للمعالجة "غير المتصلة" لعدد كبير من الطلبات

العميل دفعة تجهيز تجهيز


مجموعات المعالجة الدفعية من جانب العميل بتجميع العديد من الطلبات الواردة في واحد.

نظرًا لأن نموذج ResNet ينتظر الإدخال بتنسيق NHWC (البعد الأول هو عدد المدخلات) ، يمكننا دمج عدة صور إدخال في طلب RPC واحد:

 ... batch = [] for jpeg in os.listdir(FLAGS.images_path): path = os.path.join(FLAGS.images_path, jpeg) img = cv2.imread(path).astype(np.float32) batch.append(img) ... batch_np = np.array(batch).astype(np.float32) dims = [tensor_shape_pb2.TensorShapeProto.Dim(size=dim) for dim in batch_np.shape] t_shape = tensor_shape_pb2.TensorShapeProto(dim=dims) tensor = tensor_pb2.TensorProto( dtype=types_pb2.DT_FLOAT, tensor_shape=t_shape, float_val=list(batched_np.reshape(-1))) request.inputs['inputs'].CopyFrom(tensor) 

بالنسبة لحزمة من الصور N ، سيحتوي موتر الإخراج في الاستجابة على نتائج التنبؤ لنفس عدد المدخلات. في حالتنا ، N = 2:

 outputs { key: "classes" value { dtype: DT_INT64 tensor_shape { dim { size: 2 } } int64_val: 238 int64_val: 121 } } ... 

تسريع الأجهزة


بضع كلمات عن GPUs.

تستخدم عملية التعلم بشكل طبيعي التوازي على وحدة معالجة الرسومات ، حيث أن بناء الشبكات العصبية العميقة يتطلب حسابات ضخمة لتحقيق الحل الأمثل.

لكن بالنسبة لنتائج النتائج ، فإن التوازي ليس واضحًا للغاية. غالبًا ما يمكنك تسريع إخراج الشبكة العصبية إلى وحدة معالجة الرسومات ، ولكن عليك اختيار المعدات واختبارها بعناية ، وإجراء تحليل تقني واقتصادي متعمق. تعد موازنة الأجهزة أكثر قيمة في معالجة الدُفعة من الاستنتاجات "المستقلة" (وحدات التخزين الضخمة).

قبل الانتقال إلى وحدة معالجة الرسومات ، فكر في متطلبات العمل من خلال تحليل دقيق للتكاليف (النقدية والتشغيلية والفنية) لتحقيق أكبر فائدة (تقليل زمن الوصول ، وارتفاع الإنتاجية).

Source: https://habr.com/ru/post/ar445928/


All Articles