diff --git a/docusaurus/docs/systems/omy/resources/technical_story/technical_story.mdx b/docusaurus/docs/systems/omy/resources/technical_story/technical_story.mdx
new file mode 100644
index 00000000..d00e6029
--- /dev/null
+++ b/docusaurus/docs/systems/omy/resources/technical_story/technical_story.mdx
@@ -0,0 +1,24 @@
+---
+title: Technical Story
+sidebar_label: Technical Story
+---
+
+# Technical Story
+
+Welcome to the OMY Technical Story section, where we share the AI technologies and techniques that
+bring the OMY robot to life.
+
+Building a capable robot isn't just about assembling hardware — it's about giving it the intelligence
+to perform real-world tasks effectively. OMY leverages modern AI techniques like imitation learning and
+vision-language-action (VLA) models to learn from demonstrations and act in unstructured, real-world
+environments.
+
+Each story below documents a real project end to end: what we built, what worked, what didn't, and
+enough detail to reproduce it or learn something new. If you are adding your own OMY technical story,
+use these as a template — a clear problem, the method, honest results, and the commands to run it.
+
+## Stories
+
+- [The VLA Showdown: Four 2026 Models on the OMY-F3M](./vla_showdown) — GR00T N1.7, MolmoAct2, VLA-JEPA,
+ and FastWAM head-to-head on a long-horizon plate-organizing task, and the deployment engineering that
+ made one of them actually work.
diff --git a/docusaurus/docs/systems/omy/resources/technical_story/vla_showdown.mdx b/docusaurus/docs/systems/omy/resources/technical_story/vla_showdown.mdx
new file mode 100644
index 00000000..3a22f36d
--- /dev/null
+++ b/docusaurus/docs/systems/omy/resources/technical_story/vla_showdown.mdx
@@ -0,0 +1,1037 @@
+---
+title: "The VLA Showdown: Four 2026 Models on the OMY-F3M"
+description: "GR00T N1.7, MolmoAct2, VLA-JEPA, and FastWAM go head-to-head on one arm and one long-horizon task, and why strong offline numbers were not enough."
+sidebar_label: "VLA Showdown"
+---
+
+{/* Figures/animations live in static/img/systems/omy/resources/technical_story/vla_showdown/. GIFs animate inline. */}
+
+# The VLA Showdown: Four 2026 Models on the OMY-F3M
+
+LeRobot has recently released four new 2026-generation Vision-Language-Action (VLA) and world models (**GR00T N1.7**, **MolmoAct2**, **VLA-JEPA**, and **FastWAM**) and trained them on the
+teleoperation-collected dataset, and ran them on a pick-and-organize long-horizon task on a OMY-F3M arm. This section explains what each model is, how they compare, and the deployment.
+
+
+
+
Six standard metrics, each normalized so the outer edge is the ideal (task success 100%, MAE and end-effector error 0, gripper accuracy 100%, smoothness = the human demo, latency 0). GR00T N1.7 is strongest overall but still short of the ideal edge.
+
+
+
+
+**Main Results.** On this three-plate organizing task, **only GR00T N1.7 completed the task** (60% end-to-end with ensembling). MolmoAct2 was nearly as accurate *offline* yet grabbed only the first plate; VLA-JEPA and FastWAM did not grasp at all.
+
+
+---
+
+## Full demo
+
+
+
+
Full demo video
+
YouTube embed coming soon
+
+
+---
+
+## 1. Task and Data
+
+The arm starts at a fixed home pose. In front of it are three plates: gray (#1), blue (#2), and green
+(#3), and a rack of slots. The task is to place plate #1 in the first (left-most) slot, plate #2 in the
+third slot, and plate #3 in the fifth. Each plate is a separate subtask with its own language
+instruction. A subtask is not a simple pick-and-place, though; the arm must **grab the plate at sufficient
+depth, tilt it, and insert it** into the slot.
+
+
+
+
The OMY-F3M arm and the three-plate / wooden-slot layout used throughout the study.
+
+
+**Dataset.** All four models were fine-tuned on the same dataset with 100 episodes,
+184,459 frames at 30 fps, three RGB cameras, and a 7-DOF action/state space (six arm joints plus the
+gripper joint `rh_r1_joint`). Actions are **absolute joint positions**, not deltas.
+
+**Data Collection.** Every episode was recorded by teleoperating the
+OMY-F3M through [Cyclo Intelligence](/docs/systems/omy/imitation_learning/imitation_learning).
+
+
+
+
+
Recording an episode.
+
+
+
+
Reviewing the episodes.
+
+
+
+**Three cameras.** A wrist RealSense (a close, moving view of the gripper and plate), a top-down camera
+(`camera1`, which sees all plates and slots at once), and a side external camera (`camera2_external`),
+so the policy sees roughly three sides of the workspace.
+
+Camera placement matters because it decides how much of the environment the robot can actually perceive.
+Thinking of the workspace as a cube, the three cameras must jointly cover enough of its faces for the
+policy to localize the plates and slots; here it sees the top, front, and right.
+
+
+
+
+
Camera layout: wrist RealSense, overview (camera1), and side external (camera2_external).
+
+
+
+
Camera1 covers the top, cam_wrist the front, camera2_external the right; the bottom, back, and left have no camera.
+
+
+
+During inference the cameras are *published by the robot* and only *subscribed to* by our tool, so the
+first step is to bring the camera drivers up on the robot side.
+
+
+Camera bring-up commands (User computer side)
+
+Each camera runs in its own terminal, inside the `open_manipulator` Docker container. In **every** terminal, first enter the container and point Zenoh at the robot (replace `robot_ip` with the arm's IP):
+
+```bash
+cd open_manipulator
+./docker/container.sh enter
+
+# run these in every terminal/window
+export RMW_IMPLEMENTATION=rmw_zenoh_cpp
+export ZENOH_CONFIG_OVERRIDE='transport/shared_memory/enabled=true;mode="client";connect/endpoints=["tcp/robot_ip:7447"]'
+```
+
+Two driver packages are used: `usb_cam` for the two USB cameras and `realsense2_camera` for the wrist RealSense. Bring each camera up in its own terminal.
+
+Launch one `usb_cam` node per USB camera:
+
+```bash
+ros2 run usb_cam usb_cam_node_exe --ros-args \
+ --remap __node:=usb_cam_NODE \
+ -p video_device:=DEVICE \
+ -r image_raw:=CAM_KEY/image_raw \
+ -r image_raw/compressed:=CAM_KEY/image_raw/compressed \
+ -r camera_info:=CAM_KEY/camera_info
+```
+
+Fill in the three blanks:
+
+- `NODE` is a unique node name so two `usb_cam` processes do not clash, for example `camera1` or `external2`.
+- `DEVICE` is the V4L2 path for that camera, for example `/dev/video6`. List the candidates with `v4l2-ctl --list-devices`. One physical camera often exposes several `/dev/videoN` nodes, and the one that streams is usually the lowest-numbered capture node.
+- `CAM_KEY` is the topic namespace, and it must be the exact key the dataset was recorded under (here `camera1` and `cam_external2`). The subscriber matches by this name, so a typo lands the frame nowhere and the policy conditions on a blank image.
+
+Bring the wrist camera up with the RealSense launch file:
+
+```bash
+ros2 launch realsense2_camera rs_launch.py \
+ camera_name:=CAM_KEY \
+ enable_depth:=false \
+ depth_module.color_profile:=WIDTHxHEIGHTxFPS \
+ depth_module.enable_auto_exposure:=false \
+ depth_module.exposure:=EXPOSURE_US \
+ depth_module.gain:=GAIN
+```
+
+Here `CAM_KEY` follows the same rule as above (the wrist key is `cam_wrist`), and `WIDTHxHEIGHTxFPS` is the color stream profile, for example `424x240x60`. Depth is disabled because the policy only reads RGB. `EXPOSURE_US` and `GAIN` set a fixed manual exposure (in microseconds) and sensor gain: auto-exposure is turned off so the wrist image does not brighten and darken as the arm moves, which would drift the pixels away from what training saw. Start from `11000` and `25` and adjust until the live image matches the recorded look.
+
+The concrete values used in this study are:
+
+| Camera | `CAM_KEY` | Driver | Device or profile |
+| --- | --- | --- | --- |
+| Overview | `camera1` | usb_cam | `/dev/video6` |
+| Side | `cam_external2` | usb_cam | `/dev/video0` |
+| Wrist | `cam_wrist` | realsense2_camera | `424x240x60` |
+
+If a USB camera looks too dark or its color drifts, reset its controls to a neutral baseline. This is only needed when the live image does not match training:
+
+```bash
+for d in DEVICE_A DEVICE_B; do
+ v4l2-ctl -d $d --set-ctrl \
+ brightness=128,contrast=128,saturation=128,sharpness=128,gain=0,backlight_compensation=0,white_balance_automatic=1,auto_exposure=3
+done
+```
+
+List the same `/dev/videoN` devices you launched in place of `DEVICE_A` and `DEVICE_B`. The values recenter brightness, contrast, saturation, and sharpness to mid-range, drop the extra gain and backlight compensation, and re-enable automatic white balance and exposure (`auto_exposure=3` is the V4L2 automatic mode).
+
+
+
+
+
+**Camera keys must match training.** On the subscriber side, each camera's `width`/`height` and its `image_keys` value must match **training**, not the live stream. A wrong key means the image lands nowhere and the policy will predict on a blank frame.
+
+
+
+---
+
+## 2. Four Models
+
+Each model is a Vision-Language-Action or World Model policy: it reads the three camera images, the joint state, and
+(if it uses language) the instruction, and outputs a short *chunk* of future joint commands. What
+differs is the visual/language backbone, how the action "head" turns understanding into motion, and how
+much of the network is actually trained.
+
+Also, note that, compared to regular VLAs that directly translate vision or language data as actions, world models
+internally simulates how the physical environment will change over time before deciding what to do.
+
+### 2.1 GR00T N1.7
+
+GR00T N1.7 is NVIDIA's Isaac GR00T, ported into LeRobot. Its backbone is **Cosmos-Reason2-2B**, a Qwen3-VL-family
+vision-language model; features are read from an intermediate layer. The action head is a
+**flow-matching cross-attention Transformer** (a 32-layer "DiT"): rather than emitting joints directly,
+it starts from noise and refines a candidate action chunk over a few integration steps, cross-attending
+to the vision-language features. Chunk length is 40 steps, ~3.1 B parameters.
+
+It is the **only model
+whose backbone stays frozen** — only the projector, the vision-language LayerNorm, the action head, and
+the top four LLM layers are trained (~2.1 B), because a full fine-tune on 100 episodes would erode the
+pretrained priors.
+
+
+
+
GR00T N1.7: Cosmos-Reason2-2B backbone + flow-matching DiT action head.
+
+
+### 2.2 MolmoAct2
+
+MolmoAct2 is AllenAI's Action Reasoning Model, with three parts: a **SigLIP-style vision Transformer**, an
+**OLMo-class 7B language model**, and a separate **flow-matching action expert** that cross-attends to
+the vision-language context. It can also emit discrete "FAST" action tokens, but we use the continuous
+flow-matching head.
+
+Chunk length 30, ~5.4 B parameters, **fully fine-tuned.** Only the input
+word-embedding table (~0.4 B) is frozen, so ~5.0 B are trained. It is the largest *trained* model here.
+
+
+
+
MolmoAct2: SigLIP ViT + OLMo LLM + flow-matching action expert.
+
+
+### 2.3 VLA-JEPA
+
+VLA-JEPA is a world-model. A **frozen Qwen3-VL-2B** fuses the images and the instruction;
+the hidden states of special action tokens condition a **flow-matching DiT-B action head** (16 layers).
+Separately, a **V-JEPA2** video encoder is meant to predict the *latent embedding of the next frame*,
+the Joint-Embedding Predictive Architecture idea of imagining the future in feature space, not pixels.
+
+In this study the world-model branch is switched off (it is auto-disabled when the Qwen backbone is
+frozen -- using the preconfigured world model but not fine-tuned), so only the ~155 M-parameter action head is trained. Chunk length 30.
+
+
+
+
+
VLA-JEPA (frozen Qwen3-VL + V-JEPA2 next-latent predictor + DiT flow head), and its teacher-forcing vs. rollout loss (immediate-step versus multi-step autoregressive forecasting).
+
+
+### 2.4 FastWAM
+
+FastWAM is the largest world model here. Its backbone is **Wan2.2-TI2V-5B**, a video-generation Transformer arranged as a
+Mixture-of-Transformers: a 5 B-parameter *video expert* and a smaller *action expert* share attention,
+alongside a video VAE and a **UMT5-XXL** text encoder. The paper's premise is whether a world model
+must actually *imagine* future video at test time, or whether that step can be skipped.
+
+The action head
+is again flow-matching; action horizon 32. ~6 B trainable (full fine-tune), and it **does not fit on a
+24 GB GPU** — forcing the CPU-offload deployment discussed in §5.
+
+
+
+
FastWAM: Wan2.2-5B video DiT + action expert + UMT5-XXL text encoder + VAE.
+
+
+
+What "latent state" and "flow-matching" mean
+
+Every action head above reads a **latent state** and produces motion by **flow-matching**.
+
+**Latent state.** The vision-language backbone does not output joint angles. It squeezes the camera images and the instruction into one compact vector, the *latent state*, that captures what is in front of the arm and what it was asked to do, as features rather than pixels. Everything downstream reads only this vector.
+
+**Flow-matching.** The action head does not predict the motion in one shot. It starts from pure random noise and cleans it up over a few small steps, and each step nudges the guess a little closer to a real action, until the noise has become a full (or continuous) chunk of joint commands. The network only has to point the direction of each nudge:
+
+$$
+a_{\text{next}} \;=\; a_{\text{now}} \;+\; (\text{small step})\times(\text{direction the network predicts}).
+$$
+
+Running that from noise to a clean chunk is the whole sampling process. More steps mean smaller, finer nudges, so the chunk comes out smoother and more accurate but takes longer to compute. That step count is the `--flow-steps` knob tuned per model in §5.
+
+
+
+
+
+
+
+### 2.5 At a glance
+
+| | GR00T N1.7 | MolmoAct2 | VLA-JEPA | FastWAM |
+|---|---|---|---|---|
+| Backbone | Cosmos-Reason2-2B | SigLIP + OLMo 7B | Qwen3-VL-2B (frozen) | Wan2.2-5B video |
+| Action head | flow-matching DiT | flow-matching | flow-matching DiT-B | flow-matching |
+| World model | none | none | V-JEPA2 (frozen) | Wan video expert |
+| Chunk (steps) | 40 | 30 | 30 | 32 |
+| Total params | ~3.1 B | ~5.4 B | ~2.5 B | ~6 B |
+| Trained params | ~2.1 B | ~5.0 B | ~155 M | ~6 B (full) |
+| Fits 24 GB? | yes | no | yes | no |
+
+
+
+
+
Total vs. trained parameters. VLA-JEPA trains only its ~155 M head; GR00T about two-thirds of its network (backbone frozen); MolmoAct2 and FastWAM are larger fine-tunes.
+
+
+---
+
+## 3. Training and Offline Accuracy
+
+Each model was trained with its authors' most-recommended or publicly verified configs. The training hardware or GPU count was chosen on size and availability. One thing modified was VLA-JEPA's learning rate ($1\times10^{-4}$, published); it diverged into NaNs on two
+runs, so we used $3\times10^{-5}$.
+
+| | GR00T N1.7 | MolmoAct2 | VLA-JEPA | FastWAM |
+|---|---|---|---|---|
+| Regime | partial (head + top-4 LLM) | full FT | head-only (Qwen frozen) | full FT |
+| Final ckpt | 57643 | 20000 | 230574 | 165000 |
+| Hardware | 2×A100-80GB | 4×H100-80GB | 1×RTX 4090 | 2×A100-80GB |
+| Wall-clock | ~9 h | ~6 h | ~36 h | ~36 h |
+| Global batch | 32 | 32 | 8 | 8 |
+| Precision | bf16 | bf16 | bf16 | bf16 |
+
+
+
+
Training curves (loss vs. step) exported from Weights & Biases.
+
+
+**Offline accuracy (held-out MAE).** Before running inference, each checkpoint is scored by replaying
+held-out episodes and measuring the mean absolute error between commanded and demonstrated joints.
+
+| Model | Held-out arm MAE (rad) | End-effector error | Source (best ckpt) |
+|---|---|---|---|
+| **GR00T N1.7** | **0.017** | **7.5 mm** | ckpt 57643, held-out |
+| MolmoAct2 | 0.019 | 9.9 mm | ckpt 12000, held-out |
+| FastWAM | 0.034 | 24.8 mm | ckpt 105000, held-out |
+| VLA-JEPA | 0.038 | 21.2 mm | ckpt 184000, held-out |
+
+
+
+
Held-out arm MAE as training proceeds. GR00T and MolmoAct2 improve sharply over their (short) schedules; VLA-JEPA and FastWAM stay essentially flat over much longer ones. Read the trend of each line rather than comparing absolute heights; a "step" is not comparable across different batch sizes.
+
+
+
+
+**Offline accuracy is not task success.** MolmoAct2 is nearly as accurate as GR00T *offline* (0.019 vs. 0.017 rad). As §5 shows, that did not translate to the robot at all. Low offline MAE is a necessary standard, but does not promise success.
+
+
+
+---
+
+## 4. Inference Smoothing
+
+Every model emits a *chunk* of future joint commands each tick. Turning those chunks into motion the
+arm can actually execute, smoothly and to full grasp depth, is important. This section covers core smoothing strategies. (The other smoothing methods
+(seam-interpolation, Savitzky-Golay, RTC) are in the [Appendix](#12-appendix-other-smoothing-methods--token-pruning).) GR00T is used, as an example, to show the efficacy of smoothing methods.
+
+The full inference path is: **observe → pre-process → predict →
+post-process → smooth → clamp → send**.
+
+
+
+
Each tick, one observation becomes a short chunk of future joint commands, smoothed and rate-limited, published as a single trajectory the controller splines at 400 Hz. The arm is homed and approach-ramped once before the loop begins.
+
+
+
+The pipeline in detail: nodes, solvers, and helpers
+
+**Nodes.**
+
+| Process (node) | What it does |
+| --- | --- |
+| `omy_deploy` policy loop (Python) | Each tick: reads an observation, runs the model, then smooths, clamps, and publishes the predicted chunk as one trajectory. |
+| `lerobot_robot_ros2_zenoh` bridge | Sends the `JointTrajectory` to the controller and relays `joint_states` and camera frames back as the observation. |
+| `JointTrajectoryController` (ros2_control, C++) | Fits cubic splines through the trajectory points and drives the actuators at 400 Hz in hard real time. |
+
+One iteration of the loop runs seven stages:
+
+1. **Observe:** `check_observation` reads `joint_states` and the two camera frames, failing loud on a stale or wrong-shaped frame.
+2. **Pre-process:** resize and normalize the images, assemble the state, and tokenize the plate instruction, exactly as in training.
+3. **Predict:** the policy returns a chunk of future joint commands (16 to 32 waypoints).
+4. **Post-process:** un-normalize to radians and snap the gripper to open or closed.
+5. **Smooth:** the chosen mode (whole-chunk, ensembling, seam-blend, and so on) stitches overlapping chunks into one continuous stream.
+6. **Clamp:** bound every waypoint to the joint position and velocity limits.
+7. **Send:** `send_trajectory` publishes the chunk as one multi-point `JointTrajectory` with per-waypoint velocities.
+
+**Solvers.** There is no learned controller in the loop. The one solver is the `JointTrajectoryController`: it fits a cubic spline between consecutive waypoints, so a coarse chunk becomes a smooth 400 Hz command. Each waypoint carries a position and a velocity (a central difference of the chunk), and matching those velocities is what bridges each waypoint smoothly instead of leaving a corner.
+
+
+
+
Linear interpolation (grey) leaves a corner at every waypoint. The cubic spline (navy) passes through the same points and matches the attached velocity (gold tangent), so the 400 Hz command stays smooth.
+
+
+**Helpers.** Three pieces sit on top of the bridge: `send_trajectory` does the whole-chunk publish, `install_timed_send_action` times that publish to the right chunk offset (the grab-depth fix in §7), and `check_observation` is the fail-loud reader from stage 1.
+
+
+
+### 4.1 Whole Chunks
+
+A `JointTrajectoryController` interpolates between the points of a trajectory with splines, in C++, on a
+hard real-time loop at 400 Hz. If instead we stream one one-point goal per Python tick, the arm sprints
+to each target in a few milliseconds and then idles waiting for the next command: accelerate, stop,
+accelerate, stop, many times a second. This may cause audible grinding.
+
+
+
+
Point-by-point (grey) makes the arm sprint to each goal and idle until the next, a stair-stepped command the actuators grind through. Publishing the whole chunk (navy) lets the controller spline all the points at 400 Hz, so the motion is continuous.
+
+
+
+send_trajectory core (Python)
+
+```python
+# core of send_trajectory: one multi-point JointTrajectory with per-waypoint velocities (abridged)
+step = dt / speed # seconds between chunk points, time-scaled by --speed
+vel = central_difference(chunk) / step # per-waypoint velocity -> cubic-spline continuity
+vel[:, gripper] = 0.0 # gripper is near-binary; a huge velocity would be rejected
+points = [JointTrajectoryPoint(positions=q, velocities=v, time_from_start=Duration((i+1)*step))
+ for i, (q, v) in enumerate(zip(chunk, vel))]
+publisher.publish(joint_names=joints, points=points)
+```
+
+
+
+The commands below use two placeholders. `CKPT` is the path to a trained checkpoint (the name differs
+per model and run), and `TASKS` is the per-plate instruction string. For example:
+
+```bash
+CKPT=outputs/train/groot_plates100_10ep/checkpoints/057643/pretrained_model
+TASKS="Place gray plate #1 in the first slot.||Place blue plate #2 in the third slot.||Place green plate #3 in the fifth slot."
+```
+
+### 4.2 More Integration Steps
+
+A dense recorder (logging commanded vs. actual joints at ~250 Hz) split the judder cleanly: about
+**77%** of the velocity dips happen *inside* a chunk, and **23%** *at* its boundaries. The 77% is not
+inherent policy jitter; rather, it is due to the flow-matching head as it builds a chunk by taking `N` small integration steps from
+noise toward the final motion.
+
+
+
+
Coarse (N = 4) vs. converged (N = 32) flow integration: fewer steps leave the chunk rippled; more steps track the target cleanly.
+
+
+For example, GR00T's default `N=4` is a quite coarse, so the chunk's *values* carry
+high-frequency ripple — and that ripple *is* the within-chunk dip. Raising denoising steps `N` to 16-32
+(`--flow-steps`) integrates the flow more finely and removes the ripple **at the source**. The inference time runs
+a little longer, but it is minimal next to the vision backbone (per-inference 132 → 135 ms).
+
+
+
+
+
Within-chunk command jerk over three GR00T decodes. 4 → 16 roughly halves the jerk; 16 → 32 adds nothing (the solver has converged). Savitzky-Golay filter drives it lower still, but by low-passing the command, which blunts the genuine grasp/place peaks.
+
+
+```bash
+# Run it: more denoising (flow-integration) steps, in chunk mode (for example).
+omy-infer CKPT --chunk-mode --flow-steps 32
+```
+
+### 4.3 Temporal Ensembling
+
+Ensembling gave the best motion. Instead of running one chunk then the next, it re-infers on *every*
+tick and, for each upcoming timestep, averages all the overlapping chunk predictions that cover it. A
+memoryless policy re-plans from a slightly different (lagging) pose each tick, so the chunks disagree a
+little; averaging cancels this out.
+
+
+
+
Overlapping chunk predictions are blended by an age weight into one smooth command; the coefficient c controls how strongly the older, committed reach is favored — and that sets grab depth.
+
+
+The one catch is **grab depth**: averaging damps the reach peak, so a naive average grabs short. The
+blend weights each prediction by its age, `w = exp(-c·(a_max − age))`. The *oldest* overlapping
+prediction is the committed reach (the tail of an old chunk); the *newest* sits near the current pose.
+The single coefficient `c` sets how strongly the older reach is favored.
+
+Across a sweep of `c`, **`c = 0.15` gave the best success rate**, so it is the default.
+
+
+What each `c` value does (and why it stays non-negative)
+
+Higher `c` favors the oldest, committed plan. Only `c ≥ 0` is valid; reach is a percentage of the committed 0.566 rad.
+
+| `c` | Oldest plan's share | Weighting | Reach | On the arm |
+| --- | --- | --- | --- | --- |
+| `< 0` | n/a | inverted, favors the newest plan without bound | n/a | disallowed: re-commands the current pose, never reaches depth |
+| `0` | 20% | uniform, every plan counts equally | 96.7% | touches but cannot hold |
+| `0.15` *(deployed)* | 26% | tips to the oldest, still tracks newer plans | 98.4% | near-full depth, smooth |
+| `0.5` | 43% | dominated by the oldest plan | 100% | full depth, but lags the scene and ignores corrections |
+
+
+
+
+There are some additions to improve grabbing:
+`--grab-lookahead` (read a few steps ahead during the grab), `--grasp-dwell` (hold at depth as the
+gripper closes), `--grip-hold` (debounce the release); `--dwell-break` handles the ~2.5 s home pause so
+the arm actually starts.
+
+Chunk mode and ensembling track the demonstration almost identically over a full reach; they differ at
+the re-plan seams, where chunk mode dips and ensembling stays continuous:
+
+
+
+
Chunk mode vs. GR00T ensembling, decoded open-loop along held-out episode 5: commanded angle for the six arm joints, demonstration dashed, re-plan seams marked. Both run the same flow-integration (denoising) steps.
+
+
+
+
+
The same episode magnified at a seam (joint4, joint5). Both modes use the same integration (denoising) steps; chunk mode still dips away from the demonstration at the seam, while GR00T ensembling stays continuous.
+
+
+Despite the flag name, `--groot-ensemble` is policy-agnostic: age-weighted averaging works for all four policies, not just GR00T.
+
+
+
+**`--speed` scales time, not values.** The actions are absolute joint angles, so scaling the *values* would drive the arm toward its zero pose. `--speed 0.6` plays the same trajectory over more wall-clock. A velocity/acceleration clamp (derived from the dataset) is on by default, so deployed motion is never faster than the demonstrations.
+
+
+
+---
+
+## 5. Deployment Results
+
+Success is measured per plate and split into **grab** (did the arm pick the plate up) and **place** (did
+it seat the plate). "Task" is the full three-plate completion; place can never exceed grab. Each rate is
+first-try success over **10 trials per plate** with the robot.
+
+
+
+
+
Model
P1 grab
P1 place
P2 grab
P2 place
P3 grab
P3 place
Task
+
+
+
+
GR00T N1.7 (chunk)
100
100
100
100
50
40
40
+
GR00T N1.7 (ensemble)
100
100
100
100
60
60
60
+
MolmoAct2
20
0
0
0
0
0
0
+
VLA-JEPA
0
0
0
0
0
0
0
+
FastWAM
0
0
0
0
0
0
0
+
+
+
+GR00T completed plates 1 and 2 every time. Plate 3 is harder because it requires extending the arm further,
+and the ensemble mode lifted it from 50/40 to 60/60. MolmoAct2 managed only a partial first grab;
+VLA-JEPA and FastWAM did not grasp the first plate at all.
+
+
+Why ensembling moves GR00T from 40% to 60%
+
+Plates 1 and 2 are 100% in both chunk and ensembling, so the change is plate 3, and it comes down to **grabbing**.
+
+
+
+
At the same instant (orange), the oldest chunk is already at a deep step while the newest restarts shallow. The red arrows are the lag: each chunk's deepest step is discarded and the next chunk begins again near the arm's trailing pose.
+
+
+**Why chunk mode lands short.** Every chunk is planned from where the arm is right now, and the arm
+always trails the command. So at any instant an older chunk is already deep into its reach, while a
+brand-new chunk is still on its first steps, near the arm's trailing position.
+
+Chunk mode always jumps to
+the newest chunk, so it keeps restarting from behind and the command settles slightly shallow. This does not wash out with time nor chunk length either, because each chunk is replaced after a few steps due to the late-responding robot, so its deep steps are discarded before the arm ever reaches them and the replacement starts from the same trailing pose.
+
+**Why the age weight *helps* it.** That shortfall sits almost entirely in the newest chunks. Giving
+older chunks more weight lets the chunk that already reached full depth lead the blend, so the command
+keeps its depth and still follows the live scene. The old chunk keeps contributing after the point where chunk mode would have thrown it away, so depth carries across each seam instead of resetting.
+
+However, it mitigates rather than fixes: the weighting can only re-use plans the policy already produced, so it cannot add reach that was never predicted. Leaning harder on the old plans gets us the last of the depth but may ignore fresh grab corrections. Also, ensembling is nevertheless still an averaging method, mixing chunks and dampening overall actions (with constant, low `c`). Due to these two, this is maybe why plate 3 is at 60% and not 100%.
+
+A variable `c` scheduler may fix this issue, with a custom curriculum, but that is set as future work.
+
+
+
+
Sketch of the idea, not an evaluated result. A fixed c has to serve the whole subtask at once. A scheduled c could stay low while the arm approaches and still tracks the scene, rise into the grasp where the committed depth matters, then relax again for the lift and place.
+
+
+**Smoother motion helps too.** The blend is continuous across the seams, so the arm stops decelerating
+and re-accelerating at every re-plan (because of robot latency, following newest chunk). That removes the start-stop jerk of chunk mode, which matters most
+as the gripper closes.
+
+
+
+**Inference latency** (one `predict_action_chunk` on a single RTX 4090; "per action" divides by chunk
+length). All are at 30 Hz.
+
+
+
+
+
Model
Latency (ms/chunk)
Chunk
Per action (ms)
Notes
+
+
+
+
GR00T N1.7 (chunk)
76
40
1.90
n/a
+
GR00T N1.7 (ensemble)
76
40
1.90
no additional latency cost
+
MolmoAct2
137
30
4.57
n/a
+
VLA-JEPA
74
30
2.47
n/a
+
FastWAM
240
32
7.5
5B DiT needs CPU offload
+
+
+
+Only **GR00T N1.7** with ensembling completes the task best, but plate 3 needs to be trained more. **MolmoAct2** moves okay and grabbed the
+first plate about one run in five but never finished; more training doesn't seem to improve. **VLA-JEPA** is strong offline yet fails to grasp on the arm. **FastWAM** produces sensible chunks but its 5 B video DiT does not fit on 24 GB (the
+UMT5 text encoder stays on the CPU), carries the highest per-chunk latency, and also fails.
+
+---
+
+## 6. Limitations
+
+The table summarizes where each model falls short on this setup.
+
+| Model | Main limitations |
+|---|---|
+| GR00T N1.7 | Plate 3 only ~50-60%; occasionally shows dwell at initial position, needs `--dwell-break`; requires ≥24 GB GPU. |
+| MolmoAct2 | ~5B params; training needs 4×80 GB; 12k checkpoint grasps but never completes; more training showed no clear convergence. |
+| VLA-JEPA | Low offline error; flow head sensitive to learning rate; world model imagination dropped at inference. |
+| FastWAM | ~6B params; highest latency, CPU offload required; native action space mismatched to a joint-space robot. |
+
+---
+
+## 7. LeRobot v0.6.0
+
+All four models were trained and evaluated on [**LeRobot v0.6.0**](https://github.com/huggingface/lerobot/tree/v0.6.0). The deployment code is separate and
+additive (§7); the changes to the LeRobot fork itself are policy-side. All four reported models were
+*already present*; the changes below fix some issues when using OMY with LeRobot.
+
+- **NaN-gradient guard** (`lerobot_train.py`): non-finite gradients poison a whole run (VLA-JEPA:
+ 248/873 `NaN` tensors in every later checkpoint), and norm-clipping does not stop them. Skip the step.
+- **Mixed-resolution cameras** (`processor_groot.py`): 640×480 externals and 424×240 wrist cannot stack
+ into one tensor. Upscale smaller views first.
+- **FastWAM cameras + proprio** (`fastwam/*`): a 3-camera width that is not a multiple of 16 crashes the
+ Wan VAE (split in units of 16); 7-dim proprio ≠ pretrained dim (pad/truncate).
+- **Inverted gripper** (`processor_vla_jepa.py`): the gripper mapping was flipped for the OMY
+ joint-position convention (gripper MAE 1.58). Flip the sign.
+- **Backbone LR** (`configuration_vla_jepa.py`): add `qwen_lr` for a separate backbone learning rate.
+
+
+Three of the fixes, in code (NaN guard, mixed-resolution cameras, inverted gripper)
+
+```python
+# NaN/Inf-gradient guard (lerobot_train.py): a single non-finite step poisons every later checkpoint.
+_skip_step = not torch.isfinite(grad_norm)
+if _skip_step:
+ logging.warning(f"non-finite grad_norm ({grad_norm.item()}); skipping optimizer step")
+ optimizer.zero_grad(set_to_none=True)
+else:
+ optimizer.step()
+```
+
+```python
+# Mixed-resolution camera stacking for GR00T (processor_groot.py).
+# 480x640 externals + 240x424 wrist cannot stack into one (B,T,V,H,W,C) tensor.
+shapes = set(c.shape[2:4] for c in cams)
+if len(shapes) > 1:
+ tgt_h = max(s[0] for s in shapes); tgt_w = max(s[1] for s in shapes)
+ cams = [c if c.shape[2:4] == (tgt_h, tgt_w) else resize_view(c, tgt_h, tgt_w) for c in cams]
+```
+
+```python
+# Inverted-gripper fix for VLA-JEPA (processor_vla_jepa.py).
+# Targets are {0 = closed/low, 1 = open/high}; emit +1 for OPEN, -1 for closed.
+# The original starVLA mapping (1 - 2*(x>thr)) INVERTED this -> gripper MAE 1.58, ~60% agreement.
+a[..., gripper_dim] = 2.0 * (a[..., gripper_dim] > threshold).float() - 1.0
+```
+
+
+
+### 7.1 The deployment package
+
+[`lerobot_robot_ros2_zenoh`](https://github.com/ROBOTIS-GIT/lerobot_robot_ros2_zenoh) is ROBOTIS's
+open-source bridge that lets a LeRobot policy talk to a ROS 2 robot over the
+[Zenoh](/docs/systems/omy/quick_start_guide/zenoh_communication) transport: it carries camera images and
+joint states in, and joint commands out.
+
+Our deployment package, `omy_deploy`, is additive: it runs the policy and the smoothing on top of that
+bridge, so LeRobot policies can drive the OMY robot without changing the bridge itself.
+
+Read the diagram as one control loop. `omy_deploy` predicts a chunk and hands the whole chunk to the
+bridge (`send_trajectory`); the bridge forwards it as a `JointTrajectory` to the
+`JointTrajectoryController`, which splines it onto the arm at 400 Hz. The arm's `joint_states` and camera
+frames flow back through the bridge, and `omy_deploy` reads them (`get_observation`) to plan the next chunk.
+
+```mermaid
+flowchart LR
+ P["omy_deploy policy + smoothing"] -- "send_trajectory (chunk)" --> Z["lerobot_robot_ros2_zenoh plugin"]
+ Z -- "JointTrajectory" --> C["JointTrajectoryController splines at 400 Hz"]
+ C --> A["OMY-F3M arm"]
+ A -- "joint_states + 3 total cameras" --> Z
+ Z -- "get_observation" --> P
+```
+
+---
+
+## 8. Deployment Code
+
+The deployment code runs against `lerobot_robot_ros2_zenoh`. Reproducing this needs only the
+repositories below: LeRobot, the ROS2/Zenoh plugin, and its SDK, plus our one additional package
+`omy_deploy`. The `pip install -e` step pulls in the Python libraries each repo declares (PyTorch,
+Hugging Face, and so on), so beyond those repos there is no additional code.
+
+```bash
+# one-time: LeRobot (local install), the ROS2/Zenoh plugin, and the SDK
+git clone https://github.com/huggingface/lerobot.git
+git clone https://github.com/ROBOTIS-GIT/lerobot_robot_ros2_zenoh.git
+git clone https://github.com/ROBOTIS-GIT/zenoh_ros2_sdk.git
+pip install -e lerobot -e zenoh_ros2_sdk -e lerobot_robot_ros2_zenoh
+
+# the deployment package -> into the lerobot directory
+cd lerobot
+git clone https://github.com/da-dawit/lerobot-omy-tests.git _dep
+mv _dep/omy_deploy _dep/omy-infer . && rm -rf _dep
+```
+
+```bash
+# generalized: set your dataset root and the three per-plate instructions
+export DATASET=path/to/your/dataset
+export TASKS="||||"
+
+omy-infer CKPT --repo-id your-username/your-dataset --root "$DATASET" \
+ --groot-ensemble 0.15 --flow-steps 32 --grab-lookahead 24 --grip-close-delay 0 \
+ --grasp-dwell 1.0 --grip-hold 9 --dwell-break --speed 0.6 --seconds 300 --tasks "$TASKS"
+```
+
+---
+
+```bash
+# example, our best run: GR00T N1.7, ckpt 57643
+export DATASET=datasets/plates100_subtask
+export TASKS="Place gray plate #1 to the first (very left) slot.||Place blue plate #2 to the third slot, a slot after the gray plate.||Place green plate #3 to the fifth slot, a slot after the blue plate."
+
+omy-infer outputs/train/groot_plates100_10ep/checkpoints/057643/pretrained_model \
+ --repo-id dawity/plates100_subtask --root "$DATASET" \
+ --groot-ensemble 0.15 --flow-steps 32 --grab-lookahead 24 --grip-close-delay 0 \
+ --grasp-dwell 1.0 --grip-hold 9 --dwell-break --speed 0.6 --seconds 300 --tasks "$TASKS"
+```
+The `--repo-id` and `--root` do not replay any episodes. Inference reads only the dataset's *metadata*:
+the normalization statistics including mean, standard deviation (used to normalize the live observation and to un-normalize the predicted
+action back to radians) and the feature schema (the camera keys and the joint layout).
+
+Point them at the
+dataset the checkpoint was trained on, set the checkpoint to yours, and see `omy-infer --help` for the
+full argument list.
+
+---
+
+## 9. Practical Tips
+
+
+
+
〰️
+
Run inference in a smoothing mode. Use chunk or ensemble, never point-by-point (robot-side command below).
+
+
+
🎲
+
Collect 100+ varied episodes. Change background, lighting, and distractors between takes so the policy keys on the plates, not the background.
+
+
+
🖥️
+
Budget heavy GPUs. The 5–6 B models need ≥80 GB; a 24 GB card is inference-only, and FastWAM still needs CPU offload.
+
+
+
📷
+
Place cameras well. Cover enough scene and depth to localize the plates and slots.
+
+
+
🐢
+
Teleoperate slowly. Jerky or too quick demos may not extract quality data.
+
+
+
+
+The smoothing command run on the robot
+
+On the robot, bring the arm controller up in its smoothed `ros2_control` mode before inference. This loads the smoothed
+control profile, so the servos ease between waypoints instead of snapping to each one:
+
+```bash
+ros2 launch open_manipulator_bringup omy_f3m_follower_ai.launch.py ros2_control_type:=omy_f3m_smooth
+```
+
+
+
+---
+
+## 10. Conclusion
+
+Four 2026 VLA and world models were trained on the same 100-episode, three-camera OMY-F3M dataset and
+evaluated on a three-plate organizing task through a shared deployment pipeline. **GR00T N1.7** achieved
+the best overall performance — the lowest offline error, the fastest practical inference, and the only
+successful completion of the task on the real robot. Motion quality was further improved by inference
+refinements (more flow integration, velocity continuity, cross-fading, temporal ensembling), which
+produced smoother trajectories and more reliable grasps.
+
+---
+
+## 11. Resources
+
+- [LeRobot](https://github.com/huggingface/lerobot): training/inference framework
+- [ROBOTIS lerobot_robot_ros2_zenoh](https://github.com/ROBOTIS-GIT/lerobot_robot_ros2_zenoh): the ROS2/Zenoh transport
+- [**omy_deploy**: OMY-F3M deployment toolkit](https://github.com/da-dawit/lerobot-omy-tests): the `omy-infer` launcher and the LeRobot-fork change log
+- [GR00T N1.7](https://github.com/NVIDIA/Isaac-GR00T) · [MolmoAct2](https://github.com/allenai/molmoact2) · [FastWAM](https://github.com/yuantianyuan01/FastWAM) · [VLA-JEPA](https://github.com/ginwind/VLA-JEPA/) · [LeRobot Policies](https://huggingface.co/docs/lerobot/en/index)
+- Reference (3 Plates) Dataset: [`dawity/plates100_subtask`](https://huggingface.co/datasets/dawity/plates100_subtask)
+
+---
+
+## 12. Appendix: Other Smoothing Methods and Token Pruning
+
+The two techniques in §4 (whole-chunk publish, flow-steps, ensembling) carried our best run. The
+following are the remaining smoothing methods and a separate attention study.
+
+
+All five deployment modes
+
+
+Section 4 showed only chunk mode and ensembling. Here are all five modes together (vanilla, chunk, RTC, seam-interpolation, and ensembling) on the same rollout.
+
+
+
+
All five deployment modes on a live GR00T rollout (held-out episode 5): commanded angle for the six arm joints, demonstration dashed, re-plan seams marked.
+
+
+
+
+
The same episode magnified at a seam (joint4, joint5): vanilla stair-steps; chunk mode and RTC bump away from the demonstration at the seam (RTC less than chunk); seam-interpolation and ensembling stay continuous.
+
+
+The three unexplained individual methods:
+
+
+Seam-interpolation: cross-fade the boundaries
+
+
+The 23% of dips at chunk boundaries come from stitching consecutive chunks. The smoother resamples each
+chunk from 30 Hz up to the 400 Hz control rate, then *cross-fades* the first few steps of a new chunk
+out of the point the old chunk was still commanding. Because the arm lags the command, each fresh chunk
+(planned from the lagging pose) starts a few hundredths of a radian away from where the last one left off, and that step is what the servo lurches on.
+
+The cross-fade eases across it so the commanded position
+is *continuous*. It is adaptive per joint: on a sharp reversal (grab → lift) following the old command
+would blunt the turn, so those joints snap to the fresh plan while the rest keep the full cross-fade. A
+companion flag, `--carry-vel`, hands the controller the arm's live velocity so it splines through the
+seam with momentum instead of re-accelerating from rest.
+
+
+
+
The old chunk (blue) is still commanding when the new chunk (amber) arrives a step away; the blend (green) eases across the seam so the command is continuous.
+
+
+```bash
+# Run it: cross-fade N steps at each seam, and carry velocity through it.
+omy-infer CKPT --chunk-mode --seam-blend 6
+```
+
+
+
+
+Savitzky-Golay: a low-pass filter
+
+
+A Savitzky-Golay filter fits a low-order polynomial (order `P`) to a sliding window (width `W`) and
+replaces each sample with the fitted value. Unlike a moving average it keeps the height and location of
+peaks — which matters because the grasp and place *are* the sharp peaks we must not flatten.
+
+Appliednaively it has nothing to fit at a chunk's start and re-introduces a seam; the *seam-aware* variant
+prepends the previous chunk's tail so the fit *bridges* the boundary. It is a cheap backstop: enough
+integration steps remove the same ripple at the source without risking the peaks, so once `--flow-steps`
+is up the window can be small or dropped.
+
+
+
+
The local polynomial fit (green) rides through the noise while keeping the grasp peak; a moving average (rose) rounds the peak off.
+
+
+```bash
+# Run it: Savitzky-Golay smoothing (window W, polynomial order P).
+omy-infer CKPT --chunk-mode --chunk-savgol 13 --chunk-savgol-poly 2
+```
+
+
+
+
+Real-time chunking (RTC): why it is off
+
+
+RTC re-generates each chunk so its values are *inpainted* to agree with the previous chunk's tail, making
+the plan continuous across the seam. In principle that attacks the dip; in practice, on this arm, it does
+not help.
+
+The OMY controller takes *position-only* commands and re-linearizes at every seam regardless of
+how continuous the values were (RTC's continuity is thrown away before the motors see it) while its
+per-chunk re-generation adds one more source of frame-to-frame variation.
+
+```bash
+# RTC (retained for policies that support it; left off for GR00T)
+omy-infer CKPT --chunk-mode --rtc --rtc-horizon 8
+```
+
+
+
+
+
+
+Best command per model (smoothed)
+
+```bash
+# set the dataset and the three per-plate instructions once
+export DATASET=datasets/plates100_subtask
+export TASKS="Place gray plate #1 to the first (very left) slot.||Place blue plate #2 to the third slot, a slot after the gray plate.||Place green plate #3 to the fifth slot, a slot after the blue plate."
+
+# GR00T N1.7: tuned ensemble (the best run)
+omy-infer GROOT_CKPT --repo-id dawity/plates100_subtask --root "$DATASET" \
+ --groot-ensemble 0.15 --flow-steps 32 --grab-lookahead 24 --grasp-dwell 1.0 \
+ --grip-hold 9 --dwell-break --speed 0.6 --tasks "$TASKS"
+
+# GR00T N1.7: chunk mode
+omy-infer GROOT_CKPT --repo-id dawity/plates100_subtask --root "$DATASET" \
+ --chunk-mode --execute-steps 45 --grip-hold 5 --dwell-break --speed 0.6 --tasks "$TASKS"
+
+# MolmoAct2 (ckpt 012000)
+omy-infer MOLMO_CKPT --repo-id dawity/plates100_subtask --root "$DATASET" \
+ --groot-ensemble 0.15 --flow-steps 16 --grip-hold 8 --dwell-break --speed 0.6 --tasks "$TASKS"
+
+# VLA-JEPA (last ckpt): emits its plan a few frames late, so drop the lag
+omy-infer JEPA_CKPT --repo-id dawity/plates100_subtask --root "$DATASET" \
+ --chunk-mode --seam-blend 6 --carry-vel --lag-skip 6 --grip-hold 5 --dwell-break --speed 0.6 --tasks "$TASKS"
+
+# FastWAM (ckpt 165000, auto CPU-offload)
+omy-infer FASTWAM_CKPT --repo-id dawity/plates100_subtask --root "$DATASET" \
+ --chunk-mode --execute-steps 10 --flow-steps 16 --chunk-savgol 13 --chunk-savgol-poly 2 \
+ --grip-hold 8 --dwell-break --speed 0.4 --tasks "$TASKS"
+```
+
+
+
+
+Token pruning and attention (GR00T only)
+
+
+**Was token pruning used in the reported runs? No.** It is a GR00T-specific background-robustness
+technique we investigated on its own; it was not enabled in any reported deployment, and the other three
+models do not support it (`--prune-bg` is GR00T-only). It is documented here because it may be useful in the future.
+
+**The problem.** When the background changes (a new backdrop, or different lighting), GR00T's attention can
+drift off the plates and slots onto the background, and the arm produces smooth but *wrong* motion with
+no error raised. Each camera image becomes an 8×8 grid of 64 tokens; three cameras give 192 image tokens
+the diffusion head cross-attends to.
+
+**Pixel masking does not work.** Replacing the background *pixels* (blur, gray, or solid black) does not
+move where GR00T attends: attention is set by the learned position/query structure, not pixel content.
+
+
+
+
Left: input with the background set to solid black. Right: attention on that input, still concentrated on the black region; editing pixels does not move attention.
+
+
+**Token pruning seems to work.** Masking the background image *tokens* out of the cross-attention (weight forced
+to zero) does move attention. For fixed cameras the background tokens are identified geometrically.
+Pruning **51 of 192** tokens (external + top cameras; the moving wrist camera left whole) moved attention
+onto the workspace: the wall hotspot vanished and attention landed on the plates and rack. The predicted
+action changed by a mean |Δ| of **0.0147 rad** (max 0.0596, shoulder), on the order of GR00T's own
+offline MAE (0.0166 rad), a real effect, not numerical noise. No retraining, no weight edits.
+
+
+
+
Top: baseline attention per camera. Bottom: after pruning the background tokens, attention moves off the wall and onto the plates and slot rack.
+
+
+```bash
+# GR00T-only background-token pruning (never used in the reported runs)
+omy-infer CKPT --groot-ensemble 0.15 --flow-steps 32 --prune-bg
+```
+
+**Limits.** The model was not trained with pruned tokens, so pruning is mildly off-distribution and can
+help or destabilize. The attention shift is necessary but not sufficient, and task success must be
+confirmed on the arm. The geometric ROI also assumes a fixed camera; the wrist camera moves, so it is
+left unpruned (a moving foreground mask, e.g. SAM2 or a depth threshold, would be needed).
+
+
diff --git a/docusaurus/sidebars/omy.ts b/docusaurus/sidebars/omy.ts
index 00cc222d..13033628 100644
--- a/docusaurus/sidebars/omy.ts
+++ b/docusaurus/sidebars/omy.ts
@@ -108,6 +108,14 @@ const omySidebar: SidebarsConfig[string] = [
items: [
{type: 'doc', id: 'systems/omy/resources/open_source', label: 'Open Source'},
{type: 'doc', id: 'systems/omy/resources/release_notes', label: 'Release Notes'},
+ {
+ type: 'category',
+ label: 'Technical Story',
+ link: {type: 'doc', id: 'systems/omy/resources/technical_story/technical_story'},
+ items: [
+ {type: 'doc', id: 'systems/omy/resources/technical_story/vla_showdown', label: 'VLA Showdown'},
+ ],
+ },
],
},
{
diff --git a/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/animations/Ensembling.gif b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/animations/Ensembling.gif
new file mode 100644
index 00000000..50958a1b
Binary files /dev/null and b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/animations/Ensembling.gif differ
diff --git a/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/animations/FlowSteps.gif b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/animations/FlowSteps.gif
new file mode 100644
index 00000000..d0a5379a
Binary files /dev/null and b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/animations/FlowSteps.gif differ
diff --git a/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/animations/SavGol.gif b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/animations/SavGol.gif
new file mode 100644
index 00000000..025c1f59
Binary files /dev/null and b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/animations/SavGol.gif differ
diff --git a/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/animations/SeamInterpolation.gif b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/animations/SeamInterpolation.gif
new file mode 100644
index 00000000..27d2c4f9
Binary files /dev/null and b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/animations/SeamInterpolation.gif differ
diff --git a/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/c_schedule.png b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/c_schedule.png
new file mode 100644
index 00000000..0ff6d103
Binary files /dev/null and b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/c_schedule.png differ
diff --git a/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/camera_coverage.png b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/camera_coverage.png
new file mode 100644
index 00000000..fda428dc
Binary files /dev/null and b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/camera_coverage.png differ
diff --git a/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/cameras.png b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/cameras.png
new file mode 100644
index 00000000..7af197ff
Binary files /dev/null and b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/cameras.png differ
diff --git a/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/chunk_vs_point.png b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/chunk_vs_point.png
new file mode 100644
index 00000000..3c13b3af
Binary files /dev/null and b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/chunk_vs_point.png differ
diff --git a/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/cubic_spline.png b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/cubic_spline.png
new file mode 100644
index 00000000..e0dec6cf
Binary files /dev/null and b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/cubic_spline.png differ
diff --git a/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/cyclo_recorder.png b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/cyclo_recorder.png
new file mode 100644
index 00000000..ffa4935a
Binary files /dev/null and b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/cyclo_recorder.png differ
diff --git a/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/cyclo_replay_viewer.png b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/cyclo_replay_viewer.png
new file mode 100644
index 00000000..9e400899
Binary files /dev/null and b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/cyclo_replay_viewer.png differ
diff --git a/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/fastwam_arch.png b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/fastwam_arch.png
new file mode 100644
index 00000000..89d8c98f
Binary files /dev/null and b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/fastwam_arch.png differ
diff --git a/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/fig_pixelmask_fail.png b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/fig_pixelmask_fail.png
new file mode 100644
index 00000000..46c59572
Binary files /dev/null and b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/fig_pixelmask_fail.png differ
diff --git a/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/gr00t.png b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/gr00t.png
new file mode 100644
index 00000000..105c1f3c
Binary files /dev/null and b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/gr00t.png differ
diff --git a/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/groot_token_prune_live.png b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/groot_token_prune_live.png
new file mode 100644
index 00000000..34e36789
Binary files /dev/null and b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/groot_token_prune_live.png differ
diff --git a/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/jepa.png b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/jepa.png
new file mode 100644
index 00000000..11e9e5b6
Binary files /dev/null and b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/jepa.png differ
diff --git a/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/jepaloss.png b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/jepaloss.png
new file mode 100644
index 00000000..b73f5d16
Binary files /dev/null and b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/jepaloss.png differ
diff --git a/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/jerk.png b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/jerk.png
new file mode 100644
index 00000000..acd4ec8e
Binary files /dev/null and b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/jerk.png differ
diff --git a/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/modes.png b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/modes.png
new file mode 100644
index 00000000..158bbbc6
Binary files /dev/null and b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/modes.png differ
diff --git a/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/modes_ie.png b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/modes_ie.png
new file mode 100644
index 00000000..1bdebe72
Binary files /dev/null and b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/modes_ie.png differ
diff --git a/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/modes_zoom.png b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/modes_zoom.png
new file mode 100644
index 00000000..c1fd7f9c
Binary files /dev/null and b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/modes_zoom.png differ
diff --git a/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/modes_zoom_ie.png b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/modes_zoom_ie.png
new file mode 100644
index 00000000..398491ae
Binary files /dev/null and b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/modes_zoom_ie.png differ
diff --git a/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/molmoact2.png b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/molmoact2.png
new file mode 100644
index 00000000..25e4742f
Binary files /dev/null and b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/molmoact2.png differ
diff --git a/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/params.png b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/params.png
new file mode 100644
index 00000000..c44bf949
Binary files /dev/null and b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/params.png differ
diff --git a/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/pipeline.png b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/pipeline.png
new file mode 100644
index 00000000..1acf673b
Binary files /dev/null and b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/pipeline.png differ
diff --git a/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/radar_4model.png b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/radar_4model.png
new file mode 100644
index 00000000..aad09256
Binary files /dev/null and b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/radar_4model.png differ
diff --git a/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/robot setting.png b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/robot setting.png
new file mode 100644
index 00000000..55a51562
Binary files /dev/null and b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/robot setting.png differ
diff --git a/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/robot_setting.png b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/robot_setting.png
new file mode 100644
index 00000000..55a51562
Binary files /dev/null and b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/robot_setting.png differ
diff --git a/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/scaling.png b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/scaling.png
new file mode 100644
index 00000000..f3b6be92
Binary files /dev/null and b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/scaling.png differ
diff --git a/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/wandbloss.png b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/wandbloss.png
new file mode 100644
index 00000000..a811b539
Binary files /dev/null and b/docusaurus/static/img/systems/omy/resources/technical_story/vla_showdown/figures/wandbloss.png differ