The Commit That Saved the Stack: How a 374-Line Change Tamed OOM Crashes on Memory-Constrained GPU Nodes

git add docker/cuzk/memprobe.c docker/cuzk/benchmark.sh docker/cuzk/entrypoint.sh Dockerfile.cuzk && git diff --cached --stat

At first glance, message [msg 4015] appears to be nothing more than a routine commit command—a developer staging four files and checking the diff stat before pushing. The numbers are unremarkable: 374 insertions, 113 deletions, four files touched. But this message is the culmination of an intense debugging session that traced a production OOM crash through multiple layers of abstraction, from CUDA pinned memory pools to Linux cgroup limits to glibc arena fragmentation. It represents the moment when a two-pronged defensive strategy—a custom memory-probing utility and an adaptive OOM recovery loop—was committed to the repository, marking the transition from investigation to deployment.

The Crisis That Preceded the Commit

To understand why this message was written, one must understand the crisis that preceded it. The CuZK proving engine had been deployed on vast.ai GPU instances, and while large instances (961 GiB) ran smoothly, a 342 GiB instance was crashing with OOM kills during GPU processing. The crash manifested as a broken pipe during GPU work—the daemon was silently murdered by the kernel's out-of-memory killer.

The initial investigation (documented in the preceding messages) had already ruled out the obvious suspects. The detect_system_memory() function had been rewritten to be cgroup-aware, correctly reading memory.max from cgroup v2 and memory.limit_in_bytes from cgroup v1, rather than naively trusting /proc/meminfo which reports the host's full RAM inside Docker containers. The safety margin had been increased from 5 GiB to 10 GiB. Yet the 342 GiB instance still crashed.

The root cause, as the assistant and user jointly identified, was subtle and multi-layered. The CUDA pinned memory pool (PinnedPool) was operating outside the MemoryBudget system entirely. When pinned buffers were returned to the pool after GPU work completed, they were never freed from actual RSS (Resident Set Size). The pool accumulated buffers, the RSS grew, and the kernel's OOM killer eventually stepped in. Compounding this were kernel and driver overheads that the simple safety margin model didn't account for: glibc memory arena fragmentation, page tables, GPU driver allocations, and a transient spike during simultaneous SRS (Structured Reference String) loading that combined mmap with cudaHostAlloc. The 10 GiB safety margin was empirically insufficient for constrained instances.

What the Commit Actually Contains

The git diff --cached --stat output in message [msg 4015] reveals the precise scope of the response:

The Thinking Process Behind the Commit

The reasoning visible in this message is the reasoning of a developer who has reached a natural commit point after an intensive debugging session. The assistant's thought process, as revealed in the preceding messages, followed a clear arc:

  1. Hypothesis formation: The OOM crash on the 342 GiB instance was not a simple configuration error but a systemic issue involving the pinned memory pool, kernel overhead, and the safety margin calculation.
  2. Root cause analysis: The assistant traced the problem to three interacting factors: the PinnedPool not accounting to MemoryBudget, kernel/driver overhead invisible to /proc/meminfo, and transient SRS loading spikes.
  3. Strategy design: Rather than attempting a complex fix to the PinnedPool accounting (which would require deep changes to the Rust CUDA runtime), the assistant chose a defensive approach: measure empirically and recover gracefully.
  4. Implementation: The memprobe utility was written in C for maximum reliability—no runtime dependencies, no Go garbage collector interference, just raw mmap and memset loops. The benchmark script was restructured with a retry loop. The entrypoint was updated to integrate both.
  5. Commit readiness: Message [msg 4015] represents the moment when all four pieces were ready to be committed together as a coherent unit. The git add command stages them, the git diff --cached --stat provides a final review before the commit is sealed.

Assumptions Made and Their Validity

Several assumptions underpin this commit:

Assumption 1: The OOM crashes are caused by memory pressure, not by bugs in the proving logic. This assumption was validated by the crash signature (exit code 137, the OOM killer signal) and by the fact that larger instances with more memory ran successfully.

Assumption 2: A 10% budget reduction per retry is a reasonable degradation strategy. This is a heuristic—too aggressive a reduction might waste GPU capacity, too conservative might not prevent the next OOM. The assumption is that the OOM is caused by a modest overshoot, not a catastrophic leak.

Assumption 3: mmap/memset allocation closely approximates the memory pressure that the actual CU workload will produce. This is a simplifying assumption—real GPU proving involves CUDA allocations, pinned memory, and file-backed mappings that behave differently from anonymous mmap pages. The memprobe utility measures one specific kind of memory pressure and extrapolates to the general case.

Assumption 4: The three-retry limit is sufficient. If the budget needs more than three 10% reductions to stabilize, the benchmark will still fail. The assumption is that three retries cover the likely range of overshoot.

Input Knowledge Required

To understand this message fully, one needs knowledge of:

Output Knowledge Created

This message creates several forms of knowledge:

Mistakes and Limitations

The most significant limitation of this approach is that it addresses the symptom (OOM crashes) rather than the root cause (the PinnedPool operating outside MemoryBudget). The commit does not fix the accounting issue in the Rust CUDA runtime—it builds a safety net around it. This is a pragmatic choice: fixing the PinnedPool accounting would require deep changes to the proving engine's memory management, with risk of introducing new bugs. The defensive approach gets the system stable faster.

A secondary limitation is that memprobe measures anonymous mmap memory, not CUDA pinned memory. Pinned memory may behave differently under pressure—the GPU driver might allocate additional kernel-side structures that mmap doesn't capture. The empirical validation on the 256 GiB instance showed 6 GiB of overhead, but this number may vary across GPU models, driver versions, and kernel configurations.

The 10% reduction step is also a heuristic. If the actual overshoot is 50%, three retries won't be enough. The assumption is that the OOM is caused by a modest overshoot, but this hasn't been proven across all instance sizes.

Why This Message Matters

Message [msg 4015] is the inflection point where investigation becomes deployment. The preceding messages were about understanding the problem—reading logs, examining code, forming hypotheses. This message is about locking in the solution. The git add and git diff --cached --stat commands are the developer's ritual of final review before committing to history.

For the reader following the conversation, this message provides a clean summary of what was accomplished: four files, 374 new lines, 113 deleted lines, spanning a C utility, two shell scripts, and a Dockerfile. The diff stat is the scorecard of a successful debugging session.

The commit that followed this message would be pushed to the repository, built into a Docker image, and deployed to production vast.ai instances. On those instances, the memprobe utility would run at container startup, measuring the true usable memory with all kernel overhead accounted for. The benchmark script would wrap the daemon in a recovery loop, ready to catch any OOM kill and adapt. The 342 GiB instance that had been crashing would now have a fighting chance.

In the broader narrative of the CuZK proving engine's development, this commit represents the moment when the system learned to survive in the wild—on memory-constrained GPU instances where every gigabyte counts and the kernel is always watching.