Skip to content

Commit 7504c21

Browse files
committed
refactor
1 parent bcf923d commit 7504c21

20 files changed

Lines changed: 1310 additions & 416 deletions

examples/qwen_gr00t/conf/train/qwen_gr00t.yaml

Lines changed: 10 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -16,32 +16,23 @@ system:
1616
# TODO(yupu): Support resuming from checkpoint
1717

1818
model:
19-
# TODO: (yupu) the config layout is still a mess
2019
model_name: qwen_gr00t
21-
# Path to the checkpoint of the pretrained base VLM model, e.g. Qwen3-VL-4B-Instruct
22-
checkpoint_dir: /workspace/models/Qwen/Qwen3-VL-4B-Instruct/
2320
vlm:
2421
type: qwen3-vl
25-
qwenvl:
2622
base_vlm: /workspace/models/Qwen/Qwen3-VL-4B-Instruct/
2723
attn_implementation: flash_attention_2
28-
vl_hidden_dim: 2048
29-
dino:
30-
dino_backbone: dinov2_vits14
3124
action_model:
3225
# Whether to condition the action model on proprioceptive state (observation.state)
3326
use_state: false
34-
type: flow_matching
27+
type: gr00t_action_head
3528
action_model_type: DiT-B
36-
action_hidden_dim: 1024
3729
hidden_size: 1024
3830
add_pos_embed: True
3931
max_seq_len: 1024
4032
action_dim: 7
4133
state_dim: 7
4234
future_action_window_size: 7
4335
action_horizon: 8
44-
past_action_window_size: 0
4536
repeated_diffusion_steps: 4
4637
noise_beta_alpha: 1.5
4738
noise_beta_beta: 1.0
@@ -58,7 +49,13 @@ model:
5849
num_layers: 16
5950
output_dim: 1024
6051
positional_embeddings: None
61-
reduce_in_full_precision: True
52+
53+
prompt_template: "Your task is {instruction}. To identify the key objects for your task. Locate their bounding boxes in [x1,y1,x2,y2] format."
54+
55+
normalization_mapping:
56+
VISUAL: IDENTITY
57+
STATE: MIN_MAX
58+
ACTION: MIN_MAX
6259

6360
optimizer:
6461
name: AdamW
@@ -117,24 +114,9 @@ data:
117114
vision_root: ""
118115
action_key: eepose
119116
state_key: eepose
120-
# TODO: (yupu) Remove this once we have a proper dataset config
121-
vla_data:
122-
dataset_py: lerobot_datasets
123-
data_root_dir: playground/Datasets/
124-
data_mix: libero_goal_old
125-
action_type: delta_qpos
126-
CoT_prompt: Your task is {instruction}. To identify the key objects for your task. Locate their bounding boxes in [x1,y1,x2,y2] format.
127-
CoT_answer: bbox
128-
default_image_resolution: [3, 224, 224]
129-
load_all_data_for_training: True
130-
obs: ["image_0"]
131-
video_backend: torchvision_av
132117
# Path to the training data
133118
data_path: /workspace/datasets/IPEC-COMMUNITY/libero_goal_no_noops_1.0.0_lerobot/
134119
tolerance_s: 0.0001
135-
# TODO: (yupu) I think these indices should belong to the policy config, maybe put it in the model config?
136-
observation_delta_indices: [0]
137-
action_delta_indices: [0,1,2,3,4,5,6,7]
138120
preprocessor:
139121
name: policy_preprocessor
140122
steps:
@@ -151,18 +133,12 @@ data:
151133
config:
152134
eps: 1e-8
153135
features: {}
154-
norm_map:
155-
VISUAL: IDENTITY
156-
STATE: MIN_MAX
157-
ACTION: MIN_MAX
136+
# norm_map is injected at runtime from model.normalization_mapping
158137
postprocessor:
159138
name: policy_postprocessor
160139
steps:
161140
- registry_name: unnormalizer_processor
162141
config:
163142
eps: 1e-8
164143
features: {}
165-
norm_map:
166-
VISUAL: IDENTITY
167-
STATE: MIN_MAX
168-
ACTION: MIN_MAX
144+
# norm_map is injected at runtime from model.normalization_mapping

flagscale/inference/inference_qwen_gr00t.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import argparse
2-
import importlib
32

43
import numpy as np
54
import torch
@@ -8,7 +7,8 @@
87

98
from flagscale.logger import logger
109
from flagscale.models.utils.constants import OBS_STATE
11-
from flagscale.train.utils.train_utils import load_checkpoint
10+
from flagscale.models.vla import TrainablePolicy
11+
from flagscale.train.processor import PolicyProcessorPipeline
1212

1313

1414
def load_image(image_path: str, size: tuple[int, int] | None = None) -> torch.Tensor:
@@ -33,10 +33,16 @@ def run_inference(config_path: str):
3333
engine_cfg = cfg.engine
3434
generate_cfg = cfg.generate
3535

36-
model_variant = engine_cfg.model_variant
37-
policy = getattr(importlib.import_module("flagscale.models.vla"), model_variant)
38-
model, preprocessor, postprocessor = load_checkpoint(
39-
engine_cfg.model, policy, engine_cfg.device
36+
pretrained_dir = engine_cfg.model
37+
model = TrainablePolicy.from_pretrained(pretrained_dir, device=engine_cfg.device)
38+
39+
preprocessor = PolicyProcessorPipeline.from_pretrained(
40+
pretrained_dir,
41+
config_filename="policy_preprocessor.json",
42+
)
43+
postprocessor = PolicyProcessorPipeline.from_pretrained(
44+
pretrained_dir,
45+
config_filename="policy_postprocessor.json",
4046
)
4147

4248
# TODO: (yupu): model.to(dtype)?

flagscale/models/utils/constants.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,19 @@
5252
POLICY_PREPROCESSOR_DEFAULT_NAME = "policy_preprocessor"
5353
POLICY_POSTPROCESSOR_DEFAULT_NAME = "policy_postprocessor"
5454

55+
56+
def resolve_pretrained_dir(path: Path, sentinel: str) -> Path:
57+
"""Resolve a checkpoint root dir to the pretrained_model subdir if needed.
58+
59+
If ``sentinel`` (e.g. ``config.json``) does not exist directly under
60+
``path`` but does exist under ``path / pretrained_model/``, returns the
61+
latter. Otherwise returns ``path`` unchanged.
62+
"""
63+
if not (path / sentinel).exists() and (path / PRETRAINED_MODEL_DIR / sentinel).exists():
64+
return path / PRETRAINED_MODEL_DIR
65+
return path
66+
67+
5568
if "LEROBOT_HOME" in os.environ:
5669
raise ValueError(
5770
f"You have a 'LEROBOT_HOME' environment variable set to '{os.getenv('LEROBOT_HOME')}'.\n"

flagscale/models/vla/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from .action_model import FlowMatchingHead
1+
from .base_policy import TrainablePolicy
22
from .protocols import ActionModel, VLMBackbone
33
from .qwen_gr00t import QwenGr00t
44
from .registry import (
@@ -11,6 +11,7 @@
1111
from .vlm import Qwen3VLBackbone, Qwen25VLBackbone, QwenVLBackbone
1212

1313
__all__ = [
14+
"TrainablePolicy",
1415
"VLMBackbone",
1516
"ActionModel",
1617
"register_vlm",
@@ -22,5 +23,4 @@
2223
"QwenVLBackbone",
2324
"Qwen25VLBackbone",
2425
"Qwen3VLBackbone",
25-
"FlowMatchingHead",
2626
]
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
from .flow_matching import FlowMatchingHead
1+
from .gr00t_action_header import GR00TActionHead
22

3-
__all__ = ["FlowMatchingHead"]
3+
__all__ = ["GR00TActionHead"]

flagscale/models/vla/action_model/gr00t_action_header.py

Lines changed: 102 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@
88
# Action repeat is inspired by CogACT
99

1010

11+
from __future__ import annotations
12+
1113
from dataclasses import dataclass, field
14+
from typing import TYPE_CHECKING
1215

1316
import torch
1417
import torch.nn.functional as F
@@ -17,11 +20,65 @@
1720
from transformers import PretrainedConfig
1821
from transformers.feature_extraction_utils import BatchFeature
1922

23+
if TYPE_CHECKING:
24+
from omegaconf import DictConfig
25+
26+
from flagscale.models.utils.constants import ACTION
2027
from flagscale.models.vla.action_model.flow_matching_head.cross_attention_dit import DiT
2128
from flagscale.models.vla.action_model.flow_matching_head.encoding_utils import (
2229
SinusoidalPositionalEncoding,
2330
swish,
2431
)
32+
from flagscale.models.vla.registry import register_action_model
33+
34+
35+
@dataclass
36+
class GR00TActionHeadConfig:
37+
type: str = "gr00t_action_head"
38+
action_model_type: str = "DiT-B"
39+
hidden_size: int = 1024
40+
action_dim: int = 7
41+
state_dim: int = 7
42+
future_action_window_size: int = 7
43+
action_horizon: int = 8
44+
use_state: bool = False
45+
repeated_diffusion_steps: int = 4
46+
add_pos_embed: bool = True
47+
max_seq_len: int = 1024
48+
noise_beta_alpha: float = 1.5
49+
noise_beta_beta: float = 1.0
50+
noise_s: float = 0.999
51+
num_timestep_buckets: int = 1000
52+
num_inference_timesteps: int = 4
53+
num_target_vision_tokens: int = 32
54+
diffusion_model_cfg: dict = field(default_factory=dict)
55+
56+
@classmethod
57+
def from_omegaconf(cls, cfg: DictConfig) -> GR00TActionHeadConfig:
58+
if cfg.get("diffusion_model_cfg") is None:
59+
raise ValueError("diffusion_model_cfg is required in action_model config")
60+
diffusion_cfg = dict(cfg.diffusion_model_cfg)
61+
return cls(
62+
type=cfg.get("type", "gr00t_action_head"),
63+
action_model_type=cfg.get("action_model_type", "DiT-B"),
64+
hidden_size=cfg.get("hidden_size", 1024),
65+
action_dim=cfg.get("action_dim", 7),
66+
state_dim=cfg.get("state_dim", 7),
67+
future_action_window_size=cfg.get("future_action_window_size", 7),
68+
action_horizon=cfg.get("action_horizon", 8),
69+
use_state=cfg.get("use_state", False),
70+
repeated_diffusion_steps=cfg.get("repeated_diffusion_steps", 4),
71+
add_pos_embed=cfg.get("add_pos_embed", True),
72+
max_seq_len=cfg.get("max_seq_len", 1024),
73+
noise_beta_alpha=cfg.get("noise_beta_alpha", 1.5),
74+
noise_beta_beta=cfg.get("noise_beta_beta", 1.0),
75+
noise_s=cfg.get("noise_s", 0.999),
76+
num_timestep_buckets=cfg.get("num_timestep_buckets", 1000),
77+
num_inference_timesteps=cfg.get("num_inference_timesteps", 4),
78+
num_target_vision_tokens=cfg.get("num_target_vision_tokens", 32),
79+
diffusion_model_cfg=diffusion_cfg,
80+
)
81+
2582

2683
# TODO try to merge DiT Modules with follow_match_head, they are just the same arch, but diff loss, use diffusers package will be simple
2784

@@ -37,7 +94,6 @@ def __init__(self, num_categories, input_dim, hidden_dim):
3794
def forward(self, x, cat_ids):
3895
selected_W = self.W[cat_ids]
3996
selected_b = self.b[cat_ids]
40-
# import ipdb; ipdb.set_trace()
4197
return torch.bmm(x, selected_W) + selected_b.unsqueeze(1)
4298

4399

@@ -218,12 +274,10 @@ def __init__(self, **kwargs):
218274
class FlowmatchingActionHead(nn.Module):
219275
def __init__(
220276
self,
221-
full_config,
277+
config: GR00TActionHeadConfig,
222278
):
223279
super().__init__()
224-
config = full_config.model.action_model
225280
self.hidden_size = config.hidden_size # @JinhuiYE
226-
self.full_config = full_config
227281
action_model_type = config.action_model_type
228282
action_model_cfg = DiTConfig[action_model_type]
229283

@@ -405,3 +459,47 @@ def device(self):
405459
@property
406460
def dtype(self):
407461
return next(iter(self.parameters())).dtype
462+
463+
464+
@register_action_model("gr00t_action_head")
465+
class GR00TActionHead(nn.Module):
466+
"""
467+
GR00T action head wrapper for VLA framework.
468+
469+
Adapts the FlowmatchingActionHead (tensor-based interface) to the
470+
dict-based interface expected by the VLA framework (QwenGr00t).
471+
"""
472+
473+
def __init__(self, config: GR00TActionHeadConfig):
474+
super().__init__()
475+
self._head = FlowmatchingActionHead(config=config)
476+
477+
def forward(
478+
self, vlm_output: dict[str, torch.Tensor], action_input: dict[str, torch.Tensor]
479+
) -> dict[str, torch.Tensor]:
480+
vl_embs = vlm_output["hidden_states"]
481+
actions = action_input["actions"]
482+
state = action_input.get("state")
483+
encoder_attention_mask = action_input.get("attention_mask")
484+
mask = action_input.get("mask")
485+
486+
loss = self._head.forward(
487+
vl_embs=vl_embs,
488+
actions=actions,
489+
state=state,
490+
encoder_attention_mask=encoder_attention_mask,
491+
mask=mask,
492+
)
493+
return {"loss": loss}
494+
495+
def predict_action(
496+
self, vlm_output: dict[str, torch.Tensor], action_input: dict[str, torch.Tensor]
497+
) -> dict[str, torch.Tensor]:
498+
vl_embs = vlm_output["hidden_states"]
499+
state = action_input.get("state")
500+
501+
actions = self._head.predict_action(vl_embs=vl_embs, state=state)
502+
return {ACTION: actions}
503+
504+
def fsdp_units(self) -> list[nn.Module]:
505+
return list(self._head.model.transformer_blocks)

0 commit comments

Comments
 (0)