Benchmarks
Measured MemKV throughput — 97.4 GB/s peak on a 2-server fleet at ~97% of 2× 400GbE line rate — plus August 2026 under-load transport from the AIPerf agentic campaign (45.5 GB/s stores, 104 GB/s KV-shaped nixlbench). End-to-end TTFT and work multipliers are on the inference performance page.
This page measures the wire: how fast bytes move between a client and MemKV. For what that speed gives an inference server under a production-shaped agentic workload (continuing-turn TTFT, tok/s/GPU, and the cost below the capacity cliff), see Inference performance.
Throughput by block size (2-server aggregate)
Test harness. 1 GPU node (2× ConnectX-7 400GbE) drove 2 storage servers (12 NVMe each) with the memkv bench binary, at 64 threads per server (128 total on the dual-server sweep).
RDMA and TCP were swept separately.
The TCP sweep used the inline-bulk path with 64 client connections (one per thread).
Last verified on 2026-07-06.
| Block Size | RDMA Write | RDMA Read | TCP Write | TCP Read |
|---|---|---|---|---|
| 4 KB | 0.98 GB/s | 1.41 GB/s | 1.03 GB/s | 1.06 GB/s |
| 8 KB | 2.03 GB/s | 2.86 GB/s | 2.19 GB/s | 2.10 GB/s |
| 16 KB | 4.09 GB/s | 5.24 GB/s | 3.77 GB/s | 3.38 GB/s |
| 32 KB | 7.91 GB/s | 10.87 GB/s | 7.66 GB/s | 6.32 GB/s |
| 64 KB | 16.21 GB/s | 21.19 GB/s | 10.39 GB/s | 11.55 GB/s |
| 128 KB | 30.53 GB/s | 38.81 GB/s | 12.16 GB/s | 13.88 GB/s |
| 256 KB | 53.92 GB/s | 70.15 GB/s | 14.61 GB/s | 15.99 GB/s |
| 512 KB | 61.59 GB/s | 91.35 GB/s | 15.34 GB/s | 19.71 GB/s |
| 1 MB | 89.77 GB/s | 96.11 GB/s | 16.10 GB/s | 20.45 GB/s |
| 2 MB | 93.14 GB/s | 96.98 GB/s | 16.29 GB/s | 21.69 GB/s |
| 4 MB | 97.09 GB/s | 97.25 GB/s | 16.34 GB/s | 22.26 GB/s |
| 8 MB | 97.30 GB/s | 97.41 GB/s | 10.59 GB/s | 16.33 GB/s |
| 16 MB | 97.36 GB/s | 97.45 GB/s | 10.59 GB/s | 16.54 GB/s |
RDMA peak: 97.4 GB/s read, 97.4 GB/s write, about 97% of 2× 400GbE line rate. TCP peak: 22.3 GB/s read, 16.3 GB/s write. RDMA runs near line rate from about 1 MB blocks up. TCP still reaches tens of GB/s by pipelining many requests over each connection, so it is a practical transport wherever RDMA cannot reach end to end: routed or multi-hop fabrics, cloud, and mixed NICs.
These benchmarks used PCIe Gen4 QLC drives. Gen5 TLC or SLC drives improve latency and sustained write throughput. The network is already the limit here, so faster drives mainly help tail latency under load.
Linear scaling
| Configuration | Servers | Peak Write | Peak Read |
|---|---|---|---|
| Single Server | 1 | 48.7 GB/s | 48.7 GB/s |
| Dual Server | 2 | 97.4 GB/s | 97.4 GB/s |
Each server has 12 NVMe drives in the same PCIe domain as the NIC. The servers share no drives and do not coordinate, so adding servers scales throughput linearly.
Under live engine load
The sweeps above measure the store on its own. The August 2026 NVIDIA AIPerf agentic campaign measured it under a live inference engine: the same 198 windows on 8× H200 and 2 MemKV nodes (48 raw NVMe, dual 400 Gb rails) that feed Inference performance. The storage tier was never the bottleneck:
| Measurement | Result |
|---|---|
| Sustained stores (client → RDMA → raw NVMe, 4 endpoints) | 45.5 GB/s |
| Busiest engine feed into that path | 16.4 GB/s (~3× headroom left) |
| GPUDirect restore · 120 GB context | ~1.6 s, line rate both directions, zero penalty vs host memory |
| Eight rails on one 8-GPU node | 102 GiB moved, balanced within ±2.4%, zero transfer failures (InfiniBand and RoCE v2) |
In the same campaign, NIXL nixlbench on KV-shaped blocks over dual-rail RoCE sustained 104 GB/s.
NIXL plugin (nixlbench)
These results come from NVIDIA's nixlbench (from nixl v1.2.0) driving the MemKV NIXL plugin against the same dual-server fleet as the native sweep, with DRAM destinations.
Every run uses --recreate_xfer, which creates a new transfer on every iteration so no cached handle speeds it up.
Block sizes go up to nixlbench's default maximum of 64 MiB.
Inside each post_xfer call, the plugin merges the descriptor list: descriptors for the same key at contiguous offsets become one wide BatchRead, and the server moves the data in 2 MiB bounce-buffer chunks.
These are separate sweeps from the native memkv bench results at the top of this page (97.4 GB/s peak) and from the 104 GB/s KV-shaped figure in Under live engine load.
Batched write throughput (GB/s)
nixlbench WRITE, batch_size=16, --recreate_xfer, 64 threads, swept to the
default 64 MiB max block size:
| Block | Single rail | Dual rail |
|---|---|---|
| 1 MB | 2.7 GB/s | 10.7 GB/s |
| 4 MB | 6.4 GB/s | 25.2 GB/s |
| 8 MB | 12.5 GB/s | 33.5 GB/s |
| 16 MB | 24.0 GB/s | 48.1 GB/s |
| 32 MB | 43.0 GB/s | 69.9 GB/s |
| 64 MB | 44.3 GB/s | 71.9 GB/s |
The writes in a batch persist to the NVMe drives in parallel, so batched WRITE throughput grows with block size. At 64 MB a single rail reaches about 89% of its line rate.
Read throughput scales across rails the same way. The figures at the top of this page (48.7 GB/s for one server, 97.4 GB/s for two) are RDMA reads. The plugin adds one feature for reads: it merges reads of a shared range.
Read coalescing: fetch once, scatter locally
The plugin merges (coalesces) a list of read descriptors before it sends anything over the network.
When several descriptors in one post_xfer point to the same KV block with overlapping or contiguous ranges, the plugin:
- Picks one destination that spans the whole range as the cover.
- Issues a single RDMA read into the cover.
- Fills every other destination with a local copy from the cover:
std::memcpyfor host (DRAM) buffers, orcudaMemcpyfor GPU (VRAM) buffers through the CUDA runtime, which the plugin loads at run time.
The shared block crosses the NIC once and reaches N buffers at local memory speed (DRAM or HBM), not network speed.
This is what makes prefix sharing pay off: when many concurrent sequences read the same prompt prefix, the prefix crosses the fabric once and is copied to each reader in memory. If the destinations are GPU memory and no CUDA copy path is available, or no single destination covers the range, the plugin sends a separate network read for each destination, so the result stays correct.
Writes are never merged this way, because writing one object range from two source buffers has no defined result. Each write persists on its own, so write throughput shows the real network and NVMe bandwidth (see the table above).
Why read throughput can exceed line rate
When you benchmark the plugin with nixlbench, it can report read throughput well above NIC line rate: for example, about 150 GB/s on a single 400GbE rail at batch_size=16, roughly 3× the rail's ~50 GB/s limit.
This is expected. It is read coalescing showing through the benchmark's byte counting, not a measurement error:
nixlbenchcredits every descriptor in a batch with a full block transfer. It counts the logical bytes delivered to the caller.- The plugin keys each descriptor by its NIXL
dev_id, and thenixlbenchsynthetic workload uses very few distinct keys (one whennum_initiator_dev=1). So a batch's descriptors all point to the same KV block, which the plugin fetches once and copies to every destination locally. - Reported throughput is logical bytes divided by time. The shared bytes cross the NIC once but are counted N times, so the reported rate is above the network limit.
The bytes are real: every destination receives correct data (--check_consistency=1 passes), and the gain is real for reads that share a prefix.
But it is an effective rate, limited by local memory bandwidth for the shared part, not by network bandwidth.
A workload where every key is different gets no coalescing, and its reads follow the network-limited rates below.
Per-op read wire rate (batch_size=1)
A single read descriptor uses one rail, so single-rail and dual-rail results track together. This table shows the network limit for one operation, not the aggregate (the native figures above show the aggregate). Single rail, 16 threads. The rate climbs toward the single-rail line rate (~50 GB/s) as blocks grow and the fixed cost per operation is spread over more bytes:
| Block | Read (b=1) |
|---|---|
| 1 MB | 4.8 GB/s |
| 4 MB | 6.7 GB/s |
| 16 MB | 25.6 GB/s |
| 32 MB | 40.0 GB/s |
| 64 MB | 40.8 GB/s |
Read latency: batching shares the round trip
A batch of read descriptors that share a block becomes one merged network operation, so the whole batch finishes in about the time of one unbatched read.
Latency per descriptor therefore drops by close to the batch size: about 16× from batch_size=1 to batch_size=16 at the smaller block sizes.
All 16 descriptors are served by one fetch and a local copy, instead of 16 separate round trips.
Absolute latency per operation depends on how many threads share the rail, so the useful figure is the ratio: batching turns N descriptors into one network round trip.
Data verification (--check_consistency=1)
Throughput means nothing if the bytes are wrong, so the harness checks the data end to end with --check_consistency=1. This compares the actual bytes, not a checksum:
- Writes are read back from the drives. The harness fills the initiator buffer with a known value (
0xaa) and writes it to MemKV. It then overwrites (poisons) that local buffer with0x00, reads the data back from MemKV over RDMA, and compares every byte with the known value. A single wrong byte fails the run. This checks the whole path: client memory → server → NVMe → server → client memory. - Reads check every delivered byte in the client buffer against the expected value.
Across the full block-size sweep (4 KB → 64 MB), every verified transfer passed with zero byte mismatches. What lands on the NVMe drives is exactly what the client reads back.
Storage Internals
How MemKV stores blocks on NVMe, how its on-drive format handles upgrades, and how it frees space.
Inference Performance
How MemKV changes time-to-first-token and served throughput under a production-shaped agentic workload — eight models from 8B to 405B, 198 measured windows on 8× H200, with the cost below the capacity cliff published next to the gain above it — plus the Nemotron-3 Ultra interactivity frontier (tok/s/GPU vs tokens/s/user).