Gyredocs
Depth Practice
API

Gyre/Software Engineering

Performance

In one minuteMeasured kernel throughput on CPU and GPU, the cost of derivatives relative to a residual evaluation, and where time goes in practice — compilation for small problems, Krylov iterations for large ones.

Written at L2 Practice goes to L4 Theory 2082 words Markdown
  • Small cases are compile-bound. A new combination of mesh size and polynomial degree costs tens of seconds of JIT compilation before the first iteration; the solve itself is then short. A persistent cache and warm sessions exist to pay that once.
  • Derivatives cost a small multiple of a residual evaluation — the measured ratios are in the tables below.
  • CPU for small problems, GPU for large ones. The service chooses per job (GPU above 60 000 degrees of freedom if one is present); GYRE_DEVICE chooses per process.
  • Memory, not time, limits large cases: the Krylov basis and the element-block inverses.
  • Everything below is a measurement on one host, recorded with its load; none of it is a portable performance guarantee, and no scaling study on many cores or devices exists.

The numbers on this page are measurements recorded by the verification pipeline, not portable performance guarantees. A record written by the current timing studies states the device it was taken on and the load of the host during the measurement (the host table), with a flag for a host that was busy; read the timings together with that table and the run notes. Each timing is the minimum over at least 7 interleaved rounds, and the timing studies run after all other studies of an invocation, one at a time. On a host without a GPU the GPU study is skipped: gyre verify writes no GPU record and leaves a performance_gpu.json of an earlier invocation in the output directory untouched. The blocks below then show that earlier record, marked “Earlier run”, with the checks and tables it was written with (a block for a table the record does not contain reports it as missing); with no record in the directory they show no data.

Kernel throughput#

PassedKernel throughput (NVIDIA GeForce RTX 3060 Ti)2 of 2 checks passed · 103.5 s · quick suite

Wall-clock cost of the three kernels everything else is built from — the weak residual, a Jacobian-vector product (forward mode, including its primal sweep) and a vector-Jacobian product (reverse mode) — on warped periodic meshes, after compilation. Throughput is degrees of freedom per second for one residual evaluation. Each kernel is called 2 times before it is timed; the three kernels of a configuration are then timed in alternation, one call of each per round, and each timing is the minimum over the rounds: at least 7, continued until 5 consecutive rounds lower no minimum by more than 2 % (at most 60 rounds, and at most 20 s of timed calls per configuration beyond the first 7 rounds). Other work on the machine can only lengthen a call, so the minimum is the statistic least affected by it, and alternation spreads a burst of load over all three kernels. The ratio of the median to the minimum of the rounds is tabulated as a measure of the disturbance. Timings are measurements on the device and under the host load recorded with them (second table), not portable performance guarantees. Algorithmic differentiation bounds the operation count of a Jacobian-vector product by about 2.5 and of a vector-Jacobian product by about 4 to 5 residual evaluations; the two checks are regression guards about three times above those counts, leaving room for memory traffic, not derived limits.

  • JVP cost / residual cost (worst case)2.392expected < 8 (regression guard, about 3 x the operation-count bound of forward mode; measured while other work loaded the machine, see the notes)pass
  • VJP cost / residual cost (worst case)3.088expected < 12 (regression guard, about 3 x the operation-count bound of reverse mode; measured while other work loaded the machine, see the notes)pass

Run notes The timings were taken on a busy machine (the GPU was 17 % utilized when the study started). The reported timings are minima over interleaved rounds, which other work can raise but not lower: they are upper bounds of the undisturbed kernel costs. The median of the rounds was up to 2.20 times the minimum, which shows the size of the disturbance. The ratios of the minima, which the two checks bound from above, are less affected than the timings themselves but may still be; the checks are marked accordingly, and the throughput figures should be read as lower bounds. Timings are minima over 7 to 32 interleaved rounds.

gyre 0.4.0 · 39979fcf90 · · gpu · NVIDIA GeForce RTX 3060 Ti · quick suite · record (JSON)Same code as this build

Residual throughput on NVIDIA GeForce RTX 3060 TiKernel throughput (NVIDIA GeForce RTX 3060 Ti)
Data table
seriesdegrees of freedommillion DOF / s
2-D Euler, p = 3163848.19282
2-D Euler, p = 36553633.3211
2-D Euler, p = 326214479.9196
2-D Euler, p = 31048576114.489
2-D Euler, p = 32359296208.967
2-D Navier–Stokes, p = 36553634.8781
2-D Navier–Stokes, p = 326214473.241
2-D Navier–Stokes, p = 3104857660.567
3-D Euler, p = 26912020.6736
3-D Euler, p = 223328051.1165
3-D Euler, p = 2186624042.0342

gyre 0.4.0 · 39979fcf90 · · gpu · NVIDIA GeForce RTX 3060 Ti · quick suite · record (JSON)Same code as this build

Kernel timingsKernel throughput (NVIDIA GeForce RTX 3060 Ti)
caseelementsDOFresidual [ms]JVP [ms]VJP [ms]residual throughput [M DOF/s]JVP / residualVJP / residualtimed roundsmedian / minimum (worst kernel)
2-D Euler, p = 316x16163841.99981.92261.86768.192820.9613960.93389381.07115
2-D Euler, p = 332x32655361.96682.02783.008233.32111.031011.52949131.55257
2-D Euler, p = 364x642621443.28014.32776.911679.91961.319382.10713262.02972
2-D Euler, p = 3128x12810485769.1587921.911328.2789114.4892.392383.0876271.41766
2-D Euler, p = 3192x192235929611.290321.180234.8137208.9671.875963.08351142.07155
2-D Navier–Stokes, p = 332x32655361.8791.86562.744934.87810.9928681.46083151.86878
2-D Navier–Stokes, p = 364x642621443.57925.22047.6187973.2411.458542.12863142.19849
2-D Navier–Stokes, p = 3128x128104857617.312726.604545.682260.5671.53672.63866101.40094
3-D Euler, p = 28x8x8691203.34344.14974.383220.67361.241161.31181.24168
3-D Euler, p = 212x12x122332804.563699.0987810.146451.11651.993732.22328321.82769
3-D Euler, p = 224x24x24186624044.398260.468896.499242.03421.361972.173571.46526

gyre 0.4.0 · 39979fcf90 · · gpu · NVIDIA GeForce RTX 3060 Ti · quick suite · record (JSON)Same code as this build

Device and host load during the measurementKernel throughput (NVIDIA GeForce RTX 3060 Ti)
deviceplatformCPUsCPU share of other processesload average at start (1, 5, 15 min)load average at endGPU utilization at start [%]busy
NVIDIA GeForce RTX 3060 Tigpu80.09496.72 / 9.29 / 11.392.71 / 7.12 / 10.3817yes

gyre 0.4.0 · 39979fcf90 · · gpu · NVIDIA GeForce RTX 3060 Ti · quick suite · record (JSON)Same code as this build

PassedKernel throughput (cpu)2 of 2 checks passed · 123.8 s · quick suite

Wall-clock cost of the three kernels everything else is built from — the weak residual, a Jacobian-vector product (forward mode, including its primal sweep) and a vector-Jacobian product (reverse mode) — on warped periodic meshes, after compilation. Throughput is degrees of freedom per second for one residual evaluation. Each kernel is called 2 times before it is timed; the three kernels of a configuration are then timed in alternation, one call of each per round, and each timing is the minimum over the rounds: at least 7, continued until 5 consecutive rounds lower no minimum by more than 2 % (at most 60 rounds, and at most 20 s of timed calls per configuration beyond the first 7 rounds). Other work on the machine can only lengthen a call, so the minimum is the statistic least affected by it, and alternation spreads a burst of load over all three kernels. The ratio of the median to the minimum of the rounds is tabulated as a measure of the disturbance. Timings are measurements on the device and under the host load recorded with them (second table), not portable performance guarantees. Algorithmic differentiation bounds the operation count of a Jacobian-vector product by about 2.5 and of a vector-Jacobian product by about 4 to 5 residual evaluations; the two checks are regression guards about three times above those counts, leaving room for memory traffic, not derived limits.

  • JVP cost / residual cost (worst case)3.035expected < 8 (regression guard, about 3 x the operation-count bound of forward mode; measured while other work loaded the machine, see the notes)pass
  • VJP cost / residual cost (worst case)7.329expected < 12 (regression guard, about 3 x the operation-count bound of reverse mode; measured while other work loaded the machine, see the notes)pass

Run notes The timings were taken on a busy machine (other processes used 12 % of the capacity of the 8 CPUs while the study ran). The reported timings are minima over interleaved rounds, which other work can raise but not lower: they are upper bounds of the undisturbed kernel costs. The median of the rounds was up to 1.85 times the minimum, which shows the size of the disturbance. The ratios of the minima, which the two checks bound from above, are less affected than the timings themselves but may still be; the checks are marked accordingly, and the throughput figures should be read as lower bounds. Timings are minima over 11 to 20 interleaved rounds.

gyre 0.4.0 · 39979fcf90 · · cpu · quick suite · record (JSON)Same code as this build

Residual throughput on cpuKernel throughput (cpu)
Data table
seriesdegrees of freedommillion DOF / s
2-D Euler, p = 3163844.62864
2-D Euler, p = 3655366.77207
2-D Euler, p = 32621442.38102
2-D Navier–Stokes, p = 3655362.87552
2-D Navier–Stokes, p = 32621441.15204
3-D Euler, p = 2691203.02713
3-D Euler, p = 22332801.57031

gyre 0.4.0 · 39979fcf90 · · cpu · quick suite · record (JSON)Same code as this build

Kernel timingsKernel throughput (cpu)
caseelementsDOFresidual [ms]JVP [ms]VJP [ms]residual throughput [M DOF/s]JVP / residualVJP / residualtimed roundsmedian / minimum (worst kernel)
2-D Euler, p = 316x16163843.53976.400310.69234.628641.808153.02068111.53423
2-D Euler, p = 332x32655369.677429.368970.92146.772073.034797.32856111.85188
2-D Euler, p = 364x64262144110.097188.667420.1192.381021.713643.81589111.11999
2-D Navier–Stokes, p = 332x326553622.79162.038290.54242.875522.722053.97273131.60486
2-D Navier–Stokes, p = 364x64262144227.548355.136591.1781.152041.560712.59804121.28127
3-D Euler, p = 28x8x86912022.833549.854998.99363.027132.183414.33546201.48412
3-D Euler, p = 212x12x12233280148.557258.628353.321.570311.740942.37834151.30874

gyre 0.4.0 · 39979fcf90 · · cpu · quick suite · record (JSON)Same code as this build

Device and host load during the measurementKernel throughput (cpu)
deviceplatformCPUsCPU share of other processesload average at start (1, 5, 15 min)load average at endGPU utilization at start [%]busy
cpucpu80.11849.36 / 11.72 / 12.437.04 / 9.44 / 11.46—yes

gyre 0.4.0 · 39979fcf90 · · cpu · quick suite · record (JSON)Same code as this build

A Jacobian–vector product (forward mode, including the primal sweep it needs) and a vector–Jacobian product (reverse mode) each cost a small multiple of one residual evaluation — the columns JVP / residual and VJP / residual of the tables. The two checks of each study bound those ratios as regression guards, about three times above the operation counts of forward and reverse mode; they are not derived limits. Inside the Krylov solver the primal sweep is shared by all iterations (linearise inside the solve), so the per-iteration cost is lower than the stand-alone JVP measured here.

Problem size Dominant cost What helps
small (up to ~10⁴–10⁵ DOF) JIT compilation, tens of seconds per new mesh/order persistent cache, sessions, CPU execution
medium Krylov iterations × tangent sweeps; block-inverse set-up lagging the block inverses (solver.precond_lag), GPU; on wall-resolved meshes the line smoother for solves to a tight tolerance (measured on two small cases); p-multigrid lowers the Krylov iteration count; its net effect on operator applications and on compiled wall time is measured per case, not claimed in general
large memory: Krylov basis and block inverses (K⋅K \cdot block size²) solver.precond_precision: single halves the blocks and the working memory of their assembly without changing the answers (comparison, largest solve); lower krylov_dim; more device memory

The service picks the device per job from the estimated number of degrees of freedom (GPU above 60 000 if one is present).

The element blocks of the preconditioner come from batched Jacobian-vector products: one tangent per column of a block, for all elements at once. One tangent of the flux-differencing residual holds p+1p + 1 two-point fluxes per node and direction, so the working memory of a batch grows with

batch×Ndof×(p+1)×d,\text{batch}\times N_\text{dof}\times(p+1)\times d ,

and in three dimensions at higher degree a batch of all columns no longer fits: at p=4p = 4 on 135 000 unknowns the assembly asked an 8 GB device for 8.1 GiB. The batch is therefore sized to keep that product below a budget (gyre.implicit.BLOCK_BATCH_BUDGET, 6.4×1076.4\times10^{7}; about 1 GiB for the largest single allocation, by measurement on that device), and the columns are processed in several sweeps. The blocks do not depend on the batch; only memory and time do. GYRE_BLOCK_BATCH_BUDGET raises the budget on a device with more memory. The blocks themselves, K (nvar(p+1)d)2K\,(n_\text{var}(p+1)^d)^2 numbers and as many again for their inverses, remain the floor: 675 MB each at p=4p = 4 for 216 hexahedra.

Every kernel is a sequence of element-wise operations and small tensor contractions over arrays with a leading element axis — the pattern accelerators execute best. There is no sparse matrix assembly, no indirect addressing beyond one gather for neighbour traces, and no per-element Python control flow. The same source runs on both devices; the choice is an environment variable.

    ↑ ↓ moveEnter open[ ] change depthResults show section and depth level