CoolFace
Datasetpublic

Mahmoud-Wael/gpu-benchmark-counter

sourceHugging Faceupdated 2mo agoView on Hugging Face
0likes5downloads
Benchmark_Counter.py324 linesDownload Raw Back to root
1"""2GPU benchmark for the people-counting pipeline.3 4Purpose: run the exact detection + tracking workload against a test video,5on whatever GPU the current machine has, and report whether that GPU can6keep up with the real camera in real time (25 fps @ 3200x1800).7 8GPU isolation strategy9----------------------10Wall-clock times (time.perf_counter) always include CPU dispatch latency.11To isolate ONLY the GPU's own work, we use torch.cuda.Event timers:12  - start_event.record()  → drops a timestamp marker into the CUDA stream13  - end_event.record()    → drops a second marker into the same stream14  - torch.cuda.synchronize() → blocks the CPU until ALL previously-queued15                               GPU kernels have completed16  - start_event.elapsed_time(end_event) → returns the GPU-internal duration17    between the two markers in milliseconds, measured entirely on-device with18    no CPU-dispatch jitter or OS scheduling noise.19This is the only number that lets you compare GPU-to-GPU fairly, regardless20of the CPU speed or driver overhead of the rental machine.21 22Usage:23    python benchmark.py24 25Edit the CONFIG section below before each run.26"""27 28import time29import json30 31import cv232import numpy as np33import torch34from ultralytics import YOLO35 36# ---------------------------------------------------------------------------37# CONFIG - edit these for each test38# ---------------------------------------------------------------------------39 40VIDEO_PATH = "Test Video_Counter.mp4"       # a real clip from your camera, ideally at native 3200x180041MODEL_PATH = "yolo26l.engine"42CONFIDENCE_THRESHOLD = 0.443DEVICE = "cuda"                      # "cuda" on a GPU instance, "cpu" for comparison44 45CAMERA_FPS = 25                      # your real camera's frame rate46WARMUP_FRAMES = 10                   # frames run first and excluded from timing47 48ROI_LIST = [49    np.array([(904, 245), (1931, 89), (2450, 1718), (1096, 1768)], np.int32),50    np.array([(481, 386), (479, 1292), (1033, 1264), (921, 291)], np.int32),51]52 53with open("regions.json", "r") as f:54    regions_data = json.load(f)55    REGION1 = np.array(regions_data["region1"], np.int32)56    REGION2 = np.array(regions_data["region2"], np.int32)57 58 59# ---------------------------------------------------------------------------60# Same tracking logic as the original script - unchanged61# ---------------------------------------------------------------------------62 63def point_in_polygon(point, polygon):64    return cv2.pointPolygonTest(polygon, point, False) >= 065 66 67def get_center_position(bbox):68    x1, y1, x2, y2 = bbox69    return (int((x1 + x2) / 2), int((y1 + y2) / 2))70 71 72def process_tracking(track_id, current_position, tracked_states, counted_ids, counts):73    in_region1 = point_in_polygon(current_position, REGION1)74    in_region2 = point_in_polygon(current_position, REGION2)75    current_region = 1 if in_region1 else (2 if in_region2 else None)76 77    previous_region = tracked_states.get(track_id, None)78 79    if track_id not in counted_ids and previous_region and current_region:80        if previous_region != current_region:81            if previous_region == 1 and current_region == 2:82                counts["in"] += 183                counted_ids.add(track_id)84            elif previous_region == 2 and current_region == 1:85                counts["out"] += 186                counted_ids.add(track_id)87 88    if current_region:89        tracked_states[track_id] = current_region90 91 92# ---------------------------------------------------------------------------93# GPU spec helper94# ---------------------------------------------------------------------------95 96def print_gpu_specs():97    """Print the name and key specs of the active CUDA device."""98    if not torch.cuda.is_available():99        print("No CUDA device detected - running on CPU.")100        return101    dev = torch.cuda.current_device()102    props = torch.cuda.get_device_properties(dev)103    vram_gb = props.total_memory / (1024 ** 3)104    print("=" * 50)105    print("GPU SPECIFICATIONS")106    print("=" * 50)107    print(f"  GPU Name:              {props.name}")108    print(f"  CUDA Device Index:     {dev}")109    print(f"  Total VRAM:            {vram_gb:.2f} GB")110    print(f"  Multiprocessors (SM):  {props.multi_processor_count}")111    print(f"  CUDA Capability:       {props.major}.{props.minor}")112    print(f"  PyTorch CUDA version:  {torch.version.cuda}")113    print("=" * 50)114 115 116# ---------------------------------------------------------------------------117# Benchmark run118# ---------------------------------------------------------------------------119 120def main():121    print_gpu_specs()122 123    print(f"\nLoading model on device: {DEVICE}")124    model = YOLO(MODEL_PATH)125 126    tracked_states = {}127    counted_ids = set()128    counts = {"in": 0, "out": 0}129 130    cap = cv2.VideoCapture(VIDEO_PATH)131    if not cap.isOpened():132        print(f"ERROR: could not open video at {VIDEO_PATH}")133        return134 135    native_w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))136    native_h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))137    print(f"Video resolution: {native_w}x{native_h}\n")138 139    frame_times = []     # seconds per frame, total (decode + inference), timed frames only140    decode_times = []    # seconds per frame spent just reading/decoding (CPU-bound)141    inference_times = [] # seconds per frame spent in model.track() + tracking logic (CPU clock, mostly GPU-bound)142    gpu_times_ms = []    # milliseconds per frame spent ONLY on the GPU itself (torch.cuda.Event, cuda only)143    frame_index = 0144 145    use_cuda_events = (DEVICE == "cuda") and torch.cuda.is_available()146    if use_cuda_events:147        # One pair of events reused each frame - record() is asynchronous and148        # does not block the CPU; elapsed_time() is called only after synchronize().149        start_event = torch.cuda.Event(enable_timing=True)150        end_event   = torch.cuda.Event(enable_timing=True)151 152    # Wall-clock timestamp marking the start of the first timed frame's GPU work.153    # Used to compute total GPU session duration below.154    gpu_session_start = None155    gpu_session_end   = None156 157    while cap.isOpened():158        t_decode_start = time.perf_counter()159        ret, frame = cap.read()160        t_decode_end = time.perf_counter()161 162        if not ret:163            break164 165        frame_index += 1166        is_warmup = frame_index <= WARMUP_FRAMES167 168        t_infer_start = time.perf_counter()169 170        if use_cuda_events:171            # record() inserts the marker into the current CUDA stream NOW.172            # No CPU work between start_event.record() and end_event.record()173            # except the YOLO call itself, so the elapsed time captures ONLY174            # the GPU kernels dispatched by model.track().175            start_event.record()176 177        results = model.track(178            frame,179            persist=True,180            tracker="bytetrack.yaml",181            classes=[0],182            conf=CONFIDENCE_THRESHOLD,183            verbose=False184        )185 186        if use_cuda_events:187            end_event.record()188            # synchronize() blocks the CPU until ALL GPU work queued up to this189            # point (including every kernel launched by model.track()) has190            # actually completed. Without this, elapsed_time() would measure an191            # incomplete range and t_infer_end would be taken before the GPU192            # finishes, making both the wall-clock and CUDA-event numbers wrong.193            torch.cuda.synchronize()194            gpu_ms = start_event.elapsed_time(end_event)  # true GPU-side duration, ms195 196        if results[0].boxes is not None and results[0].boxes.id is not None:197            boxes = results[0].boxes.xyxy.cpu().numpy()198            track_ids = results[0].boxes.id.cpu().numpy().astype(int)199 200            for box, track_id in zip(boxes, track_ids):201                center_pos = get_center_position(box)202 203                is_inside_any_roi = any(point_in_polygon(center_pos, roi) for roi in ROI_LIST)204                if not is_inside_any_roi:205                    continue206 207                process_tracking(track_id, center_pos, tracked_states, counted_ids, counts)208 209        t_infer_end = time.perf_counter()210 211        if not is_warmup:212            if gpu_session_start is None:213                gpu_session_start = t_infer_start   # first timed frame214            gpu_session_end = t_infer_end            # updated every timed frame215 216            decode_times.append(t_decode_end - t_decode_start)217            inference_times.append(t_infer_end - t_infer_start)218            frame_times.append((t_decode_end - t_decode_start) + (t_infer_end - t_infer_start))219            if use_cuda_events:220                gpu_times_ms.append(gpu_ms)221 222        if frame_index % 25 == 0:223            print(f"...processed {frame_index} frames")224 225    cap.release()226 227    # -----------------------------------------------------------------------228    # Report229    # -----------------------------------------------------------------------230 231    if not frame_times:232        print("No timed frames were collected (video too short?). Nothing to report.")233        return234 235    total_frames_in_video = frame_index236    timed_frames          = len(frame_times)237 238    frame_times_ms     = [t * 1000 for t in frame_times]239    decode_times_ms    = [t * 1000 for t in decode_times]240    inference_times_ms = [t * 1000 for t in inference_times]241 242    avg_ms        = sum(frame_times_ms) / len(frame_times_ms)243    min_ms        = min(frame_times_ms)244    max_ms        = max(frame_times_ms)245    avg_fps       = 1000 / avg_ms246    worst_case_fps = 1000 / max_ms247 248    avg_decode_ms = sum(decode_times_ms) / len(decode_times_ms)249    avg_infer_ms  = sum(inference_times_ms) / len(inference_times_ms)250    decode_share  = (avg_decode_ms / avg_ms) * 100251    infer_share   = (avg_infer_ms  / avg_ms) * 100252 253    target_ms_per_frame = 1000 / CAMERA_FPS254 255    # Total wall-clock time the GPU section ran (from first timed inference start256    # to last timed inference end) - includes CPU overhead between frames but257    # gives a realistic "how long did the whole video take on this GPU" figure.258    total_gpu_session_s = (gpu_session_end - gpu_session_start) if (gpu_session_start is not None) else 0.0259 260    # Sum of pure CUDA event times - true GPU compute time with no CPU noise.261    total_pure_gpu_ms = sum(gpu_times_ms) if gpu_times_ms else 0.0262 263    print("\n" + "=" * 50)264    print("BENCHMARK RESULTS")265    print("=" * 50)266    print(f"Device:                {DEVICE}")267    print(f"Resolution:            {native_w}x{native_h}")268    print(f"Total frames in video: {total_frames_in_video}")269    print(f"Warmup frames skipped: {WARMUP_FRAMES}")270    print(f"Frames timed:          {timed_frames}")271    print(f"Avg time/frame:        {avg_ms:.1f} ms  (total: decode + inference)")272    print(f"Min time/frame:        {min_ms:.1f} ms")273    print(f"Max time/frame:        {max_ms:.1f} ms")274    print(f"Avg throughput:        {avg_fps:.1f} fps")275    print(f"Worst-case throughput: {worst_case_fps:.1f} fps")276    print(f"Total video duration (GPU session, wall-clock): {total_gpu_session_s:.2f} s")277    print("-" * 50)278    print("DECODE vs INFERENCE BREAKDOWN")279    print(f"Avg decode time:       {avg_decode_ms:.1f} ms  ({decode_share:.0f}% of total) - CPU-bound (cap.read)")280    print(f"Avg inference time:    {avg_infer_ms:.1f} ms  ({infer_share:.0f}% of total) - mostly GPU-bound (model.track)")281 282    if decode_share >= 25:283        print("NOTE: decode time is a significant chunk of total time - the CPU on this "284              "machine may be limiting your results as much as the GPU is. Compare this "285              "share across different rentals before trusting GPU-to-GPU differences.")286    else:287        print("Decode time is a small share of the total - the GPU is the dominant factor "288              "in these results, so GPU-to-GPU comparisons on this metric should be fair.")289 290    if gpu_times_ms:291        avg_gpu_ms = sum(gpu_times_ms) / len(gpu_times_ms)292        min_gpu_ms = min(gpu_times_ms)293        max_gpu_ms = max(gpu_times_ms)294        gpu_fps    = 1000 / avg_gpu_ms295        print("-" * 50)296        print("PURE GPU TIME (torch.cuda.Event - excludes CPU dispatch/decode noise entirely)")297        print(f"Frames measured:       {len(gpu_times_ms)}")298        print(f"Avg GPU time/frame:    {avg_gpu_ms:.1f} ms  →  {gpu_fps:.1f} fps at this pace")299        print(f"Min GPU time/frame:    {min_gpu_ms:.1f} ms  (fastest single frame the GPU processed)")300        print(f"Max GPU time/frame:    {max_gpu_ms:.1f} ms  (slowest single frame the GPU processed)")301        print(f"Total pure GPU time:   {total_pure_gpu_ms:.1f} ms  ({total_pure_gpu_ms/1000:.2f} s)")302        print("This is the number to trust most when comparing GPUs directly - it reflects")303        print("only the GPU's own work, regardless of what CPU it happened to be paired with.")304 305    print("-" * 50)306    print(f"Camera requires:       {CAMERA_FPS} fps ({target_ms_per_frame:.1f} ms/frame budget)")307 308    if avg_ms <= target_ms_per_frame:309        headroom = (target_ms_per_frame / avg_ms - 1) * 100310        print(f"RESULT: Keeps up with real-time on average ({headroom:.0f}% headroom).")311    else:312        deficit = (avg_ms / target_ms_per_frame - 1) * 100313        print(f"RESULT: Falls behind real-time on average (needs {deficit:.0f}% more speed).")314 315    if max_ms > target_ms_per_frame:316        print("NOTE: worst-case frame time exceeds the real-time budget - expect occasional "317              "stutter/backlog even if the average looks fine.")318 319    print(f"\nFinal counts (for reference, not the point of this test): "320          f"in={counts['in']}, out={counts['out']}")321 322 323if __name__ == "__main__":324    main()