SimLoop 1+loop x1+1 (10.0M)

A looped (weight-tied recurrent) Qwen3-style transformer trained from scratch on FineWeb under a hard budget of <= 10M parameters and <= 100M training tokens.

One middle block is applied K = 1 times; the layers around it are ordinary unlooped layers:

embed -> [1 layer] -> ( 1 looped layer ) x K -> [1 layer] -> RMSNorm -> tied head
parameters 9,962,496 (incl. embeddings, input/output tied)
d_model / d_mlp 384 / 720
heads (GQA) 6 query / 2 key-value, head_dim 64
context 512 tokens
vocabulary 16,384 byte-level BPE trained on FineWeb
training tokens 60,014,592
primitives RMSNorm, RoPE, SwiGLU, GQA, QK-norm

Results

k CE (nats) perplexity bits-per-byte
0 5.4088 223.35 1.8913
1 4.3966 81.17 1.5374 <- best
2 4.5995 99.43 1.6083
3 4.9413 139.95 1.7278
4 5.2659 193.62 1.8413
5 5.5476 256.63 1.9399
6 5.7900 327.01 2.0246
7 6.0000 403.41 2.0980
8 6.1835 484.69 2.1622
9 6.3452 569.77 2.2188
10 6.4887 657.65 2.2689
11 6.6166 747.41 2.3137
12 6.7313 838.20 2.3537

Reference points on the same validation split: a context-free unigram model scores CE 7.5476 (ppl 1896.10); uniform over the vocabulary scores CE 9.7041.

k is the number of applications of the looped block at inference. The model is weight-tied, so any k can be run; the table is a single checkpoint evaluated at every depth.

Measured behaviour: TRAINED UNLOOPED (K=1): the k>1 rows show how a model trained at one application behaves when looped anyway, not whether looping pays.

  • first application of the looped block buys +1.0122 nats (k=0 -> k=1)
  • all further applications buy +0.0000 nats (k=1 -> k=1)

Usage

import torch
from simloop.stack import StackConfig, StackedLoop

ck = torch.load("model.pt", map_location="cpu", weights_only=False)
model = StackedLoop(StackConfig(**ck["model_cfg"]))
model.load_state_dict(ck["model"]); model.eval()

ids = torch.tensor([[1, 2, 3]])            # from tokenizer.json
logits = model(ids, K=1)           # try other K: the block is tied

See load_example.py. The tokenizer is a tokenizers BPE: Tokenizer.from_file("tokenizer.json").

Honest limitations

  • Trained on 60,014,592 tokens at ~10M parameters. It is a research artifact for studying looped depth, not a useful general-purpose language model.
  • Perplexity is tokenizer-dependent; bits-per-byte is the comparable number and is reported above.
  • The looped block saturates: past the depth listed as best above, extra applications make cross-entropy worse, not better. This is measured, not assumed, and is the central finding of the project.
  • English-only, no instruction tuning, no safety filtering beyond FineWeb's.

Full experimental record, including every failed experiment: https://github.com/brkdrd/SimLoop

Downloads last month
10
Safetensors
Model size
9.96M params
Tensor type
F32
·
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Dataset used to train brkdrd/simloop