|
a |
|
b/benchmarks.py |
|
|
1 |
# YOLOv5 🚀 by Ultralytics, AGPL-3.0 license |
|
|
2 |
""" |
|
|
3 |
Run YOLOv5 benchmarks on all supported export formats |
|
|
4 |
|
|
|
5 |
Format | `export.py --include` | Model |
|
|
6 |
--- | --- | --- |
|
|
7 |
PyTorch | - | yolov5s.pt |
|
|
8 |
TorchScript | `torchscript` | yolov5s.torchscript |
|
|
9 |
ONNX | `onnx` | yolov5s.onnx |
|
|
10 |
OpenVINO | `openvino` | yolov5s_openvino_model/ |
|
|
11 |
TensorRT | `engine` | yolov5s.engine |
|
|
12 |
CoreML | `coreml` | yolov5s.mlmodel |
|
|
13 |
TensorFlow SavedModel | `saved_model` | yolov5s_saved_model/ |
|
|
14 |
TensorFlow GraphDef | `pb` | yolov5s.pb |
|
|
15 |
TensorFlow Lite | `tflite` | yolov5s.tflite |
|
|
16 |
TensorFlow Edge TPU | `edgetpu` | yolov5s_edgetpu.tflite |
|
|
17 |
TensorFlow.js | `tfjs` | yolov5s_web_model/ |
|
|
18 |
|
|
|
19 |
Requirements: |
|
|
20 |
$ pip install -r requirements.txt coremltools onnx onnx-simplifier onnxruntime openvino-dev tensorflow-cpu # CPU |
|
|
21 |
$ pip install -r requirements.txt coremltools onnx onnx-simplifier onnxruntime-gpu openvino-dev tensorflow # GPU |
|
|
22 |
$ pip install -U nvidia-tensorrt --index-url https://pypi.ngc.nvidia.com # TensorRT |
|
|
23 |
|
|
|
24 |
Usage: |
|
|
25 |
$ python benchmarks.py --weights yolov5s.pt --img 640 |
|
|
26 |
""" |
|
|
27 |
|
|
|
28 |
import argparse |
|
|
29 |
import platform |
|
|
30 |
import sys |
|
|
31 |
import time |
|
|
32 |
from pathlib import Path |
|
|
33 |
|
|
|
34 |
import pandas as pd |
|
|
35 |
|
|
|
36 |
FILE = Path(__file__).resolve() |
|
|
37 |
ROOT = FILE.parents[0] # YOLOv5 root directory |
|
|
38 |
if str(ROOT) not in sys.path: |
|
|
39 |
sys.path.append(str(ROOT)) # add ROOT to PATH |
|
|
40 |
# ROOT = ROOT.relative_to(Path.cwd()) # relative |
|
|
41 |
|
|
|
42 |
import export |
|
|
43 |
from models.experimental import attempt_load |
|
|
44 |
from models.yolo import SegmentationModel |
|
|
45 |
from segment.val import run as val_seg |
|
|
46 |
from utils import notebook_init |
|
|
47 |
from utils.general import LOGGER, check_yaml, file_size, print_args |
|
|
48 |
from utils.torch_utils import select_device |
|
|
49 |
from val import run as val_det |
|
|
50 |
|
|
|
51 |
|
|
|
52 |
def run( |
|
|
53 |
weights=ROOT / 'yolov5s.pt', # weights path |
|
|
54 |
imgsz=640, # inference size (pixels) |
|
|
55 |
batch_size=1, # batch size |
|
|
56 |
data=ROOT / 'data/coco128.yaml', # dataset.yaml path |
|
|
57 |
device='', # cuda device, i.e. 0 or 0,1,2,3 or cpu |
|
|
58 |
half=False, # use FP16 half-precision inference |
|
|
59 |
test=False, # test exports only |
|
|
60 |
pt_only=False, # test PyTorch only |
|
|
61 |
hard_fail=False, # throw error on benchmark failure |
|
|
62 |
): |
|
|
63 |
y, t = [], time.time() |
|
|
64 |
device = select_device(device) |
|
|
65 |
model_type = type(attempt_load(weights, fuse=False)) # DetectionModel, SegmentationModel, etc. |
|
|
66 |
for i, (name, f, suffix, cpu, gpu) in export.export_formats().iterrows(): # index, (name, file, suffix, CPU, GPU) |
|
|
67 |
try: |
|
|
68 |
assert i not in (9, 10), 'inference not supported' # Edge TPU and TF.js are unsupported |
|
|
69 |
assert i != 5 or platform.system() == 'Darwin', 'inference only supported on macOS>=10.13' # CoreML |
|
|
70 |
if 'cpu' in device.type: |
|
|
71 |
assert cpu, 'inference not supported on CPU' |
|
|
72 |
if 'cuda' in device.type: |
|
|
73 |
assert gpu, 'inference not supported on GPU' |
|
|
74 |
|
|
|
75 |
# Export |
|
|
76 |
if f == '-': |
|
|
77 |
w = weights # PyTorch format |
|
|
78 |
else: |
|
|
79 |
w = export.run(weights=weights, |
|
|
80 |
imgsz=[imgsz], |
|
|
81 |
include=[f], |
|
|
82 |
batch_size=batch_size, |
|
|
83 |
device=device, |
|
|
84 |
half=half)[-1] # all others |
|
|
85 |
assert suffix in str(w), 'export failed' |
|
|
86 |
|
|
|
87 |
# Validate |
|
|
88 |
if model_type == SegmentationModel: |
|
|
89 |
result = val_seg(data, w, batch_size, imgsz, plots=False, device=device, task='speed', half=half) |
|
|
90 |
metric = result[0][7] # (box(p, r, map50, map), mask(p, r, map50, map), *loss(box, obj, cls)) |
|
|
91 |
else: # DetectionModel: |
|
|
92 |
result = val_det(data, w, batch_size, imgsz, plots=False, device=device, task='speed', half=half) |
|
|
93 |
metric = result[0][3] # (p, r, map50, map, *loss(box, obj, cls)) |
|
|
94 |
speed = result[2][1] # times (preprocess, inference, postprocess) |
|
|
95 |
y.append([name, round(file_size(w), 1), round(metric, 4), round(speed, 2)]) # MB, mAP, t_inference |
|
|
96 |
except Exception as e: |
|
|
97 |
if hard_fail: |
|
|
98 |
assert type(e) is AssertionError, f'Benchmark --hard-fail for {name}: {e}' |
|
|
99 |
LOGGER.warning(f'WARNING ⚠️ Benchmark failure for {name}: {e}') |
|
|
100 |
y.append([name, None, None, None]) # mAP, t_inference |
|
|
101 |
if pt_only and i == 0: |
|
|
102 |
break # break after PyTorch |
|
|
103 |
|
|
|
104 |
# Print results |
|
|
105 |
LOGGER.info('\n') |
|
|
106 |
parse_opt() |
|
|
107 |
notebook_init() # print system info |
|
|
108 |
c = ['Format', 'Size (MB)', 'mAP50-95', 'Inference time (ms)'] if map else ['Format', 'Export', '', ''] |
|
|
109 |
py = pd.DataFrame(y, columns=c) |
|
|
110 |
LOGGER.info(f'\nBenchmarks complete ({time.time() - t:.2f}s)') |
|
|
111 |
LOGGER.info(str(py if map else py.iloc[:, :2])) |
|
|
112 |
if hard_fail and isinstance(hard_fail, str): |
|
|
113 |
metrics = py['mAP50-95'].array # values to compare to floor |
|
|
114 |
floor = eval(hard_fail) # minimum metric floor to pass, i.e. = 0.29 mAP for YOLOv5n |
|
|
115 |
assert all(x > floor for x in metrics if pd.notna(x)), f'HARD FAIL: mAP50-95 < floor {floor}' |
|
|
116 |
return py |
|
|
117 |
|
|
|
118 |
|
|
|
119 |
def test( |
|
|
120 |
weights=ROOT / 'yolov5s.pt', # weights path |
|
|
121 |
imgsz=640, # inference size (pixels) |
|
|
122 |
batch_size=1, # batch size |
|
|
123 |
data=ROOT / 'data/coco128.yaml', # dataset.yaml path |
|
|
124 |
device='', # cuda device, i.e. 0 or 0,1,2,3 or cpu |
|
|
125 |
half=False, # use FP16 half-precision inference |
|
|
126 |
test=False, # test exports only |
|
|
127 |
pt_only=False, # test PyTorch only |
|
|
128 |
hard_fail=False, # throw error on benchmark failure |
|
|
129 |
): |
|
|
130 |
y, t = [], time.time() |
|
|
131 |
device = select_device(device) |
|
|
132 |
for i, (name, f, suffix, gpu) in export.export_formats().iterrows(): # index, (name, file, suffix, gpu-capable) |
|
|
133 |
try: |
|
|
134 |
w = weights if f == '-' else \ |
|
|
135 |
export.run(weights=weights, imgsz=[imgsz], include=[f], device=device, half=half)[-1] # weights |
|
|
136 |
assert suffix in str(w), 'export failed' |
|
|
137 |
y.append([name, True]) |
|
|
138 |
except Exception: |
|
|
139 |
y.append([name, False]) # mAP, t_inference |
|
|
140 |
|
|
|
141 |
# Print results |
|
|
142 |
LOGGER.info('\n') |
|
|
143 |
parse_opt() |
|
|
144 |
notebook_init() # print system info |
|
|
145 |
py = pd.DataFrame(y, columns=['Format', 'Export']) |
|
|
146 |
LOGGER.info(f'\nExports complete ({time.time() - t:.2f}s)') |
|
|
147 |
LOGGER.info(str(py)) |
|
|
148 |
return py |
|
|
149 |
|
|
|
150 |
|
|
|
151 |
def parse_opt(): |
|
|
152 |
parser = argparse.ArgumentParser() |
|
|
153 |
parser.add_argument('--weights', type=str, default=ROOT / 'yolov5s.pt', help='weights path') |
|
|
154 |
parser.add_argument('--imgsz', '--img', '--img-size', type=int, default=640, help='inference size (pixels)') |
|
|
155 |
parser.add_argument('--batch-size', type=int, default=1, help='batch size') |
|
|
156 |
parser.add_argument('--data', type=str, default=ROOT / 'data/coco128.yaml', help='dataset.yaml path') |
|
|
157 |
parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu') |
|
|
158 |
parser.add_argument('--half', action='store_true', help='use FP16 half-precision inference') |
|
|
159 |
parser.add_argument('--test', action='store_true', help='test exports only') |
|
|
160 |
parser.add_argument('--pt-only', action='store_true', help='test PyTorch only') |
|
|
161 |
parser.add_argument('--hard-fail', nargs='?', const=True, default=False, help='Exception on error or < min metric') |
|
|
162 |
opt = parser.parse_args() |
|
|
163 |
opt.data = check_yaml(opt.data) # check YAML |
|
|
164 |
print_args(vars(opt)) |
|
|
165 |
return opt |
|
|
166 |
|
|
|
167 |
|
|
|
168 |
def main(opt): |
|
|
169 |
test(**vars(opt)) if opt.test else run(**vars(opt)) |
|
|
170 |
|
|
|
171 |
|
|
|
172 |
if __name__ == '__main__': |
|
|
173 |
opt = parse_opt() |
|
|
174 |
main(opt) |