Seeds, Determinism, and Reproducing an Image
10 min read · updated August 4, 2026
A seed does one thing: it initialises a pseudorandom number generator, whose first output becomes the initial noise tensor. Everything else about reproducing an image is downstream of that, and most of the reasons a seed “does not work” are not about the seed at all.
The short answer
Generation begins from a tensor of Gaussian noise. That tensor has to come from somewhere, and it comes from a PRNG initialised with the seed. Give the generator the same seed and it produces the same stream of numbers, so it produces the same starting tensor, so — provided every other input to the computation is identical — you get the same image.
The seed does not encode the image. It does not contain a composition. It selects a point in a very high-dimensional noise space, and the model, the prompt and the sampler decide what that point turns into.
What a seed fixes
- The initial latent. One draw of shape (batch, channels, height/8, width/8), the tensor the first denoising step receives.
- Every subsequent draw from the same generator. An ancestral or SDE sampler draws fresh noise at each step from that same stream, so the seed fixes the entire noise sequence, not just the start.
- Nothing else. Not the weights, not the tokenizer, not the kernel selection, not the floating-point rounding. Those are the parts that break.
Because the initial latent’s low-frequency structure survives into the composition — the mechanism is explained in how the early steps decide layout — a fixed seed with a changed prompt often produces a recognisably similar arrangement. That is a real effect and it is the basis of most practical seed use.
Why the same seed still gives a different image
Reproducibility requires bit-identical arithmetic all the way through, and there are more ways to lose that than people expect. In rough order of how often each one is the culprit:
| Cause | Description |
|---|---|
| resolution changed | The latent has a different shape, so a different number of values is drawn from the stream. Nothing after the first draw lines up. 1024×1024 and 1024×1023 are unrelated draws. |
| step count changed | For a deterministic sampler this changes the trajectory discretisation. For a stochastic one it changes the entire sequence of draws as well. Either way, same seed, different image. |
| generator device changed | A CPU PRNG and a CUDA PRNG produce different streams from the same seed. See the section below — this is the most common cross-machine failure. |
| batch position changed | Some implementations draw one tensor for the whole batch. The third image in a batch of four is then not the same as the same seed generated alone. |
| precision changed | fp16, bf16 and fp32 round differently. The trajectory diverges gradually and then visibly. bf16 and fp16 have different exponent ranges and are not interchangeable for this purpose. |
| attention backend changed | A fused attention kernel, a memory-efficient kernel and the plain mathematical implementation reduce in different orders, so they give bitwise different results. Library upgrades change the default silently. |
| GPU model changed | Different hardware selects different kernels and different tile sizes, which changes reduction order. Same code, same seed, same weights, different card, different image. |
| library version changed | A change to the scheduler's timestep rounding, the default sigma schedule, or the prompt-embedding padding will move the result. This is a frequent cause of an image that was reproducible last month and is not now. |
| prompt whitespace changed | A trailing space or a different comma changes the tokenisation, which changes the embedding, which changes everything. Copy prompts exactly. |
| weights changed | A re-quantised, re-merged or differently converted checkpoint is a different model. Compare file hashes, not file names. |
| guidance scale changed | Obvious, but included because it is easy to miss when a UI has a per-model default that changed under you. |
The CPU-versus-GPU generator trap
This one is worth its own section because it is the most common cause of a seed failing to transfer between machines, and because the fix is one line.
Random number generation on a GPU is parallel, and the stream it produces from a given seed is not the same stream a CPU produces from the same seed. It is also not guaranteed identical across GPU architectures. So a workflow that creates its generator on the GPU is reproducible on that machine and nowhere else.
# Portable: the noise is drawn on the CPU and moved to the device. g = torch.Generator(device="cpu").manual_seed(12345) # Fast but machine-local: the stream depends on the CUDA RNG implementation. g = torch.Generator(device="cuda").manual_seed(12345) # The cost of the portable version is one host-to-device copy of a tensor # that is a few hundred kilobytes. It is not a performance decision.
If you publish seeds — in a paper, a model card, a bug report or a workflow file — use the CPU generator, and say so. A seed without the generator device is not a reproducible instruction.
Batches and the shared draw
The behaviour here differs between implementations, and both are defensible, which is why it catches people.
- One draw for the batch. A tensor of shape (4, c, h, w) is drawn in a single call. Image 0 gets the first slice. Generating image 2 alone draws a (1, c, h, w) tensor and gets the first slice, which is image 0’s noise.
- One generator per image. A list of generators, one per batch element, each seeded separately. Now batch position is irrelevant and a seed reproduces alone or in a batch of sixteen. This is the behaviour you want, and most current pipelines accept a list of generators for exactly this reason.
# Per-image generators: batch position no longer affects the result. seeds = [1001, 1002, 1003, 1004] gens = [torch.Generator(device="cpu").manual_seed(s) for s in seeds] images = pipe([prompt] * len(seeds), generator=gens).images # Now regenerating seed 1003 alone gives the same image as the batch did.
A reproducibility recipe
If you need an image to be reproducible months later — for a regression test, for a legal record, for a paper — the seed is the smallest part of the record.
- Pin the weights by hash. Record the SHA-256 of the checkpoint files, not the repository name and not the tag. Tags move.
- Pin the library versions. A lock file, not a requirements line with a caret in it.
- Record the full parameter set. Prompt byte-for-byte, negative prompt, seed, generator device, sampler, schedule, steps, guidance scale, width, height, precision, and any adapters with their weights.
- Choose a deterministic sampler. An ancestral sampler can still be reproducible on identical hardware, but it removes your ability to change step count without changing the image, which makes the record brittle.
- Ask the framework for determinism.
torch.use_deterministic_algorithms(True)makes non-deterministic kernels raise instead of silently varying. It costs speed and it is the only way to find out that you had a non-deterministic operation in the graph. - Record the hardware. GPU model and driver version. You will not reproduce bit-identical output on a different architecture, and knowing that up front saves a day.
What seeds are actually good for
- Controlled comparison. Change exactly one parameter with the seed held fixed, and the difference you see is attributable to that parameter. Without a fixed seed and a deterministic sampler, prompt iteration is guesswork.
- Keeping a composition while changing details. Fix the seed, edit the prompt. The low-frequency structure often survives small prompt edits, which is the cheapest form of compositional control available. It is unreliable for large edits, because a sufficiently different prompt sends the trajectory somewhere else from the first step.
- Seed interpolation. Spherically interpolate between two initial latents to get a sequence of related images. Spherical rather than linear because linear interpolation of two Gaussian samples has smaller norm than either endpoint, which puts the midpoint outside the distribution the model expects.
- A batch as a search. Generating sixteen seeds and selecting is not a failure of control. It is the correct use of a sampler: you are drawing from a distribution and choosing. Fix the seed once you have found the draw you want.