Instructions to use keypa/MoonViT-V2-Standalone with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use keypa/MoonViT-V2-Standalone with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("image-feature-extraction", model="keypa/MoonViT-V2-Standalone")# Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("keypa/MoonViT-V2-Standalone", device_map="auto") - Notebooks
- Google Colab
- Kaggle
Upload precompute_colab.py with huggingface_hub
Browse files- 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()
|