Download prepare.py from zarko1321/bitsy21321: direct link, hf CLI and curl.
- Browser
- Download file 3.62 kB
-
https://huggingface.co/zarko1321/bitsy21321/resolve/main/prepare.py
- Command line
-
hf download hf://zarko1321/bitsy21321/prepare.py
-
curl -L -o prepare.py https://huggingface.co/zarko1321/bitsy21321/resolve/main/prepare.py
3.62 kB
| """Convert text or chat JSONL into streamable uint32 tokens and uint8 training masks. | |
| Chat JSONL: {"messages": [{"role":"user", "content":"..."}, {"role":"assistant", "content":"..."}]} | |
| Only assistant answers contribute to SFT loss. Input messages must be trusted training data. | |
| """ | |
| import argparse | |
| import json | |
| from pathlib import Path | |
| from array import array | |
| from tokenizers import Tokenizer | |
| def sources(paths, fmt): | |
| suffix = ".txt" if fmt == "text" else ".jsonl" | |
| for src in paths: | |
| p = Path(src) | |
| if p.is_dir(): | |
| yield from sorted(p.rglob("*")) if False else (x for x in sorted(p.rglob(f"*{suffix}")) if x.is_file()) | |
| elif p.is_file(): | |
| yield p | |
| else: | |
| raise FileNotFoundError(src) | |
| def main(): | |
| p = argparse.ArgumentParser() | |
| p.add_argument("--input", nargs="+", required=True) | |
| p.add_argument("--tokenizer", default="tokenizer.json") | |
| p.add_argument("--output-dir", required=True) | |
| p.add_argument("--format", choices=["text", "chat"], default="text") | |
| args = p.parse_args() | |
| tok = Tokenizer.from_file(args.tokenizer) | |
| eos = tok.token_to_id("<|eos|>") | |
| end = tok.token_to_id("<|end|>") | |
| if eos is None or end is None: | |
| raise ValueError("Tokenizer special tokens are missing") | |
| out = Path(args.output_dir) | |
| out.mkdir(parents=True, exist_ok=True) | |
| count = 0 | |
| with (out / "train.bin").open("wb") as ids_file, (out / "mask.bin").open("wb") as mask_file: | |
| def emit(ids, mask): | |
| nonlocal count | |
| if not ids: | |
| return | |
| array("I", ids).tofile(ids_file) | |
| mask_file.write(bytes([mask]) * len(ids)) | |
| count += len(ids) | |
| for path in sources(args.input, args.format): | |
| with path.open("r", encoding="utf-8") as f: | |
| if args.format == "text": | |
| # Read in chunks while preserving valid UTF-8 decoding. | |
| while True: | |
| chunk = f.read(256_000) | |
| if not chunk: | |
| break | |
| emit(tok.encode(chunk, add_special_tokens=False).ids, 1) | |
| emit([eos], 1) | |
| else: | |
| for lineno, line in enumerate(f, 1): | |
| if not line.strip(): | |
| continue | |
| try: | |
| record = json.loads(line) | |
| messages = record["messages"] | |
| for m in messages: | |
| role = m["role"] | |
| if role not in ("system", "user", "assistant"): | |
| raise ValueError(f"Unsupported role {role}") | |
| rid = tok.token_to_id(f"<|{role}|>") | |
| emit([rid], 0) | |
| emit(tok.encode(str(m["content"]), add_special_tokens=False).ids, | |
| 1 if role == "assistant" else 0) | |
| emit([end], 1 if role == "assistant" else 0) | |
| emit([eos], 0) | |
| except (KeyError, ValueError, TypeError) as exc: | |
| raise ValueError(f"{path}:{lineno}: {exc}") from exc | |
| if count < 3: | |
| raise ValueError("Too few tokens. Supply more training data.") | |
| (out / "metadata.json").write_text(json.dumps({"tokens": count, "format": args.format}, indent=2)) | |
| print(f"Wrote {count:,} tokens to {out} (train.bin and mask.bin)") | |
| if __name__ == "__main__": | |
| main() | |