HayrettinIscan commited on
Commit
f364390
·
verified ·
1 Parent(s): 2999e2a

Add stream train autoloop script

Browse files
Files changed (1) hide show
  1. scripts/stream_train_autoloop.py +335 -0
scripts/stream_train_autoloop.py ADDED
@@ -0,0 +1,335 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Stream egitim dongusu: chunk indir -> egit -> checkpoint/HF upload -> sonraki chunk.
4
+ Takilirsa / cokurse otomatik resume.
5
+
6
+ Ornek (VM):
7
+ export HF_TOKEN=...
8
+ export HF_REPO=HayrettinIscan/MeshAI-Base-Models
9
+ export HF_REPO_TYPE=model
10
+ python3 scripts/stream_train_autoloop.py --chunk-size 8 --epochs-per-chunk 1
11
+ """
12
+ from __future__ import annotations
13
+
14
+ import argparse
15
+ import json
16
+ import os
17
+ import shutil
18
+ import subprocess
19
+ import sys
20
+ import time
21
+ from datetime import datetime
22
+ from pathlib import Path
23
+
24
+ ROOT = Path(__file__).resolve().parents[1]
25
+ CACHE = ROOT / "data" / "stream_cache"
26
+ PROGRESS = ROOT / "data" / "stream_progress.json"
27
+ CHECKPOINT_DIR = ROOT / "checkpoints"
28
+ LOG_DIR = ROOT / "logs"
29
+ HF_PREPROCESSED = os.getenv("HF_PREPROCESSED_REPO", "HayrettinIscan/MeshAI-Preprocessed-4K")
30
+ HF_REPO = os.getenv("HF_REPO", "HayrettinIscan/MeshAI-Base-Models")
31
+ HF_REPO_TYPE = os.getenv("HF_REPO_TYPE", "model")
32
+ HF_TOKEN = os.getenv("HF_TOKEN")
33
+
34
+ REQUIRED_FILES = ("geometry_latent.npz",)
35
+ RENDER_CANDIDATES = tuple(
36
+ f"renders/{name}"
37
+ for i in range(4)
38
+ for name in (f"view_{i:02d}_tex.png", f"view_{i:02d}.png")
39
+ )
40
+
41
+
42
+ def _log(msg: str) -> None:
43
+ LOG_DIR.mkdir(parents=True, exist_ok=True)
44
+ line = f"[{datetime.now():%Y-%m-%d %H:%M:%S}] [stream] {msg}"
45
+ print(line, flush=True)
46
+ with open(LOG_DIR / "stream_train_autoloop.log", "a", encoding="utf-8") as f:
47
+ f.write(line + "\n")
48
+
49
+
50
+ def _load_progress() -> dict:
51
+ if PROGRESS.exists():
52
+ try:
53
+ return json.loads(PROGRESS.read_text(encoding="utf-8"))
54
+ except Exception:
55
+ pass
56
+ return {"next_index": 0, "done_uids": [], "rounds": 0, "restarts": 0}
57
+
58
+
59
+ def _save_progress(payload: dict) -> None:
60
+ PROGRESS.parent.mkdir(parents=True, exist_ok=True)
61
+ PROGRESS.write_text(json.dumps(payload, indent=2), encoding="utf-8")
62
+
63
+
64
+ def _load_manifest(token: str) -> list[dict]:
65
+ from huggingface_hub import hf_hub_download
66
+
67
+ path = hf_hub_download(
68
+ repo_id=HF_PREPROCESSED,
69
+ filename="preprocessed/preprocessed_objects.json",
70
+ repo_type="dataset",
71
+ token=token,
72
+ )
73
+ payload = json.loads(Path(path).read_text(encoding="utf-8"))
74
+ rows = [r for r in payload.get("objects", []) if r.get("status", "ready") == "ready"]
75
+ if not rows:
76
+ raise RuntimeError("preprocessed_objects.json bos")
77
+ return rows
78
+
79
+
80
+ def _uid_prefix(uid: str) -> str:
81
+ return f"preprocessed/{uid[:2]}/{uid}"
82
+
83
+
84
+ def _copy_hub_file(cached: Path, target: Path) -> None:
85
+ target.parent.mkdir(parents=True, exist_ok=True)
86
+ if target.exists() and target.stat().st_size > 0:
87
+ return
88
+ shutil.copy2(cached, target)
89
+
90
+
91
+ def _prefetch_uid(uid: str, token: str) -> bool:
92
+ """Tek obje dosyalarini stream_cache'e indir. Eksik/404 ise False."""
93
+ from huggingface_hub import hf_hub_download
94
+
95
+ dest = CACHE / uid[:2] / uid
96
+ dest.mkdir(parents=True, exist_ok=True)
97
+ try:
98
+ for name in REQUIRED_FILES:
99
+ cached = Path(
100
+ hf_hub_download(
101
+ repo_id=HF_PREPROCESSED,
102
+ filename=f"{_uid_prefix(uid)}/{name}",
103
+ repo_type="dataset",
104
+ token=token,
105
+ )
106
+ )
107
+ _copy_hub_file(cached, dest / name)
108
+ got_render = False
109
+ for rel in RENDER_CANDIDATES:
110
+ try:
111
+ cached = Path(
112
+ hf_hub_download(
113
+ repo_id=HF_PREPROCESSED,
114
+ filename=f"{_uid_prefix(uid)}/{rel}",
115
+ repo_type="dataset",
116
+ token=token,
117
+ )
118
+ )
119
+ _copy_hub_file(cached, dest / rel)
120
+ got_render = True
121
+ except Exception:
122
+ continue
123
+ if not got_render:
124
+ _log(f"UYARI: {uid} render yok, siyah pad ile devam")
125
+ return (dest / "geometry_latent.npz").exists()
126
+ except Exception as exc:
127
+ msg = str(exc).lower()
128
+ if "429" in msg or "rate limit" in msg:
129
+ _log(f"RATE LIMIT uid={uid}: {exc}")
130
+ raise
131
+ _log(f"ATLA uid={uid}: {exc}")
132
+ return False
133
+
134
+
135
+ def _prune_cache(keep_uids: set[str]) -> None:
136
+ if not CACHE.exists():
137
+ return
138
+ for xx in CACHE.iterdir():
139
+ if not xx.is_dir():
140
+ continue
141
+ for uid_dir in xx.iterdir():
142
+ if uid_dir.is_dir() and uid_dir.name not in keep_uids:
143
+ shutil.rmtree(uid_dir, ignore_errors=True)
144
+
145
+
146
+ def _upload_checkpoint() -> None:
147
+ latest = CHECKPOINT_DIR / "latest_model.pt"
148
+ if not latest.exists() or not HF_TOKEN:
149
+ return
150
+ try:
151
+ from huggingface_hub import HfApi
152
+
153
+ api = HfApi(token=HF_TOKEN)
154
+ api.upload_file(
155
+ path_or_fileobj=str(latest),
156
+ path_in_repo="checkpoints/latest_model.pt",
157
+ repo_id=HF_REPO,
158
+ repo_type=HF_REPO_TYPE,
159
+ token=HF_TOKEN,
160
+ commit_message="stream autoloop checkpoint",
161
+ )
162
+ _log(f"HF upload OK -> {HF_REPO}/checkpoints/latest_model.pt ({latest.stat().st_size // 1024} KB)")
163
+ except Exception as exc:
164
+ _log(f"HF upload basarisiz (devam): {exc}")
165
+
166
+
167
+ def _run_train_chunk(epochs: int, stall_sec: int, checkpoint_every: int) -> int:
168
+ """Train subprocess; stall_sec boyunca log yoksa oldur. returncode dondur."""
169
+ cmd = [
170
+ sys.executable,
171
+ "-u",
172
+ str(ROOT / "train_pipeline.py"),
173
+ "--mode",
174
+ "real",
175
+ "--epochs",
176
+ str(epochs),
177
+ "--data-root",
178
+ str(CACHE),
179
+ "--validation-every",
180
+ "999999",
181
+ "--checkpoint-every",
182
+ str(checkpoint_every),
183
+ ]
184
+ latest = CHECKPOINT_DIR / "latest_model.pt"
185
+ if latest.exists():
186
+ cmd.extend(["--resume_from", str(latest)])
187
+
188
+ env = os.environ.copy()
189
+ env["PYTHONUNBUFFERED"] = "1"
190
+ _log(f"TRAIN: {' '.join(cmd)}")
191
+
192
+ proc = subprocess.Popen(
193
+ cmd,
194
+ cwd=str(ROOT),
195
+ env=env,
196
+ stdout=subprocess.PIPE,
197
+ stderr=subprocess.STDOUT,
198
+ text=True,
199
+ bufsize=1,
200
+ )
201
+ assert proc.stdout is not None
202
+ last_out = time.time()
203
+ while True:
204
+ line = proc.stdout.readline()
205
+ if line:
206
+ print(line, end="", flush=True)
207
+ last_out = time.time()
208
+ if "checkpoint_saved" in line.lower():
209
+ _upload_checkpoint()
210
+ continue
211
+ if proc.poll() is not None:
212
+ break
213
+ if time.time() - last_out > stall_sec:
214
+ _log(f"STALL {stall_sec}s — process olduruluyor (otomatik restart)")
215
+ proc.kill()
216
+ try:
217
+ proc.wait(timeout=30)
218
+ except Exception:
219
+ pass
220
+ return 124
221
+ time.sleep(0.5)
222
+ return int(proc.returncode or 0)
223
+
224
+
225
+ def run_once(args: argparse.Namespace) -> bool:
226
+ """Bir tam tur (manifest sonuna kadar). True=bitti, False=hata/yeniden dene."""
227
+ if not HF_TOKEN:
228
+ _log("HATA: HF_TOKEN yok")
229
+ return True
230
+
231
+ progress = _load_progress()
232
+ rows = _load_manifest(HF_TOKEN)
233
+ n = len(rows)
234
+ idx = int(progress.get("next_index", 0))
235
+ _log(f"Manifest={n} | next_index={idx} | chunk={args.chunk_size}")
236
+
237
+ while idx < n:
238
+ chunk = rows[idx : idx + args.chunk_size]
239
+ uids = [str(r.get("uid") or r.get("object_id")) for r in chunk]
240
+ _log(f"CHUNK [{idx}:{idx + len(uids)}] -> {uids[:3]}{'...' if len(uids) > 3 else ''}")
241
+
242
+ ok_uids: list[str] = []
243
+ for uid in uids:
244
+ try:
245
+ if _prefetch_uid(uid, HF_TOKEN):
246
+ ok_uids.append(uid)
247
+ else:
248
+ progress.setdefault("skipped", []).append(uid)
249
+ except Exception as exc:
250
+ if "429" in str(exc) or "rate limit" in str(exc).lower():
251
+ _log("Rate limit — 90s bekle, sonra restart")
252
+ _save_progress(progress)
253
+ time.sleep(90)
254
+ return False
255
+ progress.setdefault("skipped", []).append(uid)
256
+
257
+ if not ok_uids:
258
+ idx += len(chunk)
259
+ progress["next_index"] = idx
260
+ _save_progress(progress)
261
+ continue
262
+
263
+ _prune_cache(set(ok_uids))
264
+ code = _run_train_chunk(
265
+ epochs=args.epochs_per_chunk,
266
+ stall_sec=args.stall_sec,
267
+ checkpoint_every=args.checkpoint_every,
268
+ )
269
+ if code == 124:
270
+ progress["restarts"] = int(progress.get("restarts", 0)) + 1
271
+ _save_progress(progress)
272
+ _log("Stall restart — ayni chunk tekrar denenecek")
273
+ time.sleep(15)
274
+ return False
275
+ if code != 0:
276
+ progress["restarts"] = int(progress.get("restarts", 0)) + 1
277
+ _save_progress(progress)
278
+ _log(f"Train exit={code} — 20s sonra restart")
279
+ time.sleep(20)
280
+ return False
281
+
282
+ done = list(progress.get("done_uids", []))
283
+ done.extend(ok_uids)
284
+ progress["done_uids"] = done[-500:] # dosya sismesin
285
+ idx += len(chunk)
286
+ progress["next_index"] = idx
287
+ progress["rounds"] = int(progress.get("rounds", 0)) + 1
288
+ _save_progress(progress)
289
+ _upload_checkpoint()
290
+ _log(f"Chunk OK | ilerleme {idx}/{n}")
291
+
292
+ _log("Tum objeler islendi.")
293
+ return True
294
+
295
+
296
+ def parse_args() -> argparse.Namespace:
297
+ p = argparse.ArgumentParser(description="MeshAI stream train autoloop")
298
+ p.add_argument("--chunk-size", type=int, default=8, help="Her turda kac obje indir+egit")
299
+ p.add_argument("--epochs-per-chunk", type=int, default=1)
300
+ p.add_argument("--checkpoint-every", type=int, default=10)
301
+ p.add_argument("--stall-sec", type=int, default=600, help="Log yoksa oldur (sn)")
302
+ p.add_argument("--max-restarts", type=int, default=500)
303
+ return p.parse_args()
304
+
305
+
306
+ def main() -> None:
307
+ args = parse_args()
308
+ CHECKPOINT_DIR.mkdir(parents=True, exist_ok=True)
309
+ CACHE.mkdir(parents=True, exist_ok=True)
310
+ _log(
311
+ f"START chunk={args.chunk_size} epochs/chunk={args.epochs_per_chunk} "
312
+ f"stall={args.stall_sec}s repo={HF_PREPROCESSED}"
313
+ )
314
+ restarts = 0
315
+ while restarts <= args.max_restarts:
316
+ try:
317
+ finished = run_once(args)
318
+ if finished:
319
+ _log("DONE")
320
+ return
321
+ restarts += 1
322
+ _log(f"Auto-restart #{restarts}")
323
+ except KeyboardInterrupt:
324
+ _log("Kullanici durdurdu")
325
+ raise
326
+ except Exception as exc:
327
+ restarts += 1
328
+ _log(f"Beklenmeyen hata: {exc} — restart #{restarts}")
329
+ time.sleep(30)
330
+ _log("max-restarts asildi, cikis")
331
+ sys.exit(1)
332
+
333
+
334
+ if __name__ == "__main__":
335
+ main()