NanoVSR: Towards Real-Time Video Super-Resolution on Edge Devices

August 4, 2026 · View on GitHub

NanoVSR: Towards Real-Time Video Super-Resolution on Edge Devices

ECCV 2026

Filip Pawlicki · Marcel Kańduła · Marcin Pucek · Kamil Dobies

Gdańsk University of Technology

arXiv 🤗 Space License: MIT

Low-resolution input vs. NanoVSR-644k output (4x upscaling)

4× video super-resolution with NanoVSR-644k — 27.2 FPS on a Jetson Orin NX (25 W).

Overview

NanoVSR is a scalable, fully convolutional video super-resolution (VSR) architecture designed for resource-constrained edge devices. Instead of relying on transformers or explicit optical flow, NanoVSR:

  • uses a bidirectional recurrent design with a direct additive propagation scheme that avoids channel concatenation, reducing memory bandwidth;
  • is built from reparameterizable multi-branch blocks (3×3 + 1×1 + identity) that collapse into a single stream of plain 3×3 convolutions at inference — no custom CUDA ops, natively ONNX/TensorRT compatible;
  • learns spatio-temporal alignment implicitly through a two-stage progressive training curriculum (short Vimeo-90K sequences → long REDS sequences).

The baseline NanoVSR-644k reaches 28.64 dB PSNR on REDS4 at 27.20 FPS on an NVIDIA Jetson Orin NX 16GB (25 W), and the scaled NanoVSR-1.7M reaches 29.15 dB at 19.58 FPS.

News

  • 2026-07: Code and pretrained models are released.
  • 2026-06: NanoVSR is accepted to ECCV 2026! 🎉

Model Zoo

All models perform 4× upscaling and were trained with the two-stage curriculum (50k iterations on Vimeo-90K, then 100k on REDS). PSNR/SSIM: RGB for REDS4, Y-channel for Vid4 and Vimeo-90K-T. Runtime is ms/frame for a 180×320 input on an H100 (FP32); FPS is measured on Jetson Orin NX 16GB (25 W) with TensorRT FP16, 180×320 input, T=15.

ModelParamsREDS4Vid4Vimeo-90K-TH100 (ms)Orin NX (FPS)Download
NanoVSR-226k226k28.23 / 0.805725.26 / 0.725234.31 / 0.91301.91043.86weights
NanoVSR-644k (baseline)644k28.64 / 0.821526.05 / 0.776135.00 / 0.92262.98227.20weights
NanoVSR-1.7M1.7M29.15 / 0.836426.44 / 0.796435.49 / 0.92944.26819.58weights
NanoVSR-5.4M5.4M29.73 / 0.852626.76 / 0.808935.85 / 0.93358.5478.66weights
All architectural configurations (scaling study)
ModelParametersBlocks NChannels FREDS4 PSNR
NanoVSR-22k22,10731027.62
NanoVSR-31k31,15341227.79
NanoVSR-48k48,24541627.96
NanoVSR-226k225,79783228.23
NanoVSR-644k644,245124828.64
NanoVSR-1.7M1,709,605206429.15
NanoVSR-5.4M5,447,365309629.73
NanoVSR-9.6M9,630,3093012829.90

Installation

git clone https://github.com/filippawlicki/nanovsr.git
cd nanovsr

conda create -n nanovsr python=3.11 -y
conda activate nanovsr

# Install PyTorch first (pick the right CUDA build for your system):
# https://pytorch.org/get-started/locally/
pip install -r requirements.txt

The code was tested with Python 3.13, PyTorch 2.10.0, CUDA 12.8 (training/inference on H100) and TensorRT 10.3 (deployment on Jetson Orin NX).

Quick Demo

Upscale any low-resolution video (or a directory of frames) 4× with a pretrained checkpoint — no datasets required:

python demo.py --checkpoint checkpoints/nanovsr_644k.pth --input my_clip.mp4

The result is written next to the input as my_clip_x4.mp4. Useful flags:

  • --compare — write a labeled side-by-side LR (nearest-neighbor) vs. NanoVSR video (like the teaser above);
  • --input frames_dir/ --fps 30 — read a directory of PNG/JPG frames instead of a video file;
  • --chunk_size 15 — temporal window per forward pass (the paper's edge setting); lower it if you run out of memory;
  • --fp16 — half-precision inference on CUDA;
  • --max_frames 100 — quick test on the first N frames.

NanoVSR expects genuinely low-resolution input (the paper operates on 180×320 and 270×480 frames); feeding an HD video will be slow and memory-hungry.

Data Preparation

We train on Vimeo-90K (phase 1) and REDS (phase 2), and evaluate on REDS4, Vid4 and Vimeo-90K-T.

  1. REDS — download train_sharp (GT) and train_sharp_bicubic (LR, X4) from the official REDS page. The four REDS4 clips (000, 011, 015, 020) are excluded from training automatically and used for testing.
  2. Vimeo-90K — download the septuplet dataset from the OpenDataLab page (includes sep_trainlist.txt / sep_testlist.txt). The 4× LR frames (vimeo_septuplet_matlabLRx4) are generated with MATLAB bicubic downsampling, following standard practice (BasicSR guide). If the LR folder is missing, the training dataloader falls back to on-the-fly PIL bicubic downsampling — convenient for a quick start, but use the MATLAB LR data to reproduce paper numbers.
  3. Vid4 — download GT and BIx4 using MMagic dataset guide.

Organize everything under data/:

data/
├── REDS/
│   ├── GT/
│   │   └── train/
│   │       └── train_sharp/                # 000, 001, ..., 269
│   └── LR/
│       └── train/
│           └── train_sharp_bicubic/
│               └── X4/                     # 000, 001, ..., 269
├── vimeo_septuplet/
│   ├── sequences/                          # 00001/0001/im1.png ... im7.png
│   ├── sep_trainlist.txt
│   └── sep_testlist.txt
├── vimeo_septuplet_matlabLRx4/
│   └── sequences/
└── Vid4/
    ├── GT/                                 # calendar, city, foliage, walk
    └── BIx4/

Training

The two-stage curriculum is handled automatically: 7-frame Vimeo-90K sequences for the first 50k iterations, then 30-frame REDS sequences until 150k. 256×256 GT patches, Charbonnier loss, cosine annealing from 3e-4 to 1e-7, BF16 AMP and gradient clipping are the script defaults.

Multi-GPU (paper setup) — launch with torchrun; the paper uses 4 GPUs with a per-GPU batch size of 3 (global batch size 12):

# NanoVSR-644k (baseline)
torchrun --nproc_per_node=4 train.py \
    --vimeo_root data/vimeo_septuplet \
    --reds_root data/REDS \
    --output_dir experiments/nanovsr_644k \
    --num_blocks 12 \
    --num_feat 48 \
    --batch_size 3

Single GPU — run the same script with plain python (DDP is bypassed automatically):

python train.py \
    --vimeo_root data/vimeo_septuplet \
    --reds_root data/REDS \
    --output_dir experiments/nanovsr_644k \
    --num_blocks 12 \
    --num_feat 48 \
    --batch_size 12

The global batch size is #GPUs × --batch_size; to reproduce paper results keep it at 12 (reduce --batch_size if you run out of memory, at the cost of a slightly different training trajectory).

To train other variants, change the architecture flags according to the Model Zoo table, e.g. --num_blocks 8 --num_feat 32 for NanoVSR-226k or --num_blocks 20 --num_feat 64 for NanoVSR-1.7M.

All training options
FlagDefaultDescription
--vimeo_rootPath to vimeo_septuplet/
--reds_rootPath to REDS/
--output_diroutput_auto_curriculumCheckpoint directory
--num_feat32Feature channels F
--num_blocks8Propagation blocks N per direction
--batch_size3Per-GPU batch size
--lr3e-4Initial learning rate
--patch_size256GT patch size
--switch_iter50000Iteration to switch Vimeo-90K → REDS
--total_iterations150000Total iterations
--long_num_frames30Sequence length in the REDS phase
--num_workers10Dataloader workers per GPU

Evaluation

A single command runs the model on the benchmarks and reports PSNR/SSIM with the paper's protocol (REDS4 in RGB; Vid4 and Vimeo-90K-T on the Y channel; Vimeo-90K-T on the center frame of each septuplet). The architecture is detected from the checkpoint and the model is automatically reparameterized into its fused deploy form:

python evaluate.py --checkpoint checkpoints/nanovsr_644k.pth --data_root data
  • --datasets REDS4 Vid4 evaluates a subset; benchmarks missing under --data_root are skipped.
  • --save_images additionally writes the SR frames to results/NanoVSR/{REDS4,Vid4,Vimeo90K}/<clip>/ (off by default).

Pre-computed SR frames — e.g. produced by a TensorRT engine — can be scored directly, without running the model:

python evaluate.py --datasets REDS4 \
    --sr_root results/trt/REDS4 \
    --gt_root data/REDS/GT/train/train_sharp

Pretrained Models

Download the checkpoints from the Releases page (direct links in the Model Zoo) and place them in checkpoints/:

mkdir -p checkpoints
wget -P checkpoints https://github.com/filippawlicki/nanovsr/releases/download/v1.0/nanovsr_644k.pth

Checkpoints store the multi-branch (training) topology; demo.py, evaluate.py and export_onnx.py fuse them into the single-branch deploy form on load.

ONNX Export & TensorRT Deployment

Export a reparameterized model to ONNX (the paper uses a fixed temporal window of T=15 for chunk-based execution on Jetson):

python export_onnx.py \
    --checkpoint checkpoints/nanovsr_644k.pth \
    --num_frames 15 --height 180 --width 320

Then build a TensorRT engine on the target device (paper setup: TensorRT 10.3, FP16):

trtexec --onnx=checkpoints/nanovsr_644k.onnx \
        --saveEngine=checkpoints/nanovsr_644k.engine \
        --fp16

Measured edge throughput (TensorRT FP16, T=15):

ModelResolutionOrin NX 8GB / 15 WOrin NX 16GB / 25 W
NanoVSR-226k180×32023.55 FPS43.86 FPS
NanoVSR-644k180×32016.12 FPS27.20 FPS
NanoVSR-1.7M180×32011.57 FPS19.58 FPS
NanoVSR-226k270×48010.51 FPS19.55 FPS
NanoVSR-644k270×4807.19 FPS12.82 FPS

Citation

If you find this work useful, please cite:

@misc{pawlicki2026nanovsr,
      title={NanoVSR: Towards Real-Time Video Super-Resolution on Edge Devices}, 
      author={Filip Pawlicki and Marcel Kańduła and Marcin Pucek and Kamil Dobies},
      year={2026},
      eprint={2607.10495},
      archivePrefix={arXiv},
      primaryClass={cs.CV},
      url={https://arxiv.org/abs/2607.10495}, 
}

License

This project is released under the MIT License.

Contact

For questions, please open an issue or contact the authors: