diff --git a/build_mace_run.sh b/build_mace_run.sh new file mode 100644 index 0000000000000000000000000000000000000000..a24057d27cb6fdf972de888834aa2bcefd1535c1 --- /dev/null +++ b/build_mace_run.sh @@ -0,0 +1,57 @@ +#!/bin/bash + +Usage() { + echo "Usage: bash tools/build_mace_run.sh production_mode model_output_dir" +} + +if [ $# -lt 2 ]; then + Usage + exit 1 +fi + +CURRENT_DIR=`dirname $0` +source ${CURRENT_DIR}/env.sh + +PRODUCTION_MODE=$1 +MODEL_OUTPUT_DIR=$2 + +if [ "$PRODUCTION_MODE" = 1 ]; then + PRODUCTION_MODE_BUILD_FLAGS="--define production=true" +fi + +if [ x"$RUNTIME" = x"local" ]; then + bazel build --verbose_failures -c opt --strip always codegen:generated_models \ + --copt="-std=c++11" \ + --copt="-D_GLIBCXX_USE_C99_MATH_TR1" \ + --copt="-Werror=return-type" \ + --copt="-DMACE_MODEL_TAG=${MODEL_TAG}" \ + --define openmp=true \ + $PRODUCTION_MODE_BUILD_FLAGS || exit 1 + + bazel build --verbose_failures -c opt --strip always examples:mace_run \ + --copt="-std=c++11" \ + --copt="-D_GLIBCXX_USE_C99_MATH_TR1" \ + --copt="-Werror=return-type" \ + --copt="-DMACE_MODEL_TAG=${MODEL_TAG}" \ + --define openmp=true \ + $PRODUCTION_MODE_BUILD_FLAGS || exit 1 +else + if [ x"$RUNTIME" = x"dsp" ]; then + HEXAGON_MODE_BUILD_FLAG="--define hexagon=true" + fi + + bazel build --verbose_failures -c opt --strip always examples:mace_run \ + --crosstool_top=//external:android/crosstool \ + --host_crosstool_top=@bazel_tools//tools/cpp:toolchain \ + --cpu=${ANDROID_ABI} \ + --copt="-std=c++11" \ + --copt="-D_GLIBCXX_USE_C99_MATH_TR1" \ + --copt="-Werror=return-type" \ + --copt="-DMACE_MODEL_TAG=${MODEL_TAG}" \ + $PRODUCTION_MODE_BUILD_FLAGS \ + $HEXAGON_MODE_BUILD_FLAG || exit 1 +fi + +if [ "$PRODUCTION_MODE" = 1 ]; then + cp $GENERATED_MODEL_LIB_PATH $MODEL_OUTPUT_DIR/libmace_${MODEL_TAG}.a +fi diff --git a/build_production_code.sh b/build_production_code.sh new file mode 100644 index 0000000000000000000000000000000000000000..34676542e3908a3f6b9841973bb0b90986bb1773 --- /dev/null +++ b/build_production_code.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +Usage() { + echo "Usage: bash tools/build_production_code.sh" +} + +CURRENT_DIR=`dirname $0` +source ${CURRENT_DIR}/env.sh + + +build_target() +{ + BAZEL_TARGET=$1 + bazel build --verbose_failures -c opt --strip always $BAZEL_TARGET \ + --crosstool_top=//external:android/crosstool \ + --host_crosstool_top=@bazel_tools//tools/cpp:toolchain \ + --cpu=$ANDROID_ABI \ + --copt="-std=c++11" \ + --copt="-D_GLIBCXX_USE_C99_MATH_TR1" \ + --copt="-Werror=return-type" \ + --copt="-DMACE_OBFUSCATE_LITERALS" || exit 1 +} + +build_target //codegen:generated_opencl_prod +build_target //codegen:generated_tuning_params diff --git a/clear_env.sh b/clear_env.sh new file mode 100644 index 0000000000000000000000000000000000000000..3182ff608181f71be3937e1279256150a627f9e3 --- /dev/null +++ b/clear_env.sh @@ -0,0 +1,5 @@ +CURRENT_DIR=`dirname $0` +source ${CURRENT_DIR}/env.sh + +adb shell rm -rf $PHONE_DATA_DIR +rm -rf codegen/models codegen/opencl codegen/tuning diff --git a/download_and_link_lib.sh b/download_and_link_lib.sh new file mode 100644 index 0000000000000000000000000000000000000000..06140697bccce19ccb57821ec7bfdf77b95d4d20 --- /dev/null +++ b/download_and_link_lib.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +Usage() { + echo "Usage: bash tools/download_and_link_lib.sh libmace_v7_dsp" +} + +if [ $# -lt 1 ]; then + Usage + exit 1 +fi + +CURRENT_DIR=`dirname $0` +source ${CURRENT_DIR}/env.sh + +LIB_FOLDER_NAME=$1 + +if [ ! -d "${LIBMACE_SOURCE_DIR}/lib/${LIB_FOLDER_NAME}" ]; then + wget -P ${LIBMACE_SOURCE_DIR}/lib http://cnbj1-inner-fds.api.xiaomi.net/libmace/libs/${LIBMACE_TAG}/${LIB_FOLDER_NAME}.tar.gz && \ + tar xvzf ${LIBMACE_SOURCE_DIR}/lib/${LIB_FOLDER_NAME}.tar.gz -C ${LIBMACE_SOURCE_DIR}/lib/ || exit 1 + echo "${LIB_FOLDER_NAME} download successfully!" +else + echo "${LIB_FOLDER_NAME} already exists!" +fi + +echo "Create link 'mace' of downloaded or existed ${LIB_FOLDER_NAME}" +if [ -L ${LIBMACE_SOURCE_DIR}/lib/mace ]; then + unlink ${LIBMACE_SOURCE_DIR}/lib/mace +fi +ln -s ${LIBMACE_SOURCE_DIR}/lib/${LIB_FOLDER_NAME} ${LIBMACE_SOURCE_DIR}/lib/mace && \ + rm -rf ${LIBMACE_SOURCE_DIR}/lib/${LIB_FOLDER_NAME}.tar.gz || exit 1 diff --git a/env.sh b/env.sh new file mode 100644 index 0000000000000000000000000000000000000000..49b2f28a596f23161bfb4bb09836f1832c35ad68 --- /dev/null +++ b/env.sh @@ -0,0 +1,36 @@ +LIBMACE_TAG=`git describe --abbrev=0 --tags` + +VLOG_LEVEL=0 +LIBMACE_SOURCE_DIR=`/bin/pwd` +INPUT_FILE_NAME="model_input" +OUTPUT_FILE_NAME="model.out" +OUTPUT_LIST_FILE="model.list" +TF_MODEL_NAME="model.pb" +TF_MODEL_FILE_PATH=$TF_MODEL_FILE_DIR/$TF_MODEL_NAME +PHONE_DATA_DIR="/data/local/tmp/mace_run" +KERNEL_DIR="${PHONE_DATA_DIR}/cl/" +CODEGEN_DIR=${LIBMACE_SOURCE_DIR}/codegen +MODEL_CODEGEN_DIR=${CODEGEN_DIR}/models/${MODEL_TAG} +CL_CODEGEN_DIR=${CODEGEN_DIR}/opencl +TUNING_CODEGEN_DIR=${CODEGEN_DIR}/tuning +VERSION_SOURCE_PATH=${CODEGEN_DIR}/version +GENERATED_MODEL_LIB_NAME="libgenerated_models.a" +GENERATED_MODEL_LIB_PATH="bazel-bin/codegen/${GENERATED_MODEL_LIB_NAME}" + +MACE_RUNTIME=cpu +if [ x"$RUNTIME" = x"dsp" ]; then + DATA_TYPE="DT_UINT8" + DEVICE_TYPE="HEXAGON" + LIB_FOLDER_NAME="${LIB_FOLDER_NAME}_dsp" + MACE_RUNTIME=$RUNTIME +elif [ x"$RUNTIME" = x"gpu" ]; then + DATA_TYPE="DT_HALF" + DEVICE_TYPE="OPENCL" + MACE_RUNTIME=$RUNTIME +elif [ x"$RUNTIME" = x"cpu" ]; then + DATA_TYPE="DT_FLOAT" + DEVICE_TYPE="CPU" +elif [ x"$RUNTIME" = x"local" ];then + DATA_TYPE="DT_FLOAT" + DEVICE_TYPE="CPU" +fi diff --git a/generate_model_code.sh b/generate_model_code.sh new file mode 100644 index 0000000000000000000000000000000000000000..501deea7d871d1f1ed77b728a6faf0999fd9f217 --- /dev/null +++ b/generate_model_code.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +CURRENT_DIR=`dirname $0` +source ${CURRENT_DIR}/env.sh + +bazel build //lib/python/tools:tf_converter || exit 1 +rm -rf ${MODEL_CODEGEN_DIR} +mkdir -p ${MODEL_CODEGEN_DIR} +if [ ${DSP_MODE} ]; then + DSP_MODE_FLAG="--dsp_mode=${DSP_MODE}" +fi +bazel-bin/lib/python/tools/tf_converter --input=${TF_MODEL_FILE_PATH} \ + --output=${MODEL_CODEGEN_DIR}/model.cc \ + --input_node=${TF_INPUT_NODE} \ + --output_node=${TF_OUTPUT_NODE} \ + --data_type=${DATA_TYPE} \ + --runtime=${MACE_RUNTIME} \ + --output_type=source \ + --template=${LIBMACE_SOURCE_DIR}/lib/python/tools/model.template \ + --model_tag=${MODEL_TAG} \ + ${DSP_MODE_FLAG} \ + --obfuscate=True || exit 1 diff --git a/generate_production_code.sh b/generate_production_code.sh new file mode 100644 index 0000000000000000000000000000000000000000..2d9caceb0ebdf5f4cc0e3d142140cea9477f76c7 --- /dev/null +++ b/generate_production_code.sh @@ -0,0 +1,42 @@ +#!/bin/bash + +Usage() { + echo "Usage: bash tools/generate_production_code.sh cl_bin_dirs pull_or_not" +} + +if [ $# -lt 2 ]; then + Usage + exit 1 +fi + +CURRENT_DIR=`dirname $0` +source ${CURRENT_DIR}/env.sh + +CL_BIN_DIRS=$1 +PULL_OR_NOT=$2 + +if [ "$PULL_OR_NOT" = 1 ]; then + CL_BIN_DIR=${CL_BIN_DIRS} + rm -rf ${CL_BIN_DIR} + mkdir -p ${CL_BIN_DIR} + if [ x"$RUNTIME" != x"local" ]; then + adb pull ${KERNEL_DIR}/. ${CL_BIN_DIR} + adb pull ${PHONE_DATA_DIR}/mace_run.config ${CL_BIN_DIR} + fi +fi + +rm -rf ${CL_CODEGEN_DIR} +mkdir -p ${CL_CODEGEN_DIR} +rm -rf ${TUNING_CODEGEN_DIR} +mkdir -p ${TUNING_CODEGEN_DIR} + +set -x + +python lib/python/tools/opencl_codegen.py \ + --cl_binary_dirs=${CL_BIN_DIRS} \ + --output_path=${CL_CODEGEN_DIR}/opencl_compiled_program.cc + +python lib/python/tools/binary_codegen.py \ + --binary_dirs=${CL_BIN_DIRS} \ + --binary_file_name=mace_run.config \ + --output_path=${TUNING_CODEGEN_DIR}/tuning_params.cc diff --git a/mace_tools.py b/mace_tools.py new file mode 100644 index 0000000000000000000000000000000000000000..1c00c5cfb0623e58b9666d37a738f5ad761dbbf1 --- /dev/null +++ b/mace_tools.py @@ -0,0 +1,280 @@ +#!/usr/bin/env python + +# python tools/mace_tools.py \ +# --global_config=models/config \ +# --round=100 \ +# --mode=all + +import argparse +import os +import shutil +import subprocess +import sys + +from ConfigParser import ConfigParser + +tf_model_file_dir_key = "TF_MODEL_FILE_DIR" + + +def run_command(command): + print("Run command: {}".format(command)) + result = subprocess.Popen( + command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = result.communicate() + + if out: + print("Stdout msg:\n{}".format(out)) + if err: + print("Stderr msg:\n{}".format(err)) + + if result.returncode != 0: + raise Exception("Exit not 0 from bash with code: {}, command: {}".format( + result.returncode, command)) + + +def get_libs(configs): + libmace_name = "libmace" + for config in configs: + if config["ANDROID_ABI"] == "armeabi-v7a": + libmace_name += "_v7" + break + elif config["ANDROID_ABI"] == "arm64-v8a": + libmace_name += "_v8" + break + + for config in configs: + if config["RUNTIME"] == "dsp": + libmace_name += "_dsp" + break + if config["RUNTIME"] == "local": + libmace_name += "_local" + break + + command = "bash tools/download_and_link_lib.sh " + libmace_name + run_command(command) + + +def clear_env(): + command = "bash tools/clear_env.sh" + run_command(command) + + +def generate_random_input(model_output_dir): + generate_data_or_not = True + command = "bash tools/validate_tools.sh {} {}".format( + model_output_dir, int(generate_data_or_not)) + run_command(command) + + +def generate_model_code(): + command = "bash tools/generate_model_code.sh" + run_command(command) + + +def build_mace_run(production_mode, model_output_dir): + command = "bash tools/build_mace_run.sh {} {}".format( + int(production_mode), model_output_dir) + run_command(command) + + +def tuning_run(model_output_dir, running_round, tuning, production_mode): + command = "bash tools/tuning_run.sh {} {} {} {}".format( + model_output_dir, running_round, int(tuning), int(production_mode)) + run_command(command) + + +def run_model(model_output_dir, running_round): + tuning_run(model_output_dir, running_round, False, False) + + +def generate_production_code(model_output_dirs, pull_or_not): + cl_bin_dirs = [] + for d in model_output_dirs: + cl_bin_dirs.append(os.path.join(d, "opencl_bin")) + cl_bin_dirs_str = ",".join(cl_bin_dirs) + command = "bash tools/generate_production_code.sh {} {}".format( + cl_bin_dirs_str, int(pull_or_not)) + run_command(command) + + +def build_mace_run_prod(model_output_dir, tuning): + production_or_not = False + build_mace_run(production_or_not, model_output_dir) + tuning_run( + model_output_dir, + running_round=0, + tuning=tuning, + production_mode=production_or_not) + + production_or_not = True + pull_or_not = True + generate_production_code([model_output_dir], pull_or_not) + build_mace_run(production_or_not, model_output_dir) + + +def validate_model(model_output_dir): + generate_data_or_not = False + command = "bash tools/validate_tools.sh {} {}".format( + model_output_dir, int(generate_data_or_not)) + run_command(command) + + +def build_production_code(): + command = "bash tools/build_production_code.sh" + run_command(command) + + +def merge_libs_and_tuning_results(output_dir, model_output_dirs): + pull_or_not = False + generate_production_code(model_output_dirs, pull_or_not) + production_or_not = True + build_production_code() + + model_output_dirs_str = ",".join(model_output_dirs) + command = "bash tools/merge_libs.sh {} {}".format(output_dir, + model_output_dirs_str) + run_command(command) + + +def parse_sub_model_configs(model_dirs, global_configs): + model_configs = [] + for model_dir in model_dirs: + model_config = {} + + model_config_path = os.path.join(model_dir, "config") + if os.path.exists(model_config_path): + cf = ConfigParser() + # Preserve character case + cf.optionxform = str + cf.read(model_config_path) + if "configs" in cf.sections(): + config_list = cf.items("configs") + for config_map in config_list: + model_config[config_map[0]] = config_map[1] + else: + print("No config msg found in {}".format(model_config_path)) + + model_config[tf_model_file_dir_key] = model_dir + + for config_map in global_configs: + model_config[config_map[0]] = config_map[1] + + model_configs.append(model_config) + + return model_configs + + +def parse_model_configs(): + config_parser = ConfigParser() + # Preserve character case + config_parser.optionxform = str + + global_config_dir = os.path.dirname(FLAGS.global_config) + + try: + config_parser.read(FLAGS.global_config) + config_sections = config_parser.sections() + + model_dirs = [] + model_output_map = {} + if ("models" in config_sections) and (config_parser.items("models")): + model_dirs_relative_to_config_str = config_parser.get( + "models", "DIRECTORIES") + model_dirs_relative_to_config_str = model_dirs_relative_to_config_str.rstrip( + ",") + + # Remove repetition element + model_dirs_relative_to_config_str_list = list( + set(model_dirs_relative_to_config_str.split(","))) + + for d in model_dirs_relative_to_config_str_list: + model_dir = os.path.join(global_config_dir, d) + model_dirs.append(model_dir) + + # Create output dirs + model_output_dir = os.path.join(FLAGS.output_dir, d) + + model_output_map[model_dir] = model_output_dir + else: + model_dirs = [global_config_dir] + + # Create output dirs + model_output_dir = os.path.join(FLAGS.output_dir, global_config_dir) + model_output_map[global_config_dir] = model_output_dir + except Exception as e: + print("Error in read model path msg. Exception: {}".format(e)) + return + + global_configs = [] + if "configs" in config_sections: + global_configs = config_parser.items("configs") + + return parse_sub_model_configs(model_dirs, global_configs), model_output_map + + +def parse_args(): + """Parses command line arguments.""" + parser = argparse.ArgumentParser() + parser.register("type", "bool", lambda v: v.lower() == "true") + parser.add_argument( + "--global_config", + type=str, + default="./tool/config", + help="The global config file of models.") + parser.add_argument( + "--output_dir", type=str, default="./build/", help="The output dir.") + parser.add_argument( + "--round", type=int, default=1, help="The model running round.") + parser.add_argument( + "--tuning", type="bool", default="true", help="Tune opencl params.") + parser.add_argument( + "--mode", type=str, default="all", help="[build|run|merge|all].") + return parser.parse_known_args() + + +def main(unused_args): + configs, model_output_map = parse_model_configs() + + if FLAGS.mode == "build" or FLAGS.mode == "all": + # Remove previous output dirs + if not os.path.exists(FLAGS.output_dir): + os.makedirs(FLAGS.output_dir) + elif os.path.exists(os.path.join(FLAGS.output_dir, "libmace")): + shutil.rmtree(os.path.join(FLAGS.output_dir, "libmace")) + + get_libs(configs) + + model_output_dirs = [] + for config in configs: + # Transfer params by environment + for key in config: + os.environ[key] = config[key] + model_output_dir = model_output_map[config[tf_model_file_dir_key]] + model_output_dirs.append(model_output_dir) + + if FLAGS.mode == "build" or FLAGS.mode == "all": + if os.path.exists(model_output_dir): + shutil.rmtree(model_output_dir) + os.makedirs(model_output_dir) + clear_env() + + if FLAGS.mode == "build" or FLAGS.mode == "run" or FLAGS.mode == "all": + generate_random_input(model_output_dir) + + if FLAGS.mode == "build" or FLAGS.mode == "all": + generate_model_code() + build_mace_run_prod(model_output_dir, FLAGS.tuning) + + if FLAGS.mode == "run" or FLAGS.mode == "all": + run_model(model_output_dir, FLAGS.round) + + if FLAGS.mode == "all": + validate_model(model_output_dir) + + if FLAGS.mode == "build" or FLAGS.mode == "merge" or FLAGS.mode == "all": + merge_libs_and_tuning_results(FLAGS.output_dir, model_output_dirs) + + +if __name__ == '__main__': + FLAGS, unparsed = parse_args() + main(unused_args=[sys.argv[0]] + unparsed) diff --git a/model.config b/model.config index ccfc11591e53090c6e7abaa9a7f8ba4a4079cbd1..5e92c67a97265e2a01d127412bec7147300df6bc 100644 --- a/model.config +++ b/model.config @@ -1,3 +1,8 @@ +; [models] +; DIRECTORIES = mobile_net/64/ + +[configs] +; If exists 'models' section, this section will be global variable to sub models TF_INPUT_NODE=input TF_OUTPUT_NODE=output TF_MODEL_FILE_PATH=path/to/tf_model_opt.pb @@ -6,6 +11,6 @@ INPUT_SHAPE=1,64,64,3 OUTPUT_SHAPE=1,64,64,2 RUNTIME=gpu TUNING_OR_NOT=1 -ANDROID_ABI=armeabi-v7a # Or 'arm64-v8a' +ANDROID_ABI=armeabi-v7a ; Or 'arm64-v8a' LIMIT_OPENCL_KERNEL_TIME=0 -DSP_MODE=0 # used only for dsp +DSP_MODE=0 ; used only for dsp diff --git a/tuning_run.sh b/tuning_run.sh new file mode 100644 index 0000000000000000000000000000000000000000..ec92665e7553354d2c395c179e27e5062c92dc28 --- /dev/null +++ b/tuning_run.sh @@ -0,0 +1,58 @@ +#!/bin/bash + +Usage() { + echo "Usage: bash tools/run_and_tuning.sh model_output_dir round tuning production_mode" +} + +if [ $# -lt 4 ]; then + Usage + exit 1 +fi + +CURRENT_DIR=`dirname $0` +source ${CURRENT_DIR}/env.sh + +MODEL_OUTPUT_DIR=$1 +ROUND=$2 +TUNING_OR_NOT=$3 +PRODUCTION_MODE=$4 + +if [ x"$RUNTIME" = x"local" ]; then + MACE_CPP_MIN_VLOG_LEVEL=$VLOG_LEVEL \ + bazel-bin/examples/mace_run \ + --input_shape="${INPUT_SHAPE}"\ + --output_shape="${OUTPUT_SHAPE}"\ + --input_file=${MODEL_OUTPUT_DIR}/${INPUT_FILE_NAME} \ + --output_file=${MODEL_OUTPUT_DIR}/${OUTPUT_FILE_NAME} \ + --device=${DEVICE_TYPE} \ + --round=1 || exit 1 +else + if [[ "${TUNING_OR_NOT}" != "0" && "$PRODUCTION_MODE" != 1 ]];then + tuning_flag=1 + else + tuning_flag=0 + fi + + adb shell "mkdir -p ${PHONE_DATA_DIR}" || exit 1 + if [ "$PRODUCTION_MODE" = 0 ]; then + adb shell "mkdir -p ${KERNEL_DIR}" || exit 1 + fi + adb push ${MODEL_OUTPUT_DIR}/${INPUT_FILE_NAME} ${PHONE_DATA_DIR} || exit 1 + adb push bazel-bin/examples/mace_run ${PHONE_DATA_DIR} || exit 1 + adb push lib/hexagon/libhexagon_controller.so ${PHONE_DATA_DIR} || exit 1 + + adb