# VDN FL2VA/I2VA 关键帧条件化 — ComfyUI 插件侧接口可行性报告

- 日期：2026-09-09
- 范围：纯只读调研 + 本地接口验证（未改插件代码、未提交任何渲染任务）
- 上游插件仓：Saganaki22/ComfyUI-VDN-H3（v1.5.1，commit 7271c648）
- 本地插件：/data/workspace/comfyui-incremental-nodes/ComfyUI-VDN-H3（v1.5.1 + 本地热修，只读）
- 实例：127.0.0.1:8188 / :8190（ComfyUI v0.30.0，system_stats 实测一致；容器锁版本吻合）

---

## 一、可行性判定（结论先行）

**判定：(a) 现版本已通** —— FL2VA/I2VA 关键帧条件化在 ComfyUI 插件侧**当前环境即可工作**，
升级到 v1.5.1 不是必要条件，也无需 encode_keyframes.py 的产物。证据链：

1. **关键帧图像入口不在 VDN 插件，而在 ComfyUI 核心节点 `MiniMaxH3ImageToVideo`**
   （`optional.first_frame` / `optional.last_frame`，IMAGE 类型）。:8188 与 :8190 的
   `/object_info/MiniMaxH3ImageToVideo` 实测均返回该槽位（HTTP 200）。
2. **VDN 插件对 keyframes 是"透传消费"而非"新增输入槽"**：插件在 model patch 内部读取
   核心模型传下的 `minimax_payload["keyframes"]`。该透传代码 **v1.4.0 已存在**
   （v1.4.0 `vdn_h3/hybrid.py:270`、`apply.py:272`，行号与 v1.5.1 完全相同），非 09-08 新增。
3. **本地两实例插件副本已是 v1.5.1**（pyproject.toml:3），hybrid.py:276、apply.py:272-273 的
   keyframes 消费代码均在位。
4. **FL2VA 基座权重已在本地**：两实例 UNETLoader 下拉列表实测含
   `minimax_h3_fl2va_int8_convrot.safetensors`（另有 ref2va 两枚、pruned 变体）。
   VDN stage（stage-dmd-step-250 等）与 keyframe 条件化无权重耦合（只是 LoRA/线性分支 patch），
   **不需要为 FL2VA 额外下载 encode 产物**（判 (c) 不适用，理由见 §三.4）。
5. 判 (d)（仅 CLI 可行）不成立：ComfyUI 核心在图内完成 Qwen3-VL 文本+视觉编码与 VAE 关键帧编码，
   与上游 CLI `encode_keyframes.py` 是两条等价实现。

**重要限制（必须写入实验注意事项）**：
- v1.5.1 README.md:270-272（README_ZH.md:213-214）明示："打包序列几何直接读取 ComfyUI 自带的
  PackedLayout，各条件变体 (t2va/fl2va/ref2va) 保持可用；**VDN 训练只覆盖过 t2va 风格布局**"。
  即 fl2va 下 VDN 线性分支工作在未训练域，输出质量漂移风险由使用方承担 —— 这恰是
  face-collapse 课题需要对照验证的点。
- `vdn_h3/apply.py:272-273`：payload 含 keyframes（或 image refs）时，去噪 timestep 集合额外并入
  `visual_cond_noise_aug`（默认 0.999）。timestep 集合变化会改变 VDN 窗口调度 —— A/B 实验时是显式变量。

---

## 二、上游证据（GitHub 插件仓 v1.5.1 浅克隆 @ /tmp/vdn-upstream/ComfyUI-VDN-H3）

### 2.1 插件侧 keyframes 相关代码（全部为 payload 消费端）

| 位置 | 内容 |
|---|---|
| `vdn_h3/hybrid.py:257-277` `layout_from_payload()` | L262 `layout = payload.get("layout")`；L269-272 若签名不符则重建 `minimax_model.PackedLayout(..., keyframes=payload.get("keyframes"), refs=payload.get("refs"))`；L273 从 layout.segments 取 "video" 段推导 VDN 几何 |
| `vdn_h3/hybrid.py:298` | forward wrapper 内 `state.layout = layout_from_payload(kwargs.get("minimax_payload"), ...)` |
| `vdn_h3/apply.py:270-276` `_unique_t()` | L272 `if payload.get("keyframes") or any(r.get("kind")=="image" ...)` → L273 `s.add(max(t_v, payload.get("visual_cond_noise_aug", 0.999)))` |
| `vdn_h3/apply.py:323` | adaln wrapper 读 `kwargs.get("minimax_payload")` |
| `vdn_h3/nodes.py:263-349` `ApplyVDNH3Advanced.INPUT_TYPES` | 全部输入槽 = model / vdn_checkpoint / turbo / strengths / lora_mode / branch_weights / retain_buffers / verbose / attention_backend / window_radius / window_chunk / anchor_frames / text_state / linear_branch / fast_kernels。**无任何 keyframe/first_frame/last_frame 槽**；`RETURN_TYPES=("MODEL",)`（L351） |
| `nodes.py:190-244` `ApplyVDNH3` | 同上，基础版同样无 keyframe 槽 |

**关键帧 word-level 检索命中统计**：插件仓内 fl2va/i2va/first_frame/last_frame 命中仅出现在
Benchmarks.md（基座文件名 `minimax_h3_fl2va_int8_convrot.safetensors`）、README（条件变体兼容声明）
与 example workflow（核心节点的 first/last_frame 槽）——插件 .py 源码中 keyframes 只以
`payload.get("keyframes")` 形态出现，与"透传消费"结论自洽。

### 2.2 v1.4.0 vs v1.5.1（判定 (b) 的排除证据）

- v1.4.0 `vdn_h3/hybrid.py:270` 与 `apply.py:272` 已含与 v1.5.1 逐行相同的 keyframes 透传
  （raw 文件留档：`evidence/v1.4.0_vdn_h3_*.py`）。
- example_workflows 两版本均仅 `vdn_h3_t2v_8step.json/png`（GitHub contents API 实测）。
  **v1.5.1 没有新增 fl2va 示例工作流**，与任务背景"example_workflows 当时仅 t2v"一致。

### 2.3 ComfyUI 核心 v0.30.0 的 FL2VA 生产链（tag raw 拉取留档于 evidence/）

| 位置 | 内容 |
|---|---|
| `comfy_extras/nodes_minimax_h3.py:101-152` `MiniMaxH3ImageToVideo` | L117-118 schema：`io.Image.Input("first_frame", optional=True)` / `last_frame` 同；L130-139 first_frame 走 `_resize(..., "disabled")`（拉伸上画布，`resolved_frame_index=0`），last_frame 走 `_resize(..., "center")`（cover-crop，`resolved_frame_index=frame_count-1`）；L144-152 keyframes 存在时 `vae.encode` 逐帧出 latent，`conditioning_set_values(cond, {"minimax_keyframes": keyframes, "minimax_frame_count": frame_count})` |
| `comfy/model_base.py:2085-2112`（MiniMaxH3 BaseModel extra_conds） | L2090-2094 `keyframes = kwargs.get("minimax_keyframes")` → `payload["keyframes"]/["frame_count"]/["cond_video_latents"]`；L2108-2111 跨段时预构 `PackedLayout(..., keyframes=payload.get("keyframes"), ...)`；L2112 `out['minimax_payload'] = CONDConstant(payload)` |
| `comfy/ldm/minimax/model.py:297-` `PackedLayout.__init__(..., keyframes=None, refs=None, frame_count=None)` | L314-316 按 keyframes 扩展 packed segments；L521-522 `_forward` 重建 layout 时同样传 `payload.get("keyframes")` |

即：**核心节点生产 cond key → model_base 组装 minimax_payload → 插件 patch 只读 payload**。
插件对 first/last frame 无感知入口，天然继承核心的全部条件变体。

### 2.4 example_workflows 现状

- `example_workflows/vdn_h3_t2v_8step.json`：节点 6 `MiniMaxH3ImageToVideo` 已带
  `first_frame`/`last_frame` 输入槽（JSON 行 738/744，links=None 未接线）；widgets 用
  `minimax_h3_fl2va_int8_convrot.safetensors`（行 889-894）。即官方示例本身就是
  "t2v 布线的 fl2va-capable 图"——补两条 LoadImage→first/last_frame 连线即为 FL2VA 工作流。
- 其余节点：SamplerCustomAdvanced(12) + KSamplerSelect er_sde(9) + BasicScheduler beta/8(10) +
  ApplyVDNH3Advanced(32, widgets=stage-dmd-step-250-int8_convrot_comfyui/merge/grouped) +
  MiniMaxChunkFeedForward(27)。

---

## 三、权重仓 encode_keyframes.py 的调用方式与产物格式

- **路径勘误**：`encode_keyframes.py` **不在 HF 权重仓**（hf-mirror 根目录树实测仅
  h3-base / stage-b-step-2000 / stage-dmd-step-250 / LICENSE / README，直取
  `resolve/main/encode_keyframes.py` 返回 "Entry not found"）。它在 GitHub 代码仓
  `OpenVDN/vdn-minimax-h3` 的 `src/inference/encode_keyframes.py`（git tree API 实测）。
- 调用方式（CLI，diffusers 栈）：
  `python src/inference/encode_keyframes.py --prompt "..." --first first.png --last last.png --out prompts/mine.pt`
  （仅 `--first` = i2va；`--first`+`--last` = fl2va；无关键帧应改用 encode_prompt.py）
- 产物（.pt）字段（源码 L156-163）：
  `prompt`、`prompt_embeds (L,5120) bf16`、`text_token_tags (L,)`（vision 行标 VIDEO=0）、
  `keyframe_anchors ["first","last"]`、`keyframe_files`、
  `condition_latents [(1,24,1,H/16,W/16) fp32, ...]`、`height`、`width`。
- 编码规格：768 短边画布（32 对齐，上限 768×1344 像素）、first 帧拉伸/后随帧 cover-crop、
  presentation `"<Picture i>: " + vision block` 后接 prompt、Qwen3-VL 第 50 层 hidden state、
  关键帧 VAE 编码固定 seed 42 后采样并取 fp16 归一化。
- **与 ComfyUI 路线的关系：无依赖**。该产物仅被上游 `infer.py`（CLI）消费；
  ComfyUI 核心节点在图内等价完成同类编码（clip.tokenize(prompt, images=…) + vae.encode），
  格式/尺寸语义一致（first stretch / last cover-crop 一一对应，见 §2.3）。
  → **ComfyUI 插件侧路线不需要下载或生成该 encode 产物**，判 (c) 不适用。

---

## 四、本地接口核对（:8188 / :8190 实测，留档 evidence/object_info_*.json）

两端口返回 schema **逐字段一致**；comfyui_version 均 0.30.0。

| 节点 | 端口 | HTTP | 关键输入槽 |
|---|---|---|---|
| `MiniMaxH3ImageToVideo` | 8188/8190 | 200/200 | `optional.first_frame=["IMAGE",{}]`、`optional.last_frame=["IMAGE",{}]`；required: clip/vae/prompt/width(默认1344)/height(768)/length(124,17k+5 网格) |
| `MiniMaxH3ReferenceToVideo` | 8188/8190 | 200/200 | `optional.ref_images/ref_videos/ref_video_audios/ref_audios`（COMFY_AUTOGROW_V3）；required 另有 audio_vae、ref_image_size。**无 keyframe 槽**（ref2va 走 refs 通道，`apply.py:272` 同样覆盖 image refs 的 noise-aug timestep） |
| `ApplyVDNH3Advanced` | 8188/8190 | 200/200 | required: model/vdn_checkpoint(下拉含 stage-b-step-2000 等)/apply_turbo_adapter/stage_b_strength/turbo_strength/lora_mode/branch_weights/retain_buffers/verbose/attention_backend；optional: window_radius/window_chunk/anchor_frames/text_state/linear_branch/fast_kernels。**无 keyframe 输入槽（符合设计，透传消费）** |

其他实测：
- 全量 /object_info 中 MiniMax 相关节点名含 `MiniMaxH3WorldI2VAConditioningT8Advanced`、
  `MiniMaxH3MultiKeyframeConditioningT8Advanced` 等 T8 扩展节点（与 VDN 插件无关，属另一扩展包，
  未纳入本判定）。
- 两实例 UNETLoader 文件列表均含 `minimax_h3_fl2va_int8_convrot.safetensors`、
  `minimax_h3_fl2va_pruned_int8_convrot.safetensors`、`minimax_h3_ref2va_*`；CLIPLoader 含
  `qwen3vl_32b_minimax_h3_nvfp4_awq.safetensors`；VAELoader 含
  `minimax_h3_video_vae_fp16.safetensors` + `minimax_h3_audio_vae_fp32.safetensors`。
  → FL2VA 全套权重已就位。
- 本地插件副本 `/data/workspace/comfyui-incremental-nodes/ComfyUI-VDN-H3`：pyproject v1.5.1，
  git HEAD f81f8d9（本地 sage_sm86 热修线），hybrid.py:276 `keyframes=payload.get("keyframes")`、
  apply.py:272-273 noise-aug 分支均在（与上游 diff 仅 README/_compat.py/hybrid.py 少量差异，属并行升级线的本地热修）。
- 环境备注：:8188 进程 cmdline 确认 `--port 8188`；:8190 的 system_stats.argv 显示 `--port 8188`
  （疑为启动模板/进程复制残留），但 8190 端口对外监听且全部端点响应正常，不影响接口结论。
  `/data/workspace/comfyui-incremental-nodes/ComfyUI` 目录仅含 `input/`，运行中的 ComfyUI 树在容器内
  另一路径，故核心代码证据取自 GitHub v0.30.0 tag（object_info 实测行为与该 tag 一致）。

---

## 五、接口清单表（数据流视角）

| # | 层 | 组件 | 接口/槽位 | 本地状态 |
|---|---|---|---|---|
| 1 | ComfyUI 核心 | `MiniMaxH3ImageToVideo` | `optional.first_frame: IMAGE`（i2va/fl2va 起点，stretch 到画布） | ✅ 8188/8190 已注册 |
| 2 | ComfyUI 核心 | `MiniMaxH3ImageToVideo` | `optional.last_frame: IMAGE`（fl2va 终点，cover-crop，index=length-1） | ✅ 已注册 |
| 3 | ComfyUI 核心 | cond 条件键 | `minimax_keyframes`（含 latent）、`minimax_frame_count` | ✅ v0.30.0 行为 |
| 4 | ComfyUI 核心 | `model_base` extra_conds | `minimax_payload{keyframes, frame_count, cond_video_latents, ...}` | ✅ v0.30.0 行为 |
| 5 | ComfyUI 核心 | `PackedLayout(keyframes=…)` | packed segments 含关键帧锚段 | ✅ v0.30.0 行为 |
| 6 | VDN 插件 | `ApplyVDNH3` / `ApplyVDNH3Advanced` | **无 keyframe 槽**；MODEL patch 透传 `minimax_payload` | ✅ 本地 v1.5.1 在位 |
| 7 | VDN 插件 | `vdn_h3/hybrid.py:269-276` | payload.keyframes → PackedLayout → VDN 窗口几何 | ✅ v1.4.0 起即有 |
| 8 | VDN 插件 | `vdn_h3/apply.py:272-273` | keyframes → timestep 集合并入 `visual_cond_noise_aug`(0.999) | ✅ v1.4.0 起即有 |
| 9 | 上游 CLI | `src/inference/encode_keyframes.py`（GitHub 代码仓） | `--prompt/--first/--last/--out` → prompts/*.pt | ComfyUI 路线**不需要** |

## 六、判定复核（对照任务给的四个选项）

| 选项 | 判定 | 依据 |
|---|---|---|
| (a) 现版本已通 | **✅ 成立** | 核心槽位已在两实例注册；插件透传 v1.4.0 起就有；FL2VA 基座/文本编码器/VAE 均已在本地模型列表 |
| (b) v1.5.1 升级后即通 | 部分（非必要） | v1.5.1 并未新增 keyframe 接口（与 v1.4.0 行号相同），升级非 FL2VA 的先决条件；本地已在 v1.5.1+热修 |
| (c) 需额外权重下载（encode 产物） | ❌ 不适用 | encode_keyframes.py 产物仅 CLI 消费；ComfyUI 图内自编码 |
| (d) 上游仅 CLI、ComfyUI 不可行 | ❌ 不成立 | 核心节点+payload 透传构成完整 ComfyUI 路线 |

---

## 七、最小验证实验设计（不执行）

**前置假设**：`models/vdn/` 已有 stage 目录（/object_info 下拉已列 stage-b-step-2000，说明 VDN stage 已放置）。

### 实验 A：schema 冒烟（零渲染，纯校验）
1. 以 `example_workflows/vdn_h3_t2v_8step.json` 为底，API 格式导出后仅改：
   增加 2×LoadImage → `MiniMaxH3ImageToVideo.inputs["first_frame"]/["last_frame"]`；
   UNET 确认为 `minimax_h3_fl2va_int8_convrot.safetensors`。
2. 本地静态校验：用 `/object_info` schema 对该 API JSON 做槽位/类型/链接闭包检查（Python 脚本即可，不 POST）。
   **断言**：first_frame/last_frame 链接类型 IMAGE；无孤立 link；`ApplyVDNH3Advanced` 输入与 §四一致。

### 实验 B：2 步端到端冒烟（最小渲染，仅在允许提交任务的环境执行）
1. 实验 A 的图，BasicScheduler 步数 8→2、length 124（~5s）、1344×768、固定 seed；
   `ApplyVDNH3Advanced.verbose=true`。
2. **通过断言**：
   - 无 `PackedLayout`/`layout_from_payload` 异常（hybrid.py:269-276 路径被真实执行）；
   - verbose 日志中 per-forward timestep 集合出现 ~0.999 档（apply.py:273 生效的直接证据）；
   - 出片首帧≈first_frame、尾帧≈last_frame（核心锚点语义生效）。
3. **对照 B'（同 seed 同步数，断开两帧 = 纯 t2va）**：对比 verbose 中 timestep 集合与窗口调度差异、
   出片内容差异 → 隔离"keyframes 分支 + noise-aug timestep"这个变量，供 face-collapse 归因使用。

### 实验 C：face-collapse 相关对照（VDN on/off）
同一 fl2va 图，B(2步冒烟通过后)再以 8 步跑 `ApplyVDNH3Advanced ON` vs 旁路（model 直连采样器），
同 seed 对比人脸区域稳定性 → 直接回答"VDN 未训练域(fl2va layout)是否放大 face collapse"。

**风险预案**：
- 若 B 报 layout 断言错误（segments 缺 "video" 段等）：优先核对 `hybrid.py:273` 的
  `next(s for s in layout.segments if s[2]=="video")` 在 keyframe 布局下的段序假设——这是插件在
  fl2va 布局下最可能的首个不兼容点。
- 若 2 步冒烟出片全黑：先旁路 VDN 复跑排除基座问题（fp16 VAE 变体可换 int8_convrot），
  再查 text_token_tags 的 VIDEO 行是否被正确传递（`minimax_token_tags` 条件键）。

---

## 八、证据文件清单（evidence/）

- `object_info_8188.json` / `object_info_8190.json` — 全量节点表实测快照
- `oi_8188_{MiniMaxH3ImageToVideo,MiniMaxH3ReferenceToVideo,ApplyVDNH3Advanced}.json` — 单节点 schema
- `core_v0.30.0_nodes_minimax_h3.py` / `core_v0.30.0_model_base.py` / `core_v0.30.0_ldm_minimax_model.py` — 核心链路源码（GitHub v0.30.0 tag）
- `v1.4.0_vdn_h3_{hybrid,apply}.py` — v1.4.0 透传代码留档（证明非 v1.5.1 新增）
- `encode_keyframes.py` — 上游 CLI 编码脚本全文（GitHub OpenVDN/vdn-minimax-h3 src/inference/）
