Adjudication · re-run on one machine · code & data
A paper, two formal Comments and two Replies in Nature Machine Intelligence, and nobody moving. So I re-ran it: same host, same graphs, same seeds, both sides implemented from the published papers and code — the Reply’s GraphSAGE variant reconstructed from its text alone — with an answer key.
“…the graph neural network optimizer performs on par or outperforms existing solvers, with the ability to scale beyond the state of the art to problems with millions of variables.” Schuetz, Brubaker & Katzgraber, Nature Machine Intelligence 4, 367 (2022)
“…a simple greedy algorithm, running in almost linear time, can find solutions for the MIS problem of much better quality than the GNN. The greedy algorithm is faster by a factor of 104…” Angelini & Ricci-Tersenghi, arXiv:2206.13211 (abstract); published as Nature Machine Intelligence 5, 29 (2023), whose own abstract words this differently
The measured object is the validity of a published sentence against an exact objective, not which model is better. That sentence contains two claims, and they are scored separately because they can have different answers.
A degree-based greedy finds larger independent sets at every degree and every size tested, and at n = 10,000 does it about 204,208× faster on the same machine.
Reachable — but not by the released example implementation, which builds a dense n×n matrix (3.6 TiB at n = 106); the paper does not describe its construction at that scale. With a mathematically identical sparse reformulation, a full run still projects to 6.4–8.1 days against greedy's 0.267 s.
Past degree seven the network’s raw output is empty in every run (n = 1000, the released implementation’s configuration; a pre-registered re-run at the paper’s smaller embedding finds the same, 0/15 at d = 8, 12, 20, D39). The paper's own experiments sit at degrees three and five.
Uniform random d-regular graphs. Quality is the approximation ratio against McKay's upper bound — the same metric the critique used, so these numbers sit directly beside the published ones. Every set below was verified independent and maximal by a separate checker.
“As published” and “published configuration” on this page mean the
authors’ released example implementation’s setting, which uses an embedding of
int(sqrt(n)) at every n and a patience of 100; the paper’s text uses
int(cbrt(n)) below n = 105 (10 rather than 31 at n = 1000) and a patience
of 103. A pre-registered re-run at that smaller embedding (D39) reproduces the
ceiling — 0/15 non-empty raw outputs at d = 8, 12, 20, while d = 3 and 5 still
escape in 10/10 runs. Every other result on this page uses the released setting.
| n | method | density | AR | wall clock | trials |
|---|---|---|---|---|---|
| d = 3 upper bound ρUB = 0.45537 · 1RSB optimum AR = 0.99 | |||||
| 1,000 | DGA (degree-based greedy) | 0.43260 | 0.9500 | 50 µs | 5 |
| PI-GNN, as published | 0.41820 | 0.9184 | 13.74 | 5 | |
| null control: all-ones | 0.39660 | 0.8709 | 103 µs | 5 | |
| GA (random greedy) | 0.37880 | 0.8319 | 53 µs | 5 | |
| 10,000 | DGA (degree-based greedy) | 0.43270 | 0.9502 | 579 µs | 5 |
| PI-GNN, as published | 0.41612 | 0.9138 | 118.3 | 5 | |
| null control: all-ones | 0.39386 | 0.8649 | 473 µs | 5 | |
| GA (random greedy) | 0.37442 | 0.8222 | 527 µs | 5 | |
| 100,000 | DGA (degree-based greedy) | 0.43254 | 0.9499 | 0.00664 | 3 |
| PI-GNN, as published | 0.41516 | 0.9117 | 2,176 | 1 | |
| null control: all-ones | 0.39588 | 0.8694 | 0.004563 | 3 | |
| GA (random greedy) | 0.37547 | 0.8245 | 0.006074 | 3 | |
| d = 5 upper bound ρUB = 0.38443 · 1RSB optimum AR = 0.987 | |||||
| 1,000 | DGA (degree-based greedy) | 0.35300 | 0.9182 | 77 µs | 5 |
| PI-GNN, as published | 0.34000 | 0.8844 | 20.71 | 5 | |
| null control: all-ones | 0.32800 | 0.8532 | 130 µs | 5 | |
| GA (random greedy) | 0.29900 | 0.7778 | 73 µs | 5 | |
| 10,000 | DGA (degree-based greedy) | 0.35672 | 0.9279 | 798 µs | 5 |
| PI-GNN, as published | 0.34144 | 0.8882 | 191.2 | 5 | |
| null control: all-ones | 0.32670 | 0.8498 | 718 µs | 5 | |
| GA (random greedy) | 0.30190 | 0.7853 | 727 µs | 5 | |
| 100,000 | DGA (degree-based greedy) | 0.35665 | 0.9277 | 0.009042 | 3 |
| null control: all-ones | 0.32689 | 0.8503 | 0.007353 | 3 | |
| GA (random greedy) | 0.30152 | 0.7843 | 0.008772 | 3 | |
| d = 20 no published bound adopted; density reported | |||||
| 1,000 | DGA (degree-based greedy) | 0.17000 | — | 195 µs | 5 |
| PI-GNN, as published | 0.13540 | — | 20.65 | 5 | |
| null control: all-ones | 0.15720 | — | 268 µs | 5 | |
| GA (random greedy) | 0.13820 | — | 186 µs | 5 | |
| 10,000 | DGA (degree-based greedy) | 0.17324 | — | 0.002103 | 5 |
| PI-GNN, as published | 0.13956 | — | 272.7 | 5 | |
| null control: all-ones | 0.15880 | — | 0.00234 | 5 | |
| GA (random greedy) | 0.13956 | — | 0.00202 | 5 | |
| 100,000 | DGA (degree-based greedy) | 0.17371 | — | 0.02987 | 3 |
| null control: all-ones | 0.15873 | — | 0.03015 | 3 | |
| GA (random greedy) | 0.13948 | — | 0.02888 | 3 | |
The replication holds: at d = 3, n = 10,000 (5 seeds each) the greedy lands at AR 0.9502 against the critique's published ≈0.95, and PI-GNN at 0.9138 against their ≈0.92. Both sides of their figure reproduce on independently written code.
None of the five documents in the exchange — the original paper, two Comments and two Replies — separates what the network contributes from what the post-processing contributes. Both are needed to produce a reported score, and only one of them is the thing under test.
The separation is available from data already collected: compare the raw thresholded output against the score after repair and maximalization.
| method | d | n | raw output | after post-processing | share added |
|---|---|---|---|---|---|
| PI-GNN | 3 | 1,000 | 417 | 418 | +0.3% |
| PI-GNN | 3 | 100,000 | 41,321 | 41,516 | +0.5% |
| PI-GNN | 5 | 1,000 | 332 | 340 | +2.2% |
| PI-GNN | 20 | 1,000 | 0 | 135 | +100.0% |
| null: all-ones | 3 | 1,000 | 1,000 | 397 | — |
| null: all-ones | 3 | 100,000 | 100,000 | 39,588 | — |
| null: all-ones | 5 | 1,000 | 1,000 | 328 | — |
| null: all-ones | 5 | 100,000 | 100,000 | 32,689 | — |
| null: all-ones | 20 | 1,000 | 1,000 | 157 | — |
| null: all-ones | 20 | 100,000 | 100,000 | 15,873 | — |
This is the sharper form of the comparison, and it forecloses the obvious objection. A reader might suspect the repair-and-maximalize step flatters the network; the decomposition shows it contributes 0.3–2.2% where the network is working, so the measured gap to greedy is the network’s own.
A second control puts an all-ones bitstring — every node selected, carrying no information about the instance — through that same pipeline. It reaches AR 0.8649 at d = 3, n = 10,000 against the trained network’s 0.9138, and at d = 20, n = 10,000 it wins outright (0.15880 against 0.13956). One caveat stated plainly: on an all-ones input every node has the same conflict degree, so the repair pass reduces to iterated maximum-degree deletion — a real heuristic reading the graph. The bitstring carries no information; the pipeline does. That is why the decomposition above, not this control, is the primary evidence.
A trained network scoring below the all-ones null control is not a hyperparameter problem: the next section gives the mechanism, and the one after it tests a pre-registered tuning budget, a post-hoc round, and the GraphSAGE + P = 10 configuration the Reply describes (as implemented here). At d = 20 none of them returns a non-empty raw output (0 of 57 runs).
Above degree seven the network's raw output — before any post-processing — is all zeros. My first explanation was wrong, and the way it was wrong is the finding.
I thought the culprit was the diagonal. The published loss is
L(p) = pᵀQp with Qᵢᵢ = −1, a pure quadratic
form with no linear term, so ∇L(0) = 0 exactly: the empty set is a
critical point at every degree. Fixing that should rescue it. It does not. Changing the
diagonal to −Σpᵢ removes the critical point, and d = 20
still returns nothing.
The real mechanism is visible when you compare the final training loss against the exact uniform optimum of the modified, linear-diagonal objective (a post-hoc change, not the published loss) — the value a network that learned nothing at all would reach:
| arm | d | uniform p* | closed-form L* (mean field) | observed final loss | outcome |
|---|---|---|---|---|---|
| modified objective, lr 1e-4 | 3 | 0.1667 | -83.333 | -423.254 | symmetry-broken |
| modified objective, lr 1e-4 | 5 | 0.1000 | -50.000 | -341.256 | symmetry-broken |
| modified objective, lr 1e-4 | 20 | 0.0250 | -12.500 | -12.499 | stuck at the mean field |
| modified objective, lr 1e-3 | 3 | 0.1667 | -83.333 | -421.949 | symmetry-broken |
| modified objective, lr 1e-3 | 5 | 0.1000 | -50.000 | -342.936 | symmetry-broken |
| modified objective, lr 1e-3 | 20 | 0.0250 | -12.500 | -12.519 | stuck at the mean field |
A caution this page’s first draft got wrong: matching L* does not by itself prove
the configuration is uniform. The loss is second-order blind to zero-mean scatter around
the stationary point, and two of the three seeds land slightly below L*, which no
uniform configuration can do. The trapped-at-the-saddle claim therefore rests on measuring
the output vector directly (mean p = 0.0261, standard deviation 27–29% of p*,
maximum 0.074 — recorded in results/pstats.jsonl), not on the loss
agreement alone.
For the published quadratic diagonal, that uniform solution happens to be p = 0. So “collapses to the empty set” and “stuck at the mean field” are the same event — the formulation simply makes the symmetric solution be nothing, and the fixed 0.5 threshold then reports it as nothing. This also explains why the rescue attempts tried here failed: learning rate, patience and a larger embedding (256) do not cause symmetry breaking. Neither does a smaller one: at the paper’s int(∛n) = 10 the raw output is empty in 15/15 runs at d = 8, 12, 20 (D39).
int(cbrt(n)) = 10 embedding a
pre-registered re-run finds the same ceiling: 0/15 non-empty at d = 8, 12, 20 (Wilson 95%
CI [0.000, 0.204]), with 10/10 escaping at d = 3 and 5 (D39). The transition sits below the clustering transition at
d > 16 that the critique proposed as the genuinely hard regime, so the method stops working
well before the problem becomes hard.A tuning budget was fixed in writing before any sweep ran — four learning rates × two penalty values × three seeds, at d = 3, 5 and 20 — followed by a separately-labelled post-hoc round with a bigger embedding, longer patience and the repaired objective.
That budget was aimed at the wrong target, and this page previously said otherwise. It was designed against the assumption that the Replies argue the network was under-tuned. Reading the Reply to Angelini (arXiv:2302.03602) shows the defence is two specific changes: “by simply setting P = 10 we find even further consistent improvements. Specifically, for d = 3 at P = 10 GraphSAGE achieves approximation ratios of AR ~ 0.947, on par with the DGA-based results…” — a different architecture and a penalty five times the published one (P = 10 against 2), well above the pre-registered grid, which stopped at P = 3 and never implemented GraphSAGE. The claim that the objection had been “answered on its own terms” is withdrawn.
| d | DGA | PI-GNN published | best pre-registered | best post-hoc | null control | non-empty runs |
|---|---|---|---|---|---|---|
| 3 | 0.9479 | 0.9223 | 0.9231lr 0.0001, penalty 3 | 0.9296modified_linear | 0.8704 | 24/24post-hoc 21/21 |
| 5 | 0.9148 | 0.8818 | 0.8870lr 0.0001, penalty 3 | 0.8931modified_linear_lr1e-3 | 0.8506 | 24/24post-hoc 21/21 |
| 20 | 0.16967 | 0.13500 | 0.13500lr 0.0001, penalty 2 | 0.13500posthoc_lr1e-3 | 0.15767 | 0/24post-hoc 0/21 |
On the same seeds 0–2 (every column above), the pre-registered grid buys at most 0.5 percentage points of AR, and the post-hoc round at most 1.1 — its best arms are the modified objective, a change to the loss rather than a tuned setting — and neither approaches the greedy. (The d = 20 column reports densities, as no published bound is adopted there.) At d = 20 every one of the 24 pre-registered runs and every one of the 21 post-hoc runs had an empty raw output: 0 non-empty results out of 45.
GraphSAGE with a mean aggregator was implemented here (the Reply gives no code for it) and crossed with the penalty, giving the four arms below at n = 1000, seeds 0–2, with DGA on the same seeds.
| configuration | AR / density | non-empty | mean raw output |
|---|---|---|---|
| d = 3 approximation ratio | |||
| GCN, P = 2 (as published) | 0.9223 | 3/3 | 419 |
| GCN, P = 10 | 0.9289 | 3/3 | 418 |
| GraphSAGE, P = 2 | 0.8513 | 3/3 | 388 |
| GraphSAGE, P = 10 (the Reply’s configuration) | 0.8777 | 3/3 | 400 |
| DGA (greedy) (same seeds 0–2) | 0.9479 | — | — |
| d = 5 approximation ratio | |||
| GCN, P = 2 (as published) | 0.8818 | 3/3 | 333 |
| GCN, P = 10 | 0.8073 | 1/3 | 104 |
| GraphSAGE, P = 2 | 0.8151 | 3/3 | 313 |
| GraphSAGE, P = 10 (the Reply’s configuration) | 0.8255 | 3/3 | 217 |
| DGA (greedy) (same seeds 0–2) | 0.9148 | — | — |
| d = 20 density; no published bound adopted | |||
| GCN, P = 2 (as published) | 0.1350 | 0/3 | 0 |
| GCN, P = 10 | 0.1350 | 0/3 | 0 |
| GraphSAGE, P = 2 | 0.1350 | 0/3 | 0 |
| GraphSAGE, P = 10 (the Reply’s configuration) | 0.1350 | 0/3 | 0 |
| DGA (greedy) (same seeds 0–2) | 0.1697 | — | — |
At d = 3, P = 10 moves the GCN slightly in the direction the Reply claims (0.9223 to 0.9289) and still falls short of greedy’s 0.9479 on the same seeds. At d = 5 it makes matters markedly worse (0.8818 to 0.8073, with a non-empty raw output in only 1/3 runs), which the Reply does not mention. These arms were run at d = 3, 5 and 20 only.
One result here is a failure to reproduce, and is reported as nothing more. The Reply states GraphSAGE at P = 10 reaches AR ~ 0.947 for d = 3. This implementation reaches 0.8777 — below its own GCN. The likeliest explanation is that this GraphSAGE is not equivalent to theirs: the Reply gives no architecture detail and no code for it. That is a gap in what can be checked from the published record, not evidence against their number.
The Reply also answers the high-degree benchmark on relevance rather than capability: it calls the d > 16 instances “an interesting academic exercise” but is “not convinced about its practical usefulness”, because real problems are not random regular graphs. That is a different objection from the one measured here. This page concerns random d-regular graphs only, where the ceiling sits at d ≈ 7 (raw output empty in every run from d = 8), below the regime the Reply calls academic; it says nothing about structured real-world graphs.
The published 104 figure was never a controlled measurement. The critique timed its greedy on the authors' own laptop and read the network's times off Figure 5 of the paper it was criticising — different hardware, different conditions. Everything below is one machine, one job at a time, nothing concurrent.
The reference implementation builds a dense torch.zeros(n, n) QUBO matrix. At n = 1,000,000 that is 1.0e+12 float32 entries — 3.6 TiB. The full paper never describes the QUBO construction used at that scale — the word “dense” does not appear in it — so this is a property of the released example implementation, not of a published method description. The sparse reformulation used here is mathematically identical and is what makes any test at this scale possible — a deliberate improvement in the claim’s favour.
| n | ms / epoch | × per decade | local exponent | embedding |
|---|---|---|---|---|
| 1,000 | 1.10 | — | — | 31×15 |
| 10,000 | 7.98 | 7.3× | 0.86 | 100×50 |
| 100,000 | 163.22 | 20.4× | 1.31 | 316×158 |
| 1,000,000 | 47,387.71 | 290.3× | 2.46 | 1000×500 |
The paper sets dim_embedding = int(sqrt(n)) only for n ≥ 105, and int(cbrt(n)) below that; the released notebook, and this study, use int(sqrt(n)) at every n (the embedding column above). From n = 105 up the two agree: dim_embedding = int(sqrt(n)), hidden = dim/2 makes the first layer a dense (n × dim) @ (dim × hidden) matmul, so arithmetic grows as n²/2 regardless of how sparse the graph is. Measured time does not track that cleanly: it grew more slowly than that arithmetic below n = 105, and about 3× faster than it in the last decade, where the 14.9 GiB working set goes memory-bound.
That run was stopped by its wall-clock budget, not by convergence, so it is a lower bound on time-to-parity and nothing more. It does not show the network would never converge. Its raw output fell from 137,531 nodes (epoch 3) to 220 (epoch 36) with the loss still positive, which resembles the high-degree collapse. A traced n = 104 run (one seed) shows the same dip, to 8 nodes at epoch 450, then recovers to 4,097 by epoch 3,000; the n = 106 run stopped far earlier in its schedule than that minimum, so the decay is consistent with the normal early transient (D34). The decay rate depends strongly on n (raw-size ratio 0.95 / 0.56 / 0.0016 over the first 36 epochs at n = 103 / 104 / 106), and that is not explained here.
Projection, and labelled as one. Epochs-to-convergence is stable across three decades (12,361 / 14,812 / 13,321; the n = 105 figure is a single run), so the epoch count transfers. Using the counts measured at the two sizes nearest 106 (13,321 at n = 105, 14,812 at n = 104) and the two measured per-epoch costs at n = 106 (41.75 s from the budgeted run, 47.39 s from the probe), a full run at the released implementation’s configuration would take 6.4–8.1 days, against DGA’s 0.2671 s on the same instance — a factor of roughly 2.1–2.6 million×. The critique claimed 104; this same-host figure is ~208–263× larger, because their 104 compared their own laptop’s greedy against GPU timings read off Figure 5 of the paper they were criticising. Every GNN cost at n = 106 is CPU-only: the faster-device rule (D12) was never executed at this size, an unexecuted protocol step (D33).
The Reply to Angelini reports an updated post-processing (its scaling improves from ~n2.0 to ~n1.0, total run time from ~n1.7 to ~n0.8). The timings on this page are of the original released implementation, followed by this study’s own repair pass (code/repair.c); the updated routine is not tested here. The projection above counts training epochs only.
The approximation ratios above are measured against McKay's upper bound, which is not tight. To check the scale is sane I solved small instances to proven optimality with an integer program.
| d | n | exact density | / ρUB | status |
|---|---|---|---|---|
| 3 | 100 | 0.44000 | 0.9662 | proven optimal |
| 3 | 200 | 0.44700 | 0.9816 | proven optimal |
| 5 | 100 | 0.37000 | 0.9625 | proven optimal |
| 5 | 200 | 0.37200 | 0.9677 | time limit — lower bound only |
| 20 | 100 | 0.18000 | — | proven optimal |
| 20 | 200 | 0.18000 | — | time limit — lower bound only |
Proven optima sit at 0.96–0.98 of the bound at these sizes, consistent with the 1RSB asymptotic value of 0.990. Runs that hit the time limit are reported as lower bounds, not optima.
Stated plainly, because a verdict that cannot be attacked is not worth much:
559 measurement records on one Apple M1 Pro (10 core, 32 GB). Greedy baselines are C with a bucket queue, so the timing comparison is not distorted by a slow baseline. Instances are uniform random d-regular graphs, verified simple and exactly regular at generation. Seeds control the graph, the network initialisation and the tie-break together.
# Python 3.12
pip install -r requirements.txt
cc -O3 -march=native -o code/greedy code/greedy.c # drop -march=native if your compiler rejects it
cc -O3 -march=native -o code/repair code/repair.c
python code/test_port.py # run first — forward-math port-equivalence check; needs nothing else
python code/analyze.py # summary tables from the committed results
python code/build_report.py && python code/make_site.py # regenerate report.html and site/
python code/fetch_refs.py # optional: the papers + the reference implementation into refs/
# To re-measure rather than re-read, write to a NEW file — drivers skip cells already in --out:
python code/runner.py --out results/rerun_phase1.jsonl --d 3 5 20 --n 1000 10000 --seeds 0 1 2 3 4
# collapse_sweep.py, escape_prob.py, escape_d10plus.py and reply_defence.py take no --out: each
# appends to a fixed file in results/ and skips cells already there, so move that file aside first.
# mv -n never overwrites an earlier move-aside; the published files are tracked by git, so
# `git checkout -- results/` restores them.
mkdir -p results.published
mv -n results/collapse.jsonl results/escape.jsonl results.published/
python code/collapse_sweep.py && python code/escape_prob.py && python code/escape_d10plus.py # escape table: 121 of its 127 runs (the other 6 are phase-1 runs, re-measured by runner.py above)
mv -n results/reply_defence.jsonl results.published/ && python code/reply_defence.py # the Reply’s defence, GraphSAGE + P=10 (D37)
Code, the raw results/*.jsonl and the decision log are at
github.com/ryanonline1234/gnn-vs-greedy; its README carries the same
commands.