mace_tools.py 15.8 KB
Newer Older
1 2
#!/usr/bin/env python

3
# Must run at root dir of libmace project.
4
# python tools/mace_tools.py \
Y
yejianwu 已提交
5
#     --config=tools/example.yaml \
6 7 8 9
#     --round=100 \
#     --mode=all

import argparse
10
import hashlib
11
import os
L
Liangliang He 已提交
12
import sh
13 14 15
import shutil
import subprocess
import sys
16
import urllib
Y
yejianwu 已提交
17
import yaml
L
liuqi 已提交
18
import re
19

20
import sh_commands
L
Liangliang He 已提交
21

22 23
from ConfigParser import ConfigParser

Y
yejianwu 已提交
24

25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
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))


Y
yejianwu 已提交
41
def get_global_runtime(configs):
42
  runtime_list = []
Y
yejianwu 已提交
43 44 45
  for model_name in configs["models"]:
    model_runtime = configs["models"][model_name]["runtime"]
    runtime_list.append(model_runtime.lower())
46

Y
yejianwu 已提交
47
  global_runtime = ""
48 49 50 51 52 53
  if "dsp" in runtime_list:
    global_runtime = "dsp"
  elif "gpu" in runtime_list:
    global_runtime = "gpu"
  elif "cpu" in runtime_list:
    global_runtime = "cpu"
李寅 已提交
54 55
  elif "neon" in runtime_list:
    global_runtime = "neon"
56 57 58
  else:
    raise Exception("Not found available RUNTIME in config files!")

Y
yejianwu 已提交
59
  return global_runtime
60 61


L
liuqi 已提交
62 63
def generate_version_code():
  command = "bash tools/generate_version_code.sh"
Y
yejianwu 已提交
64
  run_command(command)
65

L
liuqi 已提交
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
def generate_opencl_source_code():
  command = "bash tools/generate_opencl_code.sh source"
  run_command(command)

def generate_opencl_binay_code(target_soc, 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)
  if not cl_bin_dirs:
    command = "bash tools/generate_opencl_code.sh binary"
  else:
    command = "bash tools/generate_opencl_code.sh {} {} {} {}".format(
      'binary', target_soc, cl_bin_dirs_str, int(pull_or_not))
  run_command(command)

def generate_tuning_param_code(target_soc, 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)
  if not cl_bin_dirs:
    command = "bash tools/generate_tuning_param_code.sh"
  else:
    command = "bash tools/generate_tuning_param_code.sh {} {} {}".format(
      target_soc, cl_bin_dirs_str, int(pull_or_not))
  run_command(command)

def generate_code(target_soc, model_output_dirs, pull_or_not):
  generate_opencl_binay_code(target_soc, model_output_dirs, pull_or_not)
  generate_tuning_param_code(target_soc, model_output_dirs, pull_or_not)
97

Y
yejianwu 已提交
98 99
def clear_env(target_soc):
  command = "bash tools/clear_env.sh {}".format(target_soc)
100 101
  run_command(command)

L
liuqi 已提交
102 103 104
def input_file_name(input_name):
  return os.environ['INPUT_FILE_NAME'] + '_' + \
         re.sub('[^0-9a-zA-Z]+', '_', input_name)
105

L
liuqi 已提交
106 107
def generate_random_input(target_soc, model_output_dir,
                          input_names, input_files):
108
  generate_data_or_not = True
Y
yejianwu 已提交
109 110
  command = "bash tools/validate_tools.sh {} {} {}".format(
      target_soc, model_output_dir, int(generate_data_or_not))
111 112
  run_command(command)

L
liuqi 已提交
113 114 115 116 117
  input_file_list = []
  if isinstance(input_files, list):
    input_file_list.extend(input_files)
  else:
    input_file_list.append(input_files)
L
liuqi 已提交
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133
  if len(input_file_list) != 0:
    input_name_list = []
    if isinstance(input_names, list):
      input_name_list.extend(input_names)
    else:
      input_name_list.append(input_names)
    if len(input_file_list) != len(input_name_list):
      raise Exception('If input_files set, the input files should match the input names.')
    for i in range(len(input_file_list)):
      if input_file_list[i] is not None:
        dst_input_file = model_output_dir + '/' + input_file_name(input_name_list[i])
        if input_file_list[i].startswith("http://") or \
            input_file_list[i].startswith("https://"):
          urllib.urlretrieve(input_file_list[i], dst_input_file)
        else:
          shutil.copy(input_file_list[i], dst_input_file)
134 135 136

def generate_model_code():
  command = "bash tools/generate_model_code.sh"
Y
yejianwu 已提交
137
  run_command(command)
138 139


140 141 142
def build_mace_run(production_mode, model_output_dir, hexagon_mode):
  command = "bash tools/build_mace_run.sh {} {} {}".format(
      int(production_mode), model_output_dir, int(hexagon_mode))
143 144 145
  run_command(command)


L
Liangliang He 已提交
146 147 148 149
def tuning_run(model_name,
               target_runtime,
               target_abi,
               target_soc,
Y
yejianwu 已提交
150 151 152 153 154
               model_output_dir,
               running_round,
               tuning,
               restart_round,
               option_args=''):
L
Liangliang He 已提交
155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
  # TODO(yejianwu) refactoring the hackish code
  stdout_buff = []
  process_output = sh_commands.make_output_processor(stdout_buff)
  p = sh.bash("tools/tuning_run.sh", target_soc, model_output_dir,
              running_round, int(tuning), int(production_mode),
              restart_round, option_args, _out=process_output,
              _bg=True, _err_to_out=True)
  p.wait()
  metrics = {}
  for line in stdout_buff:
    line = line.strip()
    parts = line.split()
    if len(parts) == 6 and parts[0].startswith("time"):
      metrics["%s.create_net_ms" % model_name] = str(float(parts[1]))
      metrics["%s.mace_engine_ctor_ms" % model_name] = str(float(parts[2]))
      metrics["%s.init_ms" % model_name] = str(float(parts[3]))
      metrics["%s.warmup_ms" % model_name] = str(float(parts[4]))
      if float(parts[5]) > 0:
        metrics["%s.avg_latency_ms" % model_name] = str(float(parts[5]))
  tags = {"ro.board.platform": target_soc,
          "abi": target_abi,
          # "runtime": target_runtime, # TODO(yejianwu) Add the actual runtime
          "round": running_round, # TODO(yejianwu) change this to source/binary
          "tuning": tuning}
  sh_commands.falcon_push_metrics(metrics, endpoint="mace_model_benchmark",
                                  tags=tags)
Y
yejianwu 已提交
181

182 183 184
def benchmark_model(target_soc, model_output_dir, option_args=''):
  command = "bash tools/benchmark.sh {} {} \"{}\"".format(
      target_soc, model_output_dir, option_args)
185
  run_command(command)
186

Y
yejianwu 已提交
187

L
Liangliang He 已提交
188 189 190 191
def run_model(model_name, target_runtime, target_abi, target_soc,
              model_output_dir, running_round, restart_round, option_args):
  tuning_run(model_name, target_runtime, target_abi, target_soc,
             model_output_dir, running_round, False, False,
Y
yejianwu 已提交
192
             restart_round, option_args)
193 194


Y
yejianwu 已提交
195
def generate_production_code(target_soc, model_output_dirs, pull_or_not):
196 197 198 199
  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)
Y
yejianwu 已提交
200 201
  command = "bash tools/generate_production_code.sh {} {} {}".format(
      target_soc, cl_bin_dirs_str, int(pull_or_not))
202 203 204
  run_command(command)


L
Liangliang He 已提交
205 206 207
def build_mace_run_prod(model_name, target_runtime, target_abi, target_soc,
                        model_output_dir, tuning):
  if "dsp" == target_runtime:
208 209 210 211
    hexagon_mode = True
  else:
    hexagon_mode = False

L
liuqi 已提交
212
  generate_code(target_soc, [], False)
213
  production_or_not = False
214
  build_mace_run(production_or_not, model_output_dir, hexagon_mode)
215
  tuning_run(
L
Liangliang He 已提交
216 217 218
      model_name,
      target_runtime,
      target_abi, 
Y
yejianwu 已提交
219
      target_soc,
220 221 222
      model_output_dir,
      running_round=0,
      tuning=tuning,
李寅 已提交
223
      restart_round=1)
224

L
liuqi 已提交
225
  generate_code(target_soc, [model_output_dir], True)
226
  production_or_not = True
227
  build_mace_run(production_or_not, model_output_dir, hexagon_mode)
228 229


230 231 232 233
def build_run_throughput_test(target_soc, run_seconds, merged_lib_file,
                              model_input_dir):
  command = "bash tools/build_run_throughput_test.sh {} {} {} {}".format(
      target_soc, run_seconds, merged_lib_file, model_input_dir)
234 235 236
  run_command(command)


Y
fix run  
yejianwu 已提交
237
def validate_model(target_soc, model_output_dir):
238
  generate_data_or_not = False
Y
fix run  
yejianwu 已提交
239 240
  command = "bash tools/validate_tools.sh {} {} {}".format(
      target_soc, model_output_dir, int(generate_data_or_not))
241 242 243 244 245 246 247 248
  run_command(command)


def build_production_code():
  command = "bash tools/build_production_code.sh"
  run_command(command)


Y
yejianwu 已提交
249
def merge_libs_and_tuning_results(target_soc, output_dir, model_output_dirs):
L
liuqi 已提交
250
  generate_code(target_soc, model_output_dirs, False)
251 252 253
  build_production_code()

  model_output_dirs_str = ",".join(model_output_dirs)
L
liuqi 已提交
254
  print output_dir, model_output_dirs
Y
yejianwu 已提交
255
  command = "bash tools/merge_libs.sh {} {} {}".format(target_soc, output_dir,
Y
yejianwu 已提交
256
                                                       model_output_dirs_str)
257 258
  run_command(command)

Y
yejianwu 已提交
259

Y
yejianwu 已提交
260 261 262 263
def packaging_lib_file(output_dir):
  command = "bash tools/packaging_lib.sh {}".format(output_dir)
  run_command(command)

L
liuqi 已提交
264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283
def download_model_files(model_file_path,
                         model_output_dir,
                         weight_file_path=""):
  if model_file_path.startswith("http://") or \
      model_file_path.startswith("https://"):
    os.environ["MODEL_FILE_PATH"] = model_output_dir + "/model.pb"
    urllib.urlretrieve(model_file_path, os.environ["MODEL_FILE_PATH"])

  if weight_file_path.startswith("http://") or \
      weight_file_path.startswith("https://"):
    os.environ[
      "WEIGHT_FILE_PATH"] = model_output_dir + "/model.caffemodel"
    urllib.urlretrieve(weight_file_path,
      os.environ["WEIGHT_FILE_PATH"])

def md5sum(str):
  md5 = hashlib.md5()
  md5.update(str)
  return md5.hexdigest()

284 285

def parse_model_configs():
Y
yejianwu 已提交
286 287 288
  with open(FLAGS.config) as f:
    configs = yaml.load(f)
    return configs
289 290 291 292 293 294 295


def parse_args():
  """Parses command line arguments."""
  parser = argparse.ArgumentParser()
  parser.register("type", "bool", lambda v: v.lower() == "true")
  parser.add_argument(
Y
yejianwu 已提交
296
      "--config",
297 298 299 300
      type=str,
      default="./tool/config",
      help="The global config file of models.")
  parser.add_argument(
301
      "--output_dir", type=str, default="build", help="The output dir.")
302 303
  parser.add_argument(
      "--round", type=int, default=1, help="The model running round.")
李寅 已提交
304
  parser.add_argument(
Y
yejianwu 已提交
305 306 307 308 309 310
      "--run_seconds",
      type=int,
      default=10,
      help="The model throughput test running seconds.")
  parser.add_argument(
      "--restart_round", type=int, default=1, help="The model restart round.")
311 312
  parser.add_argument(
      "--tuning", type="bool", default="true", help="Tune opencl params.")
Y
yejianwu 已提交
313 314 315 316 317
  parser.add_argument(
      "--mode",
      type=str,
      default="all",
      help="[build|run|validate|merge|all|throughput_test].")
L
Liangliang He 已提交
318
  parser.add_argument(
319
      "--target_socs",
L
Liangliang He 已提交
320 321 322
      type=str,
      default="all",
      help="SoCs to build, comma seperated list (getprop ro.board.platform)")
323 324
  return parser.parse_known_args()

L
liuqi 已提交
325 326 327 328 329 330 331
def set_environment(configs):
  os.environ["EMBED_MODEL_DATA"] = str(configs["embed_model_data"])
  os.environ["VLOG_LEVEL"] = str(configs["vlog_level"])
  os.environ["PROJECT_NAME"] = os.path.splitext(os.path.basename(
    FLAGS.config))[0]
  os.environ['INPUT_FILE_NAME'] = "model_input"
  os.environ['OUTPUT_FILE_NAME'] = "model_out"
332 333

def main(unused_args):
Y
yejianwu 已提交
334
  configs = parse_model_configs()
335

336 337
  if FLAGS.mode == "validate":
    FLAGS.round = 1
李寅 已提交
338
    FLAGS.restart_round = 1
339

L
liuqi 已提交
340
  set_environment(configs)
341

Y
yejianwu 已提交
342 343 344 345 346 347 348 349
  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, os.environ["PROJECT_NAME"]))
      os.makedirs(os.path.join(FLAGS.output_dir, os.environ["PROJECT_NAME"]))

L
liuqi 已提交
350 351 352
    generate_version_code()
    generate_opencl_source_code()

W
wuchenghui 已提交
353
  option_args = ' '.join([arg for arg in unused_args if arg.startswith('--')])
Y
yejianwu 已提交
354

355
  available_socs = sh_commands.adb_get_all_socs()
L
Liangliang He 已提交
356 357 358 359 360
  target_socs = available_socs
  if hasattr(configs, "target_socs"):
    target_socs = set(configs["target_socs"])
    target_socs = target_socs & available_socs

361 362
  if FLAGS.target_socs != "all":
    socs = set(FLAGS.target_socs.split(','))
L
Liangliang He 已提交
363 364 365 366 367 368
    target_socs = target_socs & socs
    missing_socs = socs.difference(target_socs)
    if len(missing_socs) > 0:
      print("Error: devices with SoCs are not connected %s" % missing_socs)
      exit(1)

L
liuqi 已提交
369

L
Liangliang He 已提交
370
  for target_soc in target_socs:
371
    for target_abi in configs["target_abis"]:
Y
yejianwu 已提交
372
      global_runtime = get_global_runtime(configs)
373
      # Transfer params by environment
Y
yejianwu 已提交
374 375 376
      os.environ["TARGET_ABI"] = target_abi
      model_output_dirs = []
      for model_name in configs["models"]:
L
liuqi 已提交
377
        print '=======================', model_name, '======================='
Y
yejianwu 已提交
378 379 380
        # Transfer params by environment
        os.environ["MODEL_TAG"] = model_name
        model_config = configs["models"][model_name]
381
        input_file_list = model_config.get("validation_inputs_data", [])
Y
yejianwu 已提交
382
        for key in model_config:
Y
yejianwu 已提交
383 384 385 386 387 388
          if key in ['input_nodes', 'output_nodes'] and isinstance(
              model_config[key], list):
            os.environ[key.upper()] = ",".join(model_config[key])
          elif key in ['input_shapes', 'output_shapes'] and isinstance(
              model_config[key], list):
            os.environ[key.upper()] = ":".join(model_config[key])
Y
yejianwu 已提交
389 390 391
          else:
            os.environ[key.upper()] = str(model_config[key])

L
liuqi 已提交
392 393
        # Create model build directory
        model_path_digest = md5sum(model_config["model_file_path"])
Y
yejianwu 已提交
394 395 396 397 398
        model_output_dir = "%s/%s/%s/%s/%s/%s/%s" % (FLAGS.output_dir,
                                                     os.environ["PROJECT_NAME"],
                                                     "build", model_name,
                                                     model_path_digest,
                                                     target_soc, target_abi)
Y
yejianwu 已提交
399 400 401 402 403 404 405 406
        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(target_soc)

L
liuqi 已提交
407 408
        download_model_files(model_config["model_file_path"],
          model_output_dir, model_config.get("weight_file_path", ""))
Y
yejianwu 已提交
409 410 411

        if FLAGS.mode == "build" or FLAGS.mode == "run" or FLAGS.mode == "validate"\
            or FLAGS.mode == "benchmark" or FLAGS.mode == "all":
L
liuqi 已提交
412 413
          generate_random_input(target_soc, model_output_dir,
            model_config['input_nodes'], input_file_list)
Y
yejianwu 已提交
414 415 416

        if FLAGS.mode == "build" or FLAGS.mode == "all":
          generate_model_code()
L
Liangliang He 已提交
417 418
          build_mace_run_prod(model_name, global_runtime, target_abi,
                              target_soc, model_output_dir, FLAGS.tuning)
Y
yejianwu 已提交
419 420

        if FLAGS.mode == "run" or FLAGS.mode == "validate" or FLAGS.mode == "all":
L
Liangliang He 已提交
421 422 423
          run_model(model_name, global_runtime, target_abi, target_soc,
                    model_output_dir, FLAGS.round, FLAGS.restart_round,
                    option_args)
Y
yejianwu 已提交
424 425

        if FLAGS.mode == "benchmark":
426
          benchmark_model(target_soc, model_output_dir, option_args)
Y
yejianwu 已提交
427

428
        if FLAGS.mode == "validate" or FLAGS.mode == "all":
Y
fix run  
yejianwu 已提交
429
          validate_model(target_soc, model_output_dir)
Y
yejianwu 已提交
430 431

      if FLAGS.mode == "build" or FLAGS.mode == "merge" or FLAGS.mode == "all":
Y
yejianwu 已提交
432 433 434
        merge_libs_and_tuning_results(
            target_soc, FLAGS.output_dir + "/" + os.environ["PROJECT_NAME"],
            model_output_dirs)
Y
yejianwu 已提交
435 436

      if FLAGS.mode == "throughput_test":
437 438
        merged_lib_file = FLAGS.output_dir + "/%s/%s/libmace_%s.%s.a" % \
            (os.environ["PROJECT_NAME"], target_abi, os.environ["PROJECT_NAME"], target_soc)
L
liuqi 已提交
439
        generate_random_input(target_soc, FLAGS.output_dir, [], [])
Y
yejianwu 已提交
440 441 442
        for model_name in configs["models"]:
          runtime = configs["models"][model_name]["runtime"]
          os.environ["%s_MODEL_TAG" % runtime.upper()] = model_name
443 444
        build_run_throughput_test(target_soc, FLAGS.run_seconds,
                                  merged_lib_file, FLAGS.output_dir)
445

446 447
  if FLAGS.mode == "build" or FLAGS.mode == "all":
    packaging_lib_file(FLAGS.output_dir)
Y
yejianwu 已提交
448

449

Y
yejianwu 已提交
450
if __name__ == "__main__":
451 452
  FLAGS, unparsed = parse_args()
  main(unused_args=[sys.argv[0]] + unparsed)
李寅 已提交
453