vlm-probe / examples /tutorials /lens_embed.py
gexinh's picture
Update vlmprobe to 1.0.1 with SAE methods, guides, and technical report
31246a6 verified
Raw History Blame Contribute Delete
3.36 kB
"""Live projected visual-token nearest-neighbor tutorial. No result cache."""
import hashlib
import os
from pathlib import Path
from urllib.request import urlretrieve
import matplotlib.pyplot as plt
import torch
from PIL import Image
from transformers import AutoModelForImageTextToText, AutoProcessor
from vlmprobe import Prober
from vlmprobe.visualization import plot_input, plot_lens_heatmap
MODEL_ID = os.environ.get("VLM_PROBING_MODEL", "HuggingFaceTB/SmolVLM-500M-Instruct")
DEVICE = os.environ.get("VLM_PROBING_DEVICE", "cuda:0" if torch.cuda.is_available() else "cpu")
OUT = Path(os.environ.get("VLM_PROBING_OUTPUT", "outputs/embed_tutorial"))
OUT.mkdir(parents=True, exist_ok=True)
custom_image = os.environ.get("VLM_PROBING_IMAGE")
image_path = Path(custom_image) if custom_image else OUT / "2341576.jpg"
if custom_image and not image_path.is_file():
raise FileNotFoundError(image_path)
if not image_path.is_file():
urlretrieve("https://raw.githubusercontent.com/gexinh/vlm-probing/main/demos/assets/images/2341576.jpg", image_path)
if not custom_image:
assert hashlib.sha256(image_path.read_bytes()).hexdigest() == "f2f5880022dce631fd23240b2d690a56dd1fa551205c48b16db210b147d2f968"
image = Image.open(image_path).convert("RGB")
question = "Is the shirt black or yellow?"
model = AutoModelForImageTextToText.from_pretrained(
MODEL_ID, dtype=torch.float32, attn_implementation="eager",
).to(DEVICE).eval()
processor = AutoProcessor.from_pretrained(MODEL_ID)
probe = Prober(model, processor)
print(probe.describe())
messages = [{"role": "user", "content": [
{"type": "image"}, {"type": "text", "text": question},
]}]
prompt = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
inputs = probe.prepare(prompt=prompt, image=image, device=DEVICE)
result = probe.lens.embed(tokens="visual").run(inputs, top_k=5, vocabulary_chunk_size=4096)
print("neighbors / cosine:", result.tensors["token_ids"].shape, result.tensors["similarities"].shape)
print("coordinates:", result.tensors["positions"].shape)
for position, ids, cosine in zip(result.tensors["positions"][:8],
result.tensors["token_ids"][0, :8],
result.tensors["similarities"][0, :8]):
print(position.tolist(), [(processor.tokenizer.decode([int(i)]), round(float(s), 3))
for i, s in zip(ids, cosine)])
# Geometry is not guessed: crops/tiles are model-specific. Display exact token
# coordinates rather than stretching a multi-crop sequence over the raw image.
count = min(32, result.tensors["positions"].shape[0])
fig, axes = plt.subplots(2, 1, figsize=(12, 6), height_ratios=[4, 1], constrained_layout=True)
plot_input(image, prompt=question, answer="yellow" if not custom_image else None, ax=axes[0])
plot_lens_heatmap(result.tensors["similarities"][0, :count, 0][None],
value_kind="logit", log_scale=False, vmin=-1, vmax=1,
layer_labels=["Decoder input"],
token_labels=result.tensors["positions"][:count, 1].tolist(),
ax=axes[1], value_label="Nearest vocabulary cosine similarity",
title="First 32 visual-token positions (not image pixel coordinates)")
fig.savefig(OUT / "embed.png", dpi=160, bbox_inches="tight")
plt.show()
result.save(OUT / "embed-result")