Release MarsNext Phobos-1 (100% 35-Dataset Base Step 33977 + 60-Book 50/50 Replay Post-Trained)
07d2bfc verified Download bpe_tokenizer.py from marsnext/Phobos-1: direct link, hf CLI and curl.
- Browser
- Download file 7.19 kB
-
https://huggingface.co/marsnext/Phobos-1/resolve/main/bpe_tokenizer.py
- Command line
-
hf download hf://marsnext/Phobos-1/bpe_tokenizer.py
-
curl -L -o bpe_tokenizer.py https://huggingface.co/marsnext/Phobos-1/resolve/main/bpe_tokenizer.py
7.19 kB
| #!/usr/bin/env python3 | |
| """ | |
| SynapCore Phobos-Scratch v1 — 32,768-Vocab Byte-Level BPE Tokenizer Trainer & Wrapper | |
| (`phobos-1/models/phobos_scratch_v1/bpe_tokenizer.py`) | |
| ===================================================================================== | |
| Trains a 100% from-scratch Byte-Level BPE tokenizer (`vocab_size = 32768`) using the | |
| Rust-backed Hugging Face `tokenizers` library on the canonical Phobos training corpus: | |
| • Zero `<unk>` tokens (full UTF-8 byte fallback for code, math LaTeX, and Hinglish). | |
| • Dedicated structural & Socratic control tokens (`<|pad|>`, `<|bos|>`, `<|eos|>`, | |
| `<|user|>`, `<|assistant|>`, `<think>`, `</think>`, `<tool_call>`, `</tool_call>`, | |
| plus cortical branch tags `<|branch:E0|>..<|branch:E3|>`). | |
| • Preserves code indentation and multi-digit number splitting (`Digits(individual_digits=True)`). | |
| Strictly <= 200 lines. | |
| """ | |
| from __future__ import annotations | |
| import json | |
| from pathlib import Path | |
| from typing import Any, Dict, Iterator, List, Optional | |
| from tokenizers import Tokenizer, decoders, models, pre_tokenizers, processors, trainers | |
| TOKENIZER_DIR: Path = ( | |
| Path(__file__).resolve().parent / "tokenizer" | |
| if "__file__" in globals() | |
| else Path("/content/phobos_training_data") | |
| ) | |
| DEFAULT_TOKENIZER_PATH: Path = TOKENIZER_DIR / "phobos_bpe_32k.json" | |
| SPECIAL_TOKENS: List[str] = [ | |
| "<|pad|>", | |
| "<|bos|>", | |
| "<|eos|>", | |
| "<|user|>", | |
| "<|assistant|>", | |
| "<think>", | |
| "</think>", | |
| "<tool_call>", | |
| "</tool_call>", | |
| "<|branch:E0|>", | |
| "<|branch:E1|>", | |
| "<|branch:E2|>", | |
| "<|branch:E3|>", | |
| ] | |
| def format_canonical_record_for_lm(rec: Dict[str, Any]) -> str: | |
| """Formats a canonical 8-key JSONL record into the unified Phobos causal training string.""" | |
| user_txt = str(rec.get("user") or "").strip() | |
| think_txt = str(rec.get("think") or "").strip() | |
| asst_txt = str(rec.get("assistant") or "").strip() | |
| if think_txt: | |
| return f"<|bos|><|user|>\n{user_txt}\n<|assistant|>\n<think>\n{think_txt}\n</think>\n{asst_txt}<|eos|>" | |
| return f"<|bos|><|user|>\n{user_txt}\n<|assistant|>\n{asst_txt}<|eos|>" | |
| def _iter_balanced_corpus( | |
| data_dir: Path, max_rows_per_phase: int = 60000 | |
| ) -> Iterator[str]: | |
| """Streams balanced training texts across Phase 1, Phase 2, and Phase 4 JSONL files.""" | |
| for fname in ("phase1_foundation.jsonl", "phase2_specialists.jsonl", "phase4_socratic_think.jsonl"): | |
| fpath = data_dir / fname | |
| if not fpath.exists(): | |
| continue | |
| count = 0 | |
| with open(fpath, "r", encoding="utf-8", errors="ignore") as f: | |
| for line in f: | |
| if count >= max_rows_per_phase: | |
| break | |
| try: | |
| rec = json.loads(line) | |
| yield format_canonical_record_for_lm(rec) | |
| count += 1 | |
| except Exception: | |
| continue | |
| def train_phobos_bpe_tokenizer( | |
| data_dir: Path, | |
| save_path: Path = DEFAULT_TOKENIZER_PATH, | |
| vocab_size: int = 32768, | |
| max_rows_per_phase: int = 60000, | |
| ) -> Dict[str, Any]: | |
| """ | |
| Trains the 32,768-vocab Byte-Level BPE tokenizer on the canonical Phobos JSONL corpus | |
| and verifies lossless roundtrip + compression ratio across domains. | |
| """ | |
| tokenizer = Tokenizer(models.BPE(byte_fallback=True)) | |
| tokenizer.pre_tokenizer = pre_tokenizers.Sequence( | |
| [ | |
| pre_tokenizers.Digits(individual_digits=True), | |
| pre_tokenizers.ByteLevel(add_prefix_space=False), | |
| ] | |
| ) | |
| tokenizer.decoder = decoders.ByteLevel() | |
| tokenizer.post_processor = processors.ByteLevel(trim_offsets=False) | |
| trainer = trainers.BpeTrainer( | |
| vocab_size=vocab_size, | |
| min_frequency=2, | |
| show_progress=False, | |
| special_tokens=SPECIAL_TOKENS, | |
| initial_alphabet=pre_tokenizers.ByteLevel.alphabet(), | |
| ) | |
| tokenizer.train_from_iterator( | |
| _iter_balanced_corpus(data_dir, max_rows_per_phase=max_rows_per_phase), | |
| trainer=trainer, | |
| ) | |
| save_path.parent.mkdir(parents=True, exist_ok=True) | |
| tokenizer.save(str(save_path)) | |
| # Verify roundtrip and compression across all Phobos domains | |
| probe_samples = { | |
| "conversation": "<|bos|><|user|>\nExplain how standard railway gauges were created.\n<|assistant|>\nStandard railway gauges were established to allow trains to move across regions.<|eos|>", | |
| "python_code": "<|bos|><|user|>\nWrite binary search in Python.\n<|assistant|>\n```python\ndef binary_search(arr: list[int], target: int) -> int:\n lo, hi = 0, len(arr) - 1\n return lo\n```<|eos|>", | |
| "math_cot": "<|bos|><|user|>\nSolve 4x - 5 = 19.\n<|assistant|>\n<think>\nAdd 5 to both sides: 4x = 24. Divide by 4: x = 6.\n</think>\n\\boxed{6}<|eos|>", | |
| "sql_tool": "<|bos|><|user|>\nQuery users table.\n<|assistant|>\n<tool_call>\n{\"query\": \"SELECT id, name FROM users WHERE active = 1;\"}\n</tool_call><|eos|>", | |
| } | |
| probes_out: Dict[str, Any] = {} | |
| for name, text in probe_samples.items(): | |
| enc = tokenizer.encode(text) | |
| dec = tokenizer.decode(enc.ids, skip_special_tokens=False) | |
| probes_out[name] = { | |
| "char_len": len(text), | |
| "token_len": len(enc.ids), | |
| "chars_per_token": round(len(text) / max(1, len(enc.ids)), 2), | |
| "exact_roundtrip": (dec == text), | |
| } | |
| return { | |
| "vocab_size": tokenizer.get_vocab_size(), | |
| "save_path": str(save_path), | |
| "special_token_ids": {tok: tokenizer.token_to_id(tok) for tok in SPECIAL_TOKENS}, | |
| "domain_probes": probes_out, | |
| } | |
| class PhobosBPETokenizer: | |
| """Runtime wrapper around `phobos_bpe_32k.json` compatible with `PhobosScratchTrainer`.""" | |
| def __init__(self, tokenizer_path: Path = DEFAULT_TOKENIZER_PATH) -> None: | |
| self.backend = Tokenizer.from_file(str(tokenizer_path)) | |
| self.PAD_ID: int = int(self.backend.token_to_id("<|pad|>")) | |
| self.BOS_ID: int = int(self.backend.token_to_id("<|bos|>")) | |
| self.EOS_ID: int = int(self.backend.token_to_id("<|eos|>")) | |
| self.USER_ID: int = int(self.backend.token_to_id("<|user|>")) | |
| self.ASST_ID: int = int(self.backend.token_to_id("<|assistant|>")) | |
| self.THINK_START_ID: int = int(self.backend.token_to_id("<think>")) | |
| self.THINK_END_ID: int = int(self.backend.token_to_id("</think>")) | |
| self.VOCAB_SIZE: int = int(self.backend.get_vocab_size()) | |
| def encode(self, text: str, max_len: int = 512) -> List[int]: | |
| ids = self.backend.encode(text).ids | |
| if len(ids) > max_len: | |
| ids = ids[: max_len - 1] + [self.EOS_ID] | |
| return ids | |
| def decode(self, token_ids: List[int], skip_special_tokens: bool = False) -> str: | |
| clean = [int(t) for t in token_ids if int(t) != self.PAD_ID] | |
| return self.backend.decode(clean, skip_special_tokens=skip_special_tokens) | |
| if __name__ == "__main__": | |
| data_root = Path("/content/phobos_training_data") | |
| out_tok = data_root / "phobos_bpe_32k.json" | |
| summary = train_phobos_bpe_tokenizer(data_root, save_path=out_tok, vocab_size=32768, max_rows_per_phase=50000) | |
| print(json.dumps(summary, indent=2)) | |