Download ops/v5_daemon.py from tchbcb/qwenjev: direct link, hf CLI and curl.
- Browser
- Download file 6.9 kB
-
https://huggingface.co/tchbcb/qwenjev/resolve/main/ops/v5_daemon.py
- Command line
-
hf download hf://tchbcb/qwenjev/ops/v5_daemon.py
-
curl -L -o v5_daemon.py https://huggingface.co/tchbcb/qwenjev/resolve/main/ops/v5_daemon.py
6.9 kB
| #!/usr/bin/env python3 | |
| """v5 online teach-train daemon — closes the live-teaching loop. | |
| Every cycle: | |
| 1. Snapshot the live teach traces (/content/teach/trace_teach_*.jsonl) | |
| 2. Rebuild dataset_teach from ALL traces (labels = the teacher's actions) | |
| 3. Continue-train the student (heads + LoRA resume) for a bounded step budget | |
| 4. Upload the new generation to HF t4_assets_v5/checkpoints/ (HEAD-verified) | |
| The teacher (main agent) keeps playing via teach_server; this daemon absorbs | |
| whatever has been demonstrated so far. Cycle skips itself when no new steps. | |
| """ | |
| import json | |
| import os | |
| import shutil | |
| import subprocess | |
| import sys | |
| import time | |
| import glob | |
| sys.path.insert(0, '/content/qwenjev') | |
| REPO = 'tchbcb/qwenjev' | |
| OUT = '/content/qwenjev/checkpoints/qwenjev-s1-hf-v5t' | |
| TRACES = '/content/teach' | |
| CYCLE_SECONDS = 300 # 5 min — teacher plays continuously | |
| MIN_NEW_STEPS = 15 # skip cycle if the teacher added fewer steps | |
| STEPS_PER_CYCLE = 25 # bounded GPU budget (~8-12 min at 30s/step) | |
| TOKEN_FILE = '/content/qwenjev_work_v3/.hf_token' | |
| def log(msg): | |
| print('[daemon %s] %s' % (time.strftime('%H:%M:%S'), msg), flush=True) | |
| def count_steps(): | |
| n = 0 | |
| for f in glob.glob(TRACES + '/trace_teach_*.jsonl'): | |
| with open(f) as fh: | |
| n += sum(1 for line in fh if line.strip()) | |
| return n | |
| def snapshot_traces(): | |
| os.makedirs('/content/qwenjev/data/traces_teach', exist_ok=True) | |
| for f in glob.glob('/content/qwenjev/data/traces_teach/ep*.jsonl'): | |
| os.remove(f) | |
| copied = [] | |
| for i, src in enumerate(sorted(glob.glob(TRACES + '/trace_teach_*.jsonl'))): | |
| game = os.path.basename(src).replace('trace_teach_', '').replace('.jsonl', '') | |
| dst = '/content/qwenjev/data/traces_teach/ep%03d_%s.jsonl' % (i + 1, game) | |
| shutil.copyfile(src, dst) | |
| copied.append(dst) | |
| return copied | |
| def hf_token(): | |
| if os.path.exists(TOKEN_FILE): | |
| return open(TOKEN_FILE).read().strip() | |
| return os.environ.get('HF_TOKEN', '') | |
| def _lora_pt_name(): | |
| """PEFT saves adapter_model.safetensors, but HF strips that extension on | |
| this repo (v0.4.8 iron law) — present the SAME bytes as adapter_model.pt.""" | |
| d = OUT + '/hf_lora' | |
| pt = d + '/adapter_model.pt' | |
| st = d + '/adapter_model.safetensors' | |
| if not os.path.exists(pt) and os.path.exists(st): | |
| shutil.copyfile(st, pt) | |
| return pt | |
| def upload_generation(): | |
| from huggingface_hub import HfApi | |
| api = HfApi(token=hf_token()) | |
| # iron law #7: the INSTANCE can die any second — teaching traces are the | |
| # irreplaceable asset, push them EVERY generation before anything else | |
| try: | |
| import tarfile | |
| tb = '/content/teach_traces_snapshot.tgz' | |
| with tarfile.open(tb, 'w:gz') as t: | |
| for f in sorted(glob.glob(TRACES + '/trace_teach_*.jsonl')): | |
| t.add(f, arcname=os.path.basename(f)) | |
| api.upload_file(path_or_fileobj=tb, | |
| path_in_repo='t4_assets_v5/traces/teach_traces.tgz', | |
| repo_id=REPO) | |
| log('traces snapshot uploaded (%d files)' % len(glob.glob(TRACES + '/trace_teach_*.jsonl'))) | |
| except Exception as exc: | |
| log('traces snapshot FAILED (non-fatal): %r' % exc) | |
| uploads = [ | |
| (OUT + '/qwenjev_s1.pt', 't4_assets_v5/checkpoints/qwenjev_s1.pt'), | |
| (_lora_pt_name(), 't4_assets_v5/checkpoints/hf_lora/adapter_model.pt'), | |
| (OUT + '/hf_lora/adapter_config.json', 't4_assets_v5/checkpoints/hf_lora/adapter_config.json'), | |
| (OUT + '/stage_b_meta.json', 't4_assets_v5/checkpoints/stage_b_meta.json'), | |
| ] | |
| for local, remote in uploads: | |
| if not os.path.exists(local): | |
| log('skip missing %s' % local) | |
| continue | |
| api.upload_file(path_or_fileobj=local, path_in_repo=remote, repo_id=REPO) | |
| log('uploaded %s' % remote) | |
| # HEAD verify the weights (v0.4.8 iron law) | |
| import urllib.request | |
| for remote in ('t4_assets_v5/checkpoints/qwenjev_s1.pt', | |
| 't4_assets_v5/checkpoints/hf_lora/adapter_model.pt'): | |
| url = 'https://huggingface.co/%s/resolve/main/%s' % (REPO, remote) | |
| try: | |
| r = urllib.request.urlopen(urllib.request.Request(url, method='HEAD')) | |
| log('HEAD ok %s (%s)' % (remote, r.status)) | |
| except Exception as exc: | |
| log('HEAD FAIL %s: %s' % (remote, exc)) | |
| def train_cycle(): | |
| # LoRA continuity: the trainer resumes from stage_a_lora, saves to hf_lora. | |
| # Fold the latest hf_lora back into stage_a_lora before continuing. | |
| src = OUT + '/hf_lora' | |
| dst = OUT + '/stage_a_lora' | |
| os.makedirs(dst, exist_ok=True) | |
| _lora_pt_name() # normalize: ensure adapter_model.pt exists (copy from safetensors) | |
| if os.path.exists(src + '/adapter_model.pt'): | |
| for f in ('adapter_model.pt', 'adapter_config.json'): | |
| if os.path.exists(src + '/' + f): | |
| shutil.copyfile(src + '/' + f, dst + '/' + f) | |
| # PEFT needs the safetensors name (bytes are identical) | |
| shutil.copyfile(dst + '/adapter_model.pt', dst + '/adapter_model.safetensors') | |
| r = subprocess.run( | |
| ['python3', '-u', '-B', 'train/train_system1.py', '--config', 'configs/train_teach.yaml'], | |
| cwd='/content/qwenjev', capture_output=True, text=True, timeout=3600) | |
| log('trainer exit=%s' % r.returncode) | |
| tail = [l for l in r.stdout.splitlines() if 'step=' in l or 'done' in l.lower()] | |
| for l in tail[-3:]: | |
| log(' ' + l[:110]) | |
| if r.returncode != 0: | |
| log('STDERR tail: ' + r.stderr[-400:]) | |
| return r.returncode == 0 | |
| def build_dataset(): | |
| r = subprocess.run( | |
| ['python3', '-m', 'data.build_dataset', '--in', 'data/traces_teach', | |
| '--out', 'data/dataset_teach'], | |
| cwd='/content/qwenjev', capture_output=True, text=True, timeout=600) | |
| ok = r.returncode == 0 | |
| log('build_dataset ok=%s %s' % (ok, r.stdout.strip()[-90:])) | |
| return ok | |
| def main(): | |
| log('daemon up; baseline steps=%d' % count_steps()) | |
| last = count_steps() | |
| cycle = 0 | |
| while True: | |
| # pit 15: a bare time.sleep(300) silently freezes in the Kaggle | |
| # sandbox (observed twice) — sleep in short slices instead | |
| for _ in range(CYCLE_SECONDS // 5): | |
| time.sleep(5) | |
| now = count_steps() | |
| if now - last < MIN_NEW_STEPS: | |
| log('cycle %d skipped: only +%d new steps' % (cycle, now - last)) | |
| continue | |
| cycle += 1 | |
| log('cycle %d: %d new steps (%d total) -> teaching the student' % (cycle, now - last, now)) | |
| try: | |
| snapshot_traces() | |
| if not build_dataset(): | |
| continue | |
| if train_cycle(): | |
| upload_generation() | |
| last = now | |
| except Exception as exc: | |
| log('cycle %d FAILED: %r' % (cycle, exc)) | |
| if __name__ == '__main__': | |
| main() | |