Skip to content

Commit

Permalink
fix(optimizer): minor robustness fixes
Browse files Browse the repository at this point in the history
chore(optimizer): formatting
  • Loading branch information
lukasrothenberger committed Dec 18, 2023
1 parent b028f50 commit 64b8436
Show file tree
Hide file tree
Showing 16 changed files with 74 additions and 38 deletions.
9 changes: 8 additions & 1 deletion discopop_library/CodeGenerator/CodeGenerator.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ def from_json_strings_with_mapping(
compile_check_command: Optional[str] = None,
CC="clang",
CXX="clang++",
host_device_id=0,
) -> Dict[int, str]:
"""Insert the parallel patterns described by the given json strings into the original source code.
Returns a dictionary which maps the ID of every modified file to the updated contents of the file.
Expand All @@ -150,7 +151,13 @@ def from_json_strings_with_mapping(
continue
for json_str, device_id, device_type in pattern_json_strings_with_mapping_by_type[type_str]:
unpacked_suggestions.append(
UnpackedSuggestion(type_str, jsons.loads(json_str), device_id=device_id, device_type=device_type)
UnpackedSuggestion(
type_str,
jsons.loads(json_str),
device_id=device_id,
device_type=device_type,
host_device_id=host_device_id,
)
)

# create a dictionary mapping fileIds to ContentBuffer elements
Expand Down
10 changes: 6 additions & 4 deletions discopop_library/CodeGenerator/classes/UnpackedSuggestion.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,14 @@ class UnpackedSuggestion(object):
end_line: int
device_id: Optional[DeviceID]
device_type: Optional[DeviceTypeEnum]
host_device_id: int

def __init__(self, type_str: str, values: Dict[str, Any], device_id=None, device_type=None):
def __init__(self, type_str: str, values: Dict[str, Any], device_id=None, device_type=None, host_device_id=0):
self.type = type_str
self.values = values
self.device_id = device_id
self.device_type = device_type
self.host_device_id = host_device_id

# get start and end line of target section
self.file_id = int(self.values["start_line"].split(":")[0])
Expand All @@ -56,10 +58,10 @@ def __get_device_update_pragmas(self):
openmp_target_device_id = self.values["openmp_target_device_id"]
print("IS FIRST DATA OCCURRENCE?: ", is_first_data_occurrence)

if source_device_id == 0 and target_device_id == 0:
if source_device_id == self.host_device_id and target_device_id == self.host_device_id:
# no update required
pass
elif source_device_id == 0 and target_device_id != 0:
elif source_device_id == self.host_device_id and target_device_id != self.host_device_id:
# update type to
if is_first_data_occurrence:
pragma.pragma_str = "#pragma omp target enter data map(to:"
Expand All @@ -74,7 +76,7 @@ def __get_device_update_pragmas(self):
+ ")"
)

elif source_device_id != 0 and target_device_id == 0:
elif source_device_id != self.host_device_id and target_device_id == self.host_device_id:
# update type from
if is_first_data_occurrence:
pragma.pragma_str = "#pragma omp target exit data map(from:"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,21 @@ class ParallelConfiguration(object):
data_movement: List[Update]
pattern_id: Optional[int] # used for the representation via the patch generator
decisions: List[int]
host_device_id: int

def __init__(self, decisions: List[int]):
def __init__(self, decisions: List[int], host_device_id: int):
self.applied_patterns = []
self.data_movement = []
self.pattern_id = None
self.decisions = decisions
self.host_device_id = host_device_id

def reconstruct_from_file(self, file_path: str):
with open(file_path, "r") as f:
loaded_data = json.load(f)
self.applied_patterns = loaded_data["applied_patterns"]
self.pattern_id = loaded_data["pattern_id"]
self.host_device_id = loaded_data["host_device_id"]

for values in loaded_data["data_movement"]:
self.data_movement.append(construct_update_from_dict(values))
Expand All @@ -45,6 +48,7 @@ def dump_to_file(self, file_path: str):
dumpable_dict["applied_patterns"] = self.applied_patterns
dumpable_dict["data_movement"] = [update.toDict() for update in self.data_movement]
dumpable_dict["pattern_id"] = self.pattern_id
dumpable_dict["host_device_id"] = self.host_device_id

with open(file_path, "w") as f:
json.dump(dumpable_dict, f)
Expand Down
3 changes: 2 additions & 1 deletion discopop_library/PatchGenerator/from_configuration_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def from_configuration_file(
suggestion_strings_with_mapping: Dict[str, List[Tuple[str, DeviceID, Optional[DeviceTypeEnum]]]] = dict()
if arguments.verbose:
print("Loading configuration file: ", arguments.from_configuration_file)
config = ParallelConfiguration([])
config = ParallelConfiguration([], -1)
config.reconstruct_from_file(arguments.from_configuration_file)

# load detectionresult and pet
Expand Down Expand Up @@ -76,6 +76,7 @@ def from_configuration_file(
CC=arguments.CC,
CXX=arguments.CXX,
skip_compilation_check=True,
host_device_id=config.host_device_id,
)
print("MODIFIED CODE: ")
print(file_id_to_modified_code)
Expand Down
28 changes: 17 additions & 11 deletions discopop_library/discopop_optimizer/DataTransfers/DataTransfers.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@


def calculate_data_transfers(
graph: nx.DiGraph, function_performance_models: Dict[FunctionRoot, List[CostModel]]
graph: nx.DiGraph, function_performance_models: Dict[FunctionRoot, List[CostModel]], experiment
) -> Dict[FunctionRoot, List[Tuple[CostModel, ContextObject]]]:
"""Calculate data transfers for each performance model and append the respective ContextObject to the result."""
result_dict: Dict[FunctionRoot, List[Tuple[CostModel, ContextObject]]] = dict()
Expand All @@ -30,23 +30,25 @@ def calculate_data_transfers(
for model in function_performance_models[function]:
# create a ContextObject for the current path
context = ContextObject(function.node_id, [function.device_id])
context = get_path_context(function.node_id, graph, model, context)
context = get_path_context(function.node_id, graph, model, context, experiment)
result_dict[function].append((model, context))
return result_dict


def get_path_context(node_id: int, graph: nx.DiGraph, model: CostModel, context: ContextObject) -> ContextObject:
def get_path_context(
node_id: int, graph: nx.DiGraph, model: CostModel, context: ContextObject, experiment
) -> ContextObject:
"""passes the context Object along the path and returns the context once the end has been reached"""
# push device id to stack if necessary
node_data = data_at(graph, node_id)
if node_data.device_id is not None:
context.last_seen_device_ids.append(node_data.device_id)

# calculate context modifications for the current node
context = __check_current_node(node_id, graph, model, context)
context = __check_current_node(node_id, graph, model, context, experiment)

# calculate context modifications for the children of the current node
context = __check_children(node_id, graph, model, context)
context = __check_children(node_id, graph, model, context, experiment)

# pop device id from stack if necessary
if node_data.device_id is not None:
Expand All @@ -61,7 +63,7 @@ def get_path_context(node_id: int, graph: nx.DiGraph, model: CostModel, context:
successors = get_successors(graph, node_id)
if len(successors) == 1:
# pass context to the single successor
return get_path_context(successors[0], graph, model, context)
return get_path_context(successors[0], graph, model, context, experiment)

elif len(successors) == 0:
# no successor exists, return the current context
Expand All @@ -84,10 +86,12 @@ def get_path_context(node_id: int, graph: nx.DiGraph, model: CostModel, context:
)
# suitable successor identified.
# pass the current context to the successor
return get_path_context(suitable_successors[0], graph, model, context)
return get_path_context(suitable_successors[0], graph, model, context, experiment)


def __check_current_node(node_id: int, graph: nx.DiGraph, model: CostModel, context: ContextObject) -> ContextObject:
def __check_current_node(
node_id: int, graph: nx.DiGraph, model: CostModel, context: ContextObject, experiment
) -> ContextObject:
"""Check if the given node results in modifications to the given context.
Return a modified version of the context which contains the required updates."""
# due to the Read-Compute-Write paradigm used to create the Computational Units,
Expand All @@ -105,7 +109,7 @@ def __check_current_node(node_id: int, graph: nx.DiGraph, model: CostModel, cont
return updated_context

context = context.calculate_and_perform_necessary_updates(
node_data.read_memory_regions, cast(int, context.last_seen_device_ids[-1]), node_data.node_id, graph
node_data.read_memory_regions, cast(int, context.last_seen_device_ids[-1]), node_data.node_id, graph, experiment
)

# add the writes performed by the given node to the context
Expand All @@ -114,10 +118,12 @@ def __check_current_node(node_id: int, graph: nx.DiGraph, model: CostModel, cont
return context


def __check_children(node_id: int, graph: nx.DiGraph, model: CostModel, context: ContextObject) -> ContextObject:
def __check_children(
node_id: int, graph: nx.DiGraph, model: CostModel, context: ContextObject, experiment
) -> ContextObject:
# pass context to all children
for child in get_children(graph, node_id):
# reset last_visited_node_id inbetween visiting children
context.last_visited_node_id = node_id
context = get_path_context(child, graph, model, context)
context = get_path_context(child, graph, model, context, experiment)
return context
4 changes: 2 additions & 2 deletions discopop_library/discopop_optimizer/OptimizationGraph.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def __init__(
experiment, experiment.optimization_graph
)
sequential_function_performance_models_with_transfers = calculate_data_transfers(
experiment.optimization_graph, sequential_function_performance_models
experiment.optimization_graph, sequential_function_performance_models, experiment
)
sequential_complete_performance_models = add_data_transfer_costs(
experiment.optimization_graph,
Expand All @@ -80,7 +80,7 @@ def __init__(

# calculate and append necessary data transfers to the models
function_performance_models_with_transfers = calculate_data_transfers(
experiment.optimization_graph, function_performance_models
experiment.optimization_graph, function_performance_models, experiment
)

# calculate and append costs of data transfers to the performance models
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ def get_sequential_cost_model(experiment: Experiment) -> Dict[FunctionRoot, List
experiment, experiment.optimization_graph
)
sequential_function_performance_models_with_transfers = calculate_data_transfers(
experiment.optimization_graph, sequential_function_performance_models
experiment.optimization_graph, sequential_function_performance_models, experiment
)
sequential_complete_performance_models = add_data_transfer_costs(
experiment.optimization_graph,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,12 @@ def __str__(self):
return str(self.necessary_updates)

def calculate_and_perform_necessary_updates(
self, node_reads: Set[ReadDataAccess], reading_device_id: int, reading_node_id: int, graph: nx.DiGraph
self,
node_reads: Set[ReadDataAccess],
reading_device_id: int,
reading_node_id: int,
graph: nx.DiGraph,
experiment,
):
"""checks if the specified list of ReadDataAccesses performed by the specified device id makes updates
necessary. If so, the updates will get append to the list of updates of the current ContextObject.
Expand Down Expand Up @@ -86,16 +91,26 @@ def calculate_and_perform_necessary_updates(
# print("Device <-> Device update required!")

# check if data is known to the host
if data_write.memory_region not in self.seen_writes_by_device[0]:
self.seen_writes_by_device[0][data_write.memory_region] = set()
if data_write not in self.seen_writes_by_device[0][data_write.memory_region]:
if (
data_write.memory_region
not in self.seen_writes_by_device[experiment.get_system().get_host_device_id()]
):
self.seen_writes_by_device[experiment.get_system().get_host_device_id()][
data_write.memory_region
] = set()
if (
data_write
not in self.seen_writes_by_device[experiment.get_system().get_host_device_id()][
data_write.memory_region
]
):
# register source device -> host update
required_updates.add(
Update(
source_node_id=self.last_visited_node_id,
target_node_id=reading_node_id,
source_device_id=device_id,
target_device_id=0, # reading_device_id,
target_device_id=experiment.get_system().get_host_device_id(), # reading_device_id,
write_data_access=data_write,
is_first_data_occurrence=is_first_data_occurrence,
source_cu_id=data_at(graph, self.last_visited_node_id).original_cu_id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def __init__(self, node_id: int, experiment, cu_id: Optional[NodeID], name: str)
parallelizable_workload=Integer(0),
)
self.name = name
self.device_id = 0
self.device_id = experiment.get_system().get_host_device_id()
function_name = "function" + "_" + str(self.node_id) + "_" + self.name
self.parallelizable_costs = Symbol(function_name + "-parallelizable")
self.sequential_costs = Symbol(function_name + "-sequential")
Expand Down
4 changes: 2 additions & 2 deletions discopop_library/discopop_optimizer/classes/system/System.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class System(object):

def __init__(self, arguments: OptimizerArguments):
self.__devices = dict()
self.__host_device_id = 0
self.__host_device_id = -1
self.__network = Network()
self.__device_do_all_overhead_models = dict()
self.__device_reduction_overhead_models = dict()
Expand Down Expand Up @@ -128,7 +128,7 @@ def add_device(self, device: Device, device_id: int):

def get_device(self, device_id: Optional[int]) -> Device:
if device_id is None:
return self.__devices[0]
return self.__devices[self.get_host_device_id()]
return self.__devices[device_id]

def get_device_ids_by_type(self, device_type: type) -> List[int]:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def evaluate_configuration(
)

function_performance_models = calculate_data_transfers(
experiment.optimization_graph, function_performance_models_without_context
experiment.optimization_graph, function_performance_models_without_context, experiment
)
function_performance_models = add_data_transfer_costs(
experiment.optimization_graph,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,9 @@ def __dump_result_to_file_using_pattern_ids(
# dump the best option
for combination_tuple in sorted(costs_dict.keys(), key=lambda x: costs_dict[x]):
new_key_2 = []
best_configuration = ParallelConfiguration(list(combination_tuple))
best_configuration = ParallelConfiguration(
list(combination_tuple), experiment.get_system().get_host_device_id()
)
# collect applied suggestions
for node_id in combination_tuple:
# find pattern id
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,7 @@ def __dump_result(
# dump the best option
for idx, fitness_value in sorted(enumerate(fitness), key=lambda x: x[1]):
new_key_2 = []
best_configuration = ParallelConfiguration(population[idx])
best_configuration = ParallelConfiguration(population[idx], experiment.get_system().get_host_device_id())
for node_id in population[idx]:
# find pattern id
for pattern_id in experiment.suggestion_to_node_ids_dict:
Expand Down
4 changes: 2 additions & 2 deletions discopop_library/discopop_optimizer/optimizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,13 +118,13 @@ def run(arguments: OptimizerArguments):
if arguments.doall_microbench_file != "None":
# construct and set overhead model for doall suggestions
system.set_device_doall_overhead_model(
system.get_device(0),
system.get_device(system.get_host_device_id()),
ExtrapInterpolatedMicrobench(arguments.doall_microbench_file).getFunctionSympy(),
)
if arguments.reduction_microbench_file != "None":
# construct and set overhead model for reduction suggestions
system.set_reduction_overhead_model(
system.get_device(0),
system.get_device(system.get_host_device_id()),
ExtrapInterpolatedMicrobench(arguments.reduction_microbench_file).getFunctionSympy(
benchType=MicrobenchType.FOR
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def find_quasi_optimal_using_random_samples(
tmp_dict = dict()
tmp_dict[function_root] = [get_random_path(experiment, graph, function_root.node_id, must_contain=None)]
try:
random_paths.append(calculate_data_transfers(graph, tmp_dict)[function_root][0])
random_paths.append(calculate_data_transfers(graph, tmp_dict, experiment)[function_root][0])
i += 1
except ValueError as ve:
if verbose:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,7 @@ def get_locally_optimized_models(
)
# calculate and append necessary data transfers to the models
performance_models_with_transfers = calculate_data_transfers(
graph,
{cast(FunctionRoot, data_at(graph, function_node)): performance_models},
graph, {cast(FunctionRoot, data_at(graph, function_node)): performance_models}, experiment
)

# calculate and append costs of data transfers to the performance models
Expand Down Expand Up @@ -146,7 +145,7 @@ def get_locally_optimized_models(
)
# calculate and append necessary data transfers to the models
performance_models_with_transfers = calculate_data_transfers(
graph, {cast(FunctionRoot, data_at(graph, function_node)): performance_models}
graph, {cast(FunctionRoot, data_at(graph, function_node)): performance_models}, experiment
)

# calculate and append costs of data transfers to the performance models
Expand Down

0 comments on commit 64b8436

Please sign in to comment.