The Validation of Cross-Sector Batching: How Phase 3 of the cuzk SNARK Proving Engine Was Systematically Tested and Proven

Introduction

In the development of high-performance cryptographic proving systems, the gap between architectural design and empirical validation can be vast. A design that looks correct on paper may conceal race conditions, memory pathologies, or performance regressions that only emerge under real hardware with production-scale data. This article chronicles a pivotal chapter in the development of cuzk — a persistent, GPU-resident SNARK proving engine for Filecoin's Proof-of-Replication (PoRep) protocol — in which a systematic four-test validation campaign proved that cross-sector batching worked correctly, quantified its throughput improvement at 1.42×, documented its memory trade-offs, and closed Phase 3 of the project before handing off to Phase 4 compute-level optimizations.

The chunk spans 55 messages (indices 715–769) and represents one of the most concentrated periods of empirical engineering in the entire cuzk project. What makes this chunk remarkable is not just the technical achievement — though the 1.42× throughput improvement on an RTX 5070 Ti with real 32 GiB PoRep data is genuinely impressive — but the methodology on display: the systematic decomposition of validation into orthogonal test scenarios, the careful correlation of client-side timings with server-side logs, the rigorous memory analysis that confirmed predictions within 3%, and the disciplined documentation workflow that ensured every finding was permanently recorded.

The Baseline: Establishing the Pre-Batching Performance

Before any batching could be validated, the assistant needed a reliable baseline. This baseline had already been established in earlier messages: a single PoRep C2 proof processed through the Phase 2 pipeline (async overlap between CPU synthesis and GPU proving) completed in approximately 89 seconds — 55.6 seconds for CPU-bound circuit synthesis across 10 partitions, and 34.4 seconds for GPU-bound multi-scalar multiplication (MSM) and number-theoretic transform (NTT) operations. The peak memory footprint was 202.9 GiB RSS, with the 44 GiB SRS (Structured Reference String) parameters resident in memory throughout.

The baseline was not merely a number — it was a validated prediction. The architecture documents had estimated these figures, and the assistant confirmed them through direct measurement using a memory monitor script that sampled RSS from /proc every second. The baseline CSV contained 628 samples spanning the full test duration, giving the assistant confidence in both the peak and average memory values. This baseline would become the reference point against which all batching improvements would be measured.

The Architecture Under Test: Phase 3 Cross-Sector Batching

The Phase 3 feature being validated was the BatchCollector — a component that intercepts proof requests before they reach the synthesis pipeline, groups them by circuit type, and holds them until either a configurable batch size is reached (max_batch_size) or a timeout expires (max_batch_wait_ms). When a batch is flushed, the collector invokes synthesize_porep_c2_multi{num_sectors=N}, which synthesizes all N sectors' circuits together in a single rayon-parallelized pass. The GPU then proves the combined circuits sequentially, and a split_batched_proofs mechanism separates the combined output into per-sector Groth16 proofs.

The key insight is that CPU synthesis is already saturating all available cores when processing a single sector's 10 circuits. Adding another sector's 10 circuits keeps those same cores busy for the same wall-clock duration — the synthesis time is essentially constant regardless of how many sectors are in the batch. The GPU, which processes circuits sequentially, does scale linearly. The net effect is that two proofs complete in roughly the time it would take to synthesize one proof plus prove two, yielding an amortized throughput gain.

Not all proof types are batchable. WinningPoSt proofs use fundamentally different circuits and SRS parameters and must bypass the collector entirely. The architecture classifies proof types at the entry point, routing batchable types (PoRep, WindowPoSt) through the collector and non-batchable types (WinningPoSt) directly to the single-proof synthesis path.

Test 1: The Timeout Flush — Validating the Fallback Mechanism

The first test was the most basic correctness check: what happens when a single proof is submitted to a daemon configured with max_batch_size=2? If the BatchCollector waits forever for a partner that never arrives, the system would hang on every singleton submission — a catastrophic failure for any production deployment.

The assistant submitted a single PoRep proof via cuzk-bench single -t porep against a daemon configured with max_batch_size=2 and max_batch_wait_ms=30000. The result, captured in [msg 735], was unambiguous: the proof completed in 120.3 seconds total, with a queue time of 30,258ms — within 0.86% of the configured 30,000ms timeout. The synthesis phase took 55.6 seconds (10 circuits), the GPU phase took 34.4 seconds, and the output was a valid 1920-byte Groth16 proof. The daemon logs confirmed synth_timeout_flush{batch_size=1}.

This test validated several things simultaneously. First, the timeout mechanism worked correctly — the collector did not hang. Second, the overhead of the timeout mechanism was negligible (258ms against a 30-second wait). Third, the SRS preloading worked — srs=0 ms confirmed that the 44 GiB parameters loaded at startup were reused without additional disk I/O. Fourth, the single-proof path through the batch collector added no synthesis or GPU overhead beyond the baseline.

The assistant's methodology here is worth noting. It captured both the daemon's internal timing breakdown and independent wall-clock timestamps (Start: 22:33:24), providing dual verification. It also immediately followed up by examining daemon logs to confirm the synth_timeout_flush message, not trusting the client-side output alone. This pattern — measure twice, verify internally and externally — characterized the entire testing campaign.

Test 2: The Batch of Two — Proving Synthesis Amortization

The second test was the core validation of the entire Phase 3 architecture: submit two concurrent PoRep proofs and verify that they are synthesized together as 20 circuits in the same time as a single proof's 10 circuits.

The assistant used cuzk-bench batch -t porep --count 2 -j 2 to submit both proofs simultaneously ([msg 738]). The results were compelling:

Test 3: The Overflow Test — Validating Pipeline Interaction

The third test pushed the system beyond its configured batch size. With max_batch_size=2, three concurrent proofs should result in the first two forming a batch (immediate flush) and the third waiting for a timeout before being processed singly. Critically, the third proof's synthesis should overlap with the first batch's GPU phase, demonstrating that the Phase 2 async pipeline continues to function alongside batching.

The assistant submitted three proofs with concurrency 3 ([msg 741]). The results told a clear story:

Test 4: The WinningPoSt Bypass — Validating Non-Batchable Types

The fourth and final test addressed a critical architectural requirement: non-batchable proof types must bypass the BatchCollector entirely. WinningPoSt proofs use different circuits and SRS parameters than PoRep, complete in milliseconds rather than minutes, and are time-sensitive (must be generated within a 30-second window to avoid losing block rewards). Any delay introduced by the batching infrastructure would be unacceptable.

The assistant submitted a single WinningPoSt proof via cuzk-bench single -t winning ([msg 744]). The result was definitive:

The Memory Analysis: Theory Meets Measurement

Throughout the testing campaign, a background memory monitor sampled the daemon's RSS every second. The assistant had not examined this data during the tests — it waited until all functional tests passed before turning to the memory analysis in [msg 747].

The results were revealing:

The Documentation Workflow: From Data to Institutional Knowledge

With all tests passing and the memory analysis complete, the assistant faced a final task: transforming transient experimental data into durable engineering knowledge. This workflow, spanning messages 750–767, is a case study in disciplined documentation.

The assistant began by verifying the system state — checking that all background processes were stopped ([msg 750]), confirming the working tree was clean ([msg 752]), and surveying the existing project document structure (<msg id=753-754>). It then read specific sections of cuzk-project.md to identify the correct insertion point for Phase 3 results ([msg 756]).

The actual documentation edit in [msg 758] added a comprehensive "E2E Test Results" section with 121 lines of structured content, including the four-test matrix, performance comparison table, memory analysis, and architectural confirmation. The assistant then fixed section numbering that was disrupted by the insertion (<msg id=759-763>), updated the "Stopping Points & Cumulative Impact" roadmap table with measured numbers ([msg 764]), and finally committed the changes as 353e4c2a with the message: &#34;docs(cuzk): Phase 3 E2E test results — batch=2 validated at 1.42x throughput&#34; ([msg 767]).

The commit message itself is a model of concise technical communication. It lists all four tests, their results, the key performance numbers (55s synthesis for 20 circuits, 69s GPU, 62.7s/proof amortized), the memory trade-off (360 GiB vs 203 GiB), and the updated roadmap table. Anyone reading the git log six months later would understand exactly what Phase 3 achieved and how it was validated.

The Transition: Closing Phase 3, Opening Phase 4

The final message of the chunk ([msg 768]) is a todo list update — every Phase 3 testing task marked as completed. This seemingly trivial administrative act carries significant weight: it is the explicit boundary between phases. The assistant then asked the user for direction on Phase 4, and the user responded: "Proceed to phase 4 @c2-optimization-proposal-4.md."

The Phase 4 work that followed — implementing SmallVec for LC Indexer heap allocations (A1), pre-sizing of ProvingAssignment (A2), parallelization of B_G2 CPU MSMs (A4), pinning of a/b/c vectors via cudaHostRegister (B1), and per-MSM window tuning (D4) — would be measured against the Phase 3 baseline. The 89-second single-proof time and 62.7-second batch=2 amortized time became the reference points for regression detection. When the initial Phase 4 benchmark showed a regression to 106 seconds, the assistant immediately reverted the problematic change (A2's pre-sizing usage) and added detailed CUDA timing instrumentation to enable precise A/B testing.

Conclusion: What This Chunk Reveals About Engineering Methodology

The Phase 3 validation campaign documented in this chunk is a masterclass in systematic engineering. The assistant decomposed a complex architectural feature into four orthogonal test scenarios, each targeting a distinct behavioral requirement. It established a reliable baseline before testing, measured both client-side timings and server-side logs for cross-verification, analyzed memory data with appropriate granularity, and documented every finding in the project's permanent record.

The quantitative results speak for themselves: 1.42× throughput improvement, synthesis cost fully amortized, memory predictions validated within 3%, and all four tests passing on real hardware with production-scale data. But the lasting value of this chunk is not the numbers — it is the methodology that produced them. The disciplined separation of measurement from interpretation, the dual verification of timing data, the systematic testing of every behavioral branch, and the careful documentation workflow are patterns that generalize to any complex engineering validation effort.

For anyone studying this conversation, the Phase 3 chunk serves as a template for how to validate a performance-critical feature in a complex distributed system: test the happy path, test the timeout fallback, test the overflow boundary, test the bypass path, measure memory at every step, and document everything before moving on. The 1.42× throughput improvement was the result; the methodology was the cause.