Explainability¶
The explainability package (versatil.explainability) produces visual attribution maps for trained policies: per-camera heatmaps showing which image regions drove the predicted actions. It runs post hoc on any checkpoint over offline dataset samples or live during inference.
Three attribution methods are supported, selected through explanation_types:
| Method | ExplanationType value |
How it works | Paper |
|---|---|---|---|
| Grad-CAM | gradcam |
Gradient of the prediction score w.r.t. a target activation, channel-averaged as weights | Selvaraju et al., ICCV 2017 |
| Grad-CAM++ | gradcam++ |
Higher-order gradient weighting for sharper multi-instance maps | Chattopadhay et al., WACV 2018 |
| Ablation-CAM | ablation_cam |
Perturbation-based: each activation channel is zeroed and weighted by the resulting score drop, no backpropagation | Desai & Ramaswamy, WACV 2020 |
CNN feature maps and ViT patch-tokens are both compatible. Token activations are reshaped to their patch grid before weighting.
Running the endpoint¶
python -m versatil.endpoints.explain \
checkpoint_path=/path/to/training/checkpoint \
split=val \
sample_stride=50 \
max_samples=16
checkpoint_path is the training run directory containing config.yaml and the model checkpoint. Outputs are written to checkpoint_path/explainability/<timestamp>/ unless output_directory is set. All settings live on ExplainabilityConfig and can be overridden from the CLI.
Common overrides:
# Restrict methods, cameras, or visual modules
explanation_types='[gradcam]'
target_camera_keys='[left]'
target_vision_module_names='[left_rgb_encoder]'
# Explain different data than the checkpoint was trained on
data_path_override=/path/to/other_data.zarr
# Save raw heatmap tensors in addition to overlays
writer.save_raw_heatmaps=true
Pipeline¶
ExplainabilityRunner
-> FloatCheckpointLoader (restore policy, normalizer, tokenizer)
-> ExplanationSource (dataset windows or live inference windows)
-> per batch, per explanation type:
resolve_camera_explanation_targets (discover visual modules and cameras)
compute heatmaps (Grad-CAM/Grad-CAM++/Ablation-CAM)
-> ExplanationWriter (overlay images, optional raw .pt tensors)
ExplainabilityRunner orchestrates the loop. Sources yield ExplanationBatch objects carrying the observation window, optional action labels, display images for overlays, and per-batch metadata.
Explanation sources¶
Dataset source (source=dataset)¶
DatasetExplanationSource samples episodic windows through the same EpisodicDataset used in training, with the checkpoint's normalizer and tokenizer attached and augmentations disabled. Sampling is deterministic: every sample_stride-th window in dataset order, capped by max_samples.
splitselectstrain,val, orall(theallsplit reuses the training-side splitter withval_ratio=0, preserving every other sampling setting).data_path_overrideexplains data other than the training set. A path ending in.zarris sampled directly. A non-zarr path must be raw data in the same schema format as the checkpoint (CSV episode folders, HDF5 file, LeRobot root) and is converted tooffline_dataset.zarrbeside the override path. A list is only valid for raw schemas that accept multiple inputs; multiple zarr paths are rejected.
Dataset batches are already normalized and tokenized, so attribution runs with preprocess_observation=False.
Online inference source (source=online_inference)¶
OnlineInferenceExplanationSource attaches to the same InferenceClient loop used by the deployment endpoint. The client handles transport, preprocessing, and observation buffering; whenever a buffered observation window is ready for policy inference, the source receives the exact batch passed to the policy and explains every sample_stride-th timestep.
Client settings come from the shared InferenceClientConfig under the online key:
python -m versatil.endpoints.explain \
checkpoint_path=/path/to/checkpoint \
source=online_inference \
online.model_server_address=10.0.0.1 \
online.model_server_port=5556 \
sample_stride=10 \
max_samples=100
Attribution targets¶
Targets are discovered automatically from the policy:
- Encoding-pipeline encoders (including conditional encoders) that expose
get_explainability_targets(). - Decoder-owned VLM vision towers for VLA policies: the
decoder.vlm_backbonemodule itself and each entry of itsvision_encoderslist.
Each module declares its capture metadata through VisionExplanationTarget: the target layer, whether it produces a spatial feature map (NCHW/NHWC) or a ViT token sequence (NLC), the tuple output index when the layer returns several tensors, and for token targets the prefix-token count and patch grid. Attribution hooks that layer, converts the captured activation to NCHW, computes the map, and resizes it back to the camera image with bicubic interpolation.
When one module serves several cameras, a capture mode routes the hook to the right camera: separate forward calls per camera (per_camera_call), a camera-stacked batch dimension (stacked_camera_batch), or a single call (single_call). When multiple visual modules can explain the same camera, their normalized maps are averaged; use target_vision_module_names to isolate one module.
Prediction objective¶
The scalar score that is attributed depends on the decoder:
- Continuous-action decoders: the norm of all concatenated normalized action predictions. A custom
output_selectorcallable can replace this when calling the attribution functions from Python (compute_gradient_maps_for_policy,compute_ablation_maps_for_policy). - Tokenized-action decoders (autoregressive VLAs): the teacher-forced mean log-likelihood of the action tokens, ignoring padded positions. Dataset batches use the true tokenized actions; online batches have no labels, so an unhooked inference pass generates pseudo-target tokens first.
Decoder encoder-prefix caches are disabled during attribution forwards so that hooked activations always contribute to the scored prediction.
Ablation-CAM controls its memory/compute trade-off through channel_batch_size: that many activation channels are ablated per policy forward by repeating the batch, so peak memory scales with batch_size * channel_batch_size.
Outputs¶
ExplanationWriter writes under output_directory/<source>/ (dataset runs add a <split>/ level):
- Overlays (default on): one image per sample, timestep, method, and camera, named
sample_<index>_t<t>_<method>_<camera>.pngfor dataset runs andenv_<index>_step_<t>_...for online runs.writer.image_weightblends the original image with the JET-colormapped heatmap;writer.overlay_image_formatselects any OpenCV-writable extension. - Raw heatmaps (
writer.save_raw_heatmaps=true): onebatch_<n>_<method>.ptfile per batch containing the batch metadata and(B, T, H, W)heatmap tensors keyed by camera, for downstream analysis.
Limitations¶
- Each visual module must expose exactly one compatible explainability target. Per-target selection inside a module is not yet configurable.
- Token targets without an explicit
patch_gridrequire a perfect-square patch count to infer the grid. - Attribution runs full policy forwards (plus one forward per
channel_batch_sizechannels for Ablation-CAM), so online mode adds latency to each explained inference step. - There is no data format convention for explaining recorded inference rollouts, since recording formats depend on the simulator or hardware setup. The recommended workflow is currently to convert recorded rollouts into the same dataset schema used for training, then explain them through the dataset source by pointing
data_path_overrideat the converted data.