JEPA4Japan · 教程

第 24 章——先跑一个不会骗人的冒烟测试

1,288字 4分钟阅读 #LeVJEPA#JEPA#视频自监督学习#SIGReg

设计小批次过拟合、双视图一致性、SIGReg 统计、因果泄漏与内存检查,并定位常见故障。

课程进度 课程大纲 已发布 34/34 课

第 0 部分——先拿到地图

  1. 01 第 0 章——开始之前:这门课承诺什么 已发布
  2. 02 第 1 章——同一段视频,两扇不同的窗 已发布
  3. 03 第 2 章——沿着 Yann LeCun 的科研路线走一遍 已发布
  4. 04 第 3 章——别被名字绕晕:JEPA 家族树 已发布

第 1 部分——为什么简单目标会学会看视频

  1. 05 第 4 章——视频可以自己出题 已发布
  2. 06 第 5 章——保留意思,不重画每个像素 已发布
  3. 07 第 6 章——两张卡要相同,但不能全变成白纸 已发布
  4. 08 第 7 章——SIGReg:从许多影子检查一团点云 已发布
  5. 09 第 8 章——只用一行目标函数读懂 LeVJEPA 已发布

第 2 部分——把一段视频送进单编码器

  1. 10 第 9 章——全局视图与局部视图怎样配对 已发布
  2. 11 第 10 章——把视频切成时空小方块 已发布
  3. 12 第 11 章——一台编码器、一个投影头、一张摘要卡 已发布
  4. 13 第 12 章——一次完整前向传播 已发布
  5. 14 第 13 章——为什么扔掉 95% 反而更会看 已发布
  6. 15 第 14 章——块因果注意力:同一帧互看,未来不能偷看 已发布
  7. 16 第 15 章——RoPE、逐帧 token 与意外长出的密集特征 已发布

第 3 部分——会读实验,才算读懂论文

  1. 17 第 16 章——四组消融究竟回答了什么 已发布
  2. 18 第 17 章——相同轮数不等于相同花费 已发布
  3. 19 第 18 章——ImageNet、K400、SSv2 各考什么 已发布
  4. 20 第 19 章——把论文结果记成一本账 已发布
  5. 21 第 20 章——哪些结论现在还不能说 已发布

第 4 部分——从官方仓库到自己的实验

  1. 22 第 21 章——官方仓库地图 已发布
  2. 23 第 22 章——Walking Tours:十段长视频怎样变成训练数据 已发布
  3. 24 第 23 章——读懂默认配置并启动训练 已发布
  4. 25 第 24 章——先跑一个不会骗人的冒烟测试 当前课程
  5. 26 第 25 章——不用训练:加载公开权重提取特征 已发布
  6. 27 第 26 章——在自己的视频上做冻结评估 已发布

第 5 部分——把表征接回世界模型路线

  1. 28 第 27 章——重要边界:LeVJEPA 不是规划器 已发布
  2. 29 第 28 章——怎样把 LeVJEPA 接到下一代世界模型 已发布
  3. 30 第 29 章——十个从入门到论文级的研究项目 已发布

附录——随用随查的技术背包

  1. 31 附录 A——最低限度数学工具箱 已发布
  2. 32 附录 B——完整张量形状表 已发布
  3. 33 附录 C——术语表与论文时间线 已发布
  4. 34 附录 D——复现与审阅检查清单 已发布

长跑之前,先过五道小门

  1. 形状每个箱子贴尺寸
  2. 有限值拒绝 NaN/Inf
  3. 梯度参数真的在动
  4. 不偷看未来不改过去 token
  5. 资源账显存与吞吐留档
冒烟测试只检查管线有没有明显漏气;论文结论还要靠完整实验验证。

新玩具火车第一次上轨,只需先走两圈:数一数车厢,看看轮子会不会转,确认红灯亮时能够停下。两圈顺利并不证明它能横跨全国,却能在昂贵长跑前找出接反的电线。

第一关:箱子的尺寸

loader(数据加载器)默认输出全局 [B,16,3,224,224]、局部 [B,10,16,3,96,96];main.py 才把轴换成 encoder 所需的 [B,C,T,H,W]。训练模式保留 5% patch 时,全局 16×14×14=3136 个 patch 约留 157 个,加 [CLS] 得 158 tokens;每张局部 16×6×6=576,约留 29 个,加 [CLS] 得 30。

第二、三关:数值有限,梯度在走

pred_loss、sigreg_loss 和总 loss 都应是有限数,也就是没有 NaN(非数)或 Inf(无穷大)。执行一次 backward() 后,encoder 与 projector 应至少各有一个非零、有限的梯度,证明参数确实收到了更新信号。

两个 batch 内的 loss 上下抖动很正常:随机 crop、随机投影和小 batch 都会带来噪声。做小批过拟合检查时,把同一批数据和 seed 固定下来,同时记录表示方差;若只见 MSE 变小,常量表示造成的坍塌也可能伪装成进步。

dtype(数值类型)的交接也要查。默认 normalize_on_gpu=true 时,worker 发送 uint8,以减少约四倍 host/IPC 内存;main.to_float_normalized() 随后才除以 255,并做 ImageNet 归一化。若 loader 已经输出 float,再归一化一次会悄悄破坏输入。抽一批打印 dtype、min/max 和 shape:uint8 应落在 [0,255],进入 encoder 前则应是有限的浮点数。global crop 只有空间裁剪与归一化,local 还会加入颜色抖动、灰度和翻转;两者的时间索引保持完全相同。

第四关:未来有没有漏进过去

以下测试无需数据或预训练权重。它只改变后两帧;如果块因果 mask 正常,前两帧的 patch token 应保持一致。[CLS] 会读取完整 clip,因此有意不放进这项比较。

uv run python - <<'PY'
import torch
from module import vit_tiny
torch.manual_seed(0)
m = vit_tiny(img_size=32, patch_size=16, num_frames=4, tubelet_size=1,
             use_rope=True, token_drop_rate=0, attn_mode="block_causal").eval()
x = torch.randn(1, 3, 4, 32, 32)
y = x.clone(); y[:, :, 2:] = torch.randn_like(y[:, :, 2:])
with torch.inference_mode():
    a, b = m(x), m(y)
d = (a[:, 1:9] - b[:, 1:9]).abs().max().item()  # 前2帧×每帧4 patch
print(f"causal prefix max diff: {d:.3g}")
assert d < 1e-5
PY

第五关:资源账与有用的失败

记录峰值显存、每步时间与实际 token 数。遇到 OOM(显存不足),可以先减 batch_size、局部 crop 数或换 vit_tiny 来定位;这些改动会改变实验,日志里也要随之改名。worker 卡死时可试 loader.num_workers=0;出现 NaN 时,依次检查输入究竟是 uint8 还是已归一化 float、学习率、混合精度、SIGReg batch 与多卡同步。官方 training_workshop.ipynb 提供了更完整的小规模检查,可在 CUDA、MPS 或 CPU 上走通视图、模型、两项 loss 与分布图。

建议把通过条件写成机器可判定的断言:shape 完全相等;所有 loss/grad finite;固定批次 50–100 步后总 loss 的滑动均值低于开头且每维标准差没有整体趋零;因果 prefix 差小于容差;两次相同 seed 的首步 loss 可复现。SIGReg 每步都会重新抽随机方向,单点数值不应与参考日志逐位相等;真正异常是持续爆炸、为 NaN,或嵌入谱缩成近零。

调试时打开的三份原件

冒烟测试的验收合同

  1. loader 是 [B,T,C,H,W],encoder 是 [B,C,T,H,W]。
  2. 因果测试比较 patch 前缀;全局 [CLS] 本来就能看完整 clip。
  3. 两批训练成功只是冒烟通过,不是精度复现或独立验证。

给故障找对入口

  1. 默认全局视图训练时约输出多少 token?
  2. 为什么 MSE 降低还要同时看表示方差或 SIGReg?
  3. 修改未来帧时,是否要求 [CLS] 不变?
诊断结果
  1. 约 158:157 个保留 patch 加一个 [CLS]。
  2. 因为所有输入映到同一点也能降低不变性 MSE。
  3. 不要求;它是读取整个 clip 的汇总 token,patch 才遵守逐帧因果前缀。