Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

LLM Distributed Training: Parallelism Strategy Guide & NCCL Communication Patterns

A practical guide for selecting the right parallelism strategy for Large Language Model (LLM) training. This project provides hands-on experiments, performance analysis, and decision guidelines to help ML engineers understand when to use Data Parallelism (DDP), Pipeline Parallelism (PP), Tensor Parallelism (TP), or Hybrid approaches based on model size, GPU memory, and infrastructure constraints.

What You'll Learn:

  • How ncclAllReduce, ncclSend, and ncclRecv operations behave across different strategies
  • Trade-offs between throughput, memory efficiency, and communication overhead
  • When to choose DDP vs Pipeline vs Tensor vs Hybrid parallelism
  • How to profile and analyze NCCL communication bottlenecks

Last Updated: January 2026 Infrastructure: Oracle Cloud Infrastructure (OCI) OKE with NVIDIA A10 GPUs


Important Note on Hardware & Expected Results

This guide was developed using NVIDIA A10 GPUs (24GB VRAM, PCIe-connected, no NVLink). The A10 is a workstation/inference-class GPU with limited memory bandwidth and no high-speed GPU-to-GPU interconnect. As a result:

Hardware Aspect A10 (This Guide) Datacenter GPUs (A100/H100/H200/GB200)
GPU Memory 24 GB 40-192 GB
GPU Interconnect PCIe Gen4 NVLink (600-900 GB/s)
Network 10-25 GbE InfiniBand (200-400 Gb/s)
Tensor Parallelism Limited benefit Highly efficient
Pipeline Bubbles More pronounced Better hidden with faster comm

What This Means:

  • Throughput numbers will be significantly higher on production GPU clusters (A100, H100, H200, GB200) due to NVLink enabling near-instantaneous GPU-to-GPU communication
  • Tensor Parallelism scales much better with NVLink—our A10 results underestimate TP benefits on datacenter hardware
  • Pipeline bubble overhead decreases when using InfiniBand and NVLink, as activation transfers complete faster
  • Hybrid parallelism (PP×TP) becomes essential for 100B+ parameter models on high-end clusters

Purpose of This Guide: The goal is not to provide absolute performance numbers, but to demonstrate how to evaluate and compare parallelism strategies before committing to expensive large-scale training runs. Use this methodology to:

  1. Understand NCCL communication patterns for each strategy
  2. Profile and identify bottlenecks in your specific infrastructure
  3. Make informed decisions about parallelism configuration
  4. Validate your distributed training setup before scaling up

Always run similar benchmarks on your target hardware to establish baseline metrics before production training.


Strategy Comparison & Performance Analysis

Throughput by Parallelism Strategy

LLM Training Parallelism Benchmark

Track Strategy GPUs Tokens/sec NCCL Operations Scaling Efficiency
A Single-Node DDP 2 49,290 ncclAllReduce 100% (baseline)
B Multi-Node DDP 4 52,847 ncclAllReduce 54%
C Pipeline PP=2 2 9,665 ncclSend/ncclRecv 20%
D Hybrid PP=2×TP=2 4 10,069 ncclAllReduce + ncclSend/ncclRecv 10%
E Pipeline PP=4 4 8,343 ncclSend/ncclRecv 8%

Training Convergence

Training Convergence

Architecture Overview

Parallelism Architecture


Table of Contents

  1. Understanding NCCL Communication Patterns
  2. Parallelism Strategies Explained
  3. Infrastructure Requirements
  4. Quick Start
  5. Running on OCI Infrastructure
  6. Running On-Premises / Bare Metal
  7. Detailed Benchmark Results
  8. Nsight Systems Profiling
  9. Troubleshooting

Understanding NCCL Communication Patterns

NVIDIA Collective Communications Library (NCCL) is the backbone of distributed GPU training. Understanding its operations is critical for optimizing LLM training performance.

Core NCCL Operations

1. ncclAllReduce - Gradient Synchronization (DDP, TP)

Purpose: Combine gradients from all GPUs and distribute the result back

Operation Flow:
  GPU0: [g0] ─┐
  GPU1: [g1] ─┼──► AllReduce ──► All GPUs get: [g0+g1+g2+g3]
  GPU2: [g2] ─┤
  GPU3: [g3] ─┘

NCCL Log Pattern:
  NCCL INFO Ring 00 : 0 -> 1 -> 2 -> 3 -> 0
  NCCL INFO comm 0x7f... rank 0 nranks 4 cudaDev 0 ... allReduce

When Used:
  - Data Parallelism (DDP): Synchronize gradients after backward pass
  - Tensor Parallelism (TP): Aggregate partial results within layers

Performance Characteristics:

  • Bandwidth-bound: Scales with total gradient size
  • Ring Algorithm: O(2(n-1)/n × data_size) - near-optimal bandwidth utilization
  • Tree Algorithm: Better latency for small messages
  • Best for: High-bandwidth interconnects (NVLink, InfiniBand, high-speed Ethernet)

2. ncclSend / ncclRecv - Point-to-Point Activation Transfer (PP)

Purpose: Transfer activations between pipeline stages

Operation Flow (Forward Pass):
  Stage 0 (GPU0) ──ncclSend──► Stage 1 (GPU1)
       │                            │
   [activations]              [activations]
       │                            │
  Stage 0 (GPU0) ◄──ncclRecv── Stage 1 (GPU1)
                   (Backward Pass)

NCCL Log Pattern:
  NCCL INFO comm 0x7f... rank 0 nranks 4 ... send to 1
  NCCL INFO comm 0x7f... rank 1 nranks 4 ... recv from 0

When Used:
  - Pipeline Parallelism: Transfer activations between stages
  - Async operations (isend/irecv) for overlap with compute

Performance Characteristics:

  • Latency-bound: Each transfer adds latency
  • Sequential dependency: Creates pipeline bubbles
  • Best for: Large models that don't fit in single GPU memory

3. ncclAllGather / ncclReduceScatter - Tensor Parallelism

Purpose: Distribute/collect tensor shards across GPUs

AllGather (Column Parallel → Row Parallel transition):
  GPU0: [A0] ─┐
  GPU1: [A1] ─┼──► AllGather ──► All GPUs get: [A0, A1, A2, A3]
  GPU2: [A2] ─┤
  GPU3: [A3] ─┘

ReduceScatter (Gradient aggregation with distribution):
  GPU0: [g0,g1,g2,g3] ─┐
  GPU1: [g0,g1,g2,g3] ─┼──► ReduceScatter ──► GPU0: [Σg0], GPU1: [Σg1], ...
  GPU2: [g0,g1,g2,g3] ─┤
  GPU3: [g0,g1,g2,g3] ─┘

Communication Pattern Comparison

Operation Use Case Latency Bandwidth Scalability
ncclAllReduce DDP gradients, TP Medium High Excellent
ncclSend/Recv PP activations High Medium Limited by stages
ncclAllGather TP (fwd pass) Medium High Good
ncclReduceScatter TP (bwd pass) Medium High Good

Parallelism Strategies Explained

1. Data Parallelism (DDP) - Tracks A & B

Concept: Replicate the entire model on each GPU, partition the data batch.

┌─────────────────────────────────────────────────────────────┐
│                    DATA PARALLELISM (DDP)                    │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│   GPU 0              GPU 1              GPU 2              │
│  ┌──────────┐       ┌──────────┐       ┌──────────┐        │
│  │Full Model│       │Full Model│       │Full Model│        │
│  │ (copy)   │       │ (copy)   │       │ (copy)   │        │
│  └────┬─────┘       └────┬─────┘       └────┬─────┘        │
│       │                  │                  │               │
│  Data Batch 0       Data Batch 1       Data Batch 2        │
│       │                  │                  │               │
│       └──────────────────┼──────────────────┘               │
│                          │                                  │
│                   ncclAllReduce                             │
│              (Gradient Synchronization)                     │
│                                                              │
└─────────────────────────────────────────────────────────────┘

NCCL Communication:
  - Forward: None (independent computation)
  - Backward: ncclAllReduce after each layer's gradient computation
  - Frequency: O(num_parameters) per step

Benchmark Results (Our Tests):

  • Track A (2 GPU): 49,290 tokens/sec - Baseline performance
  • Track B (4 GPU): 52,847 tokens/sec - 54% scaling efficiency

When to Use DDP:

  • Model fits entirely in GPU memory
  • High-bandwidth interconnect available
  • Want maximum throughput for medium-sized models

2. Pipeline Parallelism (PP) - Tracks C & E

Concept: Split model layers across GPUs, process micro-batches in pipeline fashion.

┌─────────────────────────────────────────────────────────────┐
│                 PIPELINE PARALLELISM (PP=4)                  │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  Stage 0      Stage 1      Stage 2      Stage 3             │
│  (GPU 0)      (GPU 1)      (GPU 2)      (GPU 3)             │
│ ┌────────┐   ┌────────┐   ┌────────┐   ┌────────┐          │
│ │Layers  │──►│Layers  │──►│Layers  │──►│Layers  │          │
│ │ 0-2    │   │ 3-5    │   │ 6-8    │   │ 9-11   │          │
│ └────────┘   └────────┘   └────────┘   └────────┘          │
│      │            │            │            │               │
│   ncclSend ─► ncclRecv    ncclSend ─► ncclRecv             │
│        (activations)           (activations)                │
│                                                              │
│  Timeline (1F1B Schedule with 4 micro-batches):             │
│                                                              │
│  GPU0: [F0][F1][F2][F3][  ][B3][B2][B1][B0]                 │
│  GPU1:    [  ][F0][F1][F2][F3][B3][B2][B1][B0]              │
│  GPU2:       [  ][  ][F0][F1][F2][F3][B3][B2][B1][B0]       │
│  GPU3:          [  ][  ][  ][F0][F1][F2][F3][B3][B2][B1][B0]│
│             ↑                              ↑                 │
│         Bubble                          Bubble               │
│                                                              │
└─────────────────────────────────────────────────────────────┘

NCCL Communication:
  - Forward: ncclSend (stage N) → ncclRecv (stage N+1)
  - Backward: ncclSend (stage N+1) → ncclRecv (stage N)
  - Point-to-point, sequential dependency

Benchmark Results (Our Tests):

  • Track C (PP=2): 9,665 tokens/sec - 70-75% bubble overhead
  • Track E (PP=4): 8,343 tokens/sec - Higher bubble due to more stages

Pipeline Bubble Analysis:

Bubble Fraction = (Idle Time) / (Total Time)

For PP=4 with 4 micro-batches:
  Bubble ≈ (PP_size - 1) / (PP_size + num_microbatches - 1)
  Bubble ≈ (4 - 1) / (4 + 4 - 1) = 3/7 ≈ 43% (theoretical minimum)

Our measured: ~70-75% (includes communication overhead)

When to Use PP:

  • Model too large for single GPU memory
  • Acceptable latency (not real-time inference)
  • Memory-constrained environments

3. Tensor Parallelism (TP)

Concept: Split individual layers (weight matrices) across GPUs.

┌─────────────────────────────────────────────────────────────┐
│                   TENSOR PARALLELISM (TP=2)                  │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  For a Linear Layer: Y = XW + b                             │
│                                                              │
│  Column Parallel (Split W by columns):                      │
│  ┌────────────────────────────────────────────────────┐     │
│  │         X            ×      [W0 | W1]    =   [Y0|Y1]│     │
│  │     (replicated)          (split)         (partial) │     │
│  │                                                      │     │
│  │  GPU 0: Y0 = X × W0                                 │     │
│  │  GPU 1: Y1 = X × W1                                 │     │
│  │                     ↓                                │     │
│  │              ncclAllGather                          │     │
│  │           Y = [Y0, Y1] (complete)                   │     │
│  └────────────────────────────────────────────────────┘     │
│                                                              │
│  Row Parallel (Split W by rows):                            │
│  ┌────────────────────────────────────────────────────┐     │
│  │      [X0]           ×      W0      =      Y0        │     │
│  │      [X1]                  W1             Y1        │     │
│  │   (split)              (split)        (partial)     │     │
│  │                     ↓                                │     │
│  │              ncclAllReduce                          │     │
│  │           Y = Y0 + Y1 (complete)                    │     │
│  └────────────────────────────────────────────────────┘     │
│                                                              │
└─────────────────────────────────────────────────────────────┘

NCCL Communication:
  - Within each transformer block: 2× ncclAllReduce
  - High frequency, small messages
  - Requires high-bandwidth interconnect (NVLink preferred)

When to Use TP:

  • Individual layers too large for GPU memory
  • High-bandwidth intra-node connection (NVLink)
  • Combined with PP for very large models

4. Hybrid Parallelism (PP × TP) - Track D

Concept: Combine Pipeline and Tensor parallelism for optimal scaling.

┌─────────────────────────────────────────────────────────────┐
│              HYBRID PARALLELISM (PP=2 × TP=2)                │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│           Pipeline Stage 0          Pipeline Stage 1        │
│        ┌─────────────────────┐   ┌─────────────────────┐   │
│        │  GPU0 ◄─AllReduce─► GPU1│   │  GPU2 ◄─AllReduce─► GPU3│
│        │ (TP=0)    (TP)    (TP=1)│   │ (TP=0)    (TP)    (TP=1)│
│        │                     │   │                     │   │
│        │   Layers 0-5 split  │   │   Layers 6-11 split │   │
│        │   across TP dim     │   │   across TP dim     │   │
│        └──────────┬──────────┘   └──────────┬──────────┘   │
│                   │                         │               │
│                   └────── Send/Recv ────────┘               │
│                      (Pipeline Activations)                 │
│                                                              │
│  NCCL Communication Pattern:                                │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 1. Forward through Stage 0:                         │   │
│  │    - GPU0/GPU1: ncclAllReduce (TP sync)            │   │
│  │                                                     │   │
│  │ 2. Stage 0 → Stage 1:                              │   │
│  │    - GPU0 → GPU2: ncclSend (activations)           │   │
│  │    - GPU1 → GPU3: ncclSend (activations)           │   │
│  │                                                     │   │
│  │ 3. Forward through Stage 1:                         │   │
│  │    - GPU2/GPU3: ncclAllReduce (TP sync)            │   │
│  │                                                     │   │
│  │ 4. Backward: Reverse pattern                        │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                              │
└─────────────────────────────────────────────────────────────┘

Benchmark Results (Our Tests):

  • Track D (PP=2×TP=2): 10,069 tokens/sec
  • Communication: 45% AllReduce + 55% Send/Recv (from nsys profiling)

When to Use Hybrid:

  • Very large models (100B+ parameters)
  • Need to balance memory across many GPUs
  • Production LLM training (GPT-3, LLaMA, etc.)

Infrastructure Requirements

Hardware Specifications

Component Our Setup Minimum Recommended
GPU Nodes 2× VM.GPU.A10.2 2+ nodes
GPUs/Node 2× NVIDIA A10 (24GB) 2+ GPUs
Total GPUs 4 4+
Interconnect OCI VCN (Ethernet) 10Gbps+
Storage OCI FSS (shared) NFS/shared storage

Software Stack

# Verified versions from our benchmark
PyTorch: 2.2.0
CUDA: 12.1
NCCL: 2.19.3
Python: 3.10
Container: pytorch/pytorch:2.2.0-cuda12.1-cudnn8-devel

Quick Start

1. Clone and Deploy on Kubernetes

# Clone repository
git clone <repo-url>
cd training-parallelism

# Deploy to Kubernetes
kubectl apply -f k8s/namespace.yaml
kubectl apply -f k8s/configmap.yaml
kubectl apply -f k8s/service.yaml
kubectl apply -f k8s/workers.yaml

# Wait for pods
kubectl -n llm-benchmark wait --for=condition=Ready pod/worker-0 --timeout=300s
kubectl -n llm-benchmark wait --for=condition=Ready pod/worker-1 --timeout=300s

2. Run Benchmark Tracks

# Track A: Single-Node DDP (2 GPU)
kubectl -n llm-benchmark exec worker-0 -- bash -c '
  cd /mnt/fss/scripts
  torchrun --nproc_per_node=2 train_ddp.py \
    --max_steps 50 --batch_size 8 --experiment track_a
'

# Track B: Multi-Node DDP (4 GPU)
# Run on both nodes simultaneously
kubectl -n llm-benchmark exec worker-0 -- bash -c '
  cd /mnt/fss/scripts
  torchrun --nnodes=2 --nproc_per_node=2 --node_rank=0 \
    --master_addr=worker-0.llm-benchmark-svc --master_port=29500 \
    train_ddp.py --max_steps 50 --batch_size 8 --experiment track_b
' &
kubectl -n llm-benchmark exec worker-1 -- bash -c '
  cd /mnt/fss/scripts
  torchrun --nnodes=2 --nproc_per_node=2 --node_rank=1 \
    --master_addr=worker-0.llm-benchmark-svc --master_port=29500 \
    train_ddp.py --max_steps 50 --batch_size 8 --experiment track_b
'

# Track C: Pipeline PP=2
kubectl -n llm-benchmark exec worker-0 -- bash -c '
  torchrun --nproc_per_node=2 /mnt/fss/scripts/train_pipeline.py \
    --pipeline_parallel_size 2 --max_steps 50 --experiment track_c
'

# Track D: Hybrid PP=2×TP=2 (4 GPU)
kubectl -n llm-benchmark exec worker-0 -- bash -c '
  torchrun --nproc_per_node=4 /mnt/fss/scripts/train_hybrid.py \
    --pp_size 2 --tp_size 2 --max_steps 50 --experiment track_d
'

# Track E: Pipeline PP=4
kubectl -n llm-benchmark exec worker-0 -- bash -c '
  torchrun --nproc_per_node=4 /mnt/fss/scripts/train_pipeline.py \
    --pipeline_parallel_size 4 --max_steps 50 --experiment track_e
'

3. View Results

# Check results
ls results/
# BENCHMARK_SUMMARY.md
# track_a_single_node_ddp/
# track_b_multi_node_ddp/
# track_c_pipeline_pp2/
# track_d_pp2_tp2/
# track_e_pp4/
# nsys_profiles/
# LLM_Training_Parallelism_Benchmark.png
# LLM_Training_Convergence.png
# LLM_Parallelism_Architecture.png

Running on OCI Infrastructure

OCI GPU Instance Types

Shape GPUs GPU Memory Interconnect Best For
VM.GPU.A10.1 1× A10 24 GB - Development
VM.GPU.A10.2 2× A10 48 GB PCIe Small models
BM.GPU.A10.4 4× A10 96 GB PCIe Medium models
BM.GPU4.8 8× A100 320 GB NVLink Large models
BM.GPU.H100.8 8× H100 640 GB NVLink + NVSwitch Very large models

OCI OKE Setup

# 1. Create OKE Cluster with GPU Node Pool
oci ce cluster create \
  --compartment-id <compartment-ocid> \
  --name llm-benchmark-cluster \
  --kubernetes-version v1.28.2 \
  --vcn-id <vcn-ocid> \
  --service-lb-subnet-ids '["<subnet-ocid>"]'

# 2. Create GPU Node Pool
oci ce node-pool create \
  --cluster-id <cluster-ocid> \
  --compartment-id <compartment-ocid> \
  --name gpu-workers \
  --node-shape VM.GPU.A10.2 \
  --size 2 \
  --kubernetes-version v1.28.2 \
  --node-image-id <gpu-image-ocid> \
  --placement-configs '[{"availabilityDomain":"AD-1","subnetId":"<subnet-ocid>"}]'

# 3. Configure kubectl
oci ce cluster create-kubeconfig \
  --cluster-id <cluster-ocid> \
  --file ~/.kube/config \
  --region <region>

# 4. Install NVIDIA Device Plugin
kubectl apply -f https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/v0.14.0/nvidia-device-plugin.yml

# 5. Verify GPU availability
kubectl get nodes -o custom-columns=NAME:.metadata.name,GPU:.status.allocatable.nvidia\\.com/gpu

OCI File Storage Service (FSS) Setup

# Create FSS for shared storage across nodes
# 1. Create File System in OCI Console or CLI
oci fs file-system create \
  --compartment-id <compartment-ocid> \
  --availability-domain <AD> \
  --display-name llm-training-fss

# 2. Create Mount Target
oci fs mount-target create \
  --compartment-id <compartment-ocid> \
  --availability-domain <AD> \
  --subnet-id <subnet-ocid> \
  --display-name llm-mount-target

# 3. Mount on worker nodes (in pod spec or node setup)
# Mount path: /mnt/coecommonfss/llmcore/training-parallelism

OCI-Specific NCCL Configuration

# ═══════════════════════════════════════════════════════════════
# OCI NCCL SETTINGS
# ═══════════════════════════════════════════════════════════════

# Network Interface (check with: ip addr show)
export NCCL_SOCKET_IFNAME=ens3        # Primary NIC on OCI

# Disable InfiniBand for standard shapes
export NCCL_IB_DISABLE=1

# For RDMA-enabled shapes (BM.GPU4.8, BM.GPU.H100.8)
# export NCCL_IB_DISABLE=0
# export NCCL_IB_HCA=mlx5_0
# export NCCL_NET_GDR_LEVEL=2

# Debugging and logging
export NCCL_DEBUG=INFO
export NCCL_DEBUG_SUBSYS=INIT,COLL,P2P
export NCCL_DEBUG_FILE=/mnt/fss/logs/nccl_%h_%p.log

# Timeout for large models
export NCCL_TIMEOUT=1800
export TORCH_NCCL_ASYNC_ERROR_HANDLING=1

# Buffer size optimization
export NCCL_BUFFSIZE=16777216  # 16MB

OCI Networking Best Practices

# 1. Use dedicated subnet for GPU nodes
#    - Isolate training traffic
#    - Configure security lists for NCCL ports (29500-29600)

# 2. Security List Rules for NCCL
#    Ingress: Allow TCP/UDP 29500-29600 from GPU subnet CIDR
#    Egress: Allow all to GPU subnet CIDR

# 3. Check connectivity between nodes
kubectl exec -n llm-benchmark worker-0 -- ping -c 3 worker-1.llm-benchmark-svc

# 4. Verify NCCL can establish connections
kubectl exec -n llm-benchmark worker-0 -- bash -c '
  export NCCL_DEBUG=INFO
  python -c "import torch.distributed as dist; dist.init_process_group(backend=\"nccl\")"
'

NCCL Configuration Reference

# ═══════════════════════════════════════════════════════════════
# UNIVERSAL NCCL SETTINGS (Copy to all environments)
# ═══════════════════════════════════════════════════════════════

# Debugging (enable during benchmarks)
export NCCL_DEBUG=INFO
export NCCL_DEBUG_SUBSYS=INIT,COLL,P2P
export NCCL_DEBUG_FILE=/logs/nccl_%h_%p.log

# Timeout (increase for large models)
export NCCL_TIMEOUT=1800
export TORCH_NCCL_ASYNC_ERROR_HANDLING=1

# Algorithm selection
export NCCL_ALGO=Ring          # Ring, Tree, CollnetDirect
export NCCL_PROTO=Simple       # Simple, LL, LL128

# Buffer size (default 4MB, increase for large tensors)
export NCCL_BUFFSIZE=16777216  # 16MB

# ═══════════════════════════════════════════════════════════════
# NETWORK-SPECIFIC SETTINGS
# ═══════════════════════════════════════════════════════════════

# For Ethernet (most cloud providers)
export NCCL_SOCKET_IFNAME=eth0  # or ens3, ens5
export NCCL_IB_DISABLE=1

# For InfiniBand/RoCE
export NCCL_IB_DISABLE=0
export NCCL_IB_HCA=mlx5_0
export NCCL_IB_GID_INDEX=3
export NCCL_NET_GDR_LEVEL=2

# For NVLink (intra-node)
export NCCL_P2P_LEVEL=NVL
export NCCL_SHM_DISABLE=0

Running On-Premises / Bare Metal

Hardware Requirements

Component Minimum Recommended
GPU Servers 2 nodes 4+ nodes
GPUs per Node 2× NVIDIA (16GB+) 8× A100/H100
Network 10GbE Ethernet 100GbE or InfiniBand
Storage NFS shared mount High-speed NFS or Lustre
RAM 128 GB 256+ GB

Multi-Node Setup Without Kubernetes

# ═══════════════════════════════════════════════════════════════
# ON-PREMISES MULTI-NODE TRAINING SETUP
# ═══════════════════════════════════════════════════════════════

# 1. Set up shared storage (NFS example)
# On NFS server:
sudo mkdir -p /export/llm-training
sudo chown -R nobody:nogroup /export/llm-training
echo "/export/llm-training *(rw,sync,no_subtree_check)" | sudo tee -a /etc/exports
sudo exportfs -ra

# On all GPU nodes:
sudo mkdir -p /mnt/shared
sudo mount -t nfs nfs-server:/export/llm-training /mnt/shared

# 2. Copy training scripts to shared storage
scp -r scripts/ /mnt/shared/
scp -r configs/ /mnt/shared/

# 3. Set environment variables on ALL nodes
export MASTER_ADDR=192.168.1.100  # IP of master node
export MASTER_PORT=29500
export WORLD_SIZE=4               # Total GPUs across all nodes
export NCCL_SOCKET_IFNAME=eth0    # Network interface
export NCCL_DEBUG=INFO

# 4. Run training on each node
# Node 0 (master):
export NODE_RANK=0
torchrun --nnodes=2 --nproc_per_node=2 \
  --node_rank=$NODE_RANK \
  --master_addr=$MASTER_ADDR \
  --master_port=$MASTER_PORT \
  /mnt/shared/scripts/train_ddp.py --max_steps 50

# Node 1:
export NODE_RANK=1
torchrun --nnodes=2 --nproc_per_node=2 \
  --node_rank=$NODE_RANK \
  --master_addr=$MASTER_ADDR \
  --master_port=$MASTER_PORT \
  /mnt/shared/scripts/train_ddp.py --max_steps 50

NCCL Configuration for On-Premises

# ═══════════════════════════════════════════════════════════════
# ON-PREMISES NCCL SETTINGS
# ═══════════════════════════════════════════════════════════════

# For Ethernet networks
export NCCL_SOCKET_IFNAME=eth0    # Check: ip addr show
export NCCL_IB_DISABLE=1

# For InfiniBand networks
export NCCL_IB_DISABLE=0
export NCCL_IB_HCA=mlx5_0         # Check: ibstat
export NCCL_IB_GID_INDEX=3
export NCCL_NET_GDR_LEVEL=2       # GPUDirect RDMA level

# For NVLink-connected GPUs (intra-node)
export NCCL_P2P_LEVEL=NVL
export NCCL_SHM_DISABLE=0

# Debugging and logging
export NCCL_DEBUG=INFO
export NCCL_DEBUG_SUBSYS=INIT,COLL,P2P
export NCCL_DEBUG_FILE=/mnt/shared/logs/nccl_%h_%p.log

# Timeout for large models
export NCCL_TIMEOUT=1800
export TORCH_NCCL_ASYNC_ERROR_HANDLING=1

Network Setup for Multi-Node Training

# ═══════════════════════════════════════════════════════════════
# FIREWALL CONFIGURATION
# ═══════════════════════════════════════════════════════════════

# Open ports for NCCL and PyTorch distributed
sudo firewall-cmd --permanent --add-port=29500-29600/tcp
sudo firewall-cmd --permanent --add-port=29500-29600/udp
sudo firewall-cmd --reload

# Or using iptables
sudo iptables -A INPUT -p tcp --dport 29500:29600 -j ACCEPT
sudo iptables -A INPUT -p udp --dport 29500:29600 -j ACCEPT

# ═══════════════════════════════════════════════════════════════
# VERIFY CONNECTIVITY
# ═══════════════════════════════════════════════════════════════

# Test network connectivity
ping -c 3 <other-node-ip>

# Test NCCL port
nc -zv <other-node-ip> 29500

# Test NCCL initialization
python -c "
import os
import torch.distributed as dist
os.environ['MASTER_ADDR'] = '192.168.1.100'
os.environ['MASTER_PORT'] = '29500'
os.environ['RANK'] = '0'
os.environ['WORLD_SIZE'] = '1'
dist.init_process_group(backend='nccl')
print('NCCL initialized successfully')
dist.destroy_process_group()
"

Running Benchmarks On-Premises

# ═══════════════════════════════════════════════════════════════
# TRACK A: Single-Node DDP (2 GPU)
# ═══════════════════════════════════════════════════════════════
torchrun --nproc_per_node=2 \
  /mnt/shared/scripts/train_ddp.py \
  --max_steps 50 --batch_size 8 --experiment track_a

# ═══════════════════════════════════════════════════════════════
# TRACK B: Multi-Node DDP (4 GPU across 2 nodes)
# ═══════════════════════════════════════════════════════════════
# Run on Node 0:
torchrun --nnodes=2 --nproc_per_node=2 --node_rank=0 \
  --master_addr=$MASTER_ADDR --master_port=29500 \
  /mnt/shared/scripts/train_ddp.py --max_steps 50 --experiment track_b

# Run on Node 1 (simultaneously):
torchrun --nnodes=2 --nproc_per_node=2 --node_rank=1 \
  --master_addr=$MASTER_ADDR --master_port=29500 \
  /mnt/shared/scripts/train_ddp.py --max_steps 50 --experiment track_b

# ═══════════════════════════════════════════════════════════════
# TRACK C: Pipeline Parallelism (PP=2)
# ═══════════════════════════════════════════════════════════════
torchrun --nproc_per_node=2 \
  /mnt/shared/scripts/train_pipeline.py \
  --pipeline_parallel_size 2 --max_steps 50 --experiment track_c

# ═══════════════════════════════════════════════════════════════
# TRACK D: Hybrid PP=2 × TP=2 (requires 4 GPUs on single node)
# ═══════════════════════════════════════════════════════════════
torchrun --nproc_per_node=4 \
  /mnt/shared/scripts/train_hybrid.py \
  --pp_size 2 --tp_size 2 --max_steps 50 --experiment track_d

# ═══════════════════════════════════════════════════════════════
# TRACK E: Full Pipeline (PP=4)
# ═══════════════════════════════════════════════════════════════
torchrun --nproc_per_node=4 \
  /mnt/shared/scripts/train_pipeline.py \
  --pipeline_parallel_size 4 --max_steps 50 --experiment track_e

Nsight Systems Profiling On-Premises

# Install nsys if not available
# Download from NVIDIA: https://developer.nvidia.com/nsight-systems

# Profile training run
nsys profile -o /mnt/shared/nsys_profiles/track_a_ddp \
  --trace=cuda,nvtx,osrt,cudnn,cublas \
  --cuda-memory-usage=true \
  torchrun --nproc_per_node=2 /mnt/shared/scripts/train_ddp.py --max_steps 20

# Generate statistics
nsys stats /mnt/shared/nsys_profiles/track_a_ddp.nsys-rep \
  --report cuda_gpu_kern_sum

# Open in GUI
nsys-ui /mnt/shared/nsys_profiles/track_a_ddp.nsys-rep

Detailed Benchmark Results

Track A: Single-Node DDP (Baseline)

Configuration:
  - GPUs: 2× NVIDIA A10
  - Model: GPT-style, 162M parameters
  - Batch size: 8, Sequence length: 128

Results:
  - Throughput: 49,290 tokens/sec
  - Memory: 0.74 GB/GPU
  - NCCL: 100% ncclAllReduce

NCCL Profile (from nsys):
  ncclAllReduce operations: Gradient synchronization
  Ring algorithm used for optimal bandwidth

Track B: Multi-Node DDP

Configuration:
  - GPUs: 4× NVIDIA A10 (2 nodes × 2 GPUs)
  - Model: GPT-style, 162M parameters

Results:
  - Throughput: 52,847 tokens/sec
  - Scaling efficiency: 54% (vs ideal 100%)
  - Network overhead: ~7% (inter-node AllReduce)

NCCL Profile:
  Intra-node: PCIe (GPU0 ↔ GPU1)
  Inter-node: Ethernet (Node A ↔ Node B)

Track C: Pipeline Parallelism

Configuration:
  - GPUs: 2× NVIDIA A10
  - Pipeline stages: 2
  - Micro-batch size: 8

Results:
  - Throughput: 9,665 tokens/sec
  - Bubble fraction: ~70%
  - Memory: ~0.57 GB/GPU (split model)

NCCL Profile:
  ncclSend/ncclRecv: Activation transfer between stages
  Sequential dependency creates pipeline bubbles

Track D: Hybrid PP=2×TP=2

Configuration:
  - GPUs: 4× NVIDIA A10
  - Pipeline stages: 2, Tensor parallel: 2

Results:
  - Throughput: 10,069 tokens/sec
  - Communication split: 45% AllReduce, 55% Send/Recv

NCCL Profile (from nsys):
  ncclDevKernel_SendRecv: 44.8% of GPU time
  ncclDevKernel_AllReduce: 10.2% of GPU time
  ampere_sgemm kernels: Matrix operations

Track E: Full Pipeline (PP=4)

Configuration:
  - GPUs: 4× NVIDIA A10
  - Pipeline stages: 4 (1 GPU per stage)

Results:
  - Throughput: 8,343 tokens/sec
  - Bubble fraction: ~75%
  - Memory: ~2.3 GB/GPU (minimal per stage)

NCCL Profile:
  Chain of ncclSend/ncclRecv operations
  Maximum memory efficiency, lowest throughput

Nsight Systems Profiling

Available Profiles

Profile Size Key Observations
track_a_ddp.nsys-rep 8.5 MB ncclAllReduce dominates
track_b_ddp.nsys-rep 8.8 MB Inter-node AllReduce visible
track_c_pipeline.nsys-rep 8.4 MB ncclSend/Recv pattern
track_d_hybrid_pp2tp1.nsys-rep 15 MB Mixed AllReduce + Send/Recv
track_e_pipeline_pp2.nsys-rep 30 MB Sequential Send/Recv chain

How to Analyze Profiles

# Open in Nsight Systems GUI
nsys-ui results/nsys_profiles/track_d_hybrid_pp2tp1.nsys-rep

# Generate statistics
nsys stats results/nsys_profiles/track_d_hybrid_pp2tp1.nsys-rep \
  --report cuda_gpu_kern_sum

# Look for:
# 1. NCCL kernels: ncclDevKernel_SendRecv, ncclDevKernel_AllReduce
# 2. Compute kernels: ampere_sgemm, cutlass_sgemm
# 3. Memory operations: cudaMemcpy

Key Metrics from Profiles

Track D (Hybrid) GPU Kernel Summary:
  ncclDevKernel_SendRecv:        44.8% of GPU time (1.2B ns)
  ncclDevKernel_AllReduce_Sum:   10.2% of GPU time (275M ns)
  ampere_sgemm_128x64_tn:         6.9% of GPU time (185M ns)

Track E (Pipeline) GPU Kernel Summary:
  ampere_sgemm_128x64_tn:        17.7% of GPU time
  ncclDevKernel_SendRecv:        17.6% of GPU time
  cutlass_sgemm_256x128:         11.6% of GPU time

Troubleshooting

Common Issues

1. NCCL Timeout

# Error: NCCL timeout after 1800000ms

# Solution 1: Increase timeout
export NCCL_TIMEOUT=3600

# Solution 2: Check network
ping <other-node-ip>
nc -zv <other-node-ip> 29500

# Solution 3: Verify interface
export NCCL_DEBUG=INFO
export NCCL_SOCKET_IFNAME=eth0

2. GPU Out of Memory

# Error: CUDA out of memory

# Solution: Reduce batch size or increase pipeline stages
--micro_batch_size 4
--pipeline_parallel_size 4
--gradient_checkpointing

3. Pipeline Deadlock

# Symptom: Training hangs at send/recv

# Solution: Use async operations
# In code:
send_op = dist.isend(tensor, dst_rank)
recv_op = dist.irecv(buffer, src_rank)
send_op.wait()
recv_op.wait()

4. Slow Multi-Node Performance

# Check NCCL algorithm
export NCCL_DEBUG=INFO
# Look for: "Ring" or "Tree" in logs

# Try different algorithms
export NCCL_ALGO=Tree  # Better for latency
export NCCL_ALGO=Ring  # Better for bandwidth

Key Takeaways

When to Use Each Strategy

Scenario Recommended Strategy Reason
Model fits in 1 GPU DDP Maximum throughput
Model fits in node (multi-GPU) DDP + TP Balance memory/throughput
Model > node memory PP or Hybrid Split across nodes
Very large model (100B+) Hybrid (PP×TP×DP) Full utilization
Memory-constrained PP (more stages) Minimize per-GPU memory
Latency-sensitive DDP No pipeline bubbles

NCCL Operation Summary

Strategy Primary NCCL Op Secondary NCCL Op Communication %
DDP AllReduce - 5-15%
PP Send/Recv - 20-40%
TP AllReduce AllGather 10-30%
Hybrid AllReduce (TP) Send/Recv (PP) 30-50%

References

OCI Documentation

Deep Learning & Distributed Training

Research Papers


License

Apache 2.0


Infrastructure: Oracle Cloud Infrastructure (OCI) Last Updated: January 2026

About

A practical guide for selecting the right parallelism strategy for Large Language Model (LLM) training. This project provides hands-on experiments, performance analysis, and decision guidelines to help ML engineers understand when to use Data Parallelism (DDP), Pipeline Parallelism (PP), Tensor Parallelism (TP), or Hybrid approaches

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages