Alan0928's picture
Upload folder using huggingface_hub
08ff31f verified
Raw
History Blame Contribute Delete
28.7 kB
import logging
import math
import torch
from torch import Tensor
from torch import nn
import torch.nn.functional as F # noqa: N812
import openpi.models.gemma as _gemma
from openpi.models_pytorch.gemma_pytorch import PaliGemmaWithExpertModel
import openpi.models_pytorch.preprocessing_pytorch as _preprocessing
def get_safe_dtype(target_dtype, device_type):
"""Get a safe dtype for the given device type."""
if device_type == "cpu":
# CPU doesn't support bfloat16, use float32 instead
if target_dtype == torch.bfloat16:
return torch.float32
if target_dtype == torch.float64:
return torch.float64
return target_dtype
def create_sinusoidal_pos_embedding(
time: torch.tensor, dimension: int, min_period: float, max_period: float, device="cpu"
) -> Tensor:
"""Computes sine-cosine positional embedding vectors for scalar positions."""
if dimension % 2 != 0:
raise ValueError(f"dimension ({dimension}) must be divisible by 2")
if time.ndim != 1:
raise ValueError("The time tensor is expected to be of shape `(batch_size, )`.")
dtype = get_safe_dtype(torch.float64, device.type)
fraction = torch.linspace(0.0, 1.0, dimension // 2, dtype=dtype, device=device)
period = min_period * (max_period / min_period) ** fraction
# Compute the outer product
scaling_factor = 1.0 / period * 2 * math.pi
sin_input = scaling_factor[None, :] * time[:, None]
return torch.cat([torch.sin(sin_input), torch.cos(sin_input)], dim=1)
def sample_beta(alpha, beta, bsize, device):
alpha_t = torch.as_tensor(alpha, dtype=torch.float32, device=device)
beta_t = torch.as_tensor(beta, dtype=torch.float32, device=device)
dist = torch.distributions.Beta(alpha_t, beta_t)
return dist.sample((bsize,))
def make_att_2d_masks(pad_masks, att_masks):
"""Copied from big_vision.
Tokens can attend to valid inputs tokens which have a cumulative mask_ar
smaller or equal to theirs. This way `mask_ar` int[B, N] can be used to
setup several types of attention, for example:
[[1 1 1 1 1 1]]: pure causal attention.
[[0 0 0 1 1 1]]: prefix-lm attention. The first 3 tokens can attend between
themselves and the last 3 tokens have a causal attention. The first
entry could also be a 1 without changing behaviour.
[[1 0 1 0 1 0 0 1 0 0]]: causal attention between 4 blocks. Tokens of a
block can attend all previous blocks and all tokens on the same block.
Args:
input_mask: bool[B, N] true if its part of the input, false if padding.
mask_ar: int32[B, N] mask that's 1 where previous tokens cannot depend on
it and 0 where it shares the same attention mask as the previous token.
"""
if att_masks.ndim != 2:
raise ValueError(att_masks.ndim)
if pad_masks.ndim != 2:
raise ValueError(pad_masks.ndim)
cumsum = torch.cumsum(att_masks, dim=1)
att_2d_masks = cumsum[:, None, :] <= cumsum[:, :, None]
pad_2d_masks = pad_masks[:, None, :] * pad_masks[:, :, None]
return att_2d_masks & pad_2d_masks
class LoRALinear(nn.Linear):
"""Linear layer with additive LoRA weights while preserving base state keys."""
def __init__(self, linear: nn.Linear, *, rank: int, alpha: float):
super().__init__(
linear.in_features,
linear.out_features,
bias=linear.bias is not None,
device=linear.weight.device,
dtype=linear.weight.dtype,
)
self.weight.data.copy_(linear.weight.data)
if linear.bias is not None and self.bias is not None:
self.bias.data.copy_(linear.bias.data)
self.rank = int(rank)
self.alpha = float(alpha)
self.scaling = self.alpha / max(self.rank, 1)
self.lora_A = nn.Parameter(torch.empty(self.rank, linear.in_features, device=linear.weight.device))
self.lora_B = nn.Parameter(torch.empty(linear.out_features, self.rank, device=linear.weight.device))
nn.init.kaiming_uniform_(self.lora_A, a=math.sqrt(5))
nn.init.zeros_(self.lora_B)
def forward(self, input: Tensor) -> Tensor: # noqa: A002
base = F.linear(input, self.weight, self.bias)
lora_a = self.lora_A.to(dtype=input.dtype, device=input.device)
lora_b = self.lora_B.to(dtype=input.dtype, device=input.device)
update = F.linear(F.linear(input, lora_a), lora_b)
return base + update.to(dtype=base.dtype) * self.scaling
class PI0Pytorch(nn.Module):
def __init__(self, config):
super().__init__()
self.config = config
self.pi05 = config.pi05
self.speed_modulation = bool(getattr(config, "speed_modulation", False))
self.soft_prompt_p = int(getattr(config, "soft_prompt_p", 0))
self.soft_prompt_speeds = tuple(getattr(config, "soft_prompt_speeds", ()))
paligemma_config = _gemma.get_config(config.paligemma_variant)
action_expert_config = _gemma.get_config(config.action_expert_variant)
self.paligemma_with_expert = PaliGemmaWithExpertModel(
paligemma_config,
action_expert_config,
use_adarms=[False, True] if self.pi05 else [False, False],
precision=config.dtype,
)
self._pytorch_lora_module_count = 0
self._apply_pytorch_lora_if_requested(paligemma_config, action_expert_config)
self.action_in_proj = nn.Linear(config.action_dim, action_expert_config.width)
self.action_out_proj = nn.Linear(action_expert_config.width, config.action_dim)
if self.pi05:
self.time_mlp_in = nn.Linear(action_expert_config.width, action_expert_config.width)
self.time_mlp_out = nn.Linear(action_expert_config.width, action_expert_config.width)
if self.speed_modulation:
# Reads the raw scalar ``observation.speed`` (shape (B, 1)) and
# fuses it with the timestep embedding to drive adaRMSNorm in
# the action expert. The MLP has enough capacity to learn any
# monotonic transform internally, so the input does not need to
# be log-scaled.
self.speed_mod_mlp_in = nn.Linear(1, action_expert_config.width)
self.speed_mod_mlp_out = nn.Linear(action_expert_config.width, action_expert_config.width)
self.speed_condition_mlp_in = nn.Linear(2 * action_expert_config.width, action_expert_config.width)
self.speed_condition_mlp_out = nn.Linear(action_expert_config.width, action_expert_config.width)
else:
self.state_proj = nn.Linear(config.action_dim, action_expert_config.width)
self.action_time_mlp_in = nn.Linear(2 * action_expert_config.width, action_expert_config.width)
self.action_time_mlp_out = nn.Linear(action_expert_config.width, action_expert_config.width)
# Soft-prompt speed conditioning: K x P learnable tokens, one group per
# discrete training speed. At forward time the model picks the group
# whose anchor speed is closest to ``observation.speed`` and inserts P
# tokens between image and language tokens in the VLM input.
if self.soft_prompt_p > 0 and self.soft_prompt_speeds:
paligemma_width = paligemma_config.width
k = len(self.soft_prompt_speeds)
self.soft_prompt_tokens = nn.Parameter(
torch.empty(k, self.soft_prompt_p, paligemma_width)
)
nn.init.normal_(self.soft_prompt_tokens, mean=0.0, std=0.02)
self.register_buffer(
"soft_prompt_anchors",
torch.tensor(self.soft_prompt_speeds, dtype=torch.float32),
persistent=False,
)
torch.set_float32_matmul_precision("high")
if config.pytorch_compile_mode is not None:
self.sample_actions = torch.compile(self.sample_actions, mode=config.pytorch_compile_mode)
# Initialize gradient checkpointing flag
self.gradient_checkpointing_enabled = False
msg = "transformers_replace is not installed correctly. Please install it with `uv pip install transformers==4.53.2` and `cp -r ./src/openpi/models_pytorch/transformers_replace/* .venv/lib/python3.11/site-packages/transformers/`."
try:
from transformers.models.siglip import check
if not check.check_whether_transformers_replace_is_installed_correctly():
raise ValueError(msg)
except ImportError:
raise ValueError(msg) from None
def _apply_pytorch_lora_if_requested(self, paligemma_config, action_expert_config) -> None:
explicit_targets = set(getattr(self.config, "pytorch_lora_targets", ()))
use_explicit = bool(getattr(self.config, "pytorch_lora", False))
use_paligemma = use_explicit and "paligemma" in explicit_targets
use_action_expert = use_explicit and "action_expert" in explicit_targets
if "lora" in str(getattr(self.config, "paligemma_variant", "")):
use_paligemma = True
if "lora" in str(getattr(self.config, "action_expert_variant", "")):
use_action_expert = True
if use_paligemma:
self._pytorch_lora_module_count += self._replace_gemma_linears_with_lora(
self.paligemma_with_expert.paligemma.language_model,
paligemma_config,
)
if use_action_expert:
self._pytorch_lora_module_count += self._replace_gemma_linears_with_lora(
self.paligemma_with_expert.gemma_expert.model,
action_expert_config,
)
if self._pytorch_lora_module_count:
self._freeze_pytorch_lora_base()
logging.info("Enabled PyTorch LoRA on %d Linear layers", self._pytorch_lora_module_count)
def _lora_rank_alpha(self, gemma_config, kind: str) -> tuple[int, float]:
lora_config = getattr(gemma_config, "lora_configs", {}).get(kind)
rank = int(getattr(lora_config, "rank", getattr(self.config, "pytorch_lora_rank", 16)))
alpha = float(getattr(lora_config, "alpha", getattr(self.config, "pytorch_lora_alpha", 16.0)))
if bool(getattr(self.config, "pytorch_lora", False)):
rank = int(getattr(self.config, "pytorch_lora_rank", rank))
alpha = float(getattr(self.config, "pytorch_lora_alpha", alpha))
return rank, alpha
def _replace_gemma_linears_with_lora(self, root: nn.Module, gemma_config) -> int:
attn_children = {"q_proj", "k_proj", "v_proj", "o_proj"}
ffn_children = {"gate_proj", "up_proj", "down_proj"}
replaced = 0
for _module_name, module in list(root.named_modules()):
for child_name, child in list(module.named_children()):
if not isinstance(child, nn.Linear):
continue
if child_name in attn_children:
rank, alpha = self._lora_rank_alpha(gemma_config, "attn")
elif child_name in ffn_children:
rank, alpha = self._lora_rank_alpha(gemma_config, "ffn")
else:
continue
setattr(module, child_name, LoRALinear(child, rank=rank, alpha=alpha))
replaced += 1
return replaced
def _freeze_pytorch_lora_base(self) -> None:
for param in self.paligemma_with_expert.parameters():
param.requires_grad = False
for name, param in self.paligemma_with_expert.named_parameters():
if "lora_" in name:
param.requires_grad = True
@property
def pytorch_lora_enabled(self) -> bool:
return self._pytorch_lora_module_count > 0
def gradient_checkpointing_enable(self):
"""Enable gradient checkpointing for memory optimization."""
self.gradient_checkpointing_enabled = True
self.paligemma_with_expert.paligemma.language_model.gradient_checkpointing = True
self.paligemma_with_expert.paligemma.vision_tower.gradient_checkpointing = True
self.paligemma_with_expert.gemma_expert.model.gradient_checkpointing = True
logging.info("Enabled gradient checkpointing for PI0Pytorch model")
def gradient_checkpointing_disable(self):
"""Disable gradient checkpointing."""
self.gradient_checkpointing_enabled = False
self.paligemma_with_expert.paligemma.language_model.gradient_checkpointing = False
self.paligemma_with_expert.paligemma.vision_tower.gradient_checkpointing = False
self.paligemma_with_expert.gemma_expert.model.gradient_checkpointing = False
logging.info("Disabled gradient checkpointing for PI0Pytorch model")
def is_gradient_checkpointing_enabled(self):
"""Check if gradient checkpointing is enabled."""
return self.gradient_checkpointing_enabled
def _apply_checkpoint(self, func, *args, **kwargs):
"""Helper method to apply gradient checkpointing if enabled."""
if self.gradient_checkpointing_enabled and self.training:
return torch.utils.checkpoint.checkpoint(
func, *args, use_reentrant=False, preserve_rng_state=False, **kwargs
)
return func(*args, **kwargs)
def _prepare_attention_masks_4d(self, att_2d_masks):
"""Helper method to prepare 4D attention masks for transformer."""
att_2d_masks_4d = att_2d_masks[:, None, :, :]
return torch.where(att_2d_masks_4d, 0.0, -2.3819763e38)
def _preprocess_observation(self, observation, *, train=True):
"""Helper method to preprocess observation."""
observation = _preprocessing.preprocess_observation_pytorch(observation, train=train)
return (
list(observation.images.values()),
list(observation.image_masks.values()),
observation.tokenized_prompt,
observation.tokenized_prompt_mask,
observation.state,
getattr(observation, "speed", None),
)
def sample_noise(self, shape, device):
return torch.normal(
mean=0.0,
std=1.0,
size=shape,
dtype=torch.float32,
device=device,
)
def sample_time(self, bsize, device):
time_beta = sample_beta(1.5, 1.0, bsize, device)
time = time_beta * 0.999 + 0.001
return time.to(dtype=torch.float32, device=device)
def embed_prefix(
self, images, img_masks, lang_tokens, lang_masks, speed=None
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
"""Embed images with SigLIP and language tokens with embedding layer to prepare
for PaliGemma transformer processing.
When soft-prompt speed conditioning is enabled (``soft_prompt_p > 0``),
``P`` learnable tokens (looked up by nearest match between ``speed`` and
``soft_prompt_anchors``) are inserted between the image and language
tokens, with full attention.
"""
embs = []
pad_masks = []
att_masks = []
# Process images
for img, img_mask in zip(images, img_masks, strict=True):
def image_embed_func(img):
return self.paligemma_with_expert.embed_image(img)
img_emb = self._apply_checkpoint(image_embed_func, img)
bsize, num_img_embs = img_emb.shape[:2]
embs.append(img_emb)
pad_masks.append(img_mask[:, None].expand(bsize, num_img_embs))
# Create attention masks so that image tokens attend to each other
att_masks += [0] * num_img_embs
# Soft-prompt speed conditioning: insert P tokens between vision and
# language tokens. Group is selected per-sample by nearest match.
if self.soft_prompt_p > 0 and self.soft_prompt_speeds:
if speed is None:
raise ValueError(
"soft-prompt speed conditioning is enabled but observation.speed is None. "
"Make sure the data config emits speed (e.g., speed_integration='soft_prompt')."
)
# speed may arrive as (B,), (B, 1), or scalar broadcast; normalise.
speed_flat = speed.reshape(-1).to(self.soft_prompt_anchors.dtype).to(
self.soft_prompt_anchors.device
)
# Distance to each anchor: (B, K)
dist = (speed_flat[:, None] - self.soft_prompt_anchors[None, :]).abs()
speed_idx = dist.argmin(dim=1) # (B,)
soft_prompt_emb = self.soft_prompt_tokens[speed_idx] # (B, P, hidden)
soft_prompt_emb = soft_prompt_emb.to(dtype=embs[0].dtype, device=embs[0].device)
embs.append(soft_prompt_emb)
pad_masks.append(
torch.ones(
soft_prompt_emb.shape[:2], dtype=pad_masks[0].dtype, device=pad_masks[0].device
)
)
att_masks += [0] * self.soft_prompt_p
# Process language tokens
def lang_embed_func(lang_tokens):
lang_emb = self.paligemma_with_expert.embed_language_tokens(lang_tokens)
lang_emb_dim = lang_emb.shape[-1]
return lang_emb * math.sqrt(lang_emb_dim)
lang_emb = self._apply_checkpoint(lang_embed_func, lang_tokens)
embs.append(lang_emb)
pad_masks.append(lang_masks)
# full attention between image and language inputs
num_lang_embs = lang_emb.shape[1]
att_masks += [0] * num_lang_embs
embs = torch.cat(embs, dim=1)
pad_masks = torch.cat(pad_masks, dim=1)
att_masks = torch.tensor(att_masks, dtype=torch.bool, device=pad_masks.device)
# Get batch size from the first dimension of the concatenated tensors
bsize = pad_masks.shape[0]
att_masks = att_masks[None, :].expand(bsize, len(att_masks))
return embs, pad_masks, att_masks
def embed_suffix(self, state, noisy_actions, timestep, speed=None):
"""Embed state, noisy_actions, timestep to prepare for Expert Gemma processing."""
embs = []
pad_masks = []
att_masks = []
if not self.pi05:
if self.state_proj.weight.dtype == torch.float32:
state = state.to(torch.float32)
# Embed state
def state_proj_func(state):
return self.state_proj(state)
state_emb = self._apply_checkpoint(state_proj_func, state)
embs.append(state_emb[:, None, :])
bsize = state_emb.shape[0]
device = state_emb.device
state_mask = torch.ones(bsize, 1, dtype=torch.bool, device=device)
pad_masks.append(state_mask)
# Set attention masks so that image and language inputs do not attend to state or actions
att_masks += [1]
# Embed timestep using sine-cosine positional encoding with sensitivity in the range [0, 1]
time_emb = create_sinusoidal_pos_embedding(
timestep, self.action_in_proj.out_features, min_period=4e-3, max_period=4.0, device=timestep.device
)
time_emb = time_emb.type(dtype=timestep.dtype)
# Fuse timestep + action information using an MLP
def action_proj_func(noisy_actions):
return self.action_in_proj(noisy_actions)
action_emb = self._apply_checkpoint(action_proj_func, noisy_actions)
if not self.pi05:
time_emb = time_emb[:, None, :].expand_as(action_emb)
action_time_emb = torch.cat([action_emb, time_emb], dim=2)
# Apply MLP layers
def mlp_func(action_time_emb):
x = self.action_time_mlp_in(action_time_emb)
x = F.silu(x) # swish == silu
return self.action_time_mlp_out(x)
action_time_emb = self._apply_checkpoint(mlp_func, action_time_emb)
adarms_cond = None
else:
# time MLP (for adaRMS)
def time_mlp_func(time_emb):
x = self.time_mlp_in(time_emb)
x = F.silu(x) # swish == silu
x = self.time_mlp_out(x)
return F.silu(x)
time_emb = self._apply_checkpoint(time_mlp_func, time_emb)
if self.speed_modulation:
if speed is None:
speed_input = torch.ones(
noisy_actions.shape[0], 1, dtype=time_emb.dtype, device=noisy_actions.device
)
else:
speed_input = speed.reshape(-1, 1).to(dtype=time_emb.dtype, device=noisy_actions.device)
def speed_mlp_func(speed_input):
x = self.speed_mod_mlp_in(speed_input)
x = F.silu(x)
x = self.speed_mod_mlp_out(x)
return F.silu(x)
speed_emb = self._apply_checkpoint(speed_mlp_func, speed_input)
def condition_mlp_func(condition_emb):
x = self.speed_condition_mlp_in(condition_emb)
x = F.silu(x)
x = self.speed_condition_mlp_out(x)
return F.silu(x)
time_emb = self._apply_checkpoint(condition_mlp_func, torch.cat([time_emb, speed_emb], dim=-1))
action_time_emb = action_emb
adarms_cond = time_emb
# Add to input tokens
embs.append(action_time_emb)
bsize, action_time_dim = action_time_emb.shape[:2]
action_time_mask = torch.ones(bsize, action_time_dim, dtype=torch.bool, device=timestep.device)
pad_masks.append(action_time_mask)
# Set attention masks so that image, language and state inputs do not attend to action tokens
att_masks += [1] + ([0] * (self.config.action_horizon - 1))
embs = torch.cat(embs, dim=1)
pad_masks = torch.cat(pad_masks, dim=1)
att_masks = torch.tensor(att_masks, dtype=embs.dtype, device=embs.device)
att_masks = att_masks[None, :].expand(bsize, len(att_masks))
return embs, pad_masks, att_masks, adarms_cond
def forward(self, observation, actions, noise=None, time=None) -> Tensor:
"""Do a full training forward pass and compute the loss (batch_size x num_steps x num_motors)"""
images, img_masks, lang_tokens, lang_masks, state, speed = self._preprocess_observation(
observation, train=True
)
if noise is None:
noise = self.sample_noise(actions.shape, actions.device)
if time is None:
time = self.sample_time(actions.shape[0], actions.device)
time_expanded = time[:, None, None]
x_t = time_expanded * noise + (1 - time_expanded) * actions
u_t = noise - actions
prefix_embs, prefix_pad_masks, prefix_att_masks = self.embed_prefix(
images, img_masks, lang_tokens, lang_masks, speed=speed
)
suffix_embs, suffix_pad_masks, suffix_att_masks, adarms_cond = self.embed_suffix(state, x_t, time, speed=speed)
if (
self.paligemma_with_expert.paligemma.language_model.layers[0].self_attn.q_proj.weight.dtype
== torch.bfloat16
):
suffix_embs = suffix_embs.to(dtype=torch.bfloat16)
prefix_embs = prefix_embs.to(dtype=torch.bfloat16)
pad_masks = torch.cat([prefix_pad_masks, suffix_pad_masks], dim=1)
att_masks = torch.cat([prefix_att_masks, suffix_att_masks], dim=1)
att_2d_masks = make_att_2d_masks(pad_masks, att_masks)
position_ids = torch.cumsum(pad_masks, dim=1) - 1
# Prepare attention masks
att_2d_masks_4d = self._prepare_attention_masks_4d(att_2d_masks)
# Apply gradient checkpointing if enabled
def forward_func(prefix_embs, suffix_embs, att_2d_masks_4d, position_ids, adarms_cond):
(_, suffix_out), _ = self.paligemma_with_expert.forward(
attention_mask=att_2d_masks_4d,
position_ids=position_ids,
past_key_values=None,
inputs_embeds=[prefix_embs, suffix_embs],
use_cache=False,
adarms_cond=[None, adarms_cond],
)
return suffix_out
suffix_out = self._apply_checkpoint(
forward_func, prefix_embs, suffix_embs, att_2d_masks_4d, position_ids, adarms_cond
)
suffix_out = suffix_out[:, -self.config.action_horizon :]
suffix_out = suffix_out.to(dtype=torch.float32)
# Apply gradient checkpointing to final action projection if enabled
def action_out_proj_func(suffix_out):
return self.action_out_proj(suffix_out)
v_t = self._apply_checkpoint(action_out_proj_func, suffix_out)
return F.mse_loss(u_t, v_t, reduction="none")
@torch.no_grad()
def sample_actions(self, device, observation, noise=None, num_steps=10) -> Tensor:
"""Do a full inference forward and compute the action (batch_size x num_steps x num_motors)"""
bsize = observation.state.shape[0]
if noise is None:
actions_shape = (bsize, self.config.action_horizon, self.config.action_dim)
noise = self.sample_noise(actions_shape, device)
images, img_masks, lang_tokens, lang_masks, state, speed = self._preprocess_observation(
observation, train=False
)
prefix_embs, prefix_pad_masks, prefix_att_masks = self.embed_prefix(
images, img_masks, lang_tokens, lang_masks, speed=speed
)
prefix_att_2d_masks = make_att_2d_masks(prefix_pad_masks, prefix_att_masks)
prefix_position_ids = torch.cumsum(prefix_pad_masks, dim=1) - 1
# Compute image and language key value cache
prefix_att_2d_masks_4d = self._prepare_attention_masks_4d(prefix_att_2d_masks)
self.paligemma_with_expert.paligemma.language_model.config._attn_implementation = "eager" # noqa: SLF001
_, past_key_values = self.paligemma_with_expert.forward(
attention_mask=prefix_att_2d_masks_4d,
position_ids=prefix_position_ids,
past_key_values=None,
inputs_embeds=[prefix_embs, None],
use_cache=True,
)
dt = -1.0 / num_steps
dt = torch.tensor(dt, dtype=torch.float32, device=device)
x_t = noise
time = torch.tensor(1.0, dtype=torch.float32, device=device)
while time >= -dt / 2:
expanded_time = time.expand(bsize)
v_t = self.denoise_step(
state,
prefix_pad_masks,
past_key_values,
x_t,
expanded_time,
speed,
)
# Euler step - use new tensor assignment instead of in-place operation
x_t = x_t + dt * v_t
time += dt
return x_t
def denoise_step(
self,
state,
prefix_pad_masks,
past_key_values,
x_t,
timestep,
speed=None,
):
"""Apply one denoising step of the noise `x_t` at a given timestep."""
suffix_embs, suffix_pad_masks, suffix_att_masks, adarms_cond = self.embed_suffix(
state, x_t, timestep, speed=speed
)
suffix_len = suffix_pad_masks.shape[1]
batch_size = prefix_pad_masks.shape[0]
prefix_len = prefix_pad_masks.shape[1]
prefix_pad_2d_masks = prefix_pad_masks[:, None, :].expand(batch_size, suffix_len, prefix_len)
suffix_att_2d_masks = make_att_2d_masks(suffix_pad_masks, suffix_att_masks)
full_att_2d_masks = torch.cat([prefix_pad_2d_masks, suffix_att_2d_masks], dim=2)
prefix_offsets = torch.sum(prefix_pad_masks, dim=-1)[:, None]
position_ids = prefix_offsets + torch.cumsum(suffix_pad_masks, dim=1) - 1
# Prepare attention masks
full_att_2d_masks_4d = self._prepare_attention_masks_4d(full_att_2d_masks)
self.paligemma_with_expert.gemma_expert.model.config._attn_implementation = "eager" # noqa: SLF001
outputs_embeds, _ = self.paligemma_with_expert.forward(
attention_mask=full_att_2d_masks_4d,
position_ids=position_ids,
past_key_values=past_key_values,
inputs_embeds=[None, suffix_embs],
use_cache=False,
adarms_cond=[None, adarms_cond],
)
suffix_out = outputs_embeds[1]
suffix_out = suffix_out[:, -self.config.action_horizon :]
suffix_out = suffix_out.to(dtype=torch.float32)
return self.action_out_proj(suffix_out)