跳转至

Genie 系列技术演进

最后更新: 2026-04-14 | 深度调研

Genie 1 整体架构图(原论文 arXiv:2402.15391 Figure 2)

Genie 1 (2024.02) — 10.7B参数

  • ST-ViViT视频分词器(200M): VQ-VAE+时空因子化Transformer, codebook 1024

  • 潜在动作模型LAM(300M): 无监督动作学习核心创新, 8个离散动作code

  • ST-maskGIT动力学模型(10.1B): 解码器only MaskGIT+ST-Blocks

  • 训练: 680万片段(3万小时), 256 TPUv5p, 942B tokens

  • 局限: 1 FPS, 160x90分辨率

Genie 2 (2024.12) — 3D世界

  • 自回归潜在扩散模型, 单图→可交互3D世界

  • 世界一致性约1分钟, 分辨率约360p

  • 涌现: 角色动画/物理/NPC行为/多视角

  • 未公开技术论文(仅博客)

Genie 3 (2025.08) — 实时720p

  • 首个实时交互通用世界模型: 20-24 FPS, 720p

  • 四组件: 视觉分词器+动力学模型+动作接口+渲染器

  • 隐式物理学习(无硬编码引擎)

  • 记忆: 最长1分钟回溯窗口

  • 训练: 30000+小时游戏视频+网络视频

GameNGen vs DIAMOND vs Oasis vs Genie

  • GameNGen: SD v1.4改造, 860M, DOOM专用, PSNR 29.43

  • DIAMOND: EDM U-Net, 4.4M(Atari)/381M(CSGO), n=3去噪步

  • Oasis: DiT扩散, 500M, Minecraft, Diffusion Forcing

  • Genie 3: 自回归, 最强通用性, 720p实时

DIAMOND Atari-100k 性能对比图(arXiv:2405.12399 Figure 1)

DIAMOND CS:GO 世界模型生成效果(arXiv:2405.12399 Figure 6)

参考


工程实现细节

Genie LAM(潜在动作模型)信息瓶颈实现

核心创新:从无标签视频自动学习离散动作。诀窍是"非对称信息"——编码器能看到下一帧,但解码器只能用历史+动作重建下一帧,这迫使动作向量编码有意义的变化。

import torch
import torch.nn as nn
import torch.nn.functional as F

class LatentActionModel(nn.Module):
    """
    Genie 1 LAM: 300M 参数, 20 层, d=1024, 16 heads, patch=16

    关键设计:
    - Encoder 能看到 (x_1..x_t, x_{t+1})  — 历史 + 下一帧
    - Decoder 只能看到 (x_1..x_t, a_t)    — 历史 + 学到的动作
    - 这个信息不对称是动作学习的根本
    """
    def __init__(self, d_model=1024, n_layers=20, codebook_size=8, d_latent=32):
        super().__init__()

        # Patch=16 (比 ST-ViViT 的 patch=4 更大)
        # 对于 160×90 视频: 160/16 × 90/16 ≈ 10 × 5 = 50 patches/frame
        self.patch_embed = nn.Conv2d(3, d_model, kernel_size=16, stride=16)

        # Encoder: 20 层 Transformer, 同时看历史+下一帧
        self.encoder = nn.ModuleList([
            TransformerBlock(d_model, n_heads=16) for _ in range(n_layers)
        ])
        self.to_action = nn.Linear(d_model, d_latent)  # 投影到潜动作空间

        # VQ codebook: 8 个离散动作
        self.codebook = nn.Embedding(codebook_size, d_latent)
        self.codebook_size = codebook_size

        # Decoder: 只接收历史 + 离散动作
        # ST-ViViT Tokenizer 是独立的 (200M), 这里假设外部处理
        self.decoder = STMaskGITDecoder(d_model=1024)

    def forward(self, frames):
        """
        frames: (B, T+1, C, H, W)  — T 历史帧 + 1 下一帧
        返回: (frame_tokens_pred, action_indices)
        """
        B, T1, C, H, W = frames.shape
        T = T1 - 1

        # ========== 1. Encoder: 看全部 T+1 帧 ==========
        # Patch 化
        patches = self.patch_embed(frames.flatten(0, 1))       # (B*(T+1), D, h/16, w/16)
        patches = patches.flatten(2).transpose(1, 2)           # (B*(T+1), n_patches, D)
        patches = patches.reshape(B, T1 * n_patches, d_model)

        # 时序+空间位置编码
        patches = patches + self.pos_embed

        # 20 层 Transformer
        for blk in self.encoder:
            patches = blk(patches)                             # (B, (T+1)*n_patches, D)

        # 取最后一帧的聚合表示 (e.g., 平均池化)
        last_frame_repr = patches[:, -n_patches:].mean(dim=1)  # (B, D)

        # ========== 2. 投影到 32-dim 潜动作空间 ==========
        latent_action = self.to_action(last_frame_repr)        # (B, 32)

        # ========== 3. VQ 量化到 8 个离散 code ==========
        # 距离计算
        distances = torch.cdist(
            latent_action.unsqueeze(1),                        # (B, 1, 32)
            self.codebook.weight.unsqueeze(0)                  # (1, 8, 32)
        ).squeeze(1)                                           # (B, 8)
        action_idx = distances.argmin(dim=-1)                  # (B,)

        # 直通估计器 (STE)
        a_quantized = self.codebook(action_idx)                # (B, 32)
        a_quantized = latent_action + (a_quantized - latent_action).detach()

        # ========== 4. Decoder: 仅用 (历史帧, a_t) 重建下一帧 ==========
        history_frames = frames[:, :T]                         # (B, T, C, H, W)
        next_frame_logits = self.decoder(history_frames, a_quantized)

        # 损失: 重建下一帧的 token logits (cross-entropy)
        # next_frame 被 ST-ViViT 编码成 token, 作为目标
        target_tokens = st_vivit.encode(frames[:, T])          # (B, n_patches)
        L_recon = F.cross_entropy(
            next_frame_logits.reshape(-1, vocab_size),
            target_tokens.reshape(-1)
        )

        # VQ 损失
        L_codebook = F.mse_loss(a_quantized.detach(), latent_action)
        L_commit = F.mse_loss(a_quantized, latent_action.detach())

        L_total = L_recon + L_codebook + 0.25 * L_commit
        return L_total, action_idx

# ============ 为什么信息瓶颈强制动作学习 ============
# 如果 Decoder 能看到下一帧 → LAM 只需输出恒等映射, 动作无意义
# 但 Decoder 只能看到 (历史, 动作) → 动作必须编码 "从历史到下一帧的变化"
# codebook_size=8 进一步限制 → 只能编码 8 种最重要的变化模式
#   → 跑/跳/左/右/攻击/...等游戏动作自然涌现

ST-maskGIT 动力学模型(10.1B)

Genie 1 LAM 架构图(原论文 Figure 5)

class STMaskGITDynamics(nn.Module):
    """
    Genie 1 动力学模型: Decoder-only MaskGIT + ST-Blocks
    输入: 历史视频 tokens + 动作 token
    输出: 下一帧的 tokens (MaskGIT 并行预测)
    """
    def __init__(self, n_layers=48, d_model=2048, codebook=1024):
        super().__init__()
        self.st_blocks = nn.ModuleList([STBlock(d_model) for _ in range(n_layers)])
        self.output_head = nn.Linear(d_model, codebook)

    def forward(self, tokens_history, action_token, mask_ratio):
        """
        tokens_history: (B, T*n_patches) 过去帧的 tokens
        action_token: (B, 1) 离散动作
        mask_ratio: 随机 mask 下一帧一部分 tokens 让模型预测

        训练: BERT 式掩码预测
        """
        # 构建 input 序列: [history_tokens | action | masked_next_tokens]
        # 以 cosine schedule 随机 mask 部分下一帧 tokens
        next_tokens_masked = apply_mask(target_tokens, mask_ratio)

        seq = torch.cat([
            embed(tokens_history),
            embed(action_token),
            embed(next_tokens_masked)
        ], dim=1)

        # 因果掩码: 下一帧内部可互相看, 但只能看过去不看未来
        for blk in self.st_blocks:
            seq = blk(seq)

        logits = self.output_head(seq[:, -n_patches:])  # 只预测被 mask 部分
        return logits

class STBlock(nn.Module):
    """
    Spatiotemporal Block: 分离空间 + 时间注意力
    """
    def __init__(self, d_model):
        super().__init__()
        self.spatial_attn = nn.MultiheadAttention(d_model, 16, batch_first=True)
        self.temporal_attn = nn.MultiheadAttention(d_model, 16, batch_first=True)
        self.mlp = MLP(d_model)

    def forward(self, x):
        # x: (B, T, N_spatial, D)
        B, T, N, D = x.shape

        # Spatial attention: 帧内 token 之间 (B*T, N, D)
        x_sp = x.reshape(B*T, N, D)
        x_sp = x_sp + self.spatial_attn(x_sp, x_sp, x_sp)[0]

        # Temporal attention: 跨帧同 token (B*N, T, D)
        x_tp = x_sp.reshape(B, T, N, D).transpose(1, 2).reshape(B*N, T, D)
        x_tp = x_tp + self.temporal_attn(x_tp, x_tp, x_tp)[0]

        # MLP
        x = x_tp.reshape(B, N, T, D).transpose(1, 2)
        x = x + self.mlp(x)
        return x

# ============ MaskGIT Cosine Schedule Inference ============
def maskgit_sample(model, history, action, n_steps=8):
    """
    MaskGIT 非自回归并行采样
    """
    B, N = history.shape[0], n_patches  # 下一帧要生成 N 个 tokens
    next_tokens = torch.full((B, N), MASK_TOKEN_ID)  # 全部 masked

    for step in range(n_steps):
        logits = model(history, action, next_tokens)  # (B, N, vocab)
        probs = F.softmax(logits, dim=-1)
        sampled = probs.argmax(dim=-1)                # (B, N)

        # Cosine schedule: 每步保留 (1 - cos(π * (step+1)/n_steps)/2) 的 token
        keep_ratio = 1 - (1 + np.cos(np.pi * (step+1) / n_steps)) / 2
        n_keep = int(N * keep_ratio)

        # 保留最高置信度的 n_keep 个 token, 其余重新 mask
        confidence = probs.gather(-1, sampled.unsqueeze(-1)).squeeze(-1)  # (B, N)
        top_idx = confidence.topk(n_keep, dim=-1).indices
        keep_mask = torch.zeros_like(next_tokens, dtype=bool)
        keep_mask.scatter_(1, top_idx, True)

        next_tokens = torch.where(keep_mask, sampled, torch.full_like(sampled, MASK_TOKEN_ID))

    return next_tokens

GAIA-2 双峰时间采样与多视角一致性

# ============ 双峰 Logit-Normal 时间采样 ============
def sample_time_bimodal(batch_size):
    """
    GAIA-2 训练时的 τ 采样 (Flow Matching 时间)

    双峰设计的 motivation:
    - 主峰 μ=0.5, σ=1.4, p=0.8: 覆盖中段去噪, 网络学习主要降噪能力
    - 次峰 μ=-3.0, σ=1.0, p=0.2: 重点训练近纯噪声情况 (τ 接近 0)
                                 这里网络需要学会从噪声生成大结构

    为什么是 logit-normal 而非 uniform:
    - 均匀分布在极端 τ 处梯度不稳定 (尺度巨大差异)
    - Logit-Normal 在 sigmoid 变换后给特定区域更高采样密度
    """
    use_secondary = torch.rand(batch_size) < 0.2  # 20% 概率用次峰

    # 主峰
    z_main = torch.randn(batch_size) * 1.4 + 0.5
    # 次峰
    z_sec = torch.randn(batch_size) * 1.0 - 3.0

    z = torch.where(use_secondary, z_sec, z_main)
    tau = torch.sigmoid(z)  # 映射到 [0, 1]
    return tau

# ============ 相机几何位置编码 ============
class CameraAwarePositionalEncoding(nn.Module):
    """
    GAIA-2 多视角注意力: 5 摄像头各自位置编码
    包含: 空间正弦编码 + 相机内参 + 相机外参 + 畸变
    """
    def __init__(self, d_model=4096):
        super().__init__()
        self.d_model = d_model
        # 几何参数投影到 d_model
        self.cam_param_proj = nn.Linear(16, d_model)  # 4 intrinsic + 4 quat + 3 trans + 5 dist = 16

    def encode_camera(self, intrinsics, extrinsics_R, extrinsics_T, distortion):
        """
        intrinsics: (B, 4) [fx, fy, cx, cy] 归一化后
        extrinsics_R: (B, 3, 3) 旋转矩阵
        extrinsics_T: (B, 3) 平移
        distortion: (B, 5) 径向+切向畸变系数
        """
        B = intrinsics.shape[0]
        # 旋转 → 四元数 (连续, 4-D)
        quat = rotation_to_quaternion(extrinsics_R)    # (B, 4)

        # 拼接所有几何参数
        geom = torch.cat([
            intrinsics,      # (B, 4)
            quat,            # (B, 4)
            extrinsics_T,    # (B, 3)
            distortion,      # (B, 5)
        ], dim=-1)           # (B, 16)

        cam_embed = self.cam_param_proj(geom)          # (B, d_model)
        return cam_embed

    def forward(self, patch_tokens, camera_params, patch_positions_2d):
        """
        patch_tokens: (B, N_cameras  H  W, D)  5 摄像头的 patch tokens
        camera_params: dict per camera
        patch_positions_2d: (N, 2) 每 patch 的 (row, col)
        """
        B, N, D = patch_tokens.shape

        # 2D 正弦位置编码
        pos_enc = sinusoidal_2d(patch_positions_2d, D)  # (N, D)

        # 每 patch 所属的相机编码
        cam_per_patch = camera_assignment(patch_positions_2d)  # (N,) camera idx
        cam_embed_per_patch = cam_embed[cam_per_patch]         # (N, D)

        return patch_tokens + pos_enc.unsqueeze(0) + cam_embed_per_patch.unsqueeze(0)

# ============ 多视角联合注意力 ============
def multi_view_attention(tokens_all_cameras):
    """
    tokens_all_cameras: (B, 5HW, D) - 5 摄像头拼接后的 tokens
    这里的 Attention 天然跨视角 — 每个 token 可以 attend 任何其他 token
    但位置编码告诉它哪些 token 来自哪个相机 (几何感知)
    """
    # 相机间几何关系通过位置编码隐式编码
    # 不需要特殊的 cross-view attention 模块
    return transformer(tokens_all_cameras)

GAIA-2 自车动态 Symlog + AdaLN 注入

def symlog_transform(x):
    """
    对称对数变换: 处理有符号数值 (速度、曲率)
    symlog(x) = sign(x) * log(|x| + 1)
    关键性质: 对称性保留方向信息, 压缩极端值

    应用例子:
      velocity_m_per_s ∈ [-30, +30] → symlog ∈ [-3.4, +3.4]
      curvature ∈ [-0.1, +0.1] → symlog ≈ [-0.095, +0.095]
    """
    return torch.sign(x) * torch.log(torch.abs(x) + 1)

class EgoDynamicsAdaLN(nn.Module):
    """
    自车动态 (速度, 曲率) 通过 AdaLN 注入 DiT block
    不同于文本条件(交叉注意力),动态量直接调制 LN 参数
    """
    def __init__(self, d_model=4096):
        super().__init__()
        # symlog(speed) + symlog(curvature) + time_embed → MLP → (γ, β)
        self.ego_encoder = nn.Sequential(
            nn.Linear(3, 128),      # [speed, curvature, time_tau]
            nn.SiLU(),
            nn.Linear(128, d_model * 2)  # 输出 γ, β
        )

    def forward(self, x, ego_state, tau):
        """
        x: (B, N, D) — DiT 中间特征
        ego_state: (B, 2) — [speed, curvature]
        tau: (B,) — Flow Matching 时间
        """
        # 1. Symlog 变换让大小值对称
        symlog_speed = symlog_transform(ego_state[:, 0:1])
        symlog_curv = symlog_transform(ego_state[:, 1:2])
        tau_emb = sinusoidal_encoding(tau)  # (B, ?)

        # 2. 拼接并编码
        cond = torch.cat([symlog_speed, symlog_curv, tau_emb], dim=-1)
        gamma_beta = self.ego_encoder(cond)  # (B, 2*D)
        gamma, beta = gamma_beta.chunk(2, dim=-1)  # 各 (B, D)

        # 3. AdaLN: γ * LN(x) + β
        x_norm = F.layer_norm(x, x.shape[-1:])
        return x_norm * (1 + gamma.unsqueeze(1)) + beta.unsqueeze(1)

DIAMOND EDM: n=3 步去噪如何实现

# DIAMOND 基于 EDM 框架, 仅需 n=3 步即可生成高质量下一帧
# 这比 DDPM (~50 步) / Sora (~30步) 快得多

class DIAMONDDenoiser(nn.Module):
    """
    U-Net 2D with Adaptive Group Norm for action conditioning
    CS:GO: 381M 参数; Atari: 仅 4.4M 参数
    """
    def __init__(self, action_dim=17):  # CS:GO: 鼠标 XY + 15 按键
        super().__init__()
        self.unet = UNet2D(base_channels=128)  # Atari 小; CS:GO 大
        # 动作 → scale/shift (per GN group)
        self.action_encoder = nn.Linear(action_dim, 2 * 32)  # 32 groups

    def forward(self, x_noisy, sigma, past_frames, action):
        """
        x_noisy: (B, C, H, W) — 带噪的下一帧
        sigma: (B,) — 噪声水平 (EDM 用 σ 而非 t)
        past_frames: (B, C*n_past, H, W) — 过去 n 帧沿通道拼接
        action: (B, action_dim)
        """
        # EDM 预条件化
        c_in = 1 / torch.sqrt(sigma2 + 0.52)    # 归一化输入
        c_noise = sigma.log() / 4
        c_skip = 0.52 / (sigma2 + 0.52)
        c_out = sigma * 0.5 / torch.sqrt(sigma2 + 0.52)

        # 输入: 预条件化的 x + 过去帧
        x_in = torch.cat([c_in.view(-1,1,1,1) * x_noisy, past_frames], dim=1)

        # 动作通过 Adaptive Group Norm 注入
        action_params = self.action_encoder(action)  # (B, 64)

        # U-Net forward with adaptive GN
        F_theta = self.unet(x_in, c_noise, action_params)

        # EDM skip connection
        D = c_skip.view(-1,1,1,1)  x_noisy + c_out.view(-1,1,1,1)  F_theta
        return D  # 预测的去噪 x

class AdaptiveGroupNorm(nn.Module):
    """
    将动作通过 scale/shift 注入 GroupNorm
    """
    def __init__(self, num_channels, num_groups=32):
        super().__init__()
        self.gn = nn.GroupNorm(num_groups, num_channels, affine=False)
        self.action_to_scale = nn.Linear(64, num_channels)
        self.action_to_shift = nn.Linear(64, num_channels)

    def forward(self, x, action_params):
        x = self.gn(x)                                           # 先归一化
        scale = self.action_to_scale(action_params)              # (B, C)
        shift = self.action_to_shift(action_params)              # (B, C)
        return x * (1 + scale.view(-1, -1, 1, 1)) + shift.view(-1, -1, 1, 1)

# ============ EDM n=3 步 Heun 采样 ============
@torch.no_grad()
def diamond_sample_3step(model, past_frames, action, shape):
    """DIAMOND 的 3 步采样"""
    # 3 个精心选择的 σ 点
    sigmas = torch.tensor([80.0, 1.0, 0.002])  # 高→中→低

    x = torch.randn(shape) * sigmas[0]

    for i in range(2):  # 只 2 个间隔
        s_cur, s_next = sigmas[i], sigmas[i+1]

        # 去噪
        D = model(x, s_cur.expand(x.shape[0]), past_frames, action)

        # 当前方向
        d_cur = (x - D) / s_cur

        # 中间点
        x_next = x + (s_next - s_cur) * d_cur

        # Heun 修正 (二阶精度)
        if s_next > 0:
            D_next = model(x_next, s_next.expand(x.shape[0]), past_frames, action)
            d_next = (x_next - D_next) / s_next
            x = x + (s_next - s_cur)  0.5  (d_cur + d_next)
        else:
            x = x_next

    return x  # 去噪后的下一帧

# ============ 为什么只需 3 步 ============
# 1. EDM 预条件化让不同 σ 下网络输入尺度一致, 避免病态
# 2. 视频相邻帧高度相关, 预测"残差"(动作导致的变化)比从纯噪声生成容易得多
# 3. Heun 二阶求解器比 Euler 一阶误差更小
# 4. σ schedule 设计: 80 → 1 → 0.002 覆盖大→小的尺度跳跃

EDM vs DDPM 关键对比:n=3 步去噪时 DDPM 崩溃(=蓝屏),EDM 仍稳定(arXiv:2405.12399 Figure 3)

左: DDPM — n=3 时 t>10 就崩溃(蓝屏);右: EDM — n=3 时 t=1000 仍稳定

三个系列的工程权衡对比

维度 Genie 1 GAIA-2 DIAMOND
核心架构 VQ-VAE + MaskGIT Latent Diffusion DiT U-Net 2D EDM
参数量 10.7B total 8.4B 4.4M (Atari) / 381M (CSGO)
动作学习 无监督 LAM 结构化条件输入 直接嵌入
采样步数 MaskGIT 8-16 iter Flow Matching 50 EDM 3
训练数据 680 万片段游戏 2500万 2秒驾驶片段 87 小时 CS:GO
训练硬件 256 TPU v5p 未公开 (H100 集群) 单 RTX 4090 × 12 天
启示 无监督动作学习开创 双峰时间采样 + 多视角 预条件化让小模型逼近大模型

系统 参数 帧率 分辨率 特色
GameNGen ~860M 20FPS 320×240 DOOM专用
DIAMOND 4.4-381M ~10FPS 原生 单RTX3090
Oasis 500M 20FPS ~360p Diffusion Forcing
Genie 3 未公开 24FPS 720p 实时通用

关键数学公式

Genie LAM VQ 量化(STE 直通估计器)

最近邻分配:\(a_t = \arg\min_{i\in\{1,\dots,K\}}\,\|\tilde a_t - e_i\|_2^2\)

直通梯度:\(z_q = z_e + \operatorname{sg}(e_{a_t} - z_e)\)

VQ 损失:

\(\mathcal{L}_{\text{VQ}} = \|x - D(z_q)\|_2^2 + \underbrace{\|\operatorname{sg}(z_e) - e\|_2^2}_{\text{codebook}} + \beta\underbrace{\|z_e - \operatorname{sg}(e)\|_2^2}_{\text{commit}}\)

\(\beta=0.25\),codebook size \(K=8\) 迫使 LAM 学出 8 种最重要的动作模式。

MaskGIT 余弦掩码调度

迭代 n 步,第 s 步保留 ratio:

\(\operatorname{mask-ratio}(s) = \cos\!\left(\frac{\pi}{2}\cdot\frac{s}{N}\right)\)

对置信度最高的 top-k 个 token 在该步解除 mask。

GAIA-2 双峰 Logit-Normal 时间采样

训练 Flow Matching 时的 \(\tau\) 采样:

\(\tau = \sigma(z),\quad z\sim\begin{cases}\mathcal{N}(0.5,\,1.4^2) & \text{概率}\ 0.8\\ \mathcal{N}(-3.0,\,1.0^2) & \text{概率}\ 0.2\end{cases}\)

主峰覆盖中段去噪(模型主要降噪能力),次峰覆盖纯噪声附近(从零生成结构)。

Symlog 变换(自车动态)

速度、曲率等有符号量先经 symlog 压缩:

\(\operatorname{symlog}(v) = \operatorname{sign}(v)\cdot\ln(|v|+1)\)

例:\(v \in [-30, +30]\) m/s → \(\operatorname{symlog} \in [-3.43, +3.43]\);曲率类似。

AdaLN 动态量注入

\([\gamma,\beta] = \operatorname{MLP}\!\left(\left[\operatorname{symlog}(v_{\text{ego}}),\ \operatorname{symlog}(\kappa_{\text{ego}}),\ \tau_{\text{emb}}\right]\right)\)

\(\operatorname{AdaLN}(x) = \gamma\cdot\operatorname{LN}(x) + \beta\)

相机几何位置编码

每个 patch token 加上其所属相机的几何嵌入:

\(p_{\text{patch}} = \operatorname{PE}_{\text{spatial}}(r,c) + W\cdot\left[K_{\text{norm}};\ q_R;\ T;\ d_{\text{distort}}\right]\)

其中 K 是归一化内参(4 维)、q_R 是旋转四元数(4 维)、T 是平移(3 维)、distortion(5 维)。

DIAMOND EDM n=3 步 Heun 采样

\(\sigma\) schedule:

\(\sigma \in \{\sigma_0=80,\ \sigma_1=1.0,\ \sigma_2=0.002\}\)

每步应用 Heun 二阶修正:

\(d_i = \frac{x_i - D_\theta(x_i;\sigma_i,a,h)}{\sigma_i}\)

\(\tilde x_{i+1} = x_i + (\sigma_{i+1}-\sigma_i)\,d_i\)

\(x_{i+1} = x_i + (\sigma_{i+1}-\sigma_i)\cdot\tfrac{1}{2}\left(d_i + \tilde d_{i+1}\right)\)

n=3 关键:EDM 预条件化 + Heun 二阶修正 + 相邻帧高度相关(预测残差而非从噪声生成)。

DIAMOND Adaptive Group Norm(动作注入)

动作映射:\([s,b] = \operatorname{MLP}_a(a)\)

AGN 输出:\(\operatorname{AGN}(x,a) = (1+s)\cdot\operatorname{GN}(x) + b\)


Code 引用索引与可信度

✓=官方代码直接移植, △=基于论文描述重构, ⚠=推测填充 ——

注: 所有 "✓" 级代码的完整版本请参考对应 GitHub repo;本文的 PyTorch 伪代码经过简化以突出核心逻辑。


上级 · 02 技术架构与核心方法