Downloads · 30 days
34
100% of all-time downloads
NagaYu/saccade-predictor
saccade-predictor is a machine learning model from NagaYu. Use it for the machine learning task on the model card, and read the license before you ship it in a product. It is set up for pytorch. The card lists the license as mit.
A tiny forward predictor for always-on edge vision: given a motion-compensated cache of ViT patch embeddings plus a pseudo-IMU signal, it predicts what each patch embedding will look like in the next frame — so the en…
Downloads · 30 days
34
100% of all-time downloads
All-time downloads
34
Public
Repo size
4.9 MB
Likes
0
Public
Click a slice to open those files.
.safetensors2.9 MB · 100%
From the Hugging Face model README
A tiny forward predictor for always-on edge vision: given a motion-compensated cache of ViT patch embeddings plus a pseudo-IMU signal, it predicts what each patch embedding will look like in the next frame — so the encoder only has to re-run on patches whose change the prediction could not explain.
This is the learned component of Saccade (prediction-error gating + energy-budget control for an always-on edge VLM).
The predictor is not what produces most of Saccade's energy saving — motion-compensated cache reuse is. The predictor's job is to recover the part of the change that the spatial warp gets wrong. Its value therefore scales with how lossy the warp is in a given embedding space, and we measured exactly that:
| Embedding space | warp only | + predictor (cold, online) | + predictor (this checkpoint) |
|---|---|---|---|
SyntheticBackbone (patch-independent) | 0.96891 | 0.96973 | 0.96974 (+0.0008) |
facebook/dinov2-small (context-mixing) | 0.83288 | 0.84814 | 0.84988 (+0.0170) |
(mean cosine fidelity of the reconstructed patch-token map vs a Full encoder, on a held-out ego-motion stream, seed 99.)
The pattern is the point. Where each patch embedding is an independent function of its own pixels, a spatial warp is nearly exact, the residual left over is by definition the unpredictable innovation, and there is almost nothing to learn. Where tokens mix global context through self-attention (any real ViT), warping is a cruder approximation and the predictor recovers a meaningful chunk of the loss.
An earlier version of this predictor made things worse: fidelity decreased monotonically with learning rate. The cause was a train/apply distribution mismatch — at deployment, ground truth only exists for patches the gate chose to encode (the surprising ones), but the prediction is applied to the patches it skipped (the unsurprising ones). A correction fitted on the former is wrong for the latter.
Two mechanisms fix it, and both are in this checkpoint:
warped + trust · residual, where
trust ∈ [0,1] is raised or lowered by a counterfactual check ("would the full residual
have beaten doing nothing?") evaluated on a handful of randomly explored patches drawn
from the skipped population — i.e. the distribution the prediction is actually used on.The result: trust settles around 0.35–0.50 where the residual helps and decays toward 0 where it does not, so turning the predictor on is never worse than pure motion-compensated reuse — even at a learning rate that would otherwise be destructive.
trust and gain_ema are saved as buffers, so a loaded checkpoint keeps the trust it earned.
The shipped value is calibrated on the held-out stream, not on the training streams: during
offline training the head starts untrained, so the counterfactual check correctly drives trust to
0 early on and recovers only slowly. Saving that transient would hand you a predictor that applies
no correction until it re-earns trust online.
| File | Description |
|---|---|
predictor_synthetic.safetensors | Predictor for the SyntheticBackbone embedding space (D=384) |
predictor_dinov2-small.safetensors | Predictor for the facebook/dinov2-small token space (D=384) |
backbone_synthetic.safetensors | Frozen random projection of the SyntheticBackbone, so that embedding space is byte-reproducible |
config.json | Geometry, energy model, training provenance and the full eval record |
The GRU's weights are shared across patches, so a checkpoint is independent of the patch grid (196 patches at 14×14 and 256 at 16×16 both load fine). What a checkpoint is tied to is the embedding space it was trained on — use the matching file.
from huggingface_hub import hf_hub_download
from safetensors.torch import load_file
from saccade import GRUPatchPredictor, SaccadeEngine, EngineConfig, make_backbone
backbone = make_backbone("hf:facebook/dinov2-small")
predictor = GRUPatchPredictor(backbone.embed_dim, backbone.n_patches, lr=1e-3)
predictor.load_state_dict(load_file(hf_hub_download(
"NagaYu/saccade-predictor", "predictor_dinov2-small.safetensors")))
cfg = EngineConfig(mode="saccade", budget_watts=0.02, fps=10.0)
engine = SaccadeEngine(backbone, cfg)
engine.predictor = predictor # warm start instead of learning from scratch
for frame in your_video_stream: # HxWx3 uint8 RGB
r = engine.step(frame)
print(r.n_encoded, r.encoded_fraction, r.cum_joules, r.budget_ok)
Install the package first: pip install git+https://github.com/NagaYu/saccade.
Self-supervised, no labels: targets are simply the vision encoder's own outputs.
The engine trains the predictor online on whichever patches the surprisal gate already paid
to encode; for this published checkpoint we additionally remove the gate's selection bias by
supervising on every patch offline (train_predictor_on_all=True, valid only when ground
truth is being computed anyway).
synthetic_walking_stream, seeds 0/2/3[/4/5])||prediction − true embedding||², SGD(momentum=0.9), lr 1e-3Reproduce with hf/export_model.py.
FLOPs × [J/FLOP]), not from a power meter.MIT