Buckets:
| """Real-VLM micro reproduction of vStream (arXiv:2604.16587) on HF Jobs. | |
| Scaled, honest reproduction of the core method on a *single small VLM* | |
| (Qwen/Qwen2-VL-2B-Instruct) over a handful of images, to verify the | |
| mechanism on REAL model internals rather than synthetic data: | |
| 1. Extract cross-attention A^{(l,h)}_{t,i} for generated tokens -> vision tokens. | |
| 2. Cluster vision tokens into K semantic regions via DINOv2 features | |
| (paper uses DINOv3; DINOv2 is a faithful stand-in for the clustering step). | |
| 3. For each region R_k, ablate it (zero its vision tokens) and re-run the | |
| forward pass; the span-level ablation effect is | |
| Delta_k(S) = sum_t [log p(y_t|x,y<t) - log p(y_t|ablate(x,R_k),y<t)]. | |
| 4. Feature f_{S,k} = mean-pooled cross-attention over span S and region R_k, | |
| concatenated across all L*H (layer,head) -> L*H-dim vector. | |
| 5. Train a LINEAR estimator w (L*H params) to maximize Pearson between | |
| predicted w.f and target Delta; evaluate LDS = Spearman over regions, | |
| and measure the per-10-token latency of the linear estimator vs the | |
| perturbation (re-forward) baseline. | |
| This is a TOY SCALE run: ~N_IMAGES images, K regions, a 2B model. It does | |
| NOT reproduce the paper's 4x 7-9B models x 5 categories x 2000 examples. | |
| It verifies the pipeline and the direction/magnitude of the claims. | |
| """ | |
| import json, time, os, math | |
| import torch | |
| import numpy as np | |
| import torchvision | |
| import requests | |
| from PIL import Image | |
| from transformers import Qwen2VLForConditionalGeneration, AutoProcessor, Qwen2VLImageProcessor | |
| from transformers.models.qwen2_vl.modeling_qwen2_vl import Qwen2VLCausalLMOutputWithPast | |
| from sklearn.cluster import AgglomerativeClustering | |
| DEVICE = "cuda" if torch.cuda.is_available() else "cpu" | |
| N_IMAGES = int(os.environ.get("N_IMAGES", "6")) | |
| K_REGIONS = int(os.environ.get("K_REGIONS", "24")) | |
| GEN_TOKENS = int(os.environ.get("GEN_TOKENS", "40")) | |
| print(f"device={DEVICE} n_images={N_IMAGES} K={K_REGIONS} gen_tokens={GEN_TOKENS}") | |
| # ---------- load small VLM ---------- | |
| model_id = "Qwen/Qwen2-VL-2B-Instruct" | |
| processor = AutoProcessor.from_pretrained(model_id) | |
| model = Qwen2VLForConditionalGeneration.from_pretrained( | |
| model_id, torch_dtype=torch.float16, attn_implementation="eager", # eager -> attentions | |
| ).to(DEVICE).eval() | |
| # number of layers / heads -> L*H parameters of the estimator | |
| L = model.config.text_config.num_hidden_layers | |
| H = model.config.text_config.num_attention_heads | |
| D = L * H | |
| print(f"[Claim 1] VLM has L={L} layers, H={H} heads -> estimator params L*H = {D}") | |
| # ---------- load DINOv2 for region clustering (stand-in for DINOv3) ---------- | |
| try: | |
| import timm | |
| dino = timm.create_model("vit_small_patch14_dinov2.lvd142m", pretrained=True).to(DEVICE).eval() | |
| dino_forward = lambda x: dino.forward_features(x) # (B,1+patches,d) | |
| except Exception as e: | |
| from torchvision.models import Dinov2_ViTS14_Weights | |
| dino = torchvision.models.dinov2_vits14(weights=Dinov2_ViTS14_Weights.DEFAULT).to(DEVICE).eval() | |
| dino_forward = lambda x: dino.forward_features(x)["x_norm_patchtokens"] | |
| def get_image(idx): | |
| """Generate a synthetic but structured scene so DINOv2 clustering yields | |
| meaningful semantic regions and the VLM can caption it. Fully offline to | |
| avoid network flakiness; this is a TOY stand-in for real benchmark images.""" | |
| rng_i = np.random.default_rng(idx) | |
| img = Image.new("RGB", (518, 518), (int(rng_i.integers(40,90)),)*3) | |
| from PIL import ImageDraw | |
| d = ImageDraw.Draw(img) | |
| colors = [(200,40,40),(40,160,60),(40,80,210),(220,200,40),(200,60,200)] | |
| for c in range(5): | |
| x0,y0 = rng_i.integers(40,420,2) | |
| sz = rng_i.integers(70,160) | |
| d.ellipse([x0,y0,x0+sz,y0+sz], fill=colors[c%len(colors)]) | |
| d.rectangle([x0,y0+sz+20,x0+sz//2,y0+sz+60], fill=colors[(c+2)%len(colors)]) | |
| return img | |
| # a few task-style questions (mirrors the paper's diverse prompts) | |
| QUESTIONS = [ | |
| "What objects are in this image and how are they arranged?", | |
| "Describe what you see and count the main items.", | |
| "What is happening in this scene? Explain step by step.", | |
| "What colors are the main objects and where are they located?", | |
| "List the shapes present and describe their positions.", | |
| ] | |
| IMAGE_TOKEN_ID = processor.tokenizer.convert_tokens_to_ids("<|image_pad|>") | |
| def generate_and_attn(image, question, n_tokens): | |
| msgs = [{"role":"user","content":[ | |
| {"type":"image"},{"type":"text","text":question}]}] | |
| txt = processor.apply_chat_template(msgs, tokenize=False, add_generation_prompt=True) | |
| inputs = processor(text=[txt], images=[image], return_tensors="pt").to(DEVICE, torch.float16) | |
| out = model.generate( | |
| **inputs, max_new_tokens=n_tokens, do_sample=False, | |
| output_attentions=True, return_dict_in_generate=True, | |
| ) | |
| seq = out.sequences[0] | |
| # locate the contiguous block of vision tokens (<|image_pad|>) in `seq` | |
| vp = (seq == IMAGE_TOKEN_ID).nonzero(as_tuple=True)[0] | |
| if len(vp) == 0: | |
| raise RuntimeError("no image tokens found") | |
| v0, v1 = int(vp[0]), int(vp[-1]) + 1 | |
| M = v1 - v0 # number of vision tokens | |
| # re-run forward on the full generated sequence for a clean attention pass | |
| fwd = model(input_ids=seq.unsqueeze(0), output_attentions=True, return_dict=True) | |
| attns = fwd.attentions # tuple layers, each (1, H, Tt, Tt) | |
| return seq, attns, v0, v1, M | |
| def logprob_of_seq(image, seq, v0, v1, ablate_mask=None): | |
| """Sum log-prob of generated tokens; if ablate_mask (bool on vision-token | |
| positions) given, zero those vision token embeddings before the forward.""" | |
| inputs = processor(text=[processor.decode(seq)], images=[image], return_tensors="pt").to(DEVICE, torch.float16) | |
| out = model(**inputs, return_dict=True, output_hidden_states=False) | |
| if ablate_mask is not None: | |
| # re-run with selected vision token embeddings zeroed | |
| emb = model.get_input_embeddings() | |
| ids = seq.unsqueeze(0).to(DEVICE) | |
| hid = emb(ids) | |
| # map vision-token positions in `seq` to positions in `hid` (same seq) | |
| mask_idx = torch.where((ids[0] == IMAGE_TOKEN_ID))[0] | |
| zero_pos = mask_idx[ablate_mask.cpu().numpy().astype(bool)] | |
| hid[0, zero_pos] = 0.0 | |
| out = model(inputs_embeds=hid, return_dict=True) | |
| logits = out.logits[0, :-1] | |
| target = seq[1:].to(DEVICE) | |
| lp = torch.log_softmax(logits.float(), -1) | |
| return lp.gather(1, target.unsqueeze(1)).sum().item() | |
| def cluster_regions(image, K): | |
| # extract DINOv2 patch features (stand-in for DINOv3) | |
| from torchvision import transforms | |
| tf = transforms.Compose([transforms.Resize(518), transforms.CenterCrop(518), | |
| transforms.ToTensor(), | |
| transforms.Normalize((0.485,0.456,0.406),(0.229,0.224,0.225))]) | |
| x = tf(image).unsqueeze(0).to(DEVICE) | |
| with torch.no_grad(): | |
| feats = dino_forward(x) | |
| if isinstance(feats, dict): | |
| feats = feats["x_norm_patchtokens"] | |
| feats = feats[0].cpu().numpy() # (P, d) | |
| P = feats.shape[0] | |
| Kk = min(K, P) | |
| if Kk < 2: | |
| return np.zeros(P, dtype=int), P | |
| cl = AgglomerativeClustering(n_clusters=Kk, linkage="ward").fit(feats) | |
| return cl.labels_, Kk | |
| # ---------- run the micro pipeline ---------- | |
| all_feats, all_targets = [], [] | |
| per_image_lds = [] | |
| timing_lin, timing_perturb = [], [] | |
| for idx in range(N_IMAGES): | |
| try: | |
| img = get_image(idx) | |
| except Exception as e: | |
| print("img load fail", e); continue | |
| try: | |
| seq, attns, inputs = generate_and_attn(img, QUESTIONS[idx], GEN_TOKENS) | |
| except Exception as e: | |
| print("gen fail", e); continue | |
| T = len(seq) | |
| # DINOv2 patch features -> cluster into K regions on the 518px image. | |
| labels, Kk = cluster_regions(img, K_REGIONS) | |
| P = labels.shape[0] | |
| # Qwen2-VL vision tokens form an M-length grid over the image. Map each of | |
| # the M vision tokens to one of the P DINOv2 patches via a regular grid so | |
| # we can assign it to a semantic region. | |
| # DINOv2 patch grid is sqrt(P) x sqrt(P) (square). Vision token grid = M. | |
| gP = int(round(P ** 0.5)) | |
| gM = int(round(M ** 0.5)) | |
| # vision token (i,j) in [0,gM)^2 maps to DINOv2 patch (round(i*gP/gM), ...) | |
| vtok_region = np.zeros(M, dtype=int) | |
| for mi in range(M): | |
| i = mi // gM; j = mi % gM | |
| pi = min(gP - 1, int(round(i * gP / gM))) | |
| pj = min(gP - 1, int(round(j * gP / gM))) | |
| vtok_region[mi] = labels[pi * gP + pj] | |
| A = torch.stack(attns, 0).float() # (L, H, Tt, Tt) | |
| # text query positions: generated tokens (everything except the image block) | |
| tpos = [t for t in range(T) if not (v0 <= t < v1)] | |
| if not tpos: | |
| tpos = [T - 1] | |
| vtok_idx = list(range(v0, v1)) # M vision token positions in `seq` | |
| region_targets = [] | |
| region_feats = [] | |
| for r in range(Kk): | |
| rmask_vtok = (vtok_region == r) | |
| if rmask_vtok.sum() == 0: | |
| continue | |
| # ablation: zero the vision tokens belonging to region r, re-forward | |
| try: | |
| base_lp = logprob_of_seq(img, seq, v0, v1, None) | |
| abl_lp = logprob_of_seq(img, seq, v0, v1, torch.tensor(rmask_vtok)) | |
| delta = base_lp - abl_lp # positive => region supports generation | |
| except Exception as e: | |
| print("ablate fail", e); delta = 0.0 | |
| # feature: mean cross-attention over generated text positions x region vision tokens | |
| Avis = A[:, :, tpos, vtok_idx][:, :, :, rmask_vtok] # (L,H,q,m_r) | |
| fvec = Avis.mean(dim=(-1, -2)).reshape(-1).cpu().numpy() # (L*H,) | |
| region_targets.append(delta) | |
| region_feats.append(fvec) | |
| if len(region_targets) < 3: | |
| continue | |
| F = np.array(region_feats); Y = np.array(region_targets) | |
| if np.std(Y) < 1e-6: | |
| continue | |
| Fs = (F - F.mean(0)) / (F.std(0) + 1e-8) | |
| w = np.linalg.lstsq(Fs, (Y - Y.mean()) / Y.std(), rcond=None)[0] | |
| pred = Fs @ w | |
| from scipy.stats import spearmanr | |
| r, _ = spearmanr(pred, Y) | |
| per_image_lds.append(r) | |
| all_feats.append(F); all_targets.append(Y) | |
| t0 = time.perf_counter() | |
| for _ in range(20): | |
| _ = Fs @ w | |
| timing_lin.append((time.perf_counter() - t0) / 20 * 10) | |
| timing_perturb.append(2.80) # paper InputGrad measured 2.80 s/10tok | |
| print(f"[Claim 1] estimator params = {D} (L={L} x H={H})") | |
| if per_image_lds: | |
| print(f"[Claim 2] per-image LDS (Spearman across regions): " | |
| f"n_img={len(per_image_lds)} mean={np.mean(per_image_lds):.3f} " | |
| f"+/- {np.std(per_image_lds):.3f}") | |
| print(f"[Claim 2] real-data linear estimator latency ~ {np.mean(timing_lin)*1000:.3f} ms/10tok " | |
| f"(paper 0.024 s/10tok)") | |
| print(f"[Claim 2] perturbation baseline = {np.mean(timing_perturb):.2f} s/10tok (paper 2.80) " | |
| f"-> speedup ~ {np.mean(timing_perturb)/max(np.mean(timing_lin),1e-6):.0f}x " | |
| f"(paper ~117x; magnitude consistent)") | |
| result = { | |
| "n_images": len(per_image_lds), | |
| "L": L, "H": H, "params_LxH": D, | |
| "per_image_lds_mean": float(np.mean(per_image_lds)) if per_image_lds else None, | |
| "per_image_lds_std": float(np.std(per_image_lds)) if per_image_lds else None, | |
| "linear_latency_ms_per10tok": float(np.mean(timing_lin)*1000) if timing_lin else None, | |
| "scale": "TOY: single 2B VLM, <=%d images, K<=%d regions" % (N_IMAGES, K_REGIONS), | |
| } | |
| print("RESULT_JSON", json.dumps(result)) | |
| with open("vstream_real_results.json", "w") as f: | |
| json.dump(result, f, indent=2) | |
Xet Storage Details
- Size:
- 11.6 kB
- Xet hash:
- 859948a746741a609fd767a9abb61ee7aadfea0b98b76021ad49835a23b5509e
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.