فصل ۱۹: آموزش و استقرار مدلهای TensorFlow در مقیاس بزرگ
پس از ساخت مدلی که پیشبینیهای خوبی انجام میدهد، مرحلهٔ مهم بعدی رساندن آن به محیط عملیاتی است. در سادهترین حالت میتوان مدل را روی یک Batch از دادهها اجرا کرد، اما در سامانههای واقعی معمولاً چند جزء مختلف زیرساخت باید بتوانند روی دادهٔ زنده از مدل استفاده کنند. در چنین شرایطی بهتر است مدل در قالب یک سرویس مستقل ارائه شود تا سایر بخشهای سامانه از طریق یک API، مانند REST یا gRPC، درخواست پیشبینی ارسال کنند. این جداسازی باعث میشود نسخهبندی مدل، مقیاسدهی، آزمایش A/B، Rollback و بهروزرسانی مدل بدون وابستگی شدید به سایر اجزای نرمافزار انجام شود.
وقتی تعداد درخواستها زیاد میشود، سرویس پیشبینی باید QPS بالاتری را تحمل کند. TensorFlow Serving برای همین هدف طراحی شده است: یک Model Server بهینه و آزمودهشده که میتواند چند مدل و چند نسخه از هر مدل را همزمان سرو کند و Repository مدل را زیر نظر بگیرد تا نسخههای جدید را خودکار بارگذاری کند. در Cloud نیز Vertex AI علاوه بر استقرار و مقیاسدهی، قابلیتهایی مانند Monitoring، مدیریت Endpoint و اجرای Batch Prediction را فراهم میکند. از سوی دیگر، برای کاهش زمان Train میتوان از GPU، TPU و Distribution Strategyها استفاده کرد.
ارائهٔ مدل TensorFlow بهصورت سرویس
در برنامهٔ Python، استفاده از یک مدل Keras با predict() ساده است؛ اما با بزرگشدن زیرساخت، تبدیل مدل به Microservice مزایای زیادی دارد. همهٔ Clientها یک نسخهٔ یکسان از مدل را میبینند، تعویض نسخه مستقل از برنامههای دیگر انجام میشود، Service را میتوان جداگانه Scale کرد و تست و نگهداری نیز سادهتر میشود.
شکل 19-1. ارائهٔ مدل TensorFlow بهصورت سرویس
TF Serving با C++ نوشته شده و برای بار عملیاتی بالا طراحی شده است. میتواند نسخههای متعدد را از یک ساختار پوشهای مشخص بخواند و تازهترین نسخه را بدون توقف سرویس فعال کند. نخست باید مدل را در قالب SavedModel صادر کنیم.
صادر کردن SavedModel و نسخهبندی مدل
برای نسخهبندی کافی است هر نسخه در یک زیرپوشهٔ جدا ذخیره شود. بهتر است لایههای Preprocessing نیز بخشی از مدل نهایی باشند تا Client داده را در شکل طبیعی خود ارسال کند و خطر ناسازگاری میان مدل و منطق پیشپردازش کاهش یابد.
from pathlib import Path
import tensorflow as tf
X_train, X_valid, X_test = [...] # load and split MNIST
model = [...] # build and train
model_name = "my_mnist_model"
model_version = "0001"
model_path = Path(model_name) / model_version
model.save(model_path, save_format="tf")
SavedModel گراف محاسباتی، متغیرها و Signatureهای قابل فراخوانی را نگه میدارد. چون گراف ذخیره میشود، عملیات مدل باید بر پایهٔ TensorFlow باشد؛ کدی که از tf.py_function() برای اجرای Python دلخواه استفاده میکند قابل انتقال به همان شکل نیست.
ابزار خط فرمان saved_model_cli برای بررسی مدل ذخیرهشده مفید است. یک مدل Keras معمولاً MetaGraph با Tag برابر serve و Signature پیشفرض serving_default دارد.
saved_model_cli show --dir my_mnist_model/0001
saved_model_cli show --dir my_mnist_model/0001 --tag_set serve
saved_model_cli show --dir my_mnist_model/0001 --tag_set serve --signature_def serving_default
خروجی ابزار نام Tensorهای ورودی و خروجی، نوع داده، Shape و Method مربوط به Serving را نمایش میدهد. در نمونهٔ MNIST ورودی از نوع DT_UINT8 با شکل (-1, 28, 28) و خروجی احتمالهای ۱۰ کلاس است.
نصب و اجرای TensorFlow Serving
TF Serving را میتوان با Package Manager سیستمعامل، Docker یا از Source نصب کرد. در محیط Ubuntu نمونهٔ کتاب، Repository رسمی TensorFlow به apt افزوده و سپس tensorflow-model-server نصب میشود. کتاب همچنین Library مربوط به API را برای Client نصب میکند.
url="https://storage.googleapis.com/tensorflow-serving-apt"
# repository and GPG key setup omitted here
apt-get install -y tensorflow-model-server
pip install -U tensorflow-serving-api
هنگام اجرای Server، مسیر پایهٔ مدل، نام مدل و Portهای gRPC و REST مشخص میشوند. Port پیشفرض نمونه برای gRPC برابر 8500 و برای REST برابر 8501 است.
tensorflow_model_server --port=8500 --rest_api_port=8501 --model_name=my_mnist_model --model_base_path="${MODEL_DIR}"
روش پیشنهادی دیگر اجرای Image رسمی Docker است. Volume حاوی مدل به مسیر /models داخل Container وصل میشود و دو Port روی Host منتشر میشوند:
docker run -it --rm -v "/path/to/my_mnist_model:/models/my_mnist_model" -p 8500:8500 -p 8501:8501 -e MODEL_NAME=my_mnist_model tensorflow/serving
گزینهٔ --rm Container را بعد از توقف حذف میکند، -v پوشهٔ مدل را Mount میکند، گزینههای -p Portها را Forward میکنند و متغیر MODEL_NAME نام مدلی را تعیین میکند که TF Serving باید سرو کند.
ارسال درخواست از طریق REST API
برای REST، ورودی باید به JSON تبدیل شود. در نمونه سه تصویر از مجموعهٔ Test انتخاب شده و در فیلد instances قرار میگیرند. نام Signature نیز serving_default است.
import json
import requests
X_new = X_test[:3]
request_json = json.dumps({
"signature_name": "serving_default",
"instances": X_new.tolist(),
})
server_url = "http://localhost:8501/v1/models/my_mnist_model:predict"
response = requests.post(server_url, data=request_json)
response.raise_for_status()
y_proba = response.json()["predictions"]
REST ساده و قابل Debug است، ولی JSON کاملاً Text-based است و برای Tensorهای بزرگ میتواند Verbose و پرهزینه باشد. اگر Latency و حجم داده اهمیت زیادی داشته باشد، gRPC معمولاً انتخاب بهتری است.
ارسال درخواست با gRPC
gRPC داده را در قالب Protocol Buffer منتقل میکند و سربار Serialization کمتری دارد. Client ابتدا Channel به Port 8500 میسازد، سپس یک PredictionServiceStub و PredictRequest ایجاد میکند. نام مدل و Signature در Request قرار میگیرند و Tensor ورودی با tf.make_tensor_proto() ساخته میشود.
import grpc
from tensorflow_serving.apis.predict_pb2 import PredictRequest
from tensorflow_serving.apis import prediction_service_pb2_grpc
channel = grpc.insecure_channel("localhost:8500")
predict_service = prediction_service_pb2_grpc.PredictionServiceStub(channel)
request = PredictRequest()
request.model_spec.name = "my_mnist_model"
request.model_spec.signature_name = "serving_default"
input_name = model.input_names[0]
request.inputs[input_name].CopyFrom(tf.make_tensor_proto(X_new))
response = predict_service.Predict(request, timeout=10.0)
gRPC بهخصوص زمانی مفید است که Requestهای بزرگ یا پرتعداد داریم و میخواهیم انتقال باینری مؤثرتر و Latency کمتری داشته باشیم.
استقرار نسخهٔ جدید، Warmup و Rollback
برای انتشار نسخهٔ بعدی کافی است مدل جدید در زیرپوشهای مانند 0002 ذخیره شود. TF Serving Repository را Monitor میکند و نسخهٔ جدید را تشخیص میدهد. در جابهجایی نسخه، Requestهای در حال اجرا با نسخهٔ قبلی کامل میشوند و سپس Requestهای جدید به مدل تازه هدایت میشوند؛ بنابراین Deploy میتواند Graceful باشد.
در مدلهای سنگین ممکن است اولین درخواستها کند باشند، زیرا Graph و Kernelها هنوز Warm نشدهاند. TF Serving از Model Warmup پشتیبانی میکند: مجموعهای از Requestهای نمونه در Assetهای مدل قرار میگیرند تا هنگام Load شدن نسخه اجرا شوند. اگر نسخهٔ جدید مشکل داشته باشد، میتوان آن را از Repository کنار گذاشت تا Server دوباره نسخهٔ قبلی را سرو کند.
قابلیت Automatic Batching نیز میتواند چند Request مستقل را در یک Batch ترکیب کند تا GPU بهتر استفاده شود. تنظیم Batch باید با Latency مورد قبول سرویس متعادل شود؛ Batch بزرگتر Throughput را بالا میبرد اما ممکن است زمان انتظار Request را افزایش دهد.
مقیاسدهی TF Serving در چند Instance
برای بارهای خیلی زیاد، یک Server کافی نیست. میتوان چند Instance از TF Serving راهاندازی کرد و Load Balancer درخواستها را میان آنها تقسیم کند. Repository مدل میتواند مشترک یا توسط یک فرایند Deployment به همهٔ Serverها توزیع شود. Health Check و Auto Scaling برای حفظ دسترسپذیری و ظرفیت اهمیت دارند.
شکل 19-2. مقیاسدهی TF Serving در چند Instance
استقرار در Google Vertex AI
Vertex AI فرایند مدیریت زیرساخت را ساده میکند. ابتدا باید Project و Billing فعال باشد، APIهای موردنیاز فعال شوند و Credential مناسب در اختیار Client قرار گیرد. سپس یک Bucket در Google Cloud Storage ساخته میشود و SavedModel در آن Upload میشود. در محیط محلی معمولاً با gcloud auth application-default login اعتبارنامهٔ پیشفرض تنظیم میشود.
شکل 19-3. استقرار در Google Vertex AI
شکل 19-4. استقرار در Google Vertex AI
در Python میتوان از SDK مربوط به Vertex AI استفاده کرد. Initialization شامل Project، Region و Bucket مرحلهای است؛ سپس مدل با اشاره به Artifact URI و Container مناسب TensorFlow Serving ثبت میشود.
from google.cloud import aiplatform
aiplatform.init(project=project_id, location="us-central1",
staging_bucket=f"gs://{bucket_name}")
mnist_model = aiplatform.Model.upload(
display_name="my_mnist_model",
artifact_uri=f"gs://{bucket_name}/my_mnist_model",
serving_container_image_uri=
"us-docker.pkg.dev/vertex-ai/prediction/tf2-cpu.2-8:latest")
Endpoint، Online Prediction و Auto Scaling
پس از Upload، یک Endpoint ساخته و مدل روی آن Deploy میشود. هنگام Deploy میتوان نوع Machine، تعداد Replicaهای حداقل و حداکثر و در صورت نیاز GPU را تعیین کرد. Vertex AI بر اساس بار، تعداد Replicaها را در بازهٔ مشخصشده تغییر میدهد.
endpoint = aiplatform.Endpoint.create(display_name="my_mnist_endpoint")
endpoint.deploy(
mnist_model,
min_replica_count=1,
max_replica_count=5,
machine_type="n1-standard-4"
)
response = endpoint.predict(instances=X_new.tolist())
y_proba = response.predictions
این سرویس مسئول Provision کردن VMها، Load Balancing، Availability و Scale است. پس از پایان آزمایش بهتر است Resourceهای بلااستفاده، Endpoint یا Deployment پاک شوند تا هزینهٔ Cloud ادامه پیدا نکند.
پیشبینی دستهای در Vertex AI
برای دادههایی که نیاز به پاسخ Online ندارند، Batch Prediction مناسبتر است. ورودیها در GCS قرار میگیرند و یک Job ایجاد میشود؛ Vertex AI منابع لازم را فراهم میکند، داده را پردازش میکند و خروجی را در مسیر مشخصشده مینویسد. نمونهٔ کتاب دادههای MNIST را در قالب JSON Lines آماده میکند.
batch_prediction_job = mnist_model.batch_predict(
job_display_name="my_batch_prediction_job",
gcs_source=f"gs://{bucket_name}/my_mnist_batch/*.jsonl",
gcs_destination_prefix=f"gs://{bucket_name}/my_mnist_predictions/",
machine_type="n1-standard-4",
starting_replica_count=1,
max_replica_count=5,
)
Job پس از پایان، فایلهای Prediction و Error را تولید میکند. کتاب با مقایسهٔ پیشبینیهای نمونه با Labelهای واقعی نشان میدهد که مدل همان Accuracy مورد انتظار را در Batch نیز حفظ میکند. JSON Lines فرمت پیشفرض است، اما در بخش بعدی فرمتهای دیگر و استقرار روی Edge بررسی میشوند.