Files
pi06-minimal-validation/src/pi06mini/infer.py
T
2026-07-12 19:33:15 +08:00

149 lines
7.1 KiB
Python

from __future__ import annotations
import argparse
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
import torch
from .data import Pi06ExperienceDataset
from .flow import FlowMatching
from .model import Pi06Config, Pi06Mini
from .recap import classifier_free_guidance
from .runtime import choose_device, write_json
from .tokenizer import MiniTokenizer
from .value import ValueConfig, ValueModel
@torch.no_grad()
def predict_subtask_token(
model: Pi06Mini,
tokenizer: MiniTokenizer,
image: torch.Tensor,
history_xy: torch.Tensor,
action_horizon: int,
device: torch.device,
) -> int:
"""Autoregressively produce the semantic token used by low-level control."""
query = tokenizer.build_subtask_query(history_xy)[None].to(device)
zero_actions = torch.zeros(1, action_horizon, model.config.action_dim, device=device)
output = model(
image,
query,
zero_actions,
torch.zeros(1, device=device),
generated_token_start=query.shape[1],
)
return int(output.text_logits[0, -1].argmax().cpu())
@torch.no_grad()
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.get("device","auto")); tokenizer=MiniTokenizer()
dataset=Pi06ExperienceDataset(
root/"data/overfit/overfit_manifest.jsonl",image_size=cfg["data"]["image_size"],
augmentation_factor=cfg["experience"]["augmentation_factor"],
action_horizon=cfg["data"]["action_horizon"],n_step=cfg["experience"]["n_step"],
gamma=cfg["experience"]["gamma"],positive_quantile=cfg["experience"]["positive_quantile"],
action_statistics=checkpoint["action_statistics"],
)
mixed=Pi06Mini(Pi06Config(**checkpoint["policy_config"]));mixed.load_state_dict(checkpoint["mixed"]);mixed.to(device).eval()
recap=Pi06Mini(Pi06Config(**checkpoint["policy_config"]));recap.load_state_dict(checkpoint["recap"]);recap.to(device).eval()
value=ValueModel(ValueConfig(**checkpoint["value_config"]));value.load_state_dict(checkpoint["value"]);value.to(device).eval()
flow=FlowMatching(cfg["inference"]["flow_steps"])
demo=next(dataset[i] for i in range(len(dataset)) if dataset[i]["source_type"]=="demo_success")
failure=next(dataset[i] for i in range(len(dataset)) if "stop" in dataset[i]["source_type"])
image=demo["image"][None].to(device)
predicted_subtask = predict_subtask_token(
recap, tokenizer, image, demo["history_xy"], dataset.action_horizon, device
)
def condition(model:Pi06Mini, advantage:str|None, x:torch.Tensor,t:torch.Tensor)->torch.Tensor:
ids = tokenizer.build_action_condition(
demo["history_xy"], predicted_subtask, advantage
)[None].to(device)
return model(
image, ids, x, t,
generated_token_start=tokenizer.build_subtask_query(demo["history_xy"]).numel(),
).vector_field
def sample_field(fn,seed:int=2):
torch.manual_seed(seed)
return flow.sample(fn,(1,dataset.action_horizon,2),device,cfg["inference"]["flow_steps"])[0].cpu()
mixed_actions=sample_field(lambda x,t:condition(mixed,None,x,t))
uncond_actions=sample_field(lambda x,t:condition(recap,None,x,t))
positive_actions=sample_field(lambda x,t:condition(recap,"positive",x,t))
negative_actions=sample_field(lambda x,t:condition(recap,"negative",x,t))
def cfg_field(x,t):
return classifier_free_guidance(
condition(recap,"positive",x,t),condition(recap,None,x,t),
guidance_scale=cfg["inference"]["cfg_beta"],
)
cfg_actions=sample_field(cfg_field)
decoded={
"mixed":dataset.action_normalizer.decode(mixed_actions,demo["history_xy"]).numpy(),
"unconditional":dataset.action_normalizer.decode(uncond_actions,demo["history_xy"]).numpy(),
"positive":dataset.action_normalizer.decode(positive_actions,demo["history_xy"]).numpy(),
"negative":dataset.action_normalizer.decode(negative_actions,demo["history_xy"]).numpy(),
"cfg":dataset.action_normalizer.decode(cfg_actions,demo["history_xy"]).numpy(),
}
target=demo["future_xy"].numpy(); failure_xy=failure["future_xy"].numpy()
metrics={name:float(np.mean((xy-target)**2)) for name,xy in decoded.items()}
metrics["negative_to_failure_mse"]=float(np.mean((decoded["negative"]-failure_xy)**2))
fig,ax=plt.subplots(figsize=(9,6));ax.plot(target[:,0],target[:,1],"o-",label="success target",linewidth=2)
ax.plot(failure_xy[:,0],failure_xy[:,1],":",label="failure experience")
for name,color in [("mixed","gray"),("unconditional","purple"),("positive","green"),("negative","red"),("cfg","orange")]:
xy=decoded[name];ax.plot(xy[:,0],xy[:,1],"x--",label=f"{name} ({metrics[name]:.3f})",color=color)
ax.scatter([0],[0],c="black",marker="s",label="ego");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.6 Recap advantage-conditioned trajectories")
outdir=Path(checkpoint_path).parent;trajectory_path=outdir/"recap_trajectory_comparison.png";fig.tight_layout();fig.savefig(trajectory_path,dpi=170);plt.close(fig)
value_rows=[]
for i in range(len(dataset)):
s=dataset[i]
histories=s["value_histories"]
ids=torch.stack([tokenizer.build_value_input(history) for history in histories]).to(device)
images=s["image"][None].expand(len(histories),-1,-1,-1).to(device)
predictions=value(images,ids).expectation.cpu()
targets=s["value_targets"]
value_rows.append({
"source_type":s["source_type"],
"targets":targets.tolist(),
"predictions":predictions.tolist(),
"curve_mse":float(torch.mean((predictions-targets).square())),
})
fig,axes=plt.subplots(2,2,figsize=(10,7),sharex=True,sharey=True)
for ax,row in zip(axes.flat,value_rows):
ax.plot(row["targets"],"o-",label="return target")
ax.plot(row["predictions"],"x--",label="value prediction")
ax.set_title(f'{row["source_type"]} MSE={row["curve_mse"]:.3f}')
ax.set_ylim(-1.05,.05);ax.grid(True,alpha=.3)
axes[0,0].legend(fontsize=8);fig.supxlabel("episode step");fig.supylabel("normalized value")
value_path=outdir/"value_comparison.png";fig.tight_layout();fig.savefig(value_path,dpi=170);plt.close(fig)
try:
predicted_subtask_label = tokenizer.subtasks.decode(predicted_subtask)
except ValueError:
predicted_subtask_label = f"token_{predicted_subtask}"
evaluation = {
"predicted_subtask": predicted_subtask_label,
"target_subtask": tokenizer.subtasks.classify(demo["reasoning"]),
"trajectory_metrics": metrics,
"values": value_rows,
}
write_json(outdir/"evaluation.json",evaluation)
print(evaluation)
return trajectory_path,value_path
def main():
parser=argparse.ArgumentParser();parser.add_argument("--checkpoint",default="outputs/overfit/checkpoint.pt")
evaluate(parser.parse_args().checkpoint)
if __name__=="__main__":main()