tiny-dllm / 06_generate.py
sutharsan-311's picture
Upload folder using huggingface_hub
c9d2062 verified
Raw
History Blame Contribute Delete
6.45 kB
"""
Phase 5: Generate text using a trained checkpoint
---------------------------------------------------
Load a checkpoint from 05_train.py and generate text.
python 06_generate.py
python 06_generate.py --steps 30 --len 200
"""
import sys, math, json
import torch
import torch.nn as nn
import torch.nn.functional as F
# ── Model (same as 05) ────────────────────────────────────────────────────────
class SelfAttention(nn.Module):
def __init__(self, hidden, n_heads):
super().__init__()
self.n_heads = n_heads
self.head_dim = hidden // n_heads
self.qkv = nn.Linear(hidden, 3 * hidden, bias=False)
self.out = nn.Linear(hidden, hidden, bias=False)
def forward(self, x):
B, T, C = x.shape
q, k, v = self.qkv(x).chunk(3, dim=-1)
def split(t): return t.view(B,T,self.n_heads,self.head_dim).transpose(1,2)
q, k, v = split(q), split(k), split(v)
scores = (q @ k.transpose(-2,-1)) / math.sqrt(self.head_dim)
out = (F.softmax(scores,-1) @ v).transpose(1,2).contiguous().view(B,T,C)
return self.out(out)
class FeedForward(nn.Module):
def __init__(self, hidden):
super().__init__()
self.net = nn.Sequential(
nn.Linear(hidden, 4*hidden), nn.GELU(), nn.Linear(4*hidden, hidden))
def forward(self, x): return self.net(x)
class TransformerBlock(nn.Module):
def __init__(self, hidden, n_heads):
super().__init__()
self.norm1 = nn.LayerNorm(hidden); self.attn = SelfAttention(hidden, n_heads)
self.norm2 = nn.LayerNorm(hidden); self.ff = FeedForward(hidden)
def forward(self, x):
return x + self.ff(self.norm2(x + self.attn(self.norm1(x))))
class TinyDLLM(nn.Module):
def __init__(self, vocab_size, hidden=256, n_layers=4, n_heads=4, max_seq=128):
super().__init__()
self.mask_token_id = vocab_size
full_vocab = vocab_size + 1
self.token_emb = nn.Embedding(full_vocab, hidden)
self.pos_emb = nn.Embedding(max_seq, hidden)
self.blocks = nn.Sequential(*[TransformerBlock(hidden, n_heads) for _ in range(n_layers)])
self.norm = nn.LayerNorm(hidden)
self.head = nn.Linear(hidden, vocab_size, bias=False)
self.head.weight = nn.Parameter(self.token_emb.weight[:vocab_size])
def forward(self, token_ids):
B, T = token_ids.shape
pos = torch.arange(T, device=token_ids.device)
x = self.token_emb(token_ids) + self.pos_emb(pos)
return self.head(self.norm(self.blocks(x)))
# ── Sampling ──────────────────────────────────────────────────────────────────
@torch.no_grad()
def sample(model, seq_len, n_steps, temperature=1.0, top_k=0, device='cpu'):
"""
temperature > 1 β†’ more random/creative
temperature < 1 β†’ more conservative/repetitive
top_k > 0 β†’ restrict sampling to top-k tokens per position
"""
mask_id = model.mask_token_id
tokens = torch.full((1, seq_len), mask_id, dtype=torch.long, device=device)
print(f"Generating {seq_len} chars in {n_steps} diffusion steps...")
for step in range(n_steps):
frac = (step + 1) / n_steps
target = int(frac * seq_len)
logits = model(tokens) / temperature
if top_k > 0:
vocab_size = logits.size(-1)
k = min(top_k, vocab_size)
threshold, _ = logits.topk(k, dim=-1)
logits = logits.masked_fill(logits < threshold[..., -1:], float('-inf'))
probs = F.softmax(logits, dim=-1)
predicted = torch.multinomial(probs.view(seq_len, -1), 1).view(1, seq_len)
confidence, _ = probs.max(dim=-1)
still_masked = (tokens == mask_id)
confidence[~still_masked] = -1.0
already = (~still_masked).sum().item()
to_unmask = max(0, target - already)
if to_unmask > 0 and still_masked.any():
_, idx = confidence.view(-1).topk(min(to_unmask, still_masked.sum().item()))
flat = tokens.view(-1)
flat[idx] = predicted.view(-1)[idx]
tokens = flat.view(1, seq_len)
# show progress
unmasked = (~(tokens == mask_id)).sum().item()
bar = 'β–ˆ' * int(unmasked / seq_len * 20) + 'β–‘' * (20 - int(unmasked / seq_len * 20))
print(f"\r [{bar}] step {step+1}/{n_steps}", end='', flush=True)
print()
return tokens
# ── Main ──────────────────────────────────────────────────────────────────────
def main():
args = sys.argv[1:]
n_steps = int(args[args.index('--steps') + 1]) if '--steps' in args else 20
seq_len = int(args[args.index('--len') + 1]) if '--len' in args else 128
temp = float(args[args.index('--temp')+ 1]) if '--temp' in args else 1.0
top_k = int(args[args.index('--topk') + 1]) if '--topk' in args else 0
device = 'cuda' if torch.cuda.is_available() else 'cpu'
# find latest checkpoint
import os, glob
ckpts = sorted(glob.glob('checkpoints/dllm_step*.pt'), key=lambda p: int(p.split('step')[1].split('.')[0]))
if not ckpts:
print("No checkpoint found. Run 05_train.py first.")
return
ckpt_path = ckpts[-1]
print(f"Loading {ckpt_path}")
ckpt = torch.load(ckpt_path, map_location=device, weights_only=True)
vocab = json.load(open('checkpoints/vocab.json'))
i2c = {int(v): k for k, v in vocab.items()}
model = TinyDLLM(
ckpt['vocab_size'], ckpt['hidden'],
ckpt['n_layers'], ckpt['n_heads'], ckpt['seq_len']
).to(device)
model.load_state_dict(ckpt['model'])
model.eval()
print(f"Loaded checkpoint from step {ckpt['step']}")
max_len = ckpt['seq_len']
if seq_len > max_len:
print(f"Warning: --len {seq_len} exceeds trained seq_len {max_len}, clamping.")
seq_len = max_len
tokens = sample(model, seq_len, n_steps, temp, top_k, device)
text = ''.join(i2c.get(i, '?') for i in tokens[0].tolist())
print("\n" + "─" * 60)
print(text)
print("─" * 60)
if __name__ == '__main__':
main()