On Windows, the documented route to libvmaf_cuda runs FFmpeg inside a Linux container, using Docker Desktop’s WSL 2 backend and an NVIDIA GPU passed through with --gpus all. The filter only accepts CUDA frames, so both the reference and distorted videos have to reach the GPU as CUDA frames and stay there through the filter graph. This guide covers the setup, the filter command, and the points where VMAF results can go wrong.
What libvmaf_cuda needs in place
The CUDA variant of the libvmaf filter is a GPU-side scorer. FFmpeg’s filter documentation states that libvmaf_cuda “only accepts CUDA frames,” and it requires Netflix’s libvmaf library to be built into FFmpeg. Before you run anything, these pieces must all be present:
As an Amazon Associate I earn from qualifying purchases.
- A supported NVIDIA GPU with a current Windows driver that includes WSL 2 GPU support.
- WSL 2 with an up-to-date Linux kernel and a Linux distribution installed under it.
- Docker Desktop with the WSL 2 backend enabled, so that
--gpuspassthrough to Linux containers works. - An FFmpeg build that includes both the VMAF library and the CUDA filter. NVIDIA states that VMAF-CUDA “must be built from the source” (NVIDIA technical blog, 2024).
- Both input videos decoded or uploaded as CUDA frames, with matching dimensions, frame rate, and pixel format.
Version requirements change over time. Microsoft documents CUDA support for Windows 11 and for Windows 10 version 21H2, but exact minimum driver and WSL versions are maintained on live vendor pages. Check Docker’s GPU support page for Docker Desktop, NVIDIA’s WSL user guide, and Microsoft’s WSL documentation on the day you set up. A supported NVIDIA GPU is a hard prerequisite for this route; the steps do not depend on any particular card model.
Do these 3 things before closing this tab:
1Fix the driver behind crashes, sound loss and screen glitches2Repair Windows errors before they cause bigger problems3Scan for outdated or missing drivers - takes under a minuteStep 1: Confirm the GPU is visible in WSL and in Docker
Debug the GPU path before you touch FFmpeg. Many failures that look like filter errors are really container or driver problems.
#1 Best Overall
- AI Performance: 767 AI TOPS
- OC mode: 2632 MHz (OC mode)/ 2602 MHz (Default mode)
- Powered by the NVIDIA Blackwell architecture and DLSS 4
- Axial-tech fan design features a smaller fan hub that facilitates longer blades and a barrier ring that increases downward air pressure
- A 2.5-slot design maximizes compatibility and cooling efficiency for superior performance in small chassis
- In PowerShell, run
wsl --updateto bring the WSL 2 kernel up to date. - Open your Linux distribution and run
nvidia-smi. A table listing your GPU confirms the driver is reachable from WSL. NVIDIA’s WSL guide notes thatnvidia-smihas a limited feature set under WSL 2, so some queries may report unsupported values even when CUDA works. - Run a simple container with GPU access using
docker run --rm --gpus allfollowed by a command that callsnvidia-smi. Use the test command from Docker’s GPU support page, because image names change. If the container cannot see the GPU, fix the Docker Desktop backend or the driver before going further.
Step 2: Build an FFmpeg image with libvmaf and CUDA
Netflix’s VMAF Docker guide describes an NVIDIA Container Toolkit setup and a separate Dockerfile.ffmpeg that builds FFmpeg with CUDA support and the VMAF filter. Start from that upstream Dockerfile rather than writing your own. FFmpeg’s filter documentation lists the configure flags --enable-nonfree --enable-ffnvcodec --enable-libvmaf, to be used after libvmaf is installed. Those flags are necessary but do not make a complete recipe: the CUDA and FFmpeg toolchain versions must match, and the upstream Dockerfile manages that.
After the build, confirm the filters exist inside the image:
Rank #2
- Powered by the NVIDIA Blackwell architecture and DLSS 4
- Powered by GeForce RTX 5070 Ti
- Integrated with 16GB GDDR7 256bit memory interface
- PCIe 5.0
- WINDFORCE cooling system
ffmpeg -hide_banner -filters | grep -E "libvmaf|scale_cuda"
You should see libvmaf_cuda and scale_cuda in the output. If libvmaf_cuda is missing, the image was built without the CUDA filter or without libvmaf, and the filter graph below will fail with a “filter not found” style error.
Step 3: Run the container with the GPU and your media mounted
Keep the videos in a Windows folder and mount that folder into the container. From an Ubuntu shell in WSL, Windows drives appear under /mnt/c. Netflix’s examples use --gpus all together with NVIDIA_DRIVER_CAPABILITIES=compute,video, which covers the compute and video decode features the pipeline uses. Replace your-ffmpeg-vmaf-image with the tag you built in Step 2.
Rank #3
- Powered by the NVIDIA Blackwell architecture and DLSS 4. System Requirements: Minimum 850W PSU with 16-pin 12V-2x6 (12VHPWR) connector required. Verify before purchasing.
- Military-grade components deliver rock-solid power and longer lifespan for ultimate durability. Compatibility: 348mm (13.7") length, 3.6 slots, 4.3 lbs. Confirm case clearance and slot spacing. GPU bracket included.
- Protective PCB coating helps protect against short circuits caused by moisture, dust, or debris
- 3.6-slot design with massive fin array optimized for airflow from three Axial-tech fans
- Phase-change GPU thermal pad helps ensure optimal thermal performance and longevity, outlasting traditional thermal paste for graphics cards under heavy loads
docker run --rm --gpus all
-e NVIDIA_DRIVER_CAPABILITIES=compute,video
-v /mnt/c/videos:/data -w /data
your-ffmpeg-vmaf-image
ffmpeg -hwaccel cuda -hwaccel_output_format cuda -i distorted.mp4
-hwaccel cuda -hwaccel_output_format cuda -i reference.mp4
-filter_complex '[0:v]scale_cuda=format=yuv420p[dist];[1:v]scale_cuda=format=yuv420p[ref];[dist][ref]libvmaf_cuda=log_fmt=json:log_path=output.json'
-f null -
This command adapts FFmpeg’s documented CUDA example and Netflix’s Docker example. It has not been tested on a specific Windows, WSL, driver, or GPU combination, so treat it as a starting point and verify the output on your own machine. Input order matters: in FFmpeg’s libvmaf filters, the distorted video is the first input and the reference is the second.
Understanding the filter graph
Each part of the command has a specific job:
- -hwaccel cuda -hwaccel_output_format cuda asks the decoder to output frames in CUDA memory rather than system memory. It must appear before each
-iit applies to. - scale_cuda=format=yuv420p converts each stream to a common pixel format on the GPU. Whether it is needed depends on what the decoder produces, covered in the next section.
- libvmaf_cuda=log_fmt=json:log_path=output.json runs the score and writes the results to a JSON file in the mounted folder.
- -f null – discards the video output, because only the score log matters here.
Pixel format and input alignment
Netflix’s example says that 4:2:0 video decoded to NV12 needs a conversion to 4:2:0 with scale_cuda, while formats such as yuv444p or yuv422p may pass from the decoder without that conversion. Treat this as format-dependent guidance, not a universal rule. Before scoring a pair, check what each file actually contains:
Rank #4
- Powered by the NVIDIA Blackwell architecture and DLSS 4
- Powered by GeForce RTX 5060
- Integrated with 8GB GDDR7 128bit memory interface
- PCIe 5.0
- WINDFORCE cooling system
ffprobe -v error -select_streams v:0 -show_entries stream=width,height,pix_fmt,r_frame_rate -of csv=p=0 distorted.mp4
ffprobe -v error -select_streams v:0 -show_entries stream=width,height,pix_fmt,r_frame_rate -of csv=p=0 reference.mp4
The two lines should match in width, height, and frame rate. If the pixel formats differ, or the reference and distorted clips have different lengths or frame timing, align them before scoring. The sources cited for this route show CUDA decoding and scaling, but they do not provide a preprocessing recipe for every media pair, so trimming, resizing, or frame-rate conversion decisions remain yours to make and document.
Reading the JSON log
The output.json file is written inside the mounted folder, so it appears on Windows as soon as the run finishes. Open it in any text editor. The pooled metrics section contains the aggregate VMAF score for the clip, and the per-frame section lets you find where quality dips. Keep the JSON with the command and the image tag you used; a score without its model, input format, and build details is hard to reproduce later.
Best Value
- Powered by the NVIDIA Blackwell architecture and DLSS 4 OC mode: 2640MHz/Default mode: 2610MHz (Boost Clock)
- Military-grade components deliver rock-solid power and longer lifespan for ultimate durability
- Protective PCB coating helps protect against short circuits caused by moisture, dust, or debris
- 3.125-slot design with massive fin array optimized for airflow from three Axial-tech fans
- Phase-change GPU thermal pad helps ensure optimal thermal performance and longevity, outlasting traditional thermal paste for graphics cards under heavy loads
Are CPU and CUDA VMAF scores the same?
This is not established by the sources behind this guide. A community discussion on Reddit asks exactly this question, but it is a reader question, not a verified answer. Do not assume that a CUDA score will match a CPU score for every version, model, pixel format, or input pair. If the number matters, run the CPU filter and the CUDA filter on the same two files with the same VMAF model, then compare the results yourself before you rely on the CUDA numbers in a report.
Performance: what NVIDIA reports
NVIDIA’s technical blog (2024) reports that VMAF-CUDA delivers “up to 37x lower per-frame latency at 4K” and “up to 4.4x higher throughput in FFmpeg,” compared with a dual Intel Xeon 8480 CPU system. These are vendor-reported benchmark results, not an independent replication. They describe one hardware comparison and one workload type. Your speed depends on the GPU, the resolution, the codec, the decoder path, and whether the files stay on the GPU; treat the figures as an upper bound on what the vendor measured, not a guarantee.
Decode paths compared
| Path | How frames reach the filter | Evidence in the sources for this guide |
|---|---|---|
| GPU decode with CUDA output | -hwaccel cuda -hwaccel_output_format cuda keeps frames on the GPU |
Shown in FFmpeg’s documented CUDA example and Netflix’s Docker example |
| CPU decode, then upload | Frames decoded in system memory and moved to CUDA memory before scoring | Not stated for this route in the cited sources |
| Decoder output used directly | No scale_cuda step when the decoder format already matches the filter’s needs |
Netflix’s example for yuv444p and yuv422p; format-dependent |
scale_cuda conversion |
Converts NV12 or other formats to a common format on the GPU | Netflix’s example for 4:2:0 video decoded to NV12 |
The sources establish the GPU decode path and the scaling example. They do not establish that one route is always faster or easier for every user, so measure with your own files if speed is a deciding factor.
Recommended Free Tools
Common problems and fixes
- nvidia-smi fails inside WSL: Update the NVIDIA Windows driver with WSL support, run
wsl --update, then restart WSL. - The container cannot see the GPU: Confirm Docker Desktop uses the WSL 2 backend and that the run command includes
--gpus all. - The filter is not found: The image lacks the CUDA filter or libvmaf. Rebuild from the upstream Dockerfile and rerun the
-filterscheck from Step 2. - Format or conversion errors: Add or adjust the
scale_cuda=format=step after checking the pixel format withffprobe. - Scores look wrong or the run ends early: Compare frame counts, dimensions, and frame rates of both inputs. A mismatch in timing or length means the pair is not aligned.
Keep each run’s command, image tag, and JSON log together so you can repeat the result or explain a difference later.
A supported NVIDIA GPU is required for this Docker Desktop GPU-passthrough route.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




