callback.md 5.46 KB
Newer Older
1
<!--Copyright 2024 The HuggingFace Team. All rights reserved.
2
3
4
5
6
7
8
9
10
11
12

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
-->

Steven Liu's avatar
Steven Liu committed
13
# Pipeline callbacks
14

Steven Liu's avatar
Steven Liu committed
15
The denoising loop of a pipeline can be modified with custom defined functions using the `callback_on_step_end` parameter. This can be really useful for *dynamically* adjusting certain pipeline attributes, or modifying tensor variables. The flexibility of callbacks opens up some interesting use-cases such as changing the prompt embeddings at each timestep, assigning different weights to the prompt embeddings, and editing the guidance scale.
16

Steven Liu's avatar
Steven Liu committed
17
18
19
20
This guide will show you how to use the `callback_on_step_end` parameter to disable classifier-free guidance (CFG) after 40% of the inference steps to save compute with minimal cost to performance.

The callback function should have the following arguments:

21
22
* `pipe` (or the pipeline instance) provides access to useful properties such as `num_timesteps` and `guidance_scale`. You can modify these properties by updating the underlying attributes. For this example, you'll disable CFG by setting `pipe._guidance_scale=0.0`.
* `step_index` and `timestep` tell you where you are in the denoising loop. Use `step_index` to turn off CFG after reaching 40% of `num_timesteps`.
Steven Liu's avatar
Steven Liu committed
23
24
25
* `callback_kwargs` is a dict that contains tensor variables you can modify during the denoising loop. It only includes variables specified in the `callback_on_step_end_tensor_inputs` argument, which is passed to the pipeline's `__call__` method. Different pipelines may use different sets of variables, so please check a pipeline's `_callback_tensor_inputs` attribute for the list of variables you can modify. Some common variables include `latents` and `prompt_embeds`. For this function, change the batch size of `prompt_embeds` after setting `guidance_scale=0.0` in order for it to work properly.

Your callback function should look something like this:
26
27

```python
28
def callback_dynamic_cfg(pipe, step_index, timestep, callback_kwargs):
29
        # adjust the batch_size of prompt_embeds according to guidance_scale
30
        if step_index == int(pipe.num_timesteps * 0.4):
31
                prompt_embeds = callback_kwargs["prompt_embeds"]
32
                prompt_embeds = prompt_embeds.chunk(2)[-1]
33
34
35
36
37
38
39

        # update guidance_scale and prompt_embeds
        pipe._guidance_scale = 0.0
        callback_kwargs["prompt_embeds"] = prompt_embeds
        return callback_kwargs
```

Steven Liu's avatar
Steven Liu committed
40
Now, you can pass the callback function to the `callback_on_step_end` parameter and the `prompt_embeds` to `callback_on_step_end_tensor_inputs`.
41

Steven Liu's avatar
Steven Liu committed
42
```py
43
44
45
46
47
48
49
50
51
import torch
from diffusers import StableDiffusionPipeline

pipe = StableDiffusionPipeline.from_pretrained("runwayml/stable-diffusion-v1-5", torch_dtype=torch.float16)
pipe = pipe.to("cuda")

prompt = "a photo of an astronaut riding a horse on mars"

generator = torch.Generator(device="cuda").manual_seed(1)
52
out = pipe(prompt, generator=generator, callback_on_step_end=callback_dynamic_cfg, callback_on_step_end_tensor_inputs=['prompt_embeds'])
53
54
55
56

out.images[0].save("out_custom_cfg.png")
```

Steven Liu's avatar
Steven Liu committed
57
58
59
The callback function is executed at the end of each denoising step, and modifies the pipeline attributes and tensor variables for the next denoising step.

With callbacks, you can implement features such as dynamic CFG without having to modify the underlying code at all!
60
61
62

<Tip>

Steven Liu's avatar
Steven Liu committed
63
🤗 Diffusers currently only supports `callback_on_step_end`, but feel free to open a [feature request](https://github.com/huggingface/diffusers/issues/new/choose) if you have a cool use-case and require a callback function with a different execution point!
64

65
</Tip>
Dhruv Nair's avatar
Dhruv Nair committed
66

Steven Liu's avatar
Steven Liu committed
67
## Interrupt the diffusion process
Dhruv Nair's avatar
Dhruv Nair committed
68

Steven Liu's avatar
Steven Liu committed
69
Interrupting the diffusion process is particularly useful when building UIs that work with Diffusers because it allows users to stop the generation process if they're unhappy with the intermediate results. You can incorporate this into your pipeline with a callback.
Dhruv Nair's avatar
Dhruv Nair committed
70

Steven Liu's avatar
Steven Liu committed
71
<Tip>
Dhruv Nair's avatar
Dhruv Nair committed
72

Steven Liu's avatar
Steven Liu committed
73
The interruption callback is supported for text-to-image, image-to-image, and inpainting for the [StableDiffusionPipeline](../api/pipelines/stable_diffusion/overview) and [StableDiffusionXLPipeline](../api/pipelines/stable_diffusion/stable_diffusion_xl).
Dhruv Nair's avatar
Dhruv Nair committed
74

Steven Liu's avatar
Steven Liu committed
75
</Tip>
Dhruv Nair's avatar
Dhruv Nair committed
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100

This callback function should take the following arguments: `pipe`, `i`, `t`, and `callback_kwargs` (this must be returned). Set the pipeline's `_interrupt` attribute to `True` to stop the diffusion process after a certain number of steps. You are also free to implement your own custom stopping logic inside the callback.

In this example, the diffusion process is stopped after 10 steps even though `num_inference_steps` is set to 50.

```python
from diffusers import StableDiffusionPipeline

pipe = StableDiffusionPipeline.from_pretrained("runwayml/stable-diffusion-v1-5")
pipe.enable_model_cpu_offload()
num_inference_steps = 50

def interrupt_callback(pipe, i, t, callback_kwargs):
    stop_idx = 10
    if i == stop_idx:
        pipe._interrupt = True

    return callback_kwargs

pipe(
    "A photo of a cat",
    num_inference_steps=num_inference_steps,
    callback_on_step_end=interrupt_callback,
)
```