The Same RTX 5090, but the GPU Sat Idle — a CPU-Bound Go Solver and the Case for L2 Cache

작성자

카테고리:

← 피드로
DEV Community · soy · 2026-07-18 개발(SW)

soy

This is the second in a short series that benchmarks a single RTX 5090 by re-running published Go solvers — programs that don’t just play Go but prove the outcome under perfect play. The first installment was GPU-bound. This one is the mirror image: the graphics card sits almost idle while the CPU does all the work. So this piece is really about a CPU — and one number on it in particular.

What’s being solved

117 classic life-and-death puzzles from Cho Chikun’s well-known encyclopedia. Each asks one yes/no question: under perfect play, does this group live or die? You don’t need to read Go — what matters is that the solver proves the answer exhaustively rather than guessing a good move. Five-minute budget per problem.

I rebuilt the RZS-TT solver from Shih et al. (A Study of Solving Life-and-Death Problems in Go Using Relevance-Zone Based Solvers, IEEE Transactions on Games 2025; rlglab/study-LD-RZ) and ran the full set on one RTX 5090 + Core Ultra 9 285K, using the paper’s own config and 5-minute limit. The paper’s reference machine was a single GTX 1080Ti with a Xeon E5-2683 v3 — about a decade older on both the GPU and the CPU.

The GPU is a spectator

The network guiding this solver is tiny: 765,523 parameters, a 3.7 MB file. Across the entire ~11-hour run the RTX 5090 sat at about 23% utilization (measured; it never rose above ~27%). The GPU is not the bottleneck. The hot path is all on the CPU: expanding “relevance zones,” matching Go-shape patterns, and — above all — pounding a transposition table, a giant hash of already-proven positions. That access pattern is random and pointer-chasing: latency-bound work whose speed is dictated by the cache hierarchy, not by FLOPS.

So when the solver proves more problems inside the same 5-minute budget, the honest question is not “what did the 5090 do?” — it’s “what changed on the CPU in ten years?”

The numbers (with the caches lined up)

                          This run (Core Ultra 9 285K)   Paper (Xeon E5-2683 v3)
Microarchitecture         Arrow Lake / Lion Cove (2024)  Haswell-EP (2014)
Search threads used       2 of 24 cores                  2 of 14 cores
L2 cache  PER CORE        3 MB                           256 KB      <-- ~12x
L1 data   PER CORE        48 KB                          32 KB
L3 cache  (shared)        36 MB                          35 MB       (basically flat)
GPU utilization           ~23%  (idle, RTX 5090)         --  (GTX 1080Ti)
Problems proven (RZS-TT)  84 / 117  (72%)                68 / 106  (64%)

Enter fullscreen mode Exit fullscreen mode

(The problem sets differ — 117 vs the paper’s 106 — so compare the rate, 72% vs 64%.)

The case for L2

Look at which cache actually grew. The big shared L3 is essentially unchanged (35 → 36 MB). Clock and IPC rose too, of course. But the standout is private L2 per core: 256 KB → 3 MB, about 12× (measured on this machine via sysfs; the hypervisor flattens Arrow Lake’s hybrid P/E layout, but each core the solver runs on presents a private 3 MB L2).

Why that matters for this workload: a life-and-death proof search spends its time chasing entries in a transposition table that churns constantly. On Haswell, two search threads shared just 512 KB of L2 between them, so the hot part of that table spilled out to L3 and DRAM almost immediately — every spill is a stall. On Arrow Lake, each of the two threads gets its own 3 MB of fast L2: roughly 6 MB of hot working set now living a cache level closer to the core. For latency-bound pointer-chasing, that is exactly the lever that turns “ran out of time at 5 minutes” into “proved it with seconds to spare.” The fact that the shared L3 barely moved, while the private L2 grew 12×, is what points the finger specifically at L2 rather than at raw memory bandwidth.

To be clear, this is a hypothesis, not a measurement. The clean proof would be an L2 miss-rate comparison between the two machines — but hardware performance counters aren’t exposed under WSL2, so I can’t show cache-miss deltas here. What I can show is the access pattern (cache-latency-bound), the size contrast (12× L2, flat L3), and the outcome (more proofs in the same wall-clock budget). L2 is the leading suspect; higher clocks, a wider core, and DDR5 latency all contribute as well.

Either way, the headline framing has to be honest: this is a CPU-generation benchmark — Haswell 2014 to Arrow Lake 2024 — with the GPU nearly idle. Calling it “5090 vs 1080Ti” would be marketing, not measurement.

Same card, opposite bottleneck

This is why running both solvers on one machine is worth it. The killall-go solver used a bigger network and leaned GPU-bound, so there a clean per-worker GPU number fell out (~2× throughput). Same RTX 5090, opposite bottleneck. What a benchmark measures depends entirely on where the slow part is — and “I bought a 5090” tells you almost nothing until you know that.

Reproducibility and the frontier

Proof search is parallel and non-deterministic, so I ran the full set three times (two complete passes plus a single-threaded re-run of the borderline cases). 77 problems solved in both full passes; just 5 sit right at the 5-minute cliff and flip with the random seed. 33 problems went unproven by RZS-TT in every run — 3 from volume 1, 30 in volume 2’s hardest section. The pattern-table follow-up below narrows that.

How it ends: the full 2×2, and a method-limited frontier

The follow-up ran the pattern-table variant (RZS-PT) on the same machine — same CPU, same GPU, same problems, only the algorithm changed — so it isolates the algorithm contribution with zero cross-machine confound. The 2×2 came out:

                 1080Ti + Xeon (paper)   RTX 5090 + 285K (this machine)
RZS-TT           68 / 106  (64%)         84 / 117  (72%)
RZS-PT           83 / 106  (78%)         88 / 117  (75%)

Enter fullscreen mode Exit fullscreen mode

Same-machine TT → PT = +4 — but not a clean sweep: the pattern table rescued 5 problems and lost 1 (a case where its zone-reuse hurt), so the two variants are complementary, not one strictly better. Between them they prove 89/117.

That leaves 28 problems unproven by everything — the true frontier (3 from volume 1, 25 in volume 2’s hardest section). And these turned out to be method-limited, not time-limited: giving the solver a working potential-RZ pass cracked exactly zero of them, and each already burns 600k+ search nodes hitting the 5-minute wall. More time won’t help — a different kind of reasoning (seki / endgame theory) is what’s needed. (Chasing a crash in that potential-RZ code also turned up a genuine upstream bug, now reported with a fix.) (I later found this “method-limited” conclusion was wrong — see the update below.)

Update (2026-07-20): I was wrong — the frontier wasn’t method-limited

I concluded those 28 problems needed a smarter method. That was wrong, and the reason is almost embarrassing: every experiment above ran the solver on 2 threads — the config’s default, which I never touched.

Bumping it to 20 threads — same machine, same 5-minute budget, nothing else changed — cracked 27 of the 28. And not by brute force. Parallel search with “virtual loss” forces the threads to spread out: instead of two threads marching down the same doomed line, twenty fan out and one quickly finds the key move, so the proof closes with fewer nodes, not more. One problem went from 443,000 nodes → unsolved (2 threads) to 1,521 nodes → proved (20 threads).

Best-of across runs: 117 of 117 proved (100%) on a single RTX 5090 — the whole set. The last holdout, vol2_p262, resisted three runs (its node pool filling before the proof could close), and I nearly called it a genuine memory wall. Then a fourth run — same config, fresh random seed — found the proof before the pool filled (a 162 MB tree, 121 seconds). So all 28 frontier problems were the same kind: borderline, parallelism-limited, solvable given enough non-deterministic tries. None was a true memory wall — I was one seed away from another premature “unsolvable.”

Two things I’m keeping honest about. (1) I checked the new wins aren’t false positives — real proof trees (up to 343 MB), reproducible verdicts, sanity checks intact — but I have not run an independent proof-checker, which would be the gold standard. (2) Every run lived inside a hard memory cap, so when a huge proof tree hit the ceiling the solver was killed cleanly and WSL never crashed — the earlier memory-overflow lesson, learned the hard way, now enforced by the kernel.

The real lesson: a “negative result” is only valid inside the parameter space you actually searched. Ruling out seki databases and relevance-zone compression was correct in itself, but moot once the frontier turned out to be parallelism-limited, not method-limited. I nearly wrote “unsolvable with current methods.” They weren’t — I just hadn’t turned the obvious knob.

Full technical report — the 2×2, the L2 analysis, reproducibility, the frontier, the bug write-up, and threats to validity: soy-tuber.github.io/tsumego-rtx5090 (body in Japanese, English abstract at the top).

Reproduction of rlglab/study-LD-RZ (Shih, Wu, Wei, Hsu, Guei, Wu — IEEE ToG 2025) — full credit to the original authors; this work only swaps the hardware underneath and measures the difference.

원문에서 계속 ↗

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다