benchmark.py 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. # copyright (c) 2024 PaddlePaddle Authors. All Rights Reserve.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. import csv
  15. import functools
  16. from types import GeneratorType
  17. import time
  18. from pathlib import Path
  19. import numpy as np
  20. from prettytable import PrettyTable
  21. from ...utils.flags import INFER_BENCHMARK, INFER_BENCHMARK_OUTPUT
  22. from ...utils.misc import Singleton
  23. from ...utils import logging
  24. class Benchmark(metaclass=Singleton):
  25. def __init__(self):
  26. self._components = {}
  27. self._warmup_start = None
  28. self._warmup_elapse = None
  29. self._warmup_num = None
  30. self._e2e_tic = None
  31. self._e2e_elapse = None
  32. def attach(self, component):
  33. self._components[component.name] = component
  34. def start(self):
  35. self._warmup_start = time.time()
  36. self._reset()
  37. def warmup_stop(self, warmup_num):
  38. self._warmup_elapse = (time.time() - self._warmup_start) * 1000
  39. self._warmup_num = warmup_num
  40. self._reset()
  41. def _reset(self):
  42. for name, cmp in self.iterate_cmp(self._components):
  43. cmp.timer.reset()
  44. self._e2e_tic = time.time()
  45. def iterate_cmp(self, cmps):
  46. if cmps is None:
  47. return
  48. for name, cmp in cmps.items():
  49. if hasattr(cmp, "benchmark"):
  50. yield from self.iterate_cmp(cmp.benchmark)
  51. yield name, cmp
  52. def gather(self, e2e_num):
  53. # lazy import for avoiding circular import
  54. from ...utils.flags import NEW_PREDICTOR
  55. if NEW_PREDICTOR:
  56. from ..new_models.base import BasePaddlePredictor
  57. else:
  58. from ..models.common_components.paddle_predictor import BasePaddlePredictor
  59. detail = []
  60. summary = {"preprocess": 0, "inference": 0, "postprocess": 0}
  61. op_tag = "preprocess"
  62. for name, cmp in self._components.items():
  63. if isinstance(cmp, BasePaddlePredictor):
  64. # TODO(gaotingquan): show by hierarchy. Now dont show xxxPredictor benchmark info to ensure mutual exclusivity between components.
  65. for name, sub_cmp in cmp.benchmark.items():
  66. times = sub_cmp.timer.logs
  67. counts = len(times)
  68. avg = np.mean(times) * 1000
  69. total = np.sum(times) * 1000
  70. detail.append((name, total, counts, avg))
  71. summary["inference"] += total
  72. op_tag = "postprocess"
  73. else:
  74. # TODO(gaotingquan): support sub_cmps for others
  75. # if hasattr(cmp, "benchmark"):
  76. times = cmp.timer.logs
  77. counts = len(times)
  78. avg = np.mean(times) * 1000
  79. total = np.sum(times) * 1000
  80. detail.append((name, total, counts, avg))
  81. summary[op_tag] += total
  82. summary = [
  83. (
  84. "PreProcess",
  85. summary["preprocess"],
  86. e2e_num,
  87. summary["preprocess"] / e2e_num,
  88. ),
  89. (
  90. "Inference",
  91. summary["inference"],
  92. e2e_num,
  93. summary["inference"] / e2e_num,
  94. ),
  95. (
  96. "PostProcess",
  97. summary["postprocess"],
  98. e2e_num,
  99. summary["postprocess"] / e2e_num,
  100. ),
  101. ("End2End", self._e2e_elapse, e2e_num, self._e2e_elapse / e2e_num),
  102. ]
  103. if self._warmup_elapse:
  104. warmup_elapse, warmup_num, warmup_avg = (
  105. self._warmup_elapse,
  106. self._warmup_num,
  107. self._warmup_elapse / self._warmup_num,
  108. )
  109. else:
  110. warmup_elapse, warmup_num, warmup_avg = 0, 0, 0
  111. summary.append(
  112. (
  113. "WarmUp",
  114. warmup_elapse,
  115. warmup_num,
  116. warmup_avg,
  117. )
  118. )
  119. return detail, summary
  120. def collect(self, e2e_num):
  121. self._e2e_elapse = (time.time() - self._e2e_tic) * 1000
  122. detail, summary = self.gather(e2e_num)
  123. detail_head = [
  124. "Component",
  125. "Total Time (ms)",
  126. "Number of Calls",
  127. "Avg Time Per Call (ms)",
  128. ]
  129. table = PrettyTable(detail_head)
  130. table.add_rows(
  131. [
  132. (name, f"{total:.8f}", cnts, f"{avg:.8f}")
  133. for name, total, cnts, avg in detail
  134. ]
  135. )
  136. logging.info(table)
  137. summary_head = [
  138. "Stage",
  139. "Total Time (ms)",
  140. "Number of Instances",
  141. "Avg Time Per Instance (ms)",
  142. ]
  143. table = PrettyTable(summary_head)
  144. table.add_rows(
  145. [
  146. (name, f"{total:.8f}", cnts, f"{avg:.8f}")
  147. for name, total, cnts, avg in summary
  148. ]
  149. )
  150. logging.info(table)
  151. if INFER_BENCHMARK_OUTPUT:
  152. save_dir = Path(INFER_BENCHMARK_OUTPUT)
  153. save_dir.mkdir(parents=True, exist_ok=True)
  154. csv_data = [detail_head, *detail]
  155. with open(Path(save_dir) / "detail.csv", "w", newline="") as file:
  156. writer = csv.writer(file)
  157. writer.writerows(csv_data)
  158. csv_data = [summary_head, *summary]
  159. with open(Path(save_dir) / "summary.csv", "w", newline="") as file:
  160. writer = csv.writer(file)
  161. writer.writerows(csv_data)
  162. class Timer:
  163. def __init__(self, component):
  164. from ..new_models.base import BaseComponent
  165. assert isinstance(component, BaseComponent)
  166. benchmark.attach(component)
  167. component.apply = self.watch_func(component.apply)
  168. self._tic = None
  169. self._elapses = []
  170. def watch_func(self, func):
  171. @functools.wraps(func)
  172. def wrapper(*args, **kwargs):
  173. tic = time.time()
  174. output = func(*args, **kwargs)
  175. if isinstance(output, GeneratorType):
  176. return self.watch_generator(output)
  177. else:
  178. self._update(time.time() - tic)
  179. return output
  180. return wrapper
  181. def watch_generator(self, generator):
  182. @functools.wraps(generator)
  183. def wrapper():
  184. while 1:
  185. try:
  186. tic = time.time()
  187. item = next(generator)
  188. self._update(time.time() - tic)
  189. yield item
  190. except StopIteration:
  191. break
  192. return wrapper()
  193. def reset(self):
  194. self._tic = None
  195. self._elapses = []
  196. def _update(self, elapse):
  197. self._elapses.append(elapse)
  198. @property
  199. def logs(self):
  200. return self._elapses
  201. benchmark = Benchmark() if INFER_BENCHMARK else None