---
title: depthwam-v2-transfer
canonical_url: "https://www.modelscope.cn/datasets/linyinyu/depthwam-v2-transfer"
md_url: "https://www.modelscope.cn/datasets/linyinyu/depthwam-v2-transfer.md"
repository: linyinyu/depthwam-v2-transfer
last_updated: 2026-11-06
license: "Apache License 2.0"
storage_size: "2.9 TB"
downloads: 3544
stars: 0
---

# depthwam-v2-transfer

> depthwam-v2-transfer - linyinyu 在 ModelScope 开源的数据集。DepthWAM V2：接收方部署、训练与 RoboTwin 评测说明

linyinyu/depthwam-v2-transfer 是 ModelScope 魔搭社区上的数据集，存储大小 2.9 TB，采用 Apache License 2.0 许可。

- **Repository**: linyinyu/depthwam-v2-transfer
- **License**: Apache License 2.0
- **Storage size**: 2.9 TB
- **Downloads**: 3544
- **Stars**: 0
- **Last updated**: 2026-11-06

Source: https://www.modelscope.cn/datasets/linyinyu/depthwam-v2-transfer

---

# DepthWAM V2：接收方部署、训练与 RoboTwin 评测说明

交付日期：2026-10-13。本文供协作者在**另一台服务器**使用已交付的代码、数据和权重。两台服务器不需要互通。

本文命令是目标服务器操作说明，**没有在目标服务器执行过**。源端文件上传、校验已完成；不代表接收方环境、正式训练或闭环评测已验证。

如果由接收方 AI 负责部署和代跑，请先把迁移目录内 `ai_handoff_v1/START_AI_PROMPT.md` 的提示词交给它，并指定本机下载/部署目录、4 张已分配 GPU。SwanLab 目标已固定为项目所有者的 `mambaou/depthwam`，不使用代跑者的个人项目。AI 应先读 `ai_handoff_v1/AI_HANDOFF.md` 和 `experiment_contract.json`，再按本文命令执行。机器信息可填入 `receiver_settings.example.json` 的新副本。任务书已明确连续执行范围、验收、失败处理、训练后评测和回传要求；它不是可以直接启动 GPU 的自动化程序。

**给接收方 AI 的最新补充（可直接复制）：**

> SwanLab 登录已由我委托协作者提前处理，请复用现有凭据，不再安排登录或索要 API Key。用同一 Linux 用户按迁移包的 README 和 AI_HANDOFF.md 创建训练、评测环境；若人工登录使用自定义 SWANLAB_ROOT，沿用实际目录并写入注册、训练、上传器和 tmux 启动脚本；若使用项目级凭据，保持正确的工作目录。通过现有 SDK/检查脚本核验账号为 mambaou、目标为 mambaou/depthwam，为 V2 新建实验，不追加旧曲线。不要读取或输出密钥明文。凭据不可见时报告具体问题并继续环境准备，不自行换账号或重新登录。ModelScope 继续使用接收方已获下载权限的账号。完整任务见 ai_handoff_v1/START_AI_PROMPT.md。

**材料完整性提醒**：原 download_transfer.py 不会自动下载后来补充的完整源码或 AI 文档；从网页/SDK 拉取迁移目录时须包括 `source_complete_v1/` 与 `ai_handoff_v1/`。交接检查命令为 `python <迁移目录>/ai_handoff_v1/check_handoff.py --transfer-root <迁移目录>`；其默认结果仅证明文件齐全及小文件哈希正确，不代表大数据包已全部哈希验证。完整校验仍由第 3 节工具或 `--verify-bulk` 完成。

## 1. 交付范围和实验约定

| 项目 | 本次约定 |
|---|---|
| 模型 | `mixed_depth_flow_fork`，主干 R/C/A/P，保留 RGB 中间分叉和后 10 层配对深度分支 |
| V2 编码 | `body_piecewise_10_80_10_v1`，仅 `cam_high` 本体伪彩采用新映射 |
| V2 推理日程 | `depth_progress_spread_v1`，RGB/深度各 50 步，动作/未来状态各 10 步，H=5 |
| 训练时间策略 | `schedule_matched_v1`，与实际推理事件/采样器时钟匹配 |
| 数据 | click_bell、pick_dual_bottles、lift_pot，均含 clean/randomized |
| 动作/状态 | 14 维相对 EE 动作、16 维 EE 状态，`action_num=4`，预测/默认执行 32 动作 |
| 初始化 | 官方 X-WAM **weights-only** 初始化，`resume=null` |
| 训练 | 4 GPU × 每卡 batch=4，梯度累积 1，全局 batch=16，**20,000 次优化器更新** |
| 优化器/LR | DeepSpeed ZeRO-2 / FusedAdam，AdamW 语义，LR=1e-5，betas=(0.9,0.999)，eps=1e-8，WD=0.01；200 步 warmup 后 constant |
| 其他 | bf16，冻结原 VAE，均匀窗口采样，不启用接触采样，保留 SwanLab |
| 保存 | 每 2,000 步保存权重及完整状态，共 10 组；保留 20k 最终组 |
| 初筛 | 8k/10k/12k/14k，每任务 clean 10 + randomized 10，每 checkpoint 60 回合 |
| 完整评测 | 每个入选 checkpoint：3 任务 × 2 场景 × 100 = 600 回合 |

本次 V2 预算按最新要求调整为 **20k**。已封存代码包中的原模板仍是 14k；本文生成的 `_local` 配置和正式启动命令均显式覆盖到 20k。原服务器 V1 续训属于独立实验。本轮仅调整 V2 训练预算，初筛 checkpoint 仍为 8k/10k/12k/14k；16k/18k/20k 权重正常保存，暂不自动纳入筛选。

混合监督保持 `lambda_depth_prop=0`、`lambda_depth_mixed=1`；不能退回 body-only x0 监督。保留当前干净 body-depth 条件 C。推理不读取完整场景深度真值。

默认动作更新在第 **6、11、16、21、26、31、36、41、46、51** 次前向。最后一次读取深度已完成的输入。共 51 次完整前向；相对旧完整 50 轮日程多 1 次前向，不代表已测得性能收益。

## 2. 接收方先准备什么

- Linux、Bash、Git、tmux、FFmpeg/ffprobe、编译工具和与目标 GPU 兼容的 NVIDIA 驱动/CUDA Toolkit。PyTorch 自带 CUDA runtime 不等于已经有编译扩展需要的 `nvcc`。
- 可分配的 4 张 GPU。源端使用 A100 80GB；其他型号/显存能否维持 batch=4，须由目标服务器冒烟确认。**源端的 GPU 编号不能照搬。**
- 足够 CPU、主机内存和本地磁盘。下载包约 105.72 GiB，解压再需约同等容量，公共模型约 12 GiB。保留 10 组完整训练状态和运行余量，部署规划按约 **2 TiB 可用空间**准备；这是预留估算，不是精确最低值。源端 V1 的单组完整状态约 90 GiB，另有约 12.65 GiB 的权重文件。
- ModelScope **该申请制数据集的下载权限**；SwanLab 项目所有者 `mambaou` 的授权登录，上传到既有项目 `depthwam`。ModelScope 仍使用接收方自己获批的账号；两项登录不要混淆。完整项目源码已单独打包，按本文默认流程部署**不需要私有 GitHub 仓库权限**。Git 工具仍用于安装公共依赖；公共组件下载仍需网络或另行离线交付。

仓库当前采用**申请制下载**（2026-10-13 核实）。接收方先登录自己的 ModelScope 账号，打开 <https://modelscope.cn/datasets/linyinyu/depthwam-v2-transfer> 提交下载申请，并把账号名告诉仓库所有者。所有者在仓库「设置 → 申请信息」中处理对应申请；批准后，接收方在目标服务器使用自己的 Token 登录，再执行下面的下载命令。只有需要另行访问私有 GitHub 或后续协作时，才需要另外配置 GitHub 权限。

不需要公开仓库或共用源端 ModelScope 账号。不要把密码、ModelScope Token、SwanLab Key 发到 AI 对话或写入 README。SwanLab 授权由所有者与协作者在文档之外处理，本包不包含凭据。仓库所有者登录成功不等于接收方账户已有读取权限。若无法给接收方提供适当的仓库读取权限，由所有者下载完整迁移目录后，通过双方认可的方式交付，使用下面的离线校验路径。

以下命令在 Bash 终端运行，**不要粘贴到 Python 的 `>>>` 提示符中**。如果在 `>>>` 中，先输入 `exit()`。

## 3. 下载、校验和解压

仓库：<https://modelscope.cn/datasets/linyinyu/depthwam-v2-transfer>

目录前缀：`v2_migration_20261013/`

原始迁移包上传完成时间：2026-10-13 16:57（北京时间）。共 81 个文件，113,511,008,414 bytes，含最后上传的 `TRANSFER_COMPLETE.json`。这一数量仅指已封存迁移包，不包含后来新增的 README 等说明文件。原始文件及其 manifest 保持不变。

完整操作说明同时放在仓库根目录 `README.md` 和 `v2_migration_20261013/README.md`；CPU 检查记录在 `v2_migration_20261013/README.validation.json`。完整源码补充包位于 `v2_migration_20261013/source_complete_v1/`。旧下载工具仍按封存 manifest 获取原始数据包，不会自动下载这些补充文件；源码按第 4 节另行下载，本文也需单独保存。

### 3.1 设置目标目录

先把 `/YOUR/ABSOLUTE/PATH/depthwam-v2` 改为接收方可写的大容量目录。路径建议不含空格。保存这些变量，后续各终端都需重新载入。

```bash
export V2_DEPLOY=/YOUR/ABSOLUTE/PATH/depthwam-v2
export V2_PROJECT="$V2_DEPLOY/project"
export V2_UNPACK="$V2_DEPLOY/unpacked"
export V2_TRANSFER="$V2_DEPLOY/download/v2_migration_20261013"
export V2_MODELS="$V2_UNPACK/checkpoints"
mkdir -p "$V2_DEPLOY"
```

首次下载可使用独立 Python 3.10 环境，安装源端使用的 SDK：

```bash
conda create -n depthwam-transfer python=3.10 pip -y
conda activate depthwam-transfer
python -m pip install modelscope==1.34.0
```

### 3.2 在线下载：只在接收方已有读取权限时使用

交互登录，不把 Token 放进命令参数或命令历史：

```bash
python -c 'import getpass; from modelscope.hub.api import HubApi; HubApi().login(getpass.getpass("ModelScope token (hidden): "))'
```

如果采用旧下载脚本，先同时获取后来新增的完整源码和 AI 交接文件。下面只下载约 1 MiB 的补充材料，不下载大分包、不启动训练；已通过整个目录下载取得这些文件时可跳过：

```bash
python - <<'PY'
import os
from pathlib import Path
from modelscope.hub.file_download import dataset_file_download
root=Path(os.environ['V2_DEPLOY'])
files=['README.md','README.validation.json',
    'source_complete_v1/depthwam_v2_source.tar.gz','source_complete_v1/source_bundle.json',
    'source_complete_v1/SHA256SUMS','source_complete_v1/validation.json',
    'ai_handoff_v1/START_AI_PROMPT.md','ai_handoff_v1/AI_HANDOFF.md',
    'ai_handoff_v1/experiment_contract.json','ai_handoff_v1/receiver_settings.example.json',
    'ai_handoff_v1/check_handoff.py','ai_handoff_v1/check_swanlab_target.py',
    'ai_handoff_v1/validation.json','ai_handoff_v1/HANDOFF_MANIFEST.json']
for name in files:
    dataset_file_download(dataset_id='linyinyu/depthwam-v2-transfer',
        file_path='v2_migration_20261013/'+name,revision='master',local_dir=str(root/'download'))
print('SOURCE_AND_AI_HANDOFF_DOWNLOADED; verify hashes before using the source')
PY
```

先下载引导脚本，并核对固定 SHA256：

```bash
python - <<'PY'
import hashlib, os, shutil
from pathlib import Path
from modelscope.hub.file_download import dataset_file_download
root = Path(os.environ['V2_DEPLOY'])
p = Path(dataset_file_download(
    dataset_id='linyinyu/depthwam-v2-transfer',
    file_path='v2_migration_20261013/download_transfer.py',
    revision='master', local_dir=str(root/'download')))
assert hashlib.sha256(p.read_bytes()).hexdigest() == '7dc7e571148da85ee1839405532b760498672def7ac80f28025408b1509c6c4f'
shutil.copy2(p, root/'download_transfer.py')
print(root/'download_transfer.py')
PY
```

在 tmux 里执行下载和解压，断开 SSH 不影响它：

```bash
tmux new-session -s depthwam-v2-download
python "$V2_DEPLOY/download_transfer.py" \
  --download-to "$V2_DEPLOY/download" \
  --extract-to "$V2_UNPACK" > "$V2_DEPLOY/download.log" 2>&1
```

按 `Ctrl-b` 后按 `d` 离开 tmux。查看：`tail -f "$V2_DEPLOY/download.log"`。

脚本会检查私有仓库、完成标记、逐文件 SHA256/大小、各分包 SHA256、整组 TAR 哈希和成员数量。`--extract-to` 必须是**不存在的新目录**；失败目录保留，重试解压换一个新目录，不能覆盖已有项目。下载可复用已下载文件，但校验仍会重做。

### 3.3 离线交付：接收方拿到了完整目录时使用

收到的 `v2_migration_20261013` 目录放到 `$V2_TRANSFER`。不要只拿 `*.part*`；manifest、小代码包和下载脚本都需要。

下面只做本地校验/解压，不登录、不访问 ModelScope：

```bash
python - <<'PY'
import hashlib, importlib.util, json, os, tarfile
from pathlib import Path
root = Path(os.environ['V2_TRANSFER'])
dest = Path(os.environ['V2_UNPACK'])
script = root/'download_transfer.py'
assert hashlib.sha256(script.read_bytes()).hexdigest() == '7dc7e571148da85ee1839405532b760498672def7ac80f28025408b1509c6c4f'
spec = importlib.util.spec_from_file_location('transfer', script)
m = importlib.util.module_from_spec(spec); spec.loader.exec_module(m)
receipt = json.loads((root/'TRANSFER_COMPLETE.json').read_text())
assert receipt['state'] == 'TRANSFER_COMPLETE'
assert m.digest(root/'transfer_manifest.json') == receipt['manifest_sha256']
manifest = json.loads((root/'transfer_manifest.json').read_text())
assert (manifest['repo_id'], manifest['prefix']) == ('linyinyu/depthwam-v2-transfer', 'v2_migration_20261013')
for row in manifest['files']:
    m.check_relative(row['file'])
    p = root/row['file']
    assert p.stat().st_size == row['bytes'] and m.digest(p) == row['sha256'], row['file']
dest.mkdir(parents=True, exist_ok=False)
for group in manifest['groups']:
    m.extract_group(root, group, dest)
for name, subdir in [('v2_code_overlay.tar.gz','code_overlay'),
                     ('validated_v2_source_reference.tar.gz','validated_source_reference'),
                     ('robotwin_eval_code_and_cases.tar.gz','evaluation')]:
    with tarfile.open(root/'prepared'/name, 'r:gz') as archive:
        m.extract_files(archive, dest/subdir)
print('OFFLINE_VERIFY_AND_EXTRACT_OK', dest)
PY
```

各 `*.tar.partNNNN` 是同一 TAR 的连续分片，不能逐个当成独立 TAR 解压。上述脚本流式拼接读取，不额外生成一份 106 GiB 的拼接 TAR。

### 3.4 解压后应有的目录

```text
unpacked/
  workspace/data/robotwin_three_ee_clock15_v1/  # dataset、stats、provenance
  workspace/data/subsets/                     # 冻结窗口清单
  workspace/data/text_embeds_cache/robotwin/   # 8,879 个所需文本缓存
  checkpoints/X-WAM-official-bb6fd164/         # 官方预训练权重
  robotwin_assets/                            # 物体、纹理、机器人等大资产
  code_overlay/                              # V2 当前修改 + v2_transfer_provenance.json
  validated_source_reference/                 # 历史验证源码，仅供对照
  evaluation/RoboTwin_eval_private/           # 当前冻结评测后端代码及机器人配置
  evaluation/preparation/                     # 冻结案例、原协议、训练 seed 证据
```

迁移包不含 V1 完整训练状态、LIBERO、原始六任务采集全集、编译缓存或凭据。`prepared/preparation.json` 中的 `PREPARED_NOT_TRANSFERRED` 是打包前记录；传输是否完成以 `TRANSFER_COMPLETE.json` 和校验为准。

## 4. 恢复完整源码（默认不访问私有 GitHub）

完整包已经合成基准提交 `c99da64cc438493e185e575c57eecbec55a3ee03` 的文件和原封存的 36 个 V2 修改/新增文件；不再要求 clone 或二次应用 overlay。它不是打包时随意复制的当前脏工作树，也不包含 Git 历史、凭据、数据、权重、日志或编译产物。

包名：`source_complete_v1/depthwam_v2_source.tar.gz`，752,376 bytes（约 735 KiB），328 个文件。`SOURCE_SNAPSHOT.json` 保存基准、overlay 身份和逐文件校验。原始任务模板仍继承 14k，第 6 节生成的 `_local` 配置显式设置为 **20k**。

### 4.1 下载完整源码补充包

使用第 3 节已登录的 ModelScope 账号，下载源码包及其清单并核对固定哈希：

```bash
python - <<'PY'
import hashlib, os
from pathlib import Path
from modelscope.hub.file_download import dataset_file_download
root=Path(os.environ['V2_DEPLOY'])
files={
    'depthwam_v2_source.tar.gz':'adff12cc089e4e95e31111b0fd0b618bcd49cd9169f46483c73bebbc4db3ccfc',
    'source_bundle.json':'a2410a028a3ade330cf4a31827498f2a4aee8d3fd5456be9e68c57d72083735f',
}
for name,sha in files.items():
    p=Path(dataset_file_download(dataset_id='linyinyu/depthwam-v2-transfer',
        file_path='v2_migration_20261013/source_complete_v1/'+name,
        revision='master',local_dir=str(root/'download')))
    assert hashlib.sha256(p.read_bytes()).hexdigest()==sha,name
print('FULL_SOURCE_DOWNLOAD_VERIFIED')
PY
```

离线交付时，把这两个补充文件放入 `$V2_TRANSFER/source_complete_v1/`，再执行下面的同一套本地校验。旧下载脚本不会自动获取补充包。

### 4.2 校验、解压并绑定数据

`$V2_PROJECT` 必须是**不存在的新目录**；已经按旧 GitHub 流程部署过的接收方不重复解压、不覆盖原项目。数据包须先按第 3 节解压到 `$V2_UNPACK`。

```bash
python - <<'PY'
import hashlib, json, os, shutil, tarfile
from pathlib import Path, PurePosixPath
project=Path(os.environ['V2_PROJECT'])
source=Path(os.environ['V2_TRANSFER'])/'source_complete_v1'
bundle_path=source/'source_bundle.json'
assert hashlib.sha256(bundle_path.read_bytes()).hexdigest()=='a2410a028a3ade330cf4a31827498f2a4aee8d3fd5456be9e68c57d72083735f'
bundle=json.loads(bundle_path.read_text())
assert bundle['archive']=='depthwam_v2_source.tar.gz'
archive_path=source/bundle['archive']
assert archive_path.stat().st_size==bundle['bytes']
assert hashlib.sha256(archive_path.read_bytes()).hexdigest()==bundle['sha256']
project.mkdir(parents=True,exist_ok=False)
with tarfile.open(archive_path,'r:gz') as archive:
    members=archive.getmembers(); seen=set()
    for member in members:
        rel=PurePosixPath(member.name)
        assert member.isfile() and not rel.is_absolute() and '..' not in rel.parts
        assert '.git' not in rel.parts and member.name not in seen
        seen.add(member.name)
    for member in members:
        dst=project/member.name; dst.parent.mkdir(parents=True,exist_ok=True)
        with archive.extractfile(member) as src,dst.open('xb') as out:
            shutil.copyfileobj(src,out)
        dst.chmod(member.mode & 0o777)
manifest_path=project/'SOURCE_SNAPSHOT.json'
assert hashlib.sha256(manifest_path.read_bytes()).hexdigest()==bundle['source_manifest_sha256']
manifest=json.loads(manifest_path.read_text())
assert manifest['base_commit']==bundle['base_commit']
assert manifest['overlay_sha256']==bundle['overlay_sha256']
assert seen==set(manifest['files'])|{'SOURCE_SNAPSHOT.json'}
assert len(seen)==bundle['source_files']
for rel,record in manifest['files'].items():
    p=project/rel
    assert p.stat().st_size==record['bytes'] and hashlib.sha256(p.read_bytes()).hexdigest()==record['sha256'],rel
assert not (project/'.git').exists()
assert not (project/'data').exists(), '项目 data 已存在，先人工核对，不能覆盖'
(project/'data').symlink_to(Path(os.environ['V2_UNPACK'])/'workspace/data', target_is_directory=True)
report_rel=Path('runs/robotwin_three_ee_prepare_20261005/full_check/report.json')
report_dst=project/report_rel
assert not report_dst.exists()
report_dst.parent.mkdir(parents=True,exist_ok=True)
shutil.copy2(Path(os.environ['V2_UNPACK'])/'workspace'/report_rel,report_dst)
print('FULL_SOURCE_AND_DATA_BINDING_OK',len(seen))
PY
```

解压项目没有 `.git`，版本证据使用 `SOURCE_SNAPSHOT.json`、`v2_transfer_provenance.json` 和压缩包 SHA256，不伪造 Git commit。当前训练入口使用已提供的 EE 统计量，不调用需要 Git 仓库的重新统计缓存分支。若以后启用该分支，需要单独处理其 Git 依赖。

不要再应用旧 `code_overlay` 或 `validated_source_reference` 覆盖此代码；前者已合入，后者是较早的验证快照。不要复制源端 worktree 的 `.git` 文件。不要启动包内原服务器专用的 `run_reboot_training.py`、`run_mixed_v2_after_training.py` 等调度器，它们含旧路径/旧任务安排。

## 5. 安装训练环境和公共模型

### 5.1 训练环境

源端实际版本：Python 3.10.20、PyTorch 2.7.1+cu128、torchvision 0.22.1+cu128、torchcodec 0.5+cu128、DeepSpeed 0.18.5、SwanLab 0.8.2。完整清单在 `train_installed_versions.json`。

下面是新建环境的安装入口。驱动、CUDA Toolkit、编译器和 wheel 可用性须在目标服务器确认；不要把“有版本清单”当成环境已复现。

```bash
conda create -n depthwam-v2-train python=3.10 pip -y
conda activate depthwam-v2-train
python -m pip install torch==2.7.1 torchvision==0.22.1 \
  --index-url https://download.pytorch.org/whl/cu128
python -m pip install torchcodec==0.5 --index-url https://download.pytorch.org/whl/cu128
cd "$V2_PROJECT"
python -m pip install -e . --extra-index-url https://download.pytorch.org/whl/cu128
python -m pip install pytest matplotlib scipy==1.15.3 opencv-python==4.11.0.86
python -m pip check
export V2_TRAIN_PY="$(command -v python)"
```

如果 DeepSpeed 构建失败，先核对本机 `nvcc`、编译器及 CUDA 路径；不要把其他机器的 `.so` 或 torch extension cache 复制过来。正常训练使用 FusedAdam，不能为绕过安装问题静默换成另一优化器。

### 5.2 公共模型：只下载所需文件

已交付的官方 X-WAM 文件：

```text
$V2_MODELS/X-WAM-official-bb6fd164/pretrained/checkpoints/last.ckpt/checkpoint/mp_rank_00_model_states.pt
SHA256: e31a909cdd31316386c582f9ff22c9cea5d34891edbd9c7fe75bdd5d7e737880
来源: sharinka0715/X-WAM-checkpoints
revision: bb6fd1643cfa8bdc751612a7ace0bd8062a8917a
```

还需 VAE（约 1.31 GiB）、T5（约 10.58 GiB）、tokenizer。训练使用文本缓存，评测新指令仍需要 T5/tokenizer。**无需下载整个 Wan DiT 分片**，官方初始化路径会跳过它。

```bash
python - <<'PY'
import os
from pathlib import Path
from modelscope import snapshot_download
base = Path(os.environ['V2_MODELS'])
repo = 'DiffSynth-Studio/Wan-Series-Converted-Safetensors'
snapshot_download(repo, local_dir=str(base/repo), allow_file_pattern=[
    'Wan2.2_VAE.safetensors', 'models_t5_umt5-xxl-enc-bf16.safetensors'])
repo = 'Wan-AI/Wan2.1-T2V-1.3B'
snapshot_download(repo, local_dir=str(base/repo), allow_file_pattern='google/umt5-xxl/*')
print('PUBLIC_MODELS_DOWNLOADED', base)
PY
```

下载后记录文件 SHA256 和实际 revision，纳入接收方运行记录。公共仓库未在本迁移包中冻结 revision；如果下载内容与项目加载器的模型校验不兼容，停止并核对来源，不绕过校验。离线目标服务器需由有网络的一端先下载这三个组件后再交付。

## 6. 生成目标机器配置：只改路径，不改数据窗口

原窗口清单绑定源端绝对路径。下面创建**新清单、新 task 和新 sim 配置**；原清单、stats、provenance、视频及 EE parquet 均不改写。

```bash
cd "$V2_PROJECT"
python - <<'PY'
import copy, hashlib, json, os
from pathlib import Path
from omegaconf import OmegaConf
root = Path(os.environ['V2_PROJECT']).resolve()
models = Path(os.environ['V2_MODELS']).resolve()
local = root/'deployment_local'; local.mkdir(exist_ok=False)
original = root/'data/subsets/robotwin_three_mixed_ee_nohit_windows_v2.json'
raw = original.read_bytes()
assert hashlib.sha256(raw).hexdigest() == 'ce7717a838da27a40427a4f5fc7244da9299b2bb11087e1e3e8dbd8e0fbbd97b'
old = json.loads(raw); new = copy.deepcopy(old)
data = (root/'data/robotwin_three_ee_clock15_v1').resolve()
new.update(dataset_root=str(data/'dataset'), source_provenance=str(data/'provenance.json'),
           source_stats=str(data/'stats.json'))
assert {k:v for k,v in new.items() if k not in ('dataset_root','source_provenance','source_stats')} == {
        k:v for k,v in old.items() if k not in ('dataset_root','source_provenance','source_stats')}
for p, digest in [(data/'provenance.json', new['source_provenance_sha256']),
                  (data/'stats.json', new['source_stats_sha256']),
                  (data/'dataset/meta/info.json', new['dataset_info_sha256'])]:
    assert hashlib.sha256(p.read_bytes()).hexdigest() == digest, p
manifest = local/'windows.json'
manifest.write_text(json.dumps(new, ensure_ascii=False, indent=2)+'\n')
sha = hashlib.sha256(manifest.read_bytes()).hexdigest()
xwam = models/'X-WAM-official-bb6fd164/pretrained/checkpoints/last.ckpt/checkpoint/mp_rank_00_model_states.pt'
assert xwam.is_file()
config = dict(defaults=['robotwin_three_mixed_ee_v2','_self_'], resume=None,
    max_steps=20000, lr_scheduler_total_steps=20000, save_every=2000,
    data=dict(train=dict(dataset_dirs=[str(data/'dataset')],
        pretrained_norm_stats=str(data/'stats.json'), window_manifest=str(manifest),
        window_manifest_sha256=sha,
        text_embedding_cache_dir=str((root/'data/text_embeds_cache/robotwin').resolve()))),
    model=dict(xwam_pretrained_checkpoint=str(xwam),
        robotwin_data_interface=dict(window_manifest_sha256=sha)))
def save_task(name, value):
    p = root/'configs/task'/name
    with p.open('x') as f:
        f.write('# @package _global_\n'+OmegaConf.to_yaml(OmegaConf.create(value)))
save_task('robotwin_three_mixed_ee_v2_local.yaml', config)
save_task('robotwin_three_mixed_ee_v2_local_eval.yaml', dict(
    defaults=['robotwin_three_mixed_ee_v2_local','_self_'],
    model=dict(xwam_pretrained_checkpoint=None, skip_dit_load_from_pretrain=True,
               load_text_encoder=True)))
sim = dict(defaults=['sim_robotwin_three_mixed_ee_v2',
    {'override /task':'robotwin_three_mixed_ee_v2_local_eval'},'_self_'],
    EVALUATION=dict(sim_cfg_path=str(root/'configs/sim_robotwin_three_mixed_ee_v2_local.yaml'),
                    dataset_stats_path=str(data/'stats.json')))
with (root/'configs/sim_robotwin_three_mixed_ee_v2_local.yaml').open('x') as f:
    f.write('# @package _global_\n'+OmegaConf.to_yaml(OmegaConf.create(sim)))
(local/'path_rebinding.json').write_text(json.dumps(dict(
    original_manifest_sha256=hashlib.sha256(raw).hexdigest(), local_manifest_sha256=sha,
    windows_unchanged=True, stats_unchanged=True, provenance_unchanged=True,
    changed_fields=['dataset_root','source_provenance','source_stats'],
    windows=sum(len(e['starts']) for e in new['episodes'])), indent=2)+'\n')
print('LOCAL_CONFIG_READY', sha)
PY
```

生成后训练与评测必须都使用 `_local` 入口。若此前已按 14k 版说明生成过本地配置，只在接收方的 `configs/task/robotwin_three_mixed_ee_v2_local.yaml` 中显式设置 `max_steps: 20000`、`lr_scheduler_total_steps: 20000`、`save_every: 2000`，并重新生成尚未启动的新实验运行配置；不重复执行会覆盖原目录的初始化步骤。新 V2 checkpoint 会记录新的路径绑定清单哈希；不能只改训练配置、评测却仍使用原哈希。不能修改旧 checkpoint 的接口标记来“通过”检查。

数据应保持以下数量：

| 任务/场景 | 交付轨迹 | 有窗口轨迹 | 有效窗口 |
|---|---:|---:|---:|
| click_bell / clean | 50 | 47 | 1,633 |
| click_bell / randomized | 468 | 458 | 15,716 |
| pick_dual_bottles / clean | 50 | 50 | 3,112 |
| pick_dual_bottles / randomized | 467 | 467 | 29,341 |
| lift_pot / clean | 50 | 50 | 2,894 |
| lift_pot / randomized | 470 | 470 | 28,759 |
| 合计 | **1,555** | **1,542** | **81,455** |

窗口含 33 个同步观测、32 个动作，图像取 `[0,4,8,...,32]` 共 9 帧；原时间间隔筛选为 0.03–0.09 秒，保留 `physical_time_s/simulation_step/clock_error_s/source_frame_index`，没有独立重采样动作。32 次环境动作不能表述为恒定物理时长。

深度语义保持：body 原裁剪 `[0,4.095]m`，背景 4.095m，body mask 阈值 3.5m；scene 灰度 `[0.1,4.1]m`，已验证 no-hit 零值按 4.1m 处理。可见本体区域使用 scene depth 伪彩，不能用 body-only 轮廓覆盖遮挡物。左右腕保持旧编码。

## 7. CPU 检查和目标服务器短程训练验证

统一运行环境变量（以后启动 tmux 也需继承）：

```bash
export PYTHONPATH="$V2_PROJECT/src:$V2_PROJECT"
export DIFFSYNTH_MODEL_BASE_PATH="$V2_MODELS"
export DIFFSYNTH_SKIP_DOWNLOAD=true
export HF_HUB_OFFLINE=1 TRANSFORMERS_OFFLINE=1
export HF_DATASETS_CACHE="$V2_DEPLOY/hf_datasets_cache"
export OMP_NUM_THREADS=2 MKL_NUM_THREADS=2 OPENBLAS_NUM_THREADS=2
export PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True
export PYTHONUNBUFFERED=1
```

CPU 单元测试不需要 GPU：

```bash
cd "$V2_PROJECT"
CUDA_VISIBLE_DEVICES='' OMP_NUM_THREADS=1 MKL_NUM_THREADS=1 OPENBLAS_NUM_THREADS=1 \
  "$V2_TRAIN_PY" -m pytest -q tests/test_mixed_depth_v2.py \
  tests/test_mixed_depth_flow.py
CUDA_VISIBLE_DEVICES='' OMP_NUM_THREADS=1 MKL_NUM_THREADS=1 OPENBLAS_NUM_THREADS=1 \
  "$V2_TRAIN_PY" -m pytest -q tests/test_robotwin_mixed_ee.py \
  -k 'reject_before_mutation or synthetic_migration or factory_never_overwrites or ee32_mixed_forward or mock_ee_execution'
```

第二条只选择可移植的小模型/接口测试。该文件的其他测试依赖旧窗口清单或源端绝对路径下的官方源码；本迁移包未包含这些测试夹具，不能把它们在目标服务器缺文件当成新模型失败，也不能声称全部测试已通过。目标 `_local` 配置由下一条专门验证。

已有 `check_mixed_depth_v2.py` CLI 写死原 task，**不支持 `--task` 参数**。迁移后用下面方式调用其真实数据/小模型检查函数，并显式传 `_local` 配置：

```bash
CUDA_VISIBLE_DEVICES='' OMP_NUM_THREADS=1 MKL_NUM_THREADS=1 OPENBLAS_NUM_THREADS=1 \
"$V2_TRAIN_PY" - <<'PY'
import json, os
from pathlib import Path
import torch
from omegaconf import OmegaConf
from scripts.check_mixed_depth_v2 import compose, plots_and_trace, real_data, tiny_model_smoke
from fastwam.robotwin_mixed_ee import validate_ee_run_config
root = Path(os.environ['V2_PROJECT']); os.chdir(root); torch.set_num_threads(1)
out = root/'deployment_local/cpu_check'; out.mkdir(exist_ok=False)
os.environ['HF_DATASETS_CACHE'] = str(out/'hf_cache')
cfg = compose('robotwin_three_mixed_ee_v2_local')
validate_ee_run_config(cfg)
assert (cfg.batch_size,cfg.gradient_accumulation_steps,cfg.max_steps,cfg.save_every)==(4,1,20000,2000)
assert cfg.lr_scheduler_total_steps==20000
assert cfg.model.flow.depth_time_policy.mode=='schedule_matched_v1'
assert cfg.model.inference.mixed_sampling_schedule=='depth_progress_spread_v1'
assert cfg.data.train.body_depth_encoding.version=='body_piecewise_10_80_10_v1'
OmegaConf.save(OmegaConf.to_container(cfg,resolve=True),out/'resolved_train.yaml')
report=plots_and_trace(cfg,out)
report['real_data']=real_data(cfg,out)
report['cpu_smoke']=tiny_model_smoke(cfg,out)
(out/'report.json').write_text(json.dumps(report,indent=2)+'\n')
print('CPU_CHECK_PASS',out)
PY
```

应检查六个 task/source 分组的真实样本：RGB/body/mixed 均为 `[3,9,3,256,320]`、action `[32,14]`、proprio `[9,16]`，动作有效、没有终端 padding，在线 C 与训练 C 一致；输出还包含新旧伪彩对照、51 轮时钟 trace。小模型的 TinyVAE 不是生产 VAE 验证。

下面开始占用目标服务器 GPU，须由接收方确认卡已分配。没有自动显存门禁，也不停止别人的进程。将 GPU 字符串替换成目标机器允许的四张物理卡：

```bash
export V2_GPUS=GPU_A,GPU_B,GPU_C,GPU_D
unset CUDA_VISIBLE_DEVICES RANK LOCAL_RANK WORLD_SIZE
export V2_SMOKE="$V2_DEPLOY/smoke_v2"
"$(dirname "$V2_TRAIN_PY")/accelerate" launch \
  --config_file scripts/accelerate_configs/accelerate_zero2_ds.yaml \
  --num_processes 4 --gpu_ids "$V2_GPUS" --main_process_port 29671 \
  scripts/train.py task=robotwin_three_mixed_ee_v2_local \
  output_dir="$V2_SMOKE" resume=null max_steps=2 save_every=2 log_every=1 \
  swanlab.enabled=true swanlab.mode=offline swanlab.transport=sdk \
  numerics_debug.enabled=true numerics_debug.max_updates=2 \
  numerics_debug.skip_final_checkpoint=false > "$V2_DEPLOY/smoke.log" 2>&1
```

检查四个 rank 的有限 loss/gradient、显存峰值、退出码、`xwam_migration.rank*.json`、`mixed_precision.rank*.json`、`training_audit_rank*.jsonl` 和 step 2 的权重/完整状态。不得因为 OOM 静默减 batch 或分辨率。

还应验证两种恢复，输出目录分别独立，仍用上面相同的 4 卡 launch 前缀：

- **完整状态恢复**：`output_dir=$V2_DEPLOY/smoke_state_resume resume=$V2_SMOKE/checkpoints/state/step_000002 max_steps=3 model.xwam_pretrained_checkpoint=null model.skip_dit_load_from_pretrain=true`；应从第 2 步和原 Adam 状态续至 3。
- **权重恢复**：`output_dir=$V2_DEPLOY/smoke_weights_resume resume=$V2_SMOKE/checkpoints/weights/step_000002.pt max_steps=1 model.xwam_pretrained_checkpoint=null model.skip_dit_load_from_pretrain=true`；是新的优化器/步数。

恢复测试均保留 `lr_scheduler_total_steps=20000`、`save_every=0`、`numerics_debug.skip_final_checkpoint=true`、SwanLab offline；不能把 smoke 的 `max_steps` 当成 LR 总时钟。正式训练之后重新从官方初始化开始，不从 smoke 续训。

## 8. 正式训练：tmux、SwanLab 和完整状态

只在前述目标机器验证通过后执行。正式 task 为 `robotwin_three_mixed_ee_v2_local`。训练停止点和 scheduler horizon 均为 **20,000**；200 步 warmup 后仍使用原 constant LR，不改变学习率、batch 或模型设计。

保持已有的“训练写本地 scalar journal，独立 CPU 进程上传 SwanLab”方式，避免上传重试影响训练。曲线必须上传到项目所有者当前的 **`mambaou/depthwam`**（<https://swanlab.cn/@mambaou/depthwam>），在该项目下创建**新的 V2 实验身份**；不可使用代跑者账号的项目，不可复用源端 V1/V2 run ID，更不能追加到当前 V1 的 `r8c0fb1e`。

2026-10-13 源端只读核实：当前登录用户 `mambaou`、空间类型 PERSON、角色 OWNER。按所有者最新交接，目标端登录已委托协作者提前处理，AI 不再安排登录或索要 Key。使用完成登录的同一 Linux 用户复用现有凭据，更换 Conda 环境本身不要求重新登录。若使用自定义 `SWANLAB_ROOT`，沿用实际目录并将其写入注册、训练、上传器及 tmux 启动脚本；若使用项目级 `.swanlab`，保持可读取凭据的工作目录。不要假定具体目录，不读取或输出密钥明文，不覆盖其他作业凭据。人工已处理是交接信息，不等于目标服务器身份已经由本包核验；仅设置 workspace 也不能代替有效授权。

注册前运行 `ai_handoff_v1/check_swanlab_target.py`（见下面命令），验证实际登录为 mambaou、目标路径属于 mambaou/depthwam。缺授权可以继续 CPU 准备与离线 smoke，但不能把正式训练静默改传到代跑者账户。正式启动前必须完成正确项目下的新 run 注册；训练过程中若暂时断网，继续保留本地 journal 并由独立上传器重试、报告云端落后，不因上传等待阻塞 GPU。

1. 对接收方新 run 使用 `scripts/bootstrap_v2_swanlab.py --root <新运行目录> --create` 注册一次。该目录需有 `request.json`（`swanlab_run=工作区/项目/新run_id`、name/group、`scalar_columns`）和 `formal_config.json`。`scalar_columns` 包含 `train/loss`、`train/grad_norm`、`train/lr`、`performance/steps_per_sec`、`performance/samples_per_sec`，再加 smoke `loss_components` 的所有 `train/<key>`。不要直接运行不带 `--create` 的旧布局调度入口。
2. 正式训练传入 `swanlab.transport=external swanlab.run_path=<新run路径>`，训练只在 `formal/swanlog/scalars.jsonl` 写本地记录。
3. 在另一个 tmux 中启动 `upload_training_scalars.py`；这个进程不占 GPU。它的 `--worker-status` JSON 需记录本次 accelerate 进程的真实 `train_pid`；`--exit-file` 需记录本次真实退出码和 UTC `finished_at`。不能拿源端状态文件代替。

本次默认使用下面的 **external 路径**。未经项目所有者同意，不把正式训练替换成其他账户、项目或仅离线交付；网络临时中断不改变既定上传目标。新 run ID 和状态文件由接收方 AI 生成并持久保存，重试沿用同一身份。

```bash
export V2_RUN="$V2_DEPLOY/run_v2_20k"
export V2_SWAN_RUN=mambaou/depthwam/NEW_RUN_ID
mkdir -p "$V2_RUN"
```

把 `NEW_RUN_ID` 替换为**未使用的新 run ID**，其余 `mambaou/depthwam` 保持固定；复用人工提前完成的登录，确保下面各进程都能访问同一份既有授权。下面检查授权、生成注册材料并注册一次；身份不符或凭据不可见时报告具体问题，不自行重新登录。网络失败先检查注册状态和相同 run ID，不能换 ID 无限制创建实验。

```bash
cd "$V2_PROJECT"
CUDA_VISIBLE_DEVICES='' "$V2_TRAIN_PY" "$V2_TRANSFER/ai_handoff_v1/check_swanlab_target.py" --run "$V2_SWAN_RUN"
"$V2_TRAIN_PY" - <<'PY'
import json, os
from pathlib import Path
from omegaconf import OmegaConf
from scripts.check_mixed_depth_v2 import compose
root=Path(os.environ['V2_RUN'])
run=os.environ['V2_SWAN_RUN']
assert len(run.split('/'))==3 and run.split('/')[:2]==['mambaou','depthwam']
assert run.split('/')[-1] not in ('NEW_RUN_ID','r8c0fb1e')
cfg=compose('robotwin_three_mixed_ee_v2_local')
assert (cfg.max_steps,cfg.lr_scheduler_total_steps,cfg.save_every)==(20000,20000,2000)
cfg.output_dir=str(root/'formal')
cfg.swanlab.enabled=True; cfg.swanlab.transport='external'
cfg.swanlab.mode='cloud'; cfg.swanlab.run_path=run
cfg.swanlab.workspace='mambaou'; cfg.swanlab.project='depthwam'
cfg.performance.profile_updates=0; cfg.performance.trace_update=0
rows=[json.loads(s) for s in (Path(os.environ['V2_SMOKE'])/'training_audit_rank0.jsonl').read_text().splitlines()]
assert rows[-1]['step']==2
columns=['train/loss','train/grad_norm','train/lr','performance/steps_per_sec','performance/samples_per_sec']
columns += ['train/'+k for k in rows[-1]['loss_components']]
request=dict(swanlab_run=run,swanlab_name='robotwin-three-mixed-ee-v2-20k',
             swanlab_group='robotwin-three-mixed-ee-v2',scalar_columns=sorted(set(columns)),
             swanlab_description='V2 piecewise body encoding + spread depth schedule; fresh official weights, 20k updates.')
for name,value in [('request.json',request),('formal_config.json',OmegaConf.to_container(cfg,resolve=True))]:
    with (root/name).open('x') as f: json.dump(value,f,ensure_ascii=False,indent=2)
print('SWANLAB_REGISTRATION_FILES_READY')
PY
CUDA_VISIBLE_DEVICES='' "$V2_TRAIN_PY" scripts/bootstrap_v2_swanlab.py --root "$V2_RUN" --create
```

将下面保存为新服务器的 `launch_v2.sh`。脚本读取前面 export 的路径/卡号/新 SwanLab 身份，所有路径均属于新服务器：

```bash
#!/usr/bin/env bash
set -euo pipefail
: "${V2_PROJECT:?}" "${V2_TRAIN_PY:?}" "${V2_GPUS:?}" "${V2_RUN:?}" "${V2_SWAN_RUN:?}" "${V2_TRANSFER:?}"
cd "$V2_PROJECT"
test ! -e "$V2_RUN/formal"
test -f "$V2_RUN/swanlab_registered.json"
CUDA_VISIBLE_DEVICES='' "$V2_TRAIN_PY" "$V2_TRANSFER/ai_handoff_v1/check_swanlab_target.py" --run "$V2_SWAN_RUN"
unset CUDA_VISIBLE_DEVICES RANK LOCAL_RANK WORLD_SIZE SWANLAB_MODE
mkdir -p "$V2_RUN/formal"
"$(dirname "$V2_TRAIN_PY")/accelerate" launch \
  --config_file scripts/accelerate_configs/accelerate_zero2_ds.yaml \
  --num_processes 4 --gpu_ids "$V2_GPUS" --main_process_port 29671 \
  scripts/train.py task=robotwin_three_mixed_ee_v2_local \
  output_dir="$V2_RUN/formal" resume=null max_steps=20000 save_every=2000 \
  batch_size=4 gradient_accumulation_steps=1 lr_scheduler_total_steps=20000 \
  swanlab.enabled=true swanlab.transport=external swanlab.mode=cloud \
  swanlab.workspace=mambaou swanlab.project=depthwam \
  swanlab.run_path="$V2_SWAN_RUN" \
  performance.profile_updates=0 performance.trace_update=0 \
  > "$V2_RUN/formal/train.log" 2>&1 &
v2_train_pid=$!
printf '{"train_pid":%s}\n' "$v2_train_pid" > "$V2_RUN/status.json"
set +e
wait "$v2_train_pid"
v2_exit_code=$?
set -e
export V2_EXIT_CODE="$v2_exit_code"
"$V2_TRAIN_PY" - <<'PY'
import datetime, json, os
from pathlib import Path
p=Path(os.environ['V2_RUN'])/'formal/exit_status.json'
p.write_text(json.dumps(dict(exit_code=int(os.environ['V2_EXIT_CODE']),
    finished_at=datetime.datetime.now(datetime.timezone.utc).isoformat()))+'\n')
PY
exit "$v2_exit_code"
```

在已加载这些环境变量的终端，分别进入两个 tmux 会话执行（先 `--create` 注册成功）：

```bash
tmux new-session -s depthwam-v2-train
bash "$V2_DEPLOY/launch_v2.sh"
```

```bash
tmux new-session -s depthwam-v2-swanlab
CUDA_VISIBLE_DEVICES='' "$V2_TRAIN_PY" scripts/upload_training_scalars.py \
  --journal "$V2_RUN/formal/swanlog/scalars.jsonl" --run "$V2_SWAN_RUN" \
  --worker-status "$V2_RUN/status.json" --output "$V2_RUN/upload" \
  --exit-file "$V2_RUN/formal/exit_status.json" --watch \
  > "$V2_RUN/upload.log" 2>&1
```

训练实际配置在 `formal/config.yaml`。另保留 Git base、overlay provenance、接收方路径配置、环境版本、公共模型哈希、GPU 型号和上述运行脚本。训练期间不要修改使用中的源码；后续代码修改应另开副本。

预期产物：

```text
formal/checkpoints/weights/step_002000.pt        ... step_020000.pt
formal/checkpoints/weights/step_002000.pt.interface.json
formal/checkpoints/state/step_002000/            ... step_020000/
formal/training_audit_rank0.jsonl               # 其他 rank 同样保留
formal/swanlog/scalars.jsonl
formal/xwam_migration.rank0.json                # 其他 rank 同样保留
formal/config.yaml
formal/train.log
formal/exit_status.json
upload/status.json
```

SwanLab 曲线、tmux 会话消失、日志最后出现 20k 都不能单独证明成功；需同时确认真实退出码、最终权重、`.interface.json`、完整状态和 trainer step=20000。完整状态恢复使用 `resume=.../state/step_XXXXXX`，同时 `model.xwam_pretrained_checkpoint=null model.skip_dit_load_from_pretrain=true`，保持同一 V2 分布和统计/窗口版本。`.pt` 仅是 weights-only，不含完整续训进度。

tmux 支持 SSH 断线后继续，**不保证服务器重启后自动恢复**。新服务器若要求开机恢复，需要另建对应机器的服务并核对完整 checkpoint，不能照搬源端服务文件。

## 9. 评测环境与资产装配

训练可先部署，评测须用独立环境。源端评测关键版本为 Python 3.10、PyTorch 2.4.1/CUDA 12.1、torchvision 0.19.1、SAPIEN 3.0.0b1、mplib 0.2.1、scipy 1.10.1、transforms3d 0.4.2、trimesh 4.4.3、open3d 0.18.0、PyAV 17.1.0；详见 `eval_installed_versions.json`。不要在评测环境执行会把 torch 升到训练版本的 `pip install -e .`。

建议先按关键版本建立环境，再按版本清单补齐模型侧纯 Python 依赖：

```bash
conda create -n depthwam-v2-eval python=3.10 pip -y
conda activate depthwam-v2-eval
python -m pip install torch==2.4.1 torchvision==0.19.1 \
  --index-url https://download.pytorch.org/whl/cu121
python -m pip install numpy==1.26.4 scipy==1.10.1 sapien==3.0.0b1 mplib==0.2.1 \
  transforms3d==0.4.2 trimesh==4.4.3 open3d==0.18.0 gymnasium==0.29.1 av==17.1.0 \
  hydra-core==1.3.2 omegaconf==2.3.0 einops==0.8.1 transformers==4.49.0 \
  safetensors==0.5.3 accelerate==1.12.0 rich==14.2.0 ftfy \
  huggingface-hub==0.29.2 boto3==1.35.99 datasets==3.6.0 pyarrow==23.0.0 \
  jsonlines==4.0.0 gitpython==3.1.45 opencv-python==4.11.0.86 \
  toppra==0.6.10 imageio==2.34.2 imageio-ffmpeg==0.6.0 h5py==3.16.0 \
  matplotlib==3.10.9 termcolor==3.3.0 pyglet==1.5.31 moviepy==2.2.1
export V2_EVAL_PY="$(command -v python)"
```

这是核心安装集合，不是已在新服务器验证过的完整锁文件；按实际 import 错误和交付版本清单补齐其他依赖，执行 `pip check`。如果本机无可用 Vulkan/EGL/NVIDIA 渲染后端，先解决渲染环境再跑模型。

Curobo 使用原版，不带此前 expert-fix 补丁：

```bash
git clone https://github.com/NVlabs/curobo.git "$V2_DEPLOY/curobo"
git -C "$V2_DEPLOY/curobo" checkout cca894de9ec74e77a0a4071319c81958991a9108
python -m pip install setuptools_scm wheel ninja
python -m pip install pybind11==3.1.0 numpy-quaternion==2024.0.13 \
  yourdfpy==0.0.60 warp-lang==1.2.1 scikit-image==0.21.0 networkx pyyaml importlib_resources
# 确認 CUDA_HOME/nvcc 与评测 torch 的 CUDA 构建兼容后执行。
python -m pip install --no-deps --no-build-isolation -e "$V2_DEPLOY/curobo"
python -m pip check
```

在**新目录**装配资产：先大资产，再覆盖本次冻结后端的机器人 YAML，不能反过来。拷贝或 CoW 均可，不使用指向旧服务器的软链。

```bash
export V2_ROBOTWIN="$V2_DEPLOY/RoboTwin_eval_v2"
mkdir "$V2_ROBOTWIN"
mkdir "$V2_ROBOTWIN/assets"
cp -a --reflink=auto "$V2_UNPACK/robotwin_assets/." "$V2_ROBOTWIN/assets/"
cp -a "$V2_UNPACK/evaluation/RoboTwin_eval_private/." "$V2_ROBOTWIN/"
mkdir -p "$V2_ROBOTWIN/policy"
ln -s "$V2_PROJECT/experiments/robotwin/depthwam_policy" "$V2_ROBOTWIN/policy/depthwam_policy"
```

私有 `curobo_left.yml`、`curobo_right.yml` 中有源端绝对 URDF/碰撞球路径。只在新的 `$V2_ROBOTWIN` 副本中把前缀

```text
/DATA/disk0/lyy/depthwam_robotwin_share/RoboTwin2.0_official/assets/
```

改为 `$V2_ROBOTWIN/assets/`。记录替换前后哈希，确认引用文件存在；球体数值、URDF、网格、关节顺序均不改。`*_tmp.yml` 的 `${ASSETS_PATH}` 是原运行时代换变量，不直接当作 shell 变量展开。检查资产中的其他绝对路径：`rg -n '/DATA/|/home/' "$V2_ROBOTWIN" -g '*.yml' -g '*.yaml' -g '*.py'`，区分运行路径与历史注释，禁止全目录盲目字符串替换。

## 10. 冻结案例迁移和评测命令

**不要直接执行 `scripts/run_robotwin_mixed_ee_eval.py` 的原服务器 campaign。**它目前仍有 V1 task/source、旧训练目录和旧 GPU 的硬编码。单任务入口可用，但必须设置 protocol 所需环境变量。

先复制 `evaluation/preparation/` 到接收方新的 `$V2_DEPLOY/eval_preparation_v2/`，原包内证据保持原样。在新副本中：

1. 修改 `evaluation_protocol.json` 的 `private_harness_root`、`training_environment_seeds` 为接收方路径。
2. 记录 V2 模型/编码/时间策略，将 `mixed_inference.forward_rounds` 从 50 改成 **51**，明确 `depth_progress_spread_v1` 和 `body_piecewise_10_80_10_v1`。旧 `camera_scales_m` 仅是 legacy 尺度，不能把它当作 V2 主相机完整编码规则。资源字段改为接收方实际允许卡号。
3. 保持 stages、seed 范围、base seed、任务、unseen 指令、动作32步、相机 D435、官方成功标准及 `selection_rule_id` 不变。
4. 新协议文件重算 SHA256；**仅在新副本**各 `evaluation_cases/{smoke,screen,final}/{source}/{task}/frozen_cases.json` 的 `protocol_sha256` 更新为新值，并增加迁移来源记录。断言所有 `cases` 数组逐项等于交付源文件，包括顺序、episode、seed、instruction。训练 seed 文件内容和哈希不变。
5. 保留原 `protocol_attempts.jsonl`、`origin_frozen_cases.json` 作为历史专家筛选证据。它们不是新模型结果，不能改写成 V2 的成功记录。

这属于路径/模型分布元数据迁移，不重新抽 seed、不重新选择示范。协议校验器要求 frozen cases 与协议 SHA 一致，不能通过删除校验绕过。

接收方应在 CPU 下对全部 18 个 task/source/stage 组合实例化 `experiments.robotwin.six_task_protocol.ProtocolAudit`，验证新协议、案例与训练 seed 无重叠，且每个阶段回合数为 1/10/100。保持冻结案例评测模式，不运行旧 expert-fix 版本。

上述迁移可使用以下 CPU 脚本；先确认 `V2_GPUS` 是目标机器允许的数字卡号，且机器人资产路径已经按上一节修正：

```bash
export V2_EVAL_PREP="$V2_DEPLOY/eval_preparation_v2"
CUDA_VISIBLE_DEVICES='' "$V2_TRAIN_PY" - <<'PY'
import copy, hashlib, json, os, shutil
from pathlib import Path
from omegaconf import OmegaConf
from scripts.check_mixed_depth_v2 import compose
from experiments.robotwin.six_task_protocol import ProtocolAudit
src=Path(os.environ['V2_UNPACK'])/'evaluation/preparation'
dst=Path(os.environ['V2_EVAL_PREP'])
gpu_ids=[int(v) for v in os.environ['V2_GPUS'].split(',')]
assert len(gpu_ids)==4 and len(set(gpu_ids))==4 and min(gpu_ids)>=0
shutil.copytree(src,dst)  # 目标必须不存在
old=(src/'evaluation_protocol.json').read_bytes()
protocol=json.loads(old)
protocol['private_harness_root']=os.environ['V2_ROBOTWIN']
protocol['training_environment_seeds']=str(dst/'training_environment_seeds.json')
protocol['mixed_inference'].update(forward_rounds=51,schedule='depth_progress_spread_v1')
cfg=compose('robotwin_three_mixed_ee_v2_local')
protocol['body_depth_encoding']=OmegaConf.to_container(cfg.data.train.body_depth_encoding,resolve=True)
protocol['training_time_policy']=OmegaConf.to_container(cfg.model.flow.depth_time_policy,resolve=True)
protocol['resource_gate'].update(allowed_gpu_ids=gpu_ids,memory_gate_enabled=False,
    max_used_percent=100,min_free_mib=0,max_workers=4,workers_per_gpu=1)
path=dst/'evaluation_protocol.json'
path.write_text(json.dumps(protocol,ensure_ascii=False,indent=2)+'\n')
sha=hashlib.sha256(path.read_bytes()).hexdigest()
case_records=[]
for f in sorted((dst/'evaluation_cases').glob('*/*/*/frozen_cases.json')):
    source_file=src/f.relative_to(dst)
    original=json.loads(source_file.read_text()); migrated=copy.deepcopy(original)
    migrated['protocol_sha256']=sha
    migrated['receiver_migration']=dict(source_sha256=hashlib.sha256(source_file.read_bytes()).hexdigest(),
        original_protocol_sha256=original['protocol_sha256'],case_identity_unchanged=True)
    assert migrated['cases']==original['cases']
    f.write_text(json.dumps(migrated,ensure_ascii=False,indent=2)+'\n')
    case_records.append(dict(file=str(f.relative_to(dst)),cases=len(migrated['cases']),
        original_sha256=hashlib.sha256(source_file.read_bytes()).hexdigest(),
        migrated_sha256=hashlib.sha256(f.read_bytes()).hexdigest()))
assert len(case_records)==18 and sum(x['cases'] for x in case_records)==666
os.environ['DEPTHWAM_SIX_PROTOCOL']=str(path)
os.environ['DEPTHWAM_SIX_FREEZE_ONLY']='0'
for stage,definition in protocol['stages'].items():
    os.environ['DEPTHWAM_SIX_STAGE']=stage
    os.environ['DEPTHWAM_SIX_CASES_ROOT']=str(dst/'evaluation_cases'/stage)
    for scene in protocol['sources']:
        for task in protocol['tasks']:
            a=ProtocolAudit(task,'demo_'+scene,definition['base_seed'],
                definition['episodes_per_task_source'],dst/'cpu_audit'/stage/scene/task)
            for i,c in enumerate(a.frozen): assert a.seed_for_attempt(i,0)==c['seed']
for task in protocol['tasks']:
    seeds={stage:{c['seed'] for scene in protocol['sources'] for c in
        json.loads((dst/'evaluation_cases'/stage/scene/task/'frozen_cases.json').read_text())['cases']}
        for stage in protocol['stages']}
    assert not seeds['smoke']&seeds['screen'] and not seeds['smoke']&seeds['final'] and not seeds['screen']&seeds['final']
(dst/'receiver_migration.json').write_text(json.dumps(dict(
    original_protocol_sha256=hashlib.sha256(old).hexdigest(),local_protocol_sha256=sha,
    cases=case_records,cpu_protocol_check='PASS',simulator_run=False),indent=2)+'\n')
print('PROTOCOL_MIGRATION_CPU_PASS')
PY
```

单任务 smoke 示例（要使用**本次已训练的 V2 权重及同名 `.interface.json`**；第一次闭环 smoke 不计入初筛或最终分母）：

```bash
cd "$V2_PROJECT"
export V2_EVAL_PREP="$V2_DEPLOY/eval_preparation_v2"
export DEPTHWAM_SIX_PROTOCOL="$V2_EVAL_PREP/evaluation_protocol.json"
export DEPTHWAM_SIX_STAGE=smoke
export DEPTHWAM_SIX_CASES_ROOT="$V2_EVAL_PREP/evaluation_cases/smoke"
export DEPTHWAM_SIX_FREEZE_ONLY=0
export V2_CKPT="$V2_RUN/formal/checkpoints/weights/step_008000.pt"
export V2_EVAL_GPU=AUTHORIZED_PHYSICAL_GPU
CUDA_VISIBLE_DEVICES="$V2_EVAL_GPU" "$V2_EVAL_PY" -m experiments.robotwin.eval_robotwin_single \
  --config-name sim_robotwin_three_mixed_ee_v2_local \
  task=robotwin_three_mixed_ee_v2_local_eval \
  ckpt="$V2_CKPT" gpu_id=0 seed=61 \
  EVALUATION.robotwin_root="$V2_ROBOTWIN" \
  EVALUATION.task_name=click_bell EVALUATION.task_config=demo_clean \
  EVALUATION.eval_num_episodes=1 \
  EVALUATION.output_dir="$V2_DEPLOY/eval_v2/smoke/step_008000/clean" \
  EVALUATION.inference_seed_mode=xwam_derived EVALUATION.instruction_type=unseen \
  EVALUATION.replan_steps=32 EVALUATION.camera_type=D435 \
  EVALUATION.visualize_future_video=false \
  EVALUATION.visualization_depth_read_mode=isolated \
  EVALUATION.visualize_future_video_preserve_fast_action=true
```

`CUDA_VISIBLE_DEVICES` 填物理卡，`gpu_id=0` 是受限可见列表内的下标，不代表使用目标服务器物理 GPU0。实际允许卡由接收方分配。

对其余任务/场景更换 task/config，各自使用独立输出目录。初筛/完整评测沿用同一命令，仅按表同时调整 stage、cases root、base seed、回合数和 checkpoint：

| 阶段 | `DEPTHWAM_SIX_STAGE` | base `seed` | 每 task/source | 每 checkpoint 分母 |
|---|---|---:|---:|---:|
| smoke | smoke | 61 | 1 | 6（单独记录） |
| 初筛 | screen | 62 | 10 | 60 |
| 完整评测 | final | 63 | 100 | 600 |

当前交付协议的筛选规则实际为 **`top2_second_within_3`**：初筛取前两名，再纳入距离第二名成功数不超过 3/60（5个百分点）的其他 checkpoint；同分按较早步数排序。它不是历史对话中的“只取最好、差2以内取两个”。以本次已冻结协议为准；如要改变，必须在查看 V2 分数之前另行冻结。

初筛完整四组共 240 回合；完整评测每入选 checkpoint 600。最终案例与本轮 smoke/screen 独立，但为公平对照复用过往 body-only/V1 的最终案例，所以不能声称它们是从未使用的新 benchmark。

实际 case seed、unseen 指令和 X-WAM derived 推理 seed 均要在输出日志核对。保留所有模型失败和环境错误；不换 seed、不把视频副本算 episode、不根据最终成绩再择优后只报告赢家。遇到资源失败或进程中断，保存现场后从明确边界处理，不能直接重跑覆盖同目录。

正式批量调度仍需接收方 AI 基于这些单任务命令在独立部署目录生成新 launcher，记录分卡、退出码和每组完成状态。任务书已将这一步列为明确工作项，包含 dry-run、断点边界和选模/分母要求；本 README 没有声称原服务器调度器已经适配完目标机器，也不授权修改评测协议来替代适配。

## 11. 交付验收与查询

训练查询：

```bash
tmux ls
tail -n 30 "$V2_RUN/formal/train.log"
tail -n 3 "$V2_RUN/formal/training_audit_rank0.jsonl"
cat "$V2_RUN/upload/status.json"
# 退出后再读取：
cat "$V2_RUN/formal/exit_status.json"
```

接收方回传：

- `SOURCE_SNAPSHOT.json`、源码包 SHA256、基准与 overlay 哈希、所有 `_local` 配置、窗口路径重绑定记录、实际环境和 GPU 信息。
- 下载/解压校验结果，CPU 检查结果，4卡 smoke/两种恢复结果，官方权重逐项迁移报告。
- 正式训练配置、分项 loss/梯度、显存峰值、每步耗时、SwanLab 地址、退出状态和 2k–20k 全部 10 组 checkpoint 清单。
- 新评测协议/案例迁移审计、每次启动命令及退出码、逐回合结果；分开报告 smoke、四组初筛、每个入选模型最终 600 回合。

## 12. 当前已验证和仍待验证

**源端已完成**：105.72 GiB/81 文件上传；逐文件远端 SHA256/大小确认；完成标记确认；传输工具 8 项 CPU 测试通过。V2 的历史小模型/真实数据验证见代码包内 `docs/mixed_depth_v2.md`，其中记录的是对应日期的验证范围。

**本 README 的代码块**在源端执行语法检查；完整源码恢复、20k 配置生成和无 Git 历史的 CPU 测试结果记录于相邻 `README.validation.json`。路径/协议迁移沿用此前轻量 CPU 演练结果。这不等于在目标服务器安装成功。

**接收方仍需完成**：ModelScope 下载审批、环境安装、公共组件下载、路径/协议绑定、真实 VAE/官方权重 GPU 冒烟、4卡通信、完整状态恢复、机器人闭环和正式实验。源端此前 V2 运行在首次 2k 保存前结束，迁移包没有可用的生产 V2 完整续训 checkpoint；本次按官方权重新初始化。

本 README、`source_complete_v1/` 和 `ai_handoff_v1/` 是迁移包上传完成后新增的交付文件，**不属于原封存迁移包的 81 个文件，也未加入原 `transfer_manifest.json`**。交接清单 `ai_handoff_v1/HANDOFF_MANIFEST.json` 另行记录完整源码、部署说明及 AI 文档的哈希；不修改已封存分包、清单或旧完成标记。
