LLM DAILYA field guide to language models

Day 70 / 6 min read

Tensor parallelism — when faster math requires more communication

Yesterday we used the roofline model to explain why LLM inference can be limited by memory traffic rather than arithmetic. We saw that batching improves weight reuse, while long contexts can make KV-cache reads the dominant cost.

Today we'll add another resource to that picture: communication between GPUs.

The intuition is:

Splitting a model across GPUs gives each GPU less work, but the GPUs must exchange intermediate results. Sometimes the conversation between them costs more than the computation it saves.

This is the central tradeoff in tensor parallelism (TP), a technique used in both LLM training and inference.

Why split one model across multiple GPUs?

A dense model with 70 billion BF16 parameters needs roughly

\[ 70\times10^9\times2=140\text{ GB} \]

just for its weights. That excludes KV caches, temporary activations, and—in training—gradients and optimizer state.

One option is to divide the model's layers among GPUs. That's pipeline parallelism: GPU 1 runs early layers, then passes activations to GPU 2 for later layers.

Tensor parallelism does something different: multiple GPUs cooperate on the same layer by splitting its large weight matrices.

In an ideal four-way split, the 140 GB of weights becomes approximately 35 GB per GPU. Real allocations are not perfectly even because some parameters are replicated and other buffers still consume memory.

The key mathematical trick: split a matrix multiplication

Consider a linear layer:

\[ Y=XW, \]

where \(X\) contains token activations and \(W\) is the weight matrix.

Suppose we divide \(W\) by columns across two GPUs:

\[ W=[W_1\;W_2]. \]

Then

\[ Y=[XW_1\;XW_2]. \]

Each GPU computes a different slice of the output. Neither needs the other GPU's answer yet:

                    input X
                   /       \
             GPU 1           GPU 2
              XW1             XW2
                \             /
                 concatenate
                      Y

This is column-parallel linear algebra. The output is distributed across devices; we may not need to concatenate it physically if the next operation can consume the shards directly.

Now consider a second linear layer whose input dimension is split across GPUs. Write

\[ W_{\mathrm{down}}= \begin{bmatrix}V_1\\V_2\end{bmatrix}, \qquad H=[H_1\;H_2]. \]

Its output is

\[ Y=H_1V_1+H_2V_2. \]

Each GPU computes a partial answer, but those answers must be summed to obtain the full output. This is row-parallel linear algebra.

GPU 1: H1 × V1 = partial Y1 ──┐
                               ├── SUM → full Y
GPU 2: H2 × V2 = partial Y2 ──┘

The distributed sum is commonly implemented with an all-reduce collective: every participating GPU ends up with the same summed result. Some architectures use reduce-scatter instead and keep the result sharded.

Why Transformers are a natural fit

A Transformer block contains large projections in attention and in its feed-forward network (MLP).

A common tensor-parallel arrangement is:

  1. Attention: distribute query/attention heads across GPUs; combine their contributions through the output projection.
  2. MLP: split the expanding projections across GPUs, perform the elementwise activation locally, and sum partial results in the contracting projection.

For a standard Transformer layout, this often means roughly two communication collectives per layer: one around the attention output and one around the MLP output. Exact counts depend on the architecture, normalization placement, sequence-parallel choices, and implementation.

Notice what TP does not do: it does not automatically divide the KV cache perfectly. With grouped-query attention, the number of KV heads can be smaller than the number of GPUs, forcing some KV heads to be replicated or requiring a different sharding scheme.

Worked example: why tiny decode batches are awkward

Take an illustrative 32-layer Transformer with hidden dimension \(d=4096\), BF16 activations, and four-way tensor parallelism.

Suppose a decode step processes one new token for each of 32 requests. A full hidden-state tensor has

\[ S=Bd\times2=32\times4096\times2 =262{,}144\text{ bytes}=256\text{ KiB}. \]

For a ring all-reduce over \(p\) GPUs, the approximate data moved per GPU is

\[ M_{\mathrm{allreduce}}\approx 2\frac{p-1}{p}S. \]

With \(p=4\), that is

\[ M_{\mathrm{allreduce}}\approx 2\frac34\times256=384\text{ KiB}. \]

If there are two such collectives per layer, across 32 layers, total communication is approximately

\[ 32\times2\times384\text{ KiB} =24\text{ MiB per GPU per decode step}. \]

24 MiB is not the whole story. Those are 64 separate collectives. Each one has synchronization and startup latency, even when its payload is small. A ring algorithm also requires multiple communication rounds; alternative collective algorithms trade latency against bandwidth differently.

For batch size 1, the hidden-state payload is only 8 KiB. The total communicated bytes become small, but the repeated synchronization can still be expensive. This is one reason tensor parallelism may hurt single-request latency for a model that already fits on one GPU.

During prefill, by contrast, thousands of token positions can be processed together. Communication payloads grow, but so does the matrix-multiplication work available to split across GPUs. The relative economics can be much better.

These calculations are illustrative: real kernels may fuse operations, overlap transfers with computation, use different collectives, or shard activations differently.

Extending yesterday's roofline model

With one GPU, we considered compute and high-bandwidth-memory limits. With tensor parallelism, there is a third ceiling: interconnect bandwidth and collective latency.

A rough lower bound becomes

\[ t\gtrsim\max\left( \frac{F}{P}, \frac{M_{\mathrm{HBM}}}{W_{\mathrm{HBM}}}, \frac{M_{\mathrm{network}}}{W_{\mathrm{network}}} \right), \]

plus any synchronization or communication startup costs that cannot be hidden behind useful work.

That is why the topology matters. GPUs linked by a fast, low-latency interconnect can make fine-grained TP practical. Spreading the same tensor-parallel group across slower network links may turn every layer into a communication bottleneck.

TP versus the other ways to scale

Data parallelism:      complete model replica per worker
                       → more independent requests or training batches

Tensor parallelism:    split matrices within each layer
                       → GPUs cooperate on the same token/layer

Pipeline parallelism:  split layers into consecutive stages
                       → activations move between stages

In training, data-parallel replicas must also coordinate gradients, while tensor-parallel layers participate in both forward and backward communication. In inference, data parallelism is often the simplest way to increase throughput if a complete model replica fits. TP becomes especially valuable when model size, latency goals, or available hardware make one-GPU execution impractical.

These strategies are frequently combined: for example, four GPUs cooperate on each model replica using TP, while several such groups serve independent requests using data parallelism.

Mental model: four accountants and one shared spreadsheet

Imagine a large financial model. Four accountants can each calculate a different set of columns simultaneously. That is the column-parallel stage: everyone produces a distinct part of the answer.

But the final balance sheet needs totals that combine all four sets of calculations. Now the accountants must exchange and reconcile their numbers. That is the row-parallel reduction.

Adding accountants reduces individual arithmetic work. It does not eliminate the time spent coordinating. If the spreadsheet is tiny, four people may be slower than one; if it is enormous, the parallel work can outweigh the reconciliation cost.

The deeper lesson

Tensor parallelism makes a model larger than one GPU executable and can accelerate heavy computation. But it exchanges local computation and memory pressure for distributed coordination.

\[ \boxed{\text{More GPUs help only when saved work exceeds added communication.}} \]

The practical question is not merely, “How many GPUs can we use?” It is: At this batch size, context length, model architecture, and interconnect speed, where is the new bottleneck?

That's the distributed version of yesterday's roofline lesson—and a useful foundation for understanding why large-scale LLM serving and training are fundamentally systems-engineering problems.

LLM Daily Last updated