subirmansukhani commited on
Commit
67f6350
Β·
1 Parent(s): bfea990

Rewrite README for clarity: add quick start, streamline structure

Browse files

Reorganize sections with a clearer flow (quick start first, details
later), simplify explanations, tighten copy throughout, and update
the table of contents to match the new structure.

Files changed (1) hide show
  1. README.md +323 -403
README.md CHANGED
@@ -1,192 +1,166 @@
1
  # smolvla-inspect
2
 
3
- See what SmolVLA's vision encoder and action expert are looking at when the model predicts robot actions.
4
 
5
  ![Example attention grid](assets/example_grid.png)
6
- *7-row grid for a pick-and-place episode. Top: original frames, raw heatmaps (self-attn, cross-attn, saliency). Bottom: interpretable overlays β€” self-attention overlay, co-attention (self x cross), and GradCAM showing which image regions causally drive the predicted action.*
7
-
8
- ---
9
-
10
- ## Table of contents
11
-
12
- - [What this does](#what-this-does) β€” attention, gradients, extended attribution, model internals
13
- - [How it works](#how-it-works) β€” pipeline architecture, per-head analysis
14
- - [Setup](#setup) β€” Python, FFmpeg, Node.js, GPU
15
- - [Run](#run) β€” config files, CLI examples
16
- - [Web Viewer](#web-viewer) β€” interactive explorer with LLM analysis
17
- - [CLI reference](#cli-reference) β€” all flags and options
18
- - [Interpreting results](#interpreting-results) β€” what patterns mean
19
- - [Project layout](#project-layout)
20
  - [Roadmap](#roadmap)
21
 
22
- ---
23
-
24
- ## What this does
25
-
26
- SmolVLA is a **vision-language-action** policy: it takes camera images and a language instruction, then outputs robot actions. This tool has three modes:
27
-
28
- 1. **Attention visualization** (default) -- extracts and visualizes attention heatmaps showing where the model looks
29
- 2. **Gradient-based attribution** (`--gradient`) -- computes saliency maps and GradCAM to show which pixels *causally influence* the predicted action
30
- 3. **Model internals report** (`--internals-only`) -- runs spectral analysis, attention entropy, and head redundancy checks across all model components
31
-
32
- ### Attention visualization
33
-
34
- Extracts attention maps from two places:
35
-
36
- 1. **SigLIP vision encoder** (self-attention) -- which image patches the encoder considers important during feature extraction
37
- 2. **Action expert** (cross-attention) -- which image regions the action decoder actually reads when predicting actions
38
-
39
- That lets you check whether the model attends to task-relevant regions (gripper, object, goal) or background (walls, table texture) -- useful for debugging overfitting or distribution shift.
40
-
41
- **Input:** A pretrained or fine-tuned SmolVLA policy + a LeRobot dataset (e.g. episodes of pick-and-place).
42
- **Output:** A multi-row grid PNG per episode, optional per-frame PNGs, an optional per-head attention grid, and a positional baseline diagnostic (`positional_baseline.png`) showing the position-dependent attention pattern that gets subtracted.
43
 
44
- ### Gradient-based attribution
45
 
46
- Attention maps show where the model *allocates compute*, but not whether those regions actually *drive the output*. Gradient attribution answers that question by backpropagating from the predicted action to the input pixels:
47
-
48
- 1. **Saliency** (`--gradient saliency`) -- computes `|d(action) / d(pixel)|` at full pixel resolution. Highlights the raw input regions whose changes most affect the predicted action.
49
- 2. **GradCAM** (`--gradient gradcam`) -- hooks the last SigLIP encoder layer, weights activations by their gradient, and produces a patch-resolution heatmap. Shows which high-level visual features drive the action.
50
- 3. **Both** (`--gradient` or `--gradient both`) -- runs both methods and adds both rows to the output grid.
51
-
52
- **SmoothGrad** (`--smooth-grad N`) averages saliency over N noisy copies of the input, producing cleaner maps. Set `--smooth-grad-sigma` to control the noise level (default: 0.15).
53
-
54
- Since gradient computation requires `.backward()` through the full model (~176 transformer layer passes), it is slower than attention-only mode. MPS backward support is limited for some ops, so you can run attention on MPS and gradients on CPU with `--gradient-device cpu` (see [Split device execution](#split-device-execution)).
55
-
56
- **Output:** Up to 2 additional rows in the grid PNG -- saliency overlay (inferno colormap) and GradCAM overlay (magma colormap).
57
-
58
- ### Extended attribution
59
 
60
- Beyond basic saliency and GradCAM, six extended attribution features provide deeper analysis. All are enabled in `configs/gpu.yaml` by default:
61
 
62
- | Feature | Flag | Output | Description |
63
- |---------|------|--------|-------------|
64
- | Per-step cross-attention | `--per-step-cross-attention` | `per_step_cross_attn_ep*.png` | Cross-attention heatmap at each denoising step, showing how the action expert's visual focus evolves during action generation |
65
- | Connector GradCAM | `--gradcam-connector` | Extra row in main grid | GradCAM on the VLM connector output (post-pixel-shuffle, 8x8 token resolution) |
66
- | VLM layer GradCAM | `--gradcam-vlm-layers [4,8,12,16]` | `vlm_layers_ep*.png` | GradCAM at specific VLM intermediate layers, showing how visual features are transformed through the language model |
67
- | Vision vs. state | `--vision-vs-state` | `vision_vs_state_ep*.txt` | Compares gradient norms for vision vs. proprioceptive state inputs -- answers "does the model rely more on what it sees or what it feels?" |
68
- | Per-action-dim GradCAM | `--per-action-dim` | `per_action_dim_ep*.png` | Separate GradCAM per action dimension (shoulder_pan, gripper, etc.) -- shows which image regions drive each joint. Uses `retain_graph`, GPU recommended |
69
- | Language-conditional diff | `--language-diff [alt_task]` | `language_diff_ep*.png` + extra row in main grid | Compares GradCAM between two task instructions to show how language changes the model's visual attention |
70
 
71
- ### Model internals report
72
 
73
- Runs three diagnostic checks across all model components (SigLIP vision encoder, VLM text model, action expert, connector, and projection heads):
 
 
 
74
 
75
- 1. **Weight spectral analysis** -- fits a power-law to singular values of each weight matrix using WeightWatcher. The alpha exponent indicates training quality (2-4 = healthy, >6 = severely undertrained).
76
- 2. **Attention entropy** -- measures how focused or diffuse each attention head is across three attention operations: SigLIP self-attention, VLM+Expert joint self-attention, and Expert-to-VLM cross-attention.
77
- 3. **Head redundancy** -- measures pairwise cosine similarity between attention heads within each layer. High similarity means wasted capacity.
78
 
79
- Use `--internals-only` for the report by itself, or `--with-internals` to add it to a normal attention/gradient run.
 
 
80
 
81
- **Output:** Terminal report, markdown report (`model_internals_report.md`), and a 3-panel plot (`model_internals_report.png`).
82
 
83
- ![Example model internals report](assets/example_model_internals_report.png)
84
- *Example 3-panel model internals report: spectral alpha distribution, attention entropy by layer, and head redundancy matrix. See the full [markdown report](assets/example_model_internals_report.md) for per-layer details.*
 
85
 
86
- For a detailed visual walkthrough of the architecture and how it maps to the report, see **[Architecture Diagrams](assets/architecture.md)**.
87
 
88
- ---
 
 
89
 
90
- ## How it works
91
 
92
- ![Architecture and attention-to-heatmap pipeline](assets/how_it_works_architecture.png)
93
- *Left: Where the vision encoder lives and where we hook to capture attention. Right: How attention weights become a spatial heatmap.*
 
 
94
 
95
- ### Pipeline
96
 
97
- 1. **Load model and dataset** -- loads a SmolVLA policy (e.g. `lerobot/smolvla_base`) and a LeRobot dataset. The dataset provides image sequences; the script uses images as input and runs inference.
98
 
99
- 2. **Capture self-attention from SigLIP** -- the vision encoder (SigLIP ViT, 12 layers, 12 heads) splits each image into patches (32x32 grid for 512px images with 16px patches) and runs self-attention. Forward hooks on the attention layers capture the weight matrices.
100
 
101
- 3. **Aggregate across layers** (`--method`):
102
- - `last-layer` -- uses only the final encoder layer
103
- - `rollout` -- multiplies attention across all layers with residual connections, giving a more complete picture of information flow
104
- - `all-layers` -- keeps each layer separately
 
 
105
 
106
- 4. **Capture cross-attention** (`--cross-attention`) -- SmolVLA's VLM builds a KV cache from the prefix (vision + language + state tokens). The action expert queries that cache. The script monkey-patches `eager_attention_forward()` on the expert layers to intercept the softmax attention when expert Q attends to prefix K. Only columns corresponding to vision tokens are kept, giving a heatmap of which image regions the action decoder reads.
107
 
108
- 5. **Turn attention into spatial heatmaps** -- patch-level importance scores are reshaped into a 2D grid, upsampled with bilinear interpolation to image size, and normalized to [0, 1]. A percentile threshold (`--attn-threshold`, default 0.5) then zeros out low-attention values to suppress residual positional noise from SigLIP's learned position embeddings, and re-normalizes the remainder.
 
 
 
109
 
 
110
 
111
- 6. **Visualize** -- the main output grid has dynamic rows depending on which features are enabled:
112
 
113
- | Row | Content | Colormap | When shown |
114
- |-----|---------|----------|------------|
115
- | 1 | Original frame | -- | always |
116
- | 2 | SigLIP self-attention heatmap | jet (blue-to-red) | always |
117
- | 3 | Action cross-attention heatmap | Greens | `--cross-attention` |
118
- | 4 | Saliency / SmoothGrad overlay | inferno | `--gradient saliency` or `both` |
119
- | 5 | Self-attention overlay on frame | jet | always |
120
- | 6 | Co-attention overlay (self x cross) | cyan (black-cyan-white) | `--cross-attention` |
121
- | 7 | GradCAM overlay (SigLIP last layer) | magma | `--gradient gradcam` or `both` |
122
- | 8 | GradCAM overlay (Connector) | magma | `--gradcam-connector` |
123
- | 9 | Language-conditional diff | RdBu_r (diverging red-blue) | `--language-diff` |
124
 
125
- Raw heatmaps are at the top, interpretable overlays at the bottom. The bottom rows are the most useful: self-attention overlay shows where the encoder focuses, co-attention shows where encoder and action decoder agree, and GradCAM shows which regions causally drive the predicted action.
126
 
127
- Additional feature-specific output files are saved alongside the main grid (see [Extended attribution](#extended-attribution)).
 
128
 
129
- ### Per-head grid
130
 
131
- With `--show-heads`, a separate grid shows each of the 12 SigLIP attention heads individually for the first frame. Each head has its per-head positional baseline subtracted (computed from a gray-image forward pass) so the patterns reflect content-dependent attention rather than position artifacts.
132
 
133
- ![Per-head attention grid](assets/example_per_head.png)
134
- *Each subplot is one attention head. Look for specialization -- e.g. one head tracking the gripper, another tracking the object.*
135
 
136
- ---
 
 
137
 
138
- ## Setup
139
 
140
- **Requirements:** Python 3.10+, and FFmpeg 4-7 for video decoding (LeRobot uses TorchCodec). On macOS, Homebrew's default `ffmpeg` is often v8; install FFmpeg 6:
141
 
142
  ```bash
143
  brew install ffmpeg@6
144
  ```
145
 
146
- Then:
147
 
148
  ```bash
149
- cd smolvla-inspect
150
- python3.11 -m venv .venv && source .venv/bin/activate
151
  pip install -r requirements.txt
152
  ```
153
 
154
- The web viewer also requires **Node.js β‰₯ 20.19** (for `npm` and Vite):
155
 
156
- - **macOS:** `brew install node`
157
- - **Ubuntu:** The default `apt install nodejs` often gives an older version (Node 12-16). Use the [NodeSource](https://github.com/nodesource/distributions) repo instead:
158
- ```bash
159
- curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
160
- sudo apt-get install -y nodejs
161
- ```
162
- Or use [nvm](https://github.com/nvm-sh/nvm) if you manage multiple Node versions. `setup-gpu.sh` handles this automatically.
163
 
164
- ### GPU setup (Ubuntu + CUDA)
165
 
166
- For an Ubuntu machine with an NVIDIA GPU (e.g. RTX 3080), use the setup script:
167
 
168
  ```bash
169
- # Clone and set up in one step:
170
  chmod +x clone-and-setup.sh && ./clone-and-setup.sh
 
 
 
171
 
172
- # Or if already cloned:
173
  chmod +x setup-gpu.sh && ./setup-gpu.sh
174
  ```
175
 
176
- `setup-gpu.sh` installs PyTorch with CUDA 12.1 support, creates a virtualenv, installs dependencies, and verifies GPU access. It automatically checks out the `extended-attribution` branch.
 
 
177
 
178
- ---
179
 
180
  ## Run
181
 
182
- Use `run.sh` so TorchCodec finds FFmpeg 6's libs:
183
 
184
  ```bash
185
  source .venv/bin/activate
186
  ./run.sh
187
  ```
188
 
189
- Or set the library path yourself:
190
 
191
  ```bash
192
  export DYLD_LIBRARY_PATH="/opt/homebrew/opt/ffmpeg@6/lib:$DYLD_LIBRARY_PATH"
@@ -195,408 +169,354 @@ python inspect_attention.py
195
 
196
  ### Config files
197
 
198
- Defaults are loaded from `configs/defaults.yaml`. Use `--config` to load a different config file -- CLI flags still override config values:
 
 
 
 
 
 
 
199
 
200
  ```bash
201
- # Use GPU-optimized config (runs all features)
202
  ./run.sh --config configs/gpu.yaml
203
-
204
- # Override a single value from the config
205
  ./run.sh --config configs/gpu.yaml --episode 3
206
  ```
207
 
208
- Two configs are provided:
209
 
210
- | Config | Description |
211
- |--------|-------------|
212
- | `configs/defaults.yaml` | Conservative defaults: attention + cross-attention, no gradients, CPU-friendly |
213
- | `configs/gpu.yaml` | Enables everything: gradient both, SmoothGrad N=20, connector GradCAM, per-step cross-attention, VLM layer GradCAM, vision vs state, per-action-dim, language diff. Targets CUDA. |
214
-
215
- ### Examples
216
 
217
  ```bash
218
- # Default: rollout aggregation + cross-attention + per-head grid
219
  ./run.sh
220
-
221
- # Use GPU config (all features enabled)
222
- ./run.sh --config configs/gpu.yaml
223
-
224
- # Your fine-tuned model
225
  ./run.sh --model path/to/finetuned_checkpoint --dataset path/to/dataset
226
-
227
- # More frames, specific episode
228
  ./run.sh --episode 3 --num-frames 12
229
-
230
- # Override the language instruction (useful for multi-task datasets)
231
  ./run.sh --task "pick up the red cube"
232
-
233
- # Last-layer method instead of rollout
234
  ./run.sh --method last-layer
235
-
236
- # Skip cross-attention capture (faster, omits rows 4-5)
237
- ./run.sh --no-cross-attention
238
-
239
- # Skip per-head attention grid
240
- ./run.sh --no-show-heads
241
-
242
- # Raw attention without positional baseline subtraction
243
  ./run.sh --raw-attention
244
-
245
- # Higher threshold to suppress more positional noise (default 0.5)
246
  ./run.sh --attn-threshold 0.7
247
-
248
- # No threshold (show all baseline-subtracted values)
249
  ./run.sh --attn-threshold 0
 
250
 
251
- # Gradient-based attribution: saliency + GradCAM
252
- ./run.sh --gradient
253
 
254
- # Saliency only
 
255
  ./run.sh --gradient saliency
256
-
257
- # SmoothGrad with 20 samples for cleaner saliency maps
258
  ./run.sh --gradient saliency --smooth-grad 20
259
-
260
- # Attention on MPS, gradients on CPU (avoids MPS backward issues)
261
  ./run.sh --device mps --gradient both --gradient-device cpu
 
262
 
263
- # Extended: per-step cross-attention evolution
264
- ./run.sh --cross-attention --per-step-cross-attention
265
 
266
- # Extended: GradCAM at connector and VLM layers
 
267
  ./run.sh --gradient gradcam --gradcam-connector --gradcam-vlm-layers 4,8,12,16
268
-
269
- # Extended: vision vs state attribution
270
  ./run.sh --gradient gradcam --vision-vs-state
271
-
272
- # Extended: per-action-dim GradCAM (GPU recommended)
273
  ./run.sh --gradient gradcam --per-action-dim
274
-
275
- # Extended: compare attention between two task instructions
276
  ./run.sh --language-diff "pick up the blue cube"
 
277
 
278
- # Model internals report only (spectral analysis + entropy + redundancy)
279
- ./run.sh --internals-only
280
 
281
- # Add model internals to a standard run
 
282
  ./run.sh --with-internals
283
-
284
- # More sample frames for stable internals estimates
285
  ./run.sh --internals-only --internals-frames 10
286
-
287
- # Custom thresholds for internals warnings
288
  ./run.sh --internals-only --entropy-warn 0.85 --redundancy-warn 0.75
289
-
290
- # Explicit device override (auto-detected by default: mps > cuda > cpu)
291
- ./run.sh --device cuda
292
  ```
293
 
294
- Results land in `outputs/`.
 
 
295
 
296
- ---
 
 
 
 
 
 
 
 
 
297
 
298
  ## Web Viewer
299
 
300
- The web viewer lets you explore runs interactively β€” browse frames, switch between visualization types, compare two runs side by side, and read LLM-generated analysis of the model's attention behavior.
 
 
 
 
 
 
301
 
302
  ![Main visualization view](assets/web_viewer_main.png)
303
- *Browsing saliency maps across 8 frames of a pick-and-place episode. The left sidebar shows all available runs and visualization types.*
304
 
305
  ![Run Insights with LLM analysis](assets/web_viewer_insights.png)
306
- *Run Insights view: summary statistics across all visualization types, followed by LLM-generated analysis of visual grounding, information flow, and key findings.*
307
 
308
  ![Compare Runs](assets/web_viewer_compare.png)
309
- *Compare two runs (e.g. base model vs. fine-tuned) side by side for any visualization type.*
310
-
311
- ### Launch (development)
312
 
313
- The quickest way to start both backend and frontend together:
314
 
315
  ```bash
316
  source .venv/bin/activate
317
  ./start_servers.sh
318
- # or with a custom outputs folder:
319
  ./start_servers.sh --base-dir ./my_outputs
320
  ```
321
 
322
  This starts:
323
- - **Backend** (FastAPI) at `http://localhost:8080`
324
- - **Frontend** (Vite dev server) at `http://localhost:5173`
325
 
326
- ### Launch (production β€” built frontend)
 
327
 
328
- Build the frontend once, then serve everything from a single server:
 
 
329
 
330
  ```bash
331
- cd web/frontend && npm install && npm run build && cd ../..
 
 
 
332
  python inspect_attention.py serve --port 8080 --base-dir ./outputs
333
  ```
334
 
335
- The built frontend is served statically by FastAPI β€” no separate frontend process needed.
336
-
337
- ### LLM analysis setup
338
 
339
- The Run Insights view uses an LLM to analyze attention patterns. Set one of these environment variables before launching:
340
 
341
  ```bash
342
- # Anthropic (Claude)
343
  export ANTHROPIC_API_KEY=sk-ant-...
344
-
345
- # OpenAI (GPT-4o)
346
  export OPENAI_API_KEY=sk-...
347
  ```
348
 
349
- Click **LLM configured** in the top-right corner to choose the model and customize the analysis prompt.
350
-
351
  ### `serve` flags
352
 
353
  | Flag | Default | Description |
354
  |------|---------|-------------|
355
  | `--port` | `8080` | Server port |
356
  | `--host` | `0.0.0.0` | Server host |
357
- | `--base-dir` | `./outputs` | Root folder to scan for run folders |
358
- | `--no-open` | off | Don't auto-open browser |
359
 
360
- ---
361
 
362
- ## CLI reference
 
363
 
364
- **General:**
365
 
366
- | Flag | Default | Description |
367
- |------|---------|-------------|
368
- | `--config` | `configs/defaults.yaml` | Path to YAML config file (CLI flags override config values) |
369
- | `--model` | `lerobot/smolvla_base` | HuggingFace model ID or local path |
370
- | `--dataset` | `lerobot/svla_so101_pickplace` | LeRobot dataset ID or local path |
371
- | `--episode` | `0` | Episode index to visualize |
372
- | `--num-frames` | `8` | Number of frames to sample |
373
- | `--image-key` | auto-detected | Dataset image key override |
374
- | `--task` | from dataset | Override the language instruction |
375
- | `--output-dir` | `./outputs` | Output directory |
376
- | `--device` | `auto` | `auto`, `cpu`, `cuda`, or `mps` |
377
- | `--save-individual` | `true` | Save each frame as a separate PNG |
 
 
 
 
 
 
 
 
378
 
379
- **Attention visualization:**
380
 
381
- | Flag | Default | Description |
382
- |------|---------|-------------|
383
- | `--method` | `rollout` | `last-layer`, `rollout`, or `all-layers` |
384
- | `--cross-attention` | `true` | Capture action-expert cross-attention |
385
- | `--show-heads` | `true` | Save per-head attention grid for first frame |
386
- | `--raw-attention` | `false` | Skip positional baseline subtraction |
387
- | `--attn-threshold` | `0.5` | Percentile (0-1) below which attention values are zeroed to suppress positional noise |
388
 
389
- **Gradient-based attribution:**
 
390
 
391
- | Flag | Default | Description |
392
- |------|---------|-------------|
393
- | `--gradient` | off | `saliency`, `gradcam`, or `both` (bare `--gradient` means `both`) |
394
- | `--gradient-device` | same as `--device` | Device for gradient computation (`cpu`, `cuda`, `mps`) |
395
- | `--gradient-seed` | `42` | Fixed noise seed for reproducible gradient attribution |
396
- | `--smooth-grad` | `1` | SmoothGrad samples (1 = vanilla saliency, >1 = averaged over N noisy inputs) |
397
- | `--smooth-grad-sigma` | `0.15` | Gaussian noise std for SmoothGrad |
398
 
399
- **Extended attribution:**
400
 
401
- | Flag | Default | Description |
402
- |------|---------|-------------|
403
- | `--per-step-cross-attention` | `false` | Visualize cross-attention at each denoising step (auto-enables `--cross-attention`) |
404
- | `--gradcam-connector` | `false` | GradCAM on VLM connector output (post-pixel-shuffle) |
405
- | `--gradcam-vlm-layers` | off | GradCAM on VLM intermediate layers (comma-separated 1-indexed, e.g. `4,8,12,16`) |
406
- | `--vision-vs-state` | `false` | Compare gradient attribution between vision and state inputs |
407
- | `--per-action-dim` | `false` | Per-action-dimension GradCAM (uses `retain_graph` -- GPU recommended) |
408
- | `--language-diff` | off | Language-conditional comparison (`auto` to auto-generate alt task, or provide a string) |
409
 
410
- **Model internals report:**
411
 
412
- | Flag | Default | Description |
413
- |------|---------|-------------|
414
- | `--internals-only` | `false` | Run the model internals report instead of attention or gradient analysis |
415
- | `--with-internals` | `false` | Also run the model internals report after the standard analysis |
416
- | `--internals-frames` | `5` | Number of sample frames for entropy/redundancy |
417
- | `--entropy-warn` | `0.8` | Entropy ratio threshold for "unfocused" warning |
418
- | `--entropy-critical` | `0.95` | Entropy ratio threshold for "dead" heads |
419
- | `--entropy-low` | `0.1` | Entropy ratio threshold for "collapsed" heads |
420
- | `--redundancy-warn` | `0.7` | Cosine similarity threshold for "high redundancy" |
421
- | `--redundancy-critical` | `0.9` | Cosine similarity threshold for "collapsed" heads |
422
 
423
- ---
424
 
425
- ## Interpreting results
 
 
 
 
 
426
 
427
- ### Self-attention (SigLIP vision encoder -- rows 2-3)
428
 
429
- | Attention pattern | Interpretation |
430
- |-------------------|----------------|
431
- | Bright on gripper + object + goal | **Healthy** -- model attends to task-relevant regions |
432
- | Bright on shelves, cables, table grain | **Background overfitting** -- model may be using scene cues |
433
- | Uniform / diffuse everywhere | Model may not have learned focused visual features yet |
434
- | Shifts from background to object across frames | Model is tracking the task over time (good sign) |
 
 
435
 
436
- ### Cross-attention (action expert -- rows 4-5)
437
 
438
- | Attention pattern | Interpretation |
439
- |-------------------|----------------|
440
- | Tight focus on gripper tip + target object | **Healthy** -- action decoder reads exactly what it needs |
441
- | Diffuse across all vision tokens | Decoder hasn't specialized; may predict generic actions |
442
- | Self-attn diffuse but cross-attn focused | Decoder learned to select useful tokens despite a noisy encoder |
443
- | Self-attn focused but cross-attn diffuse | Encoder features are good but the decoder doesn't exploit them |
444
 
445
- ### Co-attention (row 5)
446
 
447
- The cyan overlay highlights regions where **both** the vision encoder and the action expert agree something is important. Bright cyan = high self-attention AND high cross-attention. This is the strongest signal for task-relevant regions.
 
 
 
 
448
 
449
- ### Gradient attribution (saliency + GradCAM rows)
450
 
451
- | Pattern | Interpretation |
452
- |---------|----------------|
453
- | Saliency highlights gripper/object edges | **Healthy** -- action predictions are driven by task-relevant pixel changes |
454
- | GradCAM highlights same regions as self-attention | Attention and causal influence agree -- strong signal |
455
- | Attention focused but saliency diffuse | Attention points at the right place, but the action doesn't depend on it (possible shortcut) |
456
- | Saliency focused on unexpected region (e.g. table edge) | Model may be using a visual shortcut rather than task understanding |
457
- | GradCAM and saliency disagree | GradCAM operates at patch level while saliency is pixel-level -- some divergence is normal, but large disagreement warrants investigation |
458
 
459
- ### Extended attribution
460
 
461
- | Feature | What to look for |
462
- |---------|-----------------|
463
- | Per-step cross-attention | Focus should sharpen over denoising steps. If attention is static across steps, the denoising process isn't refining its visual queries. |
464
- | Connector GradCAM | Should roughly agree with SigLIP GradCAM but at coarser resolution (8x8 vs 32x32). Large disagreement means the connector is transforming features significantly. |
465
- | VLM layer GradCAM | Early layers (4) should show broad/low-level features, later layers (12, 16) should show task-specific focus. If all layers look the same, the VLM isn't progressively refining visual information. |
466
- | Vision vs. state | A healthy manipulation policy typically shows 60-90% vision. Near-100% vision or near-0% vision suggests the model ignores one modality. |
467
- | Per-action-dim | Different joints should attend to different regions -- e.g. shoulder_pan attends to object position, gripper attends to object shape/edges. If all dims look identical, the model uses a single visual representation for all actions. |
468
- | Language diff | Changing the task instruction should shift attention to the referenced object. If attention doesn't change, the model may not be grounding language in vision. |
469
-
470
- ### Grid row reference
471
-
472
- | Row | Name | Question it answers | Resolution | Interpretation of hot spots |
473
- |-----|------|---------------------|------------|----------------------------|
474
- | SigLIP self-attn | Vision encoder attention | Which patches attend to each other inside the encoder? | 32x32 patches | Encoder's internal processing focus -- structural, not necessarily action-relevant |
475
- | Action cross-attn | Action-to-vision cross-attention | Which vision tokens does the action decoder query? | 8x8 tokens (post pixel-shuffle) | Visual regions the action decoder pulls information from |
476
- | Saliency \|dA/dpx\| | Input-gradient saliency | If I changed this pixel, would the action change? | Full pixel (480x640) | Pixels that causally influence the predicted action -- fine-grained but noisy |
477
- | Self-attn overlay | Self-attention on frame | Where does the encoder focus, overlaid on the image? | 32x32 upsampled | Visual check of encoder focus against actual scene content |
478
- | Co-attention | Self x cross product | Which regions are both visually salient and action-queried? | 8x8 upsampled | Strongest attention signal for "what the model looks at to decide what to do" |
479
- | GradCAM SigLIP L-1 | Gradient-weighted activations | Which learned feature regions drive the action? | 32x32 patches | Patch regions whose features most influence the action -- coarser but more semantic |
480
- | GradCAM Connector | Connector gradient-weighted activations | Which post-connector tokens drive the action? | 8x8 tokens | How the pixel-shuffle connector redistributes visual importance |
481
- | Lang-cond diff | GradCAM difference between two task instructions | Does changing the instruction change where the model looks? | 32x32 patches | Red = more attention with original task, blue = more with alternative |
482
-
483
- **Attention vs gradient:**
484
-
485
- | | Attention rows | Gradient rows |
486
- |--|----------------|---------------|
487
- | Measures | Correlation ("model looked here") | Causation ("changing here changes the action") |
488
- | High attn + low gradient | Model looks but doesn't use it for action | -- |
489
- | Low attn + high gradient | -- | Region subtly influences output without dominating attention |
490
- | Both high | -- | Strong evidence this region genuinely drives behavior |
491
-
492
- ### Split device execution
493
-
494
- Gradient computation requires `.backward()` through the full model, which can fail or be slow on MPS. Use `--gradient-device cpu` to run attention on MPS (fast, forward-only) and gradients on CPU (backward-compatible):
495
 
496
  ```bash
497
  ./run.sh --device mps --gradient both --gradient-device cpu
498
  ```
499
 
500
- The model is moved to the gradient device after attention extraction finishes. Since gradients run last, there is no need to move it back.
501
-
502
- ### Per-head patterns
503
 
504
- Look for heads that specialize: one head tracking the gripper, another tracking the object, another attending globally. Specialization is a sign of a well-trained encoder. Heads that all look identical suggest the model hasn't learned diverse attention strategies.
505
-
506
- ### Model internals report
507
-
508
- | Metric | Healthy | Warning | Critical |
509
- |--------|---------|---------|----------|
510
- | Spectral alpha | 2-4 | 4-6 (undertrained) | >6 (severely undertrained) or <2 (overcorrelated) |
511
- | Attention entropy | 0.10-0.80 | >0.80 (unfocused) | >0.95 (dead) or <0.10 (collapsed) |
512
- | Head redundancy | <0.70 (diverse) | >0.70 (redundant) | >0.90 (collapsed) |
513
 
514
- The report covers three attention components mapped to distinct operations in the architecture:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
515
 
516
- | Report component | Architecture operation | When it runs |
517
- |-----------------|----------------------|-------------|
518
- | SigLIP Vision (12L, 12H) | Self-attention inside the vision encoder | Image encoding |
519
- | VLM+Expert Joint Self-Attn (16L, 15H) | VLM and Expert tokens concatenated, attend to each other | Prefill (initial encoding) |
520
- | Expert-to-VLM Cross-Attn (16L, 8H) | Expert queries VLM's cached keys/values | Generation (action decoding, 10 steps) |
521
 
522
- See **[Architecture Diagrams](assets/architecture.md)** for visual explanations of each component.
 
 
 
 
 
 
 
523
 
524
- ---
525
 
526
- ## Project layout
 
 
 
 
 
 
 
 
 
 
 
 
 
 
527
 
528
- ```
 
 
 
 
 
 
 
 
 
 
 
 
 
529
  smolvla-inspect/
530
- β”œβ”€β”€ inspect_attention.py # Thin entry point (delegates to smolvla_inspect)
531
- β”œβ”€β”€ smolvla_inspect/ # Main package
532
- β”‚ β”œβ”€β”€ __init__.py
533
- β”‚ β”œβ”€β”€ cli.py # CLI args, orchestration, attention extraction
534
- β”‚ β”œβ”€β”€ capture.py # Attention hook classes (SigLIP, cross-attention)
535
- β”‚ β”œβ”€β”€ heatmap.py # Patch scores, rollout, positional baseline, upsampling
536
- β”‚ β”œβ”€β”€ gradient.py # Gradient attribution (saliency, GradCAM, extended features)
537
- β”‚ β”œβ”€β”€ data.py # Dataset helpers, batch building, image key mapping
538
- β”‚ β”œβ”€β”€ viz.py # Visualization grid, overlays, per-head grids
539
- β”‚ β”œβ”€β”€ internals.py # Model internals report (spectral, entropy, redundancy)
540
- β”‚ β”œβ”€β”€ serve.py # `smolvla-inspect serve` subcommand launcher
541
- β”‚ └── _compat.py # Resize/pad compatibility helpers
542
  β”œβ”€β”€ web/
543
- β”‚ β”œβ”€β”€ backend/ # FastAPI backend
544
- β”‚ β”‚ β”œβ”€β”€ main.py # App factory, CORS, static file serving
545
- β”‚ β”‚ β”œβ”€β”€ config.py # Settings (base_dir, host, port, CORS origins)
546
- β”‚ β”‚ β”œβ”€β”€ routers/ # API routes (runs, visualizations, internals, compare, LLM, notes)
547
- β”‚ β”‚ β”œβ”€β”€ services/ # Business logic (run scanner, image loader)
548
- β”‚ β”‚ └── models/ # Pydantic request/response schemas
549
- β”‚ └── frontend/ # React + Vite + TypeScript frontend
550
- β”‚ β”œβ”€β”€ src/
551
- β”‚ β”‚ β”œβ”€β”€ App.tsx
552
- β”‚ β”‚ β”œβ”€β”€ components/ # UI components (RunSelector, HeatmapCanvas, LLMPanel, ...)
553
- β”‚ β”‚ β”œβ”€β”€ stores/ # Zustand state stores
554
- β”‚ β”‚ β”œβ”€β”€ services/ # API client
555
- β”‚ β”‚ └── hooks/
556
- β”‚ └── package.json
557
  β”œβ”€β”€ assets/
558
- β”‚ β”œβ”€β”€ architecture.md # Architecture diagrams and report reference
559
- β”‚ β”œβ”€β”€ gradcam_walkthrough.md # GradCAM worked example with numerical walkthrough
560
- β”‚ β”œβ”€β”€ how_it_works_architecture.png
561
- β”‚ β”œβ”€β”€ example_grid.png
562
- β”‚ β”œβ”€β”€ example_per_head.png
563
- β”‚ β”œβ”€β”€ example_model_internals_report.png
564
- β”‚ β”œβ”€β”€ web_viewer_main.png # Web viewer: main visualization view
565
- β”‚ β”œβ”€β”€ web_viewer_insights.png # Web viewer: Run Insights with LLM analysis
566
- β”‚ └── web_viewer_compare.png # Web viewer: Compare Runs view
567
  β”œβ”€β”€ configs/
568
- β”‚ β”œβ”€β”€ defaults.yaml # Conservative defaults (no gradients, CPU-friendly)
569
- β”‚ └── gpu.yaml # GPU config: all features enabled (CUDA, SmoothGrad N=20)
570
- β”œβ”€β”€ scripts/
571
- β”‚ └── gen_architecture_diagram.py # Generates architecture diagram assets
572
  β”œβ”€β”€ docs/
573
- β”‚ β”œβ”€β”€ ELI5.md # Plain-language explanation of the interpretability approach
574
- β”‚ └── TESTING.md # CLI test commands and expected output
575
- β”œβ”€β”€ clone-and-setup.sh # One-command clone + GPU setup
576
- β”œβ”€β”€ setup-gpu.sh # GPU setup (Ubuntu + CUDA, creates venv, installs deps)
577
- β”œβ”€β”€ start_servers.sh # Dev launcher: starts FastAPI backend + Vite frontend together
578
- β”œβ”€β”€ run.sh # CLI wrapper that sets FFmpeg lib path (macOS)
579
  β”œβ”€β”€ requirements.txt
580
- β”œβ”€β”€ outputs/ # Generated images and reports (gitignored)
581
  └── README.md
582
  ```
583
 
584
- ---
585
-
586
  ## Roadmap
587
 
588
- - [x] **Gradient-based attribution** -- vanilla saliency (`|d(action)/d(pixel)|`) and GradCAM on SigLIP's last encoder layer, with split device support (`--gradient-device`) for running gradients on CPU while attention runs on MPS/CUDA
589
- - [x] **SmoothGrad** -- average saliency over N noisy inputs for cleaner maps (`--smooth-grad`)
590
- - [x] **Extended attribution** -- per-step cross-attention, connector GradCAM, VLM layer GradCAM, vision vs state, per-action-dim GradCAM, language-conditional comparison
591
- - [x] **Config file support** -- `--config` flag to load alternate YAML configs (e.g. `configs/gpu.yaml`)
592
- - [x] **Interactive web viewer** -- FastAPI + React frontend with run browser, per-visualization heatmap explorer, side-by-side run comparison, and LLM-powered analysis (`./start_servers.sh` or `python inspect_attention.py serve`)
593
- - [ ] **Occlusion / perturbation sensitivity** -- mask out image regions or zero out specific prefix tokens (vision, language, state) and measure action MSE change; model-agnostic and directly answers "if I cover the gripper, does the model break?"
594
- - [ ] **Representation probing** -- train small linear classifiers on intermediate layer representations to test what information is encoded at each stage (e.g., can layer N predict object position? does the Expert encode gripper state?)
595
- - [ ] **Causal tracing / activation patching** -- replace activations at specific (layer, token) positions with corrupted versions and measure output change; builds a causal map of information flow through the model
596
- - [ ] **Temporal consistency analysis** -- track attention patterns across frames in an episode to check if attention follows the object smoothly, whether cross-attention shifts predict upcoming actions, and correlation between attention movement and action direction
597
-
598
- ---
599
 
600
  ## Note on FFmpeg
601
 
602
- If you installed `ffmpeg@6` and linked it (`brew link --overwrite ffmpeg@6`), your default `ffmpeg` is now 6.x. To switch back later: `brew unlink ffmpeg@6 && brew link ffmpeg`.
 
 
 
 
 
1
  # smolvla-inspect
2
 
3
+ Inspect where a SmolVLA policy looks, what pixels actually drive its actions, and how its internal attention/weight structure behaves.
4
 
5
  ![Example attention grid](assets/example_grid.png)
6
+ *Example inspection grid for a pick-and-place episode. It combines raw attention, overlays, and gradient attribution in one view.*
7
+
8
+ ## Table of Contents
9
+
10
+ - [Quick Start](#quick-start)
11
+ - [What This Tool Does](#what-this-tool-does)
12
+ - [Setup](#setup)
13
+ - [Run](#run)
14
+ - [Web Viewer](#web-viewer)
15
+ - [How It Works](#how-it-works)
16
+ - [Interpreting Results](#interpreting-results)
17
+ - [CLI Reference](#cli-reference)
18
+ - [Project Layout](#project-layout)
 
19
  - [Roadmap](#roadmap)
20
 
21
+ ## Quick Start
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
 
23
+ ### 1. Install dependencies
24
 
25
+ ```bash
26
+ python3.11 -m venv .venv
27
+ source .venv/bin/activate
28
+ pip install -r requirements.txt
29
+ ```
 
 
 
 
 
 
 
 
30
 
31
+ On macOS, install FFmpeg 6 first:
32
 
33
+ ```bash
34
+ brew install ffmpeg@6
35
+ ```
 
 
 
 
 
36
 
37
+ ### 2. Run a standard inspection
38
 
39
+ ```bash
40
+ source .venv/bin/activate
41
+ ./run.sh
42
+ ```
43
 
44
+ ### 3. Run gradients and the web-viewer-friendly export
 
 
45
 
46
+ ```bash
47
+ ./run.sh --config configs/gpu.yaml
48
+ ```
49
 
50
+ ### 4. Run the model internals report
51
 
52
+ ```bash
53
+ ./run.sh --internals-only
54
+ ```
55
 
56
+ Or append it to a normal run:
57
 
58
+ ```bash
59
+ ./run.sh --with-internals
60
+ ```
61
 
62
+ ### 5. Launch the web viewer
63
 
64
+ ```bash
65
+ source .venv/bin/activate
66
+ ./start_servers.sh
67
+ ```
68
 
69
+ Results are written to `outputs/` by default.
70
 
71
+ ## What This Tool Does
72
 
73
+ SmolVLA is a vision-language-action policy: it takes camera images and a language instruction, then predicts robot actions. This repository gives you four practical ways to inspect that behavior:
74
 
75
+ | Capability | Primary flags | What it answers | Main outputs |
76
+ |------------|---------------|-----------------|--------------|
77
+ | Attention visualization | default, `--cross-attention`, `--show-heads` | Where does the encoder or action decoder focus? | `episode_dashboard_ep*.png`, `per_head_ep*.png` |
78
+ | Gradient attribution | `--gradient` | Which pixels causally affect the predicted action? | extra rows in the dashboard |
79
+ | Extended attribution | `--gradcam-connector`, `--gradcam-vlm-layers`, `--vision-vs-state`, `--per-action-dim`, `--language-diff` | How information moves through the connector, VLM, and action heads | feature-specific PNGs / reports |
80
+ | Model internals report | `--internals-only`, `--with-internals` | Are weights and attention heads well-behaved internally? | `model_internals_report.md`, `model_internals_report.png` |
81
 
82
+ ### Core outputs
83
 
84
+ - Main grid per episode: attention, overlays, and optional gradient rows.
85
+ - Optional per-head grid for SigLIP attention heads on the first frame.
86
+ - Structured run directory for the web viewer when `--export-data` is enabled.
87
+ - Model internals report covering spectral alpha, attention entropy, and head redundancy.
88
 
89
+ ### Model internals at a glance
90
 
91
+ The internals report runs three checks across the SigLIP vision encoder, VLM text model, action expert, connector, and projection heads:
92
 
93
+ 1. Weight spectral analysis with WeightWatcher.
94
+ 2. Attention entropy across key attention operations.
95
+ 3. Head redundancy within each layer.
 
 
 
 
 
 
 
 
96
 
97
+ Use `--internals-only` when you want just that report. Use `--with-internals` when you want it in addition to the normal attention / gradient run.
98
 
99
+ ![Example model internals report](assets/example_model_internals_report.png)
100
+ *Example 3-panel internals report. The full markdown version lives at [assets/example_model_internals_report.md](assets/example_model_internals_report.md).*
101
 
102
+ For a visual walkthrough of the architecture behind these views, see [assets/architecture.md](assets/architecture.md).
103
 
104
+ ## Setup
105
 
106
+ ### Requirements
 
107
 
108
+ - Python 3.10+
109
+ - FFmpeg 4-7 for video decoding through TorchCodec
110
+ - Node.js 20.19+ for the web viewer
111
 
112
+ ### macOS
113
 
114
+ Install FFmpeg 6:
115
 
116
  ```bash
117
  brew install ffmpeg@6
118
  ```
119
 
120
+ Then install Python dependencies:
121
 
122
  ```bash
123
+ python3.11 -m venv .venv
124
+ source .venv/bin/activate
125
  pip install -r requirements.txt
126
  ```
127
 
128
+ Install Node.js if you want the web viewer:
129
 
130
+ ```bash
131
+ brew install node
132
+ ```
 
 
 
 
133
 
134
+ ### Ubuntu + CUDA
135
 
136
+ For an NVIDIA GPU machine, use the setup helper:
137
 
138
  ```bash
 
139
  chmod +x clone-and-setup.sh && ./clone-and-setup.sh
140
+ ```
141
+
142
+ Or if the repo is already cloned:
143
 
144
+ ```bash
145
  chmod +x setup-gpu.sh && ./setup-gpu.sh
146
  ```
147
 
148
+ `setup-gpu.sh` installs CUDA-compatible PyTorch, creates a virtualenv, installs dependencies, and checks GPU access.
149
+
150
+ ### Node.js note
151
 
152
+ On Ubuntu, the default `apt install nodejs` is often too old. Prefer [NodeSource](https://github.com/nodesource/distributions) or `nvm`.
153
 
154
  ## Run
155
 
156
+ Use `run.sh` on macOS so TorchCodec can find FFmpeg 6:
157
 
158
  ```bash
159
  source .venv/bin/activate
160
  ./run.sh
161
  ```
162
 
163
+ If you prefer calling Python directly:
164
 
165
  ```bash
166
  export DYLD_LIBRARY_PATH="/opt/homebrew/opt/ffmpeg@6/lib:$DYLD_LIBRARY_PATH"
 
169
 
170
  ### Config files
171
 
172
+ Defaults come from `configs/defaults.yaml`. Use `--config` to load another config; direct CLI flags still override config values.
173
+
174
+ | Config | Purpose |
175
+ |--------|---------|
176
+ | `configs/defaults.yaml` | Conservative CPU-friendly defaults |
177
+ | `configs/gpu.yaml` | CUDA-oriented config with gradients and extended attribution enabled |
178
+
179
+ Example:
180
 
181
  ```bash
 
182
  ./run.sh --config configs/gpu.yaml
 
 
183
  ./run.sh --config configs/gpu.yaml --episode 3
184
  ```
185
 
186
+ ### Common commands
187
 
188
+ #### Basic attention inspection
 
 
 
 
 
189
 
190
  ```bash
 
191
  ./run.sh
 
 
 
 
 
192
  ./run.sh --model path/to/finetuned_checkpoint --dataset path/to/dataset
 
 
193
  ./run.sh --episode 3 --num-frames 12
 
 
194
  ./run.sh --task "pick up the red cube"
 
 
195
  ./run.sh --method last-layer
 
 
 
 
 
 
 
 
196
  ./run.sh --raw-attention
 
 
197
  ./run.sh --attn-threshold 0.7
 
 
198
  ./run.sh --attn-threshold 0
199
+ ```
200
 
201
+ #### Gradient attribution
 
202
 
203
+ ```bash
204
+ ./run.sh --gradient
205
  ./run.sh --gradient saliency
 
 
206
  ./run.sh --gradient saliency --smooth-grad 20
 
 
207
  ./run.sh --device mps --gradient both --gradient-device cpu
208
+ ```
209
 
210
+ #### Extended attribution
 
211
 
212
+ ```bash
213
+ ./run.sh --cross-attention --per-step-cross-attention
214
  ./run.sh --gradient gradcam --gradcam-connector --gradcam-vlm-layers 4,8,12,16
 
 
215
  ./run.sh --gradient gradcam --vision-vs-state
 
 
216
  ./run.sh --gradient gradcam --per-action-dim
 
 
217
  ./run.sh --language-diff "pick up the blue cube"
218
+ ```
219
 
220
+ #### Model internals
 
221
 
222
+ ```bash
223
+ ./run.sh --internals-only
224
  ./run.sh --with-internals
 
 
225
  ./run.sh --internals-only --internals-frames 10
 
 
226
  ./run.sh --internals-only --entropy-warn 0.85 --redundancy-warn 0.75
 
 
 
227
  ```
228
 
229
+ Backward-compatible aliases `--model-health` and `--health-frames` are still accepted, but `--internals-only` and `--internals-frames` are the primary names now.
230
+
231
+ ### Output layout
232
 
233
+ With `--export-data` enabled, each run gets a structured folder under `outputs/`:
234
+
235
+ ```text
236
+ run_YYYY-MM-DD_HH-MM-SS/
237
+ images/
238
+ data/
239
+ run_manifest.json
240
+ ```
241
+
242
+ That structure is what the web viewer reads.
243
 
244
  ## Web Viewer
245
 
246
+ The web viewer lets you:
247
+
248
+ - browse generated runs and available visualizations,
249
+ - inspect frames interactively,
250
+ - compare runs side by side,
251
+ - view model internals when exported,
252
+ - attach LLM-generated analysis to runs and visualizations.
253
 
254
  ![Main visualization view](assets/web_viewer_main.png)
255
+ *Browsing per-frame visualizations in the main viewer.*
256
 
257
  ![Run Insights with LLM analysis](assets/web_viewer_insights.png)
258
+ *Run Insights summarizes statistics across a run and supports LLM analysis.*
259
 
260
  ![Compare Runs](assets/web_viewer_compare.png)
261
+ *Compare multiple runs side by side.*
 
 
262
 
263
+ ### Development launch
264
 
265
  ```bash
266
  source .venv/bin/activate
267
  ./start_servers.sh
 
268
  ./start_servers.sh --base-dir ./my_outputs
269
  ```
270
 
271
  This starts:
 
 
272
 
273
+ - backend on `http://localhost:8080`
274
+ - frontend on `http://localhost:5173`
275
 
276
+ ### Production-style launch
277
+
278
+ Build the frontend once, then serve from FastAPI:
279
 
280
  ```bash
281
+ cd web/frontend
282
+ npm install
283
+ npm run build
284
+ cd ../..
285
  python inspect_attention.py serve --port 8080 --base-dir ./outputs
286
  ```
287
 
288
+ ### LLM setup
 
 
289
 
290
+ Set one of these before launching if you want LLM analysis:
291
 
292
  ```bash
 
293
  export ANTHROPIC_API_KEY=sk-ant-...
 
 
294
  export OPENAI_API_KEY=sk-...
295
  ```
296
 
 
 
297
  ### `serve` flags
298
 
299
  | Flag | Default | Description |
300
  |------|---------|-------------|
301
  | `--port` | `8080` | Server port |
302
  | `--host` | `0.0.0.0` | Server host |
303
+ | `--base-dir` | `./outputs` | Root directory scanned for runs |
304
+ | `--no-open` | off | Do not auto-open the browser |
305
 
306
+ ## How It Works
307
 
308
+ ![Architecture and attention-to-heatmap pipeline](assets/how_it_works_architecture.png)
309
+ *Left: where attention is captured. Right: how patch attention becomes a spatial heatmap.*
310
 
311
+ ### Pipeline
312
 
313
+ 1. Load a SmolVLA policy and a LeRobot dataset.
314
+ 2. Capture self-attention from the SigLIP vision encoder.
315
+ 3. Optionally capture action-expert cross-attention into the VLM prefix.
316
+ 4. Convert patch-level scores into spatial heatmaps.
317
+ 5. Optionally compute gradients, GradCAM, or extended attribution views.
318
+ 6. Save images plus structured data for the viewer.
319
+
320
+ ### Main dashboard rows
321
+
322
+ | Row | Content | When shown |
323
+ |-----|---------|------------|
324
+ | 1 | Original frame | always |
325
+ | 2 | SigLIP self-attention heatmap | always |
326
+ | 3 | Action cross-attention heatmap | `--cross-attention` |
327
+ | 4 | Saliency / SmoothGrad overlay | `--gradient saliency` or `both` |
328
+ | 5 | Self-attention overlay | always |
329
+ | 6 | Co-attention overlay | `--cross-attention` |
330
+ | 7 | GradCAM overlay (SigLIP) | `--gradient gradcam` or `both` |
331
+ | 8 | GradCAM overlay (Connector) | `--gradcam-connector` |
332
+ | 9 | Language-conditional diff | `--language-diff` |
333
 
334
+ ### Per-head grid
335
 
336
+ With `--show-heads`, the first frame gets a separate 12-head SigLIP grid:
 
 
 
 
 
 
337
 
338
+ ![Per-head attention grid](assets/example_per_head.png)
339
+ *Look for specialization: some heads should track objects, gripper geometry, or broader scene structure.*
340
 
341
+ ## Interpreting Results
 
 
 
 
 
 
342
 
343
+ ### Self-attention
344
 
345
+ | Pattern | Interpretation |
346
+ |---------|----------------|
347
+ | Bright on gripper, object, and goal | good task-relevant visual focus |
348
+ | Bright on shelves, cables, or table texture | possible background shortcut |
349
+ | Uniform / diffuse everywhere | weak or unfocused visual features |
350
+ | Focus shifts sensibly over time | model is tracking task progression |
 
 
351
 
352
+ ### Cross-attention
353
 
354
+ | Pattern | Interpretation |
355
+ |---------|----------------|
356
+ | Tight focus on gripper tip and target object | decoder is reading useful vision tokens |
357
+ | Diffuse over all vision tokens | decoder has not specialized well |
358
+ | Self-attn diffuse but cross-attn focused | decoder is filtering noisy encoder features |
359
+ | Self-attn focused but cross-attn diffuse | encoder is better than the decoder's use of it |
 
 
 
 
360
 
361
+ ### Gradient attribution
362
 
363
+ | Pattern | Interpretation |
364
+ |---------|----------------|
365
+ | Saliency highlights object / gripper edges | action depends on relevant pixels |
366
+ | GradCAM agrees with attention | representation and causal signal align |
367
+ | Attention focused but saliency diffuse | model may look there without using it |
368
+ | Saliency spikes on irrelevant structure | likely shortcut or bias |
369
 
370
+ ### Extended attribution checks
371
 
372
+ | Feature | What to look for |
373
+ |---------|-----------------|
374
+ | Per-step cross-attention | focus should sharpen over denoising steps |
375
+ | Connector GradCAM | should broadly agree with SigLIP GradCAM at coarser resolution |
376
+ | VLM layer GradCAM | later layers should become more task-specific |
377
+ | Vision vs. state | extreme imbalance can indicate one modality is ignored |
378
+ | Per-action-dim | different joints should not all attend to identical regions |
379
+ | Language diff | changing the instruction should move visual emphasis |
380
 
381
+ ### Attention vs. gradient
382
 
383
+ | Case | Meaning |
384
+ |------|---------|
385
+ | High attention, low gradient | model represents the region but may not rely on it |
386
+ | Low attention, high gradient | subtle but causally important region |
387
+ | High attention, high gradient | strongest evidence of behavior-driving focus |
 
388
 
389
+ ### Model internals report
390
 
391
+ | Metric | Healthy | Warning | Critical |
392
+ |--------|---------|---------|----------|
393
+ | Spectral alpha | 2-4 | 4-6 | >6 or <2 |
394
+ | Attention entropy | 0.10-0.80 | >0.80 | >0.95 or <0.10 |
395
+ | Head redundancy | <0.70 | >0.70 | >0.90 |
396
 
397
+ The report covers three attention components:
398
 
399
+ | Report component | Architecture operation |
400
+ |-----------------|------------------------|
401
+ | SigLIP Vision (12L, 12H) | self-attention inside the vision encoder |
402
+ | VLM+Expert Joint Self-Attn (16L, 15H) | joint prefill self-attention |
403
+ | Expert-to-VLM Cross-Attn (16L, 8H) | action decoding cross-attention |
 
 
404
 
405
+ ### Split-device tip
406
 
407
+ If MPS backward is unstable or slow, run attention on MPS and gradients on CPU:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
408
 
409
  ```bash
410
  ./run.sh --device mps --gradient both --gradient-device cpu
411
  ```
412
 
413
+ ## CLI Reference
 
 
414
 
415
+ ### General
 
 
 
 
 
 
 
 
416
 
417
+ | Flag | Default | Description |
418
+ |------|---------|-------------|
419
+ | `--config` | `configs/defaults.yaml` | Load defaults from a YAML config |
420
+ | `--model` | `lerobot/smolvla_base` | HuggingFace model ID or local path |
421
+ | `--dataset` | `lerobot/svla_so101_pickplace` | LeRobot dataset ID or local path |
422
+ | `--episode` | `0` | Episode index |
423
+ | `--num-frames` | `8` | Number of sampled frames |
424
+ | `--image-key` | auto | Dataset image key override |
425
+ | `--image-map` | off | Explicit dataset-to-policy image key mapping |
426
+ | `--task` | dataset value | Override language instruction |
427
+ | `--output-dir` | `./outputs` | Output directory |
428
+ | `--device` | `auto` | `auto`, `cpu`, `cuda`, `mps` |
429
+ | `--save-individual` | `true` | Save per-frame overlays as separate files |
430
+ | `--export-data` | `true` | Save structured run data for the web viewer |
431
+ | `--no-export-data` | off | Disable structured run export |
432
+ | `--run-name` | timestamped | Override the generated run folder name |
433
 
434
+ ### Attention
 
 
 
 
435
 
436
+ | Flag | Default | Description |
437
+ |------|---------|-------------|
438
+ | `--method` | `rollout` | `last-layer`, `rollout`, or `all-layers` |
439
+ | `--cross-attention` | `true` | Capture action-expert cross-attention |
440
+ | `--show-heads` | `true` | Save a per-head grid for frame 0 |
441
+ | `--raw-attention` | `false` | Skip positional baseline subtraction |
442
+ | `--attn-threshold` | `0.5` | Zero out low attention values after normalization |
443
+ | `--skip-attention` | `false` | Skip hook-based attention extraction and only run gradient features |
444
 
445
+ ### Gradients and extended attribution
446
 
447
+ | Flag | Default | Description |
448
+ |------|---------|-------------|
449
+ | `--gradient` | off | `saliency`, `gradcam`, or `both` |
450
+ | `--gradient-device` | same as `--device` | Device for gradient computation |
451
+ | `--gradient-seed` | `42` | Fixed seed for reproducibility |
452
+ | `--smooth-grad` | `1` | SmoothGrad sample count |
453
+ | `--smooth-grad-sigma` | `0.15` | SmoothGrad noise std |
454
+ | `--per-step-cross-attention` | `false` | Save cross-attention per denoising step |
455
+ | `--gradcam-connector` | `false` | GradCAM on connector output |
456
+ | `--gradcam-vlm-layers` | off | GradCAM on selected VLM layers |
457
+ | `--vision-vs-state` | `false` | Compare vision vs state attribution |
458
+ | `--per-action-dim` | `false` | Per-action-dimension GradCAM |
459
+ | `--language-diff` | off | Compare attribution between two task prompts |
460
+
461
+ ### Model internals
462
 
463
+ | Flag | Default | Description |
464
+ |------|---------|-------------|
465
+ | `--internals-only` | `false` | Run only the model internals report |
466
+ | `--with-internals` | `false` | Add the model internals report to a standard run |
467
+ | `--internals-frames` | `5` | Sampled frames for entropy / redundancy |
468
+ | `--entropy-warn` | `0.8` | Unfocused-head threshold |
469
+ | `--entropy-critical` | `0.95` | Dead-head threshold |
470
+ | `--entropy-low` | `0.1` | Collapsed-head threshold |
471
+ | `--redundancy-warn` | `0.7` | High-redundancy threshold |
472
+ | `--redundancy-critical` | `0.9` | Collapsed-redundancy threshold |
473
+
474
+ ## Project Layout
475
+
476
+ ```text
477
  smolvla-inspect/
478
+ β”œβ”€β”€ inspect_attention.py
479
+ β”œβ”€β”€ smolvla_inspect/
480
+ β”‚ β”œβ”€β”€ cli.py
481
+ β”‚ β”œβ”€β”€ capture.py
482
+ β”‚ β”œβ”€β”€ data.py
483
+ β”‚ β”œβ”€β”€ export.py
484
+ β”‚ β”œβ”€β”€ gradient.py
485
+ β”‚ β”œβ”€β”€ heatmap.py
486
+ β”‚ β”œβ”€β”€ internals.py
487
+ β”‚ β”œβ”€β”€ serve.py
488
+ β”‚ β”œβ”€β”€ viz.py
489
+ β”‚ └── _compat.py
490
  β”œβ”€β”€ web/
491
+ β”‚ β”œβ”€β”€ backend/
492
+ β”‚ └── frontend/
 
 
 
 
 
 
 
 
 
 
 
 
493
  β”œβ”€β”€ assets/
 
 
 
 
 
 
 
 
 
494
  β”œβ”€β”€ configs/
 
 
 
 
495
  β”œβ”€β”€ docs/
496
+ β”œβ”€β”€ clone-and-setup.sh
497
+ β”œβ”€β”€ setup-gpu.sh
498
+ β”œβ”€β”€ start_servers.sh
499
+ β”œβ”€β”€ run.sh
 
 
500
  β”œβ”€β”€ requirements.txt
 
501
  └── README.md
502
  ```
503
 
 
 
504
  ## Roadmap
505
 
506
+ - [x] Gradient-based attribution
507
+ - [x] SmoothGrad
508
+ - [x] Extended attribution features
509
+ - [x] Config file support
510
+ - [x] Interactive web viewer
511
+ - [ ] Occlusion / perturbation sensitivity
512
+ - [ ] Representation probing
513
+ - [ ] Causal tracing / activation patching
514
+ - [ ] Temporal consistency analysis
 
 
515
 
516
  ## Note on FFmpeg
517
 
518
+ If you linked `ffmpeg@6` and want to switch back later:
519
+
520
+ ```bash
521
+ brew unlink ffmpeg@6 && brew link ffmpeg
522
+ ```