subirmansukhani commited on
Commit
358bf71
Β·
1 Parent(s): a30a48a

Update README to reflect current visualization pipeline

Browse files

- Add example grid and per-head images at the top
- Document all 5 grid rows with colormaps (jet, Greens, cyan)
- Add CLI flags table with current defaults (rollout, cross-attention on)
- Add co-attention interpretation section
- Update architecture details (12 layers, 12 heads, 32x32 patches)
- Remove outdated references to dual-color overlay and gradient fallback

Files changed (3) hide show
  1. README.md +109 -80
  2. assets/example_grid.png +3 -0
  3. assets/example_per_head.png +3 -0
README.md CHANGED
@@ -1,56 +1,70 @@
1
  # smolvla-inspect
2
 
3
- See what SmolVLA’s vision encoder is looking at when it predicts robot actions.
 
 
 
4
 
5
  ---
6
 
7
  ## What this does
8
 
9
- SmolVLA is a **vision–language–action** policy: it takes camera images (and optionally language) and outputs robot actions. This repo **visualizes where the vision part β€œlooks”** in each frame by extracting **attention maps** from the vision encoder and turning them into heatmaps over the images.
 
 
 
10
 
11
- That lets you check whether the model is attending to task-relevant regions (e.g. gripper, object, goal) or to background (e.g. walls, table texture)β€”a useful signal for debugging overfitting or distribution shift.
12
 
13
- **Input:** A pretrained or fine-tuned SmolVLA policy + a LeRobot dataset (e.g. episodes of pick-and-place).
14
- **Output:** For each chosen frame, (1) the original image, (2) an attention heatmap, and (3) an overlay of the heatmap on the image, plus a grid summary PNG.
15
 
16
  ---
17
 
18
- ## How it works (for data scientists)
19
 
20
- ![Architecture and attention-to-heatmap pipeline](assets/how_it_works_architecture.png)
21
  *Left: Where the vision encoder lives and where we hook to capture attention. Right: How attention weights become a spatial heatmap.*
22
 
23
- High-level pipeline:
 
 
 
 
 
 
 
 
 
24
 
25
- 1. **Load model and dataset**
26
- The script loads a SmolVLA policy (e.g. `lerobot/smolvla_base`) and a LeRobot dataset. The dataset provides sequences of images and actions; we use the images as input and (optionally) actions only for reference.
27
 
28
- 2. **Locate the vision encoder**
29
- SmolVLA is built from a **vision encoder** (SmolVLM/SigLIP-style Vision Transformer) plus a language/action head. The script finds the vision encoder inside the policy (e.g. `model.vlm_with_expert.vlm.model.vision_model`) so it can hook into it.
30
 
31
- 3. **Run a forward pass and capture self-attention**
32
- The vision encoder is a **Transformer**: it splits the image into **patches**, runs **self-attention** over those patches, and outputs patch-level features. The script:
33
- - Registers **forward hooks** on the encoder’s attention layers so that when we run a forward pass, we get the **attention weight matrices** (which patch β€œlooks at” which).
34
- - To get weights, the code forces **eager** attention (instead of SDPA/Flash), which returns the full weight tensor.
35
- - For each frame, it builds a batch (with the right image keys and dtype), runs the vision encoder (or full policy), and the hooks record the attention.
36
- - **Aggregation methods** (`--method`): `last-layer` uses only the final encoder layer, `rollout` multiplies attention across all layers (accounting for residual connections) for a more complete picture of information flow, `all-layers` keeps each layer’s attention separately.
37
 
38
- 4. **Optionally capture cross-attention** (`--cross-attention`)
39
- SmolVLA doesn’t have explicit cross-attention layers. Instead, the VLM builds a **KV cache** from the prefix sequence (vision tokens + language tokens + state tokens), and the **action expert** queries that cache. The script monkey-patches `eager_attention_forward()` on the expert to intercept the softmax attention probabilities when expert queries attend to prefix keys (detected by Q seq-len != K seq-len). Only the columns corresponding to vision tokens are kept, giving a heatmap of which image regions the action decoder actually reads.
 
 
 
 
 
40
 
41
- 5. **Turn attention into a spatial heatmap**
42
- Attention is in **patch space** (e.g. 32Γ—32 grid for SigLIP with 512px images and 16px patches). For each patch we get an β€œimportance” score (mean attention received). Those scores are reshaped into a 2D grid, **upsampled** with bilinear interpolation to the original image size, and normalized to [0, 1]. That gives a single **heatmap** per frame (bright = high attention).
43
 
44
- 6. **Visualize**
45
- Default grid (3 rows per frame): **Row 1** original, **Row 2** self-attention heatmap, **Row 3** overlay. With `--cross-attention`, two extra rows are added: **Row 4** cross-attention heatmap (hot colormap), **Row 5** dual-color overlay (self-attention in blue, cross-attention in red). With `--show-heads`, a separate per-head grid is saved for the first frame.
46
 
47
- **Fallback:** If the vision encoder can’t be hooked or attention isn’t captured (e.g. wrong architecture), the script can fall back to **input-gradient saliency**: backprop from the action output to the image pixels and use gradient magnitude as a proxy for β€œwhat the model uses.” That’s no longer true attention but still highlights influential pixels.
 
 
 
48
 
49
  ---
50
 
51
  ## Setup
52
 
53
- **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 so the loader can find it:
54
 
55
  ```bash
56
  brew install ffmpeg@6
@@ -60,7 +74,7 @@ Then:
60
 
61
  ```bash
62
  cd smolvla-inspect
63
- python3.10 -m venv .venv && source .venv/bin/activate # or python3.11
64
  pip install -r requirements.txt
65
  ```
66
 
@@ -68,105 +82,120 @@ pip install -r requirements.txt
68
 
69
  ## Run
70
 
71
- Use `run.sh` so TorchCodec finds FFmpeg 6’s libs (needed for dataset video decoding):
72
 
73
  ```bash
74
  source .venv/bin/activate
75
  ./run.sh
76
  ```
77
 
78
- Or set the library path yourself and run Python:
79
 
80
  ```bash
81
  export DYLD_LIBRARY_PATH="/opt/homebrew/opt/ffmpeg@6/lib:$DYLD_LIBRARY_PATH"
82
  python inspect_attention.py
83
  ```
84
 
85
- Examples:
86
 
87
  ```bash
88
- # Default: pretrained SmolVLA base + real-world SO101 pick-place dataset
89
  ./run.sh
90
 
91
  # Your fine-tuned model
92
  ./run.sh --model path/to/finetuned_checkpoint --dataset path/to/dataset
93
 
94
- # More frames, specific episode, explicit GPU
95
- ./run.sh --episode 3 --num-frames 12 --device cuda
96
-
97
- # Device is auto-detected by default (mps β†’ cuda β†’ cpu)
98
- # Override with --device cpu/cuda/mps if needed
99
 
100
- # Save each frame as a separate PNG
101
- ./run.sh --save-individual
102
 
103
- # Attention rollout across all SigLIP layers (instead of last layer only)
104
- ./run.sh --method rollout
105
 
106
- # Capture action-expert β†’ vision cross-attention (slower, runs full policy forward)
107
- ./run.sh --cross-attention
108
 
109
- # Show per-head attention patterns for the first frame
110
- ./run.sh --show-heads
111
-
112
- # Combine flags
113
- ./run.sh --method rollout --cross-attention --show-heads
114
  ```
115
 
116
  Results land in `outputs/`.
117
 
118
- ---
119
-
120
- ## Project layout
121
-
122
- ```
123
- smolvla-inspect/
124
- β”œβ”€β”€ inspect_attention.py # Main script: load model/dataset, extract attention, save heatmaps
125
- β”œβ”€β”€ assets/
126
- β”‚ └── how_it_works_architecture.png # Diagram: architecture + hooks + attentionβ†’heatmap
127
- β”œβ”€β”€ configs/
128
- β”‚ └── defaults.yaml # Default model/dataset/output paths (optional)
129
- β”œβ”€β”€ docs/
130
- β”‚ β”œβ”€β”€ ELI5.md # Plain-language explanation of how the interpretability works
131
- β”‚ └── TESTING.md # CLI test commands and expected output
132
- β”œβ”€β”€ outputs/ # Generated grid and per-frame images
133
- β”œβ”€β”€ run.sh # Wrapper that sets FFmpeg lib path
134
- β”œβ”€β”€ requirements.txt
135
- └── README.md
136
- ```
137
 
138
  ---
139
 
140
  ## What to look for
141
 
142
- ### Self-attention (vision encoder β€” rows 2-3)
143
 
144
  | Attention pattern | Interpretation |
145
  |-------------------|----------------|
146
- | Bright on gripper + object + goal | **Healthy** β€” model attends to task-relevant regions |
147
- | Bright on shelves, cables, table grain | **Background overfitting** β€” model may be using scene cues |
148
  | Uniform / diffuse everywhere | Model may not have learned focused visual features yet |
149
- | Shifts from background β†’ object across frames | Model is tracking the task over time (good sign) |
150
 
151
- ### Cross-attention (action expert β†’ vision β€” rows 4-5, with `--cross-attention`)
152
 
153
  | Attention pattern | Interpretation |
154
  |-------------------|----------------|
155
- | Tight focus on gripper tip + target object | **Healthy** β€” the action decoder reads exactly the tokens it needs |
156
- | Diffuse across all vision tokens | Decoder hasn't specialised; may predict generic/averaged actions |
157
- | Self-attn diffuse but cross-attn focused | Good sign β€” the decoder learned to select useful tokens despite a noisy encoder |
158
- | Self-attn focused but cross-attn diffuse | Encoder features are good but the decoder doesn't exploit them well |
 
 
159
 
160
- ### Per-head patterns (with `--show-heads`)
161
 
162
- Look for heads that specialise: one head tracking the gripper, another tracking the object, another attending to the background. Specialisation is a sign of a well-trained encoder.
163
 
164
- ### Grid layout
 
 
 
 
165
 
166
- The default grid has three rows per frame: **Row 1 = original**, **Row 2 = self-attention heatmap**, **Row 3 = overlay**. With `--cross-attention`, two extra rows appear: **Row 4 = cross-attention heatmap**, **Row 5 = dual-color overlay** (blue = self-attention, red = cross-attention).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
 
168
  ---
169
 
170
  ## Note on FFmpeg
171
 
172
- If you installed `ffmpeg@6` and linked it (e.g. `brew link --overwrite ffmpeg@6`), your default `ffmpeg` is now 6.x. To switch back to FFmpeg 8 later: `brew unlink ffmpeg@6 && brew link ffmpeg`.
 
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
+ *5-row attention grid for a pick-and-place episode. Row 1: original frames. Row 2: SigLIP self-attention heatmap. Row 3: self-attention overlay. Row 4: action cross-attention heatmap. Row 5: co-attention (self x cross) overlay in cyan.*
7
 
8
  ---
9
 
10
  ## What this does
11
 
12
+ SmolVLA is a **vision-language-action** policy: it takes camera images and a language instruction, then outputs robot actions. This tool **visualizes where the model looks** by extracting attention maps from two places:
13
+
14
+ 1. **SigLIP vision encoder** (self-attention) -- which image patches the encoder considers important during feature extraction
15
+ 2. **Action expert** (cross-attention) -- which image regions the action decoder actually reads when predicting actions
16
 
17
+ 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.
18
 
19
+ **Input:** A pretrained or fine-tuned SmolVLA policy + a LeRobot dataset (e.g. episodes of pick-and-place).
20
+ **Output:** A multi-row grid PNG per episode, optional per-frame PNGs, and an optional per-head attention grid.
21
 
22
  ---
23
 
24
+ ## How it works
25
 
26
+ ![Architecture and attention-to-heatmap pipeline](assets/how_it_works_architecture.png)
27
  *Left: Where the vision encoder lives and where we hook to capture attention. Right: How attention weights become a spatial heatmap.*
28
 
29
+ ### Pipeline
30
+
31
+ 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.
32
+
33
+ 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.
34
+
35
+ 3. **Aggregate across layers** (`--method`):
36
+ - `last-layer` -- uses only the final encoder layer
37
+ - `rollout` (default) -- multiplies attention across all layers with residual connections, giving a more complete picture of information flow
38
+ - `all-layers` -- keeps each layer separately
39
 
40
+ 4. **Capture cross-attention** (`--cross-attention`, on by default) -- 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.
 
41
 
42
+ 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].
 
43
 
44
+ 6. **Visualize** -- the output grid has up to 5 rows per frame:
 
 
 
 
 
45
 
46
+ | Row | Content | Colormap |
47
+ |-----|---------|----------|
48
+ | 1 | Original frame | -- |
49
+ | 2 | SigLIP self-attention heatmap | jet (blue-to-red) |
50
+ | 3 | Self-attention overlay on frame | jet |
51
+ | 4 | Action cross-attention heatmap | Greens |
52
+ | 5 | Co-attention overlay (self x cross) | cyan (black-cyan-white) |
53
 
54
+ Rows 4-5 only appear when cross-attention is enabled. The co-attention overlay multiplies self-attention and cross-attention element-wise, highlighting regions that are **both** visually salient and action-relevant.
 
55
 
56
+ ### Per-head grid
 
57
 
58
+ With `--show-heads`, a separate grid shows each of the 12 SigLIP attention heads individually for the first frame:
59
+
60
+ ![Per-head attention grid](assets/example_per_head.png)
61
+ *Each subplot is one attention head. Look for specialization -- e.g. one head tracking the gripper, another tracking the object.*
62
 
63
  ---
64
 
65
  ## Setup
66
 
67
+ **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:
68
 
69
  ```bash
70
  brew install ffmpeg@6
 
74
 
75
  ```bash
76
  cd smolvla-inspect
77
+ python3.11 -m venv .venv && source .venv/bin/activate
78
  pip install -r requirements.txt
79
  ```
80
 
 
82
 
83
  ## Run
84
 
85
+ Use `run.sh` so TorchCodec finds FFmpeg 6's libs:
86
 
87
  ```bash
88
  source .venv/bin/activate
89
  ./run.sh
90
  ```
91
 
92
+ Or set the library path yourself:
93
 
94
  ```bash
95
  export DYLD_LIBRARY_PATH="/opt/homebrew/opt/ffmpeg@6/lib:$DYLD_LIBRARY_PATH"
96
  python inspect_attention.py
97
  ```
98
 
99
+ ### Examples
100
 
101
  ```bash
102
+ # Default config: rollout method, cross-attention enabled, per-head grid enabled
103
  ./run.sh
104
 
105
  # Your fine-tuned model
106
  ./run.sh --model path/to/finetuned_checkpoint --dataset path/to/dataset
107
 
108
+ # More frames, specific episode
109
+ ./run.sh --episode 3 --num-frames 12
 
 
 
110
 
111
+ # Last-layer only (faster, no rollout)
112
+ ./run.sh --method last-layer
113
 
114
+ # Skip cross-attention (faster, 3-row grid only) β€” edit configs/defaults.yaml:
115
+ # cross_attention: false
116
 
117
+ # Raw attention without positional baseline subtraction
118
+ ./run.sh --raw-attention
119
 
120
+ # Explicit device override (auto-detected by default: mps > cuda > cpu)
121
+ ./run.sh --device cuda
 
 
 
122
  ```
123
 
124
  Results land in `outputs/`.
125
 
126
+ ### CLI flags
127
+
128
+ | Flag | Default | Description |
129
+ |------|---------|-------------|
130
+ | `--model` | `lerobot/smolvla_base` | HuggingFace model ID or local path |
131
+ | `--dataset` | `lerobot/svla_so101_pickplace` | LeRobot dataset ID or local path |
132
+ | `--episode` | `0` | Episode index to visualize |
133
+ | `--num-frames` | `8` | Number of frames to sample |
134
+ | `--image-key` | auto-detected | Dataset image key override |
135
+ | `--output-dir` | `./outputs` | Output directory |
136
+ | `--device` | `auto` | `auto`, `cpu`, `cuda`, or `mps` |
137
+ | `--save-individual` | `true` | Save each frame as a separate PNG |
138
+ | `--method` | `rollout` | `last-layer`, `rollout`, or `all-layers` |
139
+ | `--cross-attention` | `true` | Capture action-expert cross-attention |
140
+ | `--show-heads` | `true` | Save per-head attention grid for first frame |
141
+ | `--raw-attention` | `false` | Skip positional baseline subtraction |
142
+
143
+ Defaults can be changed in `configs/defaults.yaml`.
 
144
 
145
  ---
146
 
147
  ## What to look for
148
 
149
+ ### Self-attention (SigLIP vision encoder -- rows 2-3)
150
 
151
  | Attention pattern | Interpretation |
152
  |-------------------|----------------|
153
+ | Bright on gripper + object + goal | **Healthy** -- model attends to task-relevant regions |
154
+ | Bright on shelves, cables, table grain | **Background overfitting** -- model may be using scene cues |
155
  | Uniform / diffuse everywhere | Model may not have learned focused visual features yet |
156
+ | Shifts from background to object across frames | Model is tracking the task over time (good sign) |
157
 
158
+ ### Cross-attention (action expert -- rows 4-5)
159
 
160
  | Attention pattern | Interpretation |
161
  |-------------------|----------------|
162
+ | Tight focus on gripper tip + target object | **Healthy** -- action decoder reads exactly what it needs |
163
+ | Diffuse across all vision tokens | Decoder hasn't specialized; may predict generic actions |
164
+ | Self-attn diffuse but cross-attn focused | Decoder learned to select useful tokens despite a noisy encoder |
165
+ | Self-attn focused but cross-attn diffuse | Encoder features are good but the decoder doesn't exploit them |
166
+
167
+ ### Co-attention (row 5)
168
 
169
+ 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.
170
 
171
+ ### Per-head patterns
172
 
173
+ 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.
174
+
175
+ ---
176
+
177
+ ## Project layout
178
 
179
+ ```
180
+ smolvla-inspect/
181
+ β”œβ”€β”€ inspect_attention.py # All logic: model loading, hooks, heatmaps, visualization
182
+ β”œβ”€β”€ assets/
183
+ β”‚ β”œβ”€β”€ how_it_works_architecture.png
184
+ β”‚ β”œβ”€β”€ example_grid.png
185
+ β”‚ └── example_per_head.png
186
+ β”œβ”€β”€ configs/
187
+ β”‚ └── defaults.yaml # Default CLI values (model, dataset, method, flags)
188
+ β”œβ”€β”€ docs/
189
+ β”‚ β”œβ”€β”€ ELI5.md # Plain-language explanation of the interpretability approach
190
+ β”‚ └── TESTING.md # CLI test commands and expected output
191
+ β”œβ”€β”€ outputs/ # Generated images (gitignored)
192
+ β”œβ”€β”€ run.sh # Wrapper that sets FFmpeg lib path
193
+ β”œβ”€β”€ requirements.txt
194
+ └── README.md
195
+ ```
196
 
197
  ---
198
 
199
  ## Note on FFmpeg
200
 
201
+ 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`.
assets/example_grid.png ADDED

Git LFS Details

  • SHA256: d896163c6260ab46be2e399abe97dd45ca56731cb6f0f17195e202378e51dad8
  • Pointer size: 132 Bytes
  • Size of remote file: 2.96 MB
assets/example_per_head.png ADDED

Git LFS Details

  • SHA256: 8025ce0d910a02b6a3ce9edaaac87b27d7a6cf944ef3841fbde8d78cc2d2a3cc
  • Pointer size: 131 Bytes
  • Size of remote file: 434 kB