---
category: 17-upscaling-refinement
angle: 17e
title: "Deploying upscalers & refiners in production: ONNX/CoreML/TensorRT, WebGPU, serverless GPU, and hybrid patterns"
subagent: 17e
date: 2026-04-19
tags:
  - deployment
  - onnx
  - coreml
  - tensorrt
  - webgpu
  - webnn
  - transformers.js
  - modal
  - replicate
  - runpod
  - comfyui
  - upscaling
  - cost-latency
status: draft
---

# Deploying upscalers & refiners in production

## Executive Summary

Shipping Real-ESRGAN, SwinIR, HAT, SUPIR, or an SDXL refiner inside a real product means picking a deployment substrate that balances **cold-start latency, per-image cost, device reach, and privacy**. No single substrate wins on all four axes, so production systems almost always end up **hybrid**: cheap, small assets run on the user's device (WebGPU / CoreML / desktop ncnn), and heavy assets — 4× SUPIR, SDXL refiner passes, 8K logo upscales — run on serverless GPU (Modal, Replicate, Runpod, Fal).

Three findings dominate the 2025–2026 landscape:

1. **WebGPU is viable for tiles, not for whole images.** `onnxruntime-web` with the WebGPU EP and `transformers.js v3` can run Swin2SR x2/x4 and Real-ESRGAN in the browser, but model-load times (10–16 s for Swin2SR on an M1 Max) and per-image runtimes (25 s+ on a 2020 MacBook Air) mean WebGPU is realistically for **thumbnails, preview refinement, and in-editor fixups**, not batch production.[^tjs-1407][^tjs-ex1]
2. **Cold start, not hourly rate, decides serverless economics.** Modal hits **sub-3-second cold starts** for ComfyUI workflows via CPU pre-hydration snapshots + `min_containers`. Replicate's generic cold start is 15–60 s on A10G and 20–90 s on A100. Runpod FlashBoot claims 48% of serverless starts under 200 ms.[^modal-3s][^markaicode-1][^introl]
3. **Hosted upscale is already a commodity at fractions of a cent.** `nightmareai/real-esrgan` on Replicate is **$0.0025/run on T4** (~400 runs/$1, ~12 s wall clock). For burst asset generation from a prompt-to-asset skill, that is cheaper than paying Modal's warm-pool idle time unless QPS is sustained.[^replicate-realesrgan]

The rest of this document maps these substrates onto a deployment matrix, a client-vs-server decision tree, a WebGPU reality check, and concrete patterns for a prompt-enhancement product that fans out many small asset requests per user session.

## Deployment Matrix

Comparing the realistic deployment substrates for a 4× upscaler / refiner in a production asset pipeline:

| Substrate | Runtime | Typical cold start | Typical wall-clock (1024→4096) | Per-image cost | Best for |
|---|---|---|---|---|---|
| **Browser WebGPU** (`onnxruntime-web`, `transformers.js`) | WebGPU compute shaders | Model download: 30–200 MB, 5–20 s on first visit | 10–60 s (Swin2SR x4) | $0 compute, user's device | Previews, in-editor fixups, privacy-first flows |
| **Browser WebNN** (Chrome 122+, Safari 18+ macOS) | CoreML / DirectML / OpenVINO under the hood | Similar to WebGPU first-load, then cached | 2–10× faster than WebGPU on supported ops | $0 compute | When the op set is covered and you can gate by browser |
| **Desktop (Upscayl / ComfyUI portable / Pinokio)** | ncnn+Vulkan or PyTorch+CUDA | Instant (already installed) | 1–10 s with a discrete GPU | $0 | Pro users, local-first, 8K/SUPIR runs |
| **iOS/macOS app (CoreML)** | CoreML w/ Neural Engine | <1 s after first convert | 0.3–2 s for 4× Real-ESRGAN on M-series | $0 | Native apps, on-device privacy, offline |
| **Serverless GPU — Replicate** | Docker + Cog | 15–90 s (A10G/A100) | 12–25 s for Real-ESRGAN | $0.0025–$0.029/run | Zero-ops prototyping, rare/spiky traffic |
| **Serverless GPU — Modal** | Python-first, custom containers | 2–15 s typical; **<3 s with snapshots** | 5–20 s for ESRGAN/SwinIR | ~$2.10/h A100, per-second billing | Production apps that need reliable p95 |
| **Serverless GPU — Runpod** | Docker, serverless endpoints | <200 ms (FlashBoot, 48% of starts); 3–6 s custom | 5–20 s | ~$1.89/h A100, lowest hourly | Steady-state, cost-sensitive workloads |
| **Dedicated GPU VM** | Anything you want | No cold start (always on) | 2–10 s | $1.5–$4/h fixed | Sustained 24/7 load, always-warm SLAs |

Notes on the numbers:

- Replicate pricing is taken directly from the two public Real-ESRGAN deployments: `nightmareai/real-esrgan` at **$0.0025/run on T4** and `replicategithubwc/real-esrgan` at **$0.029/run on A100 80GB**.[^replicate-realesrgan] The 10× difference is entirely GPU choice; for classic ESRGAN, T4 is enough.
- Modal's cold-start claim assumes you've used memory snapshots (their `@app.function(enable_memory_snapshot=True)` pattern) and you've forced initialization to happen with `torch.cuda.is_available()` patched to `False` so that GPU-binding work moves to the post-snapshot path.[^modal-3s]
- Runpod's sub-200 ms claim is specific to **FlashBoot** serverless and only holds for 48% of starts; the tail is more like 3–6 s for custom containers.[^introl]
- WebGPU wall-clock is from `web-realesrgan` (TF.js+WebGPU) reporting ~0.5× the speed of local CPU and from `transformers.js#1407` reporting 10–16 s model load alone for Swin2SR x4 on an M1 Max.[^tjs-1407][^webrealesrgan]

## Client-vs-Server Decision Tree

Use this as the default routing logic in a prompt-enhancement product that emits assets:

```
┌───────────────────────────────────────────────┐
│ Request enters: "upscale asset X to NxN, ..." │
└──────────────────────┬────────────────────────┘
                       │
          ┌────────────┴────────────┐
          │ Target resolution ≤ 2K  │
          │ AND model ≤ 30MB ONNX   │
          │ AND user on Chrome/Edge │
          │   or Safari 18+ macOS   │
          └────────┬────────────────┘
               Yes │        │ No
                   ▼        ▼
        ┌───────────────┐   ┌──────────────────────────┐
        │ Run in browser│   │ Is the asset "heavy"?     │
        │ WebGPU /      │   │ (SUPIR, 4× of 2K+,        │
        │ WebNN         │   │  SDXL refiner, ≥8K)       │
        └───────────────┘   └─────────┬────────────────┘
             │                    Yes │        │ No
             │                        ▼        ▼
             │             ┌──────────────┐   ┌────────────────┐
             │             │ Modal warm   │   │ Replicate      │
             │             │ pool A100/   │   │ (nightmareai   │
             │             │ H100 +       │   │  real-esrgan)  │
             │             │ snapshots    │   │ T4 $0.0025/run │
             │             └──────┬───────┘   └────────┬───────┘
             │                    │                    │
             ▼                    ▼                    ▼
        Privacy, $0 cost,    Predictable p95,     Cheapest per run,
        user pays latency    $2-3/h warm idle     accepts 15-90s p95
```

Heuristics:

- **"Privacy beats speed"** — legal/compliance/consumer: prefer WebGPU/WebNN or a desktop binary even if slower.
- **"Latency SLA matters"** — interactive UI (<5 s p95 to the refined result): Modal with `min_containers=1` and snapshots; accept the warm-pool idle cost.
- **"Cost beats latency"** — batch/offline/async: Replicate `nightmareai/real-esrgan` or Runpod serverless with FlashBoot, and queue the result into a webhook.
- **"Edge-case users"** — Firefox without WebGPU, iOS Safari with WebNN disabled: always have a server fallback path and a feature detection step *before* downloading the 30–200 MB model weights.

## WebGPU Reality Check (2025–2026)

The story "run Real-ESRGAN in the browser" is true but nuanced. What actually works today, as of Chrome 131+/Safari 18/Edge 131:

**What works well:**

- **`transformers.js v3` + WebGPU** for Swin2SR x2/x4 image-to-image super-resolution. Usage is one line: `await pipeline('image-to-image', 'Xenova/swin2SR-classical-sr-x2-64', { device: 'webgpu' })`.[^tjs-pr381]
- **`onnxruntime-web` with the WebGPU EP** for custom ONNX graphs, including community-exported Real-ESRGAN and SwinIR models. Microsoft's February 2024 post on ORT-Web + WebGPU is still the reference.[^ms-ortweb]
- **Tiled inference for large images.** `webnx-img` and `web-realesrgan` both slice images into chunks because browsers impose GPU buffer size and memory limits; you cannot ship a raw 4096×4096 tensor to WebGPU on most devices.[^webnx][^webrealesrgan]

**What does not work well:**

- **Model load is the dominant cost on cold visits.** Swin2SR x4 takes **10–16 seconds to load on an M1 Max** in the browser, vs ~1 s with an optimized ONNX version in Node. This is not GPU time — it's node-graph parsing overhead.[^tjs-1407]
- **WebGPU is often slower than local CPU on small models.** For Real-ESRGAN variants, `web-realesrgan` explicitly documents "WebGPU speed is approximately half the speed of local CPU execution", and FP16 gives no speedup over FP32 in WebGPU today.[^webrealesrgan]
- **CUDA is 6–14× faster than WebGPU** on the same graphs, per Microsoft's own benchmarks — so WebGPU is not a server replacement.[^ort-cudavsgpu]
- **Firefox still ships WebGPU behind a flag.** Production paths must feature-detect `navigator.gpu` and fall back to WASM or server.

**WebNN is the promising middle ground:**

- Chrome 122+ exposes WebNN with DirectML/CoreML backends; Safari 18+ on macOS supports CoreML Neural Engine via `MLComputeUnitsAll`. Ops for classic conv-based upscalers (Real-ESRGAN, SwinIR blocks) are largely covered.[^webnn-compat][^webnn-techcom]
- **DirectML status clarification (Updated 2026-04-21):** DirectML entered **sustained engineering** at Microsoft Build 2025 — it is supported but receives no new feature development. New feature work has moved to WinML / Windows ML. On Windows, Chrome's WebNN backend continues to use DirectML as the default (with Windows ML on Win 11 24H2+ as a secondary option). The W3C WebNN spec reached Candidate Recommendation in January 2026, covering 95 ops across CoreML, Windows ML/DirectML, and ONNX Runtime WebNN EP. Any shipping WebNN plan should target the ONNX Runtime Web path to be portable across the evolving Windows backends.[^webnn-compat]
- On iOS, CoreML backend is disabled by default in WebNN; the fallback is TFLite+XNNPACK, which is fine for small filters but not ideal for 4× SR.

> **Updated 2026-04-21:** Firefox has now shipped WebGPU support in stable (no longer flag-only), meaning `navigator.gpu` is available across Chrome, Edge, Firefox, and Safari as of early 2026. The feature-detect is still required (some devices and enterprise policies disable it), but the "Firefox workaround" fallback is less critical than in 2024. WebNN is also maturing rapidly — the spec reached Candidate Recommendation in January 2026.

**Practical rule for the prompt-to-asset product:**

Ship WebGPU/WebNN as a **preview tier** (thumbnail 512→1024, in-editor refinement), and keep the "Download production asset at 4096×4096" button routed to a serverless endpoint. The model load alone can exceed the server round-trip.

## Export pipeline: PyTorch → ONNX → {CoreML, TensorRT, ORT-Web}

For any of Real-ESRGAN, SwinIR, HAT, or an SDXL refiner, the canonical export path is PyTorch → ONNX, then ONNX → platform-specific format.

**ONNX export** (Real-ESRGAN PR #360 and SwinIR issue #145 agree on the pattern):[^realesrgan-pr360][^swinir-145]

```python
torch.onnx.export(
    model, dummy_img_lq, "real_esrgan_x4.onnx",
    export_params=True, opset_version=17, do_constant_folding=True,
    input_names=["input"], output_names=["output"],
    dynamic_axes={"input":  {0: "b", 2: "h", 3: "w"},
                  "output": {0: "b", 2: "h", 3: "w"}},
)
```

Dynamic axes are critical: fixed-shape exports are painful because upscaler inputs are whatever the user uploaded. SwinIR specifically has known issues where dynamic-shape ONNX fails at inference time unless the model's forward pass is tolerant of variable H/W (see the `network_swinir.py` patches in the issue thread).[^swinir-145]

**From ONNX, typical paths:**

- **CoreML:** `coremltools.convert(onnx_model, ...)` for Apple Silicon / iOS. Works cleanly for Real-ESRGAN (conv-heavy). Transformer-heavy models like SwinIR sometimes need manual op shims.
- **TensorRT:** `trtexec --onnx=real_esrgan_x4.onnx --fp16 --saveEngine=real_esrgan_x4.engine`. Significant speedups for SwinIR/HAT variants — the AniSD upscale suite documents "significant speed uplifts" when using TRT over vanilla PyTorch.[^anisd]
- **ONNX Runtime Web:** keep the ONNX as-is, optionally quantize to int8 for WASM EP or leave FP16 for WebGPU EP. `onnxruntime-web` ships the WebGPU EP as a first-class backend since mid-2024.[^ms-ortweb]
- **ncnn + Vulkan:** conversion via `onnx2ncnn` → `.param` + `.bin`. This is the path that ships inside **Upscayl** (~43–44k GitHub stars as of early 2026, latest v2.15.0) for cross-platform desktop deployment; ncnn+Vulkan is what makes AGPL-licensed Real-ESRGAN work on Windows/macOS/Linux from a single binary drop.[^upscayl]

**Pretrained ONNX availability (save yourself the export):**

- SwinIR: `rocca/swin-ir-onnx` on Hugging Face has pre-exported variants.[^rocca-swinir]
- Real-ESRGAN: multiple community ONNX bundles (search `real-esrgan onnx` on HF); Xenova has transformers.js-ready conversions for Swin2SR.
- HAT (Holistic Attention Transformer): export scripts exist in the HAT repo but pretrained ONNX is thinner on the ground; most production HAT deployments export locally then cache to S3/R2.

## Serverless GPU patterns for burst asset generation

A prompt-to-asset turning a sentence into a full app-icon set can easily fan out to **10–40 generations per session** (logo + favicon + OG + splash + iOS/Android/web icons). That is a bursty workload: idle for minutes, then 40 requests over 30 seconds.

### Pattern A — Pure pay-per-run (Replicate-style)

Call `nightmareai/real-esrgan` for each asset. At **$0.0025/run**, 40 assets = **$0.10**. Cold start is 15–60 s on A10G, but after the first, the container stays warm for a few minutes and subsequent calls are ~12 s each.[^replicate-realesrgan]

Pros: zero ops, truly free when idle.  
Cons: p95 spikes on cold start are awful for interactive UX; no batch dispatch; queueing during traffic spikes can push wait times into minutes.

### Pattern B — Warm pool on Modal with snapshots (`min_containers=1`)

Modal's documented ComfyUI pattern:[^modal-scaling][^modal-3s]

```python
import modal

app = modal.App("upscaler")

image = (
    modal.Image.debian_slim()
    .pip_install("torch", "realesrgan", "basicsr")
    .run_function(preload_weights)
)

@app.cls(
    gpu="A10G",
    image=image,
    min_containers=1,  # warm pool
    enable_memory_snapshot=True,
    max_containers=10,
)
class Upscaler:
    @modal.enter()
    def load(self):
        self.model = load_real_esrgan()

    @modal.method()
    def upscale(self, img_bytes: bytes) -> bytes:
        return run(self.model, img_bytes)
```

With CPU pre-hydration (patch `torch.cuda.is_available` to `False` during snapshot creation, then un-patch after), cold starts drop to <3 s.[^modal-3s]

> **Updated 2026-04-21:** Modal introduced **GPU memory snapshots** in 2025 (alpha opt-in via `enable_memory_snapshot=True`). These capture full GPU state including model weights in VRAM and CUDA kernels, achieving up to 10× faster cold starts on some workloads — sub-1-second is possible. Standard cold starts without snapshots are 2–4 s. Modal reached unicorn status ($1.1B valuation, September 2025 Series B).

Cost: **1 A10G at ~$1.10/h ≈ $0.018/min idle** for warm pool. Worth it if sessions-per-day × generations > ~5,000 — below that, Pattern A is cheaper.

### Pattern C — Runpod FlashBoot + webhook

Runpod's serverless has the cheapest hourly rate (**$1.89/h A100**) and FlashBoot covers 48% of cold starts under 200 ms. Best for async flows where the user uploads and waits for a notification.[^introl]

### Pattern D — Hybrid client/server

The most interesting pattern for an asset-generation product:

1. Client immediately runs **WebGPU Swin2SR x2** for a 512→1024 **preview** while the server spins up.
2. Server (Modal warm pool) runs the **4× production render** in parallel.
3. UI swaps the preview for the HD asset once the server returns (typically 5–10 s later).

This gives <1 s "time to first pixel" on screen and <10 s "time to production asset." The client model can be quite small (int8-quantized Swin2SR x2 is ~15 MB); downloading 15 MB on a capable connection is 1–2 s, which is already the server cold start anyway.

### Pattern E — Local-first "bring your own GPU"

For pro users with a 3060+ GPU at home: ship a **Pinokio** installer or point at **ComfyUI portable**. Pinokio is a one-click cross-platform installer that manages Python venvs for a catalogue of AI apps; ComfyUI portable is a self-contained Windows zip with embedded Python and CUDA wheels.[^pinokio][^comfy-portable]

Tradeoff: 4–12 GB download, but zero per-generation cost, no privacy leak, and 2–5 s wall-clock upscales on consumer GPUs. **Upscayl** (ncnn+Vulkan) is the friendliest entry point — it works on Linux, macOS, and Windows from a single 200 MB bundle and is AGPL.[^upscayl]

## Cost/latency worked example

A product doing **10,000 asset upscales/day**, average 1024→2048 Real-ESRGAN x2:

| Pattern | Per-run cost | Daily cost | p50 latency | p95 latency |
|---|---|---|---|---|
| Replicate `nightmareai` T4 | $0.0025 | $25 | ~12 s warm | 30–60 s cold |
| Modal A10G, `min_containers=1`, snapshots | ~$26/day idle + ~$0.0003/s × 8 s × 10k = ~$50 | ~$76 | ~3 s | ~8 s |
| Runpod serverless A100 (FlashBoot) | ~$0.005/run | ~$50 | ~4 s | ~15 s |
| Dedicated A10G 24/7 | $1.10/h × 24 = $26 | $26 | ~2 s | ~5 s |
| Hybrid WebGPU preview + Modal for HD | $50 server + $0 client | $50 | **<1 s preview** / ~8 s HD | ~12 s HD |

At 10k/day, a **dedicated always-on A10G is the cheapest** if utilization is reasonably continuous, and **Modal with min_containers** wins on p95 for bursty traffic. Replicate is a great starting point but dominated on cost at scale.

## References

[^modal-scaling]: Modal Labs — *Scaling ComfyUI*. https://modal.com/blog/scaling-comfyui
[^modal-3s]: Tolga Oğuz — *How I Got ComfyUI Cold Starts Down to Under 3 Seconds on Modal*. https://tolgaoguz.dev/post/comfy-workflow-api-with-modal
[^markaicode-1]: *Serverless GPU Hosting: Modal vs. Replicate vs. RunPod for AI App Deployment*. https://markaicode.com/serverless-gpu-modal-vs-replicate/
[^introl]: *Serverless GPU Platforms: RunPod, Modal, and Beam Compared (2025)*. https://introl.com/blog/serverless-gpu-platforms-runpod-modal-beam-comparison-guide-2025
[^replicate-realesrgan]: Replicate — `nightmareai/real-esrgan` API reference. https://replicate.com/nightmareai/real-esrgan/api and `replicategithubwc/real-esrgan`. https://replicate.com/replicategithubwc/real-esrgan/api
[^tjs-pr381]: `huggingface/transformers.js` PR #381 — *Add image-to-image task w/ Swin2SR*. https://github.com/huggingface/transformers.js/pull/381
[^tjs-1407]: `huggingface/transformers.js` issue #1407 — *Expected time to load a super-resolution model locally*. https://github.com/huggingface/transformers.js/issues/1407
[^tjs-ex1]: `huggingface/transformers.js-examples` issue #1 — *Which image models does WebGPU speed up?*. https://github.com/huggingface/transformers.js-examples/issues/1
[^webrealesrgan]: `xororz/web-realesrgan` README — Real-ESRGAN/Real-CUGAN in the browser (TF.js + WebGPU/WebGL). https://github.com/xororz/web-realesrgan
[^webnx]: `briPXY/webnx-img` — onnxruntime-web + WebGPU image inference bundle. https://github.com/briPXY/webnx-img
[^ms-ortweb]: Microsoft Open Source Blog — *ONNX Runtime Web unleashes generative AI in the browser using WebGPU* (Feb 2024). https://opensource.microsoft.com/blog/2024/02/29/onnx-runtime-web-unleashes-generative-ai-in-the-browser-using-webgpu/
[^ort-cudavsgpu]: `microsoft/onnxruntime` discussion #20177 — *CUDA vs WebGPU performance 6x to 14x slower*. https://github.com/microsoft/onnxruntime/discussions/20177
[^realesrgan-pr360]: `xinntao/Real-ESRGAN` PR #360 — *Inference with ONNXRuntime + pytorch2onnx refactor*. https://github.com/xinntao/Real-ESRGAN/pull/360
[^swinir-145]: `JingyunLiang/SwinIR` issue #145 — *Dynamic shape inference with ONNX model*. https://github.com/JingyunLiang/SwinIR/issues/145
[^rocca-swinir]: `rocca/swin-ir-onnx` on Hugging Face. https://huggingface.co/rocca/swin-ir-onnx
[^anisd]: `Sirosky/Upscale-Hub` — *AniSD Release notes* (TensorRT speed-ups for SwinIR variants). https://github.com/Sirosky/Upscale-Hub/releases/tag/AniSD
[^upscayl]: `upscayl/upscayl` (44k+ stars) and `upscayl/upscayl-ncnn` (ncnn+Vulkan backend). https://github.com/upscayl/upscayl and https://github.com/upscayl/upscayl-ncnn
[^webnn-compat]: WebNN.io — *Browser Compatibility* (Chrome, Safari, Edge). https://webnn.io/en/api-reference/browser-compatibility/api · Note (2026-04-21): DirectML is in *sustained engineering* (not fully deprecated) — Windows WebNN uses DirectML with WinML on Win 11 24H2+ as an option. W3C WebNN spec reached Candidate Recommendation January 2026.
[^webnn-techcom]: Microsoft Tech Community — *WebNN: Bringing AI Inference to the Browser*. https://techcommunity.microsoft.com/blog/aiplatformblog/webnn-bringing-ai-inference-to-the-browser/4175003
[^webgpu-allbrowsers]: VideoCardz — *WebGPU is now supported by all major browsers*. https://videocardz.com/newz/webgpu-is-now-supported-by-all-major-browsers (Firefox stable, early 2026).
[^modal-unicorn]: Modal Series B ($1.1B valuation, September 2025) — GPU memory snapshots (alpha, up to 10× cold-start reduction). https://modal.com/pricing
[^pinokio]: XDA Developers — *Pinokio makes installing ComfyUI a breeze*. https://xda-developers.com/pinokio-how-to
[^comfy-portable]: ComfyUI docs — *ComfyUI (portable) Windows*. http://docs.comfy.org/installation/comfyui_portable_windows
