Skip to content

Distributed Inference Overview

Table of Contents


Why Distribute?

Modern LLMs can require tens or hundreds of gigabytes of memory just for model weights alone. A 70B parameter model in 16-bit precision occupies roughly 140 GB, which exceeds the capacity of any single consumer or workstation GPU. Even when the model fits, running multiple concurrent requests can exhaust device memory for KV caches.

Distribution solves two distinct problems:

  1. Memory capacity: the model is too large for one GPU. Solution: split the model so each GPU holds only a portion.

  2. Throughput: a single GPU cannot keep up with the desired request rate. Solution: run multiple copies of the model or pipeline stages in parallel.

mlxcel provides three complementary strategies that can be combined depending on the deployment topology.


Strategy Comparison

graph TB
    subgraph TP["Tensor Parallelism (TP)"]
        direction LR
        TP_IN[Input Tokens] --> TP_R0[Rank 0<br/>W shard 0]
        TP_IN --> TP_R1[Rank 1<br/>W shard 1]
        TP_R0 -->|all-reduce| TP_OUT[Output]
        TP_R1 -->|all-reduce| TP_OUT
    end

    subgraph PP["Pipeline Parallelism (PP)"]
        direction LR
        PP_S0[Stage 0<br/>Layers 0-15] -->|activations| PP_S1[Stage 1<br/>Layers 16-31]
        PP_S1 -->|activations| PP_S2[Stage 2<br/>Layers 32-47]
        PP_S2 --> PP_OUT[Logits]
    end

    subgraph DI["Disaggregated Inference (DI)"]
        direction LR
        DI_IN[Request] --> DI_PF[Prefill Node<br/>Compute KV cache]
        DI_PF -->|transfer KV| DI_DC[Decode Node<br/>Autoregressive decode]
        DI_DC --> DI_OUT[Tokens]
    end
Dimension Tensor Parallelism Pipeline Parallelism Disaggregated Inference
Split unit Individual weight matrices Model layer ranges Prefill vs. decode steps
Communication All-reduce per layer (low latency required) Activation tensors between stages KV cache transfer once per request
Latency impact Low (synchronous all-reduce) Moderate (pipeline bubble) Low for decode; prefill TTFT unchanged
Throughput scaling Linear with GPU count Linear with stage count Independently scale prefill and decode
Hardware requirement High-bandwidth interconnect (NVLink/PCIe) Any network; TCP or Thunderbolt Any network
Best for Single-node multi-GPU Multi-node large models High-throughput production serving
KV cache Replicated per rank Per-stage local cache Transferred from prefill to decode node
Batch scheduling Rank 0 coordinator (TPScheduler) GPipe micro-batch schedule Separate prefill and decode schedulers

When to Use Each Strategy

Use Tensor Parallelism when:

  • You have multiple GPUs on the same host (or connected via NVLink/Thunderbolt).
  • The model does not fit on a single GPU but fits across 2-8 GPUs.
  • You need low per-request latency (TP all-reduce is fast on shared-memory busses).
  • The model is a standard Transformer, MoE, or SSM architecture (all are supported with architecture-aware shard plans).

Use Pipeline Parallelism when:

  • You have multiple hosts and the model is too large for any single host.
  • Per-request latency can tolerate pipeline bubble overhead.
  • Requests arrive in predictable batch sizes (micro-batching amortizes pipeline bubbles effectively).
  • You want to maximize total throughput over a set of heterogeneous nodes.

Use Disaggregated Inference when:

  • You serve workloads with highly variable prompt lengths (e.g., RAG, code completion with long context).
  • Prefill nodes and decode nodes have different hardware profiles (compute-dense GPUs for prefill, memory-dense GPUs for decode).
  • You want to scale prefill capacity and decode capacity independently without changing the other.
  • Long prompts should not delay short decode sessions that are already in flight.

Common Infrastructure

All three strategies share a common infrastructure layer defined in src/distributed/.

Node Discovery

Each node advertises itself with a unique ID and network address. The cluster configuration (TOML or CLI flags) provides the initial peer list.

Heartbeat and Failure Detection

A background heartbeat loop (src/distributed/heartbeat.rs) sends periodic pings to all known peers. The failure detector (src/distributed/failure_detector.rs) marks a node as unavailable after a configurable number of missed heartbeats, triggering re-routing in the request router.

Connection Pooling

src/distributed/connection_pool.rs maintains persistent TCP connections to peer nodes. Connections are reused across requests to avoid TCP handshake overhead on the critical path.

Transport Layer

The transport abstraction (src/distributed/transport.rs) supports:

  • TCP: Default transport, works across all network topologies.
  • Thunderbolt: High-bandwidth transport for Apple Silicon devices connected via Thunderbolt; see src/distributed/thunderbolt_transport.rs.
  • RDMA: Optional accelerated path negotiated per peer at connect time, with a TCP fallback; see src/distributed/rdma_transport.rs and Pipeline Parallelism.
graph LR
    A[Node A] -->|TCP / Thunderbolt| B[Node B]
    B --> C[Node C]
    C --> A
    style A fill:#4a90d9
    style B fill:#4a90d9
    style C fill:#4a90d9

Cluster Secret, Handshake, and Frame Protection

Pipeline roles and the disaggregated prefill, decode, and router roles refuse to start without a pre-shared cluster secret (--cluster-secret-file, MLXCEL_CLUSTER_SECRET_FILE, MLXCEL_CLUSTER_SECRET, or [cluster] secret_file in the cluster TOML; at least 16 bytes). Single-node serving (hybrid) does not read it. The secret is used in three places:

  • Handshake (src/distributed/cluster_handshake.rs, protocol version 5). Each TCP connection starts with a mutual challenge-response: both ends prove possession of the secret with HMAC-SHA256 over the full transcript, so a peer with the wrong secret is rejected on whichever side it is. The accept side binds the client's advertised identity to the TCP source IP. The server's verdict (accept, or at capacity) carries its own HMAC tag, so a party on the path cannot change one verdict into the other.
  • Frame protection (src/distributed/frame_protection.rs). Per-connection directional keys are derived from the secret and the handshake nonces with HKDF-SHA256. Every frame after the handshake is encrypted and authenticated with AES-256-GCM, the default suite, and carries an implicit sequence number, so a replayed, reordered, dropped, or injected frame fails authentication and closes the connection. Both ends must use the same suite; the handshake refuses a mismatch.
  • Discovery beacons (src/distributed/discovery_beacon.rs) are signed with the same secret.

Connections that have not finished the handshake draw from a separate pending pool with a per-source limit (src/distributed/pending_handshakes.rs), so a flood of idle sockets cannot use up the slots of established connections.

All nodes upgrade together

The handshake protocol is version 5. A build older than v0.8.0 cannot join a v0.8.0 cluster. Upgrade every node of a cluster at the same time.

Tensor Protocol

src/distributed/tensor_protocol.rs defines the wire format for transferring tensors between nodes. Supports the following data types:

Type Description
Float32 32-bit float
Float16 IEEE 754 half-precision
BFloat16 Brain float 16
Int32 Signed 32-bit integer
Int16 Signed 16-bit integer
Int8 Signed 8-bit integer

For KV cache transfer in disaggregated inference, optional LZ4 compression and on-the-fly int8/int4 quantization are available to reduce bandwidth requirements.


Cluster Configuration

A cluster is configured via a TOML file passed with --cluster-config, or via CLI flags (--node-id, --node-role, --peers) for simpler topologies.

TOML Configuration

[cluster]
name = "my-cluster"
tensor_parallel_size = 2
pipeline_parallel_size = 1

[[nodes]]
id = "node-0"
address = "192.168.1.10:8080"
role = "tensor_parallel_rank"
rank = 0

[nodes.resources]
memory_bytes = 68719476736   # 64 GiB
compute_units = 0            # 0 = auto-detect

[[nodes]]
id = "node-1"
address = "192.168.1.11:8080"
role = "tensor_parallel_rank"
rank = 1

[nodes.resources]
memory_bytes = 68719476736
compute_units = 0

Node Roles

Role Value in TOML Description
Prefill prefill Handles prompt processing only (disaggregated)
Decode decode Handles token generation only (disaggregated)
Pipeline Stage pipeline_stage Owns one stage of a PP topology
Tensor Parallel Rank tensor_parallel_rank or tp Participates in TP execution
Pipeline x Tensor Parallel pipeline_tensor_parallel (aliases pp_tp, 2d) Participates in a 2D PP x TP mesh
Hybrid hybrid General-purpose node (default for single-node serving)

--node-role router additionally selects the model-free HTTP front of a disaggregated deployment. It is a CLI role, not a TOML node role; see Disaggregated Inference.

CLI Configuration (simple topologies)

# Node 0 (rank 0)
mlxcel-server --model ./models/llama-70b \
  --node-id node-0 \
  --node-role tensor_parallel_rank \
  --peers 192.168.1.11:8080

# Node 1 (rank 1)
mlxcel-server --model ./models/llama-70b \
  --node-id node-1 \
  --node-role tensor_parallel_rank \
  --peers 192.168.1.10:8080

Validation

The cluster configuration is validated at startup:

  • Node IDs must be unique and non-empty.
  • Addresses must be unique.
  • Node IDs and cluster name must not exceed 128 characters and must not contain control characters.
  • At least one node must be present.