Benchmark: preconditioner¶
Effect of the reference-material (Green's-function) preconditioner of Ladecký et
al., Appl. Math. Comput. 446 (2023) 127835, on the homogenization
example — -P reference vs. -P none. Both use the same fused
matvec kernel and are run to convergence (relative tolerance 1e-06).
Test machine & code version
- CPU: AMD Instinct MI300A Accelerator (192 logical cores)
- GPU: 4x AMD Instinct MI300A
- muGrid:
0.110.0-dirty— run 2026-06-29T12:13:40
Run configuration: 3D, single spherical inclusion, fused stiffness kernel, 6 load cases (iterations summed over all cases).
CG iterations vs. grid size¶
The central result: unpreconditioned CG iterations grow with the grid (the
condition number is O(h⁻²)), while the reference preconditioner makes the count
nearly independent of grid size. The count depends only on the operator and
preconditioner, not the device or the MPI decomposition, so this is measured on a
single CPU core.
| Preconditioner | 16³ (4k) | 24³ (14k) | 32³ (33k) | 48³ (111k) | 64³ (262k) |
|---|---|---|---|---|---|
| none | 765 | 1290 | 1593 | 2418 | 3225 |
| reference | 105 | 105 | 105 | 102 | 102 |
| CPU-core wall-time speedup | 5× | 9× | 10× | 15× | 19× |
(last row: unpreconditioned ÷ reference wall time on one CPU core)

Reference solve: device & MPI scaling¶
This mirrors the (unpreconditioned) homogenization benchmark, but for the reference-preconditioned solve: the same single-CPU-core / full-machine-MPI-CPU / single-GPU / multi-GPU comparison, across 3D grid sizes. Because the iteration count is grid-independent, this isolates the per-iteration cost — and each preconditioned iteration applies a forward/inverse FFT pair, so it is where the FFT-engine paths matter: the native N-D transform on the GPU, and the slab MPI decomposition on multi-rank runs.
Each configuration is swept to the largest grid that still fits in memory: the
first size that runs out of memory is recorded as OOM in the table and
dropped from the plot, and larger sizes for that configuration are not attempted.
| Configuration | 16³ (4k) | 24³ (14k) | 32³ (33k) | 48³ (111k) | 64³ (262k) | 96³ (885k) | 128³ (2.1M) | 192³ (7.1M) | 256³ (16.8M) | 384³ (56.6M) | 512³ (134.2M) | 768³ (453.0M) |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| CPU (1 core) | 0.279 | 0.88 | 2.04 | 7.16 | 17.4 | 60.9 | 153 | — | — | — | — | — |
| CPU (92 cores, MPI) | — | — | — | 0.22 | 0.345 | 1.69 | 3.5 | 12.8 | 22.8 | 86.2 | 195 | — |
| GPU (1 device) | 0.605 | 0.722 | 0.728 | 0.664 | 0.65 | 1.09 | 1.99 | 5.89 | 13.2 | OOM | — | — |
| GPU (4 devices, MPI) | — | 3.07 | — | 1.33 | — | 2.1 | 1.73 | 5.12 | 10.2 | 36.6 | 75.7 | OOM |
(values are solve time in seconds, run to convergence; OOM = ran out of
memory)

The preconditioner parallelises cleanly: it is applied in Fourier space by the
FFT engine, which owns its MPI decomposition, and the per-mode block solve is
rank-local. -P reference gives identical iteration counts and homogenised
stiffness in serial and under MPI, so the single CPU core is quickly left behind
and the largest grids are reached by MPI domain decomposition across all CPU
cores or across several GPUs (one rank per device, round-robin).
All data points live in the shared benchmark database benchmarks/results.csv
(date, code version, machine, parameters, results). This page is generated by
examples/benchmark_homogenization_preconditioner.py; re-render from the
database with --render-only, or run a fresh measurement that appends a new
dated row set: