Speed up batch CMORisation: multi-process Dask, per-variable worker sizing, and skip redundant daily-file validation#499
Merged
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #499 +/- ##
=======================================
- Coverage 78.5% 78.1% -0.4%
=======================================
Files 33 33
Lines 6520 6626 +106
Branches 1233 1260 +27
=======================================
+ Hits 5117 5173 +56
- Misses 1129 1180 +51
+ Partials 274 273 -1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
rbeucher
approved these changes
Jul 12, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Batch CMORisation runs one PBS job per variable. Each job requested
ncpus=12 mem=190GBbut effectively used ~1 core, so the heavy variables(
cVeg/cSoil/nbp) took 1.5–2.7 h and the daily-input variables(
tasmax/tasmin) blew past walltime. This PR fixes the root causes:(
processes=False), so every netCDF4 read serialised on the global HDF5lock and collapsed to ~1 core.
pools too small for the heavy/daily variables, causing constant memory
pausing and killing
tasmax/tasmin.tasmax/tasminspent ~32 min of single-core time in afrequency-validation step that re-opened all 2064 daily files just to
re-confirm they are daily.
The result is a multi-process Dask client whose worker count adapts to each
variable's memory footprint and to the PBS resources, plus removal of the
redundant serial validation for daily variables. Numerical output is
unchanged (verified bit-identical).
Root cause
1. Threaded Dask client + HDF5 global lock → ~1 core
Reads go through
xr.open_mfdataset(..., engine="netcdf4"). netCDF4/HDF5 is notthread-safe, so xarray serialises every read on a single global
HDF5_LOCK.Under a threaded client (
processes=False), all 2064 file reads run on onecore regardless of thread count.
Evidence (real cVeg read+sum on a 12-core node, 1032 cold files each):
Results were bit-identical (
sum=1.19124e+49in both).2. Why only some variables were slow
The lock caps every variable at ~1 core; the heavy ones simply move far more
data along that single serial lane:
tastascVegnbptasmax/tasmin3. Naive one-worker-per-core over-divided memory
Splitting 190 GB into 12 x ~11 GB workers caused:
cSoilpaused 105x, dragging it to 2:15) while~95 GB of the node sat idle;
tasmax/tasminworkers exceeding their ~11 GB limit and being killed bythe nanny, failing the final gather.
4. Redundant serial validation of all daily files
For
tasmax/tasmin,validate_cmip6_frequency_compatibilitysampled 10 filesand correctly detected "daily", but because daily is outside the 20–35 day
"monthly" band it fell back to
_validate_monthly_files_individually, whichopened all 2064 files one at a time in the main process (no Dask), just to
re-confirm sub-monthly frequency. Measured ~941 ms/file -> ~32 min/job.
Changes
src/access_moppy/executors/dask_config.py(new)recommend_dask_config(variable, input_files, model_id)sizes aprocesses=Trueclient:PBS_NCPUS,PBS_MEM_GB), soworker count and per-worker memory adapt to whatever the PBS job requested —
nothing about node size is hard-coded.
sub-monthly input (needs resample) -> heavy; multi-MB tiled model variables
(carbon pools) -> medium; single small field -> light.
threads_per_worker=1. Extrathreads add no read throughput (HDF5 lock + GIL) and would double a worker's
resident memory — the opposite of what the memory-limited tiers need.
minimum RAM a worker of that class needs; they are env-overridable
(
MOPPY_WORKER_GB_LIGHT|MEDIUM|HEAVY,MOPPY_MEDIUM_MB_PER_FILE).Resulting sizing on a 12-core / 190 GB job:
tas,prcVeg,cSoil,nbptasmax,tasminsrc/access_moppy/templates/cmor_python_script.j2processes=True.client can be sized to the variable via
recommend_dask_config.psutilimport; quieter worker logs.src/access_moppy/utilities.py_validate_monthly_compatibility, when a variable allows sub-monthly input(
tasmax/tasmin) and the sampled detection already confirms a sub-monthlyfrequency, return it directly instead of re-opening every file. The exhaustive
per-file loop is retained as the fallback for genuinely unexpected
frequencies; monthly variables are unaffected.
Validation
273.9 s -> 63.1 s, bit-identical results, 70.8/190 GB memory.
PBS shapes (4–48 cpus); env override confirmed (heavy 28 -> 40 => 6 -> 4
workers).
tasmaxfrequency validation ~451 s -> 14.7 sfor 480 files (near-constant regardless of file count; ~32 min saved on a full
2064-file job). Monthly
cVegunchanged (freq=31 days, normal path).tasmax: CMORiser output verified bit-exact equal to theindependently computed monthly maximum of the daily data (and clearly not
the mean), with the production setting
enable_resampling=False.Risk
validation short-circuit only skips redundant re-confirmation).
_preprocessis dispatched to workers underprocesses=True; verified toround-trip correctly (bit-identical results at full scale).