diff --git a/src/feelpp/benchmarking/reframe/resources.py b/src/feelpp/benchmarking/reframe/resources.py index 98dcd5713..477165cc3 100644 --- a/src/feelpp/benchmarking/reframe/resources.py +++ b/src/feelpp/benchmarking/reframe/resources.py @@ -20,6 +20,9 @@ def validate(self, rfm_test): class TaskAndTaskPerNodeStrategy(ResourceStrategy): """ Resource Strategy to configure the resources for the test with tasks and tasks per node """ def configure(self, resources, rfm_test): + if resources.cpus_per_task: + rfm_test.num_cpus_per_task = int(resources.cpus_per_task) + rfm_test.num_tasks_per_node = int(resources.tasks_per_node) rfm_test.num_tasks = int(resources.tasks) rfm_test.num_nodes = int(np.ceil(rfm_test.num_tasks / rfm_test.num_tasks_per_node)) @@ -28,20 +31,25 @@ def validate(self, rfm_test): super().validate(rfm_test) assert rfm_test.num_tasks % rfm_test.num_tasks_per_node == 0, 'Number of tasks should be divisible by tasks per node' assert rfm_test.num_tasks >= rfm_test.num_tasks_per_node > 0, 'Number of tasks should be greater than tasks per node' - assert rfm_test.num_tasks_per_node <= rfm_test.current_partition.processor.num_cpus, f"A node has not enough capacity ({rfm_test.current_partition.processor.num_cpus}, {rfm_test.num_tasks_per_node})" + assert rfm_test.num_tasks_per_node * (rfm_test.num_cpus_per_task or 1) <= rfm_test.current_partition.processor.num_cpus, f"A node has not enough capacity ({rfm_test.current_partition.processor.num_cpus} cpus, {rfm_test.num_tasks_per_node} tasks per node, {rfm_test.num_cpus_per_task} cpus per task)" class NodesAndTasksPerNodeStrategy(ResourceStrategy): """ Resource Strategy to configure the resources for the test with nodes and tasks per node The number of tasks is calculated as the number of nodes multiplied by the number of tasks per node """ def configure(self, resources, rfm_test): + if resources.cpus_per_task: + rfm_test.num_cpus_per_task = int(resources.cpus_per_task) + rfm_test.num_tasks_per_node = int(resources.tasks_per_node) rfm_test.num_nodes = int(resources.nodes) rfm_test.num_tasks = int(rfm_test.num_tasks_per_node * rfm_test.num_nodes) def validate(self, rfm_test): super().validate(rfm_test) - assert rfm_test.num_tasks_per_node <= rfm_test.current_partition.processor.num_cpus, f"A node has not enough capacity ({rfm_test.current_partition.processor.num_cpus}, {rfm_test.num_tasks_per_node})" + assert rfm_test.num_nodes > 0, "Number of nodes should be strictly positive." + assert rfm_test.num_tasks_per_node * (rfm_test.num_cpus_per_task or 1) <= rfm_test.current_partition.processor.num_cpus, f"A node has not enough capacity ({rfm_test.current_partition.processor.num_cpus} cpus, {rfm_test.num_tasks_per_node} tasks per node, {rfm_test.num_cpus_per_task} cpus per task)" + class TasksAndNodesStrategy(ResourceStrategy): @@ -49,6 +57,9 @@ class TasksAndNodesStrategy(ResourceStrategy): The number of tasks per node is calculated as the euclidean quotient of the number of tasks divided by the number of nodes """ def configure(self, resources, rfm_test): + if resources.cpus_per_task: + rfm_test.num_cpus_per_task = int(resources.cpus_per_task) + rfm_test.num_tasks = int(resources.tasks) rfm_test.num_nodes = int(resources.nodes) rfm_test.num_tasks_per_node = rfm_test.num_tasks // rfm_test.num_nodes @@ -64,6 +75,10 @@ class TasksStrategy(ResourceStrategy): The number of tasks per node is calculated as the minimum between the number of tasks and the number of CPUs per node """ def configure(self, resources, rfm_test): + + if resources.cpus_per_task: + rfm_test.num_cpus_per_task = int(resources.cpus_per_task) + rfm_test.num_tasks = int(resources.tasks) rfm_test.num_nodes = int(np.ceil(rfm_test.num_tasks / rfm_test.current_partition.processor.num_cpus)) rfm_test.num_tasks_per_node = min(rfm_test.num_tasks, rfm_test.current_partition.processor.num_cpus) @@ -118,6 +133,20 @@ def __init__(self, exclusive_access): def enforceExclusiveAccess(self, rfm_test): rfm_test.exclusive_access = self.exclusive_access +class HyperthreadingEnforcer: + """ Plugin to enforce hyperthreading value to the nodes + The hyperthreading value is set to 1 by default + """ + def __init__(self, threads_per_core): + """Args: + threads_per_core (int): The hyperthreading value + """ + self.threads_per_core = int(threads_per_core) if threads_per_core is not None else 1 + + def enforceHyperthreading(self, rfm_test): + rfm_test.job.options += [f'--threads-per-core={self.threads_per_core}'] + rfm_test.multithreading = self.threads_per_core > 1 + class ResourceHandler: """ Resource Handler to set the resources for the test, based on the resources model """ @staticmethod @@ -155,6 +184,8 @@ def setResources(resources, rfm_test): if resources.memory: MemoryEnforcer(resources.memory).enforceMemory(rfm_test) + + ExclusiveAccessEnforcer(resources.exclusive_access).enforceExclusiveAccess(rfm_test) strategy.validate(rfm_test) diff --git a/src/feelpp/benchmarking/reframe/schemas/resources.py b/src/feelpp/benchmarking/reframe/schemas/resources.py index cbd63aad2..e0e3b3b4b 100644 --- a/src/feelpp/benchmarking/reframe/schemas/resources.py +++ b/src/feelpp/benchmarking/reframe/schemas/resources.py @@ -9,6 +9,9 @@ class Resources(BaseModel): memory: Optional[Union[str,int]] = 0 exclusive_access: Optional[Union[str,bool]] = True + cpus_per_task: Optional[Union[str,int]] = 1 + threads_per_core: Optional[Union[str,int]] = 1 #Hyperthreading + @model_validator(mode="after") def validateResources(self): assert ( diff --git a/src/feelpp/benchmarking/reframe/setup.py b/src/feelpp/benchmarking/reframe/setup.py index 3c9ad4be0..377ee269d 100644 --- a/src/feelpp/benchmarking/reframe/setup.py +++ b/src/feelpp/benchmarking/reframe/setup.py @@ -170,7 +170,6 @@ def setSchedOptions(self): """ Sets the necessary pre-run configurations""" self.job.launcher.options += self.current_partition.get_resource('launcher_options') self.job.options += self.machine_reader.config.access - self.job.options += ['--threads-per-core=1'] @run_before('run') def addPrepareCmds(self): diff --git a/tests/parameters/test_resources.py b/tests/parameters/test_resources.py index 7df8d1c31..462215558 100644 --- a/tests/parameters/test_resources.py +++ b/tests/parameters/test_resources.py @@ -4,13 +4,14 @@ class ResourcesMocker: """ Mocks the resources object """ - def __init__(self, tasks = None, tasks_per_node = None, nodes = None, memory = None, exclusive_access = None, gpus_per_node = None): + def __init__(self, tasks = None, tasks_per_node = None, nodes = None, memory = None, exclusive_access = None, gpus_per_node = None, cpus_per_task = None ): self.tasks = tasks self.tasks_per_node = tasks_per_node self.nodes = nodes self.memory = memory self.exclusive_access = exclusive_access self.gpus_per_node = gpus_per_node + self.cpus_per_task = cpus_per_task class RfmTestMocker: @@ -18,6 +19,7 @@ class RfmTestMocker: def __init__(self, num_cpus, memory_per_node): self.current_partition = self.Partition(num_cpus=num_cpus, memory_per_node=memory_per_node) self.job = self.Job() + self.num_cpus_per_task = None #Because the resource strategy does not set it if none. class Job: def __init__(self):