Reading the SRS, NTT, and MSM Kernels: A Methodical Deep-Dive into SUPRASEAL_C2's GPU Pipeline

Introduction

In the course of a sustained investigation into the SUPRASEAL_C2 Groth16 proof generation pipeline for Filecoin PoRep, message [msg 4] represents a pivotal data-gathering step. At this point in the conversation, the assistant has already mapped the high-level architecture from Curio's Go task layer through Rust FFI into C++/CUDA kernels, analyzed the memory footprint (~200 GiB peak), and read the main orchestrator file (groth16_cuda.cu) and the Rust FFI layer (c2/src/lib.rs). Message [msg 4] continues this systematic exploration by reading three additional CUDA source files that complete the picture of the GPU-side implementation: the SRS (Structured Reference String) loading header, the NTT (Number-Theoretic Transform) kernel, and the MSM (Multi-Scalar Multiplication) split kernel. This message is not a synthesis or a proposal—it is a deliberate act of data collection, driven by the need to understand every component before attempting optimization.

The Message in Full

The assistant's message is terse and purely instrumental:

[assistant] [read] /home/theuser/curio/extern/supra_seal/c2/cuda/groth16_srs.cuh
[read] /home/theuser/curio/extern/supra_seal/c2/cuda/groth16_ntt_h.cu
[read] /home/theuser/curio/extern/supra_seal/c2/cuda/groth16_split_msm.cu

Each read is followed by the full file contents, which the tool displays inline. The assistant issues three sequential read commands targeting specific CUDA source files within the supra_seal/c2/cuda/ directory. There is no commentary, no analysis, no reasoning block—just the raw act of reading. Yet this seemingly simple action carries significant weight in the investigative process.

Context and Motivation: Why These Three Files?

To understand why the assistant chose precisely these files at this moment, we must reconstruct the state of knowledge after the previous messages. By message [msg 3], the assistant had already:

  1. Read the main GPU orchestrator groth16_cuda.cu (699 lines), which contains the generate_groth16_proofs_c() entry point and orchestrates the entire GPU proof generation pipeline.
  2. Read the Rust FFI layer c2/src/lib.rs, which bridges between bellperson (the Rust Groth16 library) and the CUDA kernels. With those two files understood, the assistant had a high-level view of the pipeline but lacked detail on three critical subsystems: - SRS loading and management: The SRS (Structured Reference String) is a large (~48 GiB) data structure containing the proving key elements needed for Groth16 proof generation. Understanding how it is loaded, cached, and accessed on the GPU is essential for memory optimization, since the SRS accounts for a significant fraction of the ~200 GiB peak footprint. - NTT (Number-Theoretic Transform) kernels: The NTT is the computational heart of the polynomial operations in Groth16. The coeff_wise_mult kernel performs coefficient-wise multiplication during the NTT-based polynomial arithmetic. Understanding its structure reveals memory access patterns, parallelism, and potential bottlenecks. - MSM (Multi-Scalar Multiplication) kernels: MSM is the dominant computational cost in Groth16 proving, accounting for the majority of GPU time. The groth16_split_msm.cu file contains template instantiations for the batched MSM implementation, including bucket accumulation and point addition kernels. The assistant's choice of these three files reflects a systematic, bottom-up approach to understanding the pipeline. Having already grasped the orchestration layer (how proofs are launched and coordinated), the assistant now drills into the three computational and data-management pillars that determine performance and memory usage.

Analysis of the Files Read

groth16_srs.cuh: The SRS Loading Infrastructure

The SRS header reveals a sophisticated memory management strategy. The verifying_key structure contains the six key elements of a Groth16 verifying key (αG₁, βG₁, βG₂, γG₂, δG₁, δG₂) as affine points. More importantly, the file uses POSIX mmap for loading SRS data from disk—a deliberate choice that enables demand-paged loading and potentially shared memory across processes. The header also includes CUDA device-side code paths (guarded by __CUDA_ARCH__) for accessing SRS data directly from GPU kernels. This dual host/device design is critical for understanding why SRS loading is expensive: the ~48 GiB of proving key data must be mapped into host memory and then transferred or made accessible to the GPU, a process that can take ~60 seconds per proof invocation.

groth16_ntt_h.cu: The Coefficient-Wise Multiplication Kernel

The NTT kernel file is remarkably concise—just a single kernel function coeff_wise_mult that performs element-wise multiplication of two field element arrays. The kernel uses a straightforward grid-stride loop pattern: each thread computes its global index, then iterates across the domain with a stride equal to the total thread count. This is a classic "embarrassingly parallel" pattern, but the kernel's simplicity belies its importance. The NTT pipeline in Groth16 involves multiple passes of NTT transforms and coefficient-wise multiplications; this kernel is called repeatedly within the GPU-side proof computation. The lg_domain_size parameter (logarithm of the domain size) hints at the scale: for a partition circuit with ~2^24 constraints, the NTT domain would be 2^24 or larger, meaning the kernel operates on arrays of 16 million or more field elements.

groth16_split_msm.cu: The MSM Template Instantiations

The MSM split file contains template instantiations for the batched MSM implementation. It instantiates batch_addition for bucket_t (extended Jacobian coordinates), along with point_add, bucket_add, accumulate, and to_affine kernels. These represent the core of the Pippenger-style MSM algorithm: points are first sorted into buckets by their scalar windows, then accumulated within each bucket, and finally combined via a running sum. The fact that these are template instantiations rather than the actual kernel implementations indicates that the core MSM logic lives in a shared header (likely in the sppark library), and this file simply instantiates the templates for the specific BLS12-381 curve types used in Filecoin's Groth16 proofs.

Input Knowledge Required

To fully understand this message, the reader needs:

  1. Groth16 proof structure: Knowledge of how Groth16 proofs are constructed, including the role of the SRS (proving key), the circuit satisfiability proof, and the NTT/MSM computations.
  2. CUDA programming model: Understanding of grid-stride loops, kernel launch parameters, template instantiation, and device/host memory management.
  3. Filecoin PoRep context: Awareness that Filecoin uses 10 parallel partition circuits for a 32 GiB sector, each with ~2^24 constraints, and that the SRS is ~48 GiB.
  4. The prior investigation: The reader should know that this is message [msg 4] in a sequence that began with a request to "Dive into rust fil proofs / supraseal code" and has already produced extensive analyses of the FFI entry point, the Rust bellperson prover, the CUDA orchestrator, and the memory footprint.

Output Knowledge Created

By reading these three files, the assistant (and the reader) gains:

  1. SRS loading mechanism: The SRS is loaded via mmap, not simple file I/O, which has implications for memory sharing and page caching. The verifying key structure is confirmed to contain six affine points.
  2. NTT kernel structure: The coeff_wise_mult kernel is a simple element-wise multiplier, suggesting that the complexity of NTT lies elsewhere (in the NTT transform kernels themselves, which are in separate files).
  3. MSM kernel architecture: The MSM implementation uses Pippenger's algorithm with bucket accumulation, and the kernels are template-instantiated from a shared library (sppark).
  4. File organization: The CUDA code is organized into separate files by function (SRS, NTT, MSM, main orchestrator), which aids in understanding the modularity of the design.

Assumptions and Potential Mistakes

The assistant's approach carries several assumptions:

  1. That reading these files in isolation is sufficient: The assistant assumes that the key details of SRS loading, NTT computation, and MSM can be understood from these three files alone. In reality, the SRS loading involves a complex chain through c2/src/lib.rs and the sppark library; the NTT transform kernels are in separate files (likely groth16_ntt.cu or similar); and the MSM algorithm core is in the sppark template headers. The assistant may need to read additional files to complete the picture.
  2. That the file paths are correct and accessible: The assistant assumes the files exist at the specified paths and are readable. This is a reasonable assumption given the prior successful reads, but it's worth noting that the investigation depends on the specific build environment and repository structure.
  3. That the CUDA code represents the production path: The assistant assumes that the code in supra_seal/c2/cuda/ is the actual production path used by Curio for C2 proof generation. While this appears to be correct based on the prior analysis, there could be alternative code paths or fallback implementations. A potential mistake in this message is the absence of any explicit reasoning or synthesis. The assistant reads the files but does not immediately analyze or comment on their contents. This is a deliberate methodological choice—gather first, analyze later—but it means that the reader must infer the significance of each file from context. The assistant's thinking process is visible only through the sequence of file choices: having read the orchestrator and the Rust FFI, the natural next step is to read the remaining GPU kernel files.

The Thinking Process Visible in the Reasoning

Although the assistant does not include explicit reasoning in message [msg 4], the thinking process is revealed through the structure of the investigation. The assistant is following a systematic decomposition:

  1. Top-down entry point tracing: Start from the Go FFI call, trace through Rust, into CUDA.
  2. Orchestration first: Read the main CUDA orchestrator to understand the overall GPU pipeline.
  3. FFI bridge second: Read the Rust FFI layer to understand how data crosses the language boundary.
  4. Component drill-down third: Read the individual kernel files (SRS, NTT, MSM) to understand each computational stage. This sequence reflects a software engineering mindset: understand the system at multiple levels of abstraction, from the highest-level orchestration down to the individual kernel implementations. The choice to read SRS, NTT, and MSM files together suggests that the assistant sees these as the three "hot spots" that determine performance and memory usage. The SRS file addresses the question "Where does the ~48 GiB of proving key data live and how is it loaded?" The NTT file addresses "How are polynomial operations performed on the GPU?" The MSM file addresses "How is the dominant computational cost (MSM) implemented?"

Conclusion

Message [msg 4] is a deceptively simple act of reading three files that, in the context of the broader investigation, represents a critical phase of knowledge acquisition. The assistant is systematically filling in the gaps in its understanding of the SUPRASEAL_C2 GPU pipeline, moving from orchestration to implementation details. The choice of files—SRS loading, NTT kernel, MSM kernel—reflects a clear prioritization of the components that matter most for memory and performance optimization. While the message contains no analysis or commentary, its significance lies in what it enables: a complete, bottom-up understanding of the GPU proof generation pipeline that will later inform the optimization proposals developed in subsequent messages. This is the quiet, methodical work of comprehension that precedes any act of engineering improvement.