Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .claude/board/STATUS_BOARD.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ Plan: `.claude/plans/sql-polyfill-r2il-v1.md`.
| **D-SQLP-0** | Plan + board rows | Shipped (#1476, #1478) | — |
| **D-SQLP-1** | `FoldDialect` moved into a `lance-graph-r2il` crate | Shipped (#1479; workspace-excluded, own CI step; probe 40/40 unchanged, crate 7 tests) | existing `row_bridge.rs` tests unchanged and green; one arity table |
| **D-TELEPORT-0** | Source-restricted fold probe: scan/join vs gated gather vs extent; range intersection; sorted-lane crossing point | Shipped (#1484, probe) | every arm equals the oracle on clustered and scattered layouts |
| **D-TELEPORT-1** | quack `fold_ranges`: `Range ∧ Range` on one column lowers to the intersected range (empty stays a valid empty range; different columns, `OR`/`NOT` and `lo > hi` left alone) | In PR | one op and `Lowering::Range` for COUNT; 400 seeded conjunctions equal the oracle under both lowerings; disable runs red |
| **D-TELEPORT-1** | quack `fold_ranges`: `Range ∧ Range` on one column lowers to the intersected range (empty stays a valid empty range; different columns, `OR`/`NOT` and `lo > hi` left alone) | Shipped (#1487) | one op and `Lowering::Range` for COUNT; 400 seeded conjunctions equal the oracle under both lowerings; disable runs red |
| **D-TELEPORT-2** | Lance 13 source-pruning probe: full scan vs row-range prefilter vs zone map vs B-tree vs index-only COUNT, sorted/partial/periodic/shuffled, with and without NULLs | In PR (probe) | every arm equals the oracle; index COUNT reads no data columns and stays exact after deletes |
| **D-PHOTO-0** | 64 resident populations × 1 Mi addresses: bit-sliced planes vs extents vs u64 signatures vs signature classes; update and spill costs | In PR (probe) | canonical + 20 random expressions equal the oracle under COUNT, Any, SUM and Rows in four regimes |
| **D-SQLP-2** | First procedures (`filter_lt`, `sum`, `group_sum`, `margin`) as inlined R2IL call sequences (`lance-graph-r2il::procs`); `FoldDialect` wires `IntOr` and adds a fused mode (mask-risc `fuse`) beside the in-place mode | Shipped (#1484; 8 tests, six disable runs red; probe suite unchanged) | scalar-reference parity; disable runs red; both modes keep the same rows, fused holds ternlogs |
| **D-SQLP-3** | Benchmark: direct ndarray vs hand-built `Program` vs interpreted procedure | Queued | interpreted ≤ 1.05× hand-built at 65,536 and 1M rows (pre-registered) |
| **D-SQLP-4** | Build-time generator: declared procedure → Rust with const-generic opcodes | Queued, gated on D-SQLP-3 | ≤ 1.05× direct ndarray; no interpreter dispatch in the hot loop |
Expand Down
79 changes: 79 additions & 0 deletions .claude/board/entries/2026-10-11-lance-source-probe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# 2026-10-11 — Lance 13 source pruning before the fold (D-TELEPORT-2)

**Status:** MEASURED. Probe only, no production change.
`crates/lance-graph-benches/examples/lance_source_probe.rs`.

```text
CARGO_PROFILE_RELEASE_DEBUG=0 cargo run --release -p lance-graph-benches --example lance_source_probe
```

The question: can Lance 13's existing scanner and index paths prune source
reads before the Quack/mask-risc fold, without a row-sized intermediate
population, and with identical answers? Every arm is asserted against the
oracle COUNT and SUM. No new executor, opcode, IR or OGAR code.

Setup: 1,048,576 rows, fragments of 131,072, columns `x`, `xz`, `xb`
(identical i32 values) and `v`; zone map on `xz`, B-tree on `xb`. Cold =
fresh `Dataset::open` after `drop_caches` (works in this container); warm =
best of 3. Bytes and iops come from Lance's `ExecutionSummaryCounts`.
Host: 4 shared vCPUs; only order-of-magnitude differences are read.

## Arms

- **A** full scan of `x`, `v`; mask-risc `x < t` under the validity plane, then COUNT and masked SUM over the borrowed Arrow buffers.
- **C** sorted, no NULLs only: `with_row_addr_prefilter` over the prefix `[0, k)`, then a Range-mask SUM. Uses the fixture's known order.
- **Dz** `filter("xz < t")`, project `v`; no recheck in mask-risc.
- **Db** `filter("xb < t")`, project `v`.
- **N** `filter("xb < t")`, empty projection, COUNT only.

## Results (bytes read / warm ms)

| layout | sel | A | C | Dz | Db | N |
|---|---|---|---|---|---|---|
| sorted | 0.5 | 3.87 MB / 7.5 | 664 KB / 2.1 | 1.85 MB / 5.5 | 664 KB / 8.6 | 0 / 5.7 |
| sorted | 0.01 | 3.83 MB / 5.9 | 14 KB / 0.8 | 48 KB / 2.1 | 14 KB / 1.1 | 0 / 0.7 |
| sorted | 0.0001 | 3.83 MB / 6.5 | 1.3 KB / 0.7 | 23 KB / 1.7 | 1.3 KB / 1.1 | 0 / 0.6 |
| partial (shuffled within 64 Ki) | 0.5 | 3.84 MB / 8.1 | — | 1.86 MB / 4.1 | 664 KB / 22.6 | 0 / 14.2 |
| periodic (16 ramps) | 0.01 | 3.83 MB / 5.6 | — | 432 KB / 3.4 | 21 KB / 3.0 | 0 / 1.8 |
| shuffled | 0.5 | 3.96 MB / 9.1 | — | 3.96 MB / 9.8 | 1.33 MB / 80.4 | 0 / 35.4 |
| shuffled | 0.0001 | 3.96 MB / 5.3 | — | 2.17 MB / 5.9 | 214 KB / 2.6 | 0 / 0.9 |

NULLs (every 17th row) change none of the conclusions. After deleting
`x % 10 = 0`, N's COUNT stays exact (471,856 / 9,439 / 93) with 0 data bytes.

## Readings

1. **Pruning happens before the fold, and it is selectivity-proportional on
ordered data.** Sorted: Db and C read the same bytes, 664 KB at 50% down
to 1.3 KB at 0.01%, against a constant ~3.8 MB full scan. Nothing
row-sized is materialised on the Java/Quack side: the scanner's survivors
ARE the fold input.
2. **The zone map prunes only what clustering lets it.** Sorted it reads
~2–20× the B-tree's bytes (zone granularity 8,192 rows); fully shuffled
it reads the whole column at every selectivity above 0.01%. Shuffled
layouts are the control for lost pruning, not a timing target.
3. **Bytes and latency diverge.** On shuffled data at 50%, Db reads a third
of A's bytes and is ~9× slower warm (80 ms vs 9 ms): a scattered
row-address take is worse than a sequential scan. The B-tree pays off
only at low selectivity or on ordered data.
4. **COUNT needs no data column.** N reads 0 data bytes and 0 data iops,
stays exact after deletions, and is the fastest arm below ~1% on every
layout. Caveat: `ExecutionSummaryCounts.bytes_read` is recorded by
`FilteredReadExec`, `TakeExec` and KNN; no scalar-index exec records it,
so B-tree page I/O is NOT in the 0. Cold N at 50% sorted (21.8 ms vs
5.7 warm) shows that index I/O exists. "0 bytes" means "no data
columns", never "no I/O".
5. **Index build cost:** zone map 22–117 ms, B-tree 136–229 ms per million
rows here; amortised over queries, not per query.

## Smallest remaining seam

None is required for this to work: Lance's own `filter()` + index paths do
the pruning, and the fold consumes the scanner's output directly. The one
missing piece is a **cost choice between full scan and index take**, driven
by selectivity and clustering (reading 3 above). That is a planner
decision over existing scanner calls, not a new primitive; it is OPEN until
a real workload measures where the crossover sits. `with_row_addr_prefilter`
(arm C) is only correct when the order is witnessed; nothing in lance-graph
carries that witness yet (`ISS-WITNESSED-RANGE-DOES-NOT-ATTEST-PLANE-ORDER`),
so C stays a probe arm.
72 changes: 72 additions & 0 deletions .claude/board/entries/2026-10-11-photo-probe-64-populations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# 2026-10-11 — 64 resident populations: bit-sliced planes vs signatures vs classes (D-PHOTO-0)

**Status:** MEASURED. Probe only, no production change.
`crates/lance-graph-quack/examples/photo_probe.rs`.

```text
CARGO_PROFILE_RELEASE_DEBUG=0 cargo run --release -p lance-graph-quack --example photo_probe
```

The question: with 64 already-derived populations over 1,048,576 addresses
resident, which representation answers boolean questions over them fastest,
and what does keeping them cost? Every arm answers COUNT, Any, SUM and Rows
for the canonical `(P0|P1|P2|P3) & P17 & !P29` plus 20 random expressions,
asserted equal to an oracle. Population creation is a generator stand-in;
no real reasoning baseline exists yet.

## Arms

- **B** quack `lower_fused` / **Bi** `lower` (in-place) over the 64 mask-risc planes.
- **D** `execute_extent` over each expression's support range, from one occupied range per population (AND intersects, OR takes the hull, NOT gives up).
- **C** probe-local transpose to one u64 signature per address; the expression is tested per address.
- **E** HashMap signature → (count, sum); the expression is tested per class.

## Results (per query, COUNT, steady state; 1,000 queries for C/E)

| regime | B | Bi | D | C | E | classes |
|---|---|---|---|---|---|---|
| sparse (1%) | 0.11 ms | 0.046 ms | 0.037 ms | 12.2 ms | 0.39 ms | 24,180 |
| dense (50%) | 0.11 ms | 0.048 ms | 0.036 ms | 22.8 ms | 29.9 ms | 1,048,576 |
| clustered | 0.11 ms | 0.047 ms | 0.028 ms | 12.3 ms | 0.006 ms | 274 |
| archetypes | 0.13 ms | 0.048 ms | 0.036 ms | 22.5 ms | 0.009 ms | 256 |

Costs: transpose 4–51 ms; class build 14–176 ms; updating 1,000 addresses
of one population: planes 0.005–0.012 ms, signatures + classes 0.30–0.95 ms.
Spill of 8 MiB of planes 19–56 ms, reload 1.7–7.3 ms (page cache, not
cold). Nonzero words: sparse 47.5%, dense 100%, clustered 1.6%.

## Readings

1. **Bit-sliced planes win on every regime but one.** They touch only the
planes the expression names (~5 of 64, ~82k words) and cost the same
whatever the data looks like. Signatures (C) always test all 1M
addresses and lose by 250–600×.
2. **Classes win only when there are few of them.** At 256–274 classes E is
~5× faster than planes; at 24k classes it is ~10× slower; at 1M classes
(dense) it is slower than C. Class count is data-dependent and unknown
in advance, and every update rebuilds them; planes update 30–100× cheaper.
3. **One occupied range per population is a weak descriptor.** On clustered
data the populations occupy 1.6% of the words, yet D cuts per-query
words only from ~82k to ~56k, because OR takes the hull of four runs.
Multiple ranges per population would be needed to use the clustering.
4. **Spill/replay is cheap at this size** (8 MiB in tens of ms), and a
sparse spill is worth it only for clustered populations.

## Against the real alpha channel

`AlphaMask` must not hold these: `alpha.rs` keeps motion and reference
frames as separate types that "may share a bitmap layout; they must not
share a type, a name or a conversion" (`ISS-LXA-ALPHA-FIT`). A cached
population therefore needs its own type over the same word layout, scoped
to (universe, snapshot version, expression identity, principal and policy
version for authorization-derived populations). Alpha persistence exists
only in MedCare-rs test writers, so there is no shipped spill path to reuse.

## Smallest missing seam

None for evaluation: quack + mask-risc already answer every query shape
here, fused, at ~0.03–0.05 ms. What is missing is a **population-cache
descriptor** (the scope tuple above plus exact/approximate and dependency
state) so a cached plane can be reused safely; and, if clustered data
matters, multi-range extents. Both are OPEN until a real reasoning
workload produces populations to measure.
4 changes: 3 additions & 1 deletion .claude/board/entries/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,14 @@ index row, (3) no duplicate entry id. Checks 1 and 2 are deliberately
opposite directions; the stranding this convention prevents shows up in
exactly one of them, never both.

303 entries, 2026-08-06 .. 2026-10-11.
305 entries, 2026-08-06 .. 2026-10-11.

| date | entry id | finding | file |
|---|---|---|---|
| 2026-10-11 | `D-TELEPORT-0` | | [2026-10-11-teleport-probe-source-restriction.md](2026-10-11-teleport-probe-source-restriction.md) |
| 2026-10-11 | `synergy-ledger-classid-hotplug` | | [2026-10-11-synergy-ledger-classid-hotplug.md](2026-10-11-synergy-ledger-classid-hotplug.md) |
| 2026-10-11 | `D-PHOTO-0` | | [2026-10-11-photo-probe-64-populations.md](2026-10-11-photo-probe-64-populations.md) |
| 2026-10-11 | `D-TELEPORT-2` | | [2026-10-11-lance-source-probe.md](2026-10-11-lance-source-probe.md) |
| 2026-10-11 | `coresearch-labels-address-lifetime` | | [2026-10-11-coresearch-labels-address-lifetime.md](2026-10-11-coresearch-labels-address-lifetime.md) |
| 2026-10-11 | `coresearch-instance-authz` | | [2026-10-11-coresearch-instance-authz.md](2026-10-11-coresearch-instance-authz.md) |
| 2026-10-10 | `D-XGP-8` | | [2026-10-10-woa-anchors-timesheet.md](2026-10-10-woa-anchors-timesheet.md) |
Expand Down
2 changes: 2 additions & 0 deletions crates/lance-graph-benches/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ description = "Benchmarks for lance-graph (not published)"
[dependencies]
lance-graph = { path = "../lance-graph", version = "0.5.3" }
lance = { workspace = true }
# `examples/lance_source_probe.rs` builds zone-map and B-tree indices.
lance-index = { workspace = true }
arrow = { workspace = true }
arrow-array = { workspace = true }
arrow-schema = { workspace = true }
Expand Down
Loading
Loading