IntelMPI
Intel MPI is the other MPI implementation available on Anunna, alongside OpenMPI. It arrives as part of the Intel toolchain, together with the Intel compilers and the Intel Math Kernel Library (MKL).
Everything general about MPI — that it runs one program as many cooperating processes, that it has to be built into the program, and when it is the right tool at all — is covered on Multi-Process Workflows. This page is the Intel-specific practice: which modules exist, how to launch, how the pinning controls differ from OpenMPI's, and one thing that catches people out on our AMD nodes.
Which should you use? Whichever your program was built with. That is not a dodge — mixing MPI implementations between build and run does not work, so in practice the software chooses for you. Where you genuinely have a choice, Intel MPI and MKL are the natural pairing for code that leans on Intel's maths libraries, and OpenMPI is the default for everything else here.
What is available on Anunna
Software is built with EasyBuild and grouped into buckets; a bucket has to be loaded before you can load anything from it. The Intel toolchain follows the same yearly generations as foss:
| Bucket | Toolchain | Contains |
|---|---|---|
2023 |
intel/2023a |
Intel compilers, Intel MPI, MKL |
2024 |
intel/2024a |
Intel compilers, Intel MPI, MKL |
2025 |
intel/2025a |
Intel compilers, Intel MPI, MKL |
intel/<year>a is the counterpart of foss/<year>a: one module that brings the whole stack — compilers, MPI, MKL and FFTW. There are leaner ways in, mirroring gompi on the foss side:
iimpi/<year>a— Intel compilers and Intel MPI, without MKL. The lean choice when you only need MPI.impi/<version>— the MPI library alone. In the 2025 bucket this isimpi/2021.15.0-intel-compilers-2025.1.1; note that Intel MPI's own version numbering (2021.x) does not track the toolchain year.
To see what a bucket actually offers, ask the module system — module key reads Lmod's cache, so it needs no bucket loaded and searches every bucket at once:
module key intel # the toolchains
module key impi # the MPI library on its own
Loading it
Bucket first, then the module — the same two steps as any other software here:
module load 2024 intel/2024a
Then confirm what you got, which is worth the habit:
module list # everything loaded, dependencies included
mpirun -version # which Intel MPI is on your PATH
If your software has its own module, load that instead and let it pull the matching MPI in as a dependency. A package built with this toolchain has a name ending in -intel-2024a or similar, which tells you what you are getting:
module load 2024
module load <YourSoftware>/<version> # Intel MPI arrives as a dependency
Keep a job inside one bucket. Do not load intel and foss in the same job — they provide competing compilers, MPI libraries and BLAS, and the result is unpredictable rather than merely slow. If you want a clean starting point, module reset returns you to the system defaults; the slurm module is sticky and survives it either way.
Running on a single node
Nothing runs on the login nodes. Put the work in a script, keep data on Lustre under $myScratch, and submit with sbatch.
#!/bin/bash -l
#SBATCH --job-name=impi_single
#SBATCH --nodes=1
#SBATCH --ntasks=64
#SBATCH --cpus-per-task=1
#SBATCH --mem-per-cpu=2G # per CORE, not per node
#SBATCH --time=01:00:00
#SBATCH --output=%x-%j.out
cd "$myScratch/my_simulation"
module load 2024 intel/2024a
mpirun -np ${SLURM_NTASKS} ./my_mpi_program --input data.nc
Two things carried over from OpenMPI, for the same reasons: pass ${SLURM_NTASKS} rather than hard-coding the rank count, and use --mem-per-cpu rather than --mem, which is a per-node request divided among all the ranks on that node.
Note the #!/bin/bash -l. A login shell is what initialises Lmod, so without it module load can fail quietly. For the same reason, never launch an MPI job with sbatch --wrap — that runs under dash, where module does not exist.
On one node the ranks talk through shared memory and there is nothing to configure.
Running across several nodes
Anunna's fabric is Omni-Path (OPA100), and both MPI implementations reach it through libfabric. The provider that works reliably here is psm2; the opx provider is broken for inter-node traffic on this hardware. So an inter-node job should name the provider rather than trust auto-selection:
#!/bin/bash -l
#SBATCH --job-name=impi_multi
#SBATCH --nodes=4
#SBATCH --ntasks-per-node=64
#SBATCH --cpus-per-task=1
#SBATCH --mem-per-cpu=2G
#SBATCH --time=02:00:00
#SBATCH --output=%x-%j.out
cd "$myScratch/my_simulation"
module load 2024 intel/2024a
mpirun -genv I_MPI_OFI_PROVIDER psm2 -genv FI_PROVIDER psm2 \
-np ${SLURM_NTASKS} ./my_mpi_program --input data.nc
To see what was actually selected — provider, pinning, rank layout — turn up Intel MPI's own diagnostics:
export I_MPI_DEBUG=4
That prints the chosen fabric and the rank-to-core map at start-up, and is the fastest way to confirm a setting took effect instead of assuming it did.
Getting variables to the other nodes
Ranks on remote nodes are started fresh, so any variable your program or the fabric depends on has to reach them. Intel MPI's controls are:
| Option | Effect |
|---|---|
-genv <value> |
Set one variable for all ranks. The explicit, always-safe form. |
-genvall |
Pass the whole launching environment to all ranks. This is the default. |
-genvlist <a,b,c> |
Pass only the named variables. |
-genvnone |
Pass nothing. |
In practice a plain export in your job script does reach every rank — tested on Anunna across two nodes.
It arrives whatever you do to stop it: restricting Intel MPI's own propagation with -genvlist PATH, which should have excluded the test variable, delivered it to both nodes anyway. OpenMPI 5.0.7 behaved identically — with -x, without it, and even with Slurm's own environment forwarding suppressed.
The practical reading is that inside a Slurm allocation both implementations get your environment to the ranks, by way of the process-management layer (PMIx and the launcher's job description) rather than the batch environment. On the OpenMPI side that was pinned down: removing the variable from mpirun's own environment is the only thing that stopped it arriving — see the OpenMPI page for the full result.
None of which is worth depending on in a job script. It held for the current toolchains and not necessarily for older ones, so say what your run needs explicitly with -genv and the question stops mattering.
-genv is still the habit worth keeping for anything the run depends on, as in the script above. It does not rely on which layer happens to be propagating, it states the intent in the launch line, it survives a site or script that restricts propagation later, and it is the first thing to try if a variable does mysteriously fail to arrive.
Intel MPI, MKL, and our AMD nodes
Most of Anunna's compute is AMD, and the Intel stack has a well-known wrinkle there. It is worth being precise about where it lives: the issue is in MKL, not in Intel MPI. But MKL arrives with the same intel/<year>a module, and "our Intel build is slow on the AMD nodes" is usually this, so it belongs on this page.
MKL checks the CPU vendor at run time. On a non-Intel CPU, kernels without specific Zen coverage can fall back to an SSE code path instead of using AVX2, leaving much of the vector width unused. This is still the behaviour in MKL 2025.x — it has not been quietly fixed. The old MKL_DEBUG_CPU_TYPE workaround is gone, removed back in MKL 2020 Update 1, so anything you read recommending it is out of date.
The current mitigation is a small shim, libfakeintel.so, LD_PRELOADed ahead of MKL. It overrides the vendor test (mkl_serv_intel_cpu_true) so that it returns true, and MKL then dispatches its AVX2 kernels:
export LD_PRELOAD=/path/to/libfakeintel.so
mpirun -np ${SLURM_NTASKS} ./my_mpi_program
Before assuming this is your problem, two things are worth checking, because often it is not. The shim only widens arithmetic, so it pays off when MKL is doing compute-bound Level-3 work such as DGEMM. It does nothing when MKL is only handling bandwidth-bound Level-1/2 vector kernels, and nothing at all if your library does its own arithmetic — PETSc, for instance, performs its sparse matrix–vector products itself, so MKL never sees the dominant cost of a Krylov solve.
MKL_VERBOSE=1 is the quick way to find out: it prints every MKL call, so you can see whether MKL is on your hot path at all and what it is being asked to do. Keep it off any run you intend to time — at high call counts the logging is itself a real cost.
If MKL turns out to suit your workload poorly on the AMD nodes, the foss toolchain's OpenBLAS-based BLAS is worth benchmarking against.
Advanced: pinning and rank placement
Intel MPI and OpenMPI express placement differently: OpenMPI takes command-line flags, Intel MPI reads I_MPI_* environment variables. The intents map across cleanly, so if you know one dialect this table gives you the other:
| Intent | Intel MPI | OpenMPI |
|---|---|---|
| One rank per physical core, no migration | I_MPI_PIN=1, I_MPI_PIN_DOMAIN=core |
--bind-to core
|
| Fill the node, neighbours adjacent | I_MPI_PIN_ORDER=compact |
--map-by core
|
| Under-subscribed, spread across all NUMA domains | I_MPI_PIN_ORDER=scatter |
--map-by numa --bind-to core
|
| One rank per chiplet / L3 slice | I_MPI_PIN_DOMAIN=cache3 |
--map-by l3cache --bind-to core
|
| Print the map actually used | I_MPI_DEBUG=4 |
--report-bindings
|
Three rules that matter more than the individual settings:
- Verify, do not assume. Print the map with
I_MPI_DEBUG=4before and after any change. Placement bugs do not announce themselves; the job runs and is simply slower than it should be. - One source of truth. Pin with the MPI launcher or with Slurm (
--cpu-bind,--distribution), never both. Two pinners fighting each other produce a nonsense map. - Spread suits memory-bound work. AMD sockets are built from 8-core chiplets, each with its own cache slice and share of the memory channels. A bandwidth-bound job saturates memory well before every core is busy, so spreading a reduced number of ranks across all NUMA domains keeps every memory channel active, while packing them together leaves most idle. Asking for ranks in multiples of 8 keeps chiplets evenly filled.
For the reasoning behind that last point, and the equivalent OpenMPI syntax, see the OpenMPI page.
See also
- OpenMPI — the other MPI here, and the default for most software.
- Multi-Process Workflows — whether MPI is the right shape for your work.
- Environment Modules — buckets, and finding the module you need.
- Compute Hardware Overview — what the nodes actually are.
- Batch Jobs — writing and submitting job scripts.
- Scheduler Overview (Slurm) — how the scheduler allocates what you ask for.