evo1-meanflow / scripts /Evo1_splitMeanflow.py
SeongJoon's picture
Add Evo1 MeanFlow variants (meanflow / adaptive / nocurr / splitMeanflow)
b1c7880 verified
Raw History Blame Contribute Delete
13.3 kB
"""
EVO1 + SplitMeanFlow Action Head
compute_splitMeanflow_loss():
JVP 없이 정적뢄 가법성 ν•­λ“±μ‹μœΌλ‘œ u_tgt 계산:
r = t (flow matching, 50%):
u_tgt = v_hat = wΒ·v + (1-w)Β·u(z_r, t, t) [CFG correction]
r < t (SplitMeanFlow, 50%):
s ~ Uniform(r, t)
z_s = z_r + (s-r)Β·sg[u(z_r, s, r)] [Euler step, stop-grad]
u_tgt = [(s-r)Β·sg[u(z_r,s,r)] + (t-s)Β·sg[u(z_s,t,s)]] / (t-r)
L = adaptive_l2_loss(u_ΞΈ(z_r,t,r) - sg(u_tgt))
"""
import sys
import os
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
from typing import List, Union, Tuple
import torch
import torch.nn as nn
from PIL import Image
from model.internvl3.internvl3_embedder import InternVL3Embedder
from model.action_head.flow_matching_splitMeanflow import (
SplitMeanFlowActionHead,
adaptive_l2_loss,
stopgrad,
sample_t_r_curriculum,
get_max_gap,
)
import logging
class EVO1_SplitMeanFlow(nn.Module):
"""EVO1 λͺ¨λΈ (VLM + SplitMeanFlow Action Head)"""
def __init__(self, config: dict):
super().__init__()
self.config = config
self._device = config.get("device", "cuda")
self.return_cls_only = config.get("return_cls_only", False)
vlm_name = config.get("vlm_name", "OpenGVLab/InternVL3-1B")
self.embedder = InternVL3Embedder(model_name=vlm_name, device=self._device)
horizon = config.get("action_horizon", config.get("horizon", 16))
per_action_dim = config.get("per_action_dim", 7)
action_dim = horizon * per_action_dim
config["horizon"] = horizon
config["per_action_dim"] = per_action_dim
config["action_dim"] = action_dim
self.horizon = horizon
self.per_action_dim = per_action_dim
self.action_dim = action_dim
self.w = config.get("cfg_scale", 2.0)
from types import SimpleNamespace
self.action_head = SplitMeanFlowActionHead(
config=SimpleNamespace(
embed_dim = config.get("embed_dim", 896),
hidden_dim = config.get("hidden_dim", 1024),
action_dim = action_dim,
horizon = horizon,
per_action_dim = per_action_dim,
state_dim = config.get("state_dim", 7),
state_hidden_dim = config.get("state_hidden_dim", 1024),
num_heads = config.get("num_heads", 8),
num_layers = config.get("num_layers", 8),
dropout = config.get("dropout", 0.0),
num_categories = config.get("num_categories", 1),
cfg_scale = config.get("cfg_scale", 2.0),
)
).to(self._device)
# ── VLM 인코딩 ─────────────────────────────────────────────────────────────
def get_vl_embeddings(
self,
images: List[Image.Image],
image_mask: torch.Tensor,
prompt: str = "",
return_cls_only: Union[bool, None] = None,
) -> torch.Tensor:
if return_cls_only is None:
return_cls_only = self.return_cls_only
if images is None or len(images) == 0:
raise ValueError("Must provide at least one image.")
return self.embedder.get_fused_image_text_embedding_from_tensor_images(
image_tensors=images,
image_mask=image_mask,
text_prompt=prompt,
return_cls_only=return_cls_only,
)
def prepare_state(self, state_input):
if isinstance(state_input, list):
state_tensor = torch.tensor(state_input)
elif isinstance(state_input, torch.Tensor):
state_tensor = state_input
else:
raise TypeError("Unsupported state input type")
if state_tensor.ndim == 1:
state_tensor = state_tensor.unsqueeze(0)
return state_tensor.to(self._device)
# ── μΆ”λ‘  ──────────────────────────────────────────────────────────────────
def forward(
self,
fused_tokens: torch.Tensor,
state: torch.Tensor = None,
action_mask: torch.Tensor = None,
embodiment_ids: torch.Tensor = None,
) -> torch.Tensor:
return self.action_head.get_action(
fused_tokens,
state=state,
action_mask=action_mask,
embodiment_id=embodiment_ids,
)
@torch.no_grad()
def run_inference(self, images, image_mask, prompt, state_input,
return_cls_only=None, action_mask=None) -> torch.Tensor:
fused = self.get_vl_embeddings(images, image_mask, prompt, return_cls_only)
state = self.prepare_state(state_input)
return self.forward(fused, state=state, action_mask=action_mask)
# ── SplitMeanFlow ν•™μŠ΅ 손싀 ────────────────────────────────────────────────
def compute_splitMeanflow_loss(
self,
fused_tokens: torch.Tensor, # (B, seq_len, embed_dim)
states: torch.Tensor, # (B, state_dim)
actions_gt: torch.Tensor, # (B, data_H, per_action_dim) or (B, data_H*D)
action_mask: torch.Tensor, # (B, ...) or None
step: int,
total_steps: int,
) -> Tuple[torch.Tensor, dict]:
"""
SplitMeanFlow 손싀 β€” JVP μ—†μŒ, 순수 λŒ€μˆ˜ 항등식 + Endpoint Supervision.
μˆ˜μ‹:
z_r = (1-r)Β·x + rΒ·e [보간: μ‹œμž‘μ  at r]
gap = t - r
[r = t μƒ˜ν”Œ (flow matching, ~50%)]:
u_tgt = wΒ·v + (1-w)Β·sg[u(z_r, t, t)] [CFG correction]
[r < t μƒ˜ν”Œ (SplitMF, ~40%)]:
s ~ U(r, t)
z_s = z_r + (s-r)Β·sg[u(z_r, s, r)]
u_tgt = [(s-r)Β·sg[u_rs] + (t-s)Β·sg[u_st]] / (t-r)
[Endpoint μƒ˜ν”Œ (t=1, r=0, ~10%)]:
z_r = x (r=0μ΄λ―€λ‘œ clean actionμ—μ„œ μ‹œμž‘)
s ~ U(0, 1)
u_tgt = sΒ·sg[u(x, s, 0)] + (1-s)Β·sg[u(z_s, 1, s)]
β†’ NFE=1 μΆ”λ‘  쑰건을 직접 ν•™μŠ΅
L = adaptive_l2_loss(u_ΞΈ(z_r, t, r) - sg(u_tgt))
"""
head = self.action_head
B = fused_tokens.shape[0]
device = fused_tokens.device
# endpoint_ratio: λ°°μΉ˜μ—μ„œ (t=1, r=0)으둜 κ°•μ œν•  λΉ„μœ¨
endpoint_ratio = self.config.get("endpoint_ratio", 0.1)
embodiment_id = torch.zeros(B, dtype=torch.long, device=device)
# ── State μž„λ² λ”© ──────────────────────────────────────────────────────
state_emb = None
if states is not None and head.state_encoder is not None:
enc_dtype = next(head.state_encoder.parameters()).dtype
state_emb = head.state_encoder(
states.to(enc_dtype), embodiment_id
).to(fused_tokens.dtype)
# ── 데이터 μ€€λΉ„ ───────────────────────────────────────────────────────
x_raw = actions_gt.to(fused_tokens.dtype)
if x_raw.ndim == 2:
x_raw = x_raw.view(B, -1, self.per_action_dim)
x = x_raw[:, :self.horizon, :].contiguous() # (B, H, D)
e = torch.randn_like(x)
if action_mask is not None:
mask_raw = action_mask.view(B, -1, self.per_action_dim)
mask = mask_raw[:, :self.horizon, :].to(x.dtype)
e = e * mask
v = e - x # straight-path velocity (B, H, D)
# ── 컀리큘럼 (t, r) μƒ˜ν”Œλ§ ───────────────────────────────────────────
t, r = sample_t_r_curriculum(B, device, step, total_steps, flow_ratio=0.5)
t = t.to(x.dtype)
r = r.to(x.dtype)
# ── Endpoint κ°•μ œ: 일뢀 μƒ˜ν”Œμ„ NFE=1 μΆ”λ‘  쑰건 (t=1, r=0)으둜 κ³ μ • ──
# SplitMFλŠ” algebraic target이라 gap=1.0μ—μ„œλ„ target이 μ•ˆμ •μ 
ep_mask = torch.rand(B, device=device) < endpoint_ratio
t[ep_mask] = 1.0
r[ep_mask] = 0.0
t_ = t.view(B, 1, 1)
r_ = r.view(B, 1, 1)
# z_r: μ‹œμž‘μ  at r
z_r = (1.0 - r_) * x + r_ * e # (B, H, D)
gap = (t - r) # (B,)
fm_mask = gap < 1e-6 # r = t β†’ flow matching
mf_mask = ~fm_mask # r < t β†’ SplitMeanFlow
# ── u_tgt 계산 (stop-grad, no JVP) ──────────────────────────────────
u_tgt = torch.empty(B, self.action_dim, device=device, dtype=x.dtype)
# [1] Flow matching μƒ˜ν”Œ (r = t)
if fm_mask.any():
idx = fm_mask
with torch.no_grad():
# u(z_r, t, t): μˆœκ°„ 속도 예츑
u_t_fm = head.forward_meanflow(
fused_tokens[idx],
state_emb[idx] if state_emb is not None else None,
z_r[idx], t[idx], r[idx],
embodiment_id[idx],
)
u_t_fm_seq = u_t_fm.view(-1, self.horizon, self.per_action_dim)
# CFG 보정: v_hat = wΒ·v + (1-w)Β·u_t
v_hat_fm = self.w * v[idx] + (1.0 - self.w) * u_t_fm_seq
u_tgt[idx] = v_hat_fm.reshape(-1, self.action_dim)
# [2] SplitMeanFlow μƒ˜ν”Œ (r < t)
if mf_mask.any():
idx = mf_mask
B_mf = idx.sum().item()
z_r_mf = z_r[idx]
t_mf = t[idx]
r_mf = r[idx]
gap_mf = gap[idx]
ft_mf = fused_tokens[idx]
se_mf = state_emb[idx] if state_emb is not None else None
ei_mf = embodiment_id[idx]
# split point s ∈ (r, t)
alpha = torch.rand(B_mf, device=device, dtype=x.dtype)
s_mf = r_mf + alpha * gap_mf # (B_mf,)
with torch.no_grad():
# u(z_r, s, r) β€” 짧은 ꡬ간
u_rs = head.forward_meanflow(ft_mf, se_mf, z_r_mf, s_mf, r_mf, ei_mf)
u_rs_seq = u_rs.view(B_mf, self.horizon, self.per_action_dim)
# Euler step: z_s = z_r + (s-r)Β·u_rs
dt_rs = (s_mf - r_mf).view(B_mf, 1, 1)
z_s = z_r_mf + dt_rs * u_rs_seq # (B_mf, H, D)
# u(z_s, t, s) β€” λ‚˜λ¨Έμ§€ ꡬ간
u_st = head.forward_meanflow(ft_mf, se_mf, z_s, t_mf, s_mf, ei_mf)
u_st_seq = u_st.view(B_mf, self.horizon, self.per_action_dim)
# λŒ€μˆ˜ target: [(s-r)Β·u_rs + (t-s)Β·u_st] / (t-r)
dt_st = (t_mf - s_mf).view(B_mf, 1, 1)
dt_rt = gap_mf.view(B_mf, 1, 1).clamp(min=1e-6)
u_tgt_seq = (dt_rs * u_rs_seq + dt_st * u_st_seq) / dt_rt # (B_mf, H, D)
u_tgt[idx] = u_tgt_seq.reshape(B_mf, self.action_dim)
# ── ν˜„μž¬ λͺ¨λΈ 예츑 (gradient 흐름) ────────────────────────────────────
u_pred = head.forward_meanflow(fused_tokens, state_emb, z_r, t, r, embodiment_id)
# ── 손싀 ─────────────────────────────────────────────────────────────
error = u_pred - stopgrad(u_tgt)
loss = adaptive_l2_loss(error)
loss_dict = {
"smf_loss": loss.item(),
"mse": (stopgrad(error) ** 2).mean().item(),
"max_gap": get_max_gap(step, total_steps),
"mean_gap": float(gap.mean().item()),
"fm_ratio": float(fm_mask.float().mean().item()),
"ep_ratio": float(ep_mask.float().mean().item()),
}
return loss, loss_dict
# ── Finetune ν”Œλž˜κ·Έ ────────────────────────────────────────────────────────
def _freeze_module(self, module: nn.Module, name: str):
print(f"Freezing {name} parameters...")
for p in module.parameters():
p.requires_grad = False
def set_finetune_flags(self):
config = self.config
if not config.get("finetune_vlm", False):
self._freeze_module(self.embedder, "VLM (InternVL3)")
else:
print("Finetuning VLM (InternVL3)...")
if not config.get("finetune_action_head", False):
self._freeze_module(self.action_head, "Action Head (SplitMeanFlow)")
else:
print("Finetuning Action Head (SplitMeanFlow)...")