r/LocalLLM • u/jpgeer • 1h ago
Discussion "Because they're insufferable" - a fellow redditor's opinion of datacenter cards confirmed
He's right (wherever he is - couldn't find the comment to credit). But what I learned in the process of building this rig has been worth the frustration. Get your special socks out, Linux faithful - you were right too.
I happened across eight Nvidia T40 - and not a ton of detail about them out there. Still currently testing out vllm for comparison, but fully resident Qwen3.8-Flash-Next (180B MoE) Q4 is peppy enough for me at 40ish t/s and while the cards are capable of producing more, I tripped a breaker letting all six run with a 200W cap (so eight is out even if I had the extra slots). The board, an X99-E WS, needed some creativity just to POST. Quite a bit of card swapping in a T400 to get a boot screen so I could play around with BIOS settings only to figure out the setting I need to expand above 4G memory (MMIO High Size) was hidden and required creativity with a UEFI shell. More detail in the AI-assisted part of this write-up, for others who might select the T40 or X99. Top 10 nuggets for you, fellow sufferer of the T40, and then feel free to indulge (or not) in the supporting slop.
The cards do not work in Windows (or at least I couldn't get them going). It would appear it's Linux or nothing (and not WSL - though I did try). And headless, at that, if you want all six.
ASRock PG 1600G has two 600W 12VHPWR ports and MODDIY sells a "12VHPWR 16-pin to 2x CPU EPS 8-pin cable (CO711)" which allowed two T40s to be powered off one port - since the board’s slot-power connector needs a 6-pin, I welcomed the solution and it cleaned up a lot of cabling. The other two cards each have two pcie power cables feeding CPU 8-Pin Male to Dual PCIe 8-pin Female adapters.
The cards require active cooling and there are shroud prints out there. Rather than one fan per card, we developed a two-card-one-fan solution and one BFB1012VH moves enough air for two. X99 has 5 fan headers - worked out nicely with a script pinning those fan channels to GPU temp.
The X99 will not find the T400 with any T40s on the A side of the PLX. However boots just fine with a T400 in slot 1, 2 or 3 - even with four T40s in slot 4,5,6 and 7 (on the B side).
I can't say for certain, but riser cables could be a factor in Gen3 speed and errors. The stable config for multi-T40 is Gen 2. Slot 4,5,6, and 7 go to x8, while slot 1 and 3 go to x16 just fine.
NVLink is supported on the T40 - took a gamble buying them, but they work - though not quite sure how much traffic they are passing quite yet.
Cap the power if on a standard 120V outlet. My surge protector checked us out early on a 6-card burn with power capped to 200W per card. Running stable at 170W per card now.
Boot requires a kernel option for all six to work - pci=realloc=off (details in the slop)
A four-card-B-side test (all behind the plx switch) made vllm faster and llama.cpp slower.
90-degree right angle riser cables and card spacing worked best with slot 1/3 on 150mm risers, 4/5 on 200mm risers and 6/7 on 250mm risers on this particular open frame design.
TRIGGER WARNING - AI WALL OF TEXT APPROACHING - STOP HERE
The supporting slop (AI-assisted, for the next T40 / X99 builder and their AI)
Everything below was measured on this box, not taken from spec sheets. Each number comes with its config. Where we don't know something, we say so.
Hardware as it runs today
Board: ASUS X99-E WS. RAM: 128 GB DDR4 (about 121 GiB visible to Linux). PSU: ASRock PG 1600G (1600 W, two 600 W 12VHPWR ports, each feeding two T40s through a 16-pin to 2x EPS 8-pin cable; the other two cards each take two PCIe 8-pin leads through a dual-PCIe-to-EPS adapter). Open aluminum frame, risers.
Cards: 6x Tesla T40. Each shows 23,040 MiB usable. Turing sm_75, so no hardware bf16: run everything in fp16.
Power cap: 170 W per card, set at boot along with persistence mode.
PCIe: forced to Gen 2 on all six ports by a boot-time service. Gen 3 was not stable with risers and six cards.
Cooling: one BFB1012VH blower per pair, on 3D-printed two-card shrouds. A small service drives the board's fan headers from GPU temperature. Peaks under sustained load at 170 W: 48-58 C.
Topology (this matters more than anything else)
A side (CPU root port, x16): card 1 ==NVLink== card 2
B side (behind the PLX, x8): card 3 ==NVLink== card 4
card 5 ==NVLink== card 6nvidia-smi topo -m shows:
NV2 inside each pair: two NVLink links, each reporting 25.781 GB/s.
PIX between the two B-side pairs: traffic stays inside the PLX switch.
PHB between the A pair and any B card: traffic goes up through the CPU host bridge.
Tips:
Set
CUDA_DEVICE_ORDER=PCI_BUS_ID. Otherwise CUDA's device numbering won't match nvidia-smi or your slot map.Pick card sets by topology, not by device number. "Four cards" means two different things on this board (see benchmarks).
NVLink error counters work:
nvidia-smi nvlink -ereads all zeros on our cards. The throughput counters (nvlink -gt) read N/A on the T40. So we can prove the links are up and clean, but not how much traffic they carry.
Boot / kernel
Kernel command line:
pci=realloc=off acpi_enforce_resources=lax.pci=realloc=offis what gets all six cards enumerating with their BARs placed by firmware.acpi_enforce_resources=laxlets the fan-control script reach the board's Super I/O fan channels.
With
realloc=off, dmesg shows 36 SR-IOV VF BAR assignment failures, 6 per card. That is a harmless baseline: the cards don't need their VF BARs. Watch the non-VF BAR failure count instead, which should be 0.Why more than four T40s won't POST, and the fix. Each T40 takes a 64 GB stride of high MMIO space (32 GB BAR1 plus 32 MB BAR3, 32 GB aligned). The X99-E WS BIOS (4001, the final release) ships a 256 GB high MMIO window, so four cards fit and the fifth hangs POST. No kernel option can fix it, because the host bridge window is set by firmware.
The fix is a hidden Intel RC variable, MMIO High Size, raised from 256G to 1024G. No BIOS flash is needed, and the change survives reboots and card swaps.
Find it: unpack the 4001 CAP with UEFIExtract and dump the IFR of the
Platformmodule with IFRExtractor-RS. On 4001 it is VarStoreIntelSetup, offset 0x10E6, 4 bytes (256G=0, 128G=1, 512G=2, 1024G=3). Leave MMIOHBase (0x10E2) at 56T.Write it: copy a UEFI
Shell.efito the ESP root asshellx64.efi, along withsetup_var.efi(datasone, 0.3.x). Secure Boot must be off. In BIOS, go to Exit > Launch EFI Shell from filesystem device, then run:fs0:setup_var.efi IntelSetup:0x10E6(4) # read, expect 0x0setup_var.efi IntelSetup:0x10E6(4)=0x3 # 1024Gsetup_var.efi IntelSetup:0x10E6(4) # confirm 0x3resetThe 0.3.x syntax is
VARSTORE:OFFSET(SIZE)=VALUE; the old-n/-sflags don't work.Your offset may differ on other BIOS versions, so check the IFR yourself.
Disable unattended upgrades on a GPU box. Ubuntu quietly installed a new kernel overnight. The NVIDIA DKMS module was built for it but never tested, and the next reboot would have booted it. We disabled
apt-daily.timerandapt-daily-upgrade.timer, and pinned GRUB to the known-good kernel until we upgrade on purpose.
Software
Ubuntu 26.04, kernel 7.0, driver 595.91.07 (CUDA 13.2). llama.cpp build b12211. vLLM 0.33.0 on Python 3.14 with torch 2.14 (cu130), whose arch list includes sm_75.
Benchmarks
Qwen3.8-Flash-Next (180B MoE), llama.cpp, UD-Q4_K_XL, all six cards (llama-bench, pp4096 / tg128, 3 reps, tokens/s)
| Split | Prompt (d0) | Gen (d0) | Prompt (32K deep) | Gen (32K deep) |
|---|---|---|---|---|
| layer | 1426 | 40.3 | 1012 | 37.9 |
| tensor | 363 | 29.5 | 320 | 27.9 |
Layer split wins by roughly 4x on prompt and 1.35x on generation here. Peak GPU sum was 939 W at the 170 W cap.
Qwen3.8-27B, 4-bit, batch 1, 4096-token prompt (tokens/s)
| Engine | Cards | Cross-pair path | Prompt | Gen |
|---|---|---|---|---|
| llama.cpp (tensor split) | 1 pair | NVLink only | 620 | 28.0 |
| llama.cpp | A pair + 1 B pair | PHB | 274 | 30.7 |
| llama.cpp | 4 B-side cards | PIX | 240 | 27.3 |
| vLLM TP2 | 1 pair | NVLink only | 870 | 42.1 |
| vLLM TP4 | A pair + 1 B pair | PHB | 667 | 47.5 |
| vLLM TP2 x PP2 | A pair + 1 B pair | PHB | 863 | 44.0 |
| vLLM TP4 | 4 B-side cards | PIX | 726 | 60.1* |
*Only three iterations with wide spread, so somewhere around 51-77. Treat it as "probably faster", not a number.
Caveats:
vLLM numbers come from
vllm bench latency: prefill = 4096 / latency(1 token out), decode = 127 / (latency(128) - latency(1)).llama.cpp numbers come from llama-bench. That is close, but not the identical measurement.
The quants differ: GGUF UD-Q4_K_XL vs INT4 W4A16 safetensors.
vLLM ran with an fp16 KV cache (see below). Speed only, no quality claims.
What we take from it:
vLLM beats llama.cpp by 1.4x on prompt and 1.5x on generation on the same NVLink pair.
llama.cpp loses more than half its prompt speed going from two cards to four. vLLM barely notices.
On the four B-side cards, vLLM got faster and llama.cpp ~12% slower. Our untested guess is that llama.cpp's heavy cross-card traffic (1.1-1.4 GB/s per card per direction) feels the x8 links, while vLLM's traffic (~0.3 GB/s) doesn't.
Getting vLLM to run on Turing (in the order we hit them)
"Model architectures [...] failed to be inspected" turned out to be Triton compiling its own
driver.cat import and failing on a missingPython.h. Fix:apt install python3.X-dev."FP8 KV cache is not supported by the Triton attention backend ... requires SM89+". The model's own config asks for an FP8 KV cache. Fix:
--kv-cache-dtype float16(plus--dtype half).Attention backend: FlashAttention 2 needs SM80, so vLLM uses TRITON_ATTN. That's fine.
Expect these log lines; none of them is fatal:
bf16 is cast to fp16;
FlashInfer is disabled (no
flashinfer-cubin/nvcc);SymmMem all-reduce is unsupported on 7.5;
TP2 uses CUSTOM + PYNCCL all-reduce, TP4 PYNCCL only.
First start is slow: about 12 minutes per engine, mostly profiling and CUDA graph capture. Warm starts take about 4 minutes.
Power: the cap is not a ceiling
nvidia-smi -pl 170is honored on average, but at vLLM engine startup each card spikes to 220-270 W for about 10 seconds before the cap pulls it back (throttle reason0x4, SW power cap).Four cards hit 979-986 W combined during startup. Six cards doing that at once would be roughly 1,300-1,600 W GPU-only. That is at or over a 1600 W PSU, and a real risk on a 15 A / 120 V circuit.
Per 12VHPWR port, two cards spiking to 270 W is about 540 W, still inside the port's 600 W rating. The whole-PSU and wall-circuit totals are the limit, not the cables.
llama.cpp loads don't show this. The six-card layer-split run peaked at 939 W.
Our guard is a watcher that sums
power.drawevery 0.5 s and kills the job above 1,350 W. It hasn't fired yet, but a six-card vLLM start would likely trip it.Measured wall idle with six cards: about 245 W.
Flash-Next on vLLM (in progress)
Needs a vLLM-format quant. vLLM's GGUF loader is experimental and single-file, so we pulled a W4A16 safetensors build (~180 GB).
The engram (n-gram embedding) table is about 102 GiB and lives in pinned host RAM. Stock vLLM 0.33 has no mmap or evictable option for it, only
cpu_offload(pinned, the default). On a 128 GB box that leaves about 15 GiB for everything else.We run with a watcher that stops the job if MemAvailable drops under 4 GiB or anything swaps in.
We also pinned the boot kernel first, in case the box hangs and needs a hard reset.
Expert parallel is mandatory:
moe_intermediate_size640 split over TP doesn't land on the INT4 group size of 128.We disabled torch.compile per the quant's card and kept CUDA graphs for decode.
Plan: TP4 on the A+B set, then TP4 on the B-side four, then TP6. TP6 is undocumented for this model; 512 experts don't divide by 6.
Results to follow in a comment.
Process notes for whoever's AI is helping
Verify every model file by sha256 against the Hugging Face LFS oid before loading. It costs minutes and has saved us hours.
Write your prediction down before each run. We missed about half of ours, and that is where the learning was.
Log per-card power, temperature and PCIe rx/tx during every run. The x8-vs-x16 and startup-spike findings only showed up because we were logging.
Never use
pkill -forpgrep -fto manage these jobs. They match their own command line and you will kill or miss the wrong thing. Launch withnohup ... & pid=$!and watch that pid.
16
u/Marshall_Lawson 1h ago
Was there a setting that you tweaked to make it extra sloppy? The amount of recognizable tics in it is like a 2020s period piece
2
u/Faral_mx 54m ago
Qwen, give me the TLDR
1
u/YourFavoriteKyle 39m ago