update
Browse files- action_head.safetensors +2 -2
- flock_robotics_adapter.py +37 -4
- model.safetensors +1 -1
- model_config.json +6 -2
action_head.safetensors
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
-
size
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:70e39851fc59d264813679e439f4efceacacb484c5fcd6ded694400bc503dad4
|
| 3 |
+
size 37822956
|
flock_robotics_adapter.py
CHANGED
|
@@ -13,16 +13,50 @@ from transformers import AutoProcessor, Qwen3VLForConditionalGeneration
|
|
| 13 |
ACTION_DIM = 7
|
| 14 |
|
| 15 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
def build_action_head(hidden_size, head_hidden):
|
| 17 |
return nn.Sequential(
|
| 18 |
nn.Linear(hidden_size, head_hidden),
|
| 19 |
nn.GELU(),
|
|
|
|
| 20 |
nn.Linear(head_hidden, head_hidden),
|
| 21 |
nn.GELU(),
|
| 22 |
nn.Linear(head_hidden, ACTION_DIM),
|
| 23 |
)
|
| 24 |
|
| 25 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
class QwenVLAPolicy:
|
| 27 |
def __init__(self, model, processor, head, device):
|
| 28 |
self.model = model
|
|
@@ -35,14 +69,14 @@ class QwenVLAPolicy:
|
|
| 35 |
@torch.no_grad()
|
| 36 |
def act(self, obs):
|
| 37 |
image = np.asarray(obs["image"], dtype=np.uint8)
|
| 38 |
-
instruction = str(obs.get("instruction", ""))
|
| 39 |
pil = Image.fromarray(image)
|
|
|
|
| 40 |
messages = [
|
| 41 |
{
|
| 42 |
"role": "user",
|
| 43 |
"content": [
|
| 44 |
{"type": "image"},
|
| 45 |
-
{"type": "text", "text":
|
| 46 |
],
|
| 47 |
}
|
| 48 |
]
|
|
@@ -55,8 +89,7 @@ class QwenVLAPolicy:
|
|
| 55 |
inputs = {k: v.to(self.device) for k, v in inputs.items()}
|
| 56 |
out = self.model(**inputs, output_hidden_states=True, use_cache=False)
|
| 57 |
hidden = out.hidden_states[-1]
|
| 58 |
-
|
| 59 |
-
pooled = (hidden * mask).sum(1) / mask.sum(1).clamp(min=1)
|
| 60 |
action = torch.tanh(self.head(pooled.float())).squeeze(0).cpu().numpy()
|
| 61 |
return np.clip(action, -1.0, 1.0).astype(np.float32)
|
| 62 |
|
|
|
|
| 13 |
ACTION_DIM = 7
|
| 14 |
|
| 15 |
|
| 16 |
+
def format_user_text(obs: dict) -> str:
|
| 17 |
+
"""Build the text the VLM sees — must match training."""
|
| 18 |
+
instruction = str(obs.get("instruction", "")).strip()
|
| 19 |
+
task = str(obs.get("task", "")).strip()
|
| 20 |
+
difficulty = str(obs.get("difficulty", "")).strip()
|
| 21 |
+
step = obs.get("step", obs.get("step_index", ""))
|
| 22 |
+
horizon = obs.get("horizon", "")
|
| 23 |
+
parts = [instruction] if instruction else []
|
| 24 |
+
meta = []
|
| 25 |
+
if task:
|
| 26 |
+
meta.append(f"task={task}")
|
| 27 |
+
if difficulty:
|
| 28 |
+
meta.append(f"difficulty={difficulty}")
|
| 29 |
+
if step != "" and step is not None:
|
| 30 |
+
meta.append(f"step={step}" + (f"/{horizon}" if horizon not in ("", None) else ""))
|
| 31 |
+
if meta:
|
| 32 |
+
parts.append(" ".join(meta))
|
| 33 |
+
proprio = obs.get("proprio")
|
| 34 |
+
if proprio is not None:
|
| 35 |
+
arr = np.asarray(proprio, dtype=np.float32).reshape(-1)
|
| 36 |
+
# Compact fixed-width floats keep tokenizer length stable
|
| 37 |
+
props = " ".join(f"{x:.3f}" for x in arr.tolist())
|
| 38 |
+
parts.append(f"proprio: {props}")
|
| 39 |
+
return "\n".join(parts) if parts else "act"
|
| 40 |
+
|
| 41 |
+
|
| 42 |
def build_action_head(hidden_size, head_hidden):
|
| 43 |
return nn.Sequential(
|
| 44 |
nn.Linear(hidden_size, head_hidden),
|
| 45 |
nn.GELU(),
|
| 46 |
+
nn.Dropout(0.05),
|
| 47 |
nn.Linear(head_hidden, head_hidden),
|
| 48 |
nn.GELU(),
|
| 49 |
nn.Linear(head_hidden, ACTION_DIM),
|
| 50 |
)
|
| 51 |
|
| 52 |
|
| 53 |
+
def last_token_hidden(hidden, attention_mask):
|
| 54 |
+
# hidden: [B, T, H], mask: [B, T]
|
| 55 |
+
idx = attention_mask.long().sum(dim=1).clamp(min=1) - 1
|
| 56 |
+
b = torch.arange(hidden.size(0), device=hidden.device)
|
| 57 |
+
return hidden[b, idx]
|
| 58 |
+
|
| 59 |
+
|
| 60 |
class QwenVLAPolicy:
|
| 61 |
def __init__(self, model, processor, head, device):
|
| 62 |
self.model = model
|
|
|
|
| 69 |
@torch.no_grad()
|
| 70 |
def act(self, obs):
|
| 71 |
image = np.asarray(obs["image"], dtype=np.uint8)
|
|
|
|
| 72 |
pil = Image.fromarray(image)
|
| 73 |
+
user_text = format_user_text(obs)
|
| 74 |
messages = [
|
| 75 |
{
|
| 76 |
"role": "user",
|
| 77 |
"content": [
|
| 78 |
{"type": "image"},
|
| 79 |
+
{"type": "text", "text": user_text},
|
| 80 |
],
|
| 81 |
}
|
| 82 |
]
|
|
|
|
| 89 |
inputs = {k: v.to(self.device) for k, v in inputs.items()}
|
| 90 |
out = self.model(**inputs, output_hidden_states=True, use_cache=False)
|
| 91 |
hidden = out.hidden_states[-1]
|
| 92 |
+
pooled = last_token_hidden(hidden, inputs["attention_mask"])
|
|
|
|
| 93 |
action = torch.tanh(self.head(pooled.float())).squeeze(0).cpu().numpy()
|
| 94 |
return np.clip(action, -1.0, 1.0).astype(np.float32)
|
| 95 |
|
model.safetensors
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
size 8875719408
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d763700903b08fc9f6c1b7de760be9fac7f8d7a5b824c1fc1c1e11e348e2c7ea
|
| 3 |
size 8875719408
|
model_config.json
CHANGED
|
@@ -1,6 +1,10 @@
|
|
| 1 |
{
|
| 2 |
"base_model": "/workspace/testnet-training-node-quickstart/models/Qwen3-VL-4B-Instruct",
|
| 3 |
"hidden_size": 2560,
|
| 4 |
-
"head_hidden":
|
| 5 |
-
"action_dim": 7
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
}
|
|
|
|
| 1 |
{
|
| 2 |
"base_model": "/workspace/testnet-training-node-quickstart/models/Qwen3-VL-4B-Instruct",
|
| 3 |
"hidden_size": 2560,
|
| 4 |
+
"head_hidden": 2048,
|
| 5 |
+
"action_dim": 7,
|
| 6 |
+
"pool": "last_token",
|
| 7 |
+
"loss": "smooth_l1",
|
| 8 |
+
"best_val": 0.012188794997055084,
|
| 9 |
+
"train_avg": 0.00440433465039604
|
| 10 |
}
|