Spaces:
Sleeping
Sleeping
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
- README.md +109 -80
- assets/example_grid.png +3 -0
- assets/example_per_head.png +3 -0
README.md
CHANGED
|
@@ -1,56 +1,70 @@
|
|
| 1 |
# smolvla-inspect
|
| 2 |
|
| 3 |
-
See what SmolVLA
|
|
|
|
|
|
|
|
|
|
| 4 |
|
| 5 |
---
|
| 6 |
|
| 7 |
## What this does
|
| 8 |
|
| 9 |
-
SmolVLA is a **vision
|
|
|
|
|
|
|
|
|
|
| 10 |
|
| 11 |
-
That lets you check whether the model
|
| 12 |
|
| 13 |
-
**Input:** A pretrained or fine-tuned SmolVLA policy + a LeRobot dataset (e.g. episodes of pick-and-place).
|
| 14 |
-
**Output:**
|
| 15 |
|
| 16 |
---
|
| 17 |
|
| 18 |
-
## How it works
|
| 19 |
|
| 20 |
-

|
| 21 |
*Left: Where the vision encoder lives and where we hook to capture attention. Right: How attention weights become a spatial heatmap.*
|
| 22 |
|
| 23 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
|
| 25 |
-
|
| 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 |
-
|
| 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 |
-
|
| 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 |
-
|
| 39 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
|
| 41 |
-
5.
|
| 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 |
-
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
| 48 |
|
| 49 |
---
|
| 50 |
|
| 51 |
## Setup
|
| 52 |
|
| 53 |
-
**Requirements:** Python 3.10+, and FFmpeg 4
|
| 54 |
|
| 55 |
```bash
|
| 56 |
brew install ffmpeg@6
|
|
@@ -60,7 +74,7 @@ Then:
|
|
| 60 |
|
| 61 |
```bash
|
| 62 |
cd smolvla-inspect
|
| 63 |
-
python3.
|
| 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
|
| 72 |
|
| 73 |
```bash
|
| 74 |
source .venv/bin/activate
|
| 75 |
./run.sh
|
| 76 |
```
|
| 77 |
|
| 78 |
-
Or set the library path yourself
|
| 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:
|
| 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
|
| 95 |
-
./run.sh --episode 3 --num-frames 12
|
| 96 |
-
|
| 97 |
-
# Device is auto-detected by default (mps β cuda β cpu)
|
| 98 |
-
# Override with --device cpu/cuda/mps if needed
|
| 99 |
|
| 100 |
-
#
|
| 101 |
-
./run.sh --
|
| 102 |
|
| 103 |
-
#
|
| 104 |
-
|
| 105 |
|
| 106 |
-
#
|
| 107 |
-
./run.sh --
|
| 108 |
|
| 109 |
-
#
|
| 110 |
-
./run.sh --
|
| 111 |
-
|
| 112 |
-
# Combine flags
|
| 113 |
-
./run.sh --method rollout --cross-attention --show-heads
|
| 114 |
```
|
| 115 |
|
| 116 |
Results land in `outputs/`.
|
| 117 |
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
```
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
```
|
| 137 |
|
| 138 |
---
|
| 139 |
|
| 140 |
## What to look for
|
| 141 |
|
| 142 |
-
### Self-attention (vision encoder
|
| 143 |
|
| 144 |
| Attention pattern | Interpretation |
|
| 145 |
|-------------------|----------------|
|
| 146 |
-
| Bright on gripper + object + goal | **Healthy**
|
| 147 |
-
| Bright on shelves, cables, table grain | **Background overfitting**
|
| 148 |
| Uniform / diffuse everywhere | Model may not have learned focused visual features yet |
|
| 149 |
-
| Shifts from background
|
| 150 |
|
| 151 |
-
### Cross-attention (action expert
|
| 152 |
|
| 153 |
| Attention pattern | Interpretation |
|
| 154 |
|-------------------|----------------|
|
| 155 |
-
| Tight focus on gripper tip + target object | **Healthy**
|
| 156 |
-
| Diffuse across all vision tokens | Decoder hasn't
|
| 157 |
-
| Self-attn diffuse but cross-attn focused |
|
| 158 |
-
| Self-attn focused but cross-attn diffuse | Encoder features are good but the decoder doesn't exploit them
|
|
|
|
|
|
|
| 159 |
|
| 160 |
-
|
| 161 |
|
| 162 |
-
|
| 163 |
|
| 164 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 165 |
|
| 166 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 167 |
|
| 168 |
---
|
| 169 |
|
| 170 |
## Note on FFmpeg
|
| 171 |
|
| 172 |
-
If you installed `ffmpeg@6` and linked it (
|
|
|
|
| 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 |
+

|
| 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 |
+

|
| 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 |
+

|
| 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
|
assets/example_per_head.png
ADDED
|
Git LFS Details
|