Serverless GPU image super-resolution powered by Real-ESRGAN with optional GFPGAN face restoration. Accepts image URLs or base64 inputs, lazy-downloads model weights to disk on first use, supports tile mode for huge images, and returns base64-encoded PNG / JPG / WebP outputs.
- Multiple Real-ESRGAN models — photo, anime, anime-video, general/denoise — selected per request.
- Optional GFPGAN face restoration — set
face_enhance: trueto clean up faces. - Tile mode — split the image into overlapping tiles when GPU memory is tight or the image is large.
- Adjustable denoise — for
realesr-general-x4v3, mix the main and WDN weights viadenoise_strength. - Flexible inputs —
image_url,image_urls[],image_b64, or the typedimages[]list (mix URLs and base64 in one call). - PNG / JPG / WebP outputs, with
jpeg_qualityknob. - Per-item error isolation — one bad input doesn't fail the whole batch.
- Half-precision (fp16) on supported GPUs for ~2x speed at minimal quality cost.
- Weight caching — first request downloads from GitHub releases, subsequent ones load instantly.
| Model | Native scale | Type | Best for |
|---|---|---|---|
RealESRGAN_x4plus |
4x | photo / general | Real-world photos, default photo upscaler |
RealESRGAN_x2plus |
2x | photo / general | When you only need 2x — faster, smaller output |
RealESRGAN_x4plus_anime_6B |
4x | anime / illustration | Drawings, manga panels, anime stills |
realesr-animevideov3 |
4x | anime video | Anime video frames — smaller / faster compact arch |
realesr-general-x4v3 |
4x | photo / general | General-purpose; supports denoise_strength blending |
Weights are fetched on demand from the official xinntao/Real-ESRGAN releases into $WEIGHTS_DIR (default /root/weights). GFPGAN v1.3 weights are fetched from the TencentARC/GFPGAN release the first time face_enhance: true is used.
image_url(string) — single image URL.image_urls(string[]) — list of image URLs.image_b64(string) — raw base64, or adata:image/...;base64,...data URI.images(Image[]) — list of typed image inputs:{"type": "url", "data": "https://example.com/img.jpg"}{"type": "b64", "data": "<base64 or data URI>"}
All sources can be combined in a single request — they're processed in order: image_urls → image_url → image_b64 → images[].
| Field | Type | Default | Description |
|---|---|---|---|
model |
string | realesr-general-x4v3 (or $REALESRGAN_MODEL) |
One of the keys in the model table above |
scale |
float | model's native scale | Output multiplier vs. the input image |
tile |
int | 0 |
Tile size in pixels; 0 disables tiling |
tile_pad |
int | 10 |
Padding between tiles (to hide seams) |
pre_pad |
int | 0 |
Pre-padding before tiling |
fp16 |
bool | true |
Use half precision (auto-disabled on CPU) |
face_enhance |
bool | false |
Apply GFPGAN face restoration |
denoise_strength |
float | null |
realesr-general-x4v3 only: 0=full WDN/clean, 1=full main; controls dni_weight |
output_format |
string | "png" |
"png" / "jpg" / "webp" |
jpeg_quality |
int | 95 |
Used for JPG and WebP encoders |
max_input_pixels |
int | 16777216 (4096*4096) |
Reject inputs larger than this |
1. Simple photo upscale (x4 default):
{
"image_url": "https://example.com/photo.jpg",
"model": "RealESRGAN_x4plus"
}2. Anime illustration x4:
{
"image_url": "https://example.com/anime.png",
"model": "RealESRGAN_x4plus_anime_6B",
"scale": 4,
"output_format": "png"
}3. Portrait with face restoration:
{
"image_url": "https://example.com/portrait.jpg",
"model": "RealESRGAN_x4plus",
"scale": 2,
"face_enhance": true,
"output_format": "jpg",
"jpeg_quality": 95
}4. Huge image with tile mode (low-VRAM safe):
{
"image_url": "https://example.com/4k_photo.jpg",
"model": "realesr-general-x4v3",
"scale": 2,
"tile": 256,
"tile_pad": 10,
"max_input_pixels": 33554432
}5. Denoise blend (only for realesr-general-x4v3):
{
"image_url": "https://example.com/noisy.jpg",
"model": "realesr-general-x4v3",
"scale": 4,
"denoise_strength": 0.5
}6. Multi-image batch (URLs and base64 mixed):
{
"images": [
{"type": "url", "data": "https://example.com/a.jpg"},
{"type": "url", "data": "https://example.com/b.png"},
{"type": "b64", "data": "data:image/png;base64,iVBORw0KG..."}
],
"model": "realesr-general-x4v3",
"scale": 2,
"output_format": "webp",
"jpeg_quality": 85
}7. Base64-in / WebP-out (no public URL needed):
{
"image_b64": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEA...",
"model": "RealESRGAN_x4plus",
"scale": 4,
"output_format": "webp",
"jpeg_quality": 90
}Real-ESRGAN allocates intermediate tensors proportional to the input image area × scale². For a 2048×2048 input at x4 in fp16, that comfortably fits on a 24GB RTX 4090, but smaller GPUs (12GB) will OOM. Tile mode is the escape hatch: it splits the image into N×N tiles, upscales each, and stitches them back with tile_pad overlap to hide seams.
Rules of thumb for tile:
| GPU VRAM | Suggested tile |
|---|---|
| 24 GB | 0 (no tiling) for ≤ 2048², 512 for larger |
| 16 GB | 512 |
| 12 GB | 400 |
| 8 GB | 256 |
| 6 GB | 192 |
| CPU | 128 (slow but works) |
tile_pad of 10–32 is normal; higher hides seams better at slight cost.
face_enhance: true runs GFPGAN v1.3 on top of the Real-ESRGAN result. GFPGAN:
- detects faces with
facexlib, - crops, aligns, and runs each face through a face-restoration GAN,
- pastes the restored faces back into the upscaled background.
This makes a dramatic difference on low-resolution / blurry portraits, but adds ~0.5–2s per face and downloads ~350MB of weights the first time. Use it for portraits, group photos, and anything where faces are the focal point.
- Push this repo to GitHub.
- Connect it to Runpod Hub at https://console.runpod.io/hub.
- Runpod auto-builds the Dockerfile and runs
.runpod/tests.jsonend-to-end before publishing. - Choose a GPU type — RTX 4090, A100, H100 all work. For face_enhance pipelines, 24GB+ is recommended.
docker build -t runpod-realesrgan .
docker tag runpod-realesrgan <your-registry>/runpod-realesrgan:latest
docker push <your-registry>/runpod-realesrgan:latest
# then create a serverless endpoint from that image in the Runpod console| Var | Default | Purpose |
|---|---|---|
WEIGHTS_DIR |
/root/weights |
Where downloaded .pth files are cached |
REALESRGAN_MODEL |
realesr-general-x4v3 |
Default model when model is omitted from the request |
PYTHONUNBUFFERED |
1 |
Real-time log flushing |
The included test_handler.py runs on CPU without downloading any weights or GPUs — it stubs out realesrgan, gfpgan, basicsr, cv2, torch, and torchvision via sys.modules injection before importing handler. The mocked upsampler simply resizes the input by outscale, so tests verify end-to-end shape, format, and dispatch correctness.
python3 test_handler.py
# ALL TESTS PASSEDYou only need Pillow, numpy, and requests installed locally:
pip install Pillow numpy requests
python3 test_handler.pyIf you use this in a published work, please cite the original Real-ESRGAN paper:
@inproceedings{wang2021realesrgan,
title={{Real-ESRGAN}: Training Real-World Blind Super-Resolution with Pure Synthetic Data},
author={Wang, Xintao and Xie, Liangbin and Dong, Chao and Shan, Ying},
booktitle={International Conference on Computer Vision Workshops (ICCVW)},
year={2021}
}And GFPGAN if you use face_enhance:
@inproceedings{wang2021gfpgan,
title={Towards Real-World Blind Face Restoration with Generative Facial Prior},
author={Wang, Xintao and Li, Yu and Zhang, Honglun and Shan, Ying},
booktitle={CVPR},
year={2021}
}Code in this repo is provided as-is for educational use. Real-ESRGAN weights are licensed under BSD 3-Clause; GFPGAN weights under Apache 2.0. Please respect those terms.
{ "results": [ { "index": 0, "input": {"kind": "url", "url": "https://..."}, "image_b64": "iVBORw0KGgo...", // base64-encoded output "mime": "image/png", "width": 960, "height": 720, "input_width": 240, "input_height": 180, "scale": 4.0, "model": "RealESRGAN_x4plus", "face_enhance": false, "elapsed_sec": 1.83 } // ... or {"index": N, "input": {...}, "error": "..."} if that item failed ], "model": "RealESRGAN_x4plus", "scale": 4.0, "tile": 0, "tile_pad": 10, "pre_pad": 0, "fp16": true, "face_enhance": false, "denoise_strength": null, "output_format": "png", "jpeg_quality": 95, "max_input_pixels": 16777216, "count": 1, "elapsed_sec": 1.84, "cuda_available": true }