Self-Hosted AI Stack — Part 4: Image Generation
Part 4 of the self-hosted AI stack series. Get ComfyUI generating images end-to-end, understand the node graph well enough to debug it yourself, and learn the failure modes that come up with multi-subject compositions.
Goal for this part: get ComfyUI generating images end-to-end, understand the node graph well enough to debug it yourself, and learn the specific failure modes that show up once you try anything more complex than a single subject in frame.
Prerequisite: ComfyUI is running from Part 3's compose stack, reachable
at localhost:8188.
Why ComfyUI instead of a simpler UI
ComfyUI is node-based rather than form-based — every step of image generation (loading a checkpoint, encoding your prompt, sampling, decoding back to pixels) is its own node that you wire together explicitly. That's more setup friction than a single-page UI, but it means you can see and control exactly what's happening at each stage, which matters a lot once you're troubleshooting a generation that isn't coming out right — you can inspect and modify any single step in isolation.
Getting your first checkpoint
Download an SDXL checkpoint — AlbedoBase XL is a solid general-purpose
starting point — and place it in
~/ai-stack/comfyui/basedir/models/checkpoints/. ComfyUI scans that folder
on startup, so restart the container (or use its refresh option) if a
newly-added checkpoint doesn't show up in the dropdown.
The basic text-to-image graph
Six nodes, wired together, is the minimum viable graph:
- Load Checkpoint — select your
.safetensorsfile - CLIP Text Encode (Prompt) × 2 — one for positive prompt, one for negative
- Empty Latent Image — sets your canvas dimensions
- KSampler — the actual diffusion sampling step
- VAE Decode — turns the sampled latent back into a viewable image
- Save Image
Wiring: Load Checkpoint's MODEL → KSampler's model; your two CLIP
Text Encode nodes' CONDITIONING outputs → KSampler's positive and
negative; Empty Latent's LATENT → KSampler's latent_image; KSampler's
LATENT output → VAE Decode's samples; Load Checkpoint's VAE → VAE
Decode's vae; VAE Decode's IMAGE → Save Image.
Double-click empty canvas to open a search box and type the node name — much faster than navigating the right-click category menus, which get reorganized between ComfyUI versions. If double-click doesn't respond, make sure you're clicking genuinely empty canvas space, not a wire or a group box. Dragging a new connection out from an existing output dot and releasing on empty space also opens a search box, pre-filtered to compatible node types.
Resolution: stay near your model's native training resolution
This is the single most common cause of distorted, duplicated-looking output, and it's easy to get wrong. SDXL-family models are trained around roughly 1 megapixel (1024×1024, or equivalent-area buckets like 896×1152). Pushing your Empty Latent Image well past that — say, to 1280×2048 (2.6 megapixels) — doesn't just make a bigger picture. It pushes the model outside the composition scale it actually learned, and the classic symptom is duplicated limbs, elongated bodies, or warped anatomy, especially with more than one subject in frame.
If you want a larger final image, generate at native resolution first and upscale afterward with a dedicated upscale step, rather than requesting a huge canvas directly from the sampler. "Bigger canvas" is not "higher quality."
Samplers and settings that work well as defaults
dpmpp_2m with the karras scheduler is a solid, fast, reliable default —
20–25 steps, cfg around 7.0. dpmpp_2m_sde is a reasonable step up in fine
detail at a small speed cost. Avoid plain dpm_2 (note the missing "pp")
unless you have a specific reason — it's an older, generally lower-quality
sampler that's easy to select by accident since the names look similar in
the dropdown.
Negative prompts: a solid general-purpose starting point
worst quality, low quality, blurry, jpeg artifacts, watermark, signature,
text, extra limbs, extra fingers, missing fingers, deformed hands, bad
anatomy, disfigured, mutated, cropped, out of frameTreat this as a template, not gospel — once you see actual output, you'll notice which specific artifacts keep showing up and can tune the list toward those.
The hard part: getting multiple subjects right
This is where most of the real troubleshooting time goes, and it's worth understanding why it's hard rather than just memorizing fixes.
Why models struggle with counting and multiple subjects
Diffusion models are noticeably weaker at exact subject counts than at single-subject fidelity — ask for three people and you'll often get two, four, or subjects that partially merge into each other. This gets worse the more visually similar the subjects are.
Fix 1 — tag-based reinforcement
If your checkpoint is trained on Danbooru-style tagging (many
anime/illustration-focused SDXL finetunes are), use explicit count tags
near the front of your prompt — 2boys, 1girl, 2boys, 3girls — rather
than relying on natural-language phrasing like "three people." These are
literal trained tags and work far more reliably than prose.
Give each subject its own tight cluster of concrete visual descriptors, not
just a name or role — a bare word like "hero" gives the model little to
render distinctly, while muscular hero, lion pelt cloak, club weapon gives
it something concrete to latch onto.
Fix 2 — distinct poses beat identical poses
Subjects in visibly different poses relative to each other separate far more reliably than subjects in similar poses. Two figures both "standing side by side" are prone to merging; a scene where one figure is kneeling and another has their head resting in someone's lap gives the model strong distinct spatial anchors and tends to compose correctly without any extra work.
Fix 3 — regional conditioning
ComfyUI's Conditioning (Set Area) + Conditioning (Combine) nodes let you
assign different prompt text to different regions of the canvas — encode each
subject separately, wrap each in a Set Area node with pixel coordinates,
and combine the results before feeding KSampler's positive input.
Two things to get right:
- Area height should match your full canvas height, not just an upper portion — if you set a region's height smaller than the canvas, only that vertical slice gets subject-specific reinforcement
- Overlap your regions slightly (10–25%) rather than splitting the canvas with a hard boundary — a hard seam tends to produce an awkward gap or visible dividing line between subjects
Set Area is a soft nudge, not a hard constraint. It biases which attributes show up in a region — it doesn't strictly guarantee two separate bodies exist. For two visually similar subjects with no pose differentiation, it can still let the sampler commit to one merged figure during early denoising steps. If regional conditioning keeps producing a single hybrid figure, that's the actual limitation of the technique, not a sign you set it up wrong.
Fix 4 — two-pass inpainting compositing (the reliable fallback)
When regional conditioning isn't cutting it, generating each subject separately and compositing via inpainting is the most reliable option using only stock ComfyUI nodes:
- Generate your first subject alone on the full canvas, positioned toward one side of the frame
- Load that output image back in, and use ComfyUI's built-in Mask Editor (right-click the image thumbnail → Open in MaskEditor) to paint a mask over the empty space where your second subject should appear
- Feed the image and mask into a VAE Encode (for Inpainting) node — search for it specifically, it's different from plain VAE Encode — along with your VAE
- Connect its
LATENToutput to KSampler'slatent_image, switchpositiveto your second subject's prompt, and setdenoisearound 0.9–0.95 - Run — the masked region regenerates with your second subject while the rest of the image stays untouched
This guarantees separation because you're explicitly directing where the new content goes, rather than hoping conditioning math keeps two similar subjects apart.
Troubleshooting
"2 errors found" banner in ComfyUI with no obvious cause
Click "View details" — but also manually check for two extremely common
culprits it doesn't always surface clearly: an unconnected negative input
on KSampler (every positive needs a matching negative, even an empty one),
and a Load Image node with no actual file selected (its placeholder text
can look like a valid filename at a glance).
Duplicated limbs, warped anatomy, or "extra person" artifacts
Check your Empty Latent Image dimensions first — this is disproportionately caused by generating well above your model's native training resolution, even more than prompt wording. See the Resolution section above.
Two subjects merge into one, or inherit each other's features
Work through the fixes in order: count tags → concrete per-subject descriptors → distinct poses → regional conditioning → inpainting compositing. Stop as soon as one actually solves it — don't jump straight to inpainting if a simple pose change would have fixed it.
Inpainted region doesn't change at all, even after fixing wiring
Almost always an empty mask. Wire your Load Image node's MASK output into
a temporary Preview Image node — a mask renders as black-and-white, and
solid black means nothing is actually marked for inpainting. If it's blank,
the mask editor save didn't take — repaint and confirm the shape is visible
before wiring it back in.