GPU Memory Pool
GPU Memory Pool Allocators are the caching memory management systems that maintain pre-allocated pools of GPU memory to eliminate the overhead of frequent cudaMalloc/cudaFree calls — reducing allocation latency from milliseconds to microseconds, preventing memory fragmentation, and enabling the rapid tensor allocation/deallocation patterns required by deep learning frameworks.
The Problem with Raw CUDA Allocation
- `cudaMalloc()`: ~1-10 ms per call — extremely slow (requires GPU driver interaction, page table updates).
- Deep learning: Each training iteration allocates/frees hundreds of tensors.
- Without pooling: 200 allocations × 5 ms = 1 second of pure allocation overhead per iteration.
- With pooling: 200 allocations × 5 μs = 1 ms — 1000x faster.
How Caching Allocators Work
1. First allocation: Pool calls `cudaMalloc` for a large block (e.g., 2GB).
2. User requests 256MB: Pool carves out 256MB from the large block — returns pointer.
3. User frees 256MB: Pool marks the segment as available — does NOT call `cudaFree`.
4. Next 256MB request: Pool reuses the freed segment — zero allocation overhead.
5. Pool grows: If existing blocks are insufficient, allocate another large block.
PyTorch CUDA Caching Allocator
- Default allocator for all PyTorch GPU tensors.
- Maintains separate pools for small (< 1MB) and large (≥ 1MB) allocations.
- Uses best-fit strategy with block splitting to minimize fragmentation.
- `torch.cuda.memory_summary()`: Shows allocated, reserved, and fragmented memory.
- `torch.cuda.empty_cache()`: Returns unused cached blocks to CUDA (but doesn't help with fragmentation).
Memory Fragmentation
- Even with pooling, fragmentation occurs: Many small free blocks but no contiguous space for a large allocation.
- Example: 8GB reserved, 2GB in use, but largest free block is only 500MB → cannot allocate 1GB tensor.
- Mitigation: PyTorch 2.x uses `expandable_segments` configuration to reduce OS-level fragmentation.
CUDA Memory Pool API (CUDA 11.2+)
- `cudaMemPool_t`: Native CUDA memory pool support.
- `cudaMallocAsync()` / `cudaFreeAsync()`: Stream-ordered allocation — allocation tied to CUDA stream.
- Benefit: GPU hardware manages allocation ordering — further reduces synchronization overhead.
Memory Management Best Practices
- Pre-allocate: Allocate maximum-size tensors once at startup, reuse buffers.
- Gradient accumulation: Process smaller micro-batches to reduce peak memory.
- Mixed precision: FP16/BF16 tensors use half the memory of FP32.
- Activation checkpointing: Trade compute for memory by recomputing activations during backward.
GPU memory pool allocators are essential infrastructure for all GPU computing frameworks — without them, the rapid tensor allocation patterns of modern deep learning and scientific computing would be throttled by driver-level allocation overhead, making interactive and training workloads impractically slow.