rockerritesh commited on
Commit
e5e6cca
·
verified ·
1 Parent(s): c30fa7f

Visual steerability dashboard (CPU PaliGemma + L10 SAE)

Browse files
.gitattributes CHANGED
@@ -1,35 +1,4 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
  *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  *.pt filter=lfs diff=lfs merge=lfs -text
2
+ *.npz filter=lfs diff=lfs merge=lfs -text
3
+ examples/apple1.jpg filter=lfs diff=lfs merge=lfs -text
4
+ examples/apple2.jpg filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
README.md CHANGED
@@ -1,13 +1,18 @@
1
  ---
2
  title: Visual Steerability
3
- emoji: 🏆
4
- colorFrom: green
5
- colorTo: gray
6
  sdk: gradio
7
- sdk_version: 6.19.0
8
- python_version: '3.13'
9
  app_file: app.py
10
  pinned: false
 
11
  ---
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
1
  ---
2
  title: Visual Steerability
3
+ emoji: 🎛️
4
+ colorFrom: indigo
5
+ colorTo: red
6
  sdk: gradio
7
+ sdk_version: 5.9.1
 
8
  app_file: app.py
9
  pinned: false
10
+ short_description: Steer PaliGemma's vision model from the inside (SAE)
11
  ---
12
 
13
+ # 🎛️ Visual Steerability
14
+
15
+ Pass an image → see its **L10 SAE neuron activations** (PaliGemma's SigLIP-tower internal
16
+ representation) → **clamp a neuron's latent** → watch the caption change.
17
+
18
+ Companion demo to the explainer **[The Steerability Spectrum of Visual Attributes](https://sumityadav.com.np/study/steerability-spectrum/)**.
__pycache__/app.cpython-311.pyc ADDED
Binary file (17.7 kB). View file
 
app.py ADDED
@@ -0,0 +1,178 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Visual Steerability — steer PaliGemma's vision model from the inside (ZeroGPU Space).
2
+
3
+ Pass an image -> see its L10 SAE neuron activations (the model's internal representation) ->
4
+ clamp a neuron's latent -> watch PaliGemma's caption change. Companion to the explainer at
5
+ https://sumityadav.com.np/study/steerability-spectrum/
6
+ """
7
+ import os, json
8
+ import numpy as np
9
+ import torch, torch.nn as nn
10
+ import plotly.graph_objects as go
11
+ import gradio as gr
12
+ from PIL import Image as PILImage
13
+ from transformers import AutoProcessor, PaliGemmaForConditionalGeneration
14
+
15
+ try:
16
+ import spaces # ZeroGPU
17
+ GPU = spaces.GPU
18
+ except Exception: # local / non-ZeroGPU fallback (no-op decorator)
19
+ def GPU(*a, **k):
20
+ if a and callable(a[0]): # used bare: @GPU
21
+ return a[0]
22
+ def deco(f): # used with args: @GPU(duration=...)
23
+ return f
24
+ return deco
25
+
26
+ MODEL = "google/paligemma-3b-mix-224"
27
+ SAE_PT = "sae_l10_coco.pt"
28
+ DASH = "dash_data.npz"
29
+ HF_TOKEN = os.environ.get("HF_TOKEN")
30
+
31
+
32
+ class TopKSAE(nn.Module):
33
+ def __init__(self, d, m, k):
34
+ super().__init__(); self.k = k
35
+ self.b_pre = nn.Parameter(torch.zeros(d)); self.W_enc = nn.Parameter(torch.zeros(d, m))
36
+ self.b_enc = nn.Parameter(torch.zeros(m)); self.W_dec = nn.Parameter(torch.zeros(m, d))
37
+
38
+ def encode(self, x):
39
+ pre = (x - self.b_pre) @ self.W_enc + self.b_enc
40
+ v, i = pre.topk(self.k, dim=-1)
41
+ a = torch.zeros_like(pre); a.scatter_(-1, i, torch.relu(v)); return a
42
+
43
+
44
+ # ---- load (CPU at import; moved to GPU inside @GPU fns on ZeroGPU) ----
45
+ print("loading SAE + map …")
46
+ ckpt = torch.load(SAE_PT, map_location="cpu", weights_only=False)
47
+ D, M, K, L = ckpt["d"], ckpt["m"], ckpt["k"], ckpt["l"]
48
+ SN = float(ckpt["scale_norm"])
49
+ sae = TopKSAE(D, M, K); sae.load_state_dict(ckpt["state_dict"]); sae.eval()
50
+ mu = torch.tensor(ckpt["mu"], dtype=torch.float32)
51
+ _d = np.load(DASH, allow_pickle=True)
52
+ COORDS = _d["coords"]; LAT = _d["latents"].astype(np.float32)
53
+
54
+ print("loading PaliGemma … (first GPU call moves it to cuda)")
55
+ model = PaliGemmaForConditionalGeneration.from_pretrained(MODEL, torch_dtype=torch.bfloat16, token=HF_TOKEN).eval()
56
+ proc = AutoProcessor.from_pretrained(MODEL, token=HF_TOKEN)
57
+ tok = proc.tokenizer
58
+ vtower = lambda: model.model.vision_tower
59
+ block = model.model.vision_tower.encoder.layers[L - 1]
60
+ _state = {"on": False, "feat": 0, "strength": 0.0, "moved": False}
61
+
62
+
63
+ def _ensure(dev):
64
+ if not _state["moved"] and dev == "cuda":
65
+ model.to("cuda"); sae.to("cuda")
66
+ globals()["mu"] = mu.to("cuda"); _state["moved"] = True
67
+
68
+
69
+ def _hook(mod, inp, out):
70
+ if not _state["on"]:
71
+ return out
72
+ h = out[0] if isinstance(out, tuple) else out
73
+ hf = h.float(); hn = (hf - mu) * SN
74
+ b, P, d = hn.shape
75
+ acts = sae.encode(hn.reshape(b * P, d))
76
+ cur = acts[:, _state["feat"]]
77
+ dn = (_state["strength"] - cur).unsqueeze(-1) * sae.W_dec[_state["feat"]]
78
+ h2 = (hf + (dn / SN).reshape(b, P, d)).to(h.dtype)
79
+ return (h2,) + tuple(out[1:]) if isinstance(out, tuple) else h2
80
+ block.register_forward_hook(_hook)
81
+
82
+
83
+ @torch.no_grad()
84
+ def _latent(pil, dev):
85
+ pv = proc.image_processor([pil], return_tensors="pt")["pixel_values"].to(dev, torch.bfloat16)
86
+ hs = vtower()(pixel_values=pv, output_hidden_states=True).hidden_states[L].float()
87
+ acts = sae.encode(((hs[0] - mu) * SN))
88
+ lat = acts.mean(0).detach().cpu().numpy()
89
+ return lat
90
+
91
+
92
+ @torch.no_grad()
93
+ def _caption(pil, dev, feat=0, strength=0.0, steer=False):
94
+ _state["on"] = steer; _state["feat"] = int(feat); _state["strength"] = float(strength)
95
+ inp = proc(text="describe the object in the image", images=pil, return_tensors="pt").to(dev, torch.bfloat16)
96
+ n = inp["input_ids"].shape[1]
97
+ g = model.generate(**inp, max_new_tokens=28, do_sample=False)
98
+ _state["on"] = False
99
+ return tok.decode(g[0][n:], skip_special_tokens=True).strip()
100
+
101
+
102
+ def scatter(coord, feat):
103
+ feat = int(feat); c = LAT[:, feat]
104
+ fig = go.Figure()
105
+ fig.add_trace(go.Scatter(x=COORDS[:, 0], y=COORDS[:, 1], mode="markers",
106
+ marker=dict(size=7, color=c, colorscale="Viridis", showscale=True, colorbar=dict(title="#%d" % feat), opacity=.55),
107
+ hovertemplate="neuron #%d = %%{marker.color:.2f}<extra></extra>" % feat, name="500 ref"))
108
+ if coord is not None:
109
+ fig.add_trace(go.Scatter(x=[float(coord[0])], y=[float(coord[1])], mode="markers",
110
+ marker=dict(size=22, color="red", symbol="star", line=dict(width=1.4, color="black")),
111
+ name="your image", hovertemplate="your image<extra></extra>"))
112
+ fig.update_layout(title="L10 SAE latent map — your image (★), colored by neuron #%d" % feat,
113
+ xaxis_title="PC1", yaxis_title="PC2", height=420, margin=dict(l=8, r=8, t=44, b=8),
114
+ legend=dict(orientation="h", y=1.04, x=0))
115
+ return fig
116
+
117
+
118
+ def neuron_bar(lat):
119
+ idx = np.argsort(-lat)[:10][::-1]
120
+ fig = go.Figure(go.Bar(x=lat[idx], y=["#%d" % i for i in idx], orientation="h",
121
+ marker_color="#4f46e5", hovertemplate="neuron #%{y} = %{x:.2f}<extra></extra>"))
122
+ fig.update_layout(title="this image's strongest L10 SAE neurons", height=300,
123
+ xaxis_title="activation", margin=dict(l=8, r=8, t=44, b=8), yaxis=dict(type="category"))
124
+ return fig
125
+
126
+
127
+ @GPU(duration=70)
128
+ def analyze(pil):
129
+ if pil is None:
130
+ return "Upload or pick an image.", None, None, gr.update(choices=[], value=None), None
131
+ dev = "cuda" if torch.cuda.is_available() else "cpu"; _ensure(dev)
132
+ lat = _latent(pil, dev)
133
+ base = _caption(pil, dev, steer=False)
134
+ top = np.argsort(-lat)[:8]
135
+ choices = [("#%d (act %.1f)" % (int(t), lat[t]), int(t)) for t in top]
136
+ coord = (lat - _d["pca_mean"]) @ _d["pca_comp"].T
137
+ return base, scatter(coord, int(top[0])), neuron_bar(lat), gr.update(choices=choices, value=int(top[0])), coord.tolist()
138
+
139
+
140
+ @GPU(duration=70)
141
+ def steer(pil, neuron, strength):
142
+ if pil is None or neuron is None:
143
+ return "Run an image first."
144
+ dev = "cuda" if torch.cuda.is_available() else "cpu"; _ensure(dev)
145
+ if float(strength) == 0:
146
+ return "Move the slider to clamp this neuron."
147
+ return _caption(pil, dev, feat=int(neuron), strength=float(strength), steer=True)
148
+
149
+
150
+ def recolor(coord, neuron):
151
+ if coord is None or neuron is None:
152
+ return None
153
+ return scatter(np.array(coord), int(neuron))
154
+
155
+
156
+ with gr.Blocks(theme=gr.themes.Soft(primary_hue="indigo"), title="Visual Steerability") as demo:
157
+ gr.Markdown("# 🎛️ Steer PaliGemma's vision model from the inside\n"
158
+ "Pass an image → see its **L10 SAE neuron activations** (the model's internal representation) → "
159
+ "**clamp a neuron** and watch the caption change. "
160
+ "Read the explainer: [The Steerability Spectrum](https://sumityadav.com.np/study/steerability-spectrum/).")
161
+ st_coord = gr.State()
162
+ with gr.Row():
163
+ with gr.Column(scale=4):
164
+ img = gr.Image(type="pil", label="image", height=300)
165
+ run = gr.Button("① Caption + read internal representation", variant="primary")
166
+ base = gr.Textbox(label="caption", lines=2, interactive=False)
167
+ neuron = gr.Radio(choices=[], label="② pick a neuron to steer", interactive=True)
168
+ strength = gr.Slider(-40, 40, value=0, step=1, label="③ clamp strength → re-caption on release")
169
+ out = gr.Textbox(label="steered caption", lines=2, interactive=False)
170
+ with gr.Column(scale=5):
171
+ plot = gr.Plot(label="SAE latent map")
172
+ bar = gr.Plot(label="internal representation")
173
+ gr.Examples(examples=[["examples/" + f] for f in sorted(os.listdir("examples"))], inputs=[img], label="examples")
174
+ run.click(analyze, [img], [base, plot, bar, neuron, st_coord])
175
+ neuron.change(recolor, [st_coord, neuron], [plot])
176
+ strength.release(steer, [img, neuron, strength], [out])
177
+
178
+ demo.queue(max_size=12).launch()
dash_data.npz ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f0b8c917c09dc9910c3fc78f31858787152a62149b0415e8fd19f4a1aa7de92e
3
+ size 3147174
examples/apple1.jpg ADDED

Git LFS Details

  • SHA256: bfbaf6828607d9b94fb54056877497ac32926aa75c09de4abd61bc05ba24e2a7
  • Pointer size: 131 Bytes
  • Size of remote file: 576 kB
examples/apple2.jpg ADDED

Git LFS Details

  • SHA256: 7862439476a9b9d5ec3613b47c9b6242de534a3386b2642415d041efc0482286
  • Pointer size: 132 Bytes
  • Size of remote file: 1.95 MB
examples/ex_spot_large.png ADDED
examples/img_00000_spot_s0.500.png ADDED
examples/img_00016_spot_t000.png ADDED
examples/img_00024_teapot_t000.png ADDED
examples/img_00025_teapot_t045.png ADDED
examples/img_00119_torus_knot_s1.500.png ADDED
examples/img_00199_gear_s1.500.png ADDED
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ torch
2
+ transformers
3
+ accelerate
4
+ sentencepiece
5
+ pillow
6
+ numpy
7
+ plotly
8
+ huggingface_hub
sae_l10_coco.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d2634de709e576ef1e2c75ab66d418096cad61875d91bf0a83ed488704254203
3
+ size 84985487