How tensor parallelism works
A standard linear layer computesY = X W. With tensor parallelism the weight matrix W is split across GPUs and the computation is distributed:
- Column parallel (split output)
- Row parallel (split input)
W is split along dim=0 (output features). Each GPU computes a slice of the output independently — no communication is needed during the forward pass.dist.all_reduce at the end of each row-parallel layer.
Linear layer variants
ColumnParallelLinear
Each GPU storesoutput_size // tp_size output rows. The weight loader extracts the shard corresponding to the current rank.
RowParallelLinear
Each GPU storesinput_size // tp_size input columns. A dist.all_reduce sums partial results after the matrix multiply.
QKVColumnParallelLinear
For attention, the column split must respect head boundaries. Each GPU handles complete attention heads rather than fractional ones:load_weight_id ('q', 'k', or 'v') to identify which sub-matrix is being loaded and computes the correct offset within the merged QKV tensor.
MergedColumnParallelLinear
Holds the gate and up MLP projections in a single weight tensor. The weight loader is called once per sub-matrix with aloaded_weight_id integer (0 for gate, 1 for up):
Data flow through an attention block
Enabling multi-GPU inference
Changeworld_size in the config dict passed to LLMEngine:
All of
num_heads, num_kv_heads, and vocab_size must be divisible by world_size. The model layers assert this at construction time.Process group initialization
Every process (rank 0 and workers) callsdist.init_process_group inside ModelRunner.__init__:
dist.init_process_group is a collective barrier — rank 0 blocks until every worker rank has called it. This guarantees that all NCCL channels are established before any collective operation (e.g. all_reduce, all_gather) is executed.
Worker process lifecycle
Shared memory communication
Rank 0 (the scheduler process) communicates with worker ranks through a POSIX shared memory segment namedmyvllm. This avoids serializing data through the OS socket used by NCCL.
1
Rank 0 writes a call
2
Worker reads the call
3
Worker executes the method
self.run(...). Because their model weights are sharded identically, all collective operations (all_reduce, etc.) execute in lock-step across ranks.The shared memory segment is created by rank 0 after a
dist.barrier() to ensure all worker processes have completed their initialization before the segment is written.Why rank 0 handles scheduling and sampling
Scheduling. The scheduler maintains the waiting and running queues and decides which sequences run each step. This is a CPU-side decision that does not need to be replicated across GPUs. Sampling. After the model forward pass, logits on rank 0 are either computed directly (single GPU) or gathered viadist.gather from all ranks. Only rank 0 calls SamplerLayer.forward to convert logits to token IDs:
None from run. Only rank 0 returns the sampled token IDs to the engine, which then passes them to scheduler.postprocess.