Align pi0.7 minimal structure with paper

This commit is contained in:
seeker
2026-07-12 20:37:34 +08:00
parent b4c8db846b
commit cd4e8e6a3f
21 changed files with 1485 additions and 660 deletions
+250 -32
View File
@@ -1,40 +1,258 @@
from __future__ import annotations
import argparse
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np,torch
import numpy as np
import torch
from .data import Pi07SteerableDataset
from .flow import FlowMatching
from .model import Pi07Config,Pi07Mini,HighLevelPolicy,TinyGoalGenerator
from .runtime import load_config,choose_device,write_json
from .tokenizer import MiniTokenizer,EpisodeMetadata
from .model import HighLevelPolicy, Pi07Config, Pi07Mini, TinyGoalGenerator
from .rtc import build_rtc_conditioning
from .runtime import choose_device, write_json
from .tokenizer import EpisodeMetadata, MiniTokenizer
@torch.no_grad()
def evaluate(checkpoint_path):
ck=torch.load(checkpoint_path,map_location='cpu',weights_only=False);cfg=ck['config'];root=Path(__file__).resolve().parents[2];dev=choose_device(cfg['device']);tok=MiniTokenizer();ds=Pi07SteerableDataset(root/'data/overfit/overfit_manifest.jsonl',image_size=cfg['data']['image_size'],history_frames=cfg['data']['history_frames'],action_horizon=cfg['data']['action_horizon'],action_statistics=ck['action_statistics'])
pcfg=Pi07Config(**ck['policy_config']);policy=Pi07Mini(pcfg);policy.load_state_dict(ck['policy']);policy.to(dev).eval();high=HighLevelPolicy(pcfg);high.load_state_dict(ck['high_level']);high.to(dev).eval();goal=TinyGoalGenerator(tok.vocab_size);goal.load_state_dict(ck['goal']);goal.to(dev).eval();flow=FlowMatching(cfg['flow']['integration_steps']);s=next(ds[i] for i in range(len(ds)) if ds[i]['policy_kind']=='expert');meta=EpisodeMetadata(**s['token_metadata']);image=s['image'][None].to(dev)
prompt,_=tok.encode_prompt('drive safely',None,meta,include_subtask=False);hl=high(image,prompt[None].to(dev),s['history_images'][None].to(dev),s['history_states'][None].to(dev),s['current_state'][None].to(dev));pred_subtask=tok.subtasks.decode(int(hl.logits.argmax(-1).cpu()))
full=tok.build_policy_sequence('drive safely',s['reasoning'],s['actions'],meta);ids=full.input_ids[:full.action_start_index][None].to(dev)
torch.manual_seed(cfg['seed']);g=torch.randn_like(s['subgoal_pixel_values'][None]).to(dev);grid=torch.linspace(0,1,6,device=dev)
for i in range(5): g=g+(grid[i+1]-grid[i])*goal(image,ids,g,grid[i].expand(1)).vector_field
generated_goal=g.cpu()[0]
def sample(name,include_meta=True,use_goal=True,use_history=True,cfg_beta=None,generated=False):
seq=tok.build_policy_sequence('drive safely',s['reasoning'],s['actions'],meta,include_episode_metadata=include_meta);xids=seq.input_ids[:seq.action_start_index][None].to(dev)
goal_pixels=(generated_goal if generated else s['subgoal_pixel_values'])[None].to(dev) if use_goal else None
common=dict(pixel_values=image,history_pixel_values=s['history_images'][None].to(dev) if use_history else None,history_states=s['history_states'][None].to(dev) if use_history else None,current_state=s['current_state'][None].to(dev),subgoal_pixel_values=goal_pixels)
def field(x,t):
if cfg_beta is None:return policy(input_ids=xids,noisy_actions=x,timesteps=t,**common).vector_field
un=tok.build_policy_sequence('drive safely',s['reasoning'],s['actions'],meta,include_episode_metadata=False);uids=un.input_ids[:un.action_start_index][None].to(dev)
return policy.metadata_cfg(xids,uids,guidance_scale=cfg_beta,noisy_actions=x,timesteps=t,**common)
torch.manual_seed(cfg['evaluation']['fixed_noise_seed']);a=flow.sample(field,(1,ds.action_horizon,2),dev,cfg['flow']['integration_steps'])[0].cpu();return ds.action_normalizer.decode(a,s['history_xy']).numpy()
curves={'full_oracle_goal':sample('full'),'generated_goal':sample('generated',generated=True),'no_metadata':sample('no_metadata',False),'no_goal':sample('no_goal',use_goal=False),'no_history':sample('no_history',use_history=False),'cfg_beta2':sample('cfg',cfg_beta=2.0)}
target=ds.action_normalizer.decode(s['rtc_target_actions'],s['history_xy']).numpy();valid=s['rtc_valid_mask'].numpy().astype(bool);metrics={k:float(np.mean((v[valid]-target[valid])**2)) for k,v in curves.items()}
out=Path(checkpoint_path).parent;fig,ax=plt.subplots(figsize=(9,6));ax.plot(target[:,0],target[:,1],'o-',label='expert target',linewidth=2)
for k,v in curves.items():ax.plot(v[:,0],v[:,1],'x--',label=f'{k} ({metrics[k]:.3f})')
ax.axis('equal');ax.grid(True,alpha=.3);ax.legend(fontsize=8);ax.set_xlabel('forward x (m)');ax.set_ylabel('left y (m)');ax.set_title('π0.7 context steering and ablations');fig.tight_layout();traj=out/'context_ablation.png';fig.savefig(traj,dpi=170);plt.close(fig)
fig,axes=plt.subplots(1,3,figsize=(10,3));axes[0].imshow(image[0].cpu().permute(1,2,0));axes[0].set_title('current observation');axes[1].imshow(s['subgoal_pixel_values'].permute(1,2,0));axes[1].set_title('endpoint goal target');axes[2].imshow(generated_goal.permute(1,2,0).clamp(0,1));axes[2].set_title('generated goal');
for a in axes:a.axis('off')
fig.tight_layout();goalpath=out/'goal_generation.png';fig.savefig(goalpath,dpi=170);plt.close(fig);write_json(out/'evaluation.json',{'predicted_subtask':pred_subtask,'trajectory_metrics':metrics});print({'predicted_subtask':pred_subtask,'trajectory_metrics':metrics});return traj,goalpath
def main():
p=argparse.ArgumentParser();p.add_argument('--checkpoint',default='outputs/overfit/checkpoint.pt');evaluate(p.parse_args().checkpoint)
if __name__=='__main__':main()
def evaluate(checkpoint_path: str) -> tuple[Path, Path]:
checkpoint = torch.load(checkpoint_path, map_location="cpu", weights_only=False)
cfg = checkpoint["config"]
root = Path(__file__).resolve().parents[2]
device = choose_device(cfg["device"])
tokenizer = MiniTokenizer()
dataset = Pi07SteerableDataset(
root / "data/overfit/overfit_manifest.jsonl",
image_size=cfg["data"]["image_size"],
history_frames=cfg["data"]["history_frames"],
action_horizon=cfg["data"]["action_horizon"],
action_statistics=checkpoint["action_statistics"],
max_rtc_delay_steps=cfg["rtc"]["max_delay_steps"],
)
policy_config = Pi07Config(**checkpoint["policy_config"])
policy = Pi07Mini(policy_config)
policy.load_state_dict(checkpoint["policy"])
policy.to(device).eval()
high_level = HighLevelPolicy(policy_config)
high_level.load_state_dict(checkpoint["high_level"])
high_level.to(device).eval()
goal_generator = TinyGoalGenerator(tokenizer.vocab_size)
goal_generator.load_state_dict(checkpoint["goal"])
goal_generator.to(device).eval()
flow = FlowMatching(cfg["flow"]["integration_steps"])
sample = next(
dataset[index]
for index in range(len(dataset))
if dataset[index]["policy_kind"] == "expert"
)
metadata = EpisodeMetadata(**sample["token_metadata"])
image = sample["image"][None].to(device)
high_level_prompt = tokenizer.encode_high_level_prompt(
"drive safely", sample["past_subtasks"], metadata
)
high_level_output = high_level(
image,
high_level_prompt[None].to(device),
sample["history_images"][None].to(device),
sample["history_states"][None].to(device),
sample["current_state"][None].to(device),
)
predicted_subtask = tokenizer.subtasks.decode(
int(high_level_output.logits.argmax(-1).cpu())
)
goal_prompt, _ = tokenizer.encode_prompt(
"drive safely", predicted_subtask, metadata
)
goal_noise = torch.randn(
sample["future_observation"][None].shape,
generator=torch.Generator().manual_seed(cfg["evaluation"]["fixed_noise_seed"]),
).to(device)
goal_grid = torch.linspace(0.0, 1.0, cfg["evaluation"]["goal_flow_steps"] + 1, device=device)
generated_goal = goal_noise
for index in range(cfg["evaluation"]["goal_flow_steps"]):
time = goal_grid[index].expand(1)
generated_goal = generated_goal + (goal_grid[index + 1] - goal_grid[index]) * goal_generator(
image, goal_prompt[None].to(device), generated_goal, time
).vector_field
generated_goal_cpu = generated_goal[0].cpu().clamp(0.0, 1.0)
def sample_policy(
*,
include_metadata: bool = True,
use_goal: bool = True,
use_history: bool = True,
guidance_beta: float | None = None,
rtc_delay: int = 0,
oracle_goal: bool = False,
) -> tuple[np.ndarray, torch.Tensor]:
conditioned = tokenizer.build_policy_sequence(
"drive safely",
predicted_subtask,
sample["actions"],
metadata,
include_episode_metadata=include_metadata,
)
conditioned_ids = conditioned.input_ids[: conditioned.action_start_index][None].to(device)
common = {
"pixel_values": image,
"history_pixel_values": sample["history_images"][None].to(device)
if use_history
else None,
"history_states": sample["history_states"][None].to(device)
if use_history
else None,
"current_state": sample["current_state"][None].to(device),
"subgoal_pixel_values": (
sample["future_observation"][None].to(device)
if oracle_goal
else generated_goal
)
if use_goal
else None,
}
if guidance_beta is None:
def field(actions: torch.Tensor, times: torch.Tensor) -> torch.Tensor:
return policy(
input_ids=conditioned_ids,
noisy_actions=actions,
timesteps=times,
**common,
).vector_field
else:
unconditional = tokenizer.build_policy_sequence(
"drive safely",
predicted_subtask,
sample["actions"],
metadata,
include_episode_metadata=False,
)
unconditional_ids = unconditional.input_ids[
: unconditional.action_start_index
][None].to(device)
def field(actions: torch.Tensor, times: torch.Tensor) -> torch.Tensor:
return policy.metadata_cfg(
conditioned_ids,
unconditional_ids,
guidance_scale=guidance_beta,
noisy_actions=actions,
timesteps=times,
**common,
)
rtc = build_rtc_conditioning(
sample["actions"],
rtc_delay,
max_delay_steps=cfg["rtc"]["max_delay_steps"],
)
torch.manual_seed(cfg["evaluation"]["fixed_noise_seed"])
normalized = flow.sample(
field,
(1, dataset.action_horizon, 2),
device,
cfg["flow"]["integration_steps"],
action_prefix=rtc.action_prefix[None].to(device),
delays=torch.tensor([rtc_delay], device=device),
)[0].cpu()
trajectory = dataset.action_normalizer.decode(
normalized, sample["history_xy"]
).numpy()
return trajectory, normalized
curves: dict[str, np.ndarray] = {}
normalized_predictions: dict[str, torch.Tensor] = {}
configurations = {
"full_generated_goal": {},
"full_oracle_goal": {"oracle_goal": True},
"no_metadata": {"include_metadata": False},
"no_goal": {"use_goal": False},
"no_history": {"use_history": False},
"metadata_cfg": {"guidance_beta": cfg["steering"]["cfg_beta"]},
}
for name, arguments in configurations.items():
curves[name], normalized_predictions[name] = sample_policy(**arguments)
target = sample["future_xy"].numpy()
target_actions = sample["actions"]
trajectory_metrics = {
name: float(np.mean((trajectory - target) ** 2))
for name, trajectory in curves.items()
}
action_metrics = {
name: float(torch.mean((actions - target_actions) ** 2))
for name, actions in normalized_predictions.items()
}
rtc_metrics: dict[str, dict[str, float]] = {}
for delay in cfg["evaluation"]["compare_rtc_delays"]:
trajectory, actions = sample_policy(rtc_delay=delay)
postfix = torch.arange(dataset.action_horizon) >= delay
rtc_metrics[str(delay)] = {
"postfix_action_mse": float(
torch.mean((actions[postfix] - target_actions[postfix]) ** 2)
),
"trajectory_mse": float(np.mean((trajectory - target) ** 2)),
}
output_dir = Path(checkpoint_path).parent
figure, axis = plt.subplots(figsize=(10, 7))
axis.plot(target[:, 0], target[:, 1], "o-", label="expert target", linewidth=2)
for name, trajectory in curves.items():
axis.plot(
trajectory[:, 0],
trajectory[:, 1],
"x--",
label=f"{name} ({trajectory_metrics[name]:.3f})",
)
axis.axis("equal")
axis.grid(True, alpha=0.3)
axis.legend(fontsize=8)
axis.set_xlabel("forward x (m)")
axis.set_ylabel("left y (m)")
axis.set_title("pi0.7 predicted-subtask context and RTC evaluation")
figure.tight_layout()
trajectory_path = output_dir / "context_ablation.png"
figure.savefig(trajectory_path, dpi=170)
plt.close(figure)
figure, axes = plt.subplots(1, 3, figsize=(10, 3))
axes[0].imshow(image[0].cpu().permute(1, 2, 0))
axes[0].set_title("current observation")
axes[1].imshow(sample["future_observation"].permute(1, 2, 0))
axes[1].set_title("future observation target")
axes[2].imshow(generated_goal_cpu.permute(1, 2, 0))
axes[2].set_title("generated visual subgoal")
for axis in axes:
axis.axis("off")
figure.tight_layout()
goal_path = output_dir / "goal_generation.png"
figure.savefig(goal_path, dpi=170)
plt.close(figure)
results = {
"target_subtask": tokenizer._classify_subtask(sample["reasoning"]),
"predicted_subtask": predicted_subtask,
"goal_image_mse": float(
torch.mean((generated_goal_cpu - sample["future_observation"]) ** 2)
),
"trajectory_metrics": trajectory_metrics,
"normalized_action_metrics": action_metrics,
"rtc_metrics": rtc_metrics,
}
write_json(output_dir / "evaluation.json", results)
print(results)
return trajectory_path, goal_path
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--checkpoint", default="outputs/overfit/checkpoint.pt")
evaluate(parser.parse_args().checkpoint)
if __name__ == "__main__":
main()