diff --git a/llvm/projects/hpvm-tensor-rt/code_autogenerators/benchmark_testing_automator.py b/llvm/projects/hpvm-tensor-rt/code_autogenerators/benchmark_testing_automator.py new file mode 100644 index 0000000000000000000000000000000000000000..5f5c28032d721dcf1e77ab52407a165c0251deb2 --- /dev/null +++ b/llvm/projects/hpvm-tensor-rt/code_autogenerators/benchmark_testing_automator.py @@ -0,0 +1,51 @@ +# Automates online benchmark testing with different clock speeds +# Input: set of benchmark names to test +# Set of benchmarks format: (full_bin_name, half_bin_name) +import os +import sys + +from collections import defaultdict +from subprocess import Popen, PIPE + +def run_benchmark(bin_name, should_print_bin_output): + print("RUNNING %s" % bin_name) + proc = Popen("./%s" % bin_name, stdout = PIPE, universal_newlines = True) + proc_output = proc.communicate()[0] + assert proc.returncode == 0 + + if should_print_bin_output: + print(proc_output) + print("FINISHED RUNNING %s" % bin_name) + return proc_output + + +def parse_binary_output(proc_output): + avg_time_key_ind = proc_output.find("Average time:") + assert avg_time_key_ind >= 0 + avg_time = proc_output[avg_time_key_ind : proc_output.find("\n", avg_time_key_ind)] + print(avg_time) + return avg_time + + +# Input: a list of tuples of benchmark names +# Can change to input a file containing benchmarks to run +def run_benchmarks(builds_dir, output_filename, should_print_bin_output = True): + output_file = open(output_filename, "w") + for bin_name in os.listdir(builds_dir): + if bin_name.find("profiling") == -1: + continue + output_file.write("%s: %s\n" % (bin_name, \ + parse_binary_output(run_benchmark(os.path.join(builds_dir, bin_name), \ + should_print_bin_output)))) + print(bin_name) + output_file.close() + + +if __name__ == "__main__": + num_args = len(sys.argv) + + if num_args != 3: + print("Usage: python online_benchmark_testing_automator.py <builds dir> <outputs_file_name>") + exit(1) + print("Output file name: %s" % sys.argv[2]) + run_benchmarks(sys.argv[1], sys.argv[2]) diff --git a/llvm/projects/hpvm-tensor-rt/code_autogenerators/cmakelists_generator.py b/llvm/projects/hpvm-tensor-rt/code_autogenerators/cmakelists_generator.py new file mode 100644 index 0000000000000000000000000000000000000000..04f6c5eec378276cd0c89fcc7013cb6996a90f2f --- /dev/null +++ b/llvm/projects/hpvm-tensor-rt/code_autogenerators/cmakelists_generator.py @@ -0,0 +1,109 @@ +# Generates a CMakeLists.txt file for all generated files in a specific directory +# Input: Arbitrarily long list containing names of all generated files directories +# Ex: alexnet_cifar10_autogenerated_knobs mobilenet_cifar10_autogenerated_knobs +# If inputted 0 parameters: Generates CMakeLists.txt file for all generated files in CURRENT dir + +import sys +import os + +def get_all_generated_directory_names(): + ''' + Returns a list of all generated source code directories (<>_autogenerated_knobs) + in the current directory. Called when program is run with 0 args + ''' + generated_dir_names = [] + for dir_name in os.listdir("."): + print(dir_name) + if dir_name.endswith("autogenerated_knobs"): + generated_dir_names.append(dir_name) + return generated_dir_names + + +def generate_cmakelists_setup(cmakelists_file): + ''' + Copies over all the setup instructions (ex: finding libraries) from a "base" CMakeLists.txt + file. Ends copyng when we find the first instance of add_executable + + Args: + cmakelists_file: File object to write cmake instructions to + + Assumption: All setup instructions are being any add_executable instructions + ''' + BASE_CMAKELISTS_PATH = "/home/nvidia/Gitlab/hpvm/llvm/projects/hpvm-tensor-rt" + base_cmakelists_file = open(os.path.join(BASE_CMAKELISTS_PATH, "CMakeLists.txt"), "r") + + find_lib_line = "" + + for line in base_cmakelists_file: + if line.find("add_executable") != -1: + break + + elif line.startswith("#"): + continue + + # Special case: ignore / if -I flag exists + elif line.find("/") != -1 and line.find("-I") == -1: + dot_dot_slash_ind = line.find("../") + dot_slash_ind = line.find("./") + if dot_dot_slash_ind != -1: + start_ind = dot_dot_slash_ind + elif dot_slash_ind != -1: + start_ind = dot_slash_ind + else: + slash_ind = line.find("/") + prev_space_ind = line[:slash_ind].rfind(" ") + start_ind = prev_space_ind + 1 + + old_rel_path = [] + while start_ind < len(line): + if line[start_ind] == ")" or line[start_ind].isspace(): + break + old_rel_path.append(line[start_ind]) + start_ind += 1 + old_rel_path = ''.join(old_rel_path) + if os.path.isabs(old_rel_path): + cmakelists_file.write(line) + else: + new_path = os.path.join(BASE_CMAKELISTS_PATH, old_rel_path) + cmakelists_file.write(line.replace(old_rel_path, new_path)) + continue + cmakelists_file.write(line) + base_cmakelists_file.close() + + +def generate_cmakelists_file(cmakelists_file, source_file_dirs): + generate_cmakelists_setup(cmakelists_file) + LIBRARIES = "tensor_runtime ${GPU_PROFILER_LIB} ${SOC_SIMULATOR_LIB}" + cmake_instrs = [] + + for source_file_dir in source_file_dirs: + cmake_instrs.append("# %s" % source_file_dir) + for source_file in os.listdir(source_file_dir): + # Executable name = name of source code file without file extension + file_ext_ind = source_file.find(".cc") + if file_ext_ind == -1: + print("WARNING: Found file with wrong extension. Skipping. %s" % source_file) + continue + exec_name = source_file[ : file_ext_ind] + + source_file_path = os.path.join(source_file_dir, source_file) + cmake_instrs.append("add_executable(%s %s)" % (exec_name, source_file_path)) + cmake_instrs.append("target_link_libraries(%s %s)\n" % (exec_name, LIBRARIES)) + cmake_instrs.append("\n") + cmakelists_file.write('\n'.join(cmake_instrs)) + + +if __name__ == "__main__": + num_args = len(sys.argv) + + if num_args >= 2 and sys.argv[1] == "--usage": + print("python cmakelists_generator.py <names of all generated files directories>") + print("If given no parameters: Generates CMakeLists.txt file for all generated files in CURRENT directory") + exit(1) + + cmakelists_file = open("CMakeLists.txt", "w") + if num_args == 1: + generate_cmakelists_file(cmakelists_file, get_all_generated_directory_names()) + else: + generate_cmakelists_file(cmakelists_file, sys.argv[1:]) + cmakelists_file.close() diff --git a/llvm/projects/hpvm-tensor-rt/online_benchmark_testing_automator.py b/llvm/projects/hpvm-tensor-rt/code_autogenerators/online_benchmark_testing_automator.py similarity index 100% rename from llvm/projects/hpvm-tensor-rt/online_benchmark_testing_automator.py rename to llvm/projects/hpvm-tensor-rt/code_autogenerators/online_benchmark_testing_automator.py diff --git a/llvm/projects/hpvm-tensor-rt/code_autogenerators/source_code_autogenerator.py b/llvm/projects/hpvm-tensor-rt/code_autogenerators/source_code_autogenerator.py new file mode 100644 index 0000000000000000000000000000000000000000..0ee30bf36d170f403a645914486e500ff3bda546 --- /dev/null +++ b/llvm/projects/hpvm-tensor-rt/code_autogenerators/source_code_autogenerator.py @@ -0,0 +1,200 @@ +# Input: file of the following table format +# id knob configurations (arbitrary # of columns) orig_func_name new_func_name +# Input: file containing list of filenames to generate modified sources for +# Generates: +# a new directory called <original_source_nane>_different_knobs +# files named <original_source_name>_<id>.txt within their respective directories + +import glob +import sys +import os +import re + +class KnobConfiguration: + ''' + Stores the configurations as well as other useful information for each knob configuration + Stores: id (may factor out if ids are guaranteed to start at 0/1 and be consecutive) + original function name + modified function name + new function parameters (knobs) + new function call (modified function name(knobs)) + ''' + def __init__(self, raw_config): + ''' + Args: raw_config = line of configuration file to parse + ''' + line_as_lst = raw_config.strip().split() + + self.id = int(line_as_lst[0]) + self.orig_func_name = line_as_lst[-2] + self.modified_func_name = line_as_lst[-1] + self.params = line_as_lst[1:-2] + + +def get_new_path(old_path, orig_source_code_dir): + ''' + Returns a path that's compatible with the location of the generated source code + + Args: + old_path: Original path of file that's being included + orig_source_code_dir: Path to original source code dir wrt the current dir + ''' + if os.path.isabs(old_path): # Old path works + return old_path + # Adding an extra .. because the path should be wrt the generated directory + return os.path.join("..", orig_source_code_dir, old_path) + + +# "complete_line" = a valid line of code +def get_new_function_calls(complete_line, knob_config): + ''' + Returns a copy of an inputted line of code such that all instances of old + function calls are replaced with newFunctionCall(old params, knobs) + + Note: The old calls aren't completely overriden, as we still need the old parameters but + insert new parameters as well + + Args: + complete_line: A complete line of code to process + knob_config: KnobConfiguration object representing current configuration + ''' + orig_func_ind = complete_line.find(knob_config.orig_func_name) + new_line = [] + line_start_ind = 0 + last_ind = 0 + + while orig_func_ind != -1: + new_line.append(complete_line[line_start_ind : orig_func_ind]) + line_start_ind = complete_line.find(")", orig_func_ind) + 1 + + old_func_call = complete_line[complete_line.find("(", orig_func_ind): line_start_ind] + new_line.append("%s%s, %s)" % (knob_config.modified_func_name, old_func_call[:-1], ', '.join(knob_config.params))) + orig_func_ind = complete_line.find(knob_config.orig_func_name, line_start_ind) + new_line.append(complete_line[line_start_ind : ]) + #print(new_line) + return ''.join(new_line) + + +def generate_source_code(table, dir_name, filename, source_name): + ''' + Generates source code for all configurations in the table for one original source + Args + table: List of KnobConfigurations + dir_name: Directory new sources should be placed in + filename: Filename of original source + source_name: Filename without the file extension (ex: foo/blah.cc --> blah) + ''' + file_comment = "// AUTO-GENERATED SOURCE CODE. REPLACED ALL INSTANCES OF %s WITH %s\n" + + source_file = open(filename, "r") + filename_dir = os.path.dirname(filename) + + for knob_config in table: + source_file.seek(0, 0) + + new_file_contents = [file_comment % (knob_config.orig_func_name, knob_config.modified_func_name)] + + # Store complete line to handle cases where one line of code is split into two lines + complete_line = "" + for line in source_file: + # Replace the current path of the local include with a path that's compatible + # with the location of the generated source code + if line.startswith("#"): + include_file = line.split()[1] + if include_file.startswith("\""): + new_include_path = get_new_path(include_file.replace("\"", ""), filename_dir.replace("\"", "")) + new_file_contents.append("#include \"%s\"\n" % new_include_path) + else: + new_file_contents.append(line) + continue + # Handles case where 1 actual line of code is split into 2 lines + elif line.find("}") != -1 or line.find("{") != -1: + complete_line += line + new_file_contents.append(complete_line) + complete_line = "" + continue + elif line.find(";") == -1: # Last char is always \n + complete_line += line + continue + + complete_line += line + orig_func_ind = complete_line.find(knob_config.orig_func_name) + if orig_func_ind != -1: + new_file_contents.append(get_new_function_calls(complete_line, knob_config)) + else: + new_file_contents.append(complete_line) + complete_line = "" + + new_filename = os.path.join(dir_name, "%s_%s.cc" % (source_name, knob_config.id)) + new_file = open(new_filename, "w") + new_file.write(''.join(new_file_contents)) + new_file.close() + print("Generated source code as %s" % new_filename) + source_file.close() + + +def generate_all_sources(table, orig_files_filename): + ''' + Generates directories and source code for all original sources for all knob configurations + Args: + table: List of KnobConfiguration objects + orig_files_filename: Filename of file containing all original source names to generate new + sources for + ''' + orig_files = open(orig_files_filename, "r") + for orig_filename in orig_files: + orig_filename = orig_filename.strip() + + # Source name = original filename without the .cc + last_slash_ind = orig_filename.rfind("/") + file_ext_ind = orig_filename.find(".cc") + if last_slash_ind == -1: + source_name = orig_filename[ : file_ext_ind] + else: + source_name = orig_filename[last_slash_ind + 1 : file_ext_ind] + print("Source name: %s" % source_name) + + # Start with a clean directory + dir_name = "%s_autogenerated_knobs" % source_name + print("Setting up directory: %s" % dir_name) + if os.path.isdir(dir_name): + print("Directory exists: clearing everything") + for old_file in glob.glob(os.path.join(dir_name, "*")): + os.remove(old_file) + + else: + print("Generating directory: %s" % dir_name) + os.makedirs(dir_name) + + generate_source_code(table, dir_name, orig_filename, source_name) + print("\n") + orig_files.close() + + +def parse_table(table_filename): + ''' + Given the filename of a table, parses the table into a list of KnobConfigurations + ''' + # Can we assume that the ids always start at 1 --> if so, can index by knobs + # else: need to use a dict + table = [] + table_file = open(table_filename, "r") + for raw_config in table_file: + table.append(KnobConfiguration(raw_config)) + table_file.close() + return table + + +if __name__ == "__main__": + num_args = len(sys.argv) + if num_args != 3: + print("Usage: python source_code_autogenerator.py <table file> <original filenames file>") + if num_args >= 2 and sys.argv[1] == "--usage": + print("Table file format: <id> <knob configurations separated by spaces> <orig func name> <new func name>") + print("Original filenames file: <original_filename><newline> etc") + else: + print("Run with --usage flag for more detailed information") + exit(1) + + table = parse_table(sys.argv[1]) + generate_all_sources(table, sys.argv[2])