Vship and FFVship
A Library for GPU-accelerated visual fidelity metrics, featuring SSIMULACRA2, Butteraugli and CVVDP.
Vship is a GPU implementation of three perceptual metrics: SSIMULACRA2, Butteraugli and CVVDP. These are the metrics the encoding community prefers for comparing encodes, and they are slow on a CPU, so Vship is what makes scoring whole videos practical. Tools such as Av1an and auto-boost scripts use it for target-quality encoding.
It comes in three forms:
- FFVship, a command-line tool that decodes the two videos itself and prints or saves the scores;
- a VapourSynth plugin (namespace
vship) for use inside scripts; - a C API (libvship) for other programs.
The current release is 5.1.2 (October 2026). 5.1.1 fixed wrong decoding of full-range YUV and a CVVDP accuracy bug, so rescore anything measured with older versions before comparing.
Install
Section titled “Install”Windows builds of FFVship and the library are on the releases page, in CUDA (NVIDIA) and Vulkan versions. On Linux, build from source:
make build BACKEND=Vulkanmake buildFFVSHIPsudo make install PREFIX=/usrUse BACKEND=Cuda (needs the CUDA toolkit) or BACKEND=HIP (needs ROCm/HIP) for the native NVIDIA and AMD paths. FFVship also needs FFMS2, the libavutil headers and pkg-config. make install also links the library into $PREFIX/lib/vapoursynth, where VapourSynth loads plugins; the default /usr/local prefix may not be on its search path.
| Backend | GPUs | Notes |
|---|---|---|
Cuda | NVIDIA | Fastest on NVIDIA in the author’s benchmarks, especially for SSIMULACRA2 and CVVDP |
HIP | AMD | The author currently recommends Vulkan on AMD because of an AMD driver bug; MITIGATE_MALLOC_ASYNC=on builds a slower workaround |
Vulkan | Any GPU with a Vulkan driver | Most portable; close to CUDA for Butteraugli, slower for the other two. Even runs on a CPU through Mesa’s lavapipe, slowly |
FFVship
Section titled “FFVship”FFVship --source reference.mkv --encoded distorted.mkv -m SSIMULACRA2 --json scores.json --svg scores.svgIt prints the mean, standard deviation, median, 5th and 95th percentiles, minimum and maximum of the per-frame scores. For CVVDP it prints a single video score instead.
| Option | Use |
|---|---|
--source/-s, --encoded/-e | Reference and distorted video |
-m, --metric | SSIMULACRA2 (default), Butteraugli or CVVDP |
--json FILE | Per-frame scores; Butteraugli gives [q-norm, 3-norm, max-norm] per frame, CVVDP a running video score |
--svg FILE | Graph of the per-frame scores |
--start, --end, --every N | Score a range, or every Nth frame for a quick estimate |
--encoded-offset N | Shift the encode by N frames if the files don’t start on the same frame |
--cropTopSource and similar | Crop either input before scoring |
-g, --gpu-threads | Frames in flight on the GPU (default 3); lower it to save VRAM |
-t, --threads | Decoder processes (default 1); raise to 2–3 if decoding can’t keep the GPU busy |
--gpu-id, --list-gpu | Pick a GPU on multi-GPU systems |
--intensity-target, --qnorm | Butteraugli display brightness in nits (default 203) and extra norm (default 2) |
--displayModel, --resizeToDisplay | CVVDP display model (default standard_fhd) and scaling to its resolution |
--cache-index | Keep the FFMS2 index files so repeat runs start faster |
--verbose | Print the size, range, matrix, transfer and primaries FFVship used for each file |
Note that -e means --encoded, not --every. If the two videos have different sizes, FFVship resizes the encode to match the reference; crop first if the difference is borders rather than scaling.
FFVship converts to RGB using each file’s colour tags and guesses any that are missing, so an untagged file can score very differently from the same file tagged correctly. Check with --verbose, and tag the reference and encodes (for example BT.709, limited range) when the source is untagged.
VapourSynth plugin
Section titled “VapourSynth plugin”| Function | Main arguments | Per-frame properties |
|---|---|---|
core.vship.SSIMULACRA2(ref, dist) | numStream, gpu_id | _SSIMULACRA2 |
core.vship.BUTTERAUGLI(ref, dist) | qnorm=2, intensity_multiplier=203, distmap=0, numStream, gpu_id | _BUTTERAUGLI_QNorm, _BUTTERAUGLI_3Norm, _BUTTERAUGLI_INFNorm |
core.vship.CVVDP(ref, dist) | model_name="standard_fhd", resizeToDisplay=0, distmap=0, disableTemporal=0 | _CVVDP (running score for frames 0 to n) |
Both clips must have the same dimensions and length. numStream limits how many frames are on the GPU at once and so the VRAM used; it defaults to the number of VapourSynth threads. distmap=1 returns a greyscale difference map instead of scores, which you can step through in a previewer. For CVVDP, request the frames in order and read _CVVDP from the last one.
Run scripts that print scores with python, not vspipe:
import vapoursynth as vscore = vs.core
ref = core.bs.VideoSource("reference.mkv")dist = core.bs.VideoSource("distorted.mkv")clip = core.vship.SSIMULACRA2(ref, dist)scores = [f.props["_SSIMULACRA2"] for f in clip.frames()]print(sum(scores) / len(scores))The metric pages show complete scripts that summarise the scores with a mean, a low percentile and the worst frames.
Licence
Section titled “Licence”Vship’s README calls it MIT-licensed, but the LICENSE file is a modified “MIT NON-AI License” that forbids using the code, or including it in datasets, for training machine-learning systems.