Skip to content

feat: ZTENSOR_DETERMINISTIC mode -- deterministic GPU training runs (T4.1)#179

Open
dndungu wants to merge 4 commits into
mainfrom
feat-deterministic-mode
Open

feat: ZTENSOR_DETERMINISTIC mode -- deterministic GPU training runs (T4.1)#179
dndungu wants to merge 4 commits into
mainfrom
feat-deterministic-mode

Conversation

@dndungu

@dndungu dndungu commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements the env-gated deterministic-reductions debug mode from zerfoo
docs/plan-gpu-training-hardening.md T4.1 (zerfoo plan.md T135.5).

ZTENSOR_DETERMINISTIC=1 (read once at process init, mirroring
ZTENSOR_ARENA_POISON; off by default):

  • cuBLAS GEMMs routed to a deterministic configuration.
    internal/cublas.CreateHandle sets CUBLAS_WORKSPACE_CONFIG (if the
    process has not already set one) and calls
    cublasSetMathMode(CUBLAS_PEDANTIC_MATH) on new handles -- the two
    documented NVIDIA/PyTorch levers that disable TF32 downcasting and any
    workspace-dependent split-K/atomics reduction algorithm. Best-effort:
    degraded determinism warns on stderr, never fails handle creation.
  • Honest exclusion, enforced loudly. fused_encoder_bwd.cu's
    dScale/dBias accumulation uses atomicAdd across row blocks with no
    deterministic variant; compute.GPUEngine.FusedEncoderBackward refuses
    to run under the flag (descriptive error) instead of silently returning
    order-dependent gradients.
  • Scope documented in docs/design.md ("ZTENSOR_DETERMINISTIC scope"):
    a full nondeterminism inventory with per-source disposition (deterministic
    already / routed to deterministic config / NOT covered), what the mode
    does NOT guarantee (cross-GPU/driver/cuBLAS-version identity, CPU-vs-GPU
    bit parity), and the cost (PEDANTIC math forgoes TF32 tensor cores).

Notably, the inventory found most of the training path was already
deterministic run-to-run: CPU fp32 reductions are unconditionally fixed-order
since T135.2, and the custom GPU kernels (softmax, RMSNorm, GEMV family,
flash attention/decode, fused AdamW) reduce via warp-shuffle/tree patterns
that are pure functions of input shape, with no cross-block atomics.

GB10 proof

The bitwise double-run proof runs through zerfoo's harness (ztensor CI has
no GPU): zerfoo PR (branch wave-3-task-T135.5) adds
TestPatchTSTTrainGPUDeterministicDoubleRun, which re-execs itself as two
child processes with ZTENSOR_DETERMINISTIC=1 set at process init, trains
identically-seeded PatchTST models, and asserts per-epoch losses are
bitwise-identical (exact float64 bit patterns). Evidence recorded in
zerfoo's devlog with pod names and bit patterns.

Test plan

  • go build ./..., go vet (no new findings), gofmt clean on touched files
  • go test ./internal/cuda/... ./internal/cublas/... ./compute/... green
  • Unit tests: env-gate toggle; SetMathMode nil-handle validation;
    workspace-config set-when-unset and never-overwrites-operator-value;
    FusedEncoderBackward refuses under flag / falls through without it
  • TestCreateHandle_DeterministicMode (GPU) exercises the live
    cublasSetMathMode(CUBLAS_PEDANTIC_MATH) path -- runs on the GB10 via
    zerfoo's validation harness (skips without CUDA)
  • GB10 bitwise double-run proof via zerfoo wave-3-task-T135.5

Refs zerfoo docs/plan-gpu-training-hardening.md T4.1 / zerfoo plan.md T135.5.

dndungu added 4 commits July 3, 2026 14:28
Add internal/cuda.DeterministicEnabled(), read once from
ZTENSOR_DETERMINISTIC=1 at process init, mirroring the existing
ZTENSOR_ARENA_POISON pattern. Off by default. Also add
SetDeterministicEnabledForTesting so other packages (cublas, compute)
can flip the flag in unit tests without a real process environment.

This is the scope-gate for the deterministic-reductions debug mode
(plan-gpu-training-hardening.md T4.1): the nondeterminism inventory and
per-op disposition are documented in docs/design.md.
…TENSOR_DETERMINISTIC (T4.1)

CreateHandle now sets CUBLAS_WORKSPACE_CONFIG (if the process has not
already set one) and calls cublasSetMathMode(CUBLAS_PEDANTIC_MATH) on
new handles when ZTENSOR_DETERMINISTIC=1 -- the two documented
NVIDIA/PyTorch levers for bit-reproducible cuBLAS GEMMs: they disable
TF32 tensor-core downcasting and any workspace-dependent split-K/atomics
reduction algorithm cuBLAS's heuristic might otherwise pick.

cublasSetMathMode is resolved best-effort at load time so a cuBLAS
build stripped of it cannot break ordinary (non-deterministic-mode)
BLAS loading; both levers degrade to a stderr warning rather than a
hard failure if unavailable, since the handle is still needed outside
the debug mode.
…RMINISTIC (T4.1)

fused_encoder_bwd.cu's dScale/dBias LayerNorm-style bias-gradient
accumulation uses atomicAdd across row blocks -- order-dependent, with
no deterministic variant. Rather than silently return
order-dependent gradients when ZTENSOR_DETERMINISTIC=1 is set,
FusedEncoderBackward now checks the flag first and returns a
descriptive error naming the exclusion.

This is not reachable from zerfoo's current PatchTST training path (the
Go-side wiring is a stub that always falls back to the unfused per-op
backward, per the T135.4 audit / issue #522), so the exclusion does not
block the GB10 bitwise-identity proof on that model; it will block any
future consumer that wires the fused path in, on purpose, until a
deterministic dScale/dBias reduction exists.
Document the deterministic-reductions debug mode's nondeterminism
inventory and per-op disposition: CPU reductions and GPU custom kernels
were already deterministic (T135.2's audit found no cross-block
atomics in the reduction path); cuBLAS GEMMs are now routed to a
deterministic configuration; fused_encoder_bwd.cu's dScale/dBias
atomicAdd path is a documented, honest exclusion that errors under the
flag instead of silently returning order-dependent gradients. Also
records what the mode does NOT guarantee (cross-GPU/driver/cuBLAS-
version identity, CPU/GPU parity) and its cost (CUBLAS_PEDANTIC_MATH
forgoes TF32 tensor cores).
@dndungu

dndungu commented Jul 3, 2026

Copy link
Copy Markdown
Contributor Author

GB10 bitwise proof landed: zerfoo/zerfoo#936 (2 pods x 2 processes, 3/3 epoch losses bitwise-identical under ZTENSOR_DETERMINISTIC=1; bits 0x3fe8024d7e12aa6c / 0x3fe066b05c164401 / 0x3fd18a52c2f44ad5; pods zerfoo-validate-wave3taskT13-1783115032 and -1783115121). Evidence in zerfoo docs/devlog.md 2026-07-03.

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