naina: Architecture¶
A high-performance, embeddable document-reading runtime. C++ core, thin bindings, runs on edge → server, one API.
naina began as a face and person understanding runtime. The engine described below is that work; v0.2 repurposed it for OCR, which the name always suited (naina means eyes). The face modules live on the
face-stackbranch.
North star¶
- Plug-and-play: one C ABI, bindings everywhere (Py, Node, Rust, Swift, Kotlin, WASM).
- Edge-first: must run on a Raspberry Pi 5 and a Jetson Nano. Server is a bonus tier.
- SOTA, swappable: model weights are not hardcoded. New SOTA paper drops → new manifest entry, no code change.
- Honest benchmarks: every model in the registry ships with reproducible accuracy + latency numbers per target. README shows both default (commercial-OK) and research (max accuracy) columns.
- Open source first-class: Apache-2.0 code, permissive default weights, opt-in research weights. Adoption > bragging rights, but we get both.
Layered design¶
┌─────────────────────────────────────────────────────────────┐
│ Language Bindings │
│ Python │ Node │ Rust │ Swift │ Kotlin │ C/C++ │ WASM │
└────────────────────────┬────────────────────────────────────┘
│ Stable C ABI (naina.h)
┌────────────────────────▼────────────────────────────────────┐
│ naina-core (C++20) │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Pipeline (zero-copy, async, batched DAG) │ │
│ │ Source → Decode → Preprocess → Infer → Postprocess │ │
│ └──────────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Task modules (independently shippable) │ │
│ │ TextDetect · TextRectify · TextRecognize │ │
│ │ LayoutDetect · DocAssemble │ │
│ └──────────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Backend abstraction (IBackend / ISession) │ │
│ │ ┌────────┬─────────┬─────┬─────┬───────┬──────────┐ │ │
│ │ │ ONNXrt │ OpenVINO│ NCNN│ MNN │CoreML │ TensorRT │ │ │
│ │ └────────┴─────────┴─────┴─────┴───────┴──────────┘ │ │
│ └──────────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ HAL, CPU SIMD │ GPU │ NPU │ Hailo │ Coral │ ANE │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
The four hard decisions, locked¶
| Decision | Choice | Reason |
|---|---|---|
| API contract | C ABI (naina.h) |
Everything else (Py/Rust/Swift/WASM) is glue. llama.cpp-grade portability. |
| Model exchange | ONNX as source of truth | Convert to NCNN/CoreML/TRT at install or build time. Single graph, many deployments. |
| Backend selection | Runtime, not compile-time | Probe what's available, pick best, fall back. Build flags toggle availability, not use. |
| Model loading | Manifest-driven | YAML registry: URL, hash, preprocessing, postprocessing, license, benchmarks. New SOTA = new YAML entry. |
Module modularity, why v1 isn't vaporware¶
Each task module is independently shippable:
v0.2 Text spotting: detect + rectify + recognise (shipped)
v0.3 Structure: layout + reading order + markdown
v0.4 Browser: WASM target + client-side PWA
v1.0 Guarantees: cross-binding parity, benchmarks, MCP
Same API. Modules light up over time. No big-bang release.
Two of the five modules, TextRectify and DocAssemble, touch no model at
all. They are deterministic pure functions. That is deliberate: it makes the
cross-binding identity guarantee provable with plain equality assertions rather
than float-tolerance comparisons.
The model registry pattern¶
Weights are tiered by device, not by licence. Every model naina ships is Apache-2.0, so a permissive-vs-research split would carry no information; what actually varies is size and the hardware it suits.
- id: text_recognize.tiny
task: text_recognize
tier: tiny # ~6 MB with det, browser, phone, Pi Zero
arch: pp_ocrv6_rec
license: Apache-2.0
files:
onnx:
url: "${release_base}/ppocrv6_tiny_rec.onnx"
source_url: "${hf}/PP-OCRv6_tiny_rec_onnx/resolve/main/inference.onnx"
sha256: "9ef676d6ed3c88256a2d92c640c44f25b0c40947e111b14b8be8f594091563e6"
bytes: 4462639
output:
type: ctc_logits
postprocess: { blank_index: 0, num_classes: 6906 }
Three properties of this that matter:
url points at naina's own release, not upstream. The sha256 already meant
an upstream swap failed closed rather than corrupting output, but it would
still have broken. Mirroring removes the third-party runtime dependency
entirely. source_url records provenance and is never fetched.
A request for a tier that lacks a task falls back to a larger one rather
than failing. layout_detect exists only at medium today, so tiny and
small layout requests resolve there.
Per-tier postprocessing is data, not code. num_classes is 6906 for
tiny and 18710 for small/medium, because their charsets genuinely differ.
Hardcoding one value shifts every decoded character.
Hot-path discipline (the engineering moat)¶
This is where naina earns the "super fast" claim, these are non-negotiable for the C++ implementation phase:
- Zero allocations per frame in steady state. Arena allocator per pipeline.
- No exceptions across ABI, status codes only. Internal C++ may throw but never cross the C boundary.
- Zero-copy pixels end-to-end: V4L2 / GStreamer / DMA-BUF → GPU/NPU directly. Host roundtrips are the enemy.
- Dynamic batching across streams. One camera = single-frame inference; eight cameras = batched.
- Async pipeline: detection on frame N runs concurrent with embedding on frame N-1.
- Quantization-aware: every model has FP32 / FP16 / INT8 variants in the registry. INT8 is default on edge.
- SIMD everywhere image ops live: NEON on ARM, AVX2/AVX-512 on x86, via a thin HAL.
Deployment matrix¶
| Target | Primary backend | Fallback |
|---|---|---|
| Raspberry Pi 5 (ARM Cortex-A76) | NCNN (INT8) | ONNX Runtime CPU |
| Jetson Orin Nano | TensorRT (FP16) | ONNX Runtime CUDA |
| Intel NUC / industrial PC | OpenVINO | ONNX Runtime CPU |
| Apple Silicon / iPhone | CoreML (ANE) | ONNX Runtime CoreML EP |
| Android phone | NCNN Vulkan | ONNX Runtime NNAPI |
| Hailo-8 / Coral Edge TPU | vendor SDK | ONNX Runtime CPU |
| x86 server w/ NVIDIA GPU | TensorRT | ONNX Runtime CUDA |
| Browser (stretch) | ONNX Runtime Web (WASM SIMD) |
Out of scope (by design, not by accident)¶
- Identity database / vector store, return embeddings, integrators choose Faiss/Milvus/hnswlib.
- UI / dashboards, naina is a runtime, not an app.
- Authentication / authorization, concerns above the library.
- Government ID linkage / "crime prediction", biometric ID against state databases is regulated (EU AI Act high-risk, India DPDP). Library stays identity-agnostic; that's an integrator concern with its own legal review.
Open questions to answer before v1.0¶
- Training pipeline. Wrap pretrained for v1, train own weights for v2?
- Backend priority. Which 2 backends ship first? My pick: ONNX Runtime (portability) + NCNN (edge perf).
- Tracking lib. Adapt ByteTrack ourselves, or wrap an existing C++ port?
- CI matrix. Which targets do we gate releases on?