dongqianyu99 commited on
Commit
e1fd56d
·
verified ·
1 Parent(s): ed07a22

Publish completed Flappy QwenOFT H1 mean-profile 5000-update Blackwell run

Browse files
Files changed (26) hide show
  1. latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/README.md +12 -0
  2. latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/checkpoints/model.pt +3 -0
  3. latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/collection_quality_report.json +18 -0
  4. latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/config.full.yaml +271 -0
  5. latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/config.yaml +45 -0
  6. latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/dataset_statistics.json +194 -0
  7. latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/dataset_statistics_eval.json +194 -0
  8. latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/files.sha256.json +102 -0
  9. latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/latency_prompt_map.json +7 -0
  10. latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/launch_audit.json +62 -0
  11. latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/manifest.json +111 -0
  12. latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/provenance.json +93 -0
  13. latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/summary.jsonl +10 -0
  14. latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/task_contract.json +43 -0
  15. latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/training-code/QWen3.py +444 -0
  16. latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/training-code/QwenOFT.py +1244 -0
  17. latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/training-code/datasets.py +0 -0
  18. latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/training-code/gym-language-contract.patch +17 -0
  19. latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/training-code/loader-spawn-cache.patch +18 -0
  20. latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/training-code/loader-spawn-test.patch +31 -0
  21. latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/training-code/mean-profile-preparation.patch +147 -0
  22. latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/training-code/train_starvla.py +0 -0
  23. latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/training-code/trainer_tools.py +736 -0
  24. latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/training-code/worker_context.py +77 -0
  25. latency-aware/flappy/vla/starvla-qwenoft-h1/flappy-qwenoft-mean-3090-s42/training_code_snapshot.json +19 -0
  26. 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
+ }