File size: 2,862 Bytes
d4a30e5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
"""Benchmark single-image inference latency for the trained model.

No dataset needed — runs over the bundled sample images in `images/`. From repo root:

    uv run python scripts/bench_speed.py                 # all available devices
    uv run python scripts/bench_speed.py --device cpu     # one device
    uv run python scripts/bench_speed.py --imgsz 640 --runs 30

Reports mean inference latency (ms) and FPS per device, warmup excluded. The
"inference" figure is the model forward pass only (Ultralytics' `speed['inference']`),
matching the methodology of the model-card inference table.
"""

from __future__ import annotations

import argparse
from pathlib import Path

import torch
from ultralytics import YOLO

WEIGHTS = "runs/detect/yolov8n_v1_train/weights/best.pt"
IMAGES_DIR = "images"


def available_devices() -> list[str]:
    devices = ["cpu"]
    if torch.cuda.is_available():
        devices.append("0")
    if torch.backends.mps.is_available():
        devices.append("mps")
    return devices


def bench(weights: str, device: str, images: list[str], imgsz: int, runs: int) -> tuple[float, float]:
    model = YOLO(weights)
    # Warmup — first calls pay lazy init / kernel-compile costs we don't want to time.
    model.predict(images[0], device=device, imgsz=imgsz, verbose=False)

    latencies: list[float] = []
    for _ in range(runs):
        for img in images:
            r = model.predict(img, device=device, imgsz=imgsz, verbose=False)
            latencies.append(r[0].speed["inference"])  # ms, forward pass only

    mean_ms = sum(latencies) / len(latencies)
    return mean_ms, 1000.0 / mean_ms


def main() -> None:
    p = argparse.ArgumentParser(description=__doc__)
    p.add_argument("--weights", default=WEIGHTS)
    p.add_argument("--images", default=IMAGES_DIR)
    p.add_argument("--device", default=None, help="cpu / mps / 0 (GPU); omit to run all available")
    p.add_argument("--imgsz", type=int, default=640)
    p.add_argument("--runs", type=int, default=20, help="passes over the image set per device")
    args = p.parse_args()

    images = sorted(str(p) for p in Path(args.images).glob("*") if p.suffix.lower() in {".jpg", ".jpeg", ".png"})
    if not images:
        raise SystemExit(f"No images found in {args.images!r}")

    devices = [args.device] if args.device else available_devices()
    print(f"Weights: {args.weights}")
    print(f"Images:  {len(images)} from {args.images}/  (imgsz={args.imgsz}, {args.runs} passes each)\n")
    print(f"{'Device':<10} {'Latency (ms)':>14} {'FPS':>8}")
    print("-" * 34)
    for dev in devices:
        mean_ms, fps = bench(args.weights, dev, images, args.imgsz, args.runs)
        label = {"cpu": "CPU", "mps": "MPS (GPU)", "0": "CUDA GPU"}.get(dev, dev)
        print(f"{label:<10} {mean_ms:>14.1f} {fps:>8.1f}")


if __name__ == "__main__":
    main()