Paper · 2023-05-11 · cs.CV
Exploiting Diffusion Prior for Real-World Image Super-Resolution
StableSR restores real-world low-resolution images with a frozen Stable Diffusion 2.1 by training only a time-aware encoder and scale-and-shift modulation layers. It adds an optional fidelity module (weight w) and Gaussian patch aggregation for arbitrary sizes. Author-reported: best FID (24.44) and CLIP-IQA/MUSIQ on DIV2K-Valid; lower PSNR, SSIM and LPIPS than several baselines.
Image super-resolutionDiffusion modelsLatent diffusionClassifier-free guidancesuper-resolutiondiffusiongenerative-priorimage-restoration
arXivPDFCodeMarkdown19 min read
AI-compiled by claude-haiku-5-5 on 2026-10-19, not yet reviewed by a human. Every claim cites its source; check it when a detail matters.
In plain words
Super-resolution means turning a small, blurry or noisy photo into a sharp large one. Image generators that were trained on huge collections of pictures (here, Stable Diffusion) already know what sharp fur, text and fabric look like. StableSR reuses that knowledge instead of training a super-resolution network from scratch. The big generator stays frozen. A small add-on network looks at the blurry input and nudges the generator at every step of its drawing process, so the result stays faithful to the input.
Two extra controls make the method practical. A single dial lets a user trade realism for faithfulness to the original. A second trick lets the generator handle images of any size by drawing overlapping tiles and blending them. The authors report that the results look more detailed than earlier methods on real photos, while scoring lower on classic pixel-accuracy measures.
TL;DR
- Adapts a frozen text-to-image diffusion model (Stable Diffusion 2.1-base) for blind real-world super-resolution §1 §4.1.
- Trains only a time-aware encoder and scale-and-shift layers; the encoder sees the diffusion step, so its guidance changes over the trajectory §3.1.
- An optional fidelity module (CFW, weight w, default 0.5) and overlapping-patch sampling for arbitrary sizes are added on top §3.2 §3.3.
- Author-reported: best FID and no-reference scores on DIV2K-Valid and the real benchmarks, but lower PSNR, SSIM and LPIPS than several baselines §4.3.
- Costly: 200 sampling steps take 15.16 s for a 512x512 output on one V100; the 4-step SD-Turbo variant takes 0.83 s tab. 5.
Problem
Real-world low-resolution photos have unknown and mixed degradations. Methods that train a super-resolution diffusion model from scratch need large compute and can lose the generative prior of a pre-trained model §1. Methods that steer a frozen diffusion model with hand-built constraints need the degradation to be known, so they generalise poorly §1. Diffusion sampling is also sensitive to image size: it was trained at 512x512, and patch-wise processing leaves seams §3.3.
Method
Stage 1: time-aware encoder with modulation (training). A lightweight encoder reads the low-resolution image as a latent. It is built like the contracting path of the Stable Diffusion U-Net but with fewer channels, and it has a time-embedding layer, so its features depend on the diffusion step §3.1 app. A. Its multi-scale features modulate the residual blocks of the frozen U-Net through an affine transform per spatial position eq. 1. The diffusion weights stay frozen; only the encoder and the modulation layers are trained §3.1.
The authors argue that the encoder should give strong guidance where the signal-to-noise ratio is near 5e-2, which is where image content forms §3.1 fig. 3. Their cosine-similarity measurements peak at the same place §3.1.
Colour correction (inference, optional). Diffusion output can drift in colour. The default corrects each channel's mean and standard deviation to those of the input §3.1 eq. 2. A wavelet variant keeps the high-frequency part of the output and takes the low-frequency part from the input §3.1 eq. 3-5.
Stage 2: controllable feature wrapping (CFW, optional). CFW adds encoder features of the autoencoder into the VAE decoder, scaled by a user weight w in [0, 1]. w near 0 favours realism and w near 1 favours fidelity §3.2 tab. 4. It is trained after stage 1: 100k synthetic 512x512 pairs are made with the stage-1 model, and the autoencoder is trained on them §4.1. Because it needs extra GPU memory, the authors make it optional §3.2.
Aggregation sampling (inference, arbitrary sizes). The latent is cut into overlapping 64x64 patches. At each diffusion step, each patch's noise prediction is blended with Gaussian weights, and overlaps are averaged; then one sampler step is taken on the full latent §3.3 eq. 7 alg. 1. At the right and bottom edges, the overlap is adjusted so the output has the target size app. B. Without this blending, the tiles leave visible seams §3.3 fig. 4.
Negative-prompt guidance (optional). The model was trained with a null prompt, but it reacts to prompts. Classifier-free guidance with a negative prompt and a scale s is described in §5.1 eq. 8. The default (s = 1 with the null prompt) is unchanged.
Fast sampling. DDIM with 20 steps §5.2, and SD-Turbo with 4 steps without further fine-tuning §5.2 fig. 17.
Pre-cleaning (severe degradation, optional). Run an existing restorer first (Real-ESRGAN+ for general images, CodeFormer for faces), then downsample by 2x with bicubic filtering before StableSR app. C.
Equations and algorithm, as in the paper.
\hat{F}^n_{\rm dif} = (1 + \alpha^n) \odot F^n_{\rm dif} + \beta^n; \quad \alpha^n, \beta^n = \mathcal{M}^n_\theta(F^n) eq. 1
y^c = \frac{\hat{y}^c - \mu^c_{\hat{y}}}{\sigma^c_{\hat{y}}} \cdot \sigma^c_x + \mu^c_x eq. 2
L^i = \mathcal{C}_i(L^{i-1}, k), \quad H^i = L^{i-1} - L^i eq. 3
k = \begin{bmatrix} 1/16 & 1/8 & 1/16 \\ 1/8 & 1/4 & 1/8 \\ 1/16 & 1/8 & 1/16 \end{bmatrix} eq. 4
y = H^l_y + L^l_x eq. 5
F_m = F_d + \mathcal{C}(F_e, F_d; \theta) \times w eq. 6
\epsilon_\theta(Z^{(t)}, F, t) = \sum_{n=1}^{M} \frac{w_{\Omega_n}}{\hat{w}} \odot f\left(\epsilon_\theta(Z^{(t)}_{\Omega_n}, F_{\Omega_n}, t)\right), \quad \hat{w} = \sum_n w_{\Omega_n} eq. 7
\tilde{\epsilon}_\theta = \epsilon_\theta(Z^{(t)}, F, c, t) + s\left(\epsilon_\theta(Z^{(t)}, F, [], t) - \epsilon_\theta(Z^{(t)}, F, c, t)\right) eq. 8
Symbols: F is the LR latent feature; F^n_dif the frozen U-Net features at scale n; c a negative prompt; [] the null prompt; f pads a patch to the full grid with zeros; w_{Omega_n} a Gaussian weight map that is zero outside its patch §3.1 §3.2 §3.3 §5.1.
Algorithm 1: progressive patch aggregation alg. 1
# Progressive patch aggregation sampler [alg. 1] [§3.3]
# [code: ldm/models/diffusion/ddpm.py#L2567-L2685]
def aggregated_sample(lr_latent, steps, patch=64, overlap=32):
# Start from Gaussian noise over the whole latent grid [alg. 1]
latent = sample_gaussian(lr_latent.shape)
# Run the sampler from the noisiest step down to the last one [alg. 1]
for step in steps:
# Cut overlapping patches; the last row and column are shifted
# inward so the grid fits the image [app. B]
patches = cut_patches(latent, patch, overlap)
# Predict noise per patch, conditioned on the same LR patch [§3.3]
noise_per_patch = []
for each patch:
noise_per_patch.append(predict_noise(patch, lr_patch, step))
# Blend the patch predictions with Gaussian weights; overlaps
# are divided by the summed weights [eq. 7]
noise = weighted_average(noise_per_patch, gaussian_weights)
# One sampler step with the blended noise [alg. 1]
latent = sampler_step(latent, noise, step)
return latentStage 1 training step §3.1 ddpm.py
# Stage 1 training step: time-aware encoder with modulation [§3.1]
# [code: ldm/models/diffusion/ddpm.py#L2294-L2316]
def training_step(lr_latent, gt_latent, null_text):
# Draw one diffusion step per sample, uniformly [§3.1]
step = sample_uniform_steps(batch_size, num_steps)
# Add Gaussian noise to the clean latent at that step [§3.1]
noise = sample_gaussian(gt_latent)
noisy_latent = add_noise(gt_latent, noise, step)
# Encoder reads the LR latent and the step [§3.1]
struct_features = time_aware_encoder(lr_latent, step)
# Frozen U-Net with trainable modulation layers predicts the noise [§3.1]
predicted = unet(noisy_latent, step, null_text, struct_features)
# Mean squared error on the noise [§4.3]
return mean_squared_error(predicted, noise)Scale-and-shift modulation eq. 1 openaimodel.py
# Modulate one frozen U-Net feature map with encoder features [§3.1] [eq. 1]
def modulate(features, struct_features):
# Small conv net maps the encoder feature to per-pixel scale
# and shift [eq. 1]
scale, shift = small_conv_net(struct_features)
# Affine modulation of the frozen feature [eq. 1]
return (1 + scale) * features + shiftCFW fusion in the decoder eq. 6 model.py
# Controllable feature wrapping inside the VAE decoder [§3.2] [eq. 6]
def cfw_fuse(encoder_features, decoder_features, w):
# Trainable residual from the paired encoder and decoder features [eq. 6]
residual = fusion_block(encoder_features, decoder_features)
# Scale the residual by the user weight w in [0, 1] [eq. 6]
return decoder_features + w * residualColour correction, pixel domain (default) §3.1 eq. 2
# Colour correction in the pixel domain [§3.1] [eq. 2]
# [code: scripts/sr_val_ddim_text_T_negativeprompt_canvas_tile.py#L44-L56]
def match_channel_stats(generated, lr_image):
# Per channel: mean and standard deviation over height and width [eq. 2]
gen_mean, gen_std = channel_stats(generated)
lr_mean, lr_std = channel_stats(lr_image)
# Standardise the output, then give it the input's statistics [eq. 2]
normalised = (generated - gen_mean) / gen_std
return normalised * lr_std + lr_meanColour correction, wavelet (optional) §3.1 eq. 3-5
# Wavelet colour correction [§3.1] [eq. 3-5]
# [code: scripts/wavelet_color_fix.py#L73-L118]
def wavelet_color_fix(generated, lr_image, levels):
# Low-pass the generated image: colour survives, fine detail goes [eq. 3]
low_generated = low_pass(generated, levels)
# Low-pass the LR input the same way [eq. 3]
low_lr = low_pass(lr_image, levels)
# Keep the generated high frequencies, take the LR low frequencies [eq. 5]
return (generated - low_generated) + low_lrNegative-prompt guidance §5.1 eq. 8 ddpm.py
# Classifier-free guidance with a negative prompt [§5.1] [eq. 8]
def guided_noise(latent, step, null_text, negative_text, scale):
# Noise predicted with the null prompt, the default [§5.1]
noise_null = predict_noise(latent, step, null_text)
# Noise predicted with the negative prompt [§5.1]
noise_negative = predict_noise(latent, step, negative_text)
# Scale 1 gives the default; scale 0 gives the negative prompt
# alone [eq. 8]
return noise_negative + scale * (noise_null - noise_negative)Hyperparameters
| Name | Value | Where | Notes |
|---|---|---|---|
| Base diffusion model | Stable Diffusion 2.1-base | §4.1 | Code config for training uses the 512 checkpoint v2-finetune_text_T_512.yaml |
| Frozen / trained parts | U-Net and VAE frozen; time-aware encoder and modulation layers trained | §3.1 | Code freezes all U-Net parameters except names containing "spade" ddpm.py |
| Encoder size | about 105M including modulation layers | §4.1 app. A | Trainable total 149.91M tab. 5 |
| Encoder settings | in 4, model channels 256, out 256, residual blocks 2, dropout 0, channel mult [1, 1, 2, 2], attention at [4, 2, 1], conv resample True, dims 2, fp16 False, heads 4 | app. A | Same values in code v2-finetune_text_T_512.yaml |
| Prompt during training | fixed null prompt | §4.1 | Code default text is '' ddpm.py |
| Training resolution | 512x512 | §4.1 | Code gt_size 512 v2-finetune_text_T_512.yaml |
| Batch size | 192 | §4.1 | Code: 6 per GPU x 4 gradient accumulation v2-finetune_text_T_512.yaml v2-finetune_text_T_512.yaml; 192 total implies 8 GPUs (inferred from the paper's 8 GPUs) |
| Training length | 117 epochs | §4.1 | Code sets max_steps 800000 only v2-finetune_text_T_512.yaml |
| Optimiser | Adam | §4.1 | Code uses AdamW with PyTorch default weight decay, which the paper does not state ddpm.py |
| Learning rate | 5e-5 | §4.1 | Code base_learning_rate 5.0e-05 v2-finetune_text_T_512.yaml |
| Training hardware | 8 x NVIDIA Tesla 32 GB V100 | §4.1 | |
| Diffusion schedule | 1000 steps; linear beta 0.00085 to 0.0120 | not stated in paper | Code v2-finetune_text_T_512.yaml |
| Training step sampling | one uniform index per sample | not stated in paper | Code ddpm.py |
| Sampler (default) | DDPM, 200 steps | §4.1 | Script default is 1000 steps; 200 must be passed sr_val_ddpm_text_T_vqganfin_oldcanvas.py |
| Sampler (fast) | DDIM 20 steps; SD-Turbo 4 steps | §5.2 | Tile script default is 50 steps sr_val_ddim_text_T_negativeprompt_canvas_tile.py |
| Patch size (inference) | 64x64 latent = 512 px | §3.3 app. B | Code tile_size = input_size // 8 sr_val_ddim_text_T_negativeprompt_canvas_tile.py |
| Patch overlap | not stated in paper | §3.3 | Code default 32 latent cells ddpm.py; README example uses 48 at 768 px |
| Gaussian weight | not stated in paper | §3.3 | Code: var 0.01, computed by a formula ddpm.py |
| Pre-upsampling | LR bicubic to the output size, or shorter side 512 for smaller images | §4.2 §4.1 | Code takes the larger of 512 / short side and the upscale factor (default 4) sr_val_ddim_text_T_negativeprompt_canvas_tile.py |
| CFW weight w | 0.5 default; tested 0.0, 0.5, 0.75, 1.0 | §3.2 tab. 4 | Code default 0.5 sr_val_ddim_text_T_negativeprompt_canvas_tile.py |
| CFW training data | 100k synthetic pairs at 512x512 from Real-ESRGAN degradations | §4.1 | |
| CFW adversarial weight | 0.025, described as fixed | §4.1 | Config value 0.025 autoencoder_kl_64x64x4_resi.yaml; see Paper vs code |
| CFW training batch | 1 per GPU, accumulate 8 | not stated in paper | autoencoder_kl_64x64x4_resi.yaml autoencoder_kl_64x64x4_resi.yaml |
| Training data | DIV2K, DIV8K, Flickr2K, OutdoorSceneTraining; plus 5000 FFHQ faces | §4.2 | Code uses 10000 FFHQ faces v2-finetune_text_T_512.yaml |
| Degradation pipeline | Real-ESRGAN pipeline (two blur, resize, noise, JPEG stages) | §4.2 | Config values v2-finetune_text_T_512.yaml |
| Synthetic test set | 3k LR-HR pairs from DIV2K-Valid; LR 128 px, HR 512 px | §4.2 | |
| Real test sets | RealSR, DRealSR (centre crops, 128 px), DPED-iPhone, 40 internet images | §4.2 | |
| Colour correction | pixel domain by default; wavelet optional | §3.1 | Script default "nofix" sr_val_ddim_text_T_negativeprompt_canvas_tile.py; wavelet with 5 levels by default wavelet_color_fix.py |
| Negative-prompt scale s | tested 0, 2.5, 5.0, 7.5 | §5.1 tab. 6 | Off by default; code default scale 7.0 when enabled sr_val_ddim_text_T_negativeprompt_canvas_tile.py |
| Metrics | PSNR and SSIM on Y of YCbCr; LPIPS-Alex; FID; CLIP-IQA; MUSIQ | §4.2 |
Results
Author-reported. Setup: synthetic DIV2K-Valid (3k pairs, 128 to 512 px, Real-ESRGAN degradations), real benchmarks RealSR and DRealSR (centre crops, 128 to 512 px), DPED-iPhone (no reference, so only no-reference metrics) §4.2 §4.3. Baselines are used as released, except LDM, which the authors fine-tuned with the same settings §4.2. The paper's best/second-best colouring (red/blue) is not reproduced here; the text notes that StableSR is best on FID and on the no-reference scores §4.3.
Table 1: quantitative comparison tab. 1
| Dataset | Metric | RealSR | BSRGAN | DASR | Real-ESRGAN+ | FeMaSR | LDM | SwinIR-GAN | IF_III | StableSR |
|---|---|---|---|---|---|---|---|---|---|---|
| DIV2K Valid | PSNR ↑ | 24.62 | 24.58 | 24.47 | 24.29 | 23.06 | 23.32 | 23.93 | 23.36 | 23.26 |
| DIV2K Valid | SSIM ↑ | 0.5970 | 0.6269 | 0.6304 | 0.6372 | 0.5887 | 0.5762 | 0.6285 | 0.5636 | 0.5726 |
| DIV2K Valid | LPIPS ↓ | 0.5276 | 0.3351 | 0.3543 | 0.3112 | 0.3126 | 0.3199 | 0.3160 | 0.4641 | 0.3114 |
| DIV2K Valid | FID ↓ | 49.49 | 44.22 | 49.16 | 37.64 | 35.87 | 26.47 | 36.34 | 37.54 | 24.44 |
| DIV2K Valid | CLIP-IQA ↑ | 0.3534 | 0.5246 | 0.5036 | 0.5276 | 0.5998 | 0.6245 | 0.5338 | 0.3980 | 0.6771 |
| DIV2K Valid | MUSIQ ↑ | 28.57 | 61.19 | 55.19 | 61.05 | 60.83 | 62.27 | 60.22 | 43.71 | 65.92 |
| RealSR | PSNR ↑ | 27.30 | 26.38 | 27.02 | 25.69 | 25.06 | 25.46 | 26.31 | 25.47 | 24.65 |
| RealSR | SSIM ↑ | 0.7579 | 0.7651 | 0.7707 | 0.7614 | 0.7356 | 0.7145 | 0.7729 | 0.7067 | 0.7080 |
| RealSR | LPIPS ↓ | 0.3570 | 0.2656 | 0.3134 | 0.2709 | 0.2937 | 0.3159 | 0.2539 | 0.3462 | 0.3002 |
| RealSR | CLIP-IQA ↑ | 0.3687 | 0.5114 | 0.3198 | 0.4495 | 0.5406 | 0.5688 | 0.4360 | 0.3482 | 0.6234 |
| RealSR | MUSIQ ↑ | 38.26 | 63.28 | 41.21 | 60.36 | 59.06 | 58.90 | 58.70 | 41.71 | 65.88 |
| DRealSR | PSNR ↑ | 30.19 | 28.70 | 29.75 | 28.62 | 26.87 | 27.88 | 28.50 | 28.66 | 28.03 |
| DRealSR | SSIM ↑ | 0.8148 | 0.8028 | 0.8262 | 0.8052 | 0.7569 | 0.7448 | 0.8043 | 0.7860 | 0.7536 |
| DRealSR | LPIPS ↓ | 0.3938 | 0.2858 | 0.3099 | 0.2818 | 0.3157 | 0.3379 | 0.2743 | 0.3853 | 0.3284 |
| DRealSR | CLIP-IQA ↑ | 0.3744 | 0.5091 | 0.3813 | 0.4515 | 0.5634 | 0.5756 | 0.4447 | 0.2925 | 0.6357 |
| DRealSR | MUSIQ ↑ | 26.93 | 57.16 | 42.41 | 54.26 | 53.71 | 53.72 | 52.74 | 30.71 | 58.51 |
| DPED-iPhone | CLIP-IQA ↑ | 0.4496 | 0.4021 | 0.2826 | 0.3389 | 0.5306 | 0.4482 | 0.3373 | 0.2962 | 0.4799 |
| DPED-iPhone | MUSIQ ↑ | 45.60 | 45.89 | 32.68 | 42.42 | 49.95 | 44.23 | 43.30 | 37.49 | 50.48 |
Text claims §4.3: on DIV2K-Valid, StableSR's FID is 7.7% lower than LDM's and at least 32.9% lower than the GAN methods. A user study on 40 internet images with 35 subjects (1400 votes) gave StableSR over 80% of the votes against Real-ESRGAN+, SwinIR-GAN and LDM §4.3 fig. 7.
Table 2: comparison with follow-up ControlNet-style methods tab. 2
| Dataset | Metric | DiffBIR | SUPIR | StableSR |
|---|---|---|---|---|
| RealSR | PSNR ↑ | 25.02 | 23.70 | 24.65 |
| RealSR | SSIM ↑ | 0.6711 | 0.6647 | 0.7080 |
| RealSR | LPIPS ↓ | 0.3568 | 0.3559 | 0.3002 |
| RealSR | CLIP-IQA ↑ | 0.6568 | 0.6619 | 0.6234 |
| RealSR | MUSIQ ↑ | 64.07 | 61.97 | 65.88 |
| DRealSR | PSNR ↑ | 27.20 | 24.86 | 28.03 |
| DRealSR | SSIM ↑ | 0.6721 | 0.6441 | 0.7536 |
| DRealSR | LPIPS ↓ | 0.4274 | 0.4229 | 0.3284 |
| DRealSR | CLIP-IQA ↑ | 0.6293 | 0.6891 | 0.6357 |
| DRealSR | MUSIQ ↑ | 59.87 | 59.70 | 58.51 |
The caption attributes SUPIR's weaker numbers to a resolution mismatch: the test crops are 512 px and SUPIR's prior is 1024 px tab. 2. The text says SUPIR wins on outputs beyond 1024 px §4.3.
Table 3: ablation of time-aware guidance and colour correction tab. 3
| Exp. | Time-aware | Pixel colour | Wavelet colour | RealSR PSNR / DRealSR PSNR | RealSR SSIM / DRealSR SSIM | RealSR LPIPS / DRealSR LPIPS |
|---|---|---|---|---|---|---|
| (a) | no | yes | no | 24.65 / 27.68 | 0.7040 / 0.7280 | 0.3157 / 0.3456 |
| (b) | yes | no | no | 22.24 / 23.86 | 0.6840 / 0.7179 | 0.3180 / 0.3544 |
| (c) | yes | no | yes | 23.38 / 26.80 | 0.6870 / 0.7235 | 0.3157 / 0.3475 |
| Default | yes | yes | no | 24.65 / 28.03 | 0.7080 / 0.7536 | 0.3002 / 0.3284 |
Table 4: effect of the CFW weight w tab. 4
| Dataset | Metric | w = 0.0 | w = 0.5 (default) | w = 0.75 | w = 1.0 |
|---|---|---|---|---|---|
| DIV2K Valid | PSNR ↑ | 22.68 | 23.26 | 24.17 | 23.14 |
| DIV2K Valid | SSIM ↑ | 0.5546 | 0.5726 | 0.6209 | 0.5681 |
| DIV2K Valid | LPIPS ↓ | 0.3393 | 0.3114 | 0.3003 | 0.3077 |
| DIV2K Valid | FID ↓ | 25.83 | 24.44 | 24.05 | 26.14 |
| DIV2K Valid | CLIP-IQA ↑ | 0.6529 | 0.6771 | 0.5519 | 0.6197 |
| DIV2K Valid | MUSIQ ↑ | 65.72 | 65.92 | 59.46 | 64.31 |
| RealSR | PSNR ↑ | 24.07 | 24.65 | 25.37 | 24.70 |
| RealSR | SSIM ↑ | 0.6829 | 0.7080 | 0.7435 | 0.7157 |
| RealSR | LPIPS ↓ | 0.3190 | 0.3002 | 0.2672 | 0.2892 |
| RealSR | CLIP-IQA ↑ | 0.6127 | 0.6234 | 0.5341 | 0.5847 |
| RealSR | MUSIQ ↑ | 65.81 | 65.88 | 62.36 | 64.05 |
| DRealSR | PSNR ↑ | 27.43 | 28.03 | 29.00 | 27.97 |
| DRealSR | SSIM ↑ | 0.7341 | 0.7536 | 0.7985 | 0.7540 |
| DRealSR | LPIPS ↓ | 0.3595 | 0.3284 | 0.2721 | 0.3080 |
| DRealSR | CLIP-IQA ↑ | 0.6340 | 0.6357 | 0.5070 | 0.5893 |
| DRealSR | MUSIQ ↑ | 58.98 | 58.51 | 53.12 | 56.77 |
| DPED-iPhone | CLIP-IQA ↑ | 0.5015 | 0.4799 | 0.3405 | 0.4250 |
| DPED-iPhone | MUSIQ ↑ | 51.90 | 50.48 | 41.81 | 47.96 |
Table 5: complexity, 128 px input, 4x SR, one V100, batch 1, mean of ten runs tab. 5
| Row | Real-ESRGAN+ | FeMaSR | SwinIR-GAN | LDM | IF_III | StableSR | StableSR-Turbo |
|---|---|---|---|---|---|---|---|
| Model type | GAN | GAN | GAN | Diffusion | Diffusion | Diffusion | Diffusion |
| Inference steps | 1 | 1 | 1 | 200 | 200 | 200 | 4 |
| Runtime | 0.08 s | 0.12 s | 0.31 s | 5.25 s | 17.78 s | 15.16 s | 0.83 s |
| Trainable parameters | 16.70M | 28.29M | 28.01M | 113.62M | 473.40M | 149.91M | 149.91M |
Text §4.5 §5.2: StableSR's 200-step runtime is comparable to IF_III and slower than the GAN methods. The 4-step SD-Turbo variant is said to be 6.3 times faster than the 200-step LDM §5.2. DDIM with 20 steps keeps faithful detail, while DDIM on plain LDM loses quality badly §5.2 fig. 17.
Table 6: prompts and guidance scale tab. 6 (positive prompt (1) is "(masterpiece:2), (best quality:2), (realistic:2), (very clear:2)"; (2) is "Good photo."; negative (a) is "3d, cartoon, anime, sketches, (worst quality:2), (low quality:2)"; (b) is "Bad photo.")
| Positive | Negative | s | RealSR PSNR / DRealSR PSNR | RealSR SSIM / DRealSR SSIM | RealSR LPIPS / DRealSR LPIPS | RealSR CLIP-IQA / DRealSR CLIP-IQA | RealSR MUSIQ / DRealSR MUSIQ |
|---|---|---|---|---|---|---|---|
| none (default) | none | - | 24.65 / 28.03 | 0.7080 / 0.7536 | 0.3002 / 0.3284 | 0.6234 / 0.6357 | 65.88 / 58.51 |
| (1) | none | - | 24.68 / 28.03 | 0.7025 / 0.7461 | 0.3151 / 0.3378 | 0.6251 / 0.6370 | 65.34 / 58.07 |
| (2) | none | - | 24.71 / 28.07 | 0.7049 / 0.7500 | 0.3118 / 0.3333 | 0.6219 / 0.6291 | 65.22 / 57.75 |
| none | (a) | 0.0 | 24.80 / 28.18 | 0.7097 / 0.7562 | 0.3105 / 0.3316 | 0.6176 / 0.6224 | 64.86 / 57.31 |
| none | (a) | 2.5 | 24.41 / 27.76 | 0.6972 / 0.7383 | 0.3168 / 0.3417 | 0.6306 / 0.6422 | 66.02 / 59.21 |
| none | (a) | 5.0 | 23.96 / 27.21 | 0.6829 / 0.7188 | 0.3267 / 0.3583 | 0.6356 / 0.6558 | 66.84 / 61.07 |
| none | (a) | 7.5 | 23.53 / 26.68 | 0.6673 / 0.7003 | 0.3399 / 0.3774 | 0.6323 / 0.6621 | 67.26 / 62.41 |
| none | (b) | 0.0 | 24.77 / 28.13 | 0.7067 / 0.7520 | 0.3100 / 0.3317 | 0.6184 / 0.6239 | 64.81 / 57.27 |
| none | (b) | 2.5 | 24.46 / 27.90 | 0.7017 / 0.7467 | 0.3170 / 0.3371 | 0.6303 / 0.6409 | 66.29 / 58.97 |
| none | (b) | 5.0 | 24.13 / 27.61 | 0.6958 / 0.7391 | 0.3240 / 0.3467 | 0.6377 / 0.6490 | 67.43 / 60.69 |
| none | (b) | 7.5 | 23.78 / 27.30 | 0.6894 / 0.7310 | 0.3320 / 0.3578 | 0.6421 / 0.6583 | 68.13 / 62.12 |
Text §5.1: higher guidance raises CLIP-IQA and MUSIQ (sharper output), but s = 7.5 can over-sharpen fig. 16.
Paper vs code
Code: official repository IceClear/StableSR at commit 398ee9383777e255540ea027a704c8ce1f32145b, licensed under the S-Lab License 1.0, which permits non-commercial use only. Nothing from the code is quoted here; code is cited by path and line only.
- Optimiser. Paper: Adam §4.1. Code: AdamW ddpm.py.
- Parameterisation. Paper: loss is l2 on the noise §4.3. Code, 512 config: the v-parameterisation line is commented out, so noise prediction is used v2-finetune_text_T_512.yaml. The released 768 config sets parameterisation "v" v2-finetune_text_T_768v.yaml. The released 768 checkpoint therefore is not the paper's model.
- Ground-truth sharpening. Paper: not mentioned. Code, 512 config: unsharp-mask sharpening of ground truth is on v2-finetune_text_T_512.yaml ddpm.py; the 768 config turns it off v2-finetune_text_T_768v.yaml.
- FFHQ faces. Paper: 5000 images §4.2. Code: num_face 10000 v2-finetune_text_T_512.yaml, taking the first sorted files realesrgan_dataset.py.
- Modulation layer. Paper: scale-and-shift per position, (1 + alpha) * F + beta eq. 1. Code: a SPADE block, with normalisation followed by (1 + gamma) * x + beta, where gamma and beta come from a 3x3 conv with 128 hidden channels spade.py. It is applied after the block's output convolution and before the skip connection openaimodel.py. The code comment calls this a built-in SPADE component used instead of SFT, with no significant effect expected openaimodel.py.
- CFW fusion block. Paper: a convolution stack C(F_e, F_d) scaled by w eq. 6. Code: a residual block, two RRDB blocks and another residual block model.py. Fusion is applied only at the two middle decoder resolutions, not at the lowest or the highest one (with the 512 autoencoder's four levels) model.py.
- CFW adversarial weight. Paper: fixed 0.025, not self-adjusting §4.1. Code: the weight is the 0.025 multiplier times an adaptive ratio of gradient norms (norm of NLL gradient over norm of GAN gradient, clamped to 1e4) contperceptual.py contperceptual.py. If that ratio raises, the fallback is 1.0 times the 0.025 weight contperceptual.py.
- Wavelet colour correction. Paper: y = H^l_y + L^l_x, with a single high-frequency band H^l eq. 5. Code: the high frequencies are the sum over all bands, which telescopes to generated minus its low-pass wavelet_color_fix.py. Paper dilation for scale i is 2^i with i from 1 to l §3.1; code uses radius 2^i for i from 0 to 4, so its first level has dilation 1 wavelet_color_fix.py. Code's level count (5) is not in the paper.
- Default colour correction. Paper: pixel domain is the default §3.1. Script default: "nofix" (none) sr_val_ddim_text_T_negativeprompt_canvas_tile.py. The help text says AdaIN is the paper's method; the paper's formula is per-channel mean and std matching, which is the same operation sr_val_ddim_text_T_negativeprompt_canvas_tile.py. The README example uses wavelet.
- DDPM step count. Paper: 200 steps §4.1. Script default: 1000 sr_val_ddpm_text_T_vqganfin_oldcanvas.py. The code keeps the encoder's timestep as the original index, even with fewer steps ddpm.py.
- Gaussian weights. Paper: Gaussian filter, with no parameters given §3.3. Code: variance 0.01, with the exponent scaled by the squared tile width, so the effective spread is about 0.1 times the tile width (6.4 latent cells for 64). The vertical midpoint is H/2 rather than (H-1)/2 ddpm.py.
- Overlap at the edges. Paper: the last row and column are shifted to fit §3.3 app. B. Code: the same, with the offsets set to fit the image ddpm.py.
- Decoder-stage tiling. Not in the paper. For inputs above 1280 px, the scripts decode tiles in pixel space with stride 1000 and blend them with Gaussian weights sr_val_ddim_text_T_negativeprompt_canvas_tile.py util_image.py util_image.py.
- Input preparation. Paper: LR is upsampled to the output size; images under 512 px get a shorter side of 512 §4.1 §4.2. Code: bicubic upscaling by the larger of 512 divided by the short side and the upscale factor (default 4), padding to a multiple of 32, and a resize back at the end sr_val_ddim_text_T_negativeprompt_canvas_tile.py.
- Training length. Paper: 117 epochs §4.1. Code: max_steps 800000 with no epoch count v2-finetune_text_T_512.yaml.
- Negative-prompt guidance. Matches eq. 8: unconditional term from the negative prompt, conditional from the null prompt ddpm.py.
- Encoder and frozen weights. Matches §3.1: only encoder and modulation layers train ddpm.py.
Pitfalls
- Which modulation layer. The paper says SFT. The code uses SPADE with a convolutional gamma and beta, plus an extra normalisation step. An implementation built from the paper's equation alone will differ from the released model.
- CFW is under-specified in the paper. The paper's one-line formula does not give the fusion block's depth, its placement in the decoder, or its normalisation. Follow the code if the goal is to reproduce the release.
- Adversarial weight. The paper's 0.025 is fixed; the code's is adaptive (see above). Copying the 0.025 into a plain GAN loss will change training.
- Wavelet correction. The paper's equation keeps one band; the code keeps all bands, which is a different output. Also check the dilation offset and the number of levels (not given in the paper).
- Checkpoints. The paper's model is SD 2.1-base at 512 px with noise prediction. The released 768 model and its config use v-prediction and 768 px. Results will not transfer one-to-one.
- Step count. The paper's 200 DDPM steps are not the script default; the README's 768 example uses DDIM with 20 steps and passes dec_w 0.0, not the default 0.5.
- Fidelity dial. w = 0 gives the best perceptual scores; w = 0.75 gives the best PSNR and SSIM on several sets; w = 0.5 is the default because it balances the two §3.2 tab. 4. CFW needs extra GPU memory.
- Negative prompts. Off by default. Turning them on raises CLIP-IQA and MUSIQ but lowers PSNR and SSIM, and s = 7.5 over-sharpens tab. 6 fig. 16.
- Cost. Training from scratch needed 2.06 times the GPU memory of StableSR in the authors' test §4.4. The 200-step sampler is 15.16 s per 512 px output on one V100 tab. 5. Tiled sampling multiplies the number of network calls by the number of tiles at every step.
- Metrics. The paper argues that PSNR, SSIM and LPIPS favour methods trained with l2 or perceptual losses, and that diffusion models are not trained that way §4.3. This is the authors' conjecture, not a measured result. Compare on FID, CLIP-IQA and MUSIQ with care when reproducing.
- Failure cases. Small text, faces and fine patterns are weak because the diffusion prior has the same limits §6 fig. 18. A face-only variant was trained by fine-tuning on FFHQ for 50 epochs with CodeFormer's degradations app. C.
- Unstated values. Overlap size, Gaussian width, sampler schedule, weight decay and the exact number of CFW training steps are not in the paper.
Lineage
- Builds on: Stable Diffusion, a latent diffusion model (Rombach et al., CVPR 2022), which supplies the frozen prior and the autoencoder §1 §3.
- Builds on: CodeFormer (Zhou et al., NeurIPS 2022) for the fidelity-control idea of CFW §3.2.
- Builds on: the mixture-of-diffusers aggregation of Jiménez (2023) for overlapping-patch sampling §3.3.
- Builds on: Real-ESRGAN (Wang et al., 2021) for the training degradation pipeline §4.2.
- Compared against: BSRGAN, Real-ESRGAN+, RealSR, DASR, FeMaSR, SwinIR-GAN, LDM (fine-tuned by the authors), DeepFloyd IF stage III, ControlNet-tile (Zhang et al., ICCV 2023) §4.3, and DiffBIR and SUPIR as follow-up work tab. 2.
- Superseded by: not known from the content already in this wiki.
Source and license
Adapted from “Exploiting Diffusion Prior for Real-World Image Super-Resolution” by Jianyi Wang, Zongsheng Yue, Shangchen Zhou, Kelvin C. K. Chan, Chen Change Loy (arXiv:2305.07015), used under CC BY 4.0. Changes: condensed and restructured for implementation, with tables, equations and algorithms taken from the paper and its code, each cited to its source. Not affiliated with or endorsed by the authors. Code citations refer to https://github.com/IceClear/StableSR at commit 398ee9383777 (S-Lab License 1.0 (non-commercial)). Any quoted code is shown under that license.