Skip to content

fix(mnn): use getNumpyData() instead of getData() for output extraction (~3x rec speedup) - #717

Open
todorangrg wants to merge 1 commit into
RapidAI:mainfrom
todorangrg:fix/mnn-engine-slow-output-extraction
Open

fix(mnn): use getNumpyData() instead of getData() for output extraction (~3x rec speedup)#717
todorangrg wants to merge 1 commit into
RapidAI:mainfrom
todorangrg:fix/mnn-engine-slow-output-extraction

Conversation

@todorangrg

Copy link
Copy Markdown

Problem

MNNInferSession.__call__ in rapidocr/inference_engine/mnn/main.py extracts
the session output via:

out_tensor = MNN.Tensor(out_shape, MNN.Halide_Type_Float, MNN.Tensor_DimensionType_Caffe)
output.copyToHostTensor(out_tensor)
return np.array(out_tensor.getData()).reshape(out_shape)

getData() marshals the tensor contents through a slow per-element Python
path rather than exposing the buffer directly. For a PP-OCRv6 medium
recognizer output of shape (1, ~230, 18710) (~17MB), this step alone took
~450ms on our test hardware (RK3588, OpenCL backend), compared to
~210ms for the actual GPU inference (runSession) that produced it — i.e.
result extraction was costing more than twice as much as inference itself.

On a real-world image with 39 detected text boxes, this pushed total
recognition time from an expected ~9s to ~22s.

Fix

Use the buffer-protocol-based getNumpyData() instead, which is already
exposed on the MNN Python binding:

result = out_tensor.getNumpyData()
result = np.array(result, copy=True).reshape(out_shape)

The explicit copy=True is required — getNumpyData() returns a view into
memory owned by out_tensor. Without an explicit copy, we observed
intermittent segfaults once out_tensor went out of scope / was reused
across calls with different session shapes. With the copy forced, this
was fully stable across repeated runs and varying input shapes/batches.

Results (RK3588, OpenCL backend, PP-OCRv6 medium recognizer)

before after
per-crop output extraction ~454ms ~10ms
per-crop total (__call__) ~690ms ~240ms
39-box real image, total rec time ~22s ~8.4s

The remaining ~10ms for extraction is a legitimate memcpy+reshape cost for
~17MB of data and is not further addressed here.

Testing

  • Verified numerical output is identical to the old getData() path via
    np.allclose on sampled outputs.
  • Verified stability across varying crop widths/batch sizes (no segfaults
    over repeated runs, including shape changes between calls).
  • Benchmarked before/after on a real multi-box image end-to-end.

Notes

This affects every user of the MNN engine backend, independent of model,
backend device (CPU/OpenCL/Vulkan), or hardware — it's a fixed cost in the
Python-level extraction path, not specific to our setup.

MNNInferSession.__call__ extracted session output via
np.array(out_tensor.getData()).reshape(out_shape), which marshals
tensor contents through a slow per-element Python path. For a
PP-OCRv6 medium recognizer output (~17MB), this cost ~454ms versus
~210ms for the actual GPU inference that produced it.

Switch to getNumpyData(), which exposes the buffer directly. An
explicit copy is required since getNumpyData() returns a view into
memory owned by out_tensor; without it, out_tensor going out of
scope or being reused across shape changes caused intermittent
segfaults.

On a 39-box real-world image, total recognition time dropped from
~22s to ~8.4s. Per-crop output extraction dropped from ~454ms to
~10ms.

Fixes intermittent segfault risk from unmanaged zero-copy tensor
lifetime as a side effect.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR optimizes MNN backend output extraction in MNNInferSession.__call__ by switching from per-element getData() to buffer-based getNumpyData(), reducing Python overhead during post-inference tensor conversion.

Changes:

  • Replace np.array(out_tensor.getData()) with np.array(out_tensor.getNumpyData(), copy=True) to accelerate output extraction.
  • Keep an explicit copy during conversion to avoid lifetime-related crashes when out_tensor goes out of scope or is reused.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread python/rapidocr/inference_engine/mnn/main.py
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.

3 participants