0.6.0
Loading...
Searching...
No Matches
Scalability non-regression testing with notus.py

The following guidelines describe how to check that the parallel performance of Notus does not regress, using the non-regression-scalability mode.

Introduction

Why test performance for regression?

Non-regression testing with notus.py protects the correctness of the code: it catches a change that alters a numerical result. But a change can leave every number perfectly correct and still be a serious problem — if it makes the code twice as slow, or if it quietly breaks parallel efficiency so that adding nodes no longer helps.

That kind of damage is invisible to a standard non-regression run. This is what the non-regression-scalability mode is for: it re-runs a scalability study and compares the measured speedup and parallel efficiency to values recorded when the code was known to perform well. A drop beyond tolerance turns the case to FAIL, so a performance regression is caught like any other bug.

How it differs from the scalability mode

Notus offers two complementary tools built on the same measurements:

Scalability study with notus.py non-regression-scalability
Purpose Measure performance Check performance against a reference
Study parameters Given on the command line Read from @ directives inside the NTS file
Result Speedup / efficiency tables Same tables, plus reference, difference and PASS/FAIL
Database Not used Efficiency compared to a per-machine database

The key practical difference is where the study is described. In scalability mode you pass --arithmetic, --cells-per-direction, and so on at every launch. Here, the case carries its own study definition, so the same test always runs the same way — which is exactly what a regression test requires.

Warning
CPU times depend on the hardware, so a scalability reference is only valid on the machine that produced it. This mode therefore uses a machine-specific database, which is not shared through Git, unlike the standard non-regression database.

Preparing the NTS file

The study is declared with #@ directives at the top of the file (Notus itself ignores these comment lines; they are read by notus.py):

  # @SCALABILITY weak
  # @DIMENSION 2
  # @PROGRESSION custom 1 2 4
  # @MPI_PROCS_PER_NODE 2
  # @THREADS 1
  # @CELLS_PER_CORE 16
  # @TIME_ITERATIONS 20
Directive Role
@SCALABILITY weak\|strong\|strong-node Type of study — same meaning as the submodes of Scalability study with notus.py.
@DIMENSION 2\|3 Dimension of the case.
@PROGRESSION TYPE ... Resource configurations to run (see below).
@MPI_PROCS_PER_NODE N MPI processes per node (not used by strong-node).
@THREADS N OpenMP threads per MPI process.
@CELLS_PER_CORE N Mesh size per core (weak) or total mesh (strong).
@TIME_ITERATIONS N Number of time steps performed by each run.

The @PROGRESSION directive accepts the same three forms as the command-line options of the scalability mode:

  # @PROGRESSION custom 1 2 4          → runs on 1, 2 and 4 nodes
  # @PROGRESSION arithmetic 4 4 4      → 4, 8, 12, 16, 20
  # @PROGRESSION geometric 4 4 4       → 4, 16, 64, 256, 1024

As in the scalability mode, the resolution is injected by the script, so the grid block must use the injected variables, and the time-iteration count must come from TIME_ITERATIONS:

  grid {
     grid_type regular;
     number_of_cells (N_CELLS_X, N_CELLS_Y);
     number_of_ghost_cells 2;
  }

  numerical_parameters {
     time_iterations TIME_ITERATIONS;
  }

The script injects SCALABILITY, NODES, N_PROCS, THREADS and TIME_ITERATIONS into each run, and derives the mesh from @CELLS_PER_CORE and the number of cores of the configuration.

Note
A scalability case may also declare @PARAMETER directives. The study is then repeated for every parameter combination, each one validated independently against the database — exactly like parametric cases in standard non-regression.

Running the tests

  ./notus.py non-regression-scalability --file-filter poiseuille_scalab

Since a meaningful study needs several nodes, this mode is normally run on a cluster in two passes:

  ./notus.py non-regression-scalability --slurm path/to/template --stage submit
  ./notus.py non-regression-scalability --slurm path/to/template --stage post-process

or with the generic dual-pass job manager:

  ./notus.py non-regression-scalability --dual-pass --stage submit       --batch-template path/to/template --batch-command "sbatch {BATCH_FILE}"
  ./notus.py non-regression-scalability --dual-pass --stage post-process --batch-template path/to/template --batch-command "sbatch {BATCH_FILE}"

See Submitting options for the dual-pass workflow and Notus usage on clusters for resource allocation.

Reading the output

Each configuration writes a .cputime file; the post-process step then prints one table per timer, with the measured values next to their references:

  Total time: 1 threads, 2 cores per node

  N cpu │ Nodes │    Mesh    │ total time │ Speedup │ Reference speedup │ Efficiency │ Reference efficiency │ Tolerance │ Difference │ Pass
  ──────┼───────┼────────────┼────────────┼─────────┼───────────────────┼────────────┼──────────────────────┼───────────┼────────────┼──────
    2   │   1   │ (16, 16)   │ 1.0000     │ 1.000   │ 1.000             │ 1.000      │ 1.000                │ 10 %      │ 0.000%     │ PASS
    4   │   2   │ (32, 16)   │ 0.5200     │ 1.923   │ 1.900             │ 0.962      │ 0.950                │ 10 %      │ 1.263%     │ PASS
    8   │   4   │ (32, 32)   │ 0.2700     │ 3.704   │ 3.600             │ 0.926      │ 0.900                │ 10 %      │ 2.889%     │ PASS

The verdict is computed on the efficiency, with a relative tolerance expressed in percent:

\[ \frac{|E_\text{ref} - E_\text{run}|}{E_\text{ref}} \le \frac{\text{tolerance}}{100} \]

A relative tolerance is used here — unlike the absolute tolerance of standard non-regression — precisely because timings fluctuate: the same machine will not reproduce the same time twice to the last digit. The default scalability tolerance is 1e-1, that is 10 %.

Statuses follow the usual convention: PASS when every configuration stays within tolerance, FAIL when at least one drifts too far, and ERROR when the case cannot be evaluated — most often ERROR (not in database) when no reference has been recorded yet on this machine.

Managing the reference database

Because references are machine-specific, the default database differs from the standard one: --database-path defaults to test_scal.db here, instead of test_cases/database/non-regression.db.

References are added exactly like standard ones, with the database mode — but pointing at the right results directory, since this mode writes to non-regression-scalability rather than non-regression:

  ./notus.py database --add poiseuille_scalab --directory non-regression-scalability --database-path test_scal.db

The tolerance can be set with --default-scalability-tolerance (default 1e-1). See Manipulate the non-regression database for the full description of the database operations, including --replace to refresh a reference after a legitimate performance change.

Warning
Never commit a scalability database produced on your own machine as if it were a shared reference: what it records is the performance of that hardware.

Useful options

  • --file-filter REGEX — select cases by file path (the test_cases/ prefix is optional).
  • --parameter-filter REGEX — select parameter combinations, matched against type:name=value.
  • --threads N — force the number of OpenMP threads, overriding @THREADS.
  • --cores-per-node N — force the number of cores per node. It requires --threads to be given as well.
  • --database-path PATH — database to compare against (default: test_scal.db).
  • -o, --directory DIR — output directory (default: non-regression-scalability).
  • --reload STATUS [...] — re-run only the cases in a given state, e.g. --reload FAIL ERROR.
  • --summary [STATUS ...] — print a summary of the current results and exit.

FAQ

Why is the tolerance relative here, but absolute in standard non-regression?

Because the quantities are of a different nature. A physical result is deterministic and can be compared to 1e-14; a CPU time is not reproducible to that precision — it varies with system load, memory traffic and the machine itself. Only a relative margin (a few percent) is meaningful.

My case reports "ERROR (not in database)".

No reference exists yet for this case on this machine. Run it once, check the numbers are sensible, then record them with ./notus.py database --add ... --directory non-regression-scalability.

Can I run this on my laptop?

You can run it to check the workflow, but not to obtain trustworthy references: on a single machine all MPI processes share the same memory bus, so the measured efficiency reflects memory saturation rather than the parallel behaviour of the code. Use a real cluster, with enough cells per core (see Notus usage on clusters).

Everything fails after I changed machine or compiler.

That is expected: the references belong to the machine that produced them. Re-record the references on the new machine rather than raising the tolerance.

Why are the study parameters in the NTS file rather than on the command line?

A regression test must be reproducible without the operator remembering a long command. Storing the study in the case guarantees that everyone runs the very same configuration, which is what makes the comparison to a stored reference meaningful.