使用 exo 技术构建 Mac mini AI 推理集群:从架构到实战
1. 引言
1.1 背景
随着大语言模型(LLM)规模的不断增长,单机推理已无法满足高性能需求。DeepSeek-V3(671B 参数)、Qwen3-235B 等模型需要数百 GB 显存,单台消费级设备无法完整加载。传统解决方案依赖昂贵的企业级 GPU 集群,而 Apple Silicon 的统一内存架构(Unified Memory Architecture)为分布式推理提供了新的可能性。
使用 exo 分布式推理框架在 Mac mini 集群上部署 AI 推理服务的方法。exo 利用 MLX 作为后端,通过 Thunderbolt 5 RDMA 实现超低延迟通信,支持张量和流水线并行。文章涵盖了集群拓扑设计、硬件选型(M4 Pro)、系统配置(RDMA 启用)、模型部署流程及性能优化策略。通过实测,4 节点 M4 Pro 集群可运行 Qwen3-235B 等大模型,具备低成本、低功耗优势,适合中小企业和研究实验室使用。
随着大语言模型(LLM)规模的不断增长,单机推理已无法满足高性能需求。DeepSeek-V3(671B 参数)、Qwen3-235B 等模型需要数百 GB 显存,单台消费级设备无法完整加载。传统解决方案依赖昂贵的企业级 GPU 集群,而 Apple Silicon 的统一内存架构(Unified Memory Architecture)为分布式推理提供了新的可能性。
exo 是由 exo labs 开发的开源分布式 AI 推理框架,主要特性包括:
Mac mini M4/M4 Pro 是理想的集群节点,具有以下特点:
| 规格 | M4 | M4 Pro |
|---|---|---|
| CPU | 10 核(4P+6E) | 12 核(8P+4E)或 14 核 |
| GPU | 10 核 | 16 核或 20 核 |
| 统一内存 | 16/24/32GB | 24/48/64GB |
| 内存带宽 | 120GB/s | 273GB/s |
| Thunderbolt | TB4 (40Gb/s) | TB5 (120Gb/s) |
| 尺寸 | 5×5×2 英寸 | 5×5×2 英寸 |
| 功耗 | 最大 155W | 最大 155W |
关键优势:
exo 支持多种集群拓扑,以下是推荐配置:
[Mac mini 1] | | Thunderbolt 5 | [Mac mini 2]─┼─[Mac mini 3] | [Mac mini 4]
适用场景:中小规模模型(< 200B 参数) 通信方式:所有节点通过 Thunderbolt 5 直连至中心节点 优势:简单、低延迟 限制:中心节点成为瓶颈
[Mac mini 1]─┬─[Mac mini 2] │ │ │ │ └───────┼───[Mac mini 3] │ │ └───────────────┴───[Mac mini 4]
适用场景:超大规模模型(> 500B 参数) 通信方式:每个节点连接到多个邻居节点 优势:高带宽聚合、无单点瓶颈 限制:需要更多 Thunderbolt 端口
exo 支持两种并行方式:
将模型按层切分到不同设备:
Device 1: Layers 1-20 Device 2: Layers 21-40 Device 3: Layers 41-60 Device 4: Layers 61-80
特点:
将每层的权重矩阵切分到不同设备:
Device 1: W[:, 0:N/4] Device 2: W[:, N/4:N/2] Device 3: W[:, N/2:3N/4] Device 4: W[:, 3N/4:N]
特点:
性能数据(基于 Jeff Geerling 测试):
以 DeepSeek-V3(671B 参数)8-bit 量化为例:
4 节点配置:
优化策略:
4 节点示例配置:
Thunderbolt 5 拓扑(网状):
Mac mini 1 ─TB5─ Mac mini 2 │ │ TB5 TB5 │ │ Mac mini 4 ─TB5─ Mac mini 3 │ │ TB5───────────────TB5
注意事项:
重要:RDMA 是较新版本 macOS 引入的功能,仅支持:
启用步骤:
验证 RDMA:
system_profiler SPThunderboltDataType # 查看是否显示 RDMA 接口
启用 RDMA:
rdma_ctl enable
重要警告:
前置依赖:
# 安装 Homebrew
/bin/bash -c"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# 安装依赖
brew install uv macmon node
# 安装 Rust(需要 nightly)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup toolchain install nightly
克隆并构建:
# 克隆仓库
git clone https://github.com/exo-explore/exo
cd exo
# 构建仪表盘
cd dashboard
npm install
npm run build
cd..
# 运行 exo
uv run exo
验证启动:
系统要求:macOS 15+
下载安装:
# 下载 DMG
curl -O https://assets.exolabs.net/EXO-latest.dmg
# 挂载并安装
open EXO-latest.dmg
# 将 EXO.app 拖到 Applications
首次运行:
配置隔离命名空间(可选):
EXO_LIBP2P_NAMESPACE(如 my-cluster)禁用 Thunderbolt Bridge(使用 RDMA 时): exo 提供了配置脚本:
# 从源码运行
sudo ./tmp/set_rdma_network_config.sh
此脚本会:
验证网络:
ifconfig | grep -A5 "rdma" # 应该看到 rdma0, rdma1 等接口
设备 1(Coordinator):
uv run exo
设备 2-4(Workers):
uv run exo
自动发现:
访问 Dashboard:
检查节点列表:
curl http://localhost:52415/state | jq '.nodes'
示例输出:
{"nodes":[{"id":"local","name":"Mac-mini-1","capabilities":{"memory":68719476736,"device":"mps"}},{"id":"QmXxxx...","name":"Mac-mini-2","capabilities":{"memory":68719476736,"device":"mps"}} // ... 其他节点]}
curl http://localhost:52415/models | jq '.models[] | {id, name, size}'
常见模型:
llama-3.2-1b:小型测试模型llama-3.3-70b:中型模型qwen3-235b:大型模型deepseek-v3.1-671b:超大模型在实际部署前,使用 /instance/previews 查看可能的分片策略:
curl "http://localhost:52415/instance/previews?model_id=qwen3-235b" | jq '.previews[]'
示例输出:
{"model_id":"mlx-community/Qwen3-235B-Instruct-8bit","sharding":"Tensor","instance_meta":"MlxRing","memory_delta_by_node":{"local":62914560000,"QmAbc...":62914560000,"QmDef...":62914560000,"QmGhi...":62914560000},"error":null}
字段说明:
sharding:并行策略(Pipeline/Tensor)instance_meta:通信后端(MlxRing = RDMA)memory_delta_by_node:每个节点需要的额外内存error:如果为 null 表示可行选择最优方案:
# 过滤出无错误的方案
curl "http://localhost:52415/instance/previews?model_id=qwen3-235b" | jq '.previews[] | select(.error == null) | .instance' | head -n1 > instance.json
# 使用预览的方案
curl -X POST http://localhost:52415/instance \
-H 'Content-Type: application/json' \
-d @instance.json
响应:
{"message":"Command received.","command_id":"e9d1a8ab-1234-5678-90ab-cdef12345678"}
# 查看所有实例
curl http://localhost:52415/state | jq '.instances'
部署阶段:
实时日志:
# 在每个节点查看日志
tail -f ~/.local/share/exo/exo.log # Linux
tail -f ~/Library/Logs/exo/exo.log # macOS
exo 兼容 OpenAI API 格式:
curl -N -X POST http://localhost:52415/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{ "model": "mlx-community/Qwen3-235B-Instruct-8bit", "messages": [ {"role": "system", "content": "You are a helpful AI assistant."}, {"role": "user", "content": "Explain quantum computing in simple terms."} ], "stream": true, "max_tokens": 500, "temperature": 0.7 }'
流式输出(SSE 格式):
data: {"choices":[{"delta":{"content":"Quantum"}}]} data: {"choices":[{"delta":{"content":" computing"}}]} ... data: [DONE]
curl -X POST http://localhost:52415/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{ "model": "mlx-community/Qwen3-235B-Instruct-8bit", "messages": [{"role": "user", "content": "Hello!"}], "stream": false }' | jq '.choices[0].message.content'
from openai import OpenAI
# 指向 exo 集群
client = OpenAI(
base_url="http://localhost:52415/v1",
api_key="not-needed" # exo 不需要 API key
)
response = client.chat.completions.create(
model="mlx-community/Qwen3-235B-Instruct-8bit",
messages=[{"role":"user","content":"Write a haiku about AI clusters"}],
stream=True
)
for chunk in response:
if chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end='')
exo 提供了 exo-bench 工具用于性能测试:
# 测试不同 prompt 长度和生成长度
uv run bench/exo_bench.py \
--model Qwen3-235B-Instruct-8bit \
--pp 128,256,512,1024 \
--tg 128,256,512 \
--max-nodes 4 \
--sharding tensor \
--repeat 3 \
--json-out results.json
参数说明:
--pp:Prompt 长度(Prefill Phase)--tg:生成长度(Token Generation)--max-nodes:限制节点数--sharding:指定并行策略(tensor/pipeline/both)--repeat:重复次数cat results.json | jq '.[] | { nodes: .num_nodes, sharding: .sharding, prompt_tps: .prompt_tps, gen_tps: .generation_tps, memory_gb: (.peak_memory / 1073741824 | round) }'
典型性能指标(Qwen3-235B,4×M4 Pro 64GB):
exo Dashboard 提供可视化监控:
使用 macmon 监控 Apple Silicon:
# 安装
brew install macmon
# 实时监控
macmon
输出示例:
GPU: 45% | CPU: 68% | ANE: 12% Mem: 52.3 / 64.0 GB Pwr: 85W | Temp: 65°C
监控 RDMA 接口流量:
# 查看 RDMA 接口统计
netstat -I rdma0 -w1
# 使用 iftop 监控带宽
sudo iftop -i rdma0
量化策略:
KV Cache 管理:
# 使用 MLX 的动态 KV Cache
import mlx.core as mx
# 设置最大 cache 长度
max_cache_len = 4096
# 启用 sliding window
use_sliding_window = True
Offloading(实验性):
# 将部分层卸载到 SSD
export MLX_OFFLOAD_LAYERS=20
uv run exo
RDMA 调优:
# 检查 RDMA 配置
ifconfig rdma0 | grep mtu # 增大 MTU(如果支持)
sudo ifconfig rdma0 mtu 9000
批处理:
使用优化的模型格式:
# 从 HuggingFace 下载预量化模型
# mlx-community 提供了很多优化版本
curl "http://localhost:52415/models" | grep mlx-community
融合算子:
5 节点 HA 集群:
[Load Balancer]
|
[Coordinator]
/ | \
N1 N2 N3 N4
--no-worker)启动 Coordinator:
uv run exo --no-worker
HAProxy 配置(haproxy.cfg):
frontend exo_frontend
bind *:8080
default_backend exo_nodes
backend exo_nodes
balance roundrobin
option httpchk GET /health
server node1 192.168.1.101:52415 check
server node2 192.168.1.102:52415 check
server node3 192.168.1.103:52415 check
server node4 192.168.1.104:52415 check
防火墙规则(macOS):
# 仅允许内网访问 exo API
sudo /usr/libexec/ApplicationFirewall/socketfilterfw --add /path/to/exo
sudo pfctl -e
# 编辑 /etc/pf.conf
block in all
pass in on en0 from 192.168.1.0/24 to any port 52415
Nginx 配置:
server {
listen 443 ssl;
server_name exo.example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
auth_basic "Restricted";
auth_basic_user_file /etc/nginx/.htpasswd;
proxy_pass http://localhost:52415;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
导出指标(自定义 exporter):
# metrics_exporter.py
from prometheus_client import start_http_server, Gauge
import requests
import time
# 定义指标
node_memory = Gauge('exo_node_memory_bytes', 'Memory usage', ['node'])
inference_tps = Gauge('exo_inference_tps', 'Tokens per second')
def collect_metrics():
state = requests.get('http://localhost:52415/state').json()
for node in state['nodes']:
node_memory.labels(node=node['name']).set(node['memory_used'])
if __name__ == '__main__':
start_http_server(9090)
while True:
collect_metrics()
time.sleep(10)
Grafana 面板:
集中式日志收集(Loki):
# promtail-config.yaml
clients:
- url: http://loki:3100/loki/api/v1/push
scrape_configs:
- job_name: exo
static_configs:
- targets:
- localhost
labels:
job: exo
__path__: /Users/*/.local/share/exo/exo.log
症状:Dashboard 只显示 local 节点
排查步骤:
# 1. 检查防火墙
sudo pfctl -s rules | grep 52415
# 2. 检查 libp2p 端口
lsof -i :52415
# 3. 验证网络连通性
ping <other-node-ip>
# 4. 检查 namespace 配置
env | grep EXO_LIBP2P_NAMESPACE
解决方案:
EXO_LIBP2P_NAMESPACE症状:日志显示 RDMA not available
排查:
# 检查 RDMA 状态
system_profiler SPThunderboltDataType | grep -i rdma
# 检查 macOS 版本
sw_vers | grep ProductVersion
# 必须 >= 15+
# 检查 rdma_ctl
rdma_ctl status
解决方案:
rdma_ctl enable症状:模型加载失败,日志显示 OutOfMemoryError
排查:
# 查看预期内存使用
curl "http://localhost:52415/instance/previews?model_id=qwen3-235b" | jq '.previews[0].memory_delta_by_node'
# 检查实际可用内存
sysctl hw.memsize
解决方案:
症状:TPS 远低于预期
诊断:
# 运行 benchmark
uv run bench/exo_bench.py \
--model llama-3.2-1b \
--pp 128 --tg 128 --repeat 1
# 检查是否使用 RDMA
curl http://localhost:52415/state | jq '.instances[].meta'
# 应该显示 "MlxRing"(RDMA)而非 "MlxDist"
优化:
macOS:
# exo 主日志
~/Library/Logs/exo/exo.log
# 系统日志(RDMA 相关)
/var/log/system.log
# Thunderbolt 日志
log show --predicate 'subsystem == "com.apple.thunderbolt"' --last 1h
Linux:
# exo 主日志
~/.local/share/exo/exo.log
# 系统日志
journalctl -u exo -f
启用详细日志:
export EXO_LOG_LEVEL=DEBUG
uv run exo
硬件配置:
部署步骤: 测试推理:
curl -N -X POST http://localhost:52415/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{ "model": "mlx-community/Qwen3-235B-Instruct-8bit", "messages": [{"role": "user", "content": "你好"}], "stream": true }'
部署模型:
# 预览方案
curl "http://localhost:52415/instance/previews?model_id=qwen3-235b" | jq '.previews[] | select(.sharding=="Tensor") | .instance' | head -n1 > qwen3_instance.json
# 创建实例
curl -X POST http://localhost:52415/instance \
-H 'Content-Type: application/json' \
-d @qwen3_instance.json
安装 exo(每台设备):
git clone https://github.com/exo-explore/exo
cd exo && uv run exo
启用 RDMA(每台设备):
# 恢复模式执行
rdma_ctl enable
硬件连接:
Mini1 ─── Mini2
│ ╳ │
│ ╱ ╲ │
Mini4 ─── Mini3
性能结果:
配置:
部署模型:
| 维度 | Mac mini M4 Pro 集群 | NVIDIA H100 集群 |
|---|---|---|
| 硬件成本 | $8,000(4 节点) | $150,000+(4 卡) |
| 功耗 | 600W | 2,800W+ |
| 噪音 | 静音(风扇低速) | 80+ dB(数据中心) |
| 部署复杂度 | 低(即插即用) | 高(需要机架服务器) |
| 通信延迟 | 8μs(RDMA over TB5) | 2μs(NVLink) |
| 内存带宽 | 273GB/s/节点 | 3,350GB/s/卡 |
| 推理速度 | 中等 | 快 |
| 适用场景 | 中小企业、研究实验室 | 大规模生产环境 |
结论:
硬件趋势:
软件优化:
官方计划(2026):
新兴场景:
本文详细介绍了如何使用 exo 框架在 Mac mini 集群上部署大规模 AI 推理服务。通过 Thunderbolt 5 RDMA 和张量并行技术,我们可以用消费级硬件实现接近企业级的推理性能,成本仅为传统方案的 5-10%。
关键要点:
适用人群:
下一步建议:
资源链接:
一键启动脚本(start_cluster.sh):
#!/bin/bash
# 启动 exo 集群节点
set -e
echo "Starting exo cluster node..."
# 检查依赖
command -v uv >/dev/null 2>&1 || { echo "uv not installed"; exit 1; }
# 设置环境变量
export EXO_LIBP2P_NAMESPACE="${EXO_LIBP2P_NAMESPACE:-default}"
export EXO_LOG_LEVEL="${EXO_LOG_LEVEL:-INFO}"
# 切换到 exo 目录
cd ~/exo
# 启动 exo(后台运行)
nohup uv run exo > ~/exo.log 2>&1 &
echo $! > ~/exo.pid
echo "exo started with PID $(cat ~/exo.pid)"
echo "Dashboard: http://localhost:52415"
echo "Logs: tail -f ~/exo.log"
停止脚本(stop_cluster.sh):
#!/bin/bash
# 停止 exo 节点
if [ -f ~/exo.pid ]; then
PID=$(cat ~/exo.pid)
kill $PID
rm ~/exo.pid
echo "exo stopped (PID $PID)"
else
echo "exo not running"
fi
管理模型实例:
# 列出所有模型
curl http://localhost:52415/models | jq -r'.models[] | .id'
# 查看集群状态
curl http://localhost:52415/state | jq '.nodes[] | {name, memory, device}'
# 删除实例
curl -X DELETE http://localhost:52415/instance/<INSTANCE_ID>
# 重新加载实例
curl -X POST http://localhost:52415/instance/reload/<INSTANCE_ID>

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online