Schlüssel zum Mitnehmen
- Python-Code kann auf einem Laptop und einem großen HPC-System mit der gleichen Kernlogik ausgeführt werden, wenn der Workflow MPI4Py oder Dask korrekt verwendet.
- Die Reproduzierbarkeit der Umgebung ist oft der schwierigste Teil der HPC-Arbeit. Conda-Umgebungen, Lockfiles und Job-Skripte helfen dabei, es zu lösen.
- Der Workflow besteht aus drei Phasen: Prototyp lokal, parallelisieren mit MPI4PY oder DASK und Senden von Jobs über SLURM, PBS oder einen anderen Scheduler.
- Schreiben Sie nicht die gesamte Codebasis für den Cluster neu. Halten Sie die Simulationslogik stabil und wickeln Sie sie mit reproduzierbarer Umgebung und parallelen Ausführungsschichten ein.
Sie schreiben ein Simulationsskript auf Ihren Laptop. Sie testen es mit kleinen Datensätzen. Es läuft in Minuten. Dann benötigen Sie Tausende von Kernen und Hunderte von Gigabyte RAM, um es im Maßstab auszuführen.
Schreiben Sie die gesamte Codebasis um?
Nein. Der gleiche Python-Code, der auf Ihrem Laptop ausgeführt wird, kann auf einem Supercomputer mit minimalen Änderungen ausgeführt werden. Der Schlüssel ist nicht neu zu schreiben. es ist umwickelt.
Sie benötigen drei Dinge: eine reproduzierbare Umgebung, ein paralleles Ausführungsmodell und ein Auftragsübermittlungsskript für den Clusterplaner.
Dieser Leitfaden durchläuft den vollständigen Workflow vom lokalen Prototyp bis zur HPC-Produktion, ohne dass Sie Ihre Kernsimulationslogik ändern.
Der dreistufige HPC-Python-Workflow
Die meisten HPC-Python-Projekte folgen unabhängig vom wissenschaftlichen Bereich dem gleichen dreistufigen Muster.
Stage 1 — Local Prototyping:
Laptop → Jupyter or IDE → NumPy / Pandas → serial execution
Stage 2 — Parallelization:
Same code → mpi4py or Dask → parallel execution
Stage 3 — Cluster Deployment:
Python script → Slurm / PBS job submission → distributed compute nodes
Ziel ist es, diese Phasen schrittweise durchzugehen. Beginnen Sie mit einer funktionierenden lokalen Version. Fügen Sie die parallele Ausführung erst hinzu, wenn die Logik korrekt ist. Erst nach kleinen parallelen Tests an den Cluster senden.
Stufe 1: Aufbau einer reproduzierbaren Umgebung
Bevor Sie parallelen Code schreiben, benötigen Sie eine zuverlässige Entwicklungsumgebung. Hier scheitern viele wissenschaftliche Workflows zuerst.
Warum Reproduzierbarkeit für HPC-Cluster schwierig ist
HPC-Cluster sind gemeinsam genutzte Systeme. Viele Forschungsgruppen verwenden dieselbe Infrastruktur, und Systempakete können sich im Laufe der Zeit ändern. Eine heutige Simulation kann nach Modulaktualisierungen, Compileränderungen oder Paketversionsänderungen sechs Monate später abbrechen.
Die Lösung ist das Umweltmanagement. Conda wird häufig verwendet, weil es Pakete aus dem System Python isoliert und sowohl kompilierte Abhängigkeiten als auch Python-Pakete verwalten kann.
# Create a reproducible environment
conda create -n my-sim python=3.11 numpy mpi4py dask
# Lock dependencies for reproducibility
conda env export --no-builds --name my-sim > environment.yml
# Recreate the environment on another machine
conda env create -n my-sim -f environment.yml
Eine einzelne Umgebungsdatei bietet Mitarbeitern und zukünftigen Benutzern eine klare Möglichkeit, den Software-Stack neu zu erstellen. Verwenden Sie für eine strengere Reproduzierbarkeit Lockfiles, die jede Paketversion anheften und eine Abhängigkeit aufbauen.
Conda vs Venv: Warum Conda oft auf HPC gewinnt
Pythons integrierte venv funktioniert gut für reine Python-Pakete. HPC-Workflows hängen häufig von kompilierten wissenschaftlichen Bibliotheken, MPI-Runtimes, HDF5, BLAS, CUDA und anderen Komponenten auf Systemebene ab.
| Funktion | Venv | Konda |
|---|---|---|
| Reine Python-Pakete | Ja | Ja |
| Abhängigkeiten auf Systemebene wie MPI und HDF5 | Nein | Ja |
| Plattformübergreifende Konsistenz | Beschränkt | Stark |
| GPU-Beschleunigungspakete wie Cupy oder PyTorch CUDA | Manuelle Einrichtung | Mehr automatisierte Einrichtung |
Conda bietet Reproduzierbarkeit über mehr wissenschaftliche Stapel. Das ist bei der Arbeit mit MPI4Py von Bedeutung, da die MPI-Runtime- und Python-Umgebung sowohl auf dem lokalen Computer als auch auf dem Cluster kompatibel sein muss.
Stufe 2: Parallelisierung Ihres Python-Codes
Sobald die lokale Version funktioniert, ist der nächste Schritt die Parallelisierung. In Python-HPC-Workflows sind zwei Tools besonders verbreitet:
- MPI4PY für eine feinkörnige verteilte Steuerung über viele Knoten.
- Dask für übergeordnete Parallelität mit weniger Codeänderungen.
Warum Sie mpi4py brauchen
Die globale Dolmetschersperre von Python begrenzt die wahre Multi-Thread-Python-Ausführung in einem Prozess. Das multiprocessing-Modul kann auf einer Maschine parallelisieren, skaliert jedoch nicht über mehrere Rechenknoten hinweg.
MPI4PY bietet Python-Bindungen für MPI, die Nachrichtenübermittlungsschnittstelle. MPI ist eine Standard-API für verteiltes Parallel-Computing und wird häufig auf HPC-Systemen verwendet.
Bei MPI4Py führt jeder Prozess das gleiche Skript aus, erhält jedoch einen eindeutigen Rang. Dieser Rang steuert, welchen Teil der Arbeitslast jeder Prozess verarbeitet.
Grundlegendes mpi4py-Muster: Hallo Welt
from mpi4py import MPI
comm = MPI.COMM_WORLD
rank = comm.Get_rank()
size = comm.Get_size()
print(f"Hello from process {rank} out of {size} processes")
Starten Sie das Skript mit MPI:
mpiexec -n 16 python my_script.py
Dies startet 16 unabhängige Python-Prozesse. Jeder Prozess führt das gleiche Skript aus, aber jeder hat ein anderes rank .
Das Datenmodell: unabhängige Prozesse
MPI-Prozesse teilen sich standardmäßig keinen Speicher. Jeder Prozess hat seinen eigenen Speicherplatz. Um Informationen auszutauschen, müssen Prozesse explizit Daten senden, empfangen, übertragen, sammeln oder verkleinern.
import numpy as np
from mpi4py import MPI
comm = MPI.COMM_WORLD
rank = comm.Get_rank()
# Rank 0 creates the initial data.
if rank == 0:
data = np.arange(10, dtype="i")
else:
data = np.empty(10, dtype="i")
# Broadcast data from rank 0 to all ranks.
comm.Bcast(data, root=0)
print(f"Rank {rank} received data: {data}")
Dieses explizite Kommunikationsmodell ist ein Grund, warum MPI gut skaliert. Jeder Rang besitzt seinen lokalen Speicher, und die Kommunikation erfolgt nur, wenn Sie ihn anfordern.
Workload-Verteilung: Das Kernmuster
Die meisten wissenschaftlichen MPI-Workflows folgen demselben Muster: Teilen Sie das Problem, berechnen Sie lokal und reduzieren Sie die Ergebnisse.
from mpi4py import MPI
import numpy as np
comm = MPI.COMM_WORLD
size = comm.Get_size()
rank = comm.Get_rank()
# Total problem size
N = 10_000_000
# Calculate workload per rank
workloads = [N // size for _ in range(size)]
for i in range(N % size):
workloads[i] += 1
my_start = sum(workloads[:rank])
my_end = my_start + workloads[rank]
# Each rank works on its own slice
my_data = np.random.rand(my_end - my_start)
# Local computation
local_result = np.sum(np.sin(my_data))
# Sum results across all ranks
send_buffer = np.array([local_result])
receive_buffer = np.zeros(1)
comm.Reduce(send_buffer, receive_buffer, op=MPI.SUM, root=0)
if rank == 0:
print(f"Total computed across {size} ranks: {receive_buffer[0]}")
Dieses Muster gilt für viele Simulationsaufgaben:
- Monte-Carlo-Simulationen, bei denen jeder Rang unabhängige Stichproben verarbeitet.
- Parameter-Sweeps, bei denen jeder Rang verschiedene Parametersätze testet.
- Domänenzerlegung, wobei jeder Rang einen Teil des Simulationsrasters besitzt.
- Molekulare Dynamik, bei der die Rechenkräfte für verschiedene Teilchen-Teilmengen rangieren.
Die kollektive Kommunikations-Toolbox
MPI4Py bietet kollektive Vorgänge, die in der Regel einfacher und effizienter sind als benutzerdefinierte Punkt-zu-Punkt-Messaging.
| Betrieb | Was es tut | Wann zu verwenden |
|---|---|---|
Bcast |
Ein Prozess sendet die gleichen Daten an alle Ränge | Teilen von Anfangsbedingungen, Konstanten oder Konfigurationswerten |
Scatter |
Verteilt Teile eines Arrays über Ränge | Domänenzerlegung oder Workload-Partitionierung |
Gather |
Sammelt Daten aus allen Reihen | Sammeln von Teilausgaben |
Reduce |
Aggregiert Werte wie SUM, MAX oder MIN | Energien summieren oder globale Metriken sammeln |
Allreduce |
Aggregiert Werte und gibt jedem Rang das Ergebnis | Synchronisieren des globalen Status über alle Prozesse |
Verwenden Sie nach Möglichkeit kollektive Kommunikation. Es ist normalerweise einfacher und effizienter als die manuelle Koordinierung vieler Sends und Empfangen.
Wann sollte man das stattdessen verwenden?
Dask sitzt zwischen Serial Python und MPI. Dies ist nützlich, wenn die Arbeitslast peinlich parallel ist, z. B. die gleiche Simulation mit vielen Parametersätzen oder Anfangsbedingungen auszuführen.
from dask.distributed import Client
from dask import delayed
client = Client(n_workers=16, threads_per_worker=4)
def my_simulation(params):
# Your simulation logic here
return params["a"] * params["b"]
parameter_list = [
{"a": i, "b": 2.0}
for i in range(100)
]
tasks = [
delayed(my_simulation)(params)
for params in parameter_list
]
final_results = client.compute(tasks, sync=True)
print(final_results[:5])
Der Vorteil von Dask ist, dass Sie viele Workflows parallelisieren können, ohne die Simulation um Ränge und Kommunikatoren neu zu gestalten.
mpi4py vs. Dask: Welche wählen?
| Kriterium | MPI4PY | Dask |
|---|---|---|
| Lernkurve | Steiler, weil Sie Ränge und Kommunikatoren verstehen müssen | sanfter, weil es bekannte Python-Aufgabenmuster verwendet |
| Feinkornsteuerung | Ausgezeichnet, weil jede Kommunikation explizit ist | Eingeschränkt durch die Aufgabengrafik-Abstraktion |
| Speichereffizienz | Hoch, weil jeder Rang sein eigenes Stück speichert | gemäßigt, weil die Arbeitnehmer den Overhead erhöhen |
| Passend für | Großformatige PDE-Solver und domänenzerlegte Simulationen | Monte Carlo, Parameter-Sweeps und Datenanalyse |
| Beste Skala | Dichte Parallelität über viele Knoten | Schwache Skalierung über mäßige Arbeiterzählungen |
Beginnen Sie mit DASK für Prototyping und peinlich parallele Workloads. Gehen Sie zu MPI4PY, wenn Sie über viele Knoten hinweg eine strenge Kontrolle über Kommunikation, Speicher und Skalierung benötigen.
Stufe 3: Einreichen des Clusters
Nach lokalem Testen und Parallelisierung ist der nächste Schritt die Clusterbereitstellung. Die meisten Cluster verwenden einen Jobplaner. Slurm ist eine der häufigsten.
Das Slurm-Job-Skript
Ein Slurm-Job-Skript teilt dem Scheduler mit, welche Ressourcen Sie benötigen und wie der Code ausgeführt wird.
#!/bin/bash
#SBATCH --job-name=my-sim
#SBATCH --nodes=32
#SBATCH --tasks-per-node=4
#SBATCH --cpus-per-task=1
#SBATCH --mem=8GB
#SBATCH --time=04:00:00
#SBATCH --output=sim_output.%j
# Load required modules
module load Python/3.11
# Activate conda environment
eval "$(conda shell.bash hook)"
conda activate my-sim
# Run the MPI job
srun python my_simulation.py
Wichtige Parameter sind:
--nodes: Anzahl der Rechenknoten.--tasks-per-node: Anzahl der MPI-Aufgaben pro Knoten.--cpus-per-task: CPU-Kernen, die jeder Aufgabe zugewiesen werden.--time: Wanduhrgrenze. Jobs werden normalerweise gestoppt, wenn sie es überschreiten.--output: Ausgabedateimuster.%jFügt die Job-ID ein.
Laufen auf Clustern ohne Slurm
Nicht jeder Cluster verwendet Slurm. Häufige Alternativen sind:
- PBS oder Drehmoment, normalerweise unter Verwendung von
qsub. - LSF, normalerweise mit
bsub. - Kobalt oder andere ortsspezifische Scheduler.
Die Syntax der Jobeinreichung ändert sich, der Python-Code jedoch normalerweise nicht. MPI4PY und DASK sind meist planer-agnostisch, sobald sie korrekt gestartet wurden.
Interaktiver vs. Batch-Modus
Zum Debuggen sind interaktive Läufe nützlich:
srun --ntasks=4 --pty --time=02:00:00 python my_simulation.py
Verwenden Sie für die Produktion die Chargeneinreichung:
sbatch my_job_script.sh
Interaktive Sitzungen eignen sich gut für kurze Tests. Batch-Jobs sind besser für Langstrecken, Arbeitslasten über Nacht und Produktionssimulationen.
Der komplette Workflow: vom Laptop bis zur Produktion
Der Übergang vom lokalen Prototyp zur Clusterbereitstellung kann allmählich erfolgen. Die Kernsimulationslogik sollte stabil bleiben, während sich der Ausführungswrapper ändert.
Schritt 1: Entwickeln und testen Sie lokal
# my_simulation.py
import numpy as np
def simulate(initial_condition, params):
# Core simulation logic
result = np.sin(initial_condition) * params["factor"]
return np.sum(result)
# Local test
data = np.random.rand(1000)
result = simulate(data, {"factor": 1.5})
print(f"Local result: {result}")
Schritt 2: Parallelisierung hinzufügen
# my_simulation_mpi.py
import numpy as np
from mpi4py import MPI
comm = MPI.COMM_WORLD
rank = comm.Get_rank()
size = comm.Get_size()
def simulate(initial_condition, params):
# Core simulation logic stays the same
result = np.sin(initial_condition) * params["factor"]
return np.sum(result)
N = 10_000_000
workloads = [N // size for _ in range(size)]
for i in range(N % size):
workloads[i] += 1
my_start = sum(workloads[:rank])
my_end = my_start + workloads[rank]
my_data = np.random.rand(my_end - my_start)
local_result = simulate(my_data, {"factor": 1.5})
send_buffer = np.array([local_result])
receive_buffer = np.zeros(1)
comm.Reduce(send_buffer, receive_buffer, op=MPI.SUM, root=0)
if rank == 0:
print(f"Parallel result across {size} ranks: {receive_buffer[0]}")
Schritt 3: An den Cluster senden
Speichern Sie ein Job-Skript wie job_script.sh:
#!/bin/bash
#SBATCH --job-name=parallel-sim
#SBATCH --nodes=64
#SBATCH --tasks-per-node=8
#SBATCH --time=12:00:00
module load Python/3.11
eval "$(conda shell.bash hook)"
conda activate my-sim
srun python my_simulation_mpi.py
einreichen:
sbatch job_script.sh
Die Simulationslogik bleibt gleich. Der Wrapper ändert die Aufteilung und den Start der Arbeitslast.
Häufige Fallstricke und wie man sie vermeidet
Fallfall 1: Zu viele Daten übertragen
Das Senden großer Arrays von Rang 0 bis zu jedem Rang kann zu einem Kommunikationsengpass werden. Vermeiden Sie bei großen Simulationen, mehr Daten zu senden, als jeder Rang benötigt.
Bessere Optionen sind:
- Verwenden Sie
Scatteranstelle vonBcast, wenn jeder Rang nur eine Scheibe benötigt. - Verwenden Sie die Domänenzerlegung, damit jeder Rang eine Region der Simulation besitzt.
- Lassen Sie für Monte Carlo jeden Rang seine eigenen Zufallsstichproben generieren, anstatt sie zu senden.
Fallstricke 2: Überalloverallcluster-Ressourcen
Wenn Sie mehr Knoten als Ihr Code anfordern, können Sie Zeit für die Zuweisung von Abfällen verwenden und die Warteschlangenverzögerung erhöhen.
Beginnen Sie mit einem kleinen Testjob, z. B. 4 bis 8 Knoten. Beschleunigung messen. Skalieren erst, nachdem der Code nützliche parallele Effizienz zeigt.
Fallfall 3: Vergessen der Gil in gemischtem Code
Numpy, Scipy und kompilierte C- oder Fortran-Bibliotheken geben häufig die Python-Global-Interpreter-Sperre während schwerer numerischer Operationen frei. Reine Python-Threads bieten normalerweise nicht den gleichen Nutzen.
Wenn Ihr Workflow Python-Threads mit numerischen Bibliotheken mischt, hilft die Testskalierung sorgfältig, anstatt mehr Threads anzunehmen.
Fallfall 4: Umgebungsfehler auf dem Cluster
Ihr Laptop kann Python 3.11 verwenden, während das Clustermodul Python 3.9 bereitstellt. MPI4PY, MPI-Bibliotheken und kompilierte Abhängigkeiten können brechen, wenn Versionen nicht übereinstimmen.
Verwenden Sie eine Conda-Umgebung und dokumentieren Sie die genaue Einrichtung. Erstellen und testen Sie die Umgebung auf dem Cluster, bevor Sie große Jobs ausführen.
Entscheidungsleitfaden: Welche Parallelisierungsstrategie?
| Lage | Empfohlener Ansatz |
|---|---|
| Kleiner Datensatz und lokale Logiktests | Serienpython mit numpy |
| Einzelmaschine mit mehreren Kernen | Python multiprocessing oder Dask LocalCluster |
| 8–64 Arbeiter und peinlich parallele Aufgaben | Dask mit einem Cluster-Launcher |
| 64–1000 Knoten und domänenzerlegte PDEs | mpi4py mit expliziter Workload-Verteilung |
| Produktionssimulation auf über 1000 Knoten | MPI4PY, Scheduler-Job-Skripte und gesperrte Conda-Umgebung |
| Recherchecode ohne garantierte Reproduzierbarkeit | Dask, Conda-Umgebungsdatei und Job-Skript als Ausgangspunkt |
Ihr Parallelisierungstool sollte mit dem Maßstab und der Struktur des Problems übereinstimmen. Beginnen Sie kleiner als Sie denken und skalieren Sie nach der Messung der Leistung.
Verwandte Anleitungen
Zu verwandten Themen in wissenschaftlichen Simulations-Workflows:
- GPU-Beschleunigung für FIPY-Simulationen: Cupy- und Numba-Integrationshandbuch — Wenn parallele CPU ist nicht genug.
- Leistungsprofilierung und -optimierung für Python-PDE-Solver: Ein praktischer Leitfaden – Finden von Engpässen vor der Parallelisierung.
- Python-Debugging für wissenschaftlichen Code: Von Druckanweisungen bis Profiling – Debuggen von parallelen Simulationen.
Zusammenfassung und nächste Schritte
Bei HPC-Python-Workflows geht es um das Einpacken, nicht um das Umschreiben. Die gleiche Simulationslogik, die auf einem Laptop ausgeführt wird, kann auf vielen Kernen ausgeführt werden, wenn Sie die richtige Ausführungsstruktur erstellen.
Der Arbeitsablauf ist:
- Sperren Sie die Umgebung mit Conda, damit der Code konsistent über alle Systeme läuft.
- Fügen Sie Parallelität mit MPI4PY für eine feinkörnige verteilte Steuerung oder einen Stab für die Parallelität von Aufgaben hinzu.
- Senden Sie Jobs über Slurm, PBS, LSF oder den von Ihrem Cluster verwendeten Scheduler.
Der schwierigste Teil ist oft die Umgebung, nicht der Code. Investieren Sie frühzeitig in reproduzierbaren Setup, und parallele Simulationen lassen sich einfacher von Laptop zu Supercomputer skalieren.
Nächste Schritte
- Überprüfen Sie die aktuelle Simulation. Wenn es nur auf einem Laptop ausgeführt wird, erstellen Sie zunächst eine Conda-Umgebung und testen Sie einen kleinen Multi-Core-Lauf.
- Messen Sie vor der Skalierung. Führen Sie einen kleinen Cluster-Job aus, messen Sie die Beschleunigung und skalieren Sie erst dann auf die Produktionsgröße.
- Dokumentieren Sie die Einrichtung. Speichern Sie die Umgebungsdatei, das Auftragsskript, den Startbefehl und die erwartete Ausgabe.
Das Einrichten eines HPC-Python-Workflows erfordert das Verständnis von Python-Wissenschaftspaketen, paralleler Programmierung und Cluster-Infrastruktur. Wenn Sie Hilfe bei der Parallelisierung von MPI, der Jobplanung oder der Reproduzierbarkeit der Umgebung benötigen, kann unser Team skalierbare wissenschaftliche Python-Workflows unterstützen, einschließlich FIPY-basierter Simulationen und benutzerdefinierten Monte-Carlo-Motoren.