AlysiumTinyLLM / model.py
DeepImagix's picture
Alysium TinyLM v1.0 final checkpoint + source (from GitHub)
e644e74 verified
Raw History Blame Contribute Delete
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))