The Moment of Formalization: Writing Down Phase 10's Two-Lock Design
In the middle of a multi-week optimization campaign targeting the SUPRASEAL_C2 Groth16 proof generation pipeline for Filecoin's Proof-of-Replication (PoRep), a single, seemingly mundane message marks the transition from analysis to action. Message 2587 in the conversation is deceptively brief:
[assistant] [write] /home/theuser/curio/c2-optimization-proposal-10.md Wrote file successfully.
This is not a moment of discovery or a dramatic breakthrough. It is a moment of formalization — the act of committing a design to disk before implementation begins. Understanding why this message exists, what it represents, and the chain of reasoning that led to it reveals the disciplined, documentation-first engineering culture at the heart of this optimization project.
The Context: A Bottleneck Shift
To appreciate message 2587, one must understand the journey that preceded it. The team had just completed Phase 9, which implemented a PCIe transfer optimization for the cuzk SNARK proving engine. Phase 9 achieved a 14.2% throughput improvement in single-worker mode, but dual-worker benchmarks revealed a new bottleneck: PCIe bandwidth contention between two GPU workers sharing the same device.
The assistant then ran extensive benchmarks with higher concurrency levels (c=15–30) to diagnose "jumpy" GPU utilization. Fine-grained timing instrumentation revealed something unexpected: the pre-staging setup was negligible (~18ms), and GPU kernel time had dropped dramatically to ~1.8s per partition. Yet steady-state throughput plateaued at ~41s/proof, and the c=30 run crashed due to memory pressure.
The critical finding was a bottleneck shift. The CPU critical path — specifically prep_msm at 1.9s and b_g2_msm at 0.48s — now dominated the per-partition wall time at ~2.4s, leaving the GPU idle for ~600ms per partition waiting for the CPU thread. At high concurrency, the ten synthesis workers competed with the CPU MSM operations for 8-channel DDR5 memory bandwidth, inflating CPU times by 2–12×. The bottleneck had moved from PCIe transfers and GPU kernel execution to CPU memory bandwidth contention.
The Design Response: Two Locks Instead of One
The user proposed a two-lock design to better overlap CPU and GPU work. The assistant analyzed the deadlock risks — specifically, resource deadlock on VRAM — and designed a simplified protocol using a mem_mtx for VRAM allocation and upload, and a compute_mtx for GPU kernel execution.
The key insight that enabled the simplification was recognizing that d_a and d_bc — the two 12 GiB GPU buffers — are freed synchronously via cudaFree inside compute_mtx. This means that by the time compute_mtx is released, that 12 GiB of VRAM is genuinely available. The next worker can safely allocate in mem_mtx without needing a cooperative free slot or any complex handoff protocol.
The assistant's reasoning process in message 2583 shows this discovery in real-time. The initial design included a "cooperative free slot" — a mechanism for one worker to leave its freed buffers for the next worker to clean up. But as the assistant traced through the code paths, it realized:
"Wait — this simplifies things significantly. If d_a and d_bc are both freed inside compute_mtx, then we don't need a cooperative free slot at all!"
This self-correction during the reasoning process is characteristic of deep systems analysis. The assistant traced the lifetime of every GPU allocation, checked whether each was freed via synchronous cudaFree or stream-ordered cudaFreeAsync, and determined that the synchronous frees of the large buffers made the cooperative slot unnecessary.
The Question of cudaDeviceSynchronize
A further subtlety emerged around cudaDeviceSynchronize. The initial design included this device-wide synchronization inside mem_mtx to ensure the previous worker's kernels had completed before the new worker allocated memory. But the assistant identified a problem: if worker A holds compute_mtx running kernels, and worker B holds mem_mtx calling cudaDeviceSynchronize, worker B will block until A's kernels finish. This would recreate the same serialization the design was trying to eliminate.
The solution was to remove cudaDeviceSynchronize from the mem_mtx region entirely, relying instead on cudaMemPoolTrimTo to reclaim pool-cached memory. The assumption was that by the time compute_mtx was released, all stream operations had been synchronized through the implicit sync() calls within the MSM operations. This assumption — that cudaMemPoolTrimTo without a preceding cudaDeviceSynchronize is sufficient — would later prove to be incorrect, leading to OOM failures in the implementation phase.
Why Write It Down First?
Message 2587 exists because the user explicitly asked for it. In message 2585, the user directed:
"Write down c2-optimization-plan-10.md, then proceed to implement"
This instruction reflects a deliberate engineering discipline: formalize the design before coding. The assistant had already produced a detailed implementation plan in message 2584, complete with changes by file, risk assessment, testing plan, and expected outcomes. But the user wanted a standalone document — a design specification that could be referenced, reviewed, and committed to the repository.
The act of writing the proposal document serves several purposes. First, it forces clarity: the design must be complete enough to stand alone, without relying on the conversation context. Second, it creates a permanent record: the document can be committed to the repository and referenced by future engineers. Third, it provides a checkpoint: before investing the time to implement, the design is captured and can be reviewed.
Input Knowledge Required
Understanding message 2587 requires substantial domain knowledge. The reader must understand the Groth16 proving pipeline for Filecoin PoRep, including the role of partition synthesis, MSM (multi-scalar multiplication), NTT (number-theoretic transform), and the distinction between CPU preprocessing and GPU kernel execution. They must understand CUDA memory management — the difference between synchronous cudaFree and stream-ordered cudaFreeAsync, the behavior of the CUDA memory pool, and the semantics of cudaDeviceSynchronize versus cudaMemPoolTrimTo. They must understand the existing single-mutex architecture and why splitting it into two locks could improve throughput by allowing memory operations to overlap with computation on another worker.
Output Knowledge Created
The output of message 2587 is the file c2-optimization-proposal-10.md, which formalizes the Phase 10 plan. This document describes the two-lock architecture, the increased worker count from 2 to 3, the expected performance improvements (30–38% throughput improvement in isolation, 15–27% at high concurrency), and the risk assessment. It becomes the blueprint for the implementation that follows in subsequent messages.
The Assumptions That Would Be Tested
The design document makes several assumptions that would later be tested — and in some cases, proven incorrect — during implementation. The assumption that cudaMemPoolTrimTo without cudaDeviceSynchronize would suffice to reclaim memory proved problematic: in the implementation phase (described in Chunk 1 of Segment 27), the assistant discovered that cudaDeviceSynchronize and cudaMemPoolTrimTo inside mem_mtx are device-global operations that block while another worker holds compute_mtx and runs kernels. This effectively serialized the two locks, destroying the intended overlap and forcing all subsequent partitions to use the slow fallback path.
The assumption that 3 workers would not excessively saturate DDR5 bandwidth was also a risk flagged in the proposal itself, with the mitigation of falling back to 2 workers if needed.
The Broader Significance
Message 2587 represents a critical inflection point in the optimization campaign. The team had spent multiple phases optimizing GPU kernel execution and PCIe transfers, only to discover that the bottleneck had shifted to CPU memory bandwidth — a fundamentally different kind of constraint. The two-lock design was an attempt to address this new bottleneck by restructuring the concurrency model, allowing CPU and GPU work to overlap rather than execute serially.
The act of writing the proposal document before implementing reflects a mature engineering approach. It acknowledges that the design is complex enough to warrant formal documentation, that the assumptions need to be stated explicitly, and that the implementation should be guided by a clear specification rather than improvised during coding.
In the end, the Phase 10 implementation would encounter fundamental CUDA device-global synchronization conflicts that prevented the two-lock design from working as intended. The design would need to be redesigned. But message 2587 captures the moment of optimism and clarity before those complications emerged — the moment when the solution seemed clean, the risks were understood, and the path forward was clear.