* add mot

* update libero example

* translate zh to en

* fix load model from hf

* lint

* lint

---------

Co-authored-by: yangping <yangping@x2robot.com>
This commit is contained in:
suolyer
2026-02-03 11:35:25 +08:00
committed by GitHub
co-authored by yangping
parent 05b6d8dcf7
commit d18fa65fa1
26 changed files with 8509 additions and 1179 deletions
File diff suppressed because it is too large Load Diff
+651
View File
@@ -0,0 +1,651 @@
import torch
import torch.nn as nn
from typing import Optional, Tuple
from transformers.models.qwen2_5_vl.configuration_qwen2_5_vl import Qwen2_5_VLConfig
from transformers.cache_utils import Cache
from transformers.utils import logging
from wall_x.fusions import ops
from wall_x.model.qwen2_5_based.modeling_qwen2_5_vl import (
apply_multimodal_rotary_pos_emb,
)
from flash_attn import flash_attn_func
from transformers.modeling_flash_attention_utils import (
is_flash_attn_greater_or_equal_2_10,
)
from transformers.models.qwen2_5_vl.modeling_qwen2_5_vl import (
Qwen2_5_VLRotaryEmbedding,
repeat_kv,
)
logger = logging.get_logger(__name__)
# def rotate_half(x):
# x1 = x[..., : x.shape[-1] // 2]
# x2 = x[..., x.shape[-1] // 2 :]
# return torch.cat((-x2, x1), dim=-1)
# def apply_multimodal_rotary_pos_emb(q, k, cos, sin, mrope_section, unsqueeze_dim=2):
# mrope_section = mrope_section * 2
# cos_split = torch.cat(
# [m[i % 3] for i, m in enumerate(cos.split(mrope_section, dim=-1))], dim=-1
# ).unsqueeze(unsqueeze_dim)
# sin_split = torch.cat(
# [m[i % 3] for i, m in enumerate(sin.split(mrope_section, dim=-1))], dim=-1
# ).unsqueeze(unsqueeze_dim)
# q_embed = (q * cos_split) + (rotate_half(q) * sin_split)
# k_embed = (k * cos_split) + (rotate_half(k) * sin_split)
# return q_embed, k_embed
class JointQwen2VLAttention(nn.Module):
def __init__(self, config: Qwen2_5_VLConfig, layer_idx: Optional[int] = None):
super().__init__()
self.config = config
self.layer_idx = layer_idx
if layer_idx is None:
logger.warning_once(
f"Instantiating {self.__class__.__name__} without passing `layer_idx` is not recommended and will "
"to errors during the forward call, if caching is used. Please make sure to provide a `layer_idx` "
"when creating this class."
)
if not hasattr(config, "dim_inputs") or not config.dim_inputs:
raise ValueError("Configuration must contain a valid dim_inputs")
self.hidden_size = config.hidden_size
self.num_heads = config.num_attention_heads
self.head_dim = getattr(
config, "head_dim", config.hidden_size // config.num_attention_heads
)
self.num_key_value_heads = config.num_key_value_heads
self.num_key_value_groups = self.num_heads // self.num_key_value_heads
self.max_position_embeddings = config.max_position_embeddings
self.rope_theta = config.rope_theta
self.is_causal = True
self.attention_dropout = config.attention_dropout
self.rope_scaling = config.rope_scaling
self.dim_inputs = config.dim_inputs # Tuple[int, ...]
if config.model_type == "qwen2_5_vl":
bias_qkv = True
else:
bias_qkv = False
self.q_proj_experts = nn.ModuleList(
[
nn.Linear(dim_input, self.num_heads * self.head_dim, bias=bias_qkv)
for dim_input in self.dim_inputs
]
)
self.k_proj_experts = nn.ModuleList(
[
nn.Linear(
dim_input, self.num_key_value_heads * self.head_dim, bias=bias_qkv
)
for dim_input in self.dim_inputs
]
)
self.v_proj_experts = nn.ModuleList(
[
nn.Linear(
dim_input, self.num_key_value_heads * self.head_dim, bias=bias_qkv
)
for dim_input in self.dim_inputs
]
)
self.o_proj_experts = nn.ModuleList(
[
nn.Linear(self.num_heads * self.head_dim, dim_input, bias=False)
for dim_input in self.dim_inputs
]
)
# Rotary embedding init
if config.model_type == "qwen2_5_vl":
self.rotary_emb = Qwen2_5_VLRotaryEmbedding(config=config)
else:
raise NotImplementedError(f"Unsupported model type: {config.model_type}")
def repeat_kv(self, hidden_states: torch.Tensor, n_rep: int) -> torch.Tensor:
"""
Repeat key/value heads along the num_key_value_heads dimension (which is dim=2).
Input shape: (batch, seqlen, num_key_value_heads, head_dim)
Output shape: (batch, seqlen, num_key_value_heads * n_rep, head_dim)
Equivalent to torch.repeat_interleave(x, dim=2, repeats=n_rep)
"""
if n_rep == 1:
return hidden_states
batch, slen, num_key_value_heads, head_dim = hidden_states.shape
hidden_states = hidden_states.unsqueeze(3)
hidden_states = hidden_states.expand(
batch, slen, num_key_value_heads, n_rep, head_dim
)
return hidden_states.reshape(batch, slen, num_key_value_heads * n_rep, head_dim)
def forward(
self,
hidden_states: torch.Tensor,
attention_mask: Optional[torch.Tensor] = None,
position_ids: Optional[torch.LongTensor] = None,
past_key_value: Optional[Cache] = None,
output_attentions: bool = False,
use_cache: bool = False,
cache_position: Optional[torch.LongTensor] = None,
token_types: Optional[torch.LongTensor] = None,
position_embeddings: Optional[Tuple[torch.Tensor, torch.Tensor]] = None,
start_indices: Optional[torch.Tensor] = None,
end_indices: Optional[torch.Tensor] = None,
probs: Optional[torch.Tensor] = None,
row_id_map: Optional[torch.Tensor] = None,
orig_shape: Optional[Tuple[int]] = None,
) -> Tuple[torch.Tensor, Optional[torch.Tensor], Optional[Tuple[torch.Tensor]]]:
if token_types is None:
raise ValueError("token_types can not be None")
if token_types.max() >= len(self.dim_inputs):
raise ValueError(
f"token_types contains invalid expert indices: {token_types.max()}"
)
if self.config.mot_opt:
bsz, q_len, _ = orig_shape
query_states, key_states, value_states = self._generate_qkv_mot_opt(
hidden_states,
token_types,
start_indices,
end_indices,
probs,
row_id_map,
bsz,
q_len,
)
else:
bsz, q_len, _ = hidden_states.size()
masks = [
(token_types == expert_idx)
for expert_idx in range(len(self.dim_inputs))
]
query_states, key_states, value_states = self._generate_qkv(
hidden_states, masks
)
# Because the input can be padded, the absolute sequence length depends on the max position id.
cos, sin = position_embeddings
query_states, key_states = self._apply_rotary_pos_embed(
query_states, key_states, cos, sin, unsqueeze_dim=2
)
query_states = query_states.transpose(1, 2)
key_states = key_states.transpose(1, 2)
value_states = value_states.transpose(1, 2)
if past_key_value is not None:
cache_kwargs = {
"sin": sin,
"cos": cos,
"cache_position": cache_position,
} # Specific to RoPE models
if use_cache:
key_states, value_states = past_key_value.update(
key_states, value_states, self.layer_idx, cache_kwargs
)
else:
past_key_states, past_value_states = past_key_value[self.layer_idx]
key_states = torch.cat([past_key_states, key_states], dim=-2)
value_states = torch.cat([past_value_states, value_states], dim=-2)
key_states = repeat_kv(key_states, self.num_key_value_groups)
value_states = repeat_kv(value_states, self.num_key_value_groups)
causal_mask = attention_mask
if attention_mask is not None:
# Ensure that the attention_mask correctly matches across the head dimension.
if len(attention_mask.shape) == 2: # [batch_size, seq_len]
# Expanded to a causal mask format of [batch_size, 1, seq_len, seq_len]
bsz, seq_len = attention_mask.shape
causal_mask = attention_mask.view(bsz, 1, 1, seq_len).expand(
bsz, 1, seq_len, seq_len
)
elif len(attention_mask.shape) == 3: # [batch_size, seq_len, seq_len]
# add head dimension: [batch_size, 1, seq_len, seq_len]
causal_mask = attention_mask.unsqueeze(1)
elif (
len(attention_mask.shape) == 4
): # [batch_size, num_heads, seq_len, seq_len]
causal_mask = attention_mask
else:
raise ValueError(
f"Unsupported attention_mask dim: {attention_mask.shape}"
)
# convert the attention mask to bool type
causal_mask = causal_mask.to(torch.bool)
# SDPA with memory-efficient backend is currently (torch==2.1.2) bugged with non-contiguous inputs with custom attn_mask,
# Reference: https://github.com/pytorch/pytorch/issues/112577.
if query_states.device.type == "cuda" and attention_mask is not None:
query_states = query_states.contiguous()
key_states = key_states.contiguous()
value_states = value_states.contiguous()
# We dispatch to SDPA's Flash Attention or Efficient kernels via this `is_causal` if statement instead of an inline conditional assignment
# in SDPA to support both torch.compile's dynamic shapes and full graph options. An inline conditional prevents dynamic shapes from compiling.
# The q_len > 1 is necessary to match with AttentionMaskConverter.to_causal_4d that does not create a causal mask in case q_len == 1.
is_causal = True if causal_mask is None and q_len > 1 else False
if q_len == 1:
is_causal = False
causal_mask = torch.ones(
bsz,
1,
1,
key_states.shape[2],
device=hidden_states.device,
dtype=hidden_states.dtype,
).contiguous()
causal_mask = causal_mask.to(torch.bool)
attn_output = torch.nn.functional.scaled_dot_product_attention(
query_states,
key_states,
value_states,
attn_mask=causal_mask,
dropout_p=self.attention_dropout if self.training else 0.0,
is_causal=is_causal,
)
attn_output = attn_output.transpose(1, 2).contiguous()
attn_output = attn_output.view(bsz, q_len, -1)
if self.config.mot_opt:
output = self._generate_output_mot_opt(
attn_output, token_types, start_indices, end_indices
)
else:
output = self._generate_output(attn_output, masks)
return output, None, past_key_value
def _generate_qkv(self, hidden_states, masks):
bsz, q_len, _ = hidden_states.size()
query_states = torch.zeros(
bsz,
q_len,
self.num_heads,
self.head_dim,
device=hidden_states.device,
dtype=hidden_states.dtype,
)
key_states = torch.zeros(
bsz,
q_len,
self.num_key_value_heads,
self.head_dim,
device=hidden_states.device,
dtype=hidden_states.dtype,
)
value_states = torch.zeros(
bsz,
q_len,
self.num_key_value_heads,
self.head_dim,
device=hidden_states.device,
dtype=hidden_states.dtype,
)
# for expert_idx in range(len(self.dim_inputs)):
for expert_idx, (q_proj, k_proj, v_proj, mask) in enumerate(
zip(self.q_proj_experts, self.k_proj_experts, self.v_proj_experts, masks)
):
if not mask.any():
continue
dim_input = self.dim_inputs[expert_idx]
selected_hidden = hidden_states[mask].clone()
q_out = q_proj(selected_hidden[:, :dim_input]).view(
-1, self.num_heads, self.head_dim
)
k_out = k_proj(selected_hidden[:, :dim_input]).view(
-1, self.num_key_value_heads, self.head_dim
)
v_out = v_proj(selected_hidden[:, :dim_input]).view(
-1, self.num_key_value_heads, self.head_dim
)
if self.config.model_type == "qwen3_vl_text":
q_out = self.q_norms[expert_idx](q_out)[0]
k_out = self.k_norms[expert_idx](k_out)[0]
query_states[mask] = q_out
key_states[mask] = k_out
value_states[mask] = v_out
return query_states, key_states, value_states
def _generate_qkv_mot_opt(
self,
hidden_states: torch.Tensor,
experts_indices: torch.Tensor,
start_indices: torch.Tensor,
end_indices: torch.Tensor,
probs: torch.Tensor,
row_id_map: torch.Tensor,
batch_size: int,
seq_length: int,
) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
"""
Generate Q, K, V based on expert-sharded segments (start_indices / end_indices),
then restore them to the original sequence order.
Args:
hidden_states: [total_tokens, hidden_dim], tokens already permuted and grouped by experts
experts_indices: [B, S], expert index for each token
start_indices: start token index for each expert (in the permuted token space)
end_indices: end token index for each expert (in the permuted token space)
probs: probability vector for each token (used for unpermute)
batch_size, seq_length: original batch size and sequence length
Returns:
query_states: [B, num_heads, S, head_dim]
key_states: [B, num_key_value_heads, S, head_dim]
value_states: [B, num_key_value_heads, S, head_dim]
"""
total_tokens, hidden_dim = hidden_states.shape
device, dtype = hidden_states.device, hidden_states.dtype
# Initialize Q/K/V buffers in the permuted token space
q_buffer = torch.zeros(total_tokens, hidden_dim, device=device, dtype=dtype)
k_buffer = torch.zeros(
total_tokens,
self.num_key_value_heads * self.head_dim,
device=device,
dtype=dtype,
)
v_buffer = torch.zeros(
total_tokens,
self.num_key_value_heads * self.head_dim,
device=device,
dtype=dtype,
)
# === Each expert processes its own token slice ===
for expert_idx, (q_proj, k_proj, v_proj) in enumerate(
zip(self.q_proj_experts, self.k_proj_experts, self.v_proj_experts)
):
start, end = start_indices[expert_idx], end_indices[expert_idx]
if start == end:
continue
dim_input = self.dim_inputs[expert_idx]
expert_input = hidden_states[start:end, :dim_input]
# Compute Q/K/V
q_out = q_proj(expert_input)
k_out = k_proj(expert_input)
v_out = v_proj(expert_input)
if getattr(self.config, "model_type", None) == "qwen3_vl_text":
q_out = self.q_norms[expert_idx](q_out)
q_out = q_out[0] if isinstance(q_out, (tuple, list)) else q_out
k_out = self.k_norms[expert_idx](k_out)
k_out = k_out[0] if isinstance(k_out, (tuple, list)) else k_out
q_buffer[start:end] = q_out
k_buffer[start:end] = k_out
v_buffer[start:end] = v_out
# === Restore tokens to the original order ===
# unpermute (using the same unpermute operation)
q_unpermuted = ops.unpermute(q_buffer, row_id_map, probs)
k_unpermuted = ops.unpermute(k_buffer, row_id_map, probs)
v_unpermuted = ops.unpermute(v_buffer, row_id_map, probs)
# === Reshape to final form ===
query_states = q_unpermuted.view(
batch_size, seq_length, self.num_heads, self.head_dim
)
key_states = k_unpermuted.view(
batch_size, seq_length, self.num_key_value_heads, self.head_dim
)
value_states = v_unpermuted.view(
batch_size, seq_length, self.num_key_value_heads, self.head_dim
)
return query_states, key_states, value_states
def _apply_rotary_pos_embed(
self, query_states, key_states, cos, sin, unsqueeze_dim=1
):
if self.config.model_type == "qwen2_5_vl":
query_states, key_states = apply_multimodal_rotary_pos_emb(
query_states.contiguous(),
key_states.contiguous(),
cos.contiguous(),
sin.contiguous(),
self.rope_scaling["mrope_section"],
unsqueeze_dim,
)
else:
raise NotImplementedError(
f"Unsupported model type: {self.config.model_type}"
)
return query_states, key_states
def _generate_output(self, attn_output, masks):
output = torch.zeros(
*attn_output.shape[:2],
self.hidden_size,
device=attn_output.device,
dtype=attn_output.dtype,
)
for expert_idx, (o_proj, mask) in enumerate(zip(self.o_proj_experts, masks)):
if not mask.any():
continue
dim_input = self.dim_inputs[expert_idx]
# Obtain all necessary indexes in a single operation.
mask_indices = mask.nonzero(as_tuple=False)
if mask_indices.numel() == 0:
continue
batch_indices = mask_indices[:, 0]
seq_indices = mask_indices[:, 1]
# Use advanced indexing directly to avoid intermediate tensors.
selected_attn_output = attn_output[batch_indices, seq_indices]
projected_output = o_proj(selected_attn_output)
output[batch_indices, seq_indices, :dim_input] = projected_output
return output
def _generate_output_mot_opt(
self,
attn_output: torch.Tensor,
experts_indices: torch.Tensor,
start_indices: torch.Tensor,
end_indices: torch.Tensor,
) -> torch.Tensor:
"""
Expert-sharded version of attn_output processing based on start_indices / end_indices.
Rearranges the [B, S, H] attn_output according to expert order (permute),
applies the o_proj projection for each expert individually,
and keeps the final output in expert order ([Tokens, Hidden])
instead of restoring it back to [B, S, H].
Args:
attn_output: [B, S, hidden_dim]
experts_indices: [B, S], expert index for each token
start_indices, end_indices: start and end token indices for each expert
(in the permuted token space)
Returns:
output_buffer: [TotalTokens, hidden_dim], arranged in expert order
"""
_, _, hidden_dim = attn_output.shape
device, dtype = attn_output.device, attn_output.dtype
# === 1. Flatten and reorder by expert assignment ===
flat_attn_output = attn_output.view(-1, hidden_dim) # [B*S, H]
flat_expert_indices = experts_indices.reshape(-1) # [B*S]
permuted_inputs, _ = ops.permute(flat_attn_output, flat_expert_indices)
total_tokens = permuted_inputs.shape[0]
# === 2. Initialize output buffer (still in permuted token space) ===
output_buffer = torch.zeros(
total_tokens, hidden_dim, device=device, dtype=dtype
)
# === 3. Each expert processes its own token segment independently ===
for expert_idx, o_proj in enumerate(self.o_proj_experts):
start, end = start_indices[expert_idx], end_indices[expert_idx]
if start == end:
continue
dim_input = self.dim_inputs[expert_idx]
expert_input = permuted_inputs[start:end] # [N_e, dim_input]
expert_output = o_proj(expert_input) # [N_e, hidden_dim]
# Write results into the buffer (overwrite only valid dimension region)
output_buffer[start:end, :dim_input] = expert_output[:, :dim_input]
# === 4. Return the output ordered by expert sequence ===
return output_buffer
class JointQwen2VLFlashAttention(JointQwen2VLAttention):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# TODO: Should be removed once Flash Attention for RoCm is bumped to 2.1.
# flash_attn<2.1 generates top-left aligned causal mask, while what is needed here is bottom-right alignement, that was made default for flash_attn>=2.1. This attribute is used to handle this difference. Reference: https://github.com/Dao-AILab/flash-attention/releases/tag/v2.1.0.
# Beware that with flash_attn<2.1, using q_seqlen != k_seqlen (except for the case q_seqlen == 1) produces a wrong mask (top-left).
self._flash_attn_uses_top_left_mask = not is_flash_attn_greater_or_equal_2_10()
def forward(
self,
hidden_states: torch.Tensor,
attention_mask: Optional[torch.Tensor] = None,
position_ids: Optional[torch.LongTensor] = None,
past_key_value: Optional[Cache] = None,
output_attentions: bool = False,
use_cache: bool = False,
cache_position: Optional[torch.LongTensor] = None,
token_types: Optional[torch.LongTensor] = None,
position_embeddings: Optional[
Tuple[torch.Tensor, torch.Tensor]
] = None, # necessary, but kept here for BC
start_indices: Optional[torch.Tensor] = None,
end_indices: Optional[torch.Tensor] = None,
probs: Optional[torch.Tensor] = None,
row_id_map: Optional[torch.Tensor] = None,
orig_shape: Optional[Tuple[int]] = None,
) -> Tuple[torch.Tensor, Optional[torch.Tensor], Optional[Tuple[torch.Tensor]]]:
if token_types is None:
raise ValueError("token_types cannot be empty")
if self.config.mot_opt:
bsz, q_len, _ = orig_shape
query_states, key_states, value_states = self._generate_qkv_mot_opt(
hidden_states,
token_types,
start_indices,
end_indices,
probs,
row_id_map,
bsz,
q_len,
)
else:
bsz, q_len, _ = hidden_states.size()
masks = [
(token_types == expert_idx)
for expert_idx in range(len(self.dim_inputs))
]
query_states, key_states, value_states = self._generate_qkv(
hidden_states, masks
)
# Because the input can be padded, the absolute sequence length depends on the max position id.
cos, sin = position_embeddings
query_states, key_states = self._apply_rotary_pos_embed(
query_states, key_states, cos, sin, unsqueeze_dim=2
)
if past_key_value is not None:
cache_kwargs = {
"sin": sin,
"cos": cos,
"cache_position": cache_position,
} # Specific to RoPE models
key_states, value_states = past_key_value.update(
key_states.transpose(1, 2),
value_states.transpose(1, 2),
self.layer_idx,
cache_kwargs,
)
key_states, value_states = key_states.transpose(
1, 2
), value_states.transpose(1, 2)
dropout_rate = 0.0 if not self.training else self.attention_dropout
# In PEFT, usually we cast the layer norms in float32 for training stability reasons
# therefore the input hidden states gets silently casted in float32. Hence, we need
# cast them back in float16 just to be sure everything works as expected.
input_dtype = query_states.dtype
if input_dtype == torch.float32:
if torch.is_autocast_enabled():
target_dtype = torch.get_autocast_gpu_dtype()
# Handle the case where the model is quantized
elif hasattr(self.config, "_pre_quantization_dtype"):
target_dtype = self.config._pre_quantization_dtype
else:
target_dtype = self.q_proj.weight.dtype
logger.warning_once(
f"The input hidden states seems to be silently casted in float32, this might be related to"
f" the fact you have upcasted embedding or layer norm layers in float32. We will cast back the input in"
f" {target_dtype}."
)
query_states = query_states.to(target_dtype)
key_states = key_states.to(target_dtype)
value_states = value_states.to(target_dtype)
attn_output = flash_attn_func(
query_states,
key_states,
value_states,
dropout_rate,
softmax_scale=None,
causal=self.is_causal,
)
attn_output = attn_output.reshape(bsz, q_len, self.hidden_size).contiguous()
if self.config.mot_opt:
output = self._generate_output_mot_opt(
attn_output, token_types, start_indices, end_indices
)
else:
output = self._generate_output(attn_output, masks)
return output, None, past_key_value
JOINT_QWEN_ATTENTION_CLASSES = {
"eager": JointQwen2VLAttention,
"flash_attention_2": JointQwen2VLFlashAttention,
"sdpa": JointQwen2VLAttention,
}
+319
View File
@@ -0,0 +1,319 @@
import torch
import os
import numpy as np
from transformers import AutoProcessor
from wall_x.model.action_head import Normalizer
def update_model_config(train_config, model_config):
model_config.use_state_string_representation = train_config["data"].get(
"use_state_string_representation", False
)
model_config.flow_loss_weight = train_config.get("flow_loss_weight", 1.0)
model_config.dof_config = train_config["dof_config"]
model_config.agent_pos_config = train_config["agent_pos_config"]
model_config.action_horizon_flow = train_config["data"].get(
"action_horizon_flow", 32
)
if train_config.get("_attn_implementation", None) is not None:
model_config._attn_implementation = train_config["_attn_implementation"]
return model_config
def load_wallx_processors(config):
processor = AutoProcessor.from_pretrained(config["processor_path"], use_fast=True)
# pad side = left
processor.tokenizer.padding_side = "left"
new_tokens = ["<|propri|>", "<|action|>"]
# special_tokens = []
action_tokenizer_type = config.get("action_tokenizer_type", None)
if action_tokenizer_type == "fast":
train_action_tokenizer = AutoProcessor.from_pretrained(
config["action_tokenizer_path"], trust_remote_code=True
)
val_action_tokenizer = AutoProcessor.from_pretrained(
config["action_tokenizer_path"], trust_remote_code=True
)
new_tokens += [
f"<|action_token_{i}|>" for i in range(train_action_tokenizer.vocab_size)
]
elif action_tokenizer_type == "spatialvla":
raise NotImplementedError("SpatialActionTokenizer is not implemented")
else:
train_action_tokenizer = None
val_action_tokenizer = None
num_added_tokens = processor.tokenizer.add_tokens(new_tokens)
if action_tokenizer_type and train_action_tokenizer.vocab_size > 0:
action_mapper = {}
for i in range(train_action_tokenizer.vocab_size):
token = f"<|action_token_{i}|>"
token_id = processor.tokenizer.convert_tokens_to_ids(token)
action_mapper[token_id] = i
else:
action_mapper = None
return {
"processor": processor,
"train_action_tokenizer": train_action_tokenizer,
"val_action_tokenizer": val_action_tokenizer,
"action_mapper": action_mapper,
"num_added_tokens": num_added_tokens,
}
def register_normalizers(config, model_path):
# if config.get("customized_action_statistic_dof", None):
# action_statistic_dof = json.load(open(config["customized_action_statistic_dof"], "r"))
# else:
# action_statistic_dof = default_action_statistic_dof
action_statistic_dof = None
if os.path.exists(model_path + "/normalizer_action.pth"):
print(
"Loading normalizer_action from checkpoint",
model_path + "/normalizer_action.pth",
flush=True,
)
normalizer_action = Normalizer.from_ckpt(model_path + "/normalizer_action.pth")
else:
normalizer_action = Normalizer(
action_statistic_dof,
config["dof_config"],
min_key=config.get("min_key", "min"),
delta_key=config.get("delta_key", "delta"),
)
# print("action_statistic_dof",action_statistic_dof)
if os.path.exists(model_path + "/normalizer_propri.pth"):
print(
"Loading normalizer_propri from checkpoint",
model_path + "/normalizer_propri.pth",
flush=True,
)
normalizer_propri = Normalizer.from_ckpt(model_path + "/normalizer_propri.pth")
else:
normalizer_propri = Normalizer(
action_statistic_dof,
config["agent_pos_config"],
min_key=config.get("min_key", "min"),
delta_key=config.get("delta_key", "delta"),
)
return normalizer_action, normalizer_propri
def find_first_last_ones(tensor):
"""
Input: a tensor of shape (bs, seq_len) containing 0s and 1s
Output: (first_indices, last_indices), each of shape (bs,)
where first_indices[i] is the index of the first 1 in the i-th batch, or -1 if none exists.
last_indices[i] is the index of the last 1 in the i-th batch, or -1 if none exists.
"""
bs, seq_len = tensor.shape
masks = tensor == 1
has_ones = masks.any(dim=1)
first = torch.full((bs,), -1, dtype=torch.long, device=tensor.device)
last = first.clone()
first[has_ones] = torch.argmax(masks[has_ones].float(), dim=1)
flipped_masks = masks.flip(dims=[1])
last_argmax = torch.argmax(flipped_masks[has_ones].float(), dim=1)
last[has_ones] = seq_len - 1 - last_argmax
return first, last
def flashmask_to_densemask(startend_row_indices, dtype, causal=True):
if startend_row_indices is None:
return None
bz, num_head, seq_len, bound_num = startend_row_indices.shape
m = np.ones((bz, num_head, seq_len, seq_len), dtype=dtype)
has_end = (causal and bound_num == 2) or ((not causal) and bound_num == 4)
for bi in range(bz):
for hi in range(num_head):
for j in range(seq_len):
downstart = startend_row_indices[bi, hi, j, 0]
if has_end:
downend = startend_row_indices[bi, hi, j, 1]
m[bi, hi, downstart:downend, j] = 0
else:
m[bi, hi, downstart:, j] = 0
if causal:
m[bi, hi, :j, j] = 0
else:
if has_end:
upstart = startend_row_indices[bi, hi, j, 2]
upend = startend_row_indices[bi, hi, j, 3]
m[bi, hi, upstart:upend, j] = 0
else:
upend = startend_row_indices[bi, hi, j, 1]
m[bi, hi, :upend, j] = 0
return m
def num_floating_point_operations(
args,
batch_size: int,
num_lang_tokens: int,
num_action_tokens: int,
vision_seq_length: int = 756,
):
"""
Accurately estimate the training FLOPs of Transformer + MoE + MoT + Vision.
Supported:
- expert0 = language tokens
- expert1 = action tokens
- MoE MLP (2 experts)
- MoT Attention (2 experts)
- GQA
- Vision Transformer (full+window attention)
"""
assert args.num_experts == 2, "The current model only supports 2 experts."
dim_lang, dim_act = args.dim_inputs
# Number of tokens per layer (flattened across batch)
N_lang = batch_size * num_lang_tokens
N_action = batch_size * num_action_tokens
N_total = N_lang + N_action # Used for non-MoT attention
# ================================================================
# Text MLP FLOPs
# ================================================================
hidden_size = args.hidden_size
ffn_hidden_size = args.intermediate_size
num_layers = args.num_hidden_layers
use_moe_mlp = getattr(args, "mlp_moe", False)
# ---------- Forward-only MLP FLOPs ----------
def forward_mlp_flops(N, d_in, d_ff):
"""
SwiGLU forward:
gate = x @ W1 (2*N*d_in*d_ff)
up = x @ W2 (2*N*d_in*d_ff)
act = silu + mul (~2*N*d_ff)
down = h @ W3 (2*N*d_ff*d_in)
Forward ≈ 4*N*d_in*d_ff + 2*N*d_ff*d_in = 6*N*d_in*d_ff + 2*N*d_ff
"""
return 6 * N * d_in * d_ff + 2 * N * d_ff
if not use_moe_mlp:
# Dense MLP
F_fwd = forward_mlp_flops(N_total, hidden_size, ffn_hidden_size)
total_mlp_flops_text = 3 * num_layers * F_fwd # <-- training FLOPs
else:
# MoE: expert0(language) + expert1(action)
hid_lang = args.experts[0]["intermediate_size"]
hid_act = args.experts[1]["intermediate_size"]
F_lang_fwd = forward_mlp_flops(N_lang, dim_lang, hid_lang)
F_act_fwd = forward_mlp_flops(N_action, dim_act, hid_act)
total_mlp_flops_text = 3 * num_layers * (F_lang_fwd + F_act_fwd)
# ================================================================
# Text Attention FLOPs
# ================================================================
num_heads = args.num_attention_heads
num_kv = args.num_key_value_heads
H = hidden_size
B = batch_size
S = num_lang_tokens + num_action_tokens
N = B * S
use_mot = getattr(args, "attention_moe", False)
# ---------- attention matmul ----------
F_matmul_fwd = 4 * B * (S**2) * H
if not use_mot:
# -------- GQA + QKV / O --------
F_q_fwd = 2 * N * H * H
F_kv_fwd = 4 * N * H * H * (num_kv / num_heads)
F_o_fwd = 2 * N * H * H
F_attn_fwd = F_q_fwd + F_kv_fwd + F_o_fwd + F_matmul_fwd
else:
# -------- MoT: expert0 + expert1 QKV --------
F_lang_qkv = N_lang * dim_lang * H * (2 + 4 * num_kv / num_heads)
F_act_qkv = N_action * dim_act * H * (2 + 4 * num_kv / num_heads)
F_attn_fwd = F_lang_qkv + F_act_qkv + F_matmul_fwd
# Training FLOPs
total_attn_flops_text = 3 * num_layers * F_attn_fwd
# ================================================================
# Logits projection FLOPs
# ================================================================
vocab_size = getattr(args, "padded_vocab_size", args.vocab_size)
F_logits_fwd = 2 * N * H * vocab_size
total_logits_flops = 3 * F_logits_fwd
total_text_flops = total_mlp_flops_text + total_attn_flops_text + total_logits_flops
# ================================================================
# Vision Transformer FLOPs
# ================================================================
total_vision_flops = 0
if hasattr(args, "vision_config") and vision_seq_length is not None:
vcfg = args.vision_config
Bv = batch_size
Sv = vision_seq_length
Nv = Bv * Sv
Hv = vcfg.hidden_size
Iv = vcfg.intermediate_size
num_heads_v = vcfg.num_heads
window_size = vcfg.window_size
out_hidden = vcfg.out_hidden_size
depth_v = vcfg.depth
fullatt = set(vcfg.fullatt_block_indexes)
num_full = len(fullatt)
num_local = depth_v - num_full
# ---------- forward FLOPs ----------
def forward_vit_mlp(N, H, Inner):
return 6 * N * H * Inner + 2 * N * Inner
F_mlp_v = forward_vit_mlp(Nv, Hv, Iv)
F_qkv_v = 6 * Nv * Hv * Hv
F_o_v = 2 * Nv * Hv * Hv
F_full = 4 * Bv * (Sv**2) * Hv
num_windows = Sv / window_size
F_win = 4 * Bv * num_windows * (window_size**2) * (Hv / num_heads_v)
F_block_full_fwd = F_mlp_v + F_qkv_v + F_o_v + F_full
F_block_local_fwd = F_mlp_v + F_qkv_v + F_o_v + F_win
# ---------- train FLOPs ----------
total_vision_flops = 3 * (
num_full * F_block_full_fwd + num_local * F_block_local_fwd
)
# merger
total_vision_flops += 3 * (2 * Nv * Hv * out_hidden)
# ================================================================
# TOTAL TRAIN FLOPs
# ================================================================
return total_text_flops + total_vision_flops
@@ -21,6 +21,9 @@ class Qwen2_5_VLVisionConfig(PretrainedConfig):
window_size=112,
out_hidden_size=3584,
fullatt_block_indexes=[7, 15, 23, 31],
initializer_range=0.02,
_attn_implementation="flash_attention_2",
attn_deterministic=False,
**kwargs,
):
super().__init__(**kwargs)
@@ -38,6 +41,9 @@ class Qwen2_5_VLVisionConfig(PretrainedConfig):
self.window_size = window_size
self.fullatt_block_indexes = fullatt_block_indexes
self.out_hidden_size = out_hidden_size
self.initializer_range = initializer_range
self._attn_implementation = _attn_implementation
self.attn_deterministic = attn_deterministic
class Qwen2_5_VLConfig(PretrainedConfig):
@@ -169,6 +175,8 @@ class Qwen2_5_VLConfig(PretrainedConfig):
self,
vocab_size=152064,
hidden_size=8192,
action_hidden_size=2048,
state_hidden_size=2048,
intermediate_size=29568,
num_hidden_layers=80,
num_attention_heads=64,
@@ -193,16 +201,25 @@ class Qwen2_5_VLConfig(PretrainedConfig):
dim_inputs=(1536, 1536),
attention_moe=False,
mlp_moe=False,
norm_moe=False,
mot_opt=False,
flow_loss_weight=10,
use_state_string_representation=False,
use_adarms=False,
proj_with_mask=True,
use_flow_action_expert=True,
adarms_cond_dim=None,
action_horizon_flow=32,
causal_action_attention_mask=False,
use_x_pred=False,
attn_deterministic=False,
**kwargs,
):
if isinstance(vision_config, dict):
self.vision_config = self.sub_configs["vision_config"](**vision_config)
elif vision_config is None:
self.vision_config = self.sub_configs["vision_config"]()
self.vocab_size = vocab_size
self.max_position_embeddings = max_position_embeddings
self.hidden_size = hidden_size
self.action_hidden_size = action_hidden_size
self.state_hidden_size = state_hidden_size
self.intermediate_size = intermediate_size
self.num_hidden_layers = num_hidden_layers
self.num_attention_heads = num_attention_heads
@@ -230,6 +247,19 @@ class Qwen2_5_VLConfig(PretrainedConfig):
self.dim_inputs = tuple(dim_inputs)
self.attention_moe = attention_moe
self.mlp_moe = mlp_moe
self.norm_moe = norm_moe
self.mot_opt = mot_opt
self.flow_loss_weight = flow_loss_weight
self.use_state_string_representation = use_state_string_representation
self.use_adarms = use_adarms
self.adarms_cond_dim = adarms_cond_dim
self.proj_with_mask = proj_with_mask
self.use_flow_action_expert = use_flow_action_expert
self.action_horizon_flow = action_horizon_flow
self.causal_action_attention_mask = causal_action_attention_mask
self.use_x_pred = use_x_pred
self.attn_deterministic = attn_deterministic
# Validate the correctness of rotary position embeddings parameters
# BC: if there is a 'type' field, move it to 'rope_type'.
@@ -244,5 +274,12 @@ class Qwen2_5_VLConfig(PretrainedConfig):
super().__init__(tie_word_embeddings=tie_word_embeddings, **kwargs)
# move vision config initialization after super init to avoid recursively set in latest transformers version
# TODO: make it better
if isinstance(vision_config, dict):
self.vision_config = self.sub_configs["vision_config"](**vision_config)
elif vision_config is None:
self.vision_config = self.sub_configs["vision_config"]()
__all__ = ["Qwen2_5_VLConfig"]
@@ -120,23 +120,68 @@ class Qwen2_5_VisionRotaryEmbedding(nn.Module):
class Qwen2RMSNorm(nn.Module):
def __init__(self, hidden_size, eps=1e-6):
def __init__(
self, hidden_size: int, eps: float = 1e-6, cond_dim: Optional[int] = None
):
"""
Qwen2RMSNorm is equivalent to T5LayerNorm
Qwen2RMSNorm with optional conditional input support, equivalent to T5LayerNorm
"""
super().__init__()
self.weight = nn.Parameter(torch.ones(hidden_size))
self.variance_epsilon = eps
self.hidden_size = hidden_size
self.cond_dim = cond_dim
def forward(self, hidden_states):
# Dense layer for adaptive normalization (if cond_dim is provided)
if cond_dim is not None:
self.dense = nn.Linear(cond_dim, hidden_size * 3, bias=True)
nn.init.zeros_(self.dense.weight)
else:
self.dense = None
self.weight = nn.Parameter(torch.ones(hidden_size))
def _norm(self, x):
# Compute variance in float32 for numerical stability
variance = x.pow(2).mean(-1, keepdim=True)
# Compute normalization
normed_inputs = x * torch.rsqrt(variance + self.variance_epsilon)
return normed_inputs
def forward(self, hidden_states, cond=None):
input_dtype = hidden_states.dtype
hidden_states = hidden_states.to(torch.float32)
variance = hidden_states.pow(2).mean(-1, keepdim=True)
hidden_states = hidden_states * torch.rsqrt(variance + self.variance_epsilon)
return self.weight * hidden_states.to(input_dtype)
normed_inputs = self._norm(hidden_states)
if cond is None or self.dense is None:
# Regular RMSNorm
normed_inputs = self.weight * normed_inputs
return normed_inputs.to(input_dtype), None
# Adaptive RMSNorm
if cond.shape[-1] != self.cond_dim:
raise ValueError(
f"Expected cond dimension {self.cond_dim}, got {cond.shape[-1]}"
)
# Compute modulation parameters
cond = cond.to(dtype=self.dense.weight.dtype)
modulation = self.dense(cond)
if len(hidden_states.shape) == 3: # [batch, seq, features]
modulation = modulation.unsqueeze(1)
scale, shift, gate = torch.chunk(modulation, 3, dim=-1)
# Apply adaptive normalization
normed_inputs = normed_inputs * (1 + scale.to(torch.float32)) + shift.to(
torch.float32
)
return normed_inputs.to(input_dtype), gate.to(input_dtype)
def extra_repr(self):
return f"{tuple(self.weight.shape)}, eps={self.variance_epsilon}"
repr_str = f"{tuple(self.weight.shape)}, eps={self.variance_epsilon}"
if self.dense is not None:
repr_str += f", adaptive=True, cond_dim={self.cond_dim}"
return repr_str
class Qwen2_5_VLPatchMerger(nn.Module):
@@ -151,7 +196,7 @@ class Qwen2_5_VLPatchMerger(nn.Module):
)
def forward(self, x: torch.Tensor) -> torch.Tensor:
x = self.mlp(self.ln_q(x).view(-1, self.hidden_size))
x = self.mlp(self.ln_q(x)[0].view(-1, self.hidden_size))
return x
@@ -381,13 +426,13 @@ class Qwen2_5_VLVisionBlock(nn.Module):
position_embeddings: Optional[Tuple[torch.Tensor, torch.Tensor]] = None,
) -> torch.Tensor:
hidden_states = hidden_states + self.attn(
self.norm1(hidden_states),
self.norm1(hidden_states)[0],
cu_seqlens=cu_seqlens,
max_seqlen=max_seqlen,
rotary_pos_emb=rotary_pos_emb,
position_embeddings=position_embeddings,
)
hidden_states = hidden_states + self.mlp(self.norm2(hidden_states))
hidden_states = hidden_states + self.mlp(self.norm2(hidden_states)[0])
return hidden_states
@@ -1082,16 +1127,41 @@ class Qwen2_5_VLSdpaAttention(Qwen2_5_VLAttention):
"cos": cos,
"cache_position": cache_position,
} # Specific to RoPE models
key_states, value_states = past_key_value.update(
key_states, value_states, self.layer_idx, cache_kwargs
)
if use_cache:
key_states, value_states = past_key_value.update(
key_states, value_states, self.layer_idx, cache_kwargs
)
else:
past_key_states, past_value_states = past_key_value[self.layer_idx]
key_states = torch.cat([past_key_states, key_states], dim=-2)
value_states = torch.cat([past_value_states, value_states], dim=-2)
key_states = repeat_kv(key_states, self.num_key_value_groups)
value_states = repeat_kv(value_states, self.num_key_value_groups)
causal_mask = attention_mask
if attention_mask is not None: # no matter the length, we just slice it
causal_mask = attention_mask[:, :, :, : key_states.shape[-2]]
# Ensure the attention_mask correctly matches the head dimension
if len(attention_mask.shape) == 2: # [batch_size, seq_len]
# Expand to [batch_size, 1, seq_len, seq_len] causal mask format
bsz, seq_len = attention_mask.shape
causal_mask = attention_mask.view(bsz, 1, 1, seq_len).expand(
bsz, 1, seq_len, seq_len
)
elif len(attention_mask.shape) == 3: # [batch_size, seq_len, seq_len]
# Add head dimension: [batch_size, 1, seq_len, seq_len]
causal_mask = attention_mask.unsqueeze(1)
elif (
len(attention_mask.shape) == 4
): # [batch_size, num_heads, seq_len, seq_len]
causal_mask = attention_mask
else:
raise ValueError(
f"Unsupported attention_mask dim: {attention_mask.shape}"
)
# Convert the attention mask to boolean type
causal_mask = causal_mask.to(torch.bool)
# SDPA with memory-efficient backend is currently (torch==2.1.2) bugged with non-contiguous inputs with custom attn_mask,
# Reference: https://github.com/pytorch/pytorch/issues/112577.
@@ -1105,6 +1175,18 @@ class Qwen2_5_VLSdpaAttention(Qwen2_5_VLAttention):
# The q_len > 1 is necessary to match with AttentionMaskConverter.to_causal_4d that does not create a causal mask in case q_len == 1.
is_causal = True if causal_mask is None and q_len > 1 else False
if q_len == 1:
is_causal = False
causal_mask = torch.ones(
bsz,
1,
1,
key_states.shape[2],
device=hidden_states.device,
dtype=hidden_states.dtype,
).contiguous()
causal_mask = causal_mask.to(torch.bool)
attn_output = torch.nn.functional.scaled_dot_product_attention(
query_states,
key_states,
File diff suppressed because it is too large Load Diff
+987
View File
@@ -0,0 +1,987 @@
import torch
import torch.nn as nn
import torch.utils.checkpoint as cp
from torch.distributed.fsdp import MixedPrecision as MP
from torch.distributed.fsdp import FullyShardedDataParallel as FSDP
from wall_x.fusions import ops
from peft import LoraConfig, get_peft_model
from typing import Optional, Union, Dict
from packaging import version
from transformers import GenerationMixin
from transformers.activations import ACT2FN
from transformers.modeling_utils import AttentionInterface
from transformers.utils import logging, is_torch_xla_available
from wall_x.model.action_head import ActionProcessor
from wall_x.model.model_utils import find_first_last_ones
ALL_ATTENTION_FUNCTIONS: AttentionInterface = AttentionInterface()
logger = logging.get_logger(__name__)
X2ROBOT_ATTENTION_FUNCTIONS = []
ATTENTION_TYPES_WITH_2D_MASK = [
"sdpa",
]
ATTENTION_TYPES_WITH_FLASH_MASK = []
class TokenTypeRouter(nn.Module):
def __init__(self, num_experts: int):
super().__init__()
self.num_experts = num_experts
def forward(self, token_types: torch.Tensor) -> torch.Tensor:
"""
Assigns tokens to different experts based on `token_type`.
Args:
token_types (torch.Tensor): A tensor of shape (batch_size, seq_length) representing the type of each token.
Returns:
experts_indices (torch.Tensor): A tensor of shape (batch_size, seq_length) representing the expert index assigned to each token.
"""
experts_indices = token_types % self.num_experts
return experts_indices
class BlockSparseMLP(nn.Module):
def __init__(self, config, use_selective_recompute: bool = False):
super().__init__()
self.hidden_size = config["hidden_size"]
self.intermediate_size = config["intermediate_size"]
self.hidden_act = config["hidden_act"]
self.use_selective_recompute = use_selective_recompute
self.gate_proj = nn.Linear(self.hidden_size, self.intermediate_size, bias=False)
self.up_proj = nn.Linear(self.hidden_size, self.intermediate_size, bias=False)
self.down_proj = nn.Linear(self.intermediate_size, self.hidden_size, bias=False)
self.act_fn = ACT2FN[self.hidden_act]
def _full_mlp(self, hidden_state):
gate_out = self.gate_proj(hidden_state)
up_out = self.up_proj(hidden_state)
act_out = self.act_fn(gate_out) * up_out
return self.down_proj(act_out)
def forward(self, hidden_state):
if self.use_selective_recompute:
# Perform checkpoint recalculation for the entire expert MLP.
return cp.checkpoint(
self._full_mlp,
hidden_state,
use_reentrant=False,
)
else:
return self._full_mlp(hidden_state)
class SparseMoeBlock(nn.Module):
def __init__(self, config, num_experts: int, use_selective_recompute: bool = False):
super().__init__()
self.num_experts = num_experts
self.use_selective_recompute = use_selective_recompute
# Pass the `use_selective_recompute` parameter to each expert.
self.experts = nn.ModuleList(
[
BlockSparseMLP(
config.experts[i], use_selective_recompute=use_selective_recompute
)
for i in range(num_experts)
]
)
if not hasattr(config, "dim_inputs") or not config.dim_inputs:
raise ValueError("Configuration must contain a valid dim_inputs")
self.dim_inputs = config.dim_inputs
self.permuted = config.mot_opt
def forward(
self,
hidden_states: torch.Tensor,
experts_indices: torch.Tensor,
start_indices: torch.Tensor,
end_indices: torch.Tensor,
) -> torch.Tensor:
if self.permuted:
permuted_inputs = hidden_states
else:
batch_size, seq_length, hidden_dim = hidden_states.shape
flat_hidden = hidden_states.reshape(-1, hidden_dim)
experts_indices = experts_indices.reshape(-1)
probs = torch.ones_like(experts_indices, dtype=torch.float32).reshape(-1, 1)
permuted_inputs, row_id_map = ops.permute(flat_hidden, experts_indices)
# buffer
final_output = torch.zeros_like(permuted_inputs)
# Expert forward contain selective recompute
for expert_idx, expert in enumerate(self.experts):
start, end = start_indices[expert_idx], end_indices[expert_idx]
if start == end:
continue
dim_input = self.dim_inputs[expert_idx]
expert_input = permuted_inputs[start:end, :dim_input]
partial_output = expert(expert_input)
final_output[start:end, :dim_input] = partial_output[:, :dim_input]
if self.permuted:
return final_output
else:
final_output = ops.unpermute(final_output, row_id_map, probs)
return final_output.reshape(batch_size, seq_length, hidden_dim)
class ActionModelMixMin:
# config: Qwen2_5_VLConfig
action_preprocessor: ActionProcessor
router: TokenTypeRouter
moe: SparseMoeBlock
def __init__(self, config, action_preprocessor, router, moe):
self.config = config
self.action_preprocessor = action_preprocessor
self.router = router
self.moe = moe
self._mot_opt_warned = False
def set_normalizer(self, normalizer_action, normalizer_propri):
if hasattr(self, "action_preprocessor"):
self.action_preprocessor.set_normalizer(
normalizer_action, normalizer_propri
)
else:
logger.warning(
"ActionModelMixMin.set_normalizer is called but action_preprocessor is not set"
)
def _apply_mlp_moe(self, hidden_states, token_types, start_indices, end_indices):
if self.config.mlp_moe:
hidden_states = self.moe(
hidden_states, token_types, start_indices, end_indices
)
else:
hidden_states = self.mlp(hidden_states)
return hidden_states
def _apply_norm_moe(
self,
hidden_states,
token_types,
adarms_conds,
norms, # list of norm layers (expert-wise)
norm, # shared norm if not norm_moe
start_indices=None,
end_indices=None,
use_selective_recompute=False,
):
"""
MoE-aware LayerNorm with optional selective activation recomputation.
Only activation math is recomputed. No GEMM is recomputed.
Safe for FSDP (use_reentrant=False).
"""
gate = None
gate_mask = None
# -------------------------
# Case 1: norm_moe=True (expert-wise norm)
# -------------------------
if self.config.norm_moe:
# ---------------------------------------------------------
# Case 1A: mot_opt=True (segments assigned by start/end)
# ---------------------------------------------------------
if self.config.mot_opt:
new_hidden_states = torch.zeros_like(hidden_states)
for expert_idx, expert_norm in enumerate(norms):
start = start_indices[expert_idx]
end = end_indices[expert_idx]
if start == end:
continue
dim_input = self.config.dim_inputs[expert_idx]
selected = hidden_states[start:end] # [K, D]
# ====== reshape if adarms on flow expert ======
if self.config.use_adarms and expert_idx == 1:
selected = selected.view(
-1,
self.config.action_horizon_flow,
selected.shape[-1],
)
input_slice = selected[:, :, :dim_input]
cond = adarms_conds[expert_idx]
else:
input_slice = selected[:, :dim_input]
cond = adarms_conds[expert_idx]
if use_selective_recompute:
def norm_chunk(t_x, t_cond, expert_norm=expert_norm):
if t_cond is None or (
isinstance(t_cond, torch.Tensor) and t_cond.numel() == 0
):
out, _ = expert_norm(t_x)
else:
out, _ = expert_norm(t_x, t_cond)
return out
cond_for_cp = (
cond
if cond is not None
else torch.empty(0, device=input_slice.device)
)
processed = cp.checkpoint(
norm_chunk,
input_slice,
cond_for_cp,
use_reentrant=False,
)
else:
processed, gate = expert_norm(input_slice, cond)
# reshape back if needed
if self.config.use_adarms and expert_idx == 1:
processed = processed.view(-1, dim_input)
new_hidden_states[start:end, :dim_input] = processed.to(
hidden_states.dtype
)
hidden_states = new_hidden_states
# ---------------------------------------------------------
# Case 1B: mot_opt=False (token-level mask)
# ---------------------------------------------------------
else:
new_hidden_states = torch.zeros_like(hidden_states)
B, S, D = hidden_states.shape
for expert_idx, expert_norm in enumerate(norms):
mask = token_types == expert_idx
if mask.sum() == 0:
continue
dim_input = self.config.dim_inputs[expert_idx]
selected = hidden_states[mask] # [K, D]
if self.config.use_adarms and expert_idx == 1:
gate_mask = mask
selected = selected.view(
-1,
self.config.action_horizon_flow,
selected.shape[-1],
)
input_slice = selected[:, :, :dim_input]
cond = adarms_conds[expert_idx]
else:
input_slice = selected[:, :dim_input]
cond = adarms_conds[expert_idx]
if use_selective_recompute:
def norm_chunk(t_x, t_cond, expert_norm=expert_norm):
if t_cond is None or (
isinstance(t_cond, torch.Tensor) and t_cond.numel() == 0
):
out, _ = expert_norm(t_x)
else:
out, _ = expert_norm(t_x, t_cond)
return out
cond_for_cp = (
cond
if cond is not None
else torch.empty(0, device=input_slice.device)
)
processed = cp.checkpoint(
norm_chunk,
input_slice,
cond_for_cp,
use_reentrant=False,
)
else:
processed, gate = expert_norm(input_slice, cond)
if self.config.use_adarms and expert_idx == 1:
processed = processed.view(-1, dim_input)
# scatter back
b_id, s_id = torch.where(mask)
new_hidden_states[b_id, s_id, :dim_input] = processed.to(
hidden_states.dtype
)
hidden_states = new_hidden_states
# -------------------------
# Case 2: norm_moe=False (single LN)
# -------------------------
else:
def norm_chunk_shared(t_x, dummy, norm_module=norm):
out, _ = norm_module(t_x)
return out
if use_selective_recompute:
dummy = torch.empty(0, device=hidden_states.device)
hidden_states = cp.checkpoint(
norm_chunk_shared,
hidden_states,
dummy,
use_reentrant=False,
)
else:
hidden_states, gate = norm(hidden_states)
return hidden_states, gate, gate_mask
def _gated_residual(self, x, y, gate, start_indices=None, end_indices=None):
"""
Applies gated residual connection with optional gate parameter.
Args:
x: Input tensor (residual)
y: Output tensor to be added
gate: Optional gate tensor to modulate the addition
Returns:
x + y if gate is None, otherwise x + y * gate
"""
if x is None and y is None:
return None
if x is None or y is None:
return x if x is not None else y
if gate is None:
return x + y
new_y = y.clone()
selected_y = y[start_indices[1] : end_indices[1]]
selected_y = selected_y.view(
-1, self.config.action_horizon_flow, selected_y.shape[-1]
)[:, :, : self.config.dim_inputs[1]]
selected_y = selected_y.to(torch.float32) * gate
new_y[start_indices[1] : end_indices[1], : self.config.dim_inputs[1]] = (
selected_y.view(-1, self.config.dim_inputs[1]).to(new_y.dtype)
)
return x + new_y
def scatter_proprioception_embeddings(
self, input_ids, inputs_embeds, proprioception, dataset_names, agent_pos_mask
):
if (
proprioception is not None
and not self.config.use_state_string_representation
):
proprioception = proprioception.to(inputs_embeds.device).to(
inputs_embeds.dtype
)
agent_pos_mask = agent_pos_mask.to(inputs_embeds.device).to(
inputs_embeds.dtype
)
proprioception = self.action_preprocessor.proprioception_proj(
proprioception,
dataset_names,
agent_pos_mask,
use_history=proprioception.shape[1] > 1,
)
mask = input_ids == self.action_token_id_set["propri_token_id"]
mask_unsqueezed = mask.unsqueeze(-1)
mask_expanded = mask_unsqueezed.expand_as(inputs_embeds)
proprioception_mask = mask_expanded.to(inputs_embeds.device)
proprioception = proprioception.to(
inputs_embeds.device, inputs_embeds.dtype
)
inputs_embeds = inputs_embeds.masked_scatter(
proprioception_mask, proprioception
)
return inputs_embeds
def scatter_flow_action_embeddings(
self, input_ids, inputs_embeds, action_chunk, dataset_names, dof_mask
):
if not self.config.use_flow_action_expert:
return inputs_embeds, None, None
adarms_cond, flow = None, None
if action_chunk is not None:
action_chunk = action_chunk.to(inputs_embeds.device)
dof_mask = dof_mask.to(inputs_embeds.device)
noisy_action_emb, flow, adarms_cond = self.action_preprocessor(
action_chunk, dataset_names, dof_mask
)
mask = input_ids == self.action_token_id_set["action_token_id"]
mask_unsqueezed = mask.unsqueeze(-1)
mask_expanded = mask_unsqueezed.expand_as(inputs_embeds)
action_mask = mask_expanded.to(inputs_embeds.device)
noisy_action_emb = noisy_action_emb.to(
inputs_embeds.device, inputs_embeds.dtype
)
inputs_embeds = inputs_embeds.masked_scatter(action_mask, noisy_action_emb)
return inputs_embeds, flow, adarms_cond
@staticmethod
def _update_position_ids(
position_ids,
moe_token_types,
positional_masks,
):
if (
positional_masks is None
or "ar_predict_token_positions" not in positional_masks
):
return position_ids
new_position_ids = position_ids.clone()
ar_predict_token_positions = positional_masks["ar_predict_token_positions"]
flow_mask = moe_token_types == 1
start_ar_pos, end_ar_pos = find_first_last_ones(ar_predict_token_positions)
start_flow_pos, end_flow_pos = find_first_last_ones(flow_mask)
for bs_i in range(position_ids.shape[1]):
if start_ar_pos[bs_i] != -1 and end_ar_pos[bs_i] != -1:
start_ar_ids = new_position_ids[:, bs_i, start_ar_pos[bs_i]]
start_flow_ids = new_position_ids[:, bs_i, start_flow_pos[bs_i]]
diff = start_flow_ids - start_ar_ids
new_position_ids[:, bs_i, start_flow_pos[bs_i] :] = position_ids[
:, bs_i, start_flow_pos[bs_i] :
] - diff.unsqueeze(-1)
return new_position_ids
def _update_joint_attention_mask_2d(
self,
attention_mask,
moe_token_types,
positional_masks,
):
if attention_mask.dim() == 3: # bs, seq_len, seq_len
return attention_mask
bs, seq_len = moe_token_types.shape[0], moe_token_types.shape[1]
# Create a lower triangular matrix as a causal mask.
causal_mask = torch.tril(
torch.ones(
(seq_len, seq_len), dtype=torch.bfloat16, device=moe_token_types.device
)
)
# Extended to the batch dimension.
attention_mask = causal_mask.unsqueeze(0).expand(bs, -1, -1)
if positional_masks is not None and "padding_positions" in positional_masks:
padding_positions = positional_masks["padding_positions"]
# The padding is set to zero.
attention_mask = torch.where(
padding_positions[:, None, :],
torch.zeros_like(attention_mask),
attention_mask,
)
# The padding is set to zero.
attention_mask = torch.where(
padding_positions[:, :, None],
torch.zeros_like(attention_mask),
attention_mask,
)
# Set all values ​​in the moe1 section to 1, and disable the fast section.
moe1_mask = (moe_token_types[:, :, None]) & (moe_token_types[:, None, :])
if (
not self.config.causal_action_attention_mask
): # If a causal action attention mask is not used, then all elements in the moe1 section are set to 1.
attention_mask = torch.where(
moe1_mask, torch.ones_like(attention_mask), attention_mask
)
if (
positional_masks is not None
and "ar_predict_token_positions" in positional_masks
):
ar_predict_token_positions = positional_masks["ar_predict_token_positions"]
moe1_mask = (moe_token_types[:, :, None]) & (
ar_predict_token_positions[:, None, :]
)
attention_mask = torch.where(
moe1_mask, torch.zeros_like(attention_mask), attention_mask
)
if (
positional_masks is not None
and "valid_flow_action_positions" in positional_masks
):
# true in moe_token_types but false in valid_flow_action_positions
nonvalid_flow_action_positions = (
moe_token_types & ~positional_masks["valid_flow_action_positions"]
)
attention_mask = torch.where(
nonvalid_flow_action_positions[:, None, :],
torch.zeros_like(attention_mask),
attention_mask,
)
attention_mask = torch.where(
nonvalid_flow_action_positions[:, :, None],
torch.zeros_like(attention_mask),
attention_mask,
)
return attention_mask
def _update_joint_attention_flash_mask(
self,
attention_mask,
moe_token_types,
positional_masks,
debug=False,
):
device = moe_token_types.device
B, S = moe_token_types.shape
i32 = torch.int32
# ---- Return vector initialization ----
LTS = torch.ones((B, S), device=device, dtype=i32) * S
UTE = (
torch.arange(S, device=device, dtype=i32).unsqueeze(0).expand(B, S).clone()
)
# Handling padding positions
if positional_masks is not None and "padding_positions" in positional_masks:
padding_positions = positional_masks["padding_positions"]
LTS[padding_positions] = 0
UTE[padding_positions] = S
# Handling ar predict tokens
if (
positional_masks is not None
and "ar_predict_token_positions" in positional_masks
):
start_ar_pos, end_ar_pos = find_first_last_ones(
positional_masks["ar_predict_token_positions"]
)
for bs_i in range(B):
if end_ar_pos[bs_i] != -1:
LTS[bs_i, positional_masks["ar_predict_token_positions"][bs_i]] = (
end_ar_pos[bs_i].to(i32) + 1
)
# Handling flow action bidirectional mask
flow_mask = moe_token_types == 1
if not self.config.causal_action_attention_mask:
start_flow_pos, end_flow_pos = find_first_last_ones(flow_mask)
for bs_i in range(B):
if start_flow_pos[bs_i] != -1:
UTE[bs_i, flow_mask[bs_i]] = start_flow_pos[bs_i].to(i32)
# Handling validate flow
if (
positional_masks is not None
and "valid_flow_action_positions" in positional_masks
):
flow_mask = moe_token_types == 1
nonvalid_flow_action_positions = (
flow_mask & ~positional_masks["valid_flow_action_positions"]
)
if nonvalid_flow_action_positions.any():
LTS[nonvalid_flow_action_positions] = 0
UTE[nonvalid_flow_action_positions] = S
LTS = LTS.unsqueeze(-1)
UTE = UTE.unsqueeze(-1)
startend_row_indices = torch.cat([LTS, UTE], dim=-1)
# startend_row_indices = LTS
# add num_heads dimension
startend_row_indices = startend_row_indices.unsqueeze(1)
return startend_row_indices
class ActionGenerationMixin(GenerationMixin):
action_preprocessor: ActionProcessor
def to_bfloat16_for_selected_params(self, fsdp_plugin=None, accelerator=None):
"""
Keep some model parameters as float32, and convert others to bfloat16.
- If `fsdp_plugin` exists, use FSDP v1's `mixed_precision` wrapper.
- Otherwise, directly modify the parameter dtype.
"""
def _assign_child(root_module, dotted_name: str, new_child):
parts = dotted_name.split(".")
parent = root_module
for p in parts[:-1]:
parent = getattr(parent, p)
setattr(parent, parts[-1], new_child)
if fsdp_plugin:
fsdp_version = getattr(fsdp_plugin, "fsdp_version", None)
if fsdp_version != 1:
raise RuntimeError("Only FSDP v1 is supported (fsdp_version=1).")
device = getattr(
accelerator, "device", torch.device("cuda", torch.cuda.current_device())
)
if isinstance(device, torch.device) and device.type == "cuda":
if device.index is not None:
torch.cuda.set_device(device.index)
device_id = device.index
# move model to device
self = self.to(device)
# Define the mixed-precision strategy.
bf16_policy = MP(
param_dtype=torch.bfloat16,
reduce_dtype=torch.float32,
buffer_dtype=torch.bfloat16,
cast_forward_inputs=False,
cast_root_forward_inputs=False,
)
fp32_policy = MP(
param_dtype=torch.float32,
reduce_dtype=torch.float32,
buffer_dtype=torch.float32,
cast_forward_inputs=False,
cast_root_forward_inputs=False,
)
# Step 1️⃣: Identify the top-level ActionProcessor module and wrap it separately with FSDP (FP32).
for name, module in list(self.named_modules()):
if isinstance(module, nn.Module) and any(
k in name.lower() for k in ["action_preprocessor"]
):
if any(True for _ in module.children()):
continue
if getattr(module, "_fsdp_wrapped", False):
continue
print(f"[FSDP v1] wrapping module in FP32: {name}")
wrapped = FSDP(
module,
mixed_precision=fp32_policy,
sharding_strategy=torch.distributed.fsdp.ShardingStrategy.SHARD_GRAD_OP,
backward_prefetch="BACKWARD_PRE",
device_id=device_id,
use_orig_params=True,
)
_assign_child(self, name, wrapped)
setattr(wrapped, "_fsdp_wrapped", True)
# Step 2️⃣: The outermost layer uses unified FSDP (BF16 strategy).
print("[FSDP v1] wrapping root model with bf16 mixed precision...")
self = FSDP(
self,
mixed_precision=bf16_policy,
sharding_strategy=torch.distributed.fsdp.ShardingStrategy.SHARD_GRAD_OP,
backward_prefetch="BACKWARD_PRE",
device_id=device_id,
use_orig_params=True,
)
return self
# ----------------- Non-FSDP scenarios -----------------
else:
print("[INFO] Running manual dtype conversion (no FSDP).")
self.to(dtype=torch.float32)
params_to_keep_float32 = []
for name, _ in self.named_parameters():
if any(
k in name
for k in [
"input_layernorm",
"post_attention_layernorm",
"model.norm",
"action_preprocessor",
]
):
params_to_keep_float32.append(name)
for name, param in self.named_parameters():
if name not in params_to_keep_float32:
param.data = param.data.to(torch.bfloat16)
return self
def define_action_token_id(self):
action_token_list = []
if self.action_tokenizer_type:
for i in range(self.action_tokenizer.vocab_size):
action_token_id = self.processor.tokenizer.convert_tokens_to_ids(
f"<|action_token_{i}|>"
)
action_token_list.append(action_token_id)
action_token_id = self.processor.tokenizer.convert_tokens_to_ids("<|action|>")
propri_token_id = self.processor.tokenizer.convert_tokens_to_ids("<|propri|>")
self.action_token_id_set = {
"action_token_list": action_token_list,
"propri_token_id": propri_token_id,
"action_token_id": action_token_id,
}
def add_lora(
self, r=8, lora_alpha=32, target_modules=["q_proj", "v_proj"], lora_dropout=0.1
):
"""Add LoRA adapter"""
config = LoraConfig(
r=r,
lora_alpha=lora_alpha,
target_modules=target_modules,
lora_dropout=lora_dropout,
bias="none",
task_type="CAUSAL_LM",
)
self.model = get_peft_model(self.model, config)
# Print trainable parameter information.
self.model.print_trainable_parameters()
def compute_loss(
self,
hidden_states,
logits,
input_ids=None,
dataset_names=None,
labels=None,
action_chunk=None,
dof_mask=None,
flow=None,
flow_loss_mask=None,
**kwargs,
):
if input_ids is not None:
batch_size, seq_length = input_ids.shape
loss = 0
cross_entropy_loss, flow_loss = None, None
# if dataset_names is not None:
# unique_datasets_name = list(set(dataset_names))
# channel_loss_dict = {
# dataset_name: torch.tensor(0.0, device=logits.device)
# for dataset_name in _ACTION_DATASET_NAMES + _MULTIMODAL_DATASET_NAMES
# }
# channel_loss_count_dict = {
# dataset_name: torch.tensor(0, device=logits.device)
# for dataset_name in _ACTION_DATASET_NAMES + _MULTIMODAL_DATASET_NAMES
# }
# else:
unique_datasets_name, channel_loss_dict, channel_loss_count_dict = (
None,
None,
None,
)
if labels is not None:
action_accuracy = 0
shift_logits = logits[..., :-1, :].contiguous()
shift_labels = labels[..., 1:].contiguous()
shift_logits = shift_logits.view(-1, self.config.vocab_size)
shift_labels = shift_labels.view(-1)
# Enable model parallelism
shift_labels = shift_labels.to(shift_logits.device)
non_ignored_mask = shift_labels != -100
_cross_entropy_loss = self.loss_fct(shift_logits, shift_labels)
cross_entropy_loss = (
_cross_entropy_loss[non_ignored_mask].mean()
if non_ignored_mask.any()
else torch.tensor(0.0, device=shift_logits.device)
)
# compute channel loss
_cross_entropy_loss = _cross_entropy_loss.view(batch_size, seq_length - 1)
non_ignored_mask = non_ignored_mask.view(batch_size, seq_length - 1)
for dataset_name_i in unique_datasets_name:
dataset_mask = torch.tensor(
[name == dataset_name_i for name in dataset_names],
device=logits.device,
)
combined_mask = dataset_mask.unsqueeze(1) & non_ignored_mask
channel_loss_dict[dataset_name_i] = (
_cross_entropy_loss[combined_mask].sum()
if combined_mask.any()
else torch.tensor(0.0, device=shift_logits.device)
)
channel_loss_count_dict[dataset_name_i] += combined_mask.sum()
if not torch.isnan(cross_entropy_loss):
loss += cross_entropy_loss
else:
with torch.no_grad():
cross_entropy_loss.detach()
# compute action token accuracy
if len(self.action_token_id_set["action_token_list"]) > 0:
shift_logits = logits[..., :-1, :].contiguous()
action_preds = shift_logits.argmax(dim=-1)
shift_labels = labels[..., 1:].contiguous()
action_mask = (
shift_labels > self.action_token_id_set["action_token_list"][0]
)
correct_preds = (action_preds == shift_labels) & action_mask
action_accuracy = (
correct_preds.sum().float() / action_mask.sum().float()
)
channel_loss_dict["action_accuracy"] = action_accuracy
if action_chunk is not None:
action_mask = input_ids == self.action_token_id_set["action_token_id"]
if action_mask.any():
action_hidden_states = hidden_states[action_mask].to(torch.float32)
flow = flow.reshape(-1, flow.shape[-1])
_flow_loss = self.action_preprocessor.flow_loss(
action_hidden_states, flow, action_chunk, dof_mask, flow_loss_mask
)
if isinstance(_flow_loss, torch.Tensor):
flow_loss = _flow_loss.mean()
loss += flow_loss * self.config.flow_loss_weight
_flow_loss = _flow_loss.view(
dof_mask.shape[0], dof_mask.shape[1], dof_mask.shape[2]
)
return (
loss,
cross_entropy_loss,
flow_loss,
channel_loss_dict,
channel_loss_count_dict,
)
class AttentionsSelectorMixin:
@classmethod
def _autoset_attn_implementation(
cls,
config,
use_flash_attention_2: bool = False,
torch_dtype: Optional[torch.dtype] = None,
device_map: Optional[Union[str, Dict[str, int]]] = None,
check_device_map: bool = True,
):
"""
Automatically checks and dispatches to a default attention implementation. In order of priority:
1. An implementation specified in `config._attn_implementation` (due for example to the argument attn_implementation="sdpa" in from_pretrained).
2. DEPRECATED: if use_flash_attention_2 is set to `True` and `flash_attn` is available, flash attention. (`LlamaFlashAttention` for example)
3. SDPA implementation, if available and supported by the model type. (`LlamaSdpaAttention` for example)
4. The default model's implementation otherwise (`LlamaAttention` for example) .
"""
# Here we use config._attn_implementation_internal to check whether the attention implementation was explicitly set by the user.
# The property `PretrainedConfig._attn_implementation` is never `None`, for backward compatibility (always fall back on "eager").
# The `hasattr` here is used as some Transformers tests for some reason do not call PretrainedConfig __init__ (e.g. test_no_super_init_config_and_model)
requested_attn_implementation = None
if (
hasattr(config, "_attn_implementation_internal")
and config._attn_implementation_internal is not None
):
if (
config._attn_implementation != "flash_attention_2"
and use_flash_attention_2
):
raise ValueError(
f'Both attn_implementation="{config._attn_implementation}" and `use_flash_attention_2=True` were used when loading the model, which are not compatible.'
' We recommend to just use `attn_implementation="flash_attention_2"` when loading the model.'
)
if (
not isinstance(config._attn_implementation, dict)
and config._attn_implementation
not in ["eager"]
+ ALL_ATTENTION_FUNCTIONS.valid_keys()
+ X2ROBOT_ATTENTION_FUNCTIONS
):
message = f'Specified `attn_implementation="{config._attn_implementation}"` is not supported. The only possible arguments are `attn_implementation="eager"` (manual attention implementation)'
if cls._supports_flash_attn_2:
message += ', `"attn_implementation=flash_attention_2"` (implementation using flash attention 2)'
if cls._supports_sdpa:
message += ', `"attn_implementation=sdpa"` (implementation using torch.nn.functional.scaled_dot_product_attention)'
if cls._supports_flex_attn:
message += ', `"attn_implementation=flex_attention"` (implementation using torch\'s flex_attention)'
raise ValueError(message + ".")
# If a config is passed with a preset attn_implementation, we skip the automatic dispatch and use the user-provided config, with hard checks that the requested attention implementation is available.
requested_attn_implementation = config._attn_implementation_internal
if use_flash_attention_2:
logger.warning_once(
'The model was loaded with use_flash_attention_2=True, which is deprecated and may be removed in a future release. Please use `attn_implementation="flash_attention_2"` instead.'
)
config._attn_implementation = "flash_attention_2"
if config._attn_implementation == "flash_attention_2":
cls._check_and_enable_flash_attn_2(
config,
torch_dtype=torch_dtype,
device_map=device_map,
hard_check_only=False,
check_device_map=check_device_map,
)
elif requested_attn_implementation == "flex_attention":
config = cls._check_and_enable_flex_attn(config, hard_check_only=True)
elif (
requested_attn_implementation in [None, "sdpa"]
and not is_torch_xla_available()
):
# use_flash_attention_2 takes priority over SDPA, hence SDPA treated in this elif.
config = cls._check_and_enable_sdpa(
config,
hard_check_only=(
False if requested_attn_implementation is None else True
),
)
if (
torch.version.hip is not None
and config._attn_implementation == "sdpa"
and torch.cuda.device_count() > 1
and version.parse(torch.__version__) < version.parse("2.4.1")
):
logger.warning_once(
"Using the `SDPA` attention implementation on multi-gpu setup with ROCM may lead to performance issues due to the FA backend. Disabling it to use alternative backends."
)
torch.backends.cuda.enable_flash_sdp(False)
elif requested_attn_implementation in ALL_ATTENTION_FUNCTIONS.valid_keys():
config._attn_implementation = requested_attn_implementation
elif isinstance(requested_attn_implementation, dict):
config._attn_implementation = None
elif config._attn_implementation in X2ROBOT_ATTENTION_FUNCTIONS:
pass
else:
config._attn_implementation = "eager"
config._attn_implementation_autoset = True
return config
def _check_and_adjust_attn_implementation(
self, attn_implementation: Optional[str], is_init_check: bool = False
) -> str:
assert (
attn_implementation
in ["eager", "flash_attention_2", "sdpa"] + X2ROBOT_ATTENTION_FUNCTIONS
)
return attn_implementation