Neural-Labs commited on
Commit
4bc8857
·
verified ·
1 Parent(s): a74f98d

Add Tiny LLM weights, code and model card

Browse files
Files changed (3) hide show
  1. README.md +130 -0
  2. model.pt +3 -0
  3. model.py +66 -0
README.md ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: mit
3
+ language:
4
+ - en
5
+ library_name: pytorch
6
+ pipeline_tag: text-generation
7
+ tags:
8
+ - tiny
9
+ - character-level
10
+ - gpt
11
+ - from-scratch
12
+ - educational
13
+ ---
14
+
15
+ # Tiny LLM
16
+
17
+ A very small GPT-style language model, trained from scratch in about 15 minutes
18
+ on a laptop CPU. It writes one character at a time and only knows simple
19
+ greetings: hello, how are you, what is your name, thanks, goodbye.
20
+
21
+ It is a learning project, not a useful assistant. Anything that is not a
22
+ greeting gets an unrelated greeting-style reply.
23
+
24
+ **Try it in your browser:** [Tiny LLM demo Space](https://huggingface.co/spaces/Neural-Labs/NerualLabs)
25
+
26
+ ## Model details
27
+
28
+ | | |
29
+ | --- | --- |
30
+ | Type | Decoder-only transformer (GPT-style), character level |
31
+ | Parameters | 824,120 |
32
+ | Layers | 4 |
33
+ | Attention heads | 4 |
34
+ | Embedding width | 128 |
35
+ | Context length | 128 characters |
36
+ | Vocabulary | 56 characters (the distinct characters in the training text) |
37
+ | Language | English |
38
+ | License | MIT |
39
+
40
+ Each block uses pre-layer-norm, causal self-attention, and an MLP
41
+ (128 → 512 → 128) with GELU. Token and position embeddings are both learned.
42
+
43
+ ## Files
44
+
45
+ | File | What it is |
46
+ | --- | --- |
47
+ | `model.pt` | Trained weights: a dict with `state_dict`, `chars` (the vocabulary) and `config` |
48
+ | `model.py` | The model code in PyTorch, about 60 lines |
49
+
50
+ ## How to use
51
+
52
+ This is a custom PyTorch model, so it does not load with `transformers`.
53
+ Download `model.py` and `model.pt`, then:
54
+
55
+ ```bash
56
+ pip install torch huggingface_hub
57
+ ```
58
+
59
+ ```python
60
+ import torch
61
+ from huggingface_hub import hf_hub_download
62
+
63
+ repo = "Neural-Labs/tiny-llm"
64
+ hf_hub_download(repo, "model.py", local_dir=".")
65
+ weights = hf_hub_download(repo, "model.pt")
66
+
67
+ from model import TinyGPT
68
+
69
+ ckpt = torch.load(weights, map_location="cpu", weights_only=True)
70
+ chars = ckpt["chars"]
71
+ stoi = {c: i for i, c in enumerate(chars)}
72
+ model = TinyGPT(len(chars), **ckpt["config"])
73
+ model.load_state_dict(ckpt["state_dict"])
74
+ model.eval()
75
+
76
+
77
+ def reply(message):
78
+ # Characters the model never saw are dropped.
79
+ clean = "".join(c for c in message.replace("\n", " ") if c in stoi).strip() or "hi"
80
+ prompt = f"User: {clean[:80]}\nBot:"
81
+ idx = torch.tensor([[stoi[c] for c in prompt]])
82
+ out = ""
83
+ for tok in model.generate(idx, max_new_tokens=100, temperature=0.5, top_k=5):
84
+ if chars[tok] == "\n": # end of the bot's line
85
+ break
86
+ out += chars[tok]
87
+ return out.strip()
88
+
89
+
90
+ print(reply("Hello!"))
91
+ print(reply("How are you?"))
92
+ ```
93
+
94
+ The prompt format must match the training data: `User: <message>\nBot:`.
95
+ Generation stops at the first newline.
96
+
97
+ ## Training data
98
+
99
+ 12,000 generated exchanges in the form
100
+
101
+ ```
102
+ User: <message>
103
+ Bot: <reply>
104
+
105
+ ```
106
+
107
+ built from a short hand-written list of greeting phrases and replies
108
+ (hello, how are you, what is your name, thanks, goodbye, and similar).
109
+ No external dataset was used.
110
+
111
+ ## Training
112
+
113
+ | | |
114
+ | --- | --- |
115
+ | Steps | 4,000 |
116
+ | Batch size | 32 |
117
+ | Optimizer | AdamW |
118
+ | Learning rate | 1e-3 with cosine decay |
119
+ | Hardware | 4-core CPU |
120
+ | Time | 14.5 minutes |
121
+ | Final loss | train 0.165, validation 0.170 |
122
+
123
+ ## Limitations
124
+
125
+ - It only knows greetings. Any other message gets a greeting-style reply that
126
+ has nothing to do with the question.
127
+ - It works on characters, and drops any character that was not in its training
128
+ text (for example most non-English letters and emoji).
129
+ - It has no memory of earlier messages: each reply sees only the latest message.
130
+ - It is far too small to know facts, follow instructions, or reason.
model.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a6b71ffe18697f2e8fd1afdbd8658ad4734a1d2c82d66f9353f8ecef78c71e87
3
+ size 3311561
model.py ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """A very small GPT-style language model (character level)."""
2
+ import torch
3
+ import torch.nn as nn
4
+ import torch.nn.functional as F
5
+
6
+
7
+ class Block(nn.Module):
8
+ """One transformer block: self-attention, then a small feed-forward net."""
9
+
10
+ def __init__(self, n_embd, n_head, dropout):
11
+ super().__init__()
12
+ self.n_head = n_head
13
+ self.ln1 = nn.LayerNorm(n_embd)
14
+ self.qkv = nn.Linear(n_embd, 3 * n_embd)
15
+ self.proj = nn.Linear(n_embd, n_embd)
16
+ self.ln2 = nn.LayerNorm(n_embd)
17
+ self.mlp = nn.Sequential(
18
+ nn.Linear(n_embd, 4 * n_embd), nn.GELU(), nn.Linear(4 * n_embd, n_embd)
19
+ )
20
+ self.dropout = dropout
21
+
22
+ def forward(self, x):
23
+ B, T, C = x.shape
24
+ q, k, v = self.qkv(self.ln1(x)).split(C, dim=2)
25
+ q, k, v = (t.view(B, T, self.n_head, C // self.n_head).transpose(1, 2) for t in (q, k, v))
26
+ # is_causal=True: each character may only look at the ones before it
27
+ a = F.scaled_dot_product_attention(
28
+ q, k, v, is_causal=True, dropout_p=self.dropout if self.training else 0.0
29
+ )
30
+ x = x + self.proj(a.transpose(1, 2).reshape(B, T, C))
31
+ return x + self.mlp(self.ln2(x))
32
+
33
+
34
+ class TinyGPT(nn.Module):
35
+ def __init__(self, vocab_size, block_size=128, n_layer=4, n_head=4, n_embd=128, dropout=0.1):
36
+ super().__init__()
37
+ self.block_size = block_size
38
+ self.tok_emb = nn.Embedding(vocab_size, n_embd)
39
+ self.pos_emb = nn.Embedding(block_size, n_embd)
40
+ self.blocks = nn.ModuleList(Block(n_embd, n_head, dropout) for _ in range(n_layer))
41
+ self.ln_f = nn.LayerNorm(n_embd)
42
+ self.head = nn.Linear(n_embd, vocab_size)
43
+
44
+ def forward(self, idx, targets=None):
45
+ T = idx.shape[1]
46
+ x = self.tok_emb(idx) + self.pos_emb(torch.arange(T, device=idx.device))
47
+ for block in self.blocks:
48
+ x = block(x)
49
+ logits = self.head(self.ln_f(x))
50
+ loss = None
51
+ if targets is not None:
52
+ loss = F.cross_entropy(logits.view(-1, logits.size(-1)), targets.view(-1))
53
+ return logits, loss
54
+
55
+ @torch.no_grad()
56
+ def generate(self, idx, max_new_tokens, temperature=0.8, top_k=None):
57
+ """Yield one new token id at a time, feeding each back in as input."""
58
+ for _ in range(max_new_tokens):
59
+ logits, _ = self(idx[:, -self.block_size:])
60
+ logits = logits[:, -1, :] / max(temperature, 1e-5)
61
+ if top_k:
62
+ kth = torch.topk(logits, min(top_k, logits.size(-1))).values[:, [-1]]
63
+ logits[logits < kth] = -float("inf")
64
+ nxt = torch.multinomial(F.softmax(logits, dim=-1), 1)
65
+ idx = torch.cat([idx, nxt], dim=1)
66
+ yield nxt.item()