Download code/README.md from teawhite/ActionRoPE: direct link, hf CLI and curl.
- Browser
- Download file 46.3 kB
-
https://huggingface.co/teawhite/ActionRoPE/resolve/main/code/README.md
- Command line
-
hf download hf://teawhite/ActionRoPE/code/README.md
-
curl -L -o README.md https://huggingface.co/teawhite/ActionRoPE/resolve/main/code/README.md
ActionRoPE
在 Wan2.2-TI2V-5B 上做动作条件世界模型:动作不走文本,走 RoPE。 数据是《恶意不息》BOT 漫游录像重建的 per-frame 动作条件 latent。
- 训练分辨率 832×480,81 帧 @16 fps,对应 latent
[48, 21, 30, 52] - infra 用 DiffSynth-Studio 的全量微调链路
- 8×H200 (143 GB)
目录
| 路径 | 内容 | 来源 |
|---|---|---|
actionrope/ |
本项目代码 | — |
tools/ |
数据体检脚本 | — |
scripts/ |
shell 入口 | — |
data/ |
数据集,189 GB(解包后) | hf download teawhite/EYBX-processed |
models/Wan2.2-TI2V-5B/ |
基座权重,32 GiB | hf download Wan-AI/Wan2.2-TI2V-5B |
DiffSynth-Studio/ |
训练 infra,v2.1.6 | git clone |
outputs/ |
checkpoint / 日志 | — |
data/ models/ DiffSynth-Studio/ .venv/ 都在 .gitignore 里。
环境
python3.12 -m venv .venv
.venv/bin/pip install torch torchvision --index-url https://download.pytorch.org/whl/cu128
.venv/bin/pip install transformers "imageio[ffmpeg]" safetensors einops modelscope ftfy \
pandas accelerate peft deepspeed tensorboard av
.venv/bin/pip install -e ./DiffSynth-Studio --no-deps
实测版本:torch 2.11.0+cu128 · accelerate 1.14.0 · deepspeed 0.19.6 · peft 0.20.0 · transformers 5.16.1(CUDA 13.2 driver 595.71.05)。
数据
hf download teawhite/EYBX-processed --repo-type dataset --local-dir data
bash data/unpack.sh "$PWD/data" # 必须给绝对路径,脚本内部 cd 过
解包后:
data/latent/pool/ 22,782 个 .pt [48,21,30,52] bf16
data/latent/train_eybx/ 20,983 软链
data/latent/val_eybx/ 1,175 软链 按 leg 整体留出,不要用哈希划分
data/latent/pool_heldout/ 159 软链 组合泛化评测参照
三个划分合计 22,317,与 pool 差的 465 条是
buffer leg,按 HANDOFF 的划分策略两边都不进
data/clips/ 22,782 个 mp4 832×480 / 16fps / 81 帧
data/meta/*.jsonl 逐 clip 一行,含 21 个 cell 的逐 cell 真值
data/logs/<session>/ state.npz(20Hz 世界坐标) / input.npz(摇杆) / offsets.json(世界→屏幕)
每个 latent .pt 的内容
d["latent"] # bf16 [48, 21, 30, 52]
d["prompts"] # list[str] 21 条,含动作从句(老方案,ActionRoPE 不用)
d["prompts_bossdrop"] # list[str] 21 条,场景 dropout
d["actions"] # list[int] 21 条,9 类动作索引
d["scenes"] # list[str] 21 条
d["actions"] / d["purity"] / d["rt_ratio"] / d["video_t0"] / d["start_cell"] ...
文本表
数据集自带的 text_table_eybx.pt(1,253 键)每一条都含 the player is {动作},
是动作走文本的老方案。ActionRoPE 的动作走 RoPE,所以要重建一张只含场景的表:
老: In a 2.5D top down view, {scene}, the player is {action}.
新: In a 2.5D top down view, the player is {scene}.
.venv/bin/python -m actionrope.build_text_table # -> data/latent/text_table_actionrope.pt
361 键(18 纯场景 + 342 转场 + 1 空条件串),dict[str, bf16[512,4096]]。
编码走 DiffSynth 自己的 tokenizer + WanTextEncoder,与
WanVideoUnit_PromptEmbedder 逐字一致,训练时不必再挂 11 GB 的 UMT5。
会静默出错的地方
摘自数据集的 HANDOFF.md,加上本仓库实测:
- 分辨率只能 832×480。 848×480 会静默炸:52→53 是奇数,DiT 的 stride-2 丢掉最右一列 latent。
- 不要用训练脚本自带的哈希划分做 val。 clip 步长 84 帧、本身 81 帧,相邻两个 clip 只差
3 帧,哈希划分会把同一段素材切到训练和验证两边。验证走离线的
val_eybx。 unpack.sh要传绝对路径。 它cd "$ROOT"之后又用$ROOT/latent拼路径, 传相对路径时划分那一步会静默失败(tar 解包正常,软链 0 个)。- 号段不能撞:1e8(s1) / 2e8(s2) / 3e8(burn) / 4e8(hardcut),1e7 是 vfx 段。
- 低 rt_ratio 片段(世界降速)没有被过滤,s1 里 rt<0.8 占 27.6%。 做控制服从评测时要排除,否则「模型不听话」和「世界本来就慢」会混在一起。
数据体检
.venv/bin/python -m tools.inspect_dataset # 全部检查
.venv/bin/python -m tools.inspect_dataset --check transitions
已核对过的结论:
- 22,782 个 clip / 478,422 个 cell 的 prompt 全部能剥离动作从句,零失败
- 94% 的 clip 内部逐 cell prompt 不同(变的只有动作从句),场景从句 90% 的 clip 内恒定
- 转场 1,900 条 **100% 是
A → 过渡 → B**,没有A → 过渡 → 过渡 - 转场起始 cell 均匀落在 3..9(各 12.9%~15.9%),cell 0-2 / 10-20 无正样本 ——
成因是
burn_corpus.py:44的N_PRE_CHOICES = [9,13,17,21,25,29,33]帧, 经cell_of()映射即 3..9。已确认按现状使用(要求是「不全在同一个 cell」)。 - 三个划分两两无交集,0 断链。但转场 clip 是按 heldout 标志而不是按 src 归属划的:train_eybx 里 1,741 条转场中
257 条的
src_a/src_b在 val_eybx(123 条是src_b⇒ 该 val clip 的帧 n_pre..80 逐帧出现在训练集里,像素核对 mean|Δ| 0.69 灰度级),涉及 56 个 val clip(36 个 sidecar valid)。dataset.py按data/meta/transition.jsonl把这些 转场剔出训练集(exclude_src_in=val_dir,sidecar valid 的有 73 条),划分软链本身不动。 - 200 条 latent 抽样:形状
[48,21,30,52]bf16 全对,无非有限值, std 均值 1.247 —— 与 Wan2.2 VAE 的 mean/std 归一化后应有的量级一致
ARoPE 模型补丁(actionrope/arope.py)
动作走 RoPE 的模型侧实现,不改 DiffSynth 源码,接口见 actionrope/SPEC.md「模型契约」:
build_world_freqs(dit, f, h, w, offset_tok):f 轴查dit.freqs[0],h/w 轴按世界坐标(i_h + dy_tok, j_w + dx_tok)用polar(1, pos·inv_freq)现算(允许小数)。offset_tok=None时与原查表逐位相同;零偏移走现算路径时与查表只差 ~1e-15 (表在 CPU 上算、现算在 GPU 上,cos/sin 库函数的 ULP 差;dit.freqs实际是 complex128)。known_mask/loss_weight_map:世界坐标落在首帧足迹[-0.5, h-0.5]×[-0.5, w-0.5](闭区间)内 ⇒ known,否则 new(loss ×new_weight)。install_arope(dit, mask_channel):mask_channel=True只新增零初始化的arope_mask_embedding(Conv3d(1, 3072, (1,2,2)),15,360 个参数);False时 ckpt 键集与原模型完全一致。arope_forward(...):复刻model_fn_wan_video的seperated_timestep + fuse_vae_embedding_in_latents分支(帧 0 的 t=0)。context 可以是整条[b,512,4096]或逐 cell[b,21,512,4096](每帧 token 只 cross-attend 自己 cell 的文本,context_ids=[b,21]给出去重分组)。
自测(一张 H200,约 1 分钟):
CUDA_VISIBLE_DEVICES=0 PYTHONPATH=$PWD .venv/bin/python -m pytest tests/test_arope.py -s -v
实测:offset_px=None 时与 model_fn_wan_video 逐位相等;逐 cell 文本(21 份相同)与整条模式逐位相等;
全参数训练一步(batch 1、全尺寸、梯度检查点开)峰值显存 ~22 GB、前向+反向 ~4 s。
相机偏移 sidecar(actionrope/geometry.py / sidecar.py / verify_sidecar.py)
动作走 RoPE 需要每帧的相机(玩家屏幕投影)位移,存在 data/latent/arope_sidecar.pt
(结构见 actionrope/SPEC.md)。生成与校验:
.venv/bin/python -m actionrope.sidecar # 未修正版(gain=[1,1]),约 10 s
.venv/bin/python -m actionrope.verify_sidecar # 对视频实测符号/增益并重写 sidecar,约 75 s(默认取全部 636 个候选 clip)
.venv/bin/python -m pytest tests/test_geometry.py # 几何单测(纯 CPU)
实测结论(outputs/sidecar_check/gain_fit.json,636 个高速直行 clip、帧 0 vs 帧 16/32/48、
SIFT+RANSAC 仿射场在玩家位置取值):
- 日志 M 的符号是反的:实测背景位移 ≈ +(日志预测的玩家投影位移),两 session、
两轴、1,251 个样本 ≥96% 一致。旧流水线
align_flow.flow_series把相位相关峰取反了一次 (峰本身已经是 −位移),M 用它拟合出来符号整体翻转;verify_actions的闭环用的是同一个 测法,所以没抓到。后果:meta / prompt 里的动作标签是镜像的 ——「moving right」的 clip 里玩家实际向屏幕左走,「moving up」实际向下(2026-09-04 人工看视频确认:clip_Eybx_100000022_000210标签 "moving left",画面里场景向左滑、人物朝右跑)。sidecar 已把符号乘进gain_correction(负值),ARoPE 臂不必再处理;plain臂由prompts.mirror_action把动作词翻回真实方向 (训练--mirror_labels默认开,文本表用text_table_eybx_mirror.pt,见下)。data/HANDOFF.md里「世界 +x → 屏幕左、+z → 屏幕下」是这个翻转的产物,实际是 +x → 右、+z → 上(未改数据集文档)。 - 横向增益:g_x = 1.120 / 1.124(两 session 相差 0.4%,R² 0.99/0.98)⇒
gain_correction[0] = −1.122, 即 832 画幅下横向 29.8 px/世界单位(日志 26.6)。 - 纵向:g_y = 1.094 / 1.117(相差 2.1%)但 R² 0.90 / 0.84 < 0.9 ⇒ 按 SPEC 规则幅值保持 1.0,
gain_correction[1] = −1.0。原因是相机是斜视角透视,纵向位移随屏幕行变化 ~40%/100 px (横向 ~15–20%/100 px),单一平移只是近似,k=48 时 R² 掉到 0.75。 valid:最近邻日志时间差 ≤ 0.5 s(--max_sample_dt 0.5,2026-09-04 人定放宽)+ 跨 offset 台阶(log_jump, 只中clip_Eybx_200000340_000168)+ 传送 ⇒ n_valid = 22,781 / 22,782。原 0.15 s 规则只留 13,855(s1 丢一半); 放宽前做过实测:只在 0.15–0.5 s 规则之间的 clip(160 条纯日志缺口 + 160 条 rt<0.8 降速)过同一套视频校验, x 增益 −1.117 / −1.101、R² 0.98 / 0.98、中位残差 6.9 / 5.9 px,与 valid 基线(−1.122 / 0.99 / 4.7 px)一致。 世界降速不再靠 valid 规则兜,改用rt_ratio闸(训练 <0.8 剔、val/评测 <0.9 剔,见训练一节)。walk_speed_px_s = [69.2, 41.1](已乘 |gain|),供推理造指令用。
推理(actionrope/infer.py)
首帧 + 场景从句 + 动作脚本 ⇒ 81 帧 832×480 mp4,并对生成视频实测背景位移。示例见 scripts/infer_demo.sh:
# replay 一条 val clip:指令 = sidecar 真值,首帧 = 该 clip 帧 0,文本 = 原 prompt(剥掉动作),与 GT 比 PSNR
CUDA_VISIBLE_DEVICES=4 .venv/bin/python -m actionrope.infer --replay clip_Eybx_200000958_000273 \
--ckpt outputs/<run>/step-N.safetensors --out outputs/samples/replay.mp4
# 自造指令:玩家向屏幕右走 1.0× 步速、21 个 cell;速度取 sidecar meta 的 walk_speed_px_s
CUDA_VISIBLE_DEVICES=4 .venv/bin/python -m actionrope.infer --first_frame clip_Eybx_200000958_000273 \
--scene tidal_flats --actions "right:1.0:21" --out outputs/samples/right.mp4
--actions "dir:mult:cells,..."(8 向 + idle,cell 数合计 21)或"dx,dy:cells"(px/s); 方向名指玩家在屏幕上的真实运动方向(right ⇒ 背景左移)。--trigger burn:<B>:<start>:<n>把 cell 换成转场从句、之后切成 B(文本表里有全部 342 条转场串,查不到才回退加载 UMT5)。--arm plain:普通 RoPE + 文本带动作。默认--label_mode literal:动作词写真实屏幕方向(真实向右 ⇒ "moving right"),配--mirror_labels训出来的模型;--replay时把数据集的镜像 prompt 也翻回来。--label_mode mirror只给--no-mirror_labels的旧约定模型用(原标签、原表)。--ckptstrict 加载,mask 通道按 ckpt 键集自动判断;不给就是基座权重。采样 50 步 shift 5, 帧 0 每步写回首帧 latent(首帧用 fp32 编码再存 bf16,与数据集 latent 同精度;bf16 编码会差 ~1%)。--cfg的负条件:arope 臂NULL_PROMPT;plain 臂逐 cell 裸动作串In a 2.5D top down view, the player is {action}.(该臂训练 dropout 用的就是这 9 个串,NULL_PROMPT不在 eybx 表里)。plain + CFG 未实测。- 最后一行打印实测位移:帧 0→80 的背景位移(
verify_sidecar.measure_shift,SIFT+RANSAC 仿射场在 玩家位置取值;直接测不出时每 16 帧链式累加,打印里标明采用的是 direct 还是 chain 及段数)与指令−frame_offset_px[80]对比;同名.json里有全部数字(相位相关值只作参考,见已知问题 4),_frame0.png是首帧。 大位移场景(≥300 px)下 0→80 直接测基本都失败,头条数字来自 5 段链式累加。
自测(一张 H200,约 1.5 分钟):CUDA_VISIBLE_DEVICES=4 .venv/bin/python -m pytest tests/test_infer.py -s -v。
测试产物写到 outputs/test_artifacts/(AROPE_TEST_DIR 可改)。
实测:推理侧 VAE 编码与数据集 latent 相对误差 0.8%(bf16;fp32 编码 0.3%,数据集是 fp32 编码后存 bf16),
首帧 fp32 编码(默认)cell 0 相对误差 0.38%(bf16 编码 0.96%),首帧单独编码与整段同精度编码的 cell 0 逐位相同;GT clip 实测位移 (+385, +4) vs sidecar 的 −frame_offset[80] (+370, +2);
20 步采样 5.7 s、解码 2 s、峰值 16.4 GB。基座权重(未微调)+ ARoPE 对 right 1.0× 指令实测位移
(−353, +3) vs 指令 (−356, 0),replay 实测 (+332, +29) vs (+370, +2) —— 世界坐标 RoPE 零样本就把背景
推到了指令位置(画面有拖影,需微调);plain 臂基座对 "moving right" 文本实测 (−6, +1),基本不动。
训练(actionrope/dataset.py / train.py,配置 configs/,入口 scripts/)
只训 DiT(bf16,全参数),latent 与文本都预计算,不挂 T5 / VAE。两个原生臂(四个 baseline 臂见「五臂 baseline」一节):
| 臂 | RoPE | loss 权重 | mask 通道 | 文本 |
|---|---|---|---|---|
--arm arope |
世界坐标(sidecar offset_px) |
new 区 × --new_weight(默认 2) |
零初始化 conv(--no_mask_channel 关) |
text_table_actionrope.pt(剥动作从句) |
--arm plain |
普通 | 全 1 | 无 | text_table_eybx.pt(原 prompt,含动作从句) |
两臂都走逐 cell 文本 cross-attn(context [b,21,512,4096] + context_ids 去重),loss 与 DiffSynth
FlowMatchSFTLoss 同形(FlowMatchScheduler("Wan") 1000 个训练时间步均匀抽、帧 0 用干净 latent 替换并剔除、
Σw·(pred−target)²/Σw × training_weight(t))。场景 dropout --scene_dropout 0.1:arope 臂 21 个 cell 全换
NULL_PROMPT,plain 臂换成 .pt 自带的 prompts_bossdrop。
bash scripts/smoke_train.sh arope # 4 卡 ZeRO-2,64 clip,12 步,第 6/12 步存权重 + 验证(约 3 分钟)
bash scripts/smoke_train.sh plain
bash scripts/train_arope.sh # 8 卡正式:40k 步、save_every 1000、val_every 500(RUN=xxx 改输出名)
bash scripts/train_plain.sh
.venv/bin/python tests/check_ckpt.py outputs/<run>/step-N.safetensors --arm arope # 键集 strict 核对(CPU)
- 数据:
train_eybx20,983 个 .pt 里 sidecarvalid=False剔 1,src_a/src_b在 val_eybx 的转场剔 257,--min_rt_ratio 0.8(默认)剔世界降速 2,197 ⇒ 可用 18,528(plain 17,044 / burn 1,162 / hardcut 322); val_eybx 1,175 按--val_min_rt_ratio 0.9(默认)剔 193 ⇒ 982。40k 步 × 8 卡 ≈ 17 个 epoch。 - 输出
outputs/<run>/:step-N.safetensors(10.0 GB,只含 DiT 键,arope 臂多arope_mask_embedding.*, 能直接load_state_dict(strict=True))、train_log.csv(loss/lr/grad_norm/step_time/mem)、val_log.csv(固定 val clip × 固定 t∈{200,500,800} × 固定噪声)、tensorboard/、config.json(全部参数 + sidecar 增益 + 环境)。 --resume x.safetensors热启动权重(step 从 0 起,Adam 动量与 warmup 归零,且 bf16 导出会丢掉 fp32 master 里 小于 1 ULP 的更新 —— RMSNorm 权重 30 步内在 bf16 里逐位不变就是这个原因);--save_state(正式脚本默认开)时另存 DeepSpeed 状态到state_A/state_B两槽轮转(8 卡每份约 70 GB),<output>/state软链指向最新完整槽,--resume outputs/<run>/state续训(数据顺序不复现)。- 随机数:
set_seed(seed, device_specific=True),各 rank 的噪声 ε 与场景 dropout 抽签独立(2 进程实测:分片不相交、 ε 逐步不同、跨 epoch 重排);不加 device_specific 时 8 张卡每步共用一份 ε。 --grad_clip(默认 1.0)在 DeepSpeed 下写进gradient_clipping(启动时打印生效值);日志里的grad_norm是裁剪前的全局范数。- 学习率手动 step(不交给
accelerator.prepare,否则每步会被 stepnum_processes次),--warmup_steps线性 warmup。 - 单进程
python actionrope/train.py不走 DeepSpeed:AdamW 直接在 bf16 参数上更新、无 fp32 主权重(lr 1e-5 的更新大多被 bf16 舍入吞掉),只适合 smoke,数字不代表训练效果。
实测(4×H200,ZeRO-2,每卡 batch 1,梯度检查点开):1.22 s/步,峰值显存 32.8 GB/卡;单卡无 DeepSpeed 1.13 s/步、46.7 GB。
ActionRoPE 实现
上面各节按模块写了细节,这一节是总览:模块表、各阶段命令、8 卡端到端 smoke 的实测数字与已知问题。
所有模块的接口契约在 actionrope/SPEC.md,不改 DiffSynth 源码,只在 actionrope/ 里以函数 / 子类扩展。
模块
| 文件 | 作用 | 自测 |
|---|---|---|
actionrope/prompts.py |
strip_action 剥动作从句、NULL_PROMPT |
tools/inspect_dataset.py --check prompts |
actionrope/build_text_table.py |
只含场景的文本表 text_table_actionrope.pt(361 键) |
--check text_table |
actionrope/build_mirror_table.py |
plain 臂用的文本表 text_table_eybx_mirror.pt(1,783 键 = eybx 表 + 530 条翻回真实方向的转场串,7.5 GB,GPU 0 约 1 分钟) |
— |
actionrope/geometry.py |
常量、cell↔帧、日志→屏幕投影、sample_frame_offsets、known/new 掩码 |
tests/test_geometry.py(CPU) |
actionrope/sidecar.py |
生成 data/latent/arope_sidecar.pt(逐 clip 的 offset_px[21,2] / frame_offset_px[81,2] / valid) |
— |
actionrope/verify_sidecar.py |
对视频实测符号 / 增益(SIFT+RANSAC 仿射场),把 gain_correction 乘回 sidecar |
outputs/sidecar_check/ |
actionrope/arope.py |
世界坐标 RoPE、逐 cell 文本 cross-attn、mask 通道、arope_forward |
tests/test_arope.py(GPU) |
actionrope/dataset.py |
latent + sidecar + 文本表 ⇒ 样本;valid 剔除;场景 dropout | — |
actionrope/train.py |
各臂全参数微调(accelerate + DeepSpeed ZeRO-2),定步验证 / 导出(arm 参数带 arm. 前缀同文件) |
tests/check_ckpt.py |
actionrope/infer.py |
首帧 + 场景 + 动作脚本(或 --replay)⇒ mp4,并实测背景位移;臂按 ckpt 自动识别 |
tests/test_infer.py(GPU) |
baseline/ |
四个对照臂 linear / xattn / prompt / adaln(ActionArm 钩子),build_arm / ckpt 键集识别;见「五臂 baseline」 |
tests/test_arm_*.py(GPU)、tests/test_baseline_integration.py(CPU) |
configs/accelerate_zero2*.yaml |
8 卡 / 4 卡 DeepSpeed ZeRO-2 bf16 配置 | — |
scripts/*.sh |
smoke_all.sh(端到端)、smoke_train.sh、train_arope.sh、train_plain.sh、train_baseline.sh <arm>、smoke_baselines.sh、infer_demo.sh |
— |
sidecar 生成与校验
.venv/bin/python -m actionrope.sidecar # 未修正版(gain=[1,1]),~10 s
.venv/bin/python -m actionrope.verify_sidecar # 实测符号 / 增益并重写 sidecar,~75 s
.venv/bin/python -m actionrope.sidecar --gain_fit outputs/sidecar_check/gain_fit.json --max_sample_dt 0.5 # 当前交付用的就是这条(0.5 s 规则)
.venv/bin/python -m pytest tests/test_geometry.py -q
当前 sidecar:gain_correction = [-1.1223, -1.0](符号反转 + 横向 1.122 增益,806 个候选 clip 重测)、
walk_speed_px_s = [69.24, 41.08]、max_sample_dt = 0.5、n_valid = 22,781 / 22,782(1 条 log_jump)。
训练
bash scripts/smoke_all.sh # 8 卡端到端 smoke:单测 → 体检 → arope 30 步(含 save_state)→ plain 10 步 → 推理 → check_ckpt → 续训 2 步,约 9 分钟
FROM=c bash scripts/smoke_all.sh # 从第 c 步开始(a 单测 / b 体检 / c arope / d plain / e arope 推理 / f plain 推理 / g check_ckpt / h resume)
bash scripts/smoke_train.sh arope # 4 卡 12 步 smoke
bash scripts/train_arope.sh # 8 卡正式:40k 步、lr 1e-5、warmup 500、save_every 1000(+save_state)、val_every 500(RUN=xxx 改输出名)
bash scripts/train_plain.sh # 对照臂,同样节奏;崩溃后 bash scripts/train_arope.sh --resume outputs/arope_40k/state
.venv/bin/python tests/check_ckpt.py outputs/<run>/step-N.safetensors --arm arope
正式训练的 train.py 关键参数:--arm {arope,plain,linear,xattn,prompt,adaln}(后四个见「五臂 baseline」,scripts/train_baseline.sh <arm>)、--mask_channel/--no_mask_channel、--new_weight 2.0、
--scene_dropout 0.1、--lr 1e-5、--grad_clip 1.0、--max_steps、--save_every --save_state、--val_every --val_n、
--resume(safetensors 热启动 / state 软链续训,后者需 --save_state)、--min_rt_ratio、--limit(smoke)。
推理
# replay:指令 = sidecar 真值,首帧 = 该 clip 帧 0,与 GT 比 PSNR
CUDA_VISIBLE_DEVICES=0 .venv/bin/python -m actionrope.infer --ckpt outputs/<run>/step-N.safetensors \
--replay clip_Eybx_200000958_000273 --steps 50 --out outputs/samples/replay.mp4
# 自造指令:玩家向屏幕右走 1.0× 步速 21 个 cell(right ⇒ 背景左移 ≈ 356 px)
CUDA_VISIBLE_DEVICES=0 .venv/bin/python -m actionrope.infer --ckpt outputs/<run>/step-N.safetensors \
--first_frame clip_Eybx_200000958_000273 --scene tidal_flats --actions "right:1.0:21" --out outputs/samples/right.mp4
# 对照臂(动作走文本,动作词 = 真实屏幕方向)
CUDA_VISIBLE_DEVICES=0 .venv/bin/python -m actionrope.infer --arm plain --ckpt outputs/<plain_run>/step-N.safetensors \
--first_frame clip_Eybx_200000958_000273 --scene tidal_flats --actions "right:1.0:21" --out outputs/samples/right_plain.mp4
8 卡端到端 smoke 实测(scripts/smoke_all.sh,2026-09-04 审查修复后重跑,一次通过,总耗时 587 s)
| 步骤 | 结果 | 耗时 |
|---|---|---|
a pytest tests/ -x -q(GPU 0) |
26 passed | 60 s |
b inspect_dataset --check layout text_table |
通过 | 4 s |
c arope 臂 8 卡 ZeRO-2,256 clip,30 步,val_n 8,--save_state |
见下 | 182 s(含 2 次 10 GB 导出 + 2 次 66 GB state) |
| d plain 臂 8 卡 ZeRO-2,10 步 | 见下 | 89 s |
| e arope step-30 推理:replay + right×1(50 步) | 见下 | 88 s(两条) |
| f plain step-10 推理:right×1(50 步) | 见下 | 42 s |
g check_ckpt step-30(CPU) |
strict_load_ok,827 张量,675 张量有改动,max|Δ| 4.9e-4 | 23 s |
h 从 outputs/smoke_arope/state(→ state_B)续训 2 步 |
从 step 30 起到 32,step-32.safetensors | 99 s |
- 8 卡 step time:arope 臂首步 5.68 s(含编译 / 分配),其后 1.20 s/步;plain 臂 1.20 s/步。 与 4 卡实测(1.22 s/步)一致 —— 每卡 batch 1、ZeRO-2 通信不是瓶颈。
- 显存:8 卡下每卡峰值 22.7 GB(主进程)/ 23.9 GB(各进程最大),比 4 卡的 32.8 GB 低 —— ZeRO-2 优化器状态按 8 份切。
- arope 30 步 loss(乘过
training_weight(t),每步 t 随机、各 rank 噪声独立,单样本噪声大):0.299 0.331 0.294 0.265 0.177 0.210 0.278 0.259 0.305 0.237 0.168 0.265 0.254 0.197 0.213 0.158 0.235 0.264 0.275 0.343 0.109 0.110 0.345 0.182 0.320 0.252 0.261 0.329 0.237 0.169; lr warmup 5 步(2e-6 → 1e-5)正确,grad_norm(裁剪前)0.94 → 0.1–0.4;[env] grad_clip=1.0(生效值 1.0)印证 CLI 值写进了 DeepSpeed。 - val(8 个固定 val clip × t∈{201,501,800} × 固定噪声):arope step 15 → 30:loss_w 0.514/0.273/0.220 → 0.493/0.263/0.212 (均值 0.336 → 0.322);plain step 5 → 10:0.483/0.253/0.201 → 0.485/0.255/0.201(均值 0.312 → 0.314)。 两臂的 val 数值不可直接比:arope 的 loss_w 含 new 区 ×2 权重、文本剥了动作。
- 推理(GPU 0,50 步 shift 5,首帧 fp32 编码):采样 12.8 s、VAE 解码 2.0 s、峰值 16.4 GB、单条含加载约 33–37 s。
- arope step-30 replay:实测背景位移 (+372.0, +32.5)(0→80 直接 SIFT;链式 (+367.3, +33.6))vs 指令 (+370.2, +2.1) (GT 实测 (+384.9, +3.8),链式),帧 0 PSNR 31.4 dB,帧 1–80 平均 16.5 dB。dy 偏差 +30 px 是生成视频真实的纵向漂移 (模板匹配独立核对 GT 为 +4、生成为 +43),不是链式测量误差。
- arope step-30 right×1:实测 (−356.6, +22.3) vs 指令 (−355.7, 0),误差 (−0.9, +22.3) px。
- plain step-10 right×1(当时按旧约定:文本 "moving left" 镜像标签):实测 (−5.5, +2.3) vs 指令 (−355.7, 0) —— 30 步以内文本动作基本无响应。
这些数字与未微调基座上的结果同量级(基座 replay (+331.6, +28.7)、right×1 (−352.7, +2.7)),30 步微调不足以改变画质
(PSNR 16 dB 仍是拖影),只证明链路通。产物在
outputs/samples/smoke_e2e_*.{mp4,json,png}。
- 续训:
state_A(step 15)/state_B(step 30)各 66 GB,state软链指向state_B;步骤 h 从软链 load_state 后 step 31 的 loss / grad_norm 与训练末尾连续(0.174 / 0.164),warmup 不重来。 - 正式 40k 步预估(8 卡 1.20 s/步):纯训练 13.3 h,加 40 次导出(每次
25 s)≈ 17 min、40 次 save_state(每次 ~30 s)≈ 20 min、 80 次验证(16 clip × 3 t,3 s)可忽略,合计约 14 h/臂;40k × 8 = 32 万样本 ≈ 12,579 个有效 clip 的 25 个 epoch。
ARoPE 5k 步首次训练结果(2026-09-04,outputs/arope_5k/,scripts/eval_5k_demo.sh)
8 卡 ZeRO-2、lr 1e-5、warmup 200、训练集 18,528、1.20 s/步、1.7 h。val loss(16 clip,t=200/500/800 均值)0.2550 → 0.2482。
同一首帧(clip_Eybx_200000958_000273,tidal_flats)、50 步采样、文本只有场景,实测帧 0→80 背景位移(SIFT 链式):
| 指令 | 指令位移 px | 实测 px | 说明 |
|---|---|---|---|
| right ×0.5 | −173 | −184 | |
| right ×1.0 | −346 | −366 | |
| right ×1.5 | −519 | −543 | 三档斜率 ≈1.05,线性 |
| left ×1.0 | +346 | +385 | 与 right 对称 |
| up ×1.0 | +205 | +264 | 纵向超 27%:sidecar 纵向增益仍是 1.0,模型按真实画面(≈1.2×)学的 |
| down ×1.0 | −205 | −259 | 同上 |
| right 10 cell → left 11 cell | +35 | +34 | 折返回到出发画面 |
| replay GT 偏移 | +370 | +397 | GT 视频实测 +385;帧 1–80 PSNR 17.3 dB(新区域是生成内容,PSNR 无意义) |
画面干净、场景连贯,玩家钉屏。训练中 replay 的横向超调随步数收敛:step-1000 +445 → 2000 +405 → 5000 +397。 下一步:纵向增益应按实测(k=16 拟合 1.20/1.27,全候选 1.12)写进 sidecar 重训;plain 对照臂未训。
bg50 四科(step-5000,actionrope/eval_bg50.py,2026-09-04,outputs/eval_bg50/step5000/)
复现旧报告《世界RoPE·四科判决》的口径,只跑 ARoPE 臂:考场 data/eval/eybx_bg_50/(50 个 GPT 生成的未见背景,中央已合成玩家),
文本条件 NULL_PROMPT(训练时场景 dropout 用的串;这些背景不属于 18 个训练场景,不配场景从句,--scene 可覆盖),首帧 26:15 居中裁剪后缩放,
50 步 shift 5、bf16、seed 0。150 条视频(增益 10 景 × 5 档 + 折返 50 + 直行 50)8 卡 312 s 生成完,失败 0;
度量只读 mp4、CPU 48 进程 102 s。位移量法与 sidecar 校验相同(verify_sidecar.measure_shift:SIFT+RANSAC 仿射场在玩家位置取值;
帧 0→80 直接测不出时 16 帧链式累加)。度量代码先在合成平移视频上自检(8 项全过:位移 / 逐帧速度 / 注入倒退帧 / 折返残差与补偿 / 明暗漂移 / 增益斜率,
见 report.md)。折返 / 直行只用横向指令,纵向为 0(纵向增益已知偏大 ~25%,见已知问题 3)。
.venv/bin/python -m actionrope.eval_bg50 --ckpt outputs/arope_5k/step-5000.safetensors --run step5000 --stages gen,measure --gpus 0-7 # 可续跑
.venv/bin/python -m actionrope.eval_bg50 --run step5000 --stages measure --no_selfcheck # 只重算度量
| 科目 | 指标 | ARoPE step-5000(本机 bg50) | 旧报告 世界RoPE@5k |
|---|---|---|---|
| 1 增益线性(0.5–1.5×,10 景) | 过原点斜率 / R² | 0.932 / 0.879(剔除视差背景 01 后 0.960 / 0.962) | 1.06 / 0.95 |
| 2 折返闭环(1.5×,右 40 帧左 40 帧,50 景) | 终帧 vs 首帧 PSNR / SSIM / NCC 中位 | 19.17 dB / 0.401 / 0.787 | — |
| 回归残差中位 / p90 · 补偿后 NCC 中位 | 8.4 / 20.4 px · 0.833 | — | |
| 守门:帧 0→40 实走距离中位(指令 256) | 240.5 px(0 条 <50%) | — | |
| 3 运动平滑(1.0× 直行,50 景) | 倒退帧总数 | 16(全部 ≤ 0.22 px 的停顿帧;>1 px 的 0) | 6 |
| 归一速度波动 std/mean 中位 · 归一 jerk 中位 | 0.629(按 4 帧 cell 平均后 0.118)· 2.25 | — | |
| 平均速度 / 承诺 中位 | 1.014 | — | |
| 4 方向服从(同一批直行) | |角度误差| 中位 / p90 · 幅度比中位 · 失控数 | 1.16° / 2.49° · 0.983 · 0 / 50 | — |
- 增益:各档幅度比中位 0.5× 1.015、0.75× 0.939、1.0× 0.973、1.25× 0.973、1.5× 0.908 —— 未见背景上高速档略欠冲。R² 被背景 01(赛博城市)拉低: 生成画面带纵深视差(远景楼群带比地面带慢 ~15%),SIFT 仿射场在玩家位置取值时各段锁到不同层,16 帧链式给 −235 px 而逐帧速度累加给 −391 px(指令 −433), 是量法在视差场景下失稳,不是模型不动;头条仍按旧报告口径全量报。
- 折返:终帧回到首帧 8 px 以内(中位),补偿位置后 NCC 只从 0.787 升到 0.833 —— 余下的差距是往返 80 帧里生成内容对首帧的重绘(PSNR 最低的 ukiyo-e / 水彩类背景整幅被重画)。
- 平滑:逐帧速度有稳定的帧内节奏(各视频均值 −6.2, −1.7, −4.3, −8.2, −1.3, −9.4 … px/帧,跨视频 std 0.4–1.1 px),来自因果 VAE 每 4 帧一个 latent cell 的时间压缩; 按 cell 平均后波动 0.118,接近合成整数平移的下限 0.108。倒退帧 16 帧比旧报告的 6 多,但全是 ≤0.22 px 的停顿(集中在帧 20→21、42→43 的 cell 边界),没有一帧真往回走。
- 方向:角度误差全部来自纵向漂移(dy 中位 -6.5 px / 346 px 横向),无失控。
已知问题
- valid 规则已从 0.15 s 放宽到 0.5 s(2026-09-04 人定):原规则丢一半 s1(训练集 12,579),且日志缺口与世界降速
高度相关,等于偷偷兼做了 rt 过滤。放宽前实测:只在两规则之间的 clip 过同一套视频校验,精度与 valid 基线一致
(见 sidecar 一节)。缺口帧内偏移是"保持上一位置"(每条中位 13 帧),逐 cell 取均值后的误差已包含在残差里。
世界降速改由
rt_ratio闸处理:训练--min_rt_ratio 0.8、val/评测--val_min_rt_ratio 0.9(默认值), 控制服从评测必须用 ≥0.9 的口径,否则"模型不听话"和"世界本来就慢"分不开。 - 日志 M 符号反了:数据集 meta / prompt 里的动作标签是镜像的("moving right" 实际向屏幕左走,已人工看视频确认)。
sidecar 已用负 gain 修正,arope 臂不受影响;plain 臂训练默认
--mirror_labels把动作词翻回真实方向 (文本表text_table_eybx_mirror.pt,python -m actionrope.build_mirror_table生成),推理默认--label_mode literal。data/meta、data/HANDOFF.md、text_table_eybx.pt本身未改。 - 纵向增益已改为 1.12(2026-09-04 人定,step-5000 推理 up/down 超调 27% 证实 1.0 欠标定;
outputs/sidecar_check/gain_fit_y112.json)。以下是改前的记录:纵向增益保持 1.0,x / y 世界坐标尺度已不一致:g_y 全候选拟合 1.094 / 1.117、R² 0.90 / 0.84 < 0.9(斜视角透视,纵向位移 随屏幕行变化 ~40%/100 px,k=48 时 R² 0.75),按 SPEC 规则退回 1.0;但 k=16(透视影响最小)下 g_y = 1.20 / 1.27,独立 SIFT 抽查也是 1.2–1.4。结果 ARoPE 的 dy/32 相对 dx/32 少算 10–25%,推理 up/down 指令用walk_speed_y = 42.25 px/s会系统性 欠冲。是否把 SPEC 的 R² 判据改为按 k=16 / k=32 判定并重跑 verify(得 g_y≈−1.13~−1.2)由人决定。同一透视原因下 ARoPE 的 "同一世界位置 ⇒ 同一编码" 对远离玩家行的 token 只是近似(顶部约 0.5×、底部约 1.5×)。 - 位移指标不等于画质:未微调基座 + ARoPE 已能把背景推到指令位置(SIFT 误差 < 10 px),但画面是拖影(PSNR 16 dB);
控制服从评测必须同时看 PSNR / LPIPS。全图相位相关在真实画面上恒给 ≈0(48 个样本 |dx|,|dy|<0.6 px;合成平移能正确返回),
json 里标了
phase_note,不是第二种实测。大位移下 0→80 直接 SIFT 都失败,头条数字来自 5 段链式累加(json 里有sift_source/sift_chain_n_segments);replay 的 dy 偏差(+30 px 量级)是生成视频真实的纵向漂移,不是测量误差。 - 视频比日志领先 1–2 帧(≈0.06–0.12 s):10 个动作有变化的 valid clip 上逐帧比对,最佳 lag 全为 −1 或 −2 帧
(RMS 2.4–4.7 → 2.0–4.1 px)。来自
data/logs/*/offsets.json的对齐精度,不是本仓库代码;折算 4–9 px 的系统偏差,与 x 拟合 中位残差 4.6 px 同量级。要修可在geometry.sample_frame_offsets加time_bias_s(约 −0.06~−0.1 s),未做。 - 转场 clip 的 B 段 known 掩码只按几何定义:530 个 valid 转场里 cell ≥ cell_of(n_pre) 的 latent 像素 72.9% 被判 known (首帧足迹几何上覆盖),但内容来自 src_b。SPEC 已明确这是有意的简化;要按语义把 B 段整体标 new 需在 dataset/train 里按 n_pre 改掩码。
--save_state的 DeepSpeed 状态 8 卡每份约 70 GB,state_A/state_B两槽轮转(共 140 GB)、state软链指向最新完整槽, 不删旧槽;正式脚本默认开。- T5 回退(文本表查不到的串)已实现未实测;smoke 里所有串都命中文本表。plain 臂 +
--cfg的负条件已改为逐 cell 裸动作串 (在表里),但 plain + CFG 未实测。 - 单进程
python actionrope/train.py不走 DeepSpeed:AdamW 直接在 bf16 参数上更新、无 fp32 主权重(std 0.02 的参数 lr 1e-5 走 10 步只有 15% 元素变化),只能 smoke,数字不代表训练效果。 - 模型看到的 timestep 是 bf16 舍入值(|Δt| ≤ 2),x_t 用精确 σ(t);DiffSynth 的
FlowMatchSFTLoss是先转 bf16 再 add_noise (706/1000 个 id 落到相邻 id,max|Δσ| 2.5e-3),两者不逐位一致,但 train.py 的做法与推理 pipeline 一致,不改。 val_eybx里 0 条转场 clip,val_log.csv对触发 / 燃烧能力毫无反映;转场评测要另写脚本消费pool_heldout(159 条)。- 单样本
context [21,512,4096]88 MB 走 DataLoader 多进程 IPC,num_workers 2 反而比 0 慢(16 vs 21 样本/s),但每卡每步只需 0.8 样本/s,不是瓶颈;要优化可让 Dataset 只返回去重后的 context + 下标。
五臂 baseline(baseline/,契约 baseline/SPEC.md)
同一个 Wan2.2-TI2V-5B、同一份数据、同一套配方,唯一自由变量是动作以什么方式进模型。四个对照臂都按上游代码移植
(vender/ 只读),每个臂在 baseline/<name>.py 顶部 docstring 里逐条写了上游文件:行、对应关系、差异;
接入方式是 actionrope/arope.py::arope_forward(arm=, action_inputs=) 的固定钩子(encode / modify_t_mod / extra_context / after_patch / block_pre / block_mid / block_post,arm=None 时与原实现逐位相同),所有新增路径零初始化——
装上瞬间输出与原模型逐位相同(各臂 tests/test_arm_<name>.py 测试 1 实测 max|Δ| = 0;build_arm 时再跑一次 zero_init_check(),不过就拒绝启动)。
机制表
| 臂 | 代表作 / 上游入口(文件:行) | 动作进模型的位置 | 用的信号 | 新增参数 | 文本 |
|---|---|---|---|---|---|
arope |
本项目 actionrope/arope.py |
h/w 轴 RoPE 位置 = 格坐标 + 累计偏移(token);known/new 掩码通道(零初始化 conv) | offset_px |
0(mask 通道 15,360) | scene |
linear |
ReactiveGWM inference/models/dit.py L207-209(action_embedders)、L228-234(_inject_action)、L284-286(每 block 前注入);training/bidirectional/train.py L134-137(zero/xavier 初始化) |
每个 block 入口残差流 x += Linear_i(action)(30 个 bias-free Linear,沿 h·w 广播),钩子 block_pre |
offset_tok [b,21,2](上游是 10 键 one-hot 经 adaptive_max_pool 到 latent 帧) |
30×2×3072 = 184,320(上游 10 键 921,600) | scene |
xattn |
Matrix-Game 3 wan/modules/action_module.py L39-333(ActionModule:键盘 L267-332 / 鼠标 L201-265 / 12 帧窗口 L207-214)、wan/modules/model.py L533-536(前 15 块)、L617-662(block 内注入)、L1097-1103(proj 置零);utils/conditions.py L73-107(KEYBOARD_IDX) |
前 15 块 block_mid(自注意力后):视频 token 作 q、逐 latent 帧键盘 token 作 k/v 沿时间轴 cross-attn;鼠标分支时间轴 self-attn;proj_* 零初始化 |
键盘 ← action_idx(9 类 → 4 位多热 up/down/left/right);鼠标 ← delta_tok(逐 cell 速度) |
299,531,520(键盘 141.8M + 鼠标 157.7M;旧报告 98.6M 口径 = --arm_kwargs '{"enable_mouse": false, "window_frames": 1}') |
scene |
prompt |
Incantation inference.py :76-159(PromptGenerator,0.25 s = 1 latent 帧一条 prompt)、:247-260(唯一串编码一次)、:318-353(每帧只 attend 自己那帧的文本);modules/causal_model.py :434-450(无动作模块) |
逐 cell 文本 cross-attn(arope_forward 原生:q 重排 (b f)(h w),k/v (b f) L),零钩子 |
文本(动作从句,text_table_eybx_mirror.pt,动作词翻回真实方向);不读 action_inputs |
0 | scene_action |
adaln |
AlayaWorld alaya/control/action.py :78-115(相对位姿 6 轴)、ltx2/modules/model_ltx_2_3.py :130-175(ActionAdaLNEmbedder 32 维正弦 + MLP)、:782-786(action_adaln_projection SiLU→Linear(dim,6·dim),std 1e-3)、:1171-1176 / :1252-1253(timestep_emb += a0 按帧广播) |
modify_t_mod:t_mod[b,S,6,dim] += proj(embedder(sinusoid(actions))) 按帧广播,30 层共用(与 Wan time_projection 同路) |
[offset_tok, delta_tok] 4 轴,每轴尺度 (10, 6.5, 0.6, 0.45) token,×freq_scale 1000 |
66,478,080(embedder 9.8M + proj 56.6M;上游 LTX-2.3 dim 4096 约 118M) | scene |
plain 保留(= prompt 的旧名:train.py --arm plain 走 arm=None 的旧路径,prompt 走 PromptArm 的零钩子,前向逐位一致)。
action_inputs 由 dataset.py 统一给:offset_px(sidecar)、offset_tok = offset_px/32、delta_tok(相邻 cell 之差,cell 0 = 0)、
action_idx(.pt 的 actions 经 prompts.MIRROR_ACTION 翻回真实屏幕方向;64 条 clip 上与 delta_tok 方向的余弦均值 0.97)。
推理侧 infer.py 用 baseline.make_action_inputs(同一定义)从动作脚本生成,action_idx = velocity_to_label(mirror=False),
replay 用 meta 标签经 MIRROR_ACTION 翻回。
接入(baseline/__init__.py / train.py / infer.py)
baseline.ARMS/build_arm(name, dit, arm_kwargs):建臂 →install(dit)→ 搬到 dit 的 device / dtype;detect_arm_from_keys / detect_arm_from_ckpt按 ckpt 的arm.*键集识别臂名与结构(xattn 的window_frames/enable_mouse/blocks从张量形状反推),metadata 的arm名区分无 arm 键的 arope / plain / prompt。train.py --arm {arope,plain,linear,xattn,prompt,adaln}:新臂 =install_arope(dit, mask_channel=False)+ 普通 RoPE + 权重全 1 +text_mode按臂(prompt → scene_action,其余 scene);臂是AropeTrainModule.arm子模块(DeepSpeed 引擎里、同一个 AdamW、同 lr); 导出的 safetensors = DiT 键(去dit.前缀)+arm.*键,metadata 记arm / arm_kwargs / n_new_params,config.json的model段 也记;--resume x.safetensors按前缀拆开热启动(ckpt 无 arm 键 ⇒ 臂保持零初始化;臂不符 ⇒ 报错)。--arm_kwargs传 JSON 给臂的构造函数。infer.py --arm可省略:按 ckpt 自动识别并 strict 装臂;--arm plain/prompt文本带动作从句,其余剥掉;prompt 臂的 context 全同时也 展成逐 cell(PromptArm核对)。CFG 负条件:文本臂逐 cell 裸动作串,其余NULL_PROMPT;正负两支都带动作。tests/check_ckpt.py <ckpt>(臂自动识别)核对 DiT + 臂都能 strict 装回并报告臂参数是否已非零。
bash scripts/train_baseline.sh linear # 8 卡 ZeRO-2,与 train_arope.sh 同配方(GA 2、40k 步、每 1000 步存、每 500 步验证)
bash scripts/train_baseline.sh xattn --max_steps 5000 --warmup_steps 200 # 5k 步实验(与 arope_5k_ga2 同)
ARM_KWARGS='{"enable_mouse": false, "window_frames": 1}' RUN=xattn_report_5k bash scripts/train_baseline.sh xattn --max_steps 5000 --warmup_steps 200 # 旧报告 98.6M 口径
bash scripts/train_baseline.sh prompt # = plain 臂(动作走逐 cell 文本)
CUDA_VISIBLE_DEVICES=0 .venv/bin/python -m actionrope.infer --ckpt outputs/linear_40k/step-N.safetensors \
--first_frame clip_Eybx_200000958_000273 --scene tidal_flats --actions "right:1.0:21" --out outputs/samples/linear_right.mp4 # --arm 自动识别
bash scripts/smoke_baselines.sh # 四臂单卡 2 步 + step-2 推理 20 步 + check_ckpt,汇总到 outputs/smoke_baselines/summary.json
.venv/bin/python -m pytest tests/ -q -x # 含 tests/test_arm_*.py(GPU)与 tests/test_baseline_integration.py(CPU)
单卡 smoke 实测(scripts/smoke_baselines.sh,2026-09-05,GPU 4–7 轮流,机器上另有 8 卡训练在跑)
单进程 train.py(不走 DeepSpeed,AdamW 直接更新 bf16 参数)--limit 8 --max_steps 2,梯度检查点开;step-2 ckpt 推理 right:1.0:21 20 步;
check_ckpt strict 装回。数字只证明链路通(2 步的 ckpt 与基座无异,位移不响应指令属正常)。
| 臂 | 新增参数 | step 1 / step 2 耗时 (s) | 训练峰值显存 | loss (step 1 / 2) | 推理 20 步采样 / 峰值 | 实测背景位移 帧 0→80(指令 (−346.2, 0)) | ckpt |
|---|---|---|---|---|---|---|---|
linear |
184,320 | 7.08 / 2.71 | 46.7 GB | 0.029 / 0.104 | 12.0 s / 16.4 GB | (−5.3, +2.0) direct | 10.00 GB,825 DiT + 30 arm 张量,30 个 arm 张量已非零 |
xattn |
299,531,520 | 9.87 / 3.03 | 49.5 GB | 0.026 / 0.103 | 13.7 s / 17.0 GB | (−29.0, +81.8) chain | 10.60 GB,825 + 225,225 非零 |
prompt |
0 | 6.87 / 2.91 | 46.7 GB | 0.030 / 0.104 | 13.3 s / 16.5 GB | (−6.3, +0.9) direct | 10.00 GB,825 + 0 |
adaln |
66,478,080 | 6.74 / 2.78 | 47.3 GB | 0.038 / 0.075 | 11.7 s / 16.5 GB | (−5.3, +1.7) direct | 10.13 GB,825 + 6,6 非零 |
(对照:同一脚本单卡 arope 臂 1.13 s/步、46.7 GB,那时机器空闲;本次 8 卡训练同时在跑,稳态 2.7–3.0 s/步是共享 CPU / PCIe 的结果, 不是臂的开销——四臂互相之间只差 ≤ 0.3 s。)
- 第 1 步含 CUDA 初始化 / 首次分配,第 2 步才是稳态;单卡峰值显存 = 权重 + 梯度 + 两份 Adam 状态各 10 GB + 激活,臂的参数按同样比例叠加
(xattn 300M ⇒ +2.4 GB)。8 卡 ZeRO-2 下每卡约 23 GB(
smoke_all.sh口径),臂参数分片后更小。 pytest tests/ -q -x(GPU 4):51 passed,153 s(含 4 个test_arm_*.py、test_arope.py、test_infer.py各加载一次 DiT,新增test_baseline_integration.py4 个 CPU 用例)。--resume outputs/smoke_linear_1gpu/step-2.safetensors热启动 linear 臂:n_arm_tensors 30, arm_loaded True,1 步后再导出 855 张量;另在 GPU 6,7 上跑了 adaln 臂 2 进程 DeepSpeed ZeRO-2(GA 2、val_n 2)2 步:每卡峰值 56.8 GB(2 卡分片,8 卡约 23 GB),导出 831 张量、check_ckptstrict 装回、6 个 arm 张量非零 —— 与正式配方同一条路。注意 accelerate 的--main_process_port默认 29500 被正在跑的 8 卡训练占着,另起训练要换端口。。