Suchen

Portable Container für maschinelles Lernen einsetzen Deep Learning Container unter AWS

| Autor / Redakteur: Sascha Möllering * / Stephan Augsten

„Welche Vorteile bringen Container bei Entwicklungsprojekten zum maschinellen Lernen und wie lassen sich diese implementieren?“

Firma zum Thema

SageMaker stellt Container-Instanzen bei Bedarf bereit und fährt sie nach Abschluss eines Aiftrags wieder herunter.
SageMaker stellt Container-Instanzen bei Bedarf bereit und fährt sie nach Abschluss eines Aiftrags wieder herunter.
(Bild: AWS)

In einer klassischen Entwicklungsumgebung für das maschinelle Lernen existieren gleich mehrere Fehlerquellen: die API des maschinellen Lern-Frameworks, CPU-spezifische Bibliotheken für beschleunigte mathematische Routinen, GPU-spezifische Bibliotheken für beschleunigte Mathematik- und Inter-GPU-Kommunikationsroutinen sowie GPU-Treiber.

Fehlgeschlagene Experimente können die Folge sein. Um diese und weitere Probleme hinsichtlich Konsistenz, Portabilität und Abhängigkeitsmanagement zu vermeiden, bieten sich Container-Technologien an. Schließlich ist in einem Container alles enthalten, was zum Ausführen der Software erforderlich ist. Dazu gehören Bibliotheken, Systemtools, Code und Laufzeitumgebung.

Mithilfe der Softwareplattform Docker ist es möglich, Anwendungen in Containern schnell zu erstellen, zu testen und in jeder Umgebung bereitzustellen und zu skalieren. Gleichzeitig sind Container dazu in der Lage, nicht nur den Trainingscode, sondern die gesamten Abhängigkeiten bis hinunter zu den Hardware-Bibliotheken vollständig zu kapseln.

Nutzer erhalten damit eine Entwicklungsumgebung für maschinelles Lernen, die konsistent und portabel ist. Sowohl die Zusammenarbeit als auch die Skalierung wird dadurch auf einem Cluster wesentlich einfacher.

Einrichten einer Entwicklungsumgebung für maschinelles Lernen mit Jupyter und Docker-Containern.
Einrichten einer Entwicklungsumgebung für maschinelles Lernen mit Jupyter und Docker-Containern.
(Bild: AWS)

AWS hostet AWS Deep Learning Container mit Open Source Deep Learning Frameworks, die für rechenoptimierte CPU- und GPU-Instanzen geeignet sind. Mit den folgenden Schritten lässt sich mit einer Amazon EC2-Instanz sukzessive eine Entwicklungsumgebung mit Containern aufbauen:

Schritt 1: Eine Entwicklungsinstanz auswählen

Instanzen der C5-, P3- oder G4-Familie eignen sich ideal für Workloads mit maschinellem Lernen. Die beiden letzteren bieten bis zu acht NVIDIA-GPUs pro Instanz. Das aktuelle Amazon Machine Image (AMI) für Deep Learning enthält die neuesten Deep Learning Frameworks, Docker-Laufzeitumgebungen, sowie NVIDIA-Treiber und -Bibliotheken.

Wie erwähnt ist auf dem AMI zwar auch ein Deep-Learning-Framework installiert. Die mit den hier vorgestellten Schritten erstellten Deep-Learning-Container sind jedoch aus den genannten Gründen wesentlich effizienter.

Schritt 2: Mit SSH auf die Instanz zugreifen und einen Deep-Learning-Container herunterladen

ssh -i ~/.ssh/<pub_key> ubuntu@<IP_ADDR>

Der nächste Schritt besteht aus dem Log-In in die Deep Learning Container Registry:

$(aws ecr get-login --no-include-email --region YOUR_REGION --registry-ids 763104351884)

Zunächst muss das Framework, mit dem gearbeitet werden soll, aus der Liste auf der AWS Deep Learning Container Webseite ausgewählt werden. Der nächste Schritt besteht aus einem Übertragen des Containers Images über „docker pull“.

Um den neuesten TensorFlow Container mit GPU-Unterstützung in der Region „US-West 2“ nutzen zu können, muss folgendes Kommando ausgeführt werden:

docker pull 763104351884.dkr.ecr.us-west-2.amazonaws.com/tensorflow-training:2.1.0-gpu-py36-cu1

Schritt 3: Den Container instanziieren und Jupyter einrichten

Um den Container zu instaziieren, setzen wir folgenden Befehl ab:

docker run -it --runtime=nvidia -v $PWD:/projects --network=host --name=tf-dev 763104351884.dkr.ecr.us-west-2.amazonaws.com/tensorflow-training:2.1.0-gpu-py36-cu101-ubuntu18.04

--runtime=nvidia: instruiert docker dazu, NVIDIA GPUs innerhalb des Containers für den Host zur Verfügung zu stellen.

-v: weist docker an, das nachfolgend aufgeführte Verzeichnis zu mounten, so dass darauf aus der Containerumgebung heraus zugegriffen werden kann. Falls bereits Datensätze oder Codedateien verfügbar sind, sollten diese über ein bestimmtes Verzeichnis zur Verfügung gestellt werden, das mit dieser Option gemountet wird.

--network=host: Wenn für einen Container der Host-Netzwerkmodus verwendet wird, wird der Netzwerkstack dieses Containers nicht vom Docker-Host isoliert (der Container teilt sich den Netzwerknamensraum des Hosts), und dem Container wird keine eigene IP-Adresse zugewiesen.

Hostmodus-Netzwerke können zur Optimierung der Leistung nützlich sein, da sie keine Network Address Translation (NAT) erfordern.

pip install jupyterlab
lab --ip=0.0.0.0 --port=9999 --allow-root --NotebookApp.token='' --NotebookApp.password=''

Über ein zweites Terminal-Fenster auf dem Client-Rechner wird anschließend ein Tunnel über Port 9999 aufgebaut. Damit ist es möglich, auf den Jupyter-Notebook-Server zugreifen, der innerhalb des Containers auf dem Host-Rechner läuft.

ssh -N -L 0.0.0.0:9999:localhost:9999 -i ~/.ssh/<pub_key> ubuntu@<IP_ADDR>

Anschließend geht es im Browser unter der Adresse http://0.0.0.0:9999/lab weiter.

Nun existiert bereits eine Container-basierte Entwicklungsumgebung. Jeder Code, der über das Jupyter-Notebook läuft, arbeitet bereits innerhalb der Deep-Learning-Umgebung.

Schritt 4: Verwenden der containergestützten Entwicklungsumgebung

Container sind als zustandslose Ausführungsumgebungen gedacht. Die Arbeit sollte daher in den dafür bereitgestellten Verzeichnissen gespeichert werden, die beim Aufrufen von Docker über run mit dem Flag -v versehen wurden.

Um einen Container zu beenden, wird der Jupyter-Server gestoppt und exit im Terminal eingegeben. Mit dem Befehl docker start tf-dev lassen sich Container neu starten. Wenn der Tunnel wie in Schritt 3 angelegt wurde, kann die Entwicklung anschließend fortgesetzt werden.

Nach Änderungen am Basis-Container – etwa der Installation von Jupyter wie in Schritt 3 – empfiehlt es sich, die benutzerdefinierten Installationen zu verfolgen und in einer Docker-Datei zu erfassen. Auf diese Weise ist es möglich, ein Container-Image mit den Änderungen von Grund auf neu zu erstellen. Das hilft außerdem bei der Dokumentation der Änderungen und ermöglicht eine Versionierung zusammen mit dem Rest des Codes.

Eine andere Möglichkeit besteht in der Übertragung der Änderungen in ein neues Container-Image über:

sudo docker commit tf-dev my-tf-dev:latest

Dieser Ansatz ist jedoch nur eingeschränkt zu empfehlen und es empfielt sich, die iterative Weiterentwicklung der Dockerfiles. Ansonsten besteht das Risiko, mit der Zeit den Überblick über die Änderungen zu verlieren und sich zu sehr auf ein „funktionierendes“ Image zu verlassen – ähnlich wie bei einer kompilierte Binärdatei ohne Zugriff auf den Quellcode. Über das Dockerfile werden die Docker Images reproduzierbar.

Um einen neuen Container für Mitarbeiter freigeben zu können, kann dieser in eine Container-Registry, wie z. B. Docker Hub oder Amazon Elastic Container Registry (Amazon ECR) geschoben werden. Um diesen nach Amazon ECR zu pushen, muss zunächst eine Registry erstellt werden. Weitere Schritte sind die Anmeldung an der erstellten Registry und das Pushen des Container-Images:

aws ecr create-repository --repository-name my-tf-dev
$(aws ecr get-login --no-include-email --region <REGION>)
docker tag my-tf-dev:latest <ACCOUNT_ID>.dkr.ecr.<REGION>.amazonaws.com/my-tf-dev:latest
docker push <ACCOUNT_ID>.dkr.ecr.<REGION>.amazonaws.com/my-tf-dev:latest

Anschließend lässt sich das Container Image für einen Mitarbeiter freigeben und das Container Image sollte wie auf dem eignen Computer funktionieren – unabhängig von der Ausführungsumgebung. Zugleich kann der gleiche Container für umfangreiche Workloads in einem Cluster genutzt werden.

Container für das maschinelle Lernen und ihre Skalierung auf Clustern

SageMaker stellt Container-Instanzen bei Bedarf bereit und fährt sie nach Abschluss eines Aiftrags wieder herunter.
SageMaker stellt Container-Instanzen bei Bedarf bereit und fährt sie nach Abschluss eines Aiftrags wieder herunter.
(Bild: AWS)

Die meisten Lösungen zur Clusterverwaltung wie Kubernetes oder Amazon ECS führen Container in einem Cluster aus. Alternativ kann ein vollständig verwalteter Dienst wie Amazon SageMaker genutzt werden, bei dem Instanzen bei Bedarf bereitgestellt und nach Abschluss des Auftrags automatisch heruntergefahren werden.

Anwender erhalten damit eine vollständig verwaltete Suite von Diensten für die Datenkennzeichnung, eine gehostete Jupyter-Notebook-Entwicklungsumgebung, verwaltete Trainingscluster, Hyperparameteroptimierung, verwaltete Modellhostingdienste sowie eine IDE, die alles miteinander verbindet. Um diese Lösungen zu skalieren und maschinelles Lernen in einem Cluster durchzuführen, muss ein Container erstellt und in eine Registry übertragen werden.

Eine Container-Umgebung wie die hier beschriebene wird auch innerhalb eines Clusters skaliert gute Dienste leisten – ohne Überraschungen bei der Framework-Version oder den Abhängigkeiten.

Um einen verteilten Job zum Modelltraining mit Kubernetes und KubeFlow auf zwei Knoten auszuführen, muss innerhalb von YAML eine Konfigurationsdatei angelegt werden, die ungefähr folgendermaßen aussieht:

Auszug aus der Kubernetes-Konfigurationsdatei.
Auszug aus der Kubernetes-Konfigurationsdatei.
(Bild: AWS)

Es handelt sich um einen Auszug aus einer Kubernetes-Konfigurationsdatei für verteiltes Training mit TensorFlow- und Horovod-API, die hier auf Github zu finden ist. Der Screenshot zeigt nicht die vollständige Datei.

Im Bereich image wird das Docker-Image mit den Trainingsskripten angegeben. Unter command steht der für das Training erforderliche Befehl. Da es sich um einen verteilten Job zum Modelltraining handelt, wird der MPI-Job mit dem Befehl mpirun ausgeführt. Er kann wie folgt an einen Kubernetes-Cluster gesendet werden – vorausgesetzt, ein Cluster ist eingerichtet, wird ausgeführt und KubeFlow ist installiert:

kubectl apply -f eks_tf_training_job-cpu.yaml

Um dasselbe mit Amazon SageMaker auf acht Knoten zu tun, kommt das Amazon SageMaker SDK zum Einsatz. Dort kann ein verteilter Job für das Modelltraining mithilfe der Estimator-API angestoßen werden:

distributions = {'mpi': {
         'enabled': True,
         'processes_per_host': hvd_processes_per_host,
         'custom_mpi_options': '-verbose --NCCL_DEBUG=INFO -x OMPI_MCA_btl_vader_single_copy_mechanism=none'
         } }
estimator_hvd = TensorFlow(entry_point='cifar10-multi-gpu-horovod-sagemaker.py',            source_dir = 'code',
            role = role,
            image_name = <YOUR_DOCKER_IMAGE>,
            hyperparameters = hyperparameters,
            train_instance_count = 8,
            train_instance_type = ’p3.2xlarge’,
            output_path = output_path,
            model_dir = model_dir,
            distributions = distributions)

Sascha Möllering
Sascha Möllering
(Bild: AWS Germany GmbH)

Die hier beschriebenen Schritte geben erste Einblicke, welches Potenzial Container gerade im Umfeld des maschinellen Lernens bieten. Weitere Informationen dazu finden sich auch im Distributed Training Workshop auf AWS.com.

* Sascha Möllering arbeitet als Solutions Architect bei der Amazon Web Services Germany GmbH.

(ID:46627640)