cd /news/developer-tools/how-to-use-libvmaf-cuda-on-windows-a… · home topics developer-tools article
[ARTICLE · art-131868] src=dev.to ↗ pub= topic=developer-tools verified=true sentiment=· neutral

How to use libvmaf_cuda on Windows: an easy-to-follow guide (WSL2 + Docker + NVIDIA)

A developer documented a reliable method for running GPU-accelerated VMAF video quality analysis on Windows using WSL2, Docker Desktop, the NVIDIA Container Toolkit, and a CUDA-enabled FFmpeg via the easyVmaf project. The setup achieved roughly 15x real-time VMAF analysis on an RTX 3060 Mobile, processing a 46-minute video in about three minutes versus roughly an hour on CPU. The guide notes that libvmaf_cuda is NVIDIA-exclusive and will not work with AMD or Intel GPUs.

by read8 min views3 publishedSep 16, 2026

TL;DR: Running GPU-accelerated VMAF on Windows is surprisingly painful. After spending hours fighting broken builds and undocumented errors, the only reliable path I found is WSL2 + Docker Desktop + NVIDIA Container Toolkit + a CUDA-enabled FFmpeg. This guide walks you through the whole setup using the easyVmaf project, so you can skip the trial-and-error I went through.

This guide uses easyVmaf, a project that ships a Dockerfile.cuda specifically built for this purpose. We'll run it inside WSL2, with Docker Desktop and the NVIDIA Container Toolkit handling the GPU passthrough.

The result: VMAF running at ~15x real-time speed on an RTX 3060 Mobile. A 46-minute video gets analyzed in about 3 minutes. On CPU, the same task would take like an hour.

⚠️ AMD and Intel GPUs will not work with this guide. libvmaf_cuda is NVIDIA-exclusive.

If you've ever tried to calculate VMAF on Windows, you already know that:

libvmaf filter works, but it runs on the libvmaf_cuda Before starting, make sure you have:

Component Minimum requirement
Windows Windows 10 (version 2004+) or Windows 11
NVIDIA GPU Any CUDA-capable GPU (I'm using an RTX 3060 Mobile)
NVIDIA drivers Version 525+ (for CUDA 12.x) — download here
Disk space ~30 GB free (WSL + Docker + images)
RAM 16 GB recommended (WSL2 is memory-hungry)

⚠️ Do not install NVIDIA drivers inside WSL. WSL automatically uses the drivers from Windows. Installing Linux drivers inside WSL will break GPU passthrough.

This part covers everything needed to get a working Linux + Docker + GPU stack: WSL2, Docker Desktop, and the NVIDIA Container Toolkit.

Open PowerShell as Administrator and run

wsl --install

This command will:

Restart Windows when prompted.

After the restart, open PowerShell again and verify:

wsl --list --verbose

Expected output:

  NAME      STATE           VERSION
* Ubuntu    Running         2

Make sure VERSION is 2. If it says 1, upgrade with:

wsl --set-version Ubuntu 2
wsl --set-default-version 2

Open your Ubuntu terminal either by typing in PowerShell

ubuntu

or

wsl

Once in Ubuntu terminal enter:

sudo apt update && sudo apt upgrade -y

If you're new to Linux, one of the first things to understand is that WSL doesn't use C:\, D:\, etc. Instead, it mounts every Windows drive under /mnt/.

Windows path WSL path
C:\Users\YourName\Videos /mnt/c/Users/YourName/Videos
D:\Movies /mnt/d/Movies
E:\Backups\2026 /mnt/e/Backups/2026

if you already have installed NVIDIA drivers on Windows, run:

nvidia-smi

You should see the nvidia-smi table with your GPU listed. If it doesn't work, your Windows drivers are outdated or WSL isn't configured properly.

Wed Sep 16 09:30:35 2026
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 615.71.08              KMD Version: 616.92        CUDA UMD Version: 13.4     |
+-----------------------------------------+------------------------+----------------------+
| GPU  Name                 Persistence-M | Bus-Id          Disp.A | Volatile Uncorr. ECC |
| Fan  Temp   Perf          Pwr:Usage/Cap |           Memory-Usage | GPU-Util  Compute M. |
|                                         |                        |               MIG M. |
|=========================================+========================+======================|
|   0  NVIDIA GeForce RTX 3060 ...    On  |   00000000:01:00.0  On |                  N/A |
| N/A   55C    P8             14W /  115W |    1085MiB /   6144MiB |      6%      Default |
|                                         |                        |                  N/A |
+-----------------------------------------+------------------------+----------------------+

+-----------------------------------------------------------------------------------------+
| Processes:                                                                              |
|  GPU   GI   CI              PID   Type   Process name                        GPU Memory |
|        ID   ID                                                               Usage      |
|=========================================================================================|
|  No running processes found                                                             |
+-----------------------------------------------------------------------------------------+

Download Docker Desktop from: https://www.docker.com/products/docker-desktop/

During installation, make sure to check "Use WSL 2 instead of Hyper-V".

Once installed, open Docker Desktop and go to Settings

General tab:

Resources → WSL Integration:

Resources → Advanced (optional):

If "Resource Saver" is enabled, Docker will suspend WSL2 after inactivity, and you'll have to restart it manually.

In your Ubuntu terminal:

docker --version
Docker version 27.3.1, build ce12230

⚠️ If you get var/run/docker.sock: connect: permission denied. jump to the Troubleshooting section.

This is the component that allows Docker containers to access the GPU.

In your WSL Ubuntu terminal:

curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | \
  sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg && \
curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | \
  sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
  sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list

sudo apt update
sudo apt install -y nvidia-container-toolkit
sudo nvidia-ctk runtime configure --runtime=docker
INFO[0000] Config file does not exist; using empty config
INFO[0000] Wrote updated config to /etc/docker/daemon.json
INFO[0000] It is recommended that docker daemon be restarted.

Restart Docker Desktop from Windows (either via the restart icon or by quitting and reopening it).

Once Docker Desktop is running again, execute in your WSL terminal:

docker run --rm --gpus all nvidia/cuda:12.3.2-base-ubuntu22.04 nvidia-smi
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 615.71.08              KMD Version: 616.92        CUDA UMD Version: 13.4     |
+-----------------------------------------+------------------------+----------------------+
| GPU  Name                 Persistence-M | Bus-Id          Disp.A | Volatile Uncorr. ECC |
| Fan  Temp   Perf          Pwr:Usage/Cap |           Memory-Usage | GPU-Util  Compute M. |
|                                         |                        |               MIG M. |
|=========================================+========================+======================|
|   0  NVIDIA GeForce RTX 3060 ...    On  |   00000000:01:00.0  On |                  N/A |
| N/A   55C    P8             14W /  115W |    1085MiB /   6144MiB |      6%      Default |
|                                         |                        |                  N/A |
+-----------------------------------------+------------------------+----------------------+

+-----------------------------------------------------------------------------------------+
| Processes:                                                                              |
|  GPU   GI   CI              PID   Type   Process name                        GPU Memory |
|        ID   ID                                                               Usage      |
|=========================================================================================|
|  No running processes found                                                             |
+-----------------------------------------------------------------------------------------+

We'll use the easyVmaf project, which ships a Dockerfile.cuda configured for GPU-accelerated VMAF.

cd ~
git clone --depth 1 https://github.com/gdavila/easyVmaf.git
cd easyVmaf

💡 This step is undocumented anywhere else. I figured it out after spending hours debugging the compilation error with the help of AI. Skip it and your build will fail.

The original Dockerfile.cuda clones the latest version of nv-codec-headers, which is incompatible with FFmpeg 8.1. You'll get this error during the build:

libavcodec/nvenc.c:2529:42: error: 'NV_ENC_CLOCK_TIMESTAMP_SET' 
has no member named 'countingType'; did you mean 'countingTypeLSB'?

In recent versions, NVIDIA renamed countingType to countingTypeLSB/countingTypeMSB, but FFmpeg 8.1 still uses countingType.

The fix: Pin nv-codec-headers to version n12.1.14.0, which still uses countingType and already includes the modern CUDA functions (cuStreamCreateWithPriority, cuMemHostAlloc, etc.) that libvmaf_cuda needs.

Edit the Dockerfile:

nano Dockerfile.cuda

CTRL+W, type nv-codec-headers, press ENTER. You'll land on this line:

RUN git clone --depth 1 https://git.videolan.org/git/ffmpeg/nv-codec-headers.git && \
    cd nv-codec-headers && \
    make install

Change it by adding n12.1.14.0 before --depth:

RUN git clone --branch n12.1.14.0 --depth 1 https://git.videolan.org/git/ffmpeg/nv-codec-headers.git && \
    cd nv-codec-headers && \
    make install

Save with CTRL+X, then Y, then ENTER.

docker build -f Dockerfile.cuda -t easyvmaf:cuda .

⚠️ The easyvmaf:cuda image defines easyVmaf (its own CLI) as the ENTRYPOINT. To run raw ffmpeg, you must override the entrypoint.

docker run --rm --gpus all --entrypoint ffmpeg easyvmaf:cuda -filters | grep -E "libvmaf|scale_cuda"
php
.. libvmaf           VV->V      Calculate the VMAF between two video streams.
.. libvmaf_cuda      VV->V      Calculate the VMAF between two video streams.
.. scale_cuda        V->V       GPU accelerated video resizer

If you see libvmaf_cuda, you're done with the setup.

By default, --gpus all alone only grants the compute and utility capabilities. It does not mount the video decode/encode libraries libnvcuvid.so.1 (NVDEC) and libnvidia-encode.so.1 (NVENC). Since we tell FFmpeg to decode with -hwaccel cuda, it needs those libraries. Without them, FFmpeg fails with:

Cannot load libnvcuvid.so.1
Failed  nvcuvid.
Failed setup for format cuda: hwaccel initialisation returned error.

The fix: explicitly request the video capability:

--gpus all,capabilities=video

Now Docker mounts libcuda.so.1 (CUDA compute), libnvcuvid.so.1 (NVDEC), and libnvidia-encode.so.1 (NVENC). FFmpeg can then decode, filter, and analyze entirely on the GPU.

docker run --gpus all,capabilities=video --rm --entrypoint ffmpeg \
  -v "/path/to/your/videos":/videos \
  easyvmaf:cuda \
  -hwaccel cuda -hwaccel_output_format cuda \
  -i "/videos/distorted.mkv" \
  -hwaccel cuda -hwaccel_output_format cuda \
  -i "/videos/reference.mkv" \
  -filter_complex "[0:v]scale_cuda=format=yuv420p[dis];[1:v]scale_cuda=format=yuv420p[ref];[dis][ref]libvmaf_cuda=log_fmt=json:log_path=/videos/vmaf_full.json" \
  -f null -

Here's the output from a test on a 46-minute video file:

[Parsed_libvmaf_cuda_2 @ 0x760b44004f80] VMAF score: 93.558906
speed=14.9x elapsed=0:03:05.05
[out#0/null @ 0x5ef225e22140] video:27438KiB audio:2072848KiB subtitle:0KiB
frame=66265 fps=350 q=-0.0 Lsize=N/A time=00:46:03.79 bitrate=N/A speed=14.6x elapsed=0:03:09.43

3 minutes for a 46-minute video at ~15x real-time speed. That's the whole point of using libvmaf_cuda.

var/run/docker.sock: connect: permission denied

docker: permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Head "http://%2Fvar%2Frun%2Fdocker.sock/_ping": dial unix /var/run/docker.sock: connect: permission denied.

Your user doesn't belong to the docker group, which owns /var/run/docker.sock.

fix:

enter this command in your WSL Ubuntu terminal

sudo usermod -aG docker $USER

This adds your user to the docker group. Then close and reopen WSL (group changes only apply to new sessions), and verify:

groups
youruser adm cdrom sudo dip plugdev users docker

Cannot load libnvcuvid.so.1 Missing ,capabilities=video in the --gpus flag. See section 3.1.

NV_ENC_CLOCK_TIMESTAMP_SET has no member named 'countingType' You didn't pin nv-codec-headers to n12.1.14.0. See section 2.2.

Edit C:\Users\YOUR_USER\.wslconfig:

[wsl2]
vmIdleTimeout=-1

Then run wsl --shutdown in PowerShell. Also disable "Resource Saver" in Docker Desktop (see section 1.3).

Setting up libvmaf_cuda on Windows was a long journey. At the start, I couldn't find much information about it — most guides either stop at "use libvmaf on CPU" or assume you're on Linux. Even though this isn't 100% native to Windows (it runs through WSL2 + Docker), it's a solid alternative that is absolutely worth the effort.

Once it's working, you get VMAF analysis at 15x real-time speed, which completely changes what's practical for video quality workflows.

If you found this post useful and you're looking for a Full Stack Developer or a technical writer, feel free to reach out!

Thanks for reading! 🙌

── more in #developer-tools 4 stories · sorted by recency
── more on @easyvmaf 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/how-to-use-libvmaf-c…] indexed:0 read:8min 2026-09-16 ·