Compactbot commited on
Commit
c6c9cdf
·
verified ·
1 Parent(s): cbe0202

TinyStories-24m: 24.59M BPE GPT trained from scratch on TinyStories (val ppl 8.76, coherent)

Browse files
Files changed (3) hide show
  1. README.md +88 -0
  2. config.json +20 -0
  3. modeling.py +66 -0
README.md ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ pipeline_tag: text-generation
4
+ language: en
5
+ tags:
6
+ - tiny
7
+ - tiny-lm
8
+ - tiny-model
9
+ - slm
10
+ - small-language-model
11
+ - from-scratch
12
+ - tinystories
13
+ - bpe
14
+ - gpt
15
+ datasets:
16
+ - roneneldan/TinyStories
17
+ metrics:
18
+ - perplexity
19
+ ---
20
+
21
+ # TinyStories-24m
22
+
23
+ A **24.59M-parameter** BPE language model trained **from scratch** on
24
+ [roneneldan/TinyStories](https://huggingface.co/datasets/roneneldan/TinyStories),
25
+ producing coherent short stories with proper dialogue, names, punctuation and
26
+ narrative flow.
27
+
28
+ ## What it is
29
+
30
+ - **Architecture:** decoder-only GPT, weight-tied embeddings, RMSNorm, fused
31
+ qkv multi-head causal attention (SDPA), GELU FFN.
32
+ - **Shape:** D=384, L=12 layers, H=8 heads, FFN=1536, SEQ=512, vocab=8192 (BPE).
33
+ - **Params:** 24,585,600 (verified against the safetensors header).
34
+ - **Data:** roneneldan/TinyStories — 447.8M train tokens, 2M held-out val.
35
+ - **Training:** 1 epoch ≈ 13,600 steps, AdamW, cosine LR 6e-4 + 500 warmup,
36
+ bf16 autocast, on a single RTX 5090.
37
+
38
+ ## Quality
39
+
40
+ - **Val perplexity:** 8.76 (2.1618 nats/token on the 2M held-out val set).
41
+ - **Generation:** coherent. Sampled 9/9 seeded generations (3 seeds × 3 prompts)
42
+ produce proper dialogue, character names (Ben, Lily, Mom, Tom, Sarah, Max),
43
+ punctuation and narrative flow. This model is a story generator for its
44
+ training domain — it is **not** a general-purpose assistant and will not
45
+ answer questions it was not trained on.
46
+
47
+ ## Honest caveats
48
+
49
+ - **Divergence:** the full 13,600-step run diverged to NaN at step 9,350 (LR 6e-4
50
+ is too hot for a 24M model). The **best** checkpoint (step 6,000, val 2.1618)
51
+ is what is published here — it is clean and coherent. The divergence is late,
52
+ so a clean early checkpoint is the right artifact; always sample the best
53
+ checkpoint, not the final one.
54
+ - **Domain-bound:** trained only on TinyStories. Out-of-domain text (code,
55
+ questions, general conversation) is out of scope.
56
+
57
+ ## Usage
58
+
59
+ Not a `transformers` model — load with the bundled `modeling.py`:
60
+
61
+ ```python
62
+ import sys, torch
63
+ sys.path.insert(0, "path/to/this/repo")
64
+ from modeling import TinyStoriesGPT
65
+ from tokenizers import Tokenizer
66
+
67
+ m = TinyStoriesGPT.from_pretrained("path/to/this/repo", device="cpu")
68
+ tok = Tokenizer.from_file("path/to/this/repo/tokenizer.json")
69
+
70
+ ids = tok.encode("Ben was playing in the park.", add_special_tokens=False).ids
71
+ x = torch.tensor([ids], dtype=torch.long)
72
+ with torch.no_grad():
73
+ for _ in range(80):
74
+ logits = m(x[:, -512:])[:, -1]
75
+ nxt = torch.multinomial(torch.softmax(logits / 0.8, -1), 1).item()
76
+ ids.append(nxt)
77
+ x = torch.tensor([ids[-512:]], dtype=torch.long)
78
+ print(tok.decode(ids, skip_special_tokens=True))
79
+ ```
80
+
81
+ ## Files
82
+
83
+ | file | bytes | what |
84
+ |------|-------|------|
85
+ | `model.safetensors` | 98,349,056 | 75 tensors, float32 |
86
+ | `config.json` | — | architecture + training metadata |
87
+ | `modeling.py` | — | the `TinyStoriesGPT` class (load with `from_pretrained`) |
88
+ | `tokenizer.json` | 560,804 | BPE-8k tokenizer (HF `tokenizers` format) |
config.json ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": ["TinyStoriesGPT"],
3
+ "model_type": "tinystories-gpt",
4
+ "D": 384,
5
+ "L": 12,
6
+ "H": 8,
7
+ "FFN": 1536,
8
+ "vocab_size": 8192,
9
+ "max_position_embeddings": 512,
10
+ "n_params": 24585600,
11
+ "tie_word_embeddings": true,
12
+ "norm": "RMSNorm",
13
+ "ffn": "GELU",
14
+ "attn": "causal SDPA (fused qkv)",
15
+ "trained_on": "roneneldan/TinyStories",
16
+ "step": 6000,
17
+ "val_nats": 2.1618,
18
+ "val_ppl": 8.76,
19
+ "dtype": "float32"
20
+ }
modeling.py ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """TinyStoriesGPT — 24.59M-param BPE GPT trained on roneneldan/TinyStories.
2
+ Architecture: weight-tied decoder-only GPT, RMSNorm, fused qkv, GELU FFN.
3
+ Not a transformers model — load with this class + safetensors.
4
+ """
5
+ import torch
6
+ import torch.nn as nn
7
+ import torch.nn.functional as F
8
+
9
+ class RMSNorm(nn.Module):
10
+ def __init__(self, d):
11
+ super().__init__()
12
+ self.w = nn.Parameter(torch.ones(d))
13
+ def forward(self, x):
14
+ return self.w * x * torch.rsqrt(x.float().pow(2).mean(-1, keepdim=True) + 1e-6)
15
+
16
+ class Block(nn.Module):
17
+ def __init__(self, d, h, ffn):
18
+ super().__init__()
19
+ self.ln1 = RMSNorm(d)
20
+ self.ln2 = RMSNorm(d)
21
+ self.qkv = nn.Linear(d, 3*d, bias=False)
22
+ self.proj = nn.Linear(d, d, bias=False)
23
+ self.fc1 = nn.Linear(d, ffn, bias=False)
24
+ self.fc2 = nn.Linear(ffn, d, bias=False)
25
+ self.h, self.d = h, d
26
+ def forward(self, x):
27
+ B, T, D = x.shape
28
+ h = self.ln1(x)
29
+ qkv = self.qkv(h).view(B, T, 3, self.h, D//self.h).transpose(2,1)
30
+ q, k, v = qkv[:,0], qkv[:,1], qkv[:,2]
31
+ q, k, v = q.transpose(1,2), k.transpose(1,2), v.transpose(1,2)
32
+ att = F.scaled_dot_product_attention(q, k, v, is_causal=True)
33
+ att = att.transpose(1,2).reshape(B, T, D)
34
+ x = x + self.proj(att)
35
+ x = x + self.fc2(F.gelu(self.fc1(self.ln2(x))))
36
+ return x
37
+
38
+ class TinyStoriesGPT(nn.Module):
39
+ def __init__(self, vocab_size=8192, d=384, n_layers=12, n_heads=8, ffn=1536, seq=512):
40
+ super().__init__()
41
+ self.tok = nn.Embedding(vocab_size, d)
42
+ self.pos = nn.Embedding(seq, d)
43
+ self.blocks = nn.ModuleList([Block(d, n_heads, ffn) for _ in range(n_layers)])
44
+ self.ln_f = RMSNorm(d)
45
+ self.vocab_size = vocab_size
46
+ def forward(self, x, targets=None):
47
+ b, t = x.shape
48
+ h = self.tok(x) + self.pos(torch.arange(t, device=x.device))
49
+ for blk in self.blocks:
50
+ h = blk(h)
51
+ h = self.ln_f(h)
52
+ logits = h @ self.tok.weight.t()
53
+ if targets is not None:
54
+ return F.cross_entropy(logits.float().view(-1, self.vocab_size), targets.view(-1))
55
+ return logits
56
+ @classmethod
57
+ def from_pretrained(cls, path, device="cpu"):
58
+ import json
59
+ from safetensors.torch import load_file
60
+ cfg = json.load(open(f"{path}/config.json"))
61
+ model = cls(vocab_size=cfg["vocab_size"], d=cfg["D"], n_layers=cfg["L"],
62
+ n_heads=cfg["H"], ffn=cfg["FFN"], seq=cfg["max_position_embeddings"])
63
+ sd = load_file(f"{path}/model.safetensors")
64
+ model.load_state_dict(sd)
65
+ model = model.to(device).eval()
66
+ return model