ShesterG commited on
Commit
4e25ac8
·
1 Parent(s): 2699c3a

Add MAE and CLIP encoders alongside DINOv3 (per-model prefix/grid/normalization; MAE ids_restore un-shuffle)

Browse files
Files changed (1) hide show
  1. app.py +44 -19
app.py CHANGED
@@ -15,50 +15,72 @@ from PIL import Image
15
  print("[app] transformers import...", flush=True)
16
  from scipy.cluster.hierarchy import linkage, to_tree
17
  from scipy.spatial.distance import squareform
18
- from transformers import AutoModel
19
 
20
  RES, MS = 224, 4
21
- GRID = RES // 16 # 14x14 = 196 patches
22
  DEV = "cuda" if torch.cuda.is_available() else "cpu"
23
  TOKEN = os.environ.get("HF_TOKEN")
24
 
 
 
 
 
 
 
25
  MODELS = {
26
- "ViT-L · 0.3B (fast)": "facebook/dinov3-vitl16-pretrain-lvd1689m",
27
- "ViT-H+ · 0.84B (slower)": "facebook/dinov3-vith16plus-pretrain-lvd1689m",
28
- "ViT-7B · 6.7B (needs GPU)": "facebook/dinov3-vit7b16-pretrain-lvd1689m",
 
 
29
  }
30
  DEFAULT_MODEL = next(iter(MODELS))
31
 
32
- MEAN = torch.tensor([0.485, 0.456, 0.406]).view(3, 1, 1)
33
- STD = torch.tensor([0.229, 0.224, 0.225]).view(3, 1, 1)
34
  TREE_HTML = open("tree_widget.html").read()
35
  REVEAL_HTML = open("reveal_widget.html").read()
36
 
37
  # single-slot cache: keep only the most-recently-used model (avoids OOM when switching)
38
  _cur = {"repo": None, "model": None}
39
- def get_model(repo):
 
40
  if _cur["repo"] != repo:
41
  _cur["model"] = None
42
  import gc; gc.collect()
43
  dt = torch.bfloat16 if "vit7b16" in repo else torch.float32 # 7B in bf16 to fit
44
  print(f"[app] loading {repo} ({dt})", flush=True)
45
- _cur["model"] = AutoModel.from_pretrained(repo, dtype=dt, token=TOKEN,
46
- attn_implementation="eager").eval().to(DEV)
 
 
 
47
  _cur["repo"] = repo
48
  return _cur["model"]
49
 
50
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  def build_tree(image, model_label):
52
- model = get_model(MODELS.get(model_label, MODELS[DEFAULT_MODEL]))
 
53
  mdt = next(model.parameters()).dtype
 
54
  img = image.convert("RGB").resize((RES, RES), Image.BICUBIC)
55
  arr = np.asarray(img).astype(np.float32) / 255
56
- x = ((torch.from_numpy(arr).permute(2, 0, 1) - MEAN) / STD).unsqueeze(0).to(DEV, mdt)
57
- with torch.no_grad():
58
- o = model(x, output_attentions=True)
59
- prefix = o.last_hidden_state.shape[1] - GRID * GRID # strip CLS + register tokens
60
- feat = o.last_hidden_state[0, prefix:].float().cpu().numpy()
61
- att = o.attentions[-1][0][:, 0, prefix:].mean(0).float().cpu().numpy()
62
  att_pct = 100.0 * att / att.sum()
63
  P = feat.shape[0]; g = int(round(P ** 0.5))
64
  fn = feat / (np.linalg.norm(feat, axis=1, keepdims=True) + 1e-8)
@@ -127,9 +149,12 @@ if gr is not None:
127
  with gr.Row():
128
  inp = gr.Image(type="pil", label="Upload an image", height=300)
129
  with gr.Column(scale=0):
130
- model_sel = gr.Dropdown(choices=list(MODELS.keys()), value=DEFAULT_MODEL, label="DINOv3 model")
131
  btn = gr.Button("Build tree + reveal", variant="primary")
132
- gr.Markdown("<small>ViT-H+/7B are large — slow on the free CPU; **ViT-7B realistically needs a GPU Space**. "
 
 
 
133
  "First use of each model downloads its weights (one-time).</small>")
134
  with gr.Tab("Condensed tree (hover)"):
135
  out_tree = gr.HTML()
 
15
  print("[app] transformers import...", flush=True)
16
  from scipy.cluster.hierarchy import linkage, to_tree
17
  from scipy.spatial.distance import squareform
18
+ from transformers import AutoModel, ViTMAEModel, CLIPVisionModel
19
 
20
  RES, MS = 224, 4
 
21
  DEV = "cuda" if torch.cuda.is_available() else "cpu"
22
  TOKEN = os.environ.get("HF_TOKEN")
23
 
24
+ IMNET = (torch.tensor([0.485, 0.456, 0.406]).view(3, 1, 1),
25
+ torch.tensor([0.229, 0.224, 0.225]).view(3, 1, 1))
26
+ CLIPN = (torch.tensor([0.48145466, 0.4578275, 0.40821073]).view(3, 1, 1),
27
+ torch.tensor([0.26862954, 0.26130258, 0.27577711]).view(3, 1, 1))
28
+
29
+ # kind: how to strip prefix tokens / un-shuffle; patch: 224/patch = grid side
30
  MODELS = {
31
+ "DINOv3 ViT-L · 0.3B (fast)": dict(repo="facebook/dinov3-vitl16-pretrain-lvd1689m", kind="dinov3", patch=16, norm=IMNET),
32
+ "DINOv3 ViT-H+ · 0.84B (slower)": dict(repo="facebook/dinov3-vith16plus-pretrain-lvd1689m", kind="dinov3", patch=16, norm=IMNET),
33
+ "DINOv3 ViT-7B · 6.7B (needs GPU)": dict(repo="facebook/dinov3-vit7b16-pretrain-lvd1689m", kind="dinov3", patch=16, norm=IMNET),
34
+ "MAE ViT-L (pixel-reconstruction)": dict(repo="facebook/vit-mae-large", kind="mae", patch=16, norm=IMNET),
35
+ "CLIP ViT-L/14 (language-aligned)": dict(repo="openai/clip-vit-large-patch14", kind="clip", patch=14, norm=CLIPN),
36
  }
37
  DEFAULT_MODEL = next(iter(MODELS))
38
 
 
 
39
  TREE_HTML = open("tree_widget.html").read()
40
  REVEAL_HTML = open("reveal_widget.html").read()
41
 
42
  # single-slot cache: keep only the most-recently-used model (avoids OOM when switching)
43
  _cur = {"repo": None, "model": None}
44
+ def get_model(spec):
45
+ repo = spec["repo"]
46
  if _cur["repo"] != repo:
47
  _cur["model"] = None
48
  import gc; gc.collect()
49
  dt = torch.bfloat16 if "vit7b16" in repo else torch.float32 # 7B in bf16 to fit
50
  print(f"[app] loading {repo} ({dt})", flush=True)
51
+ cls = {"dinov3": AutoModel, "mae": ViTMAEModel, "clip": CLIPVisionModel}[spec["kind"]]
52
+ mdl = cls.from_pretrained(repo, dtype=dt, token=TOKEN, attn_implementation="eager")
53
+ if spec["kind"] == "mae":
54
+ mdl.config.mask_ratio = 0.0 # keep ALL patches (MAE masks 75% by default)
55
+ _cur["model"] = mdl.eval().to(DEV)
56
  _cur["repo"] = repo
57
  return _cur["model"]
58
 
59
 
60
+ def encode(model, spec, x):
61
+ """-> (feat[P,C], att[P]) with patches in spatial row-major order."""
62
+ with torch.no_grad():
63
+ o = model(x, output_attentions=True)
64
+ h, a = o.last_hidden_state, o.attentions[-1][0] # a: (heads, S, S)
65
+ if spec["kind"] == "mae":
66
+ ids = o.ids_restore[0] # MAE shuffles tokens -> un-shuffle
67
+ feat, att = h[0, 1:][ids], a[:, 0, 1:][:, ids].mean(0)
68
+ else:
69
+ g = RES // spec["patch"]
70
+ prefix = h.shape[1] - g * g # strip CLS (+ registers for DINOv3)
71
+ feat, att = h[0, prefix:], a[:, 0, prefix:].mean(0)
72
+ return feat.float().cpu().numpy(), att.float().cpu().numpy()
73
+
74
+
75
  def build_tree(image, model_label):
76
+ spec = MODELS.get(model_label, MODELS[DEFAULT_MODEL])
77
+ model = get_model(spec)
78
  mdt = next(model.parameters()).dtype
79
+ mean, std = spec["norm"]
80
  img = image.convert("RGB").resize((RES, RES), Image.BICUBIC)
81
  arr = np.asarray(img).astype(np.float32) / 255
82
+ x = ((torch.from_numpy(arr).permute(2, 0, 1) - mean) / std).unsqueeze(0).to(DEV, mdt)
83
+ feat, att = encode(model, spec, x)
 
 
 
 
84
  att_pct = 100.0 * att / att.sum()
85
  P = feat.shape[0]; g = int(round(P ** 0.5))
86
  fn = feat / (np.linalg.norm(feat, axis=1, keepdims=True) + 1e-8)
 
149
  with gr.Row():
150
  inp = gr.Image(type="pil", label="Upload an image", height=300)
151
  with gr.Column(scale=0):
152
+ model_sel = gr.Dropdown(choices=list(MODELS.keys()), value=DEFAULT_MODEL, label="Encoder")
153
  btn = gr.Button("Build tree + reveal", variant="primary")
154
+ gr.Markdown("<small>Compare encoder families: **DINOv3** (self-supervised, semantic), "
155
+ "**MAE** (pixel-reconstruction — more texture/appearance driven), "
156
+ "**CLIP** (language-aligned — 16×16 grid, patch-14).<br>"
157
+ "ViT-H+/7B are large — slow on the free CPU; **ViT-7B realistically needs a GPU Space**. "
158
  "First use of each model downloads its weights (one-time).</small>")
159
  with gr.Tab("Condensed tree (hover)"):
160
  out_tree = gr.HTML()