Publish completed Flappy QwenOFT H1 mean-profile 5000-update Blackwell run
Browse files- latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/README.md +12 -0
- latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/checkpoints/model.pt +3 -0
- latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/collection_quality_report.json +18 -0
- latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/config.full.yaml +271 -0
- latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/config.yaml +45 -0
- latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/dataset_statistics.json +194 -0
- latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/dataset_statistics_eval.json +194 -0
- latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/files.sha256.json +102 -0
- latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/latency_prompt_map.json +7 -0
- latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/launch_audit.json +62 -0
- latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/manifest.json +111 -0
- latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/provenance.json +93 -0
- latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/summary.jsonl +10 -0
- latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/task_contract.json +43 -0
- latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/training-code/QWen3.py +444 -0
- latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/training-code/QwenOFT.py +1244 -0
- latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/training-code/datasets.py +0 -0
- latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/training-code/gym-language-contract.patch +17 -0
- latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/training-code/loader-spawn-cache.patch +18 -0
- latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/training-code/loader-spawn-test.patch +31 -0
- latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/training-code/mean-profile-preparation.patch +147 -0
- latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/training-code/train_starvla.py +0 -0
- latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/training-code/trainer_tools.py +736 -0
- latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/training-code/worker_context.py +77 -0
- latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/training_code_snapshot.json +19 -0
- latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/training_results.json +15 -0
latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/README.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Flappy QwenOFT H1 with RTX3090 mean latency
|
| 2 |
+
|
| 3 |
+
Training complete: 5000 optimizer updates, seed42, global batch128 (2 Blackwell GPUs x64, accumulation1), BF16/ZeRO2, gradient checkpointing false. Final5000 checkpoint is published; full optimizer recovery is retained on the training host.
|
| 4 |
+
|
| 5 |
+
The data use fixed75.87417450998383ms from the archived RTX3090 OpenVLA profile, quantized to one raw frame at10Hz (100ms effective). This is mean profiling. Binary causal eight-decision history24D, current RGB image, native2D NOOP/FLAP action, horizon1. 180train/20validation episodes,3240000/360000rows,200 unique seeds.
|
| 6 |
+
|
| 7 |
+
Final offline validation action loss: 0.03496555 on 6400 sampled validation frames. Closed-loop latency-bench evaluation is pending until all four mean VLA trainings complete. Offline loss is not task-performance acceptance.
|
| 8 |
+
|
| 9 |
+
Weights: `checkpoints/model.pt` (pure tensor state_dict; weights_only CPU load and finite-tensor checks passed). `config.yaml` is portable inference config; `config.full.yaml` records actual training configuration. Backbone/tokenizer source: Qwen/Qwen3-VL-4B-Instruct@ebb281ec70b05090aa6165b016eac8ec08e71b17. Task/history contract and dataset normalization are included.
|
| 10 |
+
|
| 11 |
+
Data: https://huggingface.co/datasets/latency-sensitive-bench/Standard-Pipeline/tree/a568aa3792b49189d72c02781956c46f50d81b4e/flappy/profile_latency/QwenOFT/mean_rtx3090_s42_200ep_v1
|
| 12 |
+
Training: https://wandb.ai/dongqianyu99-zhejiang-university/starvla_tasks/runs/mean3090-flappy-vla-blackwell-20260930
|
latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/checkpoints/model.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:f784cb7d6c19872b4ac419f8a31459b395c12f23809f561ae134080a74c6e80a
|
| 3 |
+
size 9785137569
|
latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/collection_quality_report.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"dataset_repo": "latency-sensitive-bench/Standard-Pipeline",
|
| 3 |
+
"revision": "a568aa3792b49189d72c02781956c46f50d81b4e",
|
| 4 |
+
"verified_hashes": 29,
|
| 5 |
+
"episodes": 200,
|
| 6 |
+
"train_episodes": 180,
|
| 7 |
+
"val_episodes": 20,
|
| 8 |
+
"train_rows": 3240000,
|
| 9 |
+
"val_rows": 360000,
|
| 10 |
+
"state_dim": 24,
|
| 11 |
+
"action_dim": 2,
|
| 12 |
+
"action_horizon": 1,
|
| 13 |
+
"mean_latency_ms": 75.87417450998383,
|
| 14 |
+
"effective_latency_raw_frames": 1,
|
| 15 |
+
"effective_latency_ms": 100,
|
| 16 |
+
"data_root": "/home/ubuntu/lzj/mean-profiling/flappy/data",
|
| 17 |
+
"canonical_data_recollected": false
|
| 18 |
+
}
|
latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/config.full.yaml
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
framework:
|
| 2 |
+
name: QwenOFT
|
| 3 |
+
qwenvl:
|
| 4 |
+
base_vlm: /home/ubuntu/lzj/models/Qwen3-VL-4B-Instruct
|
| 5 |
+
attn_implementation: flash_attention_2
|
| 6 |
+
enable_gradient_checkpointing: false
|
| 7 |
+
action_model:
|
| 8 |
+
action_model_type: MLP
|
| 9 |
+
action_dim: 2
|
| 10 |
+
state_dim: 24
|
| 11 |
+
action_hidden_dim: 2560
|
| 12 |
+
future_action_window_size: 0
|
| 13 |
+
past_action_window_size: 0
|
| 14 |
+
loss_type: discrete_ce
|
| 15 |
+
state_encoding: continuous_projector
|
| 16 |
+
task_objective: null
|
| 17 |
+
action_horizon: 1
|
| 18 |
+
action_env_dim: 2
|
| 19 |
+
kv_memory:
|
| 20 |
+
enabled: false
|
| 21 |
+
window: 4
|
| 22 |
+
rollout_len: 8
|
| 23 |
+
packed_train: false
|
| 24 |
+
rebased_sink: true
|
| 25 |
+
datasets:
|
| 26 |
+
vla_data:
|
| 27 |
+
dataset_py: lerobot_datasets
|
| 28 |
+
include_state: true
|
| 29 |
+
data_root_dir: /home/ubuntu/lzj/mean-profiling/flappy/data
|
| 30 |
+
data_mix: lerobot
|
| 31 |
+
eval_data_mix: lerobot__val
|
| 32 |
+
custom_mixtures_path: /home/ubuntu/lzj/mean-profiling/flappy/data/_generated_mixtures/lerobot.json
|
| 33 |
+
action_type: discrete
|
| 34 |
+
sequential_step_sampling: false
|
| 35 |
+
eval_sequential_step_sampling: null
|
| 36 |
+
num_workers: 2
|
| 37 |
+
eval_num_workers: 2
|
| 38 |
+
prefetch_factor: 4
|
| 39 |
+
persistent_workers: true
|
| 40 |
+
pin_memory: true
|
| 41 |
+
shuffle: true
|
| 42 |
+
action_balance:
|
| 43 |
+
enabled: false
|
| 44 |
+
strategy: balanced_epoch
|
| 45 |
+
action_key: action_id
|
| 46 |
+
target_flap_fraction: 0.3
|
| 47 |
+
noop_id: 0
|
| 48 |
+
flap_id: 1
|
| 49 |
+
latency_curriculum:
|
| 50 |
+
enabled: false
|
| 51 |
+
strategy: exclusive
|
| 52 |
+
latencies: null
|
| 53 |
+
phase_steps: null
|
| 54 |
+
phase_distributions: null
|
| 55 |
+
new_latency_passes: 1.0
|
| 56 |
+
replay_passes: 0.25
|
| 57 |
+
target_total_passes: 2.0
|
| 58 |
+
final_equalization: true
|
| 59 |
+
step_budget_mode: auto
|
| 60 |
+
eval_at_phase_end: false
|
| 61 |
+
save_at_phase_end: false
|
| 62 |
+
computed_plan: null
|
| 63 |
+
per_device_batch_size: 64
|
| 64 |
+
load_all_data_for_training: true
|
| 65 |
+
num_obs_frames: 1
|
| 66 |
+
image_mode: single
|
| 67 |
+
prompt_mode: raw
|
| 68 |
+
stitch_grid:
|
| 69 |
+
- 2
|
| 70 |
+
- 2
|
| 71 |
+
obs_image_size: null
|
| 72 |
+
video_backend: torchvision_av
|
| 73 |
+
dataset:
|
| 74 |
+
source_hf: ''
|
| 75 |
+
config_name: null
|
| 76 |
+
source_subdir: null
|
| 77 |
+
converted_name: flappy_train
|
| 78 |
+
single_source_hf: ''
|
| 79 |
+
mixed_source_hf: ''
|
| 80 |
+
single_converted_name: flappy_train
|
| 81 |
+
mixed_converted_name: flappy_mixed_latency_train
|
| 82 |
+
single_latency_filter: null
|
| 83 |
+
mixed_latency_filter: null
|
| 84 |
+
force_download: false
|
| 85 |
+
setup_force: false
|
| 86 |
+
skip_verification: false
|
| 87 |
+
target_latency_unit: observation_steps
|
| 88 |
+
verify_rows: 200
|
| 89 |
+
max_episodes: null
|
| 90 |
+
episodes_per_latency: null
|
| 91 |
+
latency_filter: null
|
| 92 |
+
debug_subset:
|
| 93 |
+
enabled: false
|
| 94 |
+
max_episodes: 5
|
| 95 |
+
suffix: debug
|
| 96 |
+
base_model:
|
| 97 |
+
repo_id: Qwen/Qwen3-VL-4B-Instruct
|
| 98 |
+
initialization:
|
| 99 |
+
checkpoint_local_dir: null
|
| 100 |
+
checkpoint_hf_repo_id: null
|
| 101 |
+
checkpoint_filename: null
|
| 102 |
+
trainer:
|
| 103 |
+
max_train_steps: 5000
|
| 104 |
+
num_warmup_steps: 100
|
| 105 |
+
save_interval: 500
|
| 106 |
+
eval_interval: 500
|
| 107 |
+
eval_num_batches: 50
|
| 108 |
+
per_latency_eval_num_batches: null
|
| 109 |
+
eval_action_classification: false
|
| 110 |
+
eval_action_classification_interval: null
|
| 111 |
+
cc_f1_tolerance: 1
|
| 112 |
+
learning_rate:
|
| 113 |
+
base: 3.0e-05
|
| 114 |
+
qwen_vl_interface: 3.0e-06
|
| 115 |
+
action_model: 0.0001
|
| 116 |
+
lr_scheduler_type: cosine_with_min_lr
|
| 117 |
+
scheduler_specific_kwargs:
|
| 118 |
+
min_lr_rate: 0.03333333333333333
|
| 119 |
+
freeze_modules: ''
|
| 120 |
+
freeze_vit: false
|
| 121 |
+
freeze_tied_embedding: false
|
| 122 |
+
freeze_llm_layers: []
|
| 123 |
+
loss_scale:
|
| 124 |
+
vla: 1.0
|
| 125 |
+
vlm: 0.0
|
| 126 |
+
max_grad_norm: 1.0
|
| 127 |
+
weight_decay: 0.0
|
| 128 |
+
logging_frequency: 1
|
| 129 |
+
profile_timing:
|
| 130 |
+
enabled: false
|
| 131 |
+
log_interval: 10
|
| 132 |
+
gradient_clipping: 1.0
|
| 133 |
+
gradient_accumulation_steps: 1
|
| 134 |
+
distributed_backend: deepspeed
|
| 135 |
+
is_resume: false
|
| 136 |
+
pretrained_checkpoint: null
|
| 137 |
+
resume_step: 0
|
| 138 |
+
reload_modules: null
|
| 139 |
+
optimizer:
|
| 140 |
+
name: AdamW
|
| 141 |
+
betas:
|
| 142 |
+
- 0.9
|
| 143 |
+
- 0.95
|
| 144 |
+
eps: 1.0e-08
|
| 145 |
+
weight_decay: 1.0e-08
|
| 146 |
+
fused: true
|
| 147 |
+
save_format: pt
|
| 148 |
+
hist8_diagnostics:
|
| 149 |
+
steps:
|
| 150 |
+
- 0
|
| 151 |
+
- 500
|
| 152 |
+
- 1000
|
| 153 |
+
- 2500
|
| 154 |
+
- 5000
|
| 155 |
+
sample_count: 2048
|
| 156 |
+
noise_seeds:
|
| 157 |
+
- 1729
|
| 158 |
+
sample_seed: 42
|
| 159 |
+
batch_size: 8
|
| 160 |
+
bootstrap_repeats: 4000
|
| 161 |
+
workspace_dir: WORKSPACE_DIR
|
| 162 |
+
run_root_dir: /home/ubuntu/lzj/mean-profiling/flappy/vla
|
| 163 |
+
seed: 42
|
| 164 |
+
wandb_entity: dongqianyu99-zhejiang-university
|
| 165 |
+
wandb_project: starvla_tasks
|
| 166 |
+
auth:
|
| 167 |
+
env_file: null
|
| 168 |
+
hf_token_env: HF_TOKEN
|
| 169 |
+
wandb_api_key_env: WANDB_API_KEY
|
| 170 |
+
paths:
|
| 171 |
+
run_root_dir: results/Checkpoints
|
| 172 |
+
dataset_local_dir: playground/Datasets/rl_games
|
| 173 |
+
dataset_cache_dir: null
|
| 174 |
+
base_model_dir: /home/ubuntu/lzj/models/Qwen3-VL-4B-Instruct
|
| 175 |
+
accelerate_config: starVLA/config/deepseeds/deepspeed_zero2.yaml
|
| 176 |
+
launch:
|
| 177 |
+
use_accelerate: true
|
| 178 |
+
gpus: null
|
| 179 |
+
num_processes: 1
|
| 180 |
+
dry_run: false
|
| 181 |
+
conda:
|
| 182 |
+
enabled: true
|
| 183 |
+
env_name: null
|
| 184 |
+
rl_games:
|
| 185 |
+
model_alias: openvla
|
| 186 |
+
env_eval:
|
| 187 |
+
image_size: 224
|
| 188 |
+
frameskip: 1
|
| 189 |
+
image_transform: raw_rgb
|
| 190 |
+
prompt_mode: raw
|
| 191 |
+
ghost_trail:
|
| 192 |
+
history_frames: 5
|
| 193 |
+
gamma: 1.3
|
| 194 |
+
min_alpha: 35
|
| 195 |
+
scroll_px_per_step: 4.0
|
| 196 |
+
ground_fraction: 0.22
|
| 197 |
+
seed: 42
|
| 198 |
+
fixed_episode_seeds: true
|
| 199 |
+
latency_seed_stride: 0
|
| 200 |
+
task_seed_stride: 0
|
| 201 |
+
task_description: ''
|
| 202 |
+
eval_parallel_envs: 5
|
| 203 |
+
action_chunk_execution:
|
| 204 |
+
enabled: false
|
| 205 |
+
chunk_size: null
|
| 206 |
+
enabled: false
|
| 207 |
+
eval_backend: latency_bench
|
| 208 |
+
distributed_mode: none
|
| 209 |
+
vectorized:
|
| 210 |
+
enabled: false
|
| 211 |
+
batch_size: 1
|
| 212 |
+
latency:
|
| 213 |
+
prompt_map_path: null
|
| 214 |
+
mode: single
|
| 215 |
+
values:
|
| 216 |
+
- 0
|
| 217 |
+
mid_train:
|
| 218 |
+
enabled: false
|
| 219 |
+
interval_steps: 100
|
| 220 |
+
latencies:
|
| 221 |
+
- 0
|
| 222 |
+
num_episodes: 5
|
| 223 |
+
max_steps_per_episode: 3600
|
| 224 |
+
post_train:
|
| 225 |
+
enabled: false
|
| 226 |
+
latencies:
|
| 227 |
+
- 0
|
| 228 |
+
- 1
|
| 229 |
+
- 2
|
| 230 |
+
- 3
|
| 231 |
+
- 4
|
| 232 |
+
num_episodes: 5
|
| 233 |
+
max_steps_per_episode: 3600
|
| 234 |
+
task: flappy
|
| 235 |
+
initialization_mode: scratch
|
| 236 |
+
action_carrier: native
|
| 237 |
+
model: openvla
|
| 238 |
+
env: flappy
|
| 239 |
+
init: scratch
|
| 240 |
+
mode: single
|
| 241 |
+
checkpoint:
|
| 242 |
+
load: none
|
| 243 |
+
hf_repo_id: null
|
| 244 |
+
save_best_model: false
|
| 245 |
+
save_final_model: true
|
| 246 |
+
save_pt_file: true
|
| 247 |
+
save_training_state: true
|
| 248 |
+
save_safetensors_file: false
|
| 249 |
+
local:
|
| 250 |
+
keep_last_n: 2
|
| 251 |
+
sync:
|
| 252 |
+
enabled: false
|
| 253 |
+
repo_id: null
|
| 254 |
+
keep_last_n: 0
|
| 255 |
+
sync_every_n_checkpoints: 1
|
| 256 |
+
resume_policy: local_latest
|
| 257 |
+
run_id: flappy-qwenoft-mean-3090-s42
|
| 258 |
+
output_dir: /home/ubuntu/lzj/mean-profiling/flappy/vla/flappy-qwenoft-mean-3090-s42
|
| 259 |
+
config_yaml: /home/ubuntu/lzj/mean-profiling/flappy/vla.yaml
|
| 260 |
+
is_debug: false
|
| 261 |
+
version_id: '0.21'
|
| 262 |
+
wandb_name: flappy-qwenoft-mean-3090-s42
|
| 263 |
+
wandb_group: mean-profiling-3090-blackwell
|
| 264 |
+
wandb_tags:
|
| 265 |
+
- flappy
|
| 266 |
+
- qwenoft
|
| 267 |
+
- hist8-v2
|
| 268 |
+
- history01
|
| 269 |
+
- scratch
|
| 270 |
+
- profile-latency
|
| 271 |
+
training_latency_condition: mean-profile
|
latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/config.yaml
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
framework:
|
| 2 |
+
name: QwenOFT
|
| 3 |
+
qwenvl:
|
| 4 |
+
base_vlm: Qwen/Qwen3-VL-4B-Instruct
|
| 5 |
+
attn_implementation: flash_attention_2
|
| 6 |
+
enable_gradient_checkpointing: false
|
| 7 |
+
action_model:
|
| 8 |
+
action_model_type: MLP
|
| 9 |
+
action_dim: 2
|
| 10 |
+
state_dim: 24
|
| 11 |
+
action_hidden_dim: 2560
|
| 12 |
+
future_action_window_size: 0
|
| 13 |
+
past_action_window_size: 0
|
| 14 |
+
loss_type: discrete_ce
|
| 15 |
+
state_encoding: continuous_projector
|
| 16 |
+
task_objective: null
|
| 17 |
+
action_horizon: 1
|
| 18 |
+
action_env_dim: 2
|
| 19 |
+
kv_memory:
|
| 20 |
+
enabled: false
|
| 21 |
+
window: 4
|
| 22 |
+
rollout_len: 8
|
| 23 |
+
packed_train: false
|
| 24 |
+
rebased_sink: true
|
| 25 |
+
version_id: '0.21'
|
| 26 |
+
datasets:
|
| 27 |
+
vla_data:
|
| 28 |
+
include_state: true
|
| 29 |
+
obs_image_size: null
|
| 30 |
+
image_mode: single
|
| 31 |
+
stitch_grid:
|
| 32 |
+
- 2
|
| 33 |
+
- 2
|
| 34 |
+
num_obs_frames: 1
|
| 35 |
+
trainer:
|
| 36 |
+
pretrained_checkpoint: null
|
| 37 |
+
backbone:
|
| 38 |
+
repo_id: Qwen/Qwen3-VL-4B-Instruct
|
| 39 |
+
revision: ebb281ec70b05090aa6165b016eac8ec08e71b17
|
| 40 |
+
allow_patterns:
|
| 41 |
+
- '*.json'
|
| 42 |
+
- '*.safetensors'
|
| 43 |
+
- '*.txt'
|
| 44 |
+
- '*.jinja'
|
| 45 |
+
- '*.model'
|
latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/dataset_statistics.json
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"new_embodiment": {
|
| 3 |
+
"action": {
|
| 4 |
+
"mean": [
|
| 5 |
+
0.9008839726448059,
|
| 6 |
+
0.09911604970693588
|
| 7 |
+
],
|
| 8 |
+
"std": [
|
| 9 |
+
0.30056583881378174,
|
| 10 |
+
0.30056583881378174
|
| 11 |
+
],
|
| 12 |
+
"max": [
|
| 13 |
+
1.0,
|
| 14 |
+
1.0
|
| 15 |
+
],
|
| 16 |
+
"min": [
|
| 17 |
+
0.0,
|
| 18 |
+
0.0
|
| 19 |
+
],
|
| 20 |
+
"q01": [
|
| 21 |
+
0.0,
|
| 22 |
+
0.0
|
| 23 |
+
],
|
| 24 |
+
"q99": [
|
| 25 |
+
1.0,
|
| 26 |
+
1.0
|
| 27 |
+
],
|
| 28 |
+
"mask": [
|
| 29 |
+
true,
|
| 30 |
+
true
|
| 31 |
+
]
|
| 32 |
+
},
|
| 33 |
+
"state": {
|
| 34 |
+
"mean": [
|
| 35 |
+
0.9995555281639099,
|
| 36 |
+
0.09905370324850082,
|
| 37 |
+
0.09904845803976059,
|
| 38 |
+
0.9996111392974854,
|
| 39 |
+
0.09906204044818878,
|
| 40 |
+
0.09905370324850082,
|
| 41 |
+
0.999666690826416,
|
| 42 |
+
0.09907067567110062,
|
| 43 |
+
0.09906204044818878,
|
| 44 |
+
0.9997222423553467,
|
| 45 |
+
0.09909043461084366,
|
| 46 |
+
0.09907067567110062,
|
| 47 |
+
0.9997777938842773,
|
| 48 |
+
0.09909876435995102,
|
| 49 |
+
0.09909043461084366,
|
| 50 |
+
0.999833345413208,
|
| 51 |
+
0.09910740703344345,
|
| 52 |
+
0.09909876435995102,
|
| 53 |
+
0.9998888969421387,
|
| 54 |
+
0.09911327064037323,
|
| 55 |
+
0.09910740703344345,
|
| 56 |
+
0.9999444484710693,
|
| 57 |
+
0.09911450743675232,
|
| 58 |
+
0.09911327064037323
|
| 59 |
+
],
|
| 60 |
+
"std": [
|
| 61 |
+
0.02107249759137688,
|
| 62 |
+
0.30049213767051697,
|
| 63 |
+
0.30048587918281555,
|
| 64 |
+
0.019712645560502656,
|
| 65 |
+
0.30050185322761536,
|
| 66 |
+
0.30049213767051697,
|
| 67 |
+
0.018251324072479345,
|
| 68 |
+
0.30051174759864807,
|
| 69 |
+
0.30050185322761536,
|
| 70 |
+
0.016662079840898097,
|
| 71 |
+
0.3005351126194,
|
| 72 |
+
0.30051174759864807,
|
| 73 |
+
0.014903813600540161,
|
| 74 |
+
0.3005451261997223,
|
| 75 |
+
0.3005351126194,
|
| 76 |
+
0.012907790020110239,
|
| 77 |
+
0.3005552887916565,
|
| 78 |
+
0.3005451261997223,
|
| 79 |
+
0.01053975615650543,
|
| 80 |
+
0.30056232213974,
|
| 81 |
+
0.3005552887916565,
|
| 82 |
+
0.0074531468562811926,
|
| 83 |
+
0.3005639910697937,
|
| 84 |
+
0.30056232213974
|
| 85 |
+
],
|
| 86 |
+
"max": [
|
| 87 |
+
1.0,
|
| 88 |
+
1.0,
|
| 89 |
+
1.0,
|
| 90 |
+
1.0,
|
| 91 |
+
1.0,
|
| 92 |
+
1.0,
|
| 93 |
+
1.0,
|
| 94 |
+
1.0,
|
| 95 |
+
1.0,
|
| 96 |
+
1.0,
|
| 97 |
+
1.0,
|
| 98 |
+
1.0,
|
| 99 |
+
1.0,
|
| 100 |
+
1.0,
|
| 101 |
+
1.0,
|
| 102 |
+
1.0,
|
| 103 |
+
1.0,
|
| 104 |
+
1.0,
|
| 105 |
+
1.0,
|
| 106 |
+
1.0,
|
| 107 |
+
1.0,
|
| 108 |
+
1.0,
|
| 109 |
+
1.0,
|
| 110 |
+
1.0
|
| 111 |
+
],
|
| 112 |
+
"min": [
|
| 113 |
+
0.0,
|
| 114 |
+
0.0,
|
| 115 |
+
0.0,
|
| 116 |
+
0.0,
|
| 117 |
+
0.0,
|
| 118 |
+
0.0,
|
| 119 |
+
0.0,
|
| 120 |
+
0.0,
|
| 121 |
+
0.0,
|
| 122 |
+
0.0,
|
| 123 |
+
0.0,
|
| 124 |
+
0.0,
|
| 125 |
+
0.0,
|
| 126 |
+
0.0,
|
| 127 |
+
0.0,
|
| 128 |
+
0.0,
|
| 129 |
+
0.0,
|
| 130 |
+
0.0,
|
| 131 |
+
0.0,
|
| 132 |
+
0.0,
|
| 133 |
+
0.0,
|
| 134 |
+
0.0,
|
| 135 |
+
0.0,
|
| 136 |
+
0.0
|
| 137 |
+
],
|
| 138 |
+
"q01": [
|
| 139 |
+
1.0,
|
| 140 |
+
0.0,
|
| 141 |
+
0.0,
|
| 142 |
+
1.0,
|
| 143 |
+
0.0,
|
| 144 |
+
0.0,
|
| 145 |
+
1.0,
|
| 146 |
+
0.0,
|
| 147 |
+
0.0,
|
| 148 |
+
1.0,
|
| 149 |
+
0.0,
|
| 150 |
+
0.0,
|
| 151 |
+
1.0,
|
| 152 |
+
0.0,
|
| 153 |
+
0.0,
|
| 154 |
+
1.0,
|
| 155 |
+
0.0,
|
| 156 |
+
0.0,
|
| 157 |
+
1.0,
|
| 158 |
+
0.0,
|
| 159 |
+
0.0,
|
| 160 |
+
1.0,
|
| 161 |
+
0.0,
|
| 162 |
+
0.0
|
| 163 |
+
],
|
| 164 |
+
"q99": [
|
| 165 |
+
1.0,
|
| 166 |
+
1.0,
|
| 167 |
+
1.0,
|
| 168 |
+
1.0,
|
| 169 |
+
1.0,
|
| 170 |
+
1.0,
|
| 171 |
+
1.0,
|
| 172 |
+
1.0,
|
| 173 |
+
1.0,
|
| 174 |
+
1.0,
|
| 175 |
+
1.0,
|
| 176 |
+
1.0,
|
| 177 |
+
1.0,
|
| 178 |
+
1.0,
|
| 179 |
+
1.0,
|
| 180 |
+
1.0,
|
| 181 |
+
1.0,
|
| 182 |
+
1.0,
|
| 183 |
+
1.0,
|
| 184 |
+
1.0,
|
| 185 |
+
1.0,
|
| 186 |
+
1.0,
|
| 187 |
+
1.0,
|
| 188 |
+
1.0
|
| 189 |
+
]
|
| 190 |
+
},
|
| 191 |
+
"num_transitions": 3240000,
|
| 192 |
+
"num_trajectories": 180
|
| 193 |
+
}
|
| 194 |
+
}
|
latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/dataset_statistics_eval.json
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"new_embodiment": {
|
| 3 |
+
"action": {
|
| 4 |
+
"mean": [
|
| 5 |
+
0.9010527729988098,
|
| 6 |
+
0.09894721955060959
|
| 7 |
+
],
|
| 8 |
+
"std": [
|
| 9 |
+
0.2985910475254059,
|
| 10 |
+
0.2985910475254059
|
| 11 |
+
],
|
| 12 |
+
"max": [
|
| 13 |
+
1.0,
|
| 14 |
+
1.0
|
| 15 |
+
],
|
| 16 |
+
"min": [
|
| 17 |
+
0.0,
|
| 18 |
+
0.0
|
| 19 |
+
],
|
| 20 |
+
"q01": [
|
| 21 |
+
0.0,
|
| 22 |
+
0.0
|
| 23 |
+
],
|
| 24 |
+
"q99": [
|
| 25 |
+
1.0,
|
| 26 |
+
1.0
|
| 27 |
+
],
|
| 28 |
+
"mask": [
|
| 29 |
+
true,
|
| 30 |
+
true
|
| 31 |
+
]
|
| 32 |
+
},
|
| 33 |
+
"state": {
|
| 34 |
+
"mean": [
|
| 35 |
+
0.9995555281639099,
|
| 36 |
+
0.09889999777078629,
|
| 37 |
+
0.09889444708824158,
|
| 38 |
+
0.9996111392974854,
|
| 39 |
+
0.09890277683734894,
|
| 40 |
+
0.09889999777078629,
|
| 41 |
+
0.999666690826416,
|
| 42 |
+
0.09890833497047424,
|
| 43 |
+
0.09890277683734894,
|
| 44 |
+
0.9997222423553467,
|
| 45 |
+
0.09892222285270691,
|
| 46 |
+
0.09890833497047424,
|
| 47 |
+
0.9997777938842773,
|
| 48 |
+
0.09893333166837692,
|
| 49 |
+
0.09892222285270691,
|
| 50 |
+
0.999833345413208,
|
| 51 |
+
0.09894166886806488,
|
| 52 |
+
0.09893333166837692,
|
| 53 |
+
0.9998888969421387,
|
| 54 |
+
0.09894444793462753,
|
| 55 |
+
0.09894166886806488,
|
| 56 |
+
0.9999444484710693,
|
| 57 |
+
0.09894721955060959,
|
| 58 |
+
0.09894444793462753
|
| 59 |
+
],
|
| 60 |
+
"std": [
|
| 61 |
+
0.02107250504195682,
|
| 62 |
+
0.29852861166000366,
|
| 63 |
+
0.2985208034515381,
|
| 64 |
+
0.019712593406438477,
|
| 65 |
+
0.2985321879386902,
|
| 66 |
+
0.29852861166000366,
|
| 67 |
+
0.01825134269893312,
|
| 68 |
+
0.2985396385192871,
|
| 69 |
+
0.2985321879386902,
|
| 70 |
+
0.01666202954947938,
|
| 71 |
+
0.2985576391220093,
|
| 72 |
+
0.2985396385192871,
|
| 73 |
+
0.014903815463185194,
|
| 74 |
+
0.29857251048088074,
|
| 75 |
+
0.2985576391220093,
|
| 76 |
+
0.01290779560804152,
|
| 77 |
+
0.29858362674713135,
|
| 78 |
+
0.29857251048088074,
|
| 79 |
+
0.010539757087829197,
|
| 80 |
+
0.2985873222351074,
|
| 81 |
+
0.29858362674713135,
|
| 82 |
+
0.0074531468562811926,
|
| 83 |
+
0.2985910475254059,
|
| 84 |
+
0.2985873222351074
|
| 85 |
+
],
|
| 86 |
+
"max": [
|
| 87 |
+
1.0,
|
| 88 |
+
1.0,
|
| 89 |
+
1.0,
|
| 90 |
+
1.0,
|
| 91 |
+
1.0,
|
| 92 |
+
1.0,
|
| 93 |
+
1.0,
|
| 94 |
+
1.0,
|
| 95 |
+
1.0,
|
| 96 |
+
1.0,
|
| 97 |
+
1.0,
|
| 98 |
+
1.0,
|
| 99 |
+
1.0,
|
| 100 |
+
1.0,
|
| 101 |
+
1.0,
|
| 102 |
+
1.0,
|
| 103 |
+
1.0,
|
| 104 |
+
1.0,
|
| 105 |
+
1.0,
|
| 106 |
+
1.0,
|
| 107 |
+
1.0,
|
| 108 |
+
1.0,
|
| 109 |
+
1.0,
|
| 110 |
+
1.0
|
| 111 |
+
],
|
| 112 |
+
"min": [
|
| 113 |
+
0.0,
|
| 114 |
+
0.0,
|
| 115 |
+
0.0,
|
| 116 |
+
0.0,
|
| 117 |
+
0.0,
|
| 118 |
+
0.0,
|
| 119 |
+
0.0,
|
| 120 |
+
0.0,
|
| 121 |
+
0.0,
|
| 122 |
+
0.0,
|
| 123 |
+
0.0,
|
| 124 |
+
0.0,
|
| 125 |
+
0.0,
|
| 126 |
+
0.0,
|
| 127 |
+
0.0,
|
| 128 |
+
0.0,
|
| 129 |
+
0.0,
|
| 130 |
+
0.0,
|
| 131 |
+
0.0,
|
| 132 |
+
0.0,
|
| 133 |
+
0.0,
|
| 134 |
+
0.0,
|
| 135 |
+
0.0,
|
| 136 |
+
0.0
|
| 137 |
+
],
|
| 138 |
+
"q01": [
|
| 139 |
+
1.0,
|
| 140 |
+
0.0,
|
| 141 |
+
0.0,
|
| 142 |
+
1.0,
|
| 143 |
+
0.0,
|
| 144 |
+
0.0,
|
| 145 |
+
1.0,
|
| 146 |
+
0.0,
|
| 147 |
+
0.0,
|
| 148 |
+
1.0,
|
| 149 |
+
0.0,
|
| 150 |
+
0.0,
|
| 151 |
+
1.0,
|
| 152 |
+
0.0,
|
| 153 |
+
0.0,
|
| 154 |
+
1.0,
|
| 155 |
+
0.0,
|
| 156 |
+
0.0,
|
| 157 |
+
1.0,
|
| 158 |
+
0.0,
|
| 159 |
+
0.0,
|
| 160 |
+
1.0,
|
| 161 |
+
0.0,
|
| 162 |
+
0.0
|
| 163 |
+
],
|
| 164 |
+
"q99": [
|
| 165 |
+
1.0,
|
| 166 |
+
1.0,
|
| 167 |
+
1.0,
|
| 168 |
+
1.0,
|
| 169 |
+
1.0,
|
| 170 |
+
1.0,
|
| 171 |
+
1.0,
|
| 172 |
+
1.0,
|
| 173 |
+
1.0,
|
| 174 |
+
1.0,
|
| 175 |
+
1.0,
|
| 176 |
+
1.0,
|
| 177 |
+
1.0,
|
| 178 |
+
1.0,
|
| 179 |
+
1.0,
|
| 180 |
+
1.0,
|
| 181 |
+
1.0,
|
| 182 |
+
1.0,
|
| 183 |
+
1.0,
|
| 184 |
+
1.0,
|
| 185 |
+
1.0,
|
| 186 |
+
1.0,
|
| 187 |
+
1.0,
|
| 188 |
+
1.0
|
| 189 |
+
]
|
| 190 |
+
},
|
| 191 |
+
"num_transitions": 360000,
|
| 192 |
+
"num_trajectories": 20
|
| 193 |
+
}
|
| 194 |
+
}
|
latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/files.sha256.json
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"README.md": {
|
| 3 |
+
"bytes": 1513,
|
| 4 |
+
"sha256": "1f4036fe39bcc14657ad3dce517a5ee46ae764e030e38c60c808b7d29859b255"
|
| 5 |
+
},
|
| 6 |
+
"checkpoints/model.pt": {
|
| 7 |
+
"bytes": 9785137569,
|
| 8 |
+
"sha256": "f784cb7d6c19872b4ac419f8a31459b395c12f23809f561ae134080a74c6e80a"
|
| 9 |
+
},
|
| 10 |
+
"collection_quality_report.json": {
|
| 11 |
+
"bytes": 532,
|
| 12 |
+
"sha256": "0741f81a3b2bd9cf7b55d7b8478695532968bcf64abb7640535bea425bac6590"
|
| 13 |
+
},
|
| 14 |
+
"config.full.yaml": {
|
| 15 |
+
"bytes": 6375,
|
| 16 |
+
"sha256": "d6da13357b405aafb9f5b6e07934a2cd59a28ab3a7578fd81b01bcfea40e8eb2"
|
| 17 |
+
},
|
| 18 |
+
"config.yaml": {
|
| 19 |
+
"bytes": 979,
|
| 20 |
+
"sha256": "6c33e3ebf0f2a5327163329289b4b225c418bc01f65730ff8f7599702e50d61f"
|
| 21 |
+
},
|
| 22 |
+
"dataset_statistics.json": {
|
| 23 |
+
"bytes": 3299,
|
| 24 |
+
"sha256": "2c7e8a74a07ca7bec408a0e32cd00957d1af68ecc58fb83571cea5258fe178d7"
|
| 25 |
+
},
|
| 26 |
+
"dataset_statistics_eval.json": {
|
| 27 |
+
"bytes": 3302,
|
| 28 |
+
"sha256": "a57b1ff610f31dbd61bdb28f5c176e5945adfaa03be75f590e7cd550f658c3dc"
|
| 29 |
+
},
|
| 30 |
+
"latency_prompt_map.json": {
|
| 31 |
+
"bytes": 189,
|
| 32 |
+
"sha256": "e5bdad8c4747fb2e8101f5d7265e89e2c8794c573ee42bac764365a42b6bc3a4"
|
| 33 |
+
},
|
| 34 |
+
"launch_audit.json": {
|
| 35 |
+
"bytes": 2140,
|
| 36 |
+
"sha256": "6441da61d2da9190ddc6fd4d7a031acd1d455fc102c3ac40ae3e997aada1fb95"
|
| 37 |
+
},
|
| 38 |
+
"manifest.json": {
|
| 39 |
+
"bytes": 2295,
|
| 40 |
+
"sha256": "0ee6164ded3e87ffb0ea956b700bc1fae82c1a4de2c3f4b432163fa070f40fc4"
|
| 41 |
+
},
|
| 42 |
+
"provenance.json": {
|
| 43 |
+
"bytes": 3432,
|
| 44 |
+
"sha256": "17416c9020e50a6abac5e82f2001ec7e0962b479cbde84702061af45ec5116d9"
|
| 45 |
+
},
|
| 46 |
+
"summary.jsonl": {
|
| 47 |
+
"bytes": 159,
|
| 48 |
+
"sha256": "2905acb79c5fffb33fd4689d87af3bd05d74f4184b9d36ff3eae0c64deecd930"
|
| 49 |
+
},
|
| 50 |
+
"task_contract.json": {
|
| 51 |
+
"bytes": 982,
|
| 52 |
+
"sha256": "958afa1b69927f50ef9f92e50875b0f6ec937e98ce0a7a86732342a05b94f76c"
|
| 53 |
+
},
|
| 54 |
+
"training-code/QWen3.py": {
|
| 55 |
+
"bytes": 17586,
|
| 56 |
+
"sha256": "5fe76fc98eaa84e215c3c25c11326815499d2a4b0683c6ab95382b881f535370"
|
| 57 |
+
},
|
| 58 |
+
"training-code/QwenOFT.py": {
|
| 59 |
+
"bytes": 62854,
|
| 60 |
+
"sha256": "acad1b0dcde6e9fed6e2912b6534db0f8e33a74ec7f6630bbf1b0161fc298b0d"
|
| 61 |
+
},
|
| 62 |
+
"training-code/datasets.py": {
|
| 63 |
+
"bytes": 154347,
|
| 64 |
+
"sha256": "33e22556471128a2bad18a1ef8b84c399847b4d98b2979d785d139de0cdf36df"
|
| 65 |
+
},
|
| 66 |
+
"training-code/gym-language-contract.patch": {
|
| 67 |
+
"bytes": 688,
|
| 68 |
+
"sha256": "9f5dee2c5b52d5507aff27373a8cf6def4959361d2c5264d5079b416f28a4c49"
|
| 69 |
+
},
|
| 70 |
+
"training-code/loader-spawn-cache.patch": {
|
| 71 |
+
"bytes": 711,
|
| 72 |
+
"sha256": "24532ce84e5aa9aef381493fe699b4bb4987afd954422191ae852b0f5eb5b2cd"
|
| 73 |
+
},
|
| 74 |
+
"training-code/loader-spawn-test.patch": {
|
| 75 |
+
"bytes": 1500,
|
| 76 |
+
"sha256": "a36db15b13d68defeecb75ddc9efa1ac202d9aa0a40f66ba5baaee2dcaef21f1"
|
| 77 |
+
},
|
| 78 |
+
"training-code/mean-profile-preparation.patch": {
|
| 79 |
+
"bytes": 7053,
|
| 80 |
+
"sha256": "de5fe558376c98515a1cfb186ffdc09b618acc207eb8a5f667b0cbb303767c09"
|
| 81 |
+
},
|
| 82 |
+
"training-code/train_starvla.py": {
|
| 83 |
+
"bytes": 125159,
|
| 84 |
+
"sha256": "f872648f941cec272af724772e9d6fc3721e9ed6eaf0c8683ab285eb9396212e"
|
| 85 |
+
},
|
| 86 |
+
"training-code/trainer_tools.py": {
|
| 87 |
+
"bytes": 29144,
|
| 88 |
+
"sha256": "9a48bdfb99b405b03fececfc1e051d3ebb179eb423c15141f02b9be62e2e1791"
|
| 89 |
+
},
|
| 90 |
+
"training-code/worker_context.py": {
|
| 91 |
+
"bytes": 2279,
|
| 92 |
+
"sha256": "8493631104269f0630655ee2faae592a84ebfb3119aca142c8ac2701f5214464"
|
| 93 |
+
},
|
| 94 |
+
"training_code_snapshot.json": {
|
| 95 |
+
"bytes": 626,
|
| 96 |
+
"sha256": "136c2cbb92daa93a6dab1aed5644e57491da4f95075c7f4f5d12b3ca8aa16adb"
|
| 97 |
+
},
|
| 98 |
+
"training_results.json": {
|
| 99 |
+
"bytes": 512,
|
| 100 |
+
"sha256": "b08fa8927249eed1bb0a1c82fc7a75e59e05d7c2fbaf4c6c8ed6f14dc214d488"
|
| 101 |
+
}
|
| 102 |
+
}
|
latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/latency_prompt_map.json
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"1": {
|
| 3 |
+
"prompt": "You are playing Flappy Bird. Pass through the pipe gaps and stay alive. Choose the action: NOOP, FLAP.",
|
| 4 |
+
"latency_raw_frames": 1,
|
| 5 |
+
"latency_ms": 100.0
|
| 6 |
+
}
|
| 7 |
+
}
|
latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/launch_audit.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"host": "ubuntu@136.115.188.185",
|
| 3 |
+
"reference_repo_revision": "180da0effdaaaeacef218cf5fbaa4db41af67495",
|
| 4 |
+
"reference": "/home/ubuntu/lzj/mean-profiling/reference/checkpoint-configs/latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-hist8-scratch-s42-v2/config.full.yaml",
|
| 5 |
+
"config_sha256": "0d8e33d18da69ac470a3aac5973babdb36b3eb8bfaa02c0f0d3ae8aeb912850f",
|
| 6 |
+
"base_revision": "ebb281ec70b05090aa6165b016eac8ec08e71b17",
|
| 7 |
+
"physical_gpus": [
|
| 8 |
+
0,
|
| 9 |
+
1
|
| 10 |
+
],
|
| 11 |
+
"micro_batch_per_gpu": 64,
|
| 12 |
+
"gradient_accumulation": 1,
|
| 13 |
+
"global_batch": 128,
|
| 14 |
+
"gradient_checkpointing": false,
|
| 15 |
+
"optimizer_updates": 5000,
|
| 16 |
+
"seed": 42,
|
| 17 |
+
"horizon": 1,
|
| 18 |
+
"preflight_optimizer_updates": 20,
|
| 19 |
+
"preflight_discarded": true,
|
| 20 |
+
"formal_start_step": 0,
|
| 21 |
+
"preflight_gpu_peaks": [
|
| 22 |
+
{
|
| 23 |
+
"rank": "0",
|
| 24 |
+
"logical_gpu": 0,
|
| 25 |
+
"gpu_peak_allocated_gib": 92.39531517028809,
|
| 26 |
+
"gpu_peak_reserved_gib": 92.84375,
|
| 27 |
+
"main_peak_rss_gib": 18.482311248779297,
|
| 28 |
+
"wall_seconds_with_initialization": 66.88179596900227,
|
| 29 |
+
"optimizer_updates": 20,
|
| 30 |
+
"formal_updates": 0,
|
| 31 |
+
"global_batch": 128,
|
| 32 |
+
"micro_per_gpu": 64,
|
| 33 |
+
"gradient_accumulation": 1,
|
| 34 |
+
"gradient_checkpointing": false,
|
| 35 |
+
"status": "passed"
|
| 36 |
+
},
|
| 37 |
+
{
|
| 38 |
+
"rank": "1",
|
| 39 |
+
"logical_gpu": 1,
|
| 40 |
+
"gpu_peak_allocated_gib": 92.39531517028809,
|
| 41 |
+
"gpu_peak_reserved_gib": 93.5078125,
|
| 42 |
+
"main_peak_rss_gib": 18.478984832763672,
|
| 43 |
+
"wall_seconds_with_initialization": 66.89783567699851,
|
| 44 |
+
"optimizer_updates": 20,
|
| 45 |
+
"formal_updates": 0,
|
| 46 |
+
"global_batch": 128,
|
| 47 |
+
"micro_per_gpu": 64,
|
| 48 |
+
"gradient_accumulation": 1,
|
| 49 |
+
"gradient_checkpointing": false,
|
| 50 |
+
"status": "passed"
|
| 51 |
+
}
|
| 52 |
+
],
|
| 53 |
+
"data_workers_per_rank": 2,
|
| 54 |
+
"eval_workers_per_rank": 2,
|
| 55 |
+
"spawn_cache_payload_fix_test": "9 relevant tests passed",
|
| 56 |
+
"memory_high_gib": 400,
|
| 57 |
+
"memory_max_gib": 450,
|
| 58 |
+
"post_train_rollout_eval": false,
|
| 59 |
+
"evaluation": "pending until four VLA trainings complete",
|
| 60 |
+
"allocator": "expandable_segments:True",
|
| 61 |
+
"formal_retry_reason": "CUDA allocator fragmentation/near-capacity initial failure, 20 same-path steps verified"
|
| 62 |
+
}
|
latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/manifest.json
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"dataset_name": "lerobot",
|
| 3 |
+
"env_name": "flappy",
|
| 4 |
+
"episodes": 180,
|
| 5 |
+
"frames": 3240000,
|
| 6 |
+
"task_prompts": [
|
| 7 |
+
"You are playing Flappy Bird. Pass through the pipe gaps and stay alive. Choose the action: NOOP, FLAP."
|
| 8 |
+
],
|
| 9 |
+
"format": "starvla_lerobot_v2_decision_v1",
|
| 10 |
+
"source": "/home/ubuntu/lzj/mean-profiling/flappy/data/raw",
|
| 11 |
+
"robot_type": "rl_games_flappy",
|
| 12 |
+
"action_layout": "flappy_discrete",
|
| 13 |
+
"source_action_layout": "flappy_discrete",
|
| 14 |
+
"action_labels": [
|
| 15 |
+
"NOOP",
|
| 16 |
+
"FLAP"
|
| 17 |
+
],
|
| 18 |
+
"action_dim": 2,
|
| 19 |
+
"active_action_dim": 2,
|
| 20 |
+
"state_source": "decision_v1",
|
| 21 |
+
"state_dim": 24,
|
| 22 |
+
"state_labels": [
|
| 23 |
+
"decision_history_0",
|
| 24 |
+
"decision_history_1",
|
| 25 |
+
"decision_history_2",
|
| 26 |
+
"decision_history_3",
|
| 27 |
+
"decision_history_4",
|
| 28 |
+
"decision_history_5",
|
| 29 |
+
"decision_history_6",
|
| 30 |
+
"decision_history_7",
|
| 31 |
+
"decision_history_8",
|
| 32 |
+
"decision_history_9",
|
| 33 |
+
"decision_history_10",
|
| 34 |
+
"decision_history_11",
|
| 35 |
+
"decision_history_12",
|
| 36 |
+
"decision_history_13",
|
| 37 |
+
"decision_history_14",
|
| 38 |
+
"decision_history_15",
|
| 39 |
+
"decision_history_16",
|
| 40 |
+
"decision_history_17",
|
| 41 |
+
"decision_history_18",
|
| 42 |
+
"decision_history_19",
|
| 43 |
+
"decision_history_20",
|
| 44 |
+
"decision_history_21",
|
| 45 |
+
"decision_history_22",
|
| 46 |
+
"decision_history_23"
|
| 47 |
+
],
|
| 48 |
+
"state_normalization": {
|
| 49 |
+
"min": [
|
| 50 |
+
0.0,
|
| 51 |
+
0.0,
|
| 52 |
+
0.0,
|
| 53 |
+
0.0,
|
| 54 |
+
0.0,
|
| 55 |
+
0.0,
|
| 56 |
+
0.0,
|
| 57 |
+
0.0,
|
| 58 |
+
0.0,
|
| 59 |
+
0.0,
|
| 60 |
+
0.0,
|
| 61 |
+
0.0,
|
| 62 |
+
0.0,
|
| 63 |
+
0.0,
|
| 64 |
+
0.0,
|
| 65 |
+
0.0,
|
| 66 |
+
0.0,
|
| 67 |
+
0.0,
|
| 68 |
+
0.0,
|
| 69 |
+
0.0,
|
| 70 |
+
0.0,
|
| 71 |
+
0.0,
|
| 72 |
+
0.0,
|
| 73 |
+
0.0
|
| 74 |
+
],
|
| 75 |
+
"max": [
|
| 76 |
+
1.0,
|
| 77 |
+
1.0,
|
| 78 |
+
1.0,
|
| 79 |
+
1.0,
|
| 80 |
+
1.0,
|
| 81 |
+
1.0,
|
| 82 |
+
1.0,
|
| 83 |
+
1.0,
|
| 84 |
+
1.0,
|
| 85 |
+
1.0,
|
| 86 |
+
1.0,
|
| 87 |
+
1.0,
|
| 88 |
+
1.0,
|
| 89 |
+
1.0,
|
| 90 |
+
1.0,
|
| 91 |
+
1.0,
|
| 92 |
+
1.0,
|
| 93 |
+
1.0,
|
| 94 |
+
1.0,
|
| 95 |
+
1.0,
|
| 96 |
+
1.0,
|
| 97 |
+
1.0,
|
| 98 |
+
1.0,
|
| 99 |
+
1.0
|
| 100 |
+
]
|
| 101 |
+
},
|
| 102 |
+
"action_history_decisions": 8,
|
| 103 |
+
"rows_unit": "decision_step",
|
| 104 |
+
"obs_fps": 10.0,
|
| 105 |
+
"obs_stride_raw_frames": 1,
|
| 106 |
+
"latency_prompt_map_path": "/home/ubuntu/lzj/mean-profiling/flappy/data/lerobot/latency_prompt_map.json",
|
| 107 |
+
"custom_mixtures_path": "/home/ubuntu/lzj/mean-profiling/flappy/data/_generated_mixtures/lerobot.json",
|
| 108 |
+
"validation_dataset_name": "lerobot__val",
|
| 109 |
+
"validation_episodes": 20,
|
| 110 |
+
"validation_frames": 360000
|
| 111 |
+
}
|
latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/provenance.json
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"task": "flappy",
|
| 3 |
+
"model": "qwenoft",
|
| 4 |
+
"run_id": "flappy-qwenoft-mean-3090-s42",
|
| 5 |
+
"training_latency_condition": "mean-profile",
|
| 6 |
+
"training_hardware": {
|
| 7 |
+
"gpu": "NVIDIA RTX PRO6000 Blackwell Server Edition",
|
| 8 |
+
"count": 2,
|
| 9 |
+
"physical_indices": [
|
| 10 |
+
0,
|
| 11 |
+
1
|
| 12 |
+
]
|
| 13 |
+
},
|
| 14 |
+
"latency_profile_hardware": "1x-RTX3090",
|
| 15 |
+
"fixed_latency_ms": 75.87417450998383,
|
| 16 |
+
"effective_latency_raw_frames": 1,
|
| 17 |
+
"env_fps": 10,
|
| 18 |
+
"obs_fps": 10,
|
| 19 |
+
"horizon": 1,
|
| 20 |
+
"history_encoding": "binary_0_1",
|
| 21 |
+
"state_dim": 24,
|
| 22 |
+
"action_dim": 2,
|
| 23 |
+
"global_batch": 128,
|
| 24 |
+
"micro_per_gpu": 64,
|
| 25 |
+
"gradient_accumulation": 1,
|
| 26 |
+
"gradient_checkpointing": false,
|
| 27 |
+
"precision": "bf16",
|
| 28 |
+
"optimizer_backend": "ZeRO2",
|
| 29 |
+
"teacher": {
|
| 30 |
+
"repo_id": "latency-sensitive-bench/benchmark-models",
|
| 31 |
+
"revision": "3ca75d71b55ff42889a4f058d6ad90b2e9b3668f",
|
| 32 |
+
"path": "/home/ubuntu/lzj/mean-profiling/flappy/restored-teacher/latency-aware/flappy/small-policy/sample-factory-qwenoft-rtx3090-mean-hist8-capacity1-s42-v1",
|
| 33 |
+
"local_checkpoint": "/home/ubuntu/lzj/mean-profiling/flappy/restored-teacher/latency-aware/flappy/small-policy/sample-factory-qwenoft-rtx3090-mean-hist8-capacity1-s42-v1/best_000048808_25034752_reward_2216.841.pth",
|
| 34 |
+
"env_steps": 25034752,
|
| 35 |
+
"hash_verified_files": 8,
|
| 36 |
+
"weights_only_load": true
|
| 37 |
+
},
|
| 38 |
+
"dataset": {
|
| 39 |
+
"dataset_repo": "latency-sensitive-bench/Standard-Pipeline",
|
| 40 |
+
"revision": "a568aa3792b49189d72c02781956c46f50d81b4e",
|
| 41 |
+
"verified_hashes": 29,
|
| 42 |
+
"episodes": 200,
|
| 43 |
+
"train_episodes": 180,
|
| 44 |
+
"val_episodes": 20,
|
| 45 |
+
"train_rows": 3240000,
|
| 46 |
+
"val_rows": 360000,
|
| 47 |
+
"state_dim": 24,
|
| 48 |
+
"action_dim": 2,
|
| 49 |
+
"action_horizon": 1,
|
| 50 |
+
"mean_latency_ms": 75.87417450998383,
|
| 51 |
+
"effective_latency_raw_frames": 1,
|
| 52 |
+
"effective_latency_ms": 100,
|
| 53 |
+
"data_root": "/home/ubuntu/lzj/mean-profiling/flappy/data",
|
| 54 |
+
"canonical_data_recollected": false
|
| 55 |
+
},
|
| 56 |
+
"source_reference_revision": "180da0effdaaaeacef218cf5fbaa4db41af67495",
|
| 57 |
+
"runtime": {
|
| 58 |
+
"repo": "c3c6a39365a151e9b7a5e215452fd64e957c2b29",
|
| 59 |
+
"starvla": "ccca13c5177fe3d3c884b6e2de4965d916016649",
|
| 60 |
+
"runtime_fixes": [
|
| 61 |
+
"mean-profile-preparation.patch",
|
| 62 |
+
"gym-language-contract.patch",
|
| 63 |
+
"loader-spawn-cache.patch",
|
| 64 |
+
"loader-spawn-test.patch"
|
| 65 |
+
],
|
| 66 |
+
"pytorch3d": {
|
| 67 |
+
"revision": "33824be3cbc87a7dd1db0f6a9a9de9ac81b2d0ba",
|
| 68 |
+
"build": "transforms-only, no native render extension; QwenOFT uses transforms only"
|
| 69 |
+
},
|
| 70 |
+
"decord": {
|
| 71 |
+
"version": "0.6.0",
|
| 72 |
+
"build": "official source CPU decoder CP310",
|
| 73 |
+
"wheel_sha256": "e193b356b1e984b4eff08d23b62e482c2c9e5037a6efdc0b1af47079ae2e4c47"
|
| 74 |
+
}
|
| 75 |
+
},
|
| 76 |
+
"wandb_url": "https://wandb.ai/dongqianyu99-zhejiang-university/starvla_tasks/runs/mean3090-flappy-vla-blackwell-20260930",
|
| 77 |
+
"training_results": {
|
| 78 |
+
"training_complete": true,
|
| 79 |
+
"optimizer_updates": 5000,
|
| 80 |
+
"selection": "final_5000",
|
| 81 |
+
"train_loss": 0.028774918988347054,
|
| 82 |
+
"validation_action_loss": 0.034965552389621735,
|
| 83 |
+
"validation_samples": 6400,
|
| 84 |
+
"final_backbone_lr": 1e-07,
|
| 85 |
+
"final_action_lr": 3.3333333333333333e-06,
|
| 86 |
+
"closed_loop_evaluation": "pending until all four VLA trainings complete",
|
| 87 |
+
"tensor_keys": 732,
|
| 88 |
+
"serialized_tensor_elements_including_tied_aliases": 4892433410,
|
| 89 |
+
"weights_only_load": "passed",
|
| 90 |
+
"all_tensors_finite": true
|
| 91 |
+
},
|
| 92 |
+
"optimizer_recovery_states": "retained on training host; this bundle contains inference weights"
|
| 93 |
+
}
|
latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/summary.jsonl
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{"steps": 500}
|
| 2 |
+
{"steps": 1000}
|
| 3 |
+
{"steps": 1500}
|
| 4 |
+
{"steps": 2000}
|
| 5 |
+
{"steps": 2500}
|
| 6 |
+
{"steps": 3000}
|
| 7 |
+
{"steps": 3500}
|
| 8 |
+
{"steps": 4000}
|
| 9 |
+
{"steps": 4500}
|
| 10 |
+
{"steps": 5000}
|
latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/task_contract.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"task": "flappy",
|
| 3 |
+
"model": "qwenoft",
|
| 4 |
+
"action_horizon": 1,
|
| 5 |
+
"image_frames": 1,
|
| 6 |
+
"state_source": "transport",
|
| 7 |
+
"state_dim": 24,
|
| 8 |
+
"history_decisions": 8,
|
| 9 |
+
"history_encoding": "binary_0_1",
|
| 10 |
+
"history_normalization": "identity",
|
| 11 |
+
"history_reset": "all_zero",
|
| 12 |
+
"history_order": "oldest_to_newest",
|
| 13 |
+
"history_entry_fields": [
|
| 14 |
+
"admission_bit",
|
| 15 |
+
"admitted_command_non_noop_bits",
|
| 16 |
+
"last_applied_action_non_noop_bits"
|
| 17 |
+
],
|
| 18 |
+
"history_action_bit_order": [
|
| 19 |
+
"FLAP"
|
| 20 |
+
],
|
| 21 |
+
"action_dim": 2,
|
| 22 |
+
"action_layout": "flappy_discrete",
|
| 23 |
+
"action_labels": [
|
| 24 |
+
"NOOP",
|
| 25 |
+
"FLAP"
|
| 26 |
+
],
|
| 27 |
+
"task_prompts": [
|
| 28 |
+
"You are playing Flappy Bird. Pass through the pipe gaps and stay alive. Choose the action: NOOP, FLAP."
|
| 29 |
+
],
|
| 30 |
+
"env_fps": 10,
|
| 31 |
+
"obs_fps": 10,
|
| 32 |
+
"evaluation": {
|
| 33 |
+
"episodes": 100,
|
| 34 |
+
"seeds": [
|
| 35 |
+
1000000,
|
| 36 |
+
1000099
|
| 37 |
+
],
|
| 38 |
+
"parallel_envs": 32,
|
| 39 |
+
"capacity": 1,
|
| 40 |
+
"max_raw_frames": 3600,
|
| 41 |
+
"protocol_version": "hist8-history01-par32-v3"
|
| 42 |
+
}
|
| 43 |
+
}
|
latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/training-code/QWen3.py
ADDED
|
@@ -0,0 +1,444 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2025 starVLA community. All rights reserved.
|
| 2 |
+
# Licensed under the MIT License, Version 1.0 (the "License");
|
| 3 |
+
# Implemented by [Jinhui YE / HKUST University] in [2025].
|
| 4 |
+
|
| 5 |
+
import time
|
| 6 |
+
from collections import OrderedDict
|
| 7 |
+
from functools import wraps
|
| 8 |
+
from inspect import signature
|
| 9 |
+
from typing import Optional
|
| 10 |
+
|
| 11 |
+
import torch
|
| 12 |
+
from starVLA.model.profiling import stage_timer as _stage
|
| 13 |
+
from starVLA.training.trainer_utils import initialize_overwatch
|
| 14 |
+
from transformers import AutoProcessor, Qwen3VLForConditionalGeneration
|
| 15 |
+
from transformers.modeling_outputs import CausalLMOutputWithPast
|
| 16 |
+
|
| 17 |
+
logger = initialize_overwatch(__name__)
|
| 18 |
+
|
| 19 |
+
IGNORE_INDEX = -100
|
| 20 |
+
IMAGE_TOKEN_INDEX = 151655
|
| 21 |
+
VIDEO_TOKEN_INDEX = 151656
|
| 22 |
+
DEFAULT_IMAGE_TOKEN = "<image>"
|
| 23 |
+
DEFAULT_VIDEO_TOKEN = "<video>"
|
| 24 |
+
|
| 25 |
+
_ACTION_TOKEN_MIN = 151669 # how can we know this range? check how you add fast tokens into VLM
|
| 26 |
+
_ACTION_TOKEN_MAX = (
|
| 27 |
+
153716 # here only for fast_tokenizer, see starVLA/model/modules/vlm/tools/add_qwen_special_tokens/README.md
|
| 28 |
+
)
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
import torch.nn as nn
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def _patch_qwen3vl_flash_attention_position_ids() -> None:
|
| 35 |
+
from transformers.models.qwen3_vl import modeling_qwen3_vl as qwen3_vl
|
| 36 |
+
|
| 37 |
+
text_attention_cls = qwen3_vl.Qwen3VLTextAttention
|
| 38 |
+
if hasattr(text_attention_cls, "_starvla_flash_attention_position_ids_patched"):
|
| 39 |
+
return
|
| 40 |
+
|
| 41 |
+
original_forward = text_attention_cls.forward
|
| 42 |
+
|
| 43 |
+
def forward_without_flash_attention_position_ids(self, *args, **kwargs):
|
| 44 |
+
# Qwen3-VL already applies M-RoPE through position_embeddings before
|
| 45 |
+
# attention. Passing the repeated temporal M-RoPE ids into HF's FA2
|
| 46 |
+
# wrapper makes Transformers 4.57 mis-detect packed sequences and build
|
| 47 |
+
# empty cu_seqlens on KV-memory cache steps.
|
| 48 |
+
if self.config._attn_implementation == "flash_attention_2" and "position_ids" in kwargs:
|
| 49 |
+
del kwargs["position_ids"]
|
| 50 |
+
return original_forward(self, *args, **kwargs)
|
| 51 |
+
|
| 52 |
+
text_attention_cls.forward = forward_without_flash_attention_position_ids
|
| 53 |
+
text_attention_cls._starvla_flash_attention_position_ids_patched = True
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
# The fixed Triton stage counts keep the PyTorch 2.11 CUDA 12.8 kernel
|
| 57 |
+
# within the shared-memory budget on sm_120.
|
| 58 |
+
_FLEX_KERNEL_OPTIONS = {
|
| 59 |
+
"BACKEND": "TRITON",
|
| 60 |
+
"fwd_num_stages": 2,
|
| 61 |
+
"bwd_num_stages": 1,
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
def _patch_qwen3vl_flex_attention_support() -> None:
|
| 66 |
+
"""Allow loading Qwen3-VL with ``attn_implementation='flex_attention'``.
|
| 67 |
+
|
| 68 |
+
Qwen3-VL's attention already dispatches through the generic
|
| 69 |
+
``ALL_ATTENTION_FUNCTIONS`` interface (which includes the ``flex_attention``
|
| 70 |
+
integration), but the model classes conservatively declare
|
| 71 |
+
``_supports_flex_attn = False``, so Transformers 4.57 refuses to load with
|
| 72 |
+
flex at __init__. Flipping the flag lets the official dispatch path run; the
|
| 73 |
+
packed KV-memory training (`QwenOFT._forward_memory_packed`) passes a
|
| 74 |
+
BlockMask that FlexAttention consumes natively.
|
| 75 |
+
|
| 76 |
+
Also wraps the registered ``flex_attention`` interface to inject the fixed
|
| 77 |
+
Triton kernel options. Wrapping the dispatch entry — rather than threading
|
| 78 |
+
``kernel_options`` through the top-level forward kwargs — guarantees both the
|
| 79 |
+
forward and the gradient-checkpointing backward recompile pick it up.
|
| 80 |
+
"""
|
| 81 |
+
from transformers.models.qwen3_vl import modeling_qwen3_vl as qwen3_vl
|
| 82 |
+
|
| 83 |
+
for cls_name in (
|
| 84 |
+
"Qwen3VLForConditionalGeneration",
|
| 85 |
+
"Qwen3VLModel",
|
| 86 |
+
"Qwen3VLTextModel",
|
| 87 |
+
"Qwen3VLPreTrainedModel",
|
| 88 |
+
):
|
| 89 |
+
cls = getattr(qwen3_vl, cls_name, None)
|
| 90 |
+
if cls is not None:
|
| 91 |
+
cls._supports_flex_attn = True
|
| 92 |
+
|
| 93 |
+
from transformers.modeling_utils import ALL_ATTENTION_FUNCTIONS
|
| 94 |
+
|
| 95 |
+
base_flex = ALL_ATTENTION_FUNCTIONS["flex_attention"]
|
| 96 |
+
if getattr(base_flex, "_starvla_kernel_options_patched", False):
|
| 97 |
+
return
|
| 98 |
+
|
| 99 |
+
def flex_attention_with_kernel_options(*args, **kwargs):
|
| 100 |
+
kwargs["kernel_options"] = _FLEX_KERNEL_OPTIONS
|
| 101 |
+
return base_flex(*args, **kwargs)
|
| 102 |
+
|
| 103 |
+
flex_attention_with_kernel_options._starvla_kernel_options_patched = True
|
| 104 |
+
ALL_ATTENTION_FUNCTIONS["flex_attention"] = flex_attention_with_kernel_options
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
def _position_tensor_key(tensor):
|
| 108 |
+
if tensor is None:
|
| 109 |
+
return None
|
| 110 |
+
return (tensor.device, tensor.dtype, tuple(tensor.shape),
|
| 111 |
+
tuple(tensor.detach().cpu().reshape(-1).tolist()))
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
def _cache_position_method(module, name, weight):
|
| 115 |
+
"""Cache layout-only outputs on one native model instance, never image features."""
|
| 116 |
+
original = getattr(module, name)
|
| 117 |
+
parameters = signature(original)
|
| 118 |
+
cache = OrderedDict()
|
| 119 |
+
|
| 120 |
+
@wraps(original)
|
| 121 |
+
def cached(*args, **kwargs):
|
| 122 |
+
if module.training or torch.is_grad_enabled():
|
| 123 |
+
cache.clear()
|
| 124 |
+
return original(*args, **kwargs)
|
| 125 |
+
arguments = parameters.bind(*args, **kwargs)
|
| 126 |
+
arguments.apply_defaults()
|
| 127 |
+
parameter = weight()
|
| 128 |
+
key = (
|
| 129 |
+
parameter.device, parameter.dtype, parameter._version,
|
| 130 |
+
tuple(_position_tensor_key(value) for value in arguments.arguments.values()),
|
| 131 |
+
)
|
| 132 |
+
if key not in cache:
|
| 133 |
+
cache[key] = original(*args, **kwargs)
|
| 134 |
+
if len(cache) > 8:
|
| 135 |
+
cache.popitem(last=False)
|
| 136 |
+
cache.move_to_end(key)
|
| 137 |
+
return cache[key]
|
| 138 |
+
|
| 139 |
+
setattr(module, name, cached)
|
| 140 |
+
return cache
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
class _QWen3_VL_Interface(nn.Module):
|
| 144 |
+
"""
|
| 145 |
+
This exists because of the diversity of VLMs, so we encapsulate the changes here.
|
| 146 |
+
Lightweight wrapper around Qwen3-VL (Qwen3VLForConditionalGeneration).
|
| 147 |
+
|
| 148 |
+
Purpose:
|
| 149 |
+
- Unify interface with other VLM backends (CausalLM-like usage).
|
| 150 |
+
- Centralize preprocessing (tokenization + multimodal packing).
|
| 151 |
+
- Provide consistent forward / generate signatures.
|
| 152 |
+
|
| 153 |
+
"""
|
| 154 |
+
|
| 155 |
+
def __init__(self, config: Optional[dict] = None, **kwargs):
|
| 156 |
+
"""
|
| 157 |
+
Initialize the Qwen3-VL wrapper.
|
| 158 |
+
Following https://huggingface.co/Qwen/Qwen3-VL-4B-Instruct
|
| 159 |
+
|
| 160 |
+
"""
|
| 161 |
+
super().__init__()
|
| 162 |
+
|
| 163 |
+
qwenvl_config = config.framework.get("qwenvl", {})
|
| 164 |
+
model_id = qwenvl_config.get("base_vlm", "Qwen/Qwen3-VL-4B-Instruct")
|
| 165 |
+
attn_implementation = qwenvl_config.get("attn_implementation", "sdpa")
|
| 166 |
+
enable_grad_ckpt = qwenvl_config.get("enable_gradient_checkpointing", False)
|
| 167 |
+
print(
|
| 168 |
+
f"[QWen3] loading {model_id} with gradient_checkpointing={enable_grad_ckpt}",
|
| 169 |
+
flush=True,
|
| 170 |
+
)
|
| 171 |
+
|
| 172 |
+
if attn_implementation == "flash_attention_2":
|
| 173 |
+
import flash_attn_2_cuda # noqa: F401
|
| 174 |
+
|
| 175 |
+
_patch_qwen3vl_flash_attention_position_ids()
|
| 176 |
+
|
| 177 |
+
if attn_implementation == "flex_attention":
|
| 178 |
+
_patch_qwen3vl_flex_attention_support()
|
| 179 |
+
|
| 180 |
+
model = Qwen3VLForConditionalGeneration.from_pretrained(
|
| 181 |
+
model_id,
|
| 182 |
+
attn_implementation=attn_implementation,
|
| 183 |
+
dtype=torch.bfloat16,
|
| 184 |
+
ignore_mismatched_sizes=True, # resize image no longer needed? @TODO check bug
|
| 185 |
+
)
|
| 186 |
+
processor = AutoProcessor.from_pretrained(model_id)
|
| 187 |
+
processor.tokenizer.padding_side = "left"
|
| 188 |
+
|
| 189 |
+
if enable_grad_ckpt:
|
| 190 |
+
model.config.text_config.use_cache = False
|
| 191 |
+
model.gradient_checkpointing_enable(
|
| 192 |
+
gradient_checkpointing_kwargs={"use_reentrant": False}
|
| 193 |
+
)
|
| 194 |
+
model.enable_input_require_grads()
|
| 195 |
+
print(
|
| 196 |
+
"[QWen3] gradient_checkpointing ENABLED "
|
| 197 |
+
f"(use_reentrant=False, active={model.is_gradient_checkpointing}, "
|
| 198 |
+
f"text_use_cache={model.config.text_config.use_cache})",
|
| 199 |
+
flush=True,
|
| 200 |
+
)
|
| 201 |
+
|
| 202 |
+
self.model = model
|
| 203 |
+
self.processor = processor
|
| 204 |
+
self.config = config
|
| 205 |
+
# align qwen3 with qwen2.5
|
| 206 |
+
self.model.config.hidden_size = self.model.config.text_config.hidden_size
|
| 207 |
+
self._last_build_timing = {}
|
| 208 |
+
visual = model.model.visual
|
| 209 |
+
self._position_caches = [
|
| 210 |
+
_cache_position_method(model.model, "get_rope_index", lambda: visual.rotary_pos_emb.inv_freq),
|
| 211 |
+
_cache_position_method(visual, "rot_pos_emb", lambda: visual.rotary_pos_emb.inv_freq),
|
| 212 |
+
_cache_position_method(visual, "fast_pos_embed_interpolate", lambda: visual.pos_embed.weight),
|
| 213 |
+
]
|
| 214 |
+
|
| 215 |
+
# only for fast base model
|
| 216 |
+
if "-Action" in model_id:
|
| 217 |
+
self._ACTION_TOKEN_MIN = _ACTION_TOKEN_MIN
|
| 218 |
+
self._ACTION_TOKEN_MAX = _ACTION_TOKEN_MAX
|
| 219 |
+
|
| 220 |
+
def _clear_position_caches(self):
|
| 221 |
+
for cache in self._position_caches:
|
| 222 |
+
cache.clear()
|
| 223 |
+
|
| 224 |
+
def train(self, mode=True):
|
| 225 |
+
self._clear_position_caches()
|
| 226 |
+
return super().train(mode)
|
| 227 |
+
|
| 228 |
+
def _apply(self, fn, recurse=True):
|
| 229 |
+
self._clear_position_caches()
|
| 230 |
+
return super()._apply(fn, recurse=recurse)
|
| 231 |
+
|
| 232 |
+
def _load_from_state_dict(self, *args, **kwargs):
|
| 233 |
+
self._clear_position_caches()
|
| 234 |
+
return super()._load_from_state_dict(*args, **kwargs)
|
| 235 |
+
|
| 236 |
+
def _profile_timing_enabled(self) -> bool:
|
| 237 |
+
return self.config.trainer.profile_timing.enabled
|
| 238 |
+
|
| 239 |
+
def _profile_sync(self) -> None:
|
| 240 |
+
if self._profile_timing_enabled() and torch.cuda.is_available():
|
| 241 |
+
torch.cuda.synchronize()
|
| 242 |
+
|
| 243 |
+
def forward(
|
| 244 |
+
self,
|
| 245 |
+
**kwargs,
|
| 246 |
+
) -> CausalLMOutputWithPast:
|
| 247 |
+
"""
|
| 248 |
+
Forward pass delegating to underlying Qwen2.5-VL backbone.
|
| 249 |
+
"""
|
| 250 |
+
|
| 251 |
+
with torch.autocast("cuda", dtype=torch.bfloat16):
|
| 252 |
+
outputs = self.model(
|
| 253 |
+
**kwargs,
|
| 254 |
+
)
|
| 255 |
+
|
| 256 |
+
return outputs
|
| 257 |
+
|
| 258 |
+
def forward_last_hidden(self, **kwargs) -> torch.Tensor:
|
| 259 |
+
"""
|
| 260 |
+
Run the Qwen3-VL backbone and return only the final hidden state.
|
| 261 |
+
|
| 262 |
+
OFT-style action heads only consume the last hidden layer. Calling the
|
| 263 |
+
full conditional-generation wrapper with ``output_hidden_states=True``
|
| 264 |
+
returns every layer's activations, which can largely erase the memory
|
| 265 |
+
benefit of gradient checkpointing.
|
| 266 |
+
"""
|
| 267 |
+
|
| 268 |
+
kwargs = dict(kwargs)
|
| 269 |
+
kwargs.pop("output_hidden_states", None)
|
| 270 |
+
kwargs.pop("output_attentions", None)
|
| 271 |
+
kwargs.setdefault("return_dict", True)
|
| 272 |
+
kwargs.setdefault("use_cache", False)
|
| 273 |
+
|
| 274 |
+
def _wrapper_forward_last_hidden() -> torch.Tensor:
|
| 275 |
+
lm_head = getattr(self.model, "lm_head", None)
|
| 276 |
+
captured = {}
|
| 277 |
+
handle = None
|
| 278 |
+
if lm_head is not None:
|
| 279 |
+
def _capture_lm_head_input(_module, inputs):
|
| 280 |
+
captured["last_hidden"] = inputs[0]
|
| 281 |
+
|
| 282 |
+
handle = lm_head.register_forward_pre_hook(_capture_lm_head_input)
|
| 283 |
+
|
| 284 |
+
with torch.autocast("cuda", dtype=torch.bfloat16):
|
| 285 |
+
try:
|
| 286 |
+
outputs = self.model(
|
| 287 |
+
**kwargs,
|
| 288 |
+
output_attentions=False,
|
| 289 |
+
output_hidden_states=False,
|
| 290 |
+
return_dict=True,
|
| 291 |
+
)
|
| 292 |
+
finally:
|
| 293 |
+
if handle is not None:
|
| 294 |
+
handle.remove()
|
| 295 |
+
|
| 296 |
+
if "last_hidden" in captured:
|
| 297 |
+
return captured["last_hidden"]
|
| 298 |
+
|
| 299 |
+
# Last-resort compatibility path for unusual model wrappers that do
|
| 300 |
+
# not expose lm_head. This preserves correctness, but returns every
|
| 301 |
+
# layer and therefore saves less memory.
|
| 302 |
+
with torch.autocast("cuda", dtype=torch.bfloat16):
|
| 303 |
+
outputs = self.model(
|
| 304 |
+
**kwargs,
|
| 305 |
+
output_attentions=False,
|
| 306 |
+
output_hidden_states=True,
|
| 307 |
+
return_dict=True,
|
| 308 |
+
)
|
| 309 |
+
return outputs.hidden_states[-1]
|
| 310 |
+
|
| 311 |
+
backbone = getattr(self.model, "model", None)
|
| 312 |
+
if backbone is None:
|
| 313 |
+
return _wrapper_forward_last_hidden()
|
| 314 |
+
|
| 315 |
+
try:
|
| 316 |
+
with torch.autocast("cuda", dtype=torch.bfloat16):
|
| 317 |
+
outputs = backbone(**kwargs)
|
| 318 |
+
except TypeError:
|
| 319 |
+
# Some Transformers Qwen-VL wrappers inject visual embeddings before
|
| 320 |
+
# calling the text backbone, so the backbone itself may not accept
|
| 321 |
+
# pixel_values/image_grid_thw. Keep those versions working.
|
| 322 |
+
return _wrapper_forward_last_hidden()
|
| 323 |
+
|
| 324 |
+
if hasattr(outputs, "last_hidden_state"):
|
| 325 |
+
return outputs.last_hidden_state
|
| 326 |
+
return outputs[0]
|
| 327 |
+
|
| 328 |
+
def generate(
|
| 329 |
+
self,
|
| 330 |
+
**kwargs,
|
| 331 |
+
):
|
| 332 |
+
"""
|
| 333 |
+
High-level generation interface (auto-regressive decoding), optionally vision-conditioned.
|
| 334 |
+
|
| 335 |
+
Args:
|
| 336 |
+
**kwargs: fully follow raw model.generate() signature.
|
| 337 |
+
Returns:
|
| 338 |
+
GenerateOutput | Model-dependent generation return.
|
| 339 |
+
"""
|
| 340 |
+
with torch.autocast("cuda", dtype=torch.float16):
|
| 341 |
+
generation_output = self.model.generate(
|
| 342 |
+
**kwargs,
|
| 343 |
+
)
|
| 344 |
+
return generation_output
|
| 345 |
+
|
| 346 |
+
def build_qwenvl_inputs(self, images, instructions, solutions=None, profiler=None, **kwargs):
|
| 347 |
+
"""
|
| 348 |
+
Build model inputs from raw data (images + instructions + optional solutions).
|
| 349 |
+
Follow Oficial Qwen3-VL Instruct format: https://huggingface.co/Qwen/Qwen3-VL-4B-Instruct
|
| 350 |
+
"""
|
| 351 |
+
|
| 352 |
+
# Create messages: one message per sample
|
| 353 |
+
with _stage(profiler, "starvla_qwen_message_build_ms"):
|
| 354 |
+
messages = []
|
| 355 |
+
assert len(images) == len(instructions), "Images and instructions must have the same length"
|
| 356 |
+
for imgs, instruction in zip(images, instructions):
|
| 357 |
+
content = [{"type": "image", "image": img} for img in imgs]
|
| 358 |
+
|
| 359 |
+
if "CoT_prompt" in self.config.datasets.vla_data: # If using a grounding prompt to task
|
| 360 |
+
CoT_prompt = self.config.datasets.vla_data.get("CoT_prompt", "")
|
| 361 |
+
prompt = CoT_prompt.replace("{instruction}", instruction)
|
| 362 |
+
else:
|
| 363 |
+
prompt = instruction
|
| 364 |
+
|
| 365 |
+
content.append({"type": "text", "text": prompt})
|
| 366 |
+
msg = [{"role": "user", "content": content}]
|
| 367 |
+
|
| 368 |
+
if solutions is not None:
|
| 369 |
+
solution = solutions[len(messages)]
|
| 370 |
+
msg.append({"role": "assistant", "content": [{"type": "text", "text": solution}]})
|
| 371 |
+
messages.append(msg)
|
| 372 |
+
|
| 373 |
+
profile_timing = self._profile_timing_enabled()
|
| 374 |
+
build_timing = {}
|
| 375 |
+
|
| 376 |
+
t_processor = time.perf_counter() if profile_timing else None
|
| 377 |
+
with _stage(profiler, "starvla_qwen_processor_ms"):
|
| 378 |
+
batch_inputs = self.processor.apply_chat_template(
|
| 379 |
+
messages, tokenize=True, padding=True, add_generation_prompt=True, return_dict=True, return_tensors="pt"
|
| 380 |
+
)
|
| 381 |
+
if profile_timing:
|
| 382 |
+
build_timing["timing/qwen_processor"] = time.perf_counter() - t_processor
|
| 383 |
+
|
| 384 |
+
# if solutions, mask out the solution tokens in labels
|
| 385 |
+
if solutions is not None: # here only for fast_tokenizer now.
|
| 386 |
+
action_token_min = _ACTION_TOKEN_MIN # how can we know this range? --> we has other way for this, but is slower see qwenhelix branch
|
| 387 |
+
action_token_max = _ACTION_TOKEN_MAX # here only for fast_tokenizer, see starVLA/model/modules/vlm/tools/add_qwen_special_tokens/README.md
|
| 388 |
+
labels = batch_inputs["input_ids"].clone()
|
| 389 |
+
# For each sequence in the batch, find the first occurrence of an action token.
|
| 390 |
+
for i in range(labels.size(0)):
|
| 391 |
+
seq = labels[i]
|
| 392 |
+
# Create a mask for tokens within the action token range.
|
| 393 |
+
mask_seq = (seq >= action_token_min) & (seq <= action_token_max)
|
| 394 |
+
nonzero_indices = torch.nonzero(mask_seq, as_tuple=False)
|
| 395 |
+
if nonzero_indices.numel() > 0:
|
| 396 |
+
first_action_index = nonzero_indices[0].item()
|
| 397 |
+
# Mask out all tokens before the first action token.
|
| 398 |
+
seq[:first_action_index] = IGNORE_INDEX
|
| 399 |
+
else:
|
| 400 |
+
# If no action token is found, mask the entire sequence.
|
| 401 |
+
seq[:] = IGNORE_INDEX
|
| 402 |
+
RuntimeWarning(
|
| 403 |
+
"action token are on in yout tokenizer, plz see starVLA/model/modules/vlm/tools/add_qwen_special_tokens/README.md."
|
| 404 |
+
)
|
| 405 |
+
|
| 406 |
+
labels[labels == self.processor.tokenizer.pad_token_id] = -100 ## mask out pad tokens as well
|
| 407 |
+
batch_inputs["labels"] = labels
|
| 408 |
+
|
| 409 |
+
t_h2d = time.perf_counter() if profile_timing else None
|
| 410 |
+
with _stage(profiler, "starvla_qwen_h2d_ms"):
|
| 411 |
+
batch_inputs = batch_inputs.to(self.model.device)
|
| 412 |
+
if profile_timing:
|
| 413 |
+
self._profile_sync()
|
| 414 |
+
build_timing["timing/qwen_h2d"] = time.perf_counter() - t_h2d
|
| 415 |
+
self._last_build_timing = build_timing
|
| 416 |
+
return batch_inputs
|
| 417 |
+
|
| 418 |
+
|
| 419 |
+
if __name__ == "__main__":
|
| 420 |
+
import argparse
|
| 421 |
+
import os
|
| 422 |
+
|
| 423 |
+
from omegaconf import OmegaConf
|
| 424 |
+
|
| 425 |
+
parser = argparse.ArgumentParser()
|
| 426 |
+
parser.add_argument(
|
| 427 |
+
"--config_yaml",
|
| 428 |
+
type=str,
|
| 429 |
+
default="examples/SimplerEnv/train_files/starvla_cotrain_oxe.yaml",
|
| 430 |
+
help="Path to YAML config",
|
| 431 |
+
)
|
| 432 |
+
args, clipargs = parser.parse_known_args()
|
| 433 |
+
|
| 434 |
+
if os.getenv("DEBUGPY_ENABLE", "0") == "1":
|
| 435 |
+
import debugpy
|
| 436 |
+
debugpy.listen(("0.0.0.0", 10092))
|
| 437 |
+
print("Rank 0 waiting for debugger attach on port 10092...")
|
| 438 |
+
debugpy.wait_for_client()
|
| 439 |
+
|
| 440 |
+
cfg = OmegaConf.load(args.config_yaml)
|
| 441 |
+
|
| 442 |
+
cfg.framework.qwenvl.base_vlm = "./playground/Pretrained_models/Qwen3-VL-4B-Instruct"
|
| 443 |
+
qwen_vl = _QWen3_VL_Interface(cfg)
|
| 444 |
+
pass
|
latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/training-code/QwenOFT.py
ADDED
|
@@ -0,0 +1,1244 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2025 starVLA community. All rights reserved.
|
| 2 |
+
# Licensed under the MIT License, Version 1.0 (the "License");
|
| 3 |
+
# Implemented by [Jinhui YE / HKUST University] in [2025].
|
| 4 |
+
|
| 5 |
+
"""
|
| 6 |
+
Qwen-OFT Framework
|
| 7 |
+
|
| 8 |
+
A lightweight implementation that uses an action special token to parallelly predict continuous actions
|
| 9 |
+
conditioned on multi-view images plus a language instruction (shares parameters with the VLM).
|
| 10 |
+
Inspired by OpenVLA-OFT
|
| 11 |
+
Key Points:
|
| 12 |
+
- Qwen2.5 vision-language backbone
|
| 13 |
+
- Injects an action special token into the VLM
|
| 14 |
+
- Continuous action prediction via L1 regression over the action special token hidden states
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
Note: How to add special tokens to Qwen2.5:
|
| 18 |
+
download our model checkpoint with special tokens added: https://huggingface.co/StarVLA/Qwen2.5-VL-3B-Instruct-Action
|
| 19 |
+
or /starVLA/model/modules/vlm/tools/add_qwen_special_tokens/README.md (adpat a little code)
|
| 20 |
+
|
| 21 |
+
"""
|
| 22 |
+
|
| 23 |
+
from dataclasses import dataclass, field
|
| 24 |
+
import time
|
| 25 |
+
from typing import Any, List, Optional, Tuple
|
| 26 |
+
|
| 27 |
+
import numpy as np
|
| 28 |
+
import torch
|
| 29 |
+
import torch.nn as nn
|
| 30 |
+
import torch.nn.functional as F
|
| 31 |
+
from PIL import Image
|
| 32 |
+
|
| 33 |
+
from deployment.model_server.tools.image_tools import to_pil_preserve
|
| 34 |
+
from starVLA.model.tools import FRAMEWORK_REGISTRY
|
| 35 |
+
from starVLA.training.trainer_utils import initialize_overwatch
|
| 36 |
+
|
| 37 |
+
logger = initialize_overwatch(__name__)
|
| 38 |
+
|
| 39 |
+
# HuggingFace Default / LLaMa-2 IGNORE_INDEX (for labels)
|
| 40 |
+
IGNORE_INDEX = -100
|
| 41 |
+
|
| 42 |
+
from starVLA.model.framework.base_framework import baseframework
|
| 43 |
+
from starVLA.model.framework.share_tools import add_discretized_state_to_instruction, merge_framework_config
|
| 44 |
+
from starVLA.model.modules.action_model.MLP_ActionHeader import get_action_model
|
| 45 |
+
from starVLA.model.modules.vlm import get_vlm_model
|
| 46 |
+
from starVLA.training.trainer_utils.trainer_tools import resize_images
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
# ──────────────────────────────────────────────────────────────────────
|
| 50 |
+
# Default Config for QwenOFT
|
| 51 |
+
# - Documents every framework-level parameter with type + description
|
| 52 |
+
# - YAML values override these defaults; extra YAML keys are preserved
|
| 53 |
+
# ──────────────────────────────────────────────────────────────────────
|
| 54 |
+
@dataclass
|
| 55 |
+
class QwenOFTDefaultConfig:
|
| 56 |
+
"""QwenOFT framework default parameters.
|
| 57 |
+
|
| 58 |
+
All fields can be overridden by the corresponding key in the YAML
|
| 59 |
+
``framework:`` section. Extra YAML keys not listed here are kept
|
| 60 |
+
as-is (Config-as-API flexibility).
|
| 61 |
+
"""
|
| 62 |
+
|
| 63 |
+
# --- Registry identifier (must match @FRAMEWORK_REGISTRY.register) ---
|
| 64 |
+
name: str = "QwenOFT"
|
| 65 |
+
|
| 66 |
+
# === VLM backbone (Qwen2.5-VL / Qwen3-VL) ===
|
| 67 |
+
qwenvl: dict = field(
|
| 68 |
+
default_factory=lambda: {
|
| 69 |
+
# Path to base VLM checkpoint (local or HF hub id)
|
| 70 |
+
"base_vlm": "./playground/Pretrained_models/Qwen3-VL-4B-Instruct-Action",
|
| 71 |
+
# Attention implementation: "flash_attention_2" | "eager" | "sdpa"
|
| 72 |
+
"attn_implementation": "flash_attention_2",
|
| 73 |
+
}
|
| 74 |
+
)
|
| 75 |
+
|
| 76 |
+
# === Action head (MLP regression over action special tokens) ===
|
| 77 |
+
action_model: dict = field(
|
| 78 |
+
default_factory=lambda: {
|
| 79 |
+
# Action head architecture type
|
| 80 |
+
"action_model_type": "MLP",
|
| 81 |
+
# Dimensionality of each action vector (e.g., 7 for 6-DoF + gripper)
|
| 82 |
+
"action_dim": 7,
|
| 83 |
+
# Proprioceptive state dimension for continuous_projector mode
|
| 84 |
+
"state_dim": 7,
|
| 85 |
+
# Hidden dim for the action MLP (auto-set from VLM hidden_size at runtime)
|
| 86 |
+
"action_hidden_dim": 2560,
|
| 87 |
+
# How many future steps to predict
|
| 88 |
+
"future_action_window_size": 8,
|
| 89 |
+
# How many past steps included in action chunk (usually 0)
|
| 90 |
+
"past_action_window_size": 0,
|
| 91 |
+
# Loss over predicted actions. Supported: "l1", "discrete_ce",
|
| 92 |
+
# "multibinary_bce", "asterix_factorized_ce".
|
| 93 |
+
# "multibinary_ce" is accepted as a CLI alias for "multibinary_bce".
|
| 94 |
+
"loss_type": "l1",
|
| 95 |
+
# State conditioning: preserve the existing text-bin path by default.
|
| 96 |
+
"state_encoding": "discretized_text",
|
| 97 |
+
}
|
| 98 |
+
)
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
@FRAMEWORK_REGISTRY.register("QwenOFT")
|
| 102 |
+
class Qwenvl_OFT(baseframework):
|
| 103 |
+
"""
|
| 104 |
+
Multimodal vision-language-action model (OFT variant).
|
| 105 |
+
|
| 106 |
+
Components:
|
| 107 |
+
- Qwen2.5-VL / Qwen3-VL backbone for fused language/vision token embeddings
|
| 108 |
+
- Action special token injected into the VLM sequence
|
| 109 |
+
- MLP regression head over action token hidden states (L1 loss)
|
| 110 |
+
|
| 111 |
+
Focus: Predict future continuous actions conditioned on images + instruction.
|
| 112 |
+
"""
|
| 113 |
+
|
| 114 |
+
def __init__(
|
| 115 |
+
self,
|
| 116 |
+
config: Optional[dict] = None,
|
| 117 |
+
**kwargs,
|
| 118 |
+
) -> None:
|
| 119 |
+
"""
|
| 120 |
+
Construct all submodules and cache key configuration values.
|
| 121 |
+
|
| 122 |
+
Args:
|
| 123 |
+
config: Hierarchical configuration (OmegaConf/dict) containing framework + trainer sections.
|
| 124 |
+
**kwargs: Reserved for future overrides (unused).
|
| 125 |
+
"""
|
| 126 |
+
super().__init__()
|
| 127 |
+
# Merge framework defaults with YAML config (YAML wins on conflicts)
|
| 128 |
+
self.config = merge_framework_config(QwenOFTDefaultConfig, config)
|
| 129 |
+
self.qwen_vl_interface = get_vlm_model(config=self.config)
|
| 130 |
+
# align action_hidden_dim to VLM hidden_size at runtime
|
| 131 |
+
self.config.framework.action_model.action_hidden_dim = self.qwen_vl_interface.model.config.hidden_size
|
| 132 |
+
self.action_model = get_action_model(config=self.config)
|
| 133 |
+
|
| 134 |
+
# `action_horizon` is the single source of truth for chunk length.
|
| 135 |
+
# Legacy aliases (`future_action_window_size`, `past_action_window_size`)
|
| 136 |
+
# are normalised upstream by `share_tools.apply_config_compat`, so we
|
| 137 |
+
# only ever read `action_horizon` here.
|
| 138 |
+
self.action_horizon = int(self.config.framework.action_model.action_horizon)
|
| 139 |
+
self.chunk_len = self.action_horizon
|
| 140 |
+
self.action_dim = int(self.config.framework.action_model.action_dim)
|
| 141 |
+
self.action_env_dim = int(getattr(self.config.framework.action_model, "action_env_dim", self.action_dim))
|
| 142 |
+
self.action_loss_type = str(getattr(self.config.framework.action_model, "loss_type", "l1")).lower()
|
| 143 |
+
self.state_encoding = str(self.config.framework.action_model.state_encoding)
|
| 144 |
+
if self.state_encoding not in {"discretized_text", "continuous_projector"}:
|
| 145 |
+
raise ValueError(f"Unsupported state_encoding: {self.state_encoding}")
|
| 146 |
+
if self.state_encoding == "continuous_projector":
|
| 147 |
+
self.action_model.state_projector = nn.Linear(
|
| 148 |
+
int(self.config.framework.action_model.state_dim),
|
| 149 |
+
int(self.config.framework.action_model.action_hidden_dim),
|
| 150 |
+
)
|
| 151 |
+
cross_task_cfg = getattr(getattr(self.config, "rl_games", None), "cross_task", None)
|
| 152 |
+
self.loss_by_task = self._to_plain_dict(getattr(cross_task_cfg, "loss_by_task", None))
|
| 153 |
+
self.loss_weight_by_task = self._to_plain_dict(getattr(cross_task_cfg, "loss_weight_by_task", None))
|
| 154 |
+
# self.hidden_dim = config.framework.action_model.action_hidden_dim
|
| 155 |
+
|
| 156 |
+
self.action_token = "🔍" # TODO also can add spacail token to Qwen, but too complex
|
| 157 |
+
self.action_token_id = self.qwen_vl_interface.processor.tokenizer("🔍", add_special_tokens=False)["input_ids"][0]
|
| 158 |
+
|
| 159 |
+
# --- Fixed-size KV memory; default OFF so existing paths are untouched. ---
|
| 160 |
+
kv_cfg = getattr(self.config.framework, "kv_memory", None)
|
| 161 |
+
self.kv_memory_enabled = bool(getattr(kv_cfg, "enabled", False)) if kv_cfg is not None else False
|
| 162 |
+
self.kv_window = int(getattr(kv_cfg, "window", 4)) if kv_cfg is not None else 4
|
| 163 |
+
self.kv_rollout_len = int(getattr(kv_cfg, "rollout_len", max(self.kv_window + 1, 2))) if kv_cfg is not None else self.kv_window + 1
|
| 164 |
+
# Packed single-forward training path (StreamingLLM-style sliding-window+sink
|
| 165 |
+
# mask via FlexAttention) — replaces the per-frame Python loop. Eval/live still
|
| 166 |
+
# uses the streaming KV memory (`_predict_action_memory`). OFF by default so the
|
| 167 |
+
# legacy loop stays the reference; enable per experiment via
|
| 168 |
+
# `framework.kv_memory.packed_train: true`.
|
| 169 |
+
self.kv_train_packed = bool(getattr(kv_cfg, "packed_train", False)) if kv_cfg is not None else False
|
| 170 |
+
# Per-step re-based sink for the packed path: replicate the (short) text
|
| 171 |
+
# prefix once per step and re-base its M-RoPE positions so the sink→frame
|
| 172 |
+
# distance matches the eval rolling-KV layout exactly (StreamingLLM
|
| 173 |
+
# "positions within the cache"), instead of one global sink whose distance
|
| 174 |
+
# grows with the step index. ON by default — the growing-distance sink was
|
| 175 |
+
# the train/eval forward mismatch behind the packed run's eval collapse.
|
| 176 |
+
# Set false to fall back to the single global sink for A/B.
|
| 177 |
+
self.kv_rebased_sink = bool(getattr(kv_cfg, "rebased_sink", True)) if kv_cfg is not None else True
|
| 178 |
+
if self.state_encoding == "continuous_projector" and self.kv_memory_enabled:
|
| 179 |
+
raise ValueError("continuous_projector requires framework.kv_memory.enabled=false")
|
| 180 |
+
# Per-stream (eval slot) memories; rebuilt per sample during training.
|
| 181 |
+
self._kv_memories: dict = {}
|
| 182 |
+
|
| 183 |
+
# L1 loss
|
| 184 |
+
self.l1_loss = nn.L1Loss()
|
| 185 |
+
|
| 186 |
+
def _profile_timing_enabled(self) -> bool:
|
| 187 |
+
return self.config.trainer.profile_timing.enabled
|
| 188 |
+
|
| 189 |
+
def _profile_sync(self) -> None:
|
| 190 |
+
if self._profile_timing_enabled() and torch.cuda.is_available():
|
| 191 |
+
torch.cuda.synchronize()
|
| 192 |
+
|
| 193 |
+
def _profile_start(self) -> float:
|
| 194 |
+
self._profile_sync()
|
| 195 |
+
return time.perf_counter()
|
| 196 |
+
|
| 197 |
+
def _profile_elapsed(self, start: float) -> float:
|
| 198 |
+
self._profile_sync()
|
| 199 |
+
return time.perf_counter() - start
|
| 200 |
+
|
| 201 |
+
@staticmethod
|
| 202 |
+
def _to_plain_dict(value: Any) -> dict[str, Any]:
|
| 203 |
+
if value is None or value == "":
|
| 204 |
+
return {}
|
| 205 |
+
if isinstance(value, dict):
|
| 206 |
+
return {str(key): item for key, item in value.items()}
|
| 207 |
+
if hasattr(value, "items"):
|
| 208 |
+
return {str(key): item for key, item in value.items()}
|
| 209 |
+
return {}
|
| 210 |
+
|
| 211 |
+
def _prepare_action_array(self, action: Any) -> np.ndarray:
|
| 212 |
+
values = np.asarray(action)
|
| 213 |
+
if values.ndim == 1:
|
| 214 |
+
values = values[None, :]
|
| 215 |
+
if values.shape[-1] == self.action_dim:
|
| 216 |
+
return values
|
| 217 |
+
if values.shape[-1] > self.action_dim:
|
| 218 |
+
return values[..., : self.action_dim]
|
| 219 |
+
pad_width = [(0, 0)] * values.ndim
|
| 220 |
+
pad_width[-1] = (0, self.action_dim - values.shape[-1])
|
| 221 |
+
return np.pad(values, pad_width, mode="constant")
|
| 222 |
+
|
| 223 |
+
def _compute_action_loss_for_type(
|
| 224 |
+
self,
|
| 225 |
+
pred_actions: torch.Tensor,
|
| 226 |
+
actions_target: torch.Tensor,
|
| 227 |
+
*,
|
| 228 |
+
loss_type: str,
|
| 229 |
+
action_env_dims: Optional[List[int]] = None,
|
| 230 |
+
) -> torch.Tensor:
|
| 231 |
+
loss_type = str(loss_type).lower()
|
| 232 |
+
effective_dim = min(self.action_env_dim, pred_actions.shape[-1], actions_target.shape[-1])
|
| 233 |
+
if effective_dim <= 0:
|
| 234 |
+
raise ValueError(
|
| 235 |
+
f"Invalid action_env_dim={self.action_env_dim} for predicted shape={pred_actions.shape} "
|
| 236 |
+
f"and target shape={actions_target.shape}"
|
| 237 |
+
)
|
| 238 |
+
|
| 239 |
+
if loss_type in {"discrete_ce", "ce", "cross_entropy"}:
|
| 240 |
+
if action_env_dims is not None:
|
| 241 |
+
dims = torch.as_tensor(action_env_dims, device=pred_actions.device, dtype=torch.long)
|
| 242 |
+
if dims.numel() != pred_actions.shape[0]:
|
| 243 |
+
raise ValueError(f"Expected {pred_actions.shape[0]} action_env_dims, got {dims.numel()}")
|
| 244 |
+
losses = []
|
| 245 |
+
weights = []
|
| 246 |
+
for dim_value in torch.unique(dims).tolist():
|
| 247 |
+
dim = int(min(dim_value, pred_actions.shape[-1], actions_target.shape[-1]))
|
| 248 |
+
if dim < 2:
|
| 249 |
+
raise ValueError(f"discrete_ce requires at least 2 classes, got action_env_dim={dim}")
|
| 250 |
+
mask = dims == int(dim_value)
|
| 251 |
+
logits = pred_actions[mask, ..., :dim]
|
| 252 |
+
target_class = actions_target[mask, ..., :dim].argmax(dim=-1).long()
|
| 253 |
+
losses.append(F.cross_entropy(logits.reshape(-1, dim), target_class.reshape(-1), reduction="sum"))
|
| 254 |
+
weights.append(target_class.numel())
|
| 255 |
+
total_weight = max(1, int(sum(weights)))
|
| 256 |
+
return sum(losses) / total_weight
|
| 257 |
+
|
| 258 |
+
if effective_dim < 2:
|
| 259 |
+
raise ValueError(
|
| 260 |
+
f"action_model.loss_type={loss_type!r} requires at least 2 action classes, "
|
| 261 |
+
f"got effective_dim={effective_dim}"
|
| 262 |
+
)
|
| 263 |
+
logits = pred_actions[..., :effective_dim]
|
| 264 |
+
target_class = actions_target[..., :effective_dim].argmax(dim=-1).long()
|
| 265 |
+
return F.cross_entropy(
|
| 266 |
+
logits.reshape(-1, effective_dim),
|
| 267 |
+
target_class.reshape(-1),
|
| 268 |
+
)
|
| 269 |
+
|
| 270 |
+
if loss_type in {"asterix_factorized_ce", "factorized_6_ce", "factorized_ce"}:
|
| 271 |
+
if effective_dim < 6:
|
| 272 |
+
raise ValueError(
|
| 273 |
+
f"action_model.loss_type={loss_type!r} requires at least 6 action logits, "
|
| 274 |
+
f"got effective_dim={effective_dim}"
|
| 275 |
+
)
|
| 276 |
+
vertical_logits = pred_actions[..., 0:3]
|
| 277 |
+
vertical_target = actions_target[..., 0:3].argmax(dim=-1).long()
|
| 278 |
+
horizontal_logits = pred_actions[..., 3:6]
|
| 279 |
+
horizontal_target = actions_target[..., 3:6].argmax(dim=-1).long()
|
| 280 |
+
vertical_loss = F.cross_entropy(
|
| 281 |
+
vertical_logits.reshape(-1, 3),
|
| 282 |
+
vertical_target.reshape(-1),
|
| 283 |
+
)
|
| 284 |
+
horizontal_loss = F.cross_entropy(
|
| 285 |
+
horizontal_logits.reshape(-1, 3),
|
| 286 |
+
horizontal_target.reshape(-1),
|
| 287 |
+
)
|
| 288 |
+
return 0.5 * (vertical_loss + horizontal_loss)
|
| 289 |
+
|
| 290 |
+
if loss_type in {"multibinary_bce", "multibinary_ce", "bce", "binary_cross_entropy"}:
|
| 291 |
+
logits = pred_actions[..., :effective_dim]
|
| 292 |
+
targets = (actions_target[..., :effective_dim] > 0).to(dtype=logits.dtype)
|
| 293 |
+
return F.binary_cross_entropy_with_logits(logits, targets)
|
| 294 |
+
|
| 295 |
+
if loss_type in {"mse", "l2"}:
|
| 296 |
+
return F.mse_loss(
|
| 297 |
+
pred_actions[..., :effective_dim],
|
| 298 |
+
actions_target[..., :effective_dim],
|
| 299 |
+
)
|
| 300 |
+
|
| 301 |
+
if loss_type not in {"l1", "mae"}:
|
| 302 |
+
raise ValueError(
|
| 303 |
+
f"Unsupported action_model.loss_type={loss_type!r}; "
|
| 304 |
+
"expected one of: l1, discrete_ce, multibinary_bce, asterix_factorized_ce"
|
| 305 |
+
)
|
| 306 |
+
|
| 307 |
+
return self.l1_loss(
|
| 308 |
+
pred_actions[..., :effective_dim],
|
| 309 |
+
actions_target[..., :effective_dim],
|
| 310 |
+
)
|
| 311 |
+
|
| 312 |
+
def _compute_action_loss(
|
| 313 |
+
self,
|
| 314 |
+
pred_actions: torch.Tensor,
|
| 315 |
+
actions_target: torch.Tensor,
|
| 316 |
+
action_env_dims: Optional[List[int]] = None,
|
| 317 |
+
rl_games_tasks: Optional[List[str]] = None,
|
| 318 |
+
action_loss_mask: Optional[torch.Tensor] = None,
|
| 319 |
+
) -> torch.Tensor:
|
| 320 |
+
if action_loss_mask is not None:
|
| 321 |
+
error = (pred_actions[..., :self.action_env_dim] - actions_target[..., :self.action_env_dim]).abs().mean(dim=-1)
|
| 322 |
+
error = error.masked_fill(~action_loss_mask, 0)
|
| 323 |
+
return (error.sum(dim=-1) / action_loss_mask.sum(dim=-1)).mean()
|
| 324 |
+
if self.loss_by_task and rl_games_tasks is not None:
|
| 325 |
+
if len(rl_games_tasks) != pred_actions.shape[0]:
|
| 326 |
+
raise ValueError(f"Expected {pred_actions.shape[0]} task labels, got {len(rl_games_tasks)}")
|
| 327 |
+
losses = []
|
| 328 |
+
weights = []
|
| 329 |
+
for task_name in sorted(set(str(task) for task in rl_games_tasks)):
|
| 330 |
+
mask_values = [str(task) == task_name for task in rl_games_tasks]
|
| 331 |
+
mask = torch.as_tensor(mask_values, device=pred_actions.device, dtype=torch.bool)
|
| 332 |
+
if not bool(mask.any()):
|
| 333 |
+
continue
|
| 334 |
+
task_loss_type = str(self.loss_by_task.get(task_name, self.action_loss_type)).lower()
|
| 335 |
+
task_dims = (
|
| 336 |
+
[int(dim) for dim, keep in zip(action_env_dims, mask_values) if keep]
|
| 337 |
+
if action_env_dims is not None
|
| 338 |
+
else None
|
| 339 |
+
)
|
| 340 |
+
task_loss = self._compute_action_loss_for_type(
|
| 341 |
+
pred_actions[mask],
|
| 342 |
+
actions_target[mask],
|
| 343 |
+
loss_type=task_loss_type,
|
| 344 |
+
action_env_dims=task_dims,
|
| 345 |
+
)
|
| 346 |
+
task_weight = float(self.loss_weight_by_task.get(task_name, 1.0))
|
| 347 |
+
sample_count = int(mask.sum().item())
|
| 348 |
+
losses.append(task_loss * task_weight * sample_count)
|
| 349 |
+
weights.append(task_weight * sample_count)
|
| 350 |
+
if losses:
|
| 351 |
+
return sum(losses) / max(1.0, float(sum(weights)))
|
| 352 |
+
|
| 353 |
+
return self._compute_action_loss_for_type(
|
| 354 |
+
pred_actions,
|
| 355 |
+
actions_target,
|
| 356 |
+
loss_type=self.action_loss_type,
|
| 357 |
+
action_env_dims=action_env_dims,
|
| 358 |
+
)
|
| 359 |
+
|
| 360 |
+
def _forward_qwen_last_hidden(self, qwen_inputs: dict) -> torch.Tensor:
|
| 361 |
+
"""
|
| 362 |
+
Return only the final VLM hidden state when the interface supports it.
|
| 363 |
+
|
| 364 |
+
Requesting ``output_hidden_states=True`` from Hugging Face materializes
|
| 365 |
+
every layer's activations. For OFT we only need the last layer, so the
|
| 366 |
+
Qwen3 interface exposes a leaner path that preserves the practical memory
|
| 367 |
+
savings from gradient checkpointing.
|
| 368 |
+
"""
|
| 369 |
+
|
| 370 |
+
if hasattr(self.qwen_vl_interface, "forward_last_hidden"):
|
| 371 |
+
return self.qwen_vl_interface.forward_last_hidden(**qwen_inputs)
|
| 372 |
+
|
| 373 |
+
qwenvl_outputs = self.qwen_vl_interface(
|
| 374 |
+
**qwen_inputs,
|
| 375 |
+
output_attentions=False,
|
| 376 |
+
output_hidden_states=True,
|
| 377 |
+
return_dict=True,
|
| 378 |
+
use_cache=False,
|
| 379 |
+
)
|
| 380 |
+
return qwenvl_outputs.hidden_states[-1]
|
| 381 |
+
|
| 382 |
+
def _state_conditioned_instructions(self, instructions, state):
|
| 383 |
+
if state is not None and self.state_encoding == "discretized_text":
|
| 384 |
+
return self.add_discretized_state_to_instruction(instructions, state)
|
| 385 |
+
return instructions
|
| 386 |
+
|
| 387 |
+
def _condition_action_queries(self, action_queries, state):
|
| 388 |
+
if self.state_encoding == "discretized_text":
|
| 389 |
+
return action_queries
|
| 390 |
+
state_tensor = torch.as_tensor(
|
| 391 |
+
np.stack(state), device=action_queries.device, dtype=action_queries.dtype
|
| 392 |
+
).squeeze(1)
|
| 393 |
+
return action_queries + self.action_model.state_projector(state_tensor).unsqueeze(1)
|
| 394 |
+
|
| 395 |
+
def forward(
|
| 396 |
+
self,
|
| 397 |
+
examples: List[dict] = None,
|
| 398 |
+
**kwargs,
|
| 399 |
+
) -> Tuple:
|
| 400 |
+
"""
|
| 401 |
+
Training forward: directly regress future actions (no diffusion).
|
| 402 |
+
|
| 403 |
+
Flow:
|
| 404 |
+
1. Build QwenVL inputs (images + instruction tokens)
|
| 405 |
+
2. Extract hidden states from configured layer range
|
| 406 |
+
7. Predict action and compute L1 loss
|
| 407 |
+
|
| 408 |
+
Args:
|
| 409 |
+
examples: List[dict], each dict requires:
|
| 410 |
+
- image: List[PIL.Image] (multi-view)
|
| 411 |
+
- lang: str instruction
|
| 412 |
+
- action: np.ndarray or list shaped [T, action_dim]
|
| 413 |
+
**kwargs: Reserved.
|
| 414 |
+
|
| 415 |
+
Returns:
|
| 416 |
+
dict:
|
| 417 |
+
action_loss (torch.Tensor): Scalar diffusion noise prediction loss.
|
| 418 |
+
"""
|
| 419 |
+
if self.kv_memory_enabled:
|
| 420 |
+
if self.kv_train_packed:
|
| 421 |
+
return self._forward_memory_packed(examples)
|
| 422 |
+
return self._forward_memory(examples)
|
| 423 |
+
|
| 424 |
+
batch_images = [example["image"] for example in examples] # [B,[PLT]]
|
| 425 |
+
instructions = [example["lang"] for example in examples] # [B, str]
|
| 426 |
+
actions = [self._prepare_action_array(example["action"]) for example in examples] # label [B, len, action_dim]
|
| 427 |
+
action_env_dims = [int(example.get("action_env_dim", self.action_env_dim)) for example in examples]
|
| 428 |
+
rl_games_tasks = [str(example.get("rl_games_task", "")) for example in examples]
|
| 429 |
+
if not any(rl_games_tasks):
|
| 430 |
+
rl_games_tasks = None
|
| 431 |
+
state = (
|
| 432 |
+
[example["state"] for example in examples] if "state" in examples[0] else None
|
| 433 |
+
) # List[ndarray (1, state_dim)] or None
|
| 434 |
+
|
| 435 |
+
instructions = self._state_conditioned_instructions(instructions, state)
|
| 436 |
+
|
| 437 |
+
# step 0: add special action token to instruction
|
| 438 |
+
action_tokens = (
|
| 439 |
+
self.action_token * self.chunk_len
|
| 440 |
+
) # can't add " " between two tokens, otherwise will be tokenized to multiple tokens
|
| 441 |
+
prompt_suffix = f" Please predict the next {self.chunk_len} robot actions: <action>{action_tokens}<action>."
|
| 442 |
+
instructions = [instruction + prompt_suffix for instruction in instructions]
|
| 443 |
+
|
| 444 |
+
profile_timing = self._profile_timing_enabled()
|
| 445 |
+
timing_metrics = {}
|
| 446 |
+
batch_stats = {}
|
| 447 |
+
|
| 448 |
+
# Step 1: QWenVL input format
|
| 449 |
+
t_inputs = self._profile_start() if profile_timing else None
|
| 450 |
+
qwen_inputs = self.qwen_vl_interface.build_qwenvl_inputs(images=batch_images, instructions=instructions)
|
| 451 |
+
if profile_timing:
|
| 452 |
+
timing_metrics["timing/qwen_input_build_total"] = self._profile_elapsed(t_inputs)
|
| 453 |
+
timing_metrics.update(self.qwen_vl_interface._last_build_timing)
|
| 454 |
+
input_ids = qwen_inputs["input_ids"]
|
| 455 |
+
attention_mask = qwen_inputs["attention_mask"]
|
| 456 |
+
effective_tokens = attention_mask.sum()
|
| 457 |
+
effective_token_count = effective_tokens.detach().float().item()
|
| 458 |
+
batch_stats["batch/size"] = float(len(examples))
|
| 459 |
+
batch_stats["batch/input_len_max"] = float(input_ids.shape[1])
|
| 460 |
+
batch_stats["batch/input_len_mean"] = float(effective_token_count / input_ids.shape[0])
|
| 461 |
+
batch_stats["batch/padding_ratio"] = float(1.0 - effective_token_count / float(attention_mask.numel()))
|
| 462 |
+
batch_stats["batch/effective_tokens"] = float(effective_token_count)
|
| 463 |
+
batch_stats["batch/image_count"] = float(sum(len(images) for images in batch_images))
|
| 464 |
+
if "pixel_values" in qwen_inputs:
|
| 465 |
+
batch_stats["batch/pixel_values_rows"] = float(qwen_inputs["pixel_values"].shape[0])
|
| 466 |
+
|
| 467 |
+
t_vlm = self._profile_start() if profile_timing else None
|
| 468 |
+
with torch.autocast("cuda", dtype=torch.bfloat16):
|
| 469 |
+
last_hidden = self._forward_qwen_last_hidden(qwen_inputs) # [B, L, H]
|
| 470 |
+
if profile_timing:
|
| 471 |
+
timing_metrics["timing/vlm_forward"] = self._profile_elapsed(t_vlm)
|
| 472 |
+
|
| 473 |
+
# Step 4: Action Expert Forward and Loss
|
| 474 |
+
t_action = self._profile_start() if profile_timing else None
|
| 475 |
+
with torch.autocast("cuda", dtype=torch.float32):
|
| 476 |
+
# Extract action token embeddings as action prediction queries
|
| 477 |
+
input_ids = qwen_inputs["input_ids"]
|
| 478 |
+
action_queries = self._gather_action_token_embeddings(
|
| 479 |
+
last_hidden, input_ids, action_token_id=self.action_token_id
|
| 480 |
+
) # [B, chunk_len, H]
|
| 481 |
+
action_queries = self._condition_action_queries(action_queries, state)
|
| 482 |
+
pred_actions = self.action_model.predict_action(action_queries) # (B, chunk_len, action_dim)
|
| 483 |
+
|
| 484 |
+
# Label alignment: take the last chunk_len segment
|
| 485 |
+
actions = torch.tensor(
|
| 486 |
+
np.array(actions), device=pred_actions.device, dtype=pred_actions.dtype
|
| 487 |
+
) # [B, T_full, action_dim]
|
| 488 |
+
actions_target = actions[:, -self.action_horizon :, :] # (B, action_horizon, action_dim)
|
| 489 |
+
|
| 490 |
+
action_loss_mask = None
|
| 491 |
+
if "action_loss_mask" in examples[0]:
|
| 492 |
+
action_loss_mask = torch.as_tensor(
|
| 493 |
+
np.asarray([example["action_loss_mask"] for example in examples]),
|
| 494 |
+
device=pred_actions.device,
|
| 495 |
+
dtype=torch.bool,
|
| 496 |
+
)
|
| 497 |
+
|
| 498 |
+
action_loss = self._compute_action_loss(
|
| 499 |
+
pred_actions,
|
| 500 |
+
actions_target,
|
| 501 |
+
action_env_dims=action_env_dims,
|
| 502 |
+
rl_games_tasks=rl_games_tasks,
|
| 503 |
+
action_loss_mask=action_loss_mask,
|
| 504 |
+
)
|
| 505 |
+
if profile_timing:
|
| 506 |
+
timing_metrics["timing/action_head_loss"] = self._profile_elapsed(t_action)
|
| 507 |
+
|
| 508 |
+
return {"action_loss": action_loss, "loss_weight": float(len(examples)), "timing": timing_metrics, "batch_stats": batch_stats}
|
| 509 |
+
|
| 510 |
+
@torch.inference_mode()
|
| 511 |
+
def predict_action(
|
| 512 |
+
self,
|
| 513 |
+
examples: List[dict] = None,
|
| 514 |
+
**kwargs: str,
|
| 515 |
+
) -> np.ndarray:
|
| 516 |
+
"""
|
| 517 |
+
|
| 518 |
+
Steps:
|
| 519 |
+
1. Resize images to training resolution (if specified)
|
| 520 |
+
2. Encode with QwenVL (hidden states retained)
|
| 521 |
+
6. Return normalized action trajectory
|
| 522 |
+
|
| 523 |
+
Returns:
|
| 524 |
+
dict:
|
| 525 |
+
normalized_actions (np.ndarray): Shape [B, T, action_dim], diffusion-sampled normalized actions.
|
| 526 |
+
"""
|
| 527 |
+
if type(examples) is not list:
|
| 528 |
+
examples = [examples]
|
| 529 |
+
if self.kv_memory_enabled:
|
| 530 |
+
return self._predict_action_memory(examples)
|
| 531 |
+
batch_images = [to_pil_preserve(example["image"]) for example in examples] # [B,[PLT]]
|
| 532 |
+
instructions = [example["lang"] for example in examples] # [B, str]
|
| 533 |
+
state = (
|
| 534 |
+
[example["state"] for example in examples] if "state" in examples[0] else None
|
| 535 |
+
) # List[ndarray (1, state_dim)] or None
|
| 536 |
+
|
| 537 |
+
instructions = self._state_conditioned_instructions(instructions, state)
|
| 538 |
+
|
| 539 |
+
train_obs_image_size = getattr(self.config.datasets.vla_data, "obs_image_size", None)
|
| 540 |
+
if train_obs_image_size:
|
| 541 |
+
batch_images = resize_images(batch_images, target_size=train_obs_image_size)
|
| 542 |
+
|
| 543 |
+
# step 0: add special action token to instruction
|
| 544 |
+
action_tokens = (
|
| 545 |
+
self.action_token * self.chunk_len
|
| 546 |
+
) # can't add " " between two tokens, otherwise will be tokenized to multiple tokens
|
| 547 |
+
prompt_suffix = f" Please predict the next {self.chunk_len} robot actions: <action>{action_tokens}<action>."
|
| 548 |
+
instructions = [instruction + prompt_suffix for instruction in instructions]
|
| 549 |
+
|
| 550 |
+
# Step 1: QWenVL input format
|
| 551 |
+
qwen_inputs = self.qwen_vl_interface.build_qwenvl_inputs(images=batch_images, instructions=instructions)
|
| 552 |
+
with torch.autocast("cuda", dtype=torch.bfloat16):
|
| 553 |
+
last_hidden = self._forward_qwen_last_hidden(qwen_inputs) # [B, L, H]
|
| 554 |
+
|
| 555 |
+
# Step 4: Action Expert Forward and Loss
|
| 556 |
+
with torch.autocast("cuda", dtype=torch.float32):
|
| 557 |
+
# Extract action token embeddings as action prediction queries
|
| 558 |
+
input_ids = qwen_inputs["input_ids"]
|
| 559 |
+
action_queries = self._gather_action_token_embeddings(
|
| 560 |
+
last_hidden, input_ids, action_token_id=self.action_token_id
|
| 561 |
+
) # [B, chunk_len, H]
|
| 562 |
+
action_queries = self._condition_action_queries(action_queries, state)
|
| 563 |
+
pred_actions = self.action_model.predict_action(action_queries) # (B, chunk_len, action_dim)
|
| 564 |
+
|
| 565 |
+
normalized_actions = pred_actions.detach().cpu().numpy()
|
| 566 |
+
return {"normalized_actions": normalized_actions}
|
| 567 |
+
|
| 568 |
+
# ──────────────────────────────────────────────────────────────────
|
| 569 |
+
# Fixed-size KV memory. Default OFF. The cache/rotation core is
|
| 570 |
+
# unit-proven (tests/test_kv_memory_equivalence.py); the processor-driven
|
| 571 |
+
# segment construction below still needs end-to-end validation against the
|
| 572 |
+
# real Qwen3-VL-4B checkpoint on GPU.
|
| 573 |
+
# ──────────────────────────────────────────────────────────────────
|
| 574 |
+
def reset_memory(self, slot_id=None) -> None:
|
| 575 |
+
"""Clear the KV memory for one eval slot (episode boundary), or all."""
|
| 576 |
+
if slot_id is None:
|
| 577 |
+
self._kv_memories.clear()
|
| 578 |
+
else:
|
| 579 |
+
self._kv_memories.pop(slot_id, None)
|
| 580 |
+
|
| 581 |
+
def _kv_components(self):
|
| 582 |
+
hf = self.qwen_vl_interface.model # Qwen3VLForConditionalGeneration
|
| 583 |
+
backbone = hf.model # Qwen3VLModel
|
| 584 |
+
text_model = backbone.language_model # Qwen3VLTextModel
|
| 585 |
+
return backbone, text_model.rotary_emb, len(text_model.layers), text_model.config
|
| 586 |
+
|
| 587 |
+
def _new_kv_memory(self):
|
| 588 |
+
from starVLA.model.modules.vlm.kv_memory import FrameKVMemory
|
| 589 |
+
|
| 590 |
+
_, rotary_emb, num_layers, text_config = self._kv_components()
|
| 591 |
+
return FrameKVMemory(rotary_emb, self.kv_window, num_layers, text_config)
|
| 592 |
+
|
| 593 |
+
def _build_text_first_inputs(self, frames, instruction):
|
| 594 |
+
"""Tokenize a text-first window: [instruction][frame imgs...][action suffix].
|
| 595 |
+
|
| 596 |
+
Returns the processor batch (input_ids, pixel_values, image_grid_thw, ...).
|
| 597 |
+
Static text comes first so its KV stays positionally stable across steps.
|
| 598 |
+
"""
|
| 599 |
+
action_tokens = self.action_token * self.chunk_len
|
| 600 |
+
action_suffix = f" Please predict the next {self.chunk_len} robot actions: <action>{action_tokens}<action>."
|
| 601 |
+
content = [{"type": "text", "text": instruction}]
|
| 602 |
+
content += [{"type": "image", "image": img} for img in frames]
|
| 603 |
+
content += [{"type": "text", "text": action_suffix}]
|
| 604 |
+
messages = [[{"role": "user", "content": content}]]
|
| 605 |
+
batch = self.qwen_vl_interface.processor.apply_chat_template(
|
| 606 |
+
messages, tokenize=True, padding=True, add_generation_prompt=True, return_dict=True, return_tensors="pt"
|
| 607 |
+
)
|
| 608 |
+
return batch.to(self.qwen_vl_interface.model.device)
|
| 609 |
+
|
| 610 |
+
def _kv_segments(self, input_ids_1d):
|
| 611 |
+
"""Partition a text-first window's ids into (prefix_len, [frame_lens], action_len).
|
| 612 |
+
|
| 613 |
+
A frame block spans its vision_start .. vision_end (inclusive); the prefix is
|
| 614 |
+
everything before the first vision_start; the action suffix is everything
|
| 615 |
+
after the last vision_end. Using the real special-token ids keeps eviction
|
| 616 |
+
leak-free (each evicted frame removes exactly its own markers).
|
| 617 |
+
"""
|
| 618 |
+
cfg = self.qwen_vl_interface.model.config
|
| 619 |
+
vstart = int(getattr(cfg, "vision_start_token_id", 151652))
|
| 620 |
+
vend = int(getattr(cfg, "vision_end_token_id", 151653))
|
| 621 |
+
ids = input_ids_1d.tolist()
|
| 622 |
+
starts = [i for i, t in enumerate(ids) if t == vstart]
|
| 623 |
+
ends = [i for i, t in enumerate(ids) if t == vend]
|
| 624 |
+
if not starts or not ends:
|
| 625 |
+
raise RuntimeError("KV memory: window inputs have no vision_start/end tokens")
|
| 626 |
+
action_start = ends[-1] + 1
|
| 627 |
+
bounds = starts + [action_start]
|
| 628 |
+
frame_lens = [bounds[i + 1] - bounds[i] for i in range(len(starts))]
|
| 629 |
+
return starts[0], frame_lens, len(ids) - action_start
|
| 630 |
+
|
| 631 |
+
def _kv_forward_step_batched(self, memories, frames, instruction):
|
| 632 |
+
"""Run ONE streaming KV-memory step for a group of same-layout streams and
|
| 633 |
+
return ``(last_hidden_new [B, new_len, H], new_input_ids [B, new_len])``.
|
| 634 |
+
|
| 635 |
+
``memories`` and ``frames`` are aligned lists (one entry per stream / eval
|
| 636 |
+
slot); all entries share ``instruction`` and history layout (grouped by the
|
| 637 |
+
caller). Two efficiencies vs a naive per-frame, per-step encode:
|
| 638 |
+
|
| 639 |
+
- Single-frame preprocessing: the image processor runs once per stream on its
|
| 640 |
+
OWN current frame. The window's multi-frame token layout (needed only for
|
| 641 |
+
M-RoPE positions via ``get_rope_index``) is reconstructed by replicating the
|
| 642 |
+
frame's token block, so duplicate frames are never re-preprocessed.
|
| 643 |
+
- Cross-stream batching: the streams' canonical KV blocks are stacked along the
|
| 644 |
+
batch dim, so the whole group is one forward; results are sliced back to each
|
| 645 |
+
stream's memory afterwards.
|
| 646 |
+
|
| 647 |
+
Past frames come from each stream's memory; only the current frame's pixels are
|
| 648 |
+
encoded fresh. ``B == 1`` (training rollout) reuses the same path with no
|
| 649 |
+
stacking, so gradients flow through the in-place memory updates.
|
| 650 |
+
"""
|
| 651 |
+
from starVLA.model.modules.vlm.kv_memory import FrameKVMemory, memory_step
|
| 652 |
+
|
| 653 |
+
backbone = self.qwen_vl_interface.model.model # Qwen3VLModel
|
| 654 |
+
B = len(memories)
|
| 655 |
+
n_past = memories[0].num_past_frames()
|
| 656 |
+
n_visible = n_past + 1
|
| 657 |
+
|
| 658 |
+
# One processor call per stream's current frame (no duplicate-frame work).
|
| 659 |
+
per_stream = [self._build_text_first_inputs([frame], instruction) for frame in frames]
|
| 660 |
+
ids1 = per_stream[0]["input_ids"][0]
|
| 661 |
+
cur_grid = per_stream[0]["image_grid_thw"][:1]
|
| 662 |
+
prefix_len, frame_lens, action_len = self._kv_segments(ids1)
|
| 663 |
+
frame_len = frame_lens[0]
|
| 664 |
+
|
| 665 |
+
# Reconstruct the n_visible-frame window ids by replicating the frame block,
|
| 666 |
+
# then let get_rope_index assign the (content-independent) M-RoPE positions.
|
| 667 |
+
prefix, action = ids1[:prefix_len], ids1[prefix_len + frame_len :]
|
| 668 |
+
block = ids1[prefix_len : prefix_len + frame_len]
|
| 669 |
+
window_ids = torch.cat([prefix] + [block] * n_visible + [action]).unsqueeze(0)
|
| 670 |
+
full_pos1, _ = backbone.get_rope_index(window_ids, image_grid_thw=cur_grid.repeat(n_visible, 1))
|
| 671 |
+
full_pos = full_pos1.expand(3, B, full_pos1.shape[-1]).contiguous()
|
| 672 |
+
|
| 673 |
+
batched = FrameKVMemory.stack(memories) if B > 1 else memories[0]
|
| 674 |
+
past_len = batched.past_length()
|
| 675 |
+
is_prefill = not batched.has_text()
|
| 676 |
+
expected_past = 0 if is_prefill else prefix_len + n_past * frame_len
|
| 677 |
+
if past_len != expected_past:
|
| 678 |
+
raise RuntimeError(
|
| 679 |
+
f"KV memory slice misaligned: memory past_length={past_len} but window "
|
| 680 |
+
f"expects {expected_past}. Token boundaries drifted across steps."
|
| 681 |
+
)
|
| 682 |
+
if is_prefill:
|
| 683 |
+
new_start, frame_start_in_new, prefill_text_len = 0, prefix_len, prefix_len
|
| 684 |
+
else:
|
| 685 |
+
new_start, frame_start_in_new, prefill_text_len = past_len, 0, 0
|
| 686 |
+
|
| 687 |
+
new_ids = window_ids[:, new_start:].expand(B, -1).contiguous()
|
| 688 |
+
pixels = torch.cat([b["pixel_values"] for b in per_stream], dim=0)
|
| 689 |
+
grid = cur_grid.repeat(B, 1)
|
| 690 |
+
|
| 691 |
+
last_hidden = memory_step(
|
| 692 |
+
backbone,
|
| 693 |
+
batched,
|
| 694 |
+
new_input_ids=new_ids,
|
| 695 |
+
pixel_values=pixels,
|
| 696 |
+
image_grid_thw=grid,
|
| 697 |
+
full_position_ids=full_pos,
|
| 698 |
+
frame_start_in_new=frame_start_in_new,
|
| 699 |
+
frame_len=frame_len,
|
| 700 |
+
prefill_text_len=prefill_text_len,
|
| 701 |
+
)
|
| 702 |
+
|
| 703 |
+
if B > 1:
|
| 704 |
+
for i, memory in enumerate(memories):
|
| 705 |
+
sliced = batched.slice(i)
|
| 706 |
+
memory.text, memory.frames = sliced.text, sliced.frames
|
| 707 |
+
return last_hidden, new_ids
|
| 708 |
+
|
| 709 |
+
# ------------------------------------------------------------------ #
|
| 710 |
+
# Packed single-forward training path (StreamingLLM-style mask).
|
| 711 |
+
#
|
| 712 |
+
# Instead of the per-frame Python loop (`_forward_memory`), pack the whole
|
| 713 |
+
# rollout into ONE sequence
|
| 714 |
+
# [text/sink] [frame_1][action_1] ... [frame_R][action_R]
|
| 715 |
+
# and run a SINGLE Qwen3-VL forward under a FlexAttention BlockMask that
|
| 716 |
+
# reproduces the streaming visibility:
|
| 717 |
+
# * the text prefix is an always-visible attention sink (StreamingLLM);
|
| 718 |
+
# * each frame attends only the last `kv_window` frames (sliding window);
|
| 719 |
+
# * each action read-out block is isolated (seen only by its own step), so
|
| 720 |
+
# the frame KV stream is bit-identical to the streaming loop / eval.
|
| 721 |
+
# Custom M-RoPE `position_ids` keep frame->frame and action->frame relative
|
| 722 |
+
# distances exactly matching eval; only the text-sink->frame absolute distance
|
| 723 |
+
# grows with step (accepted approximation — the sink is distance-robust).
|
| 724 |
+
# Eval/live (`_predict_action_memory`) is untouched and keeps the rolling KV.
|
| 725 |
+
# ------------------------------------------------------------------ #
|
| 726 |
+
def _packed_window_layout(self, frames, instruction):
|
| 727 |
+
"""Build the packed [text][f1][a1]...[fR][aR] window for ONE stream.
|
| 728 |
+
|
| 729 |
+
Token structure is content-independent (image placeholders are identical
|
| 730 |
+
per frame), so the prefix / frame / action blocks are reconstructed by
|
| 731 |
+
token-replication (matching `_kv_forward_step_batched`'s `window_ids`),
|
| 732 |
+
which guarantees each action block equals the per-step layout exactly.
|
| 733 |
+
|
| 734 |
+
Returns dict with: input_ids [1,L], pixel_values, image_grid_thw [R,3],
|
| 735 |
+
position_ids [3,1,L] (custom M-RoPE), kind [L] (0=text,1=frame,2=action),
|
| 736 |
+
step [L] (frame/step index for frame&action tokens, -1 for text),
|
| 737 |
+
action_idx [R, chunk_len] (packed indices of each step's action tokens).
|
| 738 |
+
"""
|
| 739 |
+
R = len(frames)
|
| 740 |
+
backbone = self.qwen_vl_interface.model.model # Qwen3VLModel
|
| 741 |
+
device = self.qwen_vl_interface.model.device
|
| 742 |
+
|
| 743 |
+
# One processor call per frame: identical token block, distinct pixels.
|
| 744 |
+
per_frame = [self._build_text_first_inputs([f], instruction) for f in frames]
|
| 745 |
+
ids1 = per_frame[0]["input_ids"][0]
|
| 746 |
+
grid1 = per_frame[0]["image_grid_thw"][:1] # [1,3]
|
| 747 |
+
prefix_len, frame_lens, action_len = self._kv_segments(ids1)
|
| 748 |
+
frame_len = frame_lens[0]
|
| 749 |
+
for pf in per_frame:
|
| 750 |
+
pl, fl, al = self._kv_segments(pf["input_ids"][0])
|
| 751 |
+
if (pl, fl[0], al) != (prefix_len, frame_len, action_len):
|
| 752 |
+
raise RuntimeError(
|
| 753 |
+
"Packed KV memory: frames have inconsistent token layout "
|
| 754 |
+
f"({(pl, fl[0], al)} vs {(prefix_len, frame_len, action_len)}); "
|
| 755 |
+
"all frames in a rollout must share resolution/grid."
|
| 756 |
+
)
|
| 757 |
+
|
| 758 |
+
prefix = ids1[:prefix_len]
|
| 759 |
+
frame_block = ids1[prefix_len : prefix_len + frame_len]
|
| 760 |
+
action_block = ids1[prefix_len + frame_len :] # incl. assistant header
|
| 761 |
+
block_len = frame_len + action_len
|
| 762 |
+
|
| 763 |
+
pixel_values = torch.cat([pf["pixel_values"] for pf in per_frame], dim=0)
|
| 764 |
+
grid = grid1.repeat(R, 1) # [R, 3]
|
| 765 |
+
|
| 766 |
+
# Frames-only M-RoPE positions: frames laid out consecutively (no action
|
| 767 |
+
# in between), so frame→frame / action→frame relative distances match eval
|
| 768 |
+
# ("positions within the cache"). These are the canonical frame positions
|
| 769 |
+
# reused regardless of where the frame sits in the packed token sequence.
|
| 770 |
+
frames_only_ids = torch.cat([prefix] + [frame_block] * R).unsqueeze(0)
|
| 771 |
+
full_pos_f, _ = backbone.get_rope_index(frames_only_ids, image_grid_thw=grid) # [3,1,Lf]
|
| 772 |
+
text_pos = full_pos_f[:, :, :prefix_len]
|
| 773 |
+
|
| 774 |
+
action_offsets = (action_block == self.action_token_id).nonzero(as_tuple=False).flatten()
|
| 775 |
+
if action_offsets.numel() < self.chunk_len:
|
| 776 |
+
raise RuntimeError(
|
| 777 |
+
f"Packed KV memory: action block has {action_offsets.numel()} action "
|
| 778 |
+
f"tokens (< chunk_len={self.chunk_len})."
|
| 779 |
+
)
|
| 780 |
+
action_offsets = action_offsets[-self.chunk_len :] # last chunk_len, matches gather
|
| 781 |
+
|
| 782 |
+
def _frame_pos(k):
|
| 783 |
+
s = prefix_len + k * frame_len
|
| 784 |
+
return full_pos_f[:, :, s : s + frame_len]
|
| 785 |
+
|
| 786 |
+
def _action_pos(frame_pos_k):
|
| 787 |
+
fk_max = int(frame_pos_k.max().item())
|
| 788 |
+
ap = torch.arange(1, action_len + 1, device=device) + fk_max
|
| 789 |
+
return ap.view(1, 1, -1).expand(3, 1, -1)
|
| 790 |
+
|
| 791 |
+
W = self.kv_window
|
| 792 |
+
parts, pos_parts, kind_parts, step_parts, action_idx_rows = [], [], [], [], []
|
| 793 |
+
|
| 794 |
+
if self.kv_rebased_sink:
|
| 795 |
+
# Per-step re-based sink, with the redundant pre-eviction sinks deduped.
|
| 796 |
+
# Steps k < W all have j0=0 → o_k=0 → an identical sink at [0,P), so they
|
| 797 |
+
# share ONE front sink (step=-2, visible to queries with step < W). This
|
| 798 |
+
# is exactly eval before any eviction (sink pinned at the front, frames
|
| 799 |
+
# growing from P). Steps k >= W each carry their own re-based sink
|
| 800 |
+
# ([sink_k][frame_k][action_k]) at o_k so the sink→frame distance equals
|
| 801 |
+
# eval's rolling-KV layout (bit-exact). Net: 1 + max(0,R-W) sinks instead
|
| 802 |
+
# of R, saving (W-1) prefix copies while staying numerically identical.
|
| 803 |
+
base0 = int(full_pos_f[0, 0, prefix_len].item()) # frame 0's temporal start
|
| 804 |
+
|
| 805 |
+
# Shared front sink for the pre-eviction steps (step=-2).
|
| 806 |
+
parts.append(prefix)
|
| 807 |
+
pos_parts.append(text_pos)
|
| 808 |
+
kind_parts.append(torch.zeros(prefix_len, dtype=torch.long, device=device))
|
| 809 |
+
step_parts.append(torch.full((prefix_len,), -2, dtype=torch.long, device=device))
|
| 810 |
+
cursor = prefix_len
|
| 811 |
+
|
| 812 |
+
for k in range(R):
|
| 813 |
+
if k >= W:
|
| 814 |
+
# Re-based sink_k sits right before step k's oldest visible frame.
|
| 815 |
+
j0 = k - W + 1
|
| 816 |
+
o_k = int(full_pos_f[0, 0, prefix_len + j0 * frame_len].item()) - base0
|
| 817 |
+
parts.append(prefix)
|
| 818 |
+
pos_parts.append(text_pos + o_k) # shift all 3 M-RoPE dims by o_k
|
| 819 |
+
kind_parts.append(torch.zeros(prefix_len, dtype=torch.long, device=device))
|
| 820 |
+
step_parts.append(torch.full((prefix_len,), k, dtype=torch.long, device=device))
|
| 821 |
+
cursor += prefix_len
|
| 822 |
+
frame_pos_k = _frame_pos(k)
|
| 823 |
+
parts += [frame_block, action_block]
|
| 824 |
+
pos_parts += [frame_pos_k, _action_pos(frame_pos_k)]
|
| 825 |
+
kind_parts.append(
|
| 826 |
+
torch.cat([
|
| 827 |
+
torch.ones(frame_len, dtype=torch.long, device=device),
|
| 828 |
+
torch.full((action_len,), 2, dtype=torch.long, device=device),
|
| 829 |
+
])
|
| 830 |
+
)
|
| 831 |
+
step_parts.append(torch.full((block_len,), k, dtype=torch.long, device=device))
|
| 832 |
+
action_idx_rows.append(cursor + frame_len + action_offsets)
|
| 833 |
+
cursor += block_len
|
| 834 |
+
else:
|
| 835 |
+
# Legacy single global sink: [sink][f0 a0]...[f_{R-1} a_{R-1}]. Sink at
|
| 836 |
+
# the front (step=-1, always visible); sink→frame distance grows with k.
|
| 837 |
+
parts.append(prefix)
|
| 838 |
+
pos_parts.append(text_pos)
|
| 839 |
+
kind_parts.append(torch.zeros(prefix_len, dtype=torch.long, device=device))
|
| 840 |
+
step_parts.append(torch.full((prefix_len,), -1, dtype=torch.long, device=device))
|
| 841 |
+
cursor = prefix_len
|
| 842 |
+
for k in range(R):
|
| 843 |
+
frame_pos_k = _frame_pos(k)
|
| 844 |
+
parts += [frame_block, action_block]
|
| 845 |
+
pos_parts += [frame_pos_k, _action_pos(frame_pos_k)]
|
| 846 |
+
kind_parts.append(
|
| 847 |
+
torch.cat([
|
| 848 |
+
torch.ones(frame_len, dtype=torch.long, device=device),
|
| 849 |
+
torch.full((action_len,), 2, dtype=torch.long, device=device),
|
| 850 |
+
])
|
| 851 |
+
)
|
| 852 |
+
step_parts.append(torch.full((block_len,), k, dtype=torch.long, device=device))
|
| 853 |
+
action_idx_rows.append(cursor + frame_len + action_offsets)
|
| 854 |
+
cursor += block_len
|
| 855 |
+
|
| 856 |
+
input_ids = torch.cat(parts).unsqueeze(0) # [1, L]
|
| 857 |
+
position_ids = torch.cat(pos_parts, dim=2) # [3,1,L]
|
| 858 |
+
kind = torch.cat(kind_parts)
|
| 859 |
+
step = torch.cat(step_parts)
|
| 860 |
+
action_idx = torch.stack(action_idx_rows, dim=0) # [R, chunk_len]
|
| 861 |
+
|
| 862 |
+
return {
|
| 863 |
+
"input_ids": input_ids,
|
| 864 |
+
"pixel_values": pixel_values,
|
| 865 |
+
"image_grid_thw": grid,
|
| 866 |
+
"position_ids": position_ids,
|
| 867 |
+
"kind": kind,
|
| 868 |
+
"step": step,
|
| 869 |
+
"action_idx": action_idx,
|
| 870 |
+
}
|
| 871 |
+
|
| 872 |
+
def _packed_block_mask(self, kind, step, L, batch):
|
| 873 |
+
"""Build a FlexAttention BlockMask for the packed sliding-window+sink layout."""
|
| 874 |
+
from torch.nn.attention.flex_attention import create_block_mask
|
| 875 |
+
from starVLA.model.modules.vlm.kv_memory import make_packed_sliding_mask_mod
|
| 876 |
+
|
| 877 |
+
mask_mod = make_packed_sliding_mask_mod(kind, step, self.kv_window)
|
| 878 |
+
# B=H=None → the mask is shared across batch and heads (it depends only on
|
| 879 |
+
# token kind/step, not content), so one BlockMask covers the whole batch.
|
| 880 |
+
return create_block_mask(mask_mod, B=None, H=None, Q_LEN=L, KV_LEN=L, device=str(kind.device))
|
| 881 |
+
|
| 882 |
+
def _assert_flex_attention(self):
|
| 883 |
+
"""Packed training needs the decoder dispatched through FlexAttention.
|
| 884 |
+
|
| 885 |
+
We pass a FlexAttention BlockMask straight to the model, so the decoder
|
| 886 |
+
(forward AND gradient-checkpointing recompute during backward) must use
|
| 887 |
+
the flex backend the whole time — a transient toggle around the forward
|
| 888 |
+
would leave the recompute on sdpa and choke on the BlockMask. The eval
|
| 889 |
+
streaming path also runs fine under flex, so the whole model is simply
|
| 890 |
+
loaded with `attn_implementation: flex_attention`.
|
| 891 |
+
"""
|
| 892 |
+
_, _, _, text_config = self._kv_components()
|
| 893 |
+
impl = getattr(text_config, "_attn_implementation", None)
|
| 894 |
+
if impl != "flex_attention":
|
| 895 |
+
raise RuntimeError(
|
| 896 |
+
"Packed KV-memory training requires the VLM loaded with "
|
| 897 |
+
f"attn_implementation='flex_attention' (got {impl!r}). Set "
|
| 898 |
+
"framework.qwenvl.attn_implementation: flex_attention."
|
| 899 |
+
)
|
| 900 |
+
|
| 901 |
+
def _forward_memory_packed(self, examples: List[dict]) -> dict:
|
| 902 |
+
"""Training forward via a single packed sliding-window+sink masked pass.
|
| 903 |
+
|
| 904 |
+
Per-frame supervision identical to `_forward_memory` (every real frame is
|
| 905 |
+
supervised at the window it naturally has), but the expensive VLM forward
|
| 906 |
+
runs ONCE per rollout instead of R times. Full BPTT across the rollout
|
| 907 |
+
(no per-step detach) — pair with gradient checkpointing for memory.
|
| 908 |
+
"""
|
| 909 |
+
self._assert_flex_attention()
|
| 910 |
+
profile = self._profile_timing_enabled()
|
| 911 |
+
timing = {"timing/mem_nograd_fwd": 0.0, "timing/mem_grad_fwd": 0.0, "timing/mem_action_head": 0.0}
|
| 912 |
+
|
| 913 |
+
instructions = [
|
| 914 |
+
self._instruction_with_state(ex["lang"], ex.get("state") if "state" in ex else None)
|
| 915 |
+
for ex in examples
|
| 916 |
+
]
|
| 917 |
+
groups: dict = {}
|
| 918 |
+
for idx, instruction in enumerate(instructions):
|
| 919 |
+
valid = np.asarray(examples[idx]["valid"], dtype=bool)
|
| 920 |
+
groups.setdefault((instruction, tuple(valid.tolist())), []).append(idx)
|
| 921 |
+
|
| 922 |
+
total_weighted_loss = None
|
| 923 |
+
total_weight = 0.0
|
| 924 |
+
for (instruction, valid_key), indices in groups.items():
|
| 925 |
+
valid = np.asarray(valid_key, dtype=bool)
|
| 926 |
+
real_positions = [k for k in range(len(valid)) if valid[k]]
|
| 927 |
+
R = len(real_positions)
|
| 928 |
+
if R == 0:
|
| 929 |
+
continue
|
| 930 |
+
action_env_dims = [int(examples[i].get("action_env_dim", self.action_env_dim)) for i in indices]
|
| 931 |
+
rl_games_tasks = [str(examples[i].get("rl_games_task", "")) for i in indices]
|
| 932 |
+
if not any(rl_games_tasks):
|
| 933 |
+
rl_games_tasks = None
|
| 934 |
+
|
| 935 |
+
# Build per-sample packed layouts (identical token structure, distinct pixels).
|
| 936 |
+
layouts = [
|
| 937 |
+
self._packed_window_layout([examples[i]["image"][k] for k in real_positions], instruction)
|
| 938 |
+
for i in indices
|
| 939 |
+
]
|
| 940 |
+
base = layouts[0]
|
| 941 |
+
L = base["input_ids"].shape[1]
|
| 942 |
+
B = len(indices)
|
| 943 |
+
input_ids = base["input_ids"].expand(B, -1).contiguous()
|
| 944 |
+
position_ids = base["position_ids"].expand(3, B, -1).contiguous()
|
| 945 |
+
pixel_values = torch.cat([lay["pixel_values"] for lay in layouts], dim=0)
|
| 946 |
+
grid = torch.cat([lay["image_grid_thw"] for lay in layouts], dim=0) # [B*R, 3]
|
| 947 |
+
action_idx = base["action_idx"] # [R, chunk_len]
|
| 948 |
+
block_mask = self._packed_block_mask(base["kind"], base["step"], L, B)
|
| 949 |
+
|
| 950 |
+
t_fwd = self._profile_start() if profile else None
|
| 951 |
+
last_hidden = self._forward_qwen_last_hidden(
|
| 952 |
+
{
|
| 953 |
+
"input_ids": input_ids,
|
| 954 |
+
"position_ids": position_ids,
|
| 955 |
+
"attention_mask": block_mask,
|
| 956 |
+
"pixel_values": pixel_values,
|
| 957 |
+
"image_grid_thw": grid,
|
| 958 |
+
"use_cache": False,
|
| 959 |
+
}
|
| 960 |
+
) # [B, L, H]
|
| 961 |
+
if profile:
|
| 962 |
+
timing["timing/mem_grad_fwd"] += self._profile_elapsed(t_fwd)
|
| 963 |
+
|
| 964 |
+
t_head = self._profile_start() if profile else None
|
| 965 |
+
with torch.autocast("cuda", dtype=torch.float32):
|
| 966 |
+
for j, k in enumerate(real_positions):
|
| 967 |
+
action_queries = last_hidden[:, action_idx[j], :] # [B, chunk_len, H]
|
| 968 |
+
group_preds = self.action_model.predict_action(action_queries) # [B, chunk, dim]
|
| 969 |
+
targets = [self._prepare_action_array(examples[i]["actions_per_frame"][k]) for i in indices]
|
| 970 |
+
targets = torch.tensor(np.array(targets), device=group_preds.device, dtype=group_preds.dtype)
|
| 971 |
+
targets = targets[:, -self.action_horizon :, :]
|
| 972 |
+
step_loss = self._compute_action_loss(
|
| 973 |
+
group_preds, targets, action_env_dims=action_env_dims, rl_games_tasks=rl_games_tasks
|
| 974 |
+
)
|
| 975 |
+
weight = float(sum(float(np.asarray(examples[i]["density_weight"])[k]) for i in indices))
|
| 976 |
+
contribution = step_loss * weight
|
| 977 |
+
total_weighted_loss = contribution if total_weighted_loss is None else total_weighted_loss + contribution
|
| 978 |
+
total_weight += weight
|
| 979 |
+
if profile:
|
| 980 |
+
timing["timing/mem_action_head"] += self._profile_elapsed(t_head)
|
| 981 |
+
|
| 982 |
+
action_loss = total_weighted_loss / max(total_weight, 1e-6)
|
| 983 |
+
return {"action_loss": action_loss, "loss_weight": total_weight, "timing": timing, "batch_stats": {}}
|
| 984 |
+
|
| 985 |
+
def _instruction_with_state(self, instruction, state):
|
| 986 |
+
if state is None:
|
| 987 |
+
return instruction
|
| 988 |
+
return self.add_discretized_state_to_instruction([instruction], [state])[0]
|
| 989 |
+
|
| 990 |
+
def _forward_memory(self, examples: List[dict]) -> dict:
|
| 991 |
+
"""Training forward with the streaming KV memory: per-frame supervision.
|
| 992 |
+
|
| 993 |
+
Each sample is an R-frame rollout `[t-R+1 .. t]`; the dataloader marks clamp
|
| 994 |
+
padding via ``valid`` and supplies one action chunk per frame in
|
| 995 |
+
``actions_per_frame`` ([R, H, dim]). We skip padding frames (never feed memory,
|
| 996 |
+
never supervise) and supervise EVERY real frame at the window it naturally has
|
| 997 |
+
(1 -> window, then evict), which matches eval (`_predict_action_memory`) frame
|
| 998 |
+
for frame, including the unfilled window at an episode's start. History KV is
|
| 999 |
+
detached between steps (truncated BPTT depth 1), so each step's loss flows only
|
| 1000 |
+
through that step's forward; the per-step weighted losses are summed and the
|
| 1001 |
+
trainer backpropagates once.
|
| 1002 |
+
|
| 1003 |
+
Same-instruction, same-`valid` rollouts share a token layout and window
|
| 1004 |
+
evolution, so they run each step in one batched forward via
|
| 1005 |
+
`_kv_forward_step_batched` (degrades to per-sample otherwise), mirroring
|
| 1006 |
+
`_predict_action_memory`.
|
| 1007 |
+
"""
|
| 1008 |
+
profile = self._profile_timing_enabled()
|
| 1009 |
+
timing = {"timing/mem_nograd_fwd": 0.0, "timing/mem_grad_fwd": 0.0, "timing/mem_action_head": 0.0}
|
| 1010 |
+
|
| 1011 |
+
instructions = [
|
| 1012 |
+
self._instruction_with_state(ex["lang"], ex.get("state") if "state" in ex else None)
|
| 1013 |
+
for ex in examples
|
| 1014 |
+
]
|
| 1015 |
+
|
| 1016 |
+
# Group rollouts that share the prompt AND the same real/padding pattern: those
|
| 1017 |
+
# march through identical window states each step, so they batch cleanly.
|
| 1018 |
+
groups: dict = {}
|
| 1019 |
+
for idx, instruction in enumerate(instructions):
|
| 1020 |
+
valid = np.asarray(examples[idx]["valid"], dtype=bool)
|
| 1021 |
+
groups.setdefault((instruction, tuple(valid.tolist())), []).append(idx)
|
| 1022 |
+
|
| 1023 |
+
total_weighted_loss = None
|
| 1024 |
+
total_weight = 0.0
|
| 1025 |
+
for (instruction, valid_key), indices in groups.items():
|
| 1026 |
+
valid = np.asarray(valid_key, dtype=bool)
|
| 1027 |
+
real_positions = [k for k in range(len(valid)) if valid[k]]
|
| 1028 |
+
memories = [self._new_kv_memory() for _ in indices]
|
| 1029 |
+
rollout = [examples[i]["image"] for i in indices]
|
| 1030 |
+
action_env_dims = [int(examples[i].get("action_env_dim", self.action_env_dim)) for i in indices]
|
| 1031 |
+
rl_games_tasks = [str(examples[i].get("rl_games_task", "")) for i in indices]
|
| 1032 |
+
if not any(rl_games_tasks):
|
| 1033 |
+
rl_games_tasks = None
|
| 1034 |
+
|
| 1035 |
+
for step_pos, k in enumerate(real_positions):
|
| 1036 |
+
if step_pos > 0:
|
| 1037 |
+
for memory in memories:
|
| 1038 |
+
memory.detach_() # truncated BPTT: history is a fixed memory
|
| 1039 |
+
step_frames = [frames[k] for frames in rollout]
|
| 1040 |
+
|
| 1041 |
+
t_fwd = self._profile_start() if profile else None
|
| 1042 |
+
last_hidden, new_ids = self._kv_forward_step_batched(memories, step_frames, instruction)
|
| 1043 |
+
if profile:
|
| 1044 |
+
timing["timing/mem_grad_fwd"] += self._profile_elapsed(t_fwd)
|
| 1045 |
+
|
| 1046 |
+
t_head = self._profile_start() if profile else None
|
| 1047 |
+
with torch.autocast("cuda", dtype=torch.float32):
|
| 1048 |
+
action_queries = self._gather_action_token_embeddings(
|
| 1049 |
+
last_hidden, new_ids, action_token_id=self.action_token_id
|
| 1050 |
+
)
|
| 1051 |
+
group_preds = self.action_model.predict_action(action_queries) # [Bg, chunk, dim]
|
| 1052 |
+
targets = [
|
| 1053 |
+
self._prepare_action_array(examples[i]["actions_per_frame"][k]) for i in indices
|
| 1054 |
+
]
|
| 1055 |
+
targets = torch.tensor(
|
| 1056 |
+
np.array(targets), device=group_preds.device, dtype=group_preds.dtype
|
| 1057 |
+
) # [Bg, H, dim]
|
| 1058 |
+
targets = targets[:, -self.action_horizon :, :]
|
| 1059 |
+
step_loss = self._compute_action_loss(
|
| 1060 |
+
group_preds,
|
| 1061 |
+
targets,
|
| 1062 |
+
action_env_dims=action_env_dims,
|
| 1063 |
+
rl_games_tasks=rl_games_tasks,
|
| 1064 |
+
)
|
| 1065 |
+
if profile:
|
| 1066 |
+
timing["timing/mem_action_head"] += self._profile_elapsed(t_head)
|
| 1067 |
+
|
| 1068 |
+
# Per-supervised-step density weight: sum of the group's per-frame
|
| 1069 |
+
# weights at frame k. Uniform weights reduce this to a sample-count
|
| 1070 |
+
# weighting, i.e. the exact mean over all supervised (sample, step).
|
| 1071 |
+
weight = float(
|
| 1072 |
+
sum(float(np.asarray(examples[i]["density_weight"])[k]) for i in indices)
|
| 1073 |
+
)
|
| 1074 |
+
contribution = step_loss * weight
|
| 1075 |
+
total_weighted_loss = contribution if total_weighted_loss is None else total_weighted_loss + contribution
|
| 1076 |
+
total_weight += weight
|
| 1077 |
+
|
| 1078 |
+
action_loss = total_weighted_loss / max(total_weight, 1e-6)
|
| 1079 |
+
return {"action_loss": action_loss, "loss_weight": total_weight, "timing": timing, "batch_stats": {}}
|
| 1080 |
+
|
| 1081 |
+
@torch.inference_mode()
|
| 1082 |
+
def _predict_action_memory(self, examples: List[dict]) -> dict:
|
| 1083 |
+
"""Eval-time streaming prediction with per-slot KV memory (req3, milestone 4).
|
| 1084 |
+
|
| 1085 |
+
The newest frame (``example['image'][-1]``) is encoded fresh; past frames are
|
| 1086 |
+
served from the slot's memory. ``reset_memory(slot_id)`` (called from the
|
| 1087 |
+
policy at episode boundaries) clears a slot. Streams that currently share the
|
| 1088 |
+
same instruction and history layout are processed in one batched forward; the
|
| 1089 |
+
rest fall back to their own group (no left padding needed).
|
| 1090 |
+
"""
|
| 1091 |
+
train_obs_image_size = getattr(self.config.datasets.vla_data, "obs_image_size", None)
|
| 1092 |
+
|
| 1093 |
+
# Resolve each example to its (slot memory, current frame, instruction).
|
| 1094 |
+
entries = []
|
| 1095 |
+
for idx, example in enumerate(examples):
|
| 1096 |
+
slot_id = example.get("slot_id", idx)
|
| 1097 |
+
frames = [to_pil_preserve(f) for f in example["image"]]
|
| 1098 |
+
if train_obs_image_size:
|
| 1099 |
+
frames = resize_images(frames, target_size=train_obs_image_size)
|
| 1100 |
+
state = example.get("state") if "state" in example else None
|
| 1101 |
+
instruction = self._instruction_with_state(example["lang"], state)
|
| 1102 |
+
memory = self._kv_memories.get(slot_id)
|
| 1103 |
+
if memory is None:
|
| 1104 |
+
memory = self._new_kv_memory()
|
| 1105 |
+
self._kv_memories[slot_id] = memory
|
| 1106 |
+
entries.append({"idx": idx, "frame": frames[-1], "instruction": instruction, "memory": memory})
|
| 1107 |
+
|
| 1108 |
+
# Group same-layout streams (same prompt + same cached layout) into one forward.
|
| 1109 |
+
groups: dict = {}
|
| 1110 |
+
for entry in entries:
|
| 1111 |
+
key = (entry["instruction"], entry["memory"].layout_key())
|
| 1112 |
+
groups.setdefault(key, []).append(entry)
|
| 1113 |
+
|
| 1114 |
+
preds: List[Optional[torch.Tensor]] = [None] * len(examples)
|
| 1115 |
+
for group in groups.values():
|
| 1116 |
+
instruction = group[0]["instruction"]
|
| 1117 |
+
memories = [entry["memory"] for entry in group]
|
| 1118 |
+
frames = [entry["frame"] for entry in group]
|
| 1119 |
+
last_hidden, new_ids = self._kv_forward_step_batched(memories, frames, instruction)
|
| 1120 |
+
with torch.autocast("cuda", dtype=torch.float32):
|
| 1121 |
+
action_queries = self._gather_action_token_embeddings(
|
| 1122 |
+
last_hidden, new_ids, action_token_id=self.action_token_id
|
| 1123 |
+
)
|
| 1124 |
+
pred_actions = self.action_model.predict_action(action_queries)
|
| 1125 |
+
for i, entry in enumerate(group):
|
| 1126 |
+
preds[entry["idx"]] = pred_actions[i : i + 1]
|
| 1127 |
+
|
| 1128 |
+
normalized = torch.cat(preds, dim=0).detach().cpu().numpy()
|
| 1129 |
+
return {"normalized_actions": normalized}
|
| 1130 |
+
|
| 1131 |
+
def _gather_action_token_embeddings(
|
| 1132 |
+
self,
|
| 1133 |
+
last_hidden: torch.Tensor, # [B, L, H]
|
| 1134 |
+
input_ids: torch.Tensor, # [B, L]
|
| 1135 |
+
action_token_id=None, # Can be int or List[int]
|
| 1136 |
+
) -> torch.Tensor:
|
| 1137 |
+
"""
|
| 1138 |
+
Vectorized batch extraction of action token embeddings:
|
| 1139 |
+
- No per-sample for loop
|
| 1140 |
+
- Select the last chunk_len action placeholder tokens from each sample
|
| 1141 |
+
Args:
|
| 1142 |
+
last_hidden: [B, L, H]
|
| 1143 |
+
input_ids: [B, L]
|
| 1144 |
+
action_token_id: int or List[int]
|
| 1145 |
+
Returns:
|
| 1146 |
+
action_queries: [B, chunk_len, H]
|
| 1147 |
+
"""
|
| 1148 |
+
if action_token_id is None:
|
| 1149 |
+
raise ValueError("action_token_id must not be None")
|
| 1150 |
+
|
| 1151 |
+
device = input_ids.device
|
| 1152 |
+
B, L, H = last_hidden.shape
|
| 1153 |
+
|
| 1154 |
+
# Support multiple ids (e.g., multiple variants)
|
| 1155 |
+
if isinstance(action_token_id, (list, tuple, set)):
|
| 1156 |
+
id_list = torch.tensor(list(action_token_id), device=device, dtype=input_ids.dtype)
|
| 1157 |
+
# torch.isin requires PyTorch >=1.10
|
| 1158 |
+
mask = torch.isin(input_ids, id_list)
|
| 1159 |
+
else:
|
| 1160 |
+
mask = input_ids == action_token_id # [B, L]
|
| 1161 |
+
|
| 1162 |
+
counts = mask.sum(dim=1) # [B]
|
| 1163 |
+
if (counts < self.chunk_len).any():
|
| 1164 |
+
insufficient = (counts < self.chunk_len).nonzero(as_tuple=False).flatten().tolist()
|
| 1165 |
+
raise RuntimeError(
|
| 1166 |
+
f"The following samples have insufficient action tokens (< {self.chunk_len}): {insufficient} |"
|
| 1167 |
+
f" counts={counts.tolist()}"
|
| 1168 |
+
)
|
| 1169 |
+
|
| 1170 |
+
# Position indices
|
| 1171 |
+
idx = torch.arange(L, device=device).unsqueeze(0).expand(B, L) # [B, L]
|
| 1172 |
+
masked_pos = torch.where(mask, idx, torch.full_like(idx, -1)) # Set non-action positions to -1
|
| 1173 |
+
|
| 1174 |
+
# Take the last chunk_len positions (higher indices = later in sequence)
|
| 1175 |
+
# Note: count sufficiency already verified, so -1 won't be incorrectly selected
|
| 1176 |
+
topk_pos = masked_pos.topk(k=self.chunk_len, dim=-1).values # [B, chunk_len] unsorted
|
| 1177 |
+
# Sort in temporal order
|
| 1178 |
+
selected_pos = topk_pos.sort(dim=-1).values # [B, chunk_len]
|
| 1179 |
+
|
| 1180 |
+
# Gather
|
| 1181 |
+
expanded_index = selected_pos.unsqueeze(-1).expand(-1, -1, H) # [B, chunk_len, H]
|
| 1182 |
+
action_queries = last_hidden.gather(dim=1, index=expanded_index) # [B, chunk_len, H]
|
| 1183 |
+
return action_queries
|
| 1184 |
+
|
| 1185 |
+
# Discretised state → instruction prefix (π₀.5 style); shared with QwenPI_v3.
|
| 1186 |
+
add_discretized_state_to_instruction = staticmethod(add_discretized_state_to_instruction)
|
| 1187 |
+
|
| 1188 |
+
|
| 1189 |
+
if __name__ == "__main__":
|
| 1190 |
+
import argparse
|
| 1191 |
+
import os
|
| 1192 |
+
|
| 1193 |
+
from omegaconf import OmegaConf
|
| 1194 |
+
|
| 1195 |
+
parser = argparse.ArgumentParser()
|
| 1196 |
+
parser.add_argument(
|
| 1197 |
+
"--config_yaml",
|
| 1198 |
+
type=str,
|
| 1199 |
+
default="examples/LIBERO/train_files/starvla_cotrain_libero.yaml",
|
| 1200 |
+
help="Path to YAML config",
|
| 1201 |
+
)
|
| 1202 |
+
args, clipargs = parser.parse_known_args()
|
| 1203 |
+
|
| 1204 |
+
if os.getenv("DEBUGPY_ENABLE", "0") == "1":
|
| 1205 |
+
import debugpy
|
| 1206 |
+
|
| 1207 |
+
debugpy.listen(("0.0.0.0", 10092))
|
| 1208 |
+
print("Rank 0 waiting for debugger attach on port 10092...")
|
| 1209 |
+
debugpy.wait_for_client()
|
| 1210 |
+
|
| 1211 |
+
cfg = OmegaConf.load(args.config_yaml)
|
| 1212 |
+
|
| 1213 |
+
model = Qwenvl_OFT(cfg)
|
| 1214 |
+
print(model)
|
| 1215 |
+
|
| 1216 |
+
image = Image.fromarray(np.random.randint(0, 255, (224, 224, 3), dtype=np.uint8))
|
| 1217 |
+
sample = {
|
| 1218 |
+
"action": np.random.uniform(-1, 1, size=(16, 7)).astype(np.float16),
|
| 1219 |
+
"image": [image],
|
| 1220 |
+
"lang": "This is a fake instruction for testing.",
|
| 1221 |
+
"state": np.random.uniform(-1, 1, size=(1, 7)).astype(np.float16), # chunk, state_dim
|
| 1222 |
+
}
|
| 1223 |
+
sample2 = sample.copy()
|
| 1224 |
+
sample2["lang"] = "Another fake instruction for testing."
|
| 1225 |
+
|
| 1226 |
+
batch = [sample, sample2]
|
| 1227 |
+
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 1228 |
+
model = model.to(device)
|
| 1229 |
+
forward_output = model(batch)
|
| 1230 |
+
action_loss = forward_output["action_loss"]
|
| 1231 |
+
print(f"[train] Action Loss (with state): {action_loss.item()}")
|
| 1232 |
+
|
| 1233 |
+
predict_output = model.predict_action(examples=[batch[0]])
|
| 1234 |
+
normalized_actions = predict_output["normalized_actions"]
|
| 1235 |
+
print(f"[infer] Predicted Action shape: {normalized_actions.shape}")
|
| 1236 |
+
|
| 1237 |
+
# Backward-compat: examples without `state` should still work.
|
| 1238 |
+
sample_no_state = {k: v for k, v in sample.items() if k != "state"}
|
| 1239 |
+
forward_no_state = model([sample_no_state, sample_no_state])
|
| 1240 |
+
print(f"[train] Action Loss (no state): {forward_no_state['action_loss'].item()}")
|
| 1241 |
+
predict_no_state = model.predict_action(examples=[sample_no_state])
|
| 1242 |
+
print(f"[infer] Predicted Action shape (no state): {predict_no_state['normalized_actions'].shape}")
|
| 1243 |
+
|
| 1244 |
+
print("Finished")
|
latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/training-code/datasets.py
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/training-code/gym-language-contract.patch
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
diff --git a/examples/rl_games/train_files/data_registry/data_config.py b/examples/rl_games/train_files/data_registry/data_config.py
|
| 2 |
+
index 2ef8b39..6d76aeb 100644
|
| 3 |
+
--- a/examples/rl_games/train_files/data_registry/data_config.py
|
| 4 |
+
+++ b/examples/rl_games/train_files/data_registry/data_config.py
|
| 5 |
+
@@ -53,10 +53,10 @@ class AtlantisDataConfig(FlappyDataConfig):
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class GymnasiumDataConfig(FlappyDataConfig):
|
| 9 |
+
- pass
|
| 10 |
+
+ language_keys = ["annotation.human.task_description"]
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
-class GymnasiumNativeDataConfig(FlappyDataConfig):
|
| 14 |
+
+class GymnasiumNativeDataConfig(GymnasiumDataConfig):
|
| 15 |
+
"""Configure native Gymnasium state and action vectors."""
|
| 16 |
+
|
| 17 |
+
state_keys = ["state.native"]
|
latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/training-code/loader-spawn-cache.patch
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
diff --git a/starVLA/dataloader/gr00t_lerobot/datasets.py b/starVLA/dataloader/gr00t_lerobot/datasets.py
|
| 2 |
+
index 17ce5f1..2346230 100644
|
| 3 |
+
--- a/starVLA/dataloader/gr00t_lerobot/datasets.py
|
| 4 |
+
+++ b/starVLA/dataloader/gr00t_lerobot/datasets.py
|
| 5 |
+
@@ -667,6 +667,13 @@ class LeRobotSingleDataset(Dataset):
|
| 6 |
+
# Check if the dataset is valid
|
| 7 |
+
self._check_integrity()
|
| 8 |
+
|
| 9 |
+
+ def __getstate__(self):
|
| 10 |
+
+ state = self.__dict__.copy()
|
| 11 |
+
+ state["_parquet_cache"] = {}
|
| 12 |
+
+ state["curr_traj_data"] = None
|
| 13 |
+
+ state["curr_traj_id"] = None
|
| 14 |
+
+ return state
|
| 15 |
+
+
|
| 16 |
+
@property
|
| 17 |
+
def dataset_path(self) -> Path:
|
| 18 |
+
"""The path to the dataset that contains the METADATA_FILENAME file."""
|
latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/training-code/loader-spawn-test.patch
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
diff --git a/tests/data/test_starvla_tasks.py b/tests/data/test_starvla_tasks.py
|
| 2 |
+
index 1403fa13..62468aa6 100644
|
| 3 |
+
--- a/tests/data/test_starvla_tasks.py
|
| 4 |
+
+++ b/tests/data/test_starvla_tasks.py
|
| 5 |
+
@@ -395,3 +395,26 @@ def test_balance_masked_l1_uses_equal_sample_weights_and_no_padding_gradient(mon
|
| 6 |
+
all_valid = torch.ones((2, 8), dtype=torch.bool)
|
| 7 |
+
torch.testing.assert_close(model._compute_action_loss(pred, target, action_loss_mask=all_valid),
|
| 8 |
+
(pred - target).abs().mean())
|
| 9 |
+
+
|
| 10 |
+
+
|
| 11 |
+
+def test_lerobot_spawn_reload_preserves_data_without_copying_parent_cache(tmp_path, monkeypatch):
|
| 12 |
+
+ import pickle
|
| 13 |
+
+ pytest.importorskip("accelerate")
|
| 14 |
+
+ monkeypatch.syspath_prepend(str(Path(__file__).resolve().parents[2] / "third_party/starVLA"))
|
| 15 |
+
+ from starVLA.dataloader.gr00t_lerobot.datasets import LeRobotSingleDataset
|
| 16 |
+
+
|
| 17 |
+
+ path = tmp_path / "episode.parquet"
|
| 18 |
+
+ table = pd.DataFrame({"image": [b"rgb" * (1024 * 1024)], "action": [1]})
|
| 19 |
+
+ table.to_parquet(path)
|
| 20 |
+
+ parent = object.__new__(LeRobotSingleDataset)
|
| 21 |
+
+ parent._parquet_cache = {path: table}
|
| 22 |
+
+ parent.curr_traj_data = table
|
| 23 |
+
+ parent.curr_traj_id = 0
|
| 24 |
+
+ parent._all_steps = [(0, 0)]
|
| 25 |
+
+
|
| 26 |
+
+ payload = pickle.dumps(parent)
|
| 27 |
+
+ assert len(payload) < 64 * 1024
|
| 28 |
+
+ worker = pickle.loads(payload)
|
| 29 |
+
+ assert worker._all_steps == parent._all_steps
|
| 30 |
+
+ pd.testing.assert_frame_equal(worker._read_trajectory_parquet(path), table)
|
| 31 |
+
+ pd.testing.assert_frame_equal(parent.curr_traj_data, table)
|
latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/training-code/mean-profile-preparation.patch
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
diff --git a/latency_bench/data/sf_teacher_rollout.py b/latency_bench/data/sf_teacher_rollout.py
|
| 2 |
+
index ef45d95b..af0a9fa9 100644
|
| 3 |
+
--- a/latency_bench/data/sf_teacher_rollout.py
|
| 4 |
+
+++ b/latency_bench/data/sf_teacher_rollout.py
|
| 5 |
+
@@ -964,6 +964,11 @@ def _replay_accepted_specs(
|
| 6 |
+
row_buffer_size=int(row_buffer_size),
|
| 7 |
+
export_image_size=export_image_size,
|
| 8 |
+
)
|
| 9 |
+
+ stream_current_images = (
|
| 10 |
+
+ image_transform_config.normalized_transform() == "raw_rgb"
|
| 11 |
+
+ and int(context_window) <= 1
|
| 12 |
+
+ and filter_preset == "none"
|
| 13 |
+
+ )
|
| 14 |
+
image_resolution: list[int] | None = None
|
| 15 |
+
|
| 16 |
+
def assign_specs_to_slots(slots: list[int]) -> dict[int, int]:
|
| 17 |
+
@@ -1051,7 +1056,10 @@ def _replay_accepted_specs(
|
| 18 |
+
next_raw_frame=next_decision_step * obs_stride_raw_frames,
|
| 19 |
+
max_steps_per_episode=int(max_steps_per_episode),
|
| 20 |
+
)
|
| 21 |
+
- active_episode_frames[slot].append(np.array(image_frame, copy=True))
|
| 22 |
+
+ if stream_current_images:
|
| 23 |
+
+ image_writer.write(image_frame, image_path)
|
| 24 |
+
+ else:
|
| 25 |
+
+ active_episode_frames[slot].append(np.array(image_frame, copy=True))
|
| 26 |
+
row = teacher_dataset_row(
|
| 27 |
+
env_name=env_name,
|
| 28 |
+
spec=spec,
|
| 29 |
+
@@ -1104,7 +1112,9 @@ def _replay_accepted_specs(
|
| 30 |
+
)
|
| 31 |
+
else:
|
| 32 |
+
shard_rows = [dict(row) for row in final_rows]
|
| 33 |
+
- if _is_demon_ghost_trail:
|
| 34 |
+
+ if stream_current_images:
|
| 35 |
+
+ image_writer.flush()
|
| 36 |
+
+ elif _is_demon_ghost_trail:
|
| 37 |
+
# Frames are pre-built ghost trail images; map directly.
|
| 38 |
+
ep_steps = [int(row["decision_step"]) for row in final_rows]
|
| 39 |
+
frames_by_decision_step = {
|
| 40 |
+
@@ -1118,12 +1128,13 @@ def _replay_accepted_specs(
|
| 41 |
+
frames=active_episode_frames[slot],
|
| 42 |
+
config=image_transform_config,
|
| 43 |
+
)
|
| 44 |
+
- shard_rows = _write_filtered_episode_images(
|
| 45 |
+
- image_writer,
|
| 46 |
+
- shard_rows,
|
| 47 |
+
- frames_by_decision_step,
|
| 48 |
+
- context_window=int(context_window),
|
| 49 |
+
- )
|
| 50 |
+
+ if not stream_current_images:
|
| 51 |
+
+ shard_rows = _write_filtered_episode_images(
|
| 52 |
+
+ image_writer,
|
| 53 |
+
+ shard_rows,
|
| 54 |
+
+ frames_by_decision_step,
|
| 55 |
+
+ context_window=int(context_window),
|
| 56 |
+
+ )
|
| 57 |
+
write_episode_row_shard(output_dir, shard_rows)
|
| 58 |
+
progress.update(1)
|
| 59 |
+
replayed += 1
|
| 60 |
+
diff --git a/tests/data/test_sf_teacher_dataset_export.py b/tests/data/test_sf_teacher_dataset_export.py
|
| 61 |
+
index e6b25233..7d5537ac 100644
|
| 62 |
+
--- a/tests/data/test_sf_teacher_dataset_export.py
|
| 63 |
+
+++ b/tests/data/test_sf_teacher_dataset_export.py
|
| 64 |
+
@@ -1273,7 +1273,8 @@ def test_collect_teacher_dataset_saves_demon_attack_latest_raw_rgb_image(monkeyp
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
@pytest.mark.parametrize("export_history", [False, True])
|
| 68 |
+
-def test_collect_teacher_dataset_saves_flappy_latest_raw_rgb_image(monkeypatch, tmp_path, export_history):
|
| 69 |
+
+@pytest.mark.parametrize("filter_preset", ["clean_v1", "none"])
|
| 70 |
+
+def test_collect_teacher_dataset_saves_flappy_latest_raw_rgb_image(monkeypatch, tmp_path, export_history, filter_preset):
|
| 71 |
+
import torch
|
| 72 |
+
from gymnasium.spaces import Discrete
|
| 73 |
+
from PIL import Image
|
| 74 |
+
@@ -1307,6 +1308,9 @@ def test_collect_teacher_dataset_saves_flappy_latest_raw_rgb_image(monkeypatch,
|
| 75 |
+
class FakeEnv:
|
| 76 |
+
action_space = Discrete(2)
|
| 77 |
+
|
| 78 |
+
+ def __init__(self, exporting):
|
| 79 |
+
+ self.exporting = exporting
|
| 80 |
+
+
|
| 81 |
+
def reset(self, seed=None):
|
| 82 |
+
self.steps = 0
|
| 83 |
+
obs = {"obs": np.zeros((1, 12, 2, 2), dtype=np.uint8)}
|
| 84 |
+
@@ -1323,6 +1327,9 @@ def test_collect_teacher_dataset_saves_flappy_latest_raw_rgb_image(monkeypatch,
|
| 85 |
+
|
| 86 |
+
def step(self, action):
|
| 87 |
+
self.steps += 1
|
| 88 |
+
+ if self.exporting and self.steps == 2 and filter_preset == "none":
|
| 89 |
+
+ image_path = output_dir / "images/train/episode_000000/decision_step_000000.png"
|
| 90 |
+
+ assert image_path.is_file(), "First frame must be on disk before the episode ends"
|
| 91 |
+
obs = {"obs": np.ones((1, 12, 2, 2), dtype=np.uint8)}
|
| 92 |
+
next_stack = np.stack([np.full((6, 5, 3), 50 + idx, dtype=np.uint8) for idx in range(4)], axis=0)
|
| 93 |
+
return (
|
| 94 |
+
@@ -1354,7 +1361,7 @@ def test_collect_teacher_dataset_saves_flappy_latest_raw_rgb_image(monkeypatch,
|
| 95 |
+
exporter,
|
| 96 |
+
"build_env_and_policy",
|
| 97 |
+
lambda module, cfg, config, num_envs: {
|
| 98 |
+
- "envs": [FakeEnv()],
|
| 99 |
+
+ "envs": [FakeEnv(cfg.export_env_raw_rgb_frames)],
|
| 100 |
+
"env_info": object(),
|
| 101 |
+
"actor_critic": FakeActorCritic(),
|
| 102 |
+
"device": torch.device("cpu"),
|
| 103 |
+
@@ -1382,6 +1389,7 @@ def test_collect_teacher_dataset_saves_flappy_latest_raw_rgb_image(monkeypatch,
|
| 104 |
+
export_image_size=None,
|
| 105 |
+
image_writer_workers=0,
|
| 106 |
+
row_buffer_size=1,
|
| 107 |
+
+ filter_preset=filter_preset,
|
| 108 |
+
)
|
| 109 |
+
train_rows = _read_shard_rows(output_dir, "train")
|
| 110 |
+
val_rows = _read_shard_rows(output_dir, "val")
|
| 111 |
+
diff --git a/third_party/MIKASA-Robo b/third_party/MIKASA-Robo
|
| 112 |
+
--- a/third_party/MIKASA-Robo
|
| 113 |
+
+++ b/third_party/MIKASA-Robo
|
| 114 |
+
@@ -1 +1 @@
|
| 115 |
+
-Subproject commit 017490bbb787c6efa8c025f9558e1a691965c0e0
|
| 116 |
+
+Subproject commit 017490bbb787c6efa8c025f9558e1a691965c0e0-dirty
|
| 117 |
+
diff --git a/third_party/starVLA b/third_party/starVLA
|
| 118 |
+
--- a/third_party/starVLA
|
| 119 |
+
+++ b/third_party/starVLA
|
| 120 |
+
@@ -1 +1 @@
|
| 121 |
+
-Subproject commit ccca13c5177fe3d3c884b6e2de4965d916016649
|
| 122 |
+
+Subproject commit ccca13c5177fe3d3c884b6e2de4965d916016649-dirty
|
| 123 |
+
diff --git a/training/gymnasium_sf/integration.py b/training/gymnasium_sf/integration.py
|
| 124 |
+
index 61c65ae9..874f655f 100644
|
| 125 |
+
--- a/training/gymnasium_sf/integration.py
|
| 126 |
+
+++ b/training/gymnasium_sf/integration.py
|
| 127 |
+
@@ -64,7 +64,6 @@ from training.common.sf_raw_reward_stats_compat import (
|
| 128 |
+
)
|
| 129 |
+
from training.common.utils import env_instance_seed, seed_everything, str2bool
|
| 130 |
+
from training.common.wandb_compat import patch_sample_factory_wandb
|
| 131 |
+
-from sample_factory.algo.mc_ppo.env import EpisodeEnv
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
SF_ENV_NAME = "latency_gymnasium"
|
| 135 |
+
@@ -385,7 +384,11 @@ def make_gymnasium_sf_env(
|
| 136 |
+
env_id=int(env_config.env_id),
|
| 137 |
+
issued_action_labels=action_labels,
|
| 138 |
+
)
|
| 139 |
+
- return EpisodeEnv(env, cfg.ppo) if cfg.algo == "PPO" else env
|
| 140 |
+
+ if cfg.algo == "PPO":
|
| 141 |
+
+ from sample_factory.algo.mc_ppo.env import EpisodeEnv
|
| 142 |
+
+
|
| 143 |
+
+ return EpisodeEnv(env, cfg.ppo)
|
| 144 |
+
+ return env
|
| 145 |
+
|
| 146 |
+
|
| 147 |
+
def register_gymnasium_envs() -> None:
|
latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/training-code/train_starvla.py
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/training-code/trainer_tools.py
ADDED
|
@@ -0,0 +1,736 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
metrics.py
|
| 3 |
+
|
| 4 |
+
Utility classes defining a Metrics container and multiple Trackers to enable model/stage-specific logging to various
|
| 5 |
+
endpoints (e.g., JSONL local logs, Weights & Biases).
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
from typing import Tuple
|
| 9 |
+
import re
|
| 10 |
+
import json
|
| 11 |
+
import numpy as np
|
| 12 |
+
import torch
|
| 13 |
+
import torch.distributed as dist
|
| 14 |
+
from transformers import get_scheduler
|
| 15 |
+
|
| 16 |
+
from accelerate import Accelerator, DeepSpeedPlugin
|
| 17 |
+
from accelerate.logging import get_logger
|
| 18 |
+
from starVLA.training.trainer_utils.config_tracker import reload_module_paths
|
| 19 |
+
|
| 20 |
+
logger = get_logger(__name__)
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def build_accelerator(cfg, *, use_deepspeed: bool) -> Accelerator:
|
| 24 |
+
accelerator_kwargs = {
|
| 25 |
+
"gradient_accumulation_steps": cfg.trainer.gradient_accumulation_steps,
|
| 26 |
+
}
|
| 27 |
+
if use_deepspeed:
|
| 28 |
+
accelerator_kwargs["deepspeed_plugin"] = DeepSpeedPlugin()
|
| 29 |
+
|
| 30 |
+
accelerator = Accelerator(**accelerator_kwargs)
|
| 31 |
+
accelerator.print(accelerator.state)
|
| 32 |
+
return accelerator
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
_VOCAB_SIZED_WEIGHT_SUFFIXES = (
|
| 36 |
+
"embed_tokens.weight",
|
| 37 |
+
"lm_head.weight",
|
| 38 |
+
)
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def _adapt_vocab_sized_weights(module, state_dict):
|
| 42 |
+
"""Copy overlapping vocab rows when tokenizer-sized Qwen weights changed."""
|
| 43 |
+
target_state_dict = module.state_dict()
|
| 44 |
+
adapted = dict(state_dict)
|
| 45 |
+
adapted_keys = []
|
| 46 |
+
for key, source_tensor in state_dict.items():
|
| 47 |
+
target_tensor = target_state_dict.get(key)
|
| 48 |
+
if target_tensor is None or not hasattr(source_tensor, "shape") or not hasattr(target_tensor, "shape"):
|
| 49 |
+
continue
|
| 50 |
+
if tuple(source_tensor.shape) == tuple(target_tensor.shape):
|
| 51 |
+
continue
|
| 52 |
+
if not key.endswith(_VOCAB_SIZED_WEIGHT_SUFFIXES):
|
| 53 |
+
continue
|
| 54 |
+
if source_tensor.ndim != 2 or target_tensor.ndim != 2:
|
| 55 |
+
continue
|
| 56 |
+
if source_tensor.shape[1] != target_tensor.shape[1]:
|
| 57 |
+
continue
|
| 58 |
+
|
| 59 |
+
patched_tensor = target_tensor.detach().clone()
|
| 60 |
+
rows = min(source_tensor.shape[0], target_tensor.shape[0])
|
| 61 |
+
patched_tensor[:rows].copy_(source_tensor[:rows].to(dtype=patched_tensor.dtype, device=patched_tensor.device))
|
| 62 |
+
adapted[key] = patched_tensor
|
| 63 |
+
adapted_keys.append((key, tuple(source_tensor.shape), tuple(target_tensor.shape), rows))
|
| 64 |
+
return adapted, adapted_keys
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
# === Define Tracker Interface ===
|
| 68 |
+
#
|
| 69 |
+
|
| 70 |
+
# utils/cli_parser.py
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def normalize_dotlist_args(args):
|
| 74 |
+
"""
|
| 75 |
+
Convert ['--x.y', 'val'] and ['--flag'] → ['x.y=val', 'flag=true']
|
| 76 |
+
"""
|
| 77 |
+
normalized = []
|
| 78 |
+
skip = False
|
| 79 |
+
for i in range(len(args)):
|
| 80 |
+
if skip:
|
| 81 |
+
skip = False
|
| 82 |
+
continue
|
| 83 |
+
|
| 84 |
+
arg = args[i]
|
| 85 |
+
if arg.startswith("--"):
|
| 86 |
+
key = arg.lstrip("-")
|
| 87 |
+
if "=" in key:
|
| 88 |
+
normalized.append(key)
|
| 89 |
+
elif i + 1 < len(args) and not args[i + 1].startswith("--"):
|
| 90 |
+
normalized.append(f"{key}={args[i + 1]}")
|
| 91 |
+
skip = True
|
| 92 |
+
else:
|
| 93 |
+
normalized.append(f"{key}=true")
|
| 94 |
+
else:
|
| 95 |
+
pass # skip orphaned values
|
| 96 |
+
return normalized
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
def setup_optimizer_and_scheduler(model, cfg) -> Tuple[torch.optim.Optimizer, torch.optim.lr_scheduler._LRScheduler]:
|
| 100 |
+
"""Build AdamW optimizer + LR scheduler from cfg.trainer.
|
| 101 |
+
|
| 102 |
+
Supports:
|
| 103 |
+
- Per-module learning rates via cfg.trainer.learning_rate
|
| 104 |
+
- cosine_with_min_lr (or any transformers scheduler) via cfg.trainer.lr_scheduler_type
|
| 105 |
+
"""
|
| 106 |
+
param_groups = build_param_lr_groups(model=model, cfg=cfg)
|
| 107 |
+
fused_available = torch.cuda.is_available()
|
| 108 |
+
optimizer = torch.optim.AdamW(
|
| 109 |
+
param_groups,
|
| 110 |
+
lr=cfg.trainer.learning_rate.base,
|
| 111 |
+
betas=tuple(cfg.trainer.optimizer.betas),
|
| 112 |
+
weight_decay=cfg.trainer.optimizer.weight_decay,
|
| 113 |
+
eps=cfg.trainer.optimizer.eps,
|
| 114 |
+
fused=fused_available,
|
| 115 |
+
)
|
| 116 |
+
|
| 117 |
+
if dist.is_initialized() and dist.get_rank() == 0:
|
| 118 |
+
for group in optimizer.param_groups:
|
| 119 |
+
logger.info(f"LR Group {group['name']}: lr={group['lr']}, num_params={len(group['params'])}")
|
| 120 |
+
|
| 121 |
+
# Strip keys unknown to transformers' get_scheduler before passing kwargs.
|
| 122 |
+
sched_kwargs = cfg.trainer.scheduler_specific_kwargs
|
| 123 |
+
lr_scheduler = get_scheduler(
|
| 124 |
+
name=cfg.trainer.lr_scheduler_type,
|
| 125 |
+
optimizer=optimizer,
|
| 126 |
+
num_warmup_steps=cfg.trainer.num_warmup_steps,
|
| 127 |
+
num_training_steps=cfg.trainer.max_train_steps,
|
| 128 |
+
scheduler_specific_kwargs=sched_kwargs,
|
| 129 |
+
)
|
| 130 |
+
|
| 131 |
+
return optimizer, lr_scheduler
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
def _qwen_vl_interface(model):
|
| 135 |
+
return model.qwen_vl_interface
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
def _llm_language_model(model):
|
| 139 |
+
return _qwen_vl_interface(model).model.model.language_model
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
def _vit_module(model):
|
| 143 |
+
# Qwen-VL keeps the visual tower and its vision-to-language connector/merger
|
| 144 |
+
# under this module.
|
| 145 |
+
return _qwen_vl_interface(model).model.model.visual
|
| 146 |
+
|
| 147 |
+
|
| 148 |
+
def _llm_decoder_layers(model):
|
| 149 |
+
return _llm_language_model(model).layers
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
def _tied_embedding_modules(model):
|
| 153 |
+
language_model = _llm_language_model(model)
|
| 154 |
+
hf_model = _qwen_vl_interface(model).model
|
| 155 |
+
return language_model.embed_tokens, hf_model.lm_head
|
| 156 |
+
|
| 157 |
+
|
| 158 |
+
def resolve_freeze_llm_layer_indices(model, cfg):
|
| 159 |
+
"""Translate ``trainer.freeze_llm_layers=[start,end]`` to an inclusive layer list."""
|
| 160 |
+
freeze_llm_layers = cfg.trainer.freeze_llm_layers
|
| 161 |
+
if not freeze_llm_layers:
|
| 162 |
+
return [], len(_llm_decoder_layers(model))
|
| 163 |
+
start, end = [int(value) for value in freeze_llm_layers]
|
| 164 |
+
return list(range(start, end + 1)), len(_llm_decoder_layers(model))
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
def _module_param_ids(module):
|
| 168 |
+
return {id(param) for param in module.parameters()}
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
def _has_vit_or_llm_freeze_request(cfg) -> bool:
|
| 172 |
+
return bool(cfg.trainer.freeze_vit or cfg.trainer.freeze_tied_embedding or cfg.trainer.freeze_llm_layers)
|
| 173 |
+
|
| 174 |
+
|
| 175 |
+
def vit_llm_frozen_param_ids(model, cfg):
|
| 176 |
+
"""Parameter ids excluded from optimizer by trainer freeze controls."""
|
| 177 |
+
if not _has_vit_or_llm_freeze_request(cfg):
|
| 178 |
+
return set()
|
| 179 |
+
|
| 180 |
+
frozen_params = set()
|
| 181 |
+
freeze_vit = cfg.trainer.freeze_vit
|
| 182 |
+
if freeze_vit:
|
| 183 |
+
frozen_params.update(_module_param_ids(_vit_module(model)))
|
| 184 |
+
|
| 185 |
+
if cfg.trainer.freeze_tied_embedding:
|
| 186 |
+
for module in _tied_embedding_modules(model):
|
| 187 |
+
frozen_params.update(_module_param_ids(module))
|
| 188 |
+
|
| 189 |
+
frozen_layer_indices, _ = resolve_freeze_llm_layer_indices(model, cfg)
|
| 190 |
+
layers = list(_llm_decoder_layers(model))
|
| 191 |
+
for layer_index in frozen_layer_indices:
|
| 192 |
+
frozen_params.update(_module_param_ids(layers[layer_index]))
|
| 193 |
+
|
| 194 |
+
return frozen_params
|
| 195 |
+
|
| 196 |
+
|
| 197 |
+
def build_param_lr_groups(model, cfg):
|
| 198 |
+
"""
|
| 199 |
+
build multiple param groups based on cfg.trainer.learning_rate.
|
| 200 |
+
support specifying different learning rates for different modules, the rest use base.
|
| 201 |
+
|
| 202 |
+
Args:
|
| 203 |
+
vla: nn.Module model object
|
| 204 |
+
cfg: config object, requires cfg.trainer.learning_rate dictionary
|
| 205 |
+
|
| 206 |
+
Returns:
|
| 207 |
+
List[Dict]: param_groups that can be used to build optimizer with torch.optim
|
| 208 |
+
"""
|
| 209 |
+
|
| 210 |
+
lr_cfg = cfg.trainer.learning_rate
|
| 211 |
+
base_lr = lr_cfg.get("base", 1e-4) # default base learning rate
|
| 212 |
+
|
| 213 |
+
freeze_modules = cfg.trainer.get("freeze_modules", "")
|
| 214 |
+
if not isinstance(freeze_modules, str):
|
| 215 |
+
freeze_modules = ""
|
| 216 |
+
freeze_patterns = [p.strip() for p in freeze_modules.split(",") if p.strip()]
|
| 217 |
+
|
| 218 |
+
used_params = set()
|
| 219 |
+
frozen_params = set()
|
| 220 |
+
param_groups = []
|
| 221 |
+
|
| 222 |
+
for freeze_path in freeze_patterns:
|
| 223 |
+
module = model
|
| 224 |
+
try:
|
| 225 |
+
for attr in freeze_path.split("."):
|
| 226 |
+
module = getattr(module, attr)
|
| 227 |
+
frozen_params.update(id(p) for p in module.parameters())
|
| 228 |
+
except AttributeError:
|
| 229 |
+
print(f"⚠️ freeze module path does not exist: {freeze_path}")
|
| 230 |
+
continue
|
| 231 |
+
|
| 232 |
+
# Exclude trainer freeze controls so frozen parameters never enter an
|
| 233 |
+
# optimizer param group.
|
| 234 |
+
frozen_params.update(vit_llm_frozen_param_ids(model, cfg))
|
| 235 |
+
|
| 236 |
+
for module_name, lr in lr_cfg.items():
|
| 237 |
+
if module_name == "base":
|
| 238 |
+
continue
|
| 239 |
+
# try to find the module under vla by module_name (support nested paths)
|
| 240 |
+
module = model
|
| 241 |
+
try:
|
| 242 |
+
for attr in module_name.split("."):
|
| 243 |
+
module = getattr(module, attr)
|
| 244 |
+
# filter out frozen parameters
|
| 245 |
+
params = [p for p in module.parameters() if id(p) not in frozen_params]
|
| 246 |
+
if params: # only add param group if there are trainable parameters
|
| 247 |
+
param_groups.append({"params": params, "lr": lr, "name": module_name})
|
| 248 |
+
used_params.update(id(p) for p in params)
|
| 249 |
+
except AttributeError:
|
| 250 |
+
ReferenceError(f"⚠️ module path `{module_name}` not found in vla")
|
| 251 |
+
|
| 252 |
+
# assign base learning rate to the remaining unused parameters (exclude frozen ones)
|
| 253 |
+
other_params = [p for p in model.parameters() if id(p) not in used_params and id(p) not in frozen_params]
|
| 254 |
+
if other_params:
|
| 255 |
+
param_groups.append({"params": other_params, "lr": base_lr, "name": "base"})
|
| 256 |
+
|
| 257 |
+
return param_groups
|
| 258 |
+
|
| 259 |
+
|
| 260 |
+
import torch.distributed as dist
|
| 261 |
+
|
| 262 |
+
|
| 263 |
+
def only_main_process(func):
|
| 264 |
+
"""
|
| 265 |
+
decorator: only run in main process (rank=0)
|
| 266 |
+
"""
|
| 267 |
+
|
| 268 |
+
def wrapper(*args, **kwargs):
|
| 269 |
+
if dist.is_initialized() and dist.get_rank() != 0:
|
| 270 |
+
return None # non-main process does not execute
|
| 271 |
+
return func(*args, **kwargs)
|
| 272 |
+
|
| 273 |
+
return wrapper
|
| 274 |
+
|
| 275 |
+
|
| 276 |
+
from torchvision.ops import box_iou
|
| 277 |
+
from PIL import Image
|
| 278 |
+
|
| 279 |
+
|
| 280 |
+
def resize_images(images, target_size=(224, 224)):
|
| 281 |
+
"""
|
| 282 |
+
recursively resize all images in the nested list.
|
| 283 |
+
|
| 284 |
+
:param images: nested list of images or single image.
|
| 285 |
+
:param target_size: target size (width, height) after resizing.
|
| 286 |
+
:return: resized images list, keeping the original nested structure.
|
| 287 |
+
"""
|
| 288 |
+
if isinstance(images, Image.Image): # if it is a single PIL image
|
| 289 |
+
return images.resize(target_size)
|
| 290 |
+
elif isinstance(images, list): # if it is a list, recursively process each element
|
| 291 |
+
return [resize_images(img, target_size) for img in images]
|
| 292 |
+
else:
|
| 293 |
+
raise ValueError("Unsupported image type or structure.")
|
| 294 |
+
|
| 295 |
+
|
| 296 |
+
def stitch_frames(frames, grid=(2, 2), size=(224, 224)):
|
| 297 |
+
"""Tile temporal frames into one image, keeping the token count of a single image.
|
| 298 |
+
|
| 299 |
+
Used by the ``stitch`` multi-image mode: instead of feeding N frames as N
|
| 300 |
+
tokenized images, they are laid out on a fixed grid and the whole canvas is
|
| 301 |
+
returned at ``size``. Both training (``_pack_sample``) and eval
|
| 302 |
+
(``policy/starvla.py``) call this so the layouts match.
|
| 303 |
+
|
| 304 |
+
Args:
|
| 305 |
+
frames: list of ``PIL.Image`` or ``np.ndarray`` (H, W, C), oldest first.
|
| 306 |
+
grid: (rows, cols). Cells beyond ``len(frames)`` repeat the last frame so
|
| 307 |
+
the layout is fixed regardless of how many frames are available.
|
| 308 |
+
size: (width, height) of the returned canvas.
|
| 309 |
+
"""
|
| 310 |
+
rows, cols = grid
|
| 311 |
+
canvas_w, canvas_h = size
|
| 312 |
+
cell_w, cell_h = canvas_w // cols, canvas_h // rows
|
| 313 |
+
pil = [f if isinstance(f, Image.Image) else Image.fromarray(f) for f in frames]
|
| 314 |
+
capacity = rows * cols
|
| 315 |
+
pil = (pil + [pil[-1]] * (capacity - len(pil))) if len(pil) < capacity else pil[:capacity]
|
| 316 |
+
canvas = Image.new("RGB", (canvas_w, canvas_h))
|
| 317 |
+
for idx, frame in enumerate(pil):
|
| 318 |
+
r, c = divmod(idx, cols)
|
| 319 |
+
canvas.paste(frame.convert("RGB").resize((cell_w, cell_h)), (c * cell_w, r * cell_h))
|
| 320 |
+
return canvas
|
| 321 |
+
|
| 322 |
+
|
| 323 |
+
import torch.distributed as dist
|
| 324 |
+
|
| 325 |
+
|
| 326 |
+
class TrainerUtils:
|
| 327 |
+
@staticmethod
|
| 328 |
+
def freeze_backbones(model, freeze_modules=""):
|
| 329 |
+
"""
|
| 330 |
+
directly freeze the specified submodules based on the relative module path list (patterns), no longer recursively find all submodule names:
|
| 331 |
+
- patterns: read from config.trainer.freeze_modules, separated by commas to get the "relative path" list
|
| 332 |
+
for example "qwen_vl_interface, action_model.net",
|
| 333 |
+
it means to freeze model.qwen_vl_interface and model.action_model.net.
|
| 334 |
+
|
| 335 |
+
Args:
|
| 336 |
+
model: nn.Module model object
|
| 337 |
+
freeze_modules: relative module path list (patterns)
|
| 338 |
+
|
| 339 |
+
Returns:
|
| 340 |
+
model: nn.Module model object
|
| 341 |
+
return:
|
| 342 |
+
- model:
|
| 343 |
+
"""
|
| 344 |
+
frozen = []
|
| 345 |
+
print("#"*30)
|
| 346 |
+
print(freeze_modules)
|
| 347 |
+
if freeze_modules and type(freeze_modules) == str:
|
| 348 |
+
# split and remove whitespace
|
| 349 |
+
patterns = [p.strip() for p in freeze_modules.split(",") if p.strip()] if freeze_modules else []
|
| 350 |
+
|
| 351 |
+
for path in patterns:
|
| 352 |
+
# split the "relative path" by dots, for example "action_model.net" → ["action_model", "net"]
|
| 353 |
+
attrs = path.split(".")
|
| 354 |
+
module = model
|
| 355 |
+
try:
|
| 356 |
+
for attr in attrs:
|
| 357 |
+
module = getattr(module, attr)
|
| 358 |
+
# if the module is successfully get, freeze it and its all submodule parameters
|
| 359 |
+
for param in module.parameters():
|
| 360 |
+
param.requires_grad = False
|
| 361 |
+
frozen.append(path)
|
| 362 |
+
except AttributeError:
|
| 363 |
+
# if the attribute does not exist, skip and print warning
|
| 364 |
+
print(f"⚠️ module path does not exist, cannot freeze: {path}")
|
| 365 |
+
continue
|
| 366 |
+
|
| 367 |
+
# accelerator.wait_for_everyone() # synchronize when distributed training
|
| 368 |
+
if not dist.is_initialized() or dist.get_rank() == 0:
|
| 369 |
+
print(f"🔒 Frozen modules with re pattern: {frozen}")
|
| 370 |
+
return model
|
| 371 |
+
|
| 372 |
+
@staticmethod
|
| 373 |
+
def freeze_vit_and_llm_layers(model, cfg):
|
| 374 |
+
"""Apply trainer.freeze_vit/freeze_tied_embedding/freeze_llm_layers.
|
| 375 |
+
|
| 376 |
+
trainer.freeze_vit freezes Qwen-VL's visual module, including the
|
| 377 |
+
vision-to-language connector/merger it contains.
|
| 378 |
+
"""
|
| 379 |
+
if not _has_vit_or_llm_freeze_request(cfg):
|
| 380 |
+
return model
|
| 381 |
+
|
| 382 |
+
freeze_vit = cfg.trainer.freeze_vit
|
| 383 |
+
if freeze_vit:
|
| 384 |
+
for param in _vit_module(model).parameters():
|
| 385 |
+
param.requires_grad = False
|
| 386 |
+
|
| 387 |
+
freeze_tied_embedding = cfg.trainer.freeze_tied_embedding
|
| 388 |
+
if freeze_tied_embedding:
|
| 389 |
+
for module in _tied_embedding_modules(model):
|
| 390 |
+
for param in module.parameters():
|
| 391 |
+
param.requires_grad = False
|
| 392 |
+
|
| 393 |
+
frozen_layer_indices, total = resolve_freeze_llm_layer_indices(model, cfg)
|
| 394 |
+
layers = list(_llm_decoder_layers(model))
|
| 395 |
+
for layer_index in frozen_layer_indices:
|
| 396 |
+
for param in layers[layer_index].parameters():
|
| 397 |
+
param.requires_grad = False
|
| 398 |
+
|
| 399 |
+
if not dist.is_initialized() or dist.get_rank() == 0:
|
| 400 |
+
print(
|
| 401 |
+
"🔒 freeze_vit="
|
| 402 |
+
f"{freeze_vit}, freeze_tied_embedding={freeze_tied_embedding}, "
|
| 403 |
+
f"freeze_llm_layers={list(frozen_layer_indices)}"
|
| 404 |
+
f" / total={total}"
|
| 405 |
+
)
|
| 406 |
+
return model
|
| 407 |
+
|
| 408 |
+
@staticmethod
|
| 409 |
+
def print_trainable_parameters(model):
|
| 410 |
+
"""
|
| 411 |
+
print the total number of parameters and trainable parameters of the model
|
| 412 |
+
:param model: PyTorch model instance
|
| 413 |
+
"""
|
| 414 |
+
if dist.is_initialized() and dist.get_rank() != 0:
|
| 415 |
+
return
|
| 416 |
+
print("📊 model parameter statistics:")
|
| 417 |
+
num_params = sum(p.numel() for p in model.parameters())
|
| 418 |
+
num_trainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad)
|
| 419 |
+
print(
|
| 420 |
+
f"# Parameters (in millions): {num_params / 10**6:.3f} Total, {num_trainable_params / 10**6:.3f} Trainable"
|
| 421 |
+
)
|
| 422 |
+
return num_params, num_trainable_params
|
| 423 |
+
|
| 424 |
+
@staticmethod
|
| 425 |
+
def load_pretrained_backbones(model, checkpoint_path=None, reload_modules=None):
|
| 426 |
+
"""
|
| 427 |
+
load checkpoint:
|
| 428 |
+
- if reload_modules is set, load by path part
|
| 429 |
+
- otherwise → load the entire model parameters (overwrite model)
|
| 430 |
+
|
| 431 |
+
return:
|
| 432 |
+
replace, loaded_modules: list of module paths that successfully loaded parameters; if global load, then ["<full_model>"]
|
| 433 |
+
"""
|
| 434 |
+
if not checkpoint_path:
|
| 435 |
+
return []
|
| 436 |
+
if not dist.is_initialized() or dist.get_rank() == 0:
|
| 437 |
+
print(f"📦 loading checkpoint: {checkpoint_path}")
|
| 438 |
+
try:
|
| 439 |
+
if _is_safetensors_path(checkpoint_path):
|
| 440 |
+
from safetensors.torch import load_file
|
| 441 |
+
|
| 442 |
+
checkpoint = load_file(checkpoint_path)
|
| 443 |
+
else:
|
| 444 |
+
checkpoint = torch.load(checkpoint_path, map_location="cpu")
|
| 445 |
+
except Exception as e:
|
| 446 |
+
raise RuntimeError(f"❌ loading checkpoint failed: {e}")
|
| 447 |
+
|
| 448 |
+
loaded_modules = []
|
| 449 |
+
|
| 450 |
+
module_paths = reload_module_paths(reload_modules)
|
| 451 |
+
if module_paths: # partial load
|
| 452 |
+
for path in module_paths:
|
| 453 |
+
reload_path = path.split(".")
|
| 454 |
+
module = model
|
| 455 |
+
try:
|
| 456 |
+
for module_name in reload_path: # find the module to modify level by level
|
| 457 |
+
module = getattr(module, module_name)
|
| 458 |
+
prefix = path + "."
|
| 459 |
+
sub_state_dict = {k[len(prefix) :]: v for k, v in checkpoint.items() if k.startswith(prefix)}
|
| 460 |
+
if sub_state_dict:
|
| 461 |
+
sub_state_dict, adapted_keys = _adapt_vocab_sized_weights(module, sub_state_dict)
|
| 462 |
+
for key, source_shape, target_shape, rows in adapted_keys:
|
| 463 |
+
if not dist.is_initialized() or dist.get_rank() == 0:
|
| 464 |
+
print(
|
| 465 |
+
f"⚠️ adapted vocab-sized weight '{path}.{key}' from checkpoint shape "
|
| 466 |
+
f"{source_shape} to current shape {target_shape}; copied {rows} rows"
|
| 467 |
+
)
|
| 468 |
+
module.load_state_dict(sub_state_dict, strict=True)
|
| 469 |
+
if not dist.is_initialized() or dist.get_rank() == 0:
|
| 470 |
+
print(f"✅ parameters loaded to module '{path}'")
|
| 471 |
+
loaded_modules.append(path)
|
| 472 |
+
else:
|
| 473 |
+
print(f"⚠️ parameters not found in checkpoint '{path}'")
|
| 474 |
+
except AttributeError:
|
| 475 |
+
print(f"❌ cannot find module path: {path}")
|
| 476 |
+
else: # full load
|
| 477 |
+
try:
|
| 478 |
+
checkpoint, adapted_keys = _adapt_vocab_sized_weights(model, checkpoint)
|
| 479 |
+
for key, source_shape, target_shape, rows in adapted_keys:
|
| 480 |
+
if not dist.is_initialized() or dist.get_rank() == 0:
|
| 481 |
+
print(
|
| 482 |
+
f"⚠️ adapted vocab-sized weight '{key}' from checkpoint shape "
|
| 483 |
+
f"{source_shape} to current shape {target_shape}; copied {rows} rows"
|
| 484 |
+
)
|
| 485 |
+
model.load_state_dict(checkpoint, strict=False)
|
| 486 |
+
if not dist.is_initialized() or dist.get_rank() == 0:
|
| 487 |
+
print("✅ loaded <full_model> model parameters")
|
| 488 |
+
loaded_modules = ["<full_model>"]
|
| 489 |
+
except Exception as e:
|
| 490 |
+
raise RuntimeError(f"❌ loading full model failed: {e}")
|
| 491 |
+
return model
|
| 492 |
+
|
| 493 |
+
@staticmethod
|
| 494 |
+
def print_freeze_status(model):
|
| 495 |
+
"""
|
| 496 |
+
print the freezing status of each parameter in the model
|
| 497 |
+
:param model: PyTorch model instance
|
| 498 |
+
"""
|
| 499 |
+
for name, param in model.named_parameters():
|
| 500 |
+
status = "Frozen" if not param.requires_grad else "Trainable"
|
| 501 |
+
print(f"{name:60s} | {status}")
|
| 502 |
+
|
| 503 |
+
@staticmethod
|
| 504 |
+
def setup_distributed_training(accelerator, *components):
|
| 505 |
+
"""
|
| 506 |
+
use Accelerator to prepare distributed training components
|
| 507 |
+
:param accelerator: Accelerate instance
|
| 508 |
+
:param components: any number of components (such as model, optimizer, dataloader, etc.)
|
| 509 |
+
:return: prepared distributed components (in the same order as input)
|
| 510 |
+
"""
|
| 511 |
+
|
| 512 |
+
# use accelerator.prepare method to wrap components
|
| 513 |
+
prepared_components = accelerator.prepare(*components)
|
| 514 |
+
return prepared_components
|
| 515 |
+
|
| 516 |
+
@staticmethod
|
| 517 |
+
def euclidean_distance(predicted: np.ndarray, ground_truth: np.ndarray) -> float:
|
| 518 |
+
return np.linalg.norm(predicted - ground_truth)
|
| 519 |
+
|
| 520 |
+
@staticmethod
|
| 521 |
+
def _reset_dataloader(dataloader, epoch_counter):
|
| 522 |
+
"""safe reset dataloader iterator"""
|
| 523 |
+
# 1. update epoch counter
|
| 524 |
+
epoch_counter += 1
|
| 525 |
+
|
| 526 |
+
dataset = getattr(dataloader, "dataset", None)
|
| 527 |
+
if dataset is not None and callable(getattr(dataset, "set_epoch", None)):
|
| 528 |
+
dataset.set_epoch(epoch_counter)
|
| 529 |
+
|
| 530 |
+
# 2. set new epoch (distributed core)
|
| 531 |
+
if hasattr(dataloader, "sampler") and callable(getattr(dataloader.sampler, "set_epoch", None)):
|
| 532 |
+
dataloader.sampler.set_epoch(epoch_counter)
|
| 533 |
+
|
| 534 |
+
# 3. create new iterator
|
| 535 |
+
return iter(dataloader), epoch_counter
|
| 536 |
+
|
| 537 |
+
@staticmethod
|
| 538 |
+
def compute_grad_angle_with_stats(grads_a: list[torch.Tensor], grads_v: list[torch.Tensor]) -> Tuple[float, float]:
|
| 539 |
+
"""
|
| 540 |
+
compute the cosine angle between two groups of gradient vectors (degrees), and calculate the average angle and variance.
|
| 541 |
+
grads_a, grads_v: gradient Tensor list corresponding to the same parameter list interface_params
|
| 542 |
+
return:
|
| 543 |
+
mean_angle_deg: average angle (degrees)
|
| 544 |
+
angle_variance: angle variance
|
| 545 |
+
"""
|
| 546 |
+
angle_degs = []
|
| 547 |
+
|
| 548 |
+
# compute the cosine angle between each gradient block grads_a[0].shape = 1280, 3, 14, 14
|
| 549 |
+
# grads_1 = grads_a[0][0] # [3, 14, 14]
|
| 550 |
+
# grads_2 = grads_v[0][0]
|
| 551 |
+
# grads_a = grads_1.view(-1, 3) # reshape to [196, 3]
|
| 552 |
+
# grads_v = grads_2.view(-1, 3)
|
| 553 |
+
|
| 554 |
+
# lang linear
|
| 555 |
+
# reshape to 14*14, 3
|
| 556 |
+
# layer
|
| 557 |
+
grads_action = grads_a[0] # [2048, 11008]
|
| 558 |
+
grads_action = grads_action[
|
| 559 |
+
:32, :7
|
| 560 |
+
] # only take the first 7 elements, avoid cosim failure in high-dimensional space
|
| 561 |
+
grads_vl = grads_v[0] # [2048, 11008]
|
| 562 |
+
grads_vl = grads_vl[
|
| 563 |
+
:32, :7
|
| 564 |
+
] # only take the first 32 elements, 7 dimensions, avoid cosim failure in high-dimensional space
|
| 565 |
+
for g_a, g_v in zip(grads_action, grads_vl):
|
| 566 |
+
dot = torch.sum(g_a * g_v)
|
| 567 |
+
norm_a_sq = torch.sum(g_a * g_a)
|
| 568 |
+
norm_v_sq = torch.sum(g_v * g_v)
|
| 569 |
+
|
| 570 |
+
# avoid division by zero
|
| 571 |
+
norm_a = torch.sqrt(norm_a_sq + 1e-16)
|
| 572 |
+
norm_v = torch.sqrt(norm_v_sq + 1e-16)
|
| 573 |
+
|
| 574 |
+
cos_sim = (dot / (norm_a * norm_v)).clamp(-1.0, 1.0)
|
| 575 |
+
angle_rad = torch.acos(cos_sim)
|
| 576 |
+
angle_deg = angle_rad * (180.0 / torch.pi)
|
| 577 |
+
|
| 578 |
+
angle_degs.append(angle_deg.item())
|
| 579 |
+
|
| 580 |
+
# compute the average angle and variance
|
| 581 |
+
angle_degs_tensor = torch.tensor(angle_degs)
|
| 582 |
+
mean_angle_deg = torch.mean(angle_degs_tensor).item()
|
| 583 |
+
angle_variance = torch.sqrt(torch.var(angle_degs_tensor)).item()
|
| 584 |
+
# accelerator.wait_for_everyone()
|
| 585 |
+
return mean_angle_deg, angle_variance
|
| 586 |
+
|
| 587 |
+
@staticmethod
|
| 588 |
+
def pcgrad_project(grads_a: list[torch.Tensor], grads_v: list[torch.Tensor]) -> list[torch.Tensor]:
|
| 589 |
+
"""
|
| 590 |
+
apply PCGrad projection to the second group of gradients grads_v, suppress negative transfer between grads_a and grads_v
|
| 591 |
+
if the dot product of two groups of gradients < 0, then:
|
| 592 |
+
grads_v <- grads_v - (dot / ||grads_a||^2) * grads_a
|
| 593 |
+
return the new grads_v list
|
| 594 |
+
"""
|
| 595 |
+
# first compute dot and ||grads_a||^2
|
| 596 |
+
dot, norm_a_sq = 0.0, 0.0
|
| 597 |
+
for g_a, g_v in zip(grads_a, grads_v):
|
| 598 |
+
dot += torch.sum(g_a * g_v)
|
| 599 |
+
norm_a_sq += torch.sum(g_a * g_a)
|
| 600 |
+
|
| 601 |
+
if dot < 0:
|
| 602 |
+
coeff = dot / (norm_a_sq + 1e-6)
|
| 603 |
+
# projection
|
| 604 |
+
grads_v = [g_v - coeff * g_a for g_a, g_v in zip(grads_a, grads_v)]
|
| 605 |
+
|
| 606 |
+
return grads_v
|
| 607 |
+
|
| 608 |
+
@staticmethod
|
| 609 |
+
def eval_qwenpi(qwenpi, dataloader, num_batches=20):
|
| 610 |
+
"""
|
| 611 |
+
evaluate QwenQFormerDiT model, compute IoU and action distance.
|
| 612 |
+
|
| 613 |
+
Args:
|
| 614 |
+
qwenpi: QwenQFormerDiT model instance.
|
| 615 |
+
dataloader: data loader.
|
| 616 |
+
num_batches: number of batches to evaluate.
|
| 617 |
+
|
| 618 |
+
Returns:
|
| 619 |
+
dict: contains IoU and action distance evaluation results.
|
| 620 |
+
"""
|
| 621 |
+
iou_scores = []
|
| 622 |
+
action_distances = []
|
| 623 |
+
count = 0
|
| 624 |
+
|
| 625 |
+
dataset_iter = iter(dataloader)
|
| 626 |
+
while count < num_batches:
|
| 627 |
+
try:
|
| 628 |
+
batch_samples = next(dataset_iter)
|
| 629 |
+
count += 1
|
| 630 |
+
except StopIteration:
|
| 631 |
+
break
|
| 632 |
+
|
| 633 |
+
# extract data
|
| 634 |
+
images = [example["image"] for example in batch_samples]
|
| 635 |
+
instructions = [example["lang"] for example in batch_samples]
|
| 636 |
+
actions = [example["action"] for example in batch_samples]
|
| 637 |
+
solutions = [example["solution"] for example in batch_samples]
|
| 638 |
+
|
| 639 |
+
# model prediction
|
| 640 |
+
predicted_solutions, normalized_actions = qwenpi.predict_action_withCoT(
|
| 641 |
+
images=images, instructions=instructions, use_ddim=False, num_ddim_steps=20
|
| 642 |
+
)
|
| 643 |
+
|
| 644 |
+
# extract and convert predicted results
|
| 645 |
+
parsed_solutions = []
|
| 646 |
+
for solution in predicted_solutions:
|
| 647 |
+
parsed_solution = TrainerUtils.extract_json_from_string(solution)
|
| 648 |
+
parsed_solutions.append(parsed_solution)
|
| 649 |
+
|
| 650 |
+
# compute IoU
|
| 651 |
+
for pred_dict, gt_dict in zip(parsed_solutions, solutions):
|
| 652 |
+
pred_pick_bbox = torch.tensor(pred_dict["pick"]["bbox_2d"], dtype=torch.float32).unsqueeze(0)
|
| 653 |
+
gt_pick_bbox = torch.tensor(gt_dict["pick"]["bbox_2d"], dtype=torch.float32).unsqueeze(0)
|
| 654 |
+
pred_place_bbox = torch.tensor(pred_dict["place"]["bbox_2d"], dtype=torch.float32).unsqueeze(0)
|
| 655 |
+
gt_place_bbox = torch.tensor(gt_dict["place"]["bbox_2d"], dtype=torch.float32).unsqueeze(0)
|
| 656 |
+
|
| 657 |
+
pick_iou = box_iou(pred_pick_bbox, gt_pick_bbox).item()
|
| 658 |
+
place_iou = box_iou(pred_place_bbox, gt_place_bbox).item()
|
| 659 |
+
|
| 660 |
+
iou_scores.append({"pick_iou": pick_iou, "place_iou": place_iou})
|
| 661 |
+
|
| 662 |
+
# compute action distance
|
| 663 |
+
actions = np.array(actions) # convert to numpy array
|
| 664 |
+
num_pots = np.prod(actions.shape) # B*len*dim
|
| 665 |
+
action_distance = TrainerUtils.euclidean_distance(normalized_actions, actions)
|
| 666 |
+
average_action_distance = action_distance / num_pots
|
| 667 |
+
action_distances.append(average_action_distance)
|
| 668 |
+
|
| 669 |
+
# summarize results
|
| 670 |
+
avg_action_distance = np.mean(action_distances)
|
| 671 |
+
return {"iou_scores": iou_scores, "average_action_distance": avg_action_distance}
|
| 672 |
+
|
| 673 |
+
@staticmethod
|
| 674 |
+
def extract_json_from_string(input_string):
|
| 675 |
+
"""
|
| 676 |
+
extract valid JSON part from string and convert to dictionary.
|
| 677 |
+
|
| 678 |
+
Args:
|
| 679 |
+
input_string (str): string containing extra characters.
|
| 680 |
+
|
| 681 |
+
Returns:
|
| 682 |
+
dict: dictionary extracted and parsed.
|
| 683 |
+
"""
|
| 684 |
+
json_match = re.search(r"{.*}", input_string, re.DOTALL)
|
| 685 |
+
if json_match:
|
| 686 |
+
json_str = json_match.group(0)
|
| 687 |
+
try:
|
| 688 |
+
return json.loads(json_str)
|
| 689 |
+
except json.JSONDecodeError as e:
|
| 690 |
+
print(f"JSON decode failed: {e}")
|
| 691 |
+
return None
|
| 692 |
+
else:
|
| 693 |
+
print("No valid JSON part found")
|
| 694 |
+
return None
|
| 695 |
+
|
| 696 |
+
def _get_latest_checkpoint(self, checkpoint_dir):
|
| 697 |
+
"""Find the latest checkpoint in the directory based on step number."""
|
| 698 |
+
if not os.path.exists(checkpoint_dir):
|
| 699 |
+
self.accelerator.print(f"No checkpoint directory found at {checkpoint_dir}")
|
| 700 |
+
return None, 0
|
| 701 |
+
|
| 702 |
+
checkpoints = []
|
| 703 |
+
for name in os.listdir(checkpoint_dir):
|
| 704 |
+
path = os.path.join(checkpoint_dir, name)
|
| 705 |
+
state_match = re.match(r"steps_(\d+)_state$", name)
|
| 706 |
+
if state_match and os.path.isdir(path):
|
| 707 |
+
checkpoints.append((name, int(state_match.group(1)), 1))
|
| 708 |
+
continue
|
| 709 |
+
file_match = re.match(r"steps_(\d+)_(?:pytorch_model\.pt|model\.safetensors)$", name)
|
| 710 |
+
if file_match and os.path.isfile(path):
|
| 711 |
+
checkpoints.append((name, int(file_match.group(1)), 0))
|
| 712 |
+
|
| 713 |
+
if not checkpoints:
|
| 714 |
+
self.accelerator.print(f"No checkpoints found in {checkpoint_dir}")
|
| 715 |
+
return None, 0
|
| 716 |
+
|
| 717 |
+
# Sort by step number, preferring full training-state directories over
|
| 718 |
+
# model-only files when both exist for the same step.
|
| 719 |
+
checkpoints.sort(key=lambda x: (x[1], x[2]))
|
| 720 |
+
latest_checkpoint, completed_steps, _ = checkpoints[-1]
|
| 721 |
+
|
| 722 |
+
latest_checkpoint_path = os.path.join(checkpoint_dir, latest_checkpoint)
|
| 723 |
+
self.accelerator.print(f"Latest checkpoint found: {latest_checkpoint_path}")
|
| 724 |
+
return latest_checkpoint_path, completed_steps
|
| 725 |
+
|
| 726 |
+
import os
|
| 727 |
+
|
| 728 |
+
|
| 729 |
+
def is_main_process():
|
| 730 |
+
rank = int(os.environ.get("RANK", 0)) # if RANK is not set, default to 0
|
| 731 |
+
return rank == 0
|
| 732 |
+
|
| 733 |
+
|
| 734 |
+
def _is_safetensors_path(path):
|
| 735 |
+
"""Check if a path refers to a safetensors file."""
|
| 736 |
+
return str(path).endswith(".safetensors")
|
latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/training-code/worker_context.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import multiprocessing.context
|
| 2 |
+
import os
|
| 3 |
+
from typing import Any
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
_DISTRIBUTED_ENV_KEYS = (
|
| 7 |
+
"LOCAL_RANK",
|
| 8 |
+
"RANK",
|
| 9 |
+
"WORLD_SIZE",
|
| 10 |
+
"MASTER_ADDR",
|
| 11 |
+
"MASTER_PORT",
|
| 12 |
+
"GROUP_RANK",
|
| 13 |
+
"LOCAL_WORLD_SIZE",
|
| 14 |
+
"ROLE_RANK",
|
| 15 |
+
"ROLE_WORLD_SIZE",
|
| 16 |
+
"TORCHELASTIC_RUN_ID",
|
| 17 |
+
"TORCHELASTIC_RESTART_COUNT",
|
| 18 |
+
"TORCHELASTIC_MAX_RESTARTS",
|
| 19 |
+
)
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
class _CpuOnlyWorkerSpawnProcess(multiprocessing.context.SpawnProcess):
|
| 23 |
+
def start(self):
|
| 24 |
+
had_cuda_visible_devices = "CUDA_VISIBLE_DEVICES" in os.environ
|
| 25 |
+
if had_cuda_visible_devices:
|
| 26 |
+
cuda_visible_devices = os.environ["CUDA_VISIBLE_DEVICES"]
|
| 27 |
+
distributed_env = {key: os.environ[key] for key in _DISTRIBUTED_ENV_KEYS if key in os.environ}
|
| 28 |
+
|
| 29 |
+
os.environ["CUDA_VISIBLE_DEVICES"] = ""
|
| 30 |
+
for key in distributed_env:
|
| 31 |
+
del os.environ[key]
|
| 32 |
+
try:
|
| 33 |
+
return super().start()
|
| 34 |
+
finally:
|
| 35 |
+
if had_cuda_visible_devices:
|
| 36 |
+
os.environ["CUDA_VISIBLE_DEVICES"] = cuda_visible_devices
|
| 37 |
+
else:
|
| 38 |
+
del os.environ["CUDA_VISIBLE_DEVICES"]
|
| 39 |
+
for key, value in distributed_env.items():
|
| 40 |
+
os.environ[key] = value
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
class CpuOnlyWorkerSpawnContext(multiprocessing.context.SpawnContext):
|
| 44 |
+
Process = _CpuOnlyWorkerSpawnProcess
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
CPU_ONLY_WORKER_CONTEXT = CpuOnlyWorkerSpawnContext()
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def _as_bool(value: Any, default: bool = False) -> bool:
|
| 51 |
+
if value is None:
|
| 52 |
+
return default
|
| 53 |
+
if isinstance(value, bool):
|
| 54 |
+
return value
|
| 55 |
+
if isinstance(value, str):
|
| 56 |
+
return value.strip().lower() not in {"false", "0", "no", "off"}
|
| 57 |
+
return bool(value)
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
def build_cpu_only_dataloader_kwargs(
|
| 61 |
+
num_workers: int,
|
| 62 |
+
*,
|
| 63 |
+
pin_memory: Any = False,
|
| 64 |
+
persistent_workers: Any = False,
|
| 65 |
+
prefetch_factor: Any = None,
|
| 66 |
+
) -> dict[str, Any]:
|
| 67 |
+
dataloader_kwargs: dict[str, Any] = {
|
| 68 |
+
"pin_memory": _as_bool(pin_memory),
|
| 69 |
+
}
|
| 70 |
+
if int(num_workers) <= 0:
|
| 71 |
+
return dataloader_kwargs
|
| 72 |
+
|
| 73 |
+
dataloader_kwargs["multiprocessing_context"] = CPU_ONLY_WORKER_CONTEXT
|
| 74 |
+
dataloader_kwargs["persistent_workers"] = _as_bool(persistent_workers)
|
| 75 |
+
if prefetch_factor is not None:
|
| 76 |
+
dataloader_kwargs["prefetch_factor"] = int(prefetch_factor)
|
| 77 |
+
return dataloader_kwargs
|
latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/training_code_snapshot.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"repo": "c3c6a39365a151e9b7a5e215452fd64e957c2b29",
|
| 3 |
+
"starvla": "ccca13c5177fe3d3c884b6e2de4965d916016649",
|
| 4 |
+
"runtime_fixes": [
|
| 5 |
+
"mean-profile-preparation.patch",
|
| 6 |
+
"gym-language-contract.patch",
|
| 7 |
+
"loader-spawn-cache.patch",
|
| 8 |
+
"loader-spawn-test.patch"
|
| 9 |
+
],
|
| 10 |
+
"pytorch3d": {
|
| 11 |
+
"revision": "33824be3cbc87a7dd1db0f6a9a9de9ac81b2d0ba",
|
| 12 |
+
"build": "transforms-only, no native render extension; QwenOFT uses transforms only"
|
| 13 |
+
},
|
| 14 |
+
"decord": {
|
| 15 |
+
"version": "0.6.0",
|
| 16 |
+
"build": "official source CPU decoder CP310",
|
| 17 |
+
"wheel_sha256": "e193b356b1e984b4eff08d23b62e482c2c9e5037a6efdc0b1af47079ae2e4c47"
|
| 18 |
+
}
|
| 19 |
+
}
|
latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/training_results.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"training_complete": true,
|
| 3 |
+
"optimizer_updates": 5000,
|
| 4 |
+
"selection": "final_5000",
|
| 5 |
+
"train_loss": 0.028774918988347054,
|
| 6 |
+
"validation_action_loss": 0.034965552389621735,
|
| 7 |
+
"validation_samples": 6400,
|
| 8 |
+
"final_backbone_lr": 1e-07,
|
| 9 |
+
"final_action_lr": 3.3333333333333333e-06,
|
| 10 |
+
"closed_loop_evaluation": "pending until all four VLA trainings complete",
|
| 11 |
+
"tensor_keys": 732,
|
| 12 |
+
"serialized_tensor_elements_including_tied_aliases": 4892433410,
|
| 13 |
+
"weights_only_load": "passed",
|
| 14 |
+
"all_tensors_finite": true
|
| 15 |
+
}
|