Historical recipe — not current setup guidance. Measurements and implementation links below belong to their original workload and revision. Deployment labels and defaults may be obsolete. Use the reviewed performance recipes; browse historical evidence.

AVPlumber · Mixer optimization cookbook

One thread per GPU filter graph

Implementation links are pinned to develop at c20464a5 (2026-10-09). The mixer has changed since; this page was checked against develop at 8fa15c92 on 2026-10-13. Measurements retain their original workload and scope. Watch the current 192-input L4 demo.

Current scope. Current GPU filter graphs still use threads: 1. Per-slot scale/filter hooks have been removed; geometry belongs to the compositor or an upstream source transform. The pinned and hwupload raw paths use single-threaded FFmpeg graphs; the setup's direct path has none. A source whose colour stage would only tag has no colour graph: its fan-out stamps the tags (Colour tags at the fan-out).

The mixer runs filter graphs that only stamp metadata or call CUDA filters on one thread. FFmpeg would otherwise start an idle 16-thread slice pool for each of them.

Status: Implemented

Decoder options.threads=1 is a separate optimization: it avoids extra NVDEC surfaces reserved for frame threads. See the decoder baseline and low-DPB demo assets; both savings were already present before the CUarray comparison.

The idea

Each filter_video node builds an FFmpeg filter graph. By default FFmpeg gives every graph a slice-thread pool with one thread per CPU: 16 on the T4 test host. A graph of setparams or scale_cuda never uses that pool. The GPU does the pixel work, and the node's own thread calls the filters.

Left: with the default, each graph gets 16 idle slice threads. Right: with threads 1, the node thread runs the same graph and no pool starts. Default: threads 0 Mixer GPU graphs: threads 1 filter_video node thread filter_video node thread graph: setparams, scale_cuda pixel work runs on the GPU graph: setparams, scale_cuda pixel work runs on the GPU slice pool: 16 threads (1 per CPU) started per graph, never given work no slice pool same filters, same calls, same thread
One graph, before and after. The mixer builds many such graphs: colour, transition, aux conversion and rendition conversion graphs.
Measured live threads: 3,689 in the mixer process, of which 1,088 named mixer_color_sdr and 448 named mixer_color_bro. Threads measured live in the mixer process (default threads) whole process 3,689 mixer_color_sdr 1,088 mixer_color_bro 448 Mostly idle (12 % CPU). The change removes about 1,600 idle threads.
Bars are to scale. Both names belong to the mixer's per-source colour graphs, cut short by the system's thread-name limit. Measured on the T4 before the change; since 2026-10-13 a source whose colour stage only tagged, every browser source among them, has no such node.

At a glance

Where in the code

Source links identify the reviewed code, independently of deployment state.