Upload folder using huggingface_hub
Browse files- README.md +2 -0
- flock_robotics_adapter.py +153 -0
- model.pt +3 -0
- 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 |
+
}
|