[Feature]: MNN for Immich ML — OCR on Rockchip first; optional general backend? #30449
Replies: 3 comments 18 replies
|
It seems MNN isn't all that fast - it's slower at f16 than ANN at f32. I do actually have RKNPU working for OCR as part of the model optimization work and will have numbers for it soon. The basic idea is to have different shapes (736x736, 736x1472, 1472x736) as different models, using the best model for the input. This is actually how other backends will work as well since it turns out that they're basically all much more compute and memory efficient this way (yes, even accounting for the several models loaded). |
|
Hi @mertalev I did some more testing, and got inspired to try again RKNN for OCR based on your response. I managed to do some more graph surgery to the recognizer (ignore letterbox padding by feeding it a simple valid/pad mask and wiring that into CTC blanking plus the SVTR attention Softmaxes so pad columns can’t invent characters) and the results improved a fair amount using padding -- and model didn't get significantly slower. This might be useful if implementation goes the discrete inputs route. I still had to use quite a few shapes for reconginzer (17 I believe), and used 5 shapes for detector. Here are my thoughts / conclusions after doing more through and up-to-date tests:
Anyway, please find below some data: RK3588 OCR — MNN OpenCL vs RKNNIsolated single-model benches plus OpenCL under transcode (Immich / movie) contention. How to read metrics
Do not divide mean ms by ~3 for multi-worker; that mixes latency with throughput. Use Mpix/s for scaling. Highlights
Isolated detection — 736×1312 NHWC (0.966 Mpix)
Isolated recognition — H=48, W≈1600~0.077–0.078 Mpix/crop @ 48×1600/1632.
OpenCL during (tonemapping) transcode jobs
Streams (both stayed healthy — OCR took the hit, not the encode)
OCR slowdown
Import-relevant: +Immich transcode ≈ 2.3–2.4× OCR slowdown. Immich transcode + movie night ≈ 2.8–3.0× on OCR while both streams kept >4× realtime. |
|
hi @mertalev . Took me a while to come back as I went into introducing rknn dynamic input and ... this went sideways. I think now I have systematic enough data to share. Here is the branch: https://github.com/todorangrg/ml-models/tree/feat/ocr_improvements. Long story short: while the surgery that makes in-graph CTC be fast is cool, it does not work for dynamic shapes. RKNN compiler just chokes on it. There is however an improved formulation compared to the base branch, which increases performance substantially and works with dynamic shapes. How RKNN packs / reshapes is quite cryptic, and it needed a fair amount of tests to figure out optimal solution. Detector speed is just slightly improved, but RAM scaling with dynamic shapes is much more sane. For recognizer, I think performance improvement is substantial (and RAM scaling as well). I don't think in-graph CTC is worth it just because of the static shape / RAM issue. Note that I used only few bins, but with dynamic shapes we can add significantly more without impacting inference speed nor (much) memory - so we could do 1:1, 3:2, 2:3, 4:3, 3:4, 16:9, 9:16 and speed up inference (reduce padding) without much extra memory. Below is the (generated) report: OCR detector on RKNPU: reference vs. optimized graphRK3588, TL;DR
Setup
1. Speed and 3-core scaling
2. Memory (MB, 1-core and 3-core — N static binaries summed vs. 1 dynamic binary peak)Roughly equal savings across both models (unlike the recognizer, where the edge is backbone-size-dependent) — here it's just "3 binaries vs. 1," independent of backbone. Bottom line
OCR recognizer on RKNPU: CTC head placementRK3588, TL;DR
The three variantsSame fused ONNX (backbone + SVTR head). Differ only in where CTC decode (argmax over class axis) happens:
1. Speed and 3-core scaling (mean_ms; bar = 1-core, hatched = 3-core with its speedup labeled)
2. Memory (MB, 1-core and 3-core; N static binaries summed vs. 1 dynamic binary peak)Dynamic packing's edge is "shared backbone weights once, not N times" — a fixed MB saving that matters more on a bigger backbone. On v5_mobile (small backbone) it's mostly wiped out by per-worker pool overhead at 3 cores; on v6_medium it holds. Bottom line
|




Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
I have searched the existing feature requests, both open and closed, to make sure this is not a duplicate request.
The feature
I've been aiming to accelerate OCR for Rockchip boards for a while. As I expanded in OCR hw acceleration for Rockchip boards discussion, this is quite cumbersome as NPU (not only rockchip but in general?) cannot really deal with dynamic inputs. Fair amount of engineering needed, and still with corner-cases and reduced quality of output. I've stumbled across "mnn" inference library recently and was intrigued by it's OpenCL / Vulkan support, maturity, activity etc, so I decided to give it a go: it works really well! It worked so well on my rk3588 board, that I started to think if this could also be the "successor" of armnn, or another useful backend even for cpu inference on arm. Below you will find an (agent) structured RFC summarizing my thoughts. Have a look at the performance in the tables below, it's looking really good! (PPOCR-v6_med 0.5s detection, 7.5s recognition on a full A4 page of text). I even went ahead and checked if mnn conversion can benefit from the work you guys were are doing here: ml-models feat/optimized-model-graphs. With a few patches (upstreamable), also attention blocks and gelu get optimized!
PoC branch with working OCR on opencl for rknn builds is here: https://github.com/todorangrg/immich/tree/mnn_ocr_support
Ask
-rknnnow (RKNN stays for fixed CLIP/faces)?.mnn), not a permanent RapidOCR-only side path?Why now
Rockchip: SigLIP/faces already use RKNN. OCR is still ORT CPU. PPOCR is dynamic; RKNN is a poor fit (multi-input explosion, padding, accuracy corners). On RK3588 Mali, MNN OpenCL ≈ 6× ORT CPU on med PPOCR.
Stance to confirm: NPUs for quasi-static vision; GPU/CPU general runtimes for highly dynamic workloads (OCR).
ArmNN: unmaintained. MNN is maintained, one API for OpenCL / Vulkan / optimized ARM CPU. Pitch is coverage and choice — not beating RKNN/CUDA where they win. (Separate from optionally shipping ArmNN libs for dry-runs.)
ml-models: fused Immich ONNX + per-target adapts (ORT rewrites, RKNN compile). Natural place for ONNX → MNN later. Fused OCR stays dynamic (rec H=48, W symbolic) — RKNN still weak there.
PoC vs optimization path
This PoC branch works as-is: RapidOCR’s official
.mnnmodels, Immich runtime on-rknn(OpenCL default; Vulkan/CPU/off via env). No patches to MNN required. Kernel cache beside models + OpenCLnumThreadtuning are small and already in the branch; RapidOCR needs the upstreamgetNumpyDatafix for usable rec speed.Stock ONNX → MNNConvert does not fail on Immich vision/OCR graphs; it often just doesn’t fully optimize (e.g. ONNX
Attentionmust be decomposed; MNN--transformerFuseis LLM/mask-shaped, so SigLIP/PPOCR typically keep Softmax/MatMul). Leveraging ml-models fused ONNX + Immich prepare/fuse-oriented patches is optional upside (~15–20% if fuse lands) — not a blocker for A.Strawman
-rknn: OCR → MNN OpenCL; CLIP/faces stay RKNN. RapidOCR.mnnOK.-mnn/ ArmNN migration.Non-goals (v1)
.mnnor Attention-fuse before shipping AAttentionEvidence (RK3588, indicative; unfused MNN graphs)
OCR (A4 text page: ~39 boxes, ~48x1500pix boxes); RapidOCR +
getNumpyData:Static vision (familiar RKNN/ArmNN baselines — stay on RKNN on Rockchip):
Decisions wanted
-rknnOCR via MNN)?Related:
Platform
All reactions