← all writing

Looped transformers: what a recurrence is worth

· ~15 min read · Draft — figures first, prose still being trimmed

On weight-tied depth, the exchange rate between parameters and FLOPs, and why the loop turns out to be an architecture decision rather than an inference-time dial.

Table of Contents

Here is a result that should be strange. Ouro-1.4B, a 1.4-billion-parameter base model from a ByteDance Seed-led collaboration, scores 82.4 on MATH500 against Qwen3-4B-Base’s 59.6 — a model with three times its parameters and five times its training tokens. On MMLU the same two models go the other way: 67.4 against 73.2 (Zhu et al., 2025, Table 7). It wins by 22.8 points on the maths benchmark and loses by 5.8 on the knowledge one.

The architecture behind that split is the simplest idea in the field: take a block of layers and run it more than once. Same weights, again. Drag the slider.

Figure 1: the loop dial (SVG).
Figure 1 — A loop trades parameters for compute (interactive). Two stored blocks, applied r times: the weight file never changes, the depth and the FLOPs per token scale with r. Ouro loops its whole 24- or 48-layer stack four times; Huginn wraps a 4-layer core, looped ~32 times, in an untied prelude and coda. Everything below is an argument about the exchange rate.

In code the whole idea is one for loop. This is the Huginn layout — a prelude computed once, a core applied r times with the input re-injected on every pass, a coda — with Block an ordinary pre-norm attention-plus-MLP layer. Small enough to run on a laptop, and it does — open the block to see it.

Code — a looped language model in PyTorch, runs on a laptop (34 lines)
class LoopedLM(nn.Module):
    """Prelude -> core x r -> coda, the Huginn layout (Geiping et al., 2025)."""
    def __init__(self, vocab, d, heads=4, n_prelude=2, n_core=4, n_coda=2):
        super().__init__()
        self.embed = nn.Embedding(vocab, d)
        self.prelude = nn.ModuleList(Block(d, heads) for _ in range(n_prelude))
        self.adapter = nn.Linear(2 * d, d)      # A: concat(s, e) -> core input
        # the only weights the loop ever touches:
        self.core = nn.ModuleList(Block(d, heads) for _ in range(n_core))
        self.coda = nn.ModuleList(Block(d, heads) for _ in range(n_coda))
        self.head = nn.Linear(d, vocab)

    def run_prelude(self, tokens):
        T = tokens.shape[1]
        self.mask = torch.triu(torch.full((T, T), float("-inf")), diagonal=1)
        e = self.embed(tokens)
        for blk in self.prelude: e = blk(e, self.mask)
        return e                            # e = P(x), computed once per token

    def run_core(self, e, s):               # s_i = R(e, s_{i-1})
        h = self.adapter(torch.cat([s, e], dim=-1))   # input injected EVERY pass
        for blk in self.core: h = blk(h, self.mask)
        return h

    def run_coda(self, s):
        for blk in self.coda: s = blk(s, self.mask)
        return self.head(s)                 # p = C(s_r)

    def forward(self, tokens, r):
        e = self.run_prelude(tokens)
        s = torch.randn_like(e) * 0.4       # s_0 ~ N(0, 2/5): noise, not e
        for _ in range(r):
            s = self.run_core(e, s)
        return self.run_coda(s)
stored: 537K parameters, of which the core is 200K
at r = 32 the core runs 32 times: 6735K parameter-applications per token
logits: (2, 16, 1000)

Three choices in those lines carry the paper. The input e is concatenated back in at every iteration — without it the fixed point would depend only on the starting state, which is why the uninjected models in §2’s table diverge. The starting state is noise, not the embedding, so that different starts converge to the same trajectory (the “path independence” Huginn borrows from the deep-thinking line). And the core is the only weight matrix the loop touches — the block prints Figure 1 in numbers: 537K parameters stored, 6.7M applied per token. The real model adds a second norm after every sublayer; at 3.5B that turned out to be required.

And one thread I did not expect: the story most people absorbed from 2025 is that loops give you a dial — run more iterations at inference, get more accuracy, a third axis of test-time compute. That is the part the 2026 evidence is least kind to.


The Idea Is Eight Years Old and It Lost on the Merits

Weight-tied depth was tried, measured carefully, and set aside. The Universal Transformer (2018) had striking headline numbers — LAMBADA perplexity 142 against a vanilla Transformer’s 7,321 — but always at matched parameters. Matched compute told a different story, and by 2022 the Universal Transformer’s own co-author had run the comparison that closed the programme.

The 2018–2022 verdictMeasuredSource
A parameter-matched loop costs L× the FLOPs1,998M vs 604M MACs at equal parameters, for +2.0 BLEUSparse UT, Table 1
Tying costs quality, mostly in the FFN−1.5 to −2.5 average points; sharing attention alone is nearly freeALBERT, Table 4
…and much more in a decoder14.3 vs 27.7 BLEU all-shared; untying the first and last layers recovers itSubformer, Table 2
Wall-clock is worse than FLOPs suggestHalf the training throughput; shrunk to vanilla’s width it loses 1.38 BLEUTakase & Kiyono, Table 1
It scales worse than a vanilla stack20.3 vs 11.4 FLOPs, worse on every quality metric at 57% of the params; no UT-XL was runnableTay, Dehghani et al., Table 1
Halting is nearly free to drop — on translationRemoving it costs 0.1 BLEU on WMT14 En–De (worth +1.5 on CFQ)Sparse UT, Tables 2 and 4
A loop does not generalise off its trained depthSix-loop model decoded at one loop: 2.56 BLEU vs 25.84Dabre & Fujita, Table 2

Tay et al.’s summary line: “amongst all ten architectures that we consider, the vanilla Transformer has the best scaling behaviour.” Hold on to the last row — a loop count is a depth the model was fitted to, not a knob. That footnote from 2018 becomes the main event in §7.


What Changed Was the Accounting, Not the Architecture

Nothing above was refuted. What changed is what the numbers are worth.

2020 accounting2025 accounting
You buy quality with…parameters (Kaplan-era scaling)inference compute (test-time scaling)
A loop spends…FLOPs to save parameters — the wrong directionFLOPs without adding parameters — the point
Verdict on “FLOPs without parameters”a liability“a third axis on which to scale model performance” — Geiping et al.

The second change is a training recipe that survives a billion-parameter run — three ingredients, the first two borrowed from the “deep thinking” line, none standard in pre-2024 looped transformers:

IngredientWhat it doesWhy it is load-bearing
Input injectionRe-add the embedded input at every iteration: Yt+1 = M(Yt + P)Without it “the influence of the initial input diminishes, and the solution becomes essentially random” — the uninjected model diverges past its trained loop count (Yang et al., 2023)
Randomised depthSample the loop count per sequence in training; Huginn draws from a log-normal Poisson with mean 32The only known route to a model that still works off its trained depth (§7)
Sandwich normNormalise before and after each sublayerAt small scale every norm works; at scale Huginn found this one “required to train the recurrence”. Ouro adopted it and cites them

Two of the three ingredients are a dozen lines each. Draw the depth per sequence from a distribution with a heavy right tail, and cut the gradient before the last k iterations:

Code — depth sampling and truncated backprop (18 lines)
import math, torch

def sample_depth(r_bar=32, sigma=0.5):
    """Log-normal Poisson unrolling (Huginn): most draws below r_bar, a heavy tail."""
    tau = torch.normal(math.log(r_bar) - sigma ** 2 / 2, sigma, size=())
    return int(torch.poisson(tau.exp())) + 1

def looped_loss(model, tokens, targets, r, k=8):
    """Backprop through only the last k of r iterations; memory stops growing with r."""
    e = model.run_prelude(tokens)
    s = torch.randn_like(e) * 0.4
    with torch.no_grad():                   # iterations 1..r-k: forward only, no graph
        for _ in range(max(0, r - k)):
            s = model.run_core(e, s)
    for _ in range(min(r, k)):              # the last k keep their activations
        s = model.run_core(e, s)            # e still carries gradient into the prelude
    logits = model.run_coda(s)
    return torch.nn.functional.cross_entropy(logits.flatten(0, 1), targets.flatten())
sampled depths: [61, 27, 37, 43, 19, 48, 15, 9, 33, 60]
loss 7.05; gradient reaches prelude=True core=True coda=True

Ten draws at r̄ = 32 range from 9 to 61, and that spread is the whole reason Huginn tolerates depths it was not trained at (§7). The no_grad block is what makes r = 61 affordable: activations are kept for the last eight iterations only, so memory does not grow with depth. The prelude still gets a gradient — e is re-injected inside the graph — and this is exactly the shortcut the φ paper found costs capacity: truncation lowers the exchange rate from 0.45 to 0.38 (§5). Huginn additionally locks one r per micro-batch across workers, so no GPU waits on the deepest draw.


Every Trained Looped Model I Could Find — and Yes, Some Have Shipped

Before the analysis, the cast: every model I found that was actually trained with weight-tied depth. Two patterns. The research models loop many times and stop under 2B parameters. The ones that shipped with open weights — a 40B coder in December 2025, a 7B coder trained on 18T tokens and a 3B generalist trained on 28T tokens in mid-2026 — loop exactly twice, at a fixed depth, with a separate KV cache per pass. Nanbeige reports that more passes gave “only marginal additional improvement” and made optimisation less stable; LoopCoder-V2 found its third loop regressed. The practitioners landed on the same two loops the compute-matched studies in §5 point to.

Model (date · org)Stored → effectiveWhat loopsDepth in training · exitWeightsWhat it showed
2018–2024 · the first wave
Universal Transformer (2018 · Google Brain)base-size (matches Transformer base) → depth set by haltingall encoder and decoder layers, one shared blockACT halting per positioncode only (Tensor2Tensor)LAMBADA ppl 142 vs a Transformer’s 7,321; +0.9 BLEU on WMT14 at matched parameters — at ~3× the compute
ALBERT (2019 · Google)12M–235Mone block ×12 or ×24 (all layers shared)fixedyes — HF18× fewer parameters than BERT-large; all-sharing costs 1.5–2.5 points, mostly in the FFN
Subformer (2021 · U. Tokyo)38M / 52M / 197M“sandwich”: first and last layers untied, the middle sharedfixedcode onlyall-shared 14.3 vs 27.7 BLEU; sandwich recovers 27.7 at 38M
Sparse Universal Transformer (2023 · Mila / IBM)66M baseUT with MoE attention and FFN; stick-breaking haltingadaptive (halting)code only29.2 BLEU at 787M MACs where a dense UT needs 1,998M for 29.3
CoTFormer (2023 · EPFL)12 or 24 layers, repeated 2–5× (params not stated)the stack, with attention able to see earlier iterationsfixed; budget-adaptive variantcode only24.11 ppl vs 24.17 for a 48-layer standard model with twice the parameters
MobileLLM-LS (2024 · Meta)125M / 350M → 60 / 64 effective layerseach block run twice back-to-back (“immediate block-wise sharing”)fixedyes — HF+0.7 / +0.8 points at no size increase — the first one on a phone
Loop Neural Networks (2024 · affiliation not stated)GPT-2-81M (6 layers ×6), 67M (4 ×12)the stack, predicting a residual each passfixednone foundval loss 3.11 on OpenWebText vs 3.12 for GPT-2-124M
Relaxed Recursive Transformer (2024 · Google DeepMind / KAIST)Gemma 2B → 1B recursive, 2 loops (+ per-loop LoRA)one block of unique layers repeated, with a LoRA delta per loopfixed; early-exit training; depth-wise batchingnone foundrecursive Gemma 1B 51.7% few-shot vs Pythia-1B 48.8 after 15B uptraining tokens; at LoRA rank 512, 58.4 vs the full 2B’s 58.6
2025 · recurrent-depth language models
Huginn-0125 (Feb 2025 · ELLIS Tübingen / MPI-IS / UMD)3.5B → 132 effective layers at r = 32 (the FLOPs of a ~32B dense)a 4-layer core inside a 2-layer prelude and coda; input re-injected each steprandom, log-normal Poisson, mean 32; backprop through the last 8; zero-shot KL exityes — HF800B tokens; ≈ first-gen OLMo-7B on most tasks; GSM8K-CoT 34.8 at 3.5B
Inner Thinking Transformer (Feb 2025 · CAS / Baidu)162M–466Mper-token routed extra “thinking steps”, with a step encodingadaptive (token routing)none found162M reaches 96.5% of a 466M model with 43% less data
Mixture-of-Recursions (Jul 2025 · KAIST / Google DeepMind)135M–1.7B (non-embedding)the middle block cycled 2–4×; a router picks each token’s depthadaptive per token; recursion-wise KV caching360M only (Google Drive)at 16.5e18 FLOPs, MoR-2 (167M) beats a 315M vanilla model, 43.1 vs 42.3; 2.06× decode throughput
Encode-Think-Decode (Oct 2025 · Meta FAIR / UCL)OLMo-2 1B4 middle layers iterated 5× between 7 encoder and 5 decoder layers; mid-training only (50B tokens)fixednot stated+28.4% relative on GSM8K, +36% on MATH, same parameters and data
Parallel Loop Transformer (Oct 2025 · ByteDance Seed)680M-active / 13B and 1.7B / 40B MoE2–3 loops computed for different tokens in one pass; first-loop KV sharedfixednot statedmatches a sequential looped model’s 39.7 average at 4.9 ms vs 9.4 ms and half the KV cache
Ouro 1.4B / 2.6B (Oct 2025 · ByteDance Seed + collaborators)1.4B (24 layers) / 2.6B (48, upcycled) → ×4the whole stack, four timesfixed T = 4 (cut from 8); learned entropy-regularised exit gateyes, research-only — HF, plus Thinking variants7.7T tokens; 1.4B ≈ Qwen3-4B-Base on reasoning benchmarks, behind on MMLU; degrades past T = 4
IQuest-Coder-V1-40B-Loop (Dec 2025 · IQuest / Ubiquant)40B dense → 160 block applicationsall 80 layers, twice; iteration 2 attends to iteration 1’s KV through a gated global + local mixfixed 2yes (custom license)the largest looped model with open weights; Instruct and Thinking coding variants (report, Mar 2026)
2026
Parcae (Apr 2026 · UCSD / Together AI)140M–1.3Bprelude / core / coda with a spectrally-constrained injectionsampled per sequence, mean 2–12; backprop through halfyes — HF+2.99 Core over a parameter-matched Transformer at 1.3B; test-time gains plateau at the training depth
Sparse Layers (May 2026 · USC ISI)isoFLOP sweep, ≤711M stored8 unique layers ×2, dense or MoEfixed; training-free entropy early exitnone founda looped MoE matches the dense baseline’s scaling exponent (0.077 vs 0.076); a dense looped model does not
MELT-1.6B (May 2026 · Qualcomm)Ouro-1.4B-Thinking + 0.2B gateOuro’s loop with one gated KV cache shared across loopsfixed T = 4; ~256M tokens of conversionnot yet (“soon”)4× less KV memory (28.0 → 9.5 GB at 32K) for ~3 points of AIME
HRM-Text-1B (May 2026 · Sapient)~1.2Btwo 16-layer stacks in a nested recurrence (the HRM recipe as a language model)fixed nesting (2 × 3)yes — HF40B tokens; released as a pre-alignment base checkpoint
CART (May 2026 · independent)14M–125M storedone shared block ×6–10, cross-attending to K/V frozen from a 6-layer preludefixed; no halting by designcode + results dbnegative: 1–2% worse perplexity than a parameter-matched dense model; prelude depth matters more than loop count
LoopMoE (Jun 2026 · HKUST-GZ / Huawei)3B and 9B total MoE (0.8B / 1.9B active)2 prefix + a 2- or 3-layer shared block ×4 + suffixfixed K = 4none found+1.05 (3B) and +2.81 (9B) average over a matched vanilla MoE, 200–300B tokens
LoopCoder-V2 (Jun 2026 · Beihang / IQuest / Langboat / RUC)7B dense, 14 shared layers ×2the whole stack, twice, PLT-style (shared KV + gated sliding window)fixed per model; R = 1–4 each trained from scratchyes — HF18T tokens; SWE-bench Verified 43.0 → 64.4 from one loop to two; a third loop regresses
Looped World Models (Jun 2026 · FaceMind)~1B — a text world model, not an LMprelude / recurrent block / codaPoisson-sampled with a learnable mean; gated adaptive exitnot statedScienceWorld state-prediction EM 68.4 vs 47.2 for a closed API model — no architecture-matched baseline
DeepLoop (Jul 2026 · Princeton / UCLA)GPT-2 small / mediumthe whole stack ×3–7, with depth-scaled residualsfixed per runcode only−0.02 to −0.03 nats vs a pre-LN looped baseline at R = 7, 50B tokens
Loopie (Jul 2026 · IQuest)20B-A2B and 6B-A0.6B looped MoEeach layer applied twice in placefixed 2announced, not public as of 7 Sep 2026claims to overtake a compute-matched 30B-A3B after ~600B tokens — shown as a figure, no number in the text
Nanbeige4.2-3B (Jul 2026 · Nanbeige Lab)3B non-embedding (4B total) → 44 block applicationsall 22 layers, twice, with a separate KV cache per passfixed 2 (more passes tried and rejected)yes — HF, Apache-2.028T tokens from scratch; two passes “retain approximately 75% of the token efficiency” of a standard Transformer with “a significant capacity gain”; from-scratch beat upcycling; sharing KV across passes cost accuracy
Retrofit of Qwen2.5-0.5B (Jul 2026 · independent)0.5B frozen + a 6M adapter (or a 180M block)layers 6–17 tied and re-entered, between a prelude and codaforced depths 4–12code onlysynthetic rule-composition: 84% vs 72% for a scratchpad-tuned dense twin; holds up to ~1.5× its supervised depth
SMELT (Sep 2026 · Tsinghua / ByteDance Seed / M-A-P)~0.07–1.1B active, up to 54B non-embedding (MoE)the middle half of the layers, exactly twicefixedno (proprietary stack)6.8–18.0% of training FLOPs saved against a baseline matched on FLOPs, parameters and KV cache
Small recursive reasoners (not language models)
HRM (Jun 2025 · Sapient)27Mtwo coupled recurrent modules, plus an outer refinement loopadaptive (ACT-style)yesARC-AGI-1: 40.3% claimed; 32% on ARC Prize’s semi-private re-run, where the hierarchy was worth ~0–5 points and the outer loop did the work
TRM (Oct 2025 · Samsung SAIL Montréal)7M, 2 layersone tiny network, recursedadaptiveyesARC-AGI-1 45% / ARC-AGI-2 8% claimed; an audit found ~11 points from 1,000-sample voting and “shallow effective recursion”
Equilibrium Reasoners (May 2026 · CMU)small (not checked)DEQ-style: iterate to a task-conditioned attractorrun to convergencenot checkedSudoku-Extreme 2.6% → over 99% by unrolling to the equivalent of ~40,000 layers (abstract)

“Stored → effective” is unique parameters against the depth or FLOPs the loop unrolls to. “Weights” means downloadable checkpoints, checked on 7 September 2026, not code. The last column is each paper’s own headline against its own baseline. Reports that OpenAI’s GPT-6 uses a constrained form of recurrent depth are unconfirmed — its system card says nothing about architecture — so it is not in the table.


Loops Buy Reasoning, Not Knowledge

Write (k ⊗ L) for a k-layer block looped L times. It has the parameters of (k ⊗ 1) and the FLOPs of (kL ⊗ 1), so you can ask exactly how much of the gap between the small model and the big one the loop recovers (Saunshi et al., 2025, 1B scale, the Pile).

Horizontal bars showing the share of the quality gap a looped model recovers: perplexity 34 percent, closed-book question answering 37 percent, open-book 56 percent, reasoning primitives 131 percent, math word problems 282 percent, with a dashed line at 100 percent.
Figure 2 — Looping recovers more of the gap the more the task is reasoning. A 12-layer block looped twice, against the 12-layer model it shares parameters with and the 24-layer model it matches in FLOPs (Saunshi et al., Table 3). Past 100% it beats a baseline with twice its parameters. Perplexity — “the primary reason looped models have been ignored for language modeling” — is what it recovers least.
Three independent measurements of the splitReasoning sideKnowledge side
Saunshi et al. — (4 ⊗ 6), one sixth the parameters of the 1B baselineReasoning primitives 56.9 vs the baseline’s 47.5Closed-book QA 6.7 vs 11.2; perplexity 8.79 vs 7.40
Ouro — one loop → four, MMLU by categoryElementary mathematics +155.6%, formal logic +143%Global facts +8.3%, moral scenarios +7.8%
Ouro — knowledge-capacity sweep, “Physics of LMs” protocol (GPT-2-style models, 1M–40M params)—≈2 bits/parameter with or without looping: “looping does not increase knowledge capacity”
Huginn — OpenBookQA closed vs open bookOpen-book 49.2, within 4.2 of OLMo-2Closed-book 38.2, eight points behind — “less capacity to memorize facts but more capacity to reason about its context”

One underrated caveat. Saunshi’s middle-looping variant — untied first and last block, loop the middle — gets closed-book QA back to 11.0 against the baseline’s 11.2, at the cost of most of the math-word-problem gain (34.3 → 28.3). Subformer (2021), Huginn, Mixture-of-Recursions and SMELT all keep the ends untied too. The knowledge penalty looks substantially like an artefact of tying everything, not a law about weight sharing.


A Recurrence Is Worth About the Square Root of a Fresh Block

In April 2026 Schwethelm, Rückert and Kaissis put a number on it, fitting a Chinchilla-style law with recurrence as an exponent on the looped parameters: L = E + A·(N_once + r^φ·N_rec)^(−α) + B·D^(−β). φ = 1 would mean a loop is worth a fresh block; φ = 0 would mean it is worth nothing. Over ~50× in training compute they measure φ = 0.46.

Figure 3: the exchange rate (SVG).
Figure 3 — What one recurrence is worth (interactive). Pick φ and drag across the chart. At r = 4 the measured curve says a block is worth 1.9 blocks, for 4× the FLOPs — the paper’s worked example is a 410M looped model matching a 580M dense one at the training cost of a 1B dense one. Truncated backpropagation (the trick that makes deep unrolling affordable) drops φ to 0.38 “even though validation loss decreases”; hyperconnections raise it to 0.65. φ is a property of the recipe, not of looping.
Does looping beat untied weights at equal compute?Verdict
Ouro, Appendix D — iso-FLOP, iso-depth, five sizes to 1.36BNo. “The standard model exceeds LoopLM under the same conditions. This gap increases with the recurrent step and decreases with the model size.”
Mixture-of-Recursions, Table 3 — 16.5e18 FLOPsNo for plain recursion (NLL 2.81–2.88 vs 2.78). MoR wins only because routing is cheaper per token, so the same budget buys 27B tokens instead of 20B.
Iso-depth law — φ = 0.46No. “Replacing unique blocks with shared recurrences increases validation loss at matched training compute.”
SMELT (Sept 2026) — FLOPs, parameters and KV cache matched, via an MoE; middle half looped exactly twice, up to 1.6B active / 54B non-embeddingYes, modestly: 6.8–18.0% of training FLOPs saved on the compute-optimal frontier. Mechanism: on the second visit, one head’s attention mass moves from the BOS sink (0.60 → 0.02) to the demonstrations (0.24 → 0.85).

SMELT and the φ paper disagree, and nobody has run both protocols on the same models — SMELT’s point is that fixing FLOPs alone shrinks the looped model’s unique parameters, so φ may be measuring parameter loss. Where they agree is the shape: the margin is least unfavourable at the smallest loop counts and erodes as r grows. The most controlled win in the literature loops exactly twice.


A Loop Is Easy to Train Into a No-Op

The best section of the Huginn paper describes the two runs that failed. Two of three attempts at a large recurrent-depth run did not make the loop load-bearing — one collapsed, one left the recurrence decorative — and 2026 turned that folklore into mechanism.

FailureSymptomCauseFix
Huginn, run 1Training stalls; token-wise hidden-state correlation → 1.0“Every iteration of the recurrence block increases token correlation, mixing the sequence until collapse”Sandwich norm, learned adapter, embedding scale
Huginn, run 2Perplexity identical at r = 1 and r = 32 — the loop burns 32× the FLOPs and does nothingModel learned early to ignore the incoming statePeak LR 4e-4 → 4e-5
OuroLoss spikes and gradient oscillations at 8 recurrent steps“Compounded gradient flow through multiple recurrent iterations”Cut to T = 4 — the compromise the headline results run at
Parcae (2026)A plain recurrent-depth model retrofitted from a 140M transformer: “Divergent Training” at every depthSpectral radius of the injection parameters ≥ 1 on every divergent runConstrain it; sample depth per sequence; prelude norm
Residual scaling (2026)“Recurrent architectures require smaller learning rates” (Ouro’s words)Shared weights make residual updates correlated: the sum grows like N, not √NScale residuals by 1/N; then the optimal LR depends only on unique layers and transfers across loop counts
Readout blind spot (2026)Hidden-state norms of 56,051 ± 23,993 at 129M (vs 77 with a scale-visible readout), in loops with no inter-loop normalisationRMSNorm readouts are scale-invariant, so per-loop cross-entropy cannot see the drift it is causingMake scale visible to the loss, or normalise between loops — as Ouro and Huginn do

The sting in the last row: those drifted models are nearly depth-invariant (perplexity moves +0.01 from one loop to four), so a calibrated halting rule exits at an average of 1.00 loops and reports a 3.4× speedup — a spectacular adaptive-compute number from a model that cannot use its loops at all. Carry that lens into every efficiency claim below.


The Test-Time Dial Turns About as Far as You Trained It

Ouro’s own depth ablation is the cleanest test: one checkpoint, evaluated at every loop count from 1 to 8, trained with depth capped at 4. Drag the cursor past the dashed line.

Figure 4: the depth dial (SVG).
Figure 4 — Capped-depth training buys you a peak; randomised-depth training buys you a curve (interactive). Left: Ouro-2.6B-Thinking goes from 76.4 on OlympiadBench at T = 4 to 39.3 at T = 8; the base checkpoint degrades gently. Right: Huginn improves up to its mean training depth, every task but SciQ. The difference is not the architecture but the training distribution — Ouro’s objective is an exit-weighted sum over depths that simply has no mass above 4.
What 2026 says about the dial
Parcae — test-time law across models trained at mean depth 2–12, evaluated to T = 24A saturating exponential, not a power law (fits 7.1× better under extrapolation). “Gains plateau near μrec, suggesting training depth determines the test-time scaling ceiling.”
Iso-depth law“Effective inference depth concentrates near the training depth distribution rather than extrapolating freely past it. We therefore treat r as an architectural, not a test-time, scaling axis.”
Kuo et al. — why Huginn is the exceptionStochastic loop counts in training “sharply reduce OOD variance and stabilize predictions across inference-time loop counts”; when to stop “should be treated as a training-time design choice.”
Huginn’s honest fine printIts table stops at r = 32, its own mean. The only r = 64 GSM8K number (47.23 vs 42.08) comes from a weight-averaged checkpoint never run at r = 32; on MT-Bench the doubling moves 5.662 → 5.693, inside a ±0.39 standard error.
The result that qualifies the pessimismHuginn’s useful depth scales with context: on ARC-Challenge, 0-shot saturates around 8–12 loops, 1-shot around 20, 25–50-shot uses all 32. SMELT’s advantage likewise grows with sample length and shot count.

So loop count behaves like an architectural hyperparameter you commit to at training time. If you want flexibility at inference, you buy it by randomising depth during training, not by turning a knob afterwards — and “more context in, more depth worth spending” is a better argument for recurrence than “turn the dial up.”


Adaptive Depth Is Mostly a Threshold, and the KV Cache Is the Real Bill

If depth is not a free dial, the remaining pitch is adaptive computation: loop where it helps, exit where it does not. Two things get in the way.

ClaimMeasurement
A learned exit gate allocates depth wellOuro’s own heuristic — exit when ‖ht − ht−1‖ is small — tracks the trained gate within 1–2% at 2–3 average rounds. Popescu et al.: on a controlled task a learned gate needs ~2× the depth of a logit-margin threshold; on the reasoning-tuned Ouro checkpoints, in 7 of 12 settings “the best readout is a simple post-hoc heuristic.” Joint gate training deforms the trajectory; the gate itself is fine.
The gate saves computeMeasured on Ouro-1.4B: 1.2–2.0× wall-clock for 0.2–3.6 points. But Qualcomm’s MELT team read the released code: the default threshold “effectively disables early exiting,” and a triggered exit “affects which logits are selected, not whether subsequent loops are computed.”
Ouro’s decode-time KV sharing is free (78.92 → 78.85 GSM8K at 4× less cache)Two groups could not reproduce it. Continuous depth batching: 0.23% with a single shared slot vs 77.86% full — “We cannot reproduce the results from the Ouro paper.” MELT: zero on several benchmarks; generations “degenerate during extended reasoning.” Huginn’s version does replicate: 33.74 → 33.97% collapsing 32 slots to one.
Adaptive depth serves wellThe first end-to-end stack reaches 94–99% of the FLOP bound on Ouro: 1.5–1.9× throughput, 45–90% lower latency under load — but only if freed slots are refilled, because in the memory-bound regime “early exits save FLOPs but not wall-clock time.” And Huginn’s prelude+coda cost ~1.2 core applications, so one exit saves ~6% of a token’s FLOPs against ~25% for Ouro’s bare stack.
You can RL-post-train itOuro tried DAPO and GRPO: no gain. “vLLM/SGLang provide fast rollouts via a fixed execution path, which breaks under LoopLM’s variable-depth computation.” Every 2026 reasoning model is RL-post-trained — the shipped looped coders sidestep this by fixing the loop count at two.

The stopping rules in that table are each three lines, and Huginn’s needs no training at all. Read the logits out at every iteration and stop when they stop changing:

Code — three exit rules and the generation loop (28 lines)
import torch, torch.nn.functional as F

def kl_exit(logits_prev, logits_new, thresh=5e-4):
    """Huginn, zero-shot: stop once consecutive iterations predict the same thing."""
    p, q = F.log_softmax(logits_prev, -1), F.log_softmax(logits_new, -1)
    return (q.exp() * (q - p)).sum(-1).mean().item() < thresh

def margin_exit(logits, thresh=2.0):
    """Popescu et al.: a logit-margin threshold beats the learned gates."""
    top2 = logits.topk(2, dim=-1).values
    return (top2[..., 0] - top2[..., 1]).mean().item() > thresh

def hidden_delta_exit(s_prev, s_new, eps=1e-2):
    """Ouro's baseline heuristic: the state stopped moving."""
    rel = (s_new - s_prev).norm(dim=-1) / s_prev.norm(dim=-1)
    return rel.mean().item() < eps

def generate_step(model, tokens, r_max=32):
    e = model.run_prelude(tokens)
    s = torch.randn_like(e) * 0.4
    prev = model.run_coda(s)
    for i in range(1, r_max + 1):
        s = model.run_core(e, s)
        logits = model.run_coda(s)          # read out at every iteration
        if kl_exit(prev, logits):           # or margin_exit / hidden_delta_exit
            return logits, i
        prev = logits
    return logits, r_max
KL-exit fired after 9 of 32 iterations

The block ends by running the rule on the toy model, and the line it prints is a warning, not a demo: KL-exit fired after 9 of 32 iterations. The model is untrained and its loop still drifts to a fixed point within nine iterations — the layer norms contract the state whether or not the iterations compute anything. A convergence test tells you the state stopped moving, not that the loop did work: the same trap that produced the 3.4× “speedup” from a depth-invariant model in §6.

Figure 5: the KV bill (SVG).
Figure 5 — The loop multiplies your KV cache, not your weights (interactive). Memory for a generation of the chosen length, from vLLM measurements (Vendrell et al., 2026, Table 2): a naive looped model keeps one cache per loop, so a 1.4B model at 32K tokens needs 28 GB where a dense 1.7B needs 7. MELT replaces the per-loop caches with one gated cache for about three points on AIME.

Figure 5 is three lines of arithmetic — K and V, per layer, per head, per stored loop state:

Code — the KV-cache arithmetic behind Figure 5 (16 lines)
def kv_bytes_per_token(layers, kv_heads, head_dim, loops=1, dtype_bytes=2):
    """K and V, per layer, per head, per stored loop state — bf16."""
    return 2 * layers * kv_heads * head_dim * dtype_bytes * loops

# layers, kv_heads, head_dim, stored loop states, weights in GB
models = {
    "Qwen3-1.7B":         (28, 8, 128, 1, 3.44),   # GQA: 8 KV heads
    "MELT-1.6B":          (24, 16, 128, 1, 3.27),  # one gated cache for all 4 loops
    "Ouro-1.4B-Thinking": (24, 16, 128, 4, 2.87),  # one cache per loop
}
ctx = 32_000                    # MELT reports decimal MB/GB at 32,000 tokens
for name, (L, H, D, loops, w_gb) in models.items():
    b = kv_bytes_per_token(L, H, D, loops)
    gb = b * ctx / 1e9
    line = f"{name:19s} {b/1e6:.3f} MB/token -> {gb:5.2f} GB at 32K"
    print(line + f", {w_gb + gb:4.1f} GB with weights")
Qwen3-1.7B          0.115 MB/token ->  3.67 GB at 32K,  7.1 GB with weights
MELT-1.6B           0.197 MB/token ->  6.29 GB at 32K,  9.6 GB with weights
Ouro-1.4B-Thinking  0.786 MB/token -> 25.17 GB at 32K, 28.0 GB with weights

Ouro stores 16 heads of 128 dimensions per layer with no grouped-query sharing and keeps four states, so 0.786 MB per token, the figure in MELT’s table; Qwen3-1.7B’s eight KV heads and one state come to 0.115. At 32,000 tokens that is 25 GB of cache against 3.7.

Does a Loop Go Brrr? Only Above the Saturation Batch

The question Horace He asks of any kernel applies to a decode step: are you bound by compute, by memory bandwidth, or by overhead? Count the bytes and the FLOPs. Every pass through the core re-reads its weights from HBM, and every stored KV state is read once per token generated:

Code — a roofline model of one decode step (25 lines)
BW, PEAK = 3.35e12, 990e12   # H100 SXM: HBM bytes/s, dense bf16 FLOP/s
RIDGE = PEAK / BW            # ~295 FLOP/byte: below this a step is memory-bound

def decode_step(once, loop, r, batch, ctx, kv_tok, d, layers):
    """One decode step for `batch` sequences; weights re-read from HBM every pass."""
    weight_bytes = 2 * (once + r * loop)             # bf16; the core is read r times
    kv_bytes = batch * ctx * kv_tok                  # every sequence's cache, once
    flops = batch * (2 * (once + r * loop)           # 2 FLOP per parameter-application
                     + 4 * layers * d * ctx)         # + attention over the context
    t = max((weight_bytes + kv_bytes) / BW, flops / PEAK)   # the roofline
    return 1e3 * t / batch                           # ms per token

CTX = 4096
# params applied once, params in the loop, r, KV bytes/token, d_model, layers
models = {
    "Huginn 3.5B, r = 32":       (2.0e9, 1.5e9, 32, 2.66e6, 5280, 132),  # 32 KV states
    "Ouro 1.4B, r = 4":          (0.0,   1.4e9,  4, 0.79e6, 2048,  96),  # 4 KV states
    "dense 50B (same FLOPs)":    (50e9,  0,      1, 0.33e6, 8192,  80),  # GQA, 8 KV heads
    "dense 3.5B (same weights)": (3.5e9, 0,      1, 0.13e6, 3072,  32),
}
B = (1, 8, 64, 512)
print(f"{'ms per token':28s}" + "".join(f"{'B=' + str(b):>9s}" for b in B))
for name, (once, loop, r, kv, d, L) in models.items():
    row = [decode_step(once, loop, r, b, CTX, kv, d, L) for b in B]
    print(f"{name:28s}" + "".join(f"{x:9.2f}" for x in row))
ms per token                      B=1      B=8     B=64    B=512
Huginn 3.5B, r = 32             33.10     6.98     3.72     3.31
Ouro 1.4B, r = 4                 4.31     1.38     1.02     0.97
dense 50B (same FLOPs)          30.25     4.13     0.87     0.46
dense 3.5B (same weights)        2.25     0.42     0.19     0.16

This is a model, not a measurement — an H100’s 3.35 TB/s and 990 TFLOP/s, bf16 weights, nothing overlapped — but it says three things the measured papers agree with. Below the ridge (about 295 FLOP per byte, so a few hundred tokens per step) a decode step is memory-bound and the loop turns one weight read into r of them: at batch 1 Huginn spends ~33 ms per token, more than the 50B dense model it matches in FLOPs and fifteen times more than the 3.5B dense model it matches in weights. At large batch the step should become compute-bound and cost what the FLOPs say — but a per-loop KV cache does not let it: with 32 stored states per token, Huginn’s cache traffic dominates and it flattens near 3.3 ms where the dense model reaches 0.5. Ouro flattens near 1 ms for the same reason; MELT’s single gated cache would put it near 0.25. So the memory a loop saves is weight capacity — Huginn trained with data parallelism alone — not bandwidth, and only a shared cache makes the loop bandwidth-neutral. The continuous-depth-batching paper measured exactly this shape: step time flat below a saturation batch, and the saturation batch falling with context length.


Loops Buy Serial Time; Only Tokens Buy Space

Giannou et al. hand-built a nine-layer looped transformer that runs a Turing-complete instruction set: “without this loop, the size of the transformer would have to scale with the number of lines of code.” Saunshi et al. proved a looped model can reproduce any m steps of chain of thought in m loops — given m dummy tokens in the context. That detail is the whole theory in one line.

Chain of thoughtCoconut (latent CoT)Looped transformer
Recurs alongthe sequence, in tokensthe sequence, in hidden statesdepth, inside one token’s forward pass
What grows per stepcontext (a scratchpad)contextnothing — same state, revisited
Needs reasoning datayesyes, a staged curriculumno (Huginn: plain pretraining)
Scales with model sizeyesgains shrink: GSM8K +5.7 at 3B, +1.4 at 8Bpenalty shrinks with size (Ouro App. D); no controlled compute-matched comparison published above ~1B active
Complexity view (Zhang, 2026)polynomial CoT decides P-complete languages—a loop with a compressed state cannot (assuming P ⊄ DSPACE(polylog n)): more loops add time, not scratchpad
Monitorabilityreadable by constructionFlagged as a risk by Korbak et al.; in practice latents are decodable 65–93% of the time when the answer is right — less when it is wrong — and verbalisability tracks per-iteration supervision, i.e. the recipe, not the architecture

Loops win where working state is bounded and steps are many — modular arithmetic, multi-hop composition, graph reachability — and do not win where the task needs an externalised scratchpad.


Scorecard

Claim you’ll hearWhat the measurements say
Loops give you free test-time computeGains plateau at the trained depth; Ouro-2.6B-Thinking loses ~half its accuracy from T = 4 to T = 8. Extrapolation comes from randomised-depth training.
A looped model is as good as a deeper oneφ = 0.46: a recurrence is worth about √r fresh blocks. At r = 4, 410M looped ≈ 580M dense, at the training cost of 1B dense.
Looping beats dense at equal computeUsually no. SMELT wins by 6.8–18% of FLOPs, but with an MoE, matched three ways, at exactly two loops.
Loops make models better reasonersHolds up. ≈2 bits/parameter of storage either way; +155.6% on elementary maths vs +8.3% on global facts.
Adaptive depth saves inference compute1.2–2.0× measured; a post-hoc heuristic is the best stopping rule in 7 of 12 settings; the released Ouro gate does not skip loops.
Weight sharing saves memoryWeights yes, KV cache no — it multiplies by the loop count unless you design against it.
Somebody ships itYes, with open weights — a 40B coder (IQuest, Dec 2025), a 7B coder on 18T tokens (LoopCoder-V2) and a 3B generalist on 28T tokens (Nanbeige4.2), every one looping exactly twice at a fixed depth. Ouro stays research-only, and Google DeepMind’s Recirculation reports that plain training-free looping “does not produce robust benefits” on Gemma3.

What would change my mind, in order:

The case for looping is a case about parameter efficiency — memory, footprint, bandwidth — with a real inductive bias toward multi-step computation. It is not a case about getting compute for free, and 2025 spent a year arguing it was — which is why the models that actually shipped loop twice, at a fixed depth, and stop there. If you take one number away: a recurrence is worth about the square root of a fresh block, and you pay full price in FLOPs either way.


References & Further Reading

  1. The lineage. Graves, Adaptive Computation Time (2016); Dehghani et al., Universal Transformers (2018); Dabre & Fujita, Recurrent Stacking of Layers (2018); Bai, Kolter & Koltun, Deep Equilibrium Models (2019); Lan et al., ALBERT (2019); Reid et al., Subformer (2021); Takase & Kiyono, Lessons on Parameter Sharing (2021); Banino et al., PonderNet (2021); Tay et al., Scaling Laws vs Model Architectures (2022); Tan et al., Sparse Universal Transformer (2023); Mohtashami et al., CoTFormer (2023).
  2. Theory. Giannou et al., Looped Transformers as Programmable Computers (2023); Yang et al., Looped Transformers are Better at Learning Learning Algorithms (2023); Back de Luca & Fountoulakis, Simulation of Graph Algorithms (2024); Fan et al., Looped Transformers for Length Generalization (2024); Xu & Sato, On Expressive Power of Looped Transformers (2024); Saunshi et al., Reasoning with Latent Thoughts (2025); Merrill & Sabharwal, A Little Depth Goes a Long Way (2025); Zhang, Chain-of-Thought and Compressed Looped Transformers (2026).
  3. Recurrent-depth language models. Bae et al., Relaxed Recursive Transformers (2024); Geiping et al., Scaling up Test-Time Compute with Latent Reasoning (Huginn, 2025); Bae et al., Mixture-of-Recursions (2025); Zhu et al., Scaling Latent Reasoning via Looped Language Models (Ouro, 2025).
  4. Scaling laws and stability, 2026. Prairie et al., Parcae; Schwethelm, Rückert & Kaissis, How Much Is One Recurrence Worth?; Sharma & Vu, The Readout Blind Spot; Wang et al., On the Residual Scaling of Looped Transformers; Kuo et al., Stabilizing Extrapolation via Learned Stochastic Stopping; Wang et al., SMELT.
  5. Serving and adaptive depth. Popescu et al., Diagnosing Learned Halting Gates (2026); Vendrell et al., MELT (2026); Schwethelm et al., Continuous Depth Batching (2026); Mozer et al., Recirculation (Google DeepMind, 2026).
  6. Latent reasoning, interpretability, safety. Hao et al., Coconut (2024); Snell et al., Scaling LLM Test-Time Compute Optimally (2024); Korbak et al., Chain of Thought Monitorability (2025); Weng, Why We Think (2025); Blayney et al., A Mechanistic Analysis of Looped Reasoning LMs (2026); Dilgren & Wiegreffe, Are Latent Reasoning Models Easily Interpretable? (2026); Wang & Reid, Looped Transformers under the Jacobian Lens (2026).

Sources were gathered with a fan-out of research agents and every number was checked against the paper it is attributed to; where a claim rests on an abstract or a single small-scale sweep, the tables say so. If you spot an error — especially in the 2026 citations — I would genuinely like to know.