keypa commited on
Commit
feeb5f6
·
verified ·
1 Parent(s): e2af953

Upload precompute_colab.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. precompute_colab.py +159 -0
precompute_colab.py ADDED
@@ -0,0 +1,159 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # %% [markdown]
2
+ # # MoonViT-V2 Embedding Precompute — Colab T4 (free tier, resumable)
3
+ #
4
+ # Precomputes frozen MoonViT-V2 merged embeddings (4096-dim per merged token) for the
5
+ # agentic + cauldron image corpus, so the Vision-Adapter projector can be trained on a
6
+ # small cached tensor set instead of running the ViT in the training hot loop.
7
+ #
8
+ # Hardware target: Google Colab Tesla T4 (15GB VRAM), free tier. MoonViT is ~0.8 GB in
9
+ # bf16; we use the remaining ~10GB for packed-patch activations by batching images.
10
+ # The job is fully resumable: embeddings are flushed to Google Drive every N steps and
11
+ # already-cached images are skipped, so a 4h session limit just restarts where it left.
12
+ #
13
+ # Setup (run once in a Colab cell before this script):
14
+ # !git clone <your repo or upload these files> # need moonvit.py, preprocess.py beside this file
15
+ # !pip install safetensors pillow numpy huggingface_hub torch --quiet
16
+ # from google.colab import drive; drive.mount('/content/drive')
17
+ # # Put the image corpus under /content/drive/MyDrive/vision_adapter/images/{agentic,cauldron}
18
+ # # (export from your Modal Volume / local ETL), then point IMAGES_ROOT below at it.
19
+
20
+ # %%
21
+ import os, sys, time, json, glob, hashlib
22
+
23
+ # ------------------------------- config --------------------------------------
24
+ IMAGES_ROOT = "/content/drive/MyDrive/vision_adapter/images" # input corpus (png/jpg)
25
+ OUT_ROOT = "/content/drive/MyDrive/vision_adapter/embeddings" # .pt cache (Drive, persists)
26
+ MOONVIT_REPO = "keypa/MoonViT-V2-Standalone" # pulls weights+cfg+code from HF
27
+ BF16 = True
28
+ BATCH_PATCHES = 240_000 # cap patches/step on T4 (~10GB headroom); tune up if you can
29
+ FLUSH_EVERY = 2_000 # steps between Google Drive journal commits (log)
30
+ PROGRESS_EVERY= 250
31
+ DTYPE_STR = "bf16"
32
+
33
+ sys.path.insert(0, os.path.dirname(os.path.abspath("__file__"))) # beside moonvit.py / preprocess.py
34
+
35
+ # %%
36
+ import torch
37
+ from huggingface_hub import hf_hub_download
38
+ from PIL import Image
39
+
40
+ from moonvit import load_moonvit_from_safetensors
41
+ from preprocess import process_image, collate_images
42
+
43
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
44
+
45
+
46
+ def _ensure_outdir(path: str | None = None) -> str:
47
+ p = path or OUT_ROOT
48
+ os.makedirs(p, exist_ok=True)
49
+ return p
50
+
51
+ def _emb_key(image_path: str) -> str:
52
+ """Volume-relative hash key: strip everything up to and including the 'images/'
53
+ marker so Modal (/data/images/...) and Colab Drive paths produce identical names
54
+ for the same logical image (agentic/foo.png, cauldron/bar.png)."""
55
+ rel = image_path.split("/images/", 1)[-1] if "/images/" in image_path else image_path
56
+ return hashlib.sha1(rel.encode()).hexdigest()[:20] + ".pt"
57
+
58
+
59
+ def _emb_path(image_path: str) -> str:
60
+ return os.path.join(OUT_ROOT, _emb_key(image_path))
61
+
62
+
63
+ def _already_done(image_path: str) -> bool:
64
+ p = _emb_path(image_path)
65
+ if not os.path.exists(p) or os.path.getsize(p) == 0:
66
+ return False
67
+ try: # cheap header validation — catches truncated writes from a killed cell
68
+ d = torch.load(p, map_location="cpu", mmap=True)
69
+ return isinstance(d, torch.Tensor) and d.dim() == 2 and d.shape[-1] == 4096
70
+ except Exception:
71
+ return False
72
+
73
+
74
+ def load_vit():
75
+ cfg = json.load(open(hf_hub_download(repo_id=MOONVIT_REPO, repo_type="model",
76
+ filename="vision_config.json")))
77
+ st = hf_hub_download(repo_id=MOONVIT_REPO, repo_type="model",
78
+ filename="moonvit_v2.safetensors")
79
+ vit = load_moonvit_from_safetensors(st, cfg, device=str(device),
80
+ dtype=(torch.bfloat16 if BF16 else torch.float32))
81
+ return vit
82
+
83
+
84
+ def enumerate_images(root: str):
85
+ return sorted(glob.glob(os.path.join(root, "**", "*.*"), recursive=True))
86
+
87
+
88
+ def pack_batches(paths, batch_patch_cap):
89
+ """Greedy-pack images into batches so total patches per step <= cap (T4 headroom)."""
90
+ batches, cur, cur_p = [], [], 0
91
+ cache = {}
92
+ def patch_count(p):
93
+ if p not in cache:
94
+ with Image.open(p) as im:
95
+ w, h = im.size
96
+ import math
97
+ scale = min(1.0, math.sqrt(65536 / max(1, (w // 14) * (h // 14))),
98
+ (512 * 14) / w, (512 * 14) / h)
99
+ w2, h2 = min(int(w * scale), 7168), min(int(h * scale), 7168)
100
+ pw = (w2 + 27) // 28 * 28 // 14
101
+ ph = (h2 + 27) // 28 * 28 // 14
102
+ cache[p] = max(1, pw * ph) # raw patches (pre-merge)
103
+ return cache[p]
104
+ for p in paths:
105
+ n = patch_count(p)
106
+ if cur and cur_p + n > batch_patch_cap:
107
+ batches.append(cur); cur, cur_p = [p], n
108
+ else:
109
+ cur.append(p); cur_p += n
110
+ if cur: batches.append(cur)
111
+ return batches
112
+
113
+
114
+ def run():
115
+ _ensure_outdir()
116
+ vit = load_vit()
117
+ t0 = time.time()
118
+ todos = [p for p in enumerate_images(IMAGES_ROOT) if not _already_done(p)]
119
+ total = len(enumerate_images(IMAGES_ROOT))
120
+ print(f"[precompute] corpus={total} images | remaining={len(todos)} "
121
+ f"| cached_so_far={total - len(todos)}")
122
+ if not todos:
123
+ print("[precompute] nothing to do — cache is complete."); return
124
+ batches = pack_batches(todos, BATCH_PATCHES)
125
+ print(f"[precompute] packed into {len(batches)} batches (cap {BATCH_PATCHES} patches/step)")
126
+
127
+ done = 0
128
+ with torch.no_grad():
129
+ for bi, chunk in enumerate(batches):
130
+ ims = []
131
+ for p in chunk:
132
+ with Image.open(p) as im:
133
+ ims.append(im.convert("RGB"))
134
+ pack = collate_images(ims)
135
+ merged = vit(pack["pixel_values"].to(device).to(vit.patch_embed.proj.weight.dtype
136
+ if BF16 else torch.float32),
137
+ pack["grid_thws"].to(device))
138
+ for p, emb in zip(chunk, merged):
139
+ flat = emb.reshape(emb.shape[0], -1) # [n_merged, 4096]
140
+ out = flat.to(torch.bfloat16 if BF16 else torch.float32).cpu()
141
+ torch.save(out, _emb_path(p))
142
+ done += len(chunk)
143
+ if done % PROGRESS_EVERY < len(chunk):
144
+ used = torch.cuda.memory_allocated() / 2**30 if device.type == "cuda" else 0.0
145
+ peak = torch.cuda.max_memory_allocated() / 2**30 if device.type == "cuda" else 0.0
146
+ rate = done / max(1e-6, time.time() - t0)
147
+ eta = (len(todos) - done) / max(1e-6, rate) / 3600
148
+ print(f"[precompute] {done}/{len(todos)} "
149
+ f"gpu={used:.1f}GB peak={peak:.1f}GB {rate:.1f} img/s ETA {eta:.2f}h")
150
+ if done and done % FLUSH_EVERY < len(chunk):
151
+ with open(os.path.join(OUT_ROOT, "_journal.json"), "w") as f:
152
+ json.dump({"done": total - len(todos) + done, "total": total,
153
+ "ts": time.time()}, f)
154
+ print("[precompute] journal flushed to Drive")
155
+ print(f"[precompute] DONE. wrote {done} embeddings this session -> {OUT_ROOT}")
156
+
157
+
158
+ if __name__ == "__main__":
159
+ run()