From df9d3916ddc867a5d09f838ee381f4b7b970ab47 Mon Sep 17 00:00:00 2001 From: Rohit Goswami Date: Thu, 29 Oct 2026 20:30:04 -0500 Subject: [PATCH 1/8] Divide the libxc build parallelism by 3 instead of 2 The libxc 7.0.0 build for x86_64/amd/zen4 in EESSI/software-layer#1772 was killed twice with the same OOM in cc1 at half the cores. A divider of 3 gives 5 jobs on a 16-core build node instead of 8. --- eb_hooks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eb_hooks.py b/eb_hooks.py index fd3aed75..b7408a0f 100644 --- a/eb_hooks.py +++ b/eb_hooks.py @@ -2842,7 +2842,7 @@ def set_maximum(parallel, max_value): '*': (set_maximum, 8), }, 'libxc': { - '*': (divide_by_factor, 2), + '*': (divide_by_factor, 3), CPU_TARGET_A64FX: (set_maximum, 6), }, 'LLVM': { From f175efd87264780f451ee144bd54f32fccf36c82 Mon Sep 17 00:00:00 2001 From: Rohit Goswami Date: Tue, 3 Nov 2026 02:58:08 -0600 Subject: [PATCH 2/8] cap the libxc build parallelism at 8 --- eb_hooks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eb_hooks.py b/eb_hooks.py index b7408a0f..dde3e164 100644 --- a/eb_hooks.py +++ b/eb_hooks.py @@ -2842,7 +2842,7 @@ def set_maximum(parallel, max_value): '*': (set_maximum, 8), }, 'libxc': { - '*': (divide_by_factor, 3), + '*': (set_maximum, 8), CPU_TARGET_A64FX: (set_maximum, 6), }, 'LLVM': { From f5da7d77ed484c4b7031862c451779a77e1a7244 Mon Sep 17 00:00:00 2001 From: Caspar van Leeuwen <33718780+casparvl@users.noreply.github.com> Date: Wed, 4 Nov 2026 11:08:07 +0100 Subject: [PATCH 3/8] Apply suggestion from @casparvl --- eb_hooks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eb_hooks.py b/eb_hooks.py index dde3e164..2c6185ef 100644 --- a/eb_hooks.py +++ b/eb_hooks.py @@ -2842,7 +2842,7 @@ def set_maximum(parallel, max_value): '*': (set_maximum, 8), }, 'libxc': { - '*': (set_maximum, 8), + '*': (set_maximum, 2), CPU_TARGET_A64FX: (set_maximum, 6), }, 'LLVM': { From 0f4306100478e7f1467774d1d3a6433dbd4b1e16 Mon Sep 17 00:00:00 2001 From: Rohit Goswami Date: Wed, 4 Nov 2026 16:26:00 +0000 Subject: [PATCH 4/8] Compile the heavy libxc sources at -O1 Eleven libxc 7.0.0 translation units peaked at 2 GiB or more with GCC 15.2.0 at -O3. The hook compiles those files at -O1 for libxc 7.0.0 and newer when the easyblock is CMakeMake. Those builds use the normal job count. Older libxc and ConfigureMake libxc keep the 2-core cap. The A64FX limit of 6 cores is unchanged. --- eb_hooks.py | 130 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) diff --git a/eb_hooks.py b/eb_hooks.py index 2c6185ef..64b07692 100644 --- a/eb_hooks.py +++ b/eb_hooks.py @@ -1321,6 +1321,132 @@ def post_prepare_hook_coordgenlibs_a64fx(self, *args, **kwargs): raise EasyBuildError("CoordgenLibs-specific hook triggered for non-CoordgenLibs easyconfig?!") +# libxc 7.0.0 translation units whose cc1 peak was at least 2 GiB. +# GCC 15.2.0, -O3, -march=native, -fPIC, third and fourth derivatives on. +# 8-core Intel Xeon. -O1 brings the largest file from +# 5.631 GiB down to 1.132 GiB. Nothing else in the list exceeded 6 GiB. +# 7.0.0 is the first libxc version whose heavy sources were measured. +_LIBXC_O1_MIN_VERSION = '7.0.0' +# CMakeMake is the easyblock that reads CMAKE_PROJECT_Libxc_INCLUDE. +_LIBXC_O1_EASYBLOCK = 'CMakeMake' +_LIBXC_O1_SOURCES = ( + ('src/mgga_x_br89.c', '5.631 GiB'), + ('src/mgga_x_mggac.c', '5.373 GiB'), + ('src/mgga_x_br89_explicit.c', '4.638 GiB'), + ('src/mgga_c_rmggac.c', '4.173 GiB'), + ('src/mgga_c_kcisk.c', '3.125 GiB'), + ('src/mgga_x_mbrxc_bg.c', '2.998 GiB'), + ('src/mgga_c_rscan.c', '2.938 GiB'), + ('src/gga_x_wpbeh.c', '2.564 GiB'), + ('src/gga_c_ft97.c', '2.531 GiB'), + ('src/mgga_x_rscan.c', '2.271 GiB'), + ('src/mgga_c_kcis.c', '2.208 GiB'), +) + + +def _libxc_o1_applies(self): + """Return True when this libxc build should compile the heavy sources at -O1. + + Older libxc, and any libxc that is not CMakeMake, keeps the 2-core cap + in PARALLELISM_LIMITS and does not receive the CMake include flag. + """ + if self.name != 'libxc': + return False + if LooseVersion(str(self.version)) < LooseVersion(_LIBXC_O1_MIN_VERSION): + return False + try: + easyblock = self.cfg['easyblock'] + except EasyBuildError: + easyblock = None + if not easyblock: + easyblock = type(self).__name__ + return easyblock == _LIBXC_O1_EASYBLOCK + + +def _libxc_use_normal_job_count(self): + """Drop the general 2-core cap for a build the -O1 list covers. + + The 2-core value is the '*' entry in PARALLELISM_LIMITS for libxc. + A64FX keeps its own 6-core limit, which post_ready_hook already applied. + """ + cpu_target = get_eessi_envvar('EESSI_SOFTWARE_SUBDIR') + if cpu_target == CPU_TARGET_A64FX: + return + full = det_parallelism() + if hasattr(self, 'parallel'): + self.cfg.parallel = full + else: + self.cfg['parallel'] = full + self.cfg.template_values['parallel'] = full + self.cfg.generate_template_values() + print_msg( + "libxc: using %s jobs; libxc that this hook skips stays at the 2-core cap", + full, + ) + + +def pre_configure_hook_libxc_o1(self, *args, **kwargs): + """Compile the heavy libxc sources at -O1 and leave the rest alone. + + CMake's per-source COMPILE_OPTIONS are placed after the toolchain -O3. + gcc keeps the last -O flag, so -O1 wins for the listed files only. + The snippet is pulled in by -DCMAKE_PROJECT_Libxc_INCLUDE, which CMake + runs at the end of project(Libxc). That does not touch other projects. + """ + if not _libxc_o1_applies(self): + return + + missing = [] + for relpath, _peak in _LIBXC_O1_SOURCES: + if not os.path.isfile(os.path.join(self.start_dir, relpath)): + missing.append(relpath) + if missing: + print_warning( + "libxc -O1 list has sources missing from this tree: %s", + ', '.join(missing), + ) + + lines = [ + '# Appended -O1 wins over the toolchain -O3. Other sources are unchanged.', + 'set(_eessi_libxc_o1_sources', + ] + for relpath, _peak in _LIBXC_O1_SOURCES: + lines.append(' %s' % relpath) + lines.extend([ + ')', + 'set_source_files_properties(', + ' ${_eessi_libxc_o1_sources}', + ' PROPERTIES COMPILE_OPTIONS "-O1"', + ')', + '', + ]) + snippet = os.path.join(self.builddir, 'eessi_libxc_o1.cmake') + with open(snippet, 'w') as handle: + handle.write('\n'.join(lines)) + + extra = '-DCMAKE_PROJECT_Libxc_INCLUDE=%s' % snippet + current = self.cfg['configopts'] + if isinstance(current, (list, tuple)): + updated = [] + for item in current: + text = item or '' + if extra not in text: + text = (text + ' ' + extra).strip() + updated.append(text) + self.cfg['configopts'] = updated + else: + text = current or '' + if extra not in text: + self.cfg['configopts'] = (text + ' ' + extra).strip() + + print_msg( + "libxc: compiling %s sources at -O1 (%s); other sources keep the toolchain flags", + len(_LIBXC_O1_SOURCES), + ', '.join(os.path.basename(relpath) for relpath, _peak in _LIBXC_O1_SOURCES), + ) + _libxc_use_normal_job_count(self) + + def pre_configure_hook(self, *args, **kwargs): """Main pre-configure hook: trigger custom functions based on software name.""" if self.name in PRE_CONFIGURE_HOOKS: @@ -2762,6 +2888,7 @@ def post_easyblock_hook(self, *args, **kwargs): 'GRASS': pre_configure_hook_grass, 'LAMMPS': pre_configure_hook_LAMMPS_zen4_and_aarch64_cuda, 'libfabric': pre_configure_hook_libfabric_disable_psm3_x86_64_generic, + 'libxc': pre_configure_hook_libxc_o1, 'LLVM': pre_configure_hook_llvm, 'MetaBAT': pre_configure_hook_metabat_filtered_zlib_dep, 'OpenBLAS': pre_configure_hook_openblas_optarch_generic, @@ -2841,6 +2968,9 @@ def set_maximum(parallel, max_value): 'hipBLASLt': { '*': (set_maximum, 8), }, + # The 2-core cap stays for libxc that pre_configure_hook_libxc_o1 skips. + # That hook lifts it for libxc >= 7.0.0 built with CMakeMake. + # A64FX stays at 6 cores because of HBM, which is independent of those files. 'libxc': { '*': (set_maximum, 2), CPU_TARGET_A64FX: (set_maximum, 6), From 64cdaaa19868ae3d750b532c7caa0d64e924fda5 Mon Sep 17 00:00:00 2001 From: Rohit Goswami Date: Wed, 4 Nov 2026 21:50:00 +0000 Subject: [PATCH 5/8] Leave lda_xc_ksdt.c off the libxc -O1 list libxc 7.1.2 cc1 peaked at 2.153 GiB on lda_xc_ksdt.c with -O3. The same translation unit peaked at 2.530 GiB with -O1, so -O1 is the wrong flag. The eleven-file list is unchanged. --- eb_hooks.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/eb_hooks.py b/eb_hooks.py index 64b07692..d5bdbe2f 100644 --- a/eb_hooks.py +++ b/eb_hooks.py @@ -1325,6 +1325,8 @@ def post_prepare_hook_coordgenlibs_a64fx(self, *args, **kwargs): # GCC 15.2.0, -O3, -march=native, -fPIC, third and fourth derivatives on. # 8-core Intel Xeon. -O1 brings the largest file from # 5.631 GiB down to 1.132 GiB. Nothing else in the list exceeded 6 GiB. +# libxc 7.1.2 lda_xc_ksdt.c peaked at 2.153 GiB with -O3 and 2.530 GiB +# with -O1, so -O1 is not applied to it. # 7.0.0 is the first libxc version whose heavy sources were measured. _LIBXC_O1_MIN_VERSION = '7.0.0' # CMakeMake is the easyblock that reads CMAKE_PROJECT_Libxc_INCLUDE. From fc1581db754f110a574b845bf7aef141b0527254 Mon Sep 17 00:00:00 2001 From: Caspar van Leeuwen Date: Thu, 5 Nov 2026 17:15:22 +0100 Subject: [PATCH 6/8] Revert "Leave lda_xc_ksdt.c off the libxc -O1 list" This reverts commit 64cdaaa19868ae3d750b532c7caa0d64e924fda5. --- eb_hooks.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/eb_hooks.py b/eb_hooks.py index d5bdbe2f..64b07692 100644 --- a/eb_hooks.py +++ b/eb_hooks.py @@ -1325,8 +1325,6 @@ def post_prepare_hook_coordgenlibs_a64fx(self, *args, **kwargs): # GCC 15.2.0, -O3, -march=native, -fPIC, third and fourth derivatives on. # 8-core Intel Xeon. -O1 brings the largest file from # 5.631 GiB down to 1.132 GiB. Nothing else in the list exceeded 6 GiB. -# libxc 7.1.2 lda_xc_ksdt.c peaked at 2.153 GiB with -O3 and 2.530 GiB -# with -O1, so -O1 is not applied to it. # 7.0.0 is the first libxc version whose heavy sources were measured. _LIBXC_O1_MIN_VERSION = '7.0.0' # CMakeMake is the easyblock that reads CMAKE_PROJECT_Libxc_INCLUDE. From 0938a1b4709df94cf9187511235750976c76e531 Mon Sep 17 00:00:00 2001 From: Caspar van Leeuwen Date: Thu, 5 Nov 2026 17:17:30 +0100 Subject: [PATCH 7/8] Revert "Compile the heavy libxc sources at -O1" This reverts commit 0f4306100478e7f1467774d1d3a6433dbd4b1e16. --- eb_hooks.py | 130 ---------------------------------------------------- 1 file changed, 130 deletions(-) diff --git a/eb_hooks.py b/eb_hooks.py index 64b07692..2c6185ef 100644 --- a/eb_hooks.py +++ b/eb_hooks.py @@ -1321,132 +1321,6 @@ def post_prepare_hook_coordgenlibs_a64fx(self, *args, **kwargs): raise EasyBuildError("CoordgenLibs-specific hook triggered for non-CoordgenLibs easyconfig?!") -# libxc 7.0.0 translation units whose cc1 peak was at least 2 GiB. -# GCC 15.2.0, -O3, -march=native, -fPIC, third and fourth derivatives on. -# 8-core Intel Xeon. -O1 brings the largest file from -# 5.631 GiB down to 1.132 GiB. Nothing else in the list exceeded 6 GiB. -# 7.0.0 is the first libxc version whose heavy sources were measured. -_LIBXC_O1_MIN_VERSION = '7.0.0' -# CMakeMake is the easyblock that reads CMAKE_PROJECT_Libxc_INCLUDE. -_LIBXC_O1_EASYBLOCK = 'CMakeMake' -_LIBXC_O1_SOURCES = ( - ('src/mgga_x_br89.c', '5.631 GiB'), - ('src/mgga_x_mggac.c', '5.373 GiB'), - ('src/mgga_x_br89_explicit.c', '4.638 GiB'), - ('src/mgga_c_rmggac.c', '4.173 GiB'), - ('src/mgga_c_kcisk.c', '3.125 GiB'), - ('src/mgga_x_mbrxc_bg.c', '2.998 GiB'), - ('src/mgga_c_rscan.c', '2.938 GiB'), - ('src/gga_x_wpbeh.c', '2.564 GiB'), - ('src/gga_c_ft97.c', '2.531 GiB'), - ('src/mgga_x_rscan.c', '2.271 GiB'), - ('src/mgga_c_kcis.c', '2.208 GiB'), -) - - -def _libxc_o1_applies(self): - """Return True when this libxc build should compile the heavy sources at -O1. - - Older libxc, and any libxc that is not CMakeMake, keeps the 2-core cap - in PARALLELISM_LIMITS and does not receive the CMake include flag. - """ - if self.name != 'libxc': - return False - if LooseVersion(str(self.version)) < LooseVersion(_LIBXC_O1_MIN_VERSION): - return False - try: - easyblock = self.cfg['easyblock'] - except EasyBuildError: - easyblock = None - if not easyblock: - easyblock = type(self).__name__ - return easyblock == _LIBXC_O1_EASYBLOCK - - -def _libxc_use_normal_job_count(self): - """Drop the general 2-core cap for a build the -O1 list covers. - - The 2-core value is the '*' entry in PARALLELISM_LIMITS for libxc. - A64FX keeps its own 6-core limit, which post_ready_hook already applied. - """ - cpu_target = get_eessi_envvar('EESSI_SOFTWARE_SUBDIR') - if cpu_target == CPU_TARGET_A64FX: - return - full = det_parallelism() - if hasattr(self, 'parallel'): - self.cfg.parallel = full - else: - self.cfg['parallel'] = full - self.cfg.template_values['parallel'] = full - self.cfg.generate_template_values() - print_msg( - "libxc: using %s jobs; libxc that this hook skips stays at the 2-core cap", - full, - ) - - -def pre_configure_hook_libxc_o1(self, *args, **kwargs): - """Compile the heavy libxc sources at -O1 and leave the rest alone. - - CMake's per-source COMPILE_OPTIONS are placed after the toolchain -O3. - gcc keeps the last -O flag, so -O1 wins for the listed files only. - The snippet is pulled in by -DCMAKE_PROJECT_Libxc_INCLUDE, which CMake - runs at the end of project(Libxc). That does not touch other projects. - """ - if not _libxc_o1_applies(self): - return - - missing = [] - for relpath, _peak in _LIBXC_O1_SOURCES: - if not os.path.isfile(os.path.join(self.start_dir, relpath)): - missing.append(relpath) - if missing: - print_warning( - "libxc -O1 list has sources missing from this tree: %s", - ', '.join(missing), - ) - - lines = [ - '# Appended -O1 wins over the toolchain -O3. Other sources are unchanged.', - 'set(_eessi_libxc_o1_sources', - ] - for relpath, _peak in _LIBXC_O1_SOURCES: - lines.append(' %s' % relpath) - lines.extend([ - ')', - 'set_source_files_properties(', - ' ${_eessi_libxc_o1_sources}', - ' PROPERTIES COMPILE_OPTIONS "-O1"', - ')', - '', - ]) - snippet = os.path.join(self.builddir, 'eessi_libxc_o1.cmake') - with open(snippet, 'w') as handle: - handle.write('\n'.join(lines)) - - extra = '-DCMAKE_PROJECT_Libxc_INCLUDE=%s' % snippet - current = self.cfg['configopts'] - if isinstance(current, (list, tuple)): - updated = [] - for item in current: - text = item or '' - if extra not in text: - text = (text + ' ' + extra).strip() - updated.append(text) - self.cfg['configopts'] = updated - else: - text = current or '' - if extra not in text: - self.cfg['configopts'] = (text + ' ' + extra).strip() - - print_msg( - "libxc: compiling %s sources at -O1 (%s); other sources keep the toolchain flags", - len(_LIBXC_O1_SOURCES), - ', '.join(os.path.basename(relpath) for relpath, _peak in _LIBXC_O1_SOURCES), - ) - _libxc_use_normal_job_count(self) - - def pre_configure_hook(self, *args, **kwargs): """Main pre-configure hook: trigger custom functions based on software name.""" if self.name in PRE_CONFIGURE_HOOKS: @@ -2888,7 +2762,6 @@ def post_easyblock_hook(self, *args, **kwargs): 'GRASS': pre_configure_hook_grass, 'LAMMPS': pre_configure_hook_LAMMPS_zen4_and_aarch64_cuda, 'libfabric': pre_configure_hook_libfabric_disable_psm3_x86_64_generic, - 'libxc': pre_configure_hook_libxc_o1, 'LLVM': pre_configure_hook_llvm, 'MetaBAT': pre_configure_hook_metabat_filtered_zlib_dep, 'OpenBLAS': pre_configure_hook_openblas_optarch_generic, @@ -2968,9 +2841,6 @@ def set_maximum(parallel, max_value): 'hipBLASLt': { '*': (set_maximum, 8), }, - # The 2-core cap stays for libxc that pre_configure_hook_libxc_o1 skips. - # That hook lifts it for libxc >= 7.0.0 built with CMakeMake. - # A64FX stays at 6 cores because of HBM, which is independent of those files. 'libxc': { '*': (set_maximum, 2), CPU_TARGET_A64FX: (set_maximum, 6), From 3a58168464d40cc1e94095dcd993e65a5c1d764b Mon Sep 17 00:00:00 2001 From: Caspar van Leeuwen Date: Thu, 5 Nov 2026 17:19:17 +0100 Subject: [PATCH 8/8] Change libxc max parallelism to 4 cores --- eb_hooks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eb_hooks.py b/eb_hooks.py index 2c6185ef..7d8c204c 100644 --- a/eb_hooks.py +++ b/eb_hooks.py @@ -2842,7 +2842,7 @@ def set_maximum(parallel, max_value): '*': (set_maximum, 8), }, 'libxc': { - '*': (set_maximum, 2), + '*': (set_maximum, 4), CPU_TARGET_A64FX: (set_maximum, 6), }, 'LLVM': {