The Quantum Exact Simulation Toolkit v4.0.0
Loading...
Searching...
No Matches
🛠️  Compile

QuEST can be compiled with CMake to make a standalone executable, or an exported library, or a library installed on the system. Compiling is configured with variables supplied by the -D flag to the CMake CLI. This page details how to compile QuEST for varying purposes and hardwares.

TOC:

See also:

  • cmake.md for the full list of passable compiler variables.
  • compilers.md for a list of compatible and necessary compilers.
  • qtechtheory.org for help downloading the necessary compilers.
  • launch.md for a guide to executing the compiled application.
Remarks
QuEST's Github Actions regularly test QuEST compilation using a broad combination of deployment settings; presently 108 combinations! The compile.yml workflow can serve as a concrete example of how to compile QuEST in a sanitised, virtual setting.

Basic

Compilation is a two-step process which can generate lots of temporary files and so should be performed in a build/ folder to avoid clutter. From the QuEST/ root, run (in terminal):

# configure
cmake -B build
# build
cmake --build build

or more safely from within the QuEST/build/ folder (as we from here assume):

# configure
cmake ..
# build
cmake --build .
Remarks
Speed up building by passing -j or --parallel
cmake --build . --parallel

With no additional arguments, these commands compile min_example.cpp into an executable min_example in the build folder which can be run via

./min_example

You should expect to see

QuEST execution environment:
[precision]
qreal.................double (8 bytes)
qcomp.................std::__1::complex<double> (16 bytes)
qindex................long long int (8 bytes)
...

How boring! We must pass additional arguments in order to link QuEST to our own code; build other examples; run the unit tests; enable compiler optimisations; enable hardware acceleration; and integrate additional libraries and backends.


Optimising

QuEST's source code is careful to enable a myriad of optimisations such as inlining, loop unrolling, auto-vectorisation and cache optimisations. To utilise them fully, we must instruct our compilers to enable them; like we might do with the -O3 flag when invoking a compiler like gcc directly.

On most platforms (with the exception of Windows), this is automatic with the commands above, but can otherwise be forced by specifying CMAKE_BUILD_TYPE at configure time:

# configure
cmake .. -D CMAKE_BUILD_TYPE=Release

When compiling on Windows however (using Visual Studio), or otherwise using a "[_multi-config generator_](https://cmake.org/cmake/help/latest/manual/cmake-generators.7.html#other-generators)", we must always supply the build type at build time via config:

# build
cmake --build . --config Release

Otherwise, such generators may default to the Debug configuration which can produce executables over 10x slower than Release!

It is always safe to specify either flag even when not used, so one can gaurantee optimisations are enabled by cautiously performing:

# configure
cmake .. -D CMAKE_BUILD_TYPE=Release
# build
cmake --build . --config Release

Read more about CMake generator configurations here.

Remarks
Re-configuring a previously compiled CMake project will preserve any manually set variables so they do not need to be re-specified. Ergo subsequently running
# configure
cmake ..
will still incorporate -D CMAKE_BUILD_TYPE=Release as previously set.
Warning
The above tip does not apply to re-building, for which the --config Release must be re-specified (on Windows)

Linking

QuEST can be pre-compiled and later linked to other binaries, or compiled directly alongside the user's source code. We focus on the latter use-case, common among scientists when writing simulation scripts. Users seeking to integrate QuEST into larger stacks are likely already familiar with linking libraries through CMake and should check out cmake.md directly.

To compile a C or C++ file such as

/* myfile.c */
#include "quest.h"
int main() {
finalizeQuESTEnv(); // changed my mind
return 0;
}
void finalizeQuESTEnv()
void initQuESTEnv()

simply specify variables USER_SOURCE and OUTPUT_EXE at configure time:

# configure
cmake .. -D USER_SOURCE=myfile.c -D OUTPUT_EXE=myexec

where

  • myfile.c is your C source file (or myfile.cpp if using C++).
  • myexec is the output executable name, which will be saved in build.

To compile multiple dependent files, such as

/* myfile.cpp */
#include "quest.h"
extern void myfunc();
int main() {
myfunc();
return 0;
}
/* otherfile.cpp */
#include <stdio.h>
void myfunc() {
printf("hello quworld!\n");
}

simply separate them by ; in USER_SOURCE, wrapped in quotations:

# configure
cmake .. -D USER_SOURCE="myfile.cpp;otherfile.cpp" -D OUTPUT_EXE=myexec

Building then proceeds as normal, e.g.

# build
cmake --build . --parallel --config Release

and the executable can thereafter be run (from within build) via

./myexec

You can pass compiler and linker flags needed by your source files through the CMAKE_C_FLAGS, CMAKE_CXX_FLAGS and CMAKE_EXE_LINKER_FLAGS CMake flags as detailed in the below section. Note however that if your configuration becomes complicated or your source code requires different C/C++ standards than the QuEST source, you should consider separately compiling QuEST then linking it to your project as a library!


Configuring

Precision

QuEST's numerical precision can be configured at compile-time, informing what type, and ergo how many bytes, are used to represent each qreal (a floating-point real number) and qcomp (a complex amplitude). This affects the memory used by each Qureg, but also the user-facing qreal and qcomp types, as detailed below. Reducing the precision accelerates QuEST at the cost of worsened numerical accuracy.

Precision is set at configure-time using the FLOAT_PRECISION cmake variable, taking on the values 1, 2 (default) or 4. For example

# configure
cmake .. -D FLOAT_PRECISION=1

The values inform types:

Value Precision qreal size C (gcc) qcomp C (msvc) qcomp C++ qcomp size
1 Single float 4 bytes float _Complex _Fcomplex std::complex<float> 8 bytes
2 Double double 8 bytes double _Complex _Dcomplex std::complex<double> 16 bytes
4 Quadruple* long double <= 16 bytes long double _Complex _Lcomplex std::complex<long double> <= 32 bytes
Warning
While the size of float and double are fixed by IEEE 754 format, the size of the long double is platform and compiler dependent, and not necessarily a genuine quadruple-precision float. For example, the size of long double in Clang can be set to 64, 80 or 128 bits at compile-time. Never hardcode the size; always use sizeof!
Note
When enabling GPU-acceleration, the precision must be set to 1 or 2 since GPUs do not support quad precision.

Compilers

If multiple compilers are installed, you can choose which to use to compile your C and C++ sources (the latter including the QuEST source) with respective configure-time commands:

# configure
cmake .. -D CMAKE_C_COMPILER=gcc -D CMAKE_CXX_COMPILER=g++

replacing gcc and g++ with e.g. clang, cl, icc, ibm-clang, or aliases for specific versions like gcc-8.5.

These compilers will also be used as the host compilers (around which bespoke compilers wrap) when enabling GPU-acceleration or distribution.

Important
It is not correct to specify GPU and MPI compilers, like nvcc or mpicc, via the above flags. See the respective GPU and MPI sections.

Flags

Additional flags needed by your files can be passed to the C and C++ compilers, and the linker (respectively), at configuration time via

For example,

# configure
cmake .. -D CMAKE_C_FLAGS="-D MYMACRO=5" -D CMAKE_EXE_LINKER_FLAGS="-lm"

Such flags are listed in cmake.md. However, if your configuration is any more complicated or your source code requires different C/C++ standards than the QuEST source, you should consider separately compiling QuEST then linking it to your source code as a library!

QuEST itself accepts a variety of its preprocessors (mostly related to testing) to be overriden by compiler flags, passed through custom CMake variables, as detailed in cmake.md.


Examples

To compile all of QuEST's examples/, use

# configure
cmake .. -D BUILD_EXAMPLES=ON
# build
cmake --build .

The executables will be saved in the (current) build directory, in a sub-directory structure mimicking the examples/ folder. They can be run by e.g.

./examples/matrices/cpp_initialisation

as elaborated upon in launch.md.


Tests

v4

To compile QuEST's latest unit and integration tests, use

# configure
cmake .. -D ENABLE_TESTING=ON
# build
cmake --build .

This will compile an executable tests in subdirectory build/tests/, which can be run as explained in launch.md.

v3

QuEST's deprecated v3 API has its own unit tests which can be additionally compiled (except on Windows) via

# configure
cmake .. -D ENABLE_TESTING=ON -D ENABLE_DEPRECATED_API=ON
# build
cmake --build .

and run as explained in launch.md.


Multithreading

Multithreading allows multiple cores of a CPU, or even multiple connected CPUs, to cooperatively perform and ergo accelerate QuEST's expensive functions. Practically all modern computers have the capacity for, and benefit from, multithreading. Note it requires that the CPUs have shared memory (such as through NUMA) and so ergo live in the same machine. CPUs on different machines, connected via a network, can be parallelised over using distribution.

QuEST uses OpenMP to perform multithreading, so accelerating QuEST over multiple CPUs or cores requires a compiler integrated with OpenMP. This is true of almost all major compilers - see a list of tested compilers in compilers.md.

Important
Using Clang on MacOS requires use of the libomp library, obtainable via Homebrew:
brew install libomp
and which must be exposed to Clang prior to compilation via
export OpenMP_ROOT=$(brew --prefix)/opt/libomp

To compile with multithreading, simply enable it during configuration:

# configure
cmake .. -D ENABLE_MULTITHREADING=ON
# build
cmake --build .

This is in fact the default behaviour!

The number of threads over which to parallelise QuEST's execution is chosen through setting environment variables, like OMP_NUM_THREADS, immediately before execution. See launch.md for a general guide on multithreaded deployment.


GPU-acceleration

QuEST's core functions perform simple mathematical transformations on very large arrays, and are ergo well suited to parallelisation using general purpose GPUs. This involves creating persistent memory in the GPU VRAM which mirrors the ordinary CPU memory in RAM, and dispatching the transformations to the GPU, updating the GPU memory. The greater number of cores and massive internal memory bandwidth of the GPU can make this extraordinarily faster than using multithreading.

QuEST supports parallelisation using both NVIDIA GPUs (using CUDA) and AMD GPUs (using HIP). Using either requires obtaining a specialised compiler and passing some GPU-specific compiler flags.

NVIDIA

‍TODO!

  • CUDA-compatible GPGPU
  • nvcc compiler
  • nvidia-smi
  • minimum compute-capability

Check the CUDA compiler is installed correctly via

nvcc --version

To compile your QuEST application with CUDA-acceleration, specify both

# configure
cmake .. -D ENABLE_CUDA=ON -D CMAKE_CUDA_ARCHITECTURES=$CC

where $CC is your GPU's compute capability (excluding the full-stop) which you can look up here. For example, compiling for the NVIDIA A100 looks like:

# configure
cmake .. -D ENABLE_CUDA=ON -D CMAKE_CUDA_ARCHITECTURES=80
Attention
Setting the wrong compute capability will cause silently erroneous results. Always run the unit tests after compiling for the first time to confirm it was set correctly.

Building then proceeds as normal, e.g.

# build
cmake --build . --parallel

The compiled executable can be run like any other, though the GPU behaviour can be prior configured with environment variables. See launch.md for a general guide on GPU-accelerated deployment.

AMD

‍TODO!

  • ROCm
  • ENABLE_HIP
  • CMAKE_HIP_ARCHITECTURES

To compile your QuEST application with HIP-acceleration, specify both

# configure
cmake .. -D ENABLE_HIP=ON -D CMAKE_HIP_ARCHITECTURES=$TN

where $TN is your AMD GPU's LLVM target name. You can look this up here, or find the names of all of your local GPUs by running the ROCM agent enumerator command, i.e.

rocm_agent_enumerator -name

For example, compiling for the AMD Instinct MI210 accelerator looks like:

# configure
cmake .. -D ENABLE_HIP=ON -D CMAKE_HIP_ARCHITECTURES=gfx90a
Attention
Setting the wrong LLVM target name can cause silently erroneous results. Always run the unit tests after compiling for the first time to confirm it was set correctly.

Building then proceeds as normal, e.g.

# build
cmake --build . --parallel

The compiled executable can be run like any other, though the GPU behaviour can be prior configured with environment variables. See launch.md for a general guide on GPU-accelerated deployment.


cuQuantum

When compiling for NVIDIA GPUs, you can choose to optionally enable cuQuantum. This will replace some of QuEST's custom GPU functions with cuStateVec routines which are likely to use tailored optimisations for your particular GPU and ergo run faster.

Important
cuStateVec is presently only supported on Linux, with CUDA 11 or 12, and modern GPUs with a compute capability equal or above 7.0 (Volta, Turing, Ampere, Ada, Hopper, Blackwell). Check the updated requirements here.

Using the cuQuantum backend requires separately downloading cuStateVec, as detailed here. Note it is not necessary to download cuTensorNet and cuDensityMatrix which are not used. We recommend downloading...

After download and installation, and before compiling, you must set the CUQUANTUM_ROOT environment variable to the cuQuantum download location:

export CUQUANTUM_ROOT=/path/to/cuquantum-folder

Compilation is then simple; we specify ENABLE_CUQUANTUM in addition to the above GPU CMake variables. For example

# configure
cmake .. -D ENABLE_CUDA=ON -D CMAKE_CUDA_ARCHITECTURES=80 -D ENABLE_CUQUANTUM=ON
# build
cmake --build . --parallel

No other changes are necessary, nor does cuQuantum affect hybridising GPU acceleration and distribution. Launching the executable is the same as in the above section. See launch.md.


Distribution

Because statevectors grow exponentially with the number of simulated qubits, it is easy to run out of memory. In such settings, we may seek to use distribution whereby multiple cooperating machines on a network each store a tractable partition of the state. Distribution can also be useful to speed up our simulations, when the benefit of additional parallelisation outweighs the inter-machine communication penalties.

Enabling distribution requires compiling QuEST with an MPI compiler, such as those listed in compilers.md. Test your compiler is working via

mpicxx --version

‍This command may differ on Intel MPI compilers; try mpiicpc.

Compiling QuEST's distributed backend is as simple as

# configure
cmake .. -D ENABLE_DISTRIBUTION=ON
# build
cmake --build . --parallel

Note that distributed executables are launched in a distinct way to the other deployment mods, as explained in launch.md,


Multi-GPU

‍TODO!

  • CUDA-aware MPI
  • UCX
  • launch flags
  • checking via reportenv