Download chatcode/data.py from gamedevelop/pocketvision-chat-code: direct link, hf CLI and curl.
- Browser
- Download file 7.85 kB
-
https://huggingface.co/gamedevelop/pocketvision-chat-code/resolve/main/chatcode/data.py
- Command line
-
hf download hf://gamedevelop/pocketvision-chat-code/chatcode/data.py
-
curl -L -o data.py https://huggingface.co/gamedevelop/pocketvision-chat-code/resolve/main/chatcode/data.py
7.85 kB
| """Prepare a bounded, reproducible text-only dataset on the cloud machine.""" | |
| import argparse | |
| from collections import Counter | |
| import hashlib | |
| import json | |
| from pathlib import Path | |
| import random | |
| import re | |
| from .runtime import CONFIG | |
| from .articles import behavior_examples | |
| def normalize_messages(row): | |
| messages = row.get("messages") | |
| if not isinstance(messages, list) or not messages: | |
| raise ValueError("Expected a nonempty messages list.") | |
| clean = [] | |
| for index, item in enumerate(messages): | |
| role, content = item.get("role"), item.get("content") | |
| if role not in {"system", "user", "assistant"} or not isinstance(content, str) or not content.strip(): | |
| raise ValueError("Only nonempty text messages are supported.") | |
| if role == "system" and index != 0: | |
| raise ValueError("System messages must come first.") | |
| clean.append({"role": role, "content": content.strip()}) | |
| turns = clean[1:] if clean[0]["role"] == "system" else clean | |
| if len(turns) < 2 or len(turns) % 2: | |
| raise ValueError("Expected complete conversation turns.") | |
| if any(m["role"] != ("user" if i % 2 == 0 else "assistant") for i, m in enumerate(turns)): | |
| raise ValueError("Expected alternating user and assistant turns.") | |
| if clean[0]["role"] != "system": | |
| clean.insert(0, {"role": "system", "content": CONFIG["system_prompt"]}) | |
| return clean | |
| def prompt_key(messages): | |
| # Group identical opening questions, even when answers differ. | |
| first = next(m["content"] for m in messages if m["role"] == "user") | |
| normalized = re.sub(r"\s+", " ", first).strip().casefold() | |
| return hashlib.sha256(normalized.encode()).hexdigest() | |
| def encode_example(tokenizer, messages, max_length): | |
| """Supervise assistant content and end-of-turn tokens; mask every prompt.""" | |
| ids = tokenizer.apply_chat_template(messages, tokenize=True, add_generation_prompt=False) | |
| if len(ids) > max_length: | |
| raise ValueError("Overlength example; skip rather than truncate code or its answer.") | |
| labels = [-100] * len(ids) | |
| for index, message in enumerate(messages): | |
| if message["role"] != "assistant": | |
| continue | |
| prefix = tokenizer.apply_chat_template(messages[:index], tokenize=True, add_generation_prompt=True) | |
| end = tokenizer.apply_chat_template(messages[:index + 1], tokenize=True, add_generation_prompt=False) | |
| if ids[:len(prefix)] != prefix or ids[:len(end)] != end: | |
| raise ValueError("Chat template does not preserve assistant boundaries.") | |
| labels[len(prefix):len(end)] = ids[len(prefix):len(end)] | |
| if not any(x != -100 for x in labels[1:]): | |
| raise ValueError("No assistant target tokens.") | |
| return {"input_ids": ids, "attention_mask": [1] * len(ids), "labels": labels} | |
| def split_for(key): | |
| bucket = int(key[:8], 16) % 100 | |
| return "validation" if bucket < 2 else "test" if bucket < 4 else "train" | |
| def prepare(args): | |
| from datasets import load_dataset | |
| from transformers import AutoTokenizer | |
| output = Path(args.output) | |
| if output.exists() and any(output.iterdir()): | |
| raise FileExistsError("Use an empty data output directory.") | |
| tokenizer = AutoTokenizer.from_pretrained( | |
| CONFIG["base_model"], revision=CONFIG["base_revision"], trust_remote_code=False) | |
| result = {name: [] for name in ["train", "validation", "test"]} | |
| seen = set() | |
| stats = {} | |
| for source in CONFIG["sources"]: | |
| targets = { | |
| "train": round(args.train * source["weight"]), | |
| "validation": args.holdout_per_source, | |
| "test": args.holdout_per_source, | |
| } | |
| counts, skipped = Counter(), Counter() | |
| stream = load_dataset( | |
| CONFIG["dataset"], source["config"], split="train", | |
| revision=CONFIG["dataset_revision"], streaming=True) | |
| stream = stream.shuffle(seed=CONFIG["seed"], buffer_size=4096) | |
| for row in stream: | |
| try: | |
| messages = normalize_messages(row) | |
| key = prompt_key(messages) | |
| if key in seen: | |
| skipped["duplicate_prompt"] += 1 | |
| continue | |
| encode_example(tokenizer, messages, CONFIG["max_sequence_length"]) | |
| except (ValueError, TypeError, KeyError): | |
| skipped["invalid_or_overlength"] += 1 | |
| continue | |
| seen.add(key) | |
| split = split_for(key) | |
| if counts[split] >= targets[split]: | |
| continue | |
| content_hash = hashlib.sha256(json.dumps(messages, ensure_ascii=False, sort_keys=True).encode()).hexdigest() | |
| result[split].append({"id": content_hash, "prompt_hash": key, | |
| "source": source["config"], "messages": messages}) | |
| counts[split] += 1 | |
| if all(counts[s] >= targets[s] for s in targets): | |
| break | |
| if any(counts[s] < targets[s] for s in targets): | |
| raise RuntimeError(f"Not enough usable data for {source['config']}: {dict(counts)}") | |
| stats[source["config"]] = {"selected": dict(counts), "skipped": dict(skipped)} | |
| print(source["config"], dict(counts), flush=True) | |
| behavior_path = Path(__file__).with_name("article_examples.json") | |
| for split, rows in behavior_examples().items(): | |
| for row in rows: | |
| encode_example(tokenizer, row["messages"], CONFIG["max_sequence_length"]) | |
| key = prompt_key(row["messages"]) | |
| if key in seen: | |
| raise ValueError("Article behavior prompt duplicates a selected Hub prompt.") | |
| seen.add(key) | |
| row.update({"id": hashlib.sha256(json.dumps(row["messages"], sort_keys=True).encode()).hexdigest(), | |
| "prompt_hash": key}) | |
| result[split].append(row) | |
| stats["authored-article-behavior"] = {"selected": {k: len(v) for k, v in behavior_examples().items()}, | |
| "license": "GPL-3.0-only", "fictional": True} | |
| output.mkdir(parents=True, exist_ok=True) | |
| hashes = {} | |
| for split, rows in result.items(): | |
| random.Random(CONFIG["seed"]).shuffle(rows) | |
| content = "".join(json.dumps(row, ensure_ascii=False) + "\n" for row in rows) | |
| (output / f"{split}.jsonl").write_bytes(content.encode("utf-8")) | |
| hashes[split] = hashlib.sha256(content.encode()).hexdigest() | |
| manifest = {"config": CONFIG, "counts": {k: len(v) for k, v in result.items()}, | |
| "article_examples_sha256": hashlib.sha256(behavior_path.read_bytes()).hexdigest(), | |
| "sources": stats, "sha256": hashes, | |
| "split_policy": "Hub: hash of normalized first user question. Article behavior: source-packet groups. Official source test splits unused.", | |
| "limitations": ["Synthetic data may contain mistakes.", | |
| "Exact prompt deduplication is not semantic or repository-level decontamination.", | |
| "The pretrained base may already have seen these data.", | |
| "This pilot's code source focuses on Python; other languages need additional data and evaluation."]} | |
| (output / "manifest.json").write_text(json.dumps(manifest, indent=2), encoding="utf-8") | |
| if __name__ == "__main__": | |
| parser = argparse.ArgumentParser(description="Prepare Chat & Code data on the cloud machine") | |
| parser.add_argument("--output", default="chatcode-data") | |
| parser.add_argument("--train", type=int, default=CONFIG["train_examples"]) | |
| parser.add_argument("--holdout-per-source", type=int, default=128) | |
| args = parser.parse_args() | |
| if args.train < 10 or args.train % 10 or args.holdout_per_source < 1: | |
| parser.error("--train must be a positive multiple of 10; holdouts must be positive.") | |
| prepare(args) | |