窗口锚定
Fixing window ghosting on long Wan videos — with a big 'may not help' sticker
- model
- anchor_frames
- anchor_mask_frames
- vae
- model
The problem this is aimed at
Every long Wan video is glued together. The open base is frozen at Wan 2.2, SCAIL-2 is native at 81 frames, and past that everything gets chunked into sliding context windows with overlap and a fusion pass. Fusion is a weighted average, and averaging only works when both windows agree. When they don't - each has its own read on where the character is and how fast they're moving - you get the classic seam: soft motion, doubled limbs, two bodies in the overlap. It's the most-reported failure of chunking, and it clusters past the 81-frame mark.
Why they disagree: animate2 and SCAIL-2 have no skeleton, so nothing tells a window "the person was here". Each window's RoPE restarts at zero, and on SCAIL-2 driving frames are rigidly paired to output frames by position, so a window can only infer its baseline from its own slice of driving video. Two windows, two baselines, one average. This node works the content layer instead: it can't fix RoPE, but it can hand every window the same reference material, and stop two windows from both writing the same frames.
What it actually does
Wire the model output of Custom Context Windows (Manual) or Wan SCAIL-2 Context Windows into model, and its output onward to your KSampler. The node clones the model and wraps the handler's methods. With no context handler present it refuses outright rather than quietly doing nothing useful.
Three interventions, all optional.
Anchor frames (anchor_frames) - a few frames off the start of your driving video that every window shares as common context. Feed five, get five; there's no count parameter. They're VAE-encoded, so vae is mandatory when you connect them - the same Wan VAE as your conditioning node. With driving_only=true (the default) they're appended as extra driving tokens and repositioned by a RoPE patch to sit before the window: no frame shift, no bleed. Set it false and they're prepended into both the driving stream and the generation sequence, per-frame paired (closest to training semantics), at the cost of leakage since those are source-person frames; anchor_image_blank=true keeps their slots as noise placeholders instead. animate2 forces driving_only off regardless, because it hard-checks pose = generated frames − 1.
Read-only context (head_context_frames, latent frames) - each window may look at a few real neighbour frames: attention yes, fusion no. With overlap = 0 (schedule batched, or standard_even to flatten the tail) you get what the author calls one author per frame. The tooltip's own measurement: 2 is fine; 6 pushes per-window tokens to 27 against a training length of 21 and deforms limbs. Stay at 2–3. No tail version exists - it was removed, since those predictions get trimmed before fusion anyway.
Long-range memory (memory_frames, memory_stride, latent frames) - appends pre-window history driving frames as extra tokens, spaced per memory_stride (0, the default, spreads them across the whole history so a long clip still sees its own start). SCAIL-only; elsewhere it warns and switches itself off. Motion, not identity.
Also here: anchor_scope (fresh_only skips the window that already owns the pinned continuation frames), shared_head, and overlap_scope=single, which demotes already-written overlap to read-only. debug_log prints the divergence between adjacent windows in the overlap - a number for how ghosty this is - and flips without a restart, so it's the knob for A/B runs. Note that overlap_scope=single alone engages the node; with nothing connected and every count at zero, the model passes through untouched.
The honest read
The author labels this experimental and states plainly that its effectiveness and side effects aren't systematically verified. The changelog backs that up: an earlier version had the driving_only logic inverted, so SCAIL-2 users who believed they were on the no-frame-shift path were on the align path.
So treat it as an experiment with a protocol. Fix several seeds, and for each one render twice - node on, node off, one variable changed. Start with overlap_scope=single, one switch aimed straight at the averaging. Then anchors from the first 5–9 pixel frames of the driving clip (plus matching-frame anchor_mask_frames on SCAIL-2), then memory_frames from 4 to 8. Read the overlap divergence next to the picture, and don't trust a single run: the node's internal noise is a fixed seed, so results are deterministic per seed and config - "it worked on some seeds" is a real difference rather than luck.
Inputs, output, install
Required: model (from a context window node) plus the widgets above. Optional: anchor_frames (IMAGE), anchor_mask_frames (IMAGE, SCAIL-2 only), and vae. Output is a single model, straight into your KSampler. The errors you'll hit are all skipped steps: no handler on the model (node placed before the context window node), anchors without a VAE, or SCAIL-2 anchors without matching masks.
cd ComfyUI/custom_nodes
git clone https://github.com/user2318/ComfyUI-CustomNodeKit.git
cd ComfyUI-CustomNodeKit
pip install -r requirements.txt
Or search ComfyUI-CustomNodeKit in ComfyUI Manager and restart. The pack's install.py pip-installs groundingdino-py and transformers on startup for its unrelated pose nodes - slow on first launch, not fatal if it fails. Last thing: the display name promises a two-sided "halo", but only the head side does anything.
Inputs (9)
| Name | Type | Default | Description |
|---|---|---|---|
| model | MODEL | 已接入上下文窗口节点的模型(`Custom Context Windows (Manual)` 或 `Wan SCAIL-2 Context Windows` 的 model 输出)。Model that already has a context windows node applied. | |
| enabled | BOOLEAN | true | 总开关;关闭时模型直接透传(等价于不装本节点,并清空锚定池)。Master switch: off = exactly like not installing this node. |
| head_context_frames | INT | 00–8 | 每个窗口头部纳入几帧「只读前文」(单位 = **latent 帧**):参与注意力、不参与融合。 内容 = 共享 latent `x` 上那几帧:在 overlap=0(单作者)下其作者就是相邻窗口。 实测:2 帧正常;6 帧会让总 token 达 27(训练长度 21)并出现肢体变形 => 建议 ≤2~3。0=关闭。 (尾段已移除:它只影响注意力、不改变结果。) How many read-only preceding frames each window may see, in LATENT frames. 0=off. |
| memory_stride | INT | 00–16 | 【长程记忆组的抽样间距】(单位 = latent 帧) 0 = 自动(推荐)= **两段式**:先取【窗口开头 + head】的连续帧(贴窗口 ✓),多出的帧再均匀铺在更早的空白区(必含第 0 帧 ⇒ 覆盖片段开头 ✓);例:窗口起点 15、head=2、memory=4 => [0, 6, 13, 14]。head=0 时全部走远端 ⇒ 退化为旧的等间隔 => 片段/窗口再长也覆盖得到开头(间隔自动变大、分辨率变粗)。 >0 = 固定间距(4 ≈ 每 4 latent / 约 16 像素帧取 1 帧)。 Sampling stride (latent frames) for the memory group. 0 = auto (spread over the whole history). |
| memory_frames | INT | 00–16 | 【长程记忆组】取本窗口【之前】几个历史驱动帧(单位 = **latent 帧**,不是 token 数): 作为额外驱动 token 追加,由 RoPE 放在「窗口之前」并按真实间距编码(不与任何视频帧配对)。 0=关闭;间距见 memory_stride(默认 0=自动覆盖全部历史)。 How many low-frame-rate history LATENT frames to append. 0=off. |
| debug_log | BOOLEAN | false | 【诊断日志】开 = 打印「锚定池空间 / 每窗条件流布局 / 锚段自检 / 跨窗共享头 / 两窗重叠区预测分歧(= 重影的直接量纲)」; 关 = 由哨兵文件 CWA_DEBUG.on 或环境变量 CWA_DEBUG 决定(默认全关,零影响)。 ⇒ 做开/关 A/B 时直接切这个开关即可,**不用重启 ComfyUI**。 Diagnostic logs (pool space / per-window layout / shared head / overlap divergence). |
| anchor_framesopt | IMAGE | 【基准帧】驱动/pose 视频里你想当统一基准的那几帧(例如整段视频开头几帧)。**给多少就用多少**(没有数量参数):会 VAE 编码成 latent,给每个窗口一份共同的「前文/基准」运动。不接=不做锚定(仍可使用只读上下文与长程记忆组)。 Anchor basis frames (driving/pose video): how many you feed is how many are used. Leave unconnected to skip anchoring. | |
| anchor_mask_framesopt | IMAGE | 【仅 SCAIL-2】与 anchor_frames 同帧号的彩色遮罩帧(内部转成 28 通道,不需要 VAE)。建议一起接,否则驱动遮罩与驱动视频的时间基准可能不一致。 SCAIL-2 only: colored mask frames matching anchor_frames (no VAE needed). | |
| vaeopt | VAE | 【接了 anchor_frames 时必接】与 conditioning 节点**同一个** Wan VAE(用于把基准帧编码成 latent;接错 VAE 会得到无意义的条件)。 Same Wan VAE as the conditioning node; required when anchor_frames is connected. |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| model | MODEL | — |