docs(gfql): delete refuted and unverifiable benchmark numbers, and gate the rest - #1813
docs(gfql): delete refuted and unverifiable benchmark numbers, and gate the rest#1813lmeyerov wants to merge 1 commit into
Conversation
…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
| 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. |
There was a problem hiding this comment.
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 | |||
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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**, |
There was a problem hiding this comment.
gfql via polars can go out-of-core / multinode . we just haven't optimized this yet.
|
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. |
…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
What was wrong, and by how much
Two figures on
gfql/performance.rstare not stale. They are wrong, and a matched, row-validated re-run on the same host says so:performance.rst:145— OLAP "q8 runs in 5.0 ms vs 1,004 ms for embedded Kuzu (200×)"performance.rst:146— OLAP "q9 is 14.2×"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 tobenchmarks/gfql/bench_ladybug_cypher.py:52, which hardcodesLADYBUG = {...}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 bybenchmarks/gfql/filter_pagerank/presentation.py:18, which readsplans/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
/tmpor 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/*.pyreturns nothing — no GFQL benchmark runner records provenance at all. Withdrawn on those grounds:performance.rst, echoed inengines.rst)performance.rst,indexing.rst)seed-lookup106.1 → 28.08 ms on our side, 143.7 → 155.32 ms on Neo4j's), andrecent-repliesmaps onto no query id the current lane emits at all, so it cannot be re-derived — only re-measuredindex_adjacency.rst)~38x,9-28x vs Kuzu/Neo4j,43X+,10-50x,100X+,10X+, plus the~10K-edge crossover,100M+,80M+ output rowsand<1 msthresholds that were outputs of the withdrawn runsbenchmark_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. 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 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 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.policy.max_age_days— checked for every run, so an incremental build cannot serve an aged-out number:bench-diag:)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.rstkeeps 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.ipynbrenders the same withdrawn pipeline figures from the same missing results directory;benchmark_graphframes.rstis verifiable but not yet gated.CI
.github/workflows/ci.ymlis not touched (the push token lacksworkflowscope and the owner has declined to grant it). The patch — path-filter entries plus aGate published benchmark numbersstep intest-docs, withfetch-depth: 0for the commit-drift check — is reported separately. The Sphinx build indocs/ci.shalready enforces the reference / provenance / disclosure / freshness rules independently, sotest-docsexercises the gate even without the patch.Verification
bin/lint.shclean;ruffclean on the new files.rstcheckclean on every edited.rst(the four new roles/directives registered indocs/.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.docs/ci.shbuild (the same onetest-docsruns) exits 0, with 0gfql-benchwarnings.🤖 Generated with Claude Code
https://claude.ai/code/session_015YsqAZQLbqjSDrYSFz2GoB