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
  # 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,
L
liuqi 已提交
159
              running_round, int(tuning),
L
Liangliang He 已提交
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
              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
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,
L
liuqi 已提交
191
             model_output_dir, running_round, 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
      model_name,
      target_runtime,
218
      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)
Y
yejianwu 已提交
254
  command = "bash tools/merge_libs.sh {} {} {}".format(target_soc, output_dir,
Y
yejianwu 已提交
255
                                                       model_output_dirs_str)
256 257
  run_command(command)

Y
yejianwu 已提交
258

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

L
liuqi 已提交
263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282
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()

283 284

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


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

L
liuqi 已提交
324 325 326 327 328 329 330
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"
331 332

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

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

L
liuqi 已提交
339
  set_environment(configs)
340

Y
yejianwu 已提交
341 342 343 344 345 346 347 348
  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 已提交
349 350 351
    generate_version_code()
    generate_opencl_source_code()

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

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

360 361
  if FLAGS.target_socs != "all":
    socs = set(FLAGS.target_socs.split(','))
L
Liangliang He 已提交
362 363 364 365 366 367
    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 已提交
368

L
Liangliang He 已提交
369
  for target_soc in target_socs:
370
    for target_abi in configs["target_abis"]:
Y
yejianwu 已提交
371
      global_runtime = get_global_runtime(configs)
372
      # Transfer params by environment
Y
yejianwu 已提交
373 374 375
      os.environ["TARGET_ABI"] = target_abi
      model_output_dirs = []
      for model_name in configs["models"]:
L
liuqi 已提交
376
        print '=======================', model_name, '======================='
Y
yejianwu 已提交
377 378 379
        # Transfer params by environment
        os.environ["MODEL_TAG"] = model_name
        model_config = configs["models"][model_name]
380
        input_file_list = model_config.get("validation_inputs_data", [])
Y
yejianwu 已提交
381
        for key in model_config:
Y
yejianwu 已提交
382 383 384 385 386 387
          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 已提交
388 389 390
          else:
            os.environ[key.upper()] = str(model_config[key])

L
liuqi 已提交
391 392
        # Create model build directory
        model_path_digest = md5sum(model_config["model_file_path"])
Y
yejianwu 已提交
393 394 395 396 397
        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 已提交
398 399 400 401 402 403 404 405
        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 已提交
406 407
        download_model_files(model_config["model_file_path"],
          model_output_dir, model_config.get("weight_file_path", ""))
Y
yejianwu 已提交
408 409 410

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

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

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

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

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

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

      if FLAGS.mode == "throughput_test":
436 437
        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 已提交
438
        generate_random_input(target_soc, FLAGS.output_dir, [], [])
Y
yejianwu 已提交
439 440 441
        for model_name in configs["models"]:
          runtime = configs["models"][model_name]["runtime"]
          os.environ["%s_MODEL_TAG" % runtime.upper()] = model_name
442 443
        build_run_throughput_test(target_soc, FLAGS.run_seconds,
                                  merged_lib_file, FLAGS.output_dir)
444

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

448

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