An interactive walkthrough for AI researchers

Alpamayo 2 Super

A 34-billion parameter foundation model for autonomous vehicles. It pairs a 32B vision-language backbone with a 2B diffusion action expert to produce reasoning text and sampled future trajectories from multi-camera driving video.

34B
total params
32B
VLM backbone
2B
diffusion expert
6×4
cameras × frames
64
future waypoints
10
diffusion steps

The two-stage idea in one sentence

The VLM reasons in text (Chain-of-Causation) and emits a future-trajectory anchor token; the diffusion expert then denoises a continuous action conditioned on the VLM's KV cache to produce the final 64-waypoint trajectory. Text and trajectory share one backbone, but the continuous action comes from a separate flow-matching head.

Architecture at a glance

6 cameras × 4 frames 1080×1920 RGB ego history: 16 wp Delta tokenizer history → 48 tokens 32B VLM Qwen-VL backbone prefill (prompt KV cache) generate CoC text <traj_future_start> anchor 2B Expert flow-matching denoiser action_in_proj (Fourier) transformer + VLM KV prefix action_out_proj → velocity unicycle decode 64 wp xyz KV cache anchor Stage 1: discrete text + anchor token Stage 2: continuous action denoising
Why this design?

A pure VLM can reason about a scene but quantizing trajectories into discrete tokens loses geometric precision. A pure diffusion head can output smooth trajectories but can't explain why. Alpamayo 2 Super gets both: interpretable Chain-of-Causation text and a high-precision continuous trajectory, sharing one set of visual features.

01The Data Contract

Every inference call starts by loading one clip from nvidia/PhysicalAI-Autonomous-Vehicles and reshaping it into a fixed ego-centric tensor contract. Understanding this contract is the key to everything downstream.

What one sample contains

image_frames
[7, 4, 3, 1080, 1920] 7 cameras × 4 frames, uint8 RGB
camera_indices
[0,1,2,3,4,5,6] — the canonical 7-camera ring
ego_history_xyz
[1,1,16,3] 16 waypoints ending at t₀ (1.6 s @ 10 Hz)
ego_history_rot
[1,1,16,3,3] rotation matrices per history step
ego_future_xyz
[1,1,64,3] 64 future waypoints (6.4 s @ 10 Hz) — ground truth
absolute_timestamps
[7,4] per-camera-frame capture times (µs)
Coordinate frame

Everything is transformed into the ego frame at t₀: xyz_local = R_t₀⁻¹ · (xyz_world − xyz_t₀). So history ends at the origin and the future is expressed relative to where the car is now. This is done in load_physical_aiavdataset() before the model ever sees the data.

Interactive · The 7-camera ring → 6-camera task profile
The source data has 7 cameras, but each task selects a 6-camera × 4-frame profile. Hover a camera to see its role. The trajectory task drops camera_rear_tele_30fov (id 4).

Temporal layout — the 4 context frames

Images are sampled at t₀ − 0.3 s, t₀ − 0.2 s, t₀ − 0.1 s, t₀. The final frame index is always t₀ (ego_t0_frame_idx = num_frames − 1). Crucially, the auto-labeling task never sees future camera frames — only context through t₀, even though the offline teacher that made its labels could look ahead.

load_physical_aiavdataset.pyL176-180
# Image timestamps: if num_frames=4, load at [t0-0.3s, t0-0.2s, t0-0.1s, t0] image_timestamps = np.array( [t0_us - (num_frames - 1 - i) * int(time_step * 1_000_000) for i in range(num_frames)], dtype=np.int64, )

02Trajectory Tokenization

Continuous xyz waypoints can't go straight into a VLM. Alpamayo 2 Super uses a delta tokenizer that quantizes per-step deltas into discrete bins, plus a set of special tokens that frame each modality in the conversation.

Delta encoding (history)

History is encoded as deltas ending at the origin. With pad_origin_at_beginning=True, a zero origin is prepended so 16 waypoints → 16 deltas. Each delta is normalized to [0, num_bins−1] and flattened: 16 × 3 axes = 48 tokens.

delta_tokenizer.pyL86-98
xyz = torch.nn.functional.pad(fut_xyz, [0,0,1,0,0,0]) xyz = xyz[:, 1:] - xyz[:, :-1] # deltas xyz = (xyz - ego_xyz_min) / (ego_xyz_max - ego_xyz_min) xyz = (xyz * (num_bins - 1)).round().long().clamp(0, num_bins-1) return einops.rearrange(xyz, "b n m -> b (n m)")

Two vocabularies

history_vocab
1000 bins → tokens <i0>…<i999>
future_vocab
3000 bins → tokens <i1000>…<i3999>
history tokens
48 per trajectory
future tokens
128 per trajectory
During inference

The future span is not filled with real tokens — it's left as the <|traj_future_start|> anchor. The discrete future tokens are only a training target; the real inference future comes from the diffusion expert.

Interactive · Quantize a trajectory into delta tokens
Drag waypoints in the ego frame (history ends at origin). The bar below shows the quantized delta-bin index for each step's x-delta. Change the bin count to see quantization granularity trade off against precision.
1000
history xyz
quantized x-delta bin

The conversation template

A system message sets the persona; the user message interleaves camera-labeled images and the history-trajectory span; the assistant message is where the model writes CoC and the future anchor. The order is configurable via components_order.

conversation.pybuild_conversation()
system
image
traj_history
CoC (generated)
traj_future anchor

03The VLM Backbone

A 32B Qwen-VL model does the heavy lifting: it prefills the prompt once, then autoregressively generates the Chain-of-Causation text and stops at the <|traj_future_start|> anchor — which hands control to the expert.

Shared-prefill generation

For trajectory sampling you may want n trajectories per prompt. Rather than re-prefilling each time, the prompt is prefilled once, the KV cache is batch_repeat_interleave'd, and generation runs across all copies in parallel. This is the _generate_with_shared_prefill path.

models/alpamayo2_super.pyL154-195
prefill_outputs = self.vlm.model(input_ids[:, :-1], use_cache=True, **vision_inputs) prompt_cache = prefill_outputs.past_key_values prompt_cache.batch_repeat_interleave(n_samples_total) vlm_outputs = self.vlm.generate(..., past_key_values=prompt_cache, ...)

Masking the trajectory tokens

While the VLM generates CoC text, it must never emit a discrete trajectory token (those belong to training only). A logits processor sets the entire trajectory-token span to −inf every step.

models/alpamayo2_super.pyL56-70
class MaskDiscreteTrajectoryLogitsProcessor: def __call__(self, input_ids, scores): scores[:, self.traj_token_offset : self.traj_token_offset + self.vocab] = -inf return scores

Stopping at the anchor

The real EOS for this generation is not the text EOS — it's <|traj_future_start|>. StopAfterEOS tracks per-sequence completion, then replace_padding_after_eos cleans up trailing pad so the expert sees a clean boundary.

models/expert_utils.pyfind_eos_offset()

The expert then starts reading the KV cache right after this anchor offset.

Interactive · Watch the VLM generate (simplified)
Step through a stylized generation. Notice the trajectory-token logits are masked to −inf, so the sampler can only pick text tokens until it emits the anchor — at which point the expert takes over.
ready

04The Action Space — a Unicycle

The expert doesn't output waypoints directly. It outputs normalized acceleration and curvature per waypoint — 64 × 2 values — which are integrated through a unicycle kinematic model to recover xyz. This keeps trajectories physically realizable.

The control inputs

action shape
[64, 2] = (n_waypoints, 2)
action[…,0]
normalized acceleration (de-normalize: a·accel_std + accel_mean)
action[…,1]
normalized curvature (de-normalize: κ·curv_std + curv_mean)
accel bounds
[−9.8, 9.8] m/s²
curvature bounds
[−0.33, 0.33] rad/m
dt
0.1 s (10 Hz)

From actions to trajectory — the forward map

Given an initial velocity v₀ (estimated from history), the model integrates: velocity from acceleration, heading from curvature × velocity, and position from velocity × heading. This is the action_to_traj path used at inference.

action_space/unicycle_accel_curvature.pyL307-389
velocity = v0 + cumsum(accel * dt) # (..., N+1) theta = cumsum(kappa * velocity[:-1] * dt + kappa * accel * dt²/2) x = cumsum(v·cos(θ)·dt/2 + v_next·cos(θ_next)·dt/2) # trapezoid y = cumsum(v·sin(θ)·dt/2 + v_next·sin(θ_next)·dt/2)
Inverse for training

During training the ground-truth future trajectory is converted into actions via traj_to_action (solving constrained least-squares for smooth accel/curvature), then the expert learns to denoise those actions. So the expert lives in action space, not xyz space.

Interactive · Drive the unicycle yourself
Adjust a constant acceleration and curvature and watch the resulting 64-waypoint trajectory (6.4 s). This is exactly what the expert's per-waypoint outputs get integrated through. The car starts at the origin heading +x with v₀ from history.
+1.5
+0.020
8.0
ego at t₀
predicted trajectory
heading arrows

05Flow Matching & the Expert

The expert is a 2B transformer trained with flow matching (a continuous-time cousin of diffusion). At inference it integrates a learned vector field from pure noise to a clean action using fixed-step Euler updates.

Training target

Sample a timestep t ~ Beta(1.5, 1), build noisy action x_t = t·x + (1−t)·noise, and regress the network to the constant-velocity target v = x − noise.

diffusion/flow_matching.pyL151-184
t = beta_dist.sample((batch,)) # Beta(1.5,1) t = 0.999 - t * 0.999 noisy_x = t * x + (1 - t) * noise target = (x - noise) loss = mse_loss(target, pred)

Inference sampling

Start from x ~ N(0, I) at t=0 and take num_inference_steps (default 10) Euler steps to t=1: x ← x + dt · v_θ(x, t). That's it — no DDPM noise injection, just straight-line flow integration.

diffusion/flow_matching.pyL106-149
x = randn(batch, *x_dims) * temperature time_steps = linspace(0, 1, steps + 1) for i in range(steps): v = step_fn(x=x, t=time_steps[i]) x = x + (time_steps[i+1] - time_steps[i]) * v

What's inside one step_fn call?

Each Euler step runs the full expert transformer with the VLM's KV cache as a prefix. The noisy action + timestep are projected to tokens, attend to the cached prompt, and a linear head reads out the predicted velocity.

models/alpamayo2_super.pyL353-369
def step_fn(x, t): future_token_embeds = self.expert.action_in_proj(x, t) # Fourier + MLP expert_outputs = self.expert.expert( inputs_embeds=future_token_embeds, position_ids=position_ids, past_key_values=prompt_cache, # VLM KV prefix attention_mask=attention_mask, use_cache=True) prompt_cache.crop(prefill_seq_len) # don't grow the cache return self.expert.action_out_proj(expert_outputs.last_hidden_state)
KV-cache cropping

The expert reuses the VLM's prefilled KV cache but crops it back to prefill_seq_len after each step so the cache doesn't accumulate expert tokens across diffusion iterations. Every step sees the same prompt prefix.

Interactive · Flow matching in 1D
A toy 1-D flow: noise (t=0) flows to a bimodal data distribution (t=1). Drag the timestep slider to see samples travel along the learned vector field. More Euler steps = smoother, more accurate trajectories.
0.00
10
noise (t=0)
current samples
data target (t=1)

06The Full Inference Pipeline

Click through each stage of sample_trajectories_from_data() — the single entry point that turns one PhysicalAI-AV clip into a predicted trajectory and Chain-of-Causation text.

1
Load clip & select task profile
load_physical_aiavdataset() fetches the 7-camera ring + egomotion, transforms to ego-t₀ frame. select_task_input(data, "trajectory") drops to 6 cameras × 4 frames.
inference_smoke.py · input_profiles.py
2
Build conversation & tokenize
helper.prepare_model_inputs() builds the chat messages (system + images + history span + prompt), applies the chat template, and runs the Qwen processor to get input_ids + pixel_values.
helper.py · conversation.py
3
Fuse trajectory tokens
fuse_traj_tokens() replaces the history placeholder span with real encoded history tokens (48). The future span stays as placeholders (training-only).
models/utils.py · L154-177
4
VLM shared prefill
Run the VLM backbone once on the prompt (minus the last token) to build the KV cache, then batch_repeat_interleave it for n = num_traj_samples × num_traj_sets parallel generations.
_generate_with_shared_prefill()
5
Generate CoC + anchor (masked)
Autoregressive sampling with top_p=0.98, temp=0.6. The trajectory-token logits are masked to −inf; text EOS is masked; generation stops at <|traj_future_start|>.
MaskDiscreteTrajectoryLogitsProcessor · StopAfterEOS
6
Find EOS offset & build expert positions
find_eos_offset() locates where each sequence emitted the anchor; build_expert_pos_ids_and_attn_mask() builds MRoPE position IDs and a 4D attention mask so expert tokens attend only to the right prefix.
models/expert_utils.py
7
Expert flow-matching sampling
10 Euler steps. Each step: action_in_proj (Fourier + MLP) → expert transformer with VLM KV prefix → action_out_proj → velocity. Cache cropped back each step.
diffusion/flow_matching.py · _euler()
8
Decode actions → trajectory
action_space.action_to_traj() integrates the sampled (accel, curvature) through the unicycle model from the last history pose → pred_xyz, pred_rot shaped [B, n_sets, n_samples, 64, 3].
action_space/unicycle_accel_curvature.py
9
Extract text & visualize
extract_text_tokens() decodes the CoC text per trajectory. plot_inference_result() renders the 6-camera grid + BEV with predicted vs. ground-truth trajectory and computes minADE/minFDE.
models/token_utils.py · visualization.py
Interactive · Pipeline data-flow
Each card above lights up a stage in the diagram below. The shapes show how tensor dimensions evolve from raw video to a 64-waypoint trajectory.

Sample output (real model run)

From the checked-in outputs/blog.json — clip 030c760c… at t0_us=5100000:

Chain-of-Causation: "Nudge left to avoid the cones on the right side." Trajectory metrics: min_ade_m = 1.51 fde_m = 4.38 pred_xyz_shape = [1, 1, 1, 64, 3] image_frames_shape = [6, 4, 3, 1080, 1920] projection_available = true

07How It's Trained

Training is split across two objectives that share the VLM backbone: a discrete next-token loss on text + future-trajectory tokens, and a flow-matching MSE loss on continuous actions in the expert.

VLM forward — two masked losses

The training forward() fuses real history and future tokens into the prompt, runs the VLM, then splits the loss: a future_traj cross-entropy over the future-token span, and an others cross-entropy over everything else (CoC text, special tokens). Each is weighted via config.loss_weights.

models/alpamayo2_super.pyL197-243
traj_mask = (labels >= future_id0) & (labels < future_id0 + future_vocab) future_traj_loss = compute_next_token_loss(logits, labels, traj_mask) * w_future labels[traj_mask] = IGNORE_INDEX # don't double-count other_loss = compute_next_token_loss(logits, labels, labels != IGNORE_INDEX) * w_other return Alpamayo2SuperModelOutput(loss=future_traj_loss + other_loss)

Expert forward — flow-matching loss

The expert converts the GT future into actions (traj_to_action), adds noise via construct_training_data, runs the denoiser, and regresses to the velocity target x − noise.

models/expert.pyL98-161
action = self.action_space.traj_to_action(...) noisy = self.diffusion.construct_training_data(action) expert_embeds = self.action_in_proj(noisy.noisy_x, noisy.timesteps) out = self.expert(inputs_embeds=expert_embeds, past_key_values=vlm_outputs.past_key_values) pred = self.action_out_proj(out.last_hidden_state) loss = self.diffusion.compute_loss_from_pred(noisy, pred)

Are VLM and expert co-trained?

Controlled by config.cotrain_expert_vlm. When False (the release default), the VLM is frozen (requires_grad_(False)) and only the expert trains against the cached prompt features. When True, gradients flow through both.

models/alpamayo2_super.pyL136-139
if self.config.enable_expert: self.expert = ExpertModel._from_config(...) if not self.config.cotrain_expert_vlm: self.vlm.requires_grad_(False)
Key training insight

The discrete future-trajectory tokens act as an auxiliary teaching signal for the VLM — they force the backbone to understand future motion — while the actual deployed trajectory comes from the expert's continuous actions. So the VLM "knows" trajectories in two representations, but only the continuous one ships.

08Beyond Trajectories — Text Tasks

The same VLM backbone drives three pure-text AV tasks (no expert involved). Each uses a different prompt template and camera profile, all routed through generate_text().

Meta-action

Generates CoC text followed by a structured block of Longitudinal, Lateral, Lane actions. Stops at <|traj_future_start|>; split_cot_and_meta_action() separates the two halves.

cameras [0,1,2,3,5,6]

Auto-labeling

Outputs a 4-field JSON: critical_components_analysis, ego_vehicle_motion_analysis, trajectory_analysis, chain_of_causation. Conditions on 4 context frames through t₀ + a future ego trajectory.

cameras [0,1,2,3,5,6]

VQA & grounding

Free-form scene Q&A plus bounding-box grounding, using the no-special-token prompt format. Uses the only profile that includes camera_rear_tele (id 4).

cameras [0,1,2,3,4,5]

Shared machinery

All three tasks reuse the same logits processor (mask trajectory tokens), the same chat-template pipeline, and the same extract_text_tokens() decoder. The only differences are the components_prompt list and whether a future trajectory is fed in as context.

text_tasks.pyL352-437
outputs = model.vlm.generate(**tokenized_data, generation_config=generation_config, logits_processor=logits_processor) generated_tokens = sequences[:, prompt_length:] extracted = extract_text_tokens(model.tokenizer, generated_tokens) # task-specific parsing: split_cot_and_meta_action / parse_auto_labeling_json
One model, many tasks

This is what makes Alpamayo 2 Super a foundation model: a single set of weights serves trajectory prediction, scene reasoning, auto-labeling, and visual grounding — all from the same 32B backbone, with the 2B expert bolted on only when continuous motion is needed.

09Code Map — Where Things Live

A reference for navigating the repository. Each entry is the file (and key symbol) you'd open to understand or modify that part of the system.

Model definition

wrapper
models/alpamayo2_super.pyAlpamayo2Super
expert
models/expert.pyExpertModel
action proj
models/action_in_proj.py — Fourier + MLP
config
config.pyAlpamayo2SuperConfig

Denoising & actions

flow matching
diffusion/flow_matching.py
unicycle
action_space/unicycle_accel_curvature.py
delta tokens
models/delta_tokenizer.py
token utils
models/token_utils.py

Inference entry points

CLI smoke
inference_smoke.py
data loader
load_physical_aiavdataset.py
input profiles
input_profiles.py
text tasks
text_tasks.py

Prompting & viz

conversation
chat_template/conversation.py
helper
helper.pyprepare_model_inputs
viz API
visualization.py + viz_utils.py
expert utils
models/expert_utils.py

Key hyperparameters (release config)

history_vocab_size
1000
future_vocab_size
3000
tokens_per_history_traj
48 (= 16 wp × 3 axes)
tokens_per_future_traj
128
n_waypoints
64
dt
0.1 s
num_inference_steps
10 (Euler)
train_timestep_sampler
Beta(1.5, 1.0)
top_p / temperature
0.98 / 0.6
expert_non_causal_attention
True