From b920077341b4730d260d18522dad1926f9e8e69f Mon Sep 17 00:00:00 2001 From: Kevin Turcios Date: Thu, 19 Mar 2026 01:01:17 -0600 Subject: [PATCH 1/4] mem: free intermediate arrays during YoloX inference MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Delete origin_img, img/ort_inputs, and output at the points where they become dead instead of letting them linger until function return. The biggest win is origin_img — the full-resolution numpy copy of the input PIL image — which stays alive through ONNX inference in the current code. Savings are proportional to image size. --- unstructured_inference/models/yolox.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/unstructured_inference/models/yolox.py b/unstructured_inference/models/yolox.py index f467857d..a48fb146 100644 --- a/unstructured_inference/models/yolox.py +++ b/unstructured_inference/models/yolox.py @@ -108,12 +108,15 @@ def image_processing( input_shape = (1024, 768) origin_img = np.array(image) img, ratio = preprocess(origin_img, input_shape) + del origin_img # Free full-size image array before ONNX inference session = self.model ort_inputs = {session.get_inputs()[0].name: img[None, :, :, :]} output = session.run(None, ort_inputs) + del img, ort_inputs # Free preprocessed inputs after inference # TODO(benjamin): check for p6 predictions = demo_postprocess(output[0], input_shape, p6=False)[0] + del output boxes = predictions[:, :4] scores = predictions[:, 4:5] * predictions[:, 5:] From 97049b5d0908e4b2713f3b159efd21bf7a934732 Mon Sep 17 00:00:00 2001 From: Kevin Turcios Date: Thu, 19 Mar 2026 01:26:12 -0600 Subject: [PATCH 2/4] Add changelog entry and version bump to 1.5.3 --- CHANGELOG.md | 5 +++++ unstructured_inference/__version__.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1999e866..a81e4f50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 1.6.1 + +### Enhancement +- Free intermediate arrays (`origin_img`, `img`, `ort_inputs`, `output`) and PIL pixel buffer at dead points during YoloX `image_processing()` to reduce peak memory during inference + ## 1.6.0 ### Fix diff --git a/unstructured_inference/__version__.py b/unstructured_inference/__version__.py index e10d078b..14b8f278 100644 --- a/unstructured_inference/__version__.py +++ b/unstructured_inference/__version__.py @@ -1 +1 @@ -__version__ = "1.6.0" # pragma: no cover +__version__ = "1.6.1" # pragma: no cover From db7767189d6b4f815ebcde461ff26618fb03fc69 Mon Sep 17 00:00:00 2001 From: Kevin Turcios Date: Sun, 22 Mar 2026 07:56:14 -0500 Subject: [PATCH 3/4] Also free PIL pixel buffer after np.array() conversion Call image.close() to release the PIL pixel buffer (~22 MiB for a typical page) before ONNX inference. PIL metadata (.width, .height, .format, .size) remains accessible after close(). --- unstructured_inference/models/yolox.py | 1 + 1 file changed, 1 insertion(+) diff --git a/unstructured_inference/models/yolox.py b/unstructured_inference/models/yolox.py index a48fb146..cdf1f405 100644 --- a/unstructured_inference/models/yolox.py +++ b/unstructured_inference/models/yolox.py @@ -107,6 +107,7 @@ def image_processing( # TODO (benjamin): check other shapes for inference input_shape = (1024, 768) origin_img = np.array(image) + image.close() img, ratio = preprocess(origin_img, input_shape) del origin_img # Free full-size image array before ONNX inference session = self.model From df0bab667705eab9ea42994c9d1efe87d790801b Mon Sep 17 00:00:00 2001 From: Kevin Turcios Date: Thu, 2 Apr 2026 10:44:57 -0500 Subject: [PATCH 4/4] chore: add YoloX image_processing benchmark for compare testing --- benchmarks/__init__.py | 0 benchmarks/test_benchmark_yolox.py | 63 ++++++++++++++++++++++++++++++ pyproject.toml | 3 ++ 3 files changed, 66 insertions(+) create mode 100644 benchmarks/__init__.py create mode 100644 benchmarks/test_benchmark_yolox.py diff --git a/benchmarks/__init__.py b/benchmarks/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/benchmarks/test_benchmark_yolox.py b/benchmarks/test_benchmark_yolox.py new file mode 100644 index 00000000..4074530c --- /dev/null +++ b/benchmarks/test_benchmark_yolox.py @@ -0,0 +1,63 @@ +"""Benchmark for YoloX image_processing() memory optimization. + +Uses a fake ONNX session to isolate the memory behavior of image_processing() +without requiring the real model weights. The fake session allocates a realistic +35 MiB workspace to simulate ONNX inference memory pressure. +""" + +import numpy as np +from PIL import Image as PILImage + +from unstructured_inference.models.yolox import UnstructuredYoloXModel + + +class _FakeInput: + def __init__(self) -> None: + self.name = "input" + + +class _FakeSession: + """Simulates an ONNX inference session with realistic memory allocation.""" + + def get_inputs(self): + return [_FakeInput()] + + def run(self, _names, _inputs): + workspace = np.empty((35 * 1024 * 1024,), dtype=np.uint8) # 35 MiB # noqa: F841 + # input_shape (1024,768), strides [8,16,32] → 128*96 + 64*48 + 32*24 = 16128 + return [np.random.randn(1, 16128, 16).astype(np.float32)] + + +def make_model() -> UnstructuredYoloXModel: + model = object.__new__(UnstructuredYoloXModel) + model.model = _FakeSession() + model.model_path = "yolox_fake" + model.layout_classes = { + 0: "Caption", + 1: "Footnote", + 2: "Formula", + 3: "List-item", + 4: "Page-footer", + 5: "Page-header", + 6: "Picture", + 7: "Section-header", + 8: "Table", + 9: "Text", + 10: "Title", + } + return model + + +# Letter-size page at 200 DPI — the default render resolution +def make_letter_200dpi() -> PILImage.Image: + return PILImage.fromarray(np.random.randint(0, 255, (2200, 1700, 3), dtype=np.uint8)) + + +def run_image_processing(): + model = make_model() + img = make_letter_200dpi() + return model.image_processing(img) + + +def test_benchmark_yolox_image_processing(benchmark): + benchmark(run_image_processing) diff --git a/pyproject.toml b/pyproject.toml index 68a5eb67..f50a2618 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -128,5 +128,8 @@ filterwarnings = [ "ignore::DeprecationWarning", ] +[tool.codeflash] +benchmarks-root = "benchmarks" + [tool.coverage.report] fail_under = 90