How to Implement Efficient Batch Generation with Multiple Prompts in Stable Diffusion
Stable Diffusion achieves efficient batch generation by loading the model once, stacking heterogeneous prompts into a single conditioning tensor, and executing vectorized diffusion steps that process all prompts in parallel within one forward pass.
The CompVis/stable-diffusion repository separates inference into distinct phases that maximize GPU utilization when generating multiple images. By understanding how scripts/txt2img.py orchestrates model loading, conditioning preparation, and sampler execution, you can implement pipelines that generate dozens of images from different prompts without Python-level iteration overhead.
Core Architecture for Batch Generation
Efficient batch generation relies on three architectural separations implemented in the reference inference script.
Model Loading and Device Placement
The model is instantiated once via load_model_from_config and moved to CUDA using model.to(device) before any sampling occurs. This prevents the expensive weight transfer overhead from recurring across batches. According to the source code in scripts/txt2img.py (lines 45-52), this initialization happens outside the main generation loop, ensuring the same GPU memory allocation persists throughout the session.
from ldm.util import instantiate_from_config
import torch
# Load once, reuse indefinitely
config = OmegaConf.load(config_path)
model = instantiate_from_config(config.model)
model.load_state_dict(torch.load(ckpt_path)["state_dict"], strict=False)
model.cuda().eval()
Batch Conditioning Preparation
Prompts are transformed into latent conditioning vectors using model.get_learned_conditioning. For classifier-free guidance, the script builds two tensors: a batch of encoded prompts (c) and a corresponding batch of empty prompts (uc). In scripts/txt2img.py (lines ~990-1002), these are constructed as:
# For N different prompts
c = model.get_learned_conditioning(prompts) # Shape: (N, C, H//f, W//f)
uc = model.get_learned_conditioning([""] * len(prompts)) # Matching unconditioned
This stacking allows the U-Net to process the entire heterogeneous batch in parallel during the diffusion process.
Vectorized Sampler Execution
All samplers (DDIMSampler, PLMSSampler, DPMSolverSampler) accept a batch_size parameter that dictates how many latent samples to denoise simultaneously. The call to sampler.sample in scripts/txt2img.py (lines 1003-1011) passes opt.n_samples or the dynamic batch size, enabling the diffusion step to operate vectorized over the batch dimension rather than looping per prompt.
samples, _ = sampler.sample(
S=ddim_steps,
conditioning=c,
batch_size=len(prompts), # Vectorized over this dimension
shape=shape,
unconditional_guidance_scale=scale,
unconditional_conditioning=uc,
eta=ddim_eta,
)
Handling Multiple Prompts in a Single Batch
The reference implementation supports two modes for feeding prompts into the conditioning pipeline.
Single Prompt Duplication
When using the --prompt argument, the script duplicates the same string batch_size times to create a homogeneous batch. In scripts/txt2img.py (lines 66-73), this appears as data = [batch_size * [prompt]], resulting in identical conditioning vectors for every sample in the batch.
Heterogeneous Prompt Batches from File
For diverse prompts, the --from-file mode reads a text file line-by-line and chunks the list into batches of size batch_size. As implemented in scripts/txt2img.py (lines 74-78), this uses list(chunk(data, batch_size)) to create groups where each element is a different prompt. When a chunk contains N distinct prompts, the sampler receives a conditioning tensor of shape (N, C, H//f, W//f), yielding N different latent trajectories simultaneously.
# prompts.txt contains one prompt per line
python scripts/txt2img.py \
--from-file prompts.txt \
--n_samples 4 \
--n_iter 1
Performance Optimization Techniques
Several optimizations within the inference loop reduce memory bandwidth and computational overhead.
Memory Management with torch.no_grad and Autocast
The sampling block wraps execution in torch.no_grad() and autocast contexts to disable gradient tracking and enable mixed-precision computation. In scripts/txt2img.py (lines ~889-892), this appears as:
with torch.no_grad():
with precision_scope("cuda"): # autocast when opt.precision=="autocast"
with model.ema_scope():
samples = sampler.sample(...)
This reduces VRAM usage and accelerates matrix multiplications without altering output quality.
EMA Weight Reuse
The model.ema_scope() context manager (lines ~891-894) applies exponential-moving-average weights that are more stable for inference. By loading these weights directly onto the model rather than maintaining separate copies, the implementation avoids doubling GPU memory consumption.
Fixed Latent Seeds
Setting --fixed_code reuses the same random start tensor (start_code) across batches, avoiding redundant torch.randn calls and improving cache locality. This is implemented in scripts/txt2img.py (lines 84-87) where the start code is generated once before the loop if the flag is enabled.
Fast Sampler Selection
The DPMSolverSampler (available in ldm/models/diffusion/dpm_solver/dpm_solver.py) typically delivers the fastest throughput. Switching from DDIM to DPMSolver can reduce per-batch runtime by approximately 30% on modern GPUs, as noted in the conditional sampler initialization block (lines 51-57 of txt2img.py).
Implementation Examples
CLI Batch Generation
The most common approach uses the provided script with a prompt file to process multiple distinct prompts efficiently:
# Generate 8 images per prompt across three different prompts
python scripts/txt2img.py \
--from-file prompts.txt \
--n_samples 8 \
--n_iter 1 \
--ddim_steps 30 \
--scale 7.5 \
--dpm_solver
Where prompts.txt contains:
a photorealistic sunrise over mountains
an astronaut riding a horse in space
a cyberpunk city at night, neon lights
This creates three inference iterations, each processing one prompt with a batch size of 8, outputting 24 total images.
Python API for Notebook Integration
For programmatic use in notebooks or services, bypass the CLI and interact directly with the model classes:
from omegaconf import OmegaConf
from ldm.util import instantiate_from_config
from ldm.models.diffusion.ddim import DDIMSampler
import torch
# 1. Load model (once)
config = OmegaConf.load("configs/stable-diffusion/v1-inference.yaml")
model = instantiate_from_config(config.model)
model.load_state_dict(
torch.load("models/ldm/stable-diffusion-v1/model.ckpt", map_location="cpu")["state_dict"],
strict=False
)
model.cuda().eval()
# 2. Initialize sampler
sampler = DDIMSampler(model) # Or DPMSolverSampler for speed
# 3. Prepare heterogeneous batch
prompts = [
"a medieval castle on a cliff",
"a futuristic robot playing violin",
"portrait of a tiger in renaissance style"
]
c = model.get_learned_conditioning(prompts)
uc = model.get_learned_conditioning([""] * len(prompts))
# 4. Execute vectorized diffusion
shape = [4, 64, 64] # latent channels and downsampled spatial dims
with torch.no_grad():
with torch.autocast("cuda"):
samples, _ = sampler.sample(
S=50,
conditioning=c,
batch_size=len(prompts),
shape=shape,
unconditional_guidance_scale=7.5,
unconditional_conditioning=uc,
eta=0.0,
)
# 5. Decode latents to RGB
imgs = model.decode_first_stage(samples)
imgs = torch.clamp((imgs + 1.0) / 2.0, min=0.0, max=1.0)
Scaling to Large Batches
To maximize throughput on high-VRAM GPUs, combine mixed precision with reduced sampling steps:
opt = {"precision": "autocast", "batch_size": 32}
with torch.autocast("cuda"):
samples, _ = sampler.sample(
S=30, # Reduced steps for speed
conditioning=c,
batch_size=opt["batch_size"],
shape=shape,
unconditional_guidance_scale=7.0,
eta=0.0,
)
Monitor GPU memory usage; the limiting factor is the size of the latent tensor (batch_size, 4, height//8, width//8) and the activation maps during U-Net forward passes.
Summary
- Load once: Initialize
modelandsampleroutside generation loops to avoid repeated I/O and memory allocation overhead. - Stack conditioning: Use
model.get_learned_conditioning(prompt_list)to create heterogeneous batches rather than iterating in Python. - Vectorize sampling: Pass the full batch size to
sampler.sample()so the diffusion process runs in a single forward pass per timestep. - Optimize memory: Wrap inference in
torch.no_grad()andautocastcontexts, and usemodel.ema_scope()for efficient weight application. - Choose samplers wisely: Prefer
DPMSolverSamplerover DDIM for throughput-critical applications.
Frequently Asked Questions
What is the maximum batch size for Stable Diffusion?
The maximum batch size is constrained by available VRAM. A standard 24GB GPU can typically handle batch sizes of 8-16 at 512×512 resolution using FP16 mixed precision. Larger batches require reducing resolution, using gradient checkpointing (though disabled during inference), or employing model parallelism across multiple GPUs.
Does batch generation reduce per-image quality?
No, batch generation does not reduce quality. Each image in the batch receives independent random noise initialization (unless --fixed_code is set) and distinct conditioning vectors. The vectorized computation is mathematically identical to sequential generation, merely executed in parallel within the GPU's tensor cores.
Can I mix different image sizes in one batch?
No, all images in a batch must share the same latent dimensions because the U-Net operates on fixed-size tensors. To generate images at varying resolutions, you must either pad smaller latents to match the largest or process different sizes in separate batches. The shape parameter passed to sampler.sample() applies uniformly to the entire batch.
Which sampler is fastest for batch generation?
DPMSolverSampler (implemented in ldm/models/diffusion/dpm_solver/dpm_solver.py) generally offers the best throughput, requiring 20-30 steps versus 50+ for DDIM to achieve comparable quality. PLMS (PLMSSampler) provides a middle ground. The speed advantage comes from fewer function evaluations per image, compounding savings when multiplied across large batch sizes.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →