Nodes/saya-comfy-couple-plus/Saya Comfy Couple - Forge Coarse Main 2.3
ComfyUI Node

Saya Comfy Couple - Forge Coarse Main 2.3

The SDXL fix

By alphaziod·Created 3 months ago·Updated 2 days ago· 3
Saya Comfy Couple - Forge Coarse Main 2.3
  • model_main
  • main_positive
  • person_1_positive
  • person_2_positive
  • negative
  • latent
  • dual_sampling_model
  • support_model_1
  • support_model_2
  • support_model_3
  • naturalize_main_positive
  • naturalize_person_1_positive
  • naturalize_person_2_positive
  • naturalize_negative
  • hidream_main_positive
  • hidream_person_1_positive
  • hidream_person_2_positive
  • hidream_negative
  • patched_model_main
  • patched_dual_sampling_model
  • patched_support_model_1
  • patched_support_model_2
  • patched_support_model_3
  • positive_final
  • detailer_positive
  • negative
  • mask_person_1
  • mask_person_2
  • couple_config
  • naturalize_patched_model_main
  • naturalize_positive_final
  • naturalize_negative
use_couple_attentiontrue
orientationhorizontal
center0.50
transition0.03
mask_floor0.00
swap_person_positionsfalse

The problem you actually have

You wrote a prompt with two people in it. The girl on the left got the other one's hair colour, both outfits fused at the hip, and a hand grew out of somebody's shoulder. This is not a prompting failure and you cannot fix it by rewording - conditioning applies to the whole canvas, so two described characters reliably trade attributes. Regional prompting is the standing answer, and it has been flat at 20–40 threads a month since 2023 because nobody has actually solved it. SayaComfyCoupleForge is the SDXL implementation in this pack: give each person their own prompt and their own half of the frame, and keep a shared Base prompt for the scene they're both in.

It's the MASTER node of a two-node pair. You wire the layout here, and every later pass reuses that exact layout through SayaComfyCoupleForgeCopy instead of re-typing the geometry and letting it drift.

How it works

The Forge engine is derivable from Haoming02's sd-forge-couple (the pack vendors it, GPL-3.0 and notices intact), which matters because the behaviour is the same family as the WebUI extension - but it runs as a ComfyUI attn2 patch rather than a whole UI.

Two things happen. Masks: from the latent you wire in, the node builds two soft complementary ownership masks over the frame. center is where the split sits, transition is the width of the soft edge (0.03 by default, so a narrow blend rather than a hard line), and mask_floor lets a bit of each prompt leak everywhere - leave it at 0 unless you want cross-talk. Then routing: a cloned MODEL gets patched Forge-style. An input hook rewrites the k/v of cross-attention with the concatenated regional conditionings; an output hook composites the branch outputs by the ownership mask resized to the token grid. Each region's conditioning is Base + its person, concatenated through ComfyUI's own ConditioningConcat, so the scene prompt is present in both halves.

Two details are the reason this is labelled "Coarse Main 2.3". There's a hardcoded soft contact band down the seam (strength 0.90, width 0.24) where the Base prompt gets sole authority, so held hands and hugs don't get torn in half by the ownership split. And in coarse (low-resolution) attention passes, a bounded 0.22 slice of the private budget goes back to Base, so your background isn't permanently diluted. Self-attention is left completely native.

Inputs that matter

  • model_main - your SDXL or Illustrious checkpoint's MODEL.
  • main_positive, person_1_positive, person_2_positive, negative - four plain CONDITIONINGs, each from its own text encode. Base describes the scene and interaction; P1 and P2 describe appearance, clothing and pose.
  • latent - required, and people miss it. The masks are built from this latent's shape (×8 to pixels), so wire the same Empty Latent or image latent you're sampling.
  • center / orientation / swap_person_positions - the three you'll actually tweak. Person 1 is left by default in horizontal mode.

There are optional model sockets (dual_sampling_model, support_model_1..3) that get the identical patch, so a hires-fix or refiner model stays region-consistent. The naturalize_* and hidream_* optional sockets only exist for the pack's own phased example; you can ignore them in a small custom graph.

Outputs

patched_model_main goes into KSampler.model. positive_final goes into KSampler.positive - and here's the counter-intuitive bit: in COUPLE mode that output is just your Base conditioning, because the regional routing lives inside the patched model. negative goes to the sampler's negative. detailer_positive is per-person, mask-tagged conditioning for detailers only, never for the sampler. mask_person_1 and mask_person_2 are pixel-space masks you can feed to inpaint or detailer nodes. couple_config is the bundle every COPY node needs later.

Install

cd /path/to/ComfyUI/custom_nodes
git clone https://github.com/alphaziod/saya-comfy-couple-plus.git

Restart ComfyUI and refresh the browser. That's the whole install: requirements.txt asks for NumPy and Pillow and explicitly refuses to touch PyTorch, so don't let anything offer to "fix" your CUDA build. No model weights ship with the pack.

Keep exactly one copy. An older copy of this project living in a folder named comfy_saya_couple registers the same node IDs, and then you're debugging which of two identical nodes you actually wired.

Where people get burned

It silently gives up. If a person or negative conditioning is empty, or any of the four carries more than one entry (a ConditioningCombine upstream, say), the node drops to FALLBACK mode, concatenates everything and hands you an unregionalised image with the bleeding you were trying to fix. Start ComfyUI with SAYA_COUPLE_DEBUG=1 and the console tells you which mode you got:

SAYA_COUPLE_DEBUG=1 python main.py
# mode=COUPLE engine=forge-coarse-main23 ...   <- good
# mode=FALLBACK fallback=multi-entry conditioning cannot form exactly two stable logical regions

You turned use_couple_attention off and expected regions anyway. Off is SOLO: Base + Person 1 concatenated, one region, no split.

You tried it on the wrong model family. This patches SDXL attention. It is not the node for HiDream - that's a different node in the same pack with a different mechanism. Regional prompting is implemented per architecture and does not port; that's the whole history of the technique.

Categorysaya/rescue

Inputs (24)

NameTypeDefaultDescription
model_mainMODEL
main_positiveCONDITIONING
person_1_positiveCONDITIONING
person_2_positiveCONDITIONING
negativeCONDITIONING
latentLATENT
use_couple_attentionBOOLEANtrue
orientationCOMBOhorizontal2 options: horizontal, vertical
centerFLOAT0.500.15–0.85
transitionFLOAT0.030.01–0.2
mask_floorFLOAT0.000–0.2
swap_person_positionsBOOLEANfalse
dual_sampling_modeloptMODEL
support_model_1optMODEL
support_model_2optMODEL
support_model_3optMODEL
naturalize_main_positiveoptCONDITIONING
naturalize_person_1_positiveoptCONDITIONING
naturalize_person_2_positiveoptCONDITIONING
naturalize_negativeoptCONDITIONING
hidream_main_positiveoptCONDITIONING
hidream_person_1_positiveoptCONDITIONING
hidream_person_2_positiveoptCONDITIONING
hidream_negativeoptCONDITIONING

Outputs (14)

NameTypeDescription
patched_model_mainMODEL
patched_dual_sampling_modelMODEL
patched_support_model_1MODEL
patched_support_model_2MODEL
patched_support_model_3MODEL
positive_finalCONDITIONING
detailer_positiveCONDITIONING
negativeCONDITIONING
mask_person_1MASK
mask_person_2MASK
couple_configSAYA_COUPLE_CONFIG
naturalize_patched_model_mainMODEL
naturalize_positive_finalCONDITIONING
naturalize_negativeCONDITIONING