vszipcu
VapourSynth CUDA image processing, written in Zig.
Requirements
- An NVIDIA GPU. (Only NNEDI3 cares about the architecture: its tensor-core predictor needs sm_80+, and it falls back to an fp32 path automatically below that.)
- NVRTC at runtime: plugin looks in exactly two places: its own directory, then the
nvidia-cuda-nvrtcwheel (pip install nvidia-cuda-nvrtc, CUDA 13.3+).
Install
pip install vapoursynth-vszipcu # released wheels (Windows / Linux x86_64)
The wheel drops the plugin where VapourSynth autoloads it and pulls nvidia-cuda-nvrtc
automatically — no toolkit needed at runtime, just the NVIDIA driver.
Build
Directly from a clone, via Python (no local Zig needed — pip fetches the ziglang wheel;
the CUDA toolkit must be installed and findable via CUDA_PATH):
git clone https://github.com/dnjulek/vapoursynth-zipcu
cd vapoursynth-zipcu
pip install . # build + install into site-packages
The zig path leaves the DLL in zig-out — copy it to a VapourSynth plugin dir yourself
(and keep NVRTC findable, see Requirements).
Common arguments
device_id=0 selects the GPU. num_streams sets how many frames the filter processes
concurrently — more streams means more VRAM, raising it past 4 rarely helps.
Filters
core.vszipcu.Bilateral(clip clip[,
float[] sigma_spatial=3.0, # per-plane; chroma default = sigma[0]/sqrt((1<<ssw)*(1<<ssh))
float[] sigma_color=0.02, # per-plane; operates on the [0,1] domain at every depth
int[] radius, # per-plane; default max(1, round(sigma_spatial*3))
int device_id=0, int num_streams=4,
int use_shared_memory=1, # shared-memory tile kernel while the tile fits 48 KiB
int block_x=32, int block_y=8,
clip ref]) # joint/cross bilateral: weights from ref, values from clip
8/16-bit integer, 16-bit half, 32-bit float.
core.vszipcu.BM3Dv2(clip clip[, # and BM3D, same signature
clip ref, # empirical Wiener (2nd pass); also drives block matching
float[] sigma=3.0, # per-plane; < FLT_EPSILON => plane not processed
int[] block_step=8, # per-plane, 1..8
int[] bm_range=9, # per-plane, > 0
int radius=0, # temporal radius; 0 = spatial only
int[] ps_num=2, int[] ps_range=4,
bint chroma=False, # CBM3D; YUV444PS only, block matching on Y
int device_id=0, int num_streams=4,
int extractor_exp=0, # >= 3 => deterministic (order-independent) sums
data[] bm_error_s="ssd", # ssd | sad | zssd | zsad | ssd/norm
data[] transform_2d_s="dct", # dct | haar | wht | bior1.5
data[] transform_1d_s="dct",
bint zero_init=True, # BM3D, radius>0 only: zero the unprocessed planes of the
# stacked output instead of leaving them uninitialized
bint fast_fused=False]) # see below
core.vszipcu.VAggregate(clip clip, clip src, int[] planes)
32-bit float only. BM3Dv2 is the one-step interface; BM3D alone emits the stacked accumulator
clip if you want to drive VAggregate yourself. Per-plane arrays follow upstream's rule: element
i, when absent, falls back to element i-1.
fast_fused (BM3Dv2, radius > 0): runs the collaborative filter and the temporal
aggregation as one kernel chain, keeping the accumulator stack on the GPU. Byte-identical output,
+63 % to +169 % faster — but it costs 1.7x to 7.9x the VRAM (the accumulator cache grows
quadratically with radius and is independent of num_streams). Hence opt-in. If the cache does
not fit it raises an error telling you what it needed; it never silently falls back.
Note extractor_exp=0 (the default, matching upstream) makes the result nondeterministic —
float atomicAdd order is arbitrary. Use extractor_exp >= 3 if you need reproducibility.
core.vszipcu.Deband(clip clip[,
int[] iterations=1, # debanding passes, 0..32; 0 = grain only
float[] threshold=3.0, # cut-off threshold. Higher debands harder, but
# progressively destroys image detail
float[] radius=16.0, # initial sampling radius (grows each iteration).
# Higher finds more gradients; lower smooths more aggressively
float[] grain=4.0, # noise added on top, to cover residual
# quantization; 0 = none
int[] planes, # which planes to process at all; default: all
bint dither=True, # 8-bit output only, first processed plane only
int dither_algo=0, # 0=blue noise | 1=bayer | 2=ordered fixed | 3=white noise
int device_id=0, int num_streams=1])
8/16-bit integer, 16-bit half, 32-bit float.
The four strength parameters are per-plane arrays.
int[] planes says which planes are touched, the arrays say how.
core.vszipcu.DFTTest(clip clip[,
int ftype=0, # 0 = Wiener: mult by max((psd-sigma)/psd, 0) ** f0beta
# 1 = hard threshold: zero the bin when psd < sigma
# 2 = mult every bin by sigma
# 3 = mult by sigma inside [pmin,pmax], else by sigma2
# 4 = mult by sigma*sqrt(psd*pmax / ((psd+pmin)*(psd+pmax)))
float sigma=8.0, # main strength (scaled by the window; see slocation/ssx/..)
float sigma2=8.0, # ftype=3 only: strength outside [pmin,pmax]
float pmin=0.0, # ftype=3/4 only: PSD bounds
float pmax=500.0,
int sbsize=16, # spatial block size; must be 16 in this backend
int sosize=12, # spatial overlap, 0..15; >50% needs (sbsize-sosize) | sbsize
int tbsize=3, # temporal block size; ODD, 1..7 (1 = spatial only)
int swin=0, # spatial window: 0=hanning 1=hamming 2=blackman
int twin=7, # temporal window: 3=4-term b-harris 4=kaiser-bessel
# 5=7-term b-harris 6=flat top 7=rectangular 8=bartlett
# 9=bartlett-hann 10=nuttall 11=blackman-nuttall
float sbeta=2.5, # kaiser-bessel beta (swin=4 / twin=4)
float tbeta=2.5,
bint zmean=True, # subtract the windowed mean before filtering
float f0beta=1.0, # ftype=0 exponent (1.0 = plain Wiener, 0.5 = sqrt)
float[] slocation, # frequency-dependent sigma: [freq, sigma, freq, sigma, ...]
float[] ssx, float[] ssy, float[] sst, # per-axis variants of slocation
int ssystem=0,
int[] planes, # default: all
int device_id=0, int num_streams=1])
8-16-bit integer and 32-bit float.
core.vszipcu.EEDI3(clip clip, int field[, # and EEDI3H, same args, horizontal
bint dh=False, # double the height (keep every source line)
int mdis=20, # max connection radius, 1..40; larger connects shallower
# lines, but costs speed and risks artifacts
int nrad=2, # radius used for neighborhood similarity, 0..3
float alpha=0.2, # 0..1 (alpha+beta <= 1): weight given to connecting similar
# neighborhoods. Larger = more lines/edges connected
float beta=0.25, # 0..1: weight given to the vertical difference created by
# the interpolation. Larger = fewer edges connected
# (1.0 = no edge directedness at all)
float gamma=20.0, # >= 0: penalizes changes in interpolation direction.
# Larger = smoother interpolation field
bint hp=False, # search half-pixel directions too
int vcheck=2, # 0..3: strength of the vertical-consistency check
float vthresh0=32.0, # vcheck thresholds; all must be > 0 when vcheck > 0
float vthresh1=64.0,
float vthresh2=4.0,
clip sclip, # source for the vcheck comparison
int device_id=0, int num_streams=1])
8/16-bit integer, 16-bit half, 32-bit float. field: 0/1 pick the field to keep, 2/3 are the
double-rate variants (not allowed with dh=True).
core.vszipcu.FGrain(clip clip[,
int num_iterations=800, # Monte-Carlo samples per pixel; quality vs speed
float grain_radius_mean=0.1, # mean grain radius, in pixels
float grain_radius_std=0.0, # grain-radius spread; 0 = every grain the same size
float sigma=0.8, # std dev of the Gaussian in the grain model
int seed=0,
int device_id=0, int num_streams=1])
32-bit float. Physically-based film grain synthesis. For temporally-varying grain, set the
integer frame property FGRAIN_SEED_OFFSET on the source frame — it is added to seed.
core.vszipcu.GaussBlur(clip clip[,
float[] sigma=0.5, # per-plane; chroma default = sigma[0]/sqrt((1<<ssw)*(1<<ssh))
int device_id=0, int num_streams=1])
8/16-bit integer, 16-bit half, 32-bit float. A plane whose sigma is below FLT_EPSILON is copied
through untouched.
core.vszipcu.NLMeans(clip clip[,
int d=1, # temporal radius; 0 = spatial only
int a=2, # search-window radius, 1..64
int s=4, # patch radius, 0..8
float h=1.2, # > 0: filtering strength
string channels="auto", # auto | Y | UV | YUV | RGB
int wmode=0, # weighting function of the patch distance x, 0..3:
# 0 = exp(-x) | 1 = max(1-x, 0)
# 2 = max(1-x, 0)**2 | 3 = max(1-x, 0)**8
float wref=1.0, # >= 0: weight of the pixel itself
clip rclip, # weights computed from rclip, values from clip
int device_id=0, int num_streams=1])
8/16-bit integer, 16-bit half, 32-bit float. channels="YUV" requires 4:4:4 — a joint patch
distance needs one shared pixel lattice — so on subsampled clips run a "Y" pass and a "UV"
pass instead.
core.vszipcu.NNEDI3(clip clip, int field[,
bint dh=False, # double the height (keep every source line)
int[] planes, # default: all
int nsize=6, # predictor neighborhood, 0..6: 0=8x6 1=16x6 2=32x6
# 3=48x6 4=8x4 5=16x4 6=32x4
int nns=1, # predictor neurons, 0..4: 0=16 1=32 2=64 3=128 4=256
int qual=1, # 1 or 2: number of predictor passes averaged
int etype=0, # 0 = weights trained on absolute error, 1 = squared error
int pscrn=2, # prescreener, 0..4: 0=off (predict every pixel)
# 1=original, 2..4=new levels 0..2 (higher = fewer pixels
# left to cubic interpolation: slower, slightly better)
int device_id=0, int num_streams])
8/16-bit integer, 16-bit half, 32-bit float. Weights are embedded in the plugin — no external
nnedi3_weights.bin file needed. On sm_80+ the predictor runs on tensor cores (fp16 inputs,
fp32 accumulation); older cards fall back to an fp32 path automatically.
Credits
| filter | ported from |
|---|---|
| Bilateral | VapourSynth-BilateralGPU (rtc variant), by WolframRhodium |
| BM3D / BM3Dv2 / VAggregate | VapourSynth-BM3DCUDA, by WolframRhodium |
| DFTTest | vs-dfttest2 (NVRTC backend), by AmusementClub |
| FGrain | vs-fgrain-cuda, by AmusementClub |
| NNEDI3 | VapourSynth-nnedi3vk, by HolyWu |
| Deband | vs-placebo, by Lypheo (libplacebo's pl_shader_deband + pl_shader_dither) |
| NLMeans | KNLMeansCL, by Khanattila |
| EEDI3 / EEDI3H | VapourSynth-EEDI3 (by HolyWu) and vapoursynth-zip (by dnjulek) — the CPU references |
Built on cudaz (CUDA driver API + NVRTC bindings,
vendored in cudaz/).
Metadata
Release files for vapoursynth-vszipcu 1.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| vapoursynth_vszipcu-1.2.0.tar.gz | 12.8 MB | Details |
Built distributions (wheels)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| vapoursynth_vszipcu-1.2.0-py3-none-win_amd64.whl | Python 3 | none | Windows x86-64 | Details |
| vapoursynth_vszipcu-1.2.0-py3-none-manylinux_2_17_x86_64.whl | Python 3 | none | Linux glibc 2.17+ x86-64 | Details |
Total release size: 38.8 MB
Release files / vapoursynth_vszipcu-1.2.0.tar.gz
| Download URL | vapoursynth_vszipcu-1.2.0.tar.gz |
|---|---|
| Size | 12.8 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
43a12a562b386e42d718cd19a57948b6e53f626900c36613a327ae039ba1e828
|
|
BLAKE2b-256 checksum How to use checksums |
e1b06ae895af5a15a2c63933178f542cf547c885ae627965931b5fc742614358
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 14, 2026.
Transparency logRelease files / vapoursynth_vszipcu-1.2.0-py3-none-win_amd64.whl
| Download URL | vapoursynth_vszipcu-1.2.0-py3-none-win_amd64.whl |
|---|---|
| Size | 13.0 MB |
| Tags | Python 3 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
7c71247a8caf82883187b2560c690807cf2ea4a2db465f935cbd923ef21251fd
|
|
BLAKE2b-256 checksum How to use checksums |
06278977bc201fe31508f7af00aa261132f73af32ad4cf8bb39d31dd92dd48a1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 14, 2026.
Transparency logRelease files / vapoursynth_vszipcu-1.2.0-py3-none-manylinux_2_17_x86_64.whl
| Download URL | vapoursynth_vszipcu-1.2.0-py3-none-manylinux_2_17_x86_64.whl |
|---|---|
| Size | 13.0 MB |
| Tags | Linux glibc 2.17+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
e3f4d5f1f5eeebb1e507c6aee2b554a69b52ac716b574bc4f67f2701505cf7d7
|
|
BLAKE2b-256 checksum How to use checksums |
adaf3906459e05a23e5cbfb88037bd8e2de3ef8de0cdeae961acb2d21e1b9bcc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 14, 2026.
Transparency log