Spaces:
Runtime error
Runtime error
Download ml/tokenizer/train.py from TheAbyss99/abyss-api: direct link, hf CLI and curl.
- Browser
- Download file 4.82 kB
-
https://huggingface.co/spaces/TheAbyss99/abyss-api/resolve/main/ml/tokenizer/train.py
- Command line
-
hf download hf://spaces/TheAbyss99/abyss-api/ml/tokenizer/train.py
-
curl -L -o train.py https://huggingface.co/spaces/TheAbyss99/abyss-api/resolve/main/ml/tokenizer/train.py
4.82 kB
| """ | |
| BPETrainer: learn merge rules from raw text. | |
| Note: this recounts all pairs each iteration (clear but 0(merges x tokens)). | |
| Fine for today's small demo. For tomorrow's full TinyStories run we'll add | |
| incremental pair-count updates so it scales. | |
| """ | |
| import os | |
| import json | |
| from collections import Counter, defaultdict | |
| from .regex_split import split | |
| from .tokenizer import merge_seq | |
| from .special_tokens import SPECIAL_TOKENS, special_token_ids | |
| def _pairs(ids): | |
| """Counter of adjacent id pairs within one word.""" | |
| c = Counter() | |
| for a, b in zip(ids, ids[1:]): | |
| c[(a, b)] += 1 | |
| return c | |
| class BPETrainer: | |
| def __init__(self, vocab_size=16384, verbose=False): | |
| self.vocab_size = vocab_size | |
| self.verbose = verbose | |
| self.merges = [] | |
| self.vocab = {} | |
| # ---- entry points ---- | |
| def train(self, text): | |
| """Train from an in-memory string *used by the test).""" | |
| return self._fit(Counter(split(text))) | |
| def train_from_file(self, path): | |
| """Stream a (possibly multi-GB) text file line by line so we never | |
| hold the whole corpus in memory.""" | |
| word_freqs = Counter() | |
| with open(path, "r", encoding="utf-8") as f: | |
| for i, line in enumerate(f): | |
| if "<|endoftext|>" in line: | |
| line = line.replace("<|endoftext|>", " ") | |
| word_freqs.update(split(line)) | |
| if self.verbose and i and i % 200000 == 0: | |
| print(f" read {i:,} lines, {len(word_freqs):,} unique chunks") | |
| if self.verbose: | |
| print(f" done reading: {len(word_freqs):,} unique chunks") | |
| return self._fit(word_freqs) | |
| # ---- core training ---- | |
| def _fit(self, word_freqs): | |
| num_merges = self.vocab_size - 256 - len(SPECIAL_TOKENS) | |
| if num_merges < 0: | |
| raise ValueError("vocab_size too small for 256 + specials") | |
| # each unique word -> list of byte ids | |
| splits = {w: list(w.encode("utf-8")) for w in word_freqs} | |
| # running pair counts (weighted by word freq) + which words hold each pair | |
| pair_counts = Counter() | |
| where = defaultdict(set) | |
| for w, freq in word_freqs.items(): | |
| for p, c in _pairs(splits[w]).items(): | |
| pair_counts[p] += c * freq | |
| where[p].add(w) | |
| merges = [] | |
| vocab = {i: bytes([i]) for i in range(256)} | |
| for m in range(num_merges): | |
| if not pair_counts: | |
| break | |
| best = max(pair_counts, key=pair_counts.get) | |
| if pair_counts[best] <= 0: | |
| break | |
| new_id = 256 + m | |
| merges.append(best) | |
| vocab[new_id] = vocab[best[0]] + vocab[best[1]] | |
| # only the words containing 'best' can change | |
| for w in list(where[best]): | |
| freq = word_freqs[w] | |
| old = splits[w] | |
| before = _pairs(old) | |
| new = merge_seq(old, best, new_id) | |
| splits[w] = new | |
| after = _pairs(new) | |
| for p in set(before) | set(after): | |
| delta = (after[p] - before[p]) * freq | |
| if delta: | |
| pair_counts[p] += delta | |
| if after[p] > 0: | |
| where[p].add(w) | |
| else: | |
| where[p].discard(w) | |
| if pair_counts.get(p, 0) <= 0: | |
| pair_counts.pop(p, None) | |
| where.pop(p, None) | |
| if self.verbose and (m < 20 or m% 500 == 0): | |
| piece = vocab[new_id].decode("utf-8", errors="replace") | |
| print(f"merge {m:5d}: {best} -> id {new_id} {piece!r}") | |
| self.merges = merges | |
| self.vocab = vocab | |
| return merges, vocab | |
| # ---- save ---- | |
| def save(self, out_dir): | |
| os.makedirs(out_dir, exist_ok=True) | |
| with open(os.path.join(out_dir, "merges.txt"), "w", encoding="utf-8") as f: | |
| for a, b in self.merges: | |
| f.write(f"{a} {b}\n") | |
| # human-readable view (not used for loading, just for inspection) | |
| view = {i: b.decode("utf-8", errors="replace") | |
| for i, b in self.vocab.items()} | |
| for tok, tid in special_token_ids(self.vocab_size).items(): | |
| view[tid] = tok | |
| with open(os.path.join(out_dir, "vocab.json"), "w", encoding="utf-8") as f: | |
| json.dump(view, f, ensure_ascii=False, indent=0) | |
| with open(os.path.join(out_dir, "config.json"), "w", encoding="utf-8") as f: | |
| json.dump({"vocab_size": self.vocab_size}, f) |