flockgo commited on
Commit
bec7ed1
·
verified ·
1 Parent(s): 457c2ab

Upload folder using huggingface_hub

Browse files
Files changed (4) hide show
  1. README.md +2 -0
  2. flock_robotics_adapter.py +153 -0
  3. model.pt +3 -0
  4. vla_config.json +116 -0
README.md ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ Self-contained Robotics VLA policy for FLock task 23.
2
+ The adapter only consumes the documented validator observation dictionary.
flock_robotics_adapter.py ADDED
@@ -0,0 +1,153 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+ import hashlib
3
+ import json
4
+ import math
5
+ import re
6
+ from pathlib import Path
7
+ import numpy as np
8
+ import torch
9
+ import torch.nn as nn
10
+ import torch.nn.functional as F
11
+
12
+ VARIANTS = {
13
+ "small_cnn": {"width": 32, "depth": 3, "chunk": 1, "dropout": 0.05},
14
+ "base_cnn": {"width": 64, "depth": 4, "chunk": 1, "dropout": 0.10},
15
+ "chunk_cnn": {"width": 48, "depth": 4, "chunk": 8, "dropout": 0.10},
16
+ "ensemble_cnn": {"width": 64, "depth": 5, "chunk": 8, "dropout": 0.15},
17
+ }
18
+
19
+ class VisionBackbone(nn.Module):
20
+ def __init__(self, width, depth):
21
+ super().__init__()
22
+ layers = [nn.Conv2d(3, width, 5, 2, 2), nn.GroupNorm(max(1, width // 8), width), nn.SiLU()]
23
+ channels = width
24
+ for index in range(depth - 1):
25
+ next_channels = min(width * (2 ** min(index + 1, 2)), 256)
26
+ layers += [nn.Conv2d(channels, next_channels, 3, 2, 1),
27
+ nn.GroupNorm(max(1, next_channels // 8), next_channels), nn.SiLU()]
28
+ channels = next_channels
29
+ layers += [nn.AdaptiveAvgPool2d((1, 1)), nn.Flatten(), nn.Linear(channels, 384), nn.SiLU()]
30
+ self.net = nn.Sequential(*layers)
31
+ def forward(self, images):
32
+ return self.net(images)
33
+
34
+ class PolicyNet(nn.Module):
35
+ def __init__(self, config):
36
+ super().__init__()
37
+ variant = config["variant"]
38
+ cfg = VARIANTS[variant]
39
+ self.chunk = int(cfg["chunk"])
40
+ self.vision = VisionBackbone(int(cfg["width"]), int(cfg["depth"]))
41
+ self.proprio = nn.Sequential(nn.Linear(int(config["proprio_dim"]), 128), nn.LayerNorm(128), nn.SiLU())
42
+ self.text = nn.Sequential(nn.Linear(int(config["text_dim"]), 128), nn.LayerNorm(128), nn.SiLU())
43
+ self.task = nn.Embedding(int(config["task_count"]), 32)
44
+ self.difficulty = nn.Embedding(int(config["difficulty_count"]), 16)
45
+ self.head = nn.Sequential(
46
+ nn.Linear(384 + 128 + 128 + 32 + 16, 512), nn.LayerNorm(512), nn.SiLU(),
47
+ nn.Dropout(float(cfg["dropout"])), nn.Linear(512, 256), nn.SiLU(),
48
+ nn.Linear(256, self.chunk * 7))
49
+ def forward(self, images, proprio, text, task_id, difficulty_id):
50
+ if images.ndim != 4:
51
+ raise ValueError("image batch must have four dimensions")
52
+ if images.shape[-1] == 3:
53
+ images = images.permute(0, 3, 1, 2)
54
+ images = images.float()
55
+ if images.max() > 2:
56
+ images = images / 255.0
57
+ images = F.interpolate(images, size=(96, 96), mode="bilinear", align_corners=False)
58
+ fused = torch.cat([
59
+ self.vision(images), self.proprio(proprio.float()), self.text(text.float()),
60
+ self.task(task_id.clamp(0, self.task.num_embeddings - 1)),
61
+ self.difficulty(difficulty_id.clamp(0, self.difficulty.num_embeddings - 1)),
62
+ ], dim=-1)
63
+ return torch.tanh(self.head(fused)).reshape(-1, self.chunk, 7)
64
+
65
+ def _text_vector(text, dim):
66
+ result = np.zeros(dim, dtype=np.float32)
67
+ for token in re.findall(r"[a-z0-9_]+", str(text).lower()):
68
+ value = int.from_bytes(hashlib.blake2b(token.encode(), digest_size=8).digest(), "little")
69
+ result[value % dim] += 1.0 if value & 1 else -1.0
70
+ norm = float(np.linalg.norm(result))
71
+ return result / norm if norm else result
72
+
73
+ def _proprio(obs, config):
74
+ value = np.asarray(obs.get("proprio", np.zeros(25, dtype=np.float32)), dtype=np.float32).reshape(-1)
75
+ if value.size == 21:
76
+ value = np.pad(value, (0, 4))
77
+ if value.size != 25:
78
+ raise ValueError(f"proprio must have 25 values, got {value.size}")
79
+ step = float(obs.get("step", 0))
80
+ horizon = float(obs.get("horizon", 320) or 320)
81
+ value = np.concatenate([value, np.asarray([step / max(horizon, 1.0)], dtype=np.float32)])
82
+ if value.size < int(config["proprio_dim"]):
83
+ value = np.pad(value, (0, int(config["proprio_dim"]) - value.size))
84
+ value = value[: int(config["proprio_dim"])]
85
+ mean = np.asarray(config["proprio_mean"], dtype=np.float32)
86
+ std = np.asarray(config["proprio_std"], dtype=np.float32)
87
+ return ((value - mean) / np.maximum(std, 1e-4)).astype(np.float32)
88
+
89
+ class Policy:
90
+ def __init__(self, model, config, device):
91
+ self.model, self.config, self.device = model, config, device
92
+ self.chunk = int(config["chunk"])
93
+ self.last_step = None
94
+ self.chunks = {}
95
+ self.last_action = np.zeros(7, dtype=np.float32)
96
+ self.task_to_id = config["task_to_id"]
97
+ self.difficulty_to_id = config["difficulty_to_id"]
98
+ @torch.inference_mode()
99
+ def act(self, obs):
100
+ image = np.asarray(obs.get("image", np.zeros((96, 96, 3), dtype=np.uint8)))
101
+ if image.ndim == 2:
102
+ image = np.repeat(image[..., None], 3, axis=-1)
103
+ if image.shape[-1] == 4:
104
+ image = image[..., :3]
105
+ if image.shape[-1] != 3:
106
+ raise ValueError("image must have 3 or 4 channels")
107
+ step = int(obs.get("step", 0))
108
+ if self.last_step is None or step == 0 or step != self.last_step + 1:
109
+ self.chunks = {}
110
+ if step == self.last_step:
111
+ return self.last_action.copy()
112
+ task = str(obs.get("task", ""))
113
+ difficulty = str(obs.get("difficulty", "") or "")
114
+ text = f"task {task} difficulty {difficulty} instruction {obs.get('instruction', '')}"
115
+ text_feature = _text_vector(text, int(self.config["text_dim"]))
116
+ proprio = _proprio(obs, self.config)
117
+ task_id = self.task_to_id.get(task, len(self.task_to_id))
118
+ difficulty_id = self.difficulty_to_id.get(difficulty, len(self.difficulty_to_id))
119
+ raw = self.model(
120
+ torch.from_numpy(image).unsqueeze(0).to(self.device),
121
+ torch.from_numpy(proprio).unsqueeze(0).to(self.device),
122
+ torch.from_numpy(text_feature).unsqueeze(0).to(self.device),
123
+ torch.tensor([task_id], device=self.device),
124
+ torch.tensor([difficulty_id], device=self.device),
125
+ )[0].float().cpu().numpy()
126
+ self.chunks[step] = raw
127
+ candidates, weights = [], []
128
+ for start, chunk in list(self.chunks.items()):
129
+ offset = step - start
130
+ if 0 <= offset < self.chunk:
131
+ candidates.append(chunk[offset])
132
+ weights.append(math.exp(-0.55 * offset))
133
+ else:
134
+ del self.chunks[start]
135
+ action = np.average(np.asarray(candidates), axis=0, weights=np.asarray(weights))
136
+ action = np.clip(action, -1.0, 1.0).astype(np.float32)
137
+ if action[6] > 0.12:
138
+ action[6] = 1.0
139
+ elif action[6] < -0.12:
140
+ action[6] = -1.0
141
+ self.last_step, self.last_action = step, action.copy()
142
+ return action
143
+
144
+ def load_policy(model_dir: str, device: str, dtype: str):
145
+ root = Path(model_dir)
146
+ config = json.loads((root / "vla_config.json").read_text())
147
+ selected = torch.device(device if str(device).startswith("cuda") and torch.cuda.is_available() else "cpu")
148
+ model = PolicyNet(config).to(selected)
149
+ checkpoint = torch.load(root / "model.pt", map_location=selected, weights_only=True)
150
+ state = checkpoint["state_dict"] if "state_dict" in checkpoint else checkpoint
151
+ model.load_state_dict(state, strict=True)
152
+ model.eval()
153
+ return Policy(model, config, selected)
model.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:89d7d3bef4ec32c688f71860252c4d8eef00de0e63244abe225f0e57c46b507c
3
+ size 2624951
vla_config.json ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "schema_version": "task23_self_contained_policy_v1",
3
+ "model_type": "cnn_conditioned_action_chunk",
4
+ "variant": "small_cnn",
5
+ "chunk": 1,
6
+ "text_dim": 128,
7
+ "proprio_dim": 32,
8
+ "task_count": 14,
9
+ "difficulty_count": 5,
10
+ "proprio_mean": [
11
+ -0.013340266421437263,
12
+ 0.7383145093917847,
13
+ 0.011660933494567871,
14
+ -1.9436376094818115,
15
+ 0.019467052072286606,
16
+ 2.826350212097168,
17
+ 0.7630648612976074,
18
+ 0.0225653275847435,
19
+ 0.052658289670944214,
20
+ 0.009153264574706554,
21
+ 0.07257223129272461,
22
+ -0.0217689611017704,
23
+ -0.014813058078289032,
24
+ 0.045801397413015366,
25
+ 0.07692108303308487,
26
+ -0.06413360685110092,
27
+ 0.9298723936080933,
28
+ 0.9961475133895874,
29
+ -0.004522569477558136,
30
+ 0.07633855938911438,
31
+ -0.005218956153839827,
32
+ 0.03139699995517731,
33
+ -0.030553586781024933,
34
+ 0.0001240656420122832,
35
+ 5.485760084411595e-06,
36
+ 0.49797627329826355,
37
+ 0.0,
38
+ 0.0,
39
+ 0.0,
40
+ 0.0,
41
+ 0.0,
42
+ 0.0
43
+ ],
44
+ "proprio_std": [
45
+ 0.18346060812473297,
46
+ 0.2751201093196869,
47
+ 0.08362191170454025,
48
+ 0.4013335406780243,
49
+ 0.1880636066198349,
50
+ 0.22030314803123474,
51
+ 0.36815357208251953,
52
+ 0.09236042201519012,
53
+ 0.24793101847171783,
54
+ 0.07140849530696869,
55
+ 0.27341264486312866,
56
+ 0.12188053876161575,
57
+ 0.17399784922599792,
58
+ 0.20990528166294098,
59
+ 0.09051761776208878,
60
+ 0.1660534143447876,
61
+ 0.07342937588691711,
62
+ 0.0047762468457221985,
63
+ 0.021420147269964218,
64
+ 0.03227067366242409,
65
+ 0.016867103055119514,
66
+ 0.008550216443836689,
67
+ 0.009234834462404251,
68
+ 0.015089103952050209,
69
+ 0.012418420054018497,
70
+ 0.2886728048324585,
71
+ 1.0,
72
+ 1.0,
73
+ 1.0,
74
+ 1.0,
75
+ 1.0,
76
+ 1.0
77
+ ],
78
+ "task_to_id": {
79
+ "lift_cube": 0,
80
+ "pick_place_can": 1,
81
+ "pick_place_milk": 2,
82
+ "pick_place_bread": 3,
83
+ "pick_place_cereal": 4,
84
+ "pick_place_clutter": 5,
85
+ "stack_blocks": 6,
86
+ "open_door": 7,
87
+ "nut_assembly": 8,
88
+ "nut_assembly_square": 9,
89
+ "nut_assembly_round": 10,
90
+ "wipe_table": 11,
91
+ "tool_hang": 12
92
+ },
93
+ "difficulty_to_id": {
94
+ "low": 0,
95
+ "medium": 1,
96
+ "hard": 2,
97
+ "very_high": 3
98
+ },
99
+ "adapter_inputs": [
100
+ "image",
101
+ "instruction",
102
+ "proprio",
103
+ "task",
104
+ "step",
105
+ "difficulty",
106
+ "horizon"
107
+ ],
108
+ "forbidden_adapter_inputs": [
109
+ "raw_obs",
110
+ "object_state",
111
+ "sim_state",
112
+ "mujoco_data"
113
+ ],
114
+ "training_data": "data/hf",
115
+ "trainval": false
116
+ }