Skip to content

perf(gfql): seeded typed-hop fast paths use resident indexes (#1658 × #1755)#1768

Merged
lmeyerov merged 3 commits into
masterfrom
perf/gfql-seeded-fastpath-resident-index
Jul 22, 2026
Merged

perf(gfql): seeded typed-hop fast paths use resident indexes (#1658 × #1755)#1768
lmeyerov merged 3 commits into
masterfrom
perf/gfql-seeded-fastpath-resident-index

Conversation

@lmeyerov

@lmeyerov lmeyerov commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

What

With gfql_index_all() resident, the #1755 seeded fast-path helpers (_seeded_typed_hop_pandas_cudf, _seeded_typed_return_dst_pandas_cudf, _seeded_typed_return_dst_polars) replace their three full-frame scans — O(N) seed-row lookup, O(E) frontier isin, O(N) endpoint gather — with positional index lookups (lookup_node_rows searchsorted + lookup_edge_rows CSR gather via take_rows). A seeded Cypher lookup stops paying graph-size costs; this is the lever aimed at the C2 covered-shape gap vs Kuzu (2.60 vs 1.06ms) and the IS5 seed-scan residue.

Decline contract (falls back to the byte-for-byte scan body)

  • no resident index / stale fingerprint or identity (get_valid)
  • non-numeric id families (object/str ids keep the scan path's null-object semantics)
  • seed not id-filtered (node not in n0f)
  • engine family unrecognized

Results identical either way (row order may differ — node-id index implies unique ids; value-identical contract). Null endpoints never link (float-NaN dropped pre-lookup; numeric-only gate excludes object nulls).

Measured (DGX Spark GB10, container polars 1.35.2, warm median N=30)

Covered shape (seeded whole-row MATCH (m {id})-[:T]->(p) RETURN p):

scale engine scan fast path indexed fast path
50k/200k (C4 pair) pandas 1.71ms 0.86ms
50k/200k (C4 pair) polars 2.02ms 0.69ms
3.2M/17M (SF1-scale) pandas 36.7ms 15.7ms
3.2M/17M (SF1-scale) polars 47.2ms 2.25ms (21×)

The 50k covered-shape pair vs Kuzu's same-box 1.06ms: GFQL moves from 2.5× behind to ahead. The pandas SF1 residue (15.7ms) is outside the three replaced scans — open question tracked in the campaign plan; polars is the shipping engine for this surface.

Tests

TestResidentIndexSeededFastPath: indexed-vs-plain parity with engagement spies (pandas + polars), string-id decline, stale-index (rebound edges) decline — plus the full seeded fast-path + index suites green. Test file already in POLARS_TEST_FILES.

Independence from #1767 (retargeted to master)

Rebased onto master (now incl. #1764/#1766) and independently landable. The code never depended on #1767; the coupling was behavioral: without #1767, gfql_index_all() under AUTO swaps polars frames to pandas, so polars graphs get index service only via an explicit gfql_index_all(engine='polars') (works on master today; test adapted accordingly). pandas/cuDF flows are fully independent. When #1767 lands, bare AUTO indexing on polars frames starts engaging this path too — no changes needed here.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Y6dQEcjdazEnzuvuwf73ZL

@lmeyerov
lmeyerov force-pushed the perf/gfql-seeded-fastpath-resident-index branch from 4cbaf3b to c7419a3 Compare July 22, 2026 01:28
lmeyerov and others added 2 commits July 21, 2026 19:12
…1755)

With gfql_index_all() resident, the seeded helpers replace their three
full-frame scans (O(N) seed row, O(E) frontier isin, O(N) endpoint
gather) with node-id searchsorted + CSR adjacency gathers. Decline-gated
to the identical scan body: absent/stale index (get_valid fingerprint +
identity), non-numeric id families, seed not id-filtered. Local 50k/200k
covered shape: pandas 3.11->1.85ms, polars 1.74->1.10ms; win scales with
graph size. Pinned: parity+engagement (pandas/polars), string-id decline,
stale-index decline.

Stacked on perf/gfql-index-preserve-polars-frames (#1767) — polars index
residency requires it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y6dQEcjdazEnzuvuwf73ZL
…f device path, polars-gpu tags, serve-asserted tests

Review findings (plans/review-pr-1768/review.md):
B1 int64<->uint64 promotes to float64 and collapses ids >= 2^53 into
false matches (repro'd: seed 2^62+1 matched node 2^62) — integral pairs
whose promote is float now DECLINE to the exact-compare scan path.
M2 spies count SERVES (non-None), not calls, so a gate regression that
declines everything fails the suite. M1 native-chain hop indexed branch
+ reverse/EDGE_IN_ADJ now tested (serve-asserted, parity). M3 cudf ids
via .dropna().values (device array; to_numpy raised on nulls and
round-tripped host). M4 polars frames also accept polars-gpu-tagged
indexes (same numpy sidecars + polars gather).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y6dQEcjdazEnzuvuwf73ZL
@lmeyerov
lmeyerov force-pushed the perf/gfql-seeded-fastpath-resident-index branch from c7419a3 to a929331 Compare July 22, 2026 02:14
@lmeyerov
lmeyerov changed the base branch from perf/gfql-index-preserve-polars-frames to master July 22, 2026 02:14
… gate

Review feedback: _ids_to_key_array & friends were Any-typed — now
NodeIdIndex/AdjacencyIndex/ArrayLike/ArrayNamespace/SeriesT protocol
types (isnan added to the ArrayNamespace protocol), localized ignores
only at the cudf module-check narrows. New GFQL_PERF_TESTS=1 opt-in
wall-clock gate (indexed must beat scan >=1.5x at 500k edges) for the
benchmarking-rig protocol; CI keeps the deterministic engagement asserts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y6dQEcjdazEnzuvuwf73ZL
@lmeyerov
lmeyerov merged commit bffdc34 into master Jul 22, 2026
69 checks passed
@lmeyerov
lmeyerov deleted the perf/gfql-seeded-fastpath-resident-index branch July 22, 2026 02:38
pull Bot pushed a commit to admariner/pygraphistry that referenced this pull request Jul 22, 2026
New docs/source/gfql/indexing.rst: lifecycle guide to GFQL resident
indexes (gfql_index_all / gfql_index_edges / create_index / show_indexes
/ drop_index) — node-id + CSR in/out adjacency sidecars, what engages
them on 0.58.0 (seeded typed-hop fast paths incl. property RETURNs and
property-seeded lookups per graphistry#1768/graphistry#1770, direct g.hop(); general polars
chain traversal honestly noted as not yet covered), the identity +
fingerprint staleness contract with safe declines, engine notes (polars
needs gfql_index_all(engine='polars') until graphistry#1767), 0.58.0-tag measured
numbers, and a runnable end-to-end example (executed).

Wired into the GFQL toctree + recommended paths next to
index_adjacency; CHANGELOG entry under Development > Documentation.

Co-Authored-By: Claude Fable 5 <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