Text-to-Image
Diffusers
anima
lora
in-context
character-reference
ip-adapter-alternative
comfyui
anime
Instructions to use darask0/Anima-InContext-Character with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Diffusers
How to use darask0/Anima-InContext-Character with Diffusers:
pip install -U diffusers transformers accelerate
import torch from diffusers import DiffusionPipeline # switch to "mps" for apple devices pipe = DiffusionPipeline.from_pretrained("circlestone-labs/Anima", dtype=torch.bfloat16, device_map="cuda") pipe.load_lora_weights("darask0/Anima-InContext-Character") prompt = "Astronaut in a jungle, cold color palette, muted colors, detailed, 8k" image = pipe(prompt).images[0] - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- Draw Things
- DiffusionBee
Anima In-Context Character LoRA v1 + ComfyUI nodes + workflow
Browse files- .gitattributes +3 -0
- README.md +119 -0
- anima-incontext-character.safetensors +3 -0
- comfyui-anima-incontext/README.md +123 -0
- comfyui-anima-incontext/__init__.py +14 -0
- comfyui-anima-incontext/incontext.py +340 -0
- comfyui-anima-incontext/nodes.py +179 -0
- comfyui-anima-incontext/style_adapter.py +186 -0
- comfyui-anima-incontext/style_nodes.py +194 -0
- samples/example_output.png +3 -0
- samples/example_output2.png +3 -0
- samples/example_reference.jpg +3 -0
- workflow_anima_incontext_character.json +809 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,6 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
samples/example_output.png filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
samples/example_output2.png filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
samples/example_reference.jpg filter=lfs diff=lfs merge=lfs -text
|
README.md
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: other
|
| 3 |
+
license_name: circlestone-labs-non-commercial
|
| 4 |
+
license_link: https://huggingface.co/circlestone-labs/Anima
|
| 5 |
+
base_model: circlestone-labs/Anima
|
| 6 |
+
tags:
|
| 7 |
+
- anima
|
| 8 |
+
- lora
|
| 9 |
+
- in-context
|
| 10 |
+
- character-reference
|
| 11 |
+
- ip-adapter-alternative
|
| 12 |
+
- comfyui
|
| 13 |
+
- anime
|
| 14 |
+
pipeline_tag: text-to-image
|
| 15 |
+
library_name: diffusers
|
| 16 |
+
---
|
| 17 |
+
|
| 18 |
+
# Anima In-Context Character
|
| 19 |
+
|
| 20 |
+
**Reference-driven character generation for [Anima](https://huggingface.co/circlestone-labs/Anima)** — attach a few images of a character and generate that character in new poses, scenes and expressions. No per-character training. Works on characters the base model has never seen.
|
| 21 |
+
|
| 22 |
+
This is a LoRA + a small ComfyUI node pack. Unlike CLIP-embedding IP-Adapters, the reference enters the model as its **own VAE latent inside self-attention**, so fine details (hair ornaments, clothing patterns, eye color) are preserved in principle rather than summarized into a single embedding.
|
| 23 |
+
|
| 24 |
+
| | |
|
| 25 |
+
|---|---|
|
| 26 |
+
| Base model | Anima 2B (Cosmos-Predict2 DiT + Qwen3-0.6B text encoder, WanVAE) |
|
| 27 |
+
| Type | in-context reference LoRA (DiT, rank 64) + ComfyUI nodes |
|
| 28 |
+
| Trained on | ~994k anime images / ~62k (reference≠target) character pairs |
|
| 29 |
+
| Use | attach 1–3 reference images → generate in any pose/scene |
|
| 30 |
+
|
| 31 |
+
---
|
| 32 |
+
|
| 33 |
+
## How it works
|
| 34 |
+
|
| 35 |
+
Anima's DiT is a **video** architecture: latents flow as `(B, C, T, H, W)` and self-attention runs over the flattened `(t h w)` sequence with 3D RoPE (`max_frames=128`, `patch_temporal=1`).
|
| 36 |
+
|
| 37 |
+
This adapter exploits that:
|
| 38 |
+
|
| 39 |
+
1. The reference image's VAE latent is **concatenated as an extra frame on the T axis** — `[generated frame, reference frame(s)]`. It gets a distinct temporal RoPE coordinate, so it never collides spatially with the generated frame.
|
| 40 |
+
2. Per-frame timesteps: the reference frame is conditioned at **timestep 0 (a clean image)** while the generated frame follows the sampler. This is the OminiControl-style "clean condition token" recipe.
|
| 41 |
+
3. The generated frame attends to the reference tokens via **shared self-attention** — the reference's appearance flows in directly, not through a lossy embedding.
|
| 42 |
+
4. Reference frames are sliced off the output before the sampler sees it.
|
| 43 |
+
|
| 44 |
+
The LoRA teaches the base T2I model to *use* those reference tokens. It was trained on **same-character / different-artist** pairs with character names removed from the captions, so identity must flow through the reference frames (not the text) — the network learns a transferable "copy this character into a new context" skill rather than memorizing specific characters.
|
| 45 |
+
|
| 46 |
+
## How it was made
|
| 47 |
+
|
| 48 |
+
- **Data**: ~994k tagged anime images → grouped by character → **same character, different artist** pairs (so the character stream carries identity, not style) → filtered by anime-character identity similarity (deepghs *ccip*) to drop costume/wrong-tag noise → references composited onto white via anime segmentation.
|
| 49 |
+
- **Captions**: general tags only ([OppaiOracle](https://huggingface.co/Grio43/OppaiOracle)), with character/artist/copyright tags stripped — so identity can only flow through the reference.
|
| 50 |
+
- **Training**: DiT LoRA (rank 64, α 32), reference frames appended at timestep 0, loss on the generated frame only, 10% reference-dropout for CFG. Multi-view pairs (2 references from distinct artists) so "attach a few images" is trained, not just inference-time.
|
| 51 |
+
- Full pipeline & node source: [github.com/daraskme/anima-duet](https://github.com/daraskme) *(see project repo)*.
|
| 52 |
+
|
| 53 |
+
---
|
| 54 |
+
|
| 55 |
+
## ComfyUI usage
|
| 56 |
+
|
| 57 |
+
### 1. Install the custom nodes
|
| 58 |
+
Clone the node pack into `ComfyUI/custom_nodes/`:
|
| 59 |
+
```
|
| 60 |
+
comfyui-anima-incontext/ (from this repo's `comfyui-anima-incontext/` folder)
|
| 61 |
+
```
|
| 62 |
+
Restart ComfyUI. You should see nodes under the **anima/incontext** category.
|
| 63 |
+
|
| 64 |
+
### 2. Get the models
|
| 65 |
+
| File | Put in |
|
| 66 |
+
|---|---|
|
| 67 |
+
| `anima-incontext-character.safetensors` (this repo) | `ComfyUI/models/loras/` |
|
| 68 |
+
| `anima-base-v1.0.safetensors` | `ComfyUI/models/diffusion_models/` |
|
| 69 |
+
| `qwen_3_06b_base.safetensors` | `ComfyUI/models/text_encoders/` |
|
| 70 |
+
| `qwen_image_vae.safetensors` | `ComfyUI/models/vae/` |
|
| 71 |
+
|
| 72 |
+
(The three base files come from the [Anima](https://huggingface.co/circlestone-labs/Anima) release.)
|
| 73 |
+
|
| 74 |
+
### 3. Load the workflow
|
| 75 |
+
Drag `workflow_anima_incontext_character.json` onto the ComfyUI canvas. It's wired as:
|
| 76 |
+
|
| 77 |
+
```
|
| 78 |
+
UNETLoader ─► LoraLoaderModelOnly (this LoRA) ─┐
|
| 79 |
+
LoadImage ×2 ─► AnimaRefEncode ×2 ─► AnimaRefLatentBatch ─► AnimaInContextApply ─► KSampler ─► VAEDecode ─► SaveImage
|
| 80 |
+
```
|
| 81 |
+
|
| 82 |
+
### 4. Nodes
|
| 83 |
+
- **Anima Reference Encode** — IMAGE (+ optional MASK) → LATENT. A mask composites the subject on white (recommended). `target_width/height` resizes onto a white canvas so refs match the generation resolution.
|
| 84 |
+
- **Anima Reference Latent Batch** — combine 2+ references (full-body + face works best).
|
| 85 |
+
- **Anima In-Context Reference Apply** — attach references to the model.
|
| 86 |
+
- `strength` 1.0 = neutral, >1 stronger reference pull, 0 = off
|
| 87 |
+
- `cond_only` (default on) — reference masked on the CFG-uncond half; matches training
|
| 88 |
+
- `fit_mode` `pad` (aspect-preserving, default) / `stretch` / `crop`
|
| 89 |
+
- `start_percent`/`end_percent` — sampling window
|
| 90 |
+
|
| 91 |
+
## Tips for best results
|
| 92 |
+
|
| 93 |
+
- **Attach a full-body shot + a face close-up.** Two references (batched) noticeably improve hair-length and face fidelity over a single one.
|
| 94 |
+
- **Also describe the appearance in the prompt** (hair color, outfit, ears, etc.). Reference + matching tags is the strongest combination — the prompt describes the *pose/scene*, the reference carries the *identity*.
|
| 95 |
+
- Composite the subject on a **white background** (use the mask input) — reduces background bleed.
|
| 96 |
+
- If identity drifts, raise `strength` to 1.2–1.5 or add a third reference.
|
| 97 |
+
- Recommended base sampler: `er_sde` / `simple`, 30 steps, CFG 4, `discrete_flow_shift` 3.0.
|
| 98 |
+
|
| 99 |
+
## Limitations
|
| 100 |
+
|
| 101 |
+
- Fine ornament/pattern detail can drift; multi-reference + appearance tags mitigate it.
|
| 102 |
+
- Strong reference pull can slightly wash out backgrounds — trade off with `strength` and the sampling window.
|
| 103 |
+
- Anime domain (the training data is anime illustration).
|
| 104 |
+
|
| 105 |
+
## License
|
| 106 |
+
|
| 107 |
+
Base model **Anima** is under the **CircleStone Labs Non-Commercial License** (derives from Cosmos-Predict2 → NVIDIA Open Model License also applies). **This LoRA is a derivative and is released for non-commercial use.** Generated images may be usable commercially per the base license, but **verify the current Anima LICENSE before any commercial use or redistribution.** Training data is derived from public booru sources.
|
| 108 |
+
|
| 109 |
+
---
|
| 110 |
+
|
| 111 |
+
## 日本語
|
| 112 |
+
|
| 113 |
+
**Anima 向けの参照画像キャラ生成 LoRA。** キャラ画像を数枚添付するだけで、そのキャラを別ポーズ・別シーンで生成できます(キャラ毎の学習不要、未知キャラも可)。
|
| 114 |
+
|
| 115 |
+
CLIP 埋め込み型 IP-Adapter と違い、参照画像を**モデル自身の VAE latent のまま self-attention に入れる**ため、髪飾り・服の柄・瞳の色などの細部が原理的に落ちません。参照を DiT の時間軸に「クリーンフレーム(timestep=0)」として連結する OminiControl 系の方式です。
|
| 116 |
+
|
| 117 |
+
**使い方**: `comfyui-anima-incontext` ノードを導入 → このLoRAを `models/loras/` へ → `workflow_anima_incontext_character.json` を読み込み → 参照は**全身1枚+顔アップ1枚**を推奨、プロンプトにも外見タグを併記すると最も安定します。`strength` は 1.0 中立、効きが弱ければ 1.2〜1.5。
|
| 118 |
+
|
| 119 |
+
**ライセンス**: ベースの Anima は CircleStone Labs 非商用ライセンス。本LoRAは派生物として**非商用**での配布です。商用利用・再配布前に最新 LICENSE を確認してください。
|
anima-incontext-character.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:77eeb2c4263551f4760cc7af46196c6811ff623b7706f97a8f97c31ce5f0fe56
|
| 3 |
+
size 554343064
|
comfyui-anima-incontext/README.md
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# comfyui-anima-incontext
|
| 2 |
+
|
| 3 |
+
Anima (circlestone-labs, Cosmos-Predict2 2B DiT) 向けの
|
| 4 |
+
**参照画像条件付け**カスタムノード集。2 つの独立したストリームを持つ:
|
| 5 |
+
|
| 6 |
+
- **キャラストリーム (in-context)** — 参照画像のVAE latentをDiTの
|
| 7 |
+
時間軸(T軸)に「クリーンフレーム」として連結し、shared
|
| 8 |
+
self-attentionでキャラの見た目を参照させる。CLIP埋め込み型
|
| 9 |
+
IP-Adapterと違い、参照はモデル自身のVAE latentのまま自己注意に
|
| 10 |
+
入るため、髪飾り・服の柄・瞳などの細部が原理的に落ちない
|
| 11 |
+
(OminiControl / IC-LoRA / UNO 系のアプローチ)。
|
| 12 |
+
- **絵柄ストリーム (decoupled cross-attn)** — SigLIP パッチトークンを
|
| 13 |
+
各ブロックのテキスト cross-attn に decoupled attention で注入する
|
| 14 |
+
統計的転写(AnimeAdapter 系)。細部をコピーしないことが目的なので、
|
| 15 |
+
あえて埋め込み注入型。**要学習済みアダプタ**。
|
| 16 |
+
|
| 17 |
+
2 つは別モジュール(self-attn / cross-attn)をパッチするので併用可能。
|
| 18 |
+
`char` と `style` の強度は独立に制御できる。
|
| 19 |
+
|
| 20 |
+
## なぜT軸連結か
|
| 21 |
+
|
| 22 |
+
ComfyUI本体の実装(`comfy/ldm/cosmos/predict2.py`, `comfy/ldm/anima/model.py`)
|
| 23 |
+
を読んだ結果:
|
| 24 |
+
|
| 25 |
+
- AnimaのDiTはvideoアーキテクチャで、latentは `(B, C, T, H, W)`、
|
| 26 |
+
自己注意は `(t h w)` を平坦化した列に **3D RoPE** 付きで計算される。
|
| 27 |
+
Animaのconfigは `max_frames=128`, `patch_temporal=1` なので、
|
| 28 |
+
T=2(生成1 + 参照1)はRoPE的に完全に正当。参照トークンは時間座標が
|
| 29 |
+
ずれるため、生成フレームと空間位置が衝突しない。
|
| 30 |
+
- `timesteps` は `(B, T)` のフレーム毎形式を受け付ける
|
| 31 |
+
(`ndim==1` のときだけ unsqueeze)。よって **参照フレームだけ
|
| 32 |
+
timestep=0(クリーン画像)としてAdaLN変調** できる。
|
| 33 |
+
- `forward` は `WrappersMP.DIFFUSION_MODEL` のWrapperExecutor経由
|
| 34 |
+
なので、ModelPatcherのwrapper登録だけで非侵襲にフックできる。
|
| 35 |
+
- 各Blockの `self_attn.attn_op` は属性(`torch_attention_op`)なので、
|
| 36 |
+
`add_object_patch` で差し替えて参照トークンへの
|
| 37 |
+
attention bias(log(strength))による強度制御ができる。
|
| 38 |
+
|
| 39 |
+
## ノード
|
| 40 |
+
|
| 41 |
+
### Anima Reference Encode (in-context)
|
| 42 |
+
IMAGE (+ 任意で MASK) + VAE → LATENT。
|
| 43 |
+
マスクを与えるとキャラを白背景に合成してからエンコードする
|
| 44 |
+
(背景バイアス除去。アニメ被写体では効果が確認されている定石)。
|
| 45 |
+
`target_width` / `target_height` を生成解像度に合わせると、
|
| 46 |
+
アスペクト保持の白パディングでリサイズしてからエンコードする
|
| 47 |
+
(サンプリング時のlatent空間リサンプルを回避できるので推奨)。
|
| 48 |
+
複数枚バッチ入力可 — 各画像が独立した参照フレームになる。
|
| 49 |
+
|
| 50 |
+
### Anima Reference Latent Batch
|
| 51 |
+
解像度の異なる参照LATENT同士を1つの複数フレーム参照に結合する
|
| 52 |
+
(2つ目を1つ目のサイズにfit)。チェーン可能。
|
| 53 |
+
|
| 54 |
+
### Anima In-Context Reference Apply
|
| 55 |
+
MODEL + 参照LATENT → MODEL。
|
| 56 |
+
|
| 57 |
+
- `strength` — 参照トークンへのattention bias。1.0=中立、0=無効
|
| 58 |
+
- `start_percent` / `end_percent` — 参照を有効にするステップ範囲
|
| 59 |
+
- `cond_only`(既定 ON)— CFGのuncond側では参照キーをマスクする。
|
| 60 |
+
学習契約(uncond=参照なし分布)と一致するので推奨
|
| 61 |
+
- `fit_mode` — 参照latentが生成解像度と違う場合のfit方法
|
| 62 |
+
(`pad`=アスペクト保持+エッジ複製パディング(既定)/ `stretch` / `crop`)
|
| 63 |
+
- `ref_timestep` — 参照フレームのtimestep(既定0=クリーン。
|
| 64 |
+
noise augmentation学習をした場合のみ変更)
|
| 65 |
+
|
| 66 |
+
### Anima Style Adapter Loader / Anima Style Encode (SigLIP) / Anima Style Apply
|
| 67 |
+
絵柄ストリーム。`models/anima_style_adapters/` に学習済みアダプタを置く。
|
| 68 |
+
SigLIP系の CLIP_VISION モデルでエンコードし、`style_weight` で強度制御。
|
| 69 |
+
`cond_only`(既定 ON)。未学習アダプタは gate=0 で完全no-op。
|
| 70 |
+
|
| 71 |
+
## ワークフロー例
|
| 72 |
+
|
| 73 |
+
```
|
| 74 |
+
LoadImage ─┐
|
| 75 |
+
├→ AnimaRefEncode ─┐
|
| 76 |
+
VAELoader ─┘ │
|
| 77 |
+
▼
|
| 78 |
+
UNETLoader → LoraLoader → AnimaInContextApply → (AnimaStyleApply) → KSampler → ...
|
| 79 |
+
(in-context LoRA) ▲
|
| 80 |
+
│ (絵柄も使う場合)
|
| 81 |
+
CLIPVisionLoader → AnimaStyleEncode ┘ + AnimaStyleAdapterLoader
|
| 82 |
+
```
|
| 83 |
+
|
| 84 |
+
## 重要: 学習との契約 (training contract)
|
| 85 |
+
|
| 86 |
+
Anima本体は単フレームT2Iとして学習されているため、**このノード単体
|
| 87 |
+
(zero-shot)での参照効果は弱い/不安定**。専用のin-context LoRAを
|
| 88 |
+
学習して初めて本来の精度が出る。学習コード
|
| 89 |
+
(リポジトリの `training/anima_incontext_train_network.py`)は
|
| 90 |
+
以下の推論時仕様をバイト単位��再現する:
|
| 91 |
+
|
| 92 |
+
1. 参照latentは生成フレームの **後ろ** にT軸連結(生成frames→参照frames)
|
| 93 |
+
2. 参照フレームのtimestepは **0**(生成フレームはサンプラーのt)
|
| 94 |
+
3. 参照latentは `latent_format` 正規化済み(`process_latent_in` 相当、
|
| 95 |
+
Wan21のper-channel mean/std)
|
| 96 |
+
4. 損失は生成フレームのみに適用(参照フレームの出力は破棄)
|
| 97 |
+
5. テキスト条件(LLMAdapter経由)は変更なし
|
| 98 |
+
6. 学習ペア: 同一キャラ・**異なる**出典の2枚(参照≠ターゲット)。
|
| 99 |
+
同一画像ペアはコピー機化するため禁止
|
| 100 |
+
7. CFG dropout: 参照を確率~10%でドロップ(フレーム連結自体を外す)
|
| 101 |
+
→ 推論側 `cond_only=True` のuncondがこの分布に対応
|
| 102 |
+
|
| 103 |
+
## 実機検証結果(2026-07-04, RTX PRO 6000 / anima-base-v1.0)
|
| 104 |
+
|
| 105 |
+
- **strength=0 ⇔ 通常生成: bit単位で完全一致**(strength≤0 は連結自体を
|
| 106 |
+
スキップする実装。連結+全マスクでも数学的には同値だが、bf16 では
|
| 107 |
+
attention kernel差がステップ毎に複利で増幅する — fp32 では1ステップ差
|
| 108 |
+
mean 0.05/255 と確認済み)
|
| 109 |
+
- 全バリアント完走: strength=1.0(cond_only 両方)/ 解像度違い参照
|
| 110 |
+
(512x768→1024x1024, fit=pad) / 複数参照 (RefLatentBatch) / sigma窓
|
| 111 |
+
- **zero-shot(LoRA なし, strength=1.0)の挙動**: 参照キャラを
|
| 112 |
+
ポーズ・衣装込みで**ほぼ完全にコピー**し、背景が崩壊する。
|
| 113 |
+
video プライアが参照を「隣接フレーム」として扱うため。
|
| 114 |
+
経路が機能している証拠だが、実用には in-context LoRA が必須
|
| 115 |
+
(参照≠ターゲットのペア学習がこのコピー機挙動を
|
| 116 |
+
「同一キャラ・別ポーズ/シーン」に矯正する)。
|
| 117 |
+
strength を下げても中間にはならず画質が劣化するだけ
|
| 118 |
+
|
| 119 |
+
## 既知の制約
|
| 120 |
+
|
| 121 |
+
- 参照フレームのK/Vは全ステップ不変だがキャッシュしていない
|
| 122 |
+
(2Bモデルなので実害は小さい。最適化はTODO)
|
| 123 |
+
- `tests/` にモックComfyUIでの契約テストあり
|
comfyui-anima-incontext/__init__.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .nodes import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
|
| 2 |
+
|
| 3 |
+
try:
|
| 4 |
+
from .style_nodes import (
|
| 5 |
+
NODE_CLASS_MAPPINGS as STYLE_CLASS_MAPPINGS,
|
| 6 |
+
NODE_DISPLAY_NAME_MAPPINGS as STYLE_DISPLAY_NAME_MAPPINGS,
|
| 7 |
+
)
|
| 8 |
+
|
| 9 |
+
NODE_CLASS_MAPPINGS = {**NODE_CLASS_MAPPINGS, **STYLE_CLASS_MAPPINGS}
|
| 10 |
+
NODE_DISPLAY_NAME_MAPPINGS = {**NODE_DISPLAY_NAME_MAPPINGS, **STYLE_DISPLAY_NAME_MAPPINGS}
|
| 11 |
+
except ImportError: # style stream needs full ComfyUI (folder_paths)
|
| 12 |
+
pass
|
| 13 |
+
|
| 14 |
+
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]
|
comfyui-anima-incontext/incontext.py
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Anima In-Context Reference — core logic.
|
| 3 |
+
|
| 4 |
+
Strategy
|
| 5 |
+
--------
|
| 6 |
+
Anima's DiT (Cosmos-Predict2 MiniTrainDIT) is a *video* architecture:
|
| 7 |
+
latents flow through the blocks as (B, T, H, W, D) and self-attention is
|
| 8 |
+
computed over the flattened (t h w) sequence with 3D RoPE
|
| 9 |
+
(max_frames=128, patch_temporal=1).
|
| 10 |
+
|
| 11 |
+
We exploit this: the reference image latent is concatenated as an extra
|
| 12 |
+
*frame* along the T axis. This gives us, for free:
|
| 13 |
+
|
| 14 |
+
* a distinct temporal RoPE coordinate for reference tokens
|
| 15 |
+
(no spatial position collision with the generated frame),
|
| 16 |
+
* per-frame timestep conditioning — MiniTrainDIT accepts
|
| 17 |
+
timesteps of shape (B, T), so the reference frame can be
|
| 18 |
+
conditioned at t=0 (clean image) while the generated frame
|
| 19 |
+
follows the sampler's sigma. This matches the
|
| 20 |
+
OminiControl-style "clean condition token" recipe.
|
| 21 |
+
|
| 22 |
+
The generated frame's self-attention can then attend to reference
|
| 23 |
+
tokens (shared attention / in-context conditioning). Reference frames
|
| 24 |
+
are sliced off the output before returning to the sampler.
|
| 25 |
+
|
| 26 |
+
Strength control is implemented by patching each block's
|
| 27 |
+
`self_attn.attn_op` (a plain attribute, cleanly replaceable via
|
| 28 |
+
ModelPatcher.add_object_patch) with a version that adds a per-sample
|
| 29 |
+
additive bias on reference-token key columns:
|
| 30 |
+
|
| 31 |
+
* log(strength) amplifies/attenuates reference attention,
|
| 32 |
+
* cond_only masks reference keys for the uncond half of the CFG
|
| 33 |
+
batch (equivalent to not concatenating the reference at all for
|
| 34 |
+
the uncond forward — this matches the training contract, where
|
| 35 |
+
the reference is dropped ~10% of the time to form the ref-free
|
| 36 |
+
distribution).
|
| 37 |
+
|
| 38 |
+
NOTE: the base model was finetuned as a T2I model (single frame), so
|
| 39 |
+
zero-shot behaviour without a trained in-context LoRA is expected to be
|
| 40 |
+
weak. This module defines the exact inference-time contract that the
|
| 41 |
+
LoRA training code must replicate:
|
| 42 |
+
- reference frames appended after generated frames on the T axis
|
| 43 |
+
- reference frames receive timestep 0
|
| 44 |
+
- reference latents are latent_format-normalized (process_latent_in)
|
| 45 |
+
- text conditioning unchanged
|
| 46 |
+
"""
|
| 47 |
+
|
| 48 |
+
import math
|
| 49 |
+
|
| 50 |
+
import torch
|
| 51 |
+
import torch.nn.functional as F
|
| 52 |
+
|
| 53 |
+
import comfy.patcher_extension
|
| 54 |
+
from comfy.patcher_extension import WrappersMP
|
| 55 |
+
|
| 56 |
+
WRAPPER_KEY = "anima_incontext_ref"
|
| 57 |
+
|
| 58 |
+
NEG_BIAS = -1e9 # finite mask value; softmax subtracts the row max so this is NaN-safe
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
class RefState:
|
| 62 |
+
"""Mutable state shared between the diffusion-model wrapper and the
|
| 63 |
+
patched attention ops. The wrapper fills in per-forward token counts
|
| 64 |
+
and the per-sample reference bias (they depend on resolution and on
|
| 65 |
+
the CFG batch layout), the attention ops read them."""
|
| 66 |
+
|
| 67 |
+
def __init__(self):
|
| 68 |
+
self.active = False
|
| 69 |
+
self.total_tokens = -1
|
| 70 |
+
self.gen_tokens = -1
|
| 71 |
+
# per-sample additive bias on reference key columns, shape (B,).
|
| 72 |
+
# None means "all zero" (neutral -> attn ops fall back).
|
| 73 |
+
self.bias_B = None
|
| 74 |
+
# lazily-built full bias tensor (B, 1, 1, S), cached across the
|
| 75 |
+
# 28 blocks of one forward pass
|
| 76 |
+
self._bias_cache = None
|
| 77 |
+
|
| 78 |
+
def bias_for(self, device, dtype):
|
| 79 |
+
if self._bias_cache is None or self._bias_cache.device != device or self._bias_cache.dtype != dtype:
|
| 80 |
+
b = torch.zeros((self.bias_B.shape[0], 1, 1, self.total_tokens), device=device, dtype=dtype)
|
| 81 |
+
b[:, 0, 0, self.gen_tokens:] = self.bias_B.to(device=device, dtype=dtype).unsqueeze(1)
|
| 82 |
+
self._bias_cache = b
|
| 83 |
+
return self._bias_cache
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
def _tokens_per_frame(h, w, patch_spatial):
|
| 87 |
+
# pad_to_patch_size pads H and W up to a multiple of patch_spatial
|
| 88 |
+
hp = math.ceil(h / patch_spatial)
|
| 89 |
+
wp = math.ceil(w / patch_spatial)
|
| 90 |
+
return hp * wp
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
def _fit_latent(r, H, W, mode):
|
| 94 |
+
"""Fit reference latent frames (N, C, h, w) to the generation
|
| 95 |
+
latent size (H, W).
|
| 96 |
+
|
| 97 |
+
stretch: plain bilinear resize (aspect distortion)
|
| 98 |
+
pad: aspect-preserving resize + edge-replicate center pad.
|
| 99 |
+
Replicate keeps a white-background reference white at the
|
| 100 |
+
borders instead of introducing a mean-gray frame.
|
| 101 |
+
crop: aspect-filling resize + center crop
|
| 102 |
+
"""
|
| 103 |
+
h, w = r.shape[-2:]
|
| 104 |
+
if (h, w) == (H, W):
|
| 105 |
+
return r
|
| 106 |
+
if mode == "stretch":
|
| 107 |
+
return F.interpolate(r, size=(H, W), mode="bilinear", align_corners=False)
|
| 108 |
+
if mode == "pad":
|
| 109 |
+
scale = min(H / h, W / w)
|
| 110 |
+
nh = max(1, min(H, round(h * scale)))
|
| 111 |
+
nw = max(1, min(W, round(w * scale)))
|
| 112 |
+
r = F.interpolate(r, size=(nh, nw), mode="bilinear", align_corners=False)
|
| 113 |
+
pt = (H - nh) // 2
|
| 114 |
+
pl = (W - nw) // 2
|
| 115 |
+
return F.pad(r, (pl, W - nw - pl, pt, H - nh - pt), mode="replicate")
|
| 116 |
+
if mode == "crop":
|
| 117 |
+
scale = max(H / h, W / w)
|
| 118 |
+
nh = max(H, round(h * scale))
|
| 119 |
+
nw = max(W, round(w * scale))
|
| 120 |
+
r = F.interpolate(r, size=(nh, nw), mode="bilinear", align_corners=False)
|
| 121 |
+
ot = (nh - H) // 2
|
| 122 |
+
ol = (nw - W) // 2
|
| 123 |
+
return r[:, :, ot:ot + H, ol:ol + W]
|
| 124 |
+
raise ValueError(f"unknown fit mode: {mode}")
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
def make_ref_attn_op(state, fallback_op):
|
| 128 |
+
"""Replacement for Attention.attn_op on self-attention modules.
|
| 129 |
+
|
| 130 |
+
Adds the per-sample reference bias to attention logits for
|
| 131 |
+
reference-token key columns. Falls back to the original op whenever
|
| 132 |
+
the reference is not active, the bias is neutral, or the sequence
|
| 133 |
+
length does not match the full (gen + ref) self-attention sequence
|
| 134 |
+
(which excludes cross-attention and the LLMAdapter, whose K length
|
| 135 |
+
differs).
|
| 136 |
+
"""
|
| 137 |
+
|
| 138 |
+
def ref_attn_op(q_B_S_H_D, k_B_S_H_D, v_B_S_H_D, transformer_options={}):
|
| 139 |
+
if (
|
| 140 |
+
not state.active
|
| 141 |
+
or state.bias_B is None
|
| 142 |
+
or k_B_S_H_D.shape[1] != state.total_tokens
|
| 143 |
+
or q_B_S_H_D.shape[1] != state.total_tokens
|
| 144 |
+
):
|
| 145 |
+
return fallback_op(q_B_S_H_D, k_B_S_H_D, v_B_S_H_D, transformer_options=transformer_options)
|
| 146 |
+
|
| 147 |
+
# (B, S, H, D) -> (B, H, S, D)
|
| 148 |
+
q = q_B_S_H_D.transpose(1, 2)
|
| 149 |
+
k = k_B_S_H_D.transpose(1, 2)
|
| 150 |
+
v = v_B_S_H_D.transpose(1, 2)
|
| 151 |
+
|
| 152 |
+
bias = state.bias_for(q.device, q.dtype)
|
| 153 |
+
|
| 154 |
+
out = F.scaled_dot_product_attention(q, k, v, attn_mask=bias)
|
| 155 |
+
# (B, H, S, D) -> (B, S, H*D)
|
| 156 |
+
out = out.transpose(1, 2).reshape(q_B_S_H_D.shape[0], q_B_S_H_D.shape[1], -1)
|
| 157 |
+
return out
|
| 158 |
+
|
| 159 |
+
return ref_attn_op
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
def _per_sample_bias(B, strength, cond_only, cond_or_uncond, device):
|
| 163 |
+
"""Build the per-sample reference-key bias, shape (B,).
|
| 164 |
+
|
| 165 |
+
cond samples get log(strength) (0 at strength=1); uncond samples get
|
| 166 |
+
the same unless cond_only, in which case their reference keys are
|
| 167 |
+
masked out entirely. strength <= 0 masks the reference everywhere.
|
| 168 |
+
|
| 169 |
+
Returns None when every entry is zero (neutral -> no attn patch).
|
| 170 |
+
"""
|
| 171 |
+
if strength <= 0.0:
|
| 172 |
+
base = NEG_BIAS
|
| 173 |
+
else:
|
| 174 |
+
base = math.log(strength)
|
| 175 |
+
|
| 176 |
+
bias = torch.full((B,), base, device=device, dtype=torch.float32)
|
| 177 |
+
|
| 178 |
+
if cond_only and cond_or_uncond is not None and len(cond_or_uncond) > 0 and B % len(cond_or_uncond) == 0:
|
| 179 |
+
# calc_cond_batch concatenates equal-sized chunks along B, one
|
| 180 |
+
# per entry of cond_or_uncond (0 = cond, 1 = uncond).
|
| 181 |
+
chunk = B // len(cond_or_uncond)
|
| 182 |
+
for i, kind in enumerate(cond_or_uncond):
|
| 183 |
+
if kind == 1:
|
| 184 |
+
bias[i * chunk:(i + 1) * chunk] = NEG_BIAS
|
| 185 |
+
|
| 186 |
+
if torch.count_nonzero(bias) == 0:
|
| 187 |
+
return None
|
| 188 |
+
return bias
|
| 189 |
+
|
| 190 |
+
|
| 191 |
+
def make_diffusion_wrapper(opts):
|
| 192 |
+
"""DIFFUSION_MODEL wrapper around MiniTrainDIT._forward.
|
| 193 |
+
|
| 194 |
+
opts is a dict with:
|
| 195 |
+
ref_latent: (N, C, 1, H, W) latent tensor, already
|
| 196 |
+
latent_format-normalized (process_latent_in)
|
| 197 |
+
state: RefState instance shared with the attn ops
|
| 198 |
+
strength: attention bias multiplier for reference tokens
|
| 199 |
+
cond_only: mask reference keys for the uncond CFG half
|
| 200 |
+
fit_mode: stretch | pad | crop (reference latent resize)
|
| 201 |
+
sigma_start: apply when current sigma <= sigma_start
|
| 202 |
+
sigma_end: ... and sigma >= sigma_end
|
| 203 |
+
patch_spatial: DiT spatial patch size (2 for Anima)
|
| 204 |
+
ref_timestep: timestep value for reference frames (default 0.0)
|
| 205 |
+
"""
|
| 206 |
+
|
| 207 |
+
def wrapper(executor, x, timesteps, context, fps=None, padding_mask=None, **kwargs):
|
| 208 |
+
state = opts["state"]
|
| 209 |
+
ref = opts["ref_latent"]
|
| 210 |
+
to = kwargs.get("transformer_options", {})
|
| 211 |
+
|
| 212 |
+
# strength <= 0 fully masks the reference for every sample, which
|
| 213 |
+
# is mathematically identical to not attaching it — skip the
|
| 214 |
+
# concat so the output is bit-exact with the reference-free
|
| 215 |
+
# forward (and faster). Verified on-device: with the concat, the
|
| 216 |
+
# masked-attention kernel差 compounds over sampling steps.
|
| 217 |
+
if opts["strength"] <= 0.0:
|
| 218 |
+
return executor(x, timesteps, context, fps, padding_mask, **kwargs)
|
| 219 |
+
|
| 220 |
+
# ---- sigma window gating ----
|
| 221 |
+
sigmas = to.get("sigmas", None)
|
| 222 |
+
if sigmas is not None:
|
| 223 |
+
s = float(sigmas.max())
|
| 224 |
+
if s > opts["sigma_start"] or s < opts["sigma_end"]:
|
| 225 |
+
return executor(x, timesteps, context, fps, padding_mask, **kwargs)
|
| 226 |
+
|
| 227 |
+
squeeze_t = False
|
| 228 |
+
if x.ndim == 4: # (B, C, H, W) -> (B, C, 1, H, W)
|
| 229 |
+
x = x.unsqueeze(2)
|
| 230 |
+
squeeze_t = True
|
| 231 |
+
|
| 232 |
+
B, C, T, H, W = x.shape
|
| 233 |
+
n_ref = ref.shape[0]
|
| 234 |
+
|
| 235 |
+
# ---- prepare reference frames ----
|
| 236 |
+
r = ref.to(device=x.device, dtype=x.dtype) # (N, C, 1, H', W')
|
| 237 |
+
r = r.squeeze(2) # (N, C, H', W')
|
| 238 |
+
r = _fit_latent(r, H, W, opts.get("fit_mode", "pad"))
|
| 239 |
+
# (N, C, H, W) -> (1, C, N, H, W) -> (B, C, N, H, W)
|
| 240 |
+
r = r.permute(1, 0, 2, 3).unsqueeze(0).expand(B, -1, -1, -1, -1)
|
| 241 |
+
|
| 242 |
+
x_cat = torch.cat([x, r], dim=2) # (B, C, T + N, H, W)
|
| 243 |
+
|
| 244 |
+
# ---- per-frame timesteps: generated frames keep the sampler's t,
|
| 245 |
+
# reference frames get ref_timestep (0 = clean) ----
|
| 246 |
+
t = timesteps
|
| 247 |
+
if t.ndim == 1:
|
| 248 |
+
t = t.unsqueeze(1) # (B, 1)
|
| 249 |
+
t = t.expand(B, T)
|
| 250 |
+
t_ref = torch.full((B, n_ref), opts.get("ref_timestep", 0.0), device=t.device, dtype=t.dtype)
|
| 251 |
+
t_cat = torch.cat([t, t_ref], dim=1) # (B, T + N)
|
| 252 |
+
|
| 253 |
+
# ---- arm the attention-op state ----
|
| 254 |
+
tpf = _tokens_per_frame(H, W, opts["patch_spatial"])
|
| 255 |
+
state.gen_tokens = T * tpf
|
| 256 |
+
state.total_tokens = (T + n_ref) * tpf
|
| 257 |
+
state.bias_B = _per_sample_bias(
|
| 258 |
+
B, opts["strength"], opts.get("cond_only", False), to.get("cond_or_uncond", None), x.device
|
| 259 |
+
)
|
| 260 |
+
state._bias_cache = None
|
| 261 |
+
state.active = True
|
| 262 |
+
try:
|
| 263 |
+
out = executor(x_cat, t_cat, context, fps, padding_mask, **kwargs)
|
| 264 |
+
finally:
|
| 265 |
+
state.active = False
|
| 266 |
+
state.bias_B = None
|
| 267 |
+
state._bias_cache = None
|
| 268 |
+
|
| 269 |
+
out = out[:, :, :T] # drop reference frames
|
| 270 |
+
if squeeze_t:
|
| 271 |
+
out = out.squeeze(2)
|
| 272 |
+
return out
|
| 273 |
+
|
| 274 |
+
return wrapper
|
| 275 |
+
|
| 276 |
+
|
| 277 |
+
def apply_incontext_ref(
|
| 278 |
+
model_patcher,
|
| 279 |
+
ref_latent,
|
| 280 |
+
strength,
|
| 281 |
+
start_percent,
|
| 282 |
+
end_percent,
|
| 283 |
+
cond_only=True,
|
| 284 |
+
fit_mode="pad",
|
| 285 |
+
ref_timestep=0.0,
|
| 286 |
+
):
|
| 287 |
+
"""Clone the ModelPatcher and install the in-context reference patches.
|
| 288 |
+
|
| 289 |
+
ref_latent: raw LATENT samples tensor from a VAE encode,
|
| 290 |
+
(N, C, H, W) or (N, C, 1, H, W).
|
| 291 |
+
"""
|
| 292 |
+
m = model_patcher.clone()
|
| 293 |
+
|
| 294 |
+
lat = ref_latent
|
| 295 |
+
if lat.ndim == 4:
|
| 296 |
+
lat = lat.unsqueeze(2) # (N, C, 1, H, W)
|
| 297 |
+
# Normalize into the model's latent space (Wan21 per-channel
|
| 298 |
+
# mean/std). The sampler does this for the generated latent via
|
| 299 |
+
# process_latent_in; we must match it for reference frames.
|
| 300 |
+
lat = m.model.process_latent_in(lat.clone())
|
| 301 |
+
|
| 302 |
+
ms = m.get_model_object("model_sampling")
|
| 303 |
+
sigma_start = ms.percent_to_sigma(start_percent)
|
| 304 |
+
sigma_end = ms.percent_to_sigma(end_percent)
|
| 305 |
+
|
| 306 |
+
dm = m.get_model_object("diffusion_model")
|
| 307 |
+
patch_spatial = getattr(dm, "patch_spatial", 2)
|
| 308 |
+
|
| 309 |
+
state = RefState()
|
| 310 |
+
opts = {
|
| 311 |
+
"ref_latent": lat,
|
| 312 |
+
"state": state,
|
| 313 |
+
"strength": strength,
|
| 314 |
+
"cond_only": cond_only,
|
| 315 |
+
"fit_mode": fit_mode,
|
| 316 |
+
"sigma_start": sigma_start,
|
| 317 |
+
"sigma_end": sigma_end,
|
| 318 |
+
"patch_spatial": patch_spatial,
|
| 319 |
+
"ref_timestep": ref_timestep,
|
| 320 |
+
}
|
| 321 |
+
|
| 322 |
+
wrapper = make_diffusion_wrapper(opts)
|
| 323 |
+
if hasattr(m, "add_wrapper_with_key"):
|
| 324 |
+
m.add_wrapper_with_key(WrappersMP.DIFFUSION_MODEL, WRAPPER_KEY, wrapper)
|
| 325 |
+
else:
|
| 326 |
+
comfy.patcher_extension.add_wrapper_with_key(
|
| 327 |
+
WrappersMP.DIFFUSION_MODEL, WRAPPER_KEY, wrapper, m.model_options, is_model_options=True
|
| 328 |
+
)
|
| 329 |
+
|
| 330 |
+
# Patch every block's self-attention op for strength control.
|
| 331 |
+
# TODO: reference-side K/V is constant across steps and could be
|
| 332 |
+
# cached; skipped for now (2B model, minor win).
|
| 333 |
+
for i, block in enumerate(dm.blocks):
|
| 334 |
+
orig_op = block.self_attn.attn_op
|
| 335 |
+
m.add_object_patch(
|
| 336 |
+
"diffusion_model.blocks.{}.self_attn.attn_op".format(i),
|
| 337 |
+
make_ref_attn_op(state, orig_op),
|
| 338 |
+
)
|
| 339 |
+
|
| 340 |
+
return m
|
comfyui-anima-incontext/nodes.py
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""ComfyUI nodes for Anima in-context character reference."""
|
| 2 |
+
|
| 3 |
+
import torch
|
| 4 |
+
import torch.nn.functional as F
|
| 5 |
+
|
| 6 |
+
from .incontext import apply_incontext_ref, _fit_latent
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def _fit_pixels_white_pad(pixels, target_h, target_w):
|
| 10 |
+
"""Aspect-preserving resize of (B, H, W, C) pixels to the target
|
| 11 |
+
size, centered on a white canvas. White matches the background that
|
| 12 |
+
AnimaRefEncode composites masked subjects onto."""
|
| 13 |
+
b, h, w, c = pixels.shape
|
| 14 |
+
if (h, w) == (target_h, target_w):
|
| 15 |
+
return pixels
|
| 16 |
+
scale = min(target_h / h, target_w / w)
|
| 17 |
+
nh = max(1, min(target_h, round(h * scale)))
|
| 18 |
+
nw = max(1, min(target_w, round(w * scale)))
|
| 19 |
+
resized = F.interpolate(
|
| 20 |
+
pixels.permute(0, 3, 1, 2), size=(nh, nw), mode="bilinear", align_corners=False
|
| 21 |
+
).permute(0, 2, 3, 1)
|
| 22 |
+
canvas = torch.ones((b, target_h, target_w, c), device=pixels.device, dtype=pixels.dtype)
|
| 23 |
+
top = (target_h - nh) // 2
|
| 24 |
+
left = (target_w - nw) // 2
|
| 25 |
+
canvas[:, top:top + nh, left:left + nw] = resized
|
| 26 |
+
return canvas
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
class AnimaRefEncode:
|
| 30 |
+
"""Prepare a reference image for in-context conditioning.
|
| 31 |
+
|
| 32 |
+
Optionally composites the subject onto a white background using a
|
| 33 |
+
mask (recommended: character segmentation mask). Background removal
|
| 34 |
+
measurably improves appearance fidelity for anime subjects.
|
| 35 |
+
|
| 36 |
+
target_width / target_height (0 = keep): aspect-preserving resize
|
| 37 |
+
onto a white canvas before encoding. Set these to the generation
|
| 38 |
+
resolution to avoid any latent-space resampling at sampling time.
|
| 39 |
+
|
| 40 |
+
Multiple reference images can be batched; each becomes its own
|
| 41 |
+
reference frame.
|
| 42 |
+
"""
|
| 43 |
+
|
| 44 |
+
@classmethod
|
| 45 |
+
def INPUT_TYPES(cls):
|
| 46 |
+
return {
|
| 47 |
+
"required": {
|
| 48 |
+
"vae": ("VAE",),
|
| 49 |
+
"image": ("IMAGE",),
|
| 50 |
+
},
|
| 51 |
+
"optional": {
|
| 52 |
+
"mask": ("MASK",),
|
| 53 |
+
"target_width": ("INT", {"default": 0, "min": 0, "max": 8192, "step": 8}),
|
| 54 |
+
"target_height": ("INT", {"default": 0, "min": 0, "max": 8192, "step": 8}),
|
| 55 |
+
},
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
RETURN_TYPES = ("LATENT",)
|
| 59 |
+
FUNCTION = "encode"
|
| 60 |
+
CATEGORY = "anima/incontext"
|
| 61 |
+
|
| 62 |
+
def encode(self, vae, image, mask=None, target_width=0, target_height=0):
|
| 63 |
+
pixels = image
|
| 64 |
+
if mask is not None:
|
| 65 |
+
m = mask
|
| 66 |
+
if m.ndim == 2:
|
| 67 |
+
m = m.unsqueeze(0)
|
| 68 |
+
m = m.unsqueeze(-1) # (B, H, W, 1)
|
| 69 |
+
if m.shape[1:3] != pixels.shape[1:3]:
|
| 70 |
+
m = F.interpolate(
|
| 71 |
+
m.permute(0, 3, 1, 2), size=pixels.shape[1:3], mode="bilinear", align_corners=False
|
| 72 |
+
).permute(0, 2, 3, 1)
|
| 73 |
+
pixels = pixels * m + (1.0 - m) # subject on white
|
| 74 |
+
|
| 75 |
+
if target_width > 0 and target_height > 0:
|
| 76 |
+
pixels = _fit_pixels_white_pad(pixels, target_height, target_width)
|
| 77 |
+
|
| 78 |
+
latent = vae.encode(pixels[:, :, :, :3])
|
| 79 |
+
return ({"samples": latent},)
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
class AnimaRefLatentBatch:
|
| 83 |
+
"""Batch two reference latents into one multi-frame reference.
|
| 84 |
+
|
| 85 |
+
Reference latents of different resolutions are combined by fitting
|
| 86 |
+
the second latent to the first one's size (aspect-preserving pad by
|
| 87 |
+
default). Chain several of these to stack many references."""
|
| 88 |
+
|
| 89 |
+
@classmethod
|
| 90 |
+
def INPUT_TYPES(cls):
|
| 91 |
+
return {
|
| 92 |
+
"required": {
|
| 93 |
+
"ref_latent_1": ("LATENT",),
|
| 94 |
+
"ref_latent_2": ("LATENT",),
|
| 95 |
+
"fit_mode": (["pad", "stretch", "crop"], {"default": "pad"}),
|
| 96 |
+
},
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
RETURN_TYPES = ("LATENT",)
|
| 100 |
+
FUNCTION = "batch"
|
| 101 |
+
CATEGORY = "anima/incontext"
|
| 102 |
+
|
| 103 |
+
def batch(self, ref_latent_1, ref_latent_2, fit_mode):
|
| 104 |
+
s1 = ref_latent_1["samples"]
|
| 105 |
+
s2 = ref_latent_2["samples"]
|
| 106 |
+
if s1.ndim == 5:
|
| 107 |
+
s1 = s1.squeeze(2)
|
| 108 |
+
if s2.ndim == 5:
|
| 109 |
+
s2 = s2.squeeze(2)
|
| 110 |
+
if s2.shape[-2:] != s1.shape[-2:]:
|
| 111 |
+
s2 = _fit_latent(s2, s1.shape[-2], s1.shape[-1], fit_mode)
|
| 112 |
+
return ({"samples": torch.cat([s1, s2], dim=0)},)
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
class AnimaInContextApply:
|
| 116 |
+
"""Attach in-context reference frames to an Anima model.
|
| 117 |
+
|
| 118 |
+
The reference latent is concatenated on the DiT's temporal axis as
|
| 119 |
+
clean (t=0) frames; the generated frame attends to it via shared
|
| 120 |
+
self-attention. Combine with an in-context reference LoRA trained
|
| 121 |
+
with the same contract for full effect.
|
| 122 |
+
|
| 123 |
+
strength: attention bias toward reference tokens.
|
| 124 |
+
1.0 = neutral, >1 stronger, 0 = off.
|
| 125 |
+
start_percent / end_percent: sampling step window where the
|
| 126 |
+
reference is attached.
|
| 127 |
+
cond_only: mask the reference for the uncond half of CFG. Matches
|
| 128 |
+
the training contract (reference dropped for the
|
| 129 |
+
unconditional distribution) — recommended on.
|
| 130 |
+
fit_mode: how a reference latent that does not match the generation
|
| 131 |
+
resolution is fitted (pad = aspect-preserving, default).
|
| 132 |
+
ref_timestep: timestep for reference frames. 0 = clean image
|
| 133 |
+
(the training contract). Small values (~0.05–0.1 of the
|
| 134 |
+
schedule) act as noise augmentation if a future LoRA is
|
| 135 |
+
trained that way.
|
| 136 |
+
"""
|
| 137 |
+
|
| 138 |
+
@classmethod
|
| 139 |
+
def INPUT_TYPES(cls):
|
| 140 |
+
return {
|
| 141 |
+
"required": {
|
| 142 |
+
"model": ("MODEL",),
|
| 143 |
+
"ref_latent": ("LATENT",),
|
| 144 |
+
"strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.05}),
|
| 145 |
+
"start_percent": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}),
|
| 146 |
+
"end_percent": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}),
|
| 147 |
+
},
|
| 148 |
+
"optional": {
|
| 149 |
+
"cond_only": ("BOOLEAN", {"default": True}),
|
| 150 |
+
"fit_mode": (["pad", "stretch", "crop"], {"default": "pad"}),
|
| 151 |
+
"ref_timestep": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1000.0, "step": 0.5}),
|
| 152 |
+
},
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
RETURN_TYPES = ("MODEL",)
|
| 156 |
+
FUNCTION = "apply"
|
| 157 |
+
CATEGORY = "anima/incontext"
|
| 158 |
+
|
| 159 |
+
def apply(self, model, ref_latent, strength, start_percent, end_percent,
|
| 160 |
+
cond_only=True, fit_mode="pad", ref_timestep=0.0):
|
| 161 |
+
samples = ref_latent["samples"]
|
| 162 |
+
m = apply_incontext_ref(
|
| 163 |
+
model, samples, strength, start_percent, end_percent,
|
| 164 |
+
cond_only=cond_only, fit_mode=fit_mode, ref_timestep=ref_timestep,
|
| 165 |
+
)
|
| 166 |
+
return (m,)
|
| 167 |
+
|
| 168 |
+
|
| 169 |
+
NODE_CLASS_MAPPINGS = {
|
| 170 |
+
"AnimaRefEncode": AnimaRefEncode,
|
| 171 |
+
"AnimaRefLatentBatch": AnimaRefLatentBatch,
|
| 172 |
+
"AnimaInContextApply": AnimaInContextApply,
|
| 173 |
+
}
|
| 174 |
+
|
| 175 |
+
NODE_DISPLAY_NAME_MAPPINGS = {
|
| 176 |
+
"AnimaRefEncode": "Anima Reference Encode (in-context)",
|
| 177 |
+
"AnimaRefLatentBatch": "Anima Reference Latent Batch",
|
| 178 |
+
"AnimaInContextApply": "Anima In-Context Reference Apply",
|
| 179 |
+
}
|
comfyui-anima-incontext/style_adapter.py
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Anima style stream — decoupled cross-attention adapter (stage 2).
|
| 2 |
+
|
| 3 |
+
Design (spec §6): the character stream is in-context latent
|
| 4 |
+
concatenation (detail copying is the point); the *style* stream must
|
| 5 |
+
NOT copy details, so it uses statistical transfer via embedding
|
| 6 |
+
injection instead:
|
| 7 |
+
|
| 8 |
+
encoder SigLIP 2 patch tokens from the last K hidden layers,
|
| 9 |
+
aggregated with learnable softmax layer weights and
|
| 10 |
+
projected to style tokens (AnimeAdapter-style).
|
| 11 |
+
injection each DiT block's text cross-attention output gains a
|
| 12 |
+
decoupled attention term:
|
| 13 |
+
out = out_text + style_weight * gamma_i * Attn(Q, K_s, V_s)
|
| 14 |
+
where Q is the block's frozen query, K_s/V_s are new
|
| 15 |
+
trainable projections of the style tokens, and gamma_i is a
|
| 16 |
+
per-block learnable gate initialized to 0 — the adapter is
|
| 17 |
+
an exact no-op at init, so training starts from the base
|
| 18 |
+
model's behaviour.
|
| 19 |
+
|
| 20 |
+
This module is pure torch (no ComfyUI imports) so the training code can
|
| 21 |
+
import the same definition. ComfyUI integration lives in style_nodes.py.
|
| 22 |
+
"""
|
| 23 |
+
|
| 24 |
+
import torch
|
| 25 |
+
import torch.nn.functional as F
|
| 26 |
+
from torch import nn
|
| 27 |
+
|
| 28 |
+
# Anima 2B DiT geometry
|
| 29 |
+
ANIMA_X_DIM = 2048
|
| 30 |
+
ANIMA_N_HEADS = 16
|
| 31 |
+
ANIMA_N_BLOCKS = 28
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
class RMSNorm(nn.Module):
|
| 35 |
+
def __init__(self, dim, eps=1e-6):
|
| 36 |
+
super().__init__()
|
| 37 |
+
self.eps = eps
|
| 38 |
+
self.weight = nn.Parameter(torch.ones(dim))
|
| 39 |
+
|
| 40 |
+
def forward(self, x):
|
| 41 |
+
norm = x.float() * torch.rsqrt(x.float().pow(2).mean(-1, keepdim=True) + self.eps)
|
| 42 |
+
return (norm * self.weight.float()).type_as(x)
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
class StyleTokenAggregator(nn.Module):
|
| 46 |
+
"""Aggregate SigLIP patch tokens from the last K hidden layers into
|
| 47 |
+
style tokens: softmax-weighted layer mix -> LayerNorm -> projection."""
|
| 48 |
+
|
| 49 |
+
def __init__(self, siglip_dim=1152, style_dim=1024, n_layers=6):
|
| 50 |
+
super().__init__()
|
| 51 |
+
self.n_layers = n_layers
|
| 52 |
+
self.layer_weights = nn.Parameter(torch.zeros(n_layers))
|
| 53 |
+
self.norm = nn.LayerNorm(siglip_dim)
|
| 54 |
+
self.proj = nn.Linear(siglip_dim, style_dim)
|
| 55 |
+
|
| 56 |
+
def forward(self, hidden_states_B_K_N_D):
|
| 57 |
+
"""hidden_states: (B, K, N, D) — the last K hidden layers of the
|
| 58 |
+
vision tower (K == n_layers), N patch tokens of dim D."""
|
| 59 |
+
assert hidden_states_B_K_N_D.shape[1] == self.n_layers, (
|
| 60 |
+
f"expected {self.n_layers} layers, got {hidden_states_B_K_N_D.shape[1]}"
|
| 61 |
+
)
|
| 62 |
+
w = torch.softmax(self.layer_weights, dim=0)
|
| 63 |
+
mixed = (hidden_states_B_K_N_D * w[None, :, None, None].to(hidden_states_B_K_N_D)).sum(dim=1)
|
| 64 |
+
return self.proj(self.norm(mixed)) # (B, N, style_dim)
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
class StyleBlockKV(nn.Module):
|
| 68 |
+
"""Per-DiT-block decoupled K/V projections + gate."""
|
| 69 |
+
|
| 70 |
+
def __init__(self, x_dim=ANIMA_X_DIM, style_dim=1024, n_heads=ANIMA_N_HEADS):
|
| 71 |
+
super().__init__()
|
| 72 |
+
self.n_heads = n_heads
|
| 73 |
+
self.head_dim = x_dim // n_heads
|
| 74 |
+
self.k_proj = nn.Linear(style_dim, x_dim, bias=False)
|
| 75 |
+
self.v_proj = nn.Linear(style_dim, x_dim, bias=False)
|
| 76 |
+
# match the base attention's K normalization (RMSNorm per head)
|
| 77 |
+
self.k_norm = RMSNorm(self.head_dim)
|
| 78 |
+
# gate init 0 -> adapter is a no-op until trained
|
| 79 |
+
self.gate = nn.Parameter(torch.zeros(1))
|
| 80 |
+
|
| 81 |
+
def kv(self, style_tokens_B_N_D):
|
| 82 |
+
B, N, _ = style_tokens_B_N_D.shape
|
| 83 |
+
k = self.k_proj(style_tokens_B_N_D).view(B, N, self.n_heads, self.head_dim)
|
| 84 |
+
v = self.v_proj(style_tokens_B_N_D).view(B, N, self.n_heads, self.head_dim)
|
| 85 |
+
k = self.k_norm(k)
|
| 86 |
+
return k, v
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
class AnimaStyleAdapter(nn.Module):
|
| 90 |
+
"""Aggregator + one StyleBlockKV per DiT block."""
|
| 91 |
+
|
| 92 |
+
def __init__(self, siglip_dim=1152, style_dim=1024, n_layers=6,
|
| 93 |
+
x_dim=ANIMA_X_DIM, n_heads=ANIMA_N_HEADS, n_blocks=ANIMA_N_BLOCKS):
|
| 94 |
+
super().__init__()
|
| 95 |
+
self.config = {
|
| 96 |
+
"siglip_dim": siglip_dim, "style_dim": style_dim, "n_layers": n_layers,
|
| 97 |
+
"x_dim": x_dim, "n_heads": n_heads, "n_blocks": n_blocks,
|
| 98 |
+
}
|
| 99 |
+
self.aggregator = StyleTokenAggregator(siglip_dim, style_dim, n_layers)
|
| 100 |
+
self.blocks = nn.ModuleList(
|
| 101 |
+
[StyleBlockKV(x_dim, style_dim, n_heads) for _ in range(n_blocks)]
|
| 102 |
+
)
|
| 103 |
+
|
| 104 |
+
@classmethod
|
| 105 |
+
def from_state_dict(cls, sd):
|
| 106 |
+
"""Instantiate with dimensions inferred from a checkpoint."""
|
| 107 |
+
siglip_dim = sd["aggregator.proj.weight"].shape[1]
|
| 108 |
+
style_dim = sd["aggregator.proj.weight"].shape[0]
|
| 109 |
+
n_layers = sd["aggregator.layer_weights"].shape[0]
|
| 110 |
+
x_dim = sd["blocks.0.k_proj.weight"].shape[0]
|
| 111 |
+
n_blocks = 0
|
| 112 |
+
while f"blocks.{n_blocks}.k_proj.weight" in sd:
|
| 113 |
+
n_blocks += 1
|
| 114 |
+
head_dim = sd["blocks.0.k_norm.weight"].shape[0]
|
| 115 |
+
adapter = cls(siglip_dim, style_dim, n_layers, x_dim, x_dim // head_dim, n_blocks)
|
| 116 |
+
adapter.load_state_dict(sd)
|
| 117 |
+
return adapter
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
class StyleState:
|
| 121 |
+
"""Runtime state shared by all patched cross-attention wrappers.
|
| 122 |
+
Armed by the style diffusion wrapper before each forward."""
|
| 123 |
+
|
| 124 |
+
def __init__(self):
|
| 125 |
+
self.active = False
|
| 126 |
+
# style K/V per block, computed once per forward: list of (k, v)
|
| 127 |
+
self.kv_per_block = None
|
| 128 |
+
# per-sample multiplier (0 masks a sample, e.g. the uncond chunk)
|
| 129 |
+
self.sample_scale_B = None
|
| 130 |
+
self.weight = 1.0
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
def style_attention(q_B_S_H_D, k_B_N_H_D, v_B_N_H_D):
|
| 134 |
+
"""Decoupled attention with the block's frozen query. Returns
|
| 135 |
+
(B, S, H*D) to match the text branch pre-output_proj layout."""
|
| 136 |
+
q = q_B_S_H_D.transpose(1, 2)
|
| 137 |
+
k = k_B_N_H_D.transpose(1, 2)
|
| 138 |
+
v = v_B_N_H_D.transpose(1, 2)
|
| 139 |
+
out = F.scaled_dot_product_attention(q, k, v)
|
| 140 |
+
return out.transpose(1, 2).reshape(q_B_S_H_D.shape[0], q_B_S_H_D.shape[1], -1)
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
class StyleCrossAttention(nn.Module):
|
| 144 |
+
"""Replacement for a DiT block's cross_attn module.
|
| 145 |
+
|
| 146 |
+
Reimplements the original attention using the original (frozen)
|
| 147 |
+
projections, then adds the decoupled style term *before*
|
| 148 |
+
output_proj — the IP-Adapter formulation:
|
| 149 |
+
|
| 150 |
+
out = output_proj( Attn(Q, K_text, V_text)
|
| 151 |
+
+ weight * gamma * Attn(Q, K_style, V_style) )
|
| 152 |
+
|
| 153 |
+
When the state is inactive it computes exactly the original
|
| 154 |
+
attention (the reimplementation is numerically identical: same
|
| 155 |
+
modules, same attn_op).
|
| 156 |
+
"""
|
| 157 |
+
|
| 158 |
+
def __init__(self, orig_attn, block_kv, state, block_index):
|
| 159 |
+
super().__init__()
|
| 160 |
+
self.orig = orig_attn
|
| 161 |
+
self.block_kv = block_kv
|
| 162 |
+
self.state = state
|
| 163 |
+
self.block_index = block_index
|
| 164 |
+
|
| 165 |
+
def forward(self, x, context=None, rope_emb=None, transformer_options={}):
|
| 166 |
+
state = self.state
|
| 167 |
+
if not state.active or state.kv_per_block is None:
|
| 168 |
+
return self.orig(x, context, rope_emb=rope_emb, transformer_options=transformer_options)
|
| 169 |
+
|
| 170 |
+
q, k, v = self.orig.compute_qkv(x, context, rope_emb=rope_emb)
|
| 171 |
+
text_out = self.orig.attn_op(q, k, v, transformer_options=transformer_options)
|
| 172 |
+
|
| 173 |
+
ks, vs = state.kv_per_block[self.block_index]
|
| 174 |
+
B = q.shape[0]
|
| 175 |
+
if ks.shape[0] != B: # single style batch broadcast over CFG batch
|
| 176 |
+
ks = ks.expand(B, -1, -1, -1)
|
| 177 |
+
vs = vs.expand(B, -1, -1, -1)
|
| 178 |
+
style_out = style_attention(q, ks.to(q.dtype), vs.to(q.dtype))
|
| 179 |
+
|
| 180 |
+
gate = self.block_kv.gate.to(q.dtype)
|
| 181 |
+
scale = state.weight * gate
|
| 182 |
+
if state.sample_scale_B is not None:
|
| 183 |
+
scale = scale * state.sample_scale_B.to(q.dtype).view(B, 1, 1)
|
| 184 |
+
text_out = text_out + scale * style_out
|
| 185 |
+
|
| 186 |
+
return self.orig.output_dropout(self.orig.output_proj(text_out))
|
comfyui-anima-incontext/style_nodes.py
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""ComfyUI nodes for the Anima style stream (decoupled cross-attention).
|
| 2 |
+
|
| 3 |
+
Requires a trained AnimaStyleAdapter checkpoint (see style_adapter.py
|
| 4 |
+
and the project docs); with an untrained adapter the gates are zero and
|
| 5 |
+
the nodes are an exact no-op.
|
| 6 |
+
|
| 7 |
+
The style stream composes freely with the in-context character stream:
|
| 8 |
+
AnimaInContextApply patches self-attention (T-axis reference frames),
|
| 9 |
+
AnimaStyleApply patches cross-attention — chain both Apply nodes and
|
| 10 |
+
control char/style strength independently.
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
import os
|
| 14 |
+
|
| 15 |
+
import torch
|
| 16 |
+
|
| 17 |
+
import comfy.utils
|
| 18 |
+
import folder_paths
|
| 19 |
+
from comfy.patcher_extension import WrappersMP
|
| 20 |
+
|
| 21 |
+
from .style_adapter import AnimaStyleAdapter, StyleState
|
| 22 |
+
|
| 23 |
+
STYLE_WRAPPER_KEY = "anima_style_ref"
|
| 24 |
+
|
| 25 |
+
_ADAPTER_DIR = "anima_style_adapters"
|
| 26 |
+
if _ADAPTER_DIR not in folder_paths.folder_names_and_paths:
|
| 27 |
+
folder_paths.add_model_folder_path(
|
| 28 |
+
_ADAPTER_DIR, os.path.join(folder_paths.models_dir, _ADAPTER_DIR)
|
| 29 |
+
)
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
class AnimaStyleAdapterLoader:
|
| 33 |
+
@classmethod
|
| 34 |
+
def INPUT_TYPES(cls):
|
| 35 |
+
return {
|
| 36 |
+
"required": {
|
| 37 |
+
"adapter_name": (folder_paths.get_filename_list(_ADAPTER_DIR),),
|
| 38 |
+
},
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
RETURN_TYPES = ("ANIMA_STYLE_ADAPTER",)
|
| 42 |
+
FUNCTION = "load"
|
| 43 |
+
CATEGORY = "anima/style"
|
| 44 |
+
|
| 45 |
+
def load(self, adapter_name):
|
| 46 |
+
path = folder_paths.get_full_path_or_raise(_ADAPTER_DIR, adapter_name)
|
| 47 |
+
sd = comfy.utils.load_torch_file(path, safe_load=True)
|
| 48 |
+
adapter = AnimaStyleAdapter.from_state_dict(sd)
|
| 49 |
+
adapter.eval()
|
| 50 |
+
return (adapter,)
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
class AnimaStyleEncode:
|
| 54 |
+
"""Encode style reference image(s) with a SigLIP CLIP_VISION model,
|
| 55 |
+
keeping the hidden-state stack the adapter aggregates over.
|
| 56 |
+
|
| 57 |
+
Multiple images are encoded independently and their patch tokens are
|
| 58 |
+
concatenated at apply time (attention pools over all of them)."""
|
| 59 |
+
|
| 60 |
+
@classmethod
|
| 61 |
+
def INPUT_TYPES(cls):
|
| 62 |
+
return {
|
| 63 |
+
"required": {
|
| 64 |
+
"clip_vision": ("CLIP_VISION",),
|
| 65 |
+
"image": ("IMAGE",),
|
| 66 |
+
},
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
RETURN_TYPES = ("ANIMA_STYLE_EMBEDS",)
|
| 70 |
+
FUNCTION = "encode"
|
| 71 |
+
CATEGORY = "anima/style"
|
| 72 |
+
|
| 73 |
+
def encode(self, clip_vision, image):
|
| 74 |
+
out = clip_vision.encode_image(image)
|
| 75 |
+
hs = out["all_hidden_states"] # (B, L, N, D)
|
| 76 |
+
if hs is None:
|
| 77 |
+
raise RuntimeError(
|
| 78 |
+
"this CLIP_VISION model does not expose all hidden states; "
|
| 79 |
+
"use a SigLIP vision model"
|
| 80 |
+
)
|
| 81 |
+
return ({"hidden_states": hs},)
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
class AnimaStyleApply:
|
| 85 |
+
"""Attach the style stream to an Anima model.
|
| 86 |
+
|
| 87 |
+
style_weight: global multiplier on the (learned, per-block) gates.
|
| 88 |
+
cond_only: apply style only to the cond half of the CFG batch.
|
| 89 |
+
start/end_percent: sampling window, same semantics as the
|
| 90 |
+
in-context Apply node.
|
| 91 |
+
"""
|
| 92 |
+
|
| 93 |
+
@classmethod
|
| 94 |
+
def INPUT_TYPES(cls):
|
| 95 |
+
return {
|
| 96 |
+
"required": {
|
| 97 |
+
"model": ("MODEL",),
|
| 98 |
+
"style_adapter": ("ANIMA_STYLE_ADAPTER",),
|
| 99 |
+
"style_embeds": ("ANIMA_STYLE_EMBEDS",),
|
| 100 |
+
"style_weight": ("FLOAT", {"default": 1.0, "min": -2.0, "max": 5.0, "step": 0.05}),
|
| 101 |
+
"start_percent": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}),
|
| 102 |
+
"end_percent": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}),
|
| 103 |
+
},
|
| 104 |
+
"optional": {
|
| 105 |
+
"cond_only": ("BOOLEAN", {"default": True}),
|
| 106 |
+
},
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
RETURN_TYPES = ("MODEL",)
|
| 110 |
+
FUNCTION = "apply"
|
| 111 |
+
CATEGORY = "anima/style"
|
| 112 |
+
|
| 113 |
+
def apply(self, model, style_adapter, style_embeds, style_weight,
|
| 114 |
+
start_percent, end_percent, cond_only=True):
|
| 115 |
+
m = model.clone()
|
| 116 |
+
|
| 117 |
+
ms = m.get_model_object("model_sampling")
|
| 118 |
+
sigma_start = ms.percent_to_sigma(start_percent)
|
| 119 |
+
sigma_end = ms.percent_to_sigma(end_percent)
|
| 120 |
+
|
| 121 |
+
dm = m.get_model_object("diffusion_model")
|
| 122 |
+
n_layers = style_adapter.config["n_layers"]
|
| 123 |
+
hidden_states = style_embeds["hidden_states"][:, -n_layers:] # (B, K, N, D)
|
| 124 |
+
|
| 125 |
+
state = StyleState()
|
| 126 |
+
# per-(device, dtype) cache of the per-block K/V — the style
|
| 127 |
+
# tokens are constant across sampling steps
|
| 128 |
+
kv_cache = {}
|
| 129 |
+
|
| 130 |
+
def compute_kv(device, dtype):
|
| 131 |
+
key = (device, dtype)
|
| 132 |
+
if key not in kv_cache:
|
| 133 |
+
adapter = style_adapter.to(device)
|
| 134 |
+
hs = hidden_states.to(device=device, dtype=next(adapter.parameters()).dtype)
|
| 135 |
+
with torch.no_grad():
|
| 136 |
+
tokens = adapter.aggregator(hs) # (B, N, style_dim)
|
| 137 |
+
# multiple style images -> one long token sequence
|
| 138 |
+
tokens = tokens.reshape(1, -1, tokens.shape[-1])
|
| 139 |
+
kv_cache[key] = [
|
| 140 |
+
tuple(t.to(dtype) for t in blk.kv(tokens)) for blk in adapter.blocks
|
| 141 |
+
]
|
| 142 |
+
return kv_cache[key]
|
| 143 |
+
|
| 144 |
+
def wrapper(executor, x, timesteps, context, fps=None, padding_mask=None, **kwargs):
|
| 145 |
+
to = kwargs.get("transformer_options", {})
|
| 146 |
+
sigmas = to.get("sigmas", None)
|
| 147 |
+
if sigmas is not None:
|
| 148 |
+
s = float(sigmas.max())
|
| 149 |
+
if s > sigma_start or s < sigma_end:
|
| 150 |
+
return executor(x, timesteps, context, fps, padding_mask, **kwargs)
|
| 151 |
+
|
| 152 |
+
state.kv_per_block = compute_kv(x.device, x.dtype)
|
| 153 |
+
state.weight = style_weight
|
| 154 |
+
|
| 155 |
+
state.sample_scale_B = None
|
| 156 |
+
cou = to.get("cond_or_uncond", None)
|
| 157 |
+
if cond_only and cou and x.shape[0] % len(cou) == 0:
|
| 158 |
+
chunk = x.shape[0] // len(cou)
|
| 159 |
+
scale = torch.ones(x.shape[0], device=x.device)
|
| 160 |
+
for i, kind in enumerate(cou):
|
| 161 |
+
if kind == 1:
|
| 162 |
+
scale[i * chunk:(i + 1) * chunk] = 0.0
|
| 163 |
+
state.sample_scale_B = scale
|
| 164 |
+
|
| 165 |
+
state.active = True
|
| 166 |
+
try:
|
| 167 |
+
return executor(x, timesteps, context, fps, padding_mask, **kwargs)
|
| 168 |
+
finally:
|
| 169 |
+
state.active = False
|
| 170 |
+
|
| 171 |
+
m.add_wrapper_with_key(WrappersMP.DIFFUSION_MODEL, STYLE_WRAPPER_KEY, wrapper)
|
| 172 |
+
|
| 173 |
+
from .style_adapter import StyleCrossAttention
|
| 174 |
+
|
| 175 |
+
for i, block in enumerate(dm.blocks):
|
| 176 |
+
m.add_object_patch(
|
| 177 |
+
"diffusion_model.blocks.{}.cross_attn".format(i),
|
| 178 |
+
StyleCrossAttention(block.cross_attn, style_adapter.blocks[i], state, i),
|
| 179 |
+
)
|
| 180 |
+
|
| 181 |
+
return (m,)
|
| 182 |
+
|
| 183 |
+
|
| 184 |
+
NODE_CLASS_MAPPINGS = {
|
| 185 |
+
"AnimaStyleAdapterLoader": AnimaStyleAdapterLoader,
|
| 186 |
+
"AnimaStyleEncode": AnimaStyleEncode,
|
| 187 |
+
"AnimaStyleApply": AnimaStyleApply,
|
| 188 |
+
}
|
| 189 |
+
|
| 190 |
+
NODE_DISPLAY_NAME_MAPPINGS = {
|
| 191 |
+
"AnimaStyleAdapterLoader": "Anima Style Adapter Loader",
|
| 192 |
+
"AnimaStyleEncode": "Anima Style Encode (SigLIP)",
|
| 193 |
+
"AnimaStyleApply": "Anima Style Apply",
|
| 194 |
+
}
|
samples/example_output.png
ADDED
|
Git LFS Details
|
samples/example_output2.png
ADDED
|
Git LFS Details
|
samples/example_reference.jpg
ADDED
|
Git LFS Details
|
workflow_anima_incontext_character.json
ADDED
|
@@ -0,0 +1,809 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"last_node_id": 25,
|
| 3 |
+
"last_link_id": 18,
|
| 4 |
+
"nodes": [
|
| 5 |
+
{
|
| 6 |
+
"id": 1,
|
| 7 |
+
"type": "UNETLoader",
|
| 8 |
+
"pos": [
|
| 9 |
+
40,
|
| 10 |
+
40
|
| 11 |
+
],
|
| 12 |
+
"size": [
|
| 13 |
+
320,
|
| 14 |
+
100
|
| 15 |
+
],
|
| 16 |
+
"flags": {},
|
| 17 |
+
"order": 1,
|
| 18 |
+
"mode": 0,
|
| 19 |
+
"inputs": [],
|
| 20 |
+
"outputs": [
|
| 21 |
+
{
|
| 22 |
+
"name": "MODEL",
|
| 23 |
+
"type": "MODEL",
|
| 24 |
+
"links": [
|
| 25 |
+
1
|
| 26 |
+
],
|
| 27 |
+
"slot_index": 0
|
| 28 |
+
}
|
| 29 |
+
],
|
| 30 |
+
"properties": {
|
| 31 |
+
"Node name for S&R": "UNETLoader"
|
| 32 |
+
},
|
| 33 |
+
"widgets_values": [
|
| 34 |
+
"anima-base-v1.0.safetensors",
|
| 35 |
+
"default"
|
| 36 |
+
]
|
| 37 |
+
},
|
| 38 |
+
{
|
| 39 |
+
"id": 2,
|
| 40 |
+
"type": "CLIPLoader",
|
| 41 |
+
"pos": [
|
| 42 |
+
40,
|
| 43 |
+
190
|
| 44 |
+
],
|
| 45 |
+
"size": [
|
| 46 |
+
320,
|
| 47 |
+
110
|
| 48 |
+
],
|
| 49 |
+
"flags": {},
|
| 50 |
+
"order": 2,
|
| 51 |
+
"mode": 0,
|
| 52 |
+
"inputs": [],
|
| 53 |
+
"outputs": [
|
| 54 |
+
{
|
| 55 |
+
"name": "CLIP",
|
| 56 |
+
"type": "CLIP",
|
| 57 |
+
"links": [
|
| 58 |
+
11,
|
| 59 |
+
12
|
| 60 |
+
],
|
| 61 |
+
"slot_index": 0
|
| 62 |
+
}
|
| 63 |
+
],
|
| 64 |
+
"properties": {
|
| 65 |
+
"Node name for S&R": "CLIPLoader"
|
| 66 |
+
},
|
| 67 |
+
"widgets_values": [
|
| 68 |
+
"qwen_3_06b_base.safetensors",
|
| 69 |
+
"stable_diffusion",
|
| 70 |
+
"default"
|
| 71 |
+
]
|
| 72 |
+
},
|
| 73 |
+
{
|
| 74 |
+
"id": 3,
|
| 75 |
+
"type": "VAELoader",
|
| 76 |
+
"pos": [
|
| 77 |
+
40,
|
| 78 |
+
340
|
| 79 |
+
],
|
| 80 |
+
"size": [
|
| 81 |
+
320,
|
| 82 |
+
80
|
| 83 |
+
],
|
| 84 |
+
"flags": {},
|
| 85 |
+
"order": 3,
|
| 86 |
+
"mode": 0,
|
| 87 |
+
"inputs": [],
|
| 88 |
+
"outputs": [
|
| 89 |
+
{
|
| 90 |
+
"name": "VAE",
|
| 91 |
+
"type": "VAE",
|
| 92 |
+
"links": [
|
| 93 |
+
3,
|
| 94 |
+
4,
|
| 95 |
+
5
|
| 96 |
+
],
|
| 97 |
+
"slot_index": 0
|
| 98 |
+
}
|
| 99 |
+
],
|
| 100 |
+
"properties": {
|
| 101 |
+
"Node name for S&R": "VAELoader"
|
| 102 |
+
},
|
| 103 |
+
"widgets_values": [
|
| 104 |
+
"qwen_image_vae.safetensors"
|
| 105 |
+
]
|
| 106 |
+
},
|
| 107 |
+
{
|
| 108 |
+
"id": 4,
|
| 109 |
+
"type": "LoraLoaderModelOnly",
|
| 110 |
+
"pos": [
|
| 111 |
+
400,
|
| 112 |
+
40
|
| 113 |
+
],
|
| 114 |
+
"size": [
|
| 115 |
+
330,
|
| 116 |
+
110
|
| 117 |
+
],
|
| 118 |
+
"flags": {},
|
| 119 |
+
"order": 4,
|
| 120 |
+
"mode": 0,
|
| 121 |
+
"inputs": [
|
| 122 |
+
{
|
| 123 |
+
"name": "model",
|
| 124 |
+
"type": "MODEL",
|
| 125 |
+
"link": 1
|
| 126 |
+
}
|
| 127 |
+
],
|
| 128 |
+
"outputs": [
|
| 129 |
+
{
|
| 130 |
+
"name": "MODEL",
|
| 131 |
+
"type": "MODEL",
|
| 132 |
+
"links": [
|
| 133 |
+
2
|
| 134 |
+
],
|
| 135 |
+
"slot_index": 0
|
| 136 |
+
}
|
| 137 |
+
],
|
| 138 |
+
"properties": {
|
| 139 |
+
"Node name for S&R": "LoraLoaderModelOnly"
|
| 140 |
+
},
|
| 141 |
+
"widgets_values": [
|
| 142 |
+
"anima-incontext-character.safetensors",
|
| 143 |
+
1.0
|
| 144 |
+
],
|
| 145 |
+
"title": "Character LoRA"
|
| 146 |
+
},
|
| 147 |
+
{
|
| 148 |
+
"id": 10,
|
| 149 |
+
"type": "LoadImage",
|
| 150 |
+
"pos": [
|
| 151 |
+
40,
|
| 152 |
+
470
|
| 153 |
+
],
|
| 154 |
+
"size": [
|
| 155 |
+
320,
|
| 156 |
+
300
|
| 157 |
+
],
|
| 158 |
+
"flags": {},
|
| 159 |
+
"order": 10,
|
| 160 |
+
"mode": 0,
|
| 161 |
+
"inputs": [],
|
| 162 |
+
"outputs": [
|
| 163 |
+
{
|
| 164 |
+
"name": "IMAGE",
|
| 165 |
+
"type": "IMAGE",
|
| 166 |
+
"links": [
|
| 167 |
+
6
|
| 168 |
+
],
|
| 169 |
+
"slot_index": 0
|
| 170 |
+
},
|
| 171 |
+
{
|
| 172 |
+
"name": "MASK",
|
| 173 |
+
"type": "MASK",
|
| 174 |
+
"links": [],
|
| 175 |
+
"slot_index": 0
|
| 176 |
+
}
|
| 177 |
+
],
|
| 178 |
+
"properties": {
|
| 179 |
+
"Node name for S&R": "LoadImage"
|
| 180 |
+
},
|
| 181 |
+
"widgets_values": [
|
| 182 |
+
"ref_fullbody.png",
|
| 183 |
+
"image"
|
| 184 |
+
],
|
| 185 |
+
"title": "Reference 1 (full body)"
|
| 186 |
+
},
|
| 187 |
+
{
|
| 188 |
+
"id": 11,
|
| 189 |
+
"type": "LoadImage",
|
| 190 |
+
"pos": [
|
| 191 |
+
40,
|
| 192 |
+
800
|
| 193 |
+
],
|
| 194 |
+
"size": [
|
| 195 |
+
320,
|
| 196 |
+
300
|
| 197 |
+
],
|
| 198 |
+
"flags": {},
|
| 199 |
+
"order": 11,
|
| 200 |
+
"mode": 0,
|
| 201 |
+
"inputs": [],
|
| 202 |
+
"outputs": [
|
| 203 |
+
{
|
| 204 |
+
"name": "IMAGE",
|
| 205 |
+
"type": "IMAGE",
|
| 206 |
+
"links": [
|
| 207 |
+
7
|
| 208 |
+
],
|
| 209 |
+
"slot_index": 0
|
| 210 |
+
},
|
| 211 |
+
{
|
| 212 |
+
"name": "MASK",
|
| 213 |
+
"type": "MASK",
|
| 214 |
+
"links": [],
|
| 215 |
+
"slot_index": 0
|
| 216 |
+
}
|
| 217 |
+
],
|
| 218 |
+
"properties": {
|
| 219 |
+
"Node name for S&R": "LoadImage"
|
| 220 |
+
},
|
| 221 |
+
"widgets_values": [
|
| 222 |
+
"ref_face.png",
|
| 223 |
+
"image"
|
| 224 |
+
],
|
| 225 |
+
"title": "Reference 2 (face, optional)"
|
| 226 |
+
},
|
| 227 |
+
{
|
| 228 |
+
"id": 12,
|
| 229 |
+
"type": "AnimaRefEncode",
|
| 230 |
+
"pos": [
|
| 231 |
+
400,
|
| 232 |
+
470
|
| 233 |
+
],
|
| 234 |
+
"size": [
|
| 235 |
+
300,
|
| 236 |
+
130
|
| 237 |
+
],
|
| 238 |
+
"flags": {},
|
| 239 |
+
"order": 12,
|
| 240 |
+
"mode": 0,
|
| 241 |
+
"inputs": [
|
| 242 |
+
{
|
| 243 |
+
"name": "vae",
|
| 244 |
+
"type": "VAE",
|
| 245 |
+
"link": 3
|
| 246 |
+
},
|
| 247 |
+
{
|
| 248 |
+
"name": "image",
|
| 249 |
+
"type": "IMAGE",
|
| 250 |
+
"link": 6
|
| 251 |
+
},
|
| 252 |
+
{
|
| 253 |
+
"name": "mask",
|
| 254 |
+
"type": "MASK",
|
| 255 |
+
"link": null
|
| 256 |
+
}
|
| 257 |
+
],
|
| 258 |
+
"outputs": [
|
| 259 |
+
{
|
| 260 |
+
"name": "LATENT",
|
| 261 |
+
"type": "LATENT",
|
| 262 |
+
"links": [
|
| 263 |
+
8
|
| 264 |
+
],
|
| 265 |
+
"slot_index": 0
|
| 266 |
+
}
|
| 267 |
+
],
|
| 268 |
+
"properties": {
|
| 269 |
+
"Node name for S&R": "AnimaRefEncode"
|
| 270 |
+
},
|
| 271 |
+
"widgets_values": [
|
| 272 |
+
1024,
|
| 273 |
+
1024
|
| 274 |
+
]
|
| 275 |
+
},
|
| 276 |
+
{
|
| 277 |
+
"id": 13,
|
| 278 |
+
"type": "AnimaRefEncode",
|
| 279 |
+
"pos": [
|
| 280 |
+
400,
|
| 281 |
+
800
|
| 282 |
+
],
|
| 283 |
+
"size": [
|
| 284 |
+
300,
|
| 285 |
+
130
|
| 286 |
+
],
|
| 287 |
+
"flags": {},
|
| 288 |
+
"order": 13,
|
| 289 |
+
"mode": 0,
|
| 290 |
+
"inputs": [
|
| 291 |
+
{
|
| 292 |
+
"name": "vae",
|
| 293 |
+
"type": "VAE",
|
| 294 |
+
"link": 4
|
| 295 |
+
},
|
| 296 |
+
{
|
| 297 |
+
"name": "image",
|
| 298 |
+
"type": "IMAGE",
|
| 299 |
+
"link": 7
|
| 300 |
+
},
|
| 301 |
+
{
|
| 302 |
+
"name": "mask",
|
| 303 |
+
"type": "MASK",
|
| 304 |
+
"link": null
|
| 305 |
+
}
|
| 306 |
+
],
|
| 307 |
+
"outputs": [
|
| 308 |
+
{
|
| 309 |
+
"name": "LATENT",
|
| 310 |
+
"type": "LATENT",
|
| 311 |
+
"links": [
|
| 312 |
+
9
|
| 313 |
+
],
|
| 314 |
+
"slot_index": 0
|
| 315 |
+
}
|
| 316 |
+
],
|
| 317 |
+
"properties": {
|
| 318 |
+
"Node name for S&R": "AnimaRefEncode"
|
| 319 |
+
},
|
| 320 |
+
"widgets_values": [
|
| 321 |
+
1024,
|
| 322 |
+
1024
|
| 323 |
+
]
|
| 324 |
+
},
|
| 325 |
+
{
|
| 326 |
+
"id": 14,
|
| 327 |
+
"type": "AnimaRefLatentBatch",
|
| 328 |
+
"pos": [
|
| 329 |
+
740,
|
| 330 |
+
470
|
| 331 |
+
],
|
| 332 |
+
"size": [
|
| 333 |
+
300,
|
| 334 |
+
110
|
| 335 |
+
],
|
| 336 |
+
"flags": {},
|
| 337 |
+
"order": 14,
|
| 338 |
+
"mode": 0,
|
| 339 |
+
"inputs": [
|
| 340 |
+
{
|
| 341 |
+
"name": "ref_latent_1",
|
| 342 |
+
"type": "LATENT",
|
| 343 |
+
"link": 8
|
| 344 |
+
},
|
| 345 |
+
{
|
| 346 |
+
"name": "ref_latent_2",
|
| 347 |
+
"type": "LATENT",
|
| 348 |
+
"link": 9
|
| 349 |
+
}
|
| 350 |
+
],
|
| 351 |
+
"outputs": [
|
| 352 |
+
{
|
| 353 |
+
"name": "LATENT",
|
| 354 |
+
"type": "LATENT",
|
| 355 |
+
"links": [
|
| 356 |
+
10
|
| 357 |
+
],
|
| 358 |
+
"slot_index": 0
|
| 359 |
+
}
|
| 360 |
+
],
|
| 361 |
+
"properties": {
|
| 362 |
+
"Node name for S&R": "AnimaRefLatentBatch"
|
| 363 |
+
},
|
| 364 |
+
"widgets_values": [
|
| 365 |
+
"pad"
|
| 366 |
+
]
|
| 367 |
+
},
|
| 368 |
+
{
|
| 369 |
+
"id": 15,
|
| 370 |
+
"type": "AnimaInContextApply",
|
| 371 |
+
"pos": [
|
| 372 |
+
760,
|
| 373 |
+
40
|
| 374 |
+
],
|
| 375 |
+
"size": [
|
| 376 |
+
330,
|
| 377 |
+
220
|
| 378 |
+
],
|
| 379 |
+
"flags": {},
|
| 380 |
+
"order": 15,
|
| 381 |
+
"mode": 0,
|
| 382 |
+
"inputs": [
|
| 383 |
+
{
|
| 384 |
+
"name": "model",
|
| 385 |
+
"type": "MODEL",
|
| 386 |
+
"link": 2
|
| 387 |
+
},
|
| 388 |
+
{
|
| 389 |
+
"name": "ref_latent",
|
| 390 |
+
"type": "LATENT",
|
| 391 |
+
"link": 10
|
| 392 |
+
}
|
| 393 |
+
],
|
| 394 |
+
"outputs": [
|
| 395 |
+
{
|
| 396 |
+
"name": "MODEL",
|
| 397 |
+
"type": "MODEL",
|
| 398 |
+
"links": [
|
| 399 |
+
13
|
| 400 |
+
],
|
| 401 |
+
"slot_index": 0
|
| 402 |
+
}
|
| 403 |
+
],
|
| 404 |
+
"properties": {
|
| 405 |
+
"Node name for S&R": "AnimaInContextApply"
|
| 406 |
+
},
|
| 407 |
+
"widgets_values": [
|
| 408 |
+
1.0,
|
| 409 |
+
0.0,
|
| 410 |
+
1.0,
|
| 411 |
+
true,
|
| 412 |
+
"pad",
|
| 413 |
+
0.0
|
| 414 |
+
],
|
| 415 |
+
"title": "Anima In-Context Apply"
|
| 416 |
+
},
|
| 417 |
+
{
|
| 418 |
+
"id": 20,
|
| 419 |
+
"type": "CLIPTextEncode",
|
| 420 |
+
"pos": [
|
| 421 |
+
400,
|
| 422 |
+
640
|
| 423 |
+
],
|
| 424 |
+
"size": [
|
| 425 |
+
330,
|
| 426 |
+
140
|
| 427 |
+
],
|
| 428 |
+
"flags": {},
|
| 429 |
+
"order": 20,
|
| 430 |
+
"mode": 0,
|
| 431 |
+
"inputs": [
|
| 432 |
+
{
|
| 433 |
+
"name": "clip",
|
| 434 |
+
"type": "CLIP",
|
| 435 |
+
"link": 11
|
| 436 |
+
}
|
| 437 |
+
],
|
| 438 |
+
"outputs": [
|
| 439 |
+
{
|
| 440 |
+
"name": "CONDITIONING",
|
| 441 |
+
"type": "CONDITIONING",
|
| 442 |
+
"links": [
|
| 443 |
+
14
|
| 444 |
+
],
|
| 445 |
+
"slot_index": 0
|
| 446 |
+
}
|
| 447 |
+
],
|
| 448 |
+
"properties": {
|
| 449 |
+
"Node name for S&R": "CLIPTextEncode"
|
| 450 |
+
},
|
| 451 |
+
"widgets_values": [
|
| 452 |
+
"1girl, silver hair, twin tails, blue eyes, black sailor uniform with red ribbon, standing in a park, cherry blossoms, detailed illustration"
|
| 453 |
+
],
|
| 454 |
+
"title": "Positive (describe pose/scene + appearance)"
|
| 455 |
+
},
|
| 456 |
+
{
|
| 457 |
+
"id": 21,
|
| 458 |
+
"type": "CLIPTextEncode",
|
| 459 |
+
"pos": [
|
| 460 |
+
400,
|
| 461 |
+
300
|
| 462 |
+
],
|
| 463 |
+
"size": [
|
| 464 |
+
330,
|
| 465 |
+
100
|
| 466 |
+
],
|
| 467 |
+
"flags": {},
|
| 468 |
+
"order": 21,
|
| 469 |
+
"mode": 0,
|
| 470 |
+
"inputs": [
|
| 471 |
+
{
|
| 472 |
+
"name": "clip",
|
| 473 |
+
"type": "CLIP",
|
| 474 |
+
"link": 12
|
| 475 |
+
}
|
| 476 |
+
],
|
| 477 |
+
"outputs": [
|
| 478 |
+
{
|
| 479 |
+
"name": "CONDITIONING",
|
| 480 |
+
"type": "CONDITIONING",
|
| 481 |
+
"links": [
|
| 482 |
+
15
|
| 483 |
+
],
|
| 484 |
+
"slot_index": 0
|
| 485 |
+
}
|
| 486 |
+
],
|
| 487 |
+
"properties": {
|
| 488 |
+
"Node name for S&R": "CLIPTextEncode"
|
| 489 |
+
},
|
| 490 |
+
"widgets_values": [
|
| 491 |
+
"worst quality, low quality, blurry, jpeg artifacts"
|
| 492 |
+
],
|
| 493 |
+
"title": "Negative"
|
| 494 |
+
},
|
| 495 |
+
{
|
| 496 |
+
"id": 22,
|
| 497 |
+
"type": "EmptyLatentImage",
|
| 498 |
+
"pos": [
|
| 499 |
+
760,
|
| 500 |
+
300
|
| 501 |
+
],
|
| 502 |
+
"size": [
|
| 503 |
+
300,
|
| 504 |
+
110
|
| 505 |
+
],
|
| 506 |
+
"flags": {},
|
| 507 |
+
"order": 22,
|
| 508 |
+
"mode": 0,
|
| 509 |
+
"inputs": [],
|
| 510 |
+
"outputs": [
|
| 511 |
+
{
|
| 512 |
+
"name": "LATENT",
|
| 513 |
+
"type": "LATENT",
|
| 514 |
+
"links": [
|
| 515 |
+
16
|
| 516 |
+
],
|
| 517 |
+
"slot_index": 0
|
| 518 |
+
}
|
| 519 |
+
],
|
| 520 |
+
"properties": {
|
| 521 |
+
"Node name for S&R": "EmptyLatentImage"
|
| 522 |
+
},
|
| 523 |
+
"widgets_values": [
|
| 524 |
+
1024,
|
| 525 |
+
1024,
|
| 526 |
+
1
|
| 527 |
+
]
|
| 528 |
+
},
|
| 529 |
+
{
|
| 530 |
+
"id": 23,
|
| 531 |
+
"type": "KSampler",
|
| 532 |
+
"pos": [
|
| 533 |
+
1140,
|
| 534 |
+
40
|
| 535 |
+
],
|
| 536 |
+
"size": [
|
| 537 |
+
300,
|
| 538 |
+
270
|
| 539 |
+
],
|
| 540 |
+
"flags": {},
|
| 541 |
+
"order": 23,
|
| 542 |
+
"mode": 0,
|
| 543 |
+
"inputs": [
|
| 544 |
+
{
|
| 545 |
+
"name": "model",
|
| 546 |
+
"type": "MODEL",
|
| 547 |
+
"link": 13
|
| 548 |
+
},
|
| 549 |
+
{
|
| 550 |
+
"name": "positive",
|
| 551 |
+
"type": "CONDITIONING",
|
| 552 |
+
"link": 14
|
| 553 |
+
},
|
| 554 |
+
{
|
| 555 |
+
"name": "negative",
|
| 556 |
+
"type": "CONDITIONING",
|
| 557 |
+
"link": 15
|
| 558 |
+
},
|
| 559 |
+
{
|
| 560 |
+
"name": "latent_image",
|
| 561 |
+
"type": "LATENT",
|
| 562 |
+
"link": 16
|
| 563 |
+
}
|
| 564 |
+
],
|
| 565 |
+
"outputs": [
|
| 566 |
+
{
|
| 567 |
+
"name": "LATENT",
|
| 568 |
+
"type": "LATENT",
|
| 569 |
+
"links": [
|
| 570 |
+
17
|
| 571 |
+
],
|
| 572 |
+
"slot_index": 0
|
| 573 |
+
}
|
| 574 |
+
],
|
| 575 |
+
"properties": {
|
| 576 |
+
"Node name for S&R": "KSampler"
|
| 577 |
+
},
|
| 578 |
+
"widgets_values": [
|
| 579 |
+
42,
|
| 580 |
+
"fixed",
|
| 581 |
+
30,
|
| 582 |
+
4.0,
|
| 583 |
+
"er_sde",
|
| 584 |
+
"simple",
|
| 585 |
+
1.0
|
| 586 |
+
]
|
| 587 |
+
},
|
| 588 |
+
{
|
| 589 |
+
"id": 24,
|
| 590 |
+
"type": "VAEDecode",
|
| 591 |
+
"pos": [
|
| 592 |
+
1480,
|
| 593 |
+
40
|
| 594 |
+
],
|
| 595 |
+
"size": [
|
| 596 |
+
260,
|
| 597 |
+
80
|
| 598 |
+
],
|
| 599 |
+
"flags": {},
|
| 600 |
+
"order": 24,
|
| 601 |
+
"mode": 0,
|
| 602 |
+
"inputs": [
|
| 603 |
+
{
|
| 604 |
+
"name": "samples",
|
| 605 |
+
"type": "LATENT",
|
| 606 |
+
"link": 17
|
| 607 |
+
},
|
| 608 |
+
{
|
| 609 |
+
"name": "vae",
|
| 610 |
+
"type": "VAE",
|
| 611 |
+
"link": 5
|
| 612 |
+
}
|
| 613 |
+
],
|
| 614 |
+
"outputs": [
|
| 615 |
+
{
|
| 616 |
+
"name": "IMAGE",
|
| 617 |
+
"type": "IMAGE",
|
| 618 |
+
"links": [
|
| 619 |
+
18
|
| 620 |
+
],
|
| 621 |
+
"slot_index": 0
|
| 622 |
+
}
|
| 623 |
+
],
|
| 624 |
+
"properties": {
|
| 625 |
+
"Node name for S&R": "VAEDecode"
|
| 626 |
+
},
|
| 627 |
+
"widgets_values": []
|
| 628 |
+
},
|
| 629 |
+
{
|
| 630 |
+
"id": 25,
|
| 631 |
+
"type": "SaveImage",
|
| 632 |
+
"pos": [
|
| 633 |
+
1760,
|
| 634 |
+
40
|
| 635 |
+
],
|
| 636 |
+
"size": [
|
| 637 |
+
320,
|
| 638 |
+
300
|
| 639 |
+
],
|
| 640 |
+
"flags": {},
|
| 641 |
+
"order": 25,
|
| 642 |
+
"mode": 0,
|
| 643 |
+
"inputs": [
|
| 644 |
+
{
|
| 645 |
+
"name": "images",
|
| 646 |
+
"type": "IMAGE",
|
| 647 |
+
"link": 18
|
| 648 |
+
}
|
| 649 |
+
],
|
| 650 |
+
"outputs": [],
|
| 651 |
+
"properties": {
|
| 652 |
+
"Node name for S&R": "SaveImage"
|
| 653 |
+
},
|
| 654 |
+
"widgets_values": [
|
| 655 |
+
"Anima_InContext"
|
| 656 |
+
]
|
| 657 |
+
}
|
| 658 |
+
],
|
| 659 |
+
"links": [
|
| 660 |
+
[
|
| 661 |
+
1,
|
| 662 |
+
1,
|
| 663 |
+
0,
|
| 664 |
+
4,
|
| 665 |
+
0,
|
| 666 |
+
"MODEL"
|
| 667 |
+
],
|
| 668 |
+
[
|
| 669 |
+
2,
|
| 670 |
+
4,
|
| 671 |
+
0,
|
| 672 |
+
15,
|
| 673 |
+
0,
|
| 674 |
+
"MODEL"
|
| 675 |
+
],
|
| 676 |
+
[
|
| 677 |
+
3,
|
| 678 |
+
3,
|
| 679 |
+
0,
|
| 680 |
+
12,
|
| 681 |
+
0,
|
| 682 |
+
"VAE"
|
| 683 |
+
],
|
| 684 |
+
[
|
| 685 |
+
4,
|
| 686 |
+
3,
|
| 687 |
+
0,
|
| 688 |
+
13,
|
| 689 |
+
0,
|
| 690 |
+
"VAE"
|
| 691 |
+
],
|
| 692 |
+
[
|
| 693 |
+
5,
|
| 694 |
+
3,
|
| 695 |
+
0,
|
| 696 |
+
24,
|
| 697 |
+
1,
|
| 698 |
+
"VAE"
|
| 699 |
+
],
|
| 700 |
+
[
|
| 701 |
+
6,
|
| 702 |
+
10,
|
| 703 |
+
0,
|
| 704 |
+
12,
|
| 705 |
+
1,
|
| 706 |
+
"IMAGE"
|
| 707 |
+
],
|
| 708 |
+
[
|
| 709 |
+
7,
|
| 710 |
+
11,
|
| 711 |
+
0,
|
| 712 |
+
13,
|
| 713 |
+
1,
|
| 714 |
+
"IMAGE"
|
| 715 |
+
],
|
| 716 |
+
[
|
| 717 |
+
8,
|
| 718 |
+
12,
|
| 719 |
+
0,
|
| 720 |
+
14,
|
| 721 |
+
0,
|
| 722 |
+
"LATENT"
|
| 723 |
+
],
|
| 724 |
+
[
|
| 725 |
+
9,
|
| 726 |
+
13,
|
| 727 |
+
0,
|
| 728 |
+
14,
|
| 729 |
+
1,
|
| 730 |
+
"LATENT"
|
| 731 |
+
],
|
| 732 |
+
[
|
| 733 |
+
10,
|
| 734 |
+
14,
|
| 735 |
+
0,
|
| 736 |
+
15,
|
| 737 |
+
1,
|
| 738 |
+
"LATENT"
|
| 739 |
+
],
|
| 740 |
+
[
|
| 741 |
+
11,
|
| 742 |
+
2,
|
| 743 |
+
0,
|
| 744 |
+
20,
|
| 745 |
+
0,
|
| 746 |
+
"CLIP"
|
| 747 |
+
],
|
| 748 |
+
[
|
| 749 |
+
12,
|
| 750 |
+
2,
|
| 751 |
+
0,
|
| 752 |
+
21,
|
| 753 |
+
0,
|
| 754 |
+
"CLIP"
|
| 755 |
+
],
|
| 756 |
+
[
|
| 757 |
+
13,
|
| 758 |
+
15,
|
| 759 |
+
0,
|
| 760 |
+
23,
|
| 761 |
+
0,
|
| 762 |
+
"MODEL"
|
| 763 |
+
],
|
| 764 |
+
[
|
| 765 |
+
14,
|
| 766 |
+
20,
|
| 767 |
+
0,
|
| 768 |
+
23,
|
| 769 |
+
1,
|
| 770 |
+
"CONDITIONING"
|
| 771 |
+
],
|
| 772 |
+
[
|
| 773 |
+
15,
|
| 774 |
+
21,
|
| 775 |
+
0,
|
| 776 |
+
23,
|
| 777 |
+
2,
|
| 778 |
+
"CONDITIONING"
|
| 779 |
+
],
|
| 780 |
+
[
|
| 781 |
+
16,
|
| 782 |
+
22,
|
| 783 |
+
0,
|
| 784 |
+
23,
|
| 785 |
+
3,
|
| 786 |
+
"LATENT"
|
| 787 |
+
],
|
| 788 |
+
[
|
| 789 |
+
17,
|
| 790 |
+
23,
|
| 791 |
+
0,
|
| 792 |
+
24,
|
| 793 |
+
0,
|
| 794 |
+
"LATENT"
|
| 795 |
+
],
|
| 796 |
+
[
|
| 797 |
+
18,
|
| 798 |
+
24,
|
| 799 |
+
0,
|
| 800 |
+
25,
|
| 801 |
+
0,
|
| 802 |
+
"IMAGE"
|
| 803 |
+
]
|
| 804 |
+
],
|
| 805 |
+
"groups": [],
|
| 806 |
+
"config": {},
|
| 807 |
+
"extra": {},
|
| 808 |
+
"version": 0.4
|
| 809 |
+
}
|