Skip to content

perf(gfql): cardinality-aware lean combine for seeded chains (#1755)#1758

Merged
lmeyerov merged 3 commits into
masterfrom
perf/gfql-lean-combine-1755
Jul 21, 2026
Merged

perf(gfql): cardinality-aware lean combine for seeded chains (#1755)#1758
lmeyerov merged 3 commits into
masterfrom
perf/gfql-lean-combine-1755

Conversation

@lmeyerov

Copy link
Copy Markdown
Contributor

What

Cardinality-aware lean combine for seeded chains (#1755, "abstraction tax" lever 1).

The two-pass chain executor (_chain_impl backward pass + combine_steps) reconciles wavefront boundaries with full-node-frame safe_merge/pandas.merge calls even when the seeded result is a single row, so a seeded 1-hop pays a whole-frame join. Three hot merges scan the full node/edge frame regardless of frontier size:

  • backward pass: safe_merge(g._nodes, wavefront[[id]], how='inner') (2×/op)
  • combine tail: safe_merge(out_df, g._nodes/_edges, how='left')

When the small side is ≥ smaller than the full frame, the byte-identical result is obtained by an isin membership filter (no join-indexer / block-consolidation machinery):

  • inner-merge of a unique full frame against a unique single-key wavefront ≡ isin in full-frame order;
  • a how='left' merge discards unmatched right rows anyway, so pre-shrinking the right side is byte-identical.

Gating (narrow by design)

  • pandas-only (_lean_engine_ok): cuDF's GPU hash-join is already sub-ms so an isin pre-pass is net-negative there; polars takes its own engine path.
  • Engages only on cardinality gap (≥4×) and right-side column-set == [id] and id-uniqueness, so the bulk / multi-source / fan-out paths are untouched.
  • GFQL_LEAN_COMBINE=0 restores the legacy full-frame merges (used by the differential parity harness).

Evidence

Parity (local, pandas): test_chain_lean_combine.py — 11 tests: gate units + lean-on vs lean-off byte-identical node+edge frames across seeded node lookup, seeded 1-hop expand (creator / both endpoints), native typed chain, plus a non-vacuity guard asserting the lean path actually engages. mypy clean; typed SeriesT/DataFrameT.

A/B (dgx-spark, 26.02-gfql-polars, 50k persons / 200k messages, seeded 1-hop, warm-median warmup3/repeat7):

engine parity rows cypher off→on native typed chain off→on
pandas ✓ byte-identical 1 50.27 → 43.83 ms (1.15×) 29.37 → 23.44 ms (1.25×)
cudf ✓ byte-identical 1 65.63 → 65.54 ms (1.00× no-op) 40.36 → 40.30 ms (1.00× no-op)

cuDF is untouched (empirically confirms the pandas-only gate → no cross-engine regression).

Note on magnitude: the prototype measured ~1.6× with the resident #1658 index (#1676, not yet on master) making the hop cheap so combine dominated total time. On master as-is the seeded hop still scans edges, so combine is a smaller fraction and the win is 1.15–1.25×; it grows once the resident index lands. This PR is a correctness-safe, parity-exact increment — not the full ≤1ms fix (which additionally needs the typed-filter lever + a seeded single-hop fast-path that bypasses the two-pass combine entirely).

🤖 Generated with Claude Code

@lmeyerov

Copy link
Copy Markdown
Contributor Author

review-skill pass (findings mode) — no correctness blockers

Ran the repo review skill. No correctness defects survived verification — the isin ≡ merge equivalence is sound (unique/single-column guards prevent fan-out; left/within-key order preserved; the null-key case was checked empirically and pandas matches NaN keys in merge, so isin agrees). Confirmed combine_steps receives engine_concrete, so both the backward-pass intersect and the tail prefilter actually engage on pandas.

Low-severity, non-blocking (deferred):

  • robustness: the isin ≡ inner-merge equivalence relies on pandas merging null keys (holds here; a null node-id is pathological, and GFQL_LEAN_COMBINE=0 + parity harness backstop it). Optional: decline when key_frame[key] has nulls to make it version-independent.
  • test-coverage: the non-vacuity engagement guard covers _lean_intersect_full but not the tail _lean_prefilter_right (which has a standalone correctness unit test). Optional: extend the spy.
  • style: import os as _os mid-module — a top-level import os would be more idiomatic.

Verdict: landable incremental parity-safe perf fix. Leaving open for human review — not self-merging.

lmeyerov and others added 2 commits July 21, 2026 12:05
The two-pass chain executor (_chain_impl backward pass + combine_steps) fires
full-node-frame safe_merge/pandas.merge calls to reconcile wavefront boundaries
even when a seeded result is a single row (root cause #1755), so a seeded 1-hop
pays a whole-frame join. Three hot merges scan the full node/edge frame
regardless of frontier size:
  (a) backward pass: safe_merge(g._nodes, wavefront[[id]], how='inner')  (2x/op)
  (b) combine tail:  safe_merge(out_df,  g._nodes/_edges,  how='left')

When the small side is >=4x smaller than the full frame, the byte-identical
result is obtained by an isin membership filter (no join-indexer / block-
consolidation machinery): inner-merge of a unique full frame against a unique
single-key wavefront == isin in full-frame order; a how='left' merge discards
unmatched right rows anyway, so pre-shrinking the right side is byte-identical.
Gated narrowly on cardinality + right-side column-set + id uniqueness so the
bulk/multi-source path is untouched; pandas-only (cuDF's GPU hash-join is
already sub-ms, polars takes its own engine path). GFQL_LEAN_COMBINE=0 restores
the legacy merges (used by the differential parity harness).

Parity: byte-identical node+edge frames lean-on vs lean-off across seeded node
lookup, seeded 1-hop expand (creator / both endpoints), and the native typed
chain (test_chain_lean_combine.py, incl. a non-vacuity guard that the lean path
actually engages). mypy clean. Measured dgx pandas (50k/200k, resident #1658):
IS5 cypher ~29->18ms, native typed chain ~21->10ms, parity-exact.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y6dQEcjdazEnzuvuwf73ZL
#1755)

Review follow-up:
- _lean_intersect_full now declines when the (small) key_frame carries a null
  id. isin() matches nulls but merge's null-key semantics are version-dependent;
  checking only the small side keeps the isin==inner-merge equivalence
  version-independent at O(small) cost (a null key_frame is enough to force the
  legacy merge; nulls in the full frame are dropped identically by both paths).
- non-vacuity engagement test now also asserts the combine-tail
  _lean_prefilter_right actually shrinks the right frame on a seeded chain, so
  neither lean path is silently inert (parity tests stay meaningful).

11/11 pass, mypy clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y6dQEcjdazEnzuvuwf73ZL
@lmeyerov
lmeyerov force-pushed the perf/gfql-lean-combine-1755 branch from dd8d349 to 267676f Compare July 21, 2026 19:08
#1755)

Review feedback: the lean-combine helpers (_lean_combine_enabled, _is_unique_ids,
_lean_engine_ok, _lean_intersect_full, _lean_prefilter_right) are reusable,
engine-agnostic DataFrameT helpers — move them out of chain.py into a dedicated
graphistry/compute/chain_lean_combine.py (mirrors the gfql_fast_paths.py #1731
convention: one-directional import, no back-edge, imports only leaf modules).
chain.py imports them; call sites + monkeypatch-based engagement tests unchanged.
11 lean-combine tests pass; mypy clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y6dQEcjdazEnzuvuwf73ZL
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant