From 200 GiB to Pipeline Architecture: The Complete SUPRASEAL_C2 Groth16 Optimization Journey
Introduction
In the world of decentralized storage, Filecoin's Proof-of-Replication (PoRep) mechanism is the cryptographic backbone of trust—a guarantee that storage providers are honestly keeping the data they claim to store. But this guarantee comes at a staggering computational cost. A single 32 GiB sector's C2 proof—the second and most expensive phase of PoRep—consumes nearly 200 GiB of RAM, demands a high-end GPU for minutes of compute, and forces operators to provision machines with 256 GiB or more of memory. In cloud rental markets where RAM is the dominant cost factor, this memory wall is the primary constraint on profitability.
This article synthesizes a deep-dive investigation into the SUPRASEAL_C2 Groth16 proof generation pipeline—a journey that began with a single, dense user prompt and culminated in four comprehensive documents, three composable optimization proposals, and a fundamental rethinking of how Filecoin proofs should be generated. The investigation spanned the full call chain from Curio's Go task layer through Rust FFI (bellperson) into C++/CUDA kernels (supraseal-c2), accounting for every GiB of the ~200 GiB peak memory, identifying nine structural bottlenecks, and proposing a continuous, memory-efficient proving pipeline architecture.
The Seed of Investigation
The journey began with a single message from a user ([msg 0]): "Dive into rust fil proofs / supraseal code — Investigate SUPRASEAL_C2 code path, especially understand proof synthesis step (cpu before GPU used), the memory usage (nearly 200GB peak for PoRep 10 partition Snark) and look for opportunities to reduce data / pipeline harder / merge/parallelise work. Mostly understand the current code deeply. Entry is extern/filecoin-ffi Commit2."
This 38-word prompt was a masterclass in expert-level problem specification. It identified a concrete pain point (200 GiB memory), scoped the investigation to a specific code path (SUPRASEAL_C2 via filecoin-ffi Commit2), isolated a suspected leverage point (CPU synthesis before GPU), and established a clear priority (understand first, optimize second). The assumptions embedded in this prompt—about the target, the entry point, and the nature of the problem—were sophisticated enough to guide a multi-week investigation, yet contained the seeds of the investigation's most important discovery: that the parallelism model, not synthesis itself, was the root cause of the memory problem [1].
The Parallel Exploration Strategy
The assistant's response ([msg 1]) was not a linear code reading exercise but a carefully orchestrated burst of parallel exploration. Faced with a system spanning four programming languages (Go, Rust, C++, CUDA), multiple FFI boundaries, and a complex feature flag propagation chain, the assistant decomposed the investigation into four concurrent threads:
- FFI Entry Point: Tracing the Go-side
SealCommitPhase2function through CGO bindings into the Rustlibfilcrypto.alibrary - Supraseal C2 Rust Code: Analyzing the Rust-side implementation in
supraseal-c2, including SRS loading and thegenerate_groth16_prooffunction - Proof Synthesis Details: Investigating the CPU-intensive circuit synthesis step in bellperson's
supraseal.rs - Build System: Mapping how supraseal is built and integrated into the Curio/filecoin-ffi build system This parallel exploration strategy was a methodological necessity. The system was too large and too interconnected for any single sequential read to capture. By decomposing the investigation into four orthogonal axes, the assistant ensured that each thread could focus deeply on its domain while the results could be synthesized into a unified understanding [2].
The Anatomy of a 200 GiB Memory Problem
The synthesis of the four exploration threads produced a watershed moment ([msg 5]): a comprehensive analysis that identified the root cause of the memory problem and provided a complete memory budget. The executive summary was devastatingly clear: "Peak memory is driven by 10 partition circuits being fully synthesized in parallel on CPU, each consuming ~16 GiB, totaling ~160 GiB of host RAM just for the ProvingAssignment data. The SRS (proving key) adds another ~30-70 GiB of pinned host memory. This explains the ~200 GiB peak."
The detailed memory budget broke down every significant consumer [6]:
| Component | Memory | Location | Lifetime | |-----------|--------|----------|----------| | 10 × ProvingAssignment (a,b,c) | ~120 GiB | Rust heap | Synthesis → end of C++ call | | 10 × aux_assignment | ~40 GiB | Rust heap (Arc) | Synthesis → end of C++ call | | 10 × DensityTrackers | ~0.5 GiB | Rust heap (bitvec) | Synthesis → end of C++ call | | SRS pinned host memory | ~48 GiB | cudaHostAlloc | Cached (persistent) | | split_vectors bitmaps | ~2 GiB | C++ heap | Duration of C++ fn | | tail_msm bases + scalars | ~20 GiB | C++ heap | Duration of C++ fn | | GPU: NTT workspace | 8-12 GiB | GPU VRAM | Per-circuit NTT | | GPU: batch add temp | ~2 GiB | GPU VRAM | Per batch add call | | GPU: MSM temp | ~2-4 GiB | GPU VRAM | Per MSM invocation | | Estimated host peak | ~200-230 GiB | | | | Estimated GPU peak | ~12-16 GiB | | |
The critical finding was the collect() barrier: all 10 circuits must finish synthesis before any GPU work begins, due to the .collect() barrier in synthesize_circuits_batch(). This is the root cause of the peak memory. If circuits could be pipelined to the GPU one-by-one, peak memory would drop dramatically.
The message also mapped the complete call chain from Curio's Go task layer down to CUDA kernels, providing a navigable map of a system that spans four languages and multiple FFI boundaries. It identified nine structural bottlenecks and ranked eight optimization opportunities by memory savings, performance impact, and difficulty [6].
The "Think Bigger" Pivot
The user's response to this analysis was transformative. Rather than accepting the initial findings as complete, the user challenged the assistant to "think bigger" ([msg 6]), asking for 2-3 high-impact improvements that consider the whole picture of a constant proving pipeline optimized for maximum proof throughput per dollar of system cost.
This pivot shifted the investigation from understanding a specific memory problem to architecting a fundamentally different proving pipeline. The assistant responded by asking about the target deployment model ([msg 9]), and the user answered: "Proofshare marketplace"—a heterogeneous fleet where all dimensions matter, with RAM as the top constraint.
A critical verification task ([msg 8]) corrected a major misunderstanding: the aux_assignment vector contained approximately 130 million elements per partition circuit—roughly 4 GiB—not the ~6 million initially assumed. This changed the memory picture dramatically and informed everything that followed [9].
The Three Optimization Proposals
The synthesis of these insights produced three composable optimization proposals ([msg 10]), each building on the previous to form a coherent architectural vision [11].
Proposal 1: Sequential Partition Synthesis
The first and most foundational proposal breaks the all-at-once batch model. Instead of synthesizing all 10 partitions in parallel and sending them to the GPU together, the assistant proposed processing them one at a time in a pipeline:
Partition 0: Synthesize → NTT+H on GPU → extract aux → free a,b,c
Partition 1: Synthesize → NTT+H on GPU → extract aux → free a,b,c
...
Partition 9: Synthesize → NTT+H on GPU → extract aux → free a,b,c
Then: Run all MSMs (L, A, B) using stored aux assignments
Then: Assemble 10 proofs
The memory savings are dramatic. Instead of 10 partitions' a,b,c vectors coexisting (~120 GiB), only one partition's vectors exist at a time (~12 GiB). The aux_assignments must still be accumulated (~40 GiB for all 10), but the peak drops from ~200 GiB to ~64-103 GiB—fitting comfortably in a 128 GiB machine.
The key insight enabling this proposal is that the 10 partition circuits are identical in structure (same R1CS, same constraint system) with different witness values. The current code synthesizes all 10 in parallel, but there is no mathematical reason they must all exist simultaneously. The decision to batch them was a design choice for GPU efficiency, but the memory cost of that choice is enormous.
The pipeline overlap also improves GPU utilization. Today, the GPU is idle for the entire ~60-120s synthesis phase. With sequential processing, the GPU can start working on partition 0's NTT+H as soon as it finishes synthesis, while the CPU moves on to synthesizing partition 1. The GPU is never idle after the first partition [11].
Proposal 2: Persistent Prover Daemon
The second proposal addresses a different inefficiency: the per-proof SRS loading overhead. Today, each C2 proof spawns a child process (curio ffi) that loads the ~48 GiB SRS from disk, deserializes it into pinned memory, computes one proof, and exits. For a proofshare node processing 50+ proofs per day, this means loading 48 GiB hundreds of times.
The proposal is to replace the child-process-per-proof model with a long-lived proving daemon that:
- Loads SRS once at startup into CUDA pinned memory
- Accepts proof requests via IPC (unix socket or shared memory)
- Keeps the GPU warm between proofs
- Survives across multiple proof computations The assistant noted a particularly galling detail: the SRS cache is already implemented in
groth16_srs.cuh(an LRU cache with max 3 entries), but it's useless today because the process dies after each proof. A persistent process would actually benefit from this cache. The performance impact is significant. SRS loading takes 30-90 seconds per proof (reading + deserializing ~70 GiB from disk). Eliminating this per-proof saves minutes per proof on throughput. Combined with Proposal 1's pipeline overlap, the total proof time drops from ~360s to ~200s—a 44% improvement [11].
Proposal 3: Cross-Sector Proof Batching
The third proposal exploits the freed memory headroom from Proposals 1 and 2 to batch partitions across sectors. The supraseal generate_groth16_proofs_c() already handles batches of circuits—today it batches the 10 partitions of one sector. But there's nothing stopping it from batching partitions across sectors.
If three sectors are waiting for C2, instead of processing them sequentially, the system could do a mega-batch of 30 partitions across 3 sectors. The GPU's MSM throughput scales sub-linearly with batch size—more circuits amortize fixed costs (SRS transfer, kernel launch overhead, memory allocation).
The combined impact of all three proposals is a projected 5-6× reduction in $/proof, enabling the use of cheaper 128 GiB machines and a 2.4-3.6× increase in throughput per GPU [11].
The Documentation Threshold
The user's response to the proposals was concise but transformative: "Write down c2-improvement-background.md with all relevant insights into the realm of possible optimizations and pointers; write down c2-optimization-proposal-X.md with deeper details of each option" ([msg 11]).
This request marked the transition from analysis to artifact—the moment when investigative findings and architectural proposals were formalized into permanent, shareable, actionable documents. The assistant spent the next several messages writing these documents, creating four files totaling thousands of lines of analysis, code references, implementation plans, and performance projections [12].
The background document (c2-improvement-background.md) captured the full pipeline architecture with file:line references, memory accounting, and nine identified structural bottlenecks. The three proposal documents (c2-optimization-proposal-1.md, c2-optimization-proposal-2.md, c2-optimization-proposal-3.md) provided detailed implementation plans, estimated timelines (2-3 weeks per proposal), and quantitative projections of impact [13][14][15][16][17][18][19][20].
The delivery message ([msg 21]) summarized everything in a structured, digestible form. It curated the key findings: memory reduction from 200 GiB to 64 GiB, elimination of ~60s SRS loading per proof, 2-3× throughput improvement, and 5-6× reduction in $/proof. It reinforced the composability argument—that these proposals are not independent optimizations but a layered stack where each layer enables the next [22].
The Micro-Optimization Deep Dive
The user's final challenge was to go even deeper: "Look further for more big ideas in compute optimization, improving everything like using more advanced avx/blas/better cpu/gpu cache use/less memory copy/paging in sequantial paths/etc" ([msg 22]).
The assistant responded by launching six parallel deep-dive investigations ([msg 23]), each targeting a different layer of the performance stack [24]:
- Synthesis CPU hotpath analysis: Understanding the CPU-level performance of the circuit synthesis phase, including the
enforce()loop called ~130 million times per partition, SHA-256 bit manipulation, and Fr field arithmetic - GPU NTT performance analysis: Characterizing the Number Theoretic Transform implementation, kernel launch configurations, memory bandwidth utilization, and occupancy
- blst Fr field arithmetic analysis: Understanding BLS12-381 scalar field arithmetic performance, including the assembly-level implementation of multiplication and reduction
- Memory access pattern analysis: Characterizing cache behavior during CPU synthesis and the C++ prep_msm phase
- Host-to-device transfer pattern analysis: Documenting transfer sizes and patterns for each phase of GPU computation
- Recomputation vs. storage analysis: Exploring whether the ~12 GiB a/b/c vectors can be recomputed on-the-fly rather than stored The synthesis of these investigations ([msg 24]) produced a comprehensive micro-optimization analysis that examined CPU synthesis hotpaths at the instruction level, GPU NTT/MSM compute characteristics, H-to-D transfer patterns, and the feasibility of recomputing a/b/c vectors on-the-fly to avoid materialization entirely [25].
The Overarching Achievement
The journey documented in this chunk represents a fundamental shift in how to think about Groth16 proof generation for Filecoin PoRep. It moves from optimizing individual proof generation—tweaking parameters, reducing constant factors, improving cache behavior—toward architecting a continuous, memory-efficient proving pipeline optimized for heterogeneous cloud rental markets where RAM cost dominates.
The key achievements are:
- Complete call chain mapping: From Curio's Go task layer through Rust FFI (bellperson) into C++/CUDA kernels (supraseal-c2), with file:line references for every step
- Comprehensive memory accounting: Breaking down the ~200 GiB peak into its constituent parts, identifying exactly where each GiB goes
- Nine structural bottlenecks: Documented with clear descriptions, proposed solutions, and difficulty ratings
- Three composable optimization proposals: Sequential Partition Synthesis (memory reduction), Persistent Prover Daemon (SRS loading elimination), Cross-Sector Batching (throughput improvement)
- Micro-optimization analysis: Examining CPU and GPU hotpaths at the instruction level, identifying opportunities for cache optimization, memory transfer reduction, and recomputation strategies The overarching insight is that the current architecture's memory problem is not a performance bug—it is a design choice made for simplicity. Challenging that choice—and proposing a streaming, pipelined alternative—is the kind of architectural thinking that leads to transformative improvements. The proposals do not just optimize the existing design; they imagine a better one.
Conclusion
The SUPRASEAL_C2 investigation demonstrates the power of systematic, multi-layered analysis in tackling complex engineering problems. Starting from a single user prompt about a ~200 GiB memory footprint, the investigation traced the complete call chain across four programming languages, identified the root cause in the parallelism model, produced comprehensive documentation, and proposed a fundamental re-architecture of the proving pipeline.
The journey from "understand the current code deeply" to "architect a continuous proving pipeline" represents a shift in perspective that is applicable far beyond the specific context of Filecoin PoRep. It demonstrates that the biggest gains often come not from micro-optimizing individual components but from rethinking the architecture—challenging assumptions about what must be done in parallel, what must be stored in memory, and what can be streamed, pipelined, or recomputed.
For anyone working on Groth16 proof systems, GPU-accelerated cryptography, or memory-constrained high-performance computing, this investigation offers a case study in how to analyze a complex system, identify bottlenecks, and propose targeted optimizations. It demonstrates the value of systematic exploration, careful memory accounting, and engineering judgment in prioritizing improvements—and it shows that the first step to solving a hard problem is often not to solve it, but to map it.