Skip to content

Commit

Permalink
feat(optimizer): system configuration file
Browse files Browse the repository at this point in the history
  • Loading branch information
lukasrothenberger committed Dec 13, 2023
1 parent 37f9916 commit 9105f2c
Show file tree
Hide file tree
Showing 7 changed files with 187 additions and 49 deletions.
1 change: 1 addition & 0 deletions discopop_library/discopop_optimizer/OptimizerArguments.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class OptimizerArguments(object):
reduction_microbench_file: str
allow_nested_parallelism: bool
plot: bool
system_configuration_path: str

def __post_init__(self):
self.__validate()
Expand Down
5 changes: 5 additions & 0 deletions discopop_library/discopop_optimizer/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ def parse_args() -> OptimizerArguments:
"--reduction-microbench-file", type=str, default="None",
help="Reduction microbenchmark results"
)
parser.add_argument(
"--system-configuration", type=str, default="optimizer/system_configuration.json",
help="System configuration file"
)
# EXPERIMENTAL FLAGS:
experimental_parser.add_argument("--allow-nested-parallelism", action="store_true",
help="Allow the creation of nested parallelism suggestions. "
Expand All @@ -54,6 +58,7 @@ def parse_args() -> OptimizerArguments:
reduction_microbench_file=arguments.reduction_microbench_file,
allow_nested_parallelism=arguments.allow_nested_parallelism,
plot=arguments.plot,
system_configuration_path=arguments.system_configuration,
)


Expand Down
22 changes: 21 additions & 1 deletion discopop_library/discopop_optimizer/classes/system/Network.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,45 @@
# the 3-Clause BSD License. See the LICENSE file in the package base
# directory for details.
from typing import Dict, Tuple, List, Optional, cast
import warnings

from sympy import Expr, Symbol
from sympy import Expr, Symbol, Integer

from discopop_library.discopop_optimizer.classes.system.devices.Device import Device


class Network(object):
__host_device: Optional[Device]
__transfer_speeds: Dict[Tuple[Device, Device], Expr] # (MB/s)
__transfer_initialization_costs: Dict[Tuple[Device, Device], Expr]

def __init__(self):
self.__host_device = None
self.__transfer_speeds = dict()
self.__transfer_initialization_costs = dict()

def add_connection(self, source: Device, target: Device, transfer_speed: Expr, initialization_delay: Expr):
if source == target:
transfer_speed = Integer(10000 * 1000) # 10000 GB/s = 10000 * 1000 MB/s
self.__transfer_speeds[(source, target)] = transfer_speed
self.__transfer_initialization_costs[(source, target)] = initialization_delay

def get_transfer_speed(self, source: Device, target: Device):
if (source, target) not in self.__transfer_speeds:
if self.__host_device is None:
raise ValueError("Host device of network unspecified!")
S_to_H_speed = self.__transfer_speeds[(source, self.__host_device)]
H_to_T_speed = self.__transfer_speeds[(self.__host_device, target)]
return min(S_to_H_speed, H_to_T_speed)
return self.__transfer_speeds[(source, target)]

def get_transfer_initialization_costs(self, source: Device, target: Device):
if (source, target) not in self.__transfer_speeds:
if self.__host_device is None:
raise ValueError("Host device of network unspecified!")
S_to_H_init_costs = self.__transfer_initialization_costs[(source, self.__host_device)]
H_to_T_init_costs = self.__transfer_initialization_costs[(self.__host_device, target)]
return S_to_H_init_costs + H_to_T_init_costs
return self.__transfer_initialization_costs[(source, target)]

def get_free_symbols(self) -> List[Tuple[Symbol, Optional[Expr]]]:
Expand All @@ -37,3 +54,6 @@ def get_free_symbols(self) -> List[Tuple[Symbol, Optional[Expr]]]:
for expr in self.__transfer_initialization_costs.values():
result_list += [(cast(Symbol, s), None) for s in expr.free_symbols]
return result_list

def set_host_device(self, host_device: Device):
self.__host_device = host_device
115 changes: 67 additions & 48 deletions discopop_library/discopop_optimizer/classes/system/System.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,27 @@
# This software may be modified and distributed under the terms of
# the 3-Clause BSD License. See the LICENSE file in the package base
# directory for details.
import json
import os
import pathlib
import warnings
from typing import Dict, List, Tuple, Optional
from typing import Any, Dict, List, Tuple, Optional

from sympy import Float, Symbol, Expr, Integer
from discopop_library.discopop_optimizer.classes.enums.Distributions import FreeSymbolDistribution

from discopop_library.discopop_optimizer.classes.system.Network import Network
from discopop_library.discopop_optimizer.classes.system.devices.CPU import CPU
from discopop_library.discopop_optimizer.classes.system.devices.Device import Device
from discopop_library.discopop_optimizer.classes.system.devices.DeviceTypeEnum import DeviceTypeEnum
from discopop_library.discopop_optimizer.classes.system.devices.GPU import GPU
from discopop_library.discopop_optimizer.OptimizerArguments import OptimizerArguments


class System(object):
__devices: Dict[int, Device]
__host_device_id: int
__network: Network
__next_free_device_id: int
__device_do_all_overhead_models: Dict[Device, Expr]
__device_reduction_overhead_models: Dict[Device, Expr]
__symbol_substitutions: List[
Expand All @@ -36,57 +40,68 @@ class System(object):

def __init__(self, arguments: OptimizerArguments):
self.__devices = dict()
self.__host_device_id = 0
self.__network = Network()
self.__next_free_device_id = 0
self.__device_do_all_overhead_models = dict()
self.__device_reduction_overhead_models = dict()
self.__symbol_substitutions = []

# define a default system
# todo replace with benchmark results and / or make user definable
device_0_threads = Symbol("device_0_threads") # Integer(48)
# register substitution
self.__symbol_substitutions.append(
(device_0_threads, float(16), float(1), float(16), FreeSymbolDistribution.RIGHT_HEAVY)
)
self.__build_from_configuration_file(arguments)

# todo: support the replication of device ids (e.g. CPU-0 and GPU-0)

device_0 = CPU(
Integer(3000000000),
device_0_threads,
openmp_device_id=-1,
device_specific_compiler_flags="COMPILE FOR CPU",
) # Device 0 always acts as the host system
gpu_compiler_flags = "COMPILE FOR CPU"
device_1 = GPU(
Integer(512000000),
Integer(512),
openmp_device_id=0,
device_specific_compiler_flags="COMPILE FOR GPU",
def __build_from_configuration_file(self, arguments: OptimizerArguments):
with open(arguments.system_configuration_path, "r") as f:
system_configuration = json.load(f)
self.__host_device_id = system_configuration["host_device"]
# register devices
for device_dict in system_configuration["devices"]:
if device_dict["device_type"] == DeviceTypeEnum.CPU:
self.__build_CPU(device_dict)
elif device_dict["device_type"] == DeviceTypeEnum.GPU:
self.__build_GPU(device_dict)
else:
raise ValueError("Unknown device type: " + str(device_dict["device_type"]) + " in: " + str(device_dict))
# register host device for device -> device transfers
self.__network.set_host_device(self.get_device(self.__host_device_id))

# register connections
for device_dict in system_configuration["devices"]:
if "transfer_speeds" in device_dict:
# register connection with given transfer speed
self.__network.add_connection(
source=self.get_device(self.__host_device_id),
target=self.get_device(device_dict["device_id"]),
transfer_speed=Float(device_dict["transfer_speeds"]["H2D_MB/s"]),
initialization_delay=Float(device_dict["transfer_init_delays"]["average"]),
) # H2D
self.__network.add_connection(
source=self.get_device(device_dict["device_id"]),
target=self.get_device(self.__host_device_id),
transfer_speed=Float(device_dict["transfer_speeds"]["D2H_MB/s"]),
initialization_delay=Float(device_dict["transfer_init_delays"]["average"]),
) # D2H
else:
# no transfer speed information exists
pass

def __build_CPU(self, device_configuration: Dict[str, Any]):
cpu = CPU(
frequency=Integer(device_configuration["frequency"]),
thread_count=Integer(device_configuration["threads"]),
openmp_device_id=device_configuration["device_id"],
device_specific_compiler_flags="",
)
device_2 = GPU(
Integer(512000000),
Integer(512),
openmp_device_id=1,
device_specific_compiler_flags="COMPILE FOR GPU",
self.add_device(cpu, device_configuration["device_id"])

def __build_GPU(self, device_configuration: Dict[str, Any]):
gpu = GPU(
frequency=Integer(device_configuration["frequency"]),
thread_count=Integer(device_configuration["threads"]),
openmp_device_id=device_configuration["device_id"],
device_specific_compiler_flags="",
)
self.add_device(device_0)
self.add_device(device_1)
self.add_device(device_2)
# define Network
network = self.get_network()
network.add_connection(device_0, device_0, Integer(100000), Integer(0))
network.add_connection(device_0, device_1, Integer(10000), Integer(1000000))
network.add_connection(device_1, device_0, Integer(10000), Integer(1000000))
network.add_connection(device_1, device_1, Integer(100000), Integer(0))

network.add_connection(device_0, device_2, Integer(100), Integer(10000000))
network.add_connection(device_2, device_0, Integer(100), Integer(10000000))
network.add_connection(device_2, device_2, Integer(1000), Integer(0))

network.add_connection(device_1, device_2, Integer(100), Integer(500000))
network.add_connection(device_2, device_1, Integer(100), Integer(500000))

# todo: support the replication of device ids (e.g. CPU-0 and GPU-0)
self.add_device(gpu, device_configuration["device_id"])

def set_device_doall_overhead_model(self, device: Device, model: Expr):
print("System: Set DOALL overhead model: ", model)
Expand All @@ -108,9 +123,7 @@ def get_device_reduction_overhead_model(self, device: Device) -> Expr:
return Expr(Integer(0))
return self.__device_reduction_overhead_models[device]

def add_device(self, device: Device):
device_id = self.__next_free_device_id
self.__next_free_device_id += 1
def add_device(self, device: Device, device_id: int):
self.__devices[device_id] = device

def get_device(self, device_id: Optional[int]) -> Device:
Expand Down Expand Up @@ -141,7 +154,13 @@ def get_device_id(self, device: Device) -> int:
return key
raise ValueError("Unknown device: ", device)

def get_host_device_id(self) -> int:
return self.__host_device_id

def get_symbol_values_and_distributions(
self,
) -> List[Tuple[Symbol, Optional[float], Optional[float], Optional[float], Optional[FreeSymbolDistribution]]]:
return self.__symbol_substitutions


# define a default system
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# This file is part of the DiscoPoP software (http://www.discopop.tu-darmstadt.de)
#
# Copyright (c) 2020, Technische Universitaet Darmstadt, Germany
#
# This software may be modified and distributed under the terms of
# the 3-Clause BSD License. See the LICENSE file in the package base
# directory for details.

from enum import IntEnum


class DeviceTypeEnum(IntEnum):
CPU = 0
GPU = 1
76 changes: 76 additions & 0 deletions discopop_library/discopop_optimizer/classes/system/system_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# This file is part of the DiscoPoP software (http://www.discopop.tu-darmstadt.de)
#
# Copyright (c) 2020, Technische Universitaet Darmstadt, Germany
#
# This software may be modified and distributed under the terms of
# the 3-Clause BSD License. See the LICENSE file in the package base
# directory for details.

import json
import os
from typing import Any, Dict, List, Union

from sympy import Integer

from discopop_library.discopop_optimizer.classes.system.devices.CPU import CPU
from discopop_library.discopop_optimizer.classes.system.devices.DeviceTypeEnum import DeviceTypeEnum
from discopop_library.discopop_optimizer.classes.system.devices.GPU import GPU


def generate_default_system_configuration(file_path: str):
"""Generates a system configuration file using the default values if none exists so far."""
if os.path.exists(file_path):
return
# build default system configuration
system_configuration: Dict[str, Any] = dict()
devices: List[Dict[str, Union[float, int, Dict[str, float]]]] = []
# configure host device
host_device: Dict[str, Union[float, int, Dict[str, float]]] = {
"device_id": 0,
"device_type": DeviceTypeEnum.CPU,
"frequency": 3000000000,
"processors": 16,
"threads": 16,
}
# configure gpu_1
gpu_1: Dict[str, Union[float, int, Dict[str, float]]] = {
"compute_init_delays": {"target_teams_distribute_parallel_for": 0.01},
"device_id": 1,
"device_type": DeviceTypeEnum.GPU,
"frequency": 512000000,
"processors": 128,
"teams": 3200,
"threads": 3200,
"transfer_init_delays": {
"target_data_update": 3.6e-05,
"target_enter_data": 6.5e-05,
"target_exit_data": 2e-06,
"average": 4e-05,
},
"transfer_speeds": {"D2H_MB/s": 1800, "H2D_MB/s": 3600}, # MB/s
}
# configure gpu_2
gpu_2: Dict[str, Union[float, int, Dict[str, float]]] = {
"compute_init_delays": {"target_teams_distribute_parallel_for": 0.005},
"device_id": 2,
"device_type": DeviceTypeEnum.GPU,
"frequency": 512000000,
"processors": 128,
"teams": 3200,
"threads": 3200,
"transfer_init_delays": {
"target_data_update": 7.1e-05,
"target_enter_data": 0.0002,
"target_exit_data": 5e-06,
"average": 6e-05,
},
"transfer_speeds": {"D2H_MB/s": 1900, "H2D_MB/s": 4200}, # MB/s
}
# assemble system_configuration
devices = [host_device, gpu_1, gpu_2]
system_configuration["devices"] = devices
system_configuration["host_device"] = host_device["device_id"]

# output to file
with open(file_path, "w") as f:
json.dump(system_configuration, f)
3 changes: 3 additions & 0 deletions discopop_library/discopop_optimizer/optimizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
)
from discopop_library.discopop_optimizer.classes.enums.Distributions import FreeSymbolDistribution
from discopop_library.discopop_optimizer.classes.nodes.FunctionRoot import FunctionRoot
from discopop_library.discopop_optimizer.classes.system.system_utils import generate_default_system_configuration
from discopop_library.discopop_optimizer.gui.queries.ValueTableQuery import query_user_for_symbol_values
from discopop_library.discopop_optimizer.optimization.evaluate import evaluate_configuration
from discopop_library.discopop_optimizer.optimization.evaluate_all_decision_combinations import (
Expand Down Expand Up @@ -78,6 +79,8 @@ def run(arguments: OptimizerArguments):
+ "\nExpected file: "
+ file_mapping_path
)
if not os.path.exists(arguments.system_configuration_path):
generate_default_system_configuration(arguments.system_configuration_path)

# create a new session, load data from previous steps)
if arguments.verbose:
Expand Down

0 comments on commit 9105f2c

Please sign in to comment.