The 91-Second Proof: Validating Batch-Mode Pipelined SNARK Proving
Introduction
In the development of high-performance cryptographic proving systems, few moments are as consequential as the first end-to-end validation that a new architecture matches the baseline. This article examines a single message from an opencode coding session — message index 599 — in which an AI assistant submits a Filecoin PoRep C2 Groth16 proof through a newly implemented batch-mode pipeline and receives a result of 91.2 seconds, matching the monolithic baseline of ~93 seconds. The message is deceptively simple: a bash command invocation followed by a structured output log. But beneath this surface lies the culmination of dozens of prior decisions, a critical performance regression that was identified and resolved, and the validation of an architectural direction that will shape the future of the cuzk proving engine.
The Message
The subject message reads in full:
Daemon is ready. Now submit a proof:
>
``bash # Submit a single PoRep C2 proof through the batch-mode pipeline /home/theuser/curio/extern/cuzk/target/release/cuzk-bench \ --addr "http://127.0.0.1:9821" \ single \ --type porep \ --c1 /data/32gbench/c1.json 2>&1 ``
>
``` 2026-02-17T19:56:08.438649Z INFO cuzk_bench: loading C1 output path=/data/32gbench/c1.json 2026-02-17T19:56:08.456767Z INFO cuzk_bench: submitting proof proof_type=porep
>
=== Proof Result === status: COMPLETED job_id: 8f5ae01f-662a-4801-ae83-e3b2944a2f8c timings: total=91170 ms (queue=22 ms, srs=0 ms, synth=55736 ms, gpu=35212 ms) wall time: 91278 ms proof: 1920 bytes (hex: 8cc2b70aa5d... ```
The message contains exactly two actions: a declarative statement ("Daemon is ready") and a single bash command invocation. The output is captured and displayed inline. The proof is 1920 bytes — exactly the expected size for a 10-partition PoRep C2 proof (192 bytes per partition × 10). The timing breakdown reveals queue time (22 ms), SRS loading time (0 ms, because the SRS is already resident), synthesis time (55.7 seconds), and GPU proving time (35.2 seconds).
Why This Message Was Written
This message was written to answer a single, urgent question: does the batch-mode pipeline produce correct proofs at acceptable performance? The context immediately preceding this message reveals why this question was so pressing.
In the prior chunk of work (segment 9, chunk 0), the assistant had completed an initial end-to-end GPU test of the pipelined PoRep C2 path. That test succeeded in producing a valid proof, but the performance was catastrophic: 611 seconds total, compared to the monolithic Phase 1 baseline of ~93 seconds. This was a 6.6× slowdown — a regression that would have made the entire Phase 2 pipeline architecture unusable for single-proof latency-sensitive workloads.
The root cause was clear from the timing breakdown: the per-partition approach serialized work that the monolithic implementation parallelized. In the monolithic seal_commit_phase2() function, all 10 partitions are synthesized in a single rayon parallel-for call, and all 10 partition proofs are generated in a single supraseal GPU call. The initial pipelined implementation, by contrast, looped over partitions sequentially: synthesize partition 0 → GPU prove partition 0 → synthesize partition 1 → GPU prove partition 1, and so on. This serialization meant that synthesis could not overlap across partitions, and GPU calls could not batch.
The assistant recognized this immediately. In the message preceding the subject ([msg 598]), the assistant noted: "The per-partition approach is ~6.6× slower for a single proof because it serializes work that could be parallelized (monolithic batches all 10 partitions in one rayon synthesis call and one supraseal GPU call)." The conclusion was that per-partition pipelining is designed for throughput on a stream of proofs (overlapping synthesis of proof N+1 with GPU proving of proof N), not for single-proof latency. But the immediate priority was to fix single-proof performance before adding the more complex async overlap architecture.
This led to the batch-mode fix implemented across messages 557–597: a new synthesize_porep_c2_batch() function that synthesizes all 10 partitions in a single rayon parallel call and proves them in one GPU call. The subject message is the validation of that fix.## How Decisions Were Made
The subject message itself does not contain explicit decision-making — it is an execution message, not a design message. But the decisions it embodies are visible in the structure of the command and the interpretation of the output.
Decision 1: Use the batch-mode pipeline for single proofs. The assistant chose to test the batch-mode path (synthesize_porep_c2_batch) rather than the per-partition path. This was not an obvious choice: the entire Phase 2 architecture was built around per-partition pipelining, and the batch-mode function was a late addition to fix a performance regression. By testing batch mode first, the assistant implicitly decided that single-proof latency was the binding constraint for the current validation step.
Decision 2: Measure with detailed timing breakdown. The cuzk-bench tool outputs queue, srs, synth, and gpu timing components. This granularity was the result of deliberate instrumentation added in earlier work (segment 5). The assistant could have simply checked whether the proof was valid, but instead used the timing data to verify that synthesis (55.7s) and GPU (35.2s) matched the monolithic baseline's hidden timings (~55s and ~35s respectively). This reveals a decision to validate not just correctness but performance parity.
Decision 3: Compare against the monolithic baseline. The assistant did not simply declare success; the next message ([msg 600]) immediately computes a comparison table showing per-partition (611.3s), batch-mode (91.2s), and Phase 1 monolithic (~93s). This comparison was not automatic — it required the assistant to recall the Phase 1 baseline from earlier testing and compute the delta. The decision to frame the result as "matching the baseline" rather than "fast enough" reflects a rigorous engineering mindset: the pipeline must not regress on the primary metric it was designed to improve.
Assumptions Made
Several assumptions are embedded in this message and its interpretation:
Assumption 1: The proof is valid. The assistant treats a 1920-byte proof as evidence of correctness. This is a reasonable heuristic — PoRep C2 proofs for 10 partitions are exactly 1920 bytes (192 bytes per partition × 10), and the hex output confirms the proof is non-trivial. However, the message does not include a verification step. The assistant assumes that if the pipeline produced a proof of the correct size without errors, the proof is valid. This assumption is justified by the architecture: the pipeline calls the same bellperson and supraseal code paths as the monolithic prover, so any structural error would likely manifest as a crash or wrong-size output.
Assumption 2: The SRS is already resident. The srs=0 ms timing confirms that the SRS manager's preloading worked correctly. The assistant assumed that starting the daemon with pipeline_enabled=true would trigger SRS preloading during startup (visible in the log line "preloading ..." from [msg 598]). This assumption was validated by the timing output.
Assumption 3: The daemon is healthy. The assistant waited 20 seconds after starting the daemon before submitting the proof, assuming this was sufficient for initialization. The daemon log shows it started at 19:55:37 and the proof was submitted at 19:56:08 — a 31-second gap, well within the 20-second sleep plus command execution time.
Assumption 4: The benchmark input is representative. The C1 output at /data/32gbench/c1.json is assumed to be a valid, realistic 32 GiB sector C1 output. If this input were corrupted or atypical, the timing results might not generalize.
Mistakes and Incorrect Assumptions
The most significant mistake in the surrounding context was the initial per-partition design itself. The assistant's first implementation of the pipelined prover assumed that processing partitions sequentially was acceptable, because the pipeline architecture was designed for throughput on a stream of proofs. This assumption was incorrect for single-proof latency — the primary use case for the initial deployment. The assistant corrected this by adding batch-mode synthesis, but the mistake reveals a tension in the design: the pipeline architecture optimizes for throughput (proofs per hour under continuous load), while the immediate validation metric was latency (time to first proof).
A subtler issue is the assumption that the batch-mode pipeline matches the monolithic baseline exactly. The timing breakdown shows synthesis at 55.7s and GPU at 35.2s, totaling 91.2s. The monolithic baseline was ~93s. The 2-second improvement is likely noise (different system load, thermal conditions), but it could also reflect the SRS residency benefit (the monolithic prover loads SRS on each invocation, while the pipeline keeps it resident). The assistant's next message ([msg 600]) attributes the improvement to SRS residency, which is a plausible but unverified hypothesis.
Input Knowledge Required
To understand this message, a reader needs knowledge in several domains:
Groth16 proofs and Filecoin PoRep. The message concerns Groth16 zero-knowledge proofs for Filecoin's Proof-of-Replication (PoRep) protocol. PoRep C2 (Commit Phase 2) is the GPU-intensive step that produces the final SNARK proof. The 1920-byte output corresponds to 10 partitions, each producing a 192-byte Groth16 proof.
Rayon parallel iteration. The synthesis timing (55.7s) represents CPU-bound circuit construction parallelized across all cores via rayon. The assistant's earlier work established that the monolithic prover uses rayon's parallelize to synthesize all partitions concurrently.
Supraseal GPU proving. The GPU timing (35.2s) represents the CUDA-based Groth16 prover (supraseal) that computes the multi-scalar multiplication (MSM) and number-theoretic transform (NTT) operations on GPU. The batch mode calls supraseal once with all 10 partition proofs, rather than 10 separate calls.
SRS (Structured Reference String). The SRS is a large (~32 GiB) parameter file required for Groth16 proving. The srs=0 ms timing indicates the SRS was already loaded in GPU memory, a key benefit of the SRS manager module.
The cuzk architecture. The reader must understand that cuzk is a proving daemon that replaces Curio's existing Go-based prover with a Rust gRPC service. The pipeline mode splits the monolithic prover into separate synthesis (CPU) and GPU phases, enabling SRS residency and future async overlap.
Output Knowledge Created
This message creates several important pieces of knowledge:
Empirical validation of the batch-mode pipeline. The primary output is a validated timing: 91.2 seconds for a 10-partition PoRep C2 proof through the pipelined architecture. This confirms that the batch-mode fix resolves the 6.6× performance regression.
Timing decomposition. The message provides a detailed breakdown: queue (22ms), SRS (0ms), synthesis (55.7s), GPU (35.2s). This decomposition is valuable for future optimization — it shows that synthesis is the dominant phase (61% of total time), suggesting CPU-side optimization would have the greatest impact.
Proof of pipeline correctness. The 1920-byte proof size confirms that the pipeline produces structurally correct proofs. This is non-trivial: the pipeline involves a bellperson fork with modified APIs, a new SRS manager, and restructured synthesis functions. Any of these changes could have introduced subtle bugs that would produce invalid proofs.
Baseline for future work. The 91.2s result becomes the new baseline for evaluating the async overlap architecture. If async overlap can reduce effective latency per proof on a continuous stream, the improvement will be measured against this number.
The Thinking Process
The assistant's reasoning is visible in the sequence of actions leading to this message. The prior message ([msg 598]) shows the daemon startup and the assistant's commentary: "Fast rebuild (only cuzk crates recompiled). Now let me test the batch-mode PoRep C2 pipeline." The assistant then starts the daemon, waits 20 seconds, and checks the log. The subject message picks up from there: "Daemon is ready. Now submit a proof."
The thinking process reveals several priorities:
- Correctness first. The assistant could have run a more complex test (multiple proof types, stress testing), but chose a single PoRep C2 proof — the most critical path and the one that had the regression.
- Minimal ceremony. The command is straightforward: point the bench tool at the daemon, specify the proof type and input file. No special flags for batch mode are needed because the engine automatically selects batch mode when pipeline is enabled.
- Interpretation is immediate. The assistant does not need to compute whether 91.2s is good — it already knows the monolithic baseline is ~93s, so 91.2s is immediately recognized as a success. The next message ([msg 600]) formalizes this with a comparison table.
- Forward planning. Even as the assistant validates the batch-mode fix, it is already thinking about the next step: the async overlap architecture. The subject message is not the end of a phase but a checkpoint that enables the next phase.
Conclusion
Message 599 is a validation message — the moment when weeks of design and implementation converge into a single empirical result. The 91.2-second proof time confirms that the batch-mode pipeline matches the monolithic baseline, resolving a critical performance regression and clearing the path for the async overlap architecture. But the message is also a testament to the engineering discipline of the session: the assistant did not simply declare success based on a correct proof size, but used detailed timing decomposition to verify performance parity across every phase of the proving pipeline. In doing so, it created a new baseline, validated the SRS manager, confirmed the bellperson fork's correctness, and set the stage for the next architectural leap.