Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/znippy-zoomies

.nornir/reasoning.md

znippy-zoomies — reasoning (f0, the whole)

Class: reasoning. The why, the trade-offs, and every design we rejected. The descriptive shape we ship is in design.md, which by rule mentions none of this. If you want to know "why not X?" — X is here.

Why this repo exists at all

These primitives were born inside katana-osm, the OSM ingestion engine. Two other repos wanted them: skade (for stree/gatling) and znippy (for the codecs and gatling). Leaving them in katana-osm would have forced both consumers to depend on a whole application repo, and every primitive fix would ride a katana-osm release — a release-cascade cycle. Extracting the shared parts into a standalone leaf crate breaks the cycle: skade and znippy both depend on znippy-zoomies, neither depends on the other, and a primitive ships on its own cadence. That is the whole reason the crate is a separate repo and not a module.

Why "no rayon" is a law, not a preference

rayon is mature and ubiquitous — so why ban it? Because its global thread pool is exactly what bit katana-osm: a planet-scale job silently serialized when heavy work landed on the wrong side of a fork-join barrier, and the shared pool made the failure invisible (it looked faster while broken). The gatling engines are pure std::thread::scope + one atomic cursor: no hidden global state, no barrier, and scoped threads can borrow caller stack data with no 'static/Arc ceremony. The law is enforced mechanically by tests/rayon_free_law.rs, so a future edit that reaches for rayon trips a red test instead of quietly falsifying the documented claim.

Workspace-wide now, not just root src/. The law once scoped itself to the crate's own src/ and carved out the codec members ljar / lzip-parallel, which shipped their own OnceLock<rayon::ThreadPool> singletons — the exact private-pool shape the law exists to forbid, quietly excused. That hole is closed: those two were ported onto gatling::gatling_forkjoin::gatling_for_each (rayon deps dropped), lgz/lbzip2 folded their private scoped-thread clones onto the shared engine, and tests/rayon_free_law.rs was widened to walk every workspace member's src/ + Cargo.toml (whole_workspace_is_rayon_free) and to ban private pools (rayon::ThreadPool + the pool builders). So the law now holds crate-wide. The only surviving rayon is transitive, as a dev-dependency of criterion for the benches; cargo tree -e no-dev -i rayon prints nothing, so it never reaches a shipped build. Full dated history of the regression and fix: gatling-reasoning.md.

Rejected: a generic STree<T> over the key width

The tidy version is one STree<T> parameterised over i64/u32. We rejected it (the owner's instruction was explicit: "klona 64 till 32, ge dig inte på att parametrisera 64/32 bitar"). Two hard reasons: (1) the branchless SIMD node compare uses a different intrinsic per width (_mm256_cmpgt_epi64 vs _mm256_cmpgt_epi32) — not a single monomorphisable op, and a generic defeats the const-folding the hot loop relies on; (2) the two paths' B and batch trade-offs were measured independently (B=8 for i64, B=16 for u32). Two readable concrete files beat one cleverly-generic one. The one parameter kept generic is const P, the pipeline depth — that is the MLP knob, not the forbidden key-width parametrization. Full comparison in stree-reasoning.md.

Rejected: approximate vector search (IVF) in vann

vann briefly shipped a RagnarsEmbIndex IVF (k-means + posting lists, nprobe-tunable recall). It was removed hours after it landed: nornir requires exact (100 %-recall) search, and IVF is approximate — it skips nprobe cells and can miss the true nearest. vann today is the exact flat VectorIndex oracle plus a provably-lossless int8/VNNI prefilter → f32 rerank (byte-identical to the flat scan). The IVF design history is in stree-reasoning.md (the "why they diverged" note); the durable idea it left behind is the shared batch-prefetch SIMD DNA, kept in the exact rerank path.

Rejected: widening the ring to fix the writer-bound codecs

The DEFLATE codecs saturated cores unevenly (lgz ~3.1, ljar ~3.5, lzip ~4.0 of 12; lbz2 ~10.4). The tempting fix was more slots — widen ring_slots so more chunks are in flight. Refuted: widening moved nothing, because the bottleneck is not pipeline width but the single ordered collector + writer at the tail. The decode fan-out is already parallel; one writer thread copying and writing gigabytes is the serial drain. The right fix removes the writer, not adds slots — the gatling::parwrite layer, where each worker writes its own disjoint output (independent files, or offset-pwrited regions of one pre-sized stream). See the writer story in gatling-reasoning.md.

The codec pipeline consolidation (done)

The four codec CLIs used to hand-roll the gatling + chunk_revolver pipeline (Reader→split→N workers→ordered collector→Writer) with raw mpsc wiring — the decode fan-out is now on the shared gatling_for_each engine (no rayon, no private pools). The writer tail is now lifted too: rather than one ordered writer, the parallel-writer parwrite layer lets workers write their own output, and every DEFLATE CLI's writer is wired onto it on master (cores_busy: ljar/lzip ~11.5/12, lgz ~7.8/12, lbz2 held ~10.4). The history is tracked in the årsstädning audit (arsstadning-history.md) and the chunk_revolver-unsorted.md plan.

Why the bake-off is HEAVY-only

There is no small/smoke mode for the bake-off (heavy-bench law). A per-core throughput claim measured on a toy corpus is a lie — cache effects, memory bandwidth, and core scaling only show at full size. So the harness runs one large corpus at all cores with max memory, records host CPU + cores + peak RSS per rival, and refuses to pretend a quick run is comparable. The numbers in benchmarks.md are only ever HEAVY runs.