Validation on hardware we do not own
Every number Soup has published about layer streaming came from one machine: a 4 GB RTX 3050 Laptop running Windows. That is the machine the feature exists for, and it is also the machine that cannot check the feature's central claim.
The claim is that a streamed run computes the same thing a normal run computes. Checking it needs a resident reference: the same checkpoint, same quantisation, loaded the ordinary way, so the two can be compared tensor by tensor. On a 4 GB card that reference cannot exist for any model worth streaming. So the published checks ran on 3-layer models built from a config, with a hidden size of 32 and an NF4 layer of 0.01 MB. The paper says so in its own limitations, and names the missing experiment as the most useful one absent from it.
In August 2026 someone lent us an 8x H100 80 GB box for three days. This page is what the experiment returned, including the defect it found.
The full record is published as written, in the order things happened, at benchmarks/gate-h100-validation.md. It is a working record, not a report assembled afterwards, so it contains rejected hypotheses, withdrawn findings and measurement errors that later controls caught. Read it from the top.
The box, and why the stack matters
- 8x H100 80 GB HBM3, 503 GB host RAM, 32 cores, Ubuntu 24.04, driver 590.48.01
- PCIe between all pairs, no NVLink. This matters for the DeepSpeed comparison below and it favours nobody by accident: all-gather traffic is exactly what NVLink exists for.
- Page-locked ceiling is 62.96 GB, not 503 GB. Pinned memory is a separate budget from RAM.
- No NVMe. The only device is a virtual disk the kernel reports as rotational, which is why the RAM-versus-disk question stays unanswered.
The software stack is a different one end to end: torch 2.13, bitsandbytes 0.50.0, trl 0.26.2, peft 0.20.0. Of the five components that matter, only transformers matches the laptop. That is a feature of the experiment rather than a nuisance: a result that survives a stack change is a result about the method, not about one pinned set of wheels.
Bit-exactness is two claims, and they are not the same claim
This is the most important sentence on the page, and it is the one two expert readers got wrong before it was written down.
Every exactness result below is two independent measurements: the forward (logits, compared with
torch.equal) and the backward (every LoRA gradient tensor). Above roughly 165 MB per NF4 layer they disagreed. "Bit-exact at 72B" is a forward statement and only a forward statement.
Each row was compared against a resident reference of matching numerics: resident NF4 for a streamed NF4 run, never resident bf16, because quantisation error is wide enough to hide a real defect inside it.
| Model | Quant | MB/layer | Forward | Backward, pinned | Backward, unpinned | After the repair |
|---|---|---|---|---|---|---|
| Qwen2.5-0.5B | NF4 | 7 | exact 0.0 | exact 96/96 | not tested | already exact |
| Llama-3.1-8B | NF4 | 105 | exact 0.0 | exact 128/128 | not tested | already exact |
| Llama-3.1-8B | bf16 | 480 | exact 0.0 | exact 128/128 | not tested | never affected |
| Qwen2.5-14B | NF4 | 132 | exact 0.0 | exact 192/192 | not tested | already exact |
| Qwen2.5-14B | bf16 | 570 | exact 0.0 | exact 192/192 | not tested | never affected |
| Qwen2.5-32B | NF4 | 234 | exact 0.0 | WRONG 8/256 | exact 256/256 | exact 256/256 |
| Qwen2.5-72B | NF4 | 432 | exact 0.0 | WRONG 8/320 | exact 320/320 | exact 320/320 |
The 8B NF4 and 14B NF4 rows each survived a 50-consecutive-backward soak with a worst absolute difference of 0.0. A cell that was not measured says "not tested" rather than being left blank, because a blank reads as a pass.
The defect this found
In NF4, above roughly 165 MB per decoder layer, a streamed run produced silently wrong gradients. Filed as issue #331.
It is worth being precise about how silent. The forward stayed bit-exact on every run. The loss matched a resident reference to every digit. The run exited 0, printed no warning, and the loss curve fell normally. Only a direct gradient comparison could see that 62 of 64 layers were wrong.
- Threshold, bracketed by sweeping one shape parameter: exact at 163.8 MB per layer, broken at 171.5. Monotone on both sides over seven points, with no exceptions. It is a bracket, not a number.
- Which models. 8B sits at 105 MB per layer and 14B at 132, both below with margin. 32B (234) and 72B (432) are above. Nothing Soup has ever published is above the boundary.
- Why NF4 only. bf16 is exact at 935 MB per layer, moving 3.9 times more bytes than the failing NF4 run, so it is not about transfer size.
- The cause is aliasing, not a race. A full
cuda.synchronize()does not fix it. Giving each pooled tensor a private copy fixes it completely.bitsandbytes.MatMul4Bitstashes the packed weight and its quantisation state on the autograd context as ordinary Python attributes instead of going throughsave_for_backward, so gradient checkpointing cannot see them: the reference is captured in the forward, aliases the streaming buffer, and is read in the backward after that slot has been refilled with a different layer. bf16 goes through a native op that does usesave_for_backward, which is why it was never affected. The upstream half is filed asbitsandbytes-foundation/bitsandbytes#2034. - The obvious fix was rejected on measurement. Cloning every pooled tensor is correct and costs only about 6% of throughput, but it takes peak VRAM from 4,220 MB to 19,720 MB on a real 32B, which is approximately the whole model. A repair that is O(model) instead of O(one layer) deletes the reason the feature exists.
- The shipped repair keeps streamed NF4 weights out of that code path entirely, so the saved reference goes through the ordinary mechanism that checkpointing knows how to recompute. Gated on two real models against a control that reproduced the defect in the same process: 32B at 256/256 and 72B at 320/320, five repetitions each. It costs +2.9% peak VRAM and 4.8% throughput at 32B, and +2.6% and 3.7% at 72B.
Are you affected?
Almost certainly not, and here is the exact test. You need all three of: quantization: 4bit, layer streaming, and a decoder layer above roughly 165 MB. On the 4 GB hardware this feature targets, that layer size is out of reach: a 32B NF4 store is 14.99 GB of pinned host RAM and a 72B store is 33.74 GB. If you have streamed an 8B or smaller, you are below the boundary with margin and there is nothing to re-run.
If you did stream a 32B or larger in NF4, the adapter is not trustworthy and re-running is the only fix, exactly as with the v0.72.1 adapter-key defect. The two have the same shape: an exit-0 run that looked healthy the whole time.
The repair shipped in v0.73.0. Every release from v0.72.0 to v0.72.4 carries the defect at those sizes, so upgrade before streaming anything that large in NF4. The library behaviour it works around is upstream and unchanged, filed as bitsandbytes-foundation/bitsandbytes#2034.
Peak VRAM is flat in model size
This is the feature's whole claim, and it is the first time it has been measured across a real size range on one card. Five runs per row, streamed, NF4, through the shipped CLI.
| Model | Throughput, median of 5 | Spread | Peak VRAM | Pinned host store |
|---|---|---|---|---|
| Llama-3.1-8B | 113.00 tok/s | 5.0% | 3,397 MB | 3.35 GB |
| Qwen2.5-14B | 118.60 tok/s | 4.1% | 4,475 MB | 6.35 GB |
| Qwen2.5-32B | 76.80 tok/s | 3.9% | 4,845 MB | 14.99 GB |
The model grows fourfold and peak VRAM moves from 3,397 to 4,845 MB. Qwen2.5-72B extends the shape at n=2: 37.9 tok/s in 7,411 MB, against a 33.74 GB pinned store.
Two honest readings of that table. 14B being faster than 8B is the opposite of the naive expectation, it was not investigated, and no mechanism is asserted for it; the two rows are different architectures with different vocabularies, so the clean comparison is the two Qwen2.5 rows, which do fall. And every row here was taken before the #331 repair, which costs a few percent of throughput at the sizes it applies to.
The 72B row deserves its caveat spelled out rather than buried: under the configuration that shipped, 72B's gradients were wrong, so that line describes a memory and throughput result, not a valid training result. After the repair the gradients are exact. And the 33.74 GB host store is the real gate on reproducing it: this is not a claim about a laptop.
The laptop number reproduces on completely different hardware
The published headline is Llama-3.1-8B NF4 at 119.6 tok/s in a 3.32 GB peak, on an RTX 3050 Laptop under Windows. The same configuration on an H100 gives a median of 113.00 tok/s in a 3.32 GB peak.
An H100 is not slightly faster here. It is slightly slower, and mean GPU utilisation on those runs was 54.1%: the card sits idle half the time waiting for weights to arrive. That is the clearest evidence in the record that layer streaming is bound by host-to-device transfer, not by the GPU, which is also why the feature works at all on a card whose compute is modest.
It is also why a faster GPU is not the upgrade that helps a streamed run, and pinned memory is: turning pinning off cost 6.56x throughput at 32B NF4, which is why Soup refuses rather than silently falling back when it cannot pin.
Against DeepSpeed ZeRO-3
The first comparison against another tool, run through Soup's own --deepspeed entry point so it is one tool against itself. One H100, Llama-3.1-8B, same data, same LoRA, 256 optimizer steps.
| Approach | Base dtype | Throughput | Peak VRAM |
|---|---|---|---|
| Layer streaming | NF4 | 121.46 tok/s | 3,399 MB |
| Layer streaming | bf16 | 63.52 tok/s | 3,935 MB |
| DeepSpeed ZeRO-3, CPU parameter offload | bf16 | 21.65 tok/s | 38,135 MB |
At matched numerics, bf16 against bf16, layer streaming is 2.93x the throughput in 9.7x less peak VRAM. ZeRO-3 was then given a second, memory-tuned attempt: tightening every knob bought 5.6% less VRAM for 12% less throughput.
Do not read that as "streaming beats DeepSpeed." The record is emphatic about this and so is this page. ZeRO-3 shards across ranks, and with one GPU there is no partner to shard to, so it is being used outside the regime it was built for. That regime happens to be layer streaming's home ground, which is the point, but the honest claim is the narrow one: not faster than DeepSpeed, built for a different situation, namely one card that is too small.
The eight-card picture makes the same point from the other side, on the same model and data:
| Approach | GPUs | Throughput | Peak VRAM |
|---|---|---|---|
| Resident bf16 | 1 | 2,645.4 tok/s | 30.0 GB |
| ZeRO-3, no offload | 8 | 1,831.7 tok/s | 34.0 GB per card |
| ZeRO-3 + CPU offload | 8 | 1,752.2 tok/s | 38.0 GB per card |
| Layer streaming bf16 | 1 | 1,077.1 tok/s | 2.9 GB |
Eight cards of ZeRO-3 are slower than one card training resident, for a model that fits on one card. Against streaming on a single GPU they buy 1.70x, for 34 GB on each of eight cards against 2.9 GB on one. If the model fits, the right answer is the top row and every clever technique here is a loss. This is a PCIe box with no NVLink, and ZeRO-3 would improve on an NVLink node while the streaming row would not.
Does streaming change the model you get?
Bit-exactness answers this for one step. Convergence over a real fine-tune is a separate question, and it had never been measured.
Protocol: Llama-3.1-8B, NF4 in both arms, five paired runs where streamed run *i* and resident run *i* see byte-identical training data from disjoint subsets, scored by Soup's own soup ship against a fixed 300-row held-out set that no run trained on.
| Mean score | Within-arm spread | |
|---|---|---|
| Resident NF4 | 0.8773 | 0.0333 |
| Streamed NF4 | 0.8720 | 0.0200 |
The mean paired difference is +0.0053, which is 1.6 items out of 300, against a within-arm spread several times larger. The general-capability leg points against the convenient direction: 3 of 5 resident runs came back DON'T SHIP against 1 of 5 streamed, and the single worst regression in the matrix belongs to a resident run.
Read this as "no difference is detectable at this resolution", not as "there is no difference." The experiment resolves roughly one percentage point on a 300-item set. It does not license a claim of quality-neutrality to arbitrary precision.
Preference losses, on a real 8B
The v0.72.4 claim that DPO's reference model costs no extra weights was measured on a 365M synthetic fixture. It now holds on a real 8B through the shipped CLI, timed for the first time:
| Task | Reference model | Peak VRAM | Samples/s, median of 3 |
|---|---|---|---|
sft | none | 3,689 MB | 6.082 |
orpo | none, genuinely reference-free | 3,719 MB | 5.973 |
simpo | none, genuinely reference-free | 3,719 MB | 5.946 |
dpo | same base, adapters disabled | 3,733 MB | 4.665 |
kto | same base plus a separate KL forward | 3,669 MB | 3.595 |
Peak VRAM is flat across all five, within 64 MB or 1.7%. DPO costs 44 MB more than SFT, against roughly 5.6 GB for a second resident copy of an NF4 8B. That is the claim, measured at a size where a second copy would be impossible to miss.
Two cautions. These are samples per second, not tokens per second, and they come from 64-row single-epoch runs where setup is a large share of an 11 to 14 second step, so treat the ratios as directional. And kto runs a different dataset, so only its VRAM compares directly.
The same session also measured the streaming trade through the shipped CLI on a real model for the first time: streaming holds an 8B in 3,681 MB against the resident path's 12,087 MB, 3.28x less, for 1.13x the wall time.
What this does not say
The record's own limitations section is long, and it is the reason to trust the rest.
- The RAM-versus-disk gap is still unmeasured. There was no NVMe on the box, and a number from a virtual disk published under a heading people would read as "NVMe" is worse than no number.
- Why the defect is NF4-only, and why its boundary is so sharp, is not explained. Seven hypotheses were tested and rejected. None replaced them.
- The repair is gated at two sizes, 32B and 72B. No size between them was tested, and each gate is one sequence length and one buffer count.
- The quality result resolves about one percentage point, on 300 items, with the adapter initialisation seed uncontrolled.
- Every number is one machine, one session. No claim here is a multi-machine result.
- The published 119.6 tok/s laptop figure was measured before the #331 repair. At real training shapes bitsandbytes already took the same code path the repair forces, so the arithmetic is unchanged, and the measured cost at 32B is 4.8%. But nobody has re-run the 8B laptop configuration on the repaired code, and an 80 GB server card cannot stand in for a 4 GB laptop whose throughput is bound by host-to-device transfer. Treat it as a pre-repair figure until someone re-runs it.
The result that is not a number
Twelve defects surfaced in three days. Seven were repaired in the same window, five were filed with reproducers. The record's own summary of why is worth carrying:
Every one of them was found by running something that had never been run, not by reading code.
Four of the twelve were features the project ships and documents that had never executed once on any machine. Two produced successful, exit-0 runs the entire time: an adapter that reloaded as all zeros, and a data.max_length above 1024 that was silently ignored on every supervised run. A green test suite measured neither the features that had never run nor the paths that cannot run on one card.
That is the argument for borrowing hardware, and it is a better argument than any throughput number in the table above.
See also
- Layer streaming — the mechanism as it ships, the measured numbers and the full refusal table.
- The paper — the preprint, the correctness protocol and the limitations this page answers.
- Scaling a streaming run — architectures, batches, the VRAM pre-flight and the disk tier.
- Preference losses over streaming — DPO, ORPO, SimPO and KTO against a streamed base.
Soup is free and Apache-2.0. If it saved you a training run, starring the repo costs nothing and helps most. You can also fund the GPU time behind the work a 4 GB laptop cannot reach.