Foundry/barswap/README.md

11 KiB
Raw Blame History

Barswap — Canonical Quick Reference

This is the authoritative current-state doc. The other files in this folder are deep-dives and history; where they show interim die counts ("1 of 2 dies", "6/6"), those are historical — the current state is below. Read this first.

One sentence: the BTC-S37 board can map only one 16 GB GPU die into CPU address space at a time, so all 16 K80 dies are time-multiplexed through a single MMIO slot via bar_swap.ko — acquire a die, use it, release, repeat.

Current hardware reality (verified 2026-03-25)

Fact Value
Board / CPU BTC-S37, Intel Pentium 2117U (Ivy Bridge, 36-bit phys = 64 GB), 8 GB DDR3
GPUs 8× K80 = 16 dies, 192 GB VRAM — 16/16 tested and passing
Working MMIO slot exactly one, at 0xC00000000 (16 GB)
Simultaneous dies 1 (bs_num_slots() == 1). The other two 16 GB-aligned addresses (0x400000000, 0x800000000) hang/timeout — see MULTI_SLOT.md
Multi-slot hardware-impossible on this board without a risky BIOS reflash (closed; MULTI_SLOT.md)
Swap cost ~0.2–1.2 ms BAR remap (nvidia stays loaded, context persists); ~2.5 s only if you reload nvidia
VRAM + context persist across release/acquire — don't destroy the context on swap (Architecture A, confirmed)
In-flight compute continues executing on-die while the die is released — the BAR is only a CPU access window, not needed during kernel execution (compute_persist PASS 2026-06-26)
Driver / toolkit nvidia 470 (Kepler→Turing); CUDA 11.4; K80 = sm_37

Architectural consequence — parallel compute behind a serialized control plane. The single MMIO slot multiplexes only the control/IO path (launch kernels, copy inputs/results in and out). It is not a compute bottleneck: once work is launched on a die, that die keeps computing on its local VRAM after it's released from the slot (BAR = doorway, not the room). So all 16 dies can compute in parallel; the slot is time-multiplexed (~0.2–1.2 ms/visit) just to dispatch work and collect finished results. Effective aggregate throughput ≈ N × single-die, minus control-plane serialization — bounded by (slot-time per die-visit) ÷ (on-die compute-time per step), which is small when compute ≫ launch/copy (true on the slow K80). This is the round-robin-training.md scheduler pattern applied to inference.

Load-bearing assumption — CONFIRMED (2026-06-26, compute_persist die 1). Calibrated a 16.2 s kernel, launched it async, released the die from the slot at 128 ms (GPU unmapped from MMIO), waited 48.7 s with the die released, then re-acquired: cuCtxSynchronize returned in 0.02 ms ("work already done") with 1,048,576 / 1,048,576 elements correct, 0 zeros. In-flight GPU compute continues after BAR unmap — the parallel-compute model holds. Switch overhead ~0.2 ms (launch only); "top-2 cost = top-1 cost (parallel, not sequential)."

Build recipe (CUDA on this box)

The default toolchain does not work out of the box; these are required:

  • Use the separately-installed CUDA 11.4 at /usr/local/cuda-11.4. Do NOT use the apt nvidia-cuda-toolkit package — it installs CUDA 12, which dropped sm_37 (K80). 11.4 is the last toolkit supporting Kepler.
  • nvcc host compiler must be gcc-11 (-ccbin gcc-11). Ubuntu 24.04's default is gcc-13; nvcc 11.4 refuses any gcc > 11 (unsupported GNU version).
  • glibc 2.39 needs the -isystem nvcc_shim include shim (a patched sys/cdefs.h), or nvcc dies on error: attribute "__malloc__" does not take arguments from /usr/include/stdio.h.

All three are already wired into cuda_kernels/Makefile. To (re)compile the kernels to PTX and regenerate the embedded cuda/fc_cuda_ptx.h:

cd foundry/cuda_kernels && make      # nvcc -ptx -arch=sm_37 -ccbin gcc-11 -isystem nvcc_shim

This needs only the host CPU (no GPU, no driver). The generated fc_cuda_ptx.h is committed so foundry/Pantheon build without nvcc on dev machines.

Manual bring-up (today's procedure)

nvidia is blacklisted on purpose (/etc/modprobe.d/blacklist-nvidia.conf with install … /bin/false) so it never auto-loads and corrupts GPU state. Bring it up by hand, in this order:

# 1. Load the barswap module — registers /dev/bar_swap, slot starts empty
sudo insmod ~/foundry/barswap/bar_swap_ko/bar_swap.ko
sudo dmesg | grep bar_swap            # expect 16 dies listed, slot empty

# 2. Acquire ONE die into the 0xC00000000 slot (userspace lib: bs_acquire(die_id))
#    e.g. via a small program linking barswap.c, or a barswap CLI

# 3. Load the nvidia driver (it's blacklisted, so --ignore-install)
sudo modprobe --ignore-install nvidia nvidia_modeset nvidia_drm nvidia_uvm

# 4. Verify — read CACHED driver data, never probe hardware
cat /proc/driver/nvidia/gpus/*/information

Hazards (this box is remote/headless — a wrong move means a physical trip)

  • Never nvidia-smi on a swapped die. It probes hardware (temp/power/ECC) and can hang or crash the die. Use /proc/driver/nvidia/gpus/<BDF>/information instead.
  • Never automate PCI manipulation at boot. A systemd service doing setpci/rescan/ modprobe froze the box and required a physical power cycle + recovery mode. Barswap bring-up must be on-demand from userspace, never a boot unit. (The intended home for this is inside Pantheon, acquiring dies as needed — see the Nex-N2 spec.)

Why nvidia is blacklisted — and the cleaner replacement

Why (root cause): at boot the 16 dies are in a broken-BAR state (single-slot constraint — most have no valid CPU-routable BAR). If nvidia auto-loads it binds all 16 dies and probes them (RmInitAdapter); accessing the broken BARs / config space soft-locks the kernel (pci_conf1_read never returns), and even when it survives, nvidia stays bound and corrupts state when bar_swap later disables a die's decode. The blacklist (blacklist + install … /bin/false + update-initramfs -u) is the blunt fix: stop nvidia loading at all, load it by hand after a die is acquired.

Why it's hacky: it's system-wide — it kills nvidia for the whole box, so you can't run a normal nvidia GPU alongside the K80s; and it needs /etc/modprobe.d edits + initramfs regen that don't survive cleanly as a shipped product.

Ship-grade replacement (planned): make bar_swap bind the dies (vfio-pci / pci-stub pattern). Today bar_swap claims die resources (pci_claim_resource) but is not a registered pci_driver — so nothing stops nvidia binding them; hence the blacklist. Register bar_swap as a pci_driver matching the GK210 device ID so the kernel binds all 16 dies to bar_swap at probe → they're invisible to nvidia → no probe, no panic, no corruption — without a global blacklist. On acquire, hand the one slot die to nvidia (unbind → nvidia binds it); the other 15 stay parked with bar_swap. A box could then run a normal nvidia GPU and the K80s at once. Zero-code de-risk first: put the GK210 ID in pci-stub.ids=/vfio-pci.ids= on the kernel cmdline and check nvidia boots clean, before writing the bind path into bar_swap.

  • One die mapped at a time (slot occupancy, not compute — released dies keep computing). Acquiring a new die implicitly releases the active one.
  • Don't destroy the CUDA context on swap — it survives release/acquire; tearing it down forces a full ~2.5 s reload.
  • CUDA atexit teardown hangs on this rig (confirmed 2026-06-26). When a process with a CUDA context exits normally, the atexit context-destroy wedges a thread against the bar_swap'd die — the process becomes a Zl zombie and nvidia ends up half-unloaded (NVRM: kernel module is unloaded while lsmod still lists it). These zombies can't be killed and accumulate until the box needs a power cycle (it also makes clean shutdown crawl). Workaround for short-lived processes (tests, one-shots): call _Exit() instead of return/exit() — skip the userspace teardown and let the kernel reclaim the context on process death (clean exit, no zombie; verified with the kernel-parity harness). Proper fix (TODO, product-critical — every model process exit hits this): an ordered context-destroy + bs_release before exit, and/or the device-binding driver work above. A long-lived Pantheon server avoids it by not exiting; it still needs a clean teardown path for shutdown.

barswap userspace API (barswap.h)

bs_context* bs_init(void);                       // open /dev/bar_swap
uint32_t    bs_num_dies(ctx);                    // 16 on this rig
uint32_t    bs_num_slots(ctx);                   // 1 (v2 — hardware limit)
bs_gpu*     bs_acquire(ctx, int die_id);         // program die into the slot, mmap VRAM
void        bs_release(ctx, bs_gpu*);            // vacate the slot
int         bs_active_die(ctx);                  // die in the slot, or -1
int         bs_is_mapped(ctx, int die_id);
int         bs_refresh(ctx);
void        bs_free(ctx);

Kernel module ioctls on /dev/bar_swap: BAR_SWAP_ACQUIRE / …RELEASE / …QUERY.

Field service — identifying a physical card (future feature, not built)

This rig has no BMC/IPMI and the K80 bracket LED isn't software-programmable, so there's no turnkey "locator LED." Two practical ways to find a specific/failing card, worth building into the barswap-in-Pantheon tooling later:

  1. BDF → physical map (deterministic). Every die has a fixed PCI address (die 0 = 03:00.0, die 1 = 04:00.0, Card B = 07/08:00.0, … — see CARDS.md). A failing die's BDF → its card → its riser. One-time win: physically label each riser with its BDF at install so a bad die maps to a card by eye.
  2. identify <die> by workload (lights up the card). Acquire only the suspect die and run a continuous busy kernel for ~30 s — its activity LED flickers while the others stay dark, so you can physically spot it. barswap's single-die acquire makes this clean. This is the small operability feature to add (a pantheon --barswap identify <die> mode).

Also useful and already possible: a failed K80 often shows amber on the bracket LED (passive), and barswap can disable/skip a die by BDF in software to route around a flaky card until it's pulled.

Where to read more

  • MULTI_SLOT.md — why only one slot works (Ivy Bridge 36-bit addressing; closed).
  • CARDS.md — GPU compatibility (which cards need barswap; T4/P4 don't) + current rig.
  • memoryTricks.md — barswap-as-scheduler notes for multi-model hosting.