Engineering story · Capabilities model

Bit-identical across batch compositions

Designing nondeterminism out of a GPU decoder so an agent's choice of tool never depends on its neighbours

3 min readCapabilities modelCUDARust
One request · three batch compositions
Same bits out one block per row, reductions in a fixed order
512queries through 128 slots, each as alone
  • 12 of 12bit-identity gates across batch compositions
  • 2,345of 2,345 shuffled compositions agree on the top result
  • 484of 484 ranked lists match an independent implementation

Under continuous batching, the same request can return a different answer depending on what else is in the batch. For most workloads that is noise. For a model whose job is to tell an agent which tool to call, it is a correctness bug: the ranking must not depend on strangers. I set a gate that top results must be stable across batch compositions, then went further and built the decoder so that its output bits are identical whatever else is in the batch.

The challenge

GPU reductions are fast because they are parallel, and parallel floating-point sums are order-dependent. Different batch sizes choose different tiling, different tiling changes summation order, and the last bits of a logit shift. Here, two candidate tools separated by a hair could swap places depending on the batch, and an agent would call a different tool for the same question. The easy fixes, double precision everywhere or batch size one, throw away the throughput that justified a dedicated decoder.

Batch of 3 · one tiling

Two partial sums, added in one order.

Batch of 9 · another tiling

Three partial sums, a different order: the last bits move.

Constraints

Determinism from structure, with no slower arithmetic and no serialised execution. The same kernels for both paths, because a serial path and a batched path that differ in code will eventually differ in bits. Throughput preserved, because the decoder exists to serve two thousand clients beside a 30B model.

My approach

I ruled that top-result stability was the behaviour that mattered and gated on it. Then I asked for the stronger property, because the stronger property makes the weaker one provable: every output row is computed by exactly one block, reductions happen in a fixed order that does not depend on batch size, and the serial and batched engines share the same row kernels so there is only one implementation to be right.

What I built

One block per output row, so a row's computation never spans a partition that could change with batch size.

Fixed-order reductions: a fixed 32-lane shuffle tree within a warp and a fixed 256-thread tree above it, regardless of how many rows are in flight.

No K-split tiling, which would introduce batch-dependent partial sums, taken as a limit on kernel tuning.

Shared row kernels between the serial and batched engines, enforced by a header invariant so the two cannot drift apart.

One row, one block, one order

Pairs combine in the same order whatever else is in flight.

Rules that hold it
Per rowNo K-splitOne kernel✓

Gates that check bit-identity across batch compositions, and a cross-check of every ranked list against an independent implementation.

Difficult decisions

Giving up tuning headroom. Forbidding K-split tiling and fixing reduction order leaves performance on the table. I took the deterministic design and recovered throughput elsewhere.

A faster decoder that sometimes picks a different tool is slower at the job.

Results

Every gate held. The cross-check of ranked lists covered all classes, both engines and two transports.

Technologies used

CUDA · Rust · Floating-point reduction design · GPU kernel architecture

What this demonstrates

Numerics judgment. I know where GPU nondeterminism comes from, I decide which property the application needs, and I design the arithmetic so the property holds by design and is checked by the gates.