@@ -507,10 +507,51 @@ def parallel_wrapper(grid, params, reader, writer, chunk_output_dir, clat_rad, c
507507from tqdm import tqdm
508508
509509if __name__ == '__main__' :
510+ # ========================================================================
511+ # CONFIGURATION SELECTOR
512+ # ========================================================================
513+ # Choose one: 'generic_laptop', 'dkrz_hpc', 'laptop_performance'
514+ SYSTEM_CONFIG = 'laptop_performance' # ← Edit this line to switch configs
515+ # ========================================================================
516+
517+ CONFIGS = {
518+ 'generic_laptop' : {
519+ 'total_cores' : 12 , # Conservative: use 12 of 16 threads
520+ 'total_memory_gb' : 12.0 ,
521+ 'netcdf_chunk_size' : 100 ,
522+ 'memory_per_cpu_mb' : None , # Will calculate dynamically
523+ 'description' : 'Generic laptop (16 threads, 16GB RAM)'
524+ },
525+ 'dkrz_hpc' : {
526+ 'total_cores' : 128 ,
527+ 'total_memory_gb' : 240.0 ,
528+ 'netcdf_chunk_size' : 1000 ,
529+ 'memory_per_cpu_mb' : 1940 , # SLURM quota on interactive partition
530+ 'description' : 'DKRZ HPC interactive partition (standard memory node)'
531+ },
532+ 'laptop_performance' : {
533+ 'total_cores' : 20 , # Use 20 of 24 threads (leave 4 for background)
534+ 'total_memory_gb' : 80.0 ,
535+ 'netcdf_chunk_size' : 100 ,
536+ 'memory_per_cpu_mb' : None , # Will calculate dynamically
537+ 'description' : 'AMD Ryzen AI 9 HX 370 (24 threads, 94GB RAM)'
538+ }
539+ }
540+
541+ # Validate configuration selection
542+ if SYSTEM_CONFIG not in CONFIGS :
543+ raise ValueError (f"Invalid SYSTEM_CONFIG '{ SYSTEM_CONFIG } '. Choose from: { list (CONFIGS .keys ())} " )
544+
545+ config = CONFIGS [SYSTEM_CONFIG ]
546+
510547 # Set up logging first
511548 log_file = setup_logger (log_dir = "logs" )
512549 print (f"Logging to: { log_file } " )
513550 print ("=" * 80 )
551+ print (f"SYSTEM CONFIG: { SYSTEM_CONFIG } " )
552+ print (f" { config ['description' ]} " )
553+ print (f" Cores: { config ['total_cores' ]} , Memory: { config ['total_memory_gb' ]} GB" )
554+ print ("=" * 80 )
514555
515556 # Override/add ETOPO-specific parameters
516557 params .fn_output = "icon_etopo_global"
@@ -578,22 +619,23 @@ def parallel_wrapper(grid, params, reader, writer, chunk_output_dir, clat_rad, c
578619 import multiprocessing
579620 import os
580621
581- # Determine total system resources
582- total_cores = os .cpu_count () or 1
583-
584- # Estimate total available memory for processing
585- # On laptop: typically 60 GB available (leave some for OS)
586- # On HPC: typically 240 GB available (256 GB total - 16 GB for OS)
587- if total_cores >= 64 :
588- # High-performance node
589- total_memory_gb = 240.0
590- netcdf_chunk_size = 1000 # 1000 cells per NetCDF file
591- logger .info (f"HIGH-PERFORMANCE MODE: { total_cores } cores, ~240 GB RAM available" )
622+ # Use configuration values
623+ total_cores = config ['total_cores' ]
624+ total_memory_gb = config ['total_memory_gb' ]
625+ netcdf_chunk_size = config ['netcdf_chunk_size' ]
626+
627+ logger .info ("=" * 80 )
628+ logger .info (f"RESOURCE CONFIGURATION: { SYSTEM_CONFIG } " )
629+ logger .info (f" Description: { config ['description' ]} " )
630+ logger .info (f" Available cores: { total_cores } " )
631+ logger .info (f" Available memory: { total_memory_gb } GB" )
632+ logger .info (f" NetCDF chunk size: { netcdf_chunk_size } cells" )
633+ if config ['memory_per_cpu_mb' ] is not None :
634+ logger .info (f" SLURM quota: { config ['memory_per_cpu_mb' ]} MB per CPU" )
635+ logger .info (f" Mode: HPC (threads scale with worker memory)" )
592636 else :
593- # Laptop/workstation
594- total_memory_gb = 60.0
595- netcdf_chunk_size = 100 # 100 cells per NetCDF file
596- logger .info (f"STANDARD MODE: { total_cores } cores, ~60 GB RAM available" )
637+ logger .info (f" Mode: Laptop (threads distributed evenly)" )
638+ logger .info ("=" * 80 )
597639
598640 # Group cells by memory requirements for dynamic worker allocation
599641 logger .info (f"\n Analyzing cells by latitude for dynamic memory allocation..." )
@@ -678,12 +720,31 @@ def parallel_wrapper(grid, params, reader, writer, chunk_output_dir, clat_rad, c
678720 n_workers = batch_config ['n_workers' ]
679721 memory_per_worker = f"{ int (batch_config ['memory_per_worker_gb' ])} GB"
680722
723+ # Calculate threads per worker based on configuration
724+ if config ['memory_per_cpu_mb' ] is not None :
725+ # HPC mode: Use SLURM's memory-per-CPU quota
726+ # Each worker gets CPUs proportional to its memory allocation
727+ threads_per_worker = max (1 , int (
728+ batch_config ['memory_per_worker_gb' ] * 1000 / config ['memory_per_cpu_mb' ]
729+ ))
730+ else :
731+ # Laptop mode: Calculate based on total available resources
732+ # How many workers can we fit given memory constraints?
733+ max_workers_by_memory = max (1 , int (
734+ config ['total_memory_gb' ] / batch_config ['memory_per_worker_gb' ]
735+ ))
736+ # Limit workers to what we actually configured
737+ actual_workers = min (max_workers_by_memory , n_workers )
738+ # Distribute threads evenly across workers
739+ threads_per_worker = max (1 , config ['total_cores' ] // actual_workers )
740+
681741 logger .info (f"\n Starting Dask client for memory batch { mem_batch_idx } :" )
682742 logger .info (f" Workers: { n_workers } × { memory_per_worker } " )
743+ logger .info (f" Threads per worker: { threads_per_worker } " )
683744 logger .info (f" Expected memory per cell: { batch_config ['memory_per_cell_gb' ]:.1f} GB" )
684745
685746 client = Client (
686- threads_per_worker = 1 ,
747+ threads_per_worker = threads_per_worker ,
687748 n_workers = n_workers ,
688749 processes = True ,
689750 memory_limit = memory_per_worker ,
0 commit comments