AlexWortega commited on
Commit
ab239aa
·
verified ·
1 Parent(s): 99a9344

add missing configuration_tinyvla.py (package was incomplete)

Browse files
tinyvla_b200/src/tinyvla/configuration_tinyvla.py ADDED
@@ -0,0 +1,160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """TinyVLA policy config (LeRobot plugin, --policy.type=tinyvla)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+
7
+ from lerobot.configs import FeatureType, NormalizationMode, PreTrainedConfig
8
+ from lerobot.optim import AdamWConfig
9
+ from lerobot.optim.schedulers import CosineDecayWithWarmupSchedulerConfig
10
+
11
+
12
+ @PreTrainedConfig.register_subclass("tinyvla")
13
+ @dataclass
14
+ class TinyVLAConfig(PreTrainedConfig):
15
+ # Input / output structure
16
+ n_obs_steps: int = 1
17
+ chunk_size: int = 50
18
+ n_action_steps: int = 50
19
+
20
+ normalization_mapping: dict[str, NormalizationMode] = field(
21
+ default_factory=lambda: {
22
+ "VISUAL": NormalizationMode.IDENTITY,
23
+ "STATE": NormalizationMode.MEAN_STD,
24
+ "ACTION": NormalizationMode.MEAN_STD,
25
+ }
26
+ )
27
+
28
+ # Cross-embodiment padding
29
+ max_state_dim: int = 16
30
+ max_action_dim: int = 8
31
+ num_embodiments: int = 16
32
+ max_cameras: int = 3
33
+
34
+ # TinyVLA-2 A/B/C variant switch
35
+ # conditioning "id" -> learned embodiment embedding (variants A, B)
36
+ # conditioning "morph" -> descriptor MLP -> morphology tokens (variant C)
37
+ conditioning: str = "id" # "id" | "morph" (MLP over numeric fields) | "morph_qwen" (text via shared Qwen)
38
+ morph_tokens: int = 2
39
+ morph_to_slow: bool = False # also inject morphology into the slow Qwen prefix
40
+ num_morph_readout: int = 4 # for morph_qwen: readout slots on the robot-text encode
41
+ morph_text_max_len: int = 32
42
+ # in-context demo conditioning: K (obs, action) exemplar pairs, no gradient
43
+ # updates for the new robot at deployment. Composable with morph/morph_qwen.
44
+ use_demo_conditioning: bool = False
45
+ n_support: int = 3
46
+ # sample demo exemplars from a DIFFERENT task of the same robot, so the
47
+ # exemplar carries robot-dynamics info the identity channels don't duplicate
48
+ support_other_task: bool = False
49
+ # demo-encoder capacity (v3): wider path so exemplar content isn't destroyed
50
+ demo_tokens_per_example: int = 4
51
+ demo_visual_groups: int = 4
52
+ demo_action_keys: int = 8
53
+ demo_hidden_mult: int = 4
54
+ # Rich-slow mode: demos + robot text + instruction + image all go into the LM
55
+ # sequence (so demos live in the ATTENTION STREAM — the fix for v1-v3's
56
+ # "placed alongside → ignored" failure), and the LM emits latent tokens that
57
+ # condition the fast path. DUAL-RATE IS PRESERVED: the fast path still has its
58
+ # own spatial tokens + proprio + expert, and the LM latents are cacheable
59
+ # exactly like the old semantic latent.
60
+ vlm_native: bool = False
61
+ num_action_readout: int = 16
62
+ demo_action_keys_lm: int = 4
63
+ # DEMO-ONLY: strip every other robot-identity channel (embodiment-ID token,
64
+ # morphology descriptor/text, and the per-source normalization that leaks
65
+ # identity) so the demo exemplars are the ONLY way to know which robot this
66
+ # is. Four prior attempts failed because identity was available elsewhere and
67
+ # the gradient had no reason to read the demos.
68
+ demo_only: bool = False
69
+ # action_space "native" (A) vs "canonical" (B, C: base-frame EE deltas)
70
+ action_space: str = "native"
71
+
72
+ # Vision (shared Qwen3.5 vision tower for both paths)
73
+ image_size: int = 256
74
+ freeze_vision_encoder: bool = True # Stage 1; unfreeze at 0.1x lr in Stage 2
75
+
76
+ # Slow path (Qwen3.5 VLM with native vision tower)
77
+ lm_model_name: str = "Qwen/Qwen3.5-0.8B"
78
+ lm_num_layers: int = 12 # L/2 layer skip (24-layer language stack)
79
+ num_readout_tokens: int = 8
80
+ freeze_lm: bool = True
81
+ tokenizer_max_length: int = 48
82
+ pad_language_to: str = "longest"
83
+
84
+ # Fast path (action expert)
85
+ expert_dim: int = 512
86
+ expert_layers: int = 12
87
+ expert_heads: int = 8
88
+
89
+ # Ablation switches (Stage-2 gate baselines)
90
+ use_semantic_latent: bool = True
91
+ use_spatial_tokens: bool = True
92
+
93
+ # Spatial distillation aux task (Stage 3, XS-VLA style)
94
+ spatial_vocab: int = 1024 # 32x32 grid cells
95
+ spatial_loss_weight: float = 0.0 # 0 disables the aux head/loss
96
+
97
+ # Flow matching
98
+ num_denoise_steps: int = 10
99
+ flow_beta_alpha: float = 1.5
100
+ flow_beta_beta: float = 1.0
101
+ min_period: float = 4e-3
102
+ max_period: float = 4.0
103
+
104
+ # Staleness augmentation (Stage 2b): probability of conditioning on a latent
105
+ # computed from an earlier frame. Consumed by the training loop, not here.
106
+ staleness_prob: float = 0.0
107
+ staleness_max_s: float = 2.0
108
+
109
+ # Training presets
110
+ optimizer_lr: float = 1e-4
111
+ optimizer_betas: tuple[float, float] = (0.9, 0.95)
112
+ optimizer_eps: float = 1e-8
113
+ optimizer_weight_decay: float = 1e-10
114
+ optimizer_grad_clip_norm: float = 10.0
115
+ scheduler_warmup_steps: int = 1_000
116
+ scheduler_decay_steps: int = 40_000
117
+ scheduler_decay_lr: float = 2.5e-6
118
+
119
+ def __post_init__(self):
120
+ super().__post_init__()
121
+ if self.n_action_steps > self.chunk_size:
122
+ raise ValueError(
123
+ f"n_action_steps ({self.n_action_steps}) must be <= chunk_size ({self.chunk_size})"
124
+ )
125
+
126
+ def validate_features(self) -> None:
127
+ image_keys = [k for k, v in self.input_features.items() if v.type is FeatureType.VISUAL]
128
+ if not image_keys:
129
+ raise ValueError("TinyVLA requires at least one image feature")
130
+ if len(image_keys) > self.max_cameras:
131
+ raise ValueError(f"got {len(image_keys)} cameras, max_cameras={self.max_cameras}")
132
+
133
+ def get_optimizer_preset(self) -> AdamWConfig:
134
+ return AdamWConfig(
135
+ lr=self.optimizer_lr,
136
+ betas=self.optimizer_betas,
137
+ eps=self.optimizer_eps,
138
+ weight_decay=self.optimizer_weight_decay,
139
+ grad_clip_norm=self.optimizer_grad_clip_norm,
140
+ )
141
+
142
+ def get_scheduler_preset(self):
143
+ return CosineDecayWithWarmupSchedulerConfig(
144
+ peak_lr=self.optimizer_lr,
145
+ decay_lr=self.scheduler_decay_lr,
146
+ num_warmup_steps=self.scheduler_warmup_steps,
147
+ num_decay_steps=self.scheduler_decay_steps,
148
+ )
149
+
150
+ @property
151
+ def observation_delta_indices(self) -> list:
152
+ return [0]
153
+
154
+ @property
155
+ def action_delta_indices(self) -> list:
156
+ return list(range(self.chunk_size))
157
+
158
+ @property
159
+ def reward_delta_indices(self) -> None:
160
+ return None