FlowWAM:把光流当成动作,让预训练视频模型直接开机器人

发布时间:2026/9/2 15:28:35
FlowWAM:把光流当成动作,让预训练视频模型直接开机器人 0. 简介FlowWAM 面向机器人操作里的世界-动作模型World Action Model, WAM要解决的是一个很具体的矛盾预训练视频生成器里存着大量关于「像素怎么随时间移动」的运动先验但机器人的动作要么是各家不通用的数值关节向量要么是抽象的隐动作码这些表示都塞不进视频模型习惯的 RGB 输入格式先验就用不上。它没有再给视频模型外挂一套动作 token 或动作头而是把动作本身编码成光流视频——用 HSV 色轮把每像素位移画成一张和 RGB 同格式的图再和 RGB 一起送进同一个预训练视频生成器双流建模。从实验看最值得关注的是 RoboTwin 2.0 上Clean成功率做到92.94%、Random做到92.14%同时因为光流能从无动作标签的视频里直接抽取模型还能吃 EgoDex 这类纯人类第一视角视频做预训练。对应的部分代码已经放在Github上了。1. 为什么又造了一个新名词1.1 视频先验很强但动作表示卡在门口先看一个具体冲突。预训练视频生成器Wan、CogVideoX 这类已经在海量视频上学会了「物体怎么运动、机械臂怎么连续移动」这种帧间动态这正是操作任务最需要的先验因为操作的成败不只取决于把指令对应到物体更取决于逐帧捕捉机器人和环境的连续运动。可问题在于机器人要执行的动作是一串关节角或末端位姿数值这种符号化的东西和视频模型习惯的像素输入根本不是一个空间。要用上视频先验动作就得换一种既贴合视频输入格式、又保留跨帧运动线索的表示方式。这里的关键是过去的表示都在这两个要求上顾此失彼FlowWAM 想给出一个同时满足两者的答案。1.2 三类现有路线各自的缺口进一步看现有 WAM 的动作表示大致分三类。第一类是数值动作 tokenDreamZero、Cosmos Policy、UWM 这些把动作向量拼到视频隐变量旁边协同生成精确但不同机器人动作空间不同跨本体迁移困难而且动作始终是独立于视频先验的符号流。第二类是隐动作码Motus、LAPA 一系从帧间转移里学一个与具体机器人无关的隐表示摆脱了本体依赖却过于抽象丢掉了控制需要的稠密、空间对齐的运动线索。第三类是图像空间动作信号ray map、embodiment mask、多视角动作图把控制信号渲染成视觉形态直接放进视频模型输入域方向对了但这些大多是「指出动作发生在哪」的静态空间提示并没有编码「每个可见部件如何跨帧移动」。核心问题在于前两类接不进视频先验第三类接进去了却是帧级静态条件不是随预测视频一起演化的时间稠密动作表示。FlowWAM 的判断是光流恰好能同时补上这三个缺口它和 RGB 同格式、稠密、且天然带跨帧运动方向与幅度。2. 整体框架2.1 输入输出接口与两种运行模式FlowWAM 的输入是一张参考图I 0 I_0I0​和一句语言指令τ \tauτ输出则取决于运行在哪种模式。这里要厘清的是整个框架用一个双流扩散 Transformer 同时建模 RGB 视频和光流视频两条流而策略模式与世界模型模式的差别仅仅在于光流这条流是要被生成还是被当作条件给定。当光流由模型生成时FlowWAM 就是一个策略生成的运动计划被动作专家解码成可执行动作当光流被外部提供时FlowWAM 就是一个世界模型渲染出与指定运动一致的未来 RGB 视频。这种「一套权重、两种用法」的设计把动作预测和世界建模统一成了同一个视频到视频的生成任务。两条流的联合建模可以写成z E ( V ) , z f E ( F ) , [ z t 1 , z t 1 f ] DiT θ ( [ z t , z t f ] , t , τ ) \mathbf{z} \mathcal{E}(\mathbf{V}), \qquad \mathbf{z}^{f} \mathcal{E}(\mathbf{F}), \qquad [\mathbf{z}_{t1}, \mathbf{z}^{f}_{t1}] \text{DiT}_\theta([\mathbf{z}_t, \mathbf{z}^f_t], t, \tau)zE(V),zfE(F),[zt1​,zt1f​]DiTθ​([zt​,ztf​],t,τ)其中V \mathbf{V}V和F \mathbf{F}F分别是 RGB 与光流视频序列E \mathcal{E}E是被冻结的共享 VAE 编码器z \mathbf{z}z和z f \mathbf{z}^fzf是两条流的隐变量DiT 在共享的 Transformer 块里对两条流做联合去噪。这里要厘清的是两条流走的是完全相同的编码器与 Transformer差别只在极少量的流独立适配器patch 嵌入与输出头因此模型几乎原封不动地继承了预训练视频生成器的全部生成能力而不是从头拼一个新架构。2.2 训练与推理的关键不对称这里有一个值得注意的不对称设计涉及动作专家读取的隐变量分布。直观理解是训练时动作专家可以读到「干净」的 VAE 隐变量直接编码真值观测得到但推理时它只能读到双流模型迭代去噪生成出来的隐变量后者难免带残余去噪误差。如果不处理这个分布差异动作专家在推理时就会因为输入分布漂移而变脆。FlowWAM 的做法是在训练时以概率p 0.5 p{}0.5p0.5往喂给动作专家的隐变量里混入噪声并把采样到的噪声水平作为额外嵌入告诉动作专家让它学会在带误差的隐变量上也能稳定解码。3. 第一条核心机制把光流编码成 RGB 同格式的图3.1 HSV 色轮编码为什么是关键第一条核心机制是光流的 RGB 化编码。这里的关键是只有让光流和场景帧格式完全一致同一个 VAE 编码器和视频生成器才能不加任何动作 tokenizer 地直接处理它。给定相邻帧光流场f t ∈ R H × W × 2 \mathbf{f}_t \in \mathbb{R}^{H\times W\times 2}ft​∈RH×W×2记录每像素位移( u , v ) (u,v)(u,v)既捕捉运动发生在哪也捕捉可见点怎么移动。FlowWAM 用一个 HSV 色轮编码ϕ \phiϕ把它转成 RGB 图F t ϕ ( f t ) : H atan2 ⁡ ( v , u ) π 2 π , S ∥ f t ∥ m , V 1 F_{t}\phi(\mathbf{f}_{t}):\quad \text{H}\frac{\operatorname{atan2}(v,u)\pi}{2\pi},\quad \text{S}\frac{\|\mathbf{f}_{t}\|}{m},\quad \text{V}1Ft​ϕ(ft​):H2πatan2(v,u)π​,Sm∥ft​∥​,V1其中m mm是光流幅度的归一化常数H、S、V 分别是色相、饱和度、明度通道。这里色相编码方向、饱和度编码幅度且在选定归一化下这个编码是可逆的ϕ − 1 ( F t ) \phi^{-1}(F_t)ϕ−1(Ft​)能恢复出数值光流场。换句话说动作和视频在像素空间里被彻底统一了因为光流图F t F_tFt​和场景帧I t I_tIt​格式完全一致动作相关的运动可以被同一个 VAE 编码器和视频生成器直接处理不再需要一个单独的动作 tokenizer这正是整套统一表示能成立的技术前提。难点提示为什么用 HSV 而不是直接堆 (u,v) 两通道想象你要把「往右上方向、走了 3 格」这条运动告诉一个只认识彩色照片的画家。直接给他两个数字 (u,v) 他没概念因为他一辈子只见过 RGB 图但你把方向画成颜色右上某种青色、把快慢画成颜色深浅他立刻就能像看普通照片一样理解。HSV 编码就是这个「翻译成画家母语」的动作论文消融里把它换回裸( u , v ) (u,v)(u,v)张量成功率直接崩掉就是因为破坏了预训练 VAE 期望的输入格式。3.2 工程价值可逆编码撑起两种模式这个编码的工程价值在于可逆性带来的双向可用。因为ϕ \phiϕ可逆同一张光流图在策略模式里是生成目标、在世界模型模式里是条件输入两种角色靠一套编解码就能来回切换。论文的世界模型侧消融给了很硬的数字把条件从文本、数值动作、裸( u , v ) (u,v)(u,v)张量、图像空间 mask 一路换到完整的 RGB 光流EWMScore 从 49.31 提升到 65.23。这里要厘清的是收益不是来自「加了视觉条件」这么泛的东西而是来自 RGB 光流同时具备「每像素运动」和「视频原生格式」两个属性缺一不可。3.3 代码透视HSV 编码与逆解码下面这段直接摘自官方仓库training/reversible_flow_codec.py的FlowCodec.encode/decode它把每像素位移先转成极坐标再把方向和幅度分别塞进色相与饱和度两个通道展示极坐标分解、幅度归一化以及编码与逆解码为什么必须严格互逆——只有严格互逆世界模型模式里给定的光流条件才能被还原成物理位移# training/reversible_flow_codec.py —— FlowCodec.encode / FlowCodec.decodedefencode(self,flow,max_magnitudeNone,sigma0.15):dx,dyflow[...,0],flow[...,1]magnitudenp.sqrt(dx**2dy**2)anglenp.arctan2(dy,dx)# [-pi, pi]ifmax_magnitudeisnotNoneandmax_magnitude-1:diagnp.sqrt(float(H**2W**2))# VideoJAM 对角线归一化max_magnitudesigma*diagelifmax_magnitudeisNone:max_magnitudefloat(np.percentile(magnitude,99.5))1e-6# 自适应上限mag_normnp.clip(magnitude/max_magnitude,0,1)angle_normnp.clip((anglenp.pi)/(2*np.pi),0,1)# 方向 - [0,1]hue(angle_norm*179).astype(np.uint8)# OpenCV: H in [0,179]sat(mag_norm*255).astype(np.uint8)# 幅度 - 饱和度valnp.full_like(hue,255,dtypenp.uint8)# V 恒为 255hsvnp.stack([hue,sat,val],axis-1)returncv2.cvtColor(hsv,cv2.COLOR_HSV2RGB),max_magnitudedefdecode(self,rgb_image,max_magnitude):hsvcv2.cvtColor(rgb_image,cv2.COLOR_RGB2HSV)anglehsv[...,0].astype(np.float64)/179.0*2*np.pi-np.pi# 逆映射方向magnitudehsv[...,1].astype(np.float64)/255.0*max_magnitude# 逆映射幅度dx,dymagnitude*np.cos(angle),magnitude*np.sin(angle)# 极坐标 - 直角returnnp.stack([dx,dy],axis-1).astype(np.float32)这段代码做的事很直接编码时把位移方向经arctan2归一化成色相、把幅度归一化后写成饱和度、明度恒为 255。为什么这样设计是因为光流图必须落进 RGB 的合法值域才能被冻结的 VAE 无损吃下去。这里要厘清一个和论文正文的差异仓库里max_magnitude的默认策略不是固定 25px而是取当前帧幅度的99.5 百分位自适应上限max_magnitudeNone时另外还提供了一个按对角线σ ⋅ H 2 W 2 \sigma\cdot\sqrt{H^2W^2}σ⋅H2W2​归一化的 VideoJAM 模式论文附录提到的 25px 上限是数据预处理阶段的一个具体配置代码则把归一化常数做成了可切换项。工程细节上decode严格是encode的逆运算max_magnitude通过.metasidecar 文件或图像右下角像素回传这保证了世界模型模式里给定的光流条件能还原成物理位移正是 3.2 节说的可逆性在代码层面的落地。4. 第二条核心机制双流共享同一个视频先验4.1 设计动机为什么不能开两个模型第二条核心机制是双流架构。这里的关键是如果给 RGB 和光流各开一个独立视频模型不仅参数翻倍两条流之间还无法做深度时空交互光流也就退化成了外挂的辅助信号——这恰恰是过去把光流当辅助监督的做法的通病。FlowWAM 的做法是让两条流共享同一个冻结 VAE、同一批 Transformer 块只有 patch embedding 层和输出头是流独立的。在每个自注意力层里RGB token 和光流 token 拼接起来做联合注意力然后再拆回各自的流RoPE 位置编码对两条流独立施加。这意味着两条流能做深度时空交互同时共享的 Transformer 块保证模型完整保留了预训练视频生成器的生成能力。4.2 代码透视联合注意力与流身份嵌入仓库把这套双流拆成两块FlowStreamModulediffsynth/models/wan_video_dit_dual_stream.py持有光流独有的 patch 嵌入、输出头和流身份参数而联合注意力的具体拼接逻辑在diffsynth/pipelines/wan_video_dual_stream.py里两者配合起来才构成完整的双流通路。先看流独立适配器怎么从 RGB 层深拷贝初始化这是让新加的光流流一上来就继承视频先验的关键一步# diffsynth/models/wan_video_dit_dual_stream.py —— FlowStreamModuleclassFlowStreamModule(nn.Module):def__init__(self,dit):super().__init__()self.patch_sizetuple(dit.patch_size)self.flow_patch_embeddingcopy.deepcopy(dit.patch_embedding)# 从 RGB 深拷贝self.flow_headcopy.deepcopy(dit.head)# 输出头也深拷贝self.stream_embednn.Parameter(torch.zeros(1,1,dit.dim))# 可学习流身份再看联合自注意力那一段它是整个双流交互真正发生的地方位置编码对两条流各自独立施加避免把彼此的时空坐标搅在一起随后两流的查询、键、值沿序列维拼接共享同一套注意力权重做一次全局注意力算完之后再按各自的 token 长度切回两条流各走各的输出头。可以看到深度交互和保留先验这两个目标就是靠「独立位置编码 共享注意力权重」这一组合同时兑现的# diffsynth/pipelines/wan_video_dual_stream.py —— dual_stream_block (节选)flow_tokensflow_tokensflow_stream.stream_embed# 注入流身份rgb_qrope_apply(rgb_q,rgb_freqs,sa.num_heads)# RGB 用自己的位置频率flow_qrope_apply(flow_q,flow_freqs,sa.num_heads)# 光流用自己的位置频率qtorch.cat([rgb_q,flow_q],dim1)# 沿序列维拼接ktorch.cat([rgb_k,flow_k],dim1)vtorch.cat([rgb_v,flow_v],dim1)attn_outsa.o(flash_attention(q,k,v,sa.num_heads))# 一套权重做联合注意力这两段代码做了什么FlowStreamModule用copy.deepcopy把预训练 DiT 的 patch 嵌入和输出头各拷一份给光流通路stream_embed是一个加到光流 token 上的可学习流身份信号让模型分得清哪条是光流、哪条是 RGB。为什么这样设计是因为光流帧用同一个 VAE 编码所以用 RGB 层权重初始化光流通路能提供兼容的图像-隐变量先验比随机初始化收敛快得多。工程细节上rgb_freqs和flow_freqs是两组独立的 RoPE 频率位置编码各自施加不互相干扰但torch.cat之后走的是同一套self_attn权重注意力矩阵跨流打通——这就是「深度交互 保留先验」两个目标的平衡点。4.3 工程细节亮点进一步看两个亮点。其一基座是Wan2.2-TI2V-5B一个 50 亿参数的图生视频扩散 Transformer配 UMT5-XXL 文本编码器和 Wan2.2 因果 VAE训练时 VAE 和文本编码器全程冻结只更新 DiT。其二光流通路的 patch embedding 和输出头都是从对应 RGB 层「深拷贝」来的而不是新建随机层——这是一个很省事但很关键的初始化技巧让新加的光流流一上来就站在预训练视频先验的肩膀上而不是从零学起。5. 训练时的监督模块5.1 动作专家与运动感知重加权…详情请参照古月居