benchmark.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  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 ..new_models.base import BasePaddlePredictor
  55. detail = []
  56. summary = {"preprocess": 0, "inference": 0, "postprocess": 0}
  57. op_tag = "preprocess"
  58. for name, cmp in self._components.items():
  59. if isinstance(cmp, BasePaddlePredictor):
  60. # TODO(gaotingquan): show by hierarchy. Now dont show xxxPredictor benchmark info to ensure mutual exclusivity between components.
  61. for name, sub_cmp in cmp.benchmark.items():
  62. times = sub_cmp.timer.logs
  63. counts = len(times)
  64. avg = np.mean(times) * 1000
  65. total = np.sum(times) * 1000
  66. detail.append((name, total, counts, avg))
  67. summary["inference"] += total
  68. op_tag = "postprocess"
  69. else:
  70. # TODO(gaotingquan): support sub_cmps for others
  71. # if hasattr(cmp, "benchmark"):
  72. times = cmp.timer.logs
  73. counts = len(times)
  74. avg = np.mean(times) * 1000
  75. total = np.sum(times) * 1000
  76. detail.append((name, total, counts, avg))
  77. summary[op_tag] += total
  78. summary = [
  79. (
  80. "PreProcess",
  81. summary["preprocess"],
  82. e2e_num,
  83. summary["preprocess"] / e2e_num,
  84. ),
  85. (
  86. "Inference",
  87. summary["inference"],
  88. e2e_num,
  89. summary["inference"] / e2e_num,
  90. ),
  91. (
  92. "PostProcess",
  93. summary["postprocess"],
  94. e2e_num,
  95. summary["postprocess"] / e2e_num,
  96. ),
  97. ("End2End", self._e2e_elapse, e2e_num, self._e2e_elapse / e2e_num),
  98. ]
  99. if self._warmup_elapse:
  100. warmup_elapse, warmup_num, warmup_avg = (
  101. self._warmup_elapse,
  102. self._warmup_num,
  103. self._warmup_elapse / self._warmup_num,
  104. )
  105. else:
  106. warmup_elapse, warmup_num, warmup_avg = 0, 0, 0
  107. summary.append(
  108. (
  109. "WarmUp",
  110. warmup_elapse,
  111. warmup_num,
  112. warmup_avg,
  113. )
  114. )
  115. return detail, summary
  116. def collect(self, e2e_num):
  117. self._e2e_elapse = (time.time() - self._e2e_tic) * 1000
  118. detail, summary = self.gather(e2e_num)
  119. detail_head = [
  120. "Component",
  121. "Total Time (ms)",
  122. "Number of Calls",
  123. "Avg Time Per Call (ms)",
  124. ]
  125. table = PrettyTable(detail_head)
  126. table.add_rows(
  127. [
  128. (name, f"{total:.8f}", cnts, f"{avg:.8f}")
  129. for name, total, cnts, avg in detail
  130. ]
  131. )
  132. logging.info(table)
  133. summary_head = [
  134. "Stage",
  135. "Total Time (ms)",
  136. "Number of Instances",
  137. "Avg Time Per Instance (ms)",
  138. ]
  139. table = PrettyTable(summary_head)
  140. table.add_rows(
  141. [
  142. (name, f"{total:.8f}", cnts, f"{avg:.8f}")
  143. for name, total, cnts, avg in summary
  144. ]
  145. )
  146. logging.info(table)
  147. if INFER_BENCHMARK_OUTPUT:
  148. save_dir = Path(INFER_BENCHMARK_OUTPUT)
  149. save_dir.mkdir(parents=True, exist_ok=True)
  150. csv_data = [detail_head, *detail]
  151. with open(Path(save_dir) / "detail.csv", "w", newline="") as file:
  152. writer = csv.writer(file)
  153. writer.writerows(csv_data)
  154. csv_data = [summary_head, *summary]
  155. with open(Path(save_dir) / "summary.csv", "w", newline="") as file:
  156. writer = csv.writer(file)
  157. writer.writerows(csv_data)
  158. class Timer:
  159. def __init__(self, component):
  160. from ..new_models.base import BaseComponent
  161. assert isinstance(component, BaseComponent)
  162. benchmark.attach(component)
  163. component.apply = self.watch_func(component.apply)
  164. self._tic = None
  165. self._elapses = []
  166. def watch_func(self, func):
  167. @functools.wraps(func)
  168. def wrapper(*args, **kwargs):
  169. tic = time.time()
  170. output = func(*args, **kwargs)
  171. if isinstance(output, GeneratorType):
  172. return self.watch_generator(output)
  173. else:
  174. self._update(time.time() - tic)
  175. return output
  176. return wrapper
  177. def watch_generator(self, generator):
  178. @functools.wraps(generator)
  179. def wrapper():
  180. while 1:
  181. try:
  182. tic = time.time()
  183. item = next(generator)
  184. self._update(time.time() - tic)
  185. yield item
  186. except StopIteration:
  187. break
  188. return wrapper()
  189. def reset(self):
  190. self._tic = None
  191. self._elapses = []
  192. def _update(self, elapse):
  193. self._elapses.append(elapse)
  194. @property
  195. def logs(self):
  196. return self._elapses
  197. benchmark = Benchmark() if INFER_BENCHMARK else None