# dgx4's NVMe: why the Spark wedge is one bad drive (2026-11-04) The FLARE lane on dgx1–4 died repeatedly with `RoCE collective … timed out … the transport is poisoned` (see [`flare-roce-poison.md`](flare-roce-poison.md)). The trigger is not the fabric, not the rank order, not the engine: it is **dgx4's boot SSD**, and it is not a fault — the drive is healthy, it is simply the wrong class for the workload. ## The drive | | dgx4 | every other node | |---|---|---| | model | **KingSpec `XG7000-2TB 2242`** (`/sys/class/nvme/nvme0/model`) | `ESL01TBTLCZ-27J2-TYN` 1 TB | | firmware | `GT0d9d63` (vid `0x1e4b`, not in the public firmware DB) | `ERFM12.0` | | PCIe | Gen4 x4, 16 GT/s, ASPM disabled, no AER errors | Gen4 x4 | | HMB | **`hmpre`/`hmmin` 10240 = 40 MB, and it is allocated** (`nvme nvme0: allocated 40 MiB host memory buffer (1 segment)`) | declares 64 MB | | power states | 5 (`npss 4`), APST on, idles in **PS3** | 5, APST on | | health | 96 h powered, 54.9 TB read / 13.4 TB written, `media_errors 0`, 0 error-log entries, 0 thermal-throttle events, 42 °C | healthy | Vendor identity (community teardowns/press): KingSpec XG7000 = **DRAM-less** design, **Maxio MAP1602A** controller, **232-layer YMTC TLC**, graphene heat-spacer instead of a heatsink, no first-party management or firmware tool. It is a cheap "fast sequential" gaming drive; "DRAM-less" is the operative word — the flash-translation mapping table lives in the 40 MB host buffer plus the controller's small SRAM, not in on-drive DRAM. ## What it does under our workload Measured with a 4 KiB `O_DIRECT` random-read harness on the package's Engram shard (98 GB), reporting per-read completion latency: | working set (random 4 K, 16 threads, 15 s) | dgx4 XG7000 | dgx5 ESL01 | |---|---|---| | 2 GB | 68.8 K IOPS, p50 **69 µs** | 113 K IOPS, p50 109 µs | | 20 GB | 38.8 K IOPS, p50 169 µs | 112 K IOPS, p50 110 µs | | 98 GB | **12.9 K IOPS, p50 1,183 µs**, p99.9 4.6 ms | 80 K IOPS, p50 200 µs | | 98 GB, 64 threads | **24.8 K IOPS, p50 2.45 ms**, p99.9 10.5 ms | **204 K IOPS**, p50 283 µs | Two things stand out: 1. **Throughput collapses as the working set grows** (69 K → 13 K IOPS) while the healthy drive stays flat (113 K → 80 K). That is the mapping cache: 98 GB / 4 KiB = 24 M mappings; even at 8 B per entry that is ~190 MB of table against a 40 MB HMB, so a random read over the whole table is a mapping miss — an extra NAND fetch — most of the time. Latency p50 goes from 69 µs (hot 2 GB slice, table fits) to 1.2–2.5 ms (whole table). 2. **It degrades with queue depth instead of scaling**: 16 → 64 threads moves it 12.9 K → 24.8 K IOPS but p50 1.18 → 2.45 ms (congestion collapse). The healthy drive goes 80 K → 204 K IOPS and holds sub-millisecond p50. Sequential is fine, which is why nothing noticed before: streaming 4 GiB with `iflag=direct` reads **1.9 GB/s** on dgx4 vs 3.0 GB/s on dgx5 (~63 %). Package copies, image pulls and `flare download` are all sequential, and the weight load is read once into GPU memory. ## Why that kills the engine (the arithmetic) FLARE serves this package with disk-backed Engram tables and reads rows **at token time**. From the production lane's own metrics: a 157 K-token prefill did **6.49 M Engram reads** → **≈41 reads/token**. A 4096-token prefill chunk (the package's baked geometry) therefore needs **≈170 K random reads ≈ 680 MB**. | node | sustained random read at 98 GB | 680 MB chunk | |---|---|---| | dgx5-class drive | ~330–800 MB/s | **1–2 s** | | dgx4 XG7000 | ~52–99 MB/s | **7–13 s** | The collective's spin limit is ~8–20 s (20 M polls in the package's kernel), and it covers a whole chunk. A device that needs 7–13 s per chunk with a tail that spikes to 10–18 ms p99.9 sits right on that boundary — which is exactly what we see: healthy on a warm stand, dead on a cold boot (`seq 93`, prefill = 1 token) and dead again after sustained load (`seq 2833389` after a full bench). The upstream reader patch (PR #184, io_uring lanes) raises the reader's parallelism but cannot raise the device's ceiling, which is why it helps on the warm/cached path and does not save the cold or sustained cases. ## What this is not - **Not a failing drive**: `media_errors 0`, zero error-log entries, no thermal-throttle transitions, 42 °C, and 96 power-on hours. It answers every I/O correctly, just slowly under random load. SMART will never flag it. - **Not thermal**: no T1/T2 management transitions; the graphene pad is enough for a 6 W-class drive. - **Not ASPM/APST flakiness**: the link stays Gen4 x4 with ASPM disabled; idle wake is single-digit milliseconds, not seconds. - **Not the PCIe slot**: the same slot class reaches 3.0 GB/s sequential on the other nodes' drives; the /usr/bin and root filesystem are unaffected. ## Decisions taken (2026-11-04) - dgx4 does **not** serve. It keeps the 2 TB disk as the orbit controller and package store (CLI store 488 GB + agent cache 488 GB — still the only node that fits both) and the production lane runs on dgx1+dgx2+dgx3+dgx8. - The `4nodes-switch` / `prod4b` configuration stays in the config for after a drive swap, and the A/B in `flare-roce-poison.md` is the acceptance test: 157 K first-touch prompt **and** a full bench on a cold stand. ## What to buy instead (if dgx4 must serve) The requirement is sustained random-read at a large working set, i.e. a **DRAM'd controller with a big FTL map cache**, not peak sequential speed: 1. **Samsung PM9A3 M.2 2242** (enterprise, Elpis 8-channel, DRAM) or **PM9E1 2242** if reachable — designed for exactly this duty. 2. **Corsair MP700 Micro 2 TB** (Gen5 2242, Phison E28, 8-channel with DRAM). 3. **Corsair MP600 Micro 2 TB** (2242, Gen4) — consumer DRAM-equipped class, the floor of what is acceptable. 4. **Avoid** anything DRAM-less/HMB in this role (XG7000, MP600 Mini 2230, WD SN770M): they are the same architecture as the problem drive. Before any reinstall: back up `~/.config/flare/orbit/controller.key` — restoring it after a DGX OS reimage avoids re-pairing all eight agents. ## Reproducing the measurement ```bash # /tmp/lat2.c: N threads, 4 KiB O_DIRECT random reads in a centered working # set, per-read latency percentiles + >200 ms / >1 s / >5 s counters cc -O2 -o /tmp/lat2 /tmp/lat2.c -lpthread F=~/.cache/flare/packages//weights/engram-0-weights.safetensors /tmp/lat2 "$F" 16 15 2 # hot 2 GB slice /tmp/lat2 "$F" 16 15 98 # whole 98 GB table /tmp/lat2 "$F" 64 20 98 # queue-depth collapse # identity/health sudo nvme id-ctrl /dev/nvme0 | grep -E '^(mn|fr|hmpre|npss)' sudo nvme smart-log /dev/nvme0 | grep -E 'media_errors|Temperature|Thermal' journalctl -k | grep 'host memory buffer' ``` Run the reference arm on an idle non-serving node (dgx5) — never on a node that is currently serving.