dongqianyu99 commited on
Commit
f7ef1d6
·
verified ·
1 Parent(s): dae7306

Publish completed intercept QwenOFT H1 mean-profile 5000-update run

Browse files
Files changed (27) hide show
  1. latency-aware/mikasa-intercept-grab-fast/vla/starvla-qwenoft-h1/intercept-qwenoft-mean-3090-h1-s0/README.md +8 -0
  2. latency-aware/mikasa-intercept-grab-fast/vla/starvla-qwenoft-h1/intercept-qwenoft-mean-3090-h1-s0/checkpoints/model.pt +3 -0
  3. latency-aware/mikasa-intercept-grab-fast/vla/starvla-qwenoft-h1/intercept-qwenoft-mean-3090-h1-s0/collection_quality_report.json +306 -0
  4. latency-aware/mikasa-intercept-grab-fast/vla/starvla-qwenoft-h1/intercept-qwenoft-mean-3090-h1-s0/config.full.yaml +102 -0
  5. latency-aware/mikasa-intercept-grab-fast/vla/starvla-qwenoft-h1/intercept-qwenoft-mean-3090-h1-s0/config.yaml +41 -0
  6. latency-aware/mikasa-intercept-grab-fast/vla/starvla-qwenoft-h1/intercept-qwenoft-mean-3090-h1-s0/dataset_statistics.json +127 -0
  7. latency-aware/mikasa-intercept-grab-fast/vla/starvla-qwenoft-h1/intercept-qwenoft-mean-3090-h1-s0/dataset_statistics_eval.json +127 -0
  8. latency-aware/mikasa-intercept-grab-fast/vla/starvla-qwenoft-h1/intercept-qwenoft-mean-3090-h1-s0/files.sha256.json +106 -0
  9. latency-aware/mikasa-intercept-grab-fast/vla/starvla-qwenoft-h1/intercept-qwenoft-mean-3090-h1-s0/launch_audit.json +29 -0
  10. latency-aware/mikasa-intercept-grab-fast/vla/starvla-qwenoft-h1/intercept-qwenoft-mean-3090-h1-s0/manifest.json +10 -0
  11. latency-aware/mikasa-intercept-grab-fast/vla/starvla-qwenoft-h1/intercept-qwenoft-mean-3090-h1-s0/profile.source.json +66 -0
  12. latency-aware/mikasa-intercept-grab-fast/vla/starvla-qwenoft-h1/intercept-qwenoft-mean-3090-h1-s0/provenance.json +415 -0
  13. latency-aware/mikasa-intercept-grab-fast/vla/starvla-qwenoft-h1/intercept-qwenoft-mean-3090-h1-s0/startup_verified.json +56 -0
  14. latency-aware/mikasa-intercept-grab-fast/vla/starvla-qwenoft-h1/intercept-qwenoft-mean-3090-h1-s0/summary.jsonl +10 -0
  15. latency-aware/mikasa-intercept-grab-fast/vla/starvla-qwenoft-h1/intercept-qwenoft-mean-3090-h1-s0/task_contract.json +131 -0
  16. latency-aware/mikasa-intercept-grab-fast/vla/starvla-qwenoft-h1/intercept-qwenoft-mean-3090-h1-s0/training-code/QWen3.py +444 -0
  17. latency-aware/mikasa-intercept-grab-fast/vla/starvla-qwenoft-h1/intercept-qwenoft-mean-3090-h1-s0/training-code/QwenOFT.py +1244 -0
  18. latency-aware/mikasa-intercept-grab-fast/vla/starvla-qwenoft-h1/intercept-qwenoft-mean-3090-h1-s0/training-code/datasets.py +0 -0
  19. latency-aware/mikasa-intercept-grab-fast/vla/starvla-qwenoft-h1/intercept-qwenoft-mean-3090-h1-s0/training-code/doom-mean-reset.patch +192 -0
  20. latency-aware/mikasa-intercept-grab-fast/vla/starvla-qwenoft-h1/intercept-qwenoft-mean-3090-h1-s0/training-code/gym-language-contract.patch +17 -0
  21. latency-aware/mikasa-intercept-grab-fast/vla/starvla-qwenoft-h1/intercept-qwenoft-mean-3090-h1-s0/training-code/loader-spawn-cache.patch +18 -0
  22. latency-aware/mikasa-intercept-grab-fast/vla/starvla-qwenoft-h1/intercept-qwenoft-mean-3090-h1-s0/training-code/loader-spawn-test.patch +31 -0
  23. latency-aware/mikasa-intercept-grab-fast/vla/starvla-qwenoft-h1/intercept-qwenoft-mean-3090-h1-s0/training-code/mean-profile-preparation.patch +147 -0
  24. latency-aware/mikasa-intercept-grab-fast/vla/starvla-qwenoft-h1/intercept-qwenoft-mean-3090-h1-s0/training-code/train_starvla.py +0 -0
  25. latency-aware/mikasa-intercept-grab-fast/vla/starvla-qwenoft-h1/intercept-qwenoft-mean-3090-h1-s0/training-code/trainer_tools.py +736 -0
  26. latency-aware/mikasa-intercept-grab-fast/vla/starvla-qwenoft-h1/intercept-qwenoft-mean-3090-h1-s0/training-code/worker_context.py +77 -0
  27. latency-aware/mikasa-intercept-grab-fast/vla/starvla-qwenoft-h1/intercept-qwenoft-mean-3090-h1-s0/training_results.json +20 -0
latency-aware/mikasa-intercept-grab-fast/vla/starvla-qwenoft-h1/intercept-qwenoft-mean-3090-h1-s0/README.md ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ # intercept QwenOFT H1 mean-profile VLA
2
+
3
+ Training complete:5000 optimizer updates, seed0, globalbatch128, microbatch64 perGPU, accumulation1, gradientcheckpointingTrue, BF16/ZeRO2. Hardware:2 RTX PRO6000 Blackwell GPUs. Fixed mean99.05021289731565ms uses originalRTX3090 profile; all actual science/runtime settings and user-authorized throughput changes are recorded in config.full.yaml/launch_audit.json. Horizon1, native state/action dimensions7/7.
4
+
5
+ Puretensor weights checkCPUweights_only/allfinite passed. Portable inferenceconfig, normalization/statistics, taskcontract, sourcecode/profile/data provenance included. Full optimizer recovery remains local. Finalofflinevalidation loss0.03070492297410965; this is not closed-loop task-performance acceptance. Latency-bench evaluation pending until all four models finish.
6
+
7
+ Dataset:https://huggingface.co/datasets/latency-sensitive-bench/Standard-Pipeline/tree/bba7d56c0ad5bb2b6d6ed07adb70b4f1afd5d25a/mikasa_intercept_grab_fast/profile_latency/QwenOFT/mean_rtx3090_h1_s123_275ep_v1
8
+ Training:https://wandb.ai/dongqianyu99-zhejiang-university/starvla_tasks/runs/mean3090-intercept-vla-blackwell-20260930
latency-aware/mikasa-intercept-grab-fast/vla/starvla-qwenoft-h1/intercept-qwenoft-mean-3090-h1-s0/checkpoints/model.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7bea6746bf7973ff7d4e4e8a457a3bfbc0ce981c80fe5f7afadca651ec5ca85e
3
+ size 9785101729
latency-aware/mikasa-intercept-grab-fast/vla/starvla-qwenoft-h1/intercept-qwenoft-mean-3090-h1-s0/collection_quality_report.json ADDED
@@ -0,0 +1,306 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "episodes": 275,
3
+ "unique_seeds": 275,
4
+ "rows": 12211,
5
+ "length_min": 37,
6
+ "length_max": 55,
7
+ "all_success_once": true,
8
+ "state_dim": 7,
9
+ "action_dim": 7,
10
+ "rgb_shape": [
11
+ 128,
12
+ 128,
13
+ 6
14
+ ],
15
+ "finite_numeric": true,
16
+ "latency_raw_frames": 2,
17
+ "applied_command_matches_source_issued_action": true,
18
+ "action_horizon": 1,
19
+ "dagger": false,
20
+ "raw_bytes": 1204453838,
21
+ "episode_seeds": [
22
+ 0,
23
+ 1,
24
+ 10,
25
+ 180,
26
+ 181,
27
+ 182,
28
+ 183,
29
+ 184,
30
+ 185,
31
+ 186,
32
+ 187,
33
+ 188,
34
+ 189,
35
+ 11,
36
+ 190,
37
+ 191,
38
+ 192,
39
+ 193,
40
+ 194,
41
+ 195,
42
+ 196,
43
+ 197,
44
+ 198,
45
+ 199,
46
+ 12,
47
+ 20,
48
+ 21,
49
+ 22,
50
+ 23,
51
+ 24,
52
+ 25,
53
+ 26,
54
+ 27,
55
+ 28,
56
+ 29,
57
+ 13,
58
+ 200,
59
+ 201,
60
+ 202,
61
+ 203,
62
+ 204,
63
+ 205,
64
+ 206,
65
+ 207,
66
+ 208,
67
+ 209,
68
+ 14,
69
+ 210,
70
+ 211,
71
+ 212,
72
+ 213,
73
+ 214,
74
+ 215,
75
+ 216,
76
+ 217,
77
+ 218,
78
+ 219,
79
+ 15,
80
+ 220,
81
+ 221,
82
+ 222,
83
+ 223,
84
+ 224,
85
+ 225,
86
+ 226,
87
+ 227,
88
+ 228,
89
+ 229,
90
+ 16,
91
+ 230,
92
+ 231,
93
+ 232,
94
+ 233,
95
+ 234,
96
+ 235,
97
+ 236,
98
+ 237,
99
+ 238,
100
+ 239,
101
+ 17,
102
+ 240,
103
+ 241,
104
+ 242,
105
+ 243,
106
+ 244,
107
+ 245,
108
+ 246,
109
+ 247,
110
+ 248,
111
+ 249,
112
+ 18,
113
+ 250,
114
+ 251,
115
+ 252,
116
+ 253,
117
+ 254,
118
+ 255,
119
+ 256,
120
+ 257,
121
+ 258,
122
+ 259,
123
+ 19,
124
+ 260,
125
+ 261,
126
+ 262,
127
+ 263,
128
+ 264,
129
+ 265,
130
+ 266,
131
+ 267,
132
+ 268,
133
+ 269,
134
+ 2,
135
+ 100,
136
+ 270,
137
+ 271,
138
+ 272,
139
+ 273,
140
+ 274,
141
+ 30,
142
+ 31,
143
+ 32,
144
+ 33,
145
+ 34,
146
+ 101,
147
+ 35,
148
+ 36,
149
+ 37,
150
+ 38,
151
+ 39,
152
+ 40,
153
+ 41,
154
+ 42,
155
+ 43,
156
+ 44,
157
+ 102,
158
+ 45,
159
+ 46,
160
+ 47,
161
+ 48,
162
+ 49,
163
+ 50,
164
+ 51,
165
+ 52,
166
+ 53,
167
+ 54,
168
+ 103,
169
+ 55,
170
+ 56,
171
+ 57,
172
+ 58,
173
+ 59,
174
+ 60,
175
+ 61,
176
+ 62,
177
+ 63,
178
+ 64,
179
+ 104,
180
+ 65,
181
+ 66,
182
+ 67,
183
+ 68,
184
+ 69,
185
+ 70,
186
+ 71,
187
+ 72,
188
+ 73,
189
+ 74,
190
+ 105,
191
+ 75,
192
+ 76,
193
+ 77,
194
+ 78,
195
+ 79,
196
+ 80,
197
+ 81,
198
+ 82,
199
+ 83,
200
+ 84,
201
+ 106,
202
+ 85,
203
+ 86,
204
+ 87,
205
+ 88,
206
+ 89,
207
+ 90,
208
+ 91,
209
+ 92,
210
+ 93,
211
+ 94,
212
+ 107,
213
+ 95,
214
+ 96,
215
+ 97,
216
+ 98,
217
+ 99,
218
+ 108,
219
+ 109,
220
+ 3,
221
+ 110,
222
+ 111,
223
+ 112,
224
+ 113,
225
+ 114,
226
+ 115,
227
+ 116,
228
+ 117,
229
+ 118,
230
+ 119,
231
+ 4,
232
+ 120,
233
+ 121,
234
+ 122,
235
+ 123,
236
+ 124,
237
+ 125,
238
+ 126,
239
+ 127,
240
+ 128,
241
+ 129,
242
+ 5,
243
+ 130,
244
+ 131,
245
+ 132,
246
+ 133,
247
+ 134,
248
+ 135,
249
+ 136,
250
+ 137,
251
+ 138,
252
+ 139,
253
+ 6,
254
+ 140,
255
+ 141,
256
+ 142,
257
+ 143,
258
+ 144,
259
+ 145,
260
+ 146,
261
+ 147,
262
+ 148,
263
+ 149,
264
+ 7,
265
+ 150,
266
+ 151,
267
+ 152,
268
+ 153,
269
+ 154,
270
+ 155,
271
+ 156,
272
+ 157,
273
+ 158,
274
+ 159,
275
+ 8,
276
+ 160,
277
+ 161,
278
+ 162,
279
+ 163,
280
+ 164,
281
+ 165,
282
+ 166,
283
+ 167,
284
+ 168,
285
+ 169,
286
+ 9,
287
+ 170,
288
+ 171,
289
+ 172,
290
+ 173,
291
+ 174,
292
+ 175,
293
+ 176,
294
+ 177,
295
+ 178,
296
+ 179
297
+ ],
298
+ "train_episodes": 250,
299
+ "val_episodes": 25,
300
+ "train_rows": 11119,
301
+ "val_rows": 1092,
302
+ "teacher_checkpoint": "/home/ubuntu/lzj/mean-profiling/intercept/teacher-publication/checkpoint.pt",
303
+ "teacher_sha256": "e47526f03fad3ed251dd78f3cacfba8aa8b60bd1578f40bd7a1393e17298e973",
304
+ "fixed_latency_ms": 99.05021289731565,
305
+ "conversion_complete": true
306
+ }
latency-aware/mikasa-intercept-grab-fast/vla/starvla-qwenoft-h1/intercept-qwenoft-mean-3090-h1-s0/config.full.yaml ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ run_id: intercept-qwenoft-mean-3090-h1-s0
2
+ run_root_dir: /home/ubuntu/lzj/mean-profiling/intercept/vla
3
+ seed: 0
4
+ wandb_name: intercept-qwenoft-mean-3090-h1-s0
5
+ wandb_project: starvla_tasks
6
+ wandb_entity: dongqianyu99-zhejiang-university
7
+ wandb_group: mean-profiling-3090-blackwell
8
+ wandb_tags:
9
+ - intercept
10
+ - qwenoft
11
+ - mean-profile
12
+ - h1
13
+ training_latency_condition: mean-profile
14
+ is_debug: false
15
+ version_id: '0.21'
16
+ framework:
17
+ name: QwenOFT
18
+ qwenvl:
19
+ base_vlm: /home/ubuntu/lzj/models/Qwen3-VL-4B-Instruct
20
+ attn_implementation: flash_attention_2
21
+ enable_gradient_checkpointing: true
22
+ action_model:
23
+ action_model_type: MLP
24
+ action_dim: 7
25
+ state_dim: 7
26
+ action_hidden_dim: 2560
27
+ future_action_window_size: 0
28
+ past_action_window_size: 0
29
+ loss_type: l1
30
+ state_encoding: continuous_projector
31
+ task_objective: null
32
+ action_env_dim: 7
33
+ action_horizon: 1
34
+ datasets:
35
+ vla_data:
36
+ dataset_py: latency_bench_tasks
37
+ lerobot_version: v3.0
38
+ include_state: true
39
+ task_contract_path: /home/ubuntu/lzj/mean-profiling/intercept/data/lerobot/task_contract.json
40
+ data_root_dir: /
41
+ data_mix: train
42
+ eval_data_mix: validation
43
+ custom_mixtures_path: /home/ubuntu/lzj/mean-profiling/intercept/data/mixture.json
44
+ sequential_step_sampling: true
45
+ per_device_batch_size: 64
46
+ pin_memory: true
47
+ persistent_workers: true
48
+ num_workers: 2
49
+ prefetch_factor: 4
50
+ obs_image_size:
51
+ - 224
52
+ - 224
53
+ video_backend: pyav
54
+ image_mode: single
55
+ stitch_grid:
56
+ - 1
57
+ - 2
58
+ num_obs_frames: 1
59
+ trainer:
60
+ max_train_steps: 5000
61
+ num_warmup_steps: 100
62
+ save_interval: 500
63
+ eval_interval: 5000
64
+ eval_num_batches: 1
65
+ logging_frequency: 5
66
+ learning_rate:
67
+ base: 3.0e-05
68
+ action_model: 0.0001
69
+ lr_scheduler_type: cosine_with_min_lr
70
+ scheduler_specific_kwargs:
71
+ min_lr: 1.0e-06
72
+ freeze_modules: ''
73
+ freeze_vit: false
74
+ freeze_tied_embedding: false
75
+ freeze_llm_layers: []
76
+ loss_scale:
77
+ vla: 1.0
78
+ gradient_clipping: 1.0
79
+ gradient_accumulation_steps: 1
80
+ pretrained_checkpoint: null
81
+ is_resume: false
82
+ distributed_backend: deepspeed
83
+ optimizer:
84
+ name: AdamW
85
+ betas:
86
+ - 0.9
87
+ - 0.95
88
+ eps: 1.0e-08
89
+ weight_decay: 1.0e-08
90
+ fused: true
91
+ profile_timing:
92
+ enabled: false
93
+ log_interval: 10
94
+ checkpoint:
95
+ save_best_model: false
96
+ save_final_model: true
97
+ save_pt_file: true
98
+ save_training_state: true
99
+ local:
100
+ keep_last_n: 1
101
+ config_yaml: /home/ubuntu/lzj/mean-profiling/intercept/vla.yaml
102
+ output_dir: /home/ubuntu/lzj/mean-profiling/intercept/vla/intercept-qwenoft-mean-3090-h1-s0
latency-aware/mikasa-intercept-grab-fast/vla/starvla-qwenoft-h1/intercept-qwenoft-mean-3090-h1-s0/config.yaml ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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: true
7
+ action_model:
8
+ action_model_type: MLP
9
+ action_dim: 7
10
+ state_dim: 7
11
+ action_hidden_dim: 2560
12
+ future_action_window_size: 0
13
+ past_action_window_size: 0
14
+ loss_type: l1
15
+ state_encoding: continuous_projector
16
+ task_objective: null
17
+ action_env_dim: 7
18
+ action_horizon: 1
19
+ version_id: '0.21'
20
+ datasets:
21
+ vla_data:
22
+ include_state: true
23
+ obs_image_size:
24
+ - 224
25
+ - 224
26
+ image_mode: single
27
+ stitch_grid:
28
+ - 1
29
+ - 2
30
+ num_obs_frames: 1
31
+ trainer:
32
+ pretrained_checkpoint: null
33
+ backbone:
34
+ repo_id: Qwen/Qwen3-VL-4B-Instruct
35
+ revision: ebb281ec70b05090aa6165b016eac8ec08e71b17
36
+ allow_patterns:
37
+ - '*.json'
38
+ - '*.safetensors'
39
+ - '*.txt'
40
+ - '*.jinja'
41
+ - '*.model'
latency-aware/mikasa-intercept-grab-fast/vla/starvla-qwenoft-h1/intercept-qwenoft-mean-3090-h1-s0/dataset_statistics.json ADDED
@@ -0,0 +1,127 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "new_embodiment": {
3
+ "action": {
4
+ "mean": [
5
+ -0.048697348684072495,
6
+ 0.24886493384838104,
7
+ -0.16479986906051636,
8
+ -0.032609131187200546,
9
+ -0.501691997051239,
10
+ 0.44703611731529236,
11
+ 0.6793776154518127
12
+ ],
13
+ "std": [
14
+ 0.1079770028591156,
15
+ 0.47203660011291504,
16
+ 0.14475314319133759,
17
+ 0.2849433720111847,
18
+ 0.318120539188385,
19
+ 0.35324087738990784,
20
+ 0.6349603533744812
21
+ ],
22
+ "max": [
23
+ 0.30179348587989807,
24
+ 0.7795099020004272,
25
+ 0.2688712179660797,
26
+ 0.8059309720993042,
27
+ 0.5333064198493958,
28
+ 1.0,
29
+ 1.0
30
+ ],
31
+ "min": [
32
+ -0.6572320461273193,
33
+ -1.0,
34
+ -0.8218780755996704,
35
+ -0.7686842083930969,
36
+ -0.9920897483825684,
37
+ -0.30364304780960083,
38
+ -1.0
39
+ ],
40
+ "q01": [
41
+ -0.4552440994977951,
42
+ -1.0,
43
+ -0.7419710528850555,
44
+ -0.475678391456604,
45
+ -0.9305388247966766,
46
+ -0.20442348539829253,
47
+ -0.8981475746631622
48
+ ],
49
+ "q99": [
50
+ 0.1125871029496192,
51
+ 0.7402709639072418,
52
+ 0.22590837419033025,
53
+ 0.7005072689056396,
54
+ 0.16810789704322815,
55
+ 1.0,
56
+ 1.0
57
+ ],
58
+ "mask": [
59
+ true,
60
+ true,
61
+ true,
62
+ true,
63
+ true,
64
+ true,
65
+ true
66
+ ]
67
+ },
68
+ "state": {
69
+ "mean": [
70
+ -0.14554812014102936,
71
+ 0.07433967292308807,
72
+ 0.1436367779970169,
73
+ 2.700871706008911,
74
+ 0.45681115984916687,
75
+ -0.48229172825813293,
76
+ 0.06972828507423401
77
+ ],
78
+ "std": [
79
+ 0.07678605616092682,
80
+ 0.21988309919834137,
81
+ 0.09388109296560287,
82
+ 1.109084963798523,
83
+ 0.3012843728065491,
84
+ 0.3006882667541504,
85
+ 0.0164483655244112
86
+ ],
87
+ "max": [
88
+ 0.014208495616912842,
89
+ 0.5909395217895508,
90
+ 0.3558541536331177,
91
+ 3.1415467262268066,
92
+ 0.9420600533485413,
93
+ 0.09770228713750839,
94
+ 0.07999999821186066
95
+ ],
96
+ "min": [
97
+ -0.37446925044059753,
98
+ -0.29720592498779297,
99
+ 0.016940969973802567,
100
+ -3.140481948852539,
101
+ -0.10707131028175354,
102
+ -1.2001688480377197,
103
+ 0.03238735347986221
104
+ ],
105
+ "q01": [
106
+ -0.3139808464050293,
107
+ -0.25229972660541533,
108
+ 0.0188405978679657,
109
+ -3.134360270500183,
110
+ -0.04521620273590088,
111
+ -1.0301372480392457,
112
+ 0.03585549935698509
113
+ ],
114
+ "q99": [
115
+ -0.013631248474121185,
116
+ 0.5275326526165007,
117
+ 0.33348673582077026,
118
+ 3.1367013454437256,
119
+ 0.8922865676879882,
120
+ 0.056985778063535636,
121
+ 0.07999999821186066
122
+ ]
123
+ },
124
+ "num_transitions": 11119,
125
+ "num_trajectories": 250
126
+ }
127
+ }
latency-aware/mikasa-intercept-grab-fast/vla/starvla-qwenoft-h1/intercept-qwenoft-mean-3090-h1-s0/dataset_statistics_eval.json ADDED
@@ -0,0 +1,127 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "new_embodiment": {
3
+ "action": {
4
+ "mean": [
5
+ -0.048697348684072495,
6
+ 0.24886493384838104,
7
+ -0.16479986906051636,
8
+ -0.032609131187200546,
9
+ -0.501691997051239,
10
+ 0.44703611731529236,
11
+ 0.6793776154518127
12
+ ],
13
+ "std": [
14
+ 0.1079770028591156,
15
+ 0.47203660011291504,
16
+ 0.14475314319133759,
17
+ 0.2849433720111847,
18
+ 0.318120539188385,
19
+ 0.35324087738990784,
20
+ 0.6349603533744812
21
+ ],
22
+ "max": [
23
+ 0.30179348587989807,
24
+ 0.7795099020004272,
25
+ 0.2688712179660797,
26
+ 0.8059309720993042,
27
+ 0.5333064198493958,
28
+ 1.0,
29
+ 1.0
30
+ ],
31
+ "min": [
32
+ -0.6572320461273193,
33
+ -1.0,
34
+ -0.8218780755996704,
35
+ -0.7686842083930969,
36
+ -0.9920897483825684,
37
+ -0.30364304780960083,
38
+ -1.0
39
+ ],
40
+ "q01": [
41
+ -0.4552440994977951,
42
+ -1.0,
43
+ -0.7419710528850555,
44
+ -0.475678391456604,
45
+ -0.9305388247966766,
46
+ -0.20442348539829253,
47
+ -0.8981475746631622
48
+ ],
49
+ "q99": [
50
+ 0.1125871029496192,
51
+ 0.7402709639072418,
52
+ 0.22590837419033025,
53
+ 0.7005072689056396,
54
+ 0.16810789704322815,
55
+ 1.0,
56
+ 1.0
57
+ ],
58
+ "mask": [
59
+ true,
60
+ true,
61
+ true,
62
+ true,
63
+ true,
64
+ true,
65
+ true
66
+ ]
67
+ },
68
+ "state": {
69
+ "mean": [
70
+ -0.14554812014102936,
71
+ 0.07433967292308807,
72
+ 0.1436367779970169,
73
+ 2.700871706008911,
74
+ 0.45681115984916687,
75
+ -0.48229172825813293,
76
+ 0.06972828507423401
77
+ ],
78
+ "std": [
79
+ 0.07678605616092682,
80
+ 0.21988309919834137,
81
+ 0.09388109296560287,
82
+ 1.109084963798523,
83
+ 0.3012843728065491,
84
+ 0.3006882667541504,
85
+ 0.0164483655244112
86
+ ],
87
+ "max": [
88
+ 0.014208495616912842,
89
+ 0.5909395217895508,
90
+ 0.3558541536331177,
91
+ 3.1415467262268066,
92
+ 0.9420600533485413,
93
+ 0.09770228713750839,
94
+ 0.07999999821186066
95
+ ],
96
+ "min": [
97
+ -0.37446925044059753,
98
+ -0.29720592498779297,
99
+ 0.016940969973802567,
100
+ -3.140481948852539,
101
+ -0.10707131028175354,
102
+ -1.2001688480377197,
103
+ 0.03238735347986221
104
+ ],
105
+ "q01": [
106
+ -0.3139808464050293,
107
+ -0.25229972660541533,
108
+ 0.0188405978679657,
109
+ -3.134360270500183,
110
+ -0.04521620273590088,
111
+ -1.0301372480392457,
112
+ 0.03585549935698509
113
+ ],
114
+ "q99": [
115
+ -0.013631248474121185,
116
+ 0.5275326526165007,
117
+ 0.33348673582077026,
118
+ 3.1367013454437256,
119
+ 0.8922865676879882,
120
+ 0.056985778063535636,
121
+ 0.07999999821186066
122
+ ]
123
+ },
124
+ "num_transitions": 1092,
125
+ "num_trajectories": 25
126
+ }
127
+ }
latency-aware/mikasa-intercept-grab-fast/vla/starvla-qwenoft-h1/intercept-qwenoft-mean-3090-h1-s0/files.sha256.json ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "README.md": {
3
+ "bytes": 1155,
4
+ "sha256": "c9ccbe7aa494be2270decacd8f7f3f56afbd0e5c57c58529b6e9c08d72fe5402"
5
+ },
6
+ "checkpoints/model.pt": {
7
+ "bytes": 9785101729,
8
+ "sha256": "7bea6746bf7973ff7d4e4e8a457a3bfbc0ce981c80fe5f7afadca651ec5ca85e"
9
+ },
10
+ "collection_quality_report.json": {
11
+ "bytes": 3134,
12
+ "sha256": "ce3effc9d3267edf410642d0ecd3b5b7790c072e1ff5d0f79eeab4ae883fe1a1"
13
+ },
14
+ "config.full.yaml": {
15
+ "bytes": 2574,
16
+ "sha256": "e9e1231302aa3ae47efd1673f356c0004daf815f47eb9c02ba62714d89b53f0b"
17
+ },
18
+ "config.yaml": {
19
+ "bytes": 871,
20
+ "sha256": "d0612a41944af616e4750bb1217353a90a562ac978571d9ebd663ec7caae61ed"
21
+ },
22
+ "dataset_statistics.json": {
23
+ "bytes": 2843,
24
+ "sha256": "7a5fde37c6a0b220321d0acdb73b8b48b7ffe9f6295b7a0bb0e0abcf4849b653"
25
+ },
26
+ "dataset_statistics_eval.json": {
27
+ "bytes": 2841,
28
+ "sha256": "fcbc27a8dc2d8f6f8b5069300bae4fbf71ddacea91c8b23551fa652b88d0801e"
29
+ },
30
+ "launch_audit.json": {
31
+ "bytes": 1062,
32
+ "sha256": "afcbecdd81e19e8bf4d3a6fa946e7b891f126cb60d0ea8e24f5badeab961f07c"
33
+ },
34
+ "manifest.json": {
35
+ "bytes": 221,
36
+ "sha256": "97b5bea337c766d9db49c8e3a01ad93de4721dbdf965acf31e7eba4948f24421"
37
+ },
38
+ "profile.source.json": {
39
+ "bytes": 2190,
40
+ "sha256": "31a9b15d6b04182439934f0b377cb3d09be16ce21f3cf95c1049918f8a5d4984"
41
+ },
42
+ "provenance.json": {
43
+ "bytes": 8324,
44
+ "sha256": "67f1a7c42dbd772f40995fc73454cf954dda78c2408741bc9274f5259766f7ce"
45
+ },
46
+ "startup_verified.json": {
47
+ "bytes": 1210,
48
+ "sha256": "6bd0871b87954dbf4dfba1778e84ba91361741c4e7d106e97299965d2c0ba1c0"
49
+ },
50
+ "summary.jsonl": {
51
+ "bytes": 159,
52
+ "sha256": "2905acb79c5fffb33fd4689d87af3bd05d74f4184b9d36ff3eae0c64deecd930"
53
+ },
54
+ "task_contract.json": {
55
+ "bytes": 3063,
56
+ "sha256": "b40d58b27dfa9431f86f83ea5d4f36bcd652b3e3517c044638282df0d1e86c6b"
57
+ },
58
+ "training-code/QWen3.py": {
59
+ "bytes": 17586,
60
+ "sha256": "5fe76fc98eaa84e215c3c25c11326815499d2a4b0683c6ab95382b881f535370"
61
+ },
62
+ "training-code/QwenOFT.py": {
63
+ "bytes": 62854,
64
+ "sha256": "acad1b0dcde6e9fed6e2912b6534db0f8e33a74ec7f6630bbf1b0161fc298b0d"
65
+ },
66
+ "training-code/datasets.py": {
67
+ "bytes": 154347,
68
+ "sha256": "33e22556471128a2bad18a1ef8b84c399847b4d98b2979d785d139de0cdf36df"
69
+ },
70
+ "training-code/doom-mean-reset.patch": {
71
+ "bytes": 9569,
72
+ "sha256": "4113989e16c547bd1f02f5f10f285c4d511be3a3c18d10a0aa3316bd8ff3175d"
73
+ },
74
+ "training-code/gym-language-contract.patch": {
75
+ "bytes": 688,
76
+ "sha256": "9f5dee2c5b52d5507aff27373a8cf6def4959361d2c5264d5079b416f28a4c49"
77
+ },
78
+ "training-code/loader-spawn-cache.patch": {
79
+ "bytes": 711,
80
+ "sha256": "24532ce84e5aa9aef381493fe699b4bb4987afd954422191ae852b0f5eb5b2cd"
81
+ },
82
+ "training-code/loader-spawn-test.patch": {
83
+ "bytes": 1500,
84
+ "sha256": "a36db15b13d68defeecb75ddc9efa1ac202d9aa0a40f66ba5baaee2dcaef21f1"
85
+ },
86
+ "training-code/mean-profile-preparation.patch": {
87
+ "bytes": 7053,
88
+ "sha256": "de5fe558376c98515a1cfb186ffdc09b618acc207eb8a5f667b0cbb303767c09"
89
+ },
90
+ "training-code/train_starvla.py": {
91
+ "bytes": 125159,
92
+ "sha256": "f872648f941cec272af724772e9d6fc3721e9ed6eaf0c8683ab285eb9396212e"
93
+ },
94
+ "training-code/trainer_tools.py": {
95
+ "bytes": 29144,
96
+ "sha256": "9a48bdfb99b405b03fececfc1e051d3ebb179eb423c15141f02b9be62e2e1791"
97
+ },
98
+ "training-code/worker_context.py": {
99
+ "bytes": 2279,
100
+ "sha256": "8493631104269f0630655ee2faae592a84ebfb3119aca142c8ac2701f5214464"
101
+ },
102
+ "training_results.json": {
103
+ "bytes": 610,
104
+ "sha256": "7165309b2a84a8fa8250cff1dc2ca1717837f114b5aac269ee9a7e3188182e17"
105
+ }
106
+ }
latency-aware/mikasa-intercept-grab-fast/vla/starvla-qwenoft-h1/intercept-qwenoft-mean-3090-h1-s0/launch_audit.json ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "physical_gpus": [
3
+ 2,
4
+ 3
5
+ ],
6
+ "seed": 0,
7
+ "horizon": 1,
8
+ "dagger_updates": 0,
9
+ "state_dim": 7,
10
+ "action_dim": 7,
11
+ "micro_per_gpu": 64,
12
+ "gradient_accumulation": 1,
13
+ "global_batch": 128,
14
+ "gradient_checkpointing": true,
15
+ "formal_budget": 5000,
16
+ "post_train_rollout_eval": false,
17
+ "init": "same pinnedQwen3-VL-4B backbone; scratchactionhead",
18
+ "user_authorization": "AfterDeadlycompletes startInterceptGPU23; no newtaskGPU01afterAnt",
19
+ "runtime_capacity_choice": "GCtrue64 methodpassedAnt/Deadly; Interceptmuststillcomplete20boundedthenverifyformalstartup",
20
+ "dataset_revision": "bba7d56c0ad5bb2b6d6ed07adb70b4f1afd5d25a",
21
+ "startup_verified": true,
22
+ "recovery_keep_last_n": 1,
23
+ "storage_capacity": {
24
+ "free_before_bytes": 266315395072,
25
+ "estimated_two_intercept_saves_plus_one_ant_transient_bytes": 218520000000,
26
+ "policy": "preserveonecompleteoptimizerrecovery;newsavebeforeoldprune;originaldata/artifactsnotdeleted"
27
+ },
28
+ "formal_startup_evidence": "/home/ubuntu/lzj/mean-profiling/intercept/vla.startup-verified.json"
29
+ }
latency-aware/mikasa-intercept-grab-fast/vla/starvla-qwenoft-h1/intercept-qwenoft-mean-3090-h1-s0/manifest.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "format": "latency_bench_starvla_h1_v1",
3
+ "task": "mikasa_intercept_grab_fast",
4
+ "robot_type": "latency_mikasa_h1",
5
+ "state_dim": 7,
6
+ "action_dim": 7,
7
+ "action_horizon": 1,
8
+ "vla_fps": 20.0,
9
+ "dagger": false
10
+ }
latency-aware/mikasa-intercept-grab-fast/vla/starvla-qwenoft-h1/intercept-qwenoft-mean-3090-h1-s0/profile.source.json ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "burst_model_path": "latency_burst_model.json",
3
+ "distribution_path": "latency_distribution.json",
4
+ "env_fps": 20,
5
+ "frame_ms": 50.0,
6
+ "gpu_class": "1x-rtx3090",
7
+ "instance_id": "instance_3a0d42681a03715c",
8
+ "latency_kind": "observation_to_action_latency",
9
+ "latency_method": "temporal",
10
+ "model_id": "qwenoft",
11
+ "n_admitted_observations": 7505,
12
+ "n_capacity_drops": 7495,
13
+ "n_observation_attempts": 15000,
14
+ "per_slot_summary": {
15
+ "0": {
16
+ "admitted_count": 7505,
17
+ "mean_observation_to_action_latency_ms": 99.05021289731565,
18
+ "mean_worker_service_time_ms": 98.55270653801072,
19
+ "p95_observation_to_action_latency_ms": 100.32732703685761,
20
+ "p95_worker_service_time_ms": 99.84762226343155,
21
+ "p99_worker_service_time_ms": 100.37557450771331
22
+ }
23
+ },
24
+ "provenance": {
25
+ "base_config": "configs/examples/mikasa/official_starvla_profile.yaml",
26
+ "checkpoint_kind": "best",
27
+ "model_artifact": {
28
+ "checkpoint": "checkpoints/model.pt",
29
+ "model_config": "config.yaml",
30
+ "path_in_repo": "zero-latency/mikasa-intercept-grab-fast/vla/starvla-qwenoft-h1/mikasa_intercept_grab_fast_qwenoft_formal_latest_r8gpu8_20260907T082154Z",
31
+ "repo_id": "latency-sensitive-bench/benchmark-models",
32
+ "source": "hf"
33
+ },
34
+ "session_ids": [
35
+ 0,
36
+ 1,
37
+ 2,
38
+ 3,
39
+ 4
40
+ ]
41
+ },
42
+ "sample_model_type": "hidden_regime",
43
+ "source_run_id": "20260909T044501695676Z",
44
+ "summary": {
45
+ "frame_ms": 50.0,
46
+ "max_ms": 101.19136601686478,
47
+ "mean_effective_frames": 1.981004257946313,
48
+ "mean_ms": 99.05021289731565,
49
+ "min_ms": 57.612886011600494,
50
+ "n_samples": 7505,
51
+ "p50_frames": 1.9978350806236267,
52
+ "p50_ms": 99.89175403118134,
53
+ "p90_frames": 2.0040290887355803,
54
+ "p90_ms": 100.20145443677902,
55
+ "p95_frames": 2.006546540737152,
56
+ "p95_ms": 100.32732703685761,
57
+ "p99_frames": 2.0128874400138854,
58
+ "p99_ms": 100.64437200069428,
59
+ "prob_latency_gt_1_frame": 1.0,
60
+ "prob_latency_gt_2_frames": 0.2139906728847435,
61
+ "prob_latency_gt_3_frames": 0.0,
62
+ "std_ms": 4.583189247461202
63
+ },
64
+ "visualization_path": "latency_profile.png",
65
+ "workload_id": "mikasa_intercept_grab_fast"
66
+ }
latency-aware/mikasa-intercept-grab-fast/vla/starvla-qwenoft-h1/intercept-qwenoft-mean-3090-h1-s0/provenance.json ADDED
@@ -0,0 +1,415 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "task": "intercept",
3
+ "model": "QwenOFT",
4
+ "run_id": "intercept-qwenoft-mean-3090-h1-s0",
5
+ "training_status": "complete",
6
+ "training_hardware": {
7
+ "gpu": "RTX PRO6000 Blackwell Server Edition",
8
+ "count": 2,
9
+ "physical_indices": [
10
+ 2,
11
+ 3
12
+ ]
13
+ },
14
+ "profile": {
15
+ "mean_ms": 99.05021289731565,
16
+ "profile": "/home/ubuntu/lzj/profiles/intercept-published/profiles/qwenoft/1x-rtx3090/mikasa_intercept_grab_fast/instance_3a0d42681a03715c/profile.json",
17
+ "sha256": "31a9b15d6b04182439934f0b377cb3d09be16ce21f3cf95c1049918f8a5d4984"
18
+ },
19
+ "profile_hardware": "RTX3090",
20
+ "condition": "fixed_mean_profile",
21
+ "horizon": 1,
22
+ "seed": 0,
23
+ "state_dim": 7,
24
+ "action_dim": 7,
25
+ "micro_per_gpu": 64,
26
+ "gradient_accumulation": 1,
27
+ "global_batch": 128,
28
+ "gradient_checkpointing": true,
29
+ "precision": "BF16",
30
+ "backend": "ZeRO2",
31
+ "teacher": {
32
+ "repo_id": "latency-sensitive-bench/benchmark-models",
33
+ "revision": "ba7e4f3b6047b82711de01432a7d5bf3895df8b4",
34
+ "path": "latency-aware/mikasa-intercept-grab-fast/small-policy/ppo-qwenoft-rtx3090-mean-h1-s123-v1",
35
+ "status": "complete_verified",
36
+ "local_checkpoint": "/home/ubuntu/lzj/mean-profiling/intercept/teacher-publication/checkpoint.pt",
37
+ "completed_env_steps": 149975040,
38
+ "completed_iterations": 2441,
39
+ "selected_env_steps": 32256000,
40
+ "selected_iteration": 525,
41
+ "tensor_count": 17,
42
+ "weights_only_export": true,
43
+ "tensor_equality_verified": true,
44
+ "hash_readback_verified": true,
45
+ "url": "https://huggingface.co/latency-sensitive-bench/benchmark-models/tree/ba7e4f3b6047b82711de01432a7d5bf3895df8b4/latency-aware/mikasa-intercept-grab-fast/small-policy/ppo-qwenoft-rtx3090-mean-h1-s123-v1",
46
+ "completed_utc": "2026-09-30T16:34:58.230455+00:00"
47
+ },
48
+ "dataset": {
49
+ "repo_id": "latency-sensitive-bench/Standard-Pipeline",
50
+ "repo_type": "dataset",
51
+ "path": "mikasa_intercept_grab_fast/profile_latency/QwenOFT/mean_rtx3090_h1_s123_275ep_v1",
52
+ "revision": "bba7d56c0ad5bb2b6d6ed07adb70b4f1afd5d25a",
53
+ "data_uploaded_revision": "d746ca2b6461ea211589cdf4d796512335aa34ae",
54
+ "status": "complete_verified",
55
+ "episodes": 275,
56
+ "rows": 12211,
57
+ "raw_npz_episodes": 275,
58
+ "dataset_config": "mikasa_intercept_grab_fast_mean_qwenoft_h1_episode_manifest",
59
+ "viewer_rows": "per_episode metadata manifest",
60
+ "payload_files_verified": 308,
61
+ "checksum_manifest_verified": true,
62
+ "url": "https://huggingface.co/datasets/latency-sensitive-bench/Standard-Pipeline/tree/bba7d56c0ad5bb2b6d6ed07adb70b4f1afd5d25a/mikasa_intercept_grab_fast/profile_latency/QwenOFT/mean_rtx3090_h1_s123_275ep_v1",
63
+ "completed_utc": "2026-09-30T17:34:21.416539+00:00"
64
+ },
65
+ "collection": {
66
+ "episodes": 275,
67
+ "unique_seeds": 275,
68
+ "rows": 12211,
69
+ "length_min": 37,
70
+ "length_max": 55,
71
+ "all_success_once": true,
72
+ "state_dim": 7,
73
+ "action_dim": 7,
74
+ "rgb_shape": [
75
+ 128,
76
+ 128,
77
+ 6
78
+ ],
79
+ "finite_numeric": true,
80
+ "latency_raw_frames": 2,
81
+ "applied_command_matches_source_issued_action": true,
82
+ "action_horizon": 1,
83
+ "dagger": false,
84
+ "raw_bytes": 1204453838,
85
+ "episode_seeds": [
86
+ 0,
87
+ 1,
88
+ 10,
89
+ 180,
90
+ 181,
91
+ 182,
92
+ 183,
93
+ 184,
94
+ 185,
95
+ 186,
96
+ 187,
97
+ 188,
98
+ 189,
99
+ 11,
100
+ 190,
101
+ 191,
102
+ 192,
103
+ 193,
104
+ 194,
105
+ 195,
106
+ 196,
107
+ 197,
108
+ 198,
109
+ 199,
110
+ 12,
111
+ 20,
112
+ 21,
113
+ 22,
114
+ 23,
115
+ 24,
116
+ 25,
117
+ 26,
118
+ 27,
119
+ 28,
120
+ 29,
121
+ 13,
122
+ 200,
123
+ 201,
124
+ 202,
125
+ 203,
126
+ 204,
127
+ 205,
128
+ 206,
129
+ 207,
130
+ 208,
131
+ 209,
132
+ 14,
133
+ 210,
134
+ 211,
135
+ 212,
136
+ 213,
137
+ 214,
138
+ 215,
139
+ 216,
140
+ 217,
141
+ 218,
142
+ 219,
143
+ 15,
144
+ 220,
145
+ 221,
146
+ 222,
147
+ 223,
148
+ 224,
149
+ 225,
150
+ 226,
151
+ 227,
152
+ 228,
153
+ 229,
154
+ 16,
155
+ 230,
156
+ 231,
157
+ 232,
158
+ 233,
159
+ 234,
160
+ 235,
161
+ 236,
162
+ 237,
163
+ 238,
164
+ 239,
165
+ 17,
166
+ 240,
167
+ 241,
168
+ 242,
169
+ 243,
170
+ 244,
171
+ 245,
172
+ 246,
173
+ 247,
174
+ 248,
175
+ 249,
176
+ 18,
177
+ 250,
178
+ 251,
179
+ 252,
180
+ 253,
181
+ 254,
182
+ 255,
183
+ 256,
184
+ 257,
185
+ 258,
186
+ 259,
187
+ 19,
188
+ 260,
189
+ 261,
190
+ 262,
191
+ 263,
192
+ 264,
193
+ 265,
194
+ 266,
195
+ 267,
196
+ 268,
197
+ 269,
198
+ 2,
199
+ 100,
200
+ 270,
201
+ 271,
202
+ 272,
203
+ 273,
204
+ 274,
205
+ 30,
206
+ 31,
207
+ 32,
208
+ 33,
209
+ 34,
210
+ 101,
211
+ 35,
212
+ 36,
213
+ 37,
214
+ 38,
215
+ 39,
216
+ 40,
217
+ 41,
218
+ 42,
219
+ 43,
220
+ 44,
221
+ 102,
222
+ 45,
223
+ 46,
224
+ 47,
225
+ 48,
226
+ 49,
227
+ 50,
228
+ 51,
229
+ 52,
230
+ 53,
231
+ 54,
232
+ 103,
233
+ 55,
234
+ 56,
235
+ 57,
236
+ 58,
237
+ 59,
238
+ 60,
239
+ 61,
240
+ 62,
241
+ 63,
242
+ 64,
243
+ 104,
244
+ 65,
245
+ 66,
246
+ 67,
247
+ 68,
248
+ 69,
249
+ 70,
250
+ 71,
251
+ 72,
252
+ 73,
253
+ 74,
254
+ 105,
255
+ 75,
256
+ 76,
257
+ 77,
258
+ 78,
259
+ 79,
260
+ 80,
261
+ 81,
262
+ 82,
263
+ 83,
264
+ 84,
265
+ 106,
266
+ 85,
267
+ 86,
268
+ 87,
269
+ 88,
270
+ 89,
271
+ 90,
272
+ 91,
273
+ 92,
274
+ 93,
275
+ 94,
276
+ 107,
277
+ 95,
278
+ 96,
279
+ 97,
280
+ 98,
281
+ 99,
282
+ 108,
283
+ 109,
284
+ 3,
285
+ 110,
286
+ 111,
287
+ 112,
288
+ 113,
289
+ 114,
290
+ 115,
291
+ 116,
292
+ 117,
293
+ 118,
294
+ 119,
295
+ 4,
296
+ 120,
297
+ 121,
298
+ 122,
299
+ 123,
300
+ 124,
301
+ 125,
302
+ 126,
303
+ 127,
304
+ 128,
305
+ 129,
306
+ 5,
307
+ 130,
308
+ 131,
309
+ 132,
310
+ 133,
311
+ 134,
312
+ 135,
313
+ 136,
314
+ 137,
315
+ 138,
316
+ 139,
317
+ 6,
318
+ 140,
319
+ 141,
320
+ 142,
321
+ 143,
322
+ 144,
323
+ 145,
324
+ 146,
325
+ 147,
326
+ 148,
327
+ 149,
328
+ 7,
329
+ 150,
330
+ 151,
331
+ 152,
332
+ 153,
333
+ 154,
334
+ 155,
335
+ 156,
336
+ 157,
337
+ 158,
338
+ 159,
339
+ 8,
340
+ 160,
341
+ 161,
342
+ 162,
343
+ 163,
344
+ 164,
345
+ 165,
346
+ 166,
347
+ 167,
348
+ 168,
349
+ 169,
350
+ 9,
351
+ 170,
352
+ 171,
353
+ 172,
354
+ 173,
355
+ 174,
356
+ 175,
357
+ 176,
358
+ 177,
359
+ 178,
360
+ 179
361
+ ],
362
+ "train_episodes": 250,
363
+ "val_episodes": 25,
364
+ "train_rows": 11119,
365
+ "val_rows": 1092,
366
+ "teacher_checkpoint": "/home/ubuntu/lzj/mean-profiling/intercept/teacher-publication/checkpoint.pt",
367
+ "teacher_sha256": "e47526f03fad3ed251dd78f3cacfba8aa8b60bd1578f40bd7a1393e17298e973",
368
+ "fixed_latency_ms": 99.05021289731565,
369
+ "conversion_complete": true
370
+ },
371
+ "runtime": {
372
+ "repo": "c3c6a39365a151e9b7a5e215452fd64e957c2b29",
373
+ "starvla": "ccca13c5177fe3d3c884b6e2de4965d916016649",
374
+ "runtime_fixes": [
375
+ "mean-profile-preparation.patch",
376
+ "gym-language-contract.patch",
377
+ "loader-spawn-cache.patch",
378
+ "loader-spawn-test.patch",
379
+ "doom-mean-reset.patch"
380
+ ],
381
+ "pytorch3d": {
382
+ "revision": "33824be3cbc87a7dd1db0f6a9a9de9ac81b2d0ba",
383
+ "build": "transforms-only, no native render extension; QwenOFT uses transforms only"
384
+ },
385
+ "decord": {
386
+ "version": "0.6.0",
387
+ "build": "official source CPU decoder CP310",
388
+ "wheel_sha256": "e193b356b1e984b4eff08d23b62e482c2c9e5037a6efdc0b1af47079ae2e4c47"
389
+ }
390
+ },
391
+ "source_reference_revision": "180da0effdaaaeacef218cf5fbaa4db41af67495",
392
+ "performance_configuration_policy": "User authorized throughput priority; reference scientific parameters and actual runtime differences are documented by full config/launch audit",
393
+ "training_results": {
394
+ "training_complete": true,
395
+ "optimizer_updates": 5000,
396
+ "selection": "final_5000",
397
+ "train_loss": 0.0017812054138630629,
398
+ "validation_action_loss": 0.03070492297410965,
399
+ "validation_samples": 128,
400
+ "final_learning_rates": {
401
+ "learning_rate/base": 1e-06,
402
+ "learning_rate/action_model": 3.3333333333333333e-06
403
+ },
404
+ "closed_loop_evaluation": "pending until all four VLA trainings complete",
405
+ "tensor_keys": 732,
406
+ "serialized_tensor_elements_including_tied_aliases": 4892415495,
407
+ "weights_only_load": "passed",
408
+ "all_tensors_finite": true,
409
+ "completion_proof": {
410
+ "wrapper_exit": 0
411
+ }
412
+ },
413
+ "optimizer_recovery_states": "retained on host; inference weights only in this bundle",
414
+ "wandb_url": "https://wandb.ai/dongqianyu99-zhejiang-university/starvla_tasks/runs/mean3090-intercept-vla-blackwell-20260930"
415
+ }
latency-aware/mikasa-intercept-grab-fast/vla/starvla-qwenoft-h1/intercept-qwenoft-mean-3090-h1-s0/startup_verified.json ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "snapshot_utc": "2026-10-01T00:14:27Z",
3
+ "unit": "mean-profile-intercept-vla-20261001-g23-gc.service",
4
+ "wrapper_pid": 3705659,
5
+ "rank_pids": [
6
+ 3706042,
7
+ 3706043
8
+ ],
9
+ "physical_gpus": [
10
+ 2,
11
+ 3
12
+ ],
13
+ "formal_updates_completed": 55,
14
+ "formal_budget": 5000,
15
+ "preflight_updates_discarded": 20,
16
+ "consecutive_observed_updates": [
17
+ 50,
18
+ 51,
19
+ 52,
20
+ 53,
21
+ 54,
22
+ 55
23
+ ],
24
+ "loss_at_logged_steps": {
25
+ "50": 0.33528196811676025,
26
+ "55": 0.2620769143104553
27
+ },
28
+ "finite_loss_verified": true,
29
+ "micro_per_gpu": 64,
30
+ "global_batch": 128,
31
+ "gradient_accumulation": 1,
32
+ "gradient_checkpointing": true,
33
+ "seed": 0,
34
+ "horizon": 1,
35
+ "dagger_updates": 0,
36
+ "gpu_used_mib": [
37
+ 55707,
38
+ 65727
39
+ ],
40
+ "gpu_utilization_percent": [
41
+ 100,
42
+ 100
43
+ ],
44
+ "host_memory_current_bytes": 13883498496,
45
+ "host_memory_peak_bytes": 38294556672,
46
+ "seconds_per_update_recent": 2.74,
47
+ "data_wait_seconds_recent": 0.00069506,
48
+ "estimated_completion_utc_range": [
49
+ "2026-10-01T04:45:00Z",
50
+ "2026-10-01T05:15:00Z"
51
+ ],
52
+ "completion_check_utc": "2026-10-01T05:30:00Z",
53
+ "training_complete": false,
54
+ "publication_complete": false,
55
+ "closed_loop_evaluation_complete": false
56
+ }
latency-aware/mikasa-intercept-grab-fast/vla/starvla-qwenoft-h1/intercept-qwenoft-mean-3090-h1-s0/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/mikasa-intercept-grab-fast/vla/starvla-qwenoft-h1/intercept-qwenoft-mean-3090-h1-s0/task_contract.json ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "format": "latency_bench_starvla_h1_v1",
3
+ "task": "mikasa_intercept_grab_fast",
4
+ "robot_type": "latency_mikasa_h1",
5
+ "state_dim": 7,
6
+ "action_dim": 7,
7
+ "action_horizon": 1,
8
+ "fps": 20.0,
9
+ "state_key": "observation.state",
10
+ "action_key": "action",
11
+ "video_keys": [
12
+ "video.top",
13
+ "video.wrist"
14
+ ],
15
+ "auxiliary": {},
16
+ "normalization": {
17
+ "state": {
18
+ "mean": [
19
+ -0.14554812014102936,
20
+ 0.07433967292308807,
21
+ 0.1436367779970169,
22
+ 2.700871706008911,
23
+ 0.45681115984916687,
24
+ -0.48229172825813293,
25
+ 0.06972828507423401
26
+ ],
27
+ "std": [
28
+ 0.07678605616092682,
29
+ 0.21988309919834137,
30
+ 0.09388109296560287,
31
+ 1.109084963798523,
32
+ 0.3012843728065491,
33
+ 0.3006882667541504,
34
+ 0.0164483655244112
35
+ ],
36
+ "min": [
37
+ -0.37446925044059753,
38
+ -0.29720592498779297,
39
+ 0.016940969973802567,
40
+ -3.140481948852539,
41
+ -0.10707131028175354,
42
+ -1.2001688480377197,
43
+ 0.03238735347986221
44
+ ],
45
+ "max": [
46
+ 0.014208495616912842,
47
+ 0.5909395217895508,
48
+ 0.3558541536331177,
49
+ 3.1415467262268066,
50
+ 0.9420600533485413,
51
+ 0.09770228713750839,
52
+ 0.07999999821186066
53
+ ],
54
+ "q01": [
55
+ -0.3139808464050293,
56
+ -0.25229972660541533,
57
+ 0.0188405978679657,
58
+ -3.134360270500183,
59
+ -0.04521620273590088,
60
+ -1.0301372480392457,
61
+ 0.03585549935698509
62
+ ],
63
+ "q99": [
64
+ -0.013631248474121185,
65
+ 0.5275326526165007,
66
+ 0.33348673582077026,
67
+ 3.1367013454437256,
68
+ 0.8922865676879882,
69
+ 0.056985778063535636,
70
+ 0.07999999821186066
71
+ ]
72
+ },
73
+ "action": {
74
+ "mean": [
75
+ -0.048697348684072495,
76
+ 0.24886493384838104,
77
+ -0.16479986906051636,
78
+ -0.032609131187200546,
79
+ -0.501691997051239,
80
+ 0.44703611731529236,
81
+ 0.6793776154518127
82
+ ],
83
+ "std": [
84
+ 0.1079770028591156,
85
+ 0.47203660011291504,
86
+ 0.14475314319133759,
87
+ 0.2849433720111847,
88
+ 0.318120539188385,
89
+ 0.35324087738990784,
90
+ 0.6349603533744812
91
+ ],
92
+ "min": [
93
+ -0.6572320461273193,
94
+ -1.0,
95
+ -0.8218780755996704,
96
+ -0.7686842083930969,
97
+ -0.9920897483825684,
98
+ -0.30364304780960083,
99
+ -1.0
100
+ ],
101
+ "max": [
102
+ 0.30179348587989807,
103
+ 0.7795099020004272,
104
+ 0.2688712179660797,
105
+ 0.8059309720993042,
106
+ 0.5333064198493958,
107
+ 1.0,
108
+ 1.0
109
+ ],
110
+ "q01": [
111
+ -0.4552440994977951,
112
+ -1.0,
113
+ -0.7419710528850555,
114
+ -0.475678391456604,
115
+ -0.9305388247966766,
116
+ -0.20442348539829253,
117
+ -0.8981475746631622
118
+ ],
119
+ "q99": [
120
+ 0.1125871029496192,
121
+ 0.7402709639072418,
122
+ 0.22590837419033025,
123
+ 0.7005072689056396,
124
+ 0.16810789704322815,
125
+ 1.0,
126
+ 1.0
127
+ ]
128
+ }
129
+ },
130
+ "prompt": "Intercept the rolling ball and grasp it to stop it."
131
+ }
latency-aware/mikasa-intercept-grab-fast/vla/starvla-qwenoft-h1/intercept-qwenoft-mean-3090-h1-s0/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/mikasa-intercept-grab-fast/vla/starvla-qwenoft-h1/intercept-qwenoft-mean-3090-h1-s0/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/mikasa-intercept-grab-fast/vla/starvla-qwenoft-h1/intercept-qwenoft-mean-3090-h1-s0/training-code/datasets.py ADDED
The diff for this file is too large to render. See raw diff
 
latency-aware/mikasa-intercept-grab-fast/vla/starvla-qwenoft-h1/intercept-qwenoft-mean-3090-h1-s0/training-code/doom-mean-reset.patch ADDED
@@ -0,0 +1,192 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ diff --git a/latency_bench/data/sf_teacher_rollout.py b/latency_bench/data/sf_teacher_rollout.py
2
+ index ef45d95b..e32b942e 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
+ @@ -1272,9 +1283,8 @@ def _build_rollout_driver_runtime(
61
+ driver = BatchedTeacherRolloutDriver(runtime["envs"][0], int(num_envs))
62
+ else:
63
+ recreate_env = None
64
+ - if integration_name == "deadly_corridor" and (
65
+ - "export_zero_action_history" in config or getattr(cfg, "rollout_profile_seed_by_reset", False)
66
+ - ):
67
+ + # ViZDoom applies reset seeds only while creating its native game.
68
+ + if integration_name == "deadly_corridor":
69
+ from sample_factory.algo.utils.make_env import make_env_func_batched
70
+ from sample_factory.utils.attr_dict import AttrDict
71
+
72
+ diff --git a/tests/data/test_sf_teacher_dataset_export.py b/tests/data/test_sf_teacher_dataset_export.py
73
+ index e6b25233..61a58118 100644
74
+ --- a/tests/data/test_sf_teacher_dataset_export.py
75
+ +++ b/tests/data/test_sf_teacher_dataset_export.py
76
+ @@ -498,8 +498,11 @@ def test_serial_rollout_driver_continues_life_loss_with_seedless_reset(monkeypat
77
+ assert info == {"reset_seed": None}
78
+
79
+
80
+ -def test_serial_teacher_replay_starts_each_seed_in_a_fresh_environment(monkeypatch):
81
+ - sf_teacher_drivers = _load_sf_teacher_drivers_for_direct_test(monkeypatch)
82
+ +@pytest.mark.parametrize("latency_type", ["fixed", "profile_sample"])
83
+ +def test_serial_teacher_replay_starts_each_seed_in_a_fresh_environment(monkeypatch, tmp_path, latency_type):
84
+ + from contextlib import nullcontext
85
+ + from latency_bench.data import sf_teacher_rollout
86
+ + from sample_factory.algo.utils import make_env
87
+
88
+ class StatefulEnv:
89
+ action_space = object()
90
+ @@ -514,13 +517,17 @@ def test_serial_teacher_replay_starts_each_seed_in_a_fresh_environment(monkeypat
91
+ def close(self):
92
+ return None
93
+
94
+ - driver = sf_teacher_drivers.SerialTeacherRolloutDriver(
95
+ - [StatefulEnv()], recreate_env=lambda _slot: StatefulEnv()
96
+ + cfg = SimpleNamespace(frame_stack=4, action_history_decisions=8, latency_type=latency_type)
97
+ + monkeypatch.setattr(sf_teacher_rollout, "sample_factory_init_log", lambda _: nullcontext())
98
+ + monkeypatch.setattr(sf_teacher_rollout, "build_sample_factory_rollout_cfg", lambda _: (object(), cfg))
99
+ + monkeypatch.setattr(sf_teacher_rollout, "build_env_and_policy", lambda *args: {"envs": [StatefulEnv()]})
100
+ + monkeypatch.setattr(make_env, "make_env_func_batched", lambda *args, **kwargs: StatefulEnv())
101
+ + _, _, driver, _ = sf_teacher_rollout._build_rollout_driver_runtime(
102
+ + {"env_name": "deadly_corridor", "integration_name": "deadly_corridor"},
103
+ + output_dir=tmp_path, num_envs=1, export_env_raw_rgb_frames=False,
104
+ )
105
+ -
106
+ driver.reset_slots({0: 42})
107
+ observation, _ = driver.reset_slots({0: 43})[0]
108
+ -
109
+ assert observation["obs"].tolist() == [43, 1]
110
+
111
+
112
+ @@ -670,19 +677,22 @@ def test_collect_teacher_dataset_probe_retries_until_episode_is_accepted(monkeyp
113
+ def action_distribution(self):
114
+ return object()
115
+
116
+ + reset_seeds = []
117
+ +
118
+ class FakeEnv:
119
+ def __init__(self):
120
+ self.action_space = object()
121
+ - self.reset_seeds: list[int] = []
122
+ + self.reset_seeds = reset_seeds
123
+
124
+ def reset(self, seed=None):
125
+ self.reset_seeds.append(seed)
126
+ + self.current_seed = seed
127
+ return np.zeros((3, 2, 2), dtype=np.uint8), [
128
+ {"env_raw_rgb_frame_stack": _env_raw_rgb_frame_stack(5)}
129
+ ]
130
+
131
+ def step(self, action):
132
+ - reward = 3000.0 if len(self.reset_seeds) == 1 else 3000.001
133
+ + reward = 3000.0 if self.current_seed == 11 else 3000.001
134
+ return (
135
+ np.zeros((3, 2, 2), dtype=np.uint8),
136
+ np.float32(reward),
137
+ @@ -702,6 +712,8 @@ def test_collect_teacher_dataset_probe_retries_until_episode_is_accepted(monkeyp
138
+
139
+ fake_env = FakeEnv()
140
+ fake_actor = FakeActorCritic()
141
+ + from sample_factory.algo.utils import make_env
142
+ + monkeypatch.setattr(make_env, "make_env_func_batched", lambda *args, **kwargs: FakeEnv())
143
+ fake_cfg = SimpleNamespace(frame_stack=1, env_fps=35.0, obs_fps=17.5, seed=11)
144
+
145
+ monkeypatch.setattr(exporter, "build_sample_factory_rollout_cfg", lambda config: (object(), fake_cfg))
146
+ @@ -1273,7 +1285,8 @@ def test_collect_teacher_dataset_saves_demon_attack_latest_raw_rgb_image(monkeyp
147
+
148
+
149
+ @pytest.mark.parametrize("export_history", [False, True])
150
+ -def test_collect_teacher_dataset_saves_flappy_latest_raw_rgb_image(monkeypatch, tmp_path, export_history):
151
+ +@pytest.mark.parametrize("filter_preset", ["clean_v1", "none"])
152
+ +def test_collect_teacher_dataset_saves_flappy_latest_raw_rgb_image(monkeypatch, tmp_path, export_history, filter_preset):
153
+ import torch
154
+ from gymnasium.spaces import Discrete
155
+ from PIL import Image
156
+ @@ -1307,6 +1320,9 @@ def test_collect_teacher_dataset_saves_flappy_latest_raw_rgb_image(monkeypatch,
157
+ class FakeEnv:
158
+ action_space = Discrete(2)
159
+
160
+ + def __init__(self, exporting):
161
+ + self.exporting = exporting
162
+ +
163
+ def reset(self, seed=None):
164
+ self.steps = 0
165
+ obs = {"obs": np.zeros((1, 12, 2, 2), dtype=np.uint8)}
166
+ @@ -1323,6 +1339,9 @@ def test_collect_teacher_dataset_saves_flappy_latest_raw_rgb_image(monkeypatch,
167
+
168
+ def step(self, action):
169
+ self.steps += 1
170
+ + if self.exporting and self.steps == 2 and filter_preset == "none":
171
+ + image_path = output_dir / "images/train/episode_000000/decision_step_000000.png"
172
+ + assert image_path.is_file(), "First frame must be on disk before the episode ends"
173
+ obs = {"obs": np.ones((1, 12, 2, 2), dtype=np.uint8)}
174
+ next_stack = np.stack([np.full((6, 5, 3), 50 + idx, dtype=np.uint8) for idx in range(4)], axis=0)
175
+ return (
176
+ @@ -1354,7 +1373,7 @@ def test_collect_teacher_dataset_saves_flappy_latest_raw_rgb_image(monkeypatch,
177
+ exporter,
178
+ "build_env_and_policy",
179
+ lambda module, cfg, config, num_envs: {
180
+ - "envs": [FakeEnv()],
181
+ + "envs": [FakeEnv(cfg.export_env_raw_rgb_frames)],
182
+ "env_info": object(),
183
+ "actor_critic": FakeActorCritic(),
184
+ "device": torch.device("cpu"),
185
+ @@ -1382,6 +1401,7 @@ def test_collect_teacher_dataset_saves_flappy_latest_raw_rgb_image(monkeypatch,
186
+ export_image_size=None,
187
+ image_writer_workers=0,
188
+ row_buffer_size=1,
189
+ + filter_preset=filter_preset,
190
+ )
191
+ train_rows = _read_shard_rows(output_dir, "train")
192
+ val_rows = _read_shard_rows(output_dir, "val")
latency-aware/mikasa-intercept-grab-fast/vla/starvla-qwenoft-h1/intercept-qwenoft-mean-3090-h1-s0/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/mikasa-intercept-grab-fast/vla/starvla-qwenoft-h1/intercept-qwenoft-mean-3090-h1-s0/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/mikasa-intercept-grab-fast/vla/starvla-qwenoft-h1/intercept-qwenoft-mean-3090-h1-s0/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/mikasa-intercept-grab-fast/vla/starvla-qwenoft-h1/intercept-qwenoft-mean-3090-h1-s0/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/mikasa-intercept-grab-fast/vla/starvla-qwenoft-h1/intercept-qwenoft-mean-3090-h1-s0/training-code/train_starvla.py ADDED
The diff for this file is too large to render. See raw diff
 
latency-aware/mikasa-intercept-grab-fast/vla/starvla-qwenoft-h1/intercept-qwenoft-mean-3090-h1-s0/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/mikasa-intercept-grab-fast/vla/starvla-qwenoft-h1/intercept-qwenoft-mean-3090-h1-s0/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/mikasa-intercept-grab-fast/vla/starvla-qwenoft-h1/intercept-qwenoft-mean-3090-h1-s0/training_results.json ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "training_complete": true,
3
+ "optimizer_updates": 5000,
4
+ "selection": "final_5000",
5
+ "train_loss": 0.0017812054138630629,
6
+ "validation_action_loss": 0.03070492297410965,
7
+ "validation_samples": 128,
8
+ "final_learning_rates": {
9
+ "learning_rate/base": 1e-06,
10
+ "learning_rate/action_model": 3.3333333333333333e-06
11
+ },
12
+ "closed_loop_evaluation": "pending until all four VLA trainings complete",
13
+ "tensor_keys": 732,
14
+ "serialized_tensor_elements_including_tied_aliases": 4892415495,
15
+ "weights_only_load": "passed",
16
+ "all_tensors_finite": true,
17
+ "completion_proof": {
18
+ "wrapper_exit": 0
19
+ }
20
+ }