Download chatcode/train.py from gamedevelop/pocketvision-chat-code: direct link, hf CLI and curl.
- Browser
- Download file 7.88 kB
-
https://huggingface.co/gamedevelop/pocketvision-chat-code/resolve/main/chatcode/train.py
- Command line
-
hf download hf://gamedevelop/pocketvision-chat-code/chatcode/train.py
-
curl -L -o train.py https://huggingface.co/gamedevelop/pocketvision-chat-code/resolve/main/chatcode/train.py
7.88 kB
| """Cloud GPU QLoRA training. Never falls back to training on the user's CPU.""" | |
| import argparse | |
| import hashlib | |
| import json | |
| from pathlib import Path | |
| import shutil | |
| from .data import encode_example, normalize_messages, prompt_key | |
| from .runtime import CONFIG | |
| def read_data(folder): | |
| manifest = json.loads((folder / "manifest.json").read_text(encoding="utf-8")) | |
| if manifest["config"] != CONFIG: | |
| raise ValueError("Data manifest does not match the pinned experiment configuration.") | |
| if "article_examples_sha256" in manifest: | |
| current = hashlib.sha256(Path(__file__).with_name("article_examples.json").read_bytes()).hexdigest() | |
| if manifest["article_examples_sha256"] != current: | |
| raise ValueError("Article behavior examples changed after preparation.") | |
| records, seen, groups = {}, set(), {} | |
| for split in ["train", "validation", "test"]: | |
| raw = (folder / f"{split}.jsonl").read_bytes() | |
| if hashlib.sha256(raw).hexdigest() != manifest["sha256"][split]: | |
| raise ValueError(f"Data changed after preparation: {split}") | |
| records[split] = [json.loads(line) for line in raw.decode().splitlines() if line] | |
| if not records[split] or len(records[split]) != manifest["counts"][split]: | |
| raise ValueError(f"Unexpected split size: {split}") | |
| for row in records[split]: | |
| key = prompt_key(normalize_messages(row)) | |
| if key != row["prompt_hash"] or key in seen: | |
| raise ValueError("Duplicate or inconsistent prompts across dataset splits.") | |
| seen.add(key) | |
| group = row.get("group_id", key) | |
| if group in groups and groups[group] != split: | |
| raise ValueError("A source packet appears in multiple dataset splits.") | |
| groups[group] = split | |
| return records | |
| def collate(examples, pad_token_id): | |
| import torch | |
| length = max(len(item["input_ids"]) for item in examples) | |
| return {key: torch.tensor([item[key] + [pad] * (length - len(item[key])) for item in examples]) | |
| for key, pad in [("input_ids", pad_token_id), ("attention_mask", 0), ("labels", -100)]} | |
| def train(args): | |
| import torch | |
| if not torch.cuda.is_available(): | |
| raise RuntimeError("Cloud CUDA GPU required. No training or model download was started.") | |
| from datasets import Dataset | |
| from peft import LoraConfig, TaskType, get_peft_model, prepare_model_for_kbit_training | |
| from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig | |
| from transformers import Trainer, TrainingArguments, set_seed | |
| output = Path(args.output) | |
| if output.exists() and any(output.iterdir()) and not args.resume_from_checkpoint: | |
| raise FileExistsError("Use an empty output directory or explicitly resume a checkpoint.") | |
| records = read_data(Path(args.data)) | |
| set_seed(CONFIG["seed"]) | |
| tokenizer = AutoTokenizer.from_pretrained( | |
| CONFIG["base_model"], revision=CONFIG["base_revision"], trust_remote_code=False) | |
| tokenizer.padding_side = "right" | |
| dtype = torch.bfloat16 if torch.cuda.is_bf16_supported() else torch.float16 | |
| base = AutoModelForCausalLM.from_pretrained( | |
| CONFIG["base_model"], revision=CONFIG["base_revision"], trust_remote_code=False, | |
| dtype=dtype, device_map={"": torch.cuda.current_device()}, attn_implementation="sdpa", | |
| quantization_config=BitsAndBytesConfig(load_in_4bit=True, bnb_4bit_quant_type="nf4", | |
| bnb_4bit_use_double_quant=True, bnb_4bit_compute_dtype=dtype)) | |
| base.config.use_cache = False | |
| base = prepare_model_for_kbit_training(base, use_gradient_checkpointing=True, | |
| gradient_checkpointing_kwargs={"use_reentrant": False}) | |
| model = get_peft_model(base, LoraConfig(r=16, lora_alpha=32, lora_dropout=0.05, | |
| target_modules="all-linear", bias="none", task_type=TaskType.CAUSAL_LM)) | |
| model.peft_config["default"].base_model_name_or_path = CONFIG["base_model"] | |
| model.peft_config["default"].revision = CONFIG["base_revision"] | |
| datasets = {split: Dataset.from_list([ | |
| encode_example(tokenizer, row["messages"], CONFIG["max_sequence_length"]) for row in records[split] | |
| ]) for split in ["train", "validation"]} | |
| settings = TrainingArguments( | |
| output_dir=str(output), num_train_epochs=1, max_steps=args.max_steps, | |
| per_device_train_batch_size=1, per_device_eval_batch_size=1, | |
| gradient_accumulation_steps=16, learning_rate=5e-5, warmup_ratio=0.05, | |
| lr_scheduler_type="cosine", logging_steps=5, eval_strategy="steps", eval_steps=25, | |
| save_strategy="steps", save_steps=25, save_total_limit=2, | |
| load_best_model_at_end=True, metric_for_best_model="eval_loss", | |
| greater_is_better=False, bf16=dtype == torch.bfloat16, fp16=dtype == torch.float16, | |
| gradient_checkpointing=True, gradient_checkpointing_kwargs={"use_reentrant": False}, | |
| optim="adamw_torch", report_to="none", seed=CONFIG["seed"], | |
| dataloader_num_workers=0, remove_unused_columns=False) | |
| trainer = Trainer(model=model, args=settings, processing_class=tokenizer, | |
| train_dataset=datasets["train"], eval_dataset=datasets["validation"], | |
| data_collator=lambda batch: collate(batch, tokenizer.pad_token_id)) | |
| baseline = trainer.evaluate() | |
| trained = trainer.train(resume_from_checkpoint=args.resume_from_checkpoint) | |
| final = trainer.evaluate() | |
| destination = output / "adapter" | |
| trainer.save_model(str(destination)) | |
| tokenizer.save_pretrained(destination) | |
| shutil.copy2(Path(args.data) / "manifest.json", destination / "data_manifest.json") | |
| report = {"config": CONFIG, "baseline_validation": baseline, "final_validation": final, | |
| "training": trained.metrics, "counts": {k: len(v) for k, v in records.items()}, | |
| "status": "candidate_requires_chat_code_and_article_evaluation", "max_steps": args.max_steps} | |
| (destination / "training_report.json").write_text(json.dumps(report, indent=2), encoding="utf-8") | |
| (destination / "README.md").write_text( | |
| "---\nlicense: gpl-3.0\nlibrary_name: peft\npipeline_tag: text-generation\n" | |
| f"base_model: {CONFIG['base_model']}\nbase_model_relation: adapter\n" | |
| "datasets:\n- HuggingFaceTB/smoltalk\ntags:\n- lora\n- code\n- experimental\n---\n\n" | |
| "# PocketVision Chat, Code & Writing candidate\n\nText-only QLoRA adapter. " | |
| "This candidate has validation-loss measurements only; no claim of better chat, coding or factual article quality. " | |
| "See training_report.json and data_manifest.json. " | |
| "Training data: Smol-Magpie-Ultra, Smol-rewrite, Smol-summarize (Apache-2.0), authored fictional article behavior examples (GPL-3.0-only), and the Self-OSS-Instruct subset " | |
| "(original: bigcode/self-oss-instruct-sc2-exec-filter-50k, ODC-BY). " | |
| "Original base weights remain Apache-2.0; project code and adapter use GPL-3.0-only.\n", | |
| encoding="utf-8") | |
| for source in ["LICENSE", "LICENSE-BASE-MODEL.txt"]: | |
| shutil.copy2(Path(__file__).resolve().parent.parent / source, destination / source) | |
| print(f"Candidate saved to {destination}. Evaluate answers before publishing a release.") | |
| if __name__ == "__main__": | |
| parser = argparse.ArgumentParser(description="Train text-only Chat & Code on a cloud CUDA GPU") | |
| parser.add_argument("--data", default="chatcode-data") | |
| parser.add_argument("--output", required=True, help="Prefer mounted persistent cloud storage") | |
| parser.add_argument("--max-steps", type=int, default=-1, help="25 for a smoke run; -1 for one epoch") | |
| parser.add_argument("--resume-from-checkpoint") | |
| args = parser.parse_args() | |
| if args.max_steps != -1 and args.max_steps < 25: | |
| parser.error("Use at least 25 steps so validation and checkpoint saving occur.") | |
| train(args) | |