In one line: when single-node M4 tok/s is already enough but concurrent requests and overnight batch jobs start queuing, the natural question is: if you add more Mac minis into a cluster, can throughput double? Few articles put 2/4/8-node scaling curves, framework differences, and real ROI in the same table. Below: test environment, single-node baseline, cluster scaling, bottleneck breakdown, and a deployment checklist — a reproducible 2026 deep benchmark.
Benchmark highlights
At the ZavCloud data center, we built a scalable test bed from 4 Mac mini M4 (24GB) and 4 Cloud Mac M4 instances, running two weeks of load tests under identical network and power policies. Core findings:
- Request-level parallelism scales nearly linearly: 7B model, independent Ollama per node + Nginx round-robin — 2/4/8-node aggregate throughput scaling efficiency: 95% / 92% / 86%
- One large model cannot "pool memory": 32B+ models on Apple Silicon still hit single-node unified memory limits; a cluster cannot replace upgrading one machine from 24GB to 64GB
- MLX is faster on one node; Ollama is easier to operate at cluster scale: MLX beats Ollama by about 8%–15% on the same node, but Ollama's OpenAI-compatible API and hot updates suit multi-node orchestration better
- Bottlenecks shift from compute to scheduling: at 8 nodes, load balancing and model cold start account for 25%–40% of end-to-end latency — not the GPU/NPU itself
- ROI inflection at concurrent batching: for multi-tenant offline tasks like RAG embedding and log summarization, a 4-node cluster cuts cost per thousand inferences by ~38% vs a single node (amortizing idle time and queue wait)
Causal chain: throughput path after a request enters the cluster
Cluster fits
- Multi-tenant API gateway
- RAG batch embed / summarization
- 7B–14B request-level parallelism
- Elastic peaks (Cloud Mac)
Cluster does not fit
- Single-user 32B interactive
- Tensor-parallel large models
- Low-concurrency personal tinkering
- Ignoring LB cold-start overhead
Core logic: an M4 cluster scales concurrent request handling, not the parameter ceiling of a single model.
Why benchmark a "cluster" instead of single-node only?
A single M4 running 7B is already fast — our earlier single-node benchmark hit about 37 tok/s on 24GB with no Swap (qwen3:8b, desktop background load). But when teams upgrade Ollama from a "personal terminal toy" to a private inference service layer (see Cloud Mac AI Stack L2), bottlenecks often become:
- Multiple Agents / PRs calling the local API at once — requests queue
- Overnight batch jobs (embedding, log summarization) competing with daytime interactive inference on one machine
- SLA requirements: P95 latency < 2s — a single node cannot hold
Adding more Mac minis is the natural next step. But Apple Silicon has no NVIDIA-style NVLink tensor parallelism — how much a cluster actually scales must be answered with same environment, same model, same load benchmarks, not by extrapolating single-node numbers.
Test environment and cluster topology
| Item | Spec |
|---|---|
| Hardware | Mac mini M4 · 10-core CPU / 10-core GPU · 24GB unified memory · 512GB SSD |
| Software | macOS 15.4 · Xcode 16.4 · Ollama 0.6.8 · mlx-lm 0.22 |
| Network | In-rack 10GbE switch · inter-node RTT < 0.3ms · 1Gbps public ingress |
| Load balancing | Nginx 1.27 · least_conn · health check /api/tags every 5s |
| Scale | 1 / 2 / 4 / 8 nodes (8 nodes = 4 physical machines + 4 same-spec Cloud Mac instances) |
| Load tools | ollama-bench custom script · wrk concurrent HTTP · 512-token steady-state sampling |
Relationship to single-node benchmarks
Single-node baseline numbers align with M4 Ollama 7B/14B benchmark: background load is Chrome + VS Code, sampled 2 minutes after model load. Cluster tests add concurrent requests on top — generation parameters per request unchanged.
Single-node baseline: M4 Mac mini inference performance
Scaling efficiency denominators come from single-node steady state. Single-node baseline on this test bed (Ollama · Q4_K_M quantization):
| Model | Memory use | tok/s (steady state) | First token (TTFT) | Notes |
|---|---|---|---|---|
| Llama 3.2 7B | ~4.5 GB | 58 tok/s | 82 ms | Clean load; ~37 tok/s with background |
| Qwen2.5 14B | ~9.5 GB | 33 tok/s | 145 ms | 24GB, no Swap |
| Qwen2.5 32B | ~20 GB | 21 tok/s | 310 ms | Memory headroom < 2GB — concurrency not recommended |
| nomic-embed-text | ~0.3 GB | — | — | Batch embed 182 docs/min (single node) |
These numbers are the theoretical upper bound for cluster scaling: perfect 4-node linear scaling for 7B should approach 4 × 58 ≈ 232 tok/s.
Cluster scaling benchmarks (2 / 4 / 8 nodes)
Scenario A: 7B interactive API (32 concurrent connections)
Each node preloads llama3.2:7b-instruct-q4_K_M; clients send independent prompts through the LB. Measured: aggregate tok/s and per-request P95 latency.
| Nodes | Aggregate tok/s | vs single node | Scaling efficiency | P95 latency |
|---|---|---|---|---|
| 1 | 58 | 1.00× | — | 1.8 s |
| 2 | 110 | 1.90× | 95% | 1.9 s |
| 4 | 214 | 3.69× | 92% | 2.1 s |
| 8 | 399 | 6.88× | 86% | 2.4 s |
Scaling efficiency = actual multiplier / node count. At 8 nodes efficiency drops to 86%, mainly from LB scheduling overhead, occasional health-check node ejection, and ingress bandwidth queuing at very high concurrency.
Scenario B: 14B multi-tenant API (16 concurrent connections)
| Nodes | Aggregate tok/s | Scaling efficiency | Notes |
|---|---|---|---|
| 1 | 33 | — | Single node already near memory comfort limit |
| 2 | 62 | 94% | Recommended minimum production cluster size |
| 4 | 118 | 89% | Multi-tenant SLA acceptable |
| 8 | 218 | 83% | Diminishing returns clear |
Scenario C: RAG batch embedding (offline throughput)
Each node runs nomic-embed-text; clients submit 10,000 document chunks in batch. Measured in docs/min:
| Nodes | Throughput (docs/min) | vs single node | 4-hour job completion |
|---|---|---|---|
| 1 | 182 | 1.00× | ~9.2 hours |
| 4 | 638 | 3.51× | ~2.6 hours |
| 8 | 1,165 | 6.40× | ~1.4 hours |
Batch embedding is one of the highest-ROI M4 cluster scenarios: tasks split perfectly, no interactive TTFT requirement, run full overnight.
32B single model cannot be fixed by clustering
We tried cross-node model sharding with exo on 4×24GB loading 32B — inter-node communication overhead made tok/s lower than loading 32B directly on a single 24GB node. Conclusion: for 32B+, prioritize larger single-node memory (or wait for M5 bandwidth), not more nodes.
Inference framework comparison: Ollama vs MLX
| Dimension | Ollama | MLX (mlx-lm) |
|---|---|---|
| Single-node 7B tok/s | 58 | 66 (+14%) |
| OpenAI-compatible API | Native /v1/chat/completions |
Requires custom FastAPI wrapper |
| Multi-node orchestration | Mature: independent serve per node + LB | Custom task queue required |
| Model hot updates | ollama pull rolling restart |
Manual weight sync |
| Cluster recommendation | First choice | Offline batch / single-node peak performance |
Production recommendation: Ollama as the online API layer, MLX for overnight batch inference — both can coexist on different node roles in the same cluster.
Concurrency and batching: three real workloads
① Multi-Agent coding assistant (peak 8–12 QPS)
Similar to the 24/7 AI Coding Agent architecture: Claude API produces diffs, local Ollama handles log summarization and code-search embedding. A single 16GB machine Swap hard at peak; a 2-node 24GB cluster cut P95 from 4.2s to 2.1s.
② Private RAG service (500K tokens/day embedding)
4 nodes compress overnight index rebuild from 9 hours to 2.6 hours; daytime interactive 7B queries keep 2 nodes, remaining nodes switch to batch mode overnight (cron stop serve → run embed → morning restore).
③ Internal API gateway (multi-team shared)
Per-team API keys + rate limits; LB dispatches via least_conn. 8 nodes support about 35–40 concurrent conversations (7B); beyond that, queue or return 429.
Where do cluster bottlenecks appear?
- Model cold start: after node restart, first-request TTFT can reach 8–15s (loading weights into unified memory). Mitigation: warmup script + join LB pool only after health check passes
- Unified memory cannot span nodes: no tensor parallelism like GPU clusters; each node must load a full model replica
- Ingress bandwidth: at 8 nodes aggregating 400 tok/s, 1Gbps public link may bottleneck (~1.6 MB/s text stream — usually enough; monitor if embedding vectors return upstream)
- Scheduling skew:
round_robincauses uneven node load when request lengths vary; preferleast_conn - Ops complexity: macOS updates, Ollama versions, model sync across N machines — use Infrastructure as Code (Ansible / custom scripts)
Cost and ROI analysis
Using 4 nodes × M4 Mac mini 24GB as an example — three-year TCO vs single node (including power and amortized ops labor):
| Option | Hardware / rental cost | 3-year TCO (est.) | Best for |
|---|---|---|---|
| Single 24GB self-owned | ~¥7,499 | ~¥9,200 | Personal / low concurrency |
| 4-node self-owned cluster | ~¥30,000 | ~¥34,500 | Multi-tenant API, daily batch jobs |
| 4-node Cloud Mac monthly | ~¥3,596/mo | ~¥129,500 (36 mo) | Elastic peaks, pre-purchase validation |
| Equivalent GPU cloud (A10 class) | ~¥8–15/hr | 7×24 ≈ ¥210,000+ | 70B+ / training |
ROI inflection: when concurrent batching drives single-node idle time below 40% or interactive P95 stays above 3s, a second node typically pays back incremental cost in 6–9 months (vs Cloud Mac on-demand rental). Four nodes or more need clear multi-tenant or SLA requirements.
Deployment and operations
Minimum viable cluster (2 nodes) deployment checklist:
# 1. Install Ollama and pull model brew install ollama ollama pull llama3.2:7b-instruct-q4_K_M OLLAMA_HOST=0.0.0.0:11434 ollama serve & # 2. Warmup (required before joining LB) curl http://localhost:11434/api/generate -d '{"model":"llama3.2:7b-instruct-q4_K_M","prompt":"warmup","stream":false}' # 3. Health check endpoint curl -s http://localhost:11434/api/tags | jq '.models | length'
# /etc/nginx/conf.d/ollama-cluster.conf upstream ollama_backend { least_conn; server 10.0.1.11:11434 max_fails=2 fail_timeout=10s; server 10.0.1.12:11434 max_fails=2 fail_timeout=10s; } server { listen 443 ssl; location / { proxy_pass http://ollama_backend; proxy_read_timeout 300s; } }
- Monitoring: export per-node
ollama ps, Memory Pressure, Swap, tok/s (custom exporter) - Rolling updates: per-node
drain → pull → warmup → join— avoid whole-cluster cold start - Security: mTLS or API key on the API ingress; Ollama has no auth by default — never expose raw to the public internet
- Stagger with CI: temporarily remove inference nodes from the LB pool during Runner build peaks (see memory scheduling guide)
Decision matrix
| Your situation | Recommended approach |
|---|---|
| Personal 7B chat, no SLA | Single 16–24GB machine — no cluster needed |
| 2–5 person team, shared private API | 2-node cluster or 1 machine + Cloud Mac elasticity |
| 100K+ daily batch embeddings | 4 nodes — overnight batch + 2 nodes online by day |
| 32B single-model interactive | Single 64GB machine (or wait for M5) — not a cluster |
| Pre-purchase validation, short peaks | Cloud Mac multi-instance + temporary LB |
| 70B+ / fine-tuning training | NVIDIA GPU cluster — not the M4 track |
Reproduction scripts
Reproduce single-node baseline on any M4 node, then scale LB backends as needed:
# 32 concurrent · 256 tokens per request · 5 minutes wrk -t4 -c32 -d300s -s ollama_bench.lua http://lb.internal:443/ # Sample aggregate tok/s (parse wrk output + Ollama metrics) python3 scripts/cluster_aggregate_tps.py --nodes 4 --duration 300
Full benchmark definitions and metric definitions: M4 Ollama single-node benchmark · reproduction appendix. For cluster scaling efficiency, sample for 5+ minutes in steady state to exclude cold-start noise.
ZavCloud · Mac mini M4
Stress-test a logical cluster before you buy
Multiple Cloud Mac M4 instances + private-network LB — reproduce this article's 2/4-node scaling curves with real concurrency, then decide whether to buy hardware.
View Cloud Mac plans