Architecture & Measurement-Design Analysis

Quantifying Performance Gains of GPUDirect Storage

Source: Inupakutika, D.; Davis, B.; Yang, Q.; Kim, D.; Akopian, D. 2022 IEEE International Conference on Networking, Architecture and Storage (NAS), pp. 1-8. DOI: 10.1109/NAS55553.2022.9925516 Authors: Samsung (San Diego, CA) + University of Texas at San Antonio. Reader: Direct PDF read (gemini-reader quota exhausted; codex-reader unavailable). 9-page paper read whole via the pages parameter. Analyst: Vishwakarma Date: 2026-06-15


Table of Contents

  1. Evaluation Harness Architecture (the "instrument")
  2. System-Under-Test Architecture (GDS vs the bounce-buffer baseline)
  3. Design-Space Diagram (axes swept vs axes held fixed)
  4. Algorithm / Control Flow Diagrams (the two data paths)
  5. Quantitative Results — Empirical Findings by Regime
  6. Configuration-Regime Trade-off Tables
  7. Bottlenecks & Insights Surfaced by the Measurements
  8. Limitations of the Methodology
  9. Note on NCCL Tuning
  10. Analogy

1. Evaluation Harness Architecture (the "instrument")

The harness is a two-path A/B comparator: every workload is run twice over the same storage device — once on the GDS path (cuda_io=cufile, O_DIRECT, direct=1) and once on the traditional bounce-buffer path (cuda_io=posix, cudaMemcpy) — and the difference in throughput, IOPS, latency, CPU utilization, and GPU utilization is reported. The comparison is deliberately a paired measurement: the SSD, the GPU, the filesystem, and the workload are held identical across the two arms, so the only independent variable is which data path the bytes travel. This is the cleanest possible isolation of GDS's effect.

Crucially, the harness is run over two storage backends so that the GDS benefit can be measured both where the device is close (local DAS) and where it is far (a disaggregated Weka cluster across a 100 GbE fabric). The paper calls these Phase 1 (local, EXT4) and Phase 2 (remote, WekaFS).

+-------------------------------------------------------------------+
|                  Measurement Harness                              |
|                                                                   |
|  +---------------------+   +-----------------------------------+  |
|  | Workload Driver     |-->| Workload Loader                   |  |
|  | (FIO 3.26 +         |   |  Synthetic: 4K..256K random read  |  |
|  |  GDSIO,             |   |   (1 TB read IOs, 100% random)    |  |
|  |  DALI+TensorFlow)   |   |  Real:      ResNet-50 v1.5 train  |  |
|  | metrics: IOPS,      |   |   (ImageNet 2012, BS 4..256)      |  |
|  |  GiB/s, usec, CPU%  |   +-----------------+-----------------+  |
|  +---------------------+                     |                    |
|              |                               v                    |
|              v          +---------------------------------------+ |
|  +--------------------+ | Path-Switch Layer (the A/B knob)      | |
|  | Precondition stage | |                                       | |
|  |  - Seq precond     | |   GDS arm:  cuda_io = cufile          | |
|  |    (128K writes)   | |     O_DIRECT, direct=1, libcufile     | |
|  |  - Rand precond    | |                                       | |
|  |    (4K writes)     | |   Baseline: cuda_io = posix           | |
|  |  -> SSD steady     | |     pread + cudaMemcpyHostToDevice    | |
|  |     state          | +-------------------+-------------------+ |
|  +--------------------+                     |                     |
|                                             v                     |
|  +-----------------------------------------------------------+    |
|  | I/O Engine + GDS Stack                                     |    |
|  |   libcufile / cuFile API (engine=libcufile)                |    |
|  |   nvidia-fs.ko kernel driver  (GDS device path)            |    |
|  |   CUDA 11.4 toolkit                                         |    |
|  +-----------------------------------------------------------+    |
|              |                                                    |
|              v                                                    |
|  +-----------------------------------------------------------+    |
|  | Storage Backend (one of two)                               |    |
|  |   Phase 1 LOCAL : Samsung PM9A3 NVMe, EXT4, PCIe Gen4 x4    |    |
|  |   Phase 2 REMOTE: WekaFS over 100 GbE, 6 targets x 4 drives |    |
|  +-----------------------------------------------------------+    |
|              |                                                    |
|              v                                                    |
|  +-----------------------------------------------------------+    |
|  | Result Aggregator                                          |    |
|  |   (path x size/batch) -> Fig. 4 (local), Fig. 5 (remote)   |    |
|  |   4 panels each: CPU cores, IOPS, latency, throughput      |    |
|  +-----------------------------------------------------------+    |
+-------------------------------------------------------------------+
^ Fig 1: Measurement harness — a paired A/B comparator. The only
  swept primary variable is the data path (cufile vs posix); the SSD,
  GPU, filesystem and workload are held fixed within each cell.

The harness has two unusual properties worth flagging. First, it preconditions the SSD twice (a sequential 128K-granularity fill, then a 4K random-write fill) before every measurement so that the drive is in steady state rather than fresh-out-of-box (FOB) — the authors report ~3.365 GB/s sequential-write and ~199K random-write IOPS at steady state, matching Samsung's published PM9A3 spec. Without this, GDS-vs-baseline numbers would be polluted by the SSD's write-cliff behavior. Second, it deliberately uses two completely different benchmark front-ends for the two workload classes: GDSIO (NVIDIA's in-house tool built on libcufile) for synthetic random reads, and DALI + TensorFlow for the real ResNet-50 pipeline. This means "synthetic vs real" is also implicitly "GDSIO vs DALI"; the authors accept that conflation in order to use each tool's production-grade integration.

Methodology specifics extracted verbatim:

Knob Value
Synthetic tool GDSIO (engine = libcufile)
Real-workload tool DALI pipeline + TensorFlow reader
FIO version 3.26-59
CUDA toolkit 11.4
Preconditioning Sequential (128K) + Random (4K), run twice
Steady-state seq write ~3.365 GB/s
Steady-state rand write ~199K IOPS
Local filesystem EXT4, 512-byte logical blocks
Remote filesystem WekaFS 3.12.0, POSIX client mount
Metrics IOPS, throughput (GiB/s), latency (usec),
CPU util (USR + SYS %), GPU util %
Error bars / variance Not reported

The metric set is broader than a pure-bandwidth storage benchmark: by reporting USR and SYS CPU cores separately the harness can attribute savings to the elimination of the bounce-buffer copy (a SYS/kernel cost) rather than to user-space changes. That USR/SYS split is the single most diagnostic feature of the instrument.


2. System-Under-Test Architecture (GDS vs the bounce-buffer baseline)

The specimen is NVIDIA GPUDirect Storage (GDS) — a direct DMA path between an NVMe device and GPU memory that bypasses a staging copy through host (CPU) DRAM. The paper's whole contribution is to quantify GDS against the path it replaces, so the two paths must be drawn side by side.

2.1 The two data paths

  (a) TRADITIONAL PATH (cuda_io = posix)   "bounce buffer"

   +--------+   (1) DMA    +-----------+  (2) cudaMemcpy   +--------+
   |  NVMe  | ===========> |  CPU /    | ================> |  GPU   |
   |  SSD   |  pread()     |  RAM      |  HostToDevice     | memory |
   +--------+              | (bounce   |                   +--------+
        ^                  |  buffer)  |
        |                  +-----------+
   PCIe switch                  ^
   routes bytes UP to CPU,      |  CPU cycles burned staging the copy;
   then back DOWN to GPU        |  kernel page-cache + read-ahead engaged

  (b) GDS PATH (cuda_io = cufile, O_DIRECT, direct=1)

   +--------+   (1) direct DMA over PCIe / fabric    +--------+
   |  NVMe  | ====================================> |  GPU   |
   |  SSD   |   cuFileRead(fh, d_buf, size, 0, 0)    | memory |
   +--------+   NO bounce buffer, NO CPU copy        +--------+
        ^
        |
   PCIe switch routes bytes straight ACROSS to the GPU BAR;
   all PCIe devices share one physical address space (BAR addr)
^ Fig 2: The two data paths. (a) burns CPU cycles and DRAM bandwidth on
  a staging copy; (b) DMAs storage->GPU directly, freeing the CPU.

The mechanism that makes (b) possible: in GPUDirect RDMA / GDS, all PCIe devices see the same physical address space and can read/write each other's Base Address Register (BAR) regions. So the SSD's DMA engine can be programmed to target GPU memory directly. The traditional path cannot do this because POSIX pread lands data in host memory, after which cudaMemcpy(HostToDevice) is mandatory to reach the GPU.

2.2 The GDS software stack

+------------------------------------------------+
| Applications on CPU                            |  user app / framework
+------------------------------------------------+
| cuFile API  (libcufile.so)                     |  GDS user API
+------------------------------------------------+
| CUDA                                           |  runtime
+------------------------------------------------+   <== user / kernel line
| nvidia-fs.ko kernel driver                     |  GDS device path
+------------------------------------------------+
| Virtual File System (VFS)                      |  Linux I/O stack
+------------------------------------------------+
| Filesystem driver | Block I/O driver |         |  third-party / OS kernel
| Storage driver                       |         |
+------------------------------------------------+
|        Storage (SSDs) DMA engine               |  hardware DMA
+------------------------------------------------+

  PARALLEL PATH for proprietary distributed FS (e.g. WekaFS):
  cuFile API  -->  Distributed FS (user-space)  -->  Storage DMA
  (no nvidia-fs / VFS patching required -> "Linux compatibility")
^ Fig 3: GDS software stack (paper Fig. 2). nvidia-fs sits in the kernel
  I/O stack for local FS; proprietary distributed FS take a user-space
  shortcut that needs no kernel patching.

This stack has a consequential fork. For a local filesystem (EXT4), GDS routes through the nvidia-fs.ko kernel driver, which hooks into VFS to expose GPU memory buffers to the block/disk I/O layers. For a proprietary distributed filesystem (WekaFS), the vendor implements its own user-space GDS path, so no kernel patching is needed — the paper calls this WekaFS's "Linux compatibility." A third mode exists when no GDS-supported filesystem is present: cuFile falls back to a compatibility mode that internally uses CPU bounce buffers — i.e. it quietly degrades to path (a) while keeping the cuFile API surface.

2.3 The two physical testbeds

  PHASE 1 — LOCAL DAS (Table I)
  +-----------------------------------------------------------+
  | Client: Dell DSS8440                                       |
  |   GPU : 8x NVIDIA Tesla V100S, 32 GB each                  |
  |   CPU : 2x Intel Xeon Platinum 8268, 2.9 GHz,              |
  |         48 (24x2) total cores, 768 GB system memory        |
  |   Drive: Samsung PM9A3 (3.49 TiB) x4 per host,            |
  |          PCIe Gen4 x4, U.2                                 |
  |   OS  : Ubuntu 20.04 focal                                |
  |   FS  : EXT4 (512-byte logical blocks)                     |
  +-----------------------------------------------------------+

  PHASE 2 — REMOTE DISAGGREGATED (Table II, Fig. 3)
  +-----------------------------------------------------------+
  |  Client 1 (Dell DSS8440, 8x V100S)                        |
  |        |                                                   |
  |   100 GbE x1  (NIC: Connect-5 dual-port)                  |
  |        |                                                   |
  |   [ Data network 100 GbE ]                                |
  |        |                                                   |
  |   100 GbE x1                                              |
  |        |                                                   |
  |  Weka Target Cluster: 6 nodes (Dell R730xd)               |
  |    each: 2x Xeon E5-2640 v3 @2.6 GHz (15 cores),          |
  |          128 GB RAM, 4x PM9A3 drives, WekaFS 3.12.0       |
  |    total capacity 37.72 TiB (21.72 TiB allocated)         |
  |    DPDK data plane (kernel-bypass networking)             |
  +-----------------------------------------------------------+
^ Fig 4: The two testbeds. Phase 1 measures GDS where the SSD is local
  over PCIe Gen4; Phase 2 measures it across a 100 GbE fabric to a 6-node
  Weka cluster using kernel-bypass DPDK networking.

The remote testbed is the more demanding case because the bytes must cross a 100 GbE link and a distributed filesystem before reaching GPU memory — yet it is also where GDS has the most CPU work to eliminate, because Weka's DPDK-based data plane already consumes CPU cores for kernel-bypass networking. GDS lets those cores be reclaimed.


3. Design-Space Diagram (axes swept vs axes held fixed)

The experiment sweeps two primary axes per workload class and runs each sweep over two storage backends. The synthetic-read sweep varies the I/O transfer (block) size; the ResNet-50 sweep varies the mini-batch size. Both sweeps cross the data-path axis (GDS vs baseline), which is the variable of interest.

                   DESIGN SPACE (per workload class)
  +---------------------------------------------------------------+
  |                                                               |
  |  Axis 1: DATA PATH (2 levels) -- the variable of interest     |
  |    [ GDS   : cuda_io = cufile, O_DIRECT, direct=1 ]           |
  |    [ POSIX : cuda_io = posix, cudaMemcpy bounce buffer ]      |
  |                                                               |
  |  Axis 2a: SYNTHETIC -> I/O TRANSFER SIZE (7 levels)           |
  |    [4K] [8K] [16K] [32K] [64K] [128K] [256K]                  |
  |    workload: 100% random read, 1 TB read IOs                  |
  |                                                               |
  |  Axis 2b: REAL DL -> MINI-BATCH SIZE (7 levels)               |
  |    [4] [8] [16] [32] [64] [128] [256]                         |
  |    workload: ResNet-50 v1.5, ImageNet 2012, DALI pipeline     |
  |                                                               |
  |  Axis 3: STORAGE BACKEND (2 levels)                           |
  |    [ LOCAL  DAS : PM9A3 / EXT4 / PCIe Gen4 ]  -> Fig. 4       |
  |    [ REMOTE Weka: PM9A3 / WekaFS / 100 GbE ] -> Fig. 5        |
  |                                                               |
  |  Axis 4 (remote only): CLIENT MOUNT CPU CORES (3 levels)      |
  |    [2 cores] [4 cores] [6 cores]  -> WekaFS throughput study  |
  |                                                               |
  |  Held FIXED:                                                  |
  |    - GPU model     : NVIDIA Tesla V100S, 32 GB                |
  |    - SSD model     : Samsung PM9A3, PCIe Gen4 x4              |
  |    - CUDA toolkit  : 11.4                                     |
  |    - Workload type : 100% RANDOM read (synthetic);           |
  |                      ResNet-50 v1.5 (real) -- no other models |
  |    - Read vs write : READ only (no write-path GDS results)    |
  |    - Queue depth   : not swept in reported results            |
  |    - GPU count     : 1 (synthetic), 8 (ResNet local),        |
  |                      3 (ResNet remote)                        |
  |    - SSD state     : steady state (preconditioned twice)      |
  +---------------------------------------------------------------+
^ Fig 5: Design space. The data-path axis is crossed against transfer
  size (synthetic) or batch size (real DL), over two storage backends.
  Everything else -- GPU, SSD, CUDA, workload type -- is pinned.

Three absences scope the study. First, only the read path is characterized; although the abstract mentions reads and writes, all reported figures (Fig. 4, Fig. 5) are read workloads, and the preconditioning writes serve only to put the SSD in steady state. Second, only one real model (ResNet-50) is benchmarked — the future work explicitly names "different MLPerf benchmark models" as not-yet- done. Third, queue depth is not swept in the reported panels even though GDSIO exposes it; the only multi-threading axis that is varied is the remote-storage client mount core count (2/4/6), which controls how many CPU cores feed the Weka data plane.


4. Algorithm / Control Flow Diagrams (the two data paths)

The two I/O paths differ in exactly which system calls issue the transfer. The paper gives both call sequences explicitly; the control flow below makes the divergence point visible.

4.1 The read-issue control flow

  START (application wants file bytes in GPU memory)
       |
       v
  (1) Open the file
       baseline: fd  = open("data_file", O_RDONLY, ...)
       GDS     : fh  = open("data_file", O_RDONLY | O_DIRECT, ...)
       |                                  ^^^^^^^^^
       |                                  O_DIRECT bypasses page cache
       v
  (2) Allocate the destination buffer
       baseline: buf   = malloc(size)        // HOST memory
       GDS     : cudaMalloc(&d_buf, size)    // DEVICE memory
       |
       v
  (3) Is a GDS-supported filesystem present?
       |
       +--- NO ----> COMPATIBILITY MODE: cuFile internally uses
       |             CPU bounce buffers (silently behaves like path a)
       |
       +--- YES ---> continue to direct path
       |
       v
  (4) Issue the read
       baseline: pread(fd, buf, size, 0)            // SSD -> HOST
                 cudaMemcpy(d_buf, buf, size,        // HOST -> DEVICE
                            cudaMemcpyHostToDevice)   // the bounce copy
       GDS     : cuFileRead(fh, d_buf, size, 0, 0)   // SSD -> DEVICE
                 // direct DMA, no host copy, no CPU staging
       |
       v
  (5) Bytes now resident in GPU memory -> compute proceeds
       |
       v
  END
^ Fig 6: Read-issue control flow. The fork is at step (4): the baseline
  needs TWO calls (pread + cudaMemcpy); GDS needs ONE (cuFileRead). Step
  (3) is the silent compatibility-mode fallback that erases GDS's gain.

The decisive design point is at step (4): the baseline path is a two-stage transfer (pread then cudaMemcpy), and the second stage — the HostToDevice copy — is precisely the bounce-buffer cost GDS deletes. The single cuFileRead call collapses both stages into one DMA. The silent hazard is step (3): if no GDS-supported filesystem is configured, cuFile drops into compatibility mode and reinstates the bounce buffer without changing the API, so an unaware caller pays the baseline cost while believing it is on the fast path.

4.2 The real-workload data flow (ResNet-50 + DALI)

  +-----------+  ══ raw image files ══>  +------------------------+
  |  NVMe SSD |                          |  Data path (per fig 2) |
  | (PM9A3)   |  ◄══ (no return) ════════|  GDS: direct to GPU    |
  +-----------+                          |  POSIX: via CPU bounce |
                                         +-----------+------------+
                                                     ║
                                          ══ decoded minibatch ══>
                                                     ║
                                                     v
  +----------------------------------------------------------------+
  | GPU (V100S) -- DALI pipeline                                   |
  |   decompress -> random crop -> resize -> flip  (PREPROCESS)    |
  |        |                                                        |
  |        v                                                        |
  |   ResNet-50 forward + backward (COMPUTE on current minibatch)  |
  |        |    (overlaps with prefetch of NEXT minibatch)         |
  |        v                                                        |
  |   gradient -> weight update                                    |
  +----------------------------------------------------------------+
^ Fig 7: Real-workload data flow. With GDS the storage->GPU edge is a
  direct DMA, and DALI does decompress/crop/resize ON the GPU, so the
  CPU is freed from both the staging copy and the preprocessing.

The pipeline matters to the result: a deep-learning training step prefetches and decodes the next minibatch while the GPU computes on the current one. Because DALI performs decompression, random cropping, resizing, and flipping on the GPU, the GDS path lets the entire ingest+preprocess chain avoid the CPU — which is why CPU-utilization savings are large even when training-throughput gains are modest (the GPU is the bottleneck once data arrives, so freeing the CPU does not always speed the step, but it always frees cores).


5. Quantitative Results — Empirical Findings by Regime

5.1 Local DAS — synthetic random read (Fig. 4 a-d)

Metric Regime GDS vs baseline (CPU-GPU)
Throughput medium-high (64K+) GDS higher; ~3.3 GiB/s plateau
Throughput overall headline ~14% increase
IOPS 4K small read ~800K (GDS) vs ~580K (baseline)
IOPS out-of-box SSD ~800K (near 900K theoretical)
IOPS trend vs size decreases as I/O size grows
Latency <=32K small block similar GDS vs baseline
Latency >=128K large block GDS much lower (baseline ~1.6e4 us
at 128-256K; GDS roughly half)
Latency overall headline ~12% decrease
Latency (16K+) GDS vs baseline over ~1000 us lower per op
CPU utilization 32K..256K read 18% lower (≈ 2 cores advantage)
CPU utilization overall headline 12% decrease
GPU utilization all batch sizes no noticeable difference

Headline for local single-SSD storage, stated in the conclusion: GDS delivers a 14% throughput increase and 12% latency decrease, with a 12% CPU-utilization reduction concentrated at medium-to-high transfer sizes. The small-read regime (4K-16K) actually shows reduced GDS read rates because querying the CUDA runtime for the GPU memory buffer location (pointer metadata) adds per-op overhead that dominates when each op is tiny; the OS read-ahead/prefetch of the non-O_DIRECT baseline can out-run GDS at the smallest sizes.

5.2 Local DAS — ResNet-50 training (Fig. 4 e-h)

Metric Regime GDS vs baseline
Training throughput batch 4-16 (small) ~1-4% higher
Training throughput batch 32+ (large) ~12-15% higher
CPU cores saved batch 4-32 6-23% lower (≈5-10 cores)
CPU cores saved batch 64+ ≈42% lower (≈15 cores)
IOPS vs batch size decreases as batch grows
GPU utilization all batches no noticeable improvement
Read latency small batches no significant difference

The ResNet result is the clearest demonstration of GDS's primary value: it is a CPU-offload technology more than a speed technology. At batch 64+ GDS reclaims roughly 15 CPU cores (about 42% of the ingest+preprocess CPU work) while only improving training throughput by 12-15%. The GPU is already the bottleneck during compute, so freeing the CPU does not multiply throughput — but it does free ~15 cores for other work, which is the real prize in a shared HPC node.

5.3 Remote Weka — synthetic random read (Fig. 5 a-d)

Metric Regime GDS vs baseline (CPU-GPU)
Throughput overall plateau up to ~11 GiB/s (saturates 100GbE)
Throughput 4K small block ~2.8x advantage for GDS
IOPS 4K, 4-core mount ~740K (GDS) vs much lower baseline
IOPS 4K, 2-core mount ~400K
IOPS 6-core mount ~675K (no gain over 4-core; lower)
Throughput vs cores 2/4/6 cores ~11 GiB/s ceiling (link-bound)
Latency 4K small block lowest 256 us (kernel bypass)
Latency 32K->256K block 88% / 49% lower than baseline
Latency overall headline 45% improvement
CPU utilization 32K..1M (med-high) 18-21% lower (≈2-3x lower)
CPU utilization 32K and higher 2-3x lower with GDS

The remote result is much stronger than the local result because the baseline path on a Weka client is doubly CPU-bound: WekaFS already runs a DPDK kernel-bypass data plane that consumes CPU cores, and the bounce buffer adds a second CPU burden on top. GDS removes the bounce copy, so the freed cores can feed the DPDK data plane — and the small-block throughput jumps 2.8x while overall latency improves 45%. The remarkable 256 us floor latency at 4K is attributed to WekaFS bypassing the kernel entirely (DPDK + user-space FS in a Linux container).

A key scaling observation: WekaFS nearly saturates the 100 GbE link at ~11 GiB/s with as few as 2, 4, or 6 client cores, and IOPS jumps from ~400K (2 cores) to ~740K (4 cores) but does not improve at 6 cores — i.e. 4 cores is the sweet spot, and adding more cores or more GPUs does not help random read (it is link-bound, not core-bound, past 4 cores).

5.4 Remote Weka — ResNet-50 training (Fig. 5 e-h)

Metric Regime GDS vs baseline
Training throughput batch 4-16 (small) ~2-8% higher
Training throughput batch 32+ (large) ~12-15% higher
CPU cores saved batch 4-32 ≈5-10 cores lower
CPU cores saved batch 64+ ≈15 cores lower
CPU utilization batch 32+ 39-45% reduction
GPU utilization all batches comparable to baseline

The remote ResNet run uses 3 GPUs (not all 8), because the authors found that adding more GPUs did not improve random-read throughput on the Weka backend — the storage/network is the bottleneck, so 3 GPUs already achieve the filesystem's maximum. Training-throughput gains mirror the local case (2-8% small batch, 12-15% large batch), but CPU savings are larger (39-45% at batch 32+) for the same reason as §5.3: the Weka client was more CPU-bound to begin with.

5.5 Headline numbers, consolidated

  +-----------------------------------------------------------+
  | LOCAL single NVMe (PM9A3, EXT4):                          |
  |   throughput  : +14%                                       |
  |   latency     : -12%                                       |
  |   CPU util    : -12% (-18% to -21% at 32K..1M)            |
  |                                                            |
  | REMOTE WekaFS (6 nodes, 24 PM9A3, 100 GbE):              |
  |   IOPS (4K)   : 740K to a single client (4 cores)         |
  |   throughput  : 11 GiB/s (link-saturating)                |
  |   small-block : 2.8x throughput advantage (4K)            |
  |   latency     : -45% overall; -88%/-49% at 32K/256K       |
  |   CPU util    : -18% to -21% (2-3x lower) at 32K+         |
  |                                                            |
  | ResNet-50 (both backends):                                |
  |   throughput  : +2-8% (small batch), +12-15% (batch 32+)  |
  |   CPU cores   : -5..10 (batch 4-32), -15 (batch 64+)      |
  |   GPU util    : comparable (no change)                    |
  +-----------------------------------------------------------+
^ Fig 8: Consolidated headline numbers. GDS's gain is small-to-moderate
  on local DAS and large on remote Weka, and is dominated by CPU/latency
  savings rather than raw throughput -- larger for bigger transfers.

6. Configuration-Regime Trade-off Tables

6.1 Data path (GDS vs traditional bounce buffer)

Dimension GDS (cufile) Baseline (posix) Winner
Small read (<=16K) latency Pointer-lookup tax OS read-ahead helps Baseline (marginal)
Large read (>=32K) latency Much lower Grows with size GDS
CPU utilization Frees cores Burns cores on copy GDS
Throughput (local) +14% Baseline GDS
Throughput (remote 4K) 2.8x Baseline GDS
GPU utilization No change No change Tie
Fallback safety Silent compat mode Always works Baseline
Page-cache / read-ahead Disabled (O_DIRECT) Enabled Baseline (small)

For data-intensive GPU workloads, prefer GDS once transfers reach ~32K and above — below that the CUDA pointer-lookup overhead and the loss of OS read-ahead can erase or invert the benefit, while above it the elimination of the bounce copy wins decisively on both latency and CPU.

6.2 Storage backend (local DAS vs remote Weka)

Dimension Local DAS (EXT4) Remote Weka (100 GbE) Winner (for GDS)
GDS throughput gain +14% up to 2.8x (4K) Remote
GDS latency gain -12% -45% Remote
GDS CPU savings -12% to -18% -18% to -45% Remote
Absolute throughput ~3.3 GiB/s (1 SSD) ~11 GiB/s (cluster) Remote
Absolute 4K latency small-block bound 256 us (kernel bypass) Remote
Kernel patching needed nvidia-fs.ko in VFS None (user-space FS) Remote
Scaling with cores/GPUs n/a flat past 4 cores/3 GPU neither (link-bound)

The GDS benefit is larger on the remote Weka backend because the baseline there was more CPU-bound (DPDK data plane + bounce buffer), so removing the bounce copy frees more cores; and because WekaFS's user-space, kernel-bypass design pairs naturally with GDS's direct DMA.

6.3 Transfer/batch size regime

Dimension Small (4K-16K / BS 4-16) Large (32K-256K / BS 32+) Winner
GDS throughput gain marginal / can invert strong Large
GDS latency gain small large (-88% at 32K Weka) Large
GDS CPU savings 6-23% (5-10 cores) 42% / 39-45% (15 cores) Large
Dominant latency cost data-access overhead data-transfer time --
Read-ahead help (base) helps baseline negligible small favors base

GDS pays off most at large transfer / large batch sizes, where the per-op fixed costs (pointer lookup, access overhead) are amortized over more bytes, and where the bounce copy would have consumed the most CPU and DRAM bandwidth. At the smallest sizes the latency is dominated by access overhead rather than transfer, so GDS's transfer-path advantage is muted.


7. Bottlenecks & Insights Surfaced by the Measurements

7.1 The bounce buffer is a CPU-and-DRAM tax, not a throughput tax

The most important insight is that GDS's headline benefit is CPU and latency, not raw bandwidth. On local DAS, throughput rose only 14% but CPU fell 12% (and up to 42% on ResNet at batch 64+). The bounce buffer's real cost is the kernel cycles and DRAM bandwidth spent staging the copy — freeing those frees ~15 cores at large batch, which matters far more in a shared node than a 14% throughput bump.

7.2 The small-I/O inversion (the pointer-lookup tax)

At 4K-16K reads on local DAS, GDS is slower than the baseline, because each cuFileRead must query the CUDA runtime for the GPU buffer's memory location (pointer metadata) and because O_DIRECT forfeits the OS read-ahead/page-cache that helps small sequential-ish reads. This is a genuine crossover: the GDS fast path has a fixed per-op cost that only amortizes once the transfer is large enough. Below the crossover, the "slow" buffered path wins.

On Weka, throughput plateaus at ~11 GiB/s — the 100 GbE link limit — with as few as 4 client cores, and IOPS does not improve at 6 cores or with more than 3 GPUs. The bottleneck shifts from CPU (which GDS relieves) to the network fabric. This means GDS's value on remote storage is bounded by link bandwidth: it gets you to the link ceiling with fewer cores, but it cannot exceed the ceiling.

7.4 WekaFS's kernel-bypass design is a GDS multiplier

The 256 us floor latency at 4K and the 2-3x CPU savings on Weka are attributed to WekaFS running its data plane in a user-space Linux container with DPDK kernel-bypass networking. Because Weka already avoids kernel-specific dependencies and time-sharing, GDS's user-space direct path composes cleanly with it (no nvidia-fs/VFS patching). The lesson: GDS's benefit is amplified when the surrounding storage stack is also kernel-bypass, because the two technologies remove overlapping CPU/kernel overheads.

7.5 Latency composition flips with transfer size

The measurements reveal that at small block sizes latency is dominated by data-access overhead (metadata, queue, runtime lookup), while beyond 32K it is dominated by data-transfer time (moving the bytes). GDS attacks the transfer component, so its latency advantage grows with size — at 256K remote, latency is 49% lower; at 32K, 88% lower. Below 32K the access component, which GDS does not reduce, dominates.

7.6 The silent compatibility-mode hazard

cuFile's compatibility mode reinstates CPU bounce buffers without changing the API when no GDS-supported filesystem is present. A deployment that believes it is on the GDS path but lacks nvidia-fs or a GDS-aware FS will silently pay the full baseline cost. This is an operational bottleneck of measurement, not of hardware: confirming direct=1 actually took effect (the paper's "verify if set" note) is required to trust any GDS result.


8. Limitations of the Methodology

Limitation Implication
Read path only (no write GDS results) Write-path GDS gains uncharacterized; writes used only
for SSD preconditioning
One real model (ResNet-50 v1.5) No transformer / LLM / other-CNN ingest profile;
future work names "different MLPerf models"
Synthetic = 100% random read only No sequential, no mixed R/W, no realistic access mix
Queue depth not swept No QD sensitivity surface for the synthetic engine
GDSIO vs DALI front-ends "synthetic vs real" conflated with "tool vs tool"
One SSD model (PM9A3) / one GPU (V100S) No PCIe Gen3 vs Gen4, no A100/H100, no NVLink path
Remote = WekaFS only Lustre / NFS / S3 referenced as future work, unmeasured
No error bars / variance reported Cannot estimate measurement noise floor
GPU count not held constant 1 GPU synthetic, 8 local / 3 remote ResNet -- the
"3 GPU" choice is itself a bottleneck-driven artifact
Compatibility-mode penalty not measured Fallback cost (silent bounce buffer) quantified only
implicitly via the baseline arm
Steady-state-only SSD No FOB or write-cliff transient behavior reported
2022 stack (CUDA 11.4, WekaFS 3.12) Newer GDS / cuFile batched-I/O APIs not exercised

The most consequential limitation is the read-only, single-model, single-GPU-class scope: the paper establishes a clean baseline for GDS random-read on PM9A3 + V100S, but it does not yet map how the benefit shifts across PCIe generations, GPU classes, write workloads, or other distributed filesystems — all of which the authors flag as future work.


9. Note on NCCL Tuning

GDS and a collective-communication library tune different segments of the same byte journey, and the paper's central finding — that the win is a CPU/latency win that grows with transfer size — rhymes with the small-vs-large message regimes that govern protocol selection in collective libraries. GDS removes the storage-to-GPU staging copy; a collective library removes (or amortizes) the GPU-to-GPU staging copy, and both see their fixed per-operation overhead dominate at small sizes and vanish at large ones. The paper's crossover — buffered/read-ahead wins below ~16K, direct DMA wins above ~32K — is the same shape as a low-latency-vs-bandwidth protocol crossover, and the same diagnostic (split CPU USR/SYS cores to attribute a copy's cost) would let a tuner decide when bypassing a host-staging step is worth its fixed setup cost.


10. Analogy

GDS is a freight rail spur that runs straight from the dock to the factory floor, replacing a depot where every container had to be unloaded onto trucks and re-loaded. In the old arrangement (the bounce-buffer baseline), goods arriving at the port (the NVMe SSD) are trucked to a central warehouse (CPU/host DRAM), unloaded, then re-loaded onto a second truck bound for the factory (the GPU) — two handlings, two crews, and a warehouse full of idling forklifts (CPU cores spent on cudaMemcpy). GDS lays a dedicated rail spur so a container rolls from dock to factory in one move, and the warehouse crew (the CPU) is freed for other work. The catch the paper measures precisely: for a single small parcel the rail spur is slower, because hooking up the locomotive and clearing the track (querying the CUDA runtime for the buffer address, forfeiting the OS read-ahead) costs more than the trucks would have — so the spur only pays off once you are shipping full containers (32K+ transfers, large batches). And on the long-haul remote route (the Weka cluster across 100 GbE), the spur helps most of all, because that route's trucks were already stuck in traffic (the DPDK data plane burning CPU cores) — but no matter how good the spur, you can never move freight faster than the single track allows (the 11 GiB/s link ceiling that 4 cores already saturate). The warehouse, not the spur, was the hidden cost; GDS bills it back to the workload.