Skip to content

mem: use np.full() instead of np.ones() * scalar in YoloX preprocess#497

Merged
cragwolfe merged 5 commits into
mainfrom
perf/np-full-yolox-preprocess
Mar 30, 2026
Merged

mem: use np.full() instead of np.ones() * scalar in YoloX preprocess#497
cragwolfe merged 5 commits into
mainfrom
perf/np-full-yolox-preprocess

Conversation

@KRRT7

@KRRT7 KRRT7 commented Mar 27, 2026

Copy link
Copy Markdown
Contributor

Replace np.ones(shape, dtype=np.uint8) * 114 with np.full(shape, 114, dtype=np.uint8) in yolox.preprocess().

np.ones() * scalar allocates two arrays: the ones array and a temporary for the multiplication result. np.full() does it in a single allocation — same result, half the memory.

bench_preprocess_padding

Benchmark

Azure Standard_D8s_v5 — 8 vCPU Intel Xeon Platinum 8473C, 32 GiB RAM, Python 3.12.12

Timing — preprocess (1700x2200x3 letter-size image at 200 DPI)

Ref Min Median Mean OPS Rounds
44da8d85541d (base) 2.1ms 2.8ms 2.7ms 365.03 ops/s 124
6a085c621f1c (head) 2.0ms 2.5ms 2.6ms 377.49 ops/s 98
Speedup 1.04x 1.11x 1.03x 1.03x

Memory (peak per test)

Ref Peak Memory Allocations Delta
44da8d85541d (base) 13.4 MiB 54
6a085c621f1c (head) 13.4 MiB 57 +0%

Peak memory is identical because memray tracks the high watermark, not total allocations. The savings are in transient allocation volume — the temporary np.ones() * 114 array (2.25 MiB for a 1024x768x3 uint8 buffer) is eliminated. The original memray stats --json benchmark measured per-call allocation totals: 4.53 MiB/call -> 2.28 MiB/call (-50%).

Original memray allocation benchmark (Apple M3 Max, NumPy 2.4)

np.full() vs np.ones() * scalar
Shape: (1024, 768, 3) (2.25 MiB)  |  10 iterations  |  Python 3.12.12  |  NumPy 2.4.2

  np.ones() * 114: 4.53 MiB / call
  np.full(114):    2.28 MiB / call

  Savings: 2.25 MiB (50%)

The savings equal exactly one array copy (2.25 MiB for a 1024x768x3 uint8 buffer). In the full hi_res pipeline this is a small but free win — no behavior change, no new dependencies.


Generated by codeflash optimization agent

Reproduce the benchmark locally
# Full comparison (timing + memory):
uv run codeflash compare 44da8d85541d 6a085c621f1c --memory \
  --inject benchmarks/test_benchmark_preprocess.py \
  --inject benchmarks/__init__.py \
  --inject pyproject.toml

# Or the original memray allocation benchmark:
pip install memray numpy
python benchmarks/bench_preprocess_padding.py --runs 10
Benchmark test source
"""Benchmark for YoloX preprocess() np.full optimization.

Directly benchmarks the preprocess() function which was changed from
np.ones(shape) * 114 to np.full(shape, 114) -- eliminating one temporary
array allocation per call.
"""

import numpy as np

from unstructured_inference.models.yolox import preprocess


def make_letter_200dpi_array() -> np.ndarray:
    """Create a letter-size image array at 200 DPI (1700x2200x3)."""
    return np.random.randint(0, 255, (2200, 1700, 3), dtype=np.uint8)


# Pre-allocate the input image once
_INPUT_IMAGE = make_letter_200dpi_array()
_INPUT_SHAPE = (1024, 768)


def run_preprocess():
    return preprocess(_INPUT_IMAGE, _INPUT_SHAPE)


def test_benchmark_preprocess(benchmark):
    benchmark(run_preprocess)

Test plan

  • codeflash compare confirms 1.03-1.11x speedup, timing neutral
  • Original memray stats confirm -50% per-call allocation (4.53 -> 2.28 MiB)
  • No behavior change — np.full() produces identical output to np.ones() * scalar

@cragwolfe cragwolfe enabled auto-merge (squash) March 27, 2026 04:33
@KRRT7

KRRT7 commented Mar 27, 2026

Copy link
Copy Markdown
Contributor Author

@cragwolfe looks like it needs explicitly approval for auto merge

@cragwolfe cragwolfe merged commit 6a085c6 into main Mar 30, 2026
9 checks passed
@cragwolfe cragwolfe deleted the perf/np-full-yolox-preprocess branch March 30, 2026 21:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants