Documentation
API reference

Start here

Search every guideesc close

GPU

Hardware transcoding

Use your GPU for transcoding. Free and on by default.

6 min read

When a device can't play a file directly, tofa converts it on the fly. That's the most demanding thing your server does. With a GPU, it happens in hardware: more simultaneous streams, far less CPU, less heat and noise.

Hardware transcoding is free and on by default. If tofa finds a working GPU it uses it, and if a hardware session ever fails mid-stream it falls back to software automatically.

Does my machine qualify?#

You have Works with Rough minimum
An NVIDIA card NVENC GTX 600 series (2012) or newer, driver 470+
An Intel CPU with graphics, or Arc Quick Sync 5th gen Core (2014) or newer
An AMD GPU VAAPI GCN cards (2012) or newer
A Mac with Apple silicon VideoToolbox any Apple silicon Mac, using the native tofa app

These are guidance, not gates. tofa doesn't guess from a spec sheet: it runs a real test encode on your GPU and probes which codecs it can decode. Press Test hardware in Settings to see the result for your exact machine, including the reason if something fails.

Running Docker on a Mac or Windows? That works fine as a server, but Docker Desktop runs containers in a virtual machine that can't reach the video hardware, so transcoding stays on the CPU. On a Mac, the native tofa app uses the GPU.

Give the container your GPU#

The compose builder in the server guide has a hardware transcoding question that adds the right lines for you. If you already have a running server, add them to your docker-compose.yml yourself:

Intel or AMD:

services:
  tofa:
    # ...everything you already have...
    devices:
      - /dev/dri:/dev/dri

That is all it takes. tofa reads the group that owns the /dev/dri devices and grants it to itself on startup, so there is no group id to look up.

Two setups still need you to name the group yourself, because tofa cannot work it out from inside the container:

  • You pin the container to a fixed user (a user: line in Compose, or a Kubernetes securityContext). Add group_add with your host's render group: run getent group render and use the number it prints.
  • Your /dev/dri devices belong to root rather than to a render or video group. tofa will not give itself root's group, so either name a group that can reach the device or fix the ownership on the host.

If you already have a group_add line, leave it. It still works, and it is used alongside whatever tofa detects.

NVIDIA: install the NVIDIA container toolkit on the host once, then hand the GPU to the container. Which snippet you use depends on your Compose version: check with docker compose version.

Compose v2.30 or newer has the short gpus line:

services:
  tofa:
    # ...everything you already have...
    gpus: all
    environment:
      NVIDIA_DRIVER_CAPABILITIES: compute,video,utility

Compose v2.29 or older doesn't understand gpus and stops with Additional property gpus is not allowed. Use the longer form, which does the same thing:

services:
  tofa:
    # ...everything you already have...
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: all
              capabilities: [gpu]
    environment:
      NVIDIA_DRIVER_CAPABILITIES: compute,video,utility

Either way, keep the NVIDIA_DRIVER_CAPABILITIES line. The video part unlocks the NVENC encoder; without it, detection finds nothing even though the GPU is attached.

Then docker compose up -d to recreate the container.

Not using Docker? On a bare Linux install, add the tofa service user to the render group and restart. On a Mac, the native app needs nothing.

Podman on Fedora, RHEL, or another SELinux system#

Most people can skip this. If you run tofa under Podman on a distribution with SELinux enforcing (Fedora, RHEL, Rocky, and similar), the Docker snippets above need a few Podman-specific lines to reach the GPU. Podman does not understand gpus: all, so hand the card over with a CDI device instead:

services:
  tofa:
    # ...everything you already have...
    devices:
      - nvidia.com/gpu=all
    security_opt:
      - label=disable
    environment:
      NVIDIA_DRIVER_CAPABILITIES: compute,video,utility

Generate the CDI device once on the host with sudo nvidia-ctk cdi generate --output=/etc/cdi/nvidia.yaml, and regenerate it after every driver update or the GPU disappears. The label=disable line lifts SELinux confinement for the container so it can open the /dev/nvidia* nodes. It is the blunt option. If you would rather keep the container confined, install the container toolkit's SELinux policy and swap in label=type:nvidia_container_t.

Running Podman rootless adds two more lines. group_add: [keep-groups] keeps the host's render and video groups so the container can reach the card, and userns_mode: keep-id maps your user into the container so bind-mounted media and config keep sane ownership:

    group_add:
      - keep-groups
    userns_mode: keep-id

Run tofa as root under Podman and you can leave those last two out.

Check that it works#

Open Settings → Transcoding. The Hardware Transcoding card shows a status pill that reads Active with your backend name once detection finds a working GPU. Press Test hardware to re-run the check any time, for example right after adding the compose lines above.

Under advanced options you can see what was detected, including which codecs your GPU decodes in hardware. Codecs it can't decode still get hardware encoding, which is most of the win.

Play something that needs converting and check the activity panel: the stream shows up as a hardware session.

The settings, briefly#

The defaults are sensible, so you can skip these. If you want the knobs:

  • Mode. Auto picks the best working backend. You can force one, or turn hardware off.
  • Encoder quality. Speed, balanced, or quality. This keeps working with hardware on.
  • HDR tone mapping in hardware. Converts HDR to SDR on the GPU for devices that can't show HDR. Works on every backend, including Macs.
  • HEVC full-transcode encoding. Off by default. When a bandwidth cap forces a full conversion, the server can produce HEVC instead of H.264, which keeps more detail at the same bandwidth on devices that support it. A compatible GPU is preferred; on the processor it can be significantly slower.
  • Max hardware transcodes. How many GPU sessions to allow at once. Auto sizes this per vendor; when the GPU is full, extra streams use the CPU instead of failing.
  • Software fallback. Keep this on. A failed hardware session retries in software invisibly, and a GPU that keeps failing is rested for 30 minutes while everything continues on CPU.

If the test fails#

The card shows the actual error from the encoder, which usually points at the cause:

  • Permission denied on /dev/dri: tofa could not give itself the group that owns the devices. That happens when the container is pinned to a fixed user (a user: line, or a Kubernetes securityContext), or when the devices belong to root rather than to a render group. Run ls -l /dev/dri on the host to see which, then add the owning group with group_add.
  • No such device, or nothing detected at all: the devices: or gpus: lines are missing, or (NVIDIA) the container toolkit isn't installed on the host.
  • Additional property gpus is not allowed: your Compose is older than v2.30. Switch to the deploy: form in the NVIDIA section above.
  • Could not open encoder, or a list of encoder settings reported as unsupported: tofa reached the GPU, and the graphics driver then refused to start the encoder. That is a driver or firmware problem on the host, not a tofa setting. On Linux, vainfo shows what your driver can actually do: if it lists no VAEntrypointEncSlice or VAEntrypointEncSliceLP line for H.264, the encoder is not available to the system and updating the graphics driver package is the first thing to try. Set Mode back to Auto while you work on it, so playback stops attempting a device that isn't working.
  • Anything else: send us the error text. That output is exactly what we need.