Skip to content

⚡️ Speed up method RTDETRValidator._prepare_batch by 10% - #82

Open
codeflash-ai[bot] wants to merge 1 commit into
mainfrom
codeflash/optimize-RTDETRValidator._prepare_batch-miyiqbg3
Open

⚡️ Speed up method RTDETRValidator._prepare_batch by 10%#82
codeflash-ai[bot] wants to merge 1 commit into
mainfrom
codeflash/optimize-RTDETRValidator._prepare_batch-miyiqbg3

Conversation

@codeflash-ai

@codeflash-ai codeflash-ai Bot commented Dec 9, 2025

Copy link
Copy Markdown

📄 10% (0.10x) speedup for RTDETRValidator._prepare_batch in ultralytics/models/rtdetr/val.py

⏱️ Runtime : 3.30 milliseconds 3.01 milliseconds (best of 53 runs)

📝 Explanation and details

The optimized code achieves a 9% speedup through two key optimizations:

1. Arithmetic Operation Change in xywh2xyxy:

  • Original: wh = x[..., 2:] / 2 (division operation)
  • Optimized: wh2 = x[..., 2:] * 0.5 (multiplication operation)

This change leverages the fact that multiplication by 0.5 is faster than division by 2 in most CPU architectures. Division operations typically require more clock cycles than multiplication, and this optimization becomes significant when processing many bounding boxes in batch operations.

2. Individual Element Assignment in _prepare_batch:

  • Original: bbox[..., [0, 2]] *= ori_shape[1] and bbox[..., [1, 3]] *= ori_shape[0] (fancy indexing with lists)
  • Optimized: Four separate assignments: bbox[..., 0] *= ori_shape[1], bbox[..., 2] *= ori_shape[1], etc. (direct scalar indexing)

Fancy indexing with lists [0, 2] creates temporary arrays and involves more complex memory access patterns. Direct scalar indexing is more cache-friendly and avoids the overhead of creating intermediate index arrays.

Performance Impact by Test Case:

  • Small batches (single objects): 7-12% improvement - the arithmetic optimization dominates
  • Large batches (500+ objects): Up to 24% improvement - both optimizations compound as array operations scale
  • Empty batches: Minimal impact (2-3%) - overhead reductions are less significant

Why These Optimizations Work:
The optimizations target the computational bottlenecks identified in the line profiler: the division operation in xywh2xyxy (31.6% of function time) and the fancy indexing operations in _prepare_batch (26.7% combined). These functions are likely called frequently in object detection pipelines where bounding box transformations are performed on every detection, making even small per-operation improvements meaningful at scale.

Correctness verification report:

Test Status
⚙️ Existing Unit Tests 🔘 None Found
🌀 Generated Regression Tests 73 Passed
⏪ Replay Tests 🔘 None Found
🔎 Concolic Coverage Tests 🔘 None Found
📊 Tests Coverage 100.0%
🌀 Generated Regression Tests and Runtime
import torch
from ultralytics.models.rtdetr.val import RTDETRValidator

# ------------------ UNIT TESTS ------------------


# Helper for generating a batch dictionary
def make_batch(batch_idx, cls, bboxes, ori_shape, img, ratio_pad):
    return {
        "batch_idx": batch_idx,
        "cls": cls,
        "bboxes": bboxes,
        "ori_shape": ori_shape,
        "img": img,
        "ratio_pad": ratio_pad,
    }


# ------------------ BASIC TEST CASES ------------------


def test_single_object_single_image():
    # One image, one object
    validator = RTDETRValidator()
    si = 0
    batch = make_batch(
        batch_idx=torch.tensor([0]),  # one sample, index 0
        cls=torch.tensor([[1]]),  # class 1, shape [1, 1]
        bboxes=torch.tensor([[0.5, 0.5, 0.2, 0.4]]),  # center x/y, w/h in [0,1]
        ori_shape=torch.tensor([[100, 200]]),  # image shape (height, width)
        img=torch.zeros((1, 3, 100, 200)),  # batch size 1, 3 channels, 100x200
        ratio_pad=torch.tensor([[1.0, 1.0, 0.0, 0.0]]),
    )
    codeflash_output = validator._prepare_batch(si, batch)
    result = codeflash_output  # 143μs -> 128μs (11.8% faster)
    # Bounding box conversion
    # xywh: (0.5, 0.5, 0.2, 0.4) -> xyxy: (0.4, 0.3, 0.6, 0.7)
    # Multiply x by width (200), y by height (100)
    expected_bbox = torch.tensor([[0.4 * 200, 0.3 * 100, 0.6 * 200, 0.7 * 100]])


def test_multiple_objects_single_image():
    # One image, three objects
    validator = RTDETRValidator()
    si = 0
    batch = make_batch(
        batch_idx=torch.tensor([0, 0, 0]),
        cls=torch.tensor([[0], [1], [2]]),
        bboxes=torch.tensor([[0.1, 0.2, 0.2, 0.2], [0.5, 0.5, 0.4, 0.4], [0.8, 0.8, 0.1, 0.1]]),
        ori_shape=torch.tensor([[50, 50]]),
        img=torch.zeros((1, 3, 50, 50)),
        ratio_pad=torch.tensor([[1.0, 1.0, 0.0, 0.0]]),
    )
    codeflash_output = validator._prepare_batch(si, batch)
    result = codeflash_output  # 135μs -> 125μs (7.91% faster)
    # Check that bboxes are in xyxy and scaled
    for i in range(3):
        cx, cy, w, h = batch["bboxes"][i]
        x1 = (cx - w / 2) * 50
        y1 = (cy - h / 2) * 50
        x2 = (cx + w / 2) * 50
        y2 = (cy + h / 2) * 50


def test_multiple_images_varied_objects():
    # Two images, different number of objects per image
    validator = RTDETRValidator()
    si = 1
    batch = make_batch(
        batch_idx=torch.tensor([0, 1, 1, 1]),
        cls=torch.tensor([[1], [2], [3], [4]]),
        bboxes=torch.tensor(
            [
                [0.1, 0.1, 0.2, 0.2],  # img 0
                [0.5, 0.5, 0.5, 0.5],  # img 1
                [0.2, 0.2, 0.1, 0.1],  # img 1
                [0.8, 0.8, 0.1, 0.1],  # img 1
            ]
        ),
        ori_shape=torch.tensor([[100, 200], [50, 80]]),
        img=torch.zeros((2, 3, 50, 80)),
        ratio_pad=torch.tensor([[1.0, 1.0, 0.0, 0.0], [0.5, 0.5, 10.0, 10.0]]),
    )
    codeflash_output = validator._prepare_batch(si, batch)
    result = codeflash_output  # 136μs -> 124μs (9.92% faster)


# ------------------ EDGE TEST CASES ------------------


def test_no_objects_for_image():
    # Image with no objects (should return empty tensors)
    validator = RTDETRValidator()
    si = 0
    batch = make_batch(
        batch_idx=torch.tensor([1, 1, 1]),  # all objects belong to image 1, not 0
        cls=torch.tensor([[1], [2], [3]]),
        bboxes=torch.tensor(
            [
                [0.1, 0.1, 0.2, 0.2],
                [0.5, 0.5, 0.5, 0.5],
                [0.2, 0.2, 0.1, 0.1],
            ]
        ),
        ori_shape=torch.tensor([[100, 200], [50, 80]]),
        img=torch.zeros((2, 3, 50, 80)),
        ratio_pad=torch.tensor([[1.0, 1.0, 0.0, 0.0], [0.5, 0.5, 10.0, 10.0]]),
    )
    codeflash_output = validator._prepare_batch(si, batch)
    result = codeflash_output  # 53.3μs -> 52.2μs (2.08% faster)


def test_all_objects_for_one_image():
    # All objects for a single image, test with more than one image in batch
    validator = RTDETRValidator()
    si = 1
    batch = make_batch(
        batch_idx=torch.tensor([1, 1, 1, 1, 1]),
        cls=torch.tensor([[0], [1], [2], [3], [4]]),
        bboxes=torch.tensor(
            [
                [0.1, 0.2, 0.2, 0.2],
                [0.5, 0.5, 0.4, 0.4],
                [0.8, 0.8, 0.1, 0.1],
                [0.3, 0.7, 0.2, 0.2],
                [0.6, 0.2, 0.3, 0.3],
            ]
        ),
        ori_shape=torch.tensor([[100, 200], [300, 400]]),
        img=torch.zeros((2, 3, 300, 400)),
        ratio_pad=torch.tensor([[1.0, 1.0, 0.0, 0.0], [2.0, 2.0, 5.0, 5.0]]),
    )
    codeflash_output = validator._prepare_batch(si, batch)
    result = codeflash_output  # 143μs -> 135μs (5.37% faster)
    # Check scaling for one bbox
    cx, cy, w, h = batch["bboxes"][0]
    x1 = (cx - w / 2) * 400
    y1 = (cy - h / 2) * 300
    x2 = (cx + w / 2) * 400
    y2 = (cy + h / 2) * 300


def test_empty_batch():
    # Empty batch (no objects at all)
    validator = RTDETRValidator()
    si = 0
    batch = make_batch(
        batch_idx=torch.empty((0,), dtype=torch.long),
        cls=torch.empty((0, 1), dtype=torch.long),
        bboxes=torch.empty((0, 4)),
        ori_shape=torch.tensor([[100, 200]]),
        img=torch.zeros((1, 3, 100, 200)),
        ratio_pad=torch.tensor([[1.0, 1.0, 0.0, 0.0]]),
    )
    codeflash_output = validator._prepare_batch(si, batch)
    result = codeflash_output  # 49.3μs -> 50.7μs (2.81% slower)


def test_non_contiguous_indices():
    # Objects for images with non-contiguous indices
    validator = RTDETRValidator()
    si = 2
    batch = make_batch(
        batch_idx=torch.tensor([0, 2, 4, 2, 2]),
        cls=torch.tensor([[1], [2], [3], [4], [5]]),
        bboxes=torch.tensor(
            [
                [0.1, 0.1, 0.2, 0.2],  # img 0
                [0.5, 0.5, 0.5, 0.5],  # img 2
                [0.2, 0.2, 0.1, 0.1],  # img 4
                [0.8, 0.8, 0.1, 0.1],  # img 2
                [0.3, 0.3, 0.2, 0.2],  # img 2
            ]
        ),
        ori_shape=torch.tensor([[100, 200], [50, 80], [30, 40], [60, 70], [90, 120]]),
        img=torch.zeros((5, 3, 30, 40)),
        ratio_pad=torch.tensor([[1.0, 1.0, 0.0, 0.0]] * 5),
    )
    codeflash_output = validator._prepare_batch(si, batch)
    result = codeflash_output  # 140μs -> 127μs (9.86% faster)
    # Check scaling for one bbox
    cx, cy, w, h = batch["bboxes"][1]
    x1 = (cx - w / 2) * 40
    y1 = (cy - h / 2) * 30
    x2 = (cx + w / 2) * 40
    y2 = (cy + h / 2) * 30


def test_single_object_multiple_images():
    # One object per image, test correct selection
    validator = RTDETRValidator()
    for si in range(3):
        batch = make_batch(
            batch_idx=torch.tensor([0, 1, 2]),
            cls=torch.tensor([[0], [1], [2]]),
            bboxes=torch.tensor([[0.1, 0.2, 0.2, 0.2], [0.5, 0.5, 0.4, 0.4], [0.8, 0.8, 0.1, 0.1]]),
            ori_shape=torch.tensor([[10, 20], [30, 40], [50, 60]]),
            img=torch.zeros((3, 3, 50, 60)),
            ratio_pad=torch.tensor([[1.0, 1.0, 0.0, 0.0]] * 3),
        )
        codeflash_output = validator._prepare_batch(si, batch)
        result = codeflash_output  # 260μs -> 239μs (8.69% faster)
        cx, cy, w, h = batch["bboxes"][si]
        x1 = (cx - w / 2) * batch["ori_shape"][si, 1].item()
        y1 = (cy - h / 2) * batch["ori_shape"][si, 0].item()
        x2 = (cx + w / 2) * batch["ori_shape"][si, 1].item()
        y2 = (cy + h / 2) * batch["ori_shape"][si, 0].item()


# ------------------ LARGE SCALE TEST CASES ------------------


def test_large_batch_many_objects():
    # Test with a large batch and many objects (but <100MB)
    validator = RTDETRValidator()
    num_images = 20
    num_objects = 800  # total objects
    # Randomly assign objects to images
    torch.manual_seed(42)
    batch_idx = torch.randint(0, num_images, (num_objects,))
    cls = torch.randint(0, 10, (num_objects, 1))
    bboxes = torch.rand((num_objects, 4))
    # Ensure w,h are not too small
    bboxes[:, 2:] = bboxes[:, 2:] * 0.5 + 0.05
    ori_shape = torch.randint(32, 256, (num_images, 2))
    img = torch.zeros((num_images, 3, 128, 128))
    ratio_pad = torch.rand((num_images, 4))
    for si in range(num_images):
        batch = make_batch(batch_idx, cls, bboxes, ori_shape, img, ratio_pad)
        codeflash_output = validator._prepare_batch(si, batch)
        result = codeflash_output  # 1.29ms -> 1.18ms (9.36% faster)
        # All objects for this image
        mask = batch_idx == si
        expected_cls = cls[mask].squeeze(-1)
        expected_bbox = bboxes[mask]
        # Check scaling for the first object if exists
        if expected_bbox.shape[0] > 0:
            cx, cy, w, h = expected_bbox[0]
            x1 = (cx - w / 2) * ori_shape[si, 1].item()
            y1 = (cy - h / 2) * ori_shape[si, 0].item()
            x2 = (cx + w / 2) * ori_shape[si, 1].item()
            y2 = (cy + w / 2) * ori_shape[si, 0].item()  # typo fix: should be h/2
            y2 = (cy + h / 2) * ori_shape[si, 0].item()


def test_large_image_size():
    # Test with a single image but large spatial size (but <100MB)
    validator = RTDETRValidator()
    si = 0
    img_h, img_w = 512, 512  # 3*512*512*4 = 3MB per image
    batch = make_batch(
        batch_idx=torch.tensor([0, 0, 0, 0, 0]),
        cls=torch.tensor([[1], [2], [3], [4], [5]]),
        bboxes=torch.rand((5, 4)),
        ori_shape=torch.tensor([[img_h, img_w]]),
        img=torch.zeros((1, 3, img_h, img_w)),
        ratio_pad=torch.tensor([[1.0, 1.0, 0.0, 0.0]]),
    )
    codeflash_output = validator._prepare_batch(si, batch)
    result = codeflash_output  # 143μs -> 132μs (8.25% faster)


def test_large_number_of_images():
    # Test with many images, each with a single object
    validator = RTDETRValidator()
    num_images = 100
    batch = make_batch(
        batch_idx=torch.arange(num_images),
        cls=torch.arange(num_images).reshape(-1, 1),
        bboxes=torch.rand((num_images, 4)),
        ori_shape=torch.randint(32, 256, (num_images, 2)),
        img=torch.zeros((num_images, 3, 64, 64)),
        ratio_pad=torch.rand((num_images, 4)),
    )
    for si in [0, num_images // 2, num_images - 1]:
        codeflash_output = validator._prepare_batch(si, batch)
        result = codeflash_output  # 269μs -> 239μs (12.8% faster)
        cx, cy, w, h = batch["bboxes"][si]
        x1 = (cx - w / 2) * batch["ori_shape"][si, 1].item()
        y1 = (cy - h / 2) * batch["ori_shape"][si, 0].item()
        x2 = (cx + w / 2) * batch["ori_shape"][si, 1].item()
        y2 = (cy + h / 2) * batch["ori_shape"][si, 0].item()


# codeflash_output is used to check that the output of the original code is the same as that of the optimized code.
import pytest
import torch
from ultralytics.models.rtdetr.val import RTDETRValidator

# =========================
# Unit Tests for _prepare_batch
# =========================

# ----------- BASIC TEST CASES -----------


def test_basic_single_object():
    # Single image, single object
    validator = RTDETRValidator()
    batch = {
        "batch_idx": torch.tensor([0]),
        "cls": torch.tensor([[1]]),  # class 1
        "bboxes": torch.tensor([[0.5, 0.5, 0.2, 0.2]]),  # center x/y, width/height (normalized)
        "ori_shape": [(640, 480)],  # height, width
        "img": torch.zeros((1, 3, 640, 480)),  # batch, channels, height, width
        "ratio_pad": [(1.0, (0, 0))],
    }
    codeflash_output = validator._prepare_batch(0, batch)
    result = codeflash_output
    # Check bbox conversion
    expected_bbox = torch.tensor([[0.5, 0.5, 0.2, 0.2]])
    expected_bbox = xywh2xyxy(expected_bbox)
    expected_bbox[..., [0, 2]] *= 480
    expected_bbox[..., [1, 3]] *= 640


def test_basic_multiple_objects():
    # Single image, multiple objects
    validator = RTDETRValidator()
    batch = {
        "batch_idx": torch.tensor([0, 0, 0]),
        "cls": torch.tensor([[0], [1], [2]]),
        "bboxes": torch.tensor([[0.1, 0.1, 0.2, 0.2], [0.5, 0.5, 0.4, 0.4], [0.8, 0.8, 0.1, 0.1]]),
        "ori_shape": [(640, 480)],
        "img": torch.zeros((1, 3, 640, 480)),
        "ratio_pad": [(1.0, (0, 0))],
    }
    codeflash_output = validator._prepare_batch(0, batch)
    result = codeflash_output
    expected_bbox = xywh2xyxy(batch["bboxes"])
    expected_bbox[..., [0, 2]] *= 480
    expected_bbox[..., [1, 3]] *= 640


def test_basic_multiple_images():
    # Two images, objects in both
    validator = RTDETRValidator()
    batch = {
        "batch_idx": torch.tensor([0, 1, 1]),
        "cls": torch.tensor([[1], [2], [3]]),
        "bboxes": torch.tensor([[0.2, 0.3, 0.1, 0.1], [0.7, 0.8, 0.2, 0.2], [0.4, 0.5, 0.3, 0.3]]),
        "ori_shape": [(640, 480), (800, 600)],
        "img": torch.zeros((2, 3, 800, 600)),
        "ratio_pad": [(1.0, (0, 0)), (1.2, (10, 20))],
    }
    # Image 0
    codeflash_output = validator._prepare_batch(0, batch)
    result0 = codeflash_output
    expected_bbox0 = xywh2xyxy(torch.tensor([[0.2, 0.3, 0.1, 0.1]]))
    expected_bbox0[..., [0, 2]] *= 480
    expected_bbox0[..., [1, 3]] *= 640
    # Image 1
    codeflash_output = validator._prepare_batch(1, batch)
    result1 = codeflash_output
    expected_bbox1 = xywh2xyxy(torch.tensor([[0.7, 0.8, 0.2, 0.2], [0.4, 0.5, 0.3, 0.3]]))
    expected_bbox1[..., [0, 2]] *= 600
    expected_bbox1[..., [1, 3]] *= 800


# ----------- EDGE TEST CASES -----------


def test_empty_objects():
    # Single image, no objects
    validator = RTDETRValidator()
    batch = {
        "batch_idx": torch.tensor([], dtype=torch.int64),
        "cls": torch.empty((0, 1), dtype=torch.int64),
        "bboxes": torch.empty((0, 4), dtype=torch.float32),
        "ori_shape": [(640, 480)],
        "img": torch.zeros((1, 3, 640, 480)),
        "ratio_pad": [(1.0, (0, 0))],
    }
    codeflash_output = validator._prepare_batch(0, batch)
    result = codeflash_output  # 59.4μs -> 58.3μs (1.91% faster)


def test_single_object_squeezed():
    # Single image, single object, cls shape already squeezed
    validator = RTDETRValidator()
    batch = {
        "batch_idx": torch.tensor([0]),
        "cls": torch.tensor([1]),  # already squeezed
        "bboxes": torch.tensor([[0.5, 0.5, 0.2, 0.2]]),
        "ori_shape": [(640, 480)],
        "img": torch.zeros((1, 3, 640, 480)),
        "ratio_pad": [(1.0, (0, 0))],
    }
    # Should handle this gracefully (squeeze(-1) on 1d tensor is no-op)
    codeflash_output = validator._prepare_batch(0, batch)
    result = codeflash_output


def test_non_square_image():
    # Non-square image, check bbox scaling
    validator = RTDETRValidator()
    batch = {
        "batch_idx": torch.tensor([0, 0]),
        "cls": torch.tensor([[2], [3]]),
        "bboxes": torch.tensor([[0.2, 0.8, 0.4, 0.1], [0.7, 0.3, 0.1, 0.4]]),
        "ori_shape": [(123, 456)],
        "img": torch.zeros((1, 3, 123, 456)),
        "ratio_pad": [(0.9, (5, 7))],
    }
    codeflash_output = validator._prepare_batch(0, batch)
    result = codeflash_output
    expected_bbox = xywh2xyxy(batch["bboxes"])
    expected_bbox[..., [0, 2]] *= 456
    expected_bbox[..., [1, 3]] *= 123


def test_multiple_batch_indices():
    # Multiple images, objects spread across images
    validator = RTDETRValidator()
    batch = {
        "batch_idx": torch.tensor([0, 1, 2, 1, 0]),
        "cls": torch.tensor([[0], [1], [2], [3], [4]]),
        "bboxes": torch.tensor(
            [
                [0.1, 0.1, 0.2, 0.2],
                [0.5, 0.5, 0.4, 0.4],
                [0.8, 0.8, 0.1, 0.1],
                [0.3, 0.7, 0.2, 0.2],
                [0.6, 0.4, 0.1, 0.3],
            ]
        ),
        "ori_shape": [(100, 200), (300, 400), (500, 600)],
        "img": torch.zeros((3, 3, 500, 600)),
        "ratio_pad": [(1.0, (0, 0)), (1.1, (5, 6)), (1.2, (7, 8))],
    }
    # Image 1
    codeflash_output = validator._prepare_batch(1, batch)
    result1 = codeflash_output
    idx = batch["batch_idx"] == 1
    expected_cls = batch["cls"][idx].squeeze(-1)
    expected_bbox = xywh2xyxy(batch["bboxes"][idx])
    expected_bbox[..., [0, 2]] *= 400
    expected_bbox[..., [1, 3]] *= 300


def test_zero_sized_bbox():
    # Object with zero width/height
    validator = RTDETRValidator()
    batch = {
        "batch_idx": torch.tensor([0]),
        "cls": torch.tensor([[1]]),
        "bboxes": torch.tensor([[0.5, 0.5, 0.0, 0.0]]),
        "ori_shape": [(640, 480)],
        "img": torch.zeros((1, 3, 640, 480)),
        "ratio_pad": [(1.0, (0, 0))],
    }
    codeflash_output = validator._prepare_batch(0, batch)
    result = codeflash_output
    # The bbox should be a single point
    expected_bbox = xywh2xyxy(batch["bboxes"])
    expected_bbox[..., [0, 2]] *= 480
    expected_bbox[..., [1, 3]] *= 640


def test_float_and_int_types():
    # Check robustness to float/int types in ori_shape and ratio_pad
    validator = RTDETRValidator()
    batch = {
        "batch_idx": torch.tensor([0]),
        "cls": torch.tensor([[1]]),
        "bboxes": torch.tensor([[0.5, 0.5, 0.2, 0.2]]),
        "ori_shape": [(640.0, 480)],  # float height, int width
        "img": torch.zeros((1, 3, 640, 480)),
        "ratio_pad": [(1, (0, 0))],  # int scale
    }
    codeflash_output = validator._prepare_batch(0, batch)
    result = codeflash_output
    expected_bbox = xywh2xyxy(batch["bboxes"])
    expected_bbox[..., [0, 2]] *= 480
    expected_bbox[..., [1, 3]] *= 640.0


def test_empty_batch():
    # All batch fields are empty
    validator = RTDETRValidator()
    batch = {
        "batch_idx": torch.tensor([], dtype=torch.int64),
        "cls": torch.empty((0, 1), dtype=torch.int64),
        "bboxes": torch.empty((0, 4), dtype=torch.float32),
        "ori_shape": [],
        "img": torch.empty((0, 3, 10, 10)),
        "ratio_pad": [],
    }
    # Should raise IndexError for ori_shape/ratio_pad/imgsz
    with pytest.raises(IndexError):
        validator._prepare_batch(0, batch)  # 44.3μs -> 44.3μs (0.117% faster)


def test_invalid_bbox_shape():
    # Bboxes with wrong shape
    validator = RTDETRValidator()
    batch = {
        "batch_idx": torch.tensor([0]),
        "cls": torch.tensor([[1]]),
        "bboxes": torch.tensor([[0.5, 0.5, 0.2]]),  # only 3 values
        "ori_shape": [(640, 480)],
        "img": torch.zeros((1, 3, 640, 480)),
        "ratio_pad": [(1.0, (0, 0))],
    }
    with pytest.raises(AssertionError):
        validator._prepare_batch(0, batch)  # 63.5μs -> 61.1μs (3.94% faster)


# ----------- LARGE SCALE TEST CASES -----------


def test_large_batch():
    # Large batch, many objects
    validator = RTDETRValidator()
    num_images = 10
    num_objs = 500
    batch = {
        "batch_idx": torch.randint(0, num_images, (num_objs,)),
        "cls": torch.randint(0, 80, (num_objs, 1)),  # 80 classes
        "bboxes": torch.rand((num_objs, 4)),  # normalized bbox
        "ori_shape": [(640, 480)] * num_images,
        "img": torch.zeros((num_images, 3, 640, 480)),
        "ratio_pad": [(1.0, (0, 0))] * num_images,
    }
    # Pick a random image index
    si = 7
    codeflash_output = validator._prepare_batch(si, batch)
    result = codeflash_output  # 166μs -> 151μs (9.78% faster)
    # Check that all batch_idx == si
    idx = batch["batch_idx"] == si


def test_large_objects_per_image():
    # Large number of objects in a single image
    validator = RTDETRValidator()
    num_objs = 999
    batch = {
        "batch_idx": torch.zeros(num_objs, dtype=torch.int64),
        "cls": torch.arange(num_objs).unsqueeze(-1),
        "bboxes": torch.rand((num_objs, 4)),
        "ori_shape": [(640, 480)],
        "img": torch.zeros((1, 3, 640, 480)),
        "ratio_pad": [(1.0, (0, 0))],
    }
    codeflash_output = validator._prepare_batch(0, batch)
    result = codeflash_output  # 200μs -> 160μs (24.6% faster)


def test_large_image_size():
    # Large image size, but <100MB tensor
    validator = RTDETRValidator()
    batch = {
        "batch_idx": torch.tensor([0, 0, 0]),
        "cls": torch.tensor([[1], [2], [3]]),
        "bboxes": torch.tensor([[0.5, 0.5, 0.2, 0.2], [0.2, 0.8, 0.1, 0.1], [0.7, 0.3, 0.4, 0.4]]),
        "ori_shape": [(1024, 768)],
        "img": torch.zeros((1, 3, 1024, 768)),
        "ratio_pad": [(1.0, (0, 0))],
    }
    codeflash_output = validator._prepare_batch(0, batch)
    result = codeflash_output
    expected_bbox = xywh2xyxy(batch["bboxes"])
    expected_bbox[..., [0, 2]] *= 768
    expected_bbox[..., [1, 3]] *= 1024

To edit these changes git checkout codeflash/optimize-RTDETRValidator._prepare_batch-miyiqbg3 and push.

Codeflash Static Badge

The optimized code achieves a 9% speedup through two key optimizations:

**1. Arithmetic Operation Change in `xywh2xyxy`:**
- **Original:** `wh = x[..., 2:] / 2` (division operation)
- **Optimized:** `wh2 = x[..., 2:] * 0.5` (multiplication operation)

This change leverages the fact that multiplication by 0.5 is faster than division by 2 in most CPU architectures. Division operations typically require more clock cycles than multiplication, and this optimization becomes significant when processing many bounding boxes in batch operations.

**2. Individual Element Assignment in `_prepare_batch`:**
- **Original:** `bbox[..., [0, 2]] *= ori_shape[1]` and `bbox[..., [1, 3]] *= ori_shape[0]` (fancy indexing with lists)
- **Optimized:** Four separate assignments: `bbox[..., 0] *= ori_shape[1]`, `bbox[..., 2] *= ori_shape[1]`, etc. (direct scalar indexing)

Fancy indexing with lists `[0, 2]` creates temporary arrays and involves more complex memory access patterns. Direct scalar indexing is more cache-friendly and avoids the overhead of creating intermediate index arrays.

**Performance Impact by Test Case:**
- **Small batches** (single objects): 7-12% improvement - the arithmetic optimization dominates
- **Large batches** (500+ objects): Up to 24% improvement - both optimizations compound as array operations scale
- **Empty batches**: Minimal impact (2-3%) - overhead reductions are less significant

**Why These Optimizations Work:**
The optimizations target the computational bottlenecks identified in the line profiler: the division operation in `xywh2xyxy` (31.6% of function time) and the fancy indexing operations in `_prepare_batch` (26.7% combined). These functions are likely called frequently in object detection pipelines where bounding box transformations are performed on every detection, making even small per-operation improvements meaningful at scale.
@codeflash-ai
codeflash-ai Bot requested a review from mashraf-222 December 9, 2025 11:50
@codeflash-ai codeflash-ai Bot added ⚡️ codeflash Optimization PR opened by Codeflash AI 🎯 Quality: Medium Optimization Quality according to Codeflash labels Dec 9, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

⚡️ codeflash Optimization PR opened by Codeflash AI 🎯 Quality: Medium Optimization Quality according to Codeflash

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants