diff --git a/.claude/board/EPIPHANIES.md b/.claude/board/EPIPHANIES.md index c7a363a2..30c4bd9e 100644 --- a/.claude/board/EPIPHANIES.md +++ b/.claude/board/EPIPHANIES.md @@ -1,3 +1,16 @@ +## 2026-07-08 — E-OCR-PARALLEL-1 — page-chunk rayon closes the throughput gap losslessly: 3.34× on 4 cores, byte-identical to serial (the operator-pinned topology, not whole-doc lanes / not a global crop pool) +**Status:** FINDING (measured; tesseract-rs `ocr-pdf`, determinism-gated; PR forthcoming) + +**The topology (operator-ratified, sharpened from "lane per document" → "lane per document-CHUNK, mostly page"):** rayon over PAGE-CHUNK jobs — `Job = (doc_id, page_no)` + owned grey buffer. NOT whole-document lanes (a 200-page PDF would re-serialize on one thread — "ein Wal im Gartenteich") and NOT a global line/crop bucket pool (that reintroduces the serial gather/bucket/scatter phase + `(doc,line)` reassembly tags — "Tag-Konfetti"). Reassembly = `Vec` then `sort_by (doc_id, page_no)`: clean ownership, ordered, cheap. `OcrPipeline::ocr_pages_parallel` (feature `parallel`, default-on, rayon optional dep). + +**Two iron invariants, both enforced not asserted:** +- **NO NESTED PARALLELISM.** Outer rayon owns all CPU parallelism; the inner forward (ndarray int8 VNNI GEMM) stays single-threaded BY DESIGN — documented as an invariant in `parallel.rs`. Verified zero `Rc`/`RefCell`/`UnsafeCell`/`par_iter` in the recognizer→network→core chain, so `&OcrPipeline` is `Sync` and task-sharing is compile-safe (no `unsafe impl Send/Sync` anywhere). The classic nested-rayon thread-salat is structurally avoided. +- **DETERMINISM = byte-identical to serial.** Parallel output must equal the serial path exactly (each line seeds its own `TRand` from `sample_iteration`; `&self` compute; no shared mutable state). The `parallel_determinism` test (2/2: per-page byte equality + out-of-order-jobs-resort) and `par_bench`'s per-run self-check are the HARD gate. Nothing about the math changes — only scheduling. + +**Measured (`par_bench`, 10 committed pages, release, this 4-core box):** 1 thread **0.99×** (self-check ≈ serial), 2 threads **1.94×** (97% eff), 4 threads **3.34×** (84% eff) — near-linear, matching the pre-registered ~3.3–3.8× prediction. Determinism self-check green at every thread count (30/30, 10/10). This turns ~15 lines/s into ~50, closing the matched H2H line-gap (E-OCR-H2H-1) from **7.2× to ~2×** on 4 cores. The remainder is per-core throughput — width-bucketed batch-forward to fill VNNI/AMX tiles (`ndarray simd_soa`/"gridlake") — a LATER, **probe-gated** lever (truth-architect: only build it once benchmarks show underfilled tiles are the residual bottleneck). + +**Where Morton/2-bit/cascade do NOT apply (asked this session, ruled out with reasons):** those are SEARCH/codec superpowers (CAM-PQ/palette/bgz-tensor at 611M lookups/s + 95% skip) — the dense int8 LSTM forward has no candidate set to prune, tiny cache-resident matrices, and a VNNI layout Morton would fight; 2-bit breaks byte-parity. The forward's only lawful levers are parallelism (this) + SoA-batched tile-fill (gridlake, probe-gated). Branch `claude/happy-hamilton-0azlw4`. + ## 2026-07-08 — E-OCR-H2H-1 — the defensible head-to-head: recognition byte-parity confirmed on real photographed lines, the quality gap is INPUT CONDITIONING not the core, the matched speed gap is 7.3× (single-thread forward) with 4.6× leaner RAM **Status:** FINDING (measured over 2286 HierText line crops + 300-crop matched speed; harnesses committed tesseract-rs `a02cb96`, report `.claude/harvest/h2h-report.md`)