From 4f7bdeb03672b674eb4a5b4a5be2ad7674290f25 Mon Sep 17 00:00:00 2001 From: Yifan Zhao <yifanz16@illinois.edu> Date: Mon, 1 Feb 2021 17:22:50 -0600 Subject: [PATCH] Added approxhpvm.py for compiling hpvm-c to binary --- hpvm/tools/CMakeLists.txt | 1 + hpvm/tools/py-approxhpvm/CMakeLists.txt | 59 ++++++++ hpvm/tools/py-approxhpvm/main.py.in | 185 ++++++++++++++++++++++++ 3 files changed, 245 insertions(+) create mode 100644 hpvm/tools/py-approxhpvm/CMakeLists.txt create mode 100644 hpvm/tools/py-approxhpvm/main.py.in diff --git a/hpvm/tools/CMakeLists.txt b/hpvm/tools/CMakeLists.txt index c653aebd50..495348404f 100644 --- a/hpvm/tools/CMakeLists.txt +++ b/hpvm/tools/CMakeLists.txt @@ -1 +1,2 @@ add_llvm_tool_subdirectory(hpvm-config) +add_llvm_tool_subdirectory(py-approxhpvm) \ No newline at end of file diff --git a/hpvm/tools/py-approxhpvm/CMakeLists.txt b/hpvm/tools/py-approxhpvm/CMakeLists.txt new file mode 100644 index 0000000000..0d6a7fe71b --- /dev/null +++ b/hpvm/tools/py-approxhpvm/CMakeLists.txt @@ -0,0 +1,59 @@ +# This file is very tightly coupled with main.py.in. +# Watch out and keep them in sync. + +set(LLVM_PROJECT_DIR ${PROJECT_SOURCE_DIR}) +set(LLVM_BUILD_DIR ${PROJECT_BINARY_DIR}) +set(LIB_DIR ${PROJECT_BINARY_DIR}/lib) +# The hpvm-rt runtime +# This has to be explicitly set as hpvm-rt.bc is created in a custom_target +# and does not export its file location. +# Keep this in sync with hpvm/projects/hpvm-rt/CMakeLists.txt. +set(HPVM_RT_PATH ${LLVM_BUILD_DIR}/tools/hpvm/projects/hpvm-rt/hpvm-rt.bc) +set( + TENSOR_RUNTIME_LIBS + "$<TARGET_FILE:tensor_runtime>" + "$<TARGET_FILE:gpu_profiler>" + "$<TARGET_FILE:promise_profiler>" +) +set( + AVAILABLE_PASSES + LLVMBuildDFG + LLVMInPlaceDFGAnalysis + LLVMDFG2LLVM_CPU + LLVMDFG2LLVM_CUDNN + LLVMDFG2LLVM_WrapperAPI + LLVMFuseHPVMTensorNodes + LLVMClearDFG + LLVMGenHPVM +) +# CUDA_TOOLKIT_ROOT_DIR is already defined +# First resolve all `@symbol@` by configuring the file +configure_file(main.py.in ${CMAKE_CURRENT_BINARY_DIR}/main.py.conf) +# Then resolve all generator expressions we configured into the previous file +file(GENERATE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/main.py INPUT ${CMAKE_CURRENT_BINARY_DIR}/main.py.conf) +# Delibrately create an extra step of moving file +# which is carried out at build time (as a target) +# so we can set these dependencies on it +set( + DEPS + tensor_runtime + gpu_profiler + promise_profiler + LLVMBuildDFG + LLVMInPlaceDFGAnalysis + LLVMDFG2LLVM_CPU + LLVMDFG2LLVM_CUDNN + LLVMDFG2LLVM_WrapperAPI + LLVMFuseHPVMTensorNodes + LLVMClearDFG + LLVMGenHPVM + hpvm-rt.bc + clang opt llvm-link +) +add_custom_command( + OUTPUT ${PROJECT_BINARY_DIR}/bin/approxhpvm.py + COMMAND mv ${CMAKE_CURRENT_BINARY_DIR}/main.py ${PROJECT_BINARY_DIR}/bin/approxhpvm.py + COMMAND chmod +x ${PROJECT_BINARY_DIR}/bin/approxhpvm.py + DEPENDS ${DEPS} ${CMAKE_CURRENT_BINARY_DIR}/main.py +) +add_custom_target(approxhpvm.py DEPENDS ${PROJECT_BINARY_DIR}/bin/approxhpvm.py) diff --git a/hpvm/tools/py-approxhpvm/main.py.in b/hpvm/tools/py-approxhpvm/main.py.in new file mode 100644 index 0000000000..0bb08a9356 --- /dev/null +++ b/hpvm/tools/py-approxhpvm/main.py.in @@ -0,0 +1,185 @@ +#!/usr/bin/env python +import argparse +import os +from pathlib import Path +from typing import List, Union, Optional + +PathLike = Union[Path, str] + +HPVM_PROJECT_DIR = Path("@LLVM_PROJECT_DIR@") / "tools/hpvm" +LLVM_BUILD_DIR = Path("@LLVM_BUILD_DIR@") +CUDA_TOOLKIT_ROOT_DIR = Path("@CUDA_TOOLKIT_ROOT_DIR@") +TENSOR_RUNTIME_LIBS = "@TENSOR_RUNTIME_LIBS@".split(";") +AVAILABLE_PASSES = "@AVAILABLE_PASSES@".split(";") +HPVM_RT_PATH = "@HPVM_RT_PATH@" + +# Directories to include +INCLUDE_DIRS = [ + HPVM_PROJECT_DIR / "include", # HPVM include dir + # Tensor runtime include dir + HPVM_PROJECT_DIR / "projects/hpvm-tensor-rt/tensor_runtime/include", + HPVM_PROJECT_DIR / "test/dnn_benchmarks/hpvm-c/include", # hpvm-c intrinsics decl dir + CUDA_TOOLKIT_ROOT_DIR / "include", # CUDA include dir +] +LINK_DIRS = [CUDA_TOOLKIT_ROOT_DIR / "lib64"] +LINK_LIBS = [ + "pthread", "cudart", "curand", "cudnn", "cublas", "cufft", "OpenCL", "stdc++fs", "omp", "m" +] +COMPILE_FLAGS = ["fno-exceptions", "std=c++11", "O3"] + + +def compile_hpvm_c( + input_file: PathLike, + output_file: PathLike, + codegen_target: str = "tensor", + include: List[PathLike] = None, + working_dir: PathLike = None, + quant_file: PathLike = None, + conf_file: PathLike = None, +): + from subprocess import check_output + + codegen_functions = { + "tensor": lambda i, o: opt_codegen_tensor(i, o, quant_file, conf_file), + "cudnn": opt_codegen_cudnn + } + codegen_f = codegen_functions[codegen_target] + working_dir = Path(working_dir or ".") + if not working_dir.is_dir(): + os.makedirs(working_dir) + name_stem = Path(input_file).stem + + ll_file = working_dir / f"{name_stem}.ll" + hpvm_ll_file = working_dir / f"{name_stem}.hpvm.ll" + llvm_ll_file = working_dir / f"{name_stem}.llvm.ll" + hpvm_rt_linked_file = working_dir / f"{name_stem}.linked.bc" + commands = [ + hpvm_c_to_ll(input_file, ll_file, extra_includes=include), + opt_codegen_hpvm(ll_file, hpvm_ll_file), + codegen_f(hpvm_ll_file, llvm_ll_file), + link_hpvm_rt(llvm_ll_file, hpvm_rt_linked_file), + link_binary(hpvm_rt_linked_file, output_file), + ] + for command in commands: + print(" ".join(command)) + check_output(command) + + +def hpvm_c_to_ll( + src_file: PathLike, + target_file: PathLike, + extra_includes: Optional[List[PathLike]] = None, + flags: List[str] = None, +) -> List[str]: + extra_includes = extra_includes or [] + includes = [f"-I{path}" for path in INCLUDE_DIRS + extra_includes] + flags = [f"-{flg}" for flg in (flags or []) + COMPILE_FLAGS] + return [ + "clang++", *includes, *flags, "-emit-llvm", "-S", + str(src_file), "-o", str(target_file) + ] + + +def opt_codegen_hpvm(src_file: PathLike, target_file: PathLike) -> List[str]: + return _run_opt(src_file, target_file, ["LLVMGenHPVM"], ["genhpvm", "globaldce"]) + + +def opt_codegen_cudnn(src_file: PathLike, target_file: PathLike) -> List[str]: + passes = [ + "LLVMBuildDFG", "LLVMInPlaceDFGAnalysis", + "LLVMDFG2LLVM_CUDNN", "LLVMDFG2LLVM_CPU", + "LLVMFuseHPVMTensorNodes", "LLVMClearDFG", "LLVMGenHPVM" + ] + flags = [ + "buildDFG", "inplace", "hpvm-fuse", + "dfg2llvm-cudnn", "dfg2llvm-cpu", "clearDFG" + ] + return _run_opt(src_file, target_file, passes, flags) + + +def opt_codegen_tensor( + src_file: PathLike, target_file: PathLike, quant_file: PathLike, conf_file: PathLike +): + passes = [ + "LLVMBuildDFG", "LLVMInPlaceDFGAnalysis", + "LLVMDFG2LLVM_WrapperAPI", "LLVMDFG2LLVM_CPU", + "LLVMFuseHPVMTensorNodes", "LLVMClearDFG", "LLVMGenHPVM" + ] + flags = [ + "buildDFG", "inplace", "hpvm-fuse", + "dfg2llvm-wrapperapi", + f"quantization-levels-filename={quant_file}", + f"configuration-inputs-filename={conf_file}", + "dfg2llvm-cpu", "clearDFG", + ] + return _run_opt(src_file, target_file, passes, flags) + + +def link_hpvm_rt(src_file: PathLike, target_file: PathLike) -> List[str]: + return ["llvm-link", str(src_file), HPVM_RT_PATH, "-o", str(target_file)] + + +def link_binary(src_file: PathLike, target_file: PathLike) -> List[str]: + linker_dir_flags = [f"-L{path}" for path in LINK_DIRS] + linker_lib_flags = [f"-l{lib}" for lib in LINK_LIBS] + return [ + "clang++", str(src_file), *TENSOR_RUNTIME_LIBS, "-o", str(target_file), + *linker_dir_flags, *linker_lib_flags + ] + + +def _run_opt( + src_file: PathLike, + target_file: PathLike, + pass_names: List[str], + pass_flags: List[str], +) -> List[str]: + unavailable = set(pass_names) - set(AVAILABLE_PASSES) + if unavailable: + raise ValueError(f"Passes {unavailable} are unavailable from CMake") + load_passes_strs = [s for pass_ in pass_names for s in ["-load", f"{pass_}.so"]] + pass_flags_strs = [f"-{flag}" for flag in pass_flags] + return [ + "opt", *load_passes_strs, *pass_flags_strs, + "-S", str(src_file), "-o", str(target_file) + ] + + +def parse_args(): + parser = argparse.ArgumentParser("approxhpvm") + parser.add_argument("input_file", type=Path, help="HPVM-C code to compile") + parser.add_argument("output_file", type=Path, help="Path to generate binary to") + parser.add_argument( + "-t", + "--codegen-target", + type=str, + choices=["tensor", "cudnn"], + help="Backend to use", + ) + parser.add_argument( + "-d", "--working-dir", type=Path, help="Directory to generate temp files in" + ) + parser.add_argument( + "--quant-file", type=Path, + help="File to quantization levels of layers; required for 'tensor' target" + ) + parser.add_argument( + "--conf-file", type=Path, + help="File to approximation configurations; required for 'tensor' target" + ) + parser.add_argument( + "-I", "--include", type=Path, nargs="+", + help="Additional include directories to use" + ) + + args = parser.parse_args() + if args.codegen_target == "tensor": + if args.quant_file is None: + parser.error('Codegen target "tensor" requires -quant-file argument') + if args.conf_file is None: + parser.error('Codegen target "tensor" requires -conf-file argument') + return args + + +if __name__ == "__main__": + compile_hpvm_c(**vars(parse_args())) -- GitLab