lfhe commited on
Commit
fcdc93d
·
verified ·
1 Parent(s): c85be5d
action_head.safetensors CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:5971a6c6f805bf4c622a30fc3a127f85011577eb297b670ad6491f09d64d7fc0
3
- size 109199860
 
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": instruction},
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
- mask = inputs["attention_mask"].unsqueeze(-1).to(hidden.dtype)
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:a92011b869ded0ebf4deb4fc67dfda81f16c8c2bbcd64a557aa160d71cd24714
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": 4096,
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
  }