Support for multi-process and multi-threaded acceleration

This page documents the extent to which qiskit-addon-sqd supports multi-threaded and multi-process acceleration, and the assumptions that a high-performance-computing (HPC) developer can rely on when integrating this package into an accelerated workload.

The single-threaded assumption

Unless otherwise specified, the APIs in this package are meant to be called from a single thread. High-level APIs exposed by this package are not guaranteed to be re-entrant, and end users should not invoke them from any thread other than the main thread.

Collective multi-process execution

This package supports collective multi-process acceleration in the single-program, multiple-data (SPMD) style, in which the entire program is launched as multiple isolated processes that run with explicit global synchronization and communication between them (for example, mpirun -n 128 python my_program.py). Because the SPMD style composes cleanly with job schedulers and other parallel software and scales to large process counts, it is the recommended model for high-performance and large-scale workloads, and it is the subject of this page.

Although this package is designed to accommodate different collective-execution backends, its current implementation targets MPI, the standard message-passing API for HPC systems. Regardless of implementation, this package assumes that a single thread controls each process. In the case of MPI, this corresponds to MPI_THREAD_FUNNELED or lower.

If a function supports collective execution, its documentation must say so. If the documentation does not mention collective execution, the function has no collective semantics and must be called from the control process alone.

The specific return-value and synchronization semantics of a collective function are documented with the function itself. In general, that documentation should make clear:

  • Whether the function is meant to be called independently by each process on its own local data, or collectively by all processes.

  • How its arguments must agree across processes.

  • How its return value is delivered—whether the result exists only on the control process, whether all processes receive the same value, or whether each process receives a handle to a local portion of a distributed data structure.

Only one function currently supports being called collectively from all processes: diagonalize_fermionic_hamiltonian(). When it is invoked collectively, the eigensolver step is where all processes participate and contribute work, so an eigensolver implementation can use every process. The remaining parts of the configuration-recovery loop have no distributed implementation and run on the control process (rank 0) only.

Some existing eigensolver implementations instead require the calling program to run outside an MPI/SPMD environment, because they launch and manage their own parallel processes internally, for example by invoking mpirun on the user’s behalf. That mode is convenient for interactive and notebook-based work and remains supported; its requirements on the calling program are described in the API reference of the diagonalize_fermionic_hamiltonian() function.

Error handling in a multi-process context

A function that is called collectively from all processes must not raise an exception or abort only a single process, because that would leave the remaining processes deadlocked or in an inconsistent state. Instead, error handling follows fail-stop semantics for the execution context as a whole: upon an error, the implementation must be prepared to abort the entire execution context collectively (for example, by using MPI_Abort).

The API cannot guarantee coordinated error reporting or collective delivery of exceptions across processes, because MPI implementations do not provide such guarantees. An implementation might additionally attempt to make an error visible to all participating processes—for example, by having each process raise an exception—but this can only be provided on a best-effort basis and must not be relied upon for correctness or recovery.

These are properties that a collective implementation is permitted to have, not guarantees about any particular one. In this package, the only collective step is the eigensolver (see the preceding section); its default implementation is not distributed, so these considerations apply to a custom, collective eigensolver supplied by the user.