Unterstützung für Multi-Prozess- und Multi-Thread-Beschleunigung
Auf dieser Seite wird beschrieben, inwieweit die Beschleunigung durch Multithreading und Multiprocessing unterstützt qiskit-addon-sqd und auf welche Annahmen sich ein Entwickler im Bereich High-Performance-Computing (HPC) verlassen kann, wenn er dieses Paket in eine beschleunigte Arbeitslast integriert.
Die Annahme der Single-Thread-Ausführung
Sofern nicht anders angegeben, sind die APIs in diesem Paket dafür vorgesehen, von einem einzigen Thread aus aufgerufen zu werden. Es kann nicht garantiert werden, dass die von diesem Paket bereitgestellten High-Level-APIs reentrant sind, und Endbenutzer sollten sie nicht von einem anderen Thread als dem Hauptthread aus aufrufen.
Kollektive Ausführung mehrerer Prozesse
Dieses Paket unterstützt die kollektive Multiprozess-Beschleunigung im SPMD-Stil (Single-Program, Multiple-Data), bei dem das gesamte Programm als mehrere isolierte Prozesse gestartet wird, die mit expliziter globaler Synchronisation und Kommunikation untereinander ausgeführt werden (zum Beispiel mpirun -n 128 python my_program.py). Da sich der SPMD-Ansatz nahtlos in Job-Scheduler und andere parallele Software integrieren lässt und auf eine große Anzahl von Prozessen skalierbar ist, ist er das empfohlene Modell für hochleistungsfähige und groß angelegte Workloads und steht im Mittelpunkt dieser Seite.
Obwohl dieses Paket so konzipiert ist, dass es verschiedene Backends für die kollektive Ausführung unterstützt, ist die aktuelle Implementierung auf MPI ausgerichtet, die Standard-API für den Nachrichtenaustausch in HPC-Systemen. Unabhängig von der Implementierung geht dieses Paket davon aus, dass jeder Prozess von einem einzigen Thread gesteuert wird. Im Fall von MPI entspricht dies MPI_THREAD_FUNNELED oder weniger.
Wenn eine Funktion die kollektive Ausführung unterstützt, muss dies in ihrer Dokumentation angegeben sein. Wenn in der Dokumentation keine kollektive Ausführung erwähnt wird, verfügt die Funktion über keine kollektive Semantik und darf ausschließlich vom Steuerprozess aus aufgerufen werden.
Der konkrete Rückgabewert und die Synchronisationssemantik einer kollektiven Funktion sind in der Dokumentation der Funktion selbst beschrieben. Im Allgemeinen sollte aus dieser Dokumentation Folgendes hervorgehen:
- Ob die Funktion von jedem Prozess einzeln mit seinen eigenen lokalen Daten oder von allen Prozessen gemeinsam aufgerufen werden soll.
- Wie seine Argumente prozessübergreifend übereinstimmen müssen.
- Wie der Rückgabewert übermittelt wird – ob das Ergebnis nur im Steuerprozess vorliegt, ob alle Prozesse denselben Wert erhalten oder ob jeder Prozess einen Zeiger auf einen lokalen Teil einer verteilten Datenstruktur erhält.
Derzeit unterstützt nur eine Funktion den gemeinsamen Aufruf aus allen Prozessen: diagonalize_fermionic_hamiltonian(). Wenn der Eigensolver gemeinsam aufgerufen wird, ist dies der Schritt, an dem alle Prozesse beteiligt sind und ihren Beitrag leisten; daher kann eine Eigensolver-Implementierung jeden Prozess nutzen. Die übrigen Teile des Konfigurations-Wiederherstellungszyklus sind nicht verteilt implementiert und laufen ausschließlich im Steuerungsprozess (Rang 0).
Einige bestehende Implementierungen von Eigenwertlösern erfordern hingegen, dass das aufrufende Programm außerhalb einer MPI/SPMD-Umgebung ausgeführt wird, da sie intern ihre eigenen parallelen Prozesse starten und verwalten, beispielsweise durch den Aufruf von mpirun im Namen des Benutzers. Dieser Modus eignet sich für interaktives Arbeiten und die Arbeit mit Notizbüchern und wird weiterhin unterstützt; die Anforderungen an das aufrufende Programm sind in der API-Referenz der Funktion diagonalize_fermionic_hamiltonian() beschrieben.
Fehlerbehandlung in einem Multiprozess-Kontext
Eine Funktion, die von allen Prozessen gemeinsam aufgerufen wird, darf keine Ausnahme auslösen und auch nicht nur einen einzelnen Prozess abbrechen, da dies dazu führen würde, dass die verbleibenden Prozesse in ein Deadlock geraten oder in einen inkonsistenten Zustand geraten. Stattdessen folgt die Fehlerbehandlung einer „Fail-Stop“-Semantik für den gesamten Ausführungskontext: Bei einem Fehler muss die Implementierung darauf vorbereitet sein, den gesamten Ausführungskontext einheitlich abzubrechen (beispielsweise mithilfe von MPI_Abort).
Die API kann keine koordinierte Fehlermeldung oder gemeinsame Übermittlung von Ausnahmen prozessübergreifend gewährleisten, da MPI-Implementierungen solche Garantien nicht bieten. Eine Implementierung könnte zusätzlich versuchen, einen Fehler für alle beteiligten Prozesse sichtbar zu machen – beispielsweise indem jeder Prozess eine Ausnahme auslöst –, doch dies kann nur nach bestem Bemühen gewährleistet werden und darf nicht als Garantie für Korrektheit oder Wiederherstellung herangezogen werden.
Dies sind Eigenschaften, die eine kollektive Implementierung aufweisen darf, jedoch keine Garantien hinsichtlich einer bestimmten Eigenschaft. In diesem Paket ist der Eigensolver der einzige kollektive Schritt (siehe den vorangehenden Abschnitt); seine Standardimplementierung ist nicht verteilt, daher gelten diese Überlegungen für einen benutzerdefinierten, kollektiven Eigensolver, der vom Benutzer bereitgestellt wird.