Uh oh!
There was an error while loading. Please reload this page.
feat(gradcheck): finite-difference gradient checker + OpInfo registry (ADR 091, T1.1) - #129
Merged
Conversation
Check() compares a graph.Node's analytic Backward against central finite differences at float64 on the CPU engine, per input element and per trainable parameter, with absolute+relative tolerances. Fresh node instance per evaluation via a constructor closure so Forward-cached state can never leak between perturbed runs. OpInfo carries the op constructor, input shapes, sampling domains (positive-only, away-from-kink), and per-op tolerance overrides, modeled on torch.autograd.gradcheck + OpInfo (zerfoo ADR 091, T1.1). Refs #128
ztensor's graph package ships no public op nodes, so the registry covers graph.Node wrappers around the compute.Engine operations training graphs are built from: Add/Sub/Mul/Div/Pow, Tanh (via the fused TanhPrime kernel), Sigmoid/ReLU/LeakyReLU, Exp/Log/Sqrt/Rsqrt, Sin/Cos, Add/MulScalar, MatMul (MatMulTransposeB when available), Transpose, Reshape, HadamardTransform, Softmax, ReduceSum/Mean/Max, and a parameterized LayerNorm that caches xhat and inv-stddev in Forward like the production node whose GPU bug motivated this harness. Refs #128
TestRegistry gradchecks every registered op. The red proof: a BadTanh node whose Backward returns 2x the true gradient MUST be flagged by the checker. TestFreshNodePerEvaluation proves instances are never reused across finite-difference evaluations via a fixture that poisons its output on any second Forward. Refs #128
Uh oh!
There was an error while loading. Please reload this page.
This was referenced Jun 10, 2026
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 T1.1 of the GPU training-stack hardening plan (zerfoo
docs/plan-gpu-training-hardening.md), the gradcheck leg of zerfoo ADR 091. Refs #128.Package location:
testing/gradcheckChosen over
graph/gradcheckbecause the checker is test infrastructure, not graph runtime: it sits beside the existingtesting/testutils, and it must importcompute(for the CPU engine) in addition tograph-- keeping it out ofgraph/keeps the graph package free of a compute-engine-facing test dependency and avoids any future cycle when graph nodes want to import the checker in their own tests.Checker math
For a node
fwith inputsx_jand parametersp_j:Backward(FullBackprop, g, inputs...)run at float64 on the CPU engine (precision first; GPU is a separate harness).g: caller-supplied, or deterministic pseudo-random entries with magnitude in [0.25, 1.0] and random sign (randomized upstreams catch transposed/structural Jacobian errors an all-ones upstream can mask).h = 1e-6 * max(1, |x|),num = sum_i g_i * (f(x+h)_i - f(x-h)_i) / (2h).|a - n| > atol + rtol * max(|a|, |n|)with defaults atol 1e-7 / rtol 1e-5 at f64; per-op overrides viaOpInfo.Tol/OpInfo.Eps.MakeNodeFnconstructor closure is invoked for the analytic pass and for every perturbed Forward, so Forward-cached state (softmax output, layernorm statistics -- the class behind the GPU LayerNorm cached-variance bug) can never leak between evaluations. Parameter values are snapshotted from a reference instance and copied into each fresh instance, so randomly initialized constructors are fine as long as parameter order/shapes are deterministic.Parameter.Gradientafter Backward (the accumulation convention), zeroed beforehand.Registered ops (26)
ztensor's
graphpackage ships no public op nodes (only unexportedinputNode/checkpointNodeplumbing), so the registry coversgraph.Nodewrappers around thecompute.Engineoperations training graphs are built from -- the wrappers' backwards are themselves composed of engine ops/kernels, so the engine is what gets exercised:TanhPrimekernel), Sigmoid, ReLU, LeakyReLU, Exp, Log, Sqrt, Rsqrt, Sin, Cos, AddScalar, MulScalarMatMulTransposeBwhen the engine implements the optional interface), Transpose, Reshape, HadamardTransformxhat/inv-stddev in Forward like the production node -- also exercises the parameter-gradient path.Non-differentiable points are steered around via OpInfo domains, as PyTorch OpInfo does: positive-only sampling for Log/Sqrt/Rsqrt/Div-denominator/Pow-base; away-from-zero sampling for the ReLU/LeakyReLU kink; continuous sampling avoids ties for ReduceMax.
Found but not registered (with reason):
graph.inputNode(unexported identity placeholder, not an op),graph.checkpointNode/CheckpointedSegment(composition wrappers requiring inner nodes, not primitive ops),Gather/ScatterAdd/OneHot(integer-indexed inputs; the checker does not yet support non-differentiable inputs -- noted in code).Red-proof fixture
TestRedProofWrongJacobianFails: aBadTanhnode whose Backward returns 2x the true gradient; the test asserts gradcheck flags it (and fails if the checker ever passes it).TestFreshNodePerEvaluationadditionally proves no instance reuse: a fixture that poisons its output on any second Forward of the same instance must still pass.Test results
go build ./...clean, gofmt cleango vetclean with CI's package set (purego GPU-binding exclusions)go test ./... -count=1: 28 packages ok, includingtesting/gradcheck(27 registry subtests + red proof + fresh-node proof + parameter-gradient + config tests)Intentionally deferred