Attn2 Prompt Injection
The node that started this pack
- model
- all
- input_4
- input_5
- input_7
- input_8
- middle_0
- output_0
- output_1
- output_2
- output_3
- output_4
- output_5
- MODEL
The "Attn2 Prompt Injection" node is the reason this pack exists, and it's still the most useful thing in it. It takes your SDXL checkpoint and lets you feed a different prompt to specific cross-attention blocks of the UNet, instead of one prompt hitting every block like normal. Same model, one knob, and it can pull off tricks no amount of prompt rewriting will get you.
The idea comes from the B-LoRA "implicit style-content separation" line of research, plus the perturbed-attention experiments that did the rounds in 2024. The short version: different UNet blocks seem to hold different jobs. The input and middle blocks handle coarse composition and layout; the output blocks carry more of the fine detail and how the subject actually looks. Inject "blue dog" everywhere and "white cat" into output:0/output:1, and you can get a scene composed like the blue-dog prompt with a cat as the subject. Same staging, different star.
This node is a deliberately simplified fork of pamparamm's sd-perturbed-attention (the famous PAG node from spring 2024). The author stripped it down on purpose so there's less to break when ComfyUI updates.
How it works
Under the hood it clones your model and installs an attention patch via set_model_attn2_patch. "attn2" is ComfyUI's name for the cross-attention layer - the one where text conditioning actually reaches the UNet. During sampling the patch watches which block is running; if that block is in its list, it swaps in the embeddings from your injected conditioning (scaled by weight) instead of the normal ones, but only while the current noise level sits inside your start/end window.
The block names come from ComfyUI's model internals, so they're stable across SDXL checkpoints: input:4, input:5, input:7, input:8, middle:0, output:0 through output:5. Those are the eleven SDXL blocks that do text cross-attention at all.
The inputs that matter
model(required) - from your checkpoint loader.all- a conditioning that acts as the default for every block in the list. Connect just this one and you've recreated the "single prompt everywhere" version.input_4…output_5- one port per cross-attention block. Connect a conditioning to any of them and that block uses it instead ofall.weight- how hard the injection bites. Default 1.0, range -2 to 5. Negative weights subtract, which is how you "un-teach" a block something.start_at/end_at- the fraction of the denoise where the patch is active (0 = high-noise start, 1 = end). Default 0→1 means the whole run.
The one output is a patched MODEL, wired into your KSampler exactly like the unpatched one.
Wiring it up
You need a second CLIP Text Encode. The base prompt feeds the KSampler's positive as usual; the injection prompt feeds this node. The patched model goes between loader and sampler:
Load Checkpoint ──▶ Attn2 Prompt Injection ──▶ KSampler
▲ (positive)
CLIP Text Encode ───────┘ (injection prompt)
Installing
ComfyUI Manager is the easy route - search "Prompt Injection" and you'll find "Prompt Injection Node for ComfyUI" by DataCTE. Or clone it directly:
cd ComfyUI/custom_nodes
git clone https://github.com/DataCTE/prompt_injection
Restart ComfyUI. No pip install, no model downloads, no extra dependencies - the whole thing is one Python file leaning on the torch and ComfyUI internals you already have. About as dependency-free as a custom node gets.
Gotchas
The big one: if you connect no conditioning at all, the node silently passes your model through unchanged. No error, nothing. If a workflow "doesn't do anything," check that you actually wired a conditioning into it.
Only blocks you patch change. Unconnected blocks keep using the base prompt from the sampler - that's the feature, but it also means the node is a no-op on architectures it wasn't built for. On Flux or other DiT models the block names never match, so it silently does nothing. SDXL (and the SD1.5 sibling nodes this pack also ships) only.
One honest warning: the exact content-vs-style mapping is still an open question. The README claims content lives in the middle blocks, then demonstrates a subject swap by injecting into the output blocks - two different models of what these layers do, in the same doc. Treat this as a surface for experiments, not a guarantee, and you'll have fun with it.
Inputs (16)
| Name | Type | Default | Description |
|---|---|---|---|
| model | MODEL | — | |
| allopt | CONDITIONING | — | |
| input_4opt | CONDITIONING | — | |
| input_5opt | CONDITIONING | — | |
| input_7opt | CONDITIONING | — | |
| input_8opt | CONDITIONING | — | |
| middle_0opt | CONDITIONING | — | |
| output_0opt | CONDITIONING | — | |
| output_1opt | CONDITIONING | — | |
| output_2opt | CONDITIONING | — | |
| output_3opt | CONDITIONING | — | |
| output_4opt | CONDITIONING | — | |
| output_5opt | CONDITIONING | — | |
| weightopt | FLOAT | 1.00-2–5 | — |
| start_atopt | FLOAT | 0.0000–1 | — |
| end_atopt | FLOAT | 1.0000–1 | — |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| MODEL | MODEL | — |