| |
| """PALIMPSESTE — Train the 1B-scale model on real data. |
| |
| Combines the built-in conversational corpus with a real trivia dataset |
| (TriviaQA, 3000 Q/A pairs) and trains at D=100,000 (the '1b' preset — |
| theoretical capacity ~2^25000 ≈ 10^7525 associations). |
| |
| Usage: |
| python examples/train_1b.py --output ./palimpseste-1b --n-trivia 2000 |
| python examples/train_1b.py --output ./palimpseste-1b --push-to-hub thefinalboss/palimpseste-1b |
| """ |
|
|
| from __future__ import annotations |
|
|
| import argparse |
| import json |
| import sys |
| import time |
| from pathlib import Path |
|
|
| import numpy as np |
|
|
| sys.path.insert(0, str(Path(__file__).resolve().parent)) |
| sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) |
|
|
| from palimseste.lm import PalimpsesteConfig, PRESETS |
| from palimseste.hf import HFPalimpsesteLM |
| from corpus_chat import get_corpus as get_chat_corpus |
|
|
|
|
| def parse_args() -> argparse.Namespace: |
| p = argparse.ArgumentParser( |
| description="Train the PALIMPSESTE 1B-scale model on real data.", |
| formatter_class=argparse.RawDescriptionHelpFormatter, |
| ) |
| p.add_argument("--output", "-o", type=str, required=True) |
| p.add_argument("--n-trivia", type=int, default=2000, |
| help="number of TriviaQA pairs to use (default 2000)") |
| p.add_argument("--trivia-file", type=str, default="trivia_qa_pairs.json", |
| help="path to extracted TriviaQA pairs JSON") |
| p.add_argument("--D", type=int, default=100000, |
| help="hypervector dimension (default 100000 = 1b preset)") |
| p.add_argument("--context-window", type=int, default=80) |
| p.add_argument("--kernel-radius", type=int, default=800) |
| p.add_argument("--push-to-hub", type=str, default=None) |
| p.add_argument("--seed", type=int, default=42) |
| return p.parse_args() |
|
|
|
|
| def load_trivia(path: str, n: int) -> list[tuple[str, str]]: |
| p = Path(path) |
| if not p.exists(): |
| print(f"warning: {path} not found, downloading TriviaQA...", file=sys.stderr) |
| from datasets import load_dataset |
| ds = load_dataset('mandarjoshi/trivia_qa', 'rc.nocontext', |
| split='train', streaming=True) |
| pairs = [] |
| for i, ex in enumerate(ds): |
| q = ex['question'].strip().lower() |
| ans = ex['answer']['value'].strip().lower() |
| if q and ans and len(ans) < 100: |
| pairs.append((q, ans)) |
| if i >= n * 2: |
| break |
| with open(path, 'w', encoding='utf-8') as f: |
| json.dump([{'q': q, 'a': a} for q, a in pairs], f, ensure_ascii=False) |
| return pairs[:n] |
| with open(path, 'r', encoding='utf-8') as f: |
| data = json.load(f) |
| return [(d['q'], d['a']) for d in data[:n]] |
|
|
|
|
| def main() -> None: |
| args = parse_args() |
|
|
| |
| chat_pairs = get_chat_corpus() |
| trivia_pairs = load_trivia(args.trivia_file, args.n_trivia) |
| all_pairs = chat_pairs + trivia_pairs |
|
|
| print("=" * 70) |
| print("PALIMPSESTE — 1B-SCALE TRAINING ON REAL DATA") |
| print("=" * 70) |
| cap_log2 = float(np.log2(0.14 * args.D) + args.D / 4.0) |
| print(f"D = {args.D:,} (theoretical capacity: 2^{cap_log2:.0f} ≈ 10^{cap_log2*0.30103:.0f})") |
| print(f"context_window = {args.context_window} kernel_radius = {args.kernel_radius}") |
| print(f"chat corpus: {len(chat_pairs)} pairs") |
| print(f"trivia corpus: {len(trivia_pairs)} pairs") |
| print(f"total: {len(all_pairs)} pairs") |
| print() |
|
|
| config = PalimpsesteConfig( |
| D=args.D, |
| context_window=args.context_window, |
| kernel_radius=args.kernel_radius, |
| temperature=0.3, |
| ) |
| rng = np.random.default_rng(args.seed) |
| lm = HFPalimpsesteLM(config=config, rng=rng) |
|
|
| |
| full_text = "".join(q + a for q, a in all_pairs) |
| lm.build_tokenizer(full_text) |
| print(f"vocab: {lm.tokenizer.vocab_size} chars") |
|
|
| |
| t0 = time.perf_counter() |
| n_tokens = lm.train_on_qa_pairs(all_pairs, verbose=True) |
| dt = time.perf_counter() - t0 |
| print(f"\ntrained: {n_tokens:,} tokens in {dt:.1f}s ({n_tokens/dt:.0f} tok/s)") |
| print(f"|M| = {len(lm.mem):,} traces") |
|
|
| |
| print("\n--- chat quality test ---") |
| chat_tests = ["bonjour", "qui es-tu", "tu utilises un gpu", "who are you", |
| "what is python", "merci"] |
| for q in chat_tests: |
| r = lm.respond(q, temperature=0.0, seed=0) |
| ok = "✓" if r and "desole" not in r.lower() else "✗" |
| print(f" {ok} {q:30s} -> {r[:70]}") |
|
|
| print("\n--- trivia recall test (exact match required) ---") |
| trivia_tests = trivia_pairs[:10] |
| correct = 0 |
| for q, expected in trivia_tests: |
| r = lm.respond(q, temperature=0.0, seed=0) |
| ok = "✓" if expected in r else "✗" |
| if expected in r: |
| correct += 1 |
| print(f" {ok} {q[:50]:50s} -> {r[:40]} (expected: {expected})") |
| print(f" trivia recall: {correct}/{len(trivia_tests)}") |
|
|
| |
| out = Path(args.output) |
| print(f"\nsaving to {out} ...") |
| lm.save_pretrained(out) |
| sz = sum(f.stat().st_size for f in out.rglob('*') if f.is_file()) |
| print(f"saved: {sz / 1e6:.1f} MB") |
|
|
| |
| if args.push_to_hub: |
| print(f"\npushing to HF Hub: {args.push_to_hub} ...") |
| url = lm.push_to_hub(args.push_to_hub) |
| print(f"pushed: {url}") |
|
|
| print("\n" + "=" * 70) |
| print("DONE. Load with:") |
| print(f" from palimseste.hf import HFPalimpsesteLM") |
| print(f" lm = HFPalimpsesteLM.from_pretrained('{args.push_to_hub or out}')") |
| print("=" * 70) |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|