The layer streaming paper

The mechanism behind training an 8B model on a 4 GB laptop GPU is written up as a preprint, together with the correctness protocol that verifies a streamed run is bit-exact against a resident one. This page is the short version, plus everything you need to cite it.

Exact Layer Streaming: LoRA Fine-Tuning of an 8B Model on a 4 GB Laptop GPU (v3). Makazhan, A. (2026). Zenodo. 10.5281/zenodo.21918325. Preprint, CC-BY-4.0.

Version 3 is current, published 13 August 2026. The title and the claim are unchanged, still 8B on 4 GB, and no measured number has changed since v1. What version 2 added is a session on hardware the project does not own; what version 3 does is retract one explanation version 2 gave for it.

The paper is scoped to Soup v0.73.0 and has not been revised since. Soup v0.76.0 has shipped and recorded things that bear on three of its statements, and v0.76.1 changes none of them. They are set out in what v0.76.0 supersedes, outside the paper's own text, which is left exactly as published. v0.77.0 leaves the preprint unchanged too: no number it states was re-measured or corrected. In upstream's words, that release's DeepSeek-V3-family, FP8-source, per-expert, chunked-loss and no-recompute streaming are outside what the paper measured, and the next preprint version covers them and cites L2L (Pudipeddi et al. 2020).

Two DOIs, and they are not interchangeable:

DOIWhat it points atUse it when
10.5281/zenodo.21771064the concept DOI, always the newest version, v3 todayyou mean "the paper" and want the reference to follow it forward
10.5281/zenodo.21918325v3 specificallyyou are citing a version you actually read

Cite the version you used. v1 was not withdrawn and stays citable at its own version DOI; nothing in it was retracted, and the section below says exactly what changed.

What it reports

Two results, both on one machine: an RTX 3050 Laptop with 4 GB of VRAM, 16.9 GB of host RAM, running Windows 11.

  • Llama-3.1-8B-Instruct in NF4 at 119.6 tok/s, 3.32 GB peak, 100% SM occupancy.
  • Qwen2.5-3B with an un-quantized bf16 base at 143 tok/s in 2.15 GB, a configuration that raises CUDA out of memory when the same model is trained resident on the same card.

Both are configurations whose weights alone exceed the card. Measured overhead against resident training is 1.43x, at 0.5B, the only size on that machine that admits a valid resident baseline at all.

These are the paper's figures as published. The 8B row was measured on v0.72.2 at 952 MHz, before the NF4 gradient repair of v0.73.0; the post-#331 row, measured by @umran666 on another RTX 3050 Laptop 4 GB (#1103), reads 208.6 tok/s at 1935 to 1957 MHz in a 2.40 GB peak, and the two are not directly comparable.

The paper is deliberate about what that does not beat. Layer streaming on a small card is well established for inference, and AirLLM's own documentation says layer sharding is not compatible with gradient propagation. The published fine-tuning result for this hardware class is 1.3B, reached by LSP-Offload (arXiv:2406.10181) by compressing the optimisation into a learned sparse subspace rather than by streaming the base. The systems that do stream weights during training are evaluated on an H200 with 1.5 TB of host memory, an RTX 4090 with 256 GB, or a cluster. The unserved combination, and the one the paper addresses, is streamed training on a small card.

That is the paper's own framing, as published in v3. Upstream's v0.77.0 release names prior art this paragraph does not: layer streaming is, in its words, Soup's implementation of layer-to-layer streaming of a frozen base (L2L, Pudipeddi et al. 2020, "Training Large Neural Networks with Constant Memory using a New Execution Algorithm", arXiv:2002.05645), and upstream's records name L2L as the prior art for streaming layers through a small device memory. Soup does not implement L2L's layer-major micro-batching. The release notes say the next preprint version cites L2L; the published v3 does not.

What version 2 adds

The paper roughly doubled, from about 9,000 words to about 19,800, and every addition comes from the same source: three borrowed days on an 8x H100 box, which was the first hardware other than the original laptop this work had ever run on.

  • Replication on hardware nothing like the original. 119.6 tok/s on the RTX 3050 (at 952 MHz) against a median of 113.00 on an H100, at the same 3.32 GB peak. Both pre-date the #331 repair and are the figures the paper reports. The server card is if anything slightly slower. Version 2 offered an explanation for that and version 3 retracts it; the measurement itself is unaffected.
  • A silent wrong-gradient defect, found and repaired. On NF4 above roughly 165 MiB per decoder layer the forward stayed bit-exact and the loss curve looked healthy while the gradients were wrong. The cause is named in the upstream library and reported there; the repair is gated against controls on real 32B and 72B.
  • Bit-exactness at real model sizes instead of three-layer toys: forward from 0.5B to 72B, backward at 8B and 14B.
  • Trained-model quality, measured for the first time, and indistinguishable from a resident run.
  • A comparison against DeepSpeed, including the result that does not flatter the method: eight cards of ZeRO-3 are slower than one card training resident.
  • The limitations section rewritten: of v1's ten items, one is closed and four more are narrowed, and seven new ones are added.

The last line is the one worth pausing on. A revision that only closed limitations would be a marketing document. This one closed one, narrowed four and opened seven, because measuring at a real size is how you find out what you did not know you were assuming.

What version 3 adds

Version 3 is current, published 13 August 2026. The title and the claim are unchanged, still 8B on 4 GB, and no measured number changed. It exists to retract one explanation version 2 gave.

Version 2 read the H100 replication, a server card coming back no faster than the laptop, as evidence that layer streaming is bound by host-to-device transfer rather than by the GPU. It was an inference, and it had never been measured. On 11 August 2026 it was measured on the original laptop and is false at the published configuration: four ablation arms interleaved in one process at a pinned clock show that removing all host-to-device traffic, 6.864 GB per step, buys 1.44%, removing the NF4 dequantisation buys 9.80%, and removing both leaves 88.7% of the step standing. The compute stream is blocked on a copy for 8.4 ms of a 4190 ms step, and the step runs at 71.3% of that card's same-session, shape-matched GEMM ceiling. The claim is true below roughly 128 tokens per step, where the fixed transfer volume dominates, and the published configuration is not there.

What survives is the measurement itself, and it is untouched: a card with roughly two orders of magnitude more compute came back no faster than the laptop. The constraint is common to both machines and is not the compute the datacenter card adds. Beyond that we no longer offer an explanation for why: the one we offered was measured and refuted, and a second guess would be the same mistake in a new sentence. The H100's own bottleneck was never instrumented and no claim is made about it.

Two things about how it was published are worth copying rather than admiring. The retraction is a new version of the paper rather than an edit to the old one, because a claim that reached readers cannot be unpublished by changing a file. And every occurrence in the measurement records is annotated in place rather than deleted: in a folder whose premise is publishing the record as written, a silent deletion costs more credibility than the error does. The record is probe-v0.73.0-what-bounds-streaming.md.

The result it defends hardest is not the throughput

In a streamed trainer the natural failure is silent. If the autograd graph is severed at the base weights, the upper layers still train, the loss still descends, and the run looks healthy. A throughput number taken before correctness is established measures nothing.

So the central check is bit-exactness against a resident reference of the same numerics: resident NF4 for a streamed NF4 run, never resident bf16, because quantisation error is wide enough to hide a real defect inside it. Against a matched reference the expected answer is 0.0, so anything else is a bug by construction.

That discipline paid for itself. It caught a PEFT dispatch path that selected a different LoRA implementation on a meta-device skeleton and produced a 0.94 maximum logit divergence with byte-identical weights and byte-identical adapters: no exception, no warning, and a loss curve that looked entirely healthy. Nothing short of bit-exactness would have surfaced it.

The protocol itself, on SmolLM2-135M in bf16 with LoRA r=16 on q_proj and v_proj:

CheckThresholdMeasured
Streamed vs resident logits, max abs diff< 1e-30.0, bit-exact
LoRA gradient at layer 0non-zero9.50e-01, 30 of 30 layers non-zero
100-step loss curve vs residentwithin noisemax relative difference 0.0
Same seed twiceidentical0.0
With pinned-host boundary offload< 2% relative4.83e-3
Two buffers vs threeidentical0.0

Bit-exactness was then re-verified across nine model_type families in both bf16 and NF4, 14 of 14 comparisons at a maximum absolute logit difference of 0.000e+00. Two rows carry more weight than the others. phi3 fuses Q, K and V into a single qkv_proj, so the projection names a Llama-shaped implementation would look for do not exist at all, and it is bit-exact anyway, which is the strongest evidence that layer detection walks the real module tree rather than a naming assumption. And gemma3 is deliberately refused while gemma3_text is accepted, with the accept and the refuse asserted together so the refusal cannot decay into a spelling accident.

The protocol runs on CPU and is part of the project's test suite rather than a one-off experiment, so a regression fails CI instead of reaching a user.

Three findings that have nothing to do with streaming

These are the parts most likely to be useful even if you never enable layer streaming.

  1. The cross-entropy logits term costs 14 bytes per element, not 6. A first-principles budget counts the bf16 logits plus an fp32 upcast. The measured figure is 14, because the loss holds the bf16 logits, the fp32 upcast, an fp32 log-softmax and an fp32 gradient live at the same time. That is a 2.33x under-prediction on a term that dominates: at batch 8 the logits tensor was over a hundred times the size of the entire layer buffer pool. Anyone budgeting VRAM for a large-vocabulary model from first principles will make the same mistake.
  2. Windows and WDDM do not raise out of memory, they spill. One row of the fitting grid allocated 9.27 GB on a 4.29 GB card and completed without an exception. The methodological consequence is blunt: on this platform, "it did not crash" is not evidence that a configuration fits. It also cost a discarded baseline, a resident run whose reported peak turned out to be the driver paging into host memory.
  3. Gradient accumulation is per-token I/O-neutral. The received wisdom, the paper's own earlier draft included, is that accumulation multiplies I/O linearly because every micro-batch re-reads the model. True per optimizer step, and misleading per token, which is the unit that sets wall-clock time: layer reads per 1000 tokens held constant at 175.78 across accumulation 1, 2 and 4. The real cost is opportunity cost, measured at 2.52x at an equal effective batch of 4, which is why the tool prints "raise batch size until the pre-flight refuses, then accumulate" rather than the standard low-VRAM advice of keeping batch at 1.

A fourth, smaller one, aimed at anyone quoting a fraction of peak on a consumer GPU: a ceiling is only comparable to a throughput measured in the same session. Repeating one GEMM six times inside a single session was stable to under 1%, while the same card varied by about 13% between sessions at the same reported clock. Any fraction-of-ceiling figure has to state the SM clock it was taken at.

What it does not claim

The limitations section is long on purpose, and it is the reason to trust the rest.

  • 8B is the largest size measured. No 14B or larger claim is supported by any number in the paper.
  • The 3B bf16 figure is a lower bound. That machine could not page-lock a 5.55 GB store, so the run fell back to a pageable one, which makes the host-to-device copy synchronous. The occupancy drop from 96.8% to 79.3% is that cost, measured rather than estimated. (Not re-run since: v0.76.0 changed how the RAM store is page-locked, in power-of-two arenas instead of tensor by tensor, and the 3B bf16 row has not been re-measured with it.)
  • Windows and WDDM throughout. The numbers are systematically pessimistic against Linux, and there is no Linux measurement.
  • One card, and architecture coverage verified at small scale. The nine-family gate used tiny from-config checkpoints rather than downloaded ones, deliberately, because the risk being tested is a naming property and not a size property. The throughput results are Qwen2.5 and Llama only.
  • Bit-exactness is verified at 135M, not at 8B. At 8B it is impossible on that machine by construction: it needs a resident reference, which cannot exist for a model that does not fit. The paper names this as the most useful missing experiment in it. That experiment has since been run on borrowed hardware, and the section below says what it returned.
  • n = 1. Every throughput row is a single run of 50 measured steps after 10 warm-up, on a working laptop that was not otherwise idle, and no variance is reported. The one exception is the accumulation comparison, repeated in an interleaved order precisely because a monotonic clock drift would otherwise have manufactured the result.
  • The RAM-versus-disk performance gap is unmeasured and no figure is claimed for it. Correctness of the disk tier is established; relative speed is not. (This is the paper's own statement, scoped to v0.73.0. Since partly addressed in v0.76.0: the disk tier was rebuilt and its reader was timed on a second laptop, which is not a RAM-versus-disk comparison; see what v0.76.0 supersedes.)

The experiment it named as missing is now in the paper

The limitation above is the one worth acting on, and in August 2026 a borrowed 8x H100 box made it possible. It was open in v1 and it is closed in v2, which is the largest single reason the revision exists. Those cards can hold a resident 8B, 14B, 32B and even 72B, so the comparison the paper could not make on a 4 GB laptop was finally made against real checkpoints instead of 3-layer fixtures. The full record is its own page; three things from it bear directly on the paper.

No measured number from v1 changes, and nothing in it is invalidated. Its headline configuration sits at 105 MiB per decoder layer, comfortably below the boundary the exercise found, and its gradients survive a 50-backward soak against a resident NF4 reference at exactly 0.0. Its throughput claim reproduces on completely different hardware, a different operating system and a much newer software stack: 119.6 tok/s in 3.32 GB there, against a median of 113.00 tok/s in the same 3.32 GB peak on an H100 (both measured before the NF4 repair).

The exactness claim is strengthened, and the two halves are strengthened by different amounts. The forward is exact against matched resident references at 8B, 14B, 32B and 72B. The backward is exact at 8B and 14B, which is at and below the paper's own scope, and above that it was wrong until it was repaired. So "verified at 72B" is a forward statement and only a forward statement. The paper asserts nothing at 72B, so nothing in it depends on the distinction, but any future revision that reaches past 14B has to carry it explicitly rather than saying "bit-exact" and leaving a reader to decide which half was meant. Two expert readers already read it the other way.

One thing stayed open through version 3. The repair changed the code path the headline was measured on. At real training shapes the arithmetic is unchanged, and the measured cost at 32B is 4.8%, but the paper had no re-run of the 8B laptop configuration on the repaired code, and a server card cannot stand in for a 4 GB laptop, which the H100 replication showed by coming back no faster rather than faster. 119.6 tok/s is a pre-repair figure. It is one of the seven limitations v2 adds rather than something v2 quietly resolved. (Since closed in v0.76.0: the post-#331 row for the same model, measured by @umran666 on a different 4 GB RTX 3050 Laptop (#1103), at a different clock, is not directly comparable with 119.6; see what v0.76.0 supersedes.)

What v0.76.0 supersedes

The preprint is explicitly scoped to the version it measured, Soup v0.73.0, and it has not been revised. Version 3 is still the current version and the DOIs at the top of this page are unchanged. No measured number the paper states is altered by v0.76.0, and none was re-measured to a different value. What follows is not an edit to the paper: it is the three statements that upstream's v0.76.0 release notes list as superseded, set out beside the limitations above so a reader does not have to find them in a changelog. A new preprint version that restates them is future work, tracked upstream as #1207; the published preprint is not edited in place. v0.76.1 changes none of the three and no number the paper states: it restates the paper's 8B row with the clock it was taken at in upstream's README and performance docs. v0.77.0 changes none of them either, and its new streaming work is outside what the paper measured.

1. "The laptop configuration has not been re-run on the repaired code"

This is no longer true. A post-repair row for Llama-3.1-8B in NF4 is in the same record as the paper's row (gate-v0.72.2-nf4.md). The two are printed here with their conditions, because the record's own instruction is "Do not compare the two tok/s figures directly".

The paper's rowThe post-repair row
Soupv0.72.2, before the NF4 gradient repairdevelopment code after v0.75.0 and before v0.76.0
Machinethe original RTX 3050 Laptop 4 GBa different RTX 3050 Laptop 4 GB, Windows 11, measured by @umran666 (#1103)
SM clock952 MHz1935 MHz
Throughput119.6 tok/s208.6 tok/s
Share of same-session GEMM ceiling68%59%
Peak VRAM allocated3.32 GB2.40 GB (2.66 GB reserved)
Pinned host store3.60 GB5.70 GB

The old row was taken at 952 MHz and the new one at 1935 to 1957 MHz, and the two are not directly comparable. In upstream's words, that is 59% of its same-session GEMM ceiling against 68%: about 14% slower per unit clock, so the raw 1.74x gain is the clock, not a speedup. Large-layer streaming (v0.74.0) also changed the memory pattern between the two rows, so even that fraction is not like-for-like: it moved the embedding and the output head into the pinned store, which is the 2.10 GB by which the store grew while the peak fell. The post-repair row is one 50-step run after 10 warm-up steps with no repeats, and the stack differs between the two rows (torch 2.5.1 against 2.6.0, transformers 4.57.6 against 5.16.1, peft 0.18.1 against 0.21.0). The record attributes the per-clock shortfall to none of the repair, the memory change or the stack. A server card still cannot stand in for either row: an H100 ran the same configuration no faster than the laptop (median 113.00 tok/s in the same 3.32 GB peak, on a checkout made just after the v0.72.4 tag, so also before the repair), and that is a reproduction on other hardware, not a re-measurement of the laptop figure.

2. "The repaired streamed NF4 path always dequantises"

This no longer describes the shipped code. The v0.73.0 repair of the NF4 gradient defect dequantised inside the checkpointed region and used a native matmul. Since v0.76.0 (#842, #1143) the streamed CUDA NF4 forward asks bitsandbytes' own dispatch which kernel a resident NF4 layer would use for that shape and runs the same one: the fused 4-bit GEMM at a small number of rows, dequantise-and-matmul above that. The backward still dequantises, because bitsandbytes 0.50.x exposes no transposed 4-bit GEMM.

No benchmark record re-states exactness for the new dispatch. Every bit-exact result in the paper and on the external validation page was measured before this change, on the earlier path, and is scoped to those measurements. The Blackwell difference between streamed and resident NF4 that the records track as #776 is the case this change was aimed at. Upstream's contributor notes report the CUDA exactness tests passing at exactly 0.0 on an RTX 5070 after the change, but no benchmark record has a post-change row, so this page does not call the difference fixed.

3. The disk tier "is unmeasured"

The paper lists the RAM-versus-disk speed gap as unmeasured. v0.76.0 ships the disk tier as a background reader that uses direct I/O and can stripe across NVMe drives, and measures it on a second laptop (an RTX 5070 Laptop 8 GB with 31.7 GB of RAM and two NVMe drives, Windows 11). The store was a synthetic 70B-shaped NF4 store of 36 GB with random weights, larger than the machine's RAM, at batch 1 and sequence 512. A cold step took 110.5 s on the previous synchronous reader and 16.2 to 17.3 s on the direct-I/O reader; across two NVMe drives a 17.4 s step fell to 9.97 s. A sustained-load probe measured 1.28x and 1.41x in two rounds and reached no formal verdict. A store that fits in RAM is still faster on the RAM tier, which stream_source: auto picks.

Those are timings on one machine, on a synthetic store, with no quality claim and no real 70B checkpoint; the direct-I/O paths for Linux and macOS were reviewed and not executed. No RAM-versus-disk speed has been measured on a 4 GB card. The disk tier page has the mechanism and the caveats, and the measurement records index every record behind it.

The records behind it

Every number comes from measurement logs published verbatim in the benchmarks directory, indexed on the measurement records page. They are the working gate records kept while each item was built, not a summary assembled afterwards, so they carry the failures, the assumptions that turned out wrong and the numbers that were measured and then discarded, in the order those things happened. Read a file from its top rather than lifting a passage out of the middle: a passage may be one the same page later corrects.

Reproducing the headline row

The implementation shipped in Soup v0.72.3 under Apache-2.0. Two keys below are load-bearing, and omitting them is a silent divergence rather than an error: gradient_accumulation_steps (the shipped default is 4, and at batch 1 that measures roughly 2.5x lower throughput) and, for the un-quantized run, quantization (the shipped default is 4bit).

yaml
base: meta-llama/Llama-3.1-8B-Instruct
task: sft
data:
  train: data.jsonl
  max_length: 512
training:
  stream_layers: true
  stream_source: ram
  stream_buffers: 2
  quantization: 4bit
  batch_size: 1
  gradient_accumulation_steps: 1    # load-bearing: the shipped default is 4
  optimizer: paged_adamw_8bit
  lora:
    r: 16
    target_modules: [q_proj, v_proj]

Omitting the lora block is not equivalent either: it defaults to r=64 with automatic target selection rather than the r=16 on q_proj and v_proj that produced these numbers. The run also needs the store, plus any resident extras, to fit under 70% of the free host RAM (and under 55% of physical RAM), and with stream_source: ram, as here, the pre-flight refuses below that rather than thrashing. For the 8B NF4 store that was about 5.1 GB of free RAM when the paper was written (a 3.60 GB store divided by 0.7). At v0.76.0 the 8B NF4 store is 5.70 GB, because large-layer streaming moved the embedding and the output head into it, which is about 8.1 GB of free RAM by the same arithmetic. The config above loads at v0.77.0 (checked with soup doctor --config).

Cite it

bibtex
@misc{makazhan2026exact,
  title        = {Exact Layer Streaming: LoRA Fine-Tuning of an 8B Model on a 4 GB Laptop GPU},
  author       = {Makazhan, Alpamys},
  year         = {2026},
  publisher    = {Zenodo},
  version      = {v3},
  doi          = {10.5281/zenodo.21918325},
  url          = {https://doi.org/10.5281/zenodo.21918325}
}

See also

  • Layer streaming — the mechanism as it ships today, the measured numbers, and the full refusal table.
  • Scaling a streaming run — architectures, batch versus gradient accumulation, the VRAM pre-flight, resume and the disk tier.
  • The disk tier, measured — the v0.76.0 background reader, direct I/O and drive striping, with the figures from the second laptop and every caveat.
  • Measurement records — an index of every record in the benchmarks directory at the v0.77.0 tag, the failures included.
  • Streaming a Mixture-of-Experts base — the v0.77.0 DeepSeek-V3-family, FP8-source and per-expert streaming, which the paper does not cover.
  • Preference losses over streaming — DPO, ORPO, SimPO and KTO against a streamed base, which came after v1 was written and are still outside what the paper measures.
  • Validation on hardware we do not own — the resident reference at 8B, 14B, 32B and 72B in full, which v1 named as its most useful missing experiment and v2 carries.

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.