Skip to content

Commit df0f398

Browse files
committed
Enhance job script and Python template for variable-specific resource allocation and optimal Dask configuration
1 parent a12c2b5 commit df0f398

4 files changed

Lines changed: 115 additions & 5 deletions

File tree

src/access_mopper/batch_cmoriser.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,22 @@ def create_job_script(variable, config, db_path, script_dir):
4242
pbs_template = Template(pbs_template_content)
4343
python_template = Template(python_template_content)
4444

45+
# Get variable-specific resources if available
46+
variable_config = config.copy()
47+
if "variable_resources" in config and variable in config["variable_resources"]:
48+
# Override with variable-specific settings
49+
variable_config.update(config["variable_resources"][variable])
50+
print(
51+
f"Using custom resources for {variable}: {config['variable_resources'][variable]}"
52+
)
53+
4554
# Get the package path for sys.path.insert
4655
package_path = Path(__file__).parent.parent
4756

4857
# Create Python script
4958
python_script_content = python_template.render(
5059
variable=variable,
51-
config=config,
60+
config=variable_config, # Use variable-specific config
5261
db_path=db_path,
5362
package_path=package_path,
5463
)
@@ -60,7 +69,7 @@ def create_job_script(variable, config, db_path, script_dir):
6069
# Create PBS script
6170
pbs_script_content = pbs_template.render(
6271
variable=variable,
63-
config=config,
72+
config=variable_config, # Use variable-specific config
6473
script_dir=script_dir,
6574
python_script_path=python_script_path,
6675
db_path=db_path,

src/access_mopper/examples/batch_config.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,26 @@ walltime: "02:00:00"
5353
scheduler_options: "#PBS -P tm70"
5454
storage: "gdata/p73+gdata/tm70+scratch/tm70+gdata/xp65"
5555

56+
# Variable-specific resource overrides
57+
variable_resources:
58+
# 3D ocean variables need more resources
59+
Omon.thetao:
60+
cpus_per_node: 28
61+
mem: 128GB
62+
walltime: "06:00:00"
63+
64+
# Daily data needs more memory
65+
day.pr:
66+
cpus_per_node: 14
67+
mem: 64GB
68+
walltime: "04:00:00"
69+
70+
# Simple 2D monthly variables
71+
Amon.pr:
72+
cpus_per_node: 8
73+
mem: 32GB
74+
walltime: "02:00:00"
75+
5676
# Environment setup for each job
5777
worker_init: |
5878
source /g/data/tm70/rb5533/miniforge3/bin/activate

src/access_mopper/templates/cmor_job_script.j2

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
{{ config.get('worker_init', 'module load netcdf-python') }}
1616

1717
export OMP_NUM_THREADS=1
18+
# Extract memory allocation for Python script
19+
{% set mem_value = config.get('mem', '16GB') %}
20+
{% set mem_gb = mem_value.replace('GB', '').replace('gb', '') | int %}
21+
export PBS_MEM_GB="{{ mem_gb }}"
1822

1923
# Set environment variables for this job
2024
export CMOR_TRACKER_DB="{{ db_path }}"
@@ -35,6 +39,7 @@ mkdir -p $TMPDIR/cmor_temp
3539
{% endif %}
3640

3741
# Debug information
42+
echo "PBS Resources: $PBS_NCPUS CPUs, ${PBS_MEM_GB}GB memory"
3843
echo "Database path: $CMOR_TRACKER_DB"
3944
echo "Database exists: $(test -f "$CMOR_TRACKER_DB" && echo "YES" || echo "NO")"
4045
echo "Variable: $VARIABLE"

src/access_mopper/templates/cmor_python_script.j2

Lines changed: 79 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,93 @@ import glob
99
import sys
1010
from pathlib import Path
1111
import dask.distributed as dd
12+
import psutil
1213

1314
from access_mopper import ACCESS_ESM_CMORiser
1415
from access_mopper.tracking import TaskTracker
1516

17+
def detect_optimal_dask_config():
18+
"""Automatically detect optimal Dask configuration based on available resources."""
19+
20+
# Get PBS allocation
21+
n_cpus = int(os.environ.get('PBS_NCPUS', psutil.cpu_count()))
22+
allocated_memory_gb = int(os.environ.get('PBS_MEM_GB', '16'))
23+
24+
# Get system memory info as fallback
25+
system_memory_gb = psutil.virtual_memory().total / (1024**3)
26+
effective_memory = min(allocated_memory_gb, system_memory_gb * 0.9)
27+
28+
print(f"Detected: {n_cpus} CPUs, {allocated_memory_gb}GB allocated, {system_memory_gb:.1f}GB system")
29+
30+
# Heuristic for optimal configuration
31+
if effective_memory >= 128:
32+
# Very high memory jobs
33+
return {
34+
'n_workers': max(2, n_cpus // 8),
35+
'threads_per_worker': 8,
36+
'memory_per_worker': f"{int(effective_memory // max(2, n_cpus // 8) - 4)}GB",
37+
'chunk_size': '1GB'
38+
}
39+
elif effective_memory >= 64:
40+
# High memory jobs
41+
return {
42+
'n_workers': max(2, n_cpus // 4),
43+
'threads_per_worker': 4,
44+
'memory_per_worker': f"{int(effective_memory // max(2, n_cpus // 4) - 2)}GB",
45+
'chunk_size': '512MB'
46+
}
47+
elif effective_memory >= 32:
48+
# Medium memory jobs
49+
return {
50+
'n_workers': max(2, n_cpus // 2),
51+
'threads_per_worker': 2,
52+
'memory_per_worker': f"{int(effective_memory // max(2, n_cpus // 2) - 1)}GB",
53+
'chunk_size': '256MB'
54+
}
55+
else:
56+
# Low memory jobs
57+
return {
58+
'n_workers': 1,
59+
'threads_per_worker': min(n_cpus, 4),
60+
'memory_per_worker': f"{int(effective_memory - 1)}GB",
61+
'chunk_size': '128MB'
62+
}
63+
1664
def main():
17-
# Start a local Dask client for this job
65+
# Get PBS resource allocation
66+
n_cpus = int(os.environ.get('PBS_NCPUS', '4'))
67+
68+
# Get memory allocation from PBS (parse from PBS_JOBID or estimate)
69+
# PBS doesn't directly expose memory allocation, so we'll use a heuristic
70+
# or pass it via environment variable
71+
allocated_memory_gb = int(os.environ.get('PBS_MEM_GB', '16'))
72+
73+
# Configure Dask based on available resources
74+
import dask
75+
dask.config.set({
76+
'array.chunk-size': '512MB',
77+
'distributed.worker.memory.target': 0.8,
78+
'distributed.worker.memory.spill': 0.9,
79+
'distributed.worker.memory.pause': 0.95,
80+
})
81+
82+
dask_config = detect_optimal_dask_config()
83+
84+
# Dynamic worker configuration based on memory
85+
n_workers = dask_config['n_workers']
86+
threads_per_worker = dask_config['threads_per_worker']
87+
memory_per_worker = dask_config['memory_per_worker']
88+
89+
print(f"Optimal Dask Config: {n_workers} workers, {threads_per_worker} threads each, {memory_per_worker} per worker")
90+
1891
client = dd.Client(
1992
processes=False,
20-
threads_per_worker=1,
21-
n_workers=int(os.environ.get('PBS_NCPUS', '1'))
93+
threads_per_worker=threads_per_worker,
94+
n_workers=n_workers,
95+
memory_limit=memory_per_worker,
96+
silence_logs=False
2297
)
98+
2399
print(f'Dask dashboard: {client.dashboard_link}')
24100

25101
try:

0 commit comments

Comments
 (0)