The Sweet Spot: How Systematic Benchmarking Tamed a 268 GiB Memory Spike in Groth16 Proving
Introduction
In the high-stakes world of Filecoin proof generation, every gigabyte of memory and every second of throughput matters. The SUPRASEAL_C2 pipeline, responsible for Groth16 proofs in Curio's storage proving system, had been through a gauntlet of optimizations — from PCIe transfer improvements to a split GPU proving API — each incrementally shaving seconds off proof time while carefully managing the system's ~200 GiB baseline appetite. But Phase 12's split API introduced a dangerous new problem: when CPU-side synthesis outpaced GPU consumption, synthesized partitions piled up in memory like cars in a traffic jam, driving peak RSS past 668 GiB and triggering out-of-memory (OOM) failures.
Message <msg id=3235> captures the triumphant conclusion of this optimization saga. In it, the assistant completes a systematic benchmark sweep across four partition_workers (pw) configurations, confirms that the memory backpressure fixes are working, identifies the DDR5 bandwidth wall as the ultimate limiter, and commits the changes. This short message is the capstone of a deep engineering effort that reduced peak memory by 268 GiB (40%) while preserving the throughput gains of the split API.
The Message: A Capstone of Systematic Optimization
The subject message is brief but dense with meaning:
510 GiB peak. Still within budget but 113 GiB more than pw=14. The throughput is worse, confirming that pw=12-14 is the sweet spot.
>
Let me now compile the results and commit: [todowrite] {"todos":[{"content":"Increase synthesis_lookahead channel capacity to match partition_workers (pw)","priority":"high","status":"completed"},{"content":"Hold partition permit through channel send (semaphore fix)","priority":"high","status":"completed"},{"content":"Build and verify compilation","priority":"high","status":"completed"},{"content":"Benchmark pw=10: 38.5-38.9s/proof, 317 GiB peak RSS","status":"completed",...}]}
On the surface, this is a simple status update. But to understand its significance, we must trace the engineering journey that led to it.
Why This Message Was Written: Context and Motivation
The message exists because of a specific failure mode introduced by Phase 12's split GPU proving API. Previously, the proving pipeline was synchronous: a partition would be synthesized, then immediately handed to the GPU, then freed. The split API decoupled these phases, allowing synthesis to run ahead of GPU consumption. This improved throughput by keeping both CPU and GPU busy simultaneously, but it created a dangerous unbounded queue: if synthesis was faster than GPU consumption (which it was, by design), completed partitions would accumulate in memory, each holding ~12 GiB of evaluation vectors (the a/b/c wires).
The three interventions — early a/b/c free, channel capacity auto-scaling, and holding the partition permit through channel send — were designed to impose memory backpressure. The early free cleared the 12 GiB evaluation vectors immediately after prove_start returned, since the GPU had already copied what it needed. The channel capacity auto-scaling ensured the synthesis→GPU channel was sized to max(synthesis_lookahead, partition_workers) instead of the hardcoded 1, preventing completed syntheses from blocking on send() while still holding large allocations. And holding the permit through send meant the semaphore bounded total in-flight outputs to partition_workers without adding latency, since the channel had room for all of them.
But did these fixes actually work? And what was the optimal partition_workers setting? The assistant needed empirical answers.
The Benchmark Sweep: How Decisions Were Made
The decision-making process visible in the surrounding messages is a model of systematic optimization. The assistant ran four benchmark configurations in sequence:
- pw=10 ([msg 3198]–[msg 3204]): 38.5–38.9 s/proof, 317–321 GiB peak RSS. The baseline, showing the fix worked but throughput was slightly below the Phase 12 target of 37.1 s/proof.
- pw=12 ([msg 3211]–[msg 3214]): 37.7–38.5 s/proof, 399.7 GiB peak RSS. A significant improvement — 1 s/proof faster than pw=10 for only ~80 GiB more memory. Critically, this configuration had previously OOM'd at 668 GiB; the fix reduced memory by 268 GiB (40%).
- pw=14 ([msg 3224]–[msg 3226]): 37.8 s/proof, 456.9 GiB peak RSS. Virtually identical throughput to pw=12, but consuming 57 GiB more memory.
- pw=16 ([msg 3233]–[msg 3234]): 38.4 s/proof, 510 GiB peak RSS. Worse throughput and 113 GiB more memory than pw=14. Each configuration was tested with 20 proofs at concurrency 20, using the same C1 input, the same GPU configuration (gw=2, gt=32), and the same daemon lifecycle (restarted between configurations to avoid cross-contamination). The assistant tracked both wall-clock throughput and peak RSS via a background monitoring loop. The conclusion is stated in the subject message: "pw=12-14 is the sweet spot." The data shows diminishing returns beyond pw=12: pw=14 offers no throughput improvement, and pw=16 is actually worse. The assistant correctly identifies the cause: "Higher pw means more memory pressure competing for the same DDR5 bandwidth." This is the DDR5 bandwidth wall — a fundamental system-level constraint that no amount of software optimization can bypass.
Assumptions and Their Validation
Several assumptions underpin the reasoning in this message:
The memory backpressure fix works. This assumption was validated dramatically: pw=12 went from OOM at 668 GiB to a stable 399.7 GiB. The three interventions (early free, channel capacity, permit holding) collectively eliminated the unbounded queue.
510 GiB is "within budget." The assistant notes this explicitly, referencing the system's 755 GiB total RAM. This assumption is correct — 510 GiB leaves ~245 GiB headroom for OS, other processes, and burst allocations.
The DDR5 bandwidth wall is the limiter. This is an inference, not a direct measurement, but it's consistent with the data: increasing pw from 12 to 16 adds 110 GiB of memory pressure but produces no throughput gain, suggesting the memory subsystem is saturated. The assistant's earlier waterfall timing analysis (in Segment 28) had already identified DDR5 bandwidth contention as the primary bottleneck.
Benchmark results are reproducible. The assistant ran pw=12 twice: the first run gave 37.7 s/proof, the second gave 38.5 s/proof. The variation (~0.8 s) is attributed to system noise and memory fragmentation effects. The assistant acknowledges this by reporting ranges ("37.7-38.5s/proof") rather than single values.
Input Knowledge Required
To fully understand this message, one needs:
- The Phase 12 split API architecture: Synthesis and GPU work are decoupled into separate async tasks connected by a channel. Partitions flow from synthesis→channel→GPU worker→finalization.
- The memory backpressure problem: When synthesis is faster than GPU consumption, completed partitions queue up in memory. Each partition holds ~12 GiB of evaluation vectors (a/b/c), so an unbounded queue can rapidly exhaust system memory.
- The three interventions: Early a/b/c free (deallocate evaluation vectors immediately after GPU copy), channel capacity auto-scaling (size channel to
max(lookahead, pw)), and permit held through send (semaphore released only after channel send succeeds). - The system constraints: 755 GiB total RAM, dual GPU workers (gw=2), 32 GPU threads (gt=32), DDR5 memory.
- The benchmark methodology: 20 proofs at concurrency 20, using the same C1 input, measuring both throughput and peak RSS.
Output Knowledge Created
This message produces several important outputs:
The optimal configuration is pw=12 with gw=2, gt=32. This delivers the best throughput-to-memory ratio: ~38 s/proof at ~400 GiB peak RSS.
The DDR5 bandwidth wall is the ultimate limiter. Beyond pw=12, additional parallelism produces no throughput gain because the memory subsystem is saturated. This is a hardware constraint, not a software one.
The memory backpressure fix is validated. The three interventions collectively eliminated OOM conditions, reducing peak memory by 268 GiB at pw=12.
The changes are ready to commit. The todowrite block marks all tasks as completed, signaling that the optimization work is done and the code is ready for production deployment.
The Thinking Process: Analytical Rigor Under Pressure
The reasoning visible in the surrounding messages reveals a disciplined, hypothesis-driven approach. When the first pw=10 benchmark showed 38.8 s/proof — slightly slower than the Phase 12 baseline of 37.1 s — the assistant didn't panic. Instead, it systematically investigated:
- Was the
eprintln!→tracing::debugconversion adding overhead? ([msg 3199]: "The eprintln wasn't the cause of the regression.") - Was the early a/b/c free adding deallocation latency? ([msg 3200]: analyzed GPU times, found mean 7.3s vs baseline 6.8s per partition.)
- Was it a statistical artifact of sample size? ([msg 3202]: ran 20 proofs instead of 15 to match baseline.) Each hypothesis was tested with data. The assistant compared GPU timing distributions, checked RSS profiles, and ran multiple trials. When pw=12 produced 37.7 s/proof — matching the baseline — the assistant correctly attributed the earlier discrepancy to sample size and memory layout effects. The decision to try pw=14 and pw=16 was driven by the question: "Can we push further?" The answer was no, and the assistant accepted this gracefully, documenting the DDR5 bandwidth wall as the fundamental constraint.
Conclusion
Message <msg id=3235> is a study in how to conclude an optimization effort. It doesn't overclaim — "pw=12-14 is the sweet spot" is a measured, evidence-based statement. It acknowledges constraints — the DDR5 bandwidth wall is a hardware limitation, not a failure of software design. It validates assumptions — the memory backpressure fix works, reducing peak RSS by 40%. And it transitions cleanly from investigation to action: "Let me now compile the results and commit."
In the broader narrative of the SUPRASEAL_C2 optimization journey, this message marks the moment when the team stopped optimizing and started shipping. The memory backpressure fix was the last piece needed to make Phase 12 production-ready. After 31 segments of deep engineering — spanning PCIe transfers, GPU synchronization, memory bandwidth analysis, and now backpressure design — the pipeline was stable, efficient, and well-understood. The sweet spot had been found.