|
@@ -21,190 +21,41 @@ import numpy as np
|
|
|
from prettytable import PrettyTable
|
|
from prettytable import PrettyTable
|
|
|
|
|
|
|
|
from ...utils.flags import INFER_BENCHMARK, INFER_BENCHMARK_OUTPUT
|
|
from ...utils.flags import INFER_BENCHMARK, INFER_BENCHMARK_OUTPUT
|
|
|
-from ...utils.misc import Singleton
|
|
|
|
|
from ...utils import logging
|
|
from ...utils import logging
|
|
|
|
|
|
|
|
|
|
|
|
|
-class Benchmark(metaclass=Singleton):
|
|
|
|
|
- def __init__(self):
|
|
|
|
|
- self._components = {}
|
|
|
|
|
- self._warmup_start = None
|
|
|
|
|
- self._warmup_elapse = None
|
|
|
|
|
- self._warmup_num = None
|
|
|
|
|
- self._e2e_tic = None
|
|
|
|
|
- self._e2e_elapse = None
|
|
|
|
|
-
|
|
|
|
|
- def attach(self, component):
|
|
|
|
|
- self._components[component.name] = component
|
|
|
|
|
-
|
|
|
|
|
- def start(self):
|
|
|
|
|
- self._warmup_start = time.time()
|
|
|
|
|
- self._reset()
|
|
|
|
|
-
|
|
|
|
|
- def warmup_stop(self, warmup_num):
|
|
|
|
|
- self._warmup_elapse = (time.time() - self._warmup_start) * 1000
|
|
|
|
|
- self._warmup_num = warmup_num
|
|
|
|
|
- self._reset()
|
|
|
|
|
-
|
|
|
|
|
- def _reset(self):
|
|
|
|
|
- for name, cmp in self.iterate_cmp(self._components):
|
|
|
|
|
- cmp.timer.reset()
|
|
|
|
|
- self._e2e_tic = time.time()
|
|
|
|
|
-
|
|
|
|
|
- def iterate_cmp(self, cmps):
|
|
|
|
|
- if cmps is None:
|
|
|
|
|
- return
|
|
|
|
|
- for name, cmp in cmps.items():
|
|
|
|
|
- if hasattr(cmp, "benchmark"):
|
|
|
|
|
- yield from self.iterate_cmp(cmp.benchmark)
|
|
|
|
|
- yield name, cmp
|
|
|
|
|
-
|
|
|
|
|
- def gather(self, e2e_num):
|
|
|
|
|
- # lazy import for avoiding circular import
|
|
|
|
|
- from ..new_models.base import BasePaddlePredictor
|
|
|
|
|
-
|
|
|
|
|
- detail = []
|
|
|
|
|
- summary = {"preprocess": 0, "inference": 0, "postprocess": 0}
|
|
|
|
|
- op_tag = "preprocess"
|
|
|
|
|
- for name, cmp in self._components.items():
|
|
|
|
|
- if isinstance(cmp, BasePaddlePredictor):
|
|
|
|
|
- # TODO(gaotingquan): show by hierarchy. Now dont show xxxPredictor benchmark info to ensure mutual exclusivity between components.
|
|
|
|
|
- for name, sub_cmp in cmp.benchmark.items():
|
|
|
|
|
- times = sub_cmp.timer.logs
|
|
|
|
|
- counts = len(times)
|
|
|
|
|
- avg = np.mean(times) * 1000
|
|
|
|
|
- total = np.sum(times) * 1000
|
|
|
|
|
- detail.append((name, total, counts, avg))
|
|
|
|
|
- summary["inference"] += total
|
|
|
|
|
- op_tag = "postprocess"
|
|
|
|
|
- else:
|
|
|
|
|
- # TODO(gaotingquan): support sub_cmps for others
|
|
|
|
|
- # if hasattr(cmp, "benchmark"):
|
|
|
|
|
- times = cmp.timer.logs
|
|
|
|
|
- counts = len(times)
|
|
|
|
|
- avg = np.mean(times) * 1000
|
|
|
|
|
- total = np.sum(times) * 1000
|
|
|
|
|
- detail.append((name, total, counts, avg))
|
|
|
|
|
- summary[op_tag] += total
|
|
|
|
|
-
|
|
|
|
|
- summary = [
|
|
|
|
|
- (
|
|
|
|
|
- "PreProcess",
|
|
|
|
|
- summary["preprocess"],
|
|
|
|
|
- e2e_num,
|
|
|
|
|
- summary["preprocess"] / e2e_num,
|
|
|
|
|
- ),
|
|
|
|
|
- (
|
|
|
|
|
- "Inference",
|
|
|
|
|
- summary["inference"],
|
|
|
|
|
- e2e_num,
|
|
|
|
|
- summary["inference"] / e2e_num,
|
|
|
|
|
- ),
|
|
|
|
|
- (
|
|
|
|
|
- "PostProcess",
|
|
|
|
|
- summary["postprocess"],
|
|
|
|
|
- e2e_num,
|
|
|
|
|
- summary["postprocess"] / e2e_num,
|
|
|
|
|
- ),
|
|
|
|
|
- ("End2End", self._e2e_elapse, e2e_num, self._e2e_elapse / e2e_num),
|
|
|
|
|
- ]
|
|
|
|
|
- if self._warmup_elapse:
|
|
|
|
|
- warmup_elapse, warmup_num, warmup_avg = (
|
|
|
|
|
- self._warmup_elapse,
|
|
|
|
|
- self._warmup_num,
|
|
|
|
|
- self._warmup_elapse / self._warmup_num,
|
|
|
|
|
- )
|
|
|
|
|
- else:
|
|
|
|
|
- warmup_elapse, warmup_num, warmup_avg = 0, 0, 0
|
|
|
|
|
- summary.append(
|
|
|
|
|
- (
|
|
|
|
|
- "WarmUp",
|
|
|
|
|
- warmup_elapse,
|
|
|
|
|
- warmup_num,
|
|
|
|
|
- warmup_avg,
|
|
|
|
|
- )
|
|
|
|
|
- )
|
|
|
|
|
- return detail, summary
|
|
|
|
|
-
|
|
|
|
|
- def collect(self, e2e_num):
|
|
|
|
|
- self._e2e_elapse = (time.time() - self._e2e_tic) * 1000
|
|
|
|
|
- detail, summary = self.gather(e2e_num)
|
|
|
|
|
-
|
|
|
|
|
- detail_head = [
|
|
|
|
|
- "Component",
|
|
|
|
|
- "Total Time (ms)",
|
|
|
|
|
- "Number of Calls",
|
|
|
|
|
- "Avg Time Per Call (ms)",
|
|
|
|
|
- ]
|
|
|
|
|
- table = PrettyTable(detail_head)
|
|
|
|
|
- table.add_rows(
|
|
|
|
|
- [
|
|
|
|
|
- (name, f"{total:.8f}", cnts, f"{avg:.8f}")
|
|
|
|
|
- for name, total, cnts, avg in detail
|
|
|
|
|
- ]
|
|
|
|
|
- )
|
|
|
|
|
- logging.info(table)
|
|
|
|
|
|
|
+class Benchmark:
|
|
|
|
|
+ def __init__(self, enabled):
|
|
|
|
|
+ self._enabled = enabled
|
|
|
|
|
+ self._elapses = {}
|
|
|
|
|
+ self._warmup = False
|
|
|
|
|
|
|
|
- summary_head = [
|
|
|
|
|
- "Stage",
|
|
|
|
|
- "Total Time (ms)",
|
|
|
|
|
- "Number of Instances",
|
|
|
|
|
- "Avg Time Per Instance (ms)",
|
|
|
|
|
- ]
|
|
|
|
|
- table = PrettyTable(summary_head)
|
|
|
|
|
- table.add_rows(
|
|
|
|
|
- [
|
|
|
|
|
- (name, f"{total:.8f}", cnts, f"{avg:.8f}")
|
|
|
|
|
- for name, total, cnts, avg in summary
|
|
|
|
|
- ]
|
|
|
|
|
- )
|
|
|
|
|
- logging.info(table)
|
|
|
|
|
-
|
|
|
|
|
- if INFER_BENCHMARK_OUTPUT:
|
|
|
|
|
- save_dir = Path(INFER_BENCHMARK_OUTPUT)
|
|
|
|
|
- save_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
- csv_data = [detail_head, *detail]
|
|
|
|
|
- with open(Path(save_dir) / "detail.csv", "w", newline="") as file:
|
|
|
|
|
- writer = csv.writer(file)
|
|
|
|
|
- writer.writerows(csv_data)
|
|
|
|
|
-
|
|
|
|
|
- csv_data = [summary_head, *summary]
|
|
|
|
|
- with open(Path(save_dir) / "summary.csv", "w", newline="") as file:
|
|
|
|
|
- writer = csv.writer(file)
|
|
|
|
|
- writer.writerows(csv_data)
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
-class Timer:
|
|
|
|
|
- def __init__(self, component):
|
|
|
|
|
- from ..new_models.base import BaseComponent
|
|
|
|
|
-
|
|
|
|
|
- assert isinstance(component, BaseComponent)
|
|
|
|
|
- benchmark.attach(component)
|
|
|
|
|
- component.apply = self.watch_func(component.apply)
|
|
|
|
|
- self._tic = None
|
|
|
|
|
- self._elapses = []
|
|
|
|
|
-
|
|
|
|
|
- def watch_func(self, func):
|
|
|
|
|
|
|
+ def timeit(self, func):
|
|
|
@functools.wraps(func)
|
|
@functools.wraps(func)
|
|
|
def wrapper(*args, **kwargs):
|
|
def wrapper(*args, **kwargs):
|
|
|
|
|
+ if not self._enabled:
|
|
|
|
|
+ return func(*args, **kwargs)
|
|
|
|
|
+
|
|
|
|
|
+ name = func.__qualname__
|
|
|
|
|
+
|
|
|
tic = time.time()
|
|
tic = time.time()
|
|
|
output = func(*args, **kwargs)
|
|
output = func(*args, **kwargs)
|
|
|
if isinstance(output, GeneratorType):
|
|
if isinstance(output, GeneratorType):
|
|
|
- return self.watch_generator(output)
|
|
|
|
|
|
|
+ return self.watch_generator(output, name)
|
|
|
else:
|
|
else:
|
|
|
- self._update(time.time() - tic)
|
|
|
|
|
|
|
+ self._update(time.time() - tic, name)
|
|
|
return output
|
|
return output
|
|
|
|
|
|
|
|
return wrapper
|
|
return wrapper
|
|
|
|
|
|
|
|
- def watch_generator(self, generator):
|
|
|
|
|
|
|
+ def watch_generator(self, generator, name):
|
|
|
@functools.wraps(generator)
|
|
@functools.wraps(generator)
|
|
|
def wrapper():
|
|
def wrapper():
|
|
|
- while 1:
|
|
|
|
|
|
|
+ while True:
|
|
|
try:
|
|
try:
|
|
|
tic = time.time()
|
|
tic = time.time()
|
|
|
item = next(generator)
|
|
item = next(generator)
|
|
|
- self._update(time.time() - tic)
|
|
|
|
|
|
|
+ self._update(time.time() - tic, name)
|
|
|
yield item
|
|
yield item
|
|
|
except StopIteration:
|
|
except StopIteration:
|
|
|
break
|
|
break
|
|
@@ -212,15 +63,162 @@ class Timer:
|
|
|
return wrapper()
|
|
return wrapper()
|
|
|
|
|
|
|
|
def reset(self):
|
|
def reset(self):
|
|
|
- self._tic = None
|
|
|
|
|
- self._elapses = []
|
|
|
|
|
|
|
+ self._elapses = {}
|
|
|
|
|
|
|
|
- def _update(self, elapse):
|
|
|
|
|
- self._elapses.append(elapse)
|
|
|
|
|
|
|
+ def _update(self, elapse, name):
|
|
|
|
|
+ elapse = elapse * 1000
|
|
|
|
|
+ if name in self._elapses:
|
|
|
|
|
+ self._elapses[name].append(elapse)
|
|
|
|
|
+ else:
|
|
|
|
|
+ self._elapses[name] = [elapse]
|
|
|
|
|
|
|
|
@property
|
|
@property
|
|
|
def logs(self):
|
|
def logs(self):
|
|
|
return self._elapses
|
|
return self._elapses
|
|
|
|
|
|
|
|
|
|
+ def start_timing(self):
|
|
|
|
|
+ self._enabled = True
|
|
|
|
|
|
|
|
-benchmark = Benchmark() if INFER_BENCHMARK else None
|
|
|
|
|
|
|
+ def stop_timing(self):
|
|
|
|
|
+ self._enabled = False
|
|
|
|
|
+
|
|
|
|
|
+ def start_warmup(self):
|
|
|
|
|
+ self._warmup = True
|
|
|
|
|
+
|
|
|
|
|
+ def stop_warmup(self):
|
|
|
|
|
+ self._warmup = False
|
|
|
|
|
+ self.reset()
|
|
|
|
|
+
|
|
|
|
|
+ def gather(self, batch_size):
|
|
|
|
|
+ logs = {k.split(".")[0]: v for k, v in self.logs.items()}
|
|
|
|
|
+
|
|
|
|
|
+ iters = len(logs["Infer"])
|
|
|
|
|
+ instances = iters * batch_size
|
|
|
|
|
+ detail_list = []
|
|
|
|
|
+ summary = {"preprocess": 0, "inference": 0, "postprocess": 0}
|
|
|
|
|
+ op_tag = "preprocess"
|
|
|
|
|
+
|
|
|
|
|
+ for name, time_list in logs.items():
|
|
|
|
|
+ avg = np.mean(time_list)
|
|
|
|
|
+ detail_list.append(
|
|
|
|
|
+ (iters, batch_size, instances, name, avg, avg / batch_size)
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ if name in ["Copy2GPU", "Infer", "Copy2CPU"]:
|
|
|
|
|
+ summary["inference"] += avg
|
|
|
|
|
+ op_tag = "postprocess"
|
|
|
|
|
+ else:
|
|
|
|
|
+ summary[op_tag] += avg
|
|
|
|
|
+
|
|
|
|
|
+ summary["end2end"] = (
|
|
|
|
|
+ summary["preprocess"] + summary["inference"] + summary["postprocess"]
|
|
|
|
|
+ )
|
|
|
|
|
+ summary_list = [
|
|
|
|
|
+ (
|
|
|
|
|
+ iters,
|
|
|
|
|
+ batch_size,
|
|
|
|
|
+ instances,
|
|
|
|
|
+ "PreProcess",
|
|
|
|
|
+ summary["preprocess"],
|
|
|
|
|
+ summary["preprocess"] / batch_size,
|
|
|
|
|
+ ),
|
|
|
|
|
+ (
|
|
|
|
|
+ iters,
|
|
|
|
|
+ batch_size,
|
|
|
|
|
+ instances,
|
|
|
|
|
+ "Inference",
|
|
|
|
|
+ summary["inference"],
|
|
|
|
|
+ summary["inference"] / batch_size,
|
|
|
|
|
+ ),
|
|
|
|
|
+ (
|
|
|
|
|
+ iters,
|
|
|
|
|
+ batch_size,
|
|
|
|
|
+ instances,
|
|
|
|
|
+ "PostProcess",
|
|
|
|
|
+ summary["postprocess"],
|
|
|
|
|
+ summary["postprocess"] / batch_size,
|
|
|
|
|
+ ),
|
|
|
|
|
+ (
|
|
|
|
|
+ iters,
|
|
|
|
|
+ batch_size,
|
|
|
|
|
+ instances,
|
|
|
|
|
+ "End2End",
|
|
|
|
|
+ summary["end2end"],
|
|
|
|
|
+ summary["end2end"] / batch_size,
|
|
|
|
|
+ ),
|
|
|
|
|
+ ]
|
|
|
|
|
+
|
|
|
|
|
+ return detail_list, summary_list
|
|
|
|
|
+
|
|
|
|
|
+ def collect(self, batch_size):
|
|
|
|
|
+ detail_list, summary_list = self.gather(batch_size)
|
|
|
|
|
+
|
|
|
|
|
+ if self._warmup:
|
|
|
|
|
+ summary_head = [
|
|
|
|
|
+ "Iters",
|
|
|
|
|
+ "Batch Size",
|
|
|
|
|
+ "Instances",
|
|
|
|
|
+ "Stage",
|
|
|
|
|
+ "Avg Time Per Iter (ms)",
|
|
|
|
|
+ "Avg Time Per Instance (ms)",
|
|
|
|
|
+ ]
|
|
|
|
|
+ table = PrettyTable(summary_head)
|
|
|
|
|
+ summary_list = [
|
|
|
|
|
+ i[:4] + (f"{i[4]:.8f}", f"{i[5]:.8f}") for i in summary_list
|
|
|
|
|
+ ]
|
|
|
|
|
+ table.add_rows(summary_list)
|
|
|
|
|
+ header = "WarmUp Data".center(len(str(table).split("\n")[0]), " ")
|
|
|
|
|
+ logging.info(header)
|
|
|
|
|
+ logging.info(table)
|
|
|
|
|
+
|
|
|
|
|
+ else:
|
|
|
|
|
+ detail_head = [
|
|
|
|
|
+ "Iters",
|
|
|
|
|
+ "Batch Size",
|
|
|
|
|
+ "Instances",
|
|
|
|
|
+ "Operation",
|
|
|
|
|
+ "Avg Time Per Iter (ms)",
|
|
|
|
|
+ "Avg Time Per Instance (ms)",
|
|
|
|
|
+ ]
|
|
|
|
|
+ table = PrettyTable(detail_head)
|
|
|
|
|
+ detail_list = [i[:4] + (f"{i[4]:.8f}", f"{i[5]:.8f}") for i in detail_list]
|
|
|
|
|
+ table.add_rows(detail_list)
|
|
|
|
|
+ header = "Detail Data".center(len(str(table).split("\n")[0]), " ")
|
|
|
|
|
+ logging.info(header)
|
|
|
|
|
+ logging.info(table)
|
|
|
|
|
+
|
|
|
|
|
+ summary_head = [
|
|
|
|
|
+ "Iters",
|
|
|
|
|
+ "Batch Size",
|
|
|
|
|
+ "Instances",
|
|
|
|
|
+ "Stage",
|
|
|
|
|
+ "Avg Time Per Iter (ms)",
|
|
|
|
|
+ "Avg Time Per Instance (ms)",
|
|
|
|
|
+ ]
|
|
|
|
|
+ table = PrettyTable(summary_head)
|
|
|
|
|
+ summary_list = [
|
|
|
|
|
+ i[:4] + (f"{i[4]:.8f}", f"{i[5]:.8f}") for i in summary_list
|
|
|
|
|
+ ]
|
|
|
|
|
+ table.add_rows(summary_list)
|
|
|
|
|
+ header = "Summary Data".center(len(str(table).split("\n")[0]), " ")
|
|
|
|
|
+ logging.info(header)
|
|
|
|
|
+ logging.info(table)
|
|
|
|
|
+
|
|
|
|
|
+ if INFER_BENCHMARK_OUTPUT:
|
|
|
|
|
+ save_dir = Path(INFER_BENCHMARK_OUTPUT)
|
|
|
|
|
+ save_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
+ csv_data = [detail_head, *detail_list]
|
|
|
|
|
+ with open(Path(save_dir) / "detail.csv", "w", newline="") as file:
|
|
|
|
|
+ writer = csv.writer(file)
|
|
|
|
|
+ writer.writerows(csv_data)
|
|
|
|
|
+
|
|
|
|
|
+ csv_data = [summary_head, *summary_list]
|
|
|
|
|
+ with open(Path(save_dir) / "summary.csv", "w", newline="") as file:
|
|
|
|
|
+ writer = csv.writer(file)
|
|
|
|
|
+ writer.writerows(csv_data)
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+if INFER_BENCHMARK:
|
|
|
|
|
+ benchmark = Benchmark(enabled=True)
|
|
|
|
|
+else:
|
|
|
|
|
+ benchmark = Benchmark(enabled=False)
|