Uh oh!
There was an error while loading. Please reload this page.
feat(parity): CPU-vs-GPU parity harness with arena-stress schedules (ADR 091, T1.2) - #133
Merged
Conversation
…na test hook (ADR 091, T1.2) - gradcheck opNode/layerNormNode implement graph.SaverAware and register their cached intermediates (forward output; xhat+inv) via SaveForBackward when a Saver is wired, so arena-resetting harnesses can pin them. No-op for gradcheck/oracle, which never wire a Saver. - oracle.SeededUpstream exported so the parity harness feeds byte-identical upstream gradients to both engines. - compute.SetArenaBytesForTesting: byte-granular arena-capacity override (beats ZERFOO_ARENA_SIZE_GB, bypasses GB bounds) so the parity harness can construct GPU engines with a deliberately small arena.
…ed-proof fixtures (ADR 091, T1.2) testing/parity runs the gradcheck OpInfo registry through two f32 engines in interleaved schedules (all forwards, then backwards in reverse) and diffs forward outputs + input/param gradients per op within per-op tolerances (oracle-style report JSON). Schedules: - no-reset: pure kernel parity. - reset-between-fwd-bwd: the Wolf hazard. The candidate arena is Reset between phases; the runner wires a pinning Saver into SaverAware nodes (graph.Builder semantics) and releases pins after each node's Backward (graph.releaseSaved semantics). Contract-honoring ops survive; raw struct-field caches of arena-backed intermediates are flagged as NaN diffs attributed to op + schedule. CI side (no GPU): StressEngine relocates every CPU-engine result into a host-backed cuda.ArenaPool with poison semantics, so the lifetime gate and both red proofs (synthetic cached-intermediate fixture + contract-stripped real registry op) run in ordinary CI. The CPU-vs-GPU comparison itself is gated on cuda.Available() and runs on the GB10 with a deliberately small arena via compute.SetArenaBytesForTesting. oracle.DiffStats gains NaN/Inf-safe JSON (the poison verdict is +Inf max_abs, which strict JSON cannot carry as a number).
scripts/parity mirrors scripts/oracle's conventions and the issue-118 Go-GPU-test pod pattern: golang:1.26-bookworm with /usr/local/cuda and /opt/zerfoo/lib hostPath mounts, run.sh delivered base64 (Spark block-scalar gotcha), correctness encoded in the exit code (SKIP = hard failure), and per-schedule JSON reports written to a mounted hostPath for the devlog.
Uh oh!
There was an error while loading. Please reload this page.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements plan T1.2 (zerfoo
docs/plan-gpu-training-hardening.md) -- harness #2 of ADR 091: the same op set run CPU-f32 vs GPU-f32, forward AND backward, under interleaved arena-stress schedules. Builds on the gradcheck OpInfo registry (#129), poison mode (#130), the oracle harness (#131), and the SaveForBackward contract + arena Pin/Unpin (#132). Refs #128.Schedule semantics
no-reset-- pure kernel parity, no allocator interference.reset-between-fwd-bwd-- the Wolf per-sample-ResetPool hazard: the candidate's arena isReset()between phases. The runner acts as the graph executor: it wires a pinningSaverinto everySaverAwarenode (Builder semantics) and releases pins after each node's Backward (releaseSavedsemantics). Ops honoring the save-for-backward contract or recomputing from live inputs stay correct; raw struct-field caches of arena-backed intermediates read poisoned memory and are flagged as NaN diffs attributed to op + schedule. This is the schedule shape that exposed the zerfoo#842 LayerNorm/QK-norm cached-intermediate corruption class.To make the registry ops contract-clean, the gradcheck op wrappers now implement
graph.SaverAware:opNodesaves its cached forward output,layerNormNodesavesxhat+inv(no-op for gradcheck/oracle, which never wire a Saver).Tolerance policy
f32-appropriate defaults: forward
atol 1e-6 / rtol 1e-5, gradientsrtol 1e-4. MatMul / Softmax / LayerNorm / reductions / Hadamard loosened to gradrtol 1e-3: CPU loops and cuBLAS/parallel kernels legitimately reduce in different orders at f32 -- expected reduction-order divergence is why per-op tolerances exist. Overridable per op (Op.Tol,toleranceOverrides).CI vs GB10 split
CI (no GPU):
StressEngine: a CPU engine whose every result is relocated into a host-backedcuda.ArenaPool(the poison-test / WolfHazard pattern), giving CPU tensors GPU lifetime semantics. CPU-vs-CPU+host-arena runs under both schedules with poison on -- the lifetime gate runs on every CI push (TestRun_HostArenaStress_RegistryGreen, with pin-accounting assertions).TestRun_CPUvsCPU_ExactParity).GB10 (Spark pod,
scripts/parity/):TestParity_GPUvsCPU_ArenaStressSchedules_GPU(gated oncuda.Available(), skips cleanly in CI): full registry, both schedules, deliberately small 64 MiB arena via the newcompute.SetArenaBytesForTestinghook (byte-granular, bypasses the 1 GB env-var minimum; preferred over env mutation), poison-on-reset enabled. Writes one oracle-style JSON report per schedule.TestParity_GPURedProof_GPU: the fixture pair re-proven against the real CUDA arena.scripts/parity/{run.sh,parity-pod.yaml,README.md}mirror scripts/oracle + the issue-118 Go-GPU-test pod conventions (golang:1.26-bookworm, CUDA hostPath mounts, base64-delivered script, exit-code-encoded verdict, SKIP = hard failure). Not run in this PR -- DGX execution is the follow-up acceptance step (S1.2.1 / S2.3.1 territory).Red-proof story (CI-asserted)
FixtureCachedIntermediateRaw: caches an engine-computed forward intermediate in a struct field WITHOUT SaveForBackward -- the pre-fix LayerNorm shape. Underreset-between-fwd-bwd+ poison the harness flags it: gradient diffmax_abs = +Inf(NaN sentinel), forward still green (snapshotted pre-reset), attributed to the op and schedule. Asserted red. Underno-resetit passes -- proving single-op-style runs cannot see this bug class.FixtureCachedIntermediateContract: identical op honoring the contract. Asserted green under both schedules.TestRun_RedProof_RealOpWithoutContractFlagged: a real registry op (Exp) with the contract stripped is flagged the same way -- sensitivity is not a fixture artifact.Report format
JSON per (candidate, schedule), consistent with the oracle harness: per-op
max_abs/max_relfor forward and every input/parameter gradient,passed/failed/erroredtotals, per-opparity PASS/FAILlog lines for pod-log scraping.oracle.DiffStatsgained NaN/Inf-safe JSON marshaling (the poison verdict is+Inf, which strict JSON cannot carry as a number).DGX run procedure
See
scripts/parity/README.md: renderparity-pod.yamlwith the base64 ofrun.sh+ a RUNID, submit via the Spark HTTP API, poll forSucceeded, collect/home/ndungu/parity/$RUNID/parity-gpu-<schedule>.json, delete the pod.Gates
go build ./...cleango test ./... -race -count=1fully green (GPU-gated tests skip cleanly without CUDA)