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

> Source: <https://dev.to/rtagl/-how-to-use-libvmafcuda-on-windows-an-easy-to-follow-guide-wsl2-docker-nvidia-59mc>
> Published: 2026-09-16 19:21:17+00:00

**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](https://github.com/gdavila/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](https://www.nvidia.com/Download/index.aspx) | 
| **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/](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](https://github.com/gdavila/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 loading 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! 🙌
