Home · ASPLOS 2023 · Storage & File Systems

GPU-Initiated On-Demand High-Throughput Storage Access in the BaM System Architecture

Zaid Qureshi, Vikram Sharma Mailthody, Isaac Gelado, Seungwon Min, Amna Masood, Jeongmin Brian Park and 7 others

Conference
ASPLOS 2023
Published
2023
Pages
325-339
Citations
44

GPUs wait on CPUs to fetch their data from SSDs, and the CPU software stack — not the flash — is the bottleneck; BaM maps NVMe queues and doorbells straight into GPU memory so a hundred thousand GPU threads can each demand their own 512-byte reads, hitting 45.8 million IOPs and matching a host-DRAM system that costs 21.7× more.

The argument in one glance

  1. Problem

    Terabyte datasets, an 80-gigabyte accelerator

    Graph analytics, graph neural networks, and recommender systems want GPU compute over arrays spanning tens of gigabytes to tens of terabytes — with fine-grained, data-dependent access (which neighbor list you need depends on the traversal). GPU memory, despite a 53× improvement since 2006, caps at 80 GB.

  2. What people did

    Let the CPU feed the GPU, or buy enough DRAM

    CPU-centric designs either pre-tile: the CPU partitions the dataset and pipelines chunks into GPU memory around kernel launches — or fault: memory-mapped files trigger GPU page faults serviced by the CPU’s handler. DRAM-only designs hold everything in host memory (128 GB–2 TB) or pool many GPUs’ memories.

  3. Why it fell short

    The CPU software stack is the bottleneck; DRAM is the wrong price

    Tiling fetches unneeded bytes (I/O amplification) and synchronizes CPU and GPU around every chunk; fault-driven paths funnel a hundred thousand threads through a nearly-serial page-fault handler — prior GPU-initiated art managed ~823K IOPs on an A100. Even io_uring loses up to 36.4% of access latency to kernel software. And DRAM at tens of TB is absurd: a 10 TB pool needs 125 A100s, and preloading wastes it on bytes never touched.

  4. This paper's idea

    Move the storage control path onto the GPU

    BaM (Big accelerator Memory) places NVMe submission/completion queues and I/O buffers in GPU memory, maps SSD doorbell registers into the CUDA address space, and gives kernels an mmap-like bam::array backed by a highly concurrent software cache. GPU threads request exactly the bytes they need, when they need them — no CPU in the loop. A prototype with 10 SSDs reaches 45.8M random-read IOPs (90% of PCIe Gen4 ×16), matches a 21.7×-more-expensive DRAM-only system on graph analytics, and beats CPU-initiated data analytics by 5.3×.

Why this problem is worth solving

The memory-capacity wall for accelerators is not a niche inconvenience — it decides which workloads GPUs can serve at all. Graphs with hundreds of millions of nodes, embedding tables for recommenders, GNN feature stores: all are arrays of tens of GB to tens of TB, accessed in patterns computed at runtime (the traversal decides which neighbor list is next). Against that, an A100 offers 80 GB.

The conventional escape routes each fail a different way. Host DRAM costs more per GB than flash by an order of magnitude and must be pre-loaded — for data-dependent workloads, much of what is loaded is never touched. CPU-orchestrated tiling turns the CPU into a data butler: it cannot know which bytes the GPU will need, so it ships whole partitions (

fetching more bytes than the computation actually uses — e.g., reading a 128 KB block to consume one 512-byte neighbor list
), and every chunk boundary is a CPU-GPU synchronization. Fault-driven paging funnels the GPU’s hundred thousand threads through the CPU’s page-fault handler — software with CPU-scale, not GPU-scale, parallelism. The prior state of the art for GPU-initiated access (GPUfs/ActivePointers) topped out near 823 thousand IOPs; the SSDs themselves could serve fifty million.

The quiet enabler is that storage hardware outgrew its software. NVMe SSDs are simple queue machines —

submission queues (SQ) where software writes commands, completion queues (CQ) where the controller posts results, and doorbell registers that signal new work — all just memory operations
— and modern GPUs can have those queues in their own memory (GPUDirect RDMA) and ring doorbells mapped into their address space (GPUDirect Async). The hardware permits an accelerator-centric design; what was missing was the software architecture. (For the opposite philosophy — moving compute into the storage instead of giving the accelerator direct access — see the GenStore explainer on this site.)

What people did before, and where it broke

Proactive tiling. CPU code partitions the dataset, pipelines chunks to the GPU, launches kernels per chunk. Works for regular access; for data-dependent workloads it amplifies I/O (the CPU cannot predict need), multiplies kernel launches, and burns programmer effort on partition/overlap logic.

Memory-mapped files with GPU page faults. Elegant abstraction, serial reality: faults traverse CPU driver and OS machinery whose thread-level parallelism is orders of magnitude below the GPU’s request rate.

GPUfs / ActivePointers. Pioneering GPU-side file abstractions — but data transfers still route through CPU software, capping miss-handling throughput at a level BaM exceeds by 20.7× even while ActivePointers serves misses from fast CPU DRAM and BaM from actual SSDs.

NVIDIA GPUDirect Storage (GDS). Moves the data path peer-to-peer (SSD → GPU memory, no host bounce) but keeps the control path on the CPU — fine for large sequential transfers, collapsing at fine granularity: BaM saturates ~25 GB/s at 4 KB blocks where the GDS/CPU stack cannot.

The BaM system architecture

  1. bam::array abstraction new

    An mmap-like array over storage: GPU code indexes it as ordinary memory (val = array[tid]). The abstraction computes the cache-line offset; per-warp coalescing deduplicates — one thread probes the cache per unique line on behalf of its warp.

  2. GPU software cache new

    All virtual and physical memory pre-allocated at startup — no OS memory management, no legacy critical sections; a lock exists only for line insertion/eviction. Coalesces concurrent misses to the same line into one storage request, exploits the GPU’s enormous memory and atomics bandwidth, and lets applications configure line size (512 B for scattered graph access, larger for locality-rich workloads).

  3. User-level GPU I/O stack new

    NVMe SQ/CQ pairs and I/O buffers live in GPU memory; SSD doorbell registers are mapped into the CUDA address space (custom Linux driver + GPUDirect RDMA/Async). Thousands of threads enqueue concurrently via a ticket/turn protocol with minimal critical sections and batched doorbell rings.

  4. Peer-to-peer data path new

    The SSD controller fetches commands from GPU-resident queues and DMAs data directly into GPU memory — the CPU is on neither the control nor the data path. The design is storage-agnostic: Optane, enterprise flash, or consumer NVMe.

  5. Prototype

    Off-the-shelf everything: A100-80GB, a PCIe expansion chassis holding up to 10 SSDs, a custom (SmartIO-style) driver. No new hardware — the contribution is the software architecture the existing hardware was waiting for.

Little's-law sizing: sustaining ~51M 512-byte accesses/s needs 561 outstanding requests on 11 µs Optane — or 16,524 on 324 µs consumer flash. Trivial for a GPU with 100K+ threads; impossible for a CPU page-fault handler.

The idea

Latency tolerance is a parallelism budget — and the GPU is rich. The foundational arithmetic is Little’s Law: throughput × latency = required outstanding requests. Fifty-one million 512-byte accesses per second against Optane’s 11 µs needs 561 requests in flight; against a consumer SSD’s 324 µs, 16,524. For a CPU-driven control path those are absurd numbers; for a GPU running hundreds of thousands of threads, they are a rounding error — if the software lets threads issue requests without serializing. BaM’s whole design flows from this: don’t reduce storage latency, hide it under thread parallelism, which the GPU has in three orders of magnitude more than the CPU.

Queues that thousands of threads can share. The NVMe protocol was designed for a few CPU cores; its doorbell registers are write-only and must be updated with monotonic tails — a natural critical section that would strangle GPU-scale concurrency. BaM’s queue protocol dissolves it: an atomic ticket counter assigns each thread a slot in a virtual queue (slot = ticket mod queue size, turn = ticket div queue size); a turn_counter array lets each physical slot act as an ordered sub-queue; threads copy commands into their slots in parallel and set bits in a mark bit-vector; one winning thread takes the lock, advances the tail past all consecutively marked entries, and rings the doorbell once for the whole batch. Completion handling mirrors this, lock-free polling included. The result: enqueue/dequeue at GPU thread counts with critical sections measured in a handful of instructions — the difference between 823 K and 45.8 M IOPs.

A cache built for a million concurrent probes. OS memory management supports every legacy need and pays in critical sections. BaM’s cache pre-allocates everything at startup, reduces locking to line insertion/eviction, and puts the GPU’s strengths — massive memory bandwidth, huge atomic throughput — to work on metadata. Warp-level coalescing collapses duplicate probes; miss coalescing collapses duplicate storage requests. Hot data serves at 430 GB/s — GPU-memory speeds, 11.2× ActivePointers’ cache.

Fine granularity is the point, not a detail. Configurable cache lines down to 512 bytes mean a graph traversal fetches a neighbor list, not the megabyte around it. This is where the economics flip: data-dependent workloads on tiled or big-block systems drown in amplification; BaM’s on-demand 512-byte reads cut traffic so much that four SSDs at PCIe bandwidth keep up with host DRAM on end-to-end graph analytics — because the DRAM system must first spend minutes loading files it may barely use.

One neighbor-list read, from GPU thread to flash and back

SQ/CQ = NVMe submission/completion queues (resident in GPU memory); doorbell = SSD register signaling new work (mapped into CUDA address space). Cache line = 512 B here. The walkthrough follows Figure 2's numbered path with the queue protocol of §3.4.

Step 1 of 6

A thread indexes the array

A graph-traversal thread needs node 87,234,101’s neighbor list: val = array[offset]. The bam::array abstraction converts the index to a cache-line offset. Its warp-mates needing the same line coalesce — one thread probes the cache for all of them.

bam::array: mmap-like, storage-backed
warp coalescer: 1 probe per unique line

Why it works

  • Storage latency × target throughput = required in-flight requests (Little’s Law) → the GPU’s thread count covers even consumer-SSD latencies effortlessly → latency tolerance comes from parallelism the accelerator already has, not from faster (pricier) media.
  • The CPU contributed nothing essential to the control path → moving queues, buffers, and doorbells into GPU space deletes the page-fault handler, kernel crossings (36.4% of latency under io_uring), and CPU-GPU synchronization in one stroke.
  • NVMe’s serialization points (tails, doorbells) are batchable → the ticket/turn/mark protocol lets command-copying parallelize and reduces the critical section to a tail bump plus one doorbell per batch → queue throughput scales to GPU thread counts.
  • On-demand 512-byte access eliminates amplification → four SSDs of PCIe bandwidth outperform pre-loading into host DRAM end-to-end → capacity moves to flash at 4.3–21.8× lower cost per GB with performance parity.
  • Everything is off-the-shelf (A100 features, NVMe spec, a small driver) → the design is a software architecture, adoptable without new silicon — and storage-medium-agnostic, so cost/performance is a dial (consumer 980pro: 2.7–3.2× slower than Optane, far cheaper).

What it costs

  • GPU memory spent on infrastructure. Queues, I/O buffers, and the software cache all live in the GPU’s scarce 80 GB.
  • Cache logic in every access. Software cache probes and queue protocol instructions tax each miss; the design amortizes but cannot eliminate them.
  • Trust model. User-level access to raw NVMe queues bypasses kernel file-system enforcement; the paper points to emerging frameworks for securing accelerator-centric storage rather than solving isolation itself.
  • Ordering subtleties. GPUDirect RDMA write ordering over PCIe requires care (a read must follow writes to guarantee visibility) — handled, but the kind of detail that makes such systems delicate.
  • Read-centric evaluation. Graph/data analytics are read-heavy; writes reach 10.6 M IOPs but endurance and write-path behavior under sustained load get less attention.

The evaluation

The prototype is deliberately mundane hardware: an A100-80GB, dual EPYC host, and a PCIe expansion chassis with up to 10 SSDs of three classes (Intel Optane P5800X, Samsung enterprise, Samsung 980pro consumer). Workloads: microbenchmarks, BFS and connected-components graph analytics on five large graphs (GAP-kron, GAP-urand, Friendster, MOLIERE_2016, uk-2007-05), and I/O-bound data analytics.

Feeding a GPU from storage: four architectures

PropertyCPU-centric (tiling / faults)GDS (P2P data, CPU control)DRAM-only (host memory)BaM
Control path
Fine-grained throughputSub-million IOPsDRAM-speed once loaded
End-to-end applications—Reference for graph analytics (with file-load time included)
Media flexibility——DRAM only — a 10 TB pool would need 125 A100s
Prototype: NVIDIA A100-80GB, 2× EPYC 7702, 1 TB DDR4, H3 Falcon-4016 PCIe expansion with up to 10 SSDs (Intel Optane P5800X 11 µs; Samsung DC 1735; consumer 980pro 324 µs); custom SmartIO-style Linux driver, GPUDirect RDMA/Async, CUDA 11.4. Graphs to hundreds of GB (GAP, Friendster, MOLIERE, uk-2007); host-DRAM baseline includes file-loading time, as end-to-end honesty requires.Underlined values explain themselves when clicked.

Check your understanding

  1. 1. BaM makes no attempt to reduce SSD latency (11–324 µs, thousands of times slower than HBM). Predict why the system still saturates the storage.

  2. 2. NVMe doorbells are write-only registers requiring monotonically newer tail values — a textbook critical section. How does BaM let thousands of threads enqueue without strangling on that lock?

  3. 3. Four SSDs delivering ~25 GB/s match a host-DRAM system with far higher bandwidth on end-to-end BFS (1.00×) and beat it on CC (1.49×). What two effects close the bandwidth gap?

  4. 4. BaM’s software cache pre-allocates all its memory at startup instead of using OS-style dynamic memory management. What does this buy at GPU scale?

Related papers on Storage & File Systems

See all 64 →