Skip to content

docs(gfql): delete refuted and unverifiable benchmark numbers, and gate the rest - #1813

Open
lmeyerov wants to merge 1 commit into
masterfrom
docs/bench-numbers-gate-and-deletions
Open

docs(gfql): delete refuted and unverifiable benchmark numbers, and gate the rest#1813
lmeyerov wants to merge 1 commit into
masterfrom
docs/bench-numbers-gate-and-deletions

Conversation

@lmeyerov

Copy link
Copy Markdown
Contributor

Review-only. Do not self-merge. Split out of #1806 at the owner's request: a wrong public number and badly-written prose have different urgencies, and bundling them made the urgent fix wait. This is the urgent half — deletion and mechanism only, no editorial rewriting. The prose rewrite is #PRB.

What was wrong, and by how much

Two figures on gfql/performance.rst are not stale. They are wrong, and a matched, row-validated re-run on the same host says so:

Published claim Measured
performance.rst:145 — OLAP "q8 runs in 5.0 ms vs 1,004 ms for embedded Kuzu (200×)" q8 @100k: GFQL-polars 5.06 ms vs Kuzu 0.11.3 9.76 ms = 1.93× REFUTED, off by ~100×
performance.rst:146 — OLAP "q9 is 14.2×" q9 @100k: 66.61 ms vs 84.06 ms = 1.26× REFUTED, off by ~11×

The 1,004 ms Kuzu figure is a historical board that pyg-bench's own results file records as "did not reproduce and cannot be reproduced" — a competitor handicap. It was still on the public docs.

What could not be defended, and is therefore gone

Two of these are worse than unverifiable:

  • engines.rst's LadybugDB column (65× / 1.2× / 3.5–3.7×) traces to benchmarks/gfql/bench_ladybug_cypher.py:52, which hardcodes LADYBUG = {...} with no URL, no version, no citation. That is a literal, not a measurement of a competitor. It can never be published. The whole head-to-head is withdrawn, including the uncited "1.8B-edge graph in <8 GB RAM" figure attached to the same row.
  • benchmark_filter_pagerank.rst's two committed SVG charts are produced by benchmarks/gfql/filter_pagerank/presentation.py:18, which reads plans/gfql-gpu-pagerank-benchmark/results/ — a path that has never existed in any commit. Nobody, including us, can regenerate them. The charts and the 46× / >56× / 8× / 54× / >26× figures they carry are removed, and the two SVG files are deleted.

The rest could be neither confirmed nor refuted. The reproducers wrote to /tmp or only printed, recorded no commit, host or timestamp, and their artifacts were never committed and no longer exist. grep -l "rev-parse\|gethostname\|git_sha\|platform.node" benchmarks/gfql/*.py returns nothing — no GFQL benchmark runner records provenance at all. Withdrawn on those grounds:

  • the four-engine Orkut / LiveJournal bulk table and its CPU-crossover ratios (performance.rst, echoed in engines.rst)
  • the 0.58.0 tag-sweep tables — seeded fast path, resident index, flat scaling (performance.rst, indexing.rst)
  • the LDBC-SNB-vs-Neo4j table: every row that maps onto a query id the current lane emits differs materially from the current board on both sides (e.g. seed-lookup 106.1 → 28.08 ms on our side, 143.7 → 155.32 ms on Neo4j's), and recent-replies maps onto no query id the current lane emits at all, so it cannot be re-derived — only re-measured
  • the seeded-index synthetic and vs-Kuzu/Neo4j tables and the prepared-Kuzu figures (index_adjacency.rst)
  • every downstream echo: ~38x, 9-28x vs Kuzu/Neo4j, 43X+, 10-50x, 100X+, 10X+, plus the ~10K-edge crossover, 100M+, 80M+ output rows and <1 ms thresholds that were outputs of the withdrawn runs

benchmark_graphframes.rst is kept intact. Its raw results are committed at docs/source/gfql/_static/graphframes/results.json and every headline cell round-trips against it. Bringing it under the gate is a follow-up.

This is a large removal. Removing an unverifiable number is a correct outcome, not a regression.

The mechanism, so it does not recur

Docs no longer restate a number; they reference it.

  • docs/source/_data/gfql_benchmarks.json — the single source of truth. Every cell names the run that produced it: measurement date, host, perf lock held, quiet host, rep protocol, frozen pygraphistry commit, pyg-bench commit, runtime image, dataset, competitor + version, raw artifact path. It is generated by scripts/export_docs_numbers.py in pyg-bench#109 (merged) from artifacts committed there, so regenerating the docs' numbers needs no GPU box and no benchmark re-run.
  • docs/source/_ext/gfql_bench.py — a Sphinx extension providing :bench:`key```, :bench-diag:key```, .. bench-provenance::and.. bench-disclosures::. The docs build already runs -W --keep-going -n`, so a warning is fatal.
  • bin/check_bench_numbers.py — the same gate without Sphinx, plus two checks a docs build cannot make.
  • docs/test_bench_numbers.py — 8 negative tests pinning the rejections.
Failure Caught by
a key the source of truth does not contain Sphinx + standalone
a run older than policy.max_age_days — checked for every run, so an incremental build cannot serve an aged-out number Sphinx + standalone
a non-board-quotable cell published as a bare number (it must use :bench-diag:) Sphinx + standalone
a page that references a cell without rendering its provenance / disclosures Sphinx + standalone
engine commit drift since a published run was measured standalone only (needs git history)
a hand-typed literal on a managed page standalone only

The disclosure rule is the load-bearing one: a bare ratio stripped of its asterisk launders the caveat, which is worse than manual transcription.

Scope discipline

No editorial rewriting, no restructuring, no voice changes. Where a sentence mixed a withdrawn number with surviving prose, only the numeric clause was removed and the surrounding wording kept. performance.rst keeps its existing sections and gains one factual section describing how a number now gets published; it publishes no new figures. The provenance-carrying q1–q9 board that #1806 added, and the prose rewrite around it, are deliberately not here — they need editorial judgement and are in the follow-up PR.

Known follow-ups, deliberately out of scope: demos/gfql/benchmark_filter_pagerank_cpu_gpu.ipynb renders the same withdrawn pipeline figures from the same missing results directory; benchmark_graphframes.rst is verifiable but not yet gated.

CI

.github/workflows/ci.yml is not touched (the push token lacks workflow scope and the owner has declined to grant it). The patch — path-filter entries plus a Gate published benchmark numbers step in test-docs, with fetch-depth: 0 for the commit-drift check — is reported separately. The Sphinx build in docs/ci.sh already enforces the reference / provenance / disclosure / freshness rules independently, so test-docs exercises the gate even without the patch.

Verification

  • bin/lint.sh clean; ruff clean on the new files.
  • rstcheck clean on every edited .rst (the four new roles/directives registered in docs/.rstcheck.cfg).
  • pytest docs/test_bench_numbers.py — 8 passed.
  • bin/check_bench_numbers.py — all four checks pass, including commit drift against current master.
  • Dockerised docs/ci.sh build (the same one test-docs runs) exits 0, with 0 gfql-bench warnings.

🤖 Generated with Claude Code

https://claude.ai/code/session_015YsqAZQLbqjSDrYSFz2GoB

…te the rest

Two published OLAP ratios are not stale, they are wrong. `gfql/performance.rst`
published q8 at "5.0 ms vs 1,004 ms for embedded Kuzu (200x)"; a matched,
row-validated re-run measures 5.06 ms vs 9.76 ms = 1.93x. It published q9 at
14.2x; measured, 66.61 vs 84.06 = 1.26x.

An audit of every other published performance figure found most of them can be
neither confirmed nor refuted: the reproducers wrote to /tmp or only printed,
recorded no commit/host/timestamp, and their artifacts were never committed.
Two are worse than unverifiable - the LadybugDB competitor column was a
hardcoded `LADYBUG = {...}` literal with no citation, and the filter->PageRank
SVG charts are generated from a results directory that has never existed in any
commit. All of it is removed rather than restated.

The mechanism that stops it recurring ships with the deletion: a single
generated source of truth (docs/source/_data/gfql_benchmarks.json), a Sphinx
extension that resolves `:bench:` against it and fails the build on a missing,
stale, non-quotable or undisclosed number, and a standalone gate that adds
commit-drift and hand-typed-literal checks.

Deletion and mechanism only - no editorial rewriting. The prose rewrite is a
separate PR.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015YsqAZQLbqjSDrYSFz2GoB
@lmeyerov

Copy link
Copy Markdown
Contributor Author

Split half 1 of 2, from #1806. The editorial half is #1815, stacked on this branch. Land this one first; #1815 should be re-based onto master afterwards.

the exact pipeline, the Neo4j+GDS analog, and how to run it yourself. The figures will
be republished once this pipeline runs under the provenance-carrying harness described
on :doc:`performance` — committed per-slot artifacts, recorded commit/host/perf-lock/
reps, and results validated against the competitor before any ratio is published.

@lmeyerov lmeyerov Jul 28, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wtf - make this table work, it's the whole point. we have the dgx-spark, everything was performed there, and nowadays we have pyg-bench too!

@@ -0,0 +1,266 @@
#!/usr/bin/env python3

@lmeyerov lmeyerov Jul 28, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i don't get why this big file is here when we also have docs/source/_ext/gfql_bench.py

more broadly, why is this different from the pyg-bench stuff? should all that be there, and all we do is pretty print their latest json? and if we have any boundary needs, ensure there are data contract tests that run on the pyg-bench side, and we re-verify on our side?

---------

Note the caveat at the top of this page: these reproducers print and plot, but do not
yet emit a provenance-carrying artifact, which is why their output is not published here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wtf is this disclaimer, that's inappropriate for docs


.. note::
For graphs with millions of edges, consider using ``compute_cugraph`` with a GPU for 10-50x speedup, or :ref:`gfql-remote` if no local GPU is available.
For graphs with millions of edges, consider using ``compute_cugraph`` with a GPU, or :ref:`gfql-remote` if no local GPU is available.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

quantifying gpu speedup in few select non-centralized places feels smart

mask beats Polars' plan overhead — immaterial in absolute terms.
- **Seeded / selective lookups are an indexing problem**, not an engine race: the opt-in
resident index turns the ``O(E)`` scan into an ``O(degree)`` gather on every engine, so
the cost tracks the seeds rather than the graph — see [F5] below and

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is super hard to read

:doc:`benchmark_filter_pagerank`; the head-to-head figures previously published
there are withdrawn pending a provenance-carrying re-run.
- Neo4j remains the transactional system-of-record; run the read-heavy analytics in
GFQL. No currently-publishable head-to-head latency comparison.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can use non-graph db for oltp too, neo4j often becomes unnecessary. phrase as 'gfql can work with many systems of record, like SQL, parquet files, databricks, splunk, neo4j, etc.

- GFQL's strengths are **traversals**, **multi-join OLAP**, and **covered seeded
shapes**, with no separate store to provision, load and index. No
currently-publishable head-to-head latency comparison.
- **Not claimed:** cyclic / multi-way-join patterns (triangles, cliques) where Kuzu's

@lmeyerov lmeyerov Jul 28, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gfql does not yet include Kuzu's WCOJ index type, which is useful for some queries on CPU mode

load/index. Details and reproducer in the :doc:`performance` methodology notes.
- **Complement:** Ladybug is a durable embedded store with an out-of-core mode
(billion-scale in <8 GB RAM); GFQL is a query engine over your dataframes. GFQL's
Cypher, opt-in ART *or* hash indexing, zero-copy Arrow/CSR scans, and an

@lmeyerov lmeyerov Jul 28, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove zero-copy arrow/csr strong copy, and out of core, as gfql has too. Note that it adds ART indexes, which is the only main thing afaict compared to kuzu that it adds that we don't have

script rather than a measurement, and the GFQL side has no surviving artifact.
Structurally, GFQL's angle is dataframe-native, in-process, and GPU-accelerated
with no separate store to load or index; a resident GFQL node-id index (tracked in
issue #1676) targets the point-lookup shape.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

drop this historical discussion, ignore history in writeup, only describe present. if bencharmsk TBD, say so, and note that closest comparable is Kuzu as it is a fork.

with no separate store to load or index; a resident GFQL node-id index (tracked in
issue #1676) targets the point-lookup shape.
- **Complement:** Ladybug is a durable embedded store with an out-of-core mode;
GFQL is a query engine over your dataframes. GFQL's

@lmeyerov lmeyerov Jul 28, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

polars gives us out-of-core, so stop highlighting that in kuzu, ladybug. likewise, you can save gfql to parquet, it's nbd for durability.

* - **Spark GraphFrames**
- *Distributed* graph engine on a Spark cluster; provision + tune the cluster.
- GFQL is *single-node* (CPU or one GPU): 100M+ edges in-process on **one machine**,
- GFQL is *single-node* (CPU or one GPU): a large graph in-process on **one machine**,

@lmeyerov lmeyerov Jul 28, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gfql via polars can go out-of-core / multinode . we just haven't optimized this yet.

@lmeyerov

lmeyerov commented Jul 28, 2026

Copy link
Copy Markdown
Contributor Author

there are so many issues in these rewrites. we can stack docs updates on top, but this is unlandable. if there are very specific narrow edits we want, propose a minimal pr with just those.

pull Bot pushed a commit to admariner/pygraphistry that referenced this pull request Jul 28, 2026
…erify them

The owner's redirect on graphistry#1813: "should all that be there, and all we do is
pretty print their latest json? and if we have any boundary needs, ensure there
are data contract tests that run on the pyg-bench side, and we re-verify on our
side?" This is the consuming half. Measurement, provenance and publishability
live in pyg-bench, which owns the runs. This repository renders them.

`docs/source/_data/gfql_benchmarks.json` is a vendored copy of pyg-bench's
`published/docs-numbers.json`; `gfql_benchmarks.contract.json` is a vendored
copy of the contract it satisfies. The `gfql_bench` extension resolves `:bench:`
and `:bench-diag:` against the artifact and re-verifies the contract before
rendering anything, so the build fails on a key that is not published, a
measurement older than the policy allows, a diagnostic-only figure printed as a
result, or a page that drops a number's provenance or disclosures. The
re-verification is written against the contract DOCUMENT, independently of
pyg-bench's implementation - a shared library would fail identically on both
sides and prove nothing.

`benchmark_filter_pagerank.rst` is the first page to use it. Its numbers came
from a chart generator reading a results directory that has never existed in any
commit, so nobody could re-derive them; pyg-bench re-ran the benchmark on
dgx-spark against a named commit and published the result. Twitter: GFQL GPU
0.24s and CPU 1.58s against Neo4j + GDS 11.72s, 49.5x and 7.4x. GPlus: GFQL GPU
2.42s and CPU 32.10s, 13.3x. The two lifecycle SVGs are removed - they render
the withdrawn figures as glyph paths and their generator cannot be run.

GPlus publishes no Neo4j figure. That arm did not complete, and a ratio against
a run that did not finish is not a speedup.

`docs/test_bench_numbers.py` runs the contract checks in the ordinary test lane
rather than only in the docs build, so a number going stale fails CI.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015YsqAZQLbqjSDrYSFz2GoB
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