11 min read

The Neighbors are Loud: Solving Memory Fragmentation and NVLink Contention in Multi-Tenant GPU Clusters

Solving Memory and NVLink Contention in Multi-Tenant GPU Clusters

The Neighbors are Loud: Solving Memory Fragmentation and NVLink Contention in Multi-Tenant GPU Clusters

The modern AI gold rush isn’t happening in the mines; it’s happening in the data centers. But here is the secret that nobody tells you when you’re writing a $100 million check for H100 clusters: Scaling from one GPU to ten thousand isn’t a linear upgrade—it’s a phase transition into chaos.

When you are training a massive Large Language Model (LLM) across a multi-tenant cluster, you aren’t just fighting physics; you are fighting your neighbors. In a shared environment, “fragmentation” and “contention” are the silent killers of throughput. You might have the theoretical FLOPS of a small nation, but if your NVLink fabric is congested or your memory is fragmented, your expensive silicon is mostly sitting around waiting for a bus that’s stuck in traffic.

Today, we’re going deep into the guts of the stack. We’re moving past the “Hello World” of PyTorch and into the architectural trenches where memory allocators, topology-aware schedulers, and collective communication libraries (NCCL) collide.


The Hype vs. The Hard Truth: The Compute Arms Race

If you’ve been following the news, you’ve seen the headlines about NVIDIA’s Blackwell (B200) architecture, the staggering demand for InfiniBand switches, and the birth of “Giga-clusters.” The hype suggests that if you throw enough silicon at the problem, GPT-5 (or its equivalents) will simply emerge.

The technical substance, however, is much grittier. We are currently moving away from “Siloed Training” (where one team owns a physical rack) toward “Dynamic Multi-Tenancy.” Why? Because at $30,000+ per GPU, leaving a cluster idle for even ten minutes while a researcher tweaks a learning rate is an architectural sin.

But multi-tenancy in LLM training isn’t like multi-tenancy in a standard web app. In a web app, if your neighbor gets a traffic spike, maybe your API latency goes up by 10ms. In LLM training, if your neighbor saturates the NVLink switch during a critical All-Reduce step, your entire training job—spanning 512 GPUs—stalls. You’re paying for 512 H100s to do absolutely nothing.


Part I: The Ghost in the Machine – Solving GPU Memory Fragmentation

In massive training pipelines, RuntimeError: CUDA out of memory is the bane of every engineer’s existence. But the frustrating part isn’t that you’ve run out of total VRAM; it’s that you have 4GB of free space, but it’s scattered in tiny, useless chunks.

The Anatomy of the Leak

When training an LLM with billions of parameters, your memory is occupied by four main residents:

  1. Model Weights: Static, but massive.
  2. Optimizer States: Often 2x to 3x the size of the weights (hello, Adam optimizer).
  3. Gradients: The delta changes.
  4. Activations: The dynamic data generated during the forward pass.

In a multi-tenant environment, the Caching Allocator in PyTorch or JAX is your best friend and your worst enemy. To avoid the overhead of calling the expensive cudaMalloc (which requires a host-to-device synchronization), frameworks pre-allocate large “slabs” of memory.

The problem? Over time, as different sized tensors (activations for variable sequence lengths, temporary buffers for communication) are allocated and freed, the “free” space becomes a Swiss cheese of holes.

Architectural Solution: Paged Memory and Virtual Address Management

To solve this at the cluster level, we have to move toward Virtual Memory Abstractions for GPUs, similar to how we handled RAM in the 90s.

Instead of relying on the default allocator, high-performance teams are now implementing Custom Segmented Allocators or leveraging CUDA 11.x/12.x Virtual Memory Management (VMM).

With VMM, we can map a contiguous virtual address space to non-contiguous physical memory chunks on the GPU. This allows the training process to “see” a single 80GB block, even if that block is physically scattered across the HBM (High Bandwidth Memory).

The Pro-Tip: If you are building a multi-tenant platform, you should expose cudaMallocAsync to your users. Unlike the traditional synchronous call, cudaMallocAsync uses a stream-ordered pool, allowing the driver to overlap memory deallocation with kernel execution, significantly reducing “bubbles” in the pipeline.

// Example of Stream-Ordered Memory Allocation
cudaStream_t stream;
cudaStreamCreate(&stream);

float* d_data;
size_t size = 1024 * sizeof(float);

// Allocating memory that is only valid within this stream
cudaMallocAsync(&d_data, size, stream);

// ... perform computation ...

// Freeing memory becomes an async operation on the timeline
cudaFreeAsync(d_data, stream);

If memory is the engine, NVLink is the transmission. In an LLM training pipeline, communication is not an “extra”—it is the core of the work.

When you use Data Parallelism (DP), Tensor Parallelism (TP), or Pipeline Parallelism (PP), your GPUs are constantly talking. Specifically, they use “Collective Communications”:

  • All-Reduce: Summing gradients across all GPUs.
  • All-Gather: Sharing updated weights.
  • Reduce-Scatter: Distributing the load of the optimizer.

The Multi-Tenant Nightmare

Imagine Job A (a 175B parameter LLM) and Job B (a 7B parameter fine-tuning task) are running on the same physical node or the same rack. NVLink provides a massive 900 GB/s of bandwidth between GPUs in an H100 node. However, if Job A starts a massive All-Reduce across its 8 GPUs, it consumes the internal crossbar bandwidth. If Job B tries to move data at the same time, the contention results in Tail Latency Spikes.

In synchronized training (like Distributed Data Parallel), the slowest GPU dictates the speed of the entire cluster. If Job B causes a 50ms delay on Job A’s Node #42, the other 500 GPUs in Job A stop and wait.

Solving Contention: Topology-Aware Scheduling (TAS)

We solve this by making our orchestrator (usually Kubernetes with a custom scheduler like Volcano or Yunikorn) “Topology Aware.”

Instead of treating GPUs as a pool of identical resources, the scheduler must understand the L1, L2, and L3 hierarchies of the network:

  1. L1: NVLink Mesh. GPUs inside a single chassis.
  2. L2: Leaf Switch. GPUs connected to the same Top-of-Rack (ToR) InfiniBand switch.
  3. L3: Spine/Core. The backbone connecting different racks.

The Strategy: “Rail-Local” Routing. Modern cluster architectures (like the NVIDIA DGX SuperPOD) use a “Rail-Optimized” topology. This means that GPU #1 in every rack is connected to the same InfiniBand leaf switch.

When we schedule a multi-tenant job, we don’t just find “any 8 GPUs.” We find 8 GPUs that share the highest degree of NVLink connectivity. If a job requires 16 GPUs, we ensure they are on two nodes that share the same leaf switch.

NCCL Tuning: The Secret Sauce

The NVIDIA Collective Communications Library (NCCL) is what actually manages the bits moving across the wire. In a multi-tenant cluster, the default settings are rarely optimal.

To prevent one tenant from hogging the bandwidth, we implement Adaptive Routing and Sharp (Scalable Hierarchical Aggregation and Reduction Protocol). SHARP offloads the math (like summing gradients) to the switch itself, reducing the amount of data that needs to fly back and forth across the NVLink.

# Crucial NCCL environment variables for multi-tenant isolation
export NCCL_IB_GID_INDEX=3
export NCCL_IB_TC=106
export NCCL_IB_HCA=mlx5_0,mlx5_1,mlx5_2,mlx5_3
export NCCL_ALGO=Ring # Or Tree, depending on the job size

Part III: The “Noisy Neighbor” and the Death of Determinism

In scientific computing, we love determinism. If I run a training job twice, I want the same loss curve. But in a multi-tenant GPU cluster, the “Noisy Neighbor” effect introduces non-deterministic jitter.

The PCIe Bottleneck

While NVLink handles GPU-to-GPU, the PCIe Gen5 bus handles GPU-to-CPU and GPU-to-NIC. If a tenant on your node is doing heavy I/O (like loading a massive dataset from a distributed file system like Lustre or Weka into CPU RAM), they can saturate the PCIe lanes.

This causes a delay in “feeding” the GPU. If the GPU finishes its kernel and the next batch of data isn’t ready because the PCIe bus was busy, you get a “Starvation Bubble.”

The Solution: GPUDirect RDMA and Storage Isolation

To bypass the CPU and the noisy PCIe paths entirely, we use GPUDirect RDMA (Remote Direct Memory Access). This allows the InfiniBand NIC to write data directly into the GPU memory of another node, skipping the CPU and the OS kernel of the “noisy neighbor.”

In a multi-tenant architecture, you must partition your NICs. For an 8-GPU H100 node, you ideally have 8 NICs (one per GPU). By mapping one physical NIC to one GPU and one specific “Tenant Slice,” you create a “Hardware-Level Sandbox.”


Part IV: Advanced Memory Management – Beyond the Basics

To truly solve memory fragmentation in a multi-tenant LLM environment, you have to look at how we store KV Caches (for inference) and Activation Buffers (for training).

PagedAttention for Training?

You may have heard of vLLM and its PagedAttention algorithm. It revolutionized inference by treating GPU memory like a virtual operating system treats RAM—partitioning it into “Pages” to avoid fragmentation.

We are now seeing the rise of “Paged-Aware Training.” By implementing a block-manager at the framework level, we can allow the activation tensors of a 100B parameter model to be stored in non-contiguous memory blocks.

This is revolutionary for multi-tenancy. Why? Because it allows the orchestrator to live-resize a job’s memory footprint. If Tenant A isn’t using their full 80GB, the system can “reclaim” those pages for Tenant B’s gradient accumulation without crashing Tenant A.

Unified Memory and Address Space Isolation

NVIDIA’s Unified Memory (UM) has historically been slow because of page faults. However, with the H100 and its Hardware Page Forecasting, UM is becoming viable for massive models.

In a multi-tenant cluster, UM allows us to “over-subscribe” GPU memory. We can run a job that technically requires 100GB on an 80GB card by transparently swapping cold weights to the CPU memory (DDR5) and back. The trick is to ensure that the NVLink-C2C (Chip-to-Chip) interconnect handles this swapping so fast that the GPU compute kernels never stall.


Part V: Building the “Gold Standard” Multi-Tenant GPU Stack

If you were to build this today, what does the architecture actually look like? Here is the blueprint for a high-performance, contention-resistant LLM pipeline:

1. The Compute Layer: GPU Slicing

Use MIG (Multi-Instance GPU) for small jobs (fine-tuning, inference) to provide hardware-level isolation of the HBM and the SMs (Streaming Multiprocessors). For massive LLM training, use Full-GPU Passthrough but coupled with Docker Container Runtimes that enforce strict memory limits.

2. The Network Layer: Rail-Optimized Fat Tree

Organize your InfiniBand fabric in a Fat Tree topology. Ensure that your scheduler understands “Distance Metrics.”

  • Distance 0: Same GPU.
  • Distance 1: Same NVLink Switch.
  • Distance 2: Same ToR Switch.
  • Distance 3: Different Racks.

3. The Software Layer: NCCL + Enforce Quotas

Implement custom NCCL plugins that monitor for congestion. If Job A is causing packet drops on the switch, the cluster manager should “throttle” Job A’s communication priority using Quality of Service (QoS) bits in the InfiniBand headers.

4. The Monitoring Layer: DCGM and Telemetry

You can’t fix what you can’t see. Use NVIDIA DCGM (Data Center GPU Manager) to export real-time metrics into a high-resolution time-series database (like VictoriaMetrics or Prometheus). Look specifically for:

  • DCGM_FI_DEV_MEM_COPY_UTIL: Is the memory controller saturated?
  • DCGM_FI_DEV_NVLINK_BANDWIDTH_TOTAL: Is the NVLink fabric hitting its limit?
  • DCGM_FI_DEV_GPU_UTIL: Are we seeing “bubbles”?

The Engineering Curiosity: The “Zero-Bubble” Pipeline

One of the most exciting recent developments in solving contention is the Zero-Bubble Pipeline Parallelism.

In traditional pipeline parallelism, you divide the model layers across GPUs. GPU 1 does the first layers, passes to GPU 2, and so on. This creates a “Pipeline Bubble” where GPU 1 sits idle while GPU 8 finishes the backward pass.

In a multi-tenant environment, these bubbles are even more dangerous because the scheduler might try to “fill” them with other tasks, which then creates contention when the original job resumes.

The “Zero-Bubble” approach uses clever scheduling of forward and backward passes to ensure that every GPU is constantly working. When combined with Communication-Computation Overlap (using the GPU’s DMA engines to move data while the Tensor Cores are crunching numbers), you can reach nearly 90% MFU (Model Flops Utilization).


Bringing it All Together

Architecting a multi-tenant GPU cluster for massive LLMs is not about the GPUs themselves—it’s about the space between them.

To solve memory fragmentation, we must stop thinking of VRAM as a bucket and start thinking of it as a virtualized, paged resource. To solve NVLink contention, we must stop thinking of the network as a transparent pipe and start treating it as a finite, topology-dependent highway.

The teams that win the LLM race won’t just be the ones with the most H100s; they will be the ones who can run their clusters at 95% efficiency while their neighbors are stuck at 40% because of a “noisy neighbor” two racks over.

Scaling is a science of bottlenecks. And in the era of giant models, the biggest bottleneck is no longer the speed of the chip—it’s the architecture of the system.

Keep your memory contiguous, your NVLink paths isolated, and your NCCL buffers tuned. The future of AI is being built in the microseconds of latency we shave off today.


More to explore

Keep diving in