Create chatcode/build_notebook.py
Browse files- chatcode/build_notebook.py +180 -0
chatcode/build_notebook.py
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Create a portable Colab walkthrough using the standard notebook JSON format."""
|
| 2 |
+
import ast
|
| 3 |
+
from contextlib import redirect_stdout
|
| 4 |
+
import io
|
| 5 |
+
import json
|
| 6 |
+
from pathlib import Path
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def build():
|
| 10 |
+
cells = []
|
| 11 |
+
def markdown(text):
|
| 12 |
+
cells.append({"cell_type": "markdown", "id": f"cell-{len(cells):02}", "metadata": {}, "source": text})
|
| 13 |
+
def code(text):
|
| 14 |
+
cells.append({"cell_type": "code", "id": f"cell-{len(cells):02}", "metadata": {},
|
| 15 |
+
"source": text, "execution_count": None, "outputs": []})
|
| 16 |
+
markdown("""# PocketVision: cloud training for chat, code and writing
|
| 17 |
+
|
| 18 |
+
## Goal
|
| 19 |
+
Run a small QLoRA smoke experiment on a **Colab cloud GPU**, then inspect a candidate before scaling up.
|
| 20 |
+
The PC only displays this notebook. No trained 4B weights are included.
|
| 21 |
+
|
| 22 |
+
**Validation status:** the saved outputs show a local dry run only. Cloud installation, source authentication,
|
| 23 |
+
GPU memory, training, and model quality remain untested. Free Colab GPU availability is not guaranteed.
|
| 24 |
+
Choose a free runtime yourself; this notebook does not purchase compute or alter subscriptions.
|
| 25 |
+
|
| 26 |
+
English Wikipedia restricts AI-generated article prose. Use the reference-article task for other contexts,
|
| 27 |
+
and the outline/source-review task for Wikipedia preparation.
|
| 28 |
+
[Current guideline](https://en.wikipedia.org/wiki/Wikipedia:Writing_articles_with_large_language_models).
|
| 29 |
+
""")
|
| 30 |
+
markdown("""## Setup
|
| 31 |
+
1. Upload this notebook to Colab and select a GPU runtime.
|
| 32 |
+
2. Set `DRY_RUN = False` below. Set `RUN_TRAINING = True` only when ready to use the allocated GPU.
|
| 33 |
+
3. Store an existing Hugging Face **read token** in Colab Secrets as `HF_TOKEN` and grant this notebook access.
|
| 34 |
+
The source repository is private. Do not paste a token into a code cell or chat.
|
| 35 |
+
4. The Drive step asks you to mount Google Drive. This grants notebook code access to the mounted files;
|
| 36 |
+
only the named PocketVision folder is used here for checkpoints.
|
| 37 |
+
""")
|
| 38 |
+
code('''from pathlib import Path
|
| 39 |
+
import json
|
| 40 |
+
import os
|
| 41 |
+
import subprocess
|
| 42 |
+
import sys
|
| 43 |
+
|
| 44 |
+
DRY_RUN = True
|
| 45 |
+
RUN_TRAINING = False
|
| 46 |
+
SOURCE_REPO = "gamedevelop/pocketvision-chat-code"
|
| 47 |
+
SOURCE_REVISION = None # None resolves and records one immutable commit before downloading.
|
| 48 |
+
PILOT_ROWS = 200 # Dependency/memory smoke test. Use 20000 only after reviewing the smoke run.
|
| 49 |
+
HOLDOUT_PER_SOURCE = 2
|
| 50 |
+
MAX_STEPS = 25
|
| 51 |
+
RUN_NAME = "writing-smoke-01" # Change for a fresh run; existing output is never overwritten.
|
| 52 |
+
RESUME_CHECKPOINT = None # Or an existing checkpoint-N directory from the same run.
|
| 53 |
+
cloud_python = Path("/content/pocketvision-venv/bin/python")
|
| 54 |
+
source_dir = None
|
| 55 |
+
run_dir = None
|
| 56 |
+
print("Dry-run validation only; no cloud operations." if DRY_RUN else "Cloud setup enabled; training still needs RUN_TRAINING=True.")
|
| 57 |
+
''')
|
| 58 |
+
markdown("""### 1. Install an isolated cloud environment
|
| 59 |
+
Installation uses a separate environment, preserving the notebook kernel's packages.
|
| 60 |
+
The next check requires a CUDA GPU before any model weights are loaded.
|
| 61 |
+
""")
|
| 62 |
+
code('''if not DRY_RUN:
|
| 63 |
+
subprocess.run([sys.executable, "-m", "venv", str(cloud_python.parent.parent)], check=True)
|
| 64 |
+
subprocess.run([str(cloud_python), "-m", "pip", "install", "torch==2.9.1",
|
| 65 |
+
"--index-url", "https://download.pytorch.org/whl/cu128"], check=True)
|
| 66 |
+
subprocess.run([sys.executable, "-m", "pip", "install", "huggingface-hub==0.36.2"], check=True)
|
| 67 |
+
subprocess.run([str(cloud_python), "-c",
|
| 68 |
+
"import torch; assert torch.cuda.is_available(), 'Select a Colab GPU runtime'; print(torch.cuda.get_device_name(0))"], check=True)
|
| 69 |
+
else:
|
| 70 |
+
print("Skipped: dependency installation and GPU allocation checks.")
|
| 71 |
+
''')
|
| 72 |
+
markdown("""### 2. Download only the versioned source
|
| 73 |
+
The commit is resolved once and logged; pretrained weights and source ZIPs are excluded.
|
| 74 |
+
The model and datasets have their own pinned revisions in the configuration.
|
| 75 |
+
""")
|
| 76 |
+
code('''if not DRY_RUN:
|
| 77 |
+
from google.colab import userdata
|
| 78 |
+
from huggingface_hub import HfApi, snapshot_download
|
| 79 |
+
os.environ["HF_TOKEN"] = userdata.get("HF_TOKEN")
|
| 80 |
+
revision = SOURCE_REVISION or HfApi().model_info(SOURCE_REPO).sha
|
| 81 |
+
source_dir = Path(snapshot_download(SOURCE_REPO, revision=revision, allow_patterns=[
|
| 82 |
+
"app.py", "chat.py", "LICENSE*", "chatcode/*.py", "chatcode/*.json", "chatcode/*.txt", "chatcode/*.md"]))
|
| 83 |
+
assert (source_dir / "chatcode/articles.py").is_file(), "This source commit predates article support."
|
| 84 |
+
subprocess.run([str(cloud_python), "-m", "pip", "install", "-r",
|
| 85 |
+
str(source_dir / "chatcode/requirements-cloud.txt")], check=True)
|
| 86 |
+
subprocess.run([str(cloud_python), "-m", "pip", "check"], check=True)
|
| 87 |
+
print("Source commit:", revision)
|
| 88 |
+
else:
|
| 89 |
+
print("Skipped: private Hub download and authentication. No token requested.")
|
| 90 |
+
''')
|
| 91 |
+
markdown("""### 3. Use persistent storage
|
| 92 |
+
Colab's runtime disk is temporary. Drive contains the data manifest, optimizer checkpoints and candidate.
|
| 93 |
+
Only this new run folder is created; no existing files are deleted.
|
| 94 |
+
""")
|
| 95 |
+
code('''if not DRY_RUN:
|
| 96 |
+
from google.colab import drive
|
| 97 |
+
drive.mount("/content/drive")
|
| 98 |
+
checkpoint_root = Path("/content/drive/MyDrive/PocketVision-writing")
|
| 99 |
+
run_dir = checkpoint_root / RUN_NAME
|
| 100 |
+
if run_dir.exists() and any(run_dir.iterdir()) and not RESUME_CHECKPOINT:
|
| 101 |
+
raise FileExistsError("Choose a fresh RUN_NAME or explicitly resume an existing checkpoint.")
|
| 102 |
+
run_dir.mkdir(parents=True, exist_ok=True)
|
| 103 |
+
(run_dir / "run-info.json").write_text(json.dumps({"source_repo": SOURCE_REPO,
|
| 104 |
+
"source_revision": revision, "pilot_rows": PILOT_ROWS, "max_steps": MAX_STEPS}, indent=2))
|
| 105 |
+
print("Persistent run directory:", run_dir)
|
| 106 |
+
else:
|
| 107 |
+
print("Skipped: Drive authorization and persistent cloud writes.")
|
| 108 |
+
''')
|
| 109 |
+
markdown("""## Steps
|
| 110 |
+
### 4. Prepare data and run the capped experiment
|
| 111 |
+
This cell streams selected chat, coding, rewriting and summarization data, adds the small authored behavior
|
| 112 |
+
packets, and runs 25 optimizer steps. It may consume significant GPU time even on a small sample.
|
| 113 |
+
The full 20,000-example experiment is a separate decision after measuring steps/second and memory.
|
| 114 |
+
""")
|
| 115 |
+
code('''if not DRY_RUN and RUN_TRAINING:
|
| 116 |
+
data_dir = run_dir / "data"
|
| 117 |
+
if not (data_dir / "manifest.json").is_file():
|
| 118 |
+
subprocess.run([str(cloud_python), "-m", "chatcode.data", "--output", str(data_dir),
|
| 119 |
+
"--train", str(PILOT_ROWS), "--holdout-per-source", str(HOLDOUT_PER_SOURCE)], cwd=source_dir, check=True)
|
| 120 |
+
command = [str(cloud_python), "-m", "chatcode.train", "--data", str(data_dir),
|
| 121 |
+
"--output", str(run_dir / "training"), "--max-steps", str(MAX_STEPS)]
|
| 122 |
+
if RESUME_CHECKPOINT:
|
| 123 |
+
command.extend(["--resume-from-checkpoint", str(RESUME_CHECKPOINT)])
|
| 124 |
+
subprocess.run(command, cwd=source_dir, check=True)
|
| 125 |
+
else:
|
| 126 |
+
print("Skipped: dataset preparation and weight training. No model weights downloaded.")
|
| 127 |
+
''')
|
| 128 |
+
markdown("""## Checks
|
| 129 |
+
### 5. Compare base and candidate
|
| 130 |
+
The automated article check measures JSON/citation-ID validity on four held-out fictional cases.
|
| 131 |
+
It does **not** measure factual accuracy, source reliability, or Wikipedia suitability. Inspect all saved
|
| 132 |
+
outputs, and separately test chat and code regressions. Do not execute generated programs outside a sandbox.
|
| 133 |
+
""")
|
| 134 |
+
code('''if not DRY_RUN and RUN_TRAINING:
|
| 135 |
+
candidate_dir = run_dir / "training/adapter"
|
| 136 |
+
subprocess.run([str(cloud_python), "-m", "chatcode.evaluate_articles", "--adapter", str(candidate_dir),
|
| 137 |
+
"--output", str(run_dir / "article-evaluation.json")], cwd=source_dir, check=True)
|
| 138 |
+
print("Candidate and evaluation saved under:", run_dir)
|
| 139 |
+
else:
|
| 140 |
+
print("Skipped: model evaluation. No accuracy or quality result is claimed.")
|
| 141 |
+
''')
|
| 142 |
+
markdown("""## Next Steps
|
| 143 |
+
Review the cloud smoke run before raising `PILOT_ROWS`, holdout sizes or run duration. Training data changes
|
| 144 |
+
require a new run directory; do not resume a checkpoint against different data.
|
| 145 |
+
The source recipe alone does not improve weights. Compare factual support and neutrality with the base,
|
| 146 |
+
and check programming and conversation regressions before publishing a candidate.
|
| 147 |
+
No upload or Wikipedia publication is performed by this notebook. A later Hub upload needs a separate
|
| 148 |
+
write-scoped credential and a reviewed candidate. Do not share notebook outputs containing credentials.
|
| 149 |
+
""")
|
| 150 |
+
notebook = {"nbformat": 4, "nbformat_minor": 5,
|
| 151 |
+
"metadata": {"kernelspec": {"display_name": "Python 3", "language": "python", "name": "python3"},
|
| 152 |
+
"language_info": {"name": "python", "version": "3.12"},
|
| 153 |
+
"pocketvision_validation": "local_dry_run_only_no_cloud_execution"}, "cells": cells}
|
| 154 |
+
return notebook
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
def validate_and_dry_run(notebook):
|
| 158 |
+
"""Structural and Python syntax checks when nbformat is unavailable locally."""
|
| 159 |
+
assert notebook["nbformat"] == 4 and notebook["nbformat_minor"] == 5
|
| 160 |
+
namespace, ids, count = {}, set(), 0
|
| 161 |
+
for cell in notebook["cells"]:
|
| 162 |
+
assert cell["id"] not in ids and isinstance(cell["metadata"], dict) and isinstance(cell["source"], str)
|
| 163 |
+
ids.add(cell["id"])
|
| 164 |
+
assert cell["cell_type"] in {"markdown", "code"}
|
| 165 |
+
if cell["cell_type"] == "code":
|
| 166 |
+
compiled = compile(ast.parse(cell["source"]), "<notebook-cell>", "exec")
|
| 167 |
+
output = io.StringIO()
|
| 168 |
+
with redirect_stdout(output):
|
| 169 |
+
exec(compiled, namespace)
|
| 170 |
+
count += 1
|
| 171 |
+
cell["execution_count"] = count
|
| 172 |
+
cell["outputs"] = [{"output_type": "stream", "name": "stdout", "text": output.getvalue()}]
|
| 173 |
+
assert namespace["DRY_RUN"] is True and namespace["RUN_TRAINING"] is False
|
| 174 |
+
return notebook
|
| 175 |
+
|
| 176 |
+
|
| 177 |
+
if __name__ == "__main__":
|
| 178 |
+
path = Path(__file__).with_name("cloud_training.ipynb")
|
| 179 |
+
path.write_text(json.dumps(validate_and_dry_run(build()), ensure_ascii=False, indent=2), encoding="utf-8")
|
| 180 |
+
print(f"Wrote {path}; all cells executed only in dry-run mode. GPU execution remains unverified.")
|