The box
Every number on this site comes from this machine unless the writeup says otherwise. It is a used dual-socket Xeon workstation with two consumer GPUs bolted on, bought for the memory capacity rather than the compute.
Specification
| Component | Value |
|---|---|
| CPU | 2x Intel Xeon E5-2696 v4 (Broadwell), 22 cores each, 44 cores / 88 threads total |
| Host memory | 192 GB DDR4-2133 ECC, 6x 32 GB quad-rank LRDIMMs (3 per socket, four SK Hynix and two Samsung), 188 GiB usable. Part 3 ran on four DIMMs while two were out |
| Measured memory bandwidth | Read: ~53 GB/s interleaved across both nodes, ~31 GB/s per node local. STREAM triad (read and write): ~38 GB/s interleaved, ~22 GB/s per node. 44 threads, one per core, remeasured 2026-09-10 |
| GPU | 2x NVIDIA RTX 3090, 24 GB each, 48 GB total, PCIe 3.0 x16 |
| GPU topology | One card per socket: GPU0 on NUMA node 0, GPU1 on NUMA node 1, no NVLink |
| OS | Ubuntu Server 24.04, kernel 6.8 |
| CUDA | Toolkit 12.0, driver 595.84 |
| Inference | llama.cpp (production), vLLM for the dense models, ik_llama.cpp built for comparison |
| Storage | NVMe for the model files (~1.2 GB/s single-threaded read measured during loads) |
How the offload works
The models I care about are mixture-of-experts models much larger than 48 GB. The routed expert weights, which are most of the bytes, sit in host RAM. Attention, the recurrent layers, the shared expert, embeddings, and the output head sit on the GPUs, split by layer across the two cards.
During prefill, llama.cpp streams each CPU-resident expert layer to the GPUs once per micro-batch, so prefill speed is bounded by PCIe 3.0 and by whether the host memory is pinned. During decode, each token gathers its ten active experts per layer from host RAM, so decode speed is bounded by memory latency and by how many of those gathers a GPU-side cache can absorb. The writeups are mostly about moving those two bounds.
Two things about this box shape every result:
- The two sockets are separate NUMA nodes and each GPU hangs off a different one. Where a page lands in memory can be worth 35% of decode. Everything runs under
numactl --interleave=allfor the pinned buffers and--numa distributefor the compute threads. - The quad-rank LRDIMMs run hot. Without forced air over the DIMM banks the memory controller starts throttling after a few minutes of sustained load, at a sensor reading of about 78 C. There is now a fan zip-tied over each bank, and the throttle counter read zero during the bandwidth measurement above. Part 2 of the Flash-Next writeups has the measurements.