Download model.py from DeepImagix/AlysiumTinyLLM: direct link, hf CLI and curl.
- Browser
- Download file 3.82 kB
-
https://huggingface.co/DeepImagix/AlysiumTinyLLM/resolve/main/model.py
- Command line
-
hf download hf://DeepImagix/AlysiumTinyLLM/model.py
-
curl -L -o model.py https://huggingface.co/DeepImagix/AlysiumTinyLLM/resolve/main/model.py
3.82 kB
| import numpy as np | |
| class TinyLM: | |
| """ | |
| Word-level language model. | |
| Predicts the next word from the previous CONTEXT_SIZE words. | |
| Supports batched forward/backward passes for fast training. | |
| """ | |
| def __init__(self, vocab_size, embed_dim=64, hidden_dim=128, context_size=5): | |
| self.vocab_size = vocab_size | |
| self.embed_dim = embed_dim | |
| self.hidden_dim = hidden_dim | |
| self.context_size = context_size | |
| rng = np.random.default_rng(42) | |
| self.embeddings = rng.standard_normal((vocab_size, embed_dim)).astype(np.float32) * 0.02 | |
| self.W1 = rng.standard_normal((embed_dim * context_size, hidden_dim)).astype(np.float32) * 0.02 | |
| self.b1 = np.zeros((1, hidden_dim), dtype=np.float32) | |
| self.W2 = rng.standard_normal((hidden_dim, vocab_size)).astype(np.float32) * 0.02 | |
| self.b2 = np.zeros((1, vocab_size), dtype=np.float32) | |
| def num_params(self): | |
| return (self.embeddings.size + self.W1.size + self.b1.size | |
| + self.W2.size + self.b2.size) | |
| def softmax(self, x): | |
| exp = np.exp(x - np.max(x, axis=-1, keepdims=True)) | |
| return exp / np.sum(exp, axis=-1, keepdims=True) | |
| def forward(self, token_ids): | |
| """Single-example forward pass (used at inference time).""" | |
| embs = [self.embeddings[t] for t in token_ids] | |
| x = np.concatenate(embs).reshape(1, -1) | |
| h = np.tanh(np.dot(x, self.W1) + self.b1) | |
| logits = np.dot(h, self.W2) + self.b2 | |
| probs = self.softmax(logits) | |
| return x, h, probs | |
| def forward_batch(self, token_id_batch): | |
| """ | |
| token_id_batch: (B, context_size) int array | |
| Returns x (B, embed_dim*context_size), h (B, hidden_dim), probs (B, vocab_size) | |
| """ | |
| B = token_id_batch.shape[0] | |
| x = self.embeddings[token_id_batch].reshape(B, -1) | |
| h = np.tanh(x @ self.W1 + self.b1) | |
| logits = h @ self.W2 + self.b2 | |
| probs = self.softmax(logits) | |
| return x, h, probs | |
| def train_step(self, token_ids, target, lr=0.01): | |
| """Single-example training step (kept for compatibility).""" | |
| return self.train_batch( | |
| np.array([token_ids], dtype=np.int64), | |
| np.array([target], dtype=np.int64), | |
| lr=lr, | |
| ) | |
| def train_batch(self, token_id_batch, target_batch, lr=0.01): | |
| """ | |
| Vectorized batched training step. | |
| token_id_batch: (B, context_size) int array | |
| target_batch: (B,) int array | |
| Returns mean loss over the batch. | |
| """ | |
| B = token_id_batch.shape[0] | |
| x, h, probs = self.forward_batch(token_id_batch) | |
| target_probs = probs[np.arange(B), target_batch] | |
| loss = -np.mean(np.log(target_probs + 1e-9)) | |
| dout = probs.copy() | |
| dout[np.arange(B), target_batch] -= 1.0 | |
| dout /= B | |
| dW2 = h.T @ dout | |
| db2 = dout.sum(axis=0, keepdims=True) | |
| dh = dout @ self.W2.T | |
| dtanh = (1.0 - h * h) * dh | |
| dW1 = x.T @ dtanh | |
| db1 = dtanh.sum(axis=0, keepdims=True) | |
| dx = dtanh @ self.W1.T | |
| self.W2 -= lr * dW2 | |
| self.b2 -= lr * db2 | |
| self.W1 -= lr * dW1 | |
| self.b1 -= lr * db1 | |
| dx = dx.reshape(B, self.context_size, self.embed_dim) | |
| np.add.at(self.embeddings, token_id_batch, -lr * dx) | |
| return loss | |
| def sample(self, probs, temperature=0.6, top_k=10): | |
| logits = np.log(np.clip(probs[0], 1e-9, 1.0)) / max(temperature, 0.05) | |
| if top_k and top_k < len(logits): | |
| idx = np.argpartition(logits, -top_k)[-top_k:] | |
| mask = np.full_like(logits, -1e9) | |
| mask[idx] = logits[idx] | |
| logits = mask | |
| exp = np.exp(logits - np.max(logits)) | |
| p = exp / np.sum(exp) | |
| return int(np.random.choice(len(p), p=p)) | |