Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions configs/openpi/pi05_libero.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"pi05": true,
"discrete_state_input": false,
"paligemma_variant": "gemma_2b",
"action_expert_variant": "gemma_300m",
"action_dim": 32,
"output_action_dim": 7,
"state_dim": 8,
"action_horizon": 10,
"max_token_len": 200,
"num_inference_steps": 10,
"device": "cuda",
"dtype": "bfloat16",
"pytorch_compile_mode": null
}
26 changes: 26 additions & 0 deletions configs/openpi/pi05_libero_eval.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"benchmarks": [
"libero_spatial",
"libero_object",
"libero_goal",
"libero_10"
],
"task_ids": "all",
"num_trials_per_task": 50,
"env_seed": 7,
"policy_seed": 0,
"actions_per_plan": 5,
"num_steps_wait": 10,
"render_size": 256,
"video_fps": 10,
"video_policy": "none",
"save_actions": false,
"resume": true,
"fail_fast": false,
"max_steps": {
"libero_spatial": 220,
"libero_object": 280,
"libero_goal": 300,
"libero_10": 520
}
}
2 changes: 2 additions & 0 deletions lightx2v/infer.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from lightx2v.models.runners.minimax_h3.minimax_h3_runner import MiniMaxH3Runner # noqa: F401
from lightx2v.models.runners.motus.motus_runner import MotusRunner # noqa: F401
from lightx2v.models.runners.neopp.neopp_runner import NeoppRunner # noqa: F401
from lightx2v.models.runners.openpi.openpi_runner import OpenPIRunner # noqa: F401
from lightx2v.models.runners.qwen_image.qwen_image_runner import QwenImageRunner # noqa: F401
from lightx2v.models.runners.seedvr.seedvr_runner import SeedVRRunner # noqa: F401
from lightx2v.models.runners.swiftvr.swiftvr_runner import SwiftVRRunner # noqa: F401
Expand Down Expand Up @@ -128,6 +129,7 @@ def main():
"seedvr2",
"swiftvr",
"neopp",
"openpi",
"motus",
"lingbot_world_fast",
"worldmirror",
Expand Down
7 changes: 7 additions & 0 deletions lightx2v/models/networks/openpi/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"""Native PyTorch OpenPI network family for LightX2V."""

from .config import Pi0Config
from .model import OpenPIModel
from .observation import Observation

__all__ = ["Observation", "OpenPIModel", "Pi0Config"]
77 changes: 77 additions & 0 deletions lightx2v/models/networks/openpi/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
"""Configuration for the PyTorch pi0.5-LIBERO backend."""

from __future__ import annotations

from collections.abc import Mapping
from dataclasses import dataclass
from typing import Any, Literal


@dataclass(frozen=True)
class GemmaConfig:
width: int
depth: int
mlp_dim: int
num_heads: int
num_kv_heads: int
head_dim: int


GemmaVariant = Literal["dummy", "gemma_300m", "gemma_2b"]


def get_config(variant: GemmaVariant) -> GemmaConfig:
if variant == "dummy":
return GemmaConfig(width=64, depth=4, mlp_dim=128, num_heads=8, num_kv_heads=1, head_dim=16)
if variant == "gemma_300m":
return GemmaConfig(width=1024, depth=18, mlp_dim=4096, num_heads=8, num_kv_heads=1, head_dim=256)
if variant == "gemma_2b":
return GemmaConfig(width=2048, depth=18, mlp_dim=16384, num_heads=8, num_kv_heads=1, head_dim=256)
raise ValueError(f"Unsupported OpenPI Gemma variant: {variant!r}")


@dataclass(frozen=True)
class Pi0Config:
"""Dimensions and runtime options for pi0.5-LIBERO."""

action_dim: int = 32
action_horizon: int = 10
max_token_len: int = 200
dtype: Literal["bfloat16", "float32"] = "bfloat16"
paligemma_variant: GemmaVariant = "gemma_2b"
action_expert_variant: GemmaVariant = "gemma_300m"
pi05: bool = True
discrete_state_input: bool = False
pytorch_compile_mode: str | None = None

@classmethod
def from_mapping(cls, config: Mapping[str, Any]) -> "Pi0Config":
return cls(
action_dim=config["action_dim"],
action_horizon=config["action_horizon"],
max_token_len=config["max_token_len"],
dtype=config["dtype"],
paligemma_variant=config["paligemma_variant"],
action_expert_variant=config["action_expert_variant"],
pi05=config["pi05"],
discrete_state_input=config["discrete_state_input"],
pytorch_compile_mode=config["pytorch_compile_mode"],
)

def validate_pi05_libero(self) -> None:
expected = {
"pi05": True,
"paligemma_variant": "gemma_2b",
"action_expert_variant": "gemma_300m",
"action_dim": 32,
"action_horizon": 10,
"max_token_len": 200,
"discrete_state_input": False,
}
actual = {name: getattr(self, name) for name in expected}
wrong = {name: (actual[name], value) for name, value in expected.items() if actual[name] != value}
if wrong:
details = ", ".join(f"{name}={got!r} (expected {want!r})" for name, (got, want) in wrong.items())
raise ValueError(f"Configuration does not match the released pi05_libero checkpoint: {details}")
if self.dtype not in {"bfloat16", "float32"}:
raise ValueError(f"Unsupported OpenPI dtype: {self.dtype!r}")
244 changes: 244 additions & 0 deletions lightx2v/models/networks/openpi/gemma.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
# Adapted from Physical Intelligence OpenPI (Apache-2.0), commit 15a9616.
# Localized for the LightX2V OpenPI backend; no runtime OpenPI/JAX dependency.

from typing import Literal

import torch
from torch import nn
from transformers import GemmaForCausalLM, PaliGemmaForConditionalGeneration
from transformers.models.auto import CONFIG_MAPPING
from transformers.models.gemma import modeling_gemma


class PaliGemmaWithExpertModel(nn.Module):
def __init__(
self,
vlm_config,
action_expert_config,
use_adarms=None,
precision: Literal["bfloat16", "float32"] = "bfloat16",
):
if use_adarms is None:
use_adarms = [False, False]
super().__init__()

vlm_config_hf = CONFIG_MAPPING["paligemma"]()
vlm_config_hf._vocab_size = 257152 # noqa: SLF001
vlm_config_hf.image_token_index = 257152
vlm_config_hf.text_config.hidden_size = vlm_config.width
vlm_config_hf.text_config.intermediate_size = vlm_config.mlp_dim
vlm_config_hf.text_config.num_attention_heads = vlm_config.num_heads
vlm_config_hf.text_config.head_dim = vlm_config.head_dim
vlm_config_hf.text_config.num_hidden_layers = vlm_config.depth
vlm_config_hf.text_config.num_key_value_heads = vlm_config.num_kv_heads
vlm_config_hf.text_config.hidden_activation = "gelu_pytorch_tanh"
vlm_config_hf.text_config.torch_dtype = "float32"
vlm_config_hf.text_config.vocab_size = 257152
vlm_config_hf.text_config.use_adarms = use_adarms[0]
vlm_config_hf.text_config.adarms_cond_dim = vlm_config.width if use_adarms[0] else None
vlm_config_hf.vision_config.intermediate_size = 4304
vlm_config_hf.vision_config.projection_dim = 2048
vlm_config_hf.vision_config.projector_hidden_act = "gelu_fast"
vlm_config_hf.vision_config.torch_dtype = "float32"

action_expert_config_hf = CONFIG_MAPPING["gemma"](
head_dim=action_expert_config.head_dim,
hidden_size=action_expert_config.width,
intermediate_size=action_expert_config.mlp_dim,
num_attention_heads=action_expert_config.num_heads,
num_hidden_layers=action_expert_config.depth,
num_key_value_heads=action_expert_config.num_kv_heads,
vocab_size=257152,
hidden_activation="gelu_pytorch_tanh",
torch_dtype="float32",
use_adarms=use_adarms[1],
adarms_cond_dim=action_expert_config.width if use_adarms[1] else None,
)

self.paligemma = PaliGemmaForConditionalGeneration(config=vlm_config_hf)
self.gemma_expert = GemmaForCausalLM(config=action_expert_config_hf)
self.gemma_expert.model.embed_tokens = None

self.to_bfloat16_for_selected_params(precision)

def to_bfloat16_for_selected_params(self, precision: Literal["bfloat16", "float32"] = "bfloat16"):
if precision == "bfloat16":
self.to(dtype=torch.bfloat16)
elif precision == "float32":
self.to(dtype=torch.float32)
return
else:
raise ValueError(f"Invalid precision: {precision}")

params_to_keep_float32 = [
"vision_tower.vision_model.embeddings.patch_embedding.weight",
"vision_tower.vision_model.embeddings.patch_embedding.bias",
"vision_tower.vision_model.embeddings.position_embedding.weight",
"input_layernorm",
"post_attention_layernorm",
"model.norm",
]

for name, param in self.named_parameters():
if any(selector in name for selector in params_to_keep_float32):
param.data = param.data.to(dtype=torch.float32)

def embed_image(self, image: torch.Tensor):
return self.paligemma.model.get_image_features(image)

def embed_language_tokens(self, tokens: torch.Tensor):
return self.paligemma.language_model.embed_tokens(tokens)

def forward(
self,
attention_mask: torch.Tensor | None = None,
position_ids: torch.LongTensor | None = None,
past_key_values: list[torch.FloatTensor] | None = None,
inputs_embeds: list[torch.FloatTensor] | None = None,
use_cache: bool | None = None,
adarms_cond: list[torch.Tensor] | None = None,
):
if adarms_cond is None:
adarms_cond = [None, None]
if inputs_embeds[1] is None:
prefix_output = self.paligemma.language_model.forward(
inputs_embeds=inputs_embeds[0],
attention_mask=attention_mask,
position_ids=position_ids,
past_key_values=past_key_values,
use_cache=use_cache,
adarms_cond=adarms_cond[0],
)
prefix_past_key_values = prefix_output.past_key_values
prefix_output = prefix_output.last_hidden_state
suffix_output = None
elif inputs_embeds[0] is None:
suffix_output = self.gemma_expert.model.forward(
inputs_embeds=inputs_embeds[1],
attention_mask=attention_mask,
position_ids=position_ids,
past_key_values=past_key_values,
use_cache=use_cache,
adarms_cond=adarms_cond[1],
)
suffix_output = suffix_output.last_hidden_state
prefix_output = None
prefix_past_key_values = None
else:
models = [self.paligemma.language_model, self.gemma_expert.model]
num_layers = self.paligemma.config.text_config.num_hidden_layers

use_gradient_checkpointing = (hasattr(self.gemma_expert.model, "gradient_checkpointing") and self.gemma_expert.model.gradient_checkpointing and self.training) or (
hasattr(self, "gradient_checkpointing") and self.gradient_checkpointing and self.training
)

if self.training and hasattr(self.gemma_expert.model, "gradient_checkpointing"):
if not self.gemma_expert.model.gradient_checkpointing:
self.gemma_expert.model.gradient_checkpointing = True
use_gradient_checkpointing = True

def compute_layer_complete(layer_idx, inputs_embeds, attention_mask, position_ids, adarms_cond):
models = [self.paligemma.language_model, self.gemma_expert.model]

query_states = []
key_states = []
value_states = []
gates = []
for i, hidden_states in enumerate(inputs_embeds):
layer = models[i].layers[layer_idx]
hidden_states, gate = layer.input_layernorm(hidden_states, cond=adarms_cond[i]) # noqa: PLW2901
gates.append(gate)

input_shape = hidden_states.shape[:-1]
hidden_shape = (*input_shape, -1, layer.self_attn.head_dim)
query_state = layer.self_attn.q_proj(hidden_states).view(hidden_shape).transpose(1, 2)
key_state = layer.self_attn.k_proj(hidden_states).view(hidden_shape).transpose(1, 2)
value_state = layer.self_attn.v_proj(hidden_states).view(hidden_shape).transpose(1, 2)

query_states.append(query_state)
key_states.append(key_state)
value_states.append(value_state)

query_states = torch.cat(query_states, dim=2)
key_states = torch.cat(key_states, dim=2)
value_states = torch.cat(value_states, dim=2)

dummy_tensor = torch.zeros(
query_states.shape[0],
query_states.shape[2],
query_states.shape[-1],
device=query_states.device,
dtype=query_states.dtype,
)
cos, sin = self.paligemma.model.language_model.rotary_emb(dummy_tensor, position_ids)
query_states, key_states = modeling_gemma.apply_rotary_pos_emb(query_states, key_states, cos, sin, unsqueeze_dim=1)

batch_size = query_states.shape[0]
scaling = self.paligemma.language_model.layers[layer_idx].self_attn.scaling

att_output, _ = modeling_gemma.eager_attention_forward(
self.paligemma.language_model.layers[layer_idx].self_attn,
query_states,
key_states,
value_states,
attention_mask,
scaling,
)
head_dim = self.paligemma.language_model.layers[layer_idx].self_attn.head_dim
att_output = att_output.reshape(batch_size, -1, 1 * 8 * head_dim)

outputs_embeds = []
start_pos = 0
for i, hidden_states in enumerate(inputs_embeds):
layer = models[i].layers[layer_idx]
end_pos = start_pos + hidden_states.shape[1]

if att_output.dtype != layer.self_attn.o_proj.weight.dtype:
att_output = att_output.to(layer.self_attn.o_proj.weight.dtype)
out_emb = layer.self_attn.o_proj(att_output[:, start_pos:end_pos])

out_emb = modeling_gemma._gated_residual(hidden_states, out_emb, gates[i]) # noqa: SLF001
after_first_residual = out_emb.clone()
out_emb, gate = layer.post_attention_layernorm(out_emb, cond=adarms_cond[i])
if layer.mlp.up_proj.weight.dtype == torch.bfloat16:
out_emb = out_emb.to(dtype=torch.bfloat16)

out_emb = layer.mlp(out_emb)
out_emb = modeling_gemma._gated_residual(after_first_residual, out_emb, gate) # noqa: SLF001
outputs_embeds.append(out_emb)
start_pos = end_pos

return outputs_embeds

for layer_idx in range(num_layers):
if use_gradient_checkpointing:
inputs_embeds = torch.utils.checkpoint.checkpoint(
compute_layer_complete,
layer_idx,
inputs_embeds,
attention_mask,
position_ids,
adarms_cond,
use_reentrant=False,
preserve_rng_state=False,
)
else:
inputs_embeds = compute_layer_complete(layer_idx, inputs_embeds, attention_mask, position_ids, adarms_cond)

def compute_final_norms(inputs_embeds, adarms_cond):
outputs_embeds = []
for i, hidden_states in enumerate(inputs_embeds):
out_emb, _ = models[i].norm(hidden_states, cond=adarms_cond[i])
outputs_embeds.append(out_emb)
return outputs_embeds

if use_gradient_checkpointing:
outputs_embeds = torch.utils.checkpoint.checkpoint(compute_final_norms, inputs_embeds, adarms_cond, use_reentrant=False, preserve_rng_state=False)
else:
outputs_embeds = compute_final_norms(inputs_embeds, adarms_cond)

prefix_output = outputs_embeds[0]
suffix_output = outputs_embeds[1]
prefix_past_key_values = None

return [prefix_output, suffix_output], prefix_past_key_values
Loading
Loading