TL;DR — A fresh EPYC "Turin" (EPYC 9005, Zen 5) server runs at maybe 70–90% of what it could. The defaults play it safe. Real gains come from three layers: BIOS/UEFI (NUMA layout, SMT, determinism, power), the operating system (CPU governor, C-states, sysctl, tuned, hugepages, NUMA, IRQ), and your application (compiler flags, pinning, memory layout). This guide walks every layer in plain words, with copy-paste commands, per-workload recipes, and a deep Operating-System-Optimizations section. The golden rule throughout: tune for your workload and measure every change.
How to use this guide
This is long on purpose — it's meant to be the one page your whole team can return to. You don't read it top to bottom. You:
- Read the mental model and the workflow once, so the rest makes sense.
- Jump to BIOS and Operating System for the knobs.
- Find your workload in the recipes and copy the starting settings.
- Use the verify loop and the checklist to roll it out safely.
Every command here is safe to read; test each on a non-production box first. Where the cloud takes a knob away from you (you can't open BIOS on EC2), the guide says so and gives the cloud equivalent.
What "Turin" actually is
"Turin" is the codename for AMD's 5th-generation EPYC 9005 series, built on the Zen 5 core. Compared with the previous generation at the same power envelope, a 64-core part delivers roughly 20% more integer and 34% more floating-point performance. Two important sub-families:
- Turin (classic Zen 5) — up to 128 cores, the best per-core speed and highest clocks. Pick this for latency-sensitive, per-core-licensed, and mixed work.
- Turin Dense (Zen 5c) — up to 192 cores, maximum total throughput per socket. Pick this for scale-out, containers, and throughput-bound services.
It uses the SP5 socket (same as the prior gen, so many platforms drop-in upgrade), 12 channels of DDR5 memory (up to DDR5-6000), and PCIe Gen5. Zen 5 also has a full-width AVX-512 datapath, which matters a lot for HPC and AI math.
The mental model: chiplets, the I/O die, and NUMA
You can't tune what you don't picture. A Turin chip is not one slab of silicon. It's several core chiplets (CCDs) — each a cluster of cores with its own slice of L3 cache — arranged around one central I/O die (IOD). The IOD owns the memory controllers (those 12 DDR5 channels) and the PCIe/Infinity Fabric links. Cores talk to memory through the IOD.
Two consequences drive almost every tuning decision:
- Cache locality. Each CCD has an exclusive L3 (about 32 MB). Threads that share data run fastest when pinned to the same CCD/L3. Spreading them across CCDs adds cross-chiplet hops.
- Memory locality (NUMA). How the chip groups its cores and memory into NUMA nodes (the NPS setting) decides whether a core's data sits "next door" or "across town." NUMA-aware software that keeps work near its memory wins big; software that ignores NUMA can be hurt by aggressive splitting.
Fig 1 — Turin's chiplet layout. Each CCD holds cores + exclusive L3; all memory traffic flows through the central I/O die.
The tuning workflow (read this before changing anything)
Tuning without measuring is just superstition. Follow the loop every time:
- Baseline. Record your real metric on defaults — requests/sec, query p99 latency, job wall-time, tokens/sec. Write it down with the exact config.
- Change one thing. One BIOS or OS setting at a time. If you change five at once and it gets faster, you've learned nothing about why.
- Re-run 5–10 times. Servers and especially clouds vary run to run. Report the median and the spread, never a single lucky number.
- Watch the costs. Power draw, temperature, and consistency — not just peak speed. A setting that wins benchmarks but throttles under sustained load is a loss.
- Write it as config. Bake the winning settings into infrastructure-as-code / BIOS templates so every node is identical and reproducible.
There is no universal "fast." A database, a web fleet, an HPC job, and an AI server each want different settings — sometimes opposite ones. Optimize for the workload in front of you.
Layer 1 — BIOS / UEFI tuning
These knobs live in the firmware (often under "AMD CBS" or your vendor's performance menu). They set how the silicon presents itself before the OS even boots. On bare metal you control them directly; on the cloud you usually choose them indirectly via instance type and CPU options.
NPS — NUMA Nodes Per Socket (the big one)
NPS decides how the chip's memory and cores are grouped into NUMA nodes:
- NPS=1 — one big memory pool per socket. Simplest, most forgiving. Best default for general/cloud and software that isn't NUMA-aware.
- NPS=2 — two pools. A middle ground.
- NPS=4 — four "quadrants," each with its own cores, L3 region, and memory channels. Lowest local-memory latency and highest aggregate bandwidth if your software keeps work local. Best for HPC and well-tuned databases.
Rule of thumb: NUMA-aware app → NPS=4; NUMA-blind app → NPS=1.
SMT — two threads per core
SMT (AMD's name for hyper-threading) lets each physical core run two threads. Leave it on for most servers — it raises total throughput. Turn it off for: pure HPC code that wants full uncontended cores, hard real-time latency, or to cut per-thread software licence costs (some databases bill per hardware thread).
Determinism — steady vs maximum
Power determinism makes every identical chip behave the same (predictable, slightly slower). Performance determinism lets each chip run as fast as its silicon and power budget allow. For maximum performance choose Performance; for fleets that must be cycle-for-cycle identical, choose Power.
cTDP and Package Power — how much wattage you allow
Each Turin SKU has a configurable power range (cTDP, e.g. up to 400W on top parts). Setting it to the maximum supported lets the chip sustain higher clocks under all-core load. The catch: only do this if your cooling and power delivery can handle it — otherwise the chip just thermally throttles and you lose the gain (and waste watts).
Core Performance Boost (CPB) and power profile
Keep CPB enabled so cores can opportunistically clock above base. Choose the high-performance system power profile. Energy-saving profiles cap clocks and add latency — fine for idle-heavy fleets, bad for anything user-facing.
Fabric and latency knobs
- APBDIS=1 + fixed Data Fabric P-state (DF P-state 0) — stops the internal fabric from dropping into low-power states, removing latency jitter. Set for HPC and low-latency workloads.
- LLC (L3) as NUMA — exposes each L3/CCD cluster as its own NUMA node. Helps cache-sensitive and some HPC apps that benefit from tight locality; can confuse NUMA-blind apps, so test.
- Memory interleaving — leave at the auto value matched to your NPS choice.
- DRAM / hardware prefetchers — usually best left enabled; a few streaming HPC kernels benefit from tuning them, but only after measuring.
Platform / virtualization knobs
- x2APIC — enable; required for high core counts (interrupt addressing).
- IOMMU — enable for virtualization, device passthrough, and isolation. Pair with hugepages for guests.
- SR-IOV — enable for high-performance NIC passthrough into VMs/containers.
c7a/c8a general, r7a/r8a memory-heavy, hpc7a for HPC), and CPU options like threads-per-core (effectively SMT on/off) at launch. Note many cloud AMD instances ship SMT off by default, so 1 vCPU = 1 full physical core. Match the instance to the workload the way you'd match BIOS settings on metal.Layer 2a — CPU optimizations
Once the firmware is right, squeeze the cores.
Find CPU-bound work
# near-100% utilization = CPU-bound; load average >> core count = oversubscribed
htop
uptime # load averages vs nproc
mpstat -P ALL 1 # per-core utilization and steal
Build for Zen 5 specifically
Compilers generate faster code when told the exact target. For Turin use znver5 and enable the wide vector units:
# target Zen 5 (Turin). Use znver4 for prior gen, znver3/znver2 for older.
gcc -O3 -march=znver5 your_program.c -o your_program
# enable AVX2 / AVX-512 (Zen 5 has a full-width AVX-512 datapath)
gcc -O3 -march=znver5 -mavx2 -mavx512f your_program.c -o your_program
For math-heavy code, link the AMD Optimizing CPU Libraries (AOCL) — tuned BLAS, FFT, libm, etc.:
gcc -I$AOCL_ROOT/include -L$AOCL_ROOT/lib -lamdlibm -lm your_program.c -o your_program
# faster (lower-precision) math variants:
gcc ... -lamdlibm -fveclib=AMDLIBM -lm # vectorized libm
gcc ... -lamdlibm -fsclrlib=AMDLIBM -lamdlibmfast -lm
Respect the L3 / CCD — pin hot threads together
Threads sharing data should stay on the same CCD so they share its L3 cache instead of bouncing across the fabric:
lscpu # see CPU topology
lstopo # visualize CCD / cache / NUMA layout (hwloc)
# pin a process to cores that share one L3 domain
taskset -c 0-7 your_application
# pin Docker containers
docker run --cpuset-cpus="0-7" my-container
CPU pinning also stops the scheduler from migrating a process away from its warm cache — a common silent performance leak.
Layer 2b — Memory optimizations
Turin has huge memory bandwidth (12 DDR5 channels); the trick is feeding all of it.
Use all the channels and CCDs
Each CCD has ~80 GB/s to the I/O die; per-core usable bandwidth is roughly ~10 GB/s (reads ~2× writes). To saturate bandwidth-hungry apps, spread allocations across all NUMA nodes:
# interleave memory across all NUMA nodes — best for high-bandwidth, low-sharing apps
numactl --interleave=all your_application
# or bind a process to one node's CPUs AND memory (best locality, NUMA-aware apps)
numactl --cpunodebind=0 --membind=0 your_application
Hugepages — cut TLB misses
Large pages reduce address-translation overhead for big working sets (databases, JVMs, HPC):
# reserve explicit 2MB hugepages
echo 1024 > /proc/sys/vm/nr_hugepages
mount -t hugetlbfs nodev /mnt/huge
# verify
grep Huge /proc/meminfo
Some databases prefer explicit hugepages and want Transparent Huge Pages off (next section) — follow the DB vendor's guidance.
Swappiness and overcommit
# keep the kernel from swapping out hot pages
sysctl -w vm.swappiness=10
# database-style strict overcommit (don't promise memory you don't have)
sysctl -w vm.overcommit_memory=2
sysctl -w vm.overcommit_ratio=95
Watch memory live
free -h
vmstat 1
sar -r 1
numastat # per-NUMA-node hits/misses — watch numa_miss climb if pinning is wrong
Layer 2c — Storage / I/O optimizations
- Pick the right device. Local NVMe for hottest data; provisioned-IOPS volumes for demanding DBs; general SSD for the rest. On cloud, ensure the instance is storage/EBS-optimized.
- I/O scheduler. For NVMe SSDs,
none(ormq-deadline) usually beats the rotational schedulers:
# modern kernels: 'none' for NVMe, 'mq-deadline' for mixed
echo none > /sys/block/nvme0n1/queue/scheduler
cat /sys/block/nvme0n1/queue/scheduler
- Stripe for throughput. RAID 0 across volumes raises aggregate IOPS/bandwidth (no redundancy — use for scratch/replicated data):
mdadm --create /dev/md0 --level=0 --raid-devices=2 /dev/nvme1n1 /dev/nvme2n1
- RAM disk for extreme, ephemeral I/O:
mount -t tmpfs -o size=4G tmpfs /mnt/ramdisk. - Async I/O + buffering in the app; minimize random I/O.
- Monitor:
iostat -x 1,iotop,pidstat -d 1to find the bottleneck device/process.
Layer 2d — Latency-sensitive optimizations
For trading, real-time bidding, gaming, and tight-SLA services, you trade throughput for predictability.
# 1. real-time scheduling priority for the hot process
chrt -f -p 90 <PID> # SCHED_FIFO priority 90
# 2. disable deep idle C-states (they add wake-up latency)
cpupower idle-set -d 2
# 3. SMT off for uncontended cores
echo off > /sys/devices/system/cpu/smt/control
cat /sys/devices/system/cpu/smt/active # 0 = disabled
# 4. lock clocks high
cpupower frequency-set -g performance
# 5. Transparent Huge Pages off (avoids allocation stalls/jitter)
echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/defrag
Also: pin the app to isolated cores (isolcpus= kernel param), keep interrupts off those cores (next section), use a low-latency/real-time kernel if microseconds matter, and prefer fast networking (on cloud, ENA; EFA for HPC/ML). Profile with perf, ftrace, and cyclictest (rt-tests) to find jitter sources.
Layer 3 — Operating System optimizations (the deep section)
This is the part most guides skim and the one that quietly decides whether all your BIOS work pays off. The OS sits between the silicon and your app; a perfect BIOS still loses if Linux is in power-save mode. We'll cover: the CPU governor, idle states, sysctl kernel parameters, Transparent Huge Pages, tuned profiles, kernel boot parameters, IRQ affinity, the scheduler, security mitigations, and how to make it all persist.
3.1 — CPU frequency governor
The governor is Linux's policy for clock speed. powersave (a common default) down-clocks idle cores and adds latency; performance keeps them ready. For servers that care about speed:
# set all cores to performance
cpupower frequency-set -g performance
# verify
cpupower frequency-info | grep -i governor
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq
# persist across reboots (enable the service that re-applies it)
systemctl enable --now cpupower
3.2 — Idle (C-)states
Deep C-states save power but cost microseconds to wake from. For latency-critical nodes, limit how deep cores may sleep:
# disable the deepest idle state (index varies by platform; check first)
cpupower idle-info
cpupower idle-set -d 2
# or cap at boot with a kernel param (see GRUB below): processor.max_cstate=1
Leave C-states enabled on power-sensitive, throughput-only fleets — the latency cost doesn't matter there and the power savings are real.
3.3 — sysctl: kernel parameters at runtime
sysctl changes kernel behaviour live. Below are battle-tested starting values for EPYC servers, grouped and explained so you change them with intent, not by cargo-cult. Apply with sysctl -w; persist by writing to /etc/sysctl.d/99-epyc.conf.
Networking — for high-connection, high-throughput services:
# more open file descriptors (every socket is an fd)
sysctl -w fs.file-max=2097152
# bigger socket buffers — lets high-bandwidth/high-latency links fill the pipe
sysctl -w net.core.rmem_max=16777216
sysctl -w net.core.wmem_max=16777216
sysctl -w net.ipv4.tcp_rmem="4096 87380 16777216"
sysctl -w net.ipv4.tcp_wmem="4096 65536 16777216"
# TCP Fast Open — skip a round-trip on repeat connections
sysctl -w net.ipv4.tcp_fastopen=3
# allow a deep accept queue so bursts of new connections aren't dropped
sysctl -w net.core.somaxconn=65535
What each does: file-max raises the system-wide fd ceiling (busy servers exhaust the default). rmem/wmem_max and the tcp_rmem/tcp_wmem triples size receive/send buffers (min, default, max) so a single connection can hold enough in-flight data to saturate a fast link. tcp_fastopen=3 enables TFO for client and server. somaxconn is the max backlog of pending connections — too small and you drop traffic spikes.
Virtual memory — paging and write-back behaviour:
# avoid swapping hot pages (10 = only swap under real pressure)
sysctl -w vm.swappiness=10
# how much dirty data may accumulate before forced write-back
sysctl -w vm.dirty_ratio=60
sysctl -w vm.dirty_background_ratio=2
# strict overcommit for databases (don't over-promise memory)
sysctl -w vm.overcommit_memory=2
sysctl -w vm.overcommit_ratio=95
What each does: swappiness trades off swapping vs reclaiming cache; low keeps your working set in RAM. dirty_ratio/dirty_background_ratio control when the kernel flushes dirty pages — a high foreground ratio lets writes batch (good for bursty write throughput), while the low background ratio starts flushing early to avoid a big stall. overcommit_memory=2 + overcommit_ratio make the kernel refuse allocations beyond a hard limit, which databases prefer over the OOM-killer surprising them.
Filesystem / I/O — for heavy async-I/O apps:
# raise the async I/O request ceiling (databases, high-QD storage)
sysctl -w fs.aio-max-nr=1048576
# express write-back limits in bytes instead of % (precise on big-RAM boxes)
sysctl -w vm.dirty_bytes=1073741824
sysctl -w vm.dirty_background_bytes=536870912
Note: set the *_bytes or the *_ratio pair, not both — the kernel uses whichever you set last. On large-memory Turin nodes the byte form is more predictable (60% of 1.5 TB is a lot of dirty data).
NUMA balancing:
# let the kernel migrate pages toward the cores using them
sysctl -w kernel.numa_balancing=1
Caveat: automatic NUMA balancing helps general workloads but its background page migration can hurt apps you've already pinned by hand (and some HPC/DB workloads). If you do explicit numactl pinning, test with it off (=0) too.
Persist it all:
# write to a drop-in file so it survives reboot
sudo tee /etc/sysctl.d/99-epyc.conf >/dev/null <<'EOF'
fs.file-max=2097152
net.core.rmem_max=16777216
net.core.wmem_max=16777216
net.core.somaxconn=65535
vm.swappiness=10
vm.overcommit_memory=2
vm.overcommit_ratio=95
kernel.numa_balancing=1
EOF
sudo sysctl --system # apply now
3.4 — Transparent Huge Pages (THP)
THP automatically backs memory with 2MB pages. It helps bandwidth-bound batch/HPC work but its background defrag can cause latency spikes — bad for databases (Redis, Mongo, Oracle all recommend disabling it) and tight-SLA services.
# runtime: turn THP off for latency-sensitive / DB workloads
echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/defrag
# permanent: add to the kernel boot line (GRUB)
# transparent_hugepage=never
# check current state
cat /sys/kernel/mm/transparent_hugepage/enabled
Rule of thumb: batch/HPC → THP on (madvise); database/latency → THP off.
3.5 — tuned profiles (the easy button)
tuned is a daemon that applies dozens of these knobs at once via a named profile — governor, C-states, sysctl, scheduler, disk readahead, and more. The fastest way to a sane baseline:
# list and apply
tuned-adm list
tuned-adm profile throughput-performance # general compute
# other good choices:
# balanced -> general purpose (default)
# latency-performance -> low-latency services
# accelerator-performance-> GPU/accelerator hosts
# network-throughput -> heavy network I/O
tuned-adm active # confirm
You can build a custom profile that inherits a stock one and overrides specifics — the right way to ship org-standard tuning:
# /etc/tuned/epyc-db/tuned.conf
[main]
include=throughput-performance
[sysctl]
vm.swappiness=10
vm.overcommit_memory=2
[vm]
transparent_hugepages=never
# then: tuned-adm profile epyc-db
3.6 — Kernel boot parameters (GRUB)
Some settings can only be set at boot. Edit GRUB_CMDLINE_LINUX in /etc/default/grub, then update-grub / grub2-mkconfig and reboot. Common EPYC entries:
# examples (combine only what you need):
transparent_hugepage=never # DB/latency hosts
processor.max_cstate=1 # latency: shallow idle only
isolcpus=8-15 nohz_full=8-15 rcu_nocbs=8-15 # dedicate cores to an app
iommu=pt # passthrough mode (virtualization)
numa_balancing=disable # if you pin by hand
mitigations=off # ONLY on isolated, trusted HPC clusters
3.7 — IRQ affinity
By default, device interrupts (network, storage) can land on any core — including your hot application cores, stealing cycles and adding jitter. Steer them deliberately:
# let the irqbalance daemon spread IRQs across cores
systemctl enable --now irqbalance
# OR pin a NIC queue's IRQ to specific (non-app) cores manually
# find the IRQ, then:
echo 3 > /proc/irq/<IRQ>/smp_affinity_list # bind to core 3
# keep app cores interrupt-free by binding all movable IRQs elsewhere
For latency work: pin NIC IRQs to a few housekeeping cores and run the app on isolated cores (isolcpus) so they're never interrupted.
3.8 — Scheduler and process placement
- Pin with
numactl/taskset(shown earlier) so the scheduler can't migrate hot processes off their warm cache/NUMA node. chrtfor real-time priority on the critical path (SCHED_FIFO/SCHED_RR).- cgroups v2 to cap/partition CPU, memory, and I/O between tenants — and to reserve cores for a latency-critical service.
- Watch CPU steal (
mpstat -P ALL 1, the%stealcolumn). On bare metal it should be ~0; on shared cloud, non-zero steal means a noisy neighbour (see troubleshooting).
3.9 — Security mitigations
mitigations=off for a speed bump, but only do so on isolated, single-tenant, trusted clusters (e.g. an air-gapped HPC partition) where you fully accept the risk. Never disable mitigations on multi-tenant, internet-facing, or shared hosts. When in doubt, leave them on.3.10 — Use a recent kernel
Newer kernels ship better Zen 5 enablement — smarter frequency scaling, scheduler awareness of the CCD topology, and the amd-pstate driver (often better than the legacy acpi-cpufreq on EPYC). Prefer a current LTS, and check the active driver:
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_driver # expect amd-pstate(-epp)
uname -r
Per-workload recipes
Start from the matching row, then benchmark and adjust. "Power" assumes adequate cooling.
| Workload | NPS | SMT | Determinism | Governor / THP | tuned |
|---|---|---|---|---|---|
| General / cloud / web | 1 | On | Performance | performance / THP on | throughput-performance |
| Database (NUMA-aware) | 2–4 | On | Performance | performance / THP off | custom (DB) |
| HPC / scientific | 4 | Off | Performance | performance / THP on | throughput-performance + DF P0 |
| AI/ML CPU inference | 1 or 4 | On | Performance | performance / THP on | accelerator-performance |
| Latency-critical (trading/RTB) | 4 + LLC-as-NUMA | Off | Performance | performance / THP off / C-states off | latency-performance |
| Per-thread licensed | 1 | Off | Performance | performance | throughput-performance |
Deep dive: databases
Pin the DB to one or more NUMA nodes (numactl --cpunodebind --membind), turn THP off, use explicit hugepages sized to the buffer pool, strict overcommit, low swappiness, and a deadline/none I/O scheduler on NVMe. Size the buffer pool/shared-buffers to fit a NUMA node's local memory where possible to avoid remote access.
Deep dive: HPC
NPS=4, SMT off (full cores), Performance determinism, max cTDP, fixed DF P-state (APBDIS), THP on, mitigations off only if the cluster is isolated. Build with -march=znver5 + AVX-512 and link AOCL. Pin ranks to CCDs and bind memory locally.
Deep dive: web / microservices fleet
NPS=1, SMT on (throughput), performance governor, the throughput-performance tuned profile, the networking sysctls above (big buffers, high somaxconn, TFO). Don't over-engineer NUMA — these apps are rarely NUMA-aware; keep it simple and dense (Zen 5c parts shine here).
Deep dive: AI / ML CPU inference
Use AVX-512 / VNNI-aware builds and AMD's optimized libraries; pin threads to CCDs; consider NPS=4 with local binding for big models. For real training/large inference, accelerators still win — CPU tuning here is for data prep and light/edge inference.
Monitoring & observability
You can't tune what you don't watch. Core tools:
- System:
htop,vmstat 1,iostat -x 1,mpstat -P ALL 1(steal!),sar,numastat,netstat/ss. - Profiling:
perf top/perf stat(IPC, cache misses, branch misses),ftrace,cyclictestfor latency jitter. - Fleet: Prometheus + Grafana (node_exporter), or Datadog/Dynatrace. On AWS, CloudWatch (detailed 1-min metrics) + Systems Manager.
- AMD-specific: AMD μProf for low-level CPU profiling on EPYC.
Key signals: sustained ~100% CPU (CPU-bound), high %steal (noisy neighbour), rising numa_miss (bad pinning), swap activity (memory pressure), high iowait (storage-bound).
Troubleshooting: the noisy neighbour
On shared cloud hosts, other tenants on the same physical box can steal memory bandwidth — very visible on EPYC's multi-CCD design. To detect and fix:
- Run STREAM to measure actual memory bandwidth.
- Compare to the expected figure for your instance (e.g. a single-CCD placement on a small instance should hit tens of GB/s; a full socket approaches the ~hundreds GB/s range).
- If you're well below expectation: stop and relaunch the instance until you land on a quiet host, or move to a dedicated / bare-metal instance for consistent performance.
Also check %steal in mpstat and CPU throttling (cpupower frequency-info, thermal logs) — a maxed cTDP with poor cooling silently throttles and erases your gains.
Cost considerations
Performance tuning and cost tuning go together — the goal is performance per dollar, not raw speed.
- Right-size. Monitor real utilization; downsize instances consistently below ~40% CPU. Match instance family to workload (compute
c, memoryr, HPChpc). - Commit where stable. Reserved Instances / Savings Plans for steady baseline load; Compute Savings Plans for flexibility across families.
- Spot for interruptible work. CI, batch, stateless web — big discounts, accept eviction.
- Re-review every 3–6 months or after major app changes; new EPYC generations and price changes shift the optimum.
(For the full method of measuring performance-per-dollar on your workload, see the benchmark-your-own-workload playbook.)
The verify loop (again, because it matters)
- Baseline the real metric on defaults.
- Change one knob.
- Re-run 5–10×; report median + spread.
- Check power, heat, and consistency, not just peak.
- Keep it only if it helps your workload; revert if not.
The org rollout checklist
- ☐ Classified each server group by workload type
- ☐ Picked the matching recipe (NPS / SMT / determinism / power)
- ☐ BIOS template captured as code per group (or correct cloud instance + CPU options)
- ☐ Governor = performance; verified
amd-pstatedriver active - ☐ tuned profile applied (stock or custom org profile)
- ☐ sysctl drop-in deployed and
sysctl --systemapplied - ☐ THP set correctly for the workload (off for DB/latency)
- ☐ NUMA layout confirmed (
numactl --hardware); pinning where needed - ☐ IRQ affinity set; app cores isolated for latency work
- ☐ Mitigations decision documented (default: leave on)
- ☐ Baselined → changed one knob at a time → re-ran 5–10× → checked power/heat
- ☐ Monitoring in place (steal, numa_miss, throttle, swap)
- ☐ Settings committed as IaC; re-check scheduled after firmware/kernel updates
Choosing the right Turin SKU
Tuning starts at the purchase order — the wrong SKU can't be fixed in BIOS. Turin parts split into families optimized for different things:
| Family | Optimized for | Pick when |
|---|---|---|
| Core-optimized (high core count) | Total throughput (up to 128 Zen 5 / 192 Zen 5c cores) | Scale-out, containers, web fleets, virtualization density |
| Frequency-optimized (F-series) | High per-core clock, fewer cores | Per-core-licensed databases (Oracle/SQL Server), latency-critical, EDA |
| Single-socket (P-series) | Cost — 1P platforms, no 2nd socket premium | You don't need dual-socket; saves licensing + platform cost |
| Zen 5c "Dense" | Max cores/watt (192 cores) | Cloud-native, massive concurrency, throughput per rack-U |
Key trade-off: more cores per CCD vs fewer. A 32-core part spread over 8 CCDs gives each core more L3 and memory bandwidth than a 32-core part on 2 CCDs — better for bandwidth-bound and per-core work, even at the same core count. Check the CCD count, not just the core count, for bandwidth-sensitive workloads.
Memory population rules (the hidden bandwidth killer)
Turin's 12 channels only deliver full bandwidth if you populate all 12. Half-populated memory = roughly half the bandwidth, no matter how you tune the OS. Rules:
- Populate all 12 channels per socket (12 DIMMs, or 24 in 2 DIMMs-per-channel). Balanced population is mandatory for peak bandwidth.
- 1 DIMM per channel (1DPC) runs at the highest speed (up to DDR5-6000). 2DPC adds capacity but may drop the clock — check your platform's supported speed at 2DPC.
- Match DIMMs — same size, rank, and speed across all channels. Mixed/unbalanced configs force the controller to the lowest common denominator.
- Rank matters — dual-rank DIMMs often give slightly more bandwidth than single-rank via better interleaving.
- Confirm the populated speed:
dmidecode -t memory | grep -E "Speed|Locator|Rank".
More BIOS knobs + confidential computing
Beyond the core knobs, a full BIOS pass for Turin also touches:
- Global C-state Control / DF C-states — allow or forbid package/fabric idle states. Forbid for latency (with APBDIS), allow for power savings.
- Fixed SOC/Uncore P-state — pin the I/O die clock high to remove memory-latency jitter (latency/HPC).
- CPPC (Collaborative Processor Performance Control) — lets the OS (
amd-pstate) drive frequency cooperatively; enable for best OS-level power/perf control. - TSME (Transparent SME) — always-on main-memory encryption with minimal overhead; enable for data-at-rest-in-RAM protection.
- SEV / SEV-SNP — confidential VMs: each guest's memory is encrypted and integrity-protected from the host. Enable for multi-tenant/regulated workloads; expect a small overhead and set
IOMMU+ sufficient ASIDs. - x2AVIC — hardware-accelerated interrupt virtualization for dense VM hosts.
BIOS master quick-reference
| Setting | General | Database | HPC | Latency |
|---|---|---|---|---|
| NPS | 1 | 2–4 | 4 | 4 |
| SMT | On | On | Off | Off |
| Determinism | Perf | Perf | Perf | Perf |
| cTDP | Default/Max | Max | Max | Max |
| Core Perf Boost | On | On | On | On |
| DF C-states | Auto | Auto | Disabled | Disabled |
| APBDIS / fixed DF P0 | Off | Off | On | On |
| LLC as NUMA | Off | Test | On | On |
| IOMMU | On (if virt) | Off/On | Off | Off |
Power, frequency & thermal management
Performance is bounded by power and heat. Modern EPYC tuning leans on the amd-pstate driver and its energy-performance-preference (EPP):
# confirm the modern driver is active (better than legacy acpi-cpufreq on EPYC)
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_driver # want amd-pstate-epp
# set energy-performance preference: performance | balance_performance | power
echo performance | sudo tee /sys/devices/system/cpu/cpu*/cpufreq/energy_performance_preference
# watch real clocks, C-states, and power live
turbostat --interval 1 # per-core MHz, busy%, PkgWatt, CoreTmp
sensors # temperatures
# RAPL power readings
cat /sys/class/powercap/intel-rapl*/energy_uj 2>/dev/null # (AMD exposes via hwmon/k10temp)
AVX-512 on Zen 5 is a gift: unlike older Intel parts, Turin runs full-width AVX-512 without a severe all-core frequency drop, so vectorized HPC/AI code gets the throughput without the clock penalty. Build with AVX-512 and don't fear it. To find throttling: if turbostat shows clocks well below expected under load, you're power- or thermal-limited — improve cooling, raise cTDP only if cooling allows, and check airflow/fan curves in BIOS.
Virtualization (KVM) tuning
For KVM/QEMU hosts, the guest only goes as fast as the host lets it:
- Pin vCPUs to physical cores (and keep them on one NUMA node) with
<cputune><vcpupin>in libvirt; expose the real topology with<cpu mode='host-passthrough'>so the guest sees Zen 5 + AVX-512. - Back guest RAM with hugepages (
<memoryBacking><hugepages/>) and bind guest memory to the same NUMA node as its vCPUs. - NUMA-align — never let a VM straddle NUMA nodes unless it's NUMA-aware inside.
- SR-IOV / vhost-net for fast guest networking;
IOMMUon in BIOS. - Isolate host housekeeping from vCPU cores (emulator pinning,
isolcpus).
<cpu mode='host-passthrough'/>
<cputune>
<vcpupin vcpu='0' cpuset='8'/>
<vcpupin vcpu='1' cpuset='9'/>
</cputune>
<memoryBacking><hugepages/></memoryBacking>
<numatune><memory mode='strict' nodeset='1'/></numatune>
Kubernetes / container tuning
Containers add a scheduling layer that, by default, ignores NUMA and CCD locality. To get bare-metal-like performance for latency-sensitive pods:
- CPU Manager static policy — give Guaranteed pods exclusive whole cores (kubelet
--cpu-manager-policy=static, integer CPU requests = limits). - Topology Manager — align CPU, memory, and device (NIC/GPU) allocations to the same NUMA node (
--topology-manager-policy=single-numa-node). - HugePages — expose as a schedulable resource (
hugepages-2Mi) and request them in the pod spec. - Node tuning — apply the governor/sysctl/THP settings via the Tuned Operator (NTO) or a privileged DaemonSet, not by hand.
- Reserve system cores —
--reserved-cpus/isolcpusso kubelet and the OS don't steal app cores.
# Guaranteed pod gets exclusive cores + hugepages + NUMA alignment
resources:
requests: { cpu: "8", memory: "16Gi", hugepages-2Mi: "4Gi" }
limits: { cpu: "8", memory: "16Gi", hugepages-2Mi: "4Gi" }
Network tuning (deep)
For high-throughput or low-latency networking, the NIC and kernel path need work beyond the sysctls already covered:
# bigger NIC ring buffers (fewer drops under bursts)
ethtool -G eth0 rx 4096 tx 4096
# interrupt coalescing: more for throughput, less (or adaptive) for latency
ethtool -C eth0 adaptive-rx on adaptive-tx on
# spread RX across cores (RSS); check/set queues
ethtool -L eth0 combined 16
# offloads on for throughput (GRO/GSO/TSO); off for ultra-low latency
ethtool -K eth0 gro on gso on tso on
# jumbo frames if the whole path supports them
ip link set eth0 mtu 9000
- RSS/RPS/XPS — distribute receive/transmit across cores; pin queues to cores near the app's NUMA node.
- Busy polling (
net.core.busy_poll) for the lowest latency at the cost of CPU. - Kernel bypass — DPDK or AF_XDP for millions of packets/sec; on cloud, EFA for HPC/ML collectives.
- Pin IRQs to cores on the NIC's local NUMA node (see §3.7) so packet processing stays local.
Compilers & parallel runtimes
Getting the most from the cores means telling the toolchain about Zen 5 and binding threads correctly.
# aggressive, Zen-5-targeted build with link-time optimization
gcc -O3 -march=znver5 -flto -mprefer-vector-width=512 app.c -o app
# profile-guided optimization (PGO): build, run a representative load, rebuild
gcc -O3 -march=znver5 -fprofile-generate app.c -o app && ./app <workload>
gcc -O3 -march=znver5 -fprofile-use app.c -o app
# AOCC (AMD's Clang) and AOCL math libs squeeze more on EPYC
OpenMP — bind threads to cores/CCDs so they don't migrate:
export OMP_NUM_THREADS=32
export OMP_PROC_BIND=close # keep threads near each other (shared L3)
export OMP_PLACES=cores # one thread per physical core
MPI — map and bind ranks to NUMA domains / CCDs:
mpirun --map-by numa --bind-to core -np 16 ./solver # Open MPI example
Language-runtime tuning
- JVM — enable large pages (
-XX:+UseTransparentHugePagesor-XX:+UseLargePageswith explicit hugepages), pick a modern GC (-XX:+UseZGCor G1), and consider NUMA-aware allocation (-XX:+UseNUMA) when the heap spans nodes. Size the heap to fit a NUMA node where possible. - Go — set
GOMAXPROCSto the cores you actually pinned (not the box's total) so the runtime doesn't oversubscribe; pin withtaskset. - Python — NumPy/SciPy use BLAS under the hood: link AOCL/BLIS and set
OMP_NUM_THREADS/OPENBLAS_NUM_THREADSto avoid thread storms; pin worker processes to NUMA nodes. - .NET — enable Server GC and (on NUMA) GC affinitization; .NET on Zen 5 benefits from the high per-core throughput.
Database-specific recipes
Databases are the workload that gains most from correct EPYC tuning. Common ground: THP off, explicit hugepages, low swappiness, strict overcommit, NUMA pinning, NVMe with none/mq-deadline.
- PostgreSQL —
huge_pages=on;shared_buffers≈ 25% RAM (fit within a NUMA node if possible); raiseeffective_cache_size,max_parallel_workers,work_mem; pin vianumactl. - MySQL/InnoDB —
innodb_buffer_pool_size≈ 50–75% RAM with multipleinnodb_buffer_pool_instances;innodb_numa_interleave=ON; large pages on; O_DIRECT flush. - Redis — THP must be off (latency spikes on fork/save); pin to cores on one NUMA node; watch fork latency during RDB/AOF.
- MongoDB — THP off (vendor requirement); WiredTiger cache sized to local memory; XFS recommended.
The benchmarking toolbox
| Tool | Measures |
|---|---|
| STREAM | Memory bandwidth (and noisy-neighbour detection) |
| stress-ng | Stress + micro-benchmark almost any subsystem |
| sysbench | CPU, memory, and OLTP database throughput |
| HPL / HPCG | HPC floating-point (Linpack) and memory-bound HPC |
| CoreMark / SPEC CPU | Standardized integer/FP CPU scores |
| fio | Storage IOPS / bandwidth / latency |
| netperf / iperf3 | Network throughput and latency |
| wrk / k6 | HTTP service throughput + p99 latency |
Always finish with your own application under realistic load — synthetic tools explain why, your app decides whether. (Full method: benchmark your own workload.)
Deep profiling
# IPC, cache + branch misses (low IPC + high L3 miss = memory-bound)
perf stat -e cycles,instructions,cache-misses,LLC-load-misses ./app
# where time goes
perf top
# AMD's own profiler understands EPYC PMU events well
# (AMD uProf: CPU profiling, IBS sampling, power profiling)
# memory-bandwidth + cache analysis per NUMA node
likwid-perfctr -g MEM ./app
Watch IPC (instructions per cycle): high IPC = compute-bound (more cores/clock help); low IPC with high cache/memory misses = memory-bound (fix NUMA/bandwidth/pinning, more cores won't help).
Firmware, microcode & updates
- Keep BIOS/AGESA current — AMD ships performance, stability, and security fixes; Zen 5 platforms improve meaningfully across early AGESA versions.
- Update CPU microcode (via OS package or BIOS) for errata and security fixes.
- Re-baseline after any firmware/microcode/kernel update — behaviour can shift, for better or worse.
- Check
dmesg/journalctl -kandmcelogfor ECC/MCE errors; a flaky DIMM tanks bandwidth and stability.
Glossary (plain words)
- Turin / Zen 5 — AMD's 5th-gen EPYC 9005 CPUs. Faster cores, full AVX-512, 12 DDR5 channels.
- CCD (chiplet) — a cluster of cores with its own L3 cache. Turin has several.
- I/O die (IOD) — the central hub with memory controllers and PCIe; all memory traffic flows through it.
- NUMA — cores reach nearby memory faster than far memory.
- NPS — NUMA Nodes Per Socket; how memory is grouped (1 = one big pool, 4 = four close pools).
- SMT — two threads per core; usually on, off for HPC/licensing.
- Determinism — Power (identical, steady) vs Performance (as fast as each chip allows).
- cTDP — the wattage limit you allow the chip; max it for sustained clocks if cooling allows.
- Governor — Linux clock policy;
performancekeeps clocks high. - C-states — idle sleep levels; deep ones save power but add wake-up latency.
- THP — Transparent Huge Pages; helps batch, hurts latency/DBs.
- sysctl — runtime kernel parameters.
- tuned — daemon that applies a whole profile of tuning at once.
- CPU steal — time a noisy neighbour took your core (cloud).
- AOCL — AMD's optimized math libraries.
References
- AMD EPYC 9005 BIOS & Workload Tuning Guide (58467) — exact BIOS setting names.
- AMD EPYC 9005 HPC Tuning Guide (58479) — HPC-specific.
- AMD EPYC 9005 NVMe Tuning Guide (58465) — storage path.
- Phoronix: BIOS optimizations for 5th Gen EPYC — measured impact.
- Lenovo: Tuning UEFI for 5th Gen EPYC — vendor BIOS labels.
- Optimizing Linux for EPYC 9005 (SUSE) — OS-level tuning.
Extra reads
- Benchmark Your Own Workload — the method to prove a setting helped.
- AWS vs OCI on AMD — picking the cheapest EPYC cloud.
- AMD Optimizing CPU Libraries (AOCL) — tuned math libs.
Built from AMD's EPYC 9005 (Turin / Zen 5) and EC2 EPYC tuning guidance, June 2026. BIOS labels vary by vendor and may be unavailable on cloud instances; sysctl/THP/governor commands assume a recent Linux. Always confirm against your vendor's and AMD's official guides — and benchmark on your own workload before rolling out.