Sampler Custom (DAAM)
Finally see which of your tags the model actually looked at
- model
- positive
- negative
- sampler
- sigmas
- latent_image
- output
- denoised_output
- pos_heatmaps
- neg_heatmaps
What it is
Sampler Custom (DAAM) is a drop-in replacement for ComfyUI's core SamplerCustom that also records cross-attention heatmaps while it samples. DAAM stands for Diffusion Attentive Attribution Maps (arXiv 2210.04885) - the idea that the cross-attention layers, where prompt tokens meet image patches, can be averaged over layers and denoising steps into a per-token heatmap of where that token actually pushed pixels.
You can't get those numbers anywhere except from inside the sampling pass, so the node wraps one. It captures; DAAM Tag Explorer renders. They're a pair.
Set your expectations: this was the talk of r/StableDiffusion for a month in early 2023 (22 mentions in February alone, in this corpus) and then went quiet - roughly one mention a month now, and a ComfyUI port landing in September 2025 got fifteen upvotes and no comments. It's a forensic instrument, not an everyday node. But when a 40-tag booru prompt comes back with the wrong amount of blue, this is the only thing that answers which tag did that.
How it works
When you queue, the node locates every attn2 (cross-attention) block in the UNet, patches them for the duration of the sampling call, and restores them in a finally so a crash can't leave your model patched.
The patched attention does the maths in plain PyTorch - matmul, scale, softmax, matmul - instead of Comfy's fused kernel, precisely because the point is to read the probabilities that a fused kernel never exposes. That's the slowdown, and it's unavoidable.
Each layer's map is resampled onto a common grid of image/16, the finest grid SDXL's cross-attention runs at (latent/2), so those layers land 1:1 and the deeper latent/4 layers upscale by exactly 2×. Maps accumulate in place on the GPU and are averaged over all layers and steps; nothing moves to the CPU until the end, because per-block transfers would sync on every attention layer of every step. One accumulator per image per side keeps memory flat regardless of step count.
Positive and negative stay separate because the collector reads cond_or_uncond from the attention options rather than guessing from tensor shapes - correct whether ComfyUI batched the conditional and unconditional passes together or ran them separately.
And the detail I'd point at first: before patching anything, it checks the prompt to see whether output 2 or 3 is actually wired to something. Leave both heatmap sockets empty and this node costs exactly what SamplerCustom costs - no patch, no fused-kernel downgrade. Wire the explorer up and you pay for it. That makes it safe to leave in a workflow as your permanent sampler.
Inputs and outputs
The inputs are the core SamplerCustom set, all required: model, add_noise, noise_seed, cfg (default 8), positive, negative, sampler, sigmas, latent_image. Pick the sampler with KSamplerSelect, the schedule with BasicScheduler (or a Sigmas node), exactly as you would for the stock node.
Outputs:
output- the LATENT. Goes to VAE Decode, same as ever.denoised_output- the x0 estimate, same meaning as on the core node.pos_heatmaps/neg_heatmaps- the HEATMAP data.pos_heatmapsis what you feed into DAAM Tag Explorer; the negative side is there if you also want to see where your negative prompt is pulling.
If nothing consumes the heatmap sockets, they come out empty. That's not a bug, it's the optimisation above.
Install
ComfyUI Manager: search ComfyUI-Alchemine-Pack. Or:
cd ComfyUI/custom_nodes
git clone https://github.com/alchemine/comfyui-alchemine-pack
cd comfyui-alchemine-pack
pip install -r requirements.txt
Restart, and you're done - no model files, no keys. The DAAM nodes lean on torch and numpy, which ComfyUI already has; the pack's only declared dependency is python-dotenv (needed by its API/Grok nodes, not these). The pack declares Python 3.12+.
Common problems
"DAAM: no attention heatmaps were collected during sampling." The explorer got a heatmap socket with nothing behind it - either you're sampling with a different sampler node, or the pos_heatmaps link got dropped. The sampler's heatmap output is what the explorer reads; its latent output is a separate thing that goes to VAE Decode.
neg_heatmaps comes back empty. At CFG 1 - which is where every guidance-distilled model runs - ComfyUI doesn't compute the unconditional pass at all (math.isclose(cond_scale, 1.0)), so there's no negative attention to average. If you want the negative side, sample above CFG 1.
It assumes UNet geometry. The collector walks the classic input_blocks / middle_block / output_blocks layout to find cross-attention blocks, which is SD1.5/SDXL-shaped. A DiT-architecture model (Flux, Z-Image and friends) doesn't have those blocks, so don't expect capture to work there - treat this as an SDXL-lineage tool, which happens to be exactly where reading a 60-tag booru prompt is the hard part.
Sampling gets slower and VRAM use goes up while collecting. Expected. You traded Comfy's fused attention for readable attention, and you're holding a token × 64 × 64 accumulator per image. Unwire the heatmap sockets when you're done investigating and the cost disappears.
Inputs (9)
| Name | Type | Default | Description |
|---|---|---|---|
| model | MODEL | — | |
| add_noise | BOOLEAN | true | — |
| noise_seed | INT | 00–18446744073709550000 | — |
| cfg | FLOAT | 8.00–100 | — |
| positive | CONDITIONING | — | |
| negative | CONDITIONING | — | |
| sampler | SAMPLER | — | |
| sigmas | SIGMAS | — | |
| latent_image | LATENT | — |
Outputs (4)
| Name | Type | Description |
|---|---|---|
| output | LATENT | — |
| denoised_output | LATENT | — |
| pos_heatmaps | HEATMAP | — |
| neg_heatmaps | HEATMAP | — |