Skip to content

fix(gpu): survive transient VRAM pressure — recover resident-table declines, close an R2 corruption race - #914

Open
ColoCarletti wants to merge 5 commits into
mainfrom
gpu-vram-correctness-fixes
Open

fix(gpu): survive transient VRAM pressure — recover resident-table declines, close an R2 corruption race#914
ColoCarletti wants to merge 5 commits into
mainfrom
gpu-vram-correctness-fixes

Conversation

@ColoCarletti

Copy link
Copy Markdown
Collaborator

Main today fails ~1/3 of real-block proves at epoch 2^22 on a 32 GB GPU (reproduced: 2/6 runs on 8b88a8d), and under sustained VRAM pressure can silently emit an invalid proof (~1/15 runs). This PR fixes the three failure modes without touching the perf envelope.

Hard failures under transient CUDA OOM. The resident-aux LDE and the device R2 path had no fallback for a device-only table: the aux decline was a hard prove error ("resident aux LDE failed; host aux trace is empty") and an R2 device miss was a hard abort that kills the prover thread. Now the aux decline drains the device and retries once (the transient peak is usually gone, so the table stays resident); if it still declines, the resident aux trace and the main LDE are downloaded and the table continues host-backed. The R2 miss recovers the same way (materialize the resident LDEs, continue on the host evaluator). Both paths log the downgrade with the table name.

Silent invalid proofs. Concurrent device R2 windows under VRAM pressure can transiently produce a fully wrong composition H for one or two tables while every resident input stays correct (rerunning the same chain matches the host recompute), yielding a proof that fails the verifier's composition check. Serializing only the constraint-eval + decompose window across tables eliminates it (30/30 clean cycles vs ~5 failures/30 without); commits and host arms stay parallel and the windows overlap rarely enough that the lock is near-free (~0% on real block, ~1-2% on small synthetic workloads). LAMBDA_VM_GPU_SERIALIZE_R2=0 lifts the lock. The underlying race is still being hunted; the lock is the safe default until then.

Device-only gate tightening. The device R2 path only exists for the d=2 composition split; tables with any other bound (DECODE proves with num_parts == 1) now never enter device-only mode instead of aborting downstream. Mixed states (GPU aux commit with a CPU main commit) are also excluded.

Diagnostics (env-gated, zero cost when off). LAMBDA_VM_GPU_XCHECK=1 runs the verifier's composition consistency check inside the prover per table (~µs) and, on a failure, a post-mortem that recomputes each device stage on host, reports the corruption shape and reruns the device chain to distinguish a transient race from corrupted resident inputs. LAMBDA_VM_GPU_FORCE_DOWNGRADE=1 exercises the recovery paths end to end. These are how the fixes above were found and validated.

Validation on a 5090 (32 GB): 30/30 real-block e22 prove+verify cycles under reduced VRAM budget with zero corruption and all transient OOMs recovered (vs main failing 2/6 and the pre-fix branch failing ~5/30); full GPU test suite green (898 tests).

… on an R2 miss

The device-only gate is a static predicate over a dynamic dispatch: it cannot
mirror every reason the device R2 path might decline (parts count, kernel
eligibility, transient errors, shapes a new workload brings), and each miss
was a hard abort that deadlocked the epoch pipeline — DECODE on the synthetic
workload, then a second table on the real-block bench. Instead of excluding
tables one by one, treat the resident handles as the source of truth: on a
miss, download the main/aux LDEs back to host, clear the device-only flag,
and continue on the host path. Slower for that table, never wrong; the abort
remains only when the handles themselves cannot serve the data.
gpu_device_only_downgrades() counts recoveries so a persistently-missing
condition still gets mirrored into the gate.
…declines

A transient CUDA OOM on the resident aux LDE was a hard prove failure:
the resident build leaves no host aux trace to fall back to. A device
drain releases the concurrent VRAM peaks, so one retry usually keeps the
table fully resident; if it still declines, download the resident aux
trace (and the main LDE when the table is device-only) and continue
host-backed. The drain before dropping the resident buffer also keeps
kernels enqueued by the failed attempt from reading pool memory reused
by a concurrent table.
…ption race

Concurrent device R2 windows under VRAM pressure can transiently produce
a fully wrong H for one or two tables while every input stays correct
(rerunning the same chain on the same resident inputs matches the host),
yielding a proof that fails the composition check. Serializing only the
constraint-eval + decompose window across tables eliminates it; commits
and host arms stay parallel, and the windows overlap rarely enough that
the lock is near-free. LAMBDA_VM_GPU_SERIALIZE_R2=0 lifts the lock to
bisect further or once the underlying race is found.
The device R2 path only exists for the d=2 quotient split; a table with
any other composition bound (DECODE proves with num_parts == 1) would
skip it entirely and hard-abort on its device-only trace.
@ColoCarletti
ColoCarletti force-pushed the gpu-vram-correctness-fixes branch from b5e3e52 to 8f62d7b Compare August 7, 2026 18:23
A mixed state (one commit fell back to CPU while the other stayed
device-only) left the recovery refusing to proceed: it treated a missing
device handle as fatal even when that side already had a valid host
copy. Only the missing side is downloaded now, and the R3 host-arm
guards check the buffer they are about to read instead of the
table-wide flag.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant