Spaces:
Runtime error
Runtime error
| <!--Copyright 2022 The HuggingFace Team. All rights reserved. | |
| 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. | |
| --> | |
| # Reproducibility | |
| Before reading about reproducibility for Diffusers, it is strongly recommended to take a look at | |
| [PyTorch's statement about reproducibility](https://pytorch.org/docs/stable/notes/randomness.html). | |
| PyTorch states that | |
| > *completely reproducible results are not guaranteed across PyTorch releases, individual commits, or different platforms.* | |
| While one can never expect the same results across platforms, one can expect results to be reproducible | |
| across releases, platforms, etc... within a certain tolerance. However, this tolerance strongly varies | |
| depending on the diffusion pipeline and checkpoint. | |
| In the following, we show how to best control sources of randomness for diffusion models. | |
| ## Inference | |
| During inference, diffusion pipelines heavily rely on random sampling operations, such as the creating the | |
| gaussian noise tensors to be denoised and adding noise to the scheduling step. | |
| Let's have a look at an example. We run the [DDIM pipeline](./api/pipelines/ddim.mdx) | |
| for just two inference steps and return a numpy tensor to look into the numerical values of the output. | |
| ```python | |
| from diffusers import DDIMPipeline | |
| import numpy as np | |
| model_id = "google/ddpm-cifar10-32" | |
| # load model and scheduler | |
| ddim = DDIMPipeline.from_pretrained(model_id) | |
| # run pipeline for just two steps and return numpy tensor | |
| image = ddim(num_inference_steps=2, output_type="np").images | |
| print(np.abs(image).sum()) | |
| ``` | |
| Running the above prints a value of 1464.2076, but running it again prints a different | |
| value of 1495.1768. What is going on here? Every time the pipeline is run, gaussian noise | |
| is created and step-wise denoised. To create the gaussian noise with [`torch.randn`](https://pytorch.org/docs/stable/generated/torch.randn.html), a different random seed is taken every time, thus leading to a different result. | |
| This is a desired property of diffusion pipelines, as it means that the pipeline can create a different random image every time it is run. In many cases, one would like to generate the exact same image of a certain | |
| run, for which case an instance of a [PyTorch generator](https://pytorch.org/docs/stable/generated/torch.randn.html) has to be passed: | |
| ```python | |
| import torch | |
| from diffusers import DDIMPipeline | |
| import numpy as np | |
| model_id = "google/ddpm-cifar10-32" | |
| # load model and scheduler | |
| ddim = DDIMPipeline.from_pretrained(model_id) | |
| # create a generator for reproducibility | |
| generator = torch.Generator(device="cpu").manual_seed(0) | |
| # run pipeline for just two steps and return numpy tensor | |
| image = ddim(num_inference_steps=2, output_type="np", generator=generator).images | |
| print(np.abs(image).sum()) | |
| ``` | |
| Running the above always prints a value of 1491.1711 - also upon running it again because we | |
| define the generator object to be passed to all random functions of the pipeline. | |
| If you run this code snippet on your specific hardware and version, you should get a similar, if not the same, result. | |
| <Tip> | |
| It might be a bit unintuitive at first to pass `generator` objects to the pipelines instead of | |
| just integer values representing the seed, but this is the recommended design when dealing with | |
| probabilistic models in PyTorch as generators are *random states* that are advanced and can thus be | |
| passed to multiple pipelines in a sequence. | |
| </Tip> | |
| Great! Now, we know how to write reproducible pipelines, but it gets a bit trickier since the above example only runs on the CPU. How do we also achieve reproducibility on GPU? | |
| In short, one should not expect full reproducibility across different hardware when running pipelines on GPU | |
| as matrix multiplications are less deterministic on GPU than on CPU and diffusion pipelines tend to require | |
| a lot of matrix multiplications. Let's see what we can do to keep the randomness within limits across | |
| different GPU hardware. | |
| To achieve maximum speed performance, it is recommended to create the generator directly on GPU when running | |
| the pipeline on GPU: | |
| ```python | |
| import torch | |
| from diffusers import DDIMPipeline | |
| import numpy as np | |
| model_id = "google/ddpm-cifar10-32" | |
| # load model and scheduler | |
| ddim = DDIMPipeline.from_pretrained(model_id) | |
| ddim.to("cuda") | |
| # create a generator for reproducibility | |
| generator = torch.Generator(device="cuda").manual_seed(0) | |
| # run pipeline for just two steps and return numpy tensor | |
| image = ddim(num_inference_steps=2, output_type="np", generator=generator).images | |
| print(np.abs(image).sum()) | |
| ``` | |
| Running the above now prints a value of 1389.8634 - even though we're using the exact same seed! | |
| This is unfortunate as it means we cannot reproduce the results we achieved on GPU, also on CPU. | |
| Nevertheless, it should be expected since the GPU uses a different random number generator than the CPU. | |
| To circumvent this problem, we created a [`randn_tensor`](#diffusers.utils.randn_tensor) function, which can create random noise | |
| on the CPU and then move the tensor to GPU if necessary. The function is used everywhere inside the pipelines allowing the user to **always** pass a CPU generator even if the pipeline is run on GPU: | |
| ```python | |
| import torch | |
| from diffusers import DDIMPipeline | |
| import numpy as np | |
| model_id = "google/ddpm-cifar10-32" | |
| # load model and scheduler | |
| ddim = DDIMPipeline.from_pretrained(model_id) | |
| ddim.to("cuda") | |
| # create a generator for reproducibility | |
| generator = torch.manual_seed(0) | |
| # run pipeline for just two steps and return numpy tensor | |
| image = ddim(num_inference_steps=2, output_type="np", generator=generator).images | |
| print(np.abs(image).sum()) | |
| ``` | |
| Running the above now prints a value of 1491.1713, much closer to the value of 1491.1711 when | |
| the pipeline is fully run on the CPU. | |
| <Tip> | |
| As a consequence, we recommend always passing a CPU generator if Reproducibility is important. | |
| The loss of performance is often neglectable, but one can be sure to generate much more similar | |
| values than if the pipeline would have been run on CPU. | |
| </Tip> | |
| Finally, we noticed that more complex pipelines, such as [`UnCLIPPipeline`] are often extremely | |
| susceptible to precision error propagation and thus one cannot expect even similar results across | |
| different GPU hardware or PyTorch versions. In such cases, one has to make sure to run | |
| exactly the same hardware and PyTorch version for full Reproducibility. | |
| ## Randomness utilities | |
| ### randn_tensor | |
| [[autodoc]] diffusers.utils.randn_tensor | |