Tensorflow ist zur Standardplattform für maschinelles Lernen (ML) geworden, die sowohl in der Industrie als auch in der Forschung beliebt ist. Viele kostenlose Bibliotheken, Tools und Frameworks wurden für die Schulung und Pflege von ML-Modellen erstellt. Das Tensorflow Serving-Projekt hilft bei der Pflege von ML-Modellen in einer verteilten Produktionsumgebung.
Unser Mux-Dienst verwendet Tensorflow Serving in mehreren Teilen der Infrastruktur. Wir haben bereits die Verwendung von Tensorflow Serving beim Codieren von Videotiteln erörtert. Heute konzentrieren wir uns auf Methoden, die die Latenz verbessern, indem sie sowohl den Prognoseserver als auch den Client optimieren. Modellprognosen sind in der Regel „Online“ -Operationen (auf dem kritischen Pfad der Anforderung einer Anwendung). Daher besteht das Hauptziel der Optimierung darin, große Mengen von Anforderungen mit der geringstmöglichen Verzögerung zu verarbeiten.
Was ist Tensorflow Serving?
Tensorflow Serving bietet eine flexible Serverarchitektur für die Bereitstellung und Wartung von ML-Modellen. Sobald das Modell trainiert und für die Prognose bereit ist, muss es für Tensorflow Serving in ein kompatibles (wartbares) Format exportiert werden.
Servable ist eine zentrale Abstraktion, die Tensorflow-Objekte umschließt. Beispielsweise kann ein Modell als ein oder mehrere Servable-Objekte dargestellt werden. Servable sind daher die Basisobjekte, mit denen der Client Berechnungen durchführt. Auf die wartbare Größe kommt es an: Kleinere Modelle benötigen weniger Platz, benötigen weniger Speicher und werden schneller geladen. Zum Herunterladen und Verwalten mithilfe der Predict-API müssen Modelle im SavedModel-Format vorliegen.

Tensorflow Serving kombiniert die grundlegenden Komponenten, um einen gRPC / HTTP-Server zu erstellen, der mehrere ML-Modelle (oder mehrere Versionen) bedient, Überwachungskomponenten und eine benutzerdefinierte Architektur bereitstellt.
Tensorflow Serving mit Docker
Werfen wir einen Blick auf die grundlegenden Latenzmetriken bei der Prognoseleistung mit den Standardeinstellungen für Tensorflow Serving (ohne CPU-Optimierung).
Laden Sie zunächst das neueste Bild vom TensorFlow Docker-Hub herunter:
docker pull tensorflow/serving:latest
In diesem Artikel werden alle Container auf einem Host mit vier Kernen, 15 GB, Ubuntu 16.04 ausgeführt.
Exportieren Sie das Tensorflow-Modell nach SavedModel
Wenn ein Modell mit Tensorflow trainiert wird, kann die Ausgabe als variable Kontrollpunkte (Dateien auf der Festplatte) gespeichert werden. Die Ausgabe erfolgt direkt durch Wiederherstellen von Kontrollpunkten des Modells oder in einem eingefrorenen eingefrorenen Diagrammformat (Binärdatei).
Für Tensorflow Serving muss dieses eingefrorene Diagramm in das SavedModel-Format exportiert werden. Die
Tensorflow-Dokumentation enthält Beispiele für den Export trainierter Modelle in das SavedModel-Format.
Tensorflow bietet auch viele
offizielle und Forschungsmodelle als Ausgangspunkt für Experimente, Forschung oder Produktion.
Als Beispiel verwenden wir das
ResNet-Modell (Deep Residual Neural Network) , um ein ImageNet-Dataset mit 1000 Klassen zu klassifizieren. Laden Sie das
vorgefertigte ResNet-50 v2
Modell herunter, insbesondere die
Option Channels_last (NHWC) in
SavedModel : In der Regel funktioniert es besser auf der CPU.
Kopieren Sie das RestNet-Modellverzeichnis in die folgende Struktur:
models/ 1/ saved_model.pb variables/ variables.data-00000-of-00001 variables.index
Tensorflow Serving erwartet eine numerisch geordnete Verzeichnisstruktur für die Versionierung. In unserem Fall entspricht das Verzeichnis
1/
dem Modell der Version 1, das die Architektur des Modells
saved_model.pb
mit einer Momentaufnahme der Modellgewichte (Variablen) enthält.
Laden und Verarbeiten von SavedModel
Der folgende Befehl startet den Tensorflow Serving-Modellserver in einem Docker-Container. Um SavedModel zu laden, müssen Sie das Modellverzeichnis im erwarteten Containerverzeichnis bereitstellen.
docker run -d -p 9000:8500 \ -v $(pwd)/models:/models/resnet -e MODEL_NAME=resnet \ -t tensorflow/serving:latest
Das Überprüfen der Containerprotokolle zeigt, dass ModelServer aktiv ist, um Ausgabeanforderungen für das
resnet
Modell an den gRPC- und HTTP-Endpunkten zu verarbeiten:
... 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 ...
Prognose-Client
Tensorflow Serving definiert ein API-Schema im
Protokollpufferformat (Protobufs). GRPC-Client-Implementierungen für die Prognose-API werden als Python-Paket
tensorflow_serving.apis
. Wir benötigen ein weiteres Python-Paket
tensorflow
für Dienstprogrammfunktionen.
Installieren Sie die Abhängigkeiten, um einen einfachen Client zu erstellen:
virtualenv .env && source .env/bin/activate && \ pip install numpy grpcio opencv-python tensorflow tensorflow-serving-api
Das
ResNet-50 v2
Modell erwartet die Eingabe von Gleitkomma-Tensoren in eine formatierte NHWC-Datenstruktur (canal_last). Daher wird das Eingabebild mit opencv-python gelesen und als float32-Datentyp in das numpy-Array (Höhe × Breite × Kanäle) geladen. Das folgende Skript erstellt einen Vorhersage-Client-Stub, lädt die JPEG-Daten in ein Numpy-Array und konvertiert sie in tensor_proto, um eine Prognoseanforderung für gRPC zu stellen:
Nach Erhalt einer JPEG-Eingabe erzeugt ein funktionierender Client das folgende Ergebnis:
python tf_serving_client.py --image=images/pupper.jpg total time: 2.56152906418s
Der resultierende Tensor enthält eine Prognose in Form eines ganzzahligen Werts und einer Vorzeichenwahrscheinlichkeit.
outputs { key: "classes" value { dtype: DT_INT64 tensor_shape { dim { size: 1 } } int64_val: 238 } } outputs { key: "probabilities" ...
Für eine einzelne Anfrage ist eine solche Verzögerung nicht akzeptabel. Kein Wunder: Die Tensorflow Serving-Binärdatei ist standardmäßig für die meisten Geräte in den meisten Anwendungsfällen ausgelegt. Sie haben wahrscheinlich die folgenden Zeilen in den Protokollen des Standard-Tensorflow-Serviercontainers bemerkt:
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
Dies weist auf eine TensorFlow Serving-Binärdatei hin, die auf einer CPU-Plattform ausgeführt wird, für die sie nicht optimiert wurde.
Erstellen Sie eine optimierte Binärdatei
Gemäß der Tensorflow-
Dokumentation wird empfohlen, Tensorflow aus der Quelle mit allen für die CPU verfügbaren Optimierungen auf dem Host zu kompilieren, auf dem die Binärdatei funktioniert. Beim Zusammenbau ermöglichen spezielle Flags die Aktivierung von CPU-Befehlssätzen für eine bestimmte Plattform:
Klonen Sie eine Tensorflow-Portion einer bestimmten Version. In unserem Fall ist dies 1,13 (der letzte zum Zeitpunkt der Veröffentlichung dieses Artikels):
USER=$1 TAG=$2 TF_SERVING_VERSION_GIT_BRANCH="r1.13" git clone --branch="$TF_SERVING_VERSION_GIT_BRANCH" https://github.com/tensorflow/serving
Das Tensorflow Serving-Entwicklerbild verwendet das Basel-Tool zum Erstellen. Wir konfigurieren es für bestimmte Sätze von CPU-Anweisungen:
TF_SERVING_BUILD_OPTIONS="--copt=-mavx --copt=-mavx2 --copt=-mfma --copt=-msse4.1 --copt=-msse4.2"
Wenn nicht genügend Speicher vorhanden ist, begrenzen Sie den Speicherverbrauch während des Erstellungsprozesses mit dem Flag
--local_resources=2048,.5,1.0
. Informationen zu Flags finden Sie in der Hilfe zu
Tensorflow Serving und Docker sowie in der
Bazel-Dokumentation .
Erstellen Sie ein Arbeitsbild basierend auf dem vorhandenen:
ModelServer wird mithilfe von
TensorFlow-Flags konfiguriert, um die Parallelität zu unterstützen. Mit den folgenden Optionen werden zwei Thread-Pools für den Parallelbetrieb konfiguriert:
intra_op_parallelism_threads
- steuert die maximale Anzahl von Threads für die parallele Ausführung einer Operation;
- Wird verwendet, um Operationen mit Unteroperationen zu parallelisieren, die von Natur aus unabhängig sind.
inter_op_parallelism_threads
- steuert die maximale Anzahl von Threads für die parallele Ausführung unabhängiger Operationen;
- Tensorflow-Graph-Operationen, die unabhängig voneinander sind und daher in verschiedenen Threads ausgeführt werden können.
Standardmäßig sind beide Parameter auf
0
. Dies bedeutet, dass das System selbst die entsprechende Nummer auswählt, was meistens einen Thread pro Kern bedeutet. Der Parameter kann jedoch für die Mehrkern-Parallelität manuell geändert werden.
Führen Sie dann den Serving-Container auf die gleiche Weise wie den vorherigen aus, diesmal mit einem aus den Quellen kompilierten Docker-Image und mit Tensorflow-Optimierungsflags für einen bestimmten Prozessor:
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
Containerprotokolle sollten keine Warnungen mehr über eine undefinierte CPU enthalten. Ohne den Code bei derselben Prognoseanforderung zu ändern, wird die Verzögerung um ca. 35,8% reduziert:
python tf_serving_client.py --image=images/pupper.jpg total time: 1.64234706879s
Erhöhen Sie die Geschwindigkeit bei der Kundenprognose
Kann man noch beschleunigen? Wir haben die Serverseite für unsere CPU optimiert, aber eine Verzögerung von mehr als 1 Sekunde scheint immer noch zu groß.
So kam es, dass das Laden der Bibliotheken
tensorflow_serving
und
tensorflow
einen wesentlichen Beitrag zur Verzögerung leistet. Jeder unnötige Aufruf von
tf.contrib.util.make_tensor_proto
fügt auch einen Sekundenbruchteil hinzu.
Sie fragen sich möglicherweise: "Benötigen wir keine TensorFlow Python-Pakete, um tatsächlich Vorhersageanforderungen an den Tensorflow-Server zu senden?" Tatsächlich besteht kein wirklicher
Bedarf an
tensorflow_serving
und
tensorflow
Paketen.
Wie bereits erwähnt, sind die Tensorflow-Vorhersage-APIs als Protopuffer definiert. Daher können zwei externe Abhängigkeiten durch die entsprechenden
tensorflow_serving
tensorflow
und "
tensorflow_serving
Anschließend müssen Sie nicht die gesamte (schwere) Tensorflow-Bibliothek auf dem Client abrufen.
tensorflow
tensorflow_serving
tensorflow
und
tensorflow_serving
und fügen Sie das Paket
grpcio-tools
.
pip uninstall tensorflow tensorflow-serving-api && \ pip install grpcio-tools==1.0.0
tensorflow/tensorflow
die
tensorflow/tensorflow
und
tensorflow/serving
Repositorys und kopieren Sie die folgenden Protobuf-Dateien in das Client-Projekt:
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
Kopieren Sie diese Protobuf-Dateien mit den ursprünglichen Pfaden in das Verzeichnis
protos/
:
protos/ tensorflow_serving/ apis/ *.proto tensorflow/ core/ framework/ *.proto
Der Einfachheit halber kann
Prediction_service.proto vereinfacht werden, um nur Predict RPC zu implementieren, um die verschachtelten Abhängigkeiten anderer im Service angegebener RPCs nicht herunterzuladen.
Hier ist ein Beispiel für eine vereinfachte
prediction_service.
.
Erstellen Sie Python-gRPC-Implementierungen mit
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
Jetzt kann das gesamte Modul
tensorflow_serving
entfernt werden:
from tensorflow_serving.apis import predict_pb2 from tensorflow_serving.apis import prediction_service_pb2
... und durch die generierten Protobuffer aus
protos/tensorflow_serving/apis
ersetzen:
from protos.tensorflow_serving.apis import predict_pb2 from protos.tensorflow_serving.apis import prediction_service_pb2
Die Tensorflow-Bibliothek wird importiert, um die
make_tensor_proto
, die
zum Umschließen eines Python / Numpy-Objekts als TensorProto-Objekt erforderlich ist.
Somit können wir das folgende Abhängigkeits- und Codefragment ersetzen:
import tensorflow as tf ... tensor = tf.contrib.util.make_tensor_proto(features) request.inputs['inputs'].CopyFrom(tensor)
Protobuffer importieren und ein TensorProto-Objekt erstellen:
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 ...
Das vollständige Python-Skript finden Sie
hier . Führen Sie einen aktualisierten Starter-Client aus, der eine Vorhersageanforderung für eine optimierte Tensorflow-Bedienung stellt:
python tf_inception_grpc_client.py --image=images/pupper.jpg total time: 0.58314920859s
Das folgende Diagramm zeigt die prognostizierte Ausführungszeit in der optimierten Version von Tensorflow Serving im Vergleich zum Standard über 10 Läufe:

Die durchschnittliche Verzögerung verringerte sich um das 3,38-fache.
Bandbreitenoptimierung
Tensorflow Serving kann für die Verarbeitung großer Datenmengen konfiguriert werden. Die Bandbreitenoptimierung wird normalerweise für die "eigenständige" Stapelverarbeitung durchgeführt, bei der enge Latenzgrenzen keine strenge Anforderung sind.
Serverseitige Stapelverarbeitung
Wie in der
Dokumentation angegeben , wird die serverseitige Stapelverarbeitung in Tensorflow Serving nativ unterstützt.
Die Kompromisse zwischen Latenz und Durchsatz werden durch Stapelverarbeitungsparameter bestimmt. Mit ihnen können Sie den maximalen Durchsatz erzielen, den Hardwarebeschleuniger erreichen können.
Um das
--enable_batching
zu aktivieren, setzen Sie die
--enable_batching
und
--batching_parameters_file
. Die Parameter werden gemäß
SessionBundleConfig festgelegt . Setzen
num_batch_threads
für Systeme auf der CPU
num_batch_threads
auf die Anzahl der verfügbaren Kerne. Informationen zur GPU finden Sie
hier .
Nach dem Ausfüllen des gesamten Pakets auf der Serverseite werden die Ausgabeanforderungen zu einer großen Anforderung (Tensor) zusammengefasst und mit einer kombinierten Anforderung an die Tensorflow-Sitzung gesendet. In dieser Situation ist die CPU / GPU-Parallelität wirklich involviert.
Einige häufige Anwendungen für die Tensorflow-Stapelverarbeitung:
- Verwenden von asynchronen Clientanforderungen zum Auffüllen serverseitiger Pakete
- Schnellere Stapelverarbeitung durch Übertragung der Komponenten des Modellgraphen auf die CPU / GPU
- Anfragen von mehreren Modellen von einem einzigen Server aus bearbeiten
- Die Stapelverarbeitung wird für die "Offline" -Verarbeitung einer großen Anzahl von Anforderungen dringend empfohlen
Clientseitige Stapelverarbeitung
Die clientseitige Stapelverarbeitung gruppiert mehrere eingehende Anforderungen zu einer.
Da das ResNet-Modell auf die Eingabe im NHWC-Format wartet (die erste Dimension ist die Anzahl der Eingaben), können wir mehrere Eingabebilder zu einer RPC-Anforderung kombinieren:
... 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)
Für ein Paket von N Bildern enthält der Ausgangstensor in der Antwort die Vorhersageergebnisse für die gleiche Anzahl von Eingaben. In unserem Fall ist N = 2:
outputs { key: "classes" value { dtype: DT_INT64 tensor_shape { dim { size: 2 } } int64_val: 238 int64_val: 121 } } ...
Hardwarebeschleunigung
Ein paar Worte zu GPUs.
Der Lernprozess verwendet natürlich die Parallelisierung auf der GPU, da der Aufbau tiefer neuronaler Netze umfangreiche Berechnungen erfordert, um die optimale Lösung zu erzielen.
Für die Ausgabe von Ergebnissen ist die Parallelisierung jedoch nicht so offensichtlich. Oft können Sie die Ausgabe eines neuronalen Netzwerks an eine GPU beschleunigen, aber Sie müssen die Geräte sorgfältig auswählen und testen sowie eingehende technische und wirtschaftliche Analysen durchführen. Hardware-Parallelisierung ist für die Stapelverarbeitung von "autonomen" Schlussfolgerungen (massive Volumina) wertvoller.
Berücksichtigen Sie vor dem Wechsel zu einer GPU die Geschäftsanforderungen mit einer sorgfältigen Analyse der Kosten (monetär, betrieblich, technisch), um den größten Nutzen (reduzierte Latenz, hoher Durchsatz) zu erzielen.