feat(vla): add SmolVLA conditioned agent with prefix encoder and train/eval validation
Introduce SmolVLA-style VLM prefix encoder backbone and IMF-AttnRes conditioned agent. Add episode-level train/val split, action MSE validation in training, and headless eval support.
This commit is contained in:
@@ -129,6 +129,38 @@ class EvalVLAExecutionTest(unittest.TestCase):
|
||||
["r_vis", "top", "front"],
|
||||
)
|
||||
|
||||
def test_resolve_eval_image_resize_shape_prefers_condition_encoder_override(self):
|
||||
cfg = OmegaConf.create(
|
||||
{
|
||||
"agent": {
|
||||
"condition_encoder": {
|
||||
"eval_image_resize_shape": None,
|
||||
},
|
||||
},
|
||||
"data": {
|
||||
"image_resize_shape": [224, 224],
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
self.assertIsNone(eval_vla._resolve_eval_image_resize_shape(cfg))
|
||||
|
||||
def test_resolve_eval_image_resize_shape_prefers_vision_backbone_override(self):
|
||||
cfg = OmegaConf.create(
|
||||
{
|
||||
"agent": {
|
||||
"vision_backbone": {
|
||||
"eval_image_resize_shape": [256, 256],
|
||||
},
|
||||
},
|
||||
"data": {
|
||||
"image_resize_shape": [224, 224],
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
self.assertEqual(eval_vla._resolve_eval_image_resize_shape(cfg), (256, 256))
|
||||
|
||||
def test_build_episode_plans_without_box_poses_keeps_serial_sampling_lazy(self):
|
||||
plans = eval_vla._build_episode_plans(num_episodes=3)
|
||||
|
||||
@@ -168,6 +200,58 @@ class EvalVLAExecutionTest(unittest.TestCase):
|
||||
np.array([[[[1.0]]], [[[1.0]]], [[[1.0]]]], dtype=np.float32),
|
||||
)
|
||||
|
||||
def test_prepare_local_policy_batch_keeps_latest_variable_task_when_present(self):
|
||||
queues = eval_vla._new_local_policy_queues(obs_horizon=2)
|
||||
first_observation = {
|
||||
"qpos": torch.tensor([1.0, 2.0], dtype=torch.float32),
|
||||
"images": {"front": torch.tensor([[[1.0]]], dtype=torch.float32)},
|
||||
"task": "pick the red cube",
|
||||
}
|
||||
second_observation = {
|
||||
"qpos": torch.tensor([3.0, 4.0], dtype=torch.float32),
|
||||
"images": {"front": torch.tensor([[[2.0]]], dtype=torch.float32)},
|
||||
"task": "insert the peg into the socket",
|
||||
}
|
||||
|
||||
eval_vla._populate_local_policy_queues(queues, first_observation)
|
||||
eval_vla._populate_local_policy_queues(queues, second_observation)
|
||||
batch = eval_vla._prepare_local_policy_batch(
|
||||
queues,
|
||||
obs_horizon=2,
|
||||
camera_names=["front"],
|
||||
)
|
||||
|
||||
self.assertEqual(batch["task"], ["insert the peg into the socket"])
|
||||
|
||||
def test_prepare_local_policy_batch_omits_task_for_legacy_observations(self):
|
||||
queues = eval_vla._new_local_policy_queues(obs_horizon=2)
|
||||
observation = {
|
||||
"qpos": torch.tensor([1.0, 2.0], dtype=torch.float32),
|
||||
"images": {"front": torch.tensor([[[1.0]]], dtype=torch.float32)},
|
||||
}
|
||||
|
||||
eval_vla._populate_local_policy_queues(queues, observation)
|
||||
batch = eval_vla._prepare_local_policy_batch(
|
||||
queues,
|
||||
obs_horizon=2,
|
||||
camera_names=["front"],
|
||||
)
|
||||
|
||||
self.assertNotIn("task", batch)
|
||||
|
||||
def test_serialize_deserialize_policy_batch_preserves_task(self):
|
||||
batch = {
|
||||
"qpos": torch.zeros(1, 2, 2, dtype=torch.float32),
|
||||
"images": {"front": torch.zeros(1, 2, 1, 1, 1, dtype=torch.float32)},
|
||||
"task": ["pick the red cube", "insert the peg into the socket"],
|
||||
}
|
||||
|
||||
serialized = eval_vla._serialize_policy_batch(batch)
|
||||
deserialized = eval_vla._deserialize_policy_batch(serialized, device="cpu")
|
||||
|
||||
self.assertEqual(serialized["task"], batch["task"])
|
||||
self.assertEqual(deserialized["task"], batch["task"])
|
||||
|
||||
def test_enqueue_predicted_actions_uses_executable_slice(self):
|
||||
queues = eval_vla._new_local_policy_queues(obs_horizon=2)
|
||||
predicted_actions = torch.tensor(
|
||||
|
||||
Reference in New Issue
Block a user