| import argparse |
| import json |
| import random |
| from pathlib import Path |
|
|
| import numpy as np |
| from tokenizers import Tokenizer |
| from tqdm import tqdm |
|
|
|
|
| def split_clean_text(text, max_chars=20000): |
| if "<|eos|>" in text: |
| parts = text.split("<|eos|>") |
| else: |
| parts = [] |
| current = [] |
| current_len = 0 |
| for paragraph in text.split("\n\n"): |
| paragraph = paragraph.strip() |
| if not paragraph: |
| continue |
| if current and current_len + len(paragraph) > max_chars: |
| parts.append("\n\n".join(current)) |
| current = [] |
| current_len = 0 |
| current.append(paragraph) |
| current_len += len(paragraph) |
| if current: |
| parts.append("\n\n".join(current)) |
|
|
| return [part.strip() for part in parts if len(part.strip()) >= 200] |
|
|
|
|
| def load_texts(include_clean=True, include_instructions=True, clean_globs=None, instruction_globs=None): |
| texts = [] |
|
|
| if include_clean: |
| clean_paths = [] |
| for pattern in clean_globs or ["data/clean/*.txt"]: |
| clean_paths.extend(Path().glob(pattern)) |
| for path in sorted(set(clean_paths)): |
| text = path.read_text(encoding="utf-8", errors="ignore").strip() |
| for chunk in split_clean_text(text): |
| texts.append(f"<|bos|><|godot|>\n{chunk}\n<|eos|>\n") |
|
|
| if include_instructions: |
| instruction_paths = [] |
| for pattern in instruction_globs or ["data/instructions/*.jsonl"]: |
| instruction_paths.extend(Path().glob(pattern)) |
| for path in sorted(set(instruction_paths)): |
| with path.open("r", encoding="utf-8") as f: |
| for line_no, line in enumerate(f, start=1): |
| line = line.strip() |
| if not line: |
| continue |
| item = json.loads(line) |
| instruction = item["instruction"].strip() |
| answer = item["answer"].strip() |
| domain = item.get("domain", "godot").strip().lower() |
| domain_tags = { |
| "godot": "<|godot|>\n", |
| "unity": "Domain: Unity\n", |
| "unreal": "Domain: Unreal Engine\n", |
| "general": "", |
| } |
| domain_tag = domain_tags.get(domain, "") |
| text = ( |
| f"<|bos|>{domain_tag}" |
| "<|user|>\n" |
| f"{instruction}\n" |
| "<|assistant|>\n" |
| f"{answer}\n" |
| "<|eos|>\n" |
| ) |
| texts.append(text) |
|
|
| random.shuffle(texts) |
| return texts |
|
|
|
|
| def encode_all(texts, tokenizer): |
| ids = [] |
| eos_id = tokenizer.token_to_id("<|eos|>") |
| for text in tqdm(texts, desc="Encoding"): |
| encoded = tokenizer.encode(text).ids |
| ids.extend(encoded) |
| if eos_id is not None and (not encoded or encoded[-1] != eos_id): |
| ids.append(eos_id) |
| return np.array(ids, dtype=np.uint16) |
|
|
|
|
| def main(): |
| parser = argparse.ArgumentParser() |
| parser.add_argument("--tokenizer", default="tokenizer/tokenizer.json") |
| parser.add_argument("--out-dir", default="data/tokens") |
| parser.add_argument("--val-ratio", type=float, default=0.02) |
| parser.add_argument("--seed", type=int, default=42) |
| parser.add_argument("--include-clean", action=argparse.BooleanOptionalAction, default=True) |
| parser.add_argument("--include-instructions", action=argparse.BooleanOptionalAction, default=True) |
| parser.add_argument("--clean-glob", action="append") |
| parser.add_argument("--instruction-glob", action="append") |
| args = parser.parse_args() |
|
|
| random.seed(args.seed) |
| tokenizer = Tokenizer.from_file(args.tokenizer) |
| texts = load_texts( |
| include_clean=args.include_clean, |
| include_instructions=args.include_instructions, |
| clean_globs=args.clean_glob, |
| instruction_globs=args.instruction_glob, |
| ) |
| if len(texts) < 2: |
| raise SystemExit("Need at least two texts to create train/val splits") |
|
|
| split = max(1, int(len(texts) * (1.0 - args.val_ratio))) |
| split = min(split, len(texts) - 1) |
| train_texts = texts[:split] |
| val_texts = texts[split:] |
|
|
| out_dir = Path(args.out_dir) |
| out_dir.mkdir(parents=True, exist_ok=True) |
|
|
| train_ids = encode_all(train_texts, tokenizer) |
| val_ids = encode_all(val_texts, tokenizer) |
| train_ids.tofile(out_dir / "train.bin") |
| val_ids.tofile(out_dir / "val.bin") |
|
|
| print("train tokens:", len(train_ids)) |
| print("val tokens:", len(val_ids)) |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|