│ ├── embed_suffix() (组合当前动作向量与时间步特征)
│ │ ├── action_in_proj() (将当前动作向量映射为特征 Token)
│ │ ├── posemb_sincos() (计算时间步的 Sin-Cos 位置编码)
│ │ └── time_mlp_in() / time_mlp_out() (生成供 π0.5 AdaRMS 使用的时间条件特征)
│ ├── PaliGemma.llm() (结合 KV Cache 进行 Suffix 前向传播)
│ ├── action_out_proj() (投影网络输出得到预测的速度向量 v_t)
│ └── x_t + dt * v_t (利用 Euler 步长积分更新去噪动作 x_t)
└── _output_transform() (执行结果后处理与反归一化)
torchrun ... scripts/train_pytorch.py / uv run scripts/train_pytorch.py | 📂 /Users/julyfun/Documents/GitHub/openpi/scripts/train_pytorch.py (启动 PyTorch 训练)
├── [torchrun 模式] 启动 N 个 Python 训练进程 (跨进程:每 GPU 一个 rank)
├── init_logging() (设置日志)
├── _config.cli() | 📂 /Users/julyfun/Documents/GitHub/openpi/src/openpi/training/config.py (tyro 解析 TrainConfig)
│ └── overridable_config_cli(...) (选择如 pi05_libero/debug,并应用 CLI override)
└── train_loop(config) | 📂 /Users/julyfun/Documents/GitHub/openpi/scripts/train_pytorch.py (PyTorch 主训练流程)
│ ├── 读取 WORLD_SIZE / LOCAL_RANK / RANK (判断是否 torchrun 多进程)
│ ├── if use_ddp: torch.distributed.init_process_group(...) (跨进程:NCCL/Gloo 建立通信组)
│ └── torch.cuda.set_device(device) (当前 rank 绑定 GPU)
├── set_seed(config.seed, local_rank) (设置 torch/numpy 随机种子)
├── 处理 checkpoint_dir / resume / overwrite (创建或复用实验目录)
├── if is_main: init_wandb(...) (只在主进程初始化 W&B)
├── build_datasets(config) (构造数据加载器)
│ └── _data.create_data_loader(config, framework="pytorch", shuffle=True) | 📂 /Users/julyfun/Documents/GitHub/openpi/src/openpi/training/data_loader.py
│ ├── config.data.create(config.assets_dirs, config.model) | 📂 /Users/julyfun/Documents/GitHub/openpi/src/openpi/training/config.py (生成 DataConfig)
│ │ ├── LeRobotLiberoDataConfig.create(...) (LIBERO 路径;配置 repack/data/model transforms)
│ │ │ ├── create_base_config(...) (加载 norm_stats,设置 repo_id/asset_id)
│ │ │ ├── RepackTransform(...) | 📂 /Users/julyfun/Documents/GitHub/openpi/src/openpi/transforms.py (LeRobot 字段重映射)
│ │ │ ├── LiberoInputs(...) | 📂 /Users/julyfun/Documents/GitHub/openpi/src/openpi/policies/libero_policy.py (转成模型输入格式)
│ │ │ └── ModelTransformFactory.__call__() | 📂 /Users/julyfun/Documents/GitHub/openpi/src/openpi/training/config.py (resize/tokenize/pad)
│ │ └── RLDSDroidDataConfig.create(...) (DROID RLDS 配置;但 PyTorch 下 RLDS loader 目前会 NotImplemented)
│ └── create_torch_data_loader(...) | 📂 /Users/julyfun/Documents/GitHub/openpi/src/openpi/training/data_loader.py (PyTorch 训练实际数据路径)
│ ├── create_torch_dataset(...) (创建 LeRobotDataset / FakeDataset)
│ ├── transform_dataset(...) (串联数据变换)
│ │ └── CompositeTransform.__call__() | 📂 /Users/julyfun/Documents/GitHub/openpi/src/openpi/transforms.py
│ │ ├── RepackTransform.__call__() (重排字段)
│ │ ├── LiberoInputs.__call__() | 📂 /Users/julyfun/Documents/GitHub/openpi/src/openpi/policies/libero_policy.py (构造 image/state/actions/prompt)
│ │ ├── Normalize.__call__() | 📂 /Users/julyfun/Documents/GitHub/openpi/src/openpi/transforms.py (归一化 state/actions)
│ │ ├── ResizeImages.__call__() (图像 resize 到 224)
│ │ ├── TokenizePrompt.__call__() / TokenizeFASTInputs.__call__() (prompt/token/action token)
│ │ └── PadStatesAndActions.__call__() (补齐 action_dim)
│ ├── if torch.distributed.is_initialized(): DistributedSampler(...) (跨进程:各 rank 分片取数据)
│ └── TorchDataLoader.__iter__() (循环产出 batch;num_workers>0 时 spawn DataLoader worker 进程)
│ └── DataLoaderImpl.__iter__() (dict -> Observation, actions)
│ └── Observation.from_dict(...) | 📂 /Users/julyfun/Documents/GitHub/openpi/src/openpi/models/model.py (uint8 图像转 [-1,1],PyTorch 图像转 NCHW)
├── if is_main: 取 sample_batch 并 wandb.log(camera_views) | 📂 /Users/julyfun/Documents/GitHub/openpi/scripts/train_pytorch.py
├── 构造 model_cfg (把 config.model 转成 Pi0Config,设置 dtype/action_dim/horizon)
├── PI0Pytorch(model_cfg).to(device) | 📂 /Users/julyfun/Documents/GitHub/openpi/src/openpi/models_pytorch/pi0_pytorch.py (创建 PyTorch pi0/pi05)
│ ├── PaliGemmaWithExpertModel(...) | 📂 /Users/julyfun/Documents/GitHub/openpi/src/openpi/models_pytorch/gemma_pytorch.py (封装 PaliGemma VLM + Gemma action expert)
│ ├── Linear(action_in_proj/action_out_proj/time_mlp/...) | 📂 /Users/julyfun/Documents/GitHub/openpi/src/openpi/models_pytorch/pi0_pytorch.py
│ └── 检查 transformers_replace 是否正确安装
├── model.gradient_checkpointing_enable() (开启显存优化)
├── if use_ddp: DistributedDataParallel(model, ...) (跨进程:反向传播时同步梯度)
├── if config.pytorch_weight_path: safetensors.torch.load_model(...) (加载已转换 PyTorch 权重)
├── torch.optim.AdamW(...) (构造优化器)
├── if resuming: load_checkpoint(...) (恢复 model/optimizer/global_step)
├── lr_schedule(step) (warmup + cosine decay)
└── while global_step < config.num_train_steps (训练外循环)
├── if use_ddp and hasattr(loader, "set_epoch"): loader.set_epoch(...) (DDP shuffle epoch)
└── for observation, actions in loader (训练 batch 循环)
├── observation/actions.to(device) (搬到当前 GPU)
├── 更新 optim.param_groups 的 lr
├── losses = model(observation, actions) | 📂 /Users/julyfun/Documents/GitHub/openpi/src/openpi/models_pytorch/pi0_pytorch.py
│ └── PI0Pytorch.forward(...) (完整前向并返回逐元素 MSE loss)
│ ├── _preprocess_observation(observation, train=True)
│ │ └── preprocess_observation_pytorch(...) | 📂 /Users/julyfun/Documents/GitHub/openpi/src/openpi/models_pytorch/preprocessing_pytorch.py (resize/增强/mask)
│ ├── sample_noise(...) / sample_time(...) (采样 flow matching 噪声和时间)
│ ├── 构造 x_t = t*noise + (1-t)*actions, u_t = noise-actions
│ ├── embed_prefix(images, masks, lang_tokens, lang_masks) | 📂 /Users/julyfun/Documents/GitHub/openpi/src/openpi/models_pytorch/pi0_pytorch.py
│ │ ├── PaliGemmaWithExpertModel.embed_image(...) | 📂 /Users/julyfun/Documents/GitHub/openpi/src/openpi/models_pytorch/gemma_pytorch.py (SigLIP 视觉特征)
│ │ └── PaliGemmaWithExpertModel.embed_language_tokens(...) (语言 token embedding)
│ ├── embed_suffix(state, x_t, time) | 📂 /Users/julyfun/Documents/GitHub/openpi/src/openpi/models_pytorch/pi0_pytorch.py (action/time/state token;pi05 用 adaRMS 条件)
│ ├── make_att_2d_masks(...) (构造 attention mask)
│ ├── _prepare_attention_masks_4d(...) (转成 transformer 4D mask)
│ ├── PaliGemmaWithExpertModel.forward(...) | 📂 /Users/julyfun/Documents/GitHub/openpi/src/openpi/models_pytorch/gemma_pytorch.py
│ │ ├── for layer_idx in range(num_layers) (逐层联合处理 prefix VLM 与 suffix action expert)
│ │ │ └── compute_layer_complete(...) (QKV 拼接、RoPE、attention、MLP、残差;可 checkpoint)
│ │ └── compute_final_norms(...) (分别归一化 prefix/suffix 输出)
│ ├── action_out_proj(suffix_out) | 📂 /Users/julyfun/Documents/GitHub/openpi/src/openpi/models_pytorch/pi0_pytorch.py (预测 v_t)
│ └── F.mse_loss(u_t, v_t, reduction="none") (flow matching loss)
├── loss.backward() (反向传播;DDP 时跨进程 all-reduce 梯度)
├── clip_grad_norm_(model.parameters(), ...) (梯度裁剪)
├── optim.step(); optim.zero_grad(...) (参数更新)
├── if global_step % log_interval == 0: wandb.log(...) (主进程记录 loss/lr/grad_norm)
├── save_checkpoint(...) | 📂 /Users/julyfun/Documents/GitHub/openpi/scripts/train_pytorch.py (按 save_interval 保存)
│ ├── if not is_main: return (非主进程不保存)
│ ├── safetensors.torch.save_model(...) (保存 model.safetensors)
│ ├── torch.save(optimizer.state_dict()) (保存 optimizer.pt)
│ ├── torch.save(metadata) (保存 metadata.pt)
│ ├── _normalize.save(...) | 📂 /Users/julyfun/Documents/GitHub/openpi/src/openpi/shared/normalize.py (保存 norm_stats)
│ └── tmp_dir.rename(final_ckpt_dir) (原子替换 checkpoint)
└── pbar.update / set_postfix (更新进度条)