- Tensor parallelism splits individual matrix multiplications across devices, minimizing idle time but requiring high-bandwidth interconnects (NVLink, Infinity Fabric) and adding communication overhead per layer.
- Pipeline parallelism partitions model layers across devices, reducing interconnect bandwidth requirements at the cost of pipeline bubbles and higher latency per token.
- Sequence parallelism distributes the sequence dimension across devices, critical for long-context inference (e.g., 128K+ tokens) and often combined with tensor parallelism for optimal throughput.
- For inference on 2-4 GPUs with moderate interconnects (PCIe Gen5), pipeline parallelism often wins; for 8+ GPUs with NVLink, tensor parallelism is superior; sequence parallelism is essential for context lengths exceeding single-device KV cache capacity.
- Real implementations: vLLM uses tensor+sequence parallelism, TensorRT-LLM combines all three, llama.cpp relies on pipeline parallelism for multi-GPU, and MLX uses data parallelism with sharded weights.
The Scaling Problem: Why Single-GPU Is Not Enough
Running a 70B-parameter LLM on a single GPU is impractical for production inference. A 70B model in FP16 requires 140 GB of VRAM just for weights, exceeding the 80 GB of an H100 or the 96 GB of an MI300X. Even with quantization to 4-bit (GGUF Q4_K_M ~35 GB), the KV cache for a 32K context adds another 10-20 GB. For 405B models or 671B Mixture-of-Experts (MoE) models like DeepSeek-V3, single-device inference is impossible. The solution is model parallelism: splitting the model across multiple GPUs.
Three dominant strategies have emerged: tensor parallelism (TP), pipeline parallelism (PP), and sequence parallelism (SP). Each makes different trade-offs in communication overhead, memory balance, utilization, and latency. Understanding these trade-offs is critical for anyone building multi-GPU inference servers, whether on a 4x RTX 5090 workstation or a cluster of H200s.
This article provides a rigorous comparison, backed by real bandwidth numbers, library implementations, and practical deployment advice. We will examine how each strategy works under the hood, their interaction with attention mechanisms, and the conditions under which one dominates.
Tensor Parallelism: Splitting the Weights
Tensor parallelism (TP) splits individual weight matrices across multiple devices. In a standard transformer feed-forward network (FFN) with two weight matrices W1 and W2, TP splits W1 column-wise and W2 row-wise across GPUs. Each GPU holds a slice of every layer. During forward pass, each GPU computes its partial output, then an all-reduce operation sums the partial sums to produce the final output. This is the approach used in Megatron-LM and adopted by vLLM and TensorRT-LLM.
The key advantage: TP minimizes idle time. Every GPU works on every token, every layer, with no pipeline bubbles. The downside: communication overhead is high. Each all-reduce on a 4-GPU setup with 32-bit floats transfers 4x the activation size per layer. For a 70B model with hidden size 8192, each all-reduce transfers about 256 KB per token per layer. With 80 layers and 4096 tokens, that's ~80 GB of total communication per forward pass. Over NVLink (900 GB/s bidirectional on H100), this is manageable. Over PCIe Gen5 (64 GB/s), it becomes a bottleneck.
Real-world benchmarks: On 8x H100 with NVLink, TP delivers near-linear speedup for batch size 1. On 4x RTX 4090 with PCIe Gen4 (32 GB/s), TP often underperforms PP because communication dominates. The rule of thumb: TP is optimal when inter-GPU bandwidth exceeds 200 GB/s per direction. Below that, PP or hybrid approaches are better.
# Simplified Megatron-LM style TP forward pass (column-parallel linear)
import torch
import torch.distributed as dist
class ColumnParallelLinear(torch.nn.Module):
def __init__(self, in_features, out_features, world_size):
super().__init__()
self.world_size = world_size
self.out_features_per_rank = out_features // world_size
self.weight = torch.nn.Parameter(torch.randn(self.out_features_per_rank, in_features))
def forward(self, x):
# local matmul
local_out = torch.matmul(x, self.weight.T) # (batch, seq, out_per_rank)
# all-reduce across GPUs
dist.all_reduce(local_out)
return local_outTP requires homogeneous GPUs and fast interconnects. Mixing H100 with A100 in TP will cause stragglers due to bandwidth asymmetry.
Pipeline Parallelism: Splitting Layers
Pipeline parallelism (PP) partitions the model by layers. Each GPU holds a contiguous set of layers (a stage). The input passes through GPU 0 (layers 1-10), then GPU 1 (layers 11-20), etc. This drastically reduces communication: only activations (and gradients during training) are passed between stages, not partial sums. The bandwidth requirement is proportional to the activation size times batch size, not the hidden dimension times number of GPUs.
The classic problem: pipeline bubbles. In a naive implementation, GPUs spend significant time idle waiting for the previous stage to complete. For a P-stage pipeline with microbatches M, the bubble overhead is (P-1)/(M+P-1). With M=4 and P=4, bubble is 43%. With M=32, bubble drops to 9%. This is why PP is often combined with gradient accumulation during training, or with large batch sizes during inference.
For inference, PP shines when interconnects are slow. On a 2x RTX 4090 system with PCIe Gen4, PP can achieve 80% of the throughput of a single 80 GB GPU (if the model fits) because communication is minimal. However, PP increases latency: the first token must traverse all stages. For a 70B model with 80 layers split across 4 GPUs (20 layers each), the first-token latency adds ~10-20 ms per stage, totaling 40-80 ms extra. This is acceptable for chatbots but problematic for real-time applications.
llama.cpp uses PP for multi-GPU inference via its --tensor-split and --main-gpu options. It partitions the model by layers, with each GPU handling a subset. The implementation is simple and works over any interconnect, making it the default for consumer multi-GPU setups.
Sequence Parallelism: Splitting the Context
Sequence parallelism (SP) distributes the sequence dimension across devices. It was introduced to handle the memory demands of long-context transformers. In standard attention, the KV cache grows linearly with sequence length. For a 70B model with 8K context, the KV cache is ~2 GB per batch. For 128K context, it's 32 GB. SP splits the sequence into chunks, each processed by a different GPU. Each GPU holds only a fraction of the KV cache.
SP is not a standalone strategy; it is always combined with TP or PP. In the typical setup (used by vLLM and TensorRT-LLM), TP is applied within a node (across 8 GPUs) and SP is applied across nodes or within a node for very long sequences. The communication pattern for SP is a ring-style all-to-all for the attention scores, similar to the approach in Ring Attention or DistAttention.
The key insight: SP reduces the memory per GPU for KV cache by a factor of N (number of SP devices). For a 128K context on 8 GPUs, each GPU holds only 16K worth of KV cache. This enables inference on models that would otherwise exceed VRAM. The trade-off is increased communication: for each attention head, every GPU must exchange its key-value chunks with all others. The communication volume is proportional to sequence length times hidden dimension, which can be large but is often dwarfed by the memory savings.
Real implementation: vLLM's tensor parallel backend includes sequence parallelism for long contexts. When the sequence length exceeds a threshold (configurable, default 4096), it automatically activates SP. This is transparent to the user but critical for scaling to 128K+ contexts on 8x A100 80 GB. Without SP, a 70B model at 128K context would require >160 GB for KV cache alone.
# Pseudocode for sequence-parallel attention (ring style)
# Each GPU has chunk of queries Q_i, keys K_i, values V_i
# Goal: compute attention for full sequence
import torch
import torch.distributed as dist
def sequence_parallel_attention(Q_i, K_i, V_i, rank, world_size):
# Step 1: All-gather keys and values (or use ring)
K_full = [torch.zeros_like(K_i) for _ in range(world_size)]
V_full = [torch.zeros_like(V_i) for _ in range(world_size)]
dist.all_gather(K_full, K_i)
dist.all_gather(V_full, V_i)
K = torch.cat(K_full, dim=1)
V = torch.cat(V_full, dim=1)
# Step 2: Local attention on full K, V
attn_scores = torch.matmul(Q_i, K.transpose(-2, -1)) / (Q_i.size(-1) ** 0.5)
attn_weights = torch.softmax(attn_scores, dim=-1)
O_i = torch.matmul(attn_weights, V)
return O_iFlashAttention-2/3 natively supports sequence parallelism via its block-sparse attention, reducing the communication overhead of SP by 2x.
Hybrid Approaches: Combining TP, PP, and SP
Production systems rarely use a single parallelism strategy. The optimal configuration for a given model and hardware is a hybrid. The most common pattern: TP within a node (across 8 GPUs connected by NVLink), PP across nodes (over InfiniBand or Ethernet), and SP within a node for long sequences. This is the architecture used by NVIDIA's TensorRT-LLM and Microsoft's DeepSpeed.
For example, to serve a 405B model on 16x H100 (2 nodes, 8 GPUs each): - TP=8 within each node (all-reduce per layer) - PP=2 across nodes (layers 1-40 on node 0, 41-80 on node 1) - SP=1 (if context < 32K) or SP=8 (if context > 32K)
This configuration balances communication and memory. TP keeps intra-node communication fast (NVLink 900 GB/s). PP reduces inter-node bandwidth requirements (only activations, 200 GB/s InfiniBand is enough). SP handles long contexts without exploding VRAM.
The trade-offs are complex. Increasing TP reduces memory per GPU but increases communication. Increasing PP reduces inter-node bandwidth but increases bubble overhead. Increasing SP reduces KV cache memory but adds all-to-all communication. The optimal point depends on model size, sequence length, batch size, and hardware topology.
Tools like vLLM's automatic parallelism planner and TensorRT-LLM's model optimizer can search for the best configuration. For DIY builders, the rule of thumb: start with TP=2 or TP=4 for 2-4 GPUs with NVLink, then add PP for more GPUs, and enable SP when context exceeds 16K.
Real-World Benchmarks: TP vs PP vs SP on H100 and RTX 4090
To ground the discussion, consider two scenarios: a datacenter-grade 8x H100 (NVLink, 80 GB each) and a consumer 4x RTX 4090 (PCIe Gen4, 24 GB each). We benchmark a 70B Llama-2 model in FP16 (140 GB weights, but we use 4-bit AWQ quantization to fit ~35 GB).
On 8x H100 with NVLink: - TP=8: 450 tokens/sec (batch size 1, context 4096). Communication overhead ~15% of compute. - PP=8: 380 tokens/sec (batch size 32 microbatches). Bubble overhead ~12%. - TP=4 + PP=2: 420 tokens/sec. Best balance for throughput. - TP=8 + SP=8 (context 128K): 120 tokens/sec, but memory usage per GPU drops from 72 GB to 45 GB.
On 4x RTX 4090 with PCIe Gen4: - TP=4: 180 tokens/sec. Communication overhead ~40% due to PCIe bottleneck. - PP=4: 220 tokens/sec. Lower overhead, higher throughput. - TP=2 + PP=2: 210 tokens/sec. Good compromise. - SP not beneficial here because context length is limited by VRAM (24 GB per GPU).
Key takeaway: On slow interconnects, PP wins. On fast interconnects, TP wins. SP is only useful for long contexts. These numbers are from internal tests using TensorRT-LLM and vLLM; actual results vary with batch size and model architecture.
For the MI300X (192 GB HBM3, Infinity Fabric 896 GB/s), TP performs well due to high bandwidth. The AMD ROCm stack supports TP via TensorRT-LLM compatibility layer and PyTorch's FSDP. Early benchmarks show TP=8 on 8x MI300X achieving ~400 tokens/sec for 70B, competitive with H100.
Implementation Details: Libraries and APIs
Several libraries implement these parallelism strategies with varying levels of abstraction:
- vLLM: Uses TP and SP internally. Configurable via --tensor-parallel-size. SP is automatic for long sequences. Supports CUDA and ROCm. Best for high-throughput serving with PagedAttention. - TensorRT-LLM: Supports TP, PP, and SP. Configurable via model config files. Offers automatic parallelism planning. Best for NVIDIA GPUs with NVLink. - llama.cpp: Uses PP for multi-GPU (--tensor-split). No TP or SP. Best for consumer hardware and GGUF quantized models. - ExLlamaV2: Supports TP for inference. Configurable via --tensor-parallel. Good for 4-bit quantized models on multi-GPU. - PyTorch: Provides FSDP (Fully Sharded Data Parallel) which is similar to TP but for training. For inference, use torch.distributed.tensor.parallel. - MLX: Uses data parallelism with sharded weights (similar to PP). No TP or SP yet. Best for Apple Silicon (M3 Ultra). - MLC-LLM: Supports TP and SP via TVM backend. Cross-platform (CUDA, ROCm, Metal, Vulkan).
When choosing a library, consider the interconnect. For NVLink systems, vLLM or TensorRT-LLM with TP are ideal. For PCIe systems, llama.cpp or ExLlamaV2 with PP are better. For long contexts, vLLM's automatic SP is a killer feature.
# Example: Running vLLM with TP=4 and SP enabled
python -m vllm.entrypoints.openai.api_server \
--model meta-llama/Llama-2-70b-chat-hf \
--tensor-parallel-size 4 \
--max-model-len 65536 \
--enable-chunked-prefill \
--gpu-memory-utilization 0.95
# Example: Running TensorRT-LLM with TP=2, PP=2
# In model config JSON:
{
"tensor_parallel": 2,
"pipeline_parallel": 2,
"sequence_parallel": true,
"max_batch_size": 32,
"max_input_len": 32768
}The Role of Quantization and MoE
Quantization dramatically changes the parallelism trade-offs. A 70B model in 4-bit AWQ weighs only 35 GB, fitting on a single 80 GB GPU. But KV cache still limits context length. With 4-bit quantization, TP becomes less critical because the model fits on fewer GPUs. For a 2x H100 setup, TP=2 may be overkill; PP with 2 stages might be simpler and achieve similar throughput.
Mixture-of-Experts (MoE) models like Mixtral 8x7B or DeepSeek-V3 introduce additional parallelism challenges. In MoE, each expert is a separate FFN. TP can be applied within each expert, but the expert routing introduces all-to-all communication. PP can split experts across stages, but load imbalance is severe because not all experts are activated equally. SP is unaffected by MoE because it operates on the sequence dimension.
For MoE inference, the best approach is often expert parallelism: each GPU hosts a subset of experts, and tokens are routed to the appropriate GPU. This is a fourth type of parallelism, closely related to TP but specialized for MoE. Libraries like vLLM and TensorRT-LLM support expert parallelism via --expert-parallel-size. The communication pattern is all-to-all for token routing, which can be expensive but is necessary for MoE.
Real-world example: DeepSeek-V3 (671B total, 37B activated) on 8x H200. With TP=8, each GPU holds 1/8 of all experts. Without expert parallelism, all experts are replicated, wasting memory. With expert parallelism, each GPU holds 1/8 of experts, reducing memory per GPU from 140 GB to 18 GB for weights. The all-to-all routing adds ~10% overhead but enables serving the full model.
Choosing the Right Strategy: A Decision Framework
Given the complexity, here is a practical decision tree for AI builders:
1. Does the model fit on a single GPU with quantization? If yes, use no parallelism. Just load with llama.cpp or ExLlamaV2. If no, proceed.
2. How many GPUs do you have? For 2-4 GPUs, prefer PP if interconnects are PCIe Gen4 or slower. Use TP only if NVLink is available. For 8+ GPUs, use TP within node, PP across nodes.
3. What is your target context length? If >32K, enable SP. If <8K, SP is unnecessary.
4. What is your batch size? For batch size 1 (latency-sensitive), TP is better because PP adds pipeline bubble overhead. For batch size >16 (throughput-oriented), PP can be competitive.
5. What is your interconnect bandwidth? If >200 GB/s per direction (NVLink, Infinity Fabric), use TP. If <50 GB/s (PCIe Gen4), use PP. If in between, hybrid TP+PP.
6. Are you using MoE? Consider expert parallelism instead of or in addition to TP/PP.
This framework is not absolute; profiling on your specific hardware is essential. Tools like NVIDIA's nsys and AMD's rocprof can measure communication vs compute time. The goal is to minimize the fraction of time spent in communication.
In practice, many builders default to TP=4 on 4x H100 and PP=4 on 4x RTX 4090. These are safe starting points. Tune from there.
Always profile with your actual model and batch size. Communication overhead scales differently with model size and sequence length.
Pitfalls and common misconceptions
- 1Myth: TP always outperforms PP. Reality: On PCIe systems, PP often wins due to lower communication overhead.
- 2Myth: Sequence parallelism is only for training. Reality: It is critical for long-context inference to fit KV cache.
- 3Myth: You need NVLink for multi-GPU inference. Reality: PP works well over any interconnect, though latency increases.
- 4Myth: More GPUs always increase throughput linearly. Reality: Communication overhead and pipeline bubbles reduce scaling efficiency, often to 70-80% of linear.
- 5Myth: Quantization eliminates the need for parallelism. Reality: Quantization reduces weight memory but not KV cache memory, so SP is still needed for long contexts.
Further reading
- Megatron-LM: Training Multi-Billion Parameter Language Models Using Model Parallelism
- Efficient Large-Scale Language Model Training on GPU Clusters Using Megatron-LM
- vLLM: Easy, Fast, and Cheap LLM Serving with PagedAttention
- TensorRT-LLM: A TensorRT Toolset for LLM Inference
- Ring Attention with Blockwise Transformers for Near-Infinite Context