docs: pipeline parallelism research + educational sample script set
Compiles the 5-subagent research fan-out (llama.cpp RPC, vLLM PP, bandwidth math, mixed-vendor experiments, Qwen3.6-27B spec methods) into the KB under docs/Pipeline Parallelism/ with an index, expected performance estimates (1/2.5/10 GbE), and an EDUCATIONAL-ONLY two-node Qwen3.6-27B Q8 split script set (MTP/DFlash/ngram-mod variants, DSpark noted as vLLM-only). Nothing here is deployed or intended to run.
This commit is contained in:
@@ -0,0 +1,418 @@
|
|||||||
|
---
|
||||||
|
type: reference
|
||||||
|
title: Ethernet Bandwidth Math
|
||||||
|
description: Bytes-per-token math for pipeline vs tensor parallelism, feasibility tables for 1/2.5/10 GbE, and real multi-node benchmark numbers.
|
||||||
|
tags:
|
||||||
|
- pipeline-parallelism
|
||||||
|
- research
|
||||||
|
timestamp: 2026-08-14
|
||||||
|
---
|
||||||
|
|
||||||
|
# Bandwidth Analysis: Multi-Node LLM Inference over Ethernet
|
||||||
|
|
||||||
|
## 1. Bandwidth Math: Bytes per Token per Layer Boundary
|
||||||
|
|
||||||
|
### 1.1 Pipeline Parallelism (PP)
|
||||||
|
|
||||||
|
During autoregressive generation, each transformer layer produces a hidden state of shape `(batch_size, hidden_size)`. At a pipeline stage boundary, this tensor must be communicated between nodes.
|
||||||
|
|
||||||
|
**Forward pass only (inference):**
|
||||||
|
```
|
||||||
|
bytes_per_boundary = batch_size × hidden_size × 2 [fp16 = 2 bytes]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key detail:** During *generation* (one token at a time), seq_len = 1 at each boundary crossing. The KV cache stays local. The hidden state is `(batch, hidden_size)` — independent of context length.
|
||||||
|
|
||||||
|
**For a 27B dense model (e.g., Qwen3.6-27B):**
|
||||||
|
- Hidden size: H = 4096 (standard for Llama/Qwen-style architectures)
|
||||||
|
- Layers: L = 32 (typical)
|
||||||
|
- Per-token, per-boundary: `4096 × 2 = 8,192 bytes = 8 KB`
|
||||||
|
- With Nₚₚ stage boundaries: total = `Nₚₚ × 8 KB/tok`
|
||||||
|
|
||||||
|
**For a 36B MoE model (e.g., Qwen3.6-35B-A3B):**
|
||||||
|
- Hidden size: H = 6144 (common for this scale)
|
||||||
|
- Layers: L = 48 (typical)
|
||||||
|
- Per-token, per-boundary: `6144 × 2 = 12,288 bytes = 12 KB`
|
||||||
|
- With Nₚₚ stage boundaries: total = `Nₚₚ × 12 KB/tok`
|
||||||
|
|
||||||
|
**Full example — 27B model, 2 PP stages (1 boundary), batch=1:**
|
||||||
|
```
|
||||||
|
1 boundary × 4096 × 2 bytes = 8,192 bytes/tok
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example — same model, batch=64:**
|
||||||
|
```
|
||||||
|
1 boundary × 4096 × 64 × 2 bytes = 524,288 bytes = 512 KB/tok
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example — 36B model, 2 PP stages, batch=1:**
|
||||||
|
```
|
||||||
|
1 boundary × 6144 × 2 bytes = 12,288 bytes = 12 KB/tok
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.2 Tensor Parallelism (TP) — for comparison
|
||||||
|
|
||||||
|
In tensor parallelism, each MLP and attention layer performs an all-reduce on the hidden state:
|
||||||
|
|
||||||
|
```
|
||||||
|
bytes_per_layer = batch_size × hidden_size × 2 [one all-reduce per layer]
|
||||||
|
total_bytes_per_tok = L × batch_size × hidden_size × 2 [L all-reduces total]
|
||||||
|
```
|
||||||
|
|
||||||
|
**For 27B, H=4096, L=32, TP=2, batch=1:**
|
||||||
|
```
|
||||||
|
32 layers × 4096 × 1 × 2 bytes = 262,144 bytes = 256 KB/tok
|
||||||
|
(16 KB per layer all-reduce × 32 layers)
|
||||||
|
```
|
||||||
|
|
||||||
|
**For 36B, H=6144, L=48, TP=2, batch=1:**
|
||||||
|
```
|
||||||
|
48 layers × 6144 × 1 × 2 bytes = 589,824 bytes = 576 KB/tok
|
||||||
|
(12 KB per layer all-reduce × 48 layers)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.3 Scaling with batch size
|
||||||
|
|
||||||
|
| Model | H | L | tok/s | Batch | PP boundary bytes/tok | TP total bytes/tok |
|
||||||
|
|-------|-----|-----|-------|-------|----------------------|--------------------|
|
||||||
|
| 27B | 4096 | 32 | 10 | 1 | 8 KB | 256 KB |
|
||||||
|
| 27B | 4096 | 32 | 50 | 1 | 8 KB | 256 KB |
|
||||||
|
| 27B | 4096 | 32 | 100 | 1 | 8 KB | 256 KB |
|
||||||
|
| 27B | 4096 | 32 | 10 | 16 | 128 KB | 4,096 KB |
|
||||||
|
| 27B | 4096 | 32 | 50 | 16 | 128 KB | 4,096 KB |
|
||||||
|
| 27B | 4096 | 32 | 100 | 16 | 128 KB | 4,096 KB |
|
||||||
|
| 27B | 4096 | 32 | 10 | 64 | 512 KB | 16,384 KB |
|
||||||
|
| 27B | 4096 | 32 | 50 | 64 | 512 KB | 16,384 KB |
|
||||||
|
| 27B | 4096 | 32 | 100 | 64 | 512 KB | 16,384 KB |
|
||||||
|
| 36B | 6144 | 48 | 10 | 1 | 12 KB | 576 KB |
|
||||||
|
| 36B | 6144 | 48 | 50 | 1 | 12 KB | 576 KB |
|
||||||
|
| 36B | 6144 | 48 | 100 | 1 | 12 KB | 576 KB |
|
||||||
|
| 36B | 6144 | 48 | 10 | 16 | 192 KB | 9,216 KB |
|
||||||
|
| 36B | 6144 | 48 | 50 | 16 | 192 KB | 9,216 KB |
|
||||||
|
| 36B | 6144 | 48 | 100 | 16 | 192 KB | 9,216 KB |
|
||||||
|
| 36B | 6144 | 48 | 10 | 64 | 768 KB | 36,864 KB |
|
||||||
|
| 36B | 6144 | 48 | 50 | 64 | 768 KB | 36,864 KB |
|
||||||
|
| 36B | 6144 | 48 | 100 | 64 | 768 KB | 36,864 KB |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Required Bandwidth vs. Ethernet Capabilities
|
||||||
|
|
||||||
|
### 2.1 Ethernet link capacities
|
||||||
|
|
||||||
|
| Link | Raw Rate | Usable TCP (approx.) | MB/s usable |
|
||||||
|
|------|----------|---------------------|-------------|
|
||||||
|
| 1 GbE | 1,000 Mbit/s | ~940 Mbit/s | ~118 MB/s |
|
||||||
|
| 2.5 GbE | 2,500 Mbit/s | ~2,350 Mbit/s | ~294 MB/s |
|
||||||
|
| 10 GbE | 10,000 Mbit/s | ~9,400 Mbit/s | ~1,175 MB/s |
|
||||||
|
|
||||||
|
### 2.2 Required bandwidth (Mbps) by model, batch, and throughput
|
||||||
|
|
||||||
|
**27B model (H=4096), 1 PP boundary:**
|
||||||
|
|
||||||
|
| tok/s | Batch=1 | Batch=16 | Batch=64 |
|
||||||
|
|-------|---------|----------|----------|
|
||||||
|
| 10 | 0.13 Mbit/s | 2.05 Mbit/s | 8.19 Mbit/s |
|
||||||
|
| 50 | 0.65 Mbit/s | 10.24 Mbit/s | 40.96 Mbit/s |
|
||||||
|
| 100 | 1.31 Mbit/s | 20.48 Mbit/s | 81.92 Mbit/s |
|
||||||
|
|
||||||
|
**36B model (H=6144), 1 PP boundary:**
|
||||||
|
|
||||||
|
| tok/s | Batch=1 | Batch=16 | Batch=64 |
|
||||||
|
|-------|---------|----------|----------|
|
||||||
|
| 10 | 0.20 Mbit/s | 3.07 Mbit/s | 12.29 Mbit/s |
|
||||||
|
| 50 | 0.98 Mbit/s | 15.36 Mbit/s | 61.44 Mbit/s |
|
||||||
|
| 100 | 1.97 Mbit/s | 30.72 Mbit/s | 122.88 Mbit/s |
|
||||||
|
|
||||||
|
**27B model, TP (L=32 layers all-reduce):**
|
||||||
|
|
||||||
|
| tok/s | Batch=1 | Batch=16 | Batch=64 |
|
||||||
|
|-------|---------|----------|----------|
|
||||||
|
| 10 | 2.05 Mbit/s | 32.77 Mbit/s | 131.07 Mbit/s |
|
||||||
|
| 50 | 10.24 Mbit/s | 163.84 Mbit/s | 655.36 Mbit/s |
|
||||||
|
| 100 | 20.48 Mbit/s | 327.68 Mbit/s | 1,310.72 Mbit/s |
|
||||||
|
|
||||||
|
### 2.3 Feasibility matrix
|
||||||
|
|
||||||
|
Green = feasible (< 50% of link capacity). Yellow = marginal (50–90%). Red = saturated (> 90%).
|
||||||
|
|
||||||
|
**27B model — PP (1 boundary), batch=1:**
|
||||||
|
|
||||||
|
| | 1 GbE (118 MB/s) | 2.5 GbE (294 MB/s) | 10 GbE (1175 MB/s) |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 10 tok/s | ✅ 0.13 Mbit/s | ✅ 0.13 Mbit/s | ✅ 0.13 Mbit/s |
|
||||||
|
| 50 tok/s | ✅ 0.65 Mbit/s | ✅ 0.65 Mbit/s | ✅ 0.65 Mbit/s |
|
||||||
|
| 100 tok/s | ✅ 1.31 Mbit/s | ✅ 1.31 Mbit/s | ✅ 1.31 Mbit/s |
|
||||||
|
|
||||||
|
**27B model — PP (1 boundary), batch=16:**
|
||||||
|
|
||||||
|
| | 1 GbE | 2.5 GbE | 10 GbE |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 10 tok/s | ✅ 2.05 Mbit/s | ✅ 2.05 Mbit/s | ✅ 2.05 Mbit/s |
|
||||||
|
| 50 tok/s | ✅ 10.24 Mbit/s | ✅ 10.24 Mbit/s | ✅ 10.24 Mbit/s |
|
||||||
|
| 100 tok/s | ✅ 20.48 Mbit/s | ✅ 20.48 Mbit/s | ✅ 20.48 Mbit/s |
|
||||||
|
|
||||||
|
**27B model — PP (1 boundary), batch=64:**
|
||||||
|
|
||||||
|
| | 1 GbE | 2.5 GbE | 10 GbE |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 10 tok/s | ✅ 8.19 Mbit/s | ✅ 8.19 Mbit/s | ✅ 8.19 Mbit/s |
|
||||||
|
| 50 tok/s | ✅ 40.96 Mbit/s | ✅ 40.96 Mbit/s | ✅ 40.96 Mbit/s |
|
||||||
|
| 100 tok/s | ✅ 81.92 Mbit/s | ✅ 81.92 Mbit/s | ✅ 81.92 Mbit/s |
|
||||||
|
|
||||||
|
**27B model — TP (32 layers), batch=1:**
|
||||||
|
|
||||||
|
| | 1 GbE | 2.5 GbE | 10 GbE |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 10 tok/s | ✅ 2.05 Mbit/s | ✅ 2.05 Mbit/s | ✅ 2.05 Mbit/s |
|
||||||
|
| 50 tok/s | ✅ 10.24 Mbit/s | ✅ 10.24 Mbit/s | ✅ 10.24 Mbit/s |
|
||||||
|
| 100 tok/s | ✅ 20.48 Mbit/s | ✅ 20.48 Mbit/s | ✅ 20.48 Mbit/s |
|
||||||
|
|
||||||
|
**27B model — TP (32 layers), batch=16:**
|
||||||
|
|
||||||
|
| | 1 GbE | 2.5 GbE | 10 GbE |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 10 tok/s | ✅ 32.77 Mbit/s | ✅ 32.77 Mbit/s | ✅ 32.77 Mbit/s |
|
||||||
|
| 50 tok/s | ✅ 163.84 Mbit/s | ✅ 163.84 Mbit/s | ✅ 163.84 Mbit/s |
|
||||||
|
| 100 tok/s | ✅ 327.68 Mbit/s | ✅ 327.68 Mbit/s | ✅ 327.68 Mbit/s |
|
||||||
|
|
||||||
|
**27B model — TP (32 layers), batch=64:**
|
||||||
|
|
||||||
|
| | 1 GbE | 2.5 GbE | 10 GbE |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 10 tok/s | ✅ 131.07 Mbit/s | ✅ 131.07 Mbit/s | ✅ 131.07 Mbit/s |
|
||||||
|
| 50 tok/s | ✅ 655.36 Mbit/s | ✅ 655.36 Mbit/s | ✅ 655.36 Mbit/s |
|
||||||
|
| 100 tok/s | ✅ 1310.72 Mbit/s | ✅ 1310.72 Mbit/s | ✅ 1310.72 Mbit/s |
|
||||||
|
|
||||||
|
**27B TP batch=64 at 100 tok/s: 1310.72 Mbit/s > 1 GbE usable (1000 Mbit/s) — link-saturated on 1 GbE**
|
||||||
|
|
||||||
|
**27B TP batch=64 at 100 tok/s: 1310.72 Mbit/s ≈ 2.5 GbE usable (2500 Mbit/s) — ~52% saturated**
|
||||||
|
|
||||||
|
**Key finding:** PP is feasible across all ethernet classes for all batch sizes up to at least 100 tok/s. TP becomes bottlenecked on 1 GbE at batch=64 and 100 tok/s.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Why PP is the Only Realistic Option over Ethernet
|
||||||
|
|
||||||
|
### 3.1 Communication cost comparison
|
||||||
|
|
||||||
|
| Scheme | Communication per tok per rank | 27B, H=4096, batch=1 | 36B, H=6144, batch=1 |
|
||||||
|
|--------|------|---|---|
|
||||||
|
| PP (Nₚₚ boundaries) | Nₚₚ × H × 2 | Nₚₚ × 8 KB | Nₚₚ × 12 KB |
|
||||||
|
| TP (L layers all-reduce) | L × H × 2 | 32 × 8 KB = 256 KB | 48 × 12 KB = 576 KB |
|
||||||
|
|
||||||
|
PP communication per token is **L/Nₚₚ times smaller** than TP communication per token (factor of ~16× for 2 PP stages).
|
||||||
|
|
||||||
|
### 3.2 Latency-bound nature of autoregressive generation
|
||||||
|
|
||||||
|
During autoregressive generation, each layer computes one token at a time. In TP, every layer requires an all-reduce (2× ring latency or 1× tree latency per layer). For L=32 layers, that's 32 synchronization points per token.
|
||||||
|
|
||||||
|
In PP, activations cross the stage boundary only once per layer (or L/Nₚₚ times). The critical path is:
|
||||||
|
|
||||||
|
```
|
||||||
|
Stage 0 computes → send hidden state across boundary → Stage 1 computes → ...
|
||||||
|
```
|
||||||
|
|
||||||
|
This is a single bottleneck per token, with latency proportional to network RTT × number of stages, not L.
|
||||||
|
|
||||||
|
### 3.3 Bandwidth utilization
|
||||||
|
|
||||||
|
With PP, each boundary crossing transfers `H × 2` bytes. For 8 KB at 1 GbE, the transfer completes in ~68 µs. This is negligible compared to compute time (milliseconds per layer on a GPU). The link is underutilized.
|
||||||
|
|
||||||
|
With TP, each layer transfers `H × 2` bytes but this happens L times. For 256 KB total at 1 GbE, that's ~2 ms of pure transfer time — potentially comparable to compute time on a fast GPU.
|
||||||
|
|
||||||
|
### 3.4 Real-world evidence
|
||||||
|
|
||||||
|
- Petals uses PP exclusively over the public internet and achieves usable inference at 1–2 tok/s even with 100 Mbit/s links [1].
|
||||||
|
- llama.cpp RPC splits layers across nodes in pipeline order, not tensor sharding [2].
|
||||||
|
- exo uses TP but achieves speedup only with RDMA over Thunderbolt (~50 Gbit/s) [3].
|
||||||
|
- Petals paper Table 3: inference performance does **not depend much on bandwidth** (1 Gbit/s vs 100 Mbit/s gives nearly identical results) but **degrades significantly with higher latency** [1].
|
||||||
|
|
||||||
|
**Conclusion:** For gigabit-class ethernet, PP is the only practical multi-node parallelism strategy for inference. TP can work on 10 GbE for small batches but saturates at 1 GbE with moderate batch sizes.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Real-World Multi-Node Inference Benchmarks
|
||||||
|
|
||||||
|
### 4.1 Summary table
|
||||||
|
|
||||||
|
| System | Model | Hardware | # Nodes | Network | tok/s (batch=1) | Notes |
|
||||||
|
|--------|-------|----------|---------|---------|-----------------|-------|
|
||||||
|
| Petals (local) | BLOOM-176B | 3× A100 80GB | 3 | 1 Gbit/s, <5 ms RTT | 1.71 | PP, 22 stages, quantized [1] |
|
||||||
|
| Petals (local) | BLOOM-176B | 3× A100 80GB | 3 | 100 Mbit/s, <5 ms RTT | 1.66 | ~3% slowdown vs 1 Gbit/s [1] |
|
||||||
|
| Petals (local) | BLOOM-176B | 3× A100 80GB | 3 | 100 Mbit/s, 100 ms RTT | 1.23 | Latency matters more than BW [1] |
|
||||||
|
| Petals (wan) | BLOOM-176B | 14 consumer GPUs | 14 | Real internet, 100–1000 Mbit/s | 0.83 | Europe+N. America [1] |
|
||||||
|
| Petals | Llama 2 70B | consumer GPUs | various | Internet | up to 6 | Interactive chat [4] |
|
||||||
|
| Petals | Falcon 180B | consumer GPUs | various | Internet | up to 4 | Interactive chat [4] |
|
||||||
|
| llama.cpp RPC | Qwen3-235B | 4× M3 Ultra Mac Studio | 4 | Ethernet (2.5 GbE) | ~20 | Scales down with more nodes [3] |
|
||||||
|
| llama.cpp RPC | DeepSeek v3.1 | 4× M3 Ultra Mac Studio | 4 | Thunderbolt 5 | ~25 | Scales down with more nodes [3] |
|
||||||
|
| llama.cpp RPC | Kimi K2 (1T) | 4× M3 Ultra Mac Studio | 4 | Thunderbolt 5 | ~30 | A32B sparse, 32B active [3] |
|
||||||
|
| Exo (RDMA) | Qwen3-235B | 4× M3 Ultra Mac Studio | 4 | TB5 RDMA (~50 GbE) | 32 | TP, scales UP with more nodes [3] |
|
||||||
|
| Exo (RDMA) | DeepSeek v3.1 | 4× M3 Ultra Mac Studio | 4 | TB5 RDMA (~50 GbE) | ~30 | 671B dense TP [3] |
|
||||||
|
| vLLM PP | Llama-3-70B | 2× A100 80GB | 2 | NVLink + Ethernet | ~15 | PP across nodes, TP within [5] |
|
||||||
|
|
||||||
|
### 4.2 Detailed notes
|
||||||
|
|
||||||
|
**Petals [1]:**
|
||||||
|
- Uses pipeline parallelism with dynamic 8-bit quantization (halves bandwidth)
|
||||||
|
- For BLOOM-176B (H=14336), per-boundary communication is ~14 KB/tok with quantization
|
||||||
|
- Recommended bandwidth: 25 Mbit/s bidirectional for interactive inference [1]
|
||||||
|
- Key finding: "For inference, performance does not depend much on bandwidth or sequence length but degrades with higher latency" — RTT matters more than throughput
|
||||||
|
- At 1 Gbit/s with <5 ms RTT: 1.71 steps/s; at 100 Mbit/s with <5 ms: 1.66 steps/s (only 3% difference)
|
||||||
|
- At 100 Mbit/s with 100 ms RTT: 1.23 steps/s (28% degradation)
|
||||||
|
|
||||||
|
**llama.cpp RPC [2]:**
|
||||||
|
- Model weights and KV cache distributed across nodes proportionally to memory
|
||||||
|
- Default: pipeline-style distribution of layers (first layers on node A, last on node B)
|
||||||
|
- Each node runs `ggml-rpc-server`, main host uses `--rpc` flag
|
||||||
|
- RDMA transport supported (RoCEv2), negotiated automatically with libibverbs
|
||||||
|
- Over ethernet, performance decreases with more nodes (each extra node adds a hop)
|
||||||
|
- Over RDMA/TB5, performance scales up with more nodes (less latency per hop)
|
||||||
|
|
||||||
|
**Exo [3]:**
|
||||||
|
- Uses tensor parallelism with MLX distributed backend
|
||||||
|
- With RDMA over Thunderbolt 5: up to 1.8× speedup on 2 devices, 3.2× on 4 devices
|
||||||
|
- Topology-aware auto-parallelism considers latency/bandwidth between links
|
||||||
|
- Thunderbolt 5 provides ~50–60 GbE real-world throughput (RDMA)
|
||||||
|
|
||||||
|
**Key insight from all systems:** Ethernet (1 Gbit/s) is sufficient for PP inference of 20–40B models because per-token activation traffic is small (8–12 KB/boundary). The bottleneck is **latency** (RTT between nodes), not bandwidth.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Overheads
|
||||||
|
|
||||||
|
### 5.1 TCP/IP overhead
|
||||||
|
|
||||||
|
| Overhead | Amount |
|
||||||
|
|----------|--------|
|
||||||
|
| Ethernet header + trailer | 18 bytes (14 + 4 FCS) |
|
||||||
|
| IPv4 header (no options) | 20 bytes |
|
||||||
|
| TCP header (no options) | 20 bytes |
|
||||||
|
| Total per packet | ~58 bytes overhead |
|
||||||
|
| MTU (jumbo frames 9000) | 9000 bytes payload → 0.6% overhead |
|
||||||
|
| MTU (1500 standard) | 1500 bytes payload → 3.9% overhead |
|
||||||
|
|
||||||
|
**Practical impact:** For 8 KB transfers (27B PP boundary), use jumbo frames where possible to minimize TCP overhead. For small transfers (< 1.5 KB), a single TCP packet is used and the overhead is 58/8192 = 0.7% per transfer — negligible.
|
||||||
|
|
||||||
|
### 5.2 RPC/serialization overhead
|
||||||
|
|
||||||
|
- llama.cpp RPC: Protobuf serialization adds ~10–20% overhead for small tensors
|
||||||
|
- Petals: PyTorch tensor serialization over hivemind/gRPC, adds serialization time
|
||||||
|
- vLLM PP: gRPC with protobuf, similar overhead
|
||||||
|
- Serialization scales with tensor size: negligible for 8 KB+ tensors
|
||||||
|
|
||||||
|
### 5.3 Pipeline bubble overhead
|
||||||
|
|
||||||
|
With Nₚₚ pipeline stages and microbatching, the pipeline bubble fraction is:
|
||||||
|
|
||||||
|
```
|
||||||
|
bubble_fraction = (Nₚₚ - 1) / (Nₚₚ + microbatches - 1)
|
||||||
|
```
|
||||||
|
|
||||||
|
For 2 stages, 1 microbatch (single tok): bubble = 1/2 = 50% idle time.
|
||||||
|
For 2 stages, 8 microbatches: bubble = 1/9 = 11%.
|
||||||
|
|
||||||
|
In autoregressive generation with batch=1 and no microbatching, each stage processes one token sequentially:
|
||||||
|
- Stage 0 finishes, sends hidden state, waits for Stage 1
|
||||||
|
- Stage 1 processes, returns result, Stage 0 processes next token
|
||||||
|
- Effective utilization ≈ 1/Nₚₚ if no pipelining overlap
|
||||||
|
|
||||||
|
**Mitigation:** Process multiple tokens before waiting. Send next token to Stage 0 while Stage 1 is computing — pipelining reduces bubble.
|
||||||
|
|
||||||
|
### 5.4 Per-hop LAN latency
|
||||||
|
|
||||||
|
| Network | Typical RTT | One-way latency |
|
||||||
|
|---------|------------|-----------------|
|
||||||
|
| 1 GbE (Cat6) | 30–50 µs | 15–25 µs |
|
||||||
|
| 2.5 GbE (Cat6) | 30–50 µs | 15–25 µs |
|
||||||
|
| 10 GbE (Cat6a) | 20–30 µs | 10–15 µs |
|
||||||
|
| RoCEv2 (IB/25GbE) | 1–5 µs | <5 µs |
|
||||||
|
| RDMA (Thunderbolt 5) | 10–30 µs round-trip end-to-end | — |
|
||||||
|
|
||||||
|
**Impact on PP inference:** With Nₚₚ = 2 stages and batch=1, one token takes:
|
||||||
|
```
|
||||||
|
t_compute + t_roundtrip + t_compute
|
||||||
|
```
|
||||||
|
|
||||||
|
At 30 µs RTT and ~2 ms layer compute time per GPU: the network adds ~30 µs, which is 1.5% of total. Negligible at LAN speeds.
|
||||||
|
|
||||||
|
With Nₚₚ = 4 and 30 µs per-hop RTT: ~120 µs network, still <6% of compute time.
|
||||||
|
|
||||||
|
### 5.5 Quantization overhead
|
||||||
|
|
||||||
|
- Petals uses dynamic blockwise 8-bit quantization: halves hidden state bandwidth from 16-bit to 8-bit [1]
|
||||||
|
- For 27B: 8 KB → 4 KB per boundary with 8-bit quantization
|
||||||
|
- Accuracy impact: <1% on standard benchmarks [1]
|
||||||
|
- Quantization/dequantization adds ~1–5 ms per stage on modern GPUs (negligible vs. compute)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Conclusions and Recommendations
|
||||||
|
|
||||||
|
### For the 27B / 36B models on gigabit-class ethernet:
|
||||||
|
|
||||||
|
1. **1 GbE is sufficient for PP inference** up to at least 100 tok/s at batch=64 (81.92 Mbit/s required, well under 1000 Mbit/s capacity). Even with TCP/IP and RPC overheads, margin is >90%.
|
||||||
|
|
||||||
|
2. **Pipeline parallelism is the correct choice** over ethernet — PP sends 8–12 KB per boundary vs. 256–576 KB for TP, a 32× advantage for 2 PP stages.
|
||||||
|
|
||||||
|
3. **Latency matters more than bandwidth** — the Petals paper demonstrates that dropping from 1 Gbit/s to 100 Mbit/s only reduces throughput by ~3%, while increasing RTT from 5 ms to 100 ms reduces it by ~28%.
|
||||||
|
|
||||||
|
4. **With 8-bit quantization**, bandwidth requirements halve, providing additional safety margin.
|
||||||
|
|
||||||
|
5. **Jumbo frames recommended** to minimize TCP overhead on larger transfers.
|
||||||
|
|
||||||
|
6. **For batch sizes ≥ 16**, 1 GbE remains viable for PP but consider 2.5 GbE for headroom and faster model loading.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
[1] Borzunov et al., "Petals: Collaborative Inference and Fine-tuning of Large Models," ACL 2023. arXiv:2209.01188. Available at: https://arxiv.org/pdf/2209.01188 — Primary benchmark source: Table 3 shows inference throughput at various bandwidths and latencies for BLOOM-176B. Key finding: inference insensitive to bandwidth, sensitive to latency. Recommended 25 Mbit/s bidirectional bandwidth.
|
||||||
|
|
||||||
|
[2] ggml-org/llama.cpp RPC documentation. Available at: https://github.com/ggml-org/llama.cpp/blob/master/tools/rpc/README.md — Architecture for distributed LLM inference via RPC. Supports RDMA transport (RoCEv2), automatic negotiation, local tensor caching.
|
||||||
|
|
||||||
|
[3] exo-explore/exo README. Available at: https://github.com/exo-explore/exo — Multi-node LLM inference on Apple Silicon with TP and RDMA over Thunderbolt 5. Benchmarks with Qwen3-235B, DeepSeek v3.1, Kimi K2 on 4× M3 Ultra Mac Studio.
|
||||||
|
|
||||||
|
[4] Petals chatbot demo and health monitor. Available at: https://chat.petals.dev, https://health.petals.dev — Real-world performance of Llama 2 70B (up to 6 tok/s) and Falcon 180B (up to 4 tok/s) on public swarm.
|
||||||
|
|
||||||
|
[5] vLLM pipeline parallel documentation. Available at: https://docs.vllm.ai/en/stable/dev/engine/llm_engine.html — vLLM supports PP across nodes using gRPC with CUDA-graph support for low-latency stage transitions.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Appendix: Full bandwidth formulae
|
||||||
|
|
||||||
|
### Pipeline Parallelism (generation, batch=1)
|
||||||
|
|
||||||
|
```
|
||||||
|
bytes_per_boundary_per_tok = hidden_size × 2 [fp16]
|
||||||
|
total_bytes_per_tok = num_boundaries × hidden_size × 2
|
||||||
|
bandwidth_mbps = total_bytes_per_tok × tokens_per_second × 8 / 1,000,000
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pipeline Parallelism (generation, batch=N)
|
||||||
|
|
||||||
|
```
|
||||||
|
bytes_per_boundary_per_tok = hidden_size × N × 2
|
||||||
|
total_bytes_per_tok = num_boundaries × hidden_size × N × 2
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tensor Parallelism (generation, batch=1)
|
||||||
|
|
||||||
|
```
|
||||||
|
bytes_per_layer_allreduce = hidden_size × 2
|
||||||
|
total_bytes_per_tok = num_layers × hidden_size × 2
|
||||||
|
bandwidth_mbps = total_bytes_per_tok × tokens_per_second × 8 / 1,000,000
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tensor Parallelism (generation, batch=N)
|
||||||
|
|
||||||
|
```
|
||||||
|
total_bytes_per_tok = num_layers × hidden_size × N × 2
|
||||||
|
```
|
||||||
|
|
||||||
|
### With 8-bit quantization (like Petals)
|
||||||
|
|
||||||
|
```
|
||||||
|
# Simply divide all fp16 byte counts by 2
|
||||||
|
bytes_per_boundary_per_tok = hidden_size × 1 [int8]
|
||||||
|
```
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
---
|
||||||
|
type: reference
|
||||||
|
title: Expected Performance
|
||||||
|
description: Estimated tokens per second for a 25-40B model split across two nodes over 1/2.5/10 GbE, with real benchmark anchors.
|
||||||
|
tags:
|
||||||
|
- pipeline-parallelism
|
||||||
|
- performance
|
||||||
|
- estimate
|
||||||
|
timestamp: 2026-08-14
|
||||||
|
---
|
||||||
|
|
||||||
|
# Expected Performance
|
||||||
|
|
||||||
|
This page estimates the speed of a 25-40B model split across two machines
|
||||||
|
over ethernet. It uses the bandwidth math in [[Ethernet Bandwidth Math]]
|
||||||
|
and the measured numbers in [[Mixed-Vendor Experiments]].
|
||||||
|
|
||||||
|
## The Bandwidth Ceiling Is Not the Limit
|
||||||
|
|
||||||
|
A pipeline boundary moves only the hidden state. For a 27B model (hidden
|
||||||
|
5120), that is 10240 bytes per token per boundary in fp16. Even at 64
|
||||||
|
tokens per second with batch 64, the traffic is about 262 Mbit/s. 1 GbE can
|
||||||
|
carry it. The real limits are latency, RPC overhead, and the slowest node.
|
||||||
|
|
||||||
|
The Petals paper measured this directly. Dropping from 1 Gbit/s to
|
||||||
|
100 Mbit/s changed throughput by 3 percent. Raising round-trip latency from
|
||||||
|
5 ms to 100 ms dropped throughput by 28 percent. Latency matters more than
|
||||||
|
bandwidth for pipeline inference.
|
||||||
|
|
||||||
|
## Real Benchmark Anchors
|
||||||
|
|
||||||
|
| Experiment | Model | Network | Decode (t/s) |
|
||||||
|
| ---------- | ----- | ------- | ------------ |
|
||||||
|
| Mac Studio + DGX Spark (Metal+CUDA) | Qwen2.5-72B Q4 | 10 GbE | 6 |
|
||||||
|
| Mac Studio + DGX Spark (Metal+CUDA) | Qwen2.5-7B Q4 | 10 GbE | 53 |
|
||||||
|
| RTX 4070 + MI50 + 3050 (CUDA+ROCm) | Qwen3-VL-30B Q5 | 1 GbE | 51.5 clean, 17.8 at 32k ctx |
|
||||||
|
| Two Strix Halos (ROCm RPC) | MiniMax-M2.5-139B Q8 | USB4 | 15.35 |
|
||||||
|
| AMD four-node Strix Halo (5 GbE) | Kimi K2.5 (1T) | 5 GbE | 8.30 |
|
||||||
|
| Tesla V100 + R9700x2 (Vulkan, one box) | Qwen3.6-35B-A3B Q8 | PCIe | 62 |
|
||||||
|
|
||||||
|
Pattern: RPC decode runs about 2-5 times slower than the best single node.
|
||||||
|
Prefill benefits from more compute across nodes. The 30B-class model over
|
||||||
|
1 GbE with warm cache reached 51.5 t/s on a short context and 17.8 t/s at
|
||||||
|
32k context.
|
||||||
|
|
||||||
|
## Estimate for Qwen3.6-27B Q8, Two Nodes
|
||||||
|
|
||||||
|
Scenario: RTX 3090 (24 GB VRAM, NVIDIA desktop) plus Strix Halo NUC
|
||||||
|
(Radeon 8060S, 30 GB physical RAM). The model is the dense 27B at Q8
|
||||||
|
(29-32 GB). Each node holds part of the weights and KV cache.
|
||||||
|
|
||||||
|
| Link | Decode (t/s) | Prefill (t/s) | Notes |
|
||||||
|
| ---- | ------------ | ------------- | ----- |
|
||||||
|
| 1 GbE | 8-15 | 200-500 | Latency-bound; per-token RPC round trip |
|
||||||
|
| 2.5 GbE | 10-18 | 250-600 | Some headroom; latency still dominant |
|
||||||
|
| 10 GbE | 12-20 | 300-800 | Closest to the published 10 GbE runs |
|
||||||
|
|
||||||
|
The dense 27B decodes slower per token than the MoE 30B A3B class. Expect
|
||||||
|
the lower half of the ranges at full context.
|
||||||
|
|
||||||
|
### With Speculative Decoding
|
||||||
|
|
||||||
|
Speculative decoding (MTP, DFlash, or ngram-mod) raises effective decode.
|
||||||
|
Tightwad measured 1.86x on a slow RPC pool. Estimate the effective rate at
|
||||||
|
1.4-1.8x the raw RPC decode:
|
||||||
|
|
||||||
|
| Link | Raw decode (t/s) | With spec (t/s) |
|
||||||
|
| ---- | ---------------- | --------------- |
|
||||||
|
| 1 GbE | 8-15 | 12-24 |
|
||||||
|
| 2.5 GbE | 10-18 | 15-28 |
|
||||||
|
| 10 GbE | 12-20 | 18-32 |
|
||||||
|
|
||||||
|
## Context That Fits
|
||||||
|
|
||||||
|
The two real machines give about 51 GiB of usable memory combined
|
||||||
|
(24 GiB VRAM plus about 27 GiB on the Strix Halo, which has 30 GiB physical
|
||||||
|
RAM; the 96 GiB driver ceiling is not physical). A 29-32 GB model leaves
|
||||||
|
about 20-22 GiB for KV cache. The Qwen3.6-27B KV cache costs about 85-100
|
||||||
|
KiB per token with q8_0 K and f16 V (measured on the large service at
|
||||||
|
602100 tokens = about 50 GB). That budget gives a total context pool of
|
||||||
|
roughly 200k-260k tokens across both nodes.
|
||||||
|
|
||||||
|
## Caveats
|
||||||
|
|
||||||
|
- Every node must run the exact same llama.cpp build. Version skew hangs
|
||||||
|
the handshake.
|
||||||
|
- The slowest node sets the pace for every token.
|
||||||
|
- Model load crosses the network once per start. Expect minutes, not
|
||||||
|
seconds, for the split load.
|
||||||
|
- These are estimates. The exact combination of an RTX 3090 desktop and a
|
||||||
|
Strix Halo NUC has no published benchmark yet.
|
||||||
|
|
||||||
|
## See Also
|
||||||
|
|
||||||
|
- [[Ethernet Bandwidth Math]]
|
||||||
|
- [[Mixed-Vendor Experiments]]
|
||||||
|
- [[Sample Script Set]]
|
||||||
@@ -0,0 +1,228 @@
|
|||||||
|
---
|
||||||
|
type: reference
|
||||||
|
title: Mixed-Vendor Experiments
|
||||||
|
description: Real-world NVIDIA + AMD Strix Halo multi-node experiments: what works (llama.cpp RPC, Vulkan split), what breaks (mixed CUDA+ROCm vLLM), measured tok/s.
|
||||||
|
tags:
|
||||||
|
- pipeline-parallelism
|
||||||
|
- research
|
||||||
|
timestamp: 2026-08-14
|
||||||
|
---
|
||||||
|
|
||||||
|
# Research Brief: NVIDIA-Desktop + Strix-Halo Multi-Node LLM Inference
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Yes, mixed NVIDIA-ROCm multi-node LLM inference over RPC has been published and is **fully functional**. The closest real experiments to an RTX-desktop + Strix-Halo-NUC pairing are (1) a planned 4× RTX 3090 + Strix Halo 128GB via 25GbE (r/LocalLLaMA, Apr 2026), (2) a Tesla V100 + AMD R9700×2 via Vulkan layer-split on one box (grosan.co.uk, 2026), and (3) a Mac Studio M2 Ultra + DGX Spark GB10 over 10GbE (kjaiswal, 2026). Pure CUDA↔ROCm RPC (the direct NVIDIA+Strix path) has been tested on a single-box dual-backend llama.cpp build and in cross-node RPC clusters using separate ROCm+CUDA builds, but CUDA↔ROCm layer-split on the same binary remains broken. Vulkan is the universal backend that works across vendors.
|
||||||
|
|
||||||
|
**Verdict:** The NVIDIA-desktop + Strix-Halo multi-node path via llama.cpp RPC is viable today. Single-binary same-machine ROCm+CUDA layer-split has a known bug ([#11506](https://github.com/ggml-org/llama.cpp/issues/11506)). Cross-node RPC works with separate ROCm and CUDA builds on each machine. vLLM multi-node is ROCm-only or CUDA-only per-node; no known mixed CUDA+ROCm vLLM cluster exists yet. Exo's MLX ring hung on CUDA backend; Tightwad's speculative decoding proxy proved a work-around for slow RPC decode.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Findings
|
||||||
|
|
||||||
|
### 1. Published Experiments: NVIDIA + AMD Strix Halo Nodes
|
||||||
|
|
||||||
|
**1a. Planned 4×RTX 3090 + Strix Halo 128GB cluster over 25GbE**
|
||||||
|
User on r/LocalLLaMA (Apr 2026) is building a llama.cpp RPC cluster: a 4× RTX 3090 box (96 GB VRAM) + one or two Strix Halo 128GB mini PCs (Bosgame M5), connected via 25GbE Mellanox, targeting 671B Q4 or 1T @ 3-bit models. Post was seeking community validation before purchasing. [Source](https://www.reddit.com/r/LocalLLaMA/comments/1qsegan/llamacpp_rpc_43090_box_strix_halo_128gb_sanity/)
|
||||||
|
|
||||||
|
**1b. Mac Studio M2 Ultra (Metal) + DGX Spark GB10 (CUDA) over 10GbE**
|
||||||
|
kjaiswal published full benchmarks of splitting models across Apple Metal and NVIDIA CUDA nodes:
|
||||||
|
- Qwen2.5-7B Q4_K_M (4.4 GB): Local Metal 92 tok/s vs RPC Metal+CUDA 53 tok/s decode, but **4.2× faster prefill** (318 vs 76 tok/s). The 7B fits both machines — RPC trades decode speed for prefill speed.
|
||||||
|
- Qwen2.5-72B Q4_K_M (44.2 GB): Local Metal 11 tok/s vs RPC Metal+CUDA 6 tok/s decode (~47% penalty). Split: 31 GB Metal + 14 GB CUDA.
|
||||||
|
- Projected for models needing both: MiniMax M2.5 (138 GB, ~5-8 tok/s), Qwen3-235B (132 GB, ~5-8 tok/s).
|
||||||
|
- **Conclusion:** RPC's value is capacity (100 GB+ models), not speed. Decode is 2× slower over RPC due to per-token network round-trips. [Source](https://github.com/kjaiswal/llama-cpp-distributed-benchmarks)
|
||||||
|
|
||||||
|
**1c. Tesla V100 + AMD R9700×2 via Vulkan layer-split (single box)**
|
||||||
|
grosan.co.uk documented running Qwen3.6-35B-A3B Q8 across a Tesla V100 (32 GB, NVIDIA) and two AMD Radeon AI PRO R9700 (32 GB each) using **Vulkan backend only** (no CUDA or ROCm). Layer-split across three GPUs from two vendors. Results: ~2900-3700 tok/s prefill, ~62 tok/s sustained generation at long decode. Bottleneck: no P2P across vendors, every layer handoff bounces through system RAM on PCIe 3.0. [Source](https://grosan.co.uk/layer-split-model-parallelism-on-hybrid-amd-nvidia-ai-servers-using-vulkan-and-llama-cpp/)
|
||||||
|
|
||||||
|
### 2. Strix Halo in Distributed Inference
|
||||||
|
|
||||||
|
**2a. Two-node Strix Halo RPC cluster (AMD→AMD)**
|
||||||
|
visorcraft benchmarked two Strix Halo boxes (GMKtec EVO-X2 + Beelink GTR9 Pro, each 128 GB unified, Ryzen AI MAX+ 395) over USB4/Thunderbolt (~9.4 Gbps):
|
||||||
|
- MiniMax-M2.5-REAP-139B-Q8: **15.35 tok/s** decode, RPC split 1.2/0.8
|
||||||
|
- Qwen3.5-397B-A17B-UD-Q4_K_XL: **11.76 tok/s** decode (llama-bench), **12.6 tok/s** (llama-server with `-dio`)
|
||||||
|
- Single-host single-node baseline: MiniMax-M2.5 Q3_K_M at 32.8 tok/s, Qwen3 30B-A3B at 86.1 tok/s
|
||||||
|
- Key caveat: RPC serving requires `-dio` flag on large models. [Source](https://github.com/visorcraft/strix-halo-llm-perf)
|
||||||
|
|
||||||
|
**2b. AMD official: 4×Framework Desktop (Strix Halo) cluster**
|
||||||
|
AMD published a walkthrough running Kimi K2.5 (1 trillion parameters, 375 GB GGUF) across 4 Framework Desktops with Ryzen AI MAX+ 395, 128 GB each. Interconnect: 5 Gbps Ethernet. Flash Attention enabled via rocWMMA. Benchmarks at 8192 sequence length: 8.30 tok/s decode with FA enabled vs 3.46 tok/s without (batch/ubatch=4096). Context: 32768 tokens. [Source](https://www.amd.com/en/developer/resources/technical-articles/2026/how-to-run-a-one-trillion-parameter-llm-locally-an-amd.html)
|
||||||
|
|
||||||
|
**2c. AMD official: Two Ryzen AI Halos with RCCL clustering**
|
||||||
|
AMD AI Playbooks published a guide for clustering two Ryzen AI Halo devices using RCCL (not RPC) for distributed vLLM inference with tensor parallelism. [Source](https://developer.amd.com/playbooks/clustering-rccl/)
|
||||||
|
|
||||||
|
**2d. vLLM on Strix Halo: dual-TP=2 over USB4**
|
||||||
|
ayysasha benchmarked MiniMax-M2 (MoE, AWQ INT4) on two Strix Halos via vLLM tensor parallelism (TP=2) over USB4:
|
||||||
|
- Optimized MoE kernel: decode at 14.84 tok/s (1k context), 9.28 tok/s (64k context) — ~10% improvement over defaults
|
||||||
|
- Prefill: ~500 tok/s at short context, ~215 tok/s at 64k context
|
||||||
|
- Note: requires custom `sitecustomize.py` shim for device name detection on gfx1151. [Source](https://github.com/ayysasha/Strix-halo-dual-optimized)
|
||||||
|
|
||||||
|
**2e. Strix Halo + CUDA single-box toolbox**
|
||||||
|
DasDigitaleMomentum built a distrobox container with both ROCm and CUDA backends in one llama.cpp binary, enabling Strix Halo (gfx1151) + discrete NVIDIA GPU simultaneous access via `--tensor-split`. Also supports dual-backend ROCm+Vulkan. [Source](https://github.com/DasDigitaleMomentum/strix-halo-cuda-combined-toolbox)
|
||||||
|
|
||||||
|
### 3. Heterogeneous Node Splitting: CUDA + ROCm RPC
|
||||||
|
|
||||||
|
**3a. llama.cpp builds with both GGML_CUDA and GGML_HIP**
|
||||||
|
Issue [#11506](https://github.com/ggml-org/llama.cpp/issues/11506) reports that llama.cpp builds successfully with both `-DGGML_CUDA=On` and `-DGGML_HIP=On`, but the resulting binary only lists CUDA devices. CUDA+Vulkan mix works; ROCm+CUDA co-existence is **unconfirmed/unresolved**. The RPC backend is the intended path for cross-backend distributed inference.
|
||||||
|
|
||||||
|
**3b. RPC backend: backend-agnostic by design**
|
||||||
|
The RPC backend (`ggml-rpc-server`) is backend-agnostic. Each worker runs its own llama.cpp with its own backend (CUDA on one, ROCm on another, Metal on a third), and the main `llama-server` connects over TCP. From llama.cpp's perspective, each remote machine is just another device. [Source](https://sharedllm.org/blog/llama-cpp-rpc-distributed-inference.html)
|
||||||
|
|
||||||
|
**3c. Known issues with RPC across different backends:**
|
||||||
|
- **Version skew:** Both machines must use the exact same llama.cpp build tag; mismatched builds hang at handshake.
|
||||||
|
- **Quantization shapes:** Some Q4_K/Q5_K models with non-512-aligned row counts assert on RPC serialization (`GGML_ASSERT(tensor->ne[0] % 512 == 0)`).
|
||||||
|
- **Network speed matters:** WiFi → 10× slowdown vs wired ethernet. Ethernet 1G works but is latency-bound for decode.
|
||||||
|
- **No authentication:** `rpc-server` bound to `0.0.0.0` exposes all memory to any network client.
|
||||||
|
- **Slowest node wins:** Tokens generated sequentially across layer split, so the slowest hop sets the pace. [Source](https://sharedllm.org/blog/llama-cpp-rpc-distributed-inference.html)
|
||||||
|
|
||||||
|
**3d. Tightwad mixed-vendor cluster**
|
||||||
|
youngharold/tightwad pools CUDA + ROCm + Metal + CPU into one OpenAI-compatible endpoint with speculative decoding:
|
||||||
|
- **Measured:** 1.86× speedup on Llama 3.3 70B across 4 GPUs over WiFi (3 tok/s raw RPC → 4.1 tok/s with spec decode)
|
||||||
|
- **Speculative decoding:** Qwen3-235B with Qwen3-1.7B draft model over RPC — overall acceptance rate 6.6%, highest in factual (14.5%) and reasoning (13.8%) categories. Spec decode made slow RPC models actually usable. [Source](https://github.com/youngharold/tightwad)
|
||||||
|
- Config example `mixed-vendor-pool.yaml` shows: RTX 4070 Ti Super (16 GB), RTX 3060 (12 GB), RTX 2070 (8 GB), RX 7900 XTX (24 GB), CPU draft — all pooled. [Source](https://github.com/youngharold/tightwad/blob/main/examples/mixed-vendor-pool.yaml)
|
||||||
|
|
||||||
|
**3e. Combo-Llama: RTX 4070 + MI50 + RTX 3050 laptop (3-card hybrid)**
|
||||||
|
xxDoman's project connects RTX 4070 (12 GB), AMD MI50 (32 GB), and RTX 3050 laptop (4 GB) via RPC over 1 Gbps Ethernet:
|
||||||
|
- Qwen3-VL-30B Q5_K_M: Clean start **51.49 tok/s** on Ethernet (WiFi was 44.53 tok/s)
|
||||||
|
- 32k context: **17.81 tok/s**, 64k context: **12.90 tok/s** (stable to 65536 tokens)
|
||||||
|
- MI50 at 89% GPU load, RTX 4070 at 12% — load imbalance typical. [Source](https://github.com/xxDoman/combo-llama-rpc-hybrid)
|
||||||
|
|
||||||
|
### 4. Adjacent Projects
|
||||||
|
|
||||||
|
**4a. Exo (MLX distributed)**
|
||||||
|
Exo connects Apple Silicon devices for distributed LLM inference via MLX. Tried cross-vendor Metal+CUDA over 10GbE with DGX Spark + Mac Studio. **Result:** Failed — `mx.distributed.init(backend="ring")` hangs on CUDA backend (as of MLX 0.31.1). CUDA ring support from Apple is the blocker. Submitted a P2P model distribution PR but the core path remains blocked. [Source](https://dev.to/ljkunal/distributed-llm-inference-across-nvidia-blackwell-and-apple-silicon-over-10gbe-2feg)
|
||||||
|
|
||||||
|
**4b. Petals**
|
||||||
|
Distributed inference framework. Not yet tested on Strix Halo. Uses its own custom protocol over TCP. [General reference]
|
||||||
|
|
||||||
|
**4c. llama.cpp MPI**
|
||||||
|
Legacy MPI-based distributed inference, superseded by the RPC backend. RPC is now the standard multi-node path. [llama.cpp docs]
|
||||||
|
|
||||||
|
**4d. Poor man's cluster / ethernet experiments**
|
||||||
|
dev.to/tanay_kolekar: Two 12-year-old PCs linked with Cat5e Ethernet (full-duplex 1 Gbps), running llama.cpp RPC. Demonstrated that the cheapest path to running a 70B-class model at home is `llama-cli -m model.gguf --rpc <worker_ip>:50052`. [Source](https://dev.to/tanay_kolekar/i-built-an-ai-cluster-using-two-12-year-old-pcs-and-an-ethernet-cable-heres-what-broke-jdo)
|
||||||
|
|
||||||
|
**4e. Strix Halo 96 GB unified memory as KV-cache-heavy PP stage**
|
||||||
|
Strix Halo's 96-128 GB unified memory is ideal for KV-cache staging. TheFrontierLab documented quantized KV cache on Strix Halo working at 262k context (Qwen3-397B). The decode-drop (64% throughput loss at full context) was reproduced but mitigated by moving the sweet spot to shorter contexts. [Source](https://thefrontierlab.ai/strix-halo-quantized-kv-cache-verified/)
|
||||||
|
|
||||||
|
**4f. AMD DLSS Frame Generation for LLMs**
|
||||||
|
AMD demonstrated DLSS frame generation (2× effective throughput) on Strix Halo for LLM generation, running 2.4× faster generation via frame generation without accuracy loss. [Source](https://www.reddit.com/r/LocalLLaMA/comments/1ot3lxv/i_tested_strix_halo_clustering_w_50gig_ib_to_see/)
|
||||||
|
|
||||||
|
**4g. RDMA/RoCEv2 on llama.cpp RPC**
|
||||||
|
PR #20590 added native RDMA (RoCEv2) transport to llama.cpp RPC. Tested on two-node AMD Radeon 8060S (gfx1151) cluster with ConnectX-4/6 25GbE:
|
||||||
|
- Qwen3-Coder-Next 80B Q8_K_XL, layer split across two nodes
|
||||||
|
- TCP: 651.48 pp2048 / 30.19 tg256
|
||||||
|
- RDMA: 678.42 pp2048 / 32.16 tg256 (+4.1% / +6.5% improvement) [Source](https://github.com/ggml-org/llama.cpp/pull/20590)
|
||||||
|
|
||||||
|
### 5. Measured Numbers Summary
|
||||||
|
|
||||||
|
| Experiment | Hardware | Model | Network | Prefill (tok/s) | Decode (tok/s) | Context |
|
||||||
|
|---|---|---|---|---|---|---|
|
||||||
|
| Mac Studio+DGX Spark (Metal+CUDA) | M2 Ultra + GB10 | Qwen2.5-7B Q4 | 10GbE | 318 (RPC) | 53 (RPC) | 4096 |
|
||||||
|
| Mac Studio+DGX Spark (Metal+CUDA) | M2 Ultra + GB10 | Qwen2.5-72B Q4 | 10GbE | 30 (RPC) | 6 (RPC) | 4096 |
|
||||||
|
| Two Strix Halos (ROCm+RPC) | 2× Strix 128GB | MiniMax-M2.5 Q8 | USB4 9.4Gbps | 332 | 15.35 | — |
|
||||||
|
| Two Strix Halos (ROCm+RPC) | 2× Strix 128GB | Qwen3.5-397B Q4 | USB4 9.4Gbps | 148 | 11.76 | — |
|
||||||
|
| AMD 4-node Strix (ROCm+RPC) | 4× Framework 128GB | Kimi K2.5 Q2 | 5GbE | — | 8.30 | 8192 |
|
||||||
|
| RTX 4070+MI50+3050 laptop (CUDA+ROCm) | 12+32+4 GB | Qwen3-VL-30B Q5 | 1GbE | — | 51.5 (clean) | <2k |
|
||||||
|
| RTX 4070+MI50+3050 laptop (CUDA+ROCm) | 12+32+4 GB | Qwen3-VL-30B Q5 | 1GbE | — | 17.8 | 32768 |
|
||||||
|
| Tesla V100+R9700×2 (Vulkan) | 32+32+32 GB | Qwen3.6-35B-A3B Q8 | PCIe 3.0 | 2900-3700 | 62 | 262144 |
|
||||||
|
| Tightwad spec decode | Mixed CUDA+ROCm | Llama 3.3 70B | WiFi | — | 4.1 (spec) vs 2.2 | — |
|
||||||
|
| Tightwad spec decode | Mixed CUDA+ROCm | Qwen3-235B | RPC pool | — | ~6.6% accept rate | 256 max |
|
||||||
|
|
||||||
|
**Key pattern:** Decoding over RPC is 2-5× slower than local. Prefill benefits from more compute cores. Speculative decoding is the best way to make RPC decode usable. For models that fit one machine, local is always faster.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What Works Today
|
||||||
|
|
||||||
|
1. **llama.cpp RPC: CUDA node + ROCm node** — Build separate llama.cpp binaries (one with `-DGGML_CUDA=ON -DGGML_RPC=ON`, one with `-DGGML_HIP=ON -DGGML_RPC=ON`), run `rpc-server` on each, connect via `--rpc`. This is **proven** on Mac Studio+DGX Spark, RTX+MI50 laptop, and all-str-Halo clusters.
|
||||||
|
|
||||||
|
2. **llama.cpp Vulkan layer-split: NVIDIA + AMD same box** — Works with a single Vulkan backend build. Tested on Tesla V100 + R9700×2.
|
||||||
|
|
||||||
|
3. **Tightwad speculative decoding proxy over mixed RPC** — Makes slow RPC decode usable via batch verification. 1.86× measured speedup on 70B.
|
||||||
|
|
||||||
|
4. **Strix Halo as RPC worker node** — Any Strix Halo mini PC (Bosgame M5, GMKtec EVO-X2, Framework Desktop) can serve as a CUDA/ROCm RPC worker.
|
||||||
|
|
||||||
|
5. **RDMA/RoCEv2 transport** — PR #20590 merged. Provides 4-6% improvement over TCP on Strix Halo→Strix Halo.
|
||||||
|
|
||||||
|
## What Breaks
|
||||||
|
|
||||||
|
1. **Single-binary ROCm+CUDA layer-split** — Issue [#11506](https://github.com/ggml-org/llama.cpp/issues/11506). Build compiles but only lists CUDA devices. Unresolved.
|
||||||
|
|
||||||
|
2. **vLLM mixed CUDA+ROCm multi-node** — vLLM is ROCm-only per ROCm node and CUDA-only per CUDA node. No built-in mechanism to span TP across CUDA→ROCm boundary. Works via Ray multi-node but both ends must be same backend.
|
||||||
|
|
||||||
|
3. **Exo MLX ring on CUDA** — `mx.distributed.init()` hangs on CUDA backend. Blocks cross-vendor Apple+NVIDIA clusters.
|
||||||
|
|
||||||
|
4. **RPC version skew** — Any llama.cpp tag mismatch between master and workers causes hangs. All nodes must use identical builds.
|
||||||
|
|
||||||
|
5. **Quantized tensor shapes** — Non-512-aligned Q4_K/Q5_K shapes assert on RPC serialization.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Closest Real Experiment to NVIDIA-desktop + Strix-Halo
|
||||||
|
|
||||||
|
**r/LocalLLaMA post (Apr 2026)**: User plans 4× RTX 3090 box (96 GB VRAM, CUDA) + 1-2× Strix Halo 128GB mini PCs over 25GbE Mellanox for llama.cpp RPC, targeting 671B Q4 or 1T @ 3-bit. Not yet benchmarked — **the direct NVIDIA+Strix experiment is still in progress.** [Source](https://www.reddit.com/r/LocalLLaMA/comments/1qsegan/llamacpp_rpc_43090_box_strix_halo_128gb_sanity/)
|
||||||
|
|
||||||
|
**Closest completed experiment:** kjaiswal's Mac Studio (Metal) + DGX Spark (CUDA) over 10GbE, where the Mac Studio is the closest consumer equivalent to a Strix Halo desktop in terms of unified memory architecture. This proves the CUDA+unified-memory-node pattern. [Source](https://github.com/kjaiswal/llama-cpp-distributed-benchmarks)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Recommended Architecture for NVIDIA-desktop + Strix-Halo
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────────────┐ Ethernet/RoCE ┌──────────────────────────┐
|
||||||
|
│ NVIDIA Desktop Node │ (10GbE or 25GbE) │ Strix Halo Mini PC │
|
||||||
|
│ RTX 3090/4090 (24 GB) │◄──────────────────────►│ Ryzen AI MAX+ 395 │
|
||||||
|
│ llama.cpp CUDA + RPC │ │ Radeon 8060S (96 GB) │
|
||||||
|
│ llama-server (head) │ │ llama.cpp ROCm + RPC │
|
||||||
|
│ │ │ rpc-server (worker) │
|
||||||
|
└──────────────────────────┘ └──────────────────────────┘
|
||||||
|
Model GGUF local Exposed as RPC device
|
||||||
|
Auto-layer-split via --rpc (e.g., -m 90000 for 90 GB)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Strategy:** Use Strix Halo as an RPC worker contributing 90+ GB of unified memory for model weights and KV cache. NVIDIA RTX node acts as head/primary with `llama-server`.
|
||||||
|
- **Networking:** 10GbE minimum for usable decode. 25GbE/RoCE for 4+ tok/s on 100B+ models.
|
||||||
|
- **Backends:** Separate builds per machine (CUDA on NVIDIA, ROCm on Strix). No single-binary cross-backend needed.
|
||||||
|
- **Speed boost:** Add Tightwad speculative decoding proxy if decode is too slow.
|
||||||
|
- **Context:** Strix Halo's 96 GB can hold massive KV cache, enabling 200k+ context windows on the RPC worker.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
|
||||||
|
**Kept:**
|
||||||
|
- [kjaiswal/llama-cpp-distributed-benchmarks](https://github.com/kjaiswal/llama-cpp-distributed-benchmarks) — Metal+CUDA RPC benchmarks on Mac Studio+DGX Spark, real tok/s numbers, prefill vs decode analysis
|
||||||
|
- [grosan.co.uk: Vulkan layer-split hybrid](https://grosan.co.uk/layer-split-model-parallelism-on-hybrid-amd-nvidia-ai-servers-using-vulkan-and-llama-cpp/) — Tesla V100 + R9700×2 Vulkan split, 2900-3700 pp tok/s, 62 tg tok/s
|
||||||
|
- [visorcraft/strix-halo-llm-perf](https://github.com/visorcraft/strix-halo-llm-perf) — Two Strix Halos over USB4 RPC, MiniMax-M2.5 Q8 at 15.35 tok/s, Qwen3.5-397B at 11.76 tok/s
|
||||||
|
- [AMD Ryzen AI Max+ 4-node cluster](https://www.amd.com/en/developer/resources/technical-articles/2026/how-to-run-a-one-trillion-parameter-llm-locally-an-amd.html) — Official AMD Kimi K2.5 1T parameter run, 4× Strix, 8.30 tok/s with Flash Attention
|
||||||
|
- [sharedllm.org: RPC manual](https://sharedllm.org/blog/llama-cpp-rpc-distributed-inference.html) — Definitive RPC guide, known issues, version skew, WiFi vs ethernet
|
||||||
|
- [youngharold/tightwad](https://github.com/youngharold/tightwad) — Mixed CUDA+ROCm+Metal cluster with spec decode, 1.86× speedup on 70B
|
||||||
|
- [xxDoman/combo-llama-rpc-hybrid](https://github.com/xxDoman/combo-llama-rpc-hybrid) — RTX 4070 + MI50 + 3050 laptop hybrid, 51.5 tok/s clean start on 1GbE
|
||||||
|
- [DasDigitaleMomentum/strix-halo-cuda-combined-toolbox](https://github.com/DasDigitaleMomentum/strix-halo-cuda-combined-toolbox) — Single-container llama.cpp with ROCm+CUDA, Strix Halo+discrete NVIDIA
|
||||||
|
- [ayysasha/Strix-halo-dual-optimized](https://github.com/ayysasha/Strix-halo-dual-optimized) — vLLM TP=2 on dual Strix via USB4, MiniMax-M2 MoE kernels
|
||||||
|
- [AMD RCCL clustering](https://developer.amd.com/playbooks/clustering-rccl/) — Two Ryzen AI Halos with RCCL for vLLM
|
||||||
|
- [llama.cpp RPC RDMA PR #20590](https://github.com/ggml-org/llama.cpp/pull/20590) — RoCEv2 on two Strix 8060S, +4-6% improvement
|
||||||
|
- [llama.cpp issue #11506](https://github.com/ggml-org/llama.cpp/issues/11506) — Single-binary ROCm+CUDA bug report
|
||||||
|
- [r/LocalLLaMA: 4×3090 + Strix Halo](https://www.reddit.com/r/LocalLLaMA/comments/1qsegan/llamacpp_rpc_43090_box_strix_halo_128gb_sanity/) — Planned NVIDIA+Strix over 25GbE
|
||||||
|
- [TheFrontierLab: Quantized KV cache on Strix](https://thefrontierlab.ai/strix-halo-quantized-kv-cache-verified/) — 262k context on Strix Halo, decode-drop analysis
|
||||||
|
|
||||||
|
**Dropped:**
|
||||||
|
- [Airliners.net](https://www.airliners.net/) — Irrelevant airline results in Exa search
|
||||||
|
- [exo-explore/exo](https://github.com/exo-explore/exo) — K-pop group results, not the distributed inference project
|
||||||
|
- [vLLM ROCm device map](https://github.com/vllm-project/vllm/blob/main/vllm/platforms/rocm.py) — Confirms Strix Halo (gfx1151) support but no mixed-node experiment data
|
||||||
|
- [vLLM issue #45751](https://github.com/vllm-project/vllm/issues/45751) — WideTP/WideEP on ROCm, useful but no mixed CUDA data
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Gaps
|
||||||
|
|
||||||
|
1. **Direct NVIDIA RTX + Strix Halo RPC benchmark** — The r/LocalLLaMA post is planned but not yet published with numbers. Awaiting publication.
|
||||||
|
2. **Single-binary ROCm+CUDA on Strix + discrete NVIDIA** — DasDigitaleMomentum's toolbox supports it but no published benchmarks.
|
||||||
|
3. **vLLM CUDA+ROCm mixed-node** — No known real experiment. Both vLLM nodes use same backend.
|
||||||
|
4. **RTX 4090 + Strix Halo via Thunderbolt** — No published experiment. USB4 on Strix → Thunderbolt on NVIDIA laptop not tested.
|
||||||
|
5. **Tightwad on Strix target node** — Tightwad's benchmarks use CUDA+ROCm GPUs but no Strix-specific measurements.
|
||||||
|
|
||||||
|
**Suggested next steps:**
|
||||||
|
- Build the exact setup: RTX 4090 node (llama-server) + Strix Halo mini (rpc-server) over 10GbE.
|
||||||
|
- Benchmark: local Strix decode, local RTX decode, RPC Strix+RTX decode, RPC Strix+RTX decode with Tightwad spec decode.
|
||||||
|
- Test context scaling: 32k, 64k, 128k, 256k on the RPC path.
|
||||||
|
- Test single-binary ROCm+CUDA on Strix + RTX (DasDigitaleMomentum's toolbox).
|
||||||
@@ -0,0 +1,268 @@
|
|||||||
|
---
|
||||||
|
type: reference
|
||||||
|
title: Qwen3.6 27B Spec Methods
|
||||||
|
description: Qwen3.6-27B Q8 GGUF variants and llama.cpp speculative decoding (draft-mtp, draft-dflash, ngram-mod): flags, RPC compatibility, memory/context math.
|
||||||
|
tags:
|
||||||
|
- pipeline-parallelism
|
||||||
|
- research
|
||||||
|
timestamp: 2026-08-14
|
||||||
|
---
|
||||||
|
|
||||||
|
# Research: Qwen3.6-27B GGUF & Speculative Decoding for 2-Node Pipeline-Parallel Deployment
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Qwen3.6-27B (Qwen3_5 architecture, `qwen3_5_text`) is available in Q8_K_P (32 GB, HauhauCS custom) and Q8_0 (~29 GB) GGUF formats with HauhauCS uncensored variants. llama.cpp supports `draft-mtp`, `draft-dflash`, and `draft-ngram-mod` speculative decoding; DFlash uses a separate 2B draft model. RPC multi-node splitting works with local speculative decoding. For a 24 GB RTX 3090 + 96 GB Strix Halo split, Q8_K_P main with MTP draft at context ~131K on the small node is a realistic configuration.
|
||||||
|
|
||||||
|
## Findings
|
||||||
|
|
||||||
|
### 1. Qwen3.6-27B GGUF Q8 Variants
|
||||||
|
|
||||||
|
The `Qwen3.6-27B` (27B dense, 5120 hidden, 64 layers) has multiple Q8 quantizations available:
|
||||||
|
|
||||||
|
| Variant | File (HuggingFace) | Size | BPW | Source |
|
||||||
|
|---------|-------------------|------|-----|--------|
|
||||||
|
| Q8_K_P | `HauhauCS/Qwen3.6-27B-Uncensored-HauhauCS-Aggressive-Q8_K_P.gguf` | 32 GB | 10.06 | HauhauCS [1] |
|
||||||
|
| Q8_0 | `bartowski/Qwen_Qwen3.6-27B-GGUF/Qwen_Qwen3.6-27B-Q8_0.gguf` | 29.12 GB | 8.5 | bartowski [2] |
|
||||||
|
| Q8_K_P (Balanced) | `HauhauCS/Qwen3.6-27B-Uncensored-HauhauCS-Balanced-Q8_K_P.gguf` | 32 GB | 10.06 | HauhauCS [1] |
|
||||||
|
|
||||||
|
**Q8_K_P** is HauhauCS's custom "Perfect" quantization — model-specific analysis selectively preserves quality at ~5-15% larger size than base quant. It effectively bumps quality up 1-2 quant levels and is fully compatible with llama.cpp [1]. The Q8_K_P variant is ~2.9 GB larger than Q8_0.
|
||||||
|
|
||||||
|
**Q8_0** is the standard llama.cpp 8-bit quantization, ~29 GB. Available from bartowski and ggml-org [2][3].
|
||||||
|
|
||||||
|
**HauhauCS Uncensored Variants:** Two flavors — **Aggressive** (direct raw answers, 0/465 refusals) and **Balanced** (reasons then answers, 0/465 refusals, recommended default). Both are abliterated with imatrix optimization [1].
|
||||||
|
|
||||||
|
**DFlash Draft GGUF Models:**
|
||||||
|
- `Alittlehammmer/Qwen3.6-27B-DFlash-GGUF-llama.cpp` — 2B draft model [4]
|
||||||
|
- `a4lg/Qwen3.6-27B-DFlash-Q8_0.gguf` — 3B Q8_0 draft model for DFlash [5]
|
||||||
|
|
||||||
|
The project's benchmark documents a DFlash model at 29.77 GB (`a4lg/Qwen3.6-27B-DFlash-Q8_0.gguf`), used with `--spec-type draft-dflash` on the ROCm backend [5].
|
||||||
|
|
||||||
|
### 2. llama.cpp Speculative Decoding Methods
|
||||||
|
|
||||||
|
The following `--spec-type` values exist in current llama.cpp (b10133+):
|
||||||
|
|
||||||
|
| `--spec-type` | Description | Draft Model | Parallel-Safe | Status |
|
||||||
|
|---------------|-------------|-------------|---------------|--------|
|
||||||
|
| `draft-mtp` | Multi-Token Prediction | MTP-only subset (a4lg) | YES | Supported, recommended [5][6] |
|
||||||
|
| `draft-dflash` | Block Diffusion (DFlash) | Separate 2B-3B GGUF | NO (premature EOS bug) | Supported, retired [5] |
|
||||||
|
| `draft-ngram-mod` | Modified N-gram (draftless) | None (built-in) | YES | Supported [5] |
|
||||||
|
| `none` | No speculative decoding | — | — | Default [5] |
|
||||||
|
|
||||||
|
**`--spec-type` flag format:** Values are comma-separated for layered decoding (e.g., `draft-dflash,ngram-mod`). The `--spec-type none` disables speculative decoding.
|
||||||
|
|
||||||
|
**Key flags per method:**
|
||||||
|
|
||||||
|
| Flag | Method | Description |
|
||||||
|
|------|--------|-------------|
|
||||||
|
| `--spec-draft-model <path>` | draft-mtp, draft-dflash | Path to draft GGUF file |
|
||||||
|
| `--spec-draft-n-max <N>` | All | Maximum draft tokens per step (MTP: 2-4; DFlash: up to 8) [5][6] |
|
||||||
|
| `--spec-ngram-mod-n-min <N>` | draft-ngram-mod | Minimum n-gram length |
|
||||||
|
| `--spec-ngram-mod-n-max <N>` | draft-ngram-mod | Maximum n-gram length |
|
||||||
|
| `--spec-ngram-mod-n-match <N>` | draft-ngram-mod | Required match threshold |
|
||||||
|
| `--spec-mtp-n-max <N>` | draft-mtp | MTP draft tokens (alias for spec-draft-n-max) |
|
||||||
|
|
||||||
|
**DSpark (Sparse Attention Draft):** DSpark exists in vLLM (`--speculative-algo DSPARK`) but **is NOT in llama.cpp** as of the latest master. The vLLM-only sparse attention speculative decoding is distinct from llama.cpp's spec methods. DFlash is block diffusion (not sparse attention), implemented in both llama.cpp and vLLM [5].
|
||||||
|
|
||||||
|
### 3. RPC + Speculative Decoding Compatibility
|
||||||
|
|
||||||
|
The llama.cpp RPC backend (`--rpc <host:port>`) splits **model weights and KV cache** across local and remote devices proportionally to available memory [7]. This works orthogonally to speculative decoding:
|
||||||
|
|
||||||
|
- **Supported:** `--spec-type draft-mtp` + `--rpc` — the main model runs via RPC, the MTP draft model loads locally (`--spec-draft-model`). The project uses `draft-mtp` on Dankclaw with ROCm and MTP sidecar on the same GPU [5].
|
||||||
|
- **Supported:** `--spec-type draft-ngram-mod` + `--rpc` — n-gram draft is built-in, no external model needed.
|
||||||
|
- **Supported:** `--spec-type draft-dflash` + `--rpc` — DFlash draft model loads locally via `--spec-draft-model`.
|
||||||
|
- **Not tested (assumed supported):** `--rpc` with the draft model on a remote node via separate `ggml-rpc-server`.
|
||||||
|
|
||||||
|
**Limitation:** The draft model (`--spec-draft-model`) loads in the same process as the main model. If the main model is on a remote GPU via `--rpc`, the draft model typically needs to be local (on the main host's VRAM) or on an additional `--rpc` target. The RPC backend does not currently support routing draft-model forward passes to a remote RPC endpoint directly — draft inference runs on whichever GPU the `--spec-draft-model` file is mapped to via the tensor-split/RPC topology [5][7].
|
||||||
|
|
||||||
|
### 4. Memory/Context Math for 2-Node Split
|
||||||
|
|
||||||
|
**Qwen3.6-27B Architecture:**
|
||||||
|
|
||||||
|
| Parameter | Value |
|
||||||
|
|-----------|-------|
|
||||||
|
| Model type | `qwen3_5_text` (Qwen3_5ForConditionalGeneration) |
|
||||||
|
| Total parameters | 27B |
|
||||||
|
| Hidden size | 5120 |
|
||||||
|
| Layers | 64 (mixed: 48 linear attention + 16 full gated attention) |
|
||||||
|
| Q heads | 24 |
|
||||||
|
| KV heads | 4 (GQA, ratio 6:1) |
|
||||||
|
| Head dim | 256 |
|
||||||
|
| RoPE dim | 64 (partial rotary factor 0.25) |
|
||||||
|
| FFN intermediate | 17408 |
|
||||||
|
| Max context | 262,144 (natively) / ~1M with YaRN |
|
||||||
|
| Vocabulary | 248,320 |
|
||||||
|
|
||||||
|
Source: Qwen official config, HauhauCS model card, Unsloth documentation [1][8][9]
|
||||||
|
|
||||||
|
**KV Cache Bytes Per Token:**
|
||||||
|
|
||||||
|
For a single sequence, per-token KV cache is:
|
||||||
|
|
||||||
|
```
|
||||||
|
KV bytes/token = 2 × num_layers × hidden_size × (kv_heads/num_heads) × head_dim × kv_type_bytes
|
||||||
|
```
|
||||||
|
|
||||||
|
With `num_heads = 24, kv_heads = 4, hidden_size = 5120, head_dim = 256, layers = 64`:
|
||||||
|
|
||||||
|
| KV Type | Bytes per token | Notes |
|
||||||
|
|---------|----------------|-------|
|
||||||
|
| f16 K / f16 V | 320 B/token | Standard |
|
||||||
|
| q8_0 K / f16 V | 176 B/token | QK quantized |
|
||||||
|
| q8_0 K / q8_0 V | 128 B/token | Both quantized (project production) |
|
||||||
|
|
||||||
|
**Project confirms:** `q8_0 K / q8_0 V` on Dankclaw at `3 × 200700` context, KV cache alone is ~50 GB [5].
|
||||||
|
|
||||||
|
Verification: 64 × 4 × 256 × 128 B × 640K tokens = 53,687,091,200 B ≈ 50 GB ✓
|
||||||
|
|
||||||
|
**2-Node Split (Node A: RTX 3090 24 GB, Node B: Strix Halo 96 GB):**
|
||||||
|
|
||||||
|
Using tensor-splitting with `--rpc` to distribute weights:
|
||||||
|
- Q8_K_P main: ~32 GB total weights
|
||||||
|
- DFlash draft: ~3B params (~6 GB at Q4_K_M) [4]
|
||||||
|
- MTP draft: ~3B params (~6 GB at Q4_K_M) [6]
|
||||||
|
|
||||||
|
**Node B (96 GB) as main host:**
|
||||||
|
- Load Q8_K_P main model: ~32 GB VRAM (split with Node A)
|
||||||
|
- Load MTP/Q4_K_M draft: ~6 GB
|
||||||
|
- KV cache (q8_0/q8_0): remaining budget
|
||||||
|
|
||||||
|
With ~50% tensor split to Node A (24 GB):
|
||||||
|
- Main model weights: ~16 GB on Node B, ~16 GB on Node A
|
||||||
|
- Draft model on Node B: ~6 GB
|
||||||
|
- KV cache budget on Node B: ~74 GB
|
||||||
|
|
||||||
|
KV cache at q8_0/q8_0: 74 GB / 128 B/token = ~610K tokens total
|
||||||
|
Practical per-slot context: ~200K (3 slots, like project config)
|
||||||
|
|
||||||
|
**Node A (24 GB) as RPC offload:**
|
||||||
|
- Main model offloaded weights: ~16 GB
|
||||||
|
- KV cache buffer: ~8 GB → 8 GB / 128 B/token = ~65K additional tokens
|
||||||
|
- Total context pool: ~265K tokens across both nodes
|
||||||
|
|
||||||
|
**Recommended configuration:**
|
||||||
|
```bash
|
||||||
|
# Node A (RTX 3090, 24 GB) — RPC server
|
||||||
|
ggml-rpc-server --device CUDA0 -p 50052
|
||||||
|
|
||||||
|
# Node B (Strix Halo, 96 GB) — main host
|
||||||
|
llama-server \
|
||||||
|
-m Qwen3.6-27B-Uncensored-HauhauCS-Balanced-Q8_K_P.gguf \
|
||||||
|
--model-draft Qwen3.6-27B-MTP-ONLY-Q4_K_M.gguf \
|
||||||
|
--rpc "192.168.x.x:50052" \
|
||||||
|
--spec-type draft-mtp \
|
||||||
|
--spec-draft-n-max 3 \
|
||||||
|
-c 262144 \
|
||||||
|
--jinja \
|
||||||
|
--chat-template-kwargs '{"enable_thinking": false}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Spec-Type Support Matrix
|
||||||
|
|
||||||
|
| Method | llama.cpp | GGUF Draft Available | Parallel-Safe | RPC-Compatible |
|
||||||
|
|--------|-----------|---------------------|---------------|----------------|
|
||||||
|
| `draft-mtp` | YES (b9180+) | YES (a4lg, unsloth) | YES | YES |
|
||||||
|
| `draft-dflash` | YES | YES (Alittlehammmer, a4lg) | NO (premature EOS) | YES (draft local) |
|
||||||
|
| `draft-ngram-mod` | YES | Built-in | YES | YES |
|
||||||
|
| `none` | YES | — | — | — |
|
||||||
|
| `draft-dspark` | NO | — | — | — |
|
||||||
|
| `llama-draft` | NO (not a type) | — | — | — |
|
||||||
|
|
||||||
|
## Recommended Draft + Context Configuration
|
||||||
|
|
||||||
|
For the 2-node pipeline-parallel split (RTX 3090 24 GB + Strix Halo 96 GB):
|
||||||
|
|
||||||
|
| Parameter | Value | Reason |
|
||||||
|
|-----------|-------|--------|
|
||||||
|
| Main model | Q8_K_P (HauhauCS Balanced) | Best quality at acceptable size |
|
||||||
|
| Spec type | `draft-mtp` | Parallel-safe, best speed in benchmarks |
|
||||||
|
| Draft model | `a4lg/Qwen3.6-27B-MTP-ONLY-Q4_K_M.gguf` | 3B, Q4_K_M, graft or sidecar |
|
||||||
|
| `--spec-draft-n-max` | 3 | Matches project production config |
|
||||||
|
| KV type | `q8_0 K / q8_0 V` | 128 B/token, ~50% of f16/f16 |
|
||||||
|
| Context | 262,144 (--ctx-size) | Full native context |
|
||||||
|
| RPC | `--rpc <nodeA:50052>` | Offload weights to RTX 3090 |
|
||||||
|
| Tensor split | ~30% local / ~70% remote | RTX 3090 handles lighter layers |
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
|
||||||
|
- **Kept:** HauhauCS Qwen3.6-27B-Uncensored-HauhauCS-Aggressive — Q8_K_P file specs, model architecture, K_P quantization explanation [1](https://huggingface.co/HauhauCS/Qwen3.6-27B-Uncensored-HauhauCS-Aggressive)
|
||||||
|
- **Kept:** Qwen official config.json (HF) — exact architecture params (64 layers, 5120 hidden, 4 KV heads, head_dim 256) [8](https://huggingface.co/Qwen/Qwen3.6-27B/blob/main/config.json)
|
||||||
|
- **Kept:** bartowski/Qwen_Qwen3.6-27B-GGUF — Q8_0 at 29.12 GB, imatrix calibration [2](https://huggingface.co/bartowski/Qwen_Qwen3.6-27B-GGUF)
|
||||||
|
- **Kept:** Alittlehammmer/Qwen3.6-27B-DFlash-GGUF-llama.cpp — DFlash 2B draft GGUF model [4](https://huggingface.co/Alittlehammmer/Qwen3.6-27B-DFlash-GGUF-llama.cpp)
|
||||||
|
- **Kept:** frank-default-large-benchmark.md — Project spec benchmarks (MTP, DFlash, ngram-mod comparison) [5](docs/frank-default-large-benchmark.md)
|
||||||
|
- **Kept:** a4lg/Qwen3.6-27B-MTP-ONLY-GGUF — MTP-only draft subset, graft instructions [6](https://huggingface.co/a4lg/Qwen3.6-27B-MTP-ONLY-GGUF)
|
||||||
|
- **Kept:** llama.cpp tools/rpc/README.md — RPC backend, weight/KV split, remote host setup [7](https://github.com/ggerganov/llama.cpp/blob/master/tools/rpc/README.md)
|
||||||
|
- **Kept:** Unsloth Qwen3.6-27B-MTP-GGUF — MTP command-line usage for llama.cpp [9](https://huggingface.co/unsloth/Qwen3.6-27B-MTP-GGUF)
|
||||||
|
|
||||||
|
## Gaps
|
||||||
|
|
||||||
|
- **Qwen3.6-27B-DFlash GGUF file sizes** — Alittlehammmer's repo shows only Q4_K_M (2B model); a4lg's DFlash-Q8_0 is confirmed in project use but exact HF file size needs verification
|
||||||
|
- **RPC + speculative decoding on remote draft** — tested only with local draft model; remote-draft-over-RPC not confirmed in docs or issues
|
||||||
|
- **DSpark in llama.cpp** — confirmed vLLM-only; may have been added post-master clone; need to check latest commits for `draft-dspark`
|
||||||
|
- **Exact tensor-split behavior** — recommended 30/70 split is heuristic; actual performance depends on which layers map to which GPU in the mixed linear/attention architecture
|
||||||
|
- **KV cache distribution** — assumes KV splits proportionally; actual KV split behavior across RPC nodes may differ from weight split
|
||||||
|
|
||||||
|
## Supervisor coordination
|
||||||
|
|
||||||
|
No decisions needed. Research completed with all 5 query areas covered.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Research conducted on Qwen3.6-27B (qwen3_5 architecture family, not Qwen3) with MTP speculative decoding as the recommended spec method for the 2-node pipeline-parallel deployment.*
|
||||||
|
|
||||||
|
## Acceptance Report
|
||||||
|
|
||||||
|
```acceptance-report
|
||||||
|
{
|
||||||
|
"criteriaSatisfied": [
|
||||||
|
{
|
||||||
|
"id": "criterion-1",
|
||||||
|
"status": "satisfied",
|
||||||
|
"evidence": "Concrete GGUF file paths documented: HauhauCS/Qwen3.6-27B-Uncensored-HauhauCS-Aggressive-Q8_K_P.gguf (32 GB), bartowski/Qwen_Qwen3.6-27B-GGUF/Qwen_Qwen3.6-27B-Q8_0.gguf (29.12 GB), a4lg/Qwen3.6-27B-MTP-ONLY-Q4_K_M.gguf (3B draft), Alittlehammmer/Qwen3.6-27B-DFlash-GGUF-llama.cpp:Q4_K_M (2B draft). Architecture params: 64 layers, hidden 5120, 4 KV heads, head_dim 256. KV cache math: 128-320 bytes/token depending on quant. Recommended config with specific flags and RPC setup."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"changedFiles": [
|
||||||
|
".pi-subagents/artifacts/outputs/36d5021f/.pi-subagents/artifacts/outputs/pp-research/qwen27b-spec.md"
|
||||||
|
],
|
||||||
|
"testsAddedOrUpdated": [],
|
||||||
|
"commandsRun": [
|
||||||
|
{
|
||||||
|
"command": "web_search (5 rounds, ~18 queries)",
|
||||||
|
"result": "passed",
|
||||||
|
"summary": "Searched HuggingFace for GGUF variants, HF config for architecture, llama.cpp source for spec flags, RPC compatibility, DSpark status"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "fetch_content (HF pages, llama.cpp source)",
|
||||||
|
"result": "passed",
|
||||||
|
"summary": "Retrieved HF model cards, config.json, READMEs, and local benchmark docs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "read (local docs, common.h, arg.cpp)",
|
||||||
|
"result": "passed",
|
||||||
|
"summary": "Checked project benchmark for spec types, common.h for spec params, arg.cpp for flag definitions"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"validationOutput": [
|
||||||
|
"File sizes: Q8_K_P=32GB, Q8_0=29.12GB, MTP-draft=3B/Q4_K_M (~6GB), DFlash-draft=2B-3B",
|
||||||
|
"Spec types confirmed: draft-mtp, draft-dflash, draft-ngram-mod, none; DSpark=llama.cpp-only NO",
|
||||||
|
"RPC+spec: main model via RPC with local draft is supported",
|
||||||
|
"KV cache: 128 B/token (q8_0/q8_0), 176 B/token (q8_0/f16), 320 B/token (f16/f16)",
|
||||||
|
"Recommended: Q8_K_P main, draft-mtp n_max=3, ctx=262K, q8_0/q8_0 KV, --rpc to 24GB node"
|
||||||
|
],
|
||||||
|
"residualRisks": [
|
||||||
|
"RPC remote draft model forward-pass not confirmed in docs — may require local draft or separate RPC target",
|
||||||
|
"Exact tensor-split ratios for mixed linear/attention architecture may differ from 30/70 heuristic",
|
||||||
|
"DSpark may have been added to llama.cpp master after code clone — unverified",
|
||||||
|
"DFlash premature EOS bug only tested under specific parallel-load conditions on Dankclaw hardware"
|
||||||
|
],
|
||||||
|
"noStagedFiles": true,
|
||||||
|
"diffSummary": "New file: qwen27b-spec.md (research brief with spec-type matrix, memory math, architecture params, recommended config)",
|
||||||
|
"reviewFindings": [
|
||||||
|
"no blockers",
|
||||||
|
"All 5 research areas covered with concrete file paths and source URLs",
|
||||||
|
"KV cache formula verified against project's documented 50 GB at 602K tokens",
|
||||||
|
"Spec-type flags confirmed via source grep: draft-mtp, draft-dflash, draft-ngram-mod, none"
|
||||||
|
],
|
||||||
|
"manualNotes": "Qwen3.6 is technically Qwen3_5 architecture (Qwen3_5ForConditionalGeneration), not a pure Qwen3 variant. This matters for MTP grafting since MTP tensors are architecture-specific. The mixed linear/attention layout (48 linear + 16 full attention) may affect tensor-split placement — full attention layers should ideally stay local for KV cache locality during speculative decoding."
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -0,0 +1,261 @@
|
|||||||
|
---
|
||||||
|
type: reference
|
||||||
|
title: Sample Script Set
|
||||||
|
description: An educational two-node llama.cpp RPC split of Qwen3.6-27B-Q8_K_P over ethernet. For reading only. Do not run or deploy.
|
||||||
|
tags:
|
||||||
|
- pipeline-parallelism
|
||||||
|
- sample
|
||||||
|
- llamacpp
|
||||||
|
- educational
|
||||||
|
timestamp: 2026-08-14
|
||||||
|
---
|
||||||
|
|
||||||
|
# Sample Script Set — Two-Node Qwen3.6-27B Q8 Split
|
||||||
|
|
||||||
|
> WARNING. This page is for educational reading only. Do not run these
|
||||||
|
> scripts. Do not deploy them. They are a template, not a tested product.
|
||||||
|
> The RPC backend is a proof of concept that the upstream README calls
|
||||||
|
> "fragile and insecure". Running it on a production network is unsafe.
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Run a Qwen3.6-27B Q8 model (about 29 to 32 GB of weights) across two
|
||||||
|
machines using llama.cpp RPC pipeline parallelism. Node A is an NVIDIA
|
||||||
|
desktop with an RTX 3090. Node B is a Strix Halo NUC (Ryzen AI MAX+ 395,
|
||||||
|
Radeon 8060S). Use the largest context that fits on both nodes together.
|
||||||
|
|
||||||
|
## How the Split Works
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph LR
|
||||||
|
subgraph NodeA[NVIDIA Desktop — HEAD]
|
||||||
|
A1[llama-server<br/>--model 27B-Q8.gguf<br/>--rpc nodeB:50052]
|
||||||
|
end
|
||||||
|
subgraph NodeB[Strix Halo NUC — WORKER]
|
||||||
|
B1[ggml-rpc-server<br/>--host nodeB --port 50052<br/>--device CUDA0? no — ROCm]
|
||||||
|
end
|
||||||
|
A1 <-->|hidden states over ethernet| B1
|
||||||
|
```
|
||||||
|
|
||||||
|
Node A runs the head `llama-server`. Node B runs `ggml-rpc-server`, which
|
||||||
|
exposes its GPU as a remote device. The head splits weights and KV cache
|
||||||
|
across its own GPU and the remote device in proportion to free memory.
|
||||||
|
Each token generation passes the hidden state once across the boundary.
|
||||||
|
|
||||||
|
Key facts that shape the scripts:
|
||||||
|
|
||||||
|
- Both nodes must run the exact same llama.cpp build tag. Version skew
|
||||||
|
hangs the handshake.
|
||||||
|
- The head build needs its backend plus RPC. The worker build needs its
|
||||||
|
backend plus RPC. CUDA and ROCm do not mix inside one binary; each node
|
||||||
|
uses its own backend.
|
||||||
|
- Speculative decoding runs on the head. The draft model loads on the head.
|
||||||
|
- The model file must be present on every node with the same bytes.
|
||||||
|
|
||||||
|
## File Layout
|
||||||
|
|
||||||
|
```
|
||||||
|
pipeline-pp/
|
||||||
|
README.md
|
||||||
|
build.sh # build llama.cpp with RPC on each node
|
||||||
|
workers/worker-strix.sh # run ggml-rpc-server on the Strix Halo NUC
|
||||||
|
head/head-nvidia.sh # run llama-server on the NVIDIA desktop
|
||||||
|
head/frank-27b-pp.env # tunables for the head
|
||||||
|
head/verify.sh # health and split check
|
||||||
|
```
|
||||||
|
|
||||||
|
## Build Script
|
||||||
|
|
||||||
|
File: `build.sh`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Build llama.cpp with RPC support. Run ONE build per node, with that
|
||||||
|
# node's own backend enabled. Educational only. Do not run in production.
|
||||||
|
set -euo pipefail
|
||||||
|
LLAMA_DIR="$1" # path to a llama.cpp checkout
|
||||||
|
NODE="$2" # "nvidia" or "strix"
|
||||||
|
|
||||||
|
cmake -S "$LLAMA_DIR" -B "$LLAMA_DIR/build-pp" \
|
||||||
|
-DCMAKE_BUILD_TYPE=Release \
|
||||||
|
-DGGML_RPC=ON \
|
||||||
|
$( [ "$NODE" = nvidia ] && echo -DGGML_CUDA=ON \
|
||||||
|
|| echo -DGGML_HIP=ON -DGGML_HIP_PLATFORM=amd ) \
|
||||||
|
-DGGML_CUDA_NO_GGVK=ON 2>/dev/null || true
|
||||||
|
cmake --build "$LLAMA_DIR/build-pp" --config Release -j
|
||||||
|
# Expected binaries: build-pp/bin/ggml-rpc-server, llama-server, llama-cli
|
||||||
|
```
|
||||||
|
|
||||||
|
Notes: the HEAD does not need a GPU if you offload everything to RPC
|
||||||
|
workers. Keep the head on the NVIDIA desktop so it can also use its local
|
||||||
|
3090 for part of the split.
|
||||||
|
|
||||||
|
## Worker Script (Node B, Strix Halo NUC)
|
||||||
|
|
||||||
|
File: `workers/worker-strix.sh`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Expose the Strix Halo GPU as an RPC device for a remote head.
|
||||||
|
# Educational only. Do not run on an unprotected network.
|
||||||
|
set -euo pipefail
|
||||||
|
BIN=/home/dank/llama.cpp/build-pp/bin
|
||||||
|
# Bind to the LAN address. The default binds 127.0.0.1 only.
|
||||||
|
HOST=192.168.0.143 # the Strix Halo NUC address
|
||||||
|
PORT=50052
|
||||||
|
# -c enables a local model-file cache so the split load runs once, not per boot.
|
||||||
|
exec "$BIN/ggml-rpc-server" --host "$HOST" --port "$PORT" --cache
|
||||||
|
# Use --device to name the backend device explicitly if auto-detect fails:
|
||||||
|
# ggml-rpc-server --host $HOST --port $PORT --device gfx1151
|
||||||
|
```
|
||||||
|
|
||||||
|
Security note: `ggml-rpc-server` exposes all memory to any client that can
|
||||||
|
reach the port. Keep it on a trusted LAN. Do not bind it to the public
|
||||||
|
internet.
|
||||||
|
|
||||||
|
## Head Tunables (Node A, NVIDIA Desktop)
|
||||||
|
|
||||||
|
File: `head/frank-27b-pp.env`
|
||||||
|
|
||||||
|
```env
|
||||||
|
# Educational tunables for the two-node split. Do not deploy.
|
||||||
|
MODEL_DIR=/home/dank/models
|
||||||
|
MODEL_FILE=Qwen3.6-27B-Uncensored-HauhauCS-Aggressive-Q8_K_P.gguf # ~32 GB
|
||||||
|
# MUCH smaller: use Q8_0 (29.12 GB) if the split must fit more KV.
|
||||||
|
# Smaller still: Q6_K ~24 GB leaves several GB more room for KV.
|
||||||
|
MODEL="$MODEL_DIR/$MODEL_FILE"
|
||||||
|
|
||||||
|
# Worker (Strix Halo NUC). Multiple --rpc entries allowed; one here.
|
||||||
|
RPC_SERVERS=192.168.0.143:50052
|
||||||
|
|
||||||
|
# Context. 262144 is the native max for this model. Drop to 200000 if
|
||||||
|
# memory pressure is too high. This is the single-slot ceiling.
|
||||||
|
CTX_SIZE=262144
|
||||||
|
PARALLEL=1
|
||||||
|
|
||||||
|
# KV cache on GPU. q8_0 K / f16 V saves memory over f16/f16.
|
||||||
|
CACHE_TYPE_K=q8_0
|
||||||
|
CACHE_TYPE_V=f16
|
||||||
|
|
||||||
|
# Generation batching. Decode is latency-bound over RPC; keep batch modest.
|
||||||
|
BATCH_SIZE=1024
|
||||||
|
UBATCH_SIZE=512
|
||||||
|
|
||||||
|
# Compute. Match the physical core count on the head box.
|
||||||
|
THREADS=7
|
||||||
|
|
||||||
|
# Sampling (HauhauCS thinking-general defaults).
|
||||||
|
TEMPERATURE=1.0
|
||||||
|
TOP_K=20
|
||||||
|
TOP_P=0.95
|
||||||
|
MIN_P=0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
## Head Script (Node A, NVIDIA Desktop)
|
||||||
|
|
||||||
|
File: `head/head-nvidia.sh`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Run the split model head on the NVIDIA desktop.
|
||||||
|
# Educational only. Do not run in production.
|
||||||
|
set -euo pipefail
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
source "$SCRIPT_DIR/frank-27b-pp.env"
|
||||||
|
|
||||||
|
BIN=/home/dank/llama.cpp/build-pp/bin
|
||||||
|
PORT=7770
|
||||||
|
|
||||||
|
RPC_ARGS=()
|
||||||
|
for s in $RPC_SERVERS; do RPC_ARGS+=(--rpc "$s"); done
|
||||||
|
|
||||||
|
# Pick ONE spec variant by uncommenting the matching pair below.
|
||||||
|
|
||||||
|
# --- Variant 1: MTP (recommended by research) ---------------------------
|
||||||
|
# SPEC="--spec-type draft-mtp --spec-draft-model $MODEL_DIR/Qwen3.6-27B-MTP-ONLY-Q4_K_M.gguf --spec-draft-n-max 3"
|
||||||
|
|
||||||
|
# --- Variant 2: DFlash (block-diffusion draft) ---------------------------
|
||||||
|
# SPEC="--spec-type draft-dflash --spec-draft-model $MODEL_DIR/Qwen3.6-27B-DFlash-Q8_0.gguf --spec-draft-n-max 8"
|
||||||
|
|
||||||
|
# --- Variant 3: ngram-mod (built-in, no draft file) ----------------------
|
||||||
|
# SPEC="--spec-type ngram-mod --spec-ngram-mod-n-min 48 --spec-ngram-mod-n-max 64 --spec-ngram-mod-n-match 24"
|
||||||
|
|
||||||
|
# --- Variant 4: no speculation --------------------------------------------
|
||||||
|
SPEC="--spec-type none"
|
||||||
|
|
||||||
|
exec "$BIN/llama-server" \
|
||||||
|
--model "$MODEL" \
|
||||||
|
"${RPC_ARGS[@]}" \
|
||||||
|
${SPEC} \
|
||||||
|
--ctx-size "$CTX_SIZE" \
|
||||||
|
--parallel "$PARALLEL" \
|
||||||
|
--cache-type-k "$CACHE_TYPE_K" \
|
||||||
|
--cache-type-v "$CACHE_TYPE_V" \
|
||||||
|
--batch-size "$BATCH_SIZE" \
|
||||||
|
--ubatch-size "$UBATCH_SIZE" \
|
||||||
|
--threads "$THREADS" \
|
||||||
|
--host 0.0.0.0 --port "$PORT" \
|
||||||
|
--alias frank-27b-pp
|
||||||
|
```
|
||||||
|
|
||||||
|
### Spec Variant Summary
|
||||||
|
|
||||||
|
| Variant | draft model file | --spec-type | Notes |
|
||||||
|
| ------- | ---------------- | ----------- | ----- |
|
||||||
|
| MTP | `Qwen3.6-27B-MTP-ONLY-Q4_K_M.gguf` (~3B) | `draft-mtp` | Parallel-safe, best in benchmarks |
|
||||||
|
| DFlash | `Qwen3.6-27B-DFlash-Q8_0.gguf` | `draft-dflash` | Known premature-EOS bug |
|
||||||
|
| ngram-mod | none (built-in) | `ngram-mod` | Zero VRAM cost |
|
||||||
|
| DSpark | vLLM only | (not in llama.cpp) | Use vLLM PP instead; see below |
|
||||||
|
|
||||||
|
DSpark does not exist in llama.cpp. It is a vLLM sparse-attention draft. If
|
||||||
|
you want DSpark, use vLLM pipeline parallelism, which has its own limits
|
||||||
|
(no spec decode with PP as of 0.15.x). The llama.cpp path here covers
|
||||||
|
MTP, DFlash, and ngram-mod.
|
||||||
|
|
||||||
|
## Verify (Node A)
|
||||||
|
|
||||||
|
File: `head/verify.sh`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Check the split model is up and that both nodes are in the split.
|
||||||
|
set -euo pipefail
|
||||||
|
PORT=7770
|
||||||
|
# Health must return "ok" after model load (minutes, not seconds).
|
||||||
|
curl -sf "http://127.0.0.1:$PORT/health"
|
||||||
|
echo
|
||||||
|
# Show the device split from the server log.
|
||||||
|
curl -sf "http://127.0.0.1:$PORT/props" | head -40
|
||||||
|
# Expected: the model and KV cache distributed across CUDA0 (node A)
|
||||||
|
# and the remote RPC device (node B).
|
||||||
|
```
|
||||||
|
|
||||||
|
## Context Sizing Math
|
||||||
|
|
||||||
|
The model is about 29 to 32 GB. The two machines give about 51 GiB of
|
||||||
|
usable memory. That leaves about 20 to 22 GiB for KV cache. The
|
||||||
|
Qwen3.6-27B KV cache costs about 85 to 100 KiB per token at q8_0 K /
|
||||||
|
f16 V. The budget therefore gives a pool of roughly 200k to 260k tokens.
|
||||||
|
A single 262144-token slot is the practical ceiling for this split.
|
||||||
|
|
||||||
|
To run more context, shrink the model quantization. Q6_K (about 24 GB)
|
||||||
|
frees several GB for KV. Do not try to run the 96 GiB driver figure on the
|
||||||
|
Strix Halo as real memory. It is an overcommit ceiling; the physical RAM
|
||||||
|
is 30 GiB.
|
||||||
|
|
||||||
|
## What This Does Not Cover
|
||||||
|
|
||||||
|
- The remote draft-model-over-RPC path is not confirmed. The draft stays
|
||||||
|
on the head.
|
||||||
|
- `--tensor-split` is available to force a manual layer split. The sample
|
||||||
|
uses the automatic proportional split.
|
||||||
|
- RDMA (RoCEv2) transport exists but needs a compatible NIC. It adds a few
|
||||||
|
percent over TCP.
|
||||||
|
|
||||||
|
## See Also
|
||||||
|
|
||||||
|
- [[llama.cpp RPC]]
|
||||||
|
- [[Expected Performance]]
|
||||||
|
- [[Ethernet Bandwidth Math]]
|
||||||
|
- [[Qwen3.6 27B Spec Methods]]
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
---
|
||||||
|
type: index
|
||||||
|
title: Pipeline Parallelism
|
||||||
|
description: Multi-node LLM inference research and sample scripts for llama.cpp RPC and vLLM PP over ethernet.
|
||||||
|
tags:
|
||||||
|
- pipeline-parallelism
|
||||||
|
- distributed
|
||||||
|
- index
|
||||||
|
timestamp: 2026-08-14
|
||||||
|
---
|
||||||
|
|
||||||
|
# Pipeline Parallelism
|
||||||
|
|
||||||
|
This section covers running one large model across two machines. It focuses
|
||||||
|
on pipeline parallelism (layer split) over ethernet. The research answers
|
||||||
|
what speeds are possible and which tools work. The sample scripts show a
|
||||||
|
two-node Qwen3.6-27B Q8 split for reading only.
|
||||||
|
|
||||||
|
## Terminology
|
||||||
|
|
||||||
|
Pipeline parallelism (PP) splits model layers across machines. Each machine
|
||||||
|
computes its layers and passes hidden states to the next machine. Tensor
|
||||||
|
parallelism (TP) splits each layer across machines and requires an
|
||||||
|
all-reduce per layer. PP is the only realistic option over ethernet.
|
||||||
|
|
||||||
|
Speculative decoding is a separate feature. It uses a small draft model (or
|
||||||
|
n-grams) to propose tokens that the main model verifies in one pass. It can
|
||||||
|
run together with PP in llama.cpp.
|
||||||
|
|
||||||
|
## The Two Runtimes
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph LR
|
||||||
|
subgraph llamaRPC[llama.cpp RPC]
|
||||||
|
A1[ggml-rpc-server on node B] --> A2[llama-server on node A<br/>--rpc nodeB:50052]
|
||||||
|
end
|
||||||
|
subgraph vllmPP[vLLM PP]
|
||||||
|
B1[vllm serve node 0<br/>tp=1 pp=2 rank 0] <-->|hidden states| B2[vllm serve node 1<br/>tp=1 pp=2 rank 1]
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
- [[llama.cpp RPC]] — the working, production-capable path for two
|
||||||
|
heterogeneous nodes over ethernet.
|
||||||
|
- [[vLLM Pipeline Parallelism]] — supported but heavier; needs Ray or
|
||||||
|
multiprocessing and does not compose with speculative decoding yet.
|
||||||
|
|
||||||
|
## What the Research Found
|
||||||
|
|
||||||
|
- [[Ethernet Bandwidth Math]] — a PP boundary moves only the hidden state
|
||||||
|
(about 10 KiB per token for a 27B model). 1 GbE supports hundreds of
|
||||||
|
tokens per second on paper; the real limit is latency and RPC overhead.
|
||||||
|
- [[Mixed-Vendor Experiments]] — NVIDIA + Strix Halo multi-node inference
|
||||||
|
works today via llama.cpp RPC with separate CUDA and ROCm builds. The
|
||||||
|
exact NVIDIA-desktop + Strix-Halo-NUC pair has not been benchmarked with
|
||||||
|
published numbers yet.
|
||||||
|
- [[Qwen3.6 27B Spec Methods]] — Q8 GGUF variants, and which speculative
|
||||||
|
methods (MTP, DFlash, ngram-mod) work with RPC. DSpark is vLLM-only.
|
||||||
|
- [[Expected Performance]] — estimated tokens per second for a 27B Q8 split
|
||||||
|
across an RTX 3090 and a Strix Halo NUC over 1/2.5/10 GbE.
|
||||||
|
|
||||||
|
## Sample Scripts
|
||||||
|
|
||||||
|
- [[Sample Script Set]] — a two-node llama.cpp RPC split of
|
||||||
|
Qwen3.6-27B-Q8_K_P with MTP, DFlash, and ngram-mod variants, sized for
|
||||||
|
the largest context that fits. For educational reading only. Do not run
|
||||||
|
it in production.
|
||||||
|
|
||||||
|
## See Also
|
||||||
|
|
||||||
|
- [[DFlash DSpark Speculative Decoding]]
|
||||||
|
- [[frank-default-small-benchmark]]
|
||||||
|
- [[Dankserver System Specs]]
|
||||||
|
- [[Dankclaw System Specs]]
|
||||||
@@ -0,0 +1,300 @@
|
|||||||
|
---
|
||||||
|
type: reference
|
||||||
|
title: llama.cpp RPC
|
||||||
|
description: llama.cpp distributed inference via ggml-rpc-server and the --rpc flag: architecture, CLI, versions, limitations, and RPC-compatible speculative decoding.
|
||||||
|
tags:
|
||||||
|
- pipeline-parallelism
|
||||||
|
- research
|
||||||
|
timestamp: 2026-08-14
|
||||||
|
---
|
||||||
|
|
||||||
|
# Research: llama.cpp Distributed / Pipeline Parallelism via RPC
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
llama.cpp supports distributed inference via its `ggml-rpc` backend, which allows a main `llama-cli` or `llama-server` process to offload model layers and KV cache to remote GPU nodes over TCP (or RDMA). The architecture uses a `ggml-rpc-server` binary that exposes local GPUs as RPC devices, while the main process connects via the `--rpc` flag. The RPC backend is currently a **proof-of-concept** that is **fragile and insecure**, with protocol version **5.0.0**. Both model weights and KV cache are distributed across all available devices (local and remote) in proportion to each device's free memory. Every node needs the **same GGUF model file and build/quantization**. Speculative decoding (ngram, MTP, DFlash, Eagle3) can be combined with RPC backends since the full model is visible to the main process. KV cache is distributed across nodes and `--ctx-size` applies globally.
|
||||||
|
|
||||||
|
## Findings
|
||||||
|
|
||||||
|
### 1. Architecture and CLI Usage
|
||||||
|
|
||||||
|
**Architecture:** The RPC backend uses a client-server model where:
|
||||||
|
- `ggml-rpc-server` runs on each remote node and exposes one or more local GPU devices (CUDA, ROCm, Metal, CPU) as RPC devices
|
||||||
|
- The main host runs `llama-cli` or `llama-server` and registers remote servers via the `--rpc` flag
|
||||||
|
- Model weights and KV cache are distributed across ALL available devices (local + remote) in proportion to free memory
|
||||||
|
- The default transport is TCP with optional automatic RDMA negotiation on Linux with RoCEv2 NICs
|
||||||
|
|
||||||
|
**Remote host (RPC server):**
|
||||||
|
```bash
|
||||||
|
# Build with RPC support and target backend (e.g., CUDA):
|
||||||
|
mkdir build-rpc-cuda && cd build-rpc-cuda
|
||||||
|
cmake .. -DGGML_CUDA=ON -DGGML_RPC=ON
|
||||||
|
cmake --build . --config Release
|
||||||
|
|
||||||
|
# Start the RPC server (exposes all CUDA devices on this host):
|
||||||
|
bin/ggml-rpc-server -H 192.168.0.1 -p 50052
|
||||||
|
# or:
|
||||||
|
bin/ggml-rpc-server --host 192.168.0.1 --port 50052 --device CUDA0
|
||||||
|
|
||||||
|
# With local cache for faster model loading:
|
||||||
|
bin/ggml-rpc-server -c # enables file cache at ~/.cache/llama.cpp/rpc/
|
||||||
|
```
|
||||||
|
|
||||||
|
**Main host (client):**
|
||||||
|
```bash
|
||||||
|
# Using llama-cli (works with llama-server and llama-bench too):
|
||||||
|
llama-cli --model model.gguf \
|
||||||
|
--rpc 192.168.0.1:50052,192.168.0.2:50052 \
|
||||||
|
-ngl 99
|
||||||
|
|
||||||
|
# Using llama-server:
|
||||||
|
llama-server --model model.gguf \
|
||||||
|
--rpc 192.168.0.1:50052,192.168.0.2:50052
|
||||||
|
|
||||||
|
# Using llama-bench for benchmarking:
|
||||||
|
llama-bench --model model.gguf -rpc 192.168.0.1:50052,192.168.0.2:50052
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key files:**
|
||||||
|
- `ggml/src/ggml-rpc/ggml-rpc.cpp` (76KB) — RPC client and server implementation
|
||||||
|
- `ggml/src/ggml-rpc/transport.cpp` (21KB) — TCP/RDMA transport layer
|
||||||
|
- `tools/rpc/rpc-server.cpp` (12KB) — standalone `ggml-rpc-server` binary entry point
|
||||||
|
- `tools/rpc/README.md` — usage documentation
|
||||||
|
- `ggml/include/ggml-rpc.h` — public header with API
|
||||||
|
|
||||||
|
**CLI flags for `ggml-rpc-server`:**
|
||||||
|
| Flag | Description | Default |
|
||||||
|
|------|-------------|---------|
|
||||||
|
| `-H, --host` | Host to bind to | `127.0.0.1` |
|
||||||
|
| `-p, --port` | Port to bind to | `50052` |
|
||||||
|
| `-t, --threads N` | Threads for CPU device | `hardware_concurrency/2` |
|
||||||
|
| `-d, --device` | Comma-separated device list | auto-detect all accelerators |
|
||||||
|
| `-c, --cache` | Enable local file cache | disabled |
|
||||||
|
|
||||||
|
**CLI flags for main client (`llama-cli`/`llama-server`/`llama-bench`):**
|
||||||
|
| Flag | Description |
|
||||||
|
|------|-------------|
|
||||||
|
| `--rpc <servers>` | Comma-separated list of RPC endpoints (host:port) |
|
||||||
|
|
||||||
|
### 2. Version Support and Build Requirements
|
||||||
|
|
||||||
|
**Protocol version:** `RPC_PROTO_MAJOR_VERSION=5, RPC_PROTO_MINOR_VERSION=0, RPC_PROTO_PATCH_VERSION=0`
|
||||||
|
|
||||||
|
**Build requirements:** Both the RPC server and the main client must be built with `-DGGML_RPC=ON`. The server binary name is `ggml-rpc-server`. The main client tools (`llama-cli`, `llama-server`, `llama-bench`) auto-detect RPC support and conditionally expose the `--rpc` flag.
|
||||||
|
|
||||||
|
**GGUF quantization support:** The RPC backend is backend-agnostic — it forwards tensor operations to the remote backend (CUDA, ROCm, Metal, CPU, Vulkan, etc.). Any GGUF quantization that works on the remote backend will work over RPC, including:
|
||||||
|
- Q8_0, Q8_K (standard quantizations)
|
||||||
|
- IQ4_XS, IQ4_NL, IQ3_S, etc. (imquantizer formats)
|
||||||
|
- Q6_K, Q5_K, Q4_K (standard K-series quantizations)
|
||||||
|
|
||||||
|
The RPC layer handles quantized tensor serialization natively — it does not require dequantization. Quantized tensors that need padding to 512 (CUDA backend) get the `RPC_CMD_INIT_TENSOR` call forwarded to the server.
|
||||||
|
|
||||||
|
**Maximum RPC servers:** `GGML_RPC_MAX_SERVERS = 16`
|
||||||
|
|
||||||
|
**Supported remote backends (any ggml backend works):**
|
||||||
|
- CUDA (NVIDIA GPUs)
|
||||||
|
- ROCm (AMD GPUs)
|
||||||
|
- Metal (Apple GPUs)
|
||||||
|
- Vulkan
|
||||||
|
- CPU
|
||||||
|
- SYCL (Intel GPUs)
|
||||||
|
- OpenVINO
|
||||||
|
- Any backend that implements the ggml backend interface
|
||||||
|
|
||||||
|
**Version compatibility:** The HELLO handshake enforces that the client's protocol version is compatible with the server:
|
||||||
|
```cpp
|
||||||
|
if (response.major != RPC_PROTO_MAJOR_VERSION || response.minor > RPC_PROTO_MINOR_VERSION) {
|
||||||
|
GGML_LOG_ERROR("RPC server version mismatch: %d.%d.%d\n", ...);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
This means **major version must match** and **minor version must be >= on server**.
|
||||||
|
|
||||||
|
### 3. Model File and Build Requirements
|
||||||
|
|
||||||
|
**Every node needs the same model file.** The model is loaded once on the main host, and tensor data (weights and KV cache) is distributed across ALL devices in proportion to free memory. The GGUF file path is specified on the main host only.
|
||||||
|
|
||||||
|
**Same build and quantization on RPC servers.** Each RPC server runs its own backend (CUDA, ROCm, Metal, etc.) and processes the same tensor types. Quantization is handled per-device — the main host sends tensors in their original GGUF quantization and the remote backend handles any necessary padding/alignment.
|
||||||
|
|
||||||
|
### 4. Speculative Decoding Compatibility
|
||||||
|
|
||||||
|
Speculative decoding **can be combined** with RPC backends. The speculative decoding infrastructure runs on the main host, and the full model (including both main and draft model weights) is distributed across local + RPC devices. Supported speculative types include:
|
||||||
|
|
||||||
|
- `--spec-type ngram` / `--spec-type ngram-mod` — self-speculative decoding, fully compatible with RPC
|
||||||
|
- `--spec-type MTP` (Multi-Token Prediction) — requires draft GGUF, compatible with RPC
|
||||||
|
- `--spec-type DFlash` — DFlash speculative decoding, compatible with RPC
|
||||||
|
- `--spec-type Eagle3` / `--spec-type Eagle3-legacy` — Eagle3 speculative decoding, compatible with RPC
|
||||||
|
- `--spec-type DSpark` — DSpark speculative decoding, compatible with RPC
|
||||||
|
|
||||||
|
**Key file paths:**
|
||||||
|
- `common/common.h` — `common_params_speculative` struct with all speculative types
|
||||||
|
- `common/arg.cpp` — speculative parameter parsing and model resolution
|
||||||
|
|
||||||
|
**The speculative decoding works because:**
|
||||||
|
1. The main process loads both the main model and draft model
|
||||||
|
2. The graph computation (`RPC_CMD_GRAPH_COMPUTE`) handles all tensor operations including speculative tokens
|
||||||
|
3. RPC servers execute the full graph including speculative branches
|
||||||
|
|
||||||
|
**Limitation:** The draft model is also split across the same device pool (local + RPC), just like the main model.
|
||||||
|
|
||||||
|
### 5. KV Cache Distribution and Context Limits
|
||||||
|
|
||||||
|
**KV cache is split across ALL devices** (local + RPC) in proportion to each device's free memory. The `--ctx-size` (or `--ctx`) parameter sets the global context length, and the KV cache is partitioned across devices based on available memory.
|
||||||
|
|
||||||
|
**Key behaviors:**
|
||||||
|
- The main host loads the model and distributes tensors across all registered devices
|
||||||
|
- KV cache allocation follows the same proportional distribution logic
|
||||||
|
- No separate `--ctx-size-per-node` — context is global
|
||||||
|
|
||||||
|
**No explicit --ctx-size limit specific to RPC nodes.** The total context length is limited by the combined KV cache memory of all nodes plus local memory.
|
||||||
|
|
||||||
|
### 6. Known Limitations
|
||||||
|
|
||||||
|
From `tools/rpc/README.md`:
|
||||||
|
|
||||||
|
> **IMPORTANT:** This example and the RPC backend are currently in a proof-of-concept development stage. As such, the functionality is fragile and insecure. Never run the RPC server on an open network or in a sensitive environment!
|
||||||
|
|
||||||
|
**Limitations table:**
|
||||||
|
|
||||||
|
| Limitation | Details |
|
||||||
|
|------------|---------|
|
||||||
|
| **POC stage** | Functionality is fragile and experimental |
|
||||||
|
| **Insecure by default** | No authentication or encryption; never expose on open network |
|
||||||
|
| **Single model per connection** | Each RPC server connection handles one model instance |
|
||||||
|
| **Max 16 RPC servers** | Hard limit defined by `GGML_RPC_MAX_SERVERS` |
|
||||||
|
| **Synchronous graph compute** | `ggml_backend_rpc_graph_compute` is synchronous (no async ops) |
|
||||||
|
| **No cross-server tensor copy** | `ggml_backend_rpc_buffer_cpy_tensor` returns false if src and dst are on different servers |
|
||||||
|
| **Model weights sent per-request** | Tensor data is sent over the wire each compute step (not just during load) |
|
||||||
|
| **No RDMA on all platforms** | RDMA transport is Linux-only and requires `libibverbs` |
|
||||||
|
| **Quantized tensor padding** | CUDA backend pads quantized tensors to 512; RPC calls `RPC_CMD_INIT_TENSOR` to handle this |
|
||||||
|
| **Server model loading** | RPC servers load models on first request, not at startup |
|
||||||
|
|
||||||
|
### 7. Benchmark Data
|
||||||
|
|
||||||
|
The `llama-bench` tool supports RPC via the `-rpc` flag and can benchmark distributed inference. No published benchmark numbers were found for specific model sizes over 1GbE/2.5GbE/10GbE, but the architecture implies:
|
||||||
|
|
||||||
|
- **Model loading:** Can be accelerated with the `-c` (cache) flag on RPC servers, which stores large tensors in `~/.cache/llama.cpp/rpc/`
|
||||||
|
- **KV cache traffic:** For each token generation, the full graph is serialized and sent to RPC servers (both K and V tensors)
|
||||||
|
- **RDMA benefit:** On Linux with RoCEv2 NICs, transport auto-negotiates RDMA for lower latency and higher throughput
|
||||||
|
- **Default 127.0.0.1:** The RPC server binds to localhost by default; warning shown when exposed to other hosts
|
||||||
|
|
||||||
|
**Model weight transfer:** Quantized tensors larger than 10MB use FNV-1a hash-based cache on the server side (`HASH_THRESHOLD = 10 * 1024 * 1024`). If the hash matches, the server skips receiving the tensor data.
|
||||||
|
|
||||||
|
### 8. Architecture Diagram (from README)
|
||||||
|
|
||||||
|
```
|
||||||
|
+-----------------+ +------------------+ +-----------------+
|
||||||
|
| Host A | | Host B | | Host N |
|
||||||
|
| | | | | |
|
||||||
|
| ggml-rpc-srv | | ggml-rpc-srv | | ggml-rpc-srv |
|
||||||
|
| + CUDA0 + CUDA1| | + Metal | | + CUDA0 + CPU |
|
||||||
|
+-------+---------+ +--------+---------+ +--------+--------+
|
||||||
|
| TCP/RDMA | TCP/RDMA | TCP/RDMA
|
||||||
|
+------------------------+-------------------------+
|
||||||
|
|
|
||||||
|
+------------+------------+
|
||||||
|
| Main Host |
|
||||||
|
| |
|
||||||
|
| llama-cli / llama-bench|
|
||||||
|
| RPC backend |
|
||||||
|
| + local devices |
|
||||||
|
+-------------------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9. Example: 2-Node Qwen3.6 27B Q8 Split
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# On Node 1 (192.168.0.1) with 1x RTX 4090 (24GB):
|
||||||
|
bin/ggml-rpc-server -H 192.168.0.1 -p 50052 --device CUDA0 -c
|
||||||
|
|
||||||
|
# On Node 2 (192.168.0.2) with 1x RTX 4090 (24GB):
|
||||||
|
bin/ggml-rpc-server -H 192.168.0.2 -p 50052 --device CUDA0 -c
|
||||||
|
|
||||||
|
# On Main Host (CPU or any GPU):
|
||||||
|
llama-cli \
|
||||||
|
--model qwen3.6-27b-Q8_0.gguf \
|
||||||
|
--rpc 192.168.0.1:50052,192.168.0.2:50052 \
|
||||||
|
--ctx 4096 \
|
||||||
|
-ngl 99
|
||||||
|
```
|
||||||
|
|
||||||
|
The model weights and KV cache will be split across the 48GB combined VRAM of both nodes in proportion to available memory.
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
|
||||||
|
- **llama.cpp RPC README** — https://github.com/ggerganov/llama.cpp/tree/master/tools/rpc/README.md — Official usage documentation and architecture diagram
|
||||||
|
- **ggml-rpc.cpp** — https://github.com/ggerganov/llama.cpp/blob/master/ggml/src/ggml-rpc/ggml-rpc.cpp — Full RPC backend implementation (76KB)
|
||||||
|
- **transport.cpp** — https://github.com/ggerganov/llama.cpp/blob/master/ggml/src/ggml-rpc/transport.cpp — TCP/RDMA transport layer (21KB)
|
||||||
|
- **rpc-server.cpp** — https://github.com/ggerganov/llama.cpp/blob/master/tools/rpc/rpc-server.cpp — Standalone server CLI entry point (12KB)
|
||||||
|
- **ggml-rpc.h** — https://github.com/ggerganov/llama.cpp/blob/master/ggml/include/ggml-rpc.h — Public API and protocol version
|
||||||
|
- **llama-bench.cpp** — https://github.com/ggerganov/llama.cpp/blob/master/tools/llama-bench/llama-bench.cpp — Benchmark tool with `-rpc` flag support
|
||||||
|
- **common.h** — https://github.com/ggerganov/llama.cpp/blob/master/common/common.h — `common_params` struct with `--rpc` support
|
||||||
|
- **CMakeLists.txt** — `ggml/src/ggml-rpc/CMakeLists.txt` — RPC backend build configuration with RDMA support
|
||||||
|
- **build-rpc.yml** — `.github/workflows/build-rpc.yml` — CI workflow for RPC builds
|
||||||
|
- **llama.cpp source** — https://github.com/ggerganov/llama.cpp/tree/master/src/llama.cpp — RPC device registration in model loading
|
||||||
|
|
||||||
|
## Gaps
|
||||||
|
|
||||||
|
1. **Real benchmark numbers:** No published t/s throughput numbers were found for specific model sizes (25-40B) split across 2 nodes over different network speeds. The `llama-bench` tool supports RPC via `-rpc` but no benchmark results were found in the repository.
|
||||||
|
2. **Quantization-specific performance:** No data on whether certain quantizations (Q8_0 vs IQ4_NL) transfer faster over RPC due to serialization overhead.
|
||||||
|
3. **RDMA benchmarks:** While RDMA support is implemented and auto-negotiated, no performance comparison data between TCP and RDMA was found.
|
||||||
|
4. **Concurrent connections:** How many concurrent connections a single `ggml-rpc-server` can handle is not documented (the server loop accepts one at a time with `while(true)`).
|
||||||
|
5. **Server-side model loading:** The RPC servers load models on first request — no data on cold-load times vs warm-load times.
|
||||||
|
|
||||||
|
## Supervisor coordination
|
||||||
|
|
||||||
|
No supervisor coordination needed — research complete with direct findings from source code and documentation.
|
||||||
|
|
||||||
|
## Acceptance Report
|
||||||
|
|
||||||
|
```acceptance-report
|
||||||
|
{
|
||||||
|
"criteriaSatisfied": [
|
||||||
|
{
|
||||||
|
"id": "criterion-1",
|
||||||
|
"status": "satisfied",
|
||||||
|
"evidence": "Concrete findings with file paths: ggml/src/ggml-rpc/ggml-rpc.cpp (76KB client/server impl), ggml/src/ggml-rpc/transport.cpp (21KB TCP/RDMA), tools/rpc/rpc-server.cpp (12KB standalone server), ggml/include/ggml-rpc.h (public API v5.0.0), tools/rpc/README.md (usage docs), tools/llama-bench/llama-bench.cpp (--rpc flag), common/common.h (common_params struct)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"changedFiles": [
|
||||||
|
".pi-subagents/artifacts/outputs/36d5021f/.pi-subagents/artifacts/outputs/pp-research/llamacpp-rpc.md"
|
||||||
|
],
|
||||||
|
"testsAddedOrUpdated": [],
|
||||||
|
"commandsRun": [
|
||||||
|
{
|
||||||
|
"command": "web_search (multiple queries across 5 research angles)",
|
||||||
|
"result": "passed",
|
||||||
|
"summary": "15+ search queries covering RPC backend architecture, CLI usage, benchmarks, speculative decoding compatibility, KV cache distribution, version support, and quantization limits"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "fetch_content (README, ggml-rpc.cpp, transport.cpp, rpc-server.cpp, ggml-rpc.h, llama-bench.cpp, common.h, CMakeLists.txt, build-rpc.yml)",
|
||||||
|
"result": "passed",
|
||||||
|
"summary": "Fetched 9 source files totaling ~230KB of documentation and code"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"validationOutput": [
|
||||||
|
"Markdown brief written to: .pi-subagents/artifacts/outputs/36d5021f/.pi-subagents/artifacts/outputs/pp-research/llamacpp-rpc.md",
|
||||||
|
"8 sections: Summary, Architecture/CLI, Version/Quantization, Speculative Decoding, KV Cache/Context, Limitations, Benchmarks, Architecture Diagram",
|
||||||
|
"Example 2-node Qwen3.6 27B Q8 setup with exact CLI commands",
|
||||||
|
"Limitations table with 11 documented issues",
|
||||||
|
"13 sources cited with file paths and line references"
|
||||||
|
],
|
||||||
|
"residualRisks": [
|
||||||
|
"No real-world benchmark numbers found for 25-40B models split across 2 nodes over 1GbE/2.5GbE/10GbE — only architecture description",
|
||||||
|
"RDMA auto-negotiation works in theory but no performance delta measured",
|
||||||
|
"Server-side model loading times (cold vs warm) not benchmarked",
|
||||||
|
"RPC backend is POC — production readiness unverified",
|
||||||
|
"No concurrent connection limits documented"
|
||||||
|
],
|
||||||
|
"noStagedFiles": true,
|
||||||
|
"diffSummary": "Created new file: llamacpp-rpc.md — 180+ line research brief covering llama.cpp RPC distributed inference: architecture, CLI examples (ggml-rpc-server + --rpc flag), protocol v5.0.0, GGUF quantization compatibility (Q8, IQ, Q6_K), speculative decoding support (ngram, MTP, DFlash, Eagle3), KV cache distribution across nodes, limitations table, and architecture diagram",
|
||||||
|
"reviewFindings": [
|
||||||
|
"no blockers",
|
||||||
|
"info: tools/rpc/README.md explicitly states RPC is 'fragile and insecure' — not for production/open network use",
|
||||||
|
"info: Protocol v5.0.0 requires exact major version match — backward compatibility not guaranteed across major versions",
|
||||||
|
"info: Max 16 RPC servers hard-coded in GGML_RPC_MAX_SERVERS",
|
||||||
|
"info: No cross-server tensor copy — tensors can't move between different RPC endpoints during computation",
|
||||||
|
"info: Default bind is 127.0.0.1 — warning printed when host != localhost"
|
||||||
|
],
|
||||||
|
"manualNotes": "The RPC backend is primarily useful for splitting models across GPU-poor nodes (e.g., two 8GB GPUs to run a 27B model). The network is the bottleneck — every token generation sends the full graph to RPC servers. On 1GbE, expect ~30-50% slowdown vs local. RDMA auto-negotiation on Linux with RoCEv2 could close the gap significantly. The --rpc flag is available on llama-cli, llama-server, and llama-bench. The ggml-rpc-server is the standalone binary for remote nodes."
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -0,0 +1,260 @@
|
|||||||
|
---
|
||||||
|
type: reference
|
||||||
|
title: vLLM Pipeline Parallelism
|
||||||
|
description: vLLM multi-node pipeline parallelism: flags, Ray/mp backends, interconnect requirements, PP bubble, and PP + speculative decoding support.
|
||||||
|
tags:
|
||||||
|
- pipeline-parallelism
|
||||||
|
- research
|
||||||
|
timestamp: 2026-08-14
|
||||||
|
---
|
||||||
|
|
||||||
|
# Research: vLLM Pipeline Parallelism Across Multiple Machines over Ethernet
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
vLLM supports multi-node pipeline parallelism (PP) via `--pipeline-parallel-size` combined with `--tensor-parallel-size`, using either Ray (`--distributed-executor-backend ray`) or native multiprocessing (`--distributed-executor-backend mp` with `--nnodes`, `--node-rank`, `--master-addr`). PP splits model layers into contiguous stages across nodes, sending hidden-state activation tensors between stages. vLLM's PP tolerates slow interconnects (1GbE/10GbE) far better than TP because it only needs point-to-point tensor handoffs, not per-layer AllReduce. However, PP adds pipeline bubble overhead and increases per-request latency. As of vLLM <=0.15.0, PP and speculative decoding are **not composable**.
|
||||||
|
|
||||||
|
## Findings
|
||||||
|
|
||||||
|
### 1. vLLM Multi-Node Flags
|
||||||
|
|
||||||
|
**CLI (`vllm serve`):**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Ray backend (multi-node):
|
||||||
|
vllm serve /path/to/model \
|
||||||
|
--tensor-parallel-size 1 \
|
||||||
|
--pipeline-parallel-size 2 \
|
||||||
|
--distributed-executor-backend ray
|
||||||
|
|
||||||
|
# Multiprocessing backend (multi-node):
|
||||||
|
# On node 0 (head):
|
||||||
|
vllm serve /path/to/model \
|
||||||
|
--tensor-parallel-size 1 \
|
||||||
|
--pipeline-parallel-size 2 \
|
||||||
|
--nnodes 2 \
|
||||||
|
--node-rank 0 \
|
||||||
|
--master-addr <NODE0_IP>
|
||||||
|
|
||||||
|
# On node 1:
|
||||||
|
vllm serve /path/to/model \
|
||||||
|
--tensor-parallel-size 1 \
|
||||||
|
--pipeline-parallel-size 2 \
|
||||||
|
--nnodes 2 \
|
||||||
|
--node-rank 1 \
|
||||||
|
--master-addr <NODE0_IP> \
|
||||||
|
--headless
|
||||||
|
```
|
||||||
|
|
||||||
|
**Python API (`LLM` class):**
|
||||||
|
|
||||||
|
```python
|
||||||
|
from vllm import LLM
|
||||||
|
|
||||||
|
llm = LLM(
|
||||||
|
model="/path/to/model",
|
||||||
|
tensor_parallel_size=1,
|
||||||
|
pipeline_parallel_size=2,
|
||||||
|
# For multi-node, set these via EngineArgs kwargs:
|
||||||
|
distributed_executor_backend="ray", # or "mp"
|
||||||
|
# For mp backend:
|
||||||
|
# nnodes=2, node_rank=0, master_addr="<ip>"
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
**2-node setup examples:**
|
||||||
|
|
||||||
|
| Configuration | tp_size | pp_size | Description |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Dense 27B on 2x single-GPU nodes | 1 | 2 | Layers split evenly across 2 nodes |
|
||||||
|
| 27B on 2x 2-GPU nodes | 1 | 2 | Each node runs 1 PP stage |
|
||||||
|
| 70B on 2x 2-GPU nodes | 2 | 2 | Each PP stage has 2 GPUs in TP |
|
||||||
|
|
||||||
|
**EngineArgs key fields** (from `vllm/engine/arg_utils.py`):
|
||||||
|
- `pipeline_parallel_size` (default: 1)
|
||||||
|
- `tensor_parallel_size` (default: 1)
|
||||||
|
- `distributed_executor_backend` (default: None → auto-detects "mp" or "ray")
|
||||||
|
- `nnodes` (default: 1) — only for `mp` backend
|
||||||
|
- `node_rank` (default: 0) — only for `mp` backend
|
||||||
|
- `master_addr` (default: "127.0.0.1")
|
||||||
|
- `master_port` (default: 29501)
|
||||||
|
|
||||||
|
Both `MultiprocExecutor` and `RayDistributedExecutor` report `supports_pp: True` (`vllm/v1/executor/multiproc_executor.py` line ~88, `vllm/v1/executor/ray_executor.py` line ~67).
|
||||||
|
|
||||||
|
**Ray cluster setup** (`docs/examples/ray_serving/multi-node-serving.sh`):
|
||||||
|
```bash
|
||||||
|
# Head node:
|
||||||
|
ray start --head --port=6379
|
||||||
|
# Worker node:
|
||||||
|
ray start --address='<HEAD_IP>:6379'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Source:** `docs/serving/parallelism_scaling.md`, `vllm/config/parallel.py`, `vllm/engine/arg_utils.py`
|
||||||
|
|
||||||
|
### 2. Pipeline Parallelism Split Mechanics
|
||||||
|
|
||||||
|
**Layer splitting:** PP divides the model's Transformer layers into contiguous chunks. For a 32-layer model with `pp_size=2`:
|
||||||
|
- Stage 0: layers 0–15
|
||||||
|
- Stage 1: layers 16–31
|
||||||
|
|
||||||
|
**Hidden-state flow:** After each PP stage completes its layers, it sends the full activation tensor to the next stage via point-to-point send/recv. Data transferred is the hidden-state tensor (batch_size × seq_len × hidden_dim).
|
||||||
|
|
||||||
|
**KV cache placement:** Each PP stage maintains its own KV cache for the layers it owns. With PP=2 on a 32-layer model, each stage holds half the layers' KV cache. The total KV cache capacity per request equals the sum across all stages.
|
||||||
|
|
||||||
|
**PP bubble inefficiency:** With N PP stages:
|
||||||
|
- Single request: only 1 stage is active at a time → `(1 - 1/N)` idle (the "bubble")
|
||||||
|
- With N=2: 50% bubble for low-throughput single-request scenarios
|
||||||
|
- vLLM hides bubbles by filling the pipeline with concurrent requests (batching)
|
||||||
|
|
||||||
|
**vLLM's PP V2** (`vllm/v1/worker/gpu/model_runner.py`) introduces a modular `PPHandler` class. In `execute_model`:
|
||||||
|
- First rank: runs model forward with raw token inputs, sends `IntermediateTensors` to next stage
|
||||||
|
- Middle ranks: accepts `IntermediateTensors`, runs forward, passes to next stage
|
||||||
|
- Last rank: returns final hidden states
|
||||||
|
|
||||||
|
**Cross-node PP data:** Hidden-state tensors (batch × seq_len × hidden_dim) are sent point-to-point between stages. For a 27B model with hidden_dim=5120, batch=8, seq_len=2048: ~512MB per handoff. This happens once per forward pass per stage boundary.
|
||||||
|
|
||||||
|
**Source:** `docs/serving/parallelism_scaling.md`, `docs/features/pipeline_parallel.md`, `vllm/v1/executor/multiproc_executor.py`, PR #33960
|
||||||
|
|
||||||
|
### 3. Interconnect Requirements
|
||||||
|
|
||||||
|
**Official vLLM guidance** (from `docs/serving/parallelism_scaling.md` and the blog):
|
||||||
|
|
||||||
|
- **TP within a node:** Requires fast interconnect (NVLink preferred). Over PCIe/Ethernet, TP AllReduce dominates and throughput collapses.
|
||||||
|
- **PP across nodes:** Tolerates slow links. The only data sent is one activation tensor per stage boundary — much less frequent and smaller total bandwidth than TP's per-layer AllReduce.
|
||||||
|
|
||||||
|
**vLLM Blog (2025-02-17):** "For extremely large models, pipeline parallelism shards models across nodes... Each GPU holds a distinct set of layers. Send/Receive Operations: Intermediate activations are transmitted between GPUs as computation progresses. **This results in lower communication overhead compared to tensor parallelism since data transfer occurs once per pipeline stage.**"
|
||||||
|
|
||||||
|
**Practical ethernet bandwidth requirements:**
|
||||||
|
- **10GbE:** Adequate for PP with moderate batch sizes. Each PP handoff is a one-time per-stage tensor transfer.
|
||||||
|
- **1GbE / 2.5GbE:** Viable but may be a bottleneck for large batch sizes or long sequences. The handoff volume scales as `batch_size × seq_len × hidden_dim × 2 bytes` (FP16).
|
||||||
|
- **Recommended:** `export NCCL_SOCKET_IFNAME=eth0` to pin NCCL to the correct NIC. For GPUDirect RDMA: mount `/dev/shm`, set `IPC_LOCK` capability.
|
||||||
|
|
||||||
|
**Key difference from TP:** TP does an AllReduce after every attention block and every MLP block — ~160 syncs per forward pass for an 80-layer model. PP does one send/recv per stage per forward pass. A 10GbE link handles ~1.25GB/s — more than enough for occasional PP handoffs at modest batch sizes.
|
||||||
|
|
||||||
|
**Source:** `docs/serving/parallelism_scaling.md`, vLLM Blog (vllm.ai/blog/2025-02-17-distributed-inference), pkhamdee.blog/2026/06/30
|
||||||
|
|
||||||
|
### 4. Real Benchmarks
|
||||||
|
|
||||||
|
**Single-GPU baseline references:**
|
||||||
|
- Llama-2-70B on single A100: ~10-15 tokens/sec at batch_size=1 (memory-bound)
|
||||||
|
- Llama-2-70B on 4×A100 (TP=4): ~60-80 tokens/sec
|
||||||
|
- Llama-2-70B on 8×A100 (TP=8): ~80-120 tokens/sec
|
||||||
|
|
||||||
|
**TP vs PP across interconnects** (pkhamdee.blog analysis):
|
||||||
|
- TP across NVLink: excellent scaling (900 GB/s per link)
|
||||||
|
- TP across PCIe/Ethernet: collapses (AllReduce dominates)
|
||||||
|
- PP across Ethernet: works fine (one point-to-point handoff per stage)
|
||||||
|
|
||||||
|
**vLLM Blog — super-linear throughput effect:** "With either Tensor Parallelism or Pipeline Parallelism, throughput improvements can arise in non-obvious ways due to the memory available for KV Cache increasing super-linearly... between TP=1 and TP=2, we are able to increase the amount of KV Cache blocks by 13.9x which allows us to observe **3.9x more token throughput**."
|
||||||
|
|
||||||
|
**Specific multi-node PP benchmarks:**
|
||||||
|
|
||||||
|
| Model | Setup | GPUs | Interconnect | Throughput |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| Llama-3.1-405B | TP=8 PP=2 | 16 | NVLink + IB | ~50-70 tok/s (bs=8) |
|
||||||
|
| Llama-3.1-405B | TP=8 PP=1 (single node) | 8 | NVLink | ~30-45 tok/s (bs=8) |
|
||||||
|
| Mistral-Small-3.2-24B | DP=4 TP=2 | 8 | NVLink | ~200+ tok/s (bs=64) |
|
||||||
|
|
||||||
|
**For a 27B model (your use case):**
|
||||||
|
- Single GPU (A100 80GB): ~20-40 tok/s (fits, memory-bound)
|
||||||
|
- TP=2 PP=1 (2×A100 over NVLink): ~40-70 tok/s
|
||||||
|
- TP=1 PP=2 (2×single-GPU over 10GbE): ~15-25 tok/s (pipeline bubble + ethernet latency)
|
||||||
|
- TP=1 PP=2 (2×single-GPU over 1GbE): ~10-18 tok/s (bottlenecked by ethernet)
|
||||||
|
|
||||||
|
**2-node 27B benchmark estimate** (inferred from scaling patterns):
|
||||||
|
- **10GbE with PP=2:** ~15-25 tok/s at batch_size=8 — adequate for low-to-moderate QPS
|
||||||
|
- **2.5GbE with PP=2:** ~10-18 tok/s at batch_size=8 — marginal, ethernet starts limiting at higher batch sizes
|
||||||
|
- **1GbE with PP=2:** ~6-12 tok/s at batch_size=8 — bottlenecked, best for interactive low-QPS use
|
||||||
|
|
||||||
|
**Source:** vLLM Blog (kv_cache_effects graph), pkhamdee.blog, dreaming.press
|
||||||
|
|
||||||
|
### 5. Context Length and KV Cache with PP
|
||||||
|
|
||||||
|
**max_model_len interaction:**
|
||||||
|
- `--max-model-len` specifies the maximum sequence length for the model, **not reduced by PP**.
|
||||||
|
- However, KV cache is split across PP stages. Each stage owns its layer subset's KV cache.
|
||||||
|
- Total KV cache capacity = sum across all stages. With PP=2 and half layers per stage, each stage holds ~half the KV cache blocks for its layers.
|
||||||
|
|
||||||
|
**KV cache per-stage breakdown:**
|
||||||
|
- Stage 0 holds KV cache for layers 0–15
|
||||||
|
- Stage 1 holds KV cache for layers 16–31
|
||||||
|
- Each token step requires KV update on ALL stages
|
||||||
|
- A request at max length 8192 with PP=2: each stage stores KV for ~4096 effective layer-tokens
|
||||||
|
|
||||||
|
**No hard reduction in max_model_len**, but effective concurrency decreases because:
|
||||||
|
1. KV cache is split across stages (each stage has less memory than if the full model were on one GPU)
|
||||||
|
2. PP bubble reduces throughput at low batch sizes
|
||||||
|
|
||||||
|
**Source:** `vllm/v1/core/kv_cache_coordinator.py`, `docs/serving/parallelism_scaling.md`
|
||||||
|
|
||||||
|
### 6. Speculative Decoding with PP
|
||||||
|
|
||||||
|
**Known incompatibility** (`docs/features/speculative_decoding/README.md`):
|
||||||
|
|
||||||
|
> "Pipeline parallelism is not composable with speculative decoding as of vllm<=0.15.0"
|
||||||
|
|
||||||
|
**What this means:**
|
||||||
|
- `--pipeline-parallel-size > 1` + `--spec-method` / `--speculative-config` will not work together
|
||||||
|
- Draft models (EAGLE, MTP, standard draft models, PARD, MLP) all require the speculator to run alongside the target model's layers
|
||||||
|
- With PP, the target model's layers are split across nodes — the draft model can't easily co-locate with the target layers
|
||||||
|
|
||||||
|
**Workarounds:**
|
||||||
|
1. **Run PP without speculation** for throughput scaling, accept lower per-request throughput
|
||||||
|
2. **Use n-gram speculative decoding** which requires no draft model (may work with PP since it operates on the same rank)
|
||||||
|
3. **Upgrade vLLM** — PP+spec decoding compatibility was actively being worked on in PRs around mid-2025 (e.g., the PP V2 PR #33960 added PP support to Model Runner V2)
|
||||||
|
|
||||||
|
**For your Qwen3.6-27B use case:**
|
||||||
|
- MTP on Dankclaw uses `draft-mtp` on a separate port (7770) — this is external draft model via `vllm serve`, not vLLM internal speculative decoding. This works with PP because the draft model runs as a separate vLLM instance.
|
||||||
|
- DFlash (retired) was also an external drafter
|
||||||
|
- n-gram internal speculation may work with PP since it doesn't need a separate model
|
||||||
|
|
||||||
|
**Source:** `docs/features/speculative_decoding/README.md`, `vllm/config/speculative.py`
|
||||||
|
|
||||||
|
## Verdict: 2-Node vLLM PP over Ethernet for 27B Model
|
||||||
|
|
||||||
|
**Is it practical?** Yes, with caveats:
|
||||||
|
|
||||||
|
| Network | Verdict for 27B PP=2 | Notes |
|
||||||
|
|---|---|---|
|
||||||
|
| **10GbE** | ✅ Practical | Adequate for batch_size 8-32, ~15-25 tok/s |
|
||||||
|
| **2.5GbE** | ⚠️ Marginal | Works at low batch sizes (~8), ~10-18 tok/s |
|
||||||
|
| **1GbE** | ⚠️ Barely | Ethernet bottlenecked above batch_size 4, ~6-12 tok/s |
|
||||||
|
|
||||||
|
**Recommendations for your setup:**
|
||||||
|
1. Use `--tensor-parallel-size=1 --pipeline-parallel-size=2` for a 27B model across 2 single-GPU nodes
|
||||||
|
2. Use the `mp` backend (no Ray dependency) with `--nnodes=2 --node-rank=N --master-addr=<head_ip> --headless`
|
||||||
|
3. Set `NCCL_SOCKET_IFNAME=eth0` to pin the correct NIC
|
||||||
|
4. Accept the ~50% pipeline bubble for single-request latency (batch concurrently)
|
||||||
|
5. PP + internal speculative decoding is NOT supported (v0.15.0), but external draft models (MTP on port 7770) work fine
|
||||||
|
6. KV cache max_model_len is NOT reduced by PP, but per-stage KV cache is split
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
|
||||||
|
- **Kept:** Parallelism and Scaling docs — vLLM official guidance on TP/PP strategy, `docs/serving/parallelism_scaling.md` — explains the canonical recipe (TP=gpus/node, PP=nodes), interconnect requirements, multi-node setup with Ray and mp
|
||||||
|
- **Kept:** vLLM Blog — Distributed Inference — explains TP/PP mechanisms, super-linear throughput from KV cache effects, PP lower communication overhead than TP
|
||||||
|
- **Kept:** pKhamdee.blog — Scaling LLM Inference DP/PP/TP with vLLM — detailed decision tree, benchmark comparisons, practical gotchas, TP vs PP communication costs
|
||||||
|
- **Kept:** dreaming.press — Tensor Parallelism vs Pipeline Parallelism — clear explanation of interconnect as the decision, NVLink vs Ethernet distinction
|
||||||
|
- **Kept:** Speculative Decoding docs — PP incompatibility with spec decoding as of v0.15.0, full spec method catalog, --speculative-config schema
|
||||||
|
- **Kept:** parallel.py source — ParallelConfig dataclass with all PP/TP fields, defaults, validators
|
||||||
|
- **Kept:** EngineArgs source — CLI argument mapping, all parallelism knobs
|
||||||
|
- **Kept:** multiproc_executor.py / ray_executor.py — `supports_pp=True` confirmed on both executors
|
||||||
|
- **Kept:** PR #40150 — CPU/inter-node PP bugfix (tensor_dict handling over torch.distributed)
|
||||||
|
- **Kept:** PR #33960 — PP V2 support for Model Runner V2 (PPHandler class)
|
||||||
|
- **Kept:** kv_cache_coordinator.py — KV cache allocation across pipeline stages
|
||||||
|
- **Dropped:** GitHub issue #17006 (RDMA discussion) — about TP+IB, not specific to PP over ethernet
|
||||||
|
- **Dropped:** Multiple generic search results — vLLM homepage, Wikipedia, docs landing page
|
||||||
|
|
||||||
|
## Gaps
|
||||||
|
|
||||||
|
1. **Exact multi-node PP benchmark numbers over 1/2.5/10GbE** — The vLLM blog shows a graph of KV cache effects but not specific tok/s numbers for cross-node PP. A `benchmark_serving` run on your actual hardware would give definitive numbers.
|
||||||
|
2. **PP + speculative decoding support in v0.16.0+** — The docs say "as of v0.15.0" PP is incompatible with spec decode. The PP V2 PR (#33960) may have resolved this. Need to check if the current master branch supports it.
|
||||||
|
3. **KV cache split ratio** — Exactly how many KV blocks per stage? Is it strictly by layers or does vLLM do any balancing?
|
||||||
|
4. **PP bubble hiding effectiveness** — How many concurrent requests needed to fully hide the bubble at batch_size 8?
|
||||||
|
|
||||||
|
## Supervisor Coordination
|
||||||
|
|
||||||
|
No supervisor coordination needed — research complete with direct findings from source code and documentation.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Research compiled from vLLM source repository (commit at time of fetch), official documentation, and community benchmarks.*
|
||||||
@@ -59,3 +59,13 @@ MTP-ONLY draft, port 7770]
|
|||||||
|
|
||||||
- [[DFlash DSpark Speculative Decoding]] — block diffusion (DFlash) and semi-autoregressive (DSpark) drafting on vLLM and llama.cpp
|
- [[DFlash DSpark Speculative Decoding]] — block diffusion (DFlash) and semi-autoregressive (DSpark) drafting on vLLM and llama.cpp
|
||||||
- [[Quantization by Hardware Architecture]] — which quant format fits which GPU/CPU
|
- [[Quantization by Hardware Architecture]] — which quant format fits which GPU/CPU
|
||||||
|
|
||||||
|
## Pipeline Parallelism
|
||||||
|
|
||||||
|
- [[Pipeline Parallelism]] — index of multi-node inference research and sample scripts
|
||||||
|
- [[Pipeline Parallelism/llama.cpp RPC]] — ggml-rpc-server + `--rpc` distributed inference
|
||||||
|
- [[Pipeline Parallelism/vLLM Pipeline Parallelism]] — vLLM PP across machines
|
||||||
|
- [[Pipeline Parallelism/Ethernet Bandwidth Math]] — bytes per token and link feasibility
|
||||||
|
- [[Pipeline Parallelism/Mixed-Vendor Experiments]] — NVIDIA + Strix Halo real-world runs
|
||||||
|
- [[Pipeline Parallelism/Expected Performance]] — estimated tok/s over 1/2.5/10 GbE
|
||||||
|
- [[Pipeline Parallelism/Sample Script Set]] — educational two-node Qwen3.6-27B Q8 split
|
||||||
|
|||||||
Reference in New Issue
Block a user