Optimize FFmpeg for AMD EPYC Multi Core CPUs

High-core-count processors like AMD EPYC offer massive computational power, but standard FFmpeg configurations often fail to utilize all available cores efficiently. This guide explains how to optimize FFmpeg for multi-core server CPUs by configuring thread settings, leveraging NUMA node pinning, using parallel execution, and fine-tuning codec-specific parameters to maximize encoding throughput.

1. Understand Threading Limits in Codecs

Most video encoders have upper limits on how many threads they can efficiently scale across. For example, libx264 and libx265 exhibit diminishing returns or even performance degradation when assigned more than 16 to 22 threads for a single 1080p or 4K encode.

If you run a single FFmpeg command on a 64-core AMD EPYC processor, the CPU utilization will likely remain low. To combat this, you must control the threading behavior manually:

Example of limiting a single process to 16 threads:

ffmpeg -i input.mp4 -c:v libx265 -threads 16 -preset medium output.mp4

2. Leverage Parallel Processing (Multi-Instance Encoding)

The most effective way to utilize a 64-core or 128-core EPYC processor is to run multiple FFmpeg instances simultaneously rather than trying to force one video to use all cores.

Using GNU Parallel

You can batch process multiple videos concurrently, assigning a set number of threads to each instance.

find . -name "*.mp4" | parallel -j 4 ffmpeg -i {} -c:v libx264 -threads 8 outputs/{/.}.mkv

In this example, -j 4 runs 4 parallel FFmpeg jobs, and each job is limited to 8 threads, utilizing 32 threads in total.

Segment-Based Encoding (Split and Merge)

For single large files, use tools like Chunked Encoding to split the video into segments, encode the segments in parallel across your EPYC cores, and concatenate them back together.

3. Optimize for NUMA Architecture

AMD EPYC processors use a Multi-Chip Module (MCM) design, split into multiple Non-Uniform Memory Access (NUMA) nodes. If an FFmpeg thread running on NUMA node 0 tries to access memory allocated on NUMA node 3, it introduces latency.

To prevent performance loss from inter-socket and inter-node memory transfer:

Launch FFmpeg bound to NUMA node 0 and its local memory:

numactl --cpunodebind=0 --membind=0 ffmpeg -i input.mp4 -c:v libx265 -threads 16 output.mp4

If running parallel jobs, distribute them across different NUMA nodes:

# Run Job 1 on Node 0
numactl --cpunodebind=0 --membind=0 ffmpeg -i vid1.mp4 -c:v libx265 output1.mp4 &
# Run Job 2 on Node 1
numactl --cpunodebind=1 --membind=1 ffmpeg -i vid2.mp4 -c:v libx265 output2.mp4 &

4. Codec-Specific Threading Parameters

For maximum efficiency, pass threading parameters directly to the underlying libraries (libx264, libx265, libvpx-vp9, libsvtav1).

libx265 (HEVC)

libx265 is highly sensitive to NUMA pools. Use the pools parameter to restrict the encoder to specific CPU sockets or core complexes.

ffmpeg -i input.mp4 -c:v libx265 -x265-params pools=16 output.mp4

This restricts libx265 to a pool of 16 threads, preventing it from spawning threads across the entire EPYC socket.

SVT-AV1 (AV1)

SVT-AV1 is designed specifically for modern multi-core server CPUs and scales much better than older encoders.

ffmpeg -i input.mp4 -c:v libsvtav1 -svtav1-params lp=16 output.mp4

The lp=16 parameter limits logical processor utilization to 16 threads for that specific instance.