Shiv-22 commited on
Commit
f23be9c
·
verified ·
1 Parent(s): 7cf113e

add generate.py

Browse files
Files changed (1) hide show
  1. generate.py +148 -0
generate.py ADDED
@@ -0,0 +1,148 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Generate text from a TinyLM checkpoint.
3
+
4
+ Usage:
5
+ # Download checkpoint from HF automatically:
6
+ python scripts/generate.py --prompt "The theory of relativity states that"
7
+
8
+ # Interactive mode:
9
+ python scripts/generate.py
10
+
11
+ # Local checkpoint:
12
+ python scripts/generate.py --checkpoint checkpoints/step_19999.pt
13
+
14
+ # Greedy decoding:
15
+ python scripts/generate.py --prompt "Once upon a time" --temperature 0
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import argparse
21
+ import sys
22
+ from pathlib import Path
23
+
24
+ import torch
25
+ import torch.nn.functional as F
26
+ from transformers import AutoTokenizer
27
+
28
+ try:
29
+ from tinylm.model import ModelConfig, TinyLM
30
+ except ImportError:
31
+ sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
32
+ from tinylm.model import ModelConfig, TinyLM
33
+
34
+ HF_CHECKPOINT_REPO = "Shiv-22/tinylm-checkpoints"
35
+ HF_CHECKPOINT_FILE = "step_19999.pt"
36
+ TOKENIZER = "meta-llama/Llama-2-7b-hf"
37
+
38
+
39
+ def load_model(checkpoint: str | None, device: str) -> TinyLM:
40
+ if checkpoint is None:
41
+ from huggingface_hub import hf_hub_download
42
+ print(f"Downloading checkpoint from {HF_CHECKPOINT_REPO}...")
43
+ checkpoint = hf_hub_download(
44
+ repo_id=HF_CHECKPOINT_REPO, filename=HF_CHECKPOINT_FILE
45
+ )
46
+ print(f"Loading {checkpoint} ...")
47
+ ckpt = torch.load(checkpoint, map_location="cpu", weights_only=True)
48
+ c = ckpt["config"]
49
+ model = TinyLM(ModelConfig(
50
+ n_layers=c["n_layers"], d_model=c["d_model"], n_heads=c["n_heads"],
51
+ d_latent=c["d_latent"], d_rope=c["d_rope"], ffn_hidden=c["ffn_hidden"],
52
+ ctx=c["ctx"], vocab_size=c["vocab_size"], tie_weights=c["tie_weights"],
53
+ attention=c["attention"],
54
+ ))
55
+ state = ckpt["model"]
56
+ if any(k.startswith("_orig_mod.") for k in state):
57
+ state = {k.removeprefix("_orig_mod."): v for k, v in state.items()}
58
+ model.load_state_dict(state)
59
+ return model.to(device).eval()
60
+
61
+
62
+ @torch.no_grad()
63
+ def generate(
64
+ model: TinyLM,
65
+ tokenizer,
66
+ prompt: str,
67
+ max_new_tokens: int = 200,
68
+ temperature: float = 0.8,
69
+ top_p: float = 0.9,
70
+ device: str = "cpu",
71
+ ) -> str:
72
+ bos = [tokenizer.bos_token_id] if tokenizer.bos_token_id is not None else []
73
+ ids = bos + tokenizer.encode(prompt, add_special_tokens=False)
74
+ tokens = torch.tensor([ids], dtype=torch.long, device=device)
75
+
76
+ for _ in range(max_new_tokens):
77
+ inp = tokens[:, -model.cfg.ctx:]
78
+ logits = model(inp)[:, -1, :].float() # (1, vocab)
79
+
80
+ if temperature == 0.0:
81
+ next_id = logits.argmax(dim=-1, keepdim=True)
82
+ else:
83
+ logits /= temperature
84
+ probs = F.softmax(logits, dim=-1)
85
+ sorted_probs, sorted_ids = torch.sort(probs, descending=True, dim=-1)
86
+ cumsum = sorted_probs.cumsum(dim=-1)
87
+ sorted_probs[cumsum - sorted_probs > top_p] = 0.0
88
+ sorted_probs /= sorted_probs.sum(dim=-1, keepdim=True)
89
+ sample_idx = torch.multinomial(sorted_probs, num_samples=1)
90
+ next_id = sorted_ids.gather(1, sample_idx)
91
+
92
+ tokens = torch.cat([tokens, next_id], dim=1)
93
+ if next_id.item() == tokenizer.eos_token_id:
94
+ break
95
+
96
+ generated = tokens[0, len(ids):].tolist()
97
+ return tokenizer.decode(generated, skip_special_tokens=True)
98
+
99
+
100
+ def main() -> None:
101
+ parser = argparse.ArgumentParser(description=__doc__,
102
+ formatter_class=argparse.RawDescriptionHelpFormatter)
103
+ parser.add_argument("--checkpoint", default=None,
104
+ help="Path to local .pt checkpoint (default: download from HF)")
105
+ parser.add_argument("--prompt", default=None,
106
+ help="Prompt text (omit for interactive mode)")
107
+ parser.add_argument("--max-new-tokens", type=int, default=200)
108
+ parser.add_argument("--temperature", type=float, default=0.8,
109
+ help="Sampling temperature (0 = greedy)")
110
+ parser.add_argument("--top-p", type=float, default=0.9,
111
+ help="Nucleus sampling probability threshold")
112
+ parser.add_argument("--device",
113
+ default="cuda" if torch.cuda.is_available() else "cpu")
114
+ args = parser.parse_args()
115
+
116
+ print(f"Loading tokenizer ({TOKENIZER}) ...")
117
+ tokenizer = AutoTokenizer.from_pretrained(TOKENIZER)
118
+ model = load_model(args.checkpoint, args.device)
119
+ n_params = sum(p.numel() for p in model.parameters())
120
+ print(f"Ready — {n_params / 1e6:.0f}M params on {args.device}\n")
121
+
122
+ def run(prompt: str) -> None:
123
+ out = generate(
124
+ model, tokenizer, prompt,
125
+ max_new_tokens=args.max_new_tokens,
126
+ temperature=args.temperature,
127
+ top_p=args.top_p,
128
+ device=args.device,
129
+ )
130
+ print(f"[prompt] {prompt}")
131
+ print(f"[output] {out}\n")
132
+
133
+ if args.prompt:
134
+ run(args.prompt)
135
+ else:
136
+ print("Interactive mode — enter a prompt and press Enter. Ctrl+C to quit.\n")
137
+ while True:
138
+ try:
139
+ prompt = input(">>> ").strip()
140
+ if prompt:
141
+ run(prompt)
142
+ except (KeyboardInterrupt, EOFError):
143
+ print("\nBye.")
144
+ break
145
+
146
+
147
+ if __name__ == "__main__":
148
+ main()