Download examples/tiny_model.py from gexinh/vlm-probe: direct link, hf CLI and curl.
- Browser
- Download file 4.87 kB
-
https://huggingface.co/spaces/gexinh/vlm-probe/resolve/main/examples/tiny_model.py
- Command line
-
hf download hf://spaces/gexinh/vlm-probe/examples/tiny_model.py
-
curl -L -o tiny_model.py https://huggingface.co/spaces/gexinh/vlm-probe/resolve/main/examples/tiny_model.py
4.87 kB
| """A small real attention computation for CPU examples, not a pretrained VLM. | |
| Image-token vectors are supplied explicitly; this fixture has no image encoder. | |
| """ | |
| import math | |
| import torch | |
| from torch import Tensor, nn | |
| class TinyAttention(nn.Module): | |
| def __init__(self, width: int): | |
| super().__init__() | |
| self.query = nn.Linear(width, width, bias=False) | |
| self.key = nn.Linear(width, width, bias=False) | |
| self.value = nn.Linear(width, width, bias=False) | |
| self.scores = nn.Identity() | |
| self.probs = nn.Softmax(dim=-1) | |
| self.output = nn.Linear(width, width, bias=False) | |
| def forward(self, x: Tensor) -> Tensor: | |
| q, k, v = self.query(x), self.key(x), self.value(x) | |
| scores = (q @ k.transpose(-2, -1))[:, None] / math.sqrt(x.shape[-1]) | |
| future = torch.ones(x.shape[1], x.shape[1], dtype=torch.bool, device=x.device).triu(1) | |
| scores = self.scores(scores.masked_fill(future, -torch.inf)) | |
| probs = self.probs(scores) | |
| return self.output((probs @ v[:, None]).squeeze(1)) | |
| class TinyBlock(nn.Module): | |
| def __init__(self, width: int): | |
| super().__init__() | |
| self.attention = TinyAttention(width) | |
| self.mlp = nn.Sequential(nn.Linear(width, width), nn.GELU(), nn.Linear(width, width)) | |
| def forward(self, x: Tensor) -> Tensor: | |
| x = x + self.attention(x) | |
| return x + self.mlp(x) | |
| class TinyModel(nn.Module): | |
| def __init__(self, width: int = 6, vocab_size: int = 12): | |
| super().__init__() | |
| self.embedding = nn.Embedding(vocab_size, width) | |
| self.layers = nn.ModuleList([TinyBlock(width), TinyBlock(width)]) | |
| self.norm = nn.LayerNorm(width) | |
| self.lm_head = nn.Linear(width, vocab_size, bias=False) | |
| def forward(self, input_ids: Tensor, image_tokens: Tensor) -> dict[str, Tensor]: | |
| hidden = torch.cat([image_tokens, self.embedding(input_ids)], dim=1) | |
| for layer in self.layers: | |
| hidden = layer(hidden) | |
| return {"logits": self.lm_head(self.norm(hidden))} | |
| def probing_adapter(self): | |
| """An explicit model protocol: Prober(model) uses this verified contract.""" | |
| from vlm_probing import HeadSite, HookPoint, ModelReadout, ModelSpec, TokenLayout, TorchModelAdapter | |
| sites, residuals, attentions, scores, heads = {}, {}, {}, {}, {} | |
| path_heads, path_qkv = {}, {} | |
| for i in range(len(self.layers)): | |
| residuals[i], attentions[i] = f"residual.{i}", f"attention.{i}" | |
| scores[i], heads[i] = f"scores.{i}", f"heads.{i}" | |
| sites[residuals[i]] = HookPoint(f"layers.{i}") | |
| sites[attentions[i]] = HookPoint(f"layers.{i}.attention.probs") | |
| sites[scores[i]] = HookPoint(f"layers.{i}.attention.scores") | |
| sites[heads[i]] = HookPoint(f"layers.{i}.attention.output") | |
| path_heads[i] = HeadSite(heads[i], 1, self.norm.normalized_shape[0]) | |
| path_qkv[i] = {} | |
| for kind, component in (("q", "query"), ("k", "key"), ("v", "value")): | |
| site = f"{kind}.{i}" | |
| sites[site] = HookPoint(f"layers.{i}.attention.{component}") | |
| path_qkv[i][kind] = HeadSite(site, 1, self.norm.normalized_shape[0]) | |
| sites["embeddings"] = HookPoint("layers.0", kind="input", selector=0) | |
| sites["last_mlp"] = HookPoint(f"layers.{len(self.layers)-1}.mlp") | |
| def layout(inputs): | |
| ids = inputs["input_ids"] | |
| prefix = torch.zeros(inputs["image_tokens"].shape[:2], dtype=ids.dtype, device=ids.device) | |
| expanded = torch.cat([prefix, ids], dim=1) | |
| valid = torch.ones_like(expanded, dtype=torch.bool) | |
| visual = torch.zeros_like(valid) | |
| visual[:, :prefix.shape[1]] = True | |
| return TokenLayout(valid, visual, token_ids=expanded) | |
| def linear_readout(full): | |
| scale = (full.var(-1, keepdim=True, unbiased=False) + self.norm.eps).rsqrt() | |
| return self.lm_head.weight * self.norm.weight, scale, True | |
| spec = ModelSpec( | |
| residuals, attentions=attentions, attention_scores=scores, heads=heads, | |
| embeddings="embeddings", input_embeddings=self.embedding.weight, | |
| layout=layout, project_heads=lambda _, value: value.unsqueeze(-2), | |
| linear_readout=linear_readout, editable_attention=set(attentions), | |
| path_heads=path_heads, path_qkv=path_qkv, path_final=residuals[len(self.layers)-1], | |
| edges={"last_attention_to_residual": heads[len(self.layers)-1], | |
| "last_mlp_to_residual": "last_mlp"}, | |
| notes=("Random CPU demonstration model; image embeddings are supplied directly.",), | |
| ) | |
| return TorchModelAdapter(self, sites, readout=ModelReadout(self.lm_head, norm=self.norm), | |
| model_id="tiny-demo", spec=spec) | |