Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Empirical autotuning of applicable suggestions #640

Merged
merged 25 commits into from
Jul 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
35d13a5
add autotuner module skeleton
lukasrothenberger Jul 16, 2024
2c01c58
setup logger
lukasrothenberger Jul 16, 2024
43b1ec3
check folder structure
lukasrothenberger Jul 16, 2024
94b52cd
wip
lukasrothenberger Jul 16, 2024
96fcef3
Merge branch 'release/4.0.0' into dev/empirical_autotuning
lukasrothenberger Jul 16, 2024
38e336e
wip (execute reference)
lukasrothenberger Jul 16, 2024
b8e08cd
copy configurations
lukasrothenberger Jul 16, 2024
bc5a078
update FileMapping in copied folder
lukasrothenberger Jul 16, 2024
c80e5fc
add notes
lukasrothenberger Jul 16, 2024
c81abb1
load hotspots and suggestions
lukasrothenberger Jul 16, 2024
91e7186
identify applicable suggestions
lukasrothenberger Jul 16, 2024
b8cdd09
apply suggestions
lukasrothenberger Jul 16, 2024
9eb9ad1
add reference for suggestion selection
lukasrothenberger Jul 16, 2024
5a09474
greedy search
lukasrothenberger Jul 16, 2024
606b9b4
show speedup
lukasrothenberger Jul 16, 2024
d47366f
cleanup output
lukasrothenberger Jul 16, 2024
ca59b1e
fix: prevent unnecessary executions
lukasrothenberger Jul 16, 2024
2ef4054
formatting
lukasrothenberger Jul 16, 2024
510ff8f
mypy type fixes
lukasrothenberger Jul 17, 2024
48335be
cleanup folders
lukasrothenberger Jul 17, 2024
4366402
add timeout
lukasrothenberger Jul 17, 2024
519e041
updated organization chart
lukasrothenberger Jul 17, 2024
99d0ab2
add documentation
lukasrothenberger Jul 17, 2024
09afe28
add license tags
lukasrothenberger Jul 17, 2024
e96d8cf
formatting
lukasrothenberger Jul 17, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion discopop_explorer/PEGraphX.py
Original file line number Diff line number Diff line change
Expand Up @@ -769,7 +769,7 @@ def map_static_and_dynamic_dependencies(self) -> None:

def calculateFunctionMetadata(
self,
hotspot_information: Optional[Dict[HotspotType, List[Tuple[int, int, HotspotNodeType, str]]]] = None,
hotspot_information: Optional[Dict[HotspotType, List[Tuple[int, int, HotspotNodeType, str, float]]]] = None,
func_nodes: Optional[List[FunctionNode]] = None,
) -> None:
# store id of parent function in each node
Expand Down
9 changes: 3 additions & 6 deletions discopop_explorer/discopop_explorer.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ def __run(
enable_patterns: str = "*",
enable_task_pattern: bool = False,
enable_detection_of_scheduling_clauses: bool = False,
hotspot_functions: Optional[Dict[HotspotType, List[Tuple[int, int, HotspotNodeType, str]]]] = None,
hotspot_functions: Optional[Dict[HotspotType, List[Tuple[int, int, HotspotNodeType, str, float]]]] = None,
load_existing_doall_and_reduction_patterns: bool = False,
) -> DetectionResult:
pet = PEGraphX.from_parsed_input(*parse_inputs(cu_xml, dep_file, reduction_file, file_mapping)) # type: ignore
Expand Down Expand Up @@ -209,6 +209,7 @@ def run(arguments: ExplorerArguments) -> None:
hotspots = load_hotspots(
HotspotLoaderArguments(
verbose=True,
dot_discopop_path=os.getcwd(),
get_loops=True,
get_functions=True,
get_YES=True,
Expand Down Expand Up @@ -265,8 +266,6 @@ def run(arguments: ExplorerArguments) -> None:
with open(arguments.enable_json_file, "w+") as f:
json.dump(res, f, indent=2, cls=PatternBaseSerializer)



# initialize the line_mapping.json
initialize_line_mapping(load_file_mapping(arguments.file_mapping_file), arguments.project_path)

Expand All @@ -292,7 +291,7 @@ def run(arguments: ExplorerArguments) -> None:
with open(arguments.enable_profiling_dump_file, "w+") as f:
stats = pstats2.Stats(profile, stream=f).sort_stats("tottime").reverse_order()
stats.print_stats()

except BaseException as be:
# required to correctly write profiling data if the program terminates
# print profiling results
Expand All @@ -304,5 +303,3 @@ def run(arguments: ExplorerArguments) -> None:
stats = pstats2.Stats(profile, stream=f).sort_stats("tottime").reverse_order()
stats.print_stats()
raise be


4 changes: 2 additions & 2 deletions discopop_explorer/pattern_detection.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ def detect_patterns(
enable_patterns: str,
enable_task_pattern: bool,
enable_detection_of_scheduling_clauses: bool,
hotspots: Optional[Dict[HotspotType, List[Tuple[int, int, HotspotNodeType, str]]]],
hotspots: Optional[Dict[HotspotType, List[Tuple[int, int, HotspotNodeType, str, float]]]],
) -> DetectionResult:
"""Runs pattern discovery on the CU graph"""
self.__merge(False, True)
Expand Down Expand Up @@ -207,7 +207,7 @@ def load_existing_doall_and_reduction_patterns(
enable_patterns: str,
enable_task_pattern: bool,
enable_detection_of_scheduling_clauses: bool,
hotspots: Optional[Dict[HotspotType, List[Tuple[int, int, HotspotNodeType, str]]]],
hotspots: Optional[Dict[HotspotType, List[Tuple[int, int, HotspotNodeType, str, float]]]],
) -> DetectionResult:
"""skips the pattern discovery on the CU graph and loads a pre-existing pattern file"""
self.__merge(False, True)
Expand Down
2 changes: 1 addition & 1 deletion discopop_explorer/pattern_detectors/do_all_detector.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def __str__(self) -> str:

def run_detection(
pet: PEGraphX,
hotspots: Optional[Dict[HotspotType, List[Tuple[int, int, HotspotNodeType, str]]]],
hotspots: Optional[Dict[HotspotType, List[Tuple[int, int, HotspotNodeType, str, float]]]],
reduction_info: List[ReductionInfo],
) -> List[DoAllInfo]:
"""Search for do-all loop pattern
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ def __str__(self) -> str:


def run_detection(
pet: PEGraphX, hotspots: Optional[Dict[HotspotType, List[Tuple[int, int, HotspotNodeType, str]]]]
pet: PEGraphX, hotspots: Optional[Dict[HotspotType, List[Tuple[int, int, HotspotNodeType, str, float]]]]
) -> List[GDInfo]:
"""Detects geometric decomposition

Expand Down
2 changes: 1 addition & 1 deletion discopop_explorer/pattern_detectors/pipeline_detector.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ def is_pipeline_subnode(root: Node, current: Node, children_start_lines: List[Li


def run_detection(
pet: PEGraphX, hotspots: Optional[Dict[HotspotType, List[Tuple[int, int, HotspotNodeType, str]]]]
pet: PEGraphX, hotspots: Optional[Dict[HotspotType, List[Tuple[int, int, HotspotNodeType, str, float]]]]
) -> List[PipelineInfo]:
"""Search for pipeline pattern on all the loops in the graph
except for doall loops
Expand Down
2 changes: 1 addition & 1 deletion discopop_explorer/pattern_detectors/reduction_detector.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ def __str__(self) -> str:


def run_detection(
pet: PEGraphX, hotspots: Optional[Dict[HotspotType, List[Tuple[int, int, HotspotNodeType, str]]]]
pet: PEGraphX, hotspots: Optional[Dict[HotspotType, List[Tuple[int, int, HotspotNodeType, str, float]]]]
) -> List[ReductionInfo]:
"""Search for reduction pattern

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ def build_preprocessed_graph_and_run_detection(
cu_inst_result_file: str,
llvm_cxxfilt_path: Optional[str],
discopop_build_path: Optional[str],
hotspots: Optional[Dict[HotspotType, List[Tuple[int, int, HotspotNodeType, str]]]],
hotspots: Optional[Dict[HotspotType, List[Tuple[int, int, HotspotNodeType, str, float]]]],
reduction_info: List[ReductionInfo],
) -> List[PatternInfo]:
"""execute preprocessing of given cu xml file and construct a new cu graph.
Expand Down
4 changes: 2 additions & 2 deletions discopop_explorer/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -937,15 +937,15 @@ def __is_written_prior_to_task(pet: PEGraphX, var: Variable, task: Node) -> bool
def filter_for_hotspots(
pet: PEGraphX,
nodes: List[Node],
hotspot_information: Optional[Dict[HotspotType, List[Tuple[int, int, HotspotNodeType, str]]]],
hotspot_information: Optional[Dict[HotspotType, List[Tuple[int, int, HotspotNodeType, str, float]]]],
) -> List[Node]:
"""Removes such nodes from the list which are not identified as hotspots"""
if hotspot_information is None:
return nodes
if len(hotspot_information) == 0:
return nodes
# collect hotspot information
all_hotspot_descriptions: List[Tuple[int, int, HotspotNodeType, str]] = []
all_hotspot_descriptions: List[Tuple[int, int, HotspotNodeType, str, float]] = []
for key in hotspot_information:
for entry in hotspot_information[key]:
all_hotspot_descriptions.append(entry)
Expand Down
50 changes: 50 additions & 0 deletions discopop_library/EmpiricalAutotuning/ArgumentClasses.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# 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 dataclasses import dataclass
import logging
import os
from discopop_library.ArgumentClasses.GeneralArguments import GeneralArguments

logger = logging.getLogger("AutotunerArguments")


@dataclass
class AutotunerArguments(GeneralArguments):
"""Container Class for the arguments passed to the discopop autotuner"""

project_path: str
dot_dp_path: str

def __post_init__(self) -> None:
self.__validate()

def log(self) -> None:
logger.debug("Arguments:")
for entry in self.__dict__:
logger.debug("-- " + str(entry) + ": " + str(self.__dict__[entry]))

def __validate(self) -> None:
"""Validate the arguments passed to the discopop autotuner, e.g check if given files exist"""

required_files = [
self.project_path,
os.path.join(self.project_path, "DP_COMPILE.sh"),
os.path.join(self.project_path, "DP_EXECUTE.sh"),
self.dot_dp_path,
os.path.join(self.dot_dp_path, "FileMapping.txt"),
os.path.join(self.dot_dp_path, "profiler"),
os.path.join(self.dot_dp_path, "explorer"),
os.path.join(self.dot_dp_path, "patch_generator"),
os.path.join(self.dot_dp_path, "hotspot_detection"),
os.path.join(self.dot_dp_path, "hotspot_detection", "Hotspots.json"),
os.path.join(self.dot_dp_path, "line_mapping.json"),
]
for file in required_files:
if not os.path.exists(file):
raise FileNotFoundError(file)
142 changes: 142 additions & 0 deletions discopop_library/EmpiricalAutotuning/Autotuner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# 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 logging
import os
from typing import List, Optional, Set, Tuple, cast

import jsonpickle # type: ignore
from discopop_library.EmpiricalAutotuning.ArgumentClasses import AutotunerArguments
from discopop_library.EmpiricalAutotuning.Classes.CodeConfiguration import CodeConfiguration
from discopop_library.EmpiricalAutotuning.Classes.ExecutionResult import ExecutionResult
from discopop_library.EmpiricalAutotuning.Types import SUGGESTION_ID
from discopop_library.EmpiricalAutotuning.utils import get_applicable_suggestion_ids
from discopop_library.HostpotLoader.HotspotLoaderArguments import HotspotLoaderArguments
from discopop_library.HostpotLoader.HotspotType import HotspotType
from discopop_library.HostpotLoader.hostpot_loader import run as load_hotspots
from discopop_library.result_classes.DetectionResult import DetectionResult

logger = logging.getLogger("Autotuner")

configuration_counter = 1


def get_unique_configuration_id() -> int:
global configuration_counter
buffer = configuration_counter
configuration_counter += 1
return buffer


def run(arguments: AutotunerArguments) -> None:
logger.info("Starting discopop autotuner.")
debug_stats: List[Tuple[List[SUGGESTION_ID], float]] = []

# get untuned reference result
reference_configuration = CodeConfiguration(arguments.project_path, arguments.dot_dp_path)
reference_configuration.execute(timeout=None)
timeout_after = cast(ExecutionResult, reference_configuration.execution_result).runtime * 2
debug_stats.append(([], cast(ExecutionResult, reference_configuration.execution_result).runtime))

# load hotspots
hsl_arguments = HotspotLoaderArguments(
arguments.log_level, arguments.write_log, False, arguments.dot_dp_path, True, False, True, True, True
)
hotspot_information = load_hotspots(hsl_arguments)
logger.debug("loaded hotspots")
# load suggestions
with open(os.path.join(arguments.dot_dp_path, "explorer", "detection_result_dump.json"), "r") as f:
tmp_str = f.read()
detection_result: DetectionResult = jsonpickle.decode(tmp_str)
logger.debug("loaded suggestions")

# greedy search for best suggestion configuration:
# for all hotspot types in descending importance:
visited_configurations: List[List[SUGGESTION_ID]] = []
best_suggestion_configuration: Tuple[List[SUGGESTION_ID], CodeConfiguration] = ([], reference_configuration)
for hotspot_type in [HotspotType.YES, HotspotType.MAYBE, HotspotType.NO]:
if hotspot_type not in hotspot_information:
continue
# for all loops in descending order by average execution time
loop_tuples = hotspot_information[hotspot_type]
sorted_loop_tuples = sorted(loop_tuples, key=lambda x: x[4], reverse=True)
for loop_tuple in sorted_loop_tuples:
# identify all applicable suggestions for this loop
logger.debug(str(hotspot_type) + " loop: " + str(loop_tuple))
# create code and execute for all applicable suggestions
applicable_suggestions = get_applicable_suggestion_ids(loop_tuple[0], loop_tuple[1], detection_result)
logger.debug("--> applicable suggestions: " + str(applicable_suggestions))
suggestion_effects: List[Tuple[List[SUGGESTION_ID], CodeConfiguration]] = []
for suggestion_id in applicable_suggestions:
current_config = best_suggestion_configuration[0] + [suggestion_id]
if current_config in visited_configurations:
continue
visited_configurations.append(current_config)
tmp_config = reference_configuration.create_copy(get_unique_configuration_id)
tmp_config.apply_suggestions(arguments, current_config)
tmp_config.execute(timeout=timeout_after)
# only consider valid code
if (
cast(ExecutionResult, tmp_config.execution_result).result_valid
and cast(ExecutionResult, tmp_config.execution_result).return_code == 0
):
suggestion_effects.append((current_config, tmp_config))
debug_stats.append((current_config, cast(ExecutionResult, tmp_config.execution_result).runtime))
# add current best configuration for reference / to detect "no suggestions is beneficial"
suggestion_effects.append(best_suggestion_configuration)

logger.debug(
"Suggestion effects:\n" + str([(str(t[0]), str(t[1].execution_result)) for t in suggestion_effects])
)

# select the best option and save it in the current best_configuration
sorted_suggestion_effects = sorted(
suggestion_effects, key=lambda x: cast(ExecutionResult, x[1].execution_result).runtime
)
buffer = sorted_suggestion_effects[0]
best_suggestion_configuration = buffer
sorted_suggestion_effects = sorted_suggestion_effects[1:] # in preparation of cleanup step
logger.debug(
"Current best configuration: "
+ str(best_suggestion_configuration[0])
+ " stored at "
+ best_suggestion_configuration[1].root_path
)
# cleanup other configurations (excluding original version)
logger.debug("Cleanup:")
for _, config in sorted_suggestion_effects:
if config.root_path == reference_configuration.root_path:
continue
config.deleteFolder()

# continue with the next loop

# show debug stats
stats_str = "Configuration measurements:\n"
stats_str += "[time]\t[applied suggestions]\n"
for stats in sorted(debug_stats, key=lambda x: x[1], reverse=True):
stats_str += str(round(stats[1], 3)) + "s" + "\t" + str(stats[0]) + "\n"
logger.info(stats_str)

# calculate result statistics
speedup = (
cast(ExecutionResult, reference_configuration.execution_result).runtime
/ cast(ExecutionResult, best_suggestion_configuration[1].execution_result).runtime
)
parallel_efficiency = speedup * (1 / cast(int, (0 if os.cpu_count() is None else os.cpu_count())))

# show result and statistics
if best_suggestion_configuration[1] is None:
print("No valid configuration found!")
else:
print("##############################")
print("Best configuration located at: " + best_suggestion_configuration[1].root_path)
print("Applied suggestions: " + str(best_suggestion_configuration[0]))
print("Speedup: ", round(speedup, 3))
print("Parallel efficiency: ", round(parallel_efficiency, 3))
print("##############################")
Loading
Loading