DemonKing1234 commited on
Commit
08e18ab
·
verified ·
1 Parent(s): d4eb3c8

Upload chat_train.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. chat_train.py +282 -0
chat_train.py ADDED
@@ -0,0 +1,282 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+ import os
3
+ from pathlib import Path
4
+
5
+ import torch
6
+
7
+ from train import TinyTransformerLM, build_vocab, encode_text, make_batch
8
+
9
+
10
+ def load_or_create_model(model_path, text, block_size, n_embd, n_head, n_layer):
11
+ requested_config = {
12
+ "vocab_size": None,
13
+ "block_size": block_size,
14
+ "n_embd": n_embd,
15
+ "n_head": n_head,
16
+ "n_layer": n_layer,
17
+ }
18
+ if model_path.exists():
19
+ checkpoint = torch.load(model_path, map_location="cpu")
20
+ saved_stoi = checkpoint["stoi"]
21
+ fresh_stoi, fresh_itos = build_vocab(text)
22
+ saved_config = checkpoint["config"]
23
+ config_matches = all(saved_config.get(k) == v for k, v in requested_config.items() if v is not None)
24
+ if set(saved_stoi) == set(fresh_stoi) and config_matches:
25
+ model = TinyTransformerLM(**saved_config)
26
+ model.load_state_dict(checkpoint["model"])
27
+ return model, saved_stoi, {int(k): v for k, v in checkpoint["itos"].items()}, saved_config
28
+
29
+ backup = model_path.with_suffix(".old-vocab.pt")
30
+ model_path.replace(backup)
31
+ print(f"old vocabulary checkpoint moved to {backup}")
32
+
33
+ stoi, itos = build_vocab(text)
34
+ config = {
35
+ "vocab_size": len(stoi),
36
+ "block_size": block_size,
37
+ "n_embd": n_embd,
38
+ "n_head": n_head,
39
+ "n_layer": n_layer,
40
+ }
41
+ model = TinyTransformerLM(**config)
42
+ return model, stoi, itos, config
43
+
44
+
45
+ def limit_memory_file(data_path, keep_tail_chars=200_000):
46
+ if not data_path.exists():
47
+ return
48
+ text = data_path.read_text(encoding="utf-8")
49
+ if len(text) <= keep_tail_chars:
50
+ return
51
+ data_path.write_text(text[-keep_tail_chars:], encoding="utf-8")
52
+
53
+
54
+ def normalize_text(text):
55
+ return "".join(ch.lower() if ch.isalpha() else ch for ch in text)
56
+
57
+
58
+ def score_snippet(query, snippet):
59
+ query_chars = set(ch for ch in normalize_text(query) if not ch.isspace())
60
+ snippet_chars = set(ch for ch in normalize_text(snippet) if not ch.isspace())
61
+ if not query_chars or not snippet_chars:
62
+ return 0
63
+ overlap = len(query_chars & snippet_chars)
64
+ return overlap / len(query_chars | snippet_chars)
65
+
66
+
67
+ def retrieve_context(user_text, memory_text, max_examples=3):
68
+ blocks = [b.strip() for b in memory_text.split("\n\n") if b.strip()]
69
+ scored = []
70
+ for block in blocks:
71
+ if "USER:" in block and "AI:" in block:
72
+ scored.append((score_snippet(user_text, block), block))
73
+ scored.sort(key=lambda item: item[0], reverse=True)
74
+ chosen = [block for score, block in scored[:max_examples] if score > 0]
75
+ return "\n\n".join(chosen)
76
+
77
+
78
+ @torch.no_grad()
79
+ def generate_once(model, prompt, stoi, itos, max_new_tokens, temperature):
80
+ fallback = next(iter(stoi.values()))
81
+ idx = torch.tensor([[stoi.get(ch, fallback) for ch in prompt]], dtype=torch.long)
82
+ model.eval()
83
+ for _ in range(max_new_tokens):
84
+ idx_cond = idx[:, -model.block_size :]
85
+ logits, _ = model(idx_cond)
86
+ logits = logits[:, -1, :] / temperature
87
+ probs = torch.softmax(logits, dim=-1)
88
+ next_id = torch.multinomial(probs, num_samples=1)
89
+ idx = torch.cat((idx, next_id), dim=1)
90
+ if itos[int(next_id)] == "\n" and idx.shape[1] > len(prompt) + 20:
91
+ break
92
+ return "".join(itos[int(i)] for i in idx[0])[len(prompt) :]
93
+
94
+
95
+ def score_reply(reply, user_text):
96
+ stripped = reply.strip()
97
+ if not stripped:
98
+ return -100
99
+
100
+ score = 0.0
101
+ lowered = stripped.lower()
102
+ if "user:" in lowered:
103
+ score -= 6
104
+ if "ai:" in lowered:
105
+ score -= 6
106
+ if stripped.count("\n") > 2:
107
+ score -= 2
108
+ if len(set(stripped)) < 4:
109
+ score -= 2
110
+
111
+ words = [word for word in stripped.split() if word]
112
+ if words:
113
+ unique_ratio = len(set(words)) / len(words)
114
+ score += unique_ratio * 3
115
+
116
+ if len(stripped) < 4:
117
+ score -= 2
118
+ if len(stripped) > 220:
119
+ score -= 1
120
+ if user_text and user_text.lower().strip() in lowered:
121
+ score -= 2
122
+ if any(ch.isalpha() for ch in stripped):
123
+ score += 1
124
+ return score
125
+
126
+
127
+ def generate_reply(model, prompt, user_text, stoi, itos, max_new_tokens, temperature, candidates=4):
128
+ best_reply = ""
129
+ best_score = float("-inf")
130
+ for _ in range(candidates):
131
+ reply = generate_once(model, prompt, stoi, itos, max_new_tokens, temperature)
132
+ score = score_reply(reply, user_text)
133
+ if score > best_score:
134
+ best_score = score
135
+ best_reply = reply
136
+ return best_reply
137
+
138
+
139
+ def train_steps(model, text, stoi, steps, batch_size, block_size, lr):
140
+ if len(text) < block_size + 2:
141
+ return
142
+ data = encode_text(text, stoi)
143
+ optimizer = torch.optim.AdamW(model.parameters(), lr=lr)
144
+ model.train()
145
+ for _ in range(steps):
146
+ xb, yb = make_batch(data, batch_size, block_size, "cpu")
147
+ _, loss = model(xb, yb)
148
+ optimizer.zero_grad(set_to_none=True)
149
+ loss.backward()
150
+ optimizer.step()
151
+
152
+
153
+ def save_model(model_path, model, config, stoi, itos):
154
+ model_path.parent.mkdir(parents=True, exist_ok=True)
155
+ torch.save(
156
+ {
157
+ "model": model.state_dict(),
158
+ "config": config,
159
+ "stoi": stoi,
160
+ "itos": itos,
161
+ },
162
+ model_path,
163
+ )
164
+
165
+
166
+ def main():
167
+ parser = argparse.ArgumentParser()
168
+ parser.add_argument("--data", default="data/chat_memory.txt")
169
+ parser.add_argument("--seed-data", default="data/input.txt")
170
+ parser.add_argument("--model", default="runs/chat_model.pt")
171
+ parser.add_argument("--preset", choices=["tiny", "turbo", "small", "big", "large"], default="small")
172
+ parser.add_argument("--steps-per-turn", type=int, default=8)
173
+ parser.add_argument("--tokens", type=int, default=160)
174
+ parser.add_argument("--temperature", type=float, default=1.1)
175
+ parser.add_argument("--batch-size", type=int, default=4)
176
+ parser.add_argument("--block-size", type=int, default=64)
177
+ parser.add_argument("--n-embd", type=int, default=64)
178
+ parser.add_argument("--n-head", type=int, default=2)
179
+ parser.add_argument("--n-layer", type=int, default=1)
180
+ parser.add_argument("--lr", type=float, default=3e-4)
181
+ parser.add_argument("--no-self-train", action="store_true")
182
+ args = parser.parse_args()
183
+
184
+ presets = {
185
+ "tiny": {"steps_per_turn": 4, "tokens": 120, "temperature": 1.0, "batch_size": 4, "block_size": 64, "n_embd": 64, "n_head": 2, "n_layer": 1, "lr": 3e-4},
186
+ "turbo": {"steps_per_turn": 2, "tokens": 100, "temperature": 1.2, "batch_size": 8, "block_size": 32, "n_embd": 64, "n_head": 4, "n_layer": 2, "lr": 1e-3},
187
+ "small": {"steps_per_turn": 8, "tokens": 160, "temperature": 1.1, "batch_size": 4, "block_size": 64, "n_embd": 96, "n_head": 2, "n_layer": 2, "lr": 2.5e-4},
188
+ "big": {"steps_per_turn": 12, "tokens": 180, "temperature": 1.0, "batch_size": 4, "block_size": 96, "n_embd": 192, "n_head": 4, "n_layer": 4, "lr": 2e-4},
189
+ "large": {"steps_per_turn": 16, "tokens": 220, "temperature": 0.95, "batch_size": 2, "block_size": 128, "n_embd": 256, "n_head": 8, "n_layer": 6, "lr": 1.5e-4},
190
+ }
191
+ preset = presets[args.preset]
192
+ if args.steps_per_turn == 8:
193
+ args.steps_per_turn = preset["steps_per_turn"]
194
+ if args.tokens == 160:
195
+ args.tokens = preset["tokens"]
196
+ if args.temperature == 1.1:
197
+ args.temperature = preset["temperature"]
198
+ if args.batch_size == 4:
199
+ args.batch_size = preset["batch_size"]
200
+ if args.block_size == 64:
201
+ args.block_size = preset["block_size"]
202
+ if args.n_embd == 64:
203
+ args.n_embd = preset["n_embd"]
204
+ if args.n_head == 2:
205
+ args.n_head = preset["n_head"]
206
+ if args.n_layer == 1:
207
+ args.n_layer = preset["n_layer"]
208
+ if args.lr == 3e-4:
209
+ args.lr = preset["lr"]
210
+
211
+ data_path = Path(args.data)
212
+ seed_path = Path(args.seed_data)
213
+ model_path = Path(args.model)
214
+
215
+ if not data_path.exists():
216
+ seed = seed_path.read_text(encoding="utf-8") if seed_path.exists() else ""
217
+ data_path.parent.mkdir(parents=True, exist_ok=True)
218
+ data_path.write_text(seed + "\n", encoding="utf-8")
219
+
220
+ text = data_path.read_text(encoding="utf-8")
221
+ model, stoi, itos, config = load_or_create_model(
222
+ model_path, text, args.block_size, args.n_embd, args.n_head, args.n_layer
223
+ )
224
+
225
+ if not torch.cuda.is_available():
226
+ threads = os.cpu_count() or 4
227
+ torch.set_num_threads(threads)
228
+ torch.set_num_interop_threads(1)
229
+ torch.set_float32_matmul_precision("high")
230
+ print(f"CPU optimization: using {threads} threads")
231
+
232
+ print("Tiny chat. Type /quit to exit.")
233
+ print("It uses retrieved examples and can keep training after each turn.")
234
+
235
+ while True:
236
+ try:
237
+ user = input("\nyou> ").strip()
238
+ if user.lower() in {"/quit", "quit", "exit"}:
239
+ save_model(model_path, model, config, stoi, itos)
240
+ print(f"saved {model_path}")
241
+ break
242
+
243
+ if user.startswith("/teach "):
244
+ lesson = user[len("/teach ") :].strip()
245
+ data_path.write_text(data_path.read_text(encoding="utf-8") + f"\nTEACHER: {lesson}\n", encoding="utf-8")
246
+ limit_memory_file(data_path)
247
+ text = data_path.read_text(encoding="utf-8")
248
+ print("learning...")
249
+ train_steps(model, text, stoi, args.steps_per_turn, args.batch_size, args.block_size, args.lr)
250
+ save_model(model_path, model, config, stoi, itos)
251
+ print("learned")
252
+ continue
253
+
254
+ memory_text = data_path.read_text(encoding="utf-8")
255
+ context = retrieve_context(user, memory_text)
256
+ if context:
257
+ prompt = f"{context}\n\nUSER: {user}\nAI:"
258
+ else:
259
+ prompt = f"\nUSER: {user}\nAI:"
260
+ reply = generate_reply(model, prompt, user, stoi, itos, args.tokens, args.temperature).strip()
261
+ if not reply:
262
+ reply = "..."
263
+ reply = reply.replace("USER:", "").replace("AI:", "").strip()
264
+ print(f"ai> {reply}")
265
+
266
+ addition = f"\nUSER: {user}\nAI: {reply}\n"
267
+ data_path.write_text(memory_text + addition, encoding="utf-8")
268
+ limit_memory_file(data_path)
269
+ text = data_path.read_text(encoding="utf-8")
270
+ if not args.no_self_train:
271
+ print("learning from this turn...")
272
+ train_steps(model, text, stoi, args.steps_per_turn, args.batch_size, args.block_size, args.lr)
273
+ save_model(model_path, model, config, stoi, itos)
274
+ print("saved")
275
+ except Exception as exc:
276
+ print(f"error: {exc}")
277
+ save_model(model_path, model, config, stoi, itos)
278
+ print("model saved after error, continue or /quit")
279
+
280
+
281
+ if __name__ == "__main__":
282
+ main()