Skip to content

Commit 71ec697

Browse files
YOLO dev branch
1 parent 6776245 commit 71ec697

File tree

7 files changed

+308
-0
lines changed

7 files changed

+308
-0
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
YOLO README - a working design doc
2+
3+
What does it take to get YOLO fully working?
4+
- Perf comparison local to Ultralyrics
5+
- Perf and accuracy to run with LoadGen
6+
- Calculate and output accuracy with LoadGen run
7+
- Compliance
8+
- Model distribution script
9+
- Dataset processing / distribution script
10+
11+
12+
How to run yolo_loadgen.py
13+
Examples usage:
14+
Perf run
15+
python yolo_loadgen.py --dataset-path {DATASET_PATH} --model {MODEL_FILE} --scenario {Offline, SingleStream, MultiStream} --output {OUTPUT_RESULTS_DIR}
16+
17+
Accuracy run
18+
python yolo_loadgen.py --dataset-path {DATASET_PATH} --model {MODEL_FILE} --scenario {Offline, SingleStream, MultiStream} --accuracy --output {OUTPUT_RESULTS_DIR}
19+
20+
Arguments:
21+
--dataset-path -> path to dataset images
22+
--model -> path to YOLO model
23+
--device -> device # leave as is
24+
--scenario -> ["Offline", "SingleStream", "MultiStream"]
25+
--accuracy -> run accuracy mode
26+
--count -> number of samples
27+
--output -> output directory
28+
29+
Example output is under inference/vision/classification_and_detection/yolo_result_10232025/ for YOLOv11[N, S, M, L, X]
Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
"""
2+
YOLOv11 LoadGen MLPerf
3+
"""
4+
5+
import argparse
6+
import array
7+
import json
8+
import logging
9+
import os
10+
import sys
11+
import time
12+
from pathlib import Path
13+
14+
import numpy as np
15+
import mlperf_loadgen as lg
16+
from ultralytics import YOLO
17+
18+
19+
# COCO Dataset handler for YOLO
20+
class Coco:
21+
def __init__(self, data_path, count=None):
22+
self.image_list = []
23+
self.image_ids = []
24+
self.data_path = data_path
25+
26+
# load from annotations
27+
annotations_file = Path(data_path).parent.parent / "annotations" / "instances_val2017.json"
28+
if not annotations_file.exists():
29+
annotations_file = Path(data_path).parent.parent / "annotations" / "image_info_test-dev2017.json"
30+
31+
if annotations_file.exists():
32+
with open(annotations_file, 'r') as f:
33+
coco = json.load(f)
34+
for img_info in coco['images']:
35+
img_path = os.path.join(data_path, img_info['file_name'])
36+
if os.path.exists(img_path):
37+
self.image_list.append(img_path)
38+
self.image_ids.append(img_info['id'])
39+
else:
40+
# load from directory
41+
for img_path in sorted(Path(data_path).glob("*.jpg")):
42+
self.image_list.append(str(img_path))
43+
self.image_ids.append(int(img_path.stem))
44+
45+
self.count = len(self.image_list) if count is None else min(count, len(self.image_list))
46+
print(f"Loaded {self.count} images")
47+
48+
def get_item_count(self):
49+
return self.count
50+
51+
def get_item_loc(self, idx):
52+
return self.image_list[idx], self.image_ids[idx]
53+
54+
55+
# post process COCO - convert YOLO outputs to COCO format
56+
class PostProcessCoco:
57+
def __init__(self):
58+
self.results = []
59+
60+
def start(self):
61+
self.results = []
62+
63+
def add_results(self, results):
64+
self.results.extend(results)
65+
66+
def finalize(self, output_dir):
67+
if output_dir:
68+
output_file = os.path.join(output_dir, "predictions.json")
69+
with open(output_file, 'w') as f:
70+
json.dump(self.results, f)
71+
print(f"saved {len(self.results)} predictions to {output_file}")
72+
73+
74+
# YOLO inference engine backend
75+
class BackendYOLO:
76+
def __init__(self, model_path, device="cuda:0"):
77+
print(f"loading model: {model_path}")
78+
self.model = YOLO(model_path)
79+
self.model.to(device)
80+
print("model has been loaded")
81+
82+
def predict(self, img_path):
83+
results = self.model.predict(
84+
img_path,
85+
conf=0.001,
86+
iou=0.6,
87+
max_det=300,
88+
imgsz=640,
89+
verbose=False
90+
)
91+
return results[0]
92+
93+
94+
# runner for orchestration, dataset, model and LoadGen - based on inference/vision/classification_and_detection/python/main.py
95+
class Runner:
96+
def __init__(self, model, ds, post_proc):
97+
self.model = model
98+
self.ds = ds
99+
self.post_proc = post_proc
100+
self.take_accuracy = False
101+
102+
def start_run(self, take_accuracy):
103+
self.take_accuracy = take_accuracy
104+
self.post_proc.start()
105+
106+
# convert YOLO result to COCO format
107+
def convert_to_coco(self, result, image_id):
108+
detections = []
109+
110+
if len(result.boxes) == 0:
111+
return detections
112+
113+
boxes = result.boxes.xyxy.cpu().numpy()
114+
scores = result.boxes.conf.cpu().numpy()
115+
classes = result.boxes.cls.cpu().numpy()
116+
117+
for box, score, cls in zip(boxes, scores, classes):
118+
x1, y1, x2, y2 = box
119+
detection = {
120+
"image_id": int(image_id),
121+
"category_id": int(cls) + 1, # COCO is 1-indexed
122+
"bbox": [float(x1), float(y1), float(x2 - x1), float(y2 - y1)],
123+
"score": float(score)
124+
}
125+
detections.append(detection)
126+
127+
return detections
128+
129+
# to process the query samples
130+
def enqueue(self, query_samples):
131+
for qitem in query_samples:
132+
img_path, img_id = self.ds.get_item_loc(qitem.index)
133+
134+
# run inference
135+
result = self.model.predict(img_path)
136+
137+
# convert to COCO format
138+
detections = self.convert_to_coco(result, img_id)
139+
140+
# store for accuracy
141+
if self.take_accuracy:
142+
self.post_proc.add_results(detections)
143+
144+
# prepare response for LoadGen
145+
response_data = json.dumps(detections).encode('utf-8')
146+
response_array = array.array('B', response_data)
147+
bi = response_array.buffer_info()
148+
149+
response = lg.QuerySampleResponse(qitem.id, bi[0], bi[1])
150+
lg.QuerySamplesComplete([response])
151+
152+
153+
# QSL/SUT LoadGen
154+
class QueueRunner:
155+
def __init__(self, runner):
156+
self.runner = runner
157+
self.qsl = None
158+
self.sut = None
159+
160+
def load_query_samples(self, sample_list):
161+
pass
162+
163+
def unload_query_samples(self, sample_list):
164+
pass
165+
166+
def issue_queries(self, query_samples):
167+
self.runner.enqueue(query_samples)
168+
169+
def flush_queries(self):
170+
pass
171+
172+
# creaet SUT
173+
def get_sut(ds, runner):
174+
queue_runner = QueueRunner(runner)
175+
176+
qsl = lg.ConstructQSL(
177+
ds.get_item_count(),
178+
ds.get_item_count(),
179+
queue_runner.load_query_samples,
180+
queue_runner.unload_query_samples
181+
)
182+
queue_runner.qsl = qsl
183+
184+
sut = lg.ConstructSUT(
185+
queue_runner.issue_queries,
186+
queue_runner.flush_queries
187+
)
188+
queue_runner.sut = sut
189+
190+
191+
return qsl, sut, queue_runner
192+
193+
194+
def main():
195+
parser = argparse.ArgumentParser()
196+
parser.add_argument("--dataset-path", required=True, help="path to dataset images")
197+
parser.add_argument("--model", required=True, help="path to YOLO model")
198+
parser.add_argument("--device", default="cuda:0", help="device")
199+
parser.add_argument("--scenario", default="Offline", choices=["Offline", "SingleStream", "MultiStream"])
200+
parser.add_argument("--accuracy", action="store_true", help="run accuracy mode")
201+
parser.add_argument("--count", type=int, help="number of samples")
202+
parser.add_argument("--output", default="output", help="output directory")
203+
args = parser.parse_args()
204+
205+
print("=" * 60)
206+
print("YOLOv11 MLC LoadGen POC")
207+
print("=" * 60)
208+
209+
os.makedirs(args.output, exist_ok=True)
210+
211+
# load dataset
212+
ds = Coco(args.dataset_path, count=args.count)
213+
214+
# load model
215+
backend = BackendYOLO(args.model, device=args.device)
216+
217+
# create post-processor and runner
218+
post_proc = PostProcessCoco()
219+
runner = Runner(backend, ds, post_proc)
220+
221+
# create QSL and SUT
222+
qsl, sut, queue_runner = get_sut(ds, runner)
223+
224+
# configure LoadGen
225+
settings = lg.TestSettings()
226+
settings.scenario = getattr(lg.TestScenario, args.scenario)
227+
settings.mode = lg.TestMode.AccuracyOnly if args.accuracy else lg.TestMode.PerformanceOnly
228+
229+
if args.scenario == "Offline":
230+
settings.offline_expected_qps = 100
231+
elif args.scenario == "SingleStream":
232+
settings.single_stream_expected_latency_ns = 10000000 # 10ms
233+
elif args.scenario == "MultiStream":
234+
settings.multi_stream_samples_per_query = 8
235+
settings.multi_stream_target_latency_ns = 50000000 # 50ms
236+
237+
settings.min_duration_ms = 60000
238+
settings.min_query_count = 100
239+
240+
# logging - come back to this
241+
log_settings = lg.LogSettings()
242+
log_settings.log_output.outdir = args.output
243+
log_settings.log_output.copy_summary_to_stdout = True
244+
log_settings.enable_trace = False
245+
246+
# run
247+
print(f"\nRunning {args.scenario} scenario...")
248+
print("-" * 60)
249+
250+
runner.start_run(args.accuracy)
251+
252+
start = time.time()
253+
lg.StartTestWithLogSettings(sut, qsl, settings, log_settings)
254+
elapsed = time.time() - start
255+
256+
print("-" * 60)
257+
print(f"Completed in {elapsed:.2f}s\n")
258+
259+
# save results
260+
if args.accuracy:
261+
post_proc.finalize(args.output)
262+
263+
print("=" * 60)
264+
print(f"Results: {args.output}")
265+
print("=" * 60)
266+
267+
# destroy qsl and sut cleanup
268+
lg.DestroyQSL(qsl)
269+
lg.DestroySUT(sut)
270+
271+
272+
if __name__ == "__main__":
273+
main()
274+

vision/classification_and_detection/yolo/yolo_result_10232025/results_accuracy_l/predictions.json

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)