Two P100s in one Ubuntu box: driver 580, CUDA 12.9 and the IOMMU trap

· Local AI · Guides · Tesla P100 16GB x2

Illustration of two plain server cards side by side with a band of light flowing between them
Illustration: AI-generated locally with FLUX.2 klein.
GPU-to-GPU copy speed between the two P100s (GB/s)
Default IOMMU (translated)0.14
iommu=pt (passthrough)6.57
MODEL
Qwen3.8-27B Q6_K
HARDWARE
Tesla P100 16GB + Tesla P100 16GB
CONTEXT
155K (q8_0 KV)
RESULT
55.9 tok/s decode
WHAT CHANGED
iommu=pt, own engine, MTP 3

Short answer: two P100s in one Linux box work well, but on an AMD board check GPU-to-GPU copy speed before anything else. Mine was 0.14 GB/s out of the box. Adding iommu=pt to the kernel command line took it to 6.57 GB/s, and a 27B model went from unusable to about 56 tok/s.

In Part 3 I said a second P100 belongs in the same machine, not across the network. In October I did that: both cards went into one box, and I reinstalled it with Ubuntu 26.04.

The box

1. Driver: 580, proprietary, not “-open”

Same rule as Part 2: the 580 branch is the last one for Pascal, and NVIDIA’s open kernel modules don’t support it. On a headless Ubuntu box:

sudo apt install nvidia-headless-580-server nvidia-utils-580-server
sudo reboot

Two things I hit:

2. CUDA 12.9 on a distro that wants 13

CUDA 13 dropped Pascal, so the toolkit has to be 12.9. Ubuntu’s own nvidia-cuda-toolkit package was only 12.4, so I used NVIDIA’s runfile. Two snags on 26.04:

After that, both llama.cpp and my own engine built with -DCMAKE_CUDA_ARCHITECTURES=60.

3. The trap: GPU-to-GPU copies at 0.14 GB/s

The first model run loaded in 21 seconds, and then took 16 seconds to read a 25-token prompt. Both GPUs showed 100% busy at about 39 W. That combination means they’re waiting, not working.

Splitting a model across two cards (tensor parallel) means the cards swap partial results on every layer, so GPU-to-GPU copy speed matters a lot. I measured it:

Path Speed
GPU → GPU (peer-to-peer) 0.03–0.15 GB/s
GPU → host RAM → GPU 5.6 GB/s
Host ↔ GPU 6.7 GB/s

Peer-to-peer was 40 times slower than bouncing through system RAM. nvidia-smi topo -m said peer-to-peer was supported, so nothing looked wrong on paper.

The cause: the AMD IOMMU (the chip that checks and translates device memory access) was in “translated” mode, so every peer-to-peer transfer went through address translation. dmesg | grep -i iommu showed Default domain type: Translated.

The fix: passthrough mode. In /etc/default/grub:

GRUB_CMDLINE_LINUX_DEFAULT="quiet splash iommu=pt"

then sudo update-grub and reboot. Afterwards dmesg showed Default domain type: Passthrough, and:

Path Before After
GPU 0 → GPU 1 0.14 GB/s 6.57 GB/s
Both directions at once — 4.4–4.5 GB/s each

iommu=pt keeps the IOMMU on for devices that need it and lets the rest skip translation. It’s a common setting on GPU servers, but it’s a security trade-off on a machine that runs VMs with passed-through devices. On a dedicated inference box it’s fine.

Check this first on any AMD multi-GPU Linux box. A copy-speed test (CUDA’s p2pBandwidthLatencyTest sample, or a few lines of PyTorch) takes a minute and would have saved me the evening.

4. Result

Qwen3.8-27B at Q6_K, split across both P100s on my own engine (Part 5), q8_0 KV cache, 155K context, speculative decoding with the model’s own draft head:

5. Small things worth doing

How I ran it

Next: why I wrote my own inference engine for the P100.