Training and deployment

September 6, 2026 ยท View on GitHub

OpenWAM composes each run from Hydra YAML. The top-level configuration is configs/train.yaml; it selects the model and dataloader, while each model group selects its component backbones. The trainer saves the resolved configuration with each checkpoint.

1. Choose and assemble a model

Architecture

Select the model group in configs/train.yaml, then edit the architecture block in the selected model file:

# configs/train.yaml
defaults:
  # Model and data groups
  - model: dual_system       # choose dual_system, single_system, or tri_system
  - dataloader: robotwin     # choose a supported dataloader group
# Choose one model group
model=dual_system
model=single_system
model=tri_system

All architecture options are under architecture:. Hydra exposes the composed values as model.architecture.* for CLI overrides.

# configs/model/tri_system.yaml
architecture:
  # Architecture identity
  framework: tri_system                 # single_system, dual_system, or tri_system
  variant: joint_self_attn               # dual: joint_self_attn | joint_cross_attn | idm; single: vanilla | moe

  # Token geometry and inputs
  action_dim: 80                         # action-token width; match dataloader output
  use_proprioception: true               # require a proprio field in each sample
  state_dim: 80                          # proprioception width

  # Attention visibility
  attention_mask_mode: mutual            # mutual | action_sees_video | video_sees_action | isolated
  video_attention_mask_mode: first_frame_causal  # first_frame_causal | per_frame_causal | bidirectional

  # Bridge and memory controls
  bridge_layers: null                    # explicit layers; null uses bridge_interval
  bridge_interval: 1                     # use every layer when set to 1
  mot_checkpoint_mixed_attn: true        # checkpoint mixed attention to reduce GPU memory

  # Variant-specific options
  detach_bridge: false                   # DualSystem joint_cross_attn only
  idm_video_cond_noise_prob: 0.5         # IDM video-condition noise probability
  understanding_expert:                  # TriSystem understanding stream
    dim: 512                              # projected VLM feature width
    ffn_dim: 2048
    vlm_projector_type: mlp3x_silu        # linear or mlp{N}x_silu

idm uses teacher-forced inverse dynamics and two-stage inference. tri_system adds a frozen Qwen3-VL stream. Implementations and registries are in openwam/model/architectures/. The selected model file composes the video, action, and (for TriSystem) VLM groups through its defaults list.

Video backbone

Choose a Hydra group from configs/model/video_backbone/; implementations are in openwam/model/video_backbone/.

# Choose one video group
model/video_backbone=wan22_ti2v_5b
model/video_backbone=wan21_vace_1_3b
model/video_backbone=wan21_i2v_14b_480p
model/video_backbone=cosmos_predict25_2b
model/video_backbone=cosmos3_edge

Edit the selected group file, for example wan22_ti2v_5b.yaml, cosmos_predict25_2b.yaml, or cosmos3_edge.yaml:

# configs/model/video_backbone/<selected_group>.yaml
# Identity and weights
name: wan22_ti2v_5b                 # registry key; use the selected backbone name
model_path: /path/to/video_backbone_checkpoint  # weights or bundle root

# Initialization and scheduler
from_scratch: false                 # rebuild Wan projections around an encoder
shift_video: 5.0                    # flow-matching scheduler shift
# Cosmos3 additionally defines fps, prompt_duration_template, and freeze_und.

The selected backbone supplies video geometry to the architecture and action backbone. Keep model_path consistent with the assets in Assets Preparation.

VLM backbone

TriSystem selects a VLM group from configs/model/vlm_backbone/; implementation and registry code are in openwam/model/vlm_backbone/.

# configs/model/vlm_backbone/qwen3_vl_2b.yaml
# Identity and weights
name: qwen3_vl_2b                       # Hydra group and registry key
checkpoint_path: /path/to/qwen3_vl_2b    # pretrained VLM directory

# Loading and sequence length
load_pretrained: true                    # load checkpoint weights
max_length: 512                          # processor text limit

Override with the merged path model.vlm_backbone.*:

# Select the group
model/vlm_backbone=qwen3_vl_2b

# Override its checkpoint
model.vlm_backbone.checkpoint_path=/path/to/qwen3_vl_2b

Action backbone

DualSystem and TriSystem construct separate_action_dit.yaml; SingleSystem constructs shared_action_backbone.yaml. Implementations are in openwam/model/action_backbone/, and there is no separate action-backbone registry.

# configs/model/action_backbone/separate_action_dit.yaml
# ActionDiT dimensions
dim: 1024                        # ActionDiT hidden width
ffn_dim: 4096                    # ActionDiT feed-forward width
shift_action: 5.0                # action scheduler shift
# num_layers, video_dim, num_heads, and attn_head_dim resolve from the video backbone

---
# configs/model/action_backbone/shared_action_backbone.yaml
# Shared action decoder dimensions
action_decoder_hidden_dim: 1024  # shared action decoder width
expert_ffn_dim: 4096             # MoE expert width

Select a group with model/action_backbone=separate_action_dit or model/action_backbone=shared_action_backbone; keep its output aligned with architecture.action_dim and the dataloader.

Visual encoder and S-VAE

Edit the encoder group file under configs/model/video_backbone/encoder/; the selected encoder fields are merged into model.video_backbone.encoder. When from_scratch=true, select an external encoder under configs/model/video_backbone/encoder/: wan22_vae, flux2_vae, dinov3, or vjepa21. DINOv3, FLUX.2, and V-JEPA require this path; the encoder metadata supplies the latent geometry.

# Select an external encoder when rebuilding the video backbone
model:
  video_backbone:
    from_scratch: true
    encoder:
      name: dinov3                    # wan22_vae, flux2_vae, dinov3, or vjepa21
      model_path: /path/to/visual_encoder

      # Optional S-VAE reducer
      svae_path: /path/to/svae_checkpoint
      svae_target_dim: 48              # reducer output width

S-VAE is an offline feature reducer. Collect features with the complete data chain, train the reducer, then point svae_path at the resulting file. The scripts and reducer implementation are under scripts/svae_train/ and openwam/model/video_backbone/encoder/svae/.

Dataloader selection and loading

Built-in registry names are:

robotwin, robodojo, robocasa_gr1, ebench, libero, robocasa365, vlabench

The matching dataloader fields are in configs/dataloader/, for example configs/dataloader/libero.yaml. Readers are under openwam/dataloader/ and configs under configs/dataloader/. Select one with Hydra:

# Select the dataloader and dataset
bash scripts/train.sh \
  dataloader=libero \
  dataloader.dataset_dir=<dataset_dir_path> \
  dataloader.unify_action=true \
  dataloader.num_frames=33 \
  dataloader.video_stride=4

Each registered class implements from_config(config, split) and returns window samples. The trainer keeps samples as a list of dictionaries. num_frames is the raw state/action window length; the action horizon is num_frames - 1. Video is subsampled by video_stride, so the standard 33/4 setting yields 9 video frames. Camera layout, transforms, normalization, action maps, and masks belong to the dataloader.

2. Start training

Fresh, from-scratch, and fine-tuning runs

With a pretrained video backbone, a fresh run loads the selected components and trains the unfrozen modules from step zero. from_scratch rebuilds the Wan2.2 DiT input/output projections around an external encoder.

# Model and dataloader
bash scripts/train.sh model=dual_system dataloader=robotwin \
  dataloader.dataset_dir=<dataset_dir_path> \
  training.output_path=<output_dir_path> \
  training.batch_size=1 \
  training.max_steps=20

These training fields come from configs/train.yaml; CLI overrides take precedence over the file. Use training.finetune_ckpt_path to warm-start weights into a new run. Use training.resume_ckpt_path only to continue a run that saved full Accelerate state:

training:
  # Start a new run from existing weights
  finetune_ckpt_path: /path/to/base_checkpoint

  # Continue a run with its saved optimizer and scheduler state
  resume_ckpt_path: null
  save_full_states_for_resume: true

The two paths are mutually exclusive. Fine-tuning starts at step 0 in a new output directory; resume restores optimizer, scheduler, RNG, and model state in the original run.

Memory, precision, and distributed launch

The distributed and memory fields below are defined in configs/train.yaml. scripts/train.sh invokes torchrun and detects visible GPUs.

training:
  # Precision and distributed optimizer
  mixed_precision: bf16                 # bf16, fp16, or no
  zero_stage: 2                         # ZeRO stage; stage 2 also shards gradients
  gradient_accumulation_steps: 1        # optimizer update every N micro-batches

  # Activation memory
  use_gradient_checkpointing: true      # recompute activations during backward
  use_gradient_checkpointing_offload: false  # offload checkpointed activations to CPU

  # Parameter and optimizer placement
  initialize_model_on_cpu: false        # initialize on CPU before accelerator placement
  offload_optimizer_device: none        # none or cpu

Set NPROC_PER_NODE, NNODES, NODE_RANK, and MASTER_ADDR for multi-node launch.

3. Deploy a checkpoint

Deployment defaults are defined in configs/deploy.yaml. A training output contains weights, config.yaml, normalization data, tokenizer/component assets, and the saved model specification. Start a WebSocket server with:

bash scripts/deploy.sh <ckpt_dir_path>

The newest checkpoint is selected automatically. To pin a file or launch one server per GPU:

bash scripts/deploy.sh <ckpt_dir_path> --ckpt-name <ckpt_file_name>
NUM_GPUS=4 PORT_BASE=8848 bash scripts/deploy.sh <ckpt_dir_path>

The server accepts obs, reset, and ping JSON messages. Payload details are in Benchmark integration.

Execution and denoising modes

All inference fields below belong to the inference section of configs/deploy.yaml. The two mode switches are independent:

inference:
  # Denoising trajectory
  denoise_steps: 10                    # denoising steps per generation
  denoise_mode: sync                   # sync or async noise trajectory

  # Async denoising alignment
  lead_modality: video                 # lead stream: video or action
  variance_shift_alpha: 1.0            # lead-curve shift; must be >= 1
  linear_offset: 0.0                   # lag; 0 <= value < 1

  # Request execution
  inference_mode: sync                 # sync execution or async prefetch
  inference_horizon: null              # actions consumed per chunk; null = full chunk
  inference_delay_steps: null          # async latency in action steps; < horizon

With num_frames: 33, each generated action chunk contains 32 actions. inference_horizon only limits how many are consumed before the next generation.

Optimization fields are in the same deploy YAML:

optimization:
  # Decode and cache
  decode_video: false                  # skip VAE decode when only actions are needed
  dit_cache:
    enabled: true                      # reuse similar video velocity predictions
    cosine_threshold: 0.99              # similarity threshold for a cache hit
    max_skips: 3                        # maximum consecutive skipped video forwards

  # Compile architecture-specific paths
  compile:
    enabled: true                      # compile fixed-shape paths
    self_attn: {torch_mode: default, dynamic: false}
    cross_attn: {torch_mode: default, dynamic: false}
    idm: {torch_mode: reduce-overhead, dynamic: false}
    tri_system: {torch_mode: reduce-overhead, dynamic: false}

  # Reuse text features
  prompt_embed_cache: {enabled: true, maxsize: 32}

Compilation can add first-request warm-up latency. Source code: scripts/train.py, openwam/train/openwam_trainer.py, scripts/deploy.py, and openwam/deploy/.