技术博客
VLA原理训练

Being-H0.5 代码详解:统一动作空间如何支撑跨本体 VLA 泛化

coffee co.2026-08-05 10:14
Being-H0.5 代码详解:统一动作空间如何支撑跨本体 VLA 泛化

Being-H0.5 代码详解:统一动作空间如何支撑跨本体 VLA 泛化

Being-H0.5 是 BeingBeyond 在 Being-H 系列中发布的旗舰 VLA 模型,目标是解决机器人学习中一个长期存在的问题:不同机器人本体之间的动作空间、状态空间和传感器布局差异巨大,模型如何在多个 embodiment 之间共享经验,并在新任务或新平台上保持可迁移性。

这套代码的核心并不是简单地将视觉、语言和动作拼接到一起,而是围绕“跨本体统一表示”建立了一整套工程体系。它用 200 维统一 state/action 空间承接不同机器人,将视觉和语言交给多模态主干处理,再通过动作专家、Flow Matching 动作生成、metadata normalization 等机制完成可部署的动作预测。

如果只用一句话概括 Being-H0.5 的实现思想:

用统一动作空间对齐不同机器人,用多模态主干理解任务场景,用连续动作生成头输出可执行控制序列。

1. 项目定位:从单机器人策略到跨本体策略

普通机器人策略通常与具体本体强绑定。例如,Franka 的动作可能是 7 维末端执行器控制加夹爪,移动操作平台还可能包含底盘控制,双臂机器人则需要左右臂与手部关节。不同平台的动作维度和语义结构并不一致。

Being-H0.5 的做法是定义一个固定的 200 维统一空间:

unified_state_dim  = 200
unified_action_dim = 200

不同机器人的状态和动作都映射到这个统一空间中的指定 slot。这样,相似部件可以共享相同语义位置,例如右臂末端位置总是映射到 0-2,右臂末端旋转总是映射到 3-5。未使用维度用 0 填充,并通过 mask 避免无效维度干扰损失。

这一点在 Being-H05/docs/unified_action_space.md 中有完整说明。代码层面,模型在 Being-H05/BeingH/model/beingvla.py 中直接固定了统一空间维度:

self.unified_state_dim = 200
self.unified_action_dim = 200

这不是一个附属配置,而是模型结构本身的基础假设。Being-H0.5 的跨本体能力,很大程度上建立在这个固定语义空间之上。


2. 统一动作空间:200 维 slot 的工程意义

统一空间并不是简单扩展维度,而是对机器人控制语义进行 slot 化组织。典型布局如下:

0-2     right end-effector position
3-5     right end-effector rotation
9-11    left end-effector position
12-14   left end-effector rotation
18      right gripper
19      left gripper
50-57   right arm joints
57-64   left arm joints
70-75   mobile base and control mode
90-200  human hand MANO parameters

每个机器人通过 UNIFIED_MAPPING 将自己的原始状态和动作映射到统一空间。例如,LIBERO 的配置可以抽象为:

UNIFIED_MAPPING = {
    'state.eef_position': (0, 3),
    'state.eef_rotation': (3, 6),
    'state.libero_gripper_position': (44, 46),
    'action.eef_position': (0, 3),
    'action.eef_rotation': (3, 6),
    'action.gripper_position': (18, 19),
}

RoboCasa 这样的移动操作平台则会额外使用底盘相关 slot:

UNIFIED_MAPPING = {
    'state.eef_position': (0, 3),
    'state.eef_rotation': (3, 6),
    'state.gripper_qpos': (44, 46),
    'state.base_position': (70, 73),
    'state.base_rotation': (73, 76),
    'action.eef_position': (0, 3),
    'action.eef_rotation': (3, 6),
    'action.gripper_position': (18, 19),
    'action.base_motion': (70, 74),
    'action.control_mode': (74, 75),
}

这种设计带来三个直接收益:

  1. 不同机器人可以进入同一个模型结构。

  2. 相同语义部件在不同 embodiment 间共享参数统计。

  3. 新机器人只需补充 mapping 和数据配置,而不必重写模型 head。


3. 模型主体:视觉、语言、状态与动作的统一打包

Being-H0.5 的核心模型类是 BeingH

class BeingH(PreTrainedModel):
    config_class = BeingHConfig
    main_input_name = 'pixel_values'
    base_model_prefix = 'beingh'

它由三类主要组件构成:

self.vit_model = vit_model
self.language_model = language_model
self.connector = connector

其中:

  • vit_model 负责图像编码。

  • connector 负责将视觉特征映射到 LLM hidden size。

  • language_model 负责多模态序列建模。

此外,模型还显式加入机器人状态和动作相关模块:

self.proprio_encoder_robot = SimpleMLP(
    input_dim=self.unified_state_dim,
    hidden_dim=self.action_hidden_size,
    output_dim=self.action_hidden_size,
)

proprio_encoder_robot 将 200 维机器人状态编码为动作专家可处理的 hidden representation。这里的 proprioception 不再是外部附加变量,而是作为 token-like feature 被动作专家插入序列,与图像和文本共同参与模型计算。


4. 视觉特征:从 ViT patch 到 LLM token

视觉特征提取在 extract_feature() 中完成:

def extract_feature(self, pixel_values):
    if self.select_layer == -1:
        vit_embeds = self.vit_model(
            pixel_values=pixel_values,
            output_hidden_states=False,
            return_dict=True,
        ).last_hidden_state
    else:
        vit_embeds = self.vit_model(
            pixel_values=pixel_values,
            output_hidden_states=True,
            return_dict=True,
        ).hidden_states[self.select_layer]

    vit_embeds = vit_embeds[:, 1:, :]

代码会去掉 CLS token,只保留 patch token。随后通过 pixel_shuffle 下采样视觉 token 数量:

h = w = int(vit_embeds.shape[1] ** 0.5)
vit_embeds = vit_embeds.reshape(vit_embeds.shape[0], h, w, -1)
vit_embeds = self.pixel_shuffle(vit_embeds, scale_factor=self.downsample_ratio)
vit_embeds = vit_embeds.reshape(vit_embeds.shape[0], -1, vit_embeds.shape[-1])
vit_embeds = self.connector(vit_embeds)

这里的 connector 默认是 InternVL 风格的 MLP connector:

class InternVLConnector(nn.Module):
    def __init__(self, llm_hidden_size, vit_hidden_size, downsample_ratio):
        super().__init__()
        self.mlp1 = nn.Sequential(
            nn.LayerNorm(vit_hidden_size * int(1 / self.downsample_ratio) ** 2),
            nn.Linear(vit_hidden_size * int(1 / self.downsample_ratio) ** 2, llm_hidden_size),
            nn.GELU(),
            nn.Linear(llm_hidden_size, llm_hidden_size),
        )

因此,图像 token 的处理路径可以概括为:

image -> ViT patch tokens -> pixel shuffle downsampling -> connector -> LLM hidden tokens

5. Forward:packed sequence 与稀疏注意力

Being-H0.5 的 forward 并不是传统的 batch-first dense sequence 处理,而是使用 packed sequence。文本、图像、状态、动作分别拥有自己的 index,然后被写入同一个序列缓冲区:

packed_text_embedding = self.language_model.model.embed_tokens(packed_text_ids)
packed_sequence = packed_text_embedding.new_zeros(size=(sequence_length, self.hidden_size))
packed_sequence[packed_text_indexes] = packed_text_embedding

vit_embeds = self.extract_feature(packed_vit_tokens)
packed_sequence[packed_vit_token_indexes] = vit_embeds

状态也会被编码后写入序列:

packed_state_embeds = self.proprio_encoder_robot(padded_state)
packed_sequence_gen[packed_state_indexes] = packed_state_embeds

这种 packed 方式的优势是可以更灵活地组织不同模态和不同样本长度,减少 padding 浪费。随后代码通过 create_sparse_mask 构造稀疏注意力:

sparse_mask = create_sparse_mask(
    document_lens=sample_lens,
    split_lens=sample_lens if self.config.attn_mode=="causal" else split_lens,
    attn_modes=attn_modes,
    device=device,
)

create_sparse_mask 同时支持 causal attention 和 split 内 full attention:

def create_sparse_mask(document_lens, split_lens, attn_modes, device):
    def causal_mask(b, h, q_idx, kv_idx):
        return q_idx >= kv_idx

    def full_mask(b, h, q_idx, kv_idx):
        return (split_seq_id[q_idx] == split_seq_id[kv_idx]) & (split_seq_id[q_idx] >= 0)

    def sample_mask(b, h, q_idx, kv_idx):
        return document_id[q_idx] == document_id[kv_idx]

    return and_masks(or_masks(causal_mask, full_mask), sample_mask)

这使模型可以在不同模态 split 上使用不同注意力模式,同时保证不同样本之间不会互相 attend。


6. 动作生成:L1 回归与 Flow Matching 两种路径

Being-H0.5 支持两类动作生成方式。

第一类是直接回归。当 use_flow_matching=False 时,模型从 action hidden states 经过 MLP decoder 得到 200 维动作:

self.action_decoder = SimpleMLP(
    input_dim=self.action_hidden_size,
    hidden_dim=self.action_hidden_size,
    output_dim=self.unified_action_dim,
)

训练时使用 masked L1 loss:

predicted_actions = self.action_decoder(
    last_hidden_state_act.reshape(B, self.action_chunk_length, -1)
)
action_loss = F.l1_loss(
    predicted_actions.view(B * self.action_chunk_length, -1),
    padded_action,
    reduction="none",
) * padded_action_mask.float()
action_loss = action_loss.sum() / (padded_action_mask.sum() + 1e-8)

第二类是 Flow Matching。当 use_flow_matching=True 时,模型不直接预测动作值,而是预测从噪声到真实动作的 velocity field:

noise = torch.randn(padded_action_target.shape, device=device, dtype=padded_action_target.dtype)
t = self.sample_time(B, device, padded_action_target.dtype)

noisy_trajectory_3d = (1 - t_reshaped) * noise_reshaped + t_reshaped * target_reshaped
velocity_target = padded_action_target - noise

动作编码器将 noisy action 和 timestep 编成 action features:

action_features_3d = self.action_encoder(noisy_trajectory_3d, t_discretized)
action_features = action_features_3d.view(B * self.action_chunk_length, -1)

随后 decoder 预测 velocity:

predicted_velocity = self.action_decoder(
    last_hidden_state_act.reshape(B, self.action_chunk_length, -1)
)

训练目标是 masked MSE:

masked_mse_loss = F.mse_loss(
    predicted_velocity.view(B * self.action_chunk_length, -1),
    velocity_target,
    reduction="none",
) * padded_action_mask.float()

Flow Matching 的好处是可以把动作 chunk 视为从噪声连续流向目标动作的生成过程,相比单步回归更适合建模多步动作序列的不确定性和连续性。


7. 推理:从随机动作噪声积分到动作 chunk

get_action() 中,如果启用 Flow Matching,推理从随机动作噪声开始:

num_steps = self.num_inference_timesteps
dt = 1.0 / num_steps
action_shape = (B, self.action_chunk_length, self.unified_action_dim)
actions = torch.randn(action_shape, device=device, dtype=packed_text_embedding.dtype)

每个时间步都会:

  1. 将当前 noisy actions 编码为 action features。

  2. 将 action features 插入动作 token 位置。

  3. 通过 language model 得到 action hidden states。

  4. 由 action decoder 预测 velocity。

  5. 使用 Euler 积分更新动作。

核心更新如下:

pred_velocity = self.action_decoder(
    last_hidden_state_act.reshape(B, self.action_chunk_length, -1)
)
actions = actions + dt * pred_velocity

经过若干步后,actions 就从随机噪声演化为模型预测的动作 chunk。


8. RTC:面向真实控制延迟的 chunk 连续性

Being-H0.5 还实现了 Real-Time Chunking 相关逻辑,用于缓解真实部署中动作 chunk 之间的延迟和不连续问题。

训练时,如果启用 use_training_time_rtc,模型会随机采样 delay,并将 chunk 前缀设为干净的 ground-truth 动作:

delays = torch.multinomial(weights, num_samples=B, replacement=True)

for i in range(B):
    delay_i = min(delays[i].item(), self.action_chunk_length)

    if delay_i > 0:
        t_full[start_idx:start_idx + delay_i] = 1.0
        prefix_mask_rtc[start_idx:start_idx + delay_i] = True

    t_full[start_idx + delay_i:end_idx] = t_base[i]

loss 只在 postfix 部分计算:

postfix_mask = ~prefix_mask_rtc.view(-1, 1).expand_as(padded_action_mask)
loss_mask = padded_action_mask.float() * postfix_mask.float()
action_loss = masked_mse_loss.sum() / (loss_mask.sum() + 1e-8)

推理时,如果提供上一段动作 prev_chunk,模型可以把当前 chunk 的前缀锁定为上一段动作,从而提升连续控制稳定性:

actions = torch.where(
    prefix_mask.unsqueeze(-1),
    prev_chunk_padded,
    actions,
)

这类设计体现了 Being-H0.5 面向实际机器人部署的工程取向:模型不仅要在 benchmark 上得分,还要考虑动作块执行、延迟补偿和时序连续性。


9. MPG:用动作流形反向增强观测特征

代码中还提供了 MPG(Manifold-Preserving Gating)增强模块。它的核心思想是计算观测特征与动作特征之间的分布对齐程度,并用门控残差增强观测表示。

MPG 使用 Sliced Wasserstein Distance 近似特征分布之间的 optimal transport cost:

class SlicedWassersteinDistance(nn.Module):
    def forward(self, x, y, seed=None):
        for k in range(self.num_projections):
            direction = torch.randn(feature_dim, device=device, dtype=dtype)
            direction = direction / torch.norm(direction)

            x_proj_1d = torch.matmul(x, direction)
            y_proj_1d = torch.matmul(y, direction)

            x_sorted = torch.sort(x_proj_flat)[0]
            y_sorted = torch.sort(y_proj_flat)[0]
            w2_distance = torch.mean((x_sorted - y_sorted) ** 2)

MPGEnhancement 中,transport cost 会转换成 gate:

gate = exp(-transport_cost / temperature)

当观测特征与动作特征更一致时,增强更强;当二者偏离较大时,增强被抑制。训练和推理中,MPG 都可以作为动作生成的 refinement 机制使用。


10. 部署接口:BeingHPolicy 封装了模型、数据配置和 normalization

实际推理入口是 Being-H05/BeingH/inference/beingh_policy.py 中的 BeingHPolicy

policy = BeingHPolicy(
    model_path="<path-to-checkpoint>",
    data_config_name="<config-name>",
    dataset_name="<dataset-name>",
    embodiment_tag="<robot-tag>",
    instruction_template="<prompt>",
)

这个 wrapper 不只是加载模型,还会处理:

  • 多视角图像预处理。

  • state/action normalization。

  • embodiment tag。

  • metadata variant 选择。

  • RTC 和 MPG 推理参数。

  • 统一动作空间与具体机器人动作之间的映射。

这也是跨 embodiment 模型部署中容易被忽略的一点:模型权重本身并不足以完成推理,必须结合对应数据配置和 normalization metadata,才能把 200 维统一动作还原为具体机器人可执行的动作格式。


总结

Being-H0.5 的技术重点不是单一模型结构,而是一套完整的跨本体 VLA 工程体系。它通过 200 维统一动作空间消解机器人形态差异,通过 packed multimodal sequence 统一视觉、语言、状态和动作,通过 Flow Matching 建模连续动作 chunk,并通过 RTC、MPG 和 metadata 机制面向真实部署细节。

从代码角度看,Being-H0.5 最值得关注的不是某一个网络层,而是“表示对齐”这条主线:机器人状态与动作被对齐到统一 slot,视觉特征被对齐到 LLM hidden space,动作生成被对齐到连续流场,部署阶段再通过 metadata 对齐回具体 embodiment。正是这些对齐机制,使 Being-H0.5 成为一个面向跨本体泛化的 VLA 系统。

点赞收藏
// 评论0
0 / 500
还没有评论,快来抢沙发