NUMA-aware IRQ affinity for Linux. Pins hardware IRQs (Intel 10G NICs,
LSI MegaRAID, and anything you filter on) to CPU cores by writing
/proc/irq/<n>/smp_affinity_list.
Originally a round-robin replacement for irqbalance on old systems, it
now keeps each device's IRQs on its own NUMA node and defers to the
driver/kernel affinity hint — which is exactly what latency- and
throughput-sensitive workloads want: HFT, telecom/NFV, and
AI/ML clusters where NUMA locality and network tuning (RDMA/NCCL)
decide the numbers.
- NUMA-aware, per IRQ. Each IRQ's NUMA node is read from
/proc/irq/<n>/node, falling back to a PCI reverse-map (/sys/bus/pci/devices/<addr>/msi_irqs/<n>→ that device'snuma_node), so it works for any device — NICs, MegaRAID, NVMe, GPUs. Interrupt handling stays local to the cores that touch the data, no cross-socket bouncing for RDMA/NCCL or 10G+ traffic. - Respects managed IRQs. Modern multi-queue NICs publish an
affinity_hint; the tool honours it instead of fighting the kernel's managed-IRQ logic. Override with--ignore-hintswhen you really know better. - CPU-list writes. Uses
smp_affinity_list(CPU numbers), so it works unchanged on machines with more than 32 CPUs, where the hex-mask format is comma-grouped. - Optional RPS/XPS steering.
--rpskeeps each NIC's queue softirq work on the device's NUMA node: RX (rps_cpus) gets the whole node mask so the kernel hashes flows across local cores, while each TX queue (xps_cpus) is pinned to one node core round-robin (canonical XPS, avoids lock contention). - Isolated-core aware. CPUs listed in
isolcpus=/nohz_full=(/sys/devices/system/cpu/{isolated,nohz_full}) are kept free of device IRQs by default — exactly what you want when those cores run a pinned RT or HFT thread. Use--use-isolatedto opt out. - Auditable.
--verifyis a read-only mode that prints an IRQ→CPU→node table and exits non-zero on any drift, so you can gate it in CI or monitoring.
- Linux with
/proc/interrupts,/proc/irq, and/sys/devices/system/node - Python 3.8 or newer (standard library only)
- Root privileges to write
/proc/irq/*/smp_affinity_list
# Isolated install of the CLI (recommended):
pipx install irq-affinity
# Or into the current environment:
pip install irq-affinity
# From a release wheel / this checkout:
pip install ./irq_affinity-*.whl
pip install .This installs an irq-affinity command. You can also run the single file
directly without installing anything — it has no third-party dependencies:
sudo python3 irq.py --dry-run# Preview the NUMA-aware plan without touching anything:
python3 irq.py --dry-run
# Apply for eth* and megasas* IRQs:
sudo python3 irq.py
# Custom filters (repeatable) — e.g. NVMe and Mellanox for an ML node:
sudo python3 irq.py -f mlx5 -f nvme
# Also steer RX/TX queue RPS/XPS onto each NIC's NUMA node:
sudo python3 irq.py --rps
# Ignore the driver hint and force our layout onto managed IRQs:
sudo python3 irq.py --ignore-hints
# Plain round-robin over all CPUs (disable NUMA awareness):
sudo python3 irq.py --no-numa
# Audit only: print the IRQ->CPU->node table, exit non-zero on drift:
python3 irq.py --verify --no-color
# Permit pinning onto isolcpus/nohz_full cores (off by default):
sudo python3 irq.py --use-isolated
# Leave irqbalance running / drop colors for logs:
sudo python3 irq.py --keep-irqbalance --no-colorRun python3 irq.py --help for the full option list.
- Discover NUMA topology from
/sys/devices/system/node/node*/cpulist. - Drop isolated cores (
isolcpus=/nohz_full=) from the CPU pool unless--use-isolatedis given. - Match
/proc/interruptslines against the filters and group IRQs by device. - Resolve each IRQ's NUMA node from
/proc/irq/<n>/node, falling back to the owning PCI device'snuma_node(viamsi_irqs/irq), then to a network device'snuma_node, then to all CPUs. - Plan one CPU per IRQ, round-robin within its NUMA node. With
--verify, print the plan-vs-current table and stop here. - Stop
irqbalance(systemctl stop, elsekillall) unless--keep-irqbalanceis set. - For each IRQ: skip if a driver
affinity_hintdisagrees (unless--ignore-hints), skip if already correct, otherwise write the CPU tosmp_affinity_list. Write failures are counted and produce a non-zero exit code. - With
--rps, set everyqueues/rx-*/rps_cpusto the NIC's full NUMA-node mask, and pin eachqueues/tx-*/xps_cpusto one node core round-robin.
IRQ affinity resets on reboot, driver reload, and device hotplug. A sample
oneshot unit lives in systemd/irq-affinity.service:
sudo install -D irq.py /opt/irq_affinity/irq.py
sudo cp systemd/irq-affinity.service /etc/systemd/system/
# edit ExecStart in the unit to match your filters/flags, then:
sudo systemctl daemon-reload
sudo systemctl enable --now irq-affinity.servicepython3 -m venv .venv && source .venv/bin/activate
pip install -r requirements-dev.txt
python3 -m pytest -q
ruff check . && ruff format --check .See CLAUDE.md for conventions and guidance aimed at AI coding assistants.
MIT — see LICENSE.