InvokeAI/invokeai/app/invocations
psychedelicious 14372e3818 fix(nodes): blend latents with weight=0 with DPMSolverSDEScheduler
- Pass the seed from `latents_a` to the output latents. Fixed an issue where using `BlendLatentsInvocation` could result in different outputs during denoising even when the alpha or slerp weight was 0.

## Explanation

`LatentsField` has an optional `seed` field. During denoising, if this `seed` field is not present, we **fall back to 0 for the seed**. The seed is used during denoising in a few ways:

1. Initializing the scheduler.

The seed is used in two places in `invokeai/app/invocations/latent.py`.

The `get_scheduler()` utility function has special handling for `DPMSolverSDEScheduler`, which appears to need a seed for deterministic outputs.

`DenoiseLatentsInvocation.init_scheduler()` has special handling for schedulers that accept a generator - the generator needs to be seeded in a particular way. At the time of this commit, these are the Invoke-supported schedulers that need this seed:
  - DDIMScheduler
  - DDPMScheduler
  - DPMSolverMultistepScheduler
  - EulerAncestralDiscreteScheduler
  - EulerDiscreteScheduler
  - KDPM2AncestralDiscreteScheduler
  - LCMScheduler
  - TCDScheduler

2. Adding noise during inpainting.

If a mask is used for denoising, and we are not using an inpainting model, we add noise to the unmasked area. If, for some reason, we have a mask but no noise, the seed is used to add noise.

I wonder if we should instead assert that if a mask is provided, we also have noise.

This is done in `invokeai/backend/stable_diffusion/diffusers_pipeline.py` in `StableDiffusionGeneratorPipeline.latents_from_embeddings()`.

When we create noise to be used in denoising, we are expected to set `LatentsField.seed` to the seed used to create the noise. This introduces some awkwardness when we manipulate any "latents" that will be used for denoising. We have to pass the seed along for every operation.

If the wrong seed or no seed is passed along, we can get unexpected outputs during denoising. One notable case relates to blending latents (slerping tensors).

If we slerp two noise tensors (`LatentsField`s) _without_ passing along the seed from the source latents, when we denoise with a seed-dependent scheduler*, the schedulers use the fallback seed of 0 and we get the wrong output. This is most obvious when slerping with a weight of 0, in which case we expect the exact same output after denoising.

*It looks like only the DPMSolver* schedulers are affected, but I haven't tested all of them.

Passing the seed along in the output fixes this issue.
2024-06-05 00:02:52 +10:00
..
custom_nodes fix(nodes): gracefully handle custom nodes init error 2024-04-02 13:25:14 +11:00
__init__.py fix(config): remove unnecessary resolve on config path 2024-03-19 09:24:28 +11:00
baseinvocation.py feat(app): dynamic type adapters for invocations & outputs 2024-05-30 12:03:38 +10:00
collections.py fix(nodes): restore type annotations for InvocationContext 2024-03-01 10:42:33 +11:00
compel.py Optimize RAM to VRAM transfer (#6312) 2024-05-24 17:06:09 +00:00
constants.py chore: ruff formatting 2024-03-01 10:42:33 +11:00
controlnet_image_processors.py feat(nodes): make all ModelIdentifierField inputs accept connections 2024-05-19 20:14:01 +10:00
cv.py chore: bump nodes versions 2024-03-20 10:28:07 +11:00
facetools.py chore: bump nodes versions 2024-03-20 10:28:07 +11:00
fields.py Rename MaskField to be a generice TensorField. 2024-04-09 08:12:12 -04:00
image.py feat(nodes): use new blur_if_nsfw method 2024-05-14 07:23:38 +10:00
infill.py feat(nodes): disable mosaic fill 2024-04-05 08:49:13 +11:00
ip_adapter.py feat(nodes): make all ModelIdentifierField inputs accept connections 2024-05-19 20:14:01 +10:00
latent.py fix(nodes): blend latents with weight=0 with DPMSolverSDEScheduler 2024-06-05 00:02:52 +10:00
mask.py fix ruff 2024-06-03 11:41:47 -07:00
math.py chore: bump nodes versions 2024-03-20 10:28:07 +11:00
metadata.py tidy(nodes): move cnet mode literals to utils 2024-04-25 13:20:09 +10:00
model.py feat(nodes): make ModelIdentifierInvocation a prototype 2024-05-19 20:14:01 +10:00
noise.py [util] Add generic torch device class (#6174) 2024-04-15 13:12:49 +00:00
param_easing.py chore: bump nodes versions 2024-03-20 10:28:07 +11:00
primitives.py Rename MaskField to be a generice TensorField. 2024-04-09 08:12:12 -04:00
prompt.py chore: bump nodes versions 2024-03-20 10:28:07 +11:00
sdxl.py feat(nodes): make all ModelIdentifierField inputs accept connections 2024-05-19 20:14:01 +10:00
strings.py chore: bump nodes versions 2024-03-20 10:28:07 +11:00
t2i_adapter.py feat(nodes): make all ModelIdentifierField inputs accept connections 2024-05-19 20:14:01 +10:00
tiles.py chore: bump nodes versions 2024-03-20 10:28:07 +11:00
upscale.py [util] Add generic torch device class (#6174) 2024-04-15 13:12:49 +00:00
util.py fix(nodes): fix constraints/validation for controlnet 2024-01-02 07:28:53 -05:00