Bagaimana Kami Meningkatkan Tensorflow Melayani Produktivitas hingga 70%

Tensorflow telah menjadi platform standar untuk pembelajaran mesin (ML), populer baik di industri maupun dalam penelitian. Banyak perpustakaan, alat, dan kerangka kerja gratis telah dibuat untuk melatih dan memelihara model ML. Proyek Tensorflow Melayani membantu mempertahankan model ML dalam lingkungan produksi terdistribusi.

Layanan Mux kami menggunakan Tensorflow Serving di beberapa bagian infrastruktur, kami telah membahas penggunaan Tensorflow Serving dalam mengkodekan judul video. Hari ini kami akan fokus pada metode yang meningkatkan latensi dengan mengoptimalkan server perkiraan dan klien. Perkiraan model biasanya adalah operasi "online" (di jalur kritis meminta aplikasi), oleh karena itu, tujuan utama optimasi adalah untuk memproses volume besar permintaan dengan keterlambatan serendah mungkin.

Apa yang dimaksud dengan Melayani Tensorflow?


Tensorflow Serving menyediakan arsitektur server yang fleksibel untuk menggunakan dan memelihara model ML. Setelah model dilatih dan siap digunakan untuk peramalan, Penyajian Tensorflow mengharuskan mengekspornya ke format yang kompatibel (dapat ditayangkan).

Servable adalah abstraksi pusat yang membungkus objek Tensorflow. Sebagai contoh, suatu model dapat direpresentasikan sebagai satu atau lebih objek yang dapat Servable. Dengan demikian, Servable adalah objek dasar yang digunakan klien untuk melakukan perhitungan. Masalah ukuran yang dapat diservis: model yang lebih kecil membutuhkan lebih sedikit ruang, menggunakan lebih sedikit memori dan memuat lebih cepat. Untuk mengunduh dan memelihara menggunakan Predict API, model harus dalam format SavedModel.



Tensorflow Serving menggabungkan komponen dasar untuk membuat server gRPC / HTTP yang melayani beberapa model ML (atau beberapa versi), menyediakan komponen pemantauan dan arsitektur khusus.

Tensorflow Melayani dengan Docker


Mari kita lihat metrik dasar latensi dalam memperkirakan kinerja dengan pengaturan Tensorflow Serving standar (tanpa optimasi CPU).

Pertama, unduh gambar terbaru dari hub TensorFlow Docker:

docker pull tensorflow/serving:latest 

Dalam artikel ini, semua kontainer berjalan di host dengan empat core, 15 GB, Ubuntu 16.04.

Ekspor Model Tensorflow ke SavedModel


Ketika model dilatih menggunakan Tensorflow, output dapat disimpan sebagai titik kontrol variabel (file pada disk). Output dilakukan secara langsung dengan mengembalikan titik kontrol model atau dalam format grafik beku beku (file biner).

Untuk Melayani Tensorflow, grafik beku ini perlu diekspor ke format SavedModel. Dokumentasi Tensorflow berisi contoh mengekspor model yang terlatih ke format SavedModel.

Tensorflow juga menyediakan banyak model resmi dan penelitian sebagai titik awal untuk eksperimen, penelitian, atau produksi.

Sebagai contoh, kita akan menggunakan model deep residual neural network (ResNet) untuk mengklasifikasikan dataset ImageNet dari 1000 kelas. Unduh model ResNet-50 v2 yang telah ResNet-50 v2 , khususnya pilihan Channels_last (NHWC) di SavedModel : sebagai aturan, ini berfungsi lebih baik pada CPU.

Salin direktori model RestNet ke dalam struktur berikut:

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

Tensorflow Serving mengharapkan struktur direktori yang dipesan secara numerik untuk versi. Dalam kasus kami, direktori 1/ sesuai dengan model versi 1, yang berisi arsitektur model saved_model.pb dengan snapshot bobot model (variabel).

Memuat dan memproses SavedModel


Perintah berikut memulai server model Melayani Tensorflow dalam wadah Docker. Untuk memuat SavedModel, Anda harus memasang direktori model di direktori kontainer yang diharapkan.

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

Memeriksa log kontainer menunjukkan bahwa ModelServer sudah aktif dan berjalan untuk menangani permintaan output untuk model resnet di titik akhir gRPC dan 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 ... 

Memprakirakan Klien


Tensorflow Serving mendefinisikan skema API dalam format buffer protokol (protobufs). Implementasi klien GRPC untuk API peramalan dikemas sebagai paket Python tensorflow_serving.apis . Kita akan membutuhkan tensorflow paket Python lain untuk fungsi utilitas.

Instal dependensi untuk membuat klien sederhana:

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

Model ResNet-50 v2 mengharapkan input tensor floating point dalam struktur data diformat kanal_last (NHWC). Oleh karena itu, gambar input dibaca menggunakan opencv-python dan dimuat ke array numpy (tinggi × lebar × saluran) sebagai tipe data float32. Skrip di bawah ini membuat rintisan klien prediksi dan memuat data JPEG ke dalam array numpy, mengubahnya menjadi tensor_proto untuk membuat permintaan perkiraan 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() 

Setelah menerima input JPEG, klien yang bekerja akan menghasilkan hasil berikut:

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

Tensor yang dihasilkan berisi perkiraan dalam bentuk nilai integer dan probabilitas tanda.

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

Untuk satu permintaan, penundaan seperti itu tidak dapat diterima. Tapi tidak ada yang mengejutkan: biner Tensorflow Serving secara default dirancang untuk rentang peralatan terluas untuk sebagian besar kasus penggunaan. Anda mungkin memperhatikan baris-baris berikut dalam log dari wadah Penyajian Tensorflow standar:

 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 

Ini menunjukkan biner TensorFlow Melayani yang berjalan pada platform CPU yang belum dioptimalkan.

Membangun biner yang dioptimalkan


Menurut dokumentasi Tensorflow, direkomendasikan untuk mengkompilasi Tensorflow dari sumber dengan semua optimisasi yang tersedia untuk CPU pada host di mana biner akan bekerja. Saat perakitan, flag khusus memungkinkan aktivasi set instruksi CPU untuk platform tertentu:

Set instruksiBendera
AVX--copt = -mavx
AVX2--copt = -mavx2
Fma--copt = -mfma
SSE 4.1--copt = -msse4.1
SSE 4.2--copt = -msse4.2
Semua didukung oleh prosesor--copt = -march = asli

Clone a Tensorflow Melayani versi tertentu. Dalam kasus kami, ini adalah 1,13 (yang terakhir pada saat publikasi artikel ini):

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

Gambar dev Tensorflow Melayani menggunakan alat Basel untuk membangun. Kami mengkonfigurasinya untuk set instruksi CPU tertentu:

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

Jika tidak ada cukup memori, batasi konsumsi memori selama proses build dengan flag --local_resources=2048,.5,1.0 . Untuk informasi tentang bendera, lihat bantuan Tensorflow Serving dan Docker , serta dokumentasi Bazel .

Buat gambar yang berfungsi berdasarkan yang sudah ada:

 #!/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 dikonfigurasi menggunakan bendera TensorFlow untuk mendukung konkurensi. Opsi berikut mengonfigurasi dua kumpulan utas untuk operasi paralel:

 intra_op_parallelism_threads 

  • mengontrol jumlah maksimum utas untuk eksekusi paralel satu operasi;
  • digunakan untuk memparalelkan operasi yang memiliki sub-operasi yang bersifat independen.

 inter_op_parallelism_threads 

  • mengontrol jumlah utas maksimum untuk pelaksanaan paralel operasi independen;
  • Operasi Tensorflow Graph, yang independen satu sama lain dan, oleh karena itu, dapat dilakukan di utas yang berbeda.

Secara default, kedua parameter diatur ke 0 . Ini berarti bahwa sistem itu sendiri memilih nomor yang sesuai, yang paling sering berarti satu utas per inti. Namun, parameter dapat diubah secara manual untuk konkurensi multi-core.

Kemudian jalankan wadah Penyajian dengan cara yang sama seperti yang sebelumnya, kali ini dengan gambar Docker dikompilasi dari sumber dan dengan bendera optimisasi Tensorflow untuk prosesor tertentu:

 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 

Log kontainer seharusnya tidak lagi menampilkan peringatan tentang CPU yang tidak ditentukan. Tanpa mengubah kode pada permintaan perkiraan yang sama, penundaan berkurang sekitar 35,8%:

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

Meningkatkan kecepatan dalam perkiraan klien


Apakah masih mungkin untuk mempercepat? Kami telah mengoptimalkan sisi server untuk CPU kami, tetapi penundaan lebih dari 1 detik tampaknya masih terlalu besar.

Kebetulan memuat perpustakaan tensorflow dan tensorflow memberikan kontribusi yang signifikan terhadap keterlambatan tersebut. Setiap panggilan yang tidak perlu ke tf.contrib.util.make_tensor_proto juga menambahkan sepersekian detik.

Anda mungkin bertanya: "Bukankah kita perlu paket Python TensorFlow untuk benar-benar membuat permintaan prediksi ke server Tensorflow?" Bahkan, tidak ada kebutuhan nyata untuk paket tensorflow dan tensorflow .

Seperti disebutkan sebelumnya, API prediksi Tensorflow didefinisikan sebagai proto-buffer. Oleh karena itu, dua dependensi eksternal dapat diganti dengan tensorflow_serving dan tensorflow sesuai - dan kemudian Anda tidak perlu menarik seluruh perpustakaan Tensorflow (berat) pada klien.

Pertama, singkirkan tensorflow_serving dan tensorflow_serving dan tambahkan paket grpcio-tools .

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

Kloning tensorflow/tensorflow dan tensorflow/serving dan salin file protobuf berikut ke proyek klien:

 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 

Salin file-file protobuf ini ke direktori protos/ dengan path asli dipertahankan:

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

Untuk kesederhanaan, prediction_service.proto dapat disederhanakan untuk mengimplementasikan hanya Predict RPC agar tidak mengunduh dependensi bersarang dari RPC lain yang ditentukan dalam layanan. Ini adalah contoh dari prediction_service. disederhanakan.

Buat implementasi gRPC Python menggunakan 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 

Sekarang seluruh modul tensorflow_serving dapat dihapus:

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

... dan ganti dengan protobuffer yang dihasilkan dari protos/tensorflow_serving/apis :

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

Pustaka Tensorflow diimpor untuk menggunakan fungsi helper make_tensor_proto , yang diperlukan untuk membungkus objek python / numpy sebagai objek TensorProto.

Dengan demikian, kita dapat mengganti dependensi dan fragmen kode berikut:

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

mengimpor protobuffers dan membangun objek 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) 

Skrip Python lengkap ada di sini . Jalankan klien pemula yang diperbarui yang membuat permintaan prediksi untuk Penyajian Tensorflow yang dioptimalkan:

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

Diagram berikut menunjukkan perkiraan waktu eksekusi dalam versi Tensorflow Serving yang dioptimalkan dibandingkan dengan standar, lebih dari 10 kali berjalan:



Penundaan rata-rata menurun sekitar 3,38 kali.

Optimasi Bandwidth


Tensorflow Serving dapat dikonfigurasi untuk menangani sejumlah besar data. Optimalisasi bandwidth biasanya dilakukan untuk pemrosesan batch "berdiri sendiri", di mana batas latensi yang ketat bukanlah persyaratan yang ketat.

Pemrosesan Gelombang Sisi Server


Seperti yang dinyatakan dalam dokumentasi , pemrosesan batch sisi server didukung secara asli di Tensorflow Serving.

Pertukaran antara latensi dan throughput ditentukan oleh parameter pemrosesan batch. Mereka memungkinkan Anda untuk mencapai throughput maksimum yang mampu akselerator perangkat keras.

Untuk mengaktifkan pengemasan, atur --batching_parameters_file --enable_batching dan --batching_parameters_file . Parameter ditetapkan sesuai dengan SessionBundleConfig . Untuk sistem pada CPU, set num_batch_threads ke jumlah core yang tersedia. Untuk GPU, lihat parameter yang sesuai di sini .

Setelah mengisi seluruh paket di sisi server, permintaan penerbitan digabungkan menjadi satu permintaan besar (tensor), dan dikirim ke sesi Tensorflow dengan permintaan gabungan. Dalam situasi ini, paralelisme CPU / GPU benar-benar terlibat.

Beberapa kegunaan umum untuk pemrosesan batch Tensorflow:

  • Menggunakan permintaan klien asinkron untuk mengisi paket sisi server
  • Pemrosesan batch yang lebih cepat dengan mentransfer komponen grafik model ke CPU / GPU
  • Melayani permintaan dari beberapa model dari satu server
  • Pemrosesan batch sangat disarankan untuk pemrosesan "offline" dari sejumlah besar permintaan

Pemrosesan Batch Sisi Klien


Kelompok pemrosesan batch sisi klien beberapa permintaan masuk menjadi satu.

Karena model ResNet sedang menunggu input dalam format NHWC (dimensi pertama adalah jumlah input), kami dapat menggabungkan beberapa gambar input menjadi satu permintaan 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) 

Untuk paket gambar N, tensor output dalam respons akan berisi hasil prediksi untuk jumlah input yang sama. Dalam kasus kami, N = 2:

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

Akselerasi perangkat keras


Beberapa kata tentang GPU.

Proses pembelajaran secara alami menggunakan paralelisasi pada GPU, karena pembangunan jaringan saraf yang dalam membutuhkan komputasi besar untuk mencapai solusi optimal.

Tetapi untuk hasil keluaran, paralelisasi tidak begitu jelas. Seringkali Anda dapat mempercepat output dari jaringan saraf ke GPU, tetapi Anda perlu memilih dan menguji peralatan dengan cermat, dan melakukan analisis teknis dan ekonomi yang mendalam. Paralelisasi perangkat keras lebih berharga untuk pemrosesan batch kesimpulan "otonom" (volume besar).

Sebelum pindah ke GPU, pertimbangkan persyaratan bisnis dengan analisis biaya (moneter, operasional, teknis) yang cermat untuk keuntungan terbesar (mengurangi latensi, throughput tinggi).

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


All Articles