docs(gfql): publish benchmark numbers from a provenance-carrying source of truth - #1806
Closed
lmeyerov wants to merge 2 commits into
Closed
docs(gfql): publish benchmark numbers from a provenance-carrying source of truth#1806lmeyerov wants to merge 2 commits into
lmeyerov wants to merge 2 commits into
Conversation
…ce of truth Benchmark numbers reached these docs by hand transcription. That is the failure mode that already produced a fabricated competitor column in this campaign: a number copied by hand becomes authoritative, and then gets re-copied after the board underneath it moves. An audit of every published figure found both outcomes had already happened. THE MECHANISM Docs no longer restate a number; they reference it. - `docs/source/_data/gfql_benchmarks.json` is the single source of truth. Every cell names the run that produced it: measurement date, host, whether the perf lock was held and the host was quiet, rep protocol, the frozen pygraphistry commit, the pyg-bench commit, runtime image, dataset, competitor and version, and the path to the raw artifacts. It is GENERATED by `scripts/export_docs_numbers.py` in graphistry/pyg-bench from artifacts committed there, so regenerating it needs no GPU box and no benchmark re-run. - `docs/source/_ext/gfql_bench.py` resolves `:bench:`key`` against that file and breaks the build on: a key the file does not contain; a run past `policy.max_age_days` (checked for every run, not just re-read pages, so an incremental build cannot serve an aged-out number); a cell that is not board-quotable published as a bare number (it must use `:bench-diag:`); and a page that references a cell without rendering that cell's `.. bench-provenance::` or `.. bench-disclosures::`. - `bin/check_bench_numbers.py` runs the same gate without Sphinx and adds two checks a docs build cannot make: commit drift (engine commits landed since a published run was measured) and a hand-typed-literal guard over the managed pages. - `docs/test_bench_numbers.py` pins all of those rejections as negative tests. The disclosure rule is the load-bearing one. A bare ratio stripped of its asterisk launders the caveat, which is worse than manual transcription, so a disclosure-bearing cell cannot be rendered without its disclosures. WHAT WAS PUBLISHED, AND WHAT WAS WITHDRAWN `gfql/performance.rst` now carries the one board that is fully traceable: the matched, row-validated graph-benchmark q1-q9 Cypher suite at 20k and 100k persons, GFQL-pandas vs GFQL-polars vs Kuzu 0.11.3, with every per-slot artifact committed and every GFQL cell's rows validated against Kuzu's. Both directions are shown, including the six-of-nine loss on the small graph. Everything whose provenance could not be recovered is removed rather than restated: the four-engine Orkut table, the 0.58.0 tag-sweep tables, the seeded-index synthetic and vs-Kuzu/Neo4j tables, the LadybugDB head-to-head (whose competitor side was an uncited hardcoded constant in a script), the CPU-crossover ratios, the filter->PageRank results and charts, and every downstream echo. Two of the withdrawn OLAP claims are not merely unverifiable but REFUTED by the matched re-run now published: "q8 runs in 5.0 ms vs 1,004 ms for embedded Kuzu (200x)" is 5.06 ms vs 9.76 ms = 1.93x when both sides answer the same query on a quiet, locked host, and "q9 is 14.2x" is 1.26x. The 1,004 ms Kuzu figure is the historical board that pyg-bench's own RESULTS.md records as not reproducible. The LDBC-vs-Neo4j table is stale rather than refuted: all four of its cells that map onto a query id in the current lane (seed-lookup, message-content, message-creator, one-hop-expand) differ materially from the current board on both sides, and its fifth row, "recent-replies", maps onto no query id the current lane emits at all -- so it cannot be re-derived, only re-measured. Removing an unverifiable number is the correct outcome, not a regression. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015YsqAZQLbqjSDrYSFz2GoB
lmeyerov
force-pushed
the
docs/bench-numbers-source-of-truth
branch
from
July 28, 2026 01:08
928a80d to
e64459e
Compare
…backed "sub-millisecond neighbor expansion", "hundreds of milliseconds" for the scan, and "sub-millisecond" as the pandas-wins threshold all originated in the sweeps whose artifacts no longer exist. Keeping them while withdrawing the tables they came from would republish the same unverifiable claim in prose form, which is the laundering this change exists to stop. Restated structurally: the index turns an O(E) scan into an O(degree) gather, so the gap grows with the graph. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015YsqAZQLbqjSDrYSFz2GoB
This was referenced Jul 28, 2026
Contributor
Author
|
Superseded by the split you asked for. Closing this in favour of two PRs:
Nothing from this branch is lost — #1813 carries the machinery and the deletions verbatim where they needed no judgement, and #1815 carries the board. Both are green-pending and unmerged. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The problem
Benchmark numbers reach these docs by hand transcription. That is the same failure mode that produced a fabricated competitor column earlier in this campaign: a number copied by hand becomes authoritative, and then gets re-copied after the board underneath it moves.
An audit of every published performance figure found both outcomes had already happened — including two claims that are not merely unverifiable but refuted by a matched re-run:
performance.rst: OLAP "q8 runs in 5.0 ms vs 1,004 ms for embedded Kuzu (200×)"performance.rst: OLAP "q9 is 14.2×"performance.rst: LDBC-vs-Neo4j table (5 rows)seed-lookup,message-content,message-creator,one-hop-expand) differ materially from the current board on both sides — e.g.seed-lookup106.1 ms → 28.08 ms, Neo4j 143.7 ms → 155.32 msperformance.rst: LDBCrecent-repliesrowThe Kuzu 1,004 ms figure is the historical board that pyg-bench's own
RESULTS.mdrecords as "did not reproduce and cannot be reproduced" — a competitor handicap. It was still on the docs.Most of the rest could not be confirmed or refuted: the reproducers wrote to
/tmp(or only printed), recorded no commit/host/timestamp, and their artifacts were never committed and no longer exist. A number whose provenance cannot be established is unpublishable, not presumed correct.The mechanism
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, the frozen pygraphistry commit, the pyg-bench commit, runtime image, dataset, competitor + version, raw artifact path. It is generated byscripts/export_docs_numbers.pyin 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 small Sphinx extension providing:bench:key, `:bench-diag:`key,.. bench-provenance::and.. bench-disclosures::.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.Why an extension rather than substitutions or an include
The docs build already runs
-W --keep-going -n, so a warning is fatal — but neitherrst_prologsubstitutions nor a generated.. include::can enforce requirement 2 (numbers must carry their disclosures). A substitution renders a bare value with no idea who used it; an include cannot stop a page from restating a figure in prose. The extension is ~300 lines, has no dependency beyond Sphinx itself, and is the smallest thing that can refuse a non-quotable cell at the point of use and require that page to render its caveats. The generated JSON keeps requirement 4 (regenerate offline) intact regardless.What breaks the build
policy.max_age_days— checked for every run, not just re-read pages, so an incremental build cannot serve an aged-out numbercomparison_allowed: false, or a non-okstatus) published as a bare number — it must use:bench-diag:, which labels it diagnostic-only.. bench-provenance::/.. bench-disclosures::graphistry/computelanded since a published run was measuredThe disclosure rule is the load-bearing one. A bare ratio stripped of its asterisk launders the caveat, which is worse than manual transcription.
Demonstrated, not asserted
All five failure modes were run against a real
sphinx-buildover this branch'sperformance.rstand against the standalone gate:What is published now
gfql/performance.rstcarries the one board that is fully traceable: the matched, row-validatedprrao87/graph-benchmarkq1–q9 Cypher suite at 20k and 100k persons — GFQL-pandas vs GFQL-polars vs Kuzu 0.11.3 — measured on a quiet, perf-locked dgx-spark with position-balanced slots, every per-slot artifact committed in pyg-bench, and every GFQL cell's result rows validated against Kuzu's before publication. Board-quotability is derived from the artifacts, not declared: a row mismatch suppresses the ratio entirely.Both directions are shown, including that GFQL-polars loses six of nine cells on the small graph and that GFQL-pandas loses to Kuzu on most cells at both sizes.
What was withdrawn
Removed rather than restated, on unrecoverable provenance: the four-engine Orkut table, the 0.58.0 tag-sweep tables (
performance.rstandindexing.rst), the seeded-index synthetic and vs-Kuzu/Neo4j tables, the prepared-Kuzu figures, the LadybugDB head-to-head (whose competitor column was an uncited hardcoded constant in the benchmark script, not a measurement), the CPU-crossover ratios, the filter→PageRank results and their charts (the chart generator reads a results directory that has never existed in any commit), and every downstream echo (~38x,9-28x vs Kuzu/Neo4j,43X+,10-50x,100X+,10X+).benchmark_graphframes.rstis kept intact — its raw results are committed atdocs/source/gfql/_static/graphframes/results.jsonand every headline cell round-trips against it. Bringing it under the gate is a follow-up.This is a large removal. It is deliberate: removing an unverifiable number is a correct outcome, not a regression.
CI
.github/workflows/ci.ymlis not touched (pendingworkflowOAuth scope). The patch — path-filter entries plus aGate published benchmark numbersstep intest-docs, withfetch-depth: 0for the commit-drift check — is written up and reported separately. Note the Sphinx build indocs/ci.shalready enforces the reference/provenance/disclosure/freshness rules independently, sotest-docsgains the gate even without the patch.Verification
bin/lint.shclean;ruffclean on the new files.mypy --strictclean on all three new modules (noAny, nocast, nogetattr/setattr, no bareDict[str, Any]).rstcheckclean on every edited.rst(the four new roles/directives registered indocs/.rstcheck.cfg).pytest docs/test_bench_numbers.py— 8 passed.sphinx-buildread phase over the realdocs/source: 0gfql-benchwarnings, 0 errors from this change.🤖 Generated with Claude Code
https://claude.ai/code/session_015YsqAZQLbqjSDrYSFz2GoB