@@ -9,17 +9,93 @@ import glob
99import sys
1010from pathlib import Path
1111import dask .distributed as dd
12+ import psutil
1213
1314from access_mopper import ACCESS_ESM_CMORiser
1415from 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+
1664def 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