skip to content

Two training runs with identical seeds and identical data order diverge by step 4000 - why?

level: seniorimportance: nice to knowfreq 38%

answer

  1. the seed did its job; something else did not
  2. arithmetic order, not random draws
  3. parallel threads finish in any order
  4. floating-point addition is not associative
  5. tiny perturbations amplify through a feedback loop

basics

~10 s

Floating-point addition is not associative, and parallel kernels sum partial results in an order that varies between launches. Last-bit gradient differences feed back through thousands of updates until the two runs are visibly apart.

solid answer

~50 s

Seeds control random draws, not arithmetic order. Many parallel operations accumulate partial sums in whatever order threads happen to finish - the textbook case is a scatter-add of gradients into embedding rows when a batch repeats the same high-cardinality id many times, where many threads atomically add into one row. Because floating-point addition is not associative, a different accumulation order gives a slightly different gradient in the last bits. Training is a feedback loop: that perturbation changes the next weights, which changes the next activations, and the gap grows - fastest through discrete decisions like an argmax or a hard routing choice, and near instability. So two runs are bitwise identical for a while, then differ in the last decimal, then differ visibly. If you need bitwise reruns, force deterministic algorithm selection and ordered reductions and accept the throughput cost; otherwise measure over seeds instead.

go deeper

for a junior

Know that identical seeds do not guarantee identical numbers on parallel hardware, and that summing the same values in a different order can change the final digits.

for a middle

Explain the mechanism: non-associative floating-point addition plus accumulation orders that depend on runtime scheduling, with atomic scatter-adds into shared rows as the standard example.

for a senior

Demonstrate the diagnosis - locate the first differing step, separate an unpinned random stream from accumulation order, and decide deliberately whether determinism is worth its throughput cost for this run.

for a principal

Set the policy on where determinism is required versus where variance is measured, and push back on any conclusion whose margin is smaller than the noise the platform provably produces.

## Seeds do not control arithmetic A seed fixes which random *values* are drawn. It says nothing about the order in which a parallel machine adds numbers together. Two runs can request exactly the same initialization, exactly the same batch order and exactly the same dropout masks, and still compute slightly different gradients - because the reduction that produced those gradients was executed in a different order. ## Why order matters at all Floating-point addition is not associative: `(a + b) + c` and `a + (b + c)` can differ, because each intermediate result is rounded to a finite number of digits. For a handful of similar-magnitude numbers the difference is in the last bit. For a sum over thousands of terms with mixed magnitudes it can be larger. Nothing here is a bug; it is the arithmetic behaving as specified. ## Where the order varies between launches - **Atomic accumulation.** A scatter-add of gradients into an embedding table is the canonical case. A batch of click-through examples may reference one popular ad id hundreds of times, so hundreds of threads atomically add into the same embedding row. Whichever thread wins the race first is a scheduling accident, and it changes from launch to launch. - **Multi-stage reductions.** A large sum is split across blocks, each producing a partial result that is combined later. If the split or the combination order depends on how work was distributed at runtime, the total's last bits move. - **Algorithm autotuning.** Some libraries benchmark several implementations of the same operation on first use and keep the fastest. A machine under different load can pick a different implementation, with a different internal summation order, on a different day. - **Asynchronous combination across replicas.** When partial gradients arrive in a different sequence and are summed on arrival, the sum differs in the last bits even though every contribution is identical. ## Why a last-bit difference becomes visible Training is a long feedback loop, and small perturbations do not stay small. A gradient that differs in its last bits produces weights that differ in their last bits, which produce activations that differ slightly more, and so on for thousands of steps. Three things accelerate the separation: 1. **Discrete decisions.** An argmax, a top-k selection, a hard routing or gating choice, or a threshold comparison converts a microscopic numeric difference into a completely different downstream computation the moment two candidates are nearly tied. 2. **Operating near instability.** At a learning rate close to the edge of divergence, or during a loss spike, the dynamics amplify perturbations quickly rather than damping them. 3. **Path-dependent bookkeeping.** Anything that records a best-so-far value - early stopping, best-checkpoint selection - can flip to a different decision on a tie that is decided in the last bits. The typical signature is exactly what the question describes: identical for hundreds of steps, then identical to fewer and fewer decimals, then plainly different curves. ## Diagnosing it correctly Compare the two runs step by step and find the *first* step where they differ. - **Differ at step 1** - this is not reduction order. Something random is unpinned (initialization, batch order, an augmentation or dropout stream) or the two runs are not actually running the same code or data version. - **Identical for a while, then diverge** - consistent with nondeterministic accumulation. Confirm by rerunning the same step in isolation on the same inputs and checking whether one operation's output is bitwise stable across repeats. Skipping this step is how teams spend a week hunting a data bug that was arithmetic all along - and, worse, how a real bug gets waved away as "just nondeterminism". ## What to do about it If you genuinely need bitwise reruns, most stacks offer deterministic algorithm selection, deterministic (ordered or sort-based segmented) reductions in place of atomics, and a way to pin autotuning. Expect to pay in throughput, sometimes substantially, and expect some operations to have no deterministic implementation at all. When is it worth paying? Bisecting a regression to a specific commit; unit-testing a training step; an audit that must reproduce a number exactly; debugging a rare crash that appears at one step. When is it not? Routine training, where the honest response is different: if a conclusion cannot survive arithmetic reordering, it was never going to survive a change of seed either, and the answer is to measure the effect across several runs rather than to chase bit-identical ones.

  • When is it worth paying the throughput cost of fully deterministic kernels?
    When determinism is the point of the run: bisecting a regression to a commit, unit-testing a training step, reproducing an audited number, or chasing a crash that appears at one specific step. Not for routine training, where the cost is real and the benefit is cosmetic - there you want conclusions robust across seeds, not bit-identical curves.
  • How do you tell nondeterministic accumulation apart from a genuine bug?
    Find the first step where the two runs differ. Divergence at step 1 is not reduction order - it means an unpinned random stream, a different data version or different code. Divergence after hundreds of identical steps, growing from the last decimal outward, matches accumulation order. Confirm by rerunning a single operation on fixed inputs and checking bitwise stability.
  • If you cannot get bitwise determinism, what do you make reproducible instead?
    Everything upstream of the arithmetic: the data snapshot and its version, the full config, the code commit, the logged seed list, and the evaluation protocol. Then make the reported quantity a statistic over several runs rather than one run's number, so the claim is stated at a resolution the machine can actually deliver.

Adding a long column of numbers in a different order can change the last digit you keep. Do that a few thousand times, feeding each total into the next column, and the two ledgers part ways.

saying these in an interview costs you the question

  • Insists identical seeds must give identical results
  • Blames the data pipeline without finding the first differing step
  • Calls every unexplained difference nondeterminism and stops investigating
  • Thinks determinism flags are free of throughput cost
  • Assumes last-bit differences stay negligible over thousands of steps

context