﻿---
title: "去中心化推理：把一个大模型拆给很多台机器跑"
description: "Petals 说可以像 BT 下载一样，用大家家里的显卡一起跑 70B 的大模型；io.net 说手里有几十万张分散的 GPU。顺着“模型为什么要拆”往下追：按层切开的流水线怎么传数据、为什么每个 token 都要跑一圈网络、层内切开的张量并行为什么只能待在一个房间里、并发能不能把空转的机器填满、io.net 实际上拆的是什么，最后用电费和市价算一笔账，看“闲置硬件不要钱”这个赌注在什么条件下成立。"
pubDate: 2026-10-21
tags: ["大模型", "推理", "分布式", "成本测算"]
hero: "./hero.svg"
refs:
  - id: petals-gh
    title: "Petals：Run large language models at home, BitTorrent-style"
    url: https://github.com/bigscience-workshop/petals
    site: GitHub
    date: 2026-10-20
    note: "README：Llama 2 70B 单批生成最高 6 tok/s，Falcon 180B 最高 4 tok/s。仓库最后一次提交是 2024-08-25；公共网络的监控页 health.petals.dev 在 2026-10-20 返回 503。"
  - id: petals-paper
    title: "Distributed Inference and Fine-tuning of Large Language Models Over The Internet"
    url: https://arxiv.org/abs/2312.08361
    author: "Alexander Borzunov, Max Ryabinin, Artem Chumachenko 等"
    site: NeurIPS 2023
    date: 2023-12-13
    note: "第 3.2 节讲客户端只持有嵌入层、服务器持有连续的 block、两类缓存和故障恢复；表 2（Llama 2 70B，3×T4）和表 3（BLOOM-176B，3×A100、10×RTX 3090、14 台真实异构机器）是文中引用的速度数据，网络条件用 tc qdisc 模拟。"
  - id: llamacpp-rpc
    title: "llama.cpp：tools/rpc/README.md"
    url: https://github.com/ggml-org/llama.cpp/blob/master/tools/rpc/README.md
    site: GitHub
    date: 2026-10-20
    note: "主进程分别连接每个 `ggml-rpc-server`（星形拓扑）；README 明确说这个后端还是概念验证，“不安全，不要在开放网络上运行”。文中实验用的是提交 `1242be6`。"
  - id: dllama
    title: "Distributed Llama"
    url: https://github.com/b4rtaz/distributed-llama
    site: GitHub
    date: 2026-10-20
    note: "张量并行；一个 root 加 2^n−1 个 worker，节点数上限等于模型的 KV head 数；同步用 q80 精度。Qwen3-30B-A3B Q40 模型文件 17.0 GB，Llama 3.3 70B Q40 为 40 GB。"
  - id: dllama-147
    title: "Llama 3.3 70B on 4 x Mac Mini M4 Pro 24GB RAM"
    url: https://github.com/b4rtaz/distributed-llama/discussions/147
    site: GitHub Discussions
    date: 2025-01-09
    note: "distributed-llama v0.11.1，纯 CPU，Thunderbolt 5 与 10G 以太网两种连接，分别给出每 token 的推理时间和传输时间。"
  - id: exo
    title: "exo：Run frontier AI locally"
    url: https://github.com/exo-explore/exo
    site: GitHub
    date: 2026-10-20
    note: "MLX 后端；张量并行在 2 台上提速 1.8 倍、4 台上 3.2 倍；macOS 26.2 起支持 Thunderbolt 5 上的 RDMA，README 称设备间时延降低 99%。"
  - id: geerling
    title: "1.5 TB of VRAM on Mac Studio – RDMA over Thunderbolt 5"
    url: https://jeffgeerling.com/blog/2025/15-tb-vram-on-mac-studio-rdma-over-thunderbolt-5
    author: Jeff Geerling
    date: 2025-12
    note: "4 台 Mac Studio（2 台 512GB、2 台 256GB，合计 39,596 美元）；exo 跑 Qwen3 235B 约 32 tok/s、Kimi K2 Thinking 约 30 tok/s；llama.cpp RPC 加节点反而变慢；RDMA 把时延从 300 微秒降到 50 微秒以内。"
  - id: megatron
    title: "Megatron-LM: Training Multi-Billion Parameter Language Models Using Model Parallelism"
    url: https://arxiv.org/abs/1909.08053
    author: "Mohammad Shoeybi 等"
    site: arXiv
    date: 2019-09-17
    note: "层内张量并行的经典切法：注意力和 MLP 各在前向里做一次 all-reduce，即每层两次。"
  - id: ionet-caas
    title: "io.net：Deploy a Container（CaaS API）"
    url: https://io.net/docs/reference/caas/deploy-a-container
    site: io.net
    date: 2026-10-20
    note: "“The container must expose an HTTP server on a single port, and it must run within a single node.” 模型 recipe 由用户选处理器、GPU 数量和地点。"
  - id: ionet-network
    title: "io.net：IO Network"
    url: https://io.net/docs/guides/architecture/io-network
    site: io.net
    date: 2026-10-20
    note: "每个集群在创建时建一张 mesh VPN；文档首页说供给来自独立数据中心、加密矿工以及 Filecoin、Render 等网络。"
  - id: ionet-idle
    title: "io.net：Device Utilization Threshold"
    url: https://io.net/docs/guides/workers/device-utilization-threshold
    site: io.net
    date: 2026-10-20
    note: "挂在网络上待租的设备必须空闲：CPU 利用率不超过 30%，GPU 不超过 5%（部分消费级卡 20%），否则会被封禁并扣减区块奖励。另有每小时一次的 PoW 验证。"
  - id: ionet-token
    title: "io.net：IO Tokenomics"
    url: https://io.net/docs/guides/coin/io-tokenomics
    site: io.net
    date: 2026-10-20
    note: "总量 8 亿枚 IO，其中 3 亿枚在 20 年内按小时作为区块奖励发给供应方及其质押者；网络收入用于回购销毁。"
  - id: akashml
    title: "AkashML: Managed AI Inference on the Decentralized Supercloud"
    url: https://akash.network/blog/akashml-managed-ai-inference-on-the-decentralized-supercloud
    site: Akash Network
    date: 2025-11
    note: "Akash 网络上的托管推理服务，OpenAI 兼容接口，运行在 65 个以上的数据中心。"
  - id: openrouter
    title: "OpenRouter Models（各模型的供应商与价格）"
    url: https://openrouter.ai/models
    site: OpenRouter
    date: 2026-10-20
    note: "通过公开接口 `api/v1/models/<model>/endpoints` 读取每家供应商的价格，2026-10-20 取数。供应商名 Io Net、AkashML、Chutes（Bittensor 子网 64）。"
  - id: trtllm
    title: "TensorRT-LLM Performance Overview"
    url: https://nvidia.github.io/TensorRT-LLM/developer-guide/perf-overview.html
    site: NVIDIA
    date: 2026-10-20
    note: "Llama 3.3 70B 在 H100 上最大负载输出约 2,209 tok/s/GPU（1k 输入 / 1k 输出）。"
---

Petals 的 README 第一行写着：

```text
Run large language models at home, BitTorrent-style
```

下面接着一句：Llama 2 70B 单批生成，最高每秒 6 个 token[^petals-gh]。一个 70B 的模型，就算压到 4 位也要 35 GB 左右，家里没有哪张显卡装得下。它的意思是：你只要跑模型的一小块，剩下的交给网上其他人的显卡，像 BT 下载那样大家互相帮忙。

我们自己也有过一个类似的念头：手上有 20 台小机器，每台大约 10 GB 内存，能不能连成一个网络，把 Qwen3-30B 跑起来，而且算下来比买 API 还便宜？再往外看，io.net 这类项目说它聚合了全世界几十万张闲置 GPU，听上去也是同一个思路。

那么，一个模型到底是怎么拆给很多台机器的？拆开以后每个 token 要在网络上走几趟，为什么 Petals 跨着公网还能有每秒几个 token？把闲置的机器连起来，真的能更便宜吗？我们从“为什么要拆”开始，一层一层往下看。

## 一、模型为什么要拆

### 现象：模型比任何一台机器的内存都大

先交代后文反复用到的几个词。大模型由几十层结构相同的**层**（transformer block）叠起来，每层里是一堆矩阵，这些矩阵的数字叫**权重**。生成文字时，模型每次只产出一个 **token**（一个词或半个词）：把当前的一个 token 变成一串数字，依次穿过所有层，最后算出下一个 token 是什么。在层与层之间流动的那串数字叫**激活**。为了不重复计算前文，每层还会把前面每个 token 的中间结果存下来，叫 **KV 缓存**。

权重有多大？Llama 2 70B 有 80 层、700 亿个权重，16 位存储要 140 GB，压到 4 位还有 35 GB 左右。distributed-llama 给出的 Llama 3.3 70B 4 位模型文件是 40 GB，Qwen3-30B-A3B 是 17 GB[^dllama]。而一张 T4 有 16 GB 显存，一张 RTX 3090 有 24 GB，一台入门的 Mac mini M4 Pro 也是 24 GB。

<figure class="dd dd-figure">
<div class="dd-scroll">
<svg class="dd-svg" viewBox="0 0 800 372" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="dif1-title dif1-desc">
<title id="dif1-title">模型权重与单机内存</title>
<desc id="dif1-desc">模型权重：Qwen3-30B-A3B 4 位 17 GB，Llama 2 70B 4 位 35 GB、16 位 140 GB，BLOOM-176B 8 位 176 GB；单机：10 GB 小机器、T4 16 GB、RTX 3090 24 GB、Mac mini M4 Pro 24 GB，都放不下 Llama 2 70B 的 4 位版本。</desc>
<rect class="dd-frame" width="800" height="372" rx="10"/>
<rect class="dd-zone" x="16" y="16" width="768" height="340" rx="8"/>
<text class="t-zone" x="32" y="44">放下权重要多少内存（GB）</text>
<line class="dd-hair" x1="232" y1="64" x2="232" y2="320"/>
<text class="t-note" x="232" y="338" text-anchor="middle">0</text>
<line class="dd-hair" x1="372" y1="64" x2="372" y2="320"/>
<text class="t-note" x="372" y="338" text-anchor="middle">50</text>
<line class="dd-hair" x1="512" y1="64" x2="512" y2="320"/>
<text class="t-note" x="512" y="338" text-anchor="middle">100</text>
<line class="dd-hair" x1="652" y1="64" x2="652" y2="320"/>
<text class="t-note" x="652" y="338" text-anchor="middle">150</text>
<text class="t-note" x="32" y="72">模型权重</text>
<rect class="dd-dot" x="232" y="80" width="47.6" height="18" rx="3"/>
<text class="t-name" x="220" y="94" text-anchor="end">Qwen3-30B-A3B · 4 位</text>
<text class="t-val" x="287.6" y="94">17</text>
<rect class="dd-fill-acc" x="232" y="108" width="98.0" height="18" rx="3"/>
<text class="t-name t-acc" x="220" y="122" text-anchor="end">Llama 2 70B · 4 位</text>
<text class="t-mono t-acc" x="338.0" y="122">35</text>
<rect class="dd-dot" x="232" y="136" width="392.0" height="18" rx="3"/>
<text class="t-name" x="220" y="150" text-anchor="end">Llama 2 70B · 16 位</text>
<text class="t-val" x="632.0" y="150">140</text>
<rect class="dd-dot" x="232" y="164" width="492.8" height="18" rx="3"/>
<text class="t-name" x="220" y="178" text-anchor="end">BLOOM-176B · 8 位</text>
<text class="t-val" x="732.8" y="178">176</text>
<text class="t-note" x="32" y="208">单机内存 / 显存</text>
<rect class="dd-card" x="232" y="216" width="28.0" height="18" rx="3"/>
<text class="t-name" x="220" y="230" text-anchor="end">小机器</text>
<text class="t-val" x="268.0" y="230">10</text>
<rect class="dd-card" x="232" y="244" width="44.8" height="18" rx="3"/>
<text class="t-name" x="220" y="258" text-anchor="end">NVIDIA T4</text>
<text class="t-val" x="284.8" y="258">16</text>
<rect class="dd-card" x="232" y="272" width="67.2" height="18" rx="3"/>
<text class="t-name" x="220" y="286" text-anchor="end">RTX 3090</text>
<text class="t-val" x="307.2" y="286">24</text>
<rect class="dd-card" x="232" y="300" width="67.2" height="18" rx="3"/>
<text class="t-name" x="220" y="314" text-anchor="end">Mac mini M4 Pro</text>
<text class="t-val" x="307.2" y="314">24</text>
<line class="dd-edge-acc dd-dash" x1="330" y1="104" x2="330" y2="322"/>
<text class="t-say" x="456" y="232">虚线是 Llama 2 70B 4 位的 35 GB：</text>
<text class="t-say" x="456" y="254">四种常见设备没有一台够得着。</text>
</svg>
</div>
<figcaption>图 1 · 一个 70B 模型压到 4 位，仍比一张消费级显卡或一台小机器的内存大</figcaption>
</figure>

### 原因：每生成一个 token，都要把整个模型读一遍

装不下为什么不能放在硬盘或内存里、用到哪层再搬上来？因为生成每一个 token 都要把所有层的权重读一遍。如果权重放在显卡外面，每个 token 都要把几十 GB 从内存搬进显存一次。Petals 的论文算过：哪怕用 PCIe 4.0 x16、假设显卡算得无限快，这种“卸载”（offloading）方式跑 BLOOM-176B 也只有每秒 0.18 步；实测只有 0.0427 步，也就是二十多秒才出一个 token[^petals-paper]。

### 解决：把模型切开，每台机器只放一部分

既然一台放不下、搬来搬去又太慢，就让每台机器常驻模型的一部分，权重从此不动，只让激活在机器之间流动。切法有两种：**按层切**，每台拿连续的若干层；**层内切**，每一层都拆开，每台拿每层的一部分。两种切法对网络的要求完全不同，我们先看更直观的按层切。

**按层切开以后，机器之间到底要传什么？**

## 二、按层切开：流水线并行

### 一个 token 怎么穿过三台机器

Petals 论文里的一组配置是三台各带一张 T4 的服务器，合起来 48 GB，放下 4 位的 Llama 2 70B[^petals-paper]。80 层按顺序分给三台，第一台拿 0–26 层，第二台拿 27–53 层，第三台拿 54–79 层。你自己的电脑是**客户端**，只放首尾两小块：把文字变成数字的嵌入层和把数字变回词表概率的输出层，BLOOM-176B 里这两块还不到全部权重的 3%。

这种切法叫**流水线并行**：像流水线上的工位，每个工位只做自己那几道工序，做完交给下一个。

<figure class="dd dd-figure">
<div class="dd-scroll">
<svg class="dd-svg" viewBox="0 0 800 404" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="dif2-title dif2-desc">
<title id="dif2-title">流水线并行：一个 token 穿过三台服务器</title>
<desc id="dif2-desc">Llama 2 70B 的 80 层分给三台 T4：A 拿 0–26 层，B 拿 27–53 层，C 拿 54–79 层，各自保留本段的 KV 缓存；客户端只放嵌入层和输出层。每生成一个 token，16 KB 的激活从客户端依次经过 A、B、C，再回到客户端采样。</desc>
<defs>
<marker id="dif2-arrow" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head" d="M1 1 L7 4 L1 7"/></marker>
<marker id="dif2-arrow-acc" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head-acc" d="M1 1 L7 4 L1 7"/></marker>
</defs>
<rect class="dd-frame" width="800" height="404" rx="10"/>
<rect class="dd-zone" x="16" y="16" width="160" height="280" rx="8"/>
<text class="t-zone" x="32" y="44">客户端</text>
<text class="t-note" x="32" y="64">你的电脑</text>
<rect class="dd-zone" x="192" y="16" width="192" height="280" rx="8"/>
<text class="t-zone" x="208" y="44">服务器 A</text>
<text class="t-note" x="208" y="64">NVIDIA T4 · 16 GB</text>
<rect class="dd-zone" x="392" y="16" width="192" height="280" rx="8"/>
<text class="t-zone" x="408" y="44">服务器 B</text>
<text class="t-note" x="408" y="64">NVIDIA T4 · 16 GB</text>
<rect class="dd-zone" x="592" y="16" width="192" height="280" rx="8"/>
<text class="t-zone" x="608" y="44">服务器 C</text>
<text class="t-note" x="608" y="64">NVIDIA T4 · 16 GB</text>
<line class="dd-edge-acc" x1="160" y1="151" x2="206" y2="151" marker-end="url(#dif2-arrow-acc)"/>
<line class="dd-edge-acc" x1="368" y1="151" x2="406" y2="151" marker-end="url(#dif2-arrow-acc)"/>
<line class="dd-edge-acc" x1="568" y1="151" x2="606" y2="151" marker-end="url(#dif2-arrow-acc)"/>
<path class="dd-edge-acc" d="M688 280 V308 Q688 316 680 316 H104 Q96 316 96 308 V266" marker-end="url(#dif2-arrow-acc)"/>
<text class="t-mono t-acc" x="184" y="143" text-anchor="middle">16 KB</text>
<text class="t-mono t-acc" x="387" y="143" text-anchor="middle">16 KB</text>
<text class="t-mono t-acc" x="587" y="143" text-anchor="middle">16 KB</text>
<text class="t-mono t-acc" x="392" y="334" text-anchor="middle">16 KB · 第 80 层的输出</text>
<rect class="dd-card" x="32" y="80" width="128" height="110" rx="6"/>
<path class="dd-card-head" d="M32.6 112 V86 Q32.6 80.6 38 80.6 H154 Q159.4 80.6 159.4 86 V112 Z"/>
<text class="t-name" x="44" y="101">只放首尾</text>
<text class="t-mono" x="148" y="101" text-anchor="end">&lt;3%</text>
<text class="t-mono" x="44" y="129">嵌入层</text>
<text class="t-val" x="148" y="129" text-anchor="end">词→向量</text>
<line class="dd-hair" x1="33" y1="138" x2="159" y2="138"/>
<text class="t-mono" x="44" y="155">输出层</text>
<text class="t-val" x="148" y="155" text-anchor="end">向量→词</text>
<line class="dd-hair" x1="33" y1="164" x2="159" y2="164"/>
<text class="t-mono" x="44" y="181">采样</text>
<text class="t-val" x="148" y="181" text-anchor="end">“Paris”</text>
<text class="t-note" x="32" y="214">算出下一个 token，</text>
<text class="t-note" x="32" y="232">再送回 A</text>
<rect class="dd-card" x="208" y="80" width="160" height="110" rx="6"/>
<path class="dd-card-head" d="M208.6 112 V86 Q208.6 80.6 214 80.6 H362 Q367.4 80.6 367.4 86 V112 Z"/>
<text class="t-name" x="220" y="101">blocks</text>
<text class="t-mono" x="356" y="101" text-anchor="end">0–26</text>
<text class="t-mono" x="220" y="129">层数</text>
<text class="t-val" x="356" y="129" text-anchor="end">27</text>
<line class="dd-hair" x1="209" y1="138" x2="367" y2="138"/>
<text class="t-mono" x="220" y="155">权重</text>
<text class="t-val" x="356" y="155" text-anchor="end">≈ 12 GB</text>
<line class="dd-hair" x1="209" y1="164" x2="367" y2="164"/>
<text class="t-mono" x="220" y="181">输出</text>
<text class="t-mono t-acc" x="356" y="181" text-anchor="end">8192 × fp16</text>
<rect class="dd-derived" x="208" y="206" width="160" height="60" rx="6"/>
<text class="t-name" x="220" y="228">KV 缓存</text>
<text class="t-note" x="220" y="250">本段的前文，随会话变长</text>
<rect class="dd-card" x="408" y="80" width="160" height="110" rx="6"/>
<path class="dd-card-head" d="M408.6 112 V86 Q408.6 80.6 414 80.6 H562 Q567.4 80.6 567.4 86 V112 Z"/>
<text class="t-name" x="420" y="101">blocks</text>
<text class="t-mono" x="556" y="101" text-anchor="end">27–53</text>
<text class="t-mono" x="420" y="129">层数</text>
<text class="t-val" x="556" y="129" text-anchor="end">27</text>
<line class="dd-hair" x1="409" y1="138" x2="567" y2="138"/>
<text class="t-mono" x="420" y="155">权重</text>
<text class="t-val" x="556" y="155" text-anchor="end">≈ 12 GB</text>
<line class="dd-hair" x1="409" y1="164" x2="567" y2="164"/>
<text class="t-mono" x="420" y="181">输出</text>
<text class="t-mono t-acc" x="556" y="181" text-anchor="end">8192 × fp16</text>
<rect class="dd-derived" x="408" y="206" width="160" height="60" rx="6"/>
<text class="t-name" x="420" y="228">KV 缓存</text>
<text class="t-note" x="420" y="250">本段的前文，随会话变长</text>
<rect class="dd-card" x="608" y="80" width="160" height="110" rx="6"/>
<path class="dd-card-head" d="M608.6 112 V86 Q608.6 80.6 614 80.6 H762 Q767.4 80.6 767.4 86 V112 Z"/>
<text class="t-name" x="620" y="101">blocks</text>
<text class="t-mono" x="756" y="101" text-anchor="end">54–79</text>
<text class="t-mono" x="620" y="129">层数</text>
<text class="t-val" x="756" y="129" text-anchor="end">26</text>
<line class="dd-hair" x1="609" y1="138" x2="767" y2="138"/>
<text class="t-mono" x="620" y="155">权重</text>
<text class="t-val" x="756" y="155" text-anchor="end">≈ 12 GB</text>
<line class="dd-hair" x1="609" y1="164" x2="767" y2="164"/>
<text class="t-mono" x="620" y="181">输出</text>
<text class="t-mono t-acc" x="756" y="181" text-anchor="end">8192 × fp16</text>
<rect class="dd-derived" x="608" y="206" width="160" height="60" rx="6"/>
<text class="t-name" x="620" y="228">KV 缓存</text>
<text class="t-note" x="620" y="250">本段的前文，随会话变长</text>
<text class="t-say" x="400" y="362" text-anchor="middle">每生成一个 token，这 16 KB 都要走完 A → B → C → 客户端一圈。</text>
<text class="t-say" x="400" y="384" text-anchor="middle">虚线框是会话状态：服务器掉线，它就没了，要靠客户端记下的输入重建。</text>
</svg>
</div>
<figcaption>图 2 · 按层切开以后，机器之间只传一个 token 的激活，Llama 2 70B 每一跳是 16 KB</figcaption>
</figure>

图 2 里沿着强调色走一遍：客户端把当前 token 变成 8192 个数，16 位存储是 16 KB；服务器 A 跑完 27 层，把同样大小的 16 KB 交给 B，B 交给 C，C 再把结果交回客户端，客户端采样出下一个词。之后的每一个 token 都重复这一圈。

每台服务器还要把自己那几层的 KV 缓存留在本机，这样下一个 token 只需要传这一个 token 的激活，不用把前文重传一遍。

### 网络带宽不是问题

每一跳只有十几 KB，在 100 Mbit/s 的家用宽带上传一次只要 1.3 毫秒。Petals 的实测也是这样：3 台 T4 跑 Llama 2 70B，带宽从 1 Gbit/s 降到 100 Mbit/s，每秒生成步数还是 2.29，一点没变；10 台 RTX 3090 跑 BLOOM-176B，从 1.17 降到 1.05[^petals-paper]。

### 机器会掉线：两份缓存

公网上的机器是别人的，随时可能关机。KV 缓存在服务器上，服务器一走，这一段的前文就丢了。Petals 的办法是再存一份：客户端把发给每一段的输入都记下来。某台服务器掉线时，客户端找另一台持有同样几层的服务器，把记下的输入重放一遍，重建它的 KV 缓存，然后接着生成[^petals-paper]。客户端还会测量每台服务器的延迟，挑一条总耗时最短的路线。

带宽不是问题，掉线也有办法。**那 Petals 为什么只有每秒几个 token？**

## 三、每个 token 都要跑一圈网络

### 现象：延迟一高，速度就掉

还是 Petals 的数据：带宽固定在 100 Mbit/s，把往返时延（RTT，一个数据包到对方再回来的时间）从不到 5 毫秒加到 100 毫秒，3 台 T4 每秒的步数从 2.29 掉到 1.57；10 台 RTX 3090 从 1.05 掉到 0.34，每个 token 从不到 1 秒变成将近 3 秒[^petals-paper]。

### 实验：每多一个节点，每个 token 多一次往返

为了把网络的影响单独拿出来看，我们用 llama.cpp 的 RPC 后端做了个小实验[^llamacpp-rpc]。它的做法和 Petals 一样是按层切：每台机器运行一个 `ggml-rpc-server`，主进程用 `--rpc` 连上它们，把各层分配过去。

```bash
# 每个节点
ggml-rpc-server -p 51001 -t 4
# 主进程：全部层放到 RPC 节点上
llama-bench -m qwen2.5-1.5b-instruct-q4_k_m.gguf -p 0 -n 32 -ngl 99 \
  --rpc 127.0.0.1:52001,127.0.0.1:52002,127.0.0.1:52003,127.0.0.1:52004
```

模型是 Qwen2.5-1.5B 的 4 位量化版，1.04 GiB。1、2、4 个节点都跑在同一台 4 核的云服务器上，中间各串一个自己写的 TCP 转发程序，每个方向延迟固定的毫秒数，用来模拟不同的往返时延。这样算力始终是同一份，变的只有节点数和网络。不经过 RPC、直接在本机跑，是每 token 38.4 毫秒。

<figure class="dd dd-figure">
<div class="dd-scroll">
<svg class="dd-svg" viewBox="0 0 800 360" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="dif3-title dif3-desc">
<title id="dif3-title">llama.cpp RPC：每 token 耗时与往返时延</title>
<desc id="dif3-desc">同一台 4 核机器上，Qwen2.5-1.5B 4 位分给 1、2、4 个 RPC 节点。往返时延 0、10、20、40 毫秒时，每 token 耗时：1 节点 47.1、54.6、67.9、88.3 毫秒；2 节点 56.0、74.8、87.4、132.6；4 节点 66.8、91.7、133.1、214.9。斜率分别是每 token 1.05、1.90、3.79 个往返。本机直接跑 38.4 毫秒。</desc>
<rect class="dd-frame" width="800" height="360" rx="10"/>
<rect class="dd-zone" x="16" y="16" width="560" height="328" rx="8"/>
<text class="t-zone" x="32" y="44">Qwen2.5-1.5B · 4 位 · 全部层放在 RPC 节点上</text>
<rect class="dd-zone" x="592" y="16" width="192" height="328" rx="8"/>
<line class="dd-hair" x1="88" y1="296.0" x2="520" y2="296.0"/>
<text class="t-note" x="80" y="300.0" text-anchor="end">0</text>
<line class="dd-hair" x1="88" y1="240.0" x2="520" y2="240.0"/>
<text class="t-note" x="80" y="244.0" text-anchor="end">60</text>
<line class="dd-hair" x1="88" y1="184.0" x2="520" y2="184.0"/>
<text class="t-note" x="80" y="188.0" text-anchor="end">120</text>
<line class="dd-hair" x1="88" y1="128.0" x2="520" y2="128.0"/>
<text class="t-note" x="80" y="132.0" text-anchor="end">180</text>
<line class="dd-hair" x1="88" y1="72.0" x2="520" y2="72.0"/>
<text class="t-note" x="80" y="76.0" text-anchor="end">240</text>
<text class="t-note" x="88.0" y="314" text-anchor="middle">0</text>
<text class="t-note" x="196.0" y="314" text-anchor="middle">10</text>
<text class="t-note" x="304.0" y="314" text-anchor="middle">20</text>
<text class="t-note" x="520.0" y="314" text-anchor="middle">40</text>
<text class="t-note" x="304" y="332" text-anchor="middle">往返时延 RTT（毫秒）</text>
<text class="t-note" x="32" y="64">每 token 耗时（毫秒）</text>
<line class="dd-edge dd-dash" x1="88" y1="260.2" x2="520" y2="260.2"/>
<text class="t-note" x="520" y="276.2" text-anchor="end">本机直接跑 38.4</text>
<path class="dd-edge" d="M88.0 252.0 L196.0 245.0 L304.0 232.6 L520.0 213.6" stroke-width="2"/>
<circle class="dd-dot" cx="88.0" cy="252.0" r="3.5"/>
<circle class="dd-dot" cx="196.0" cy="245.0" r="3.5"/>
<circle class="dd-dot" cx="304.0" cy="232.6" r="3.5"/>
<circle class="dd-dot" cx="520.0" cy="213.6" r="3.5"/>
<text class="t-name" x="530.0" y="217.6">1 节点</text>
<path class="dd-edge" d="M88.0 243.7 L196.0 226.2 L304.0 214.4 L520.0 172.2" stroke-width="2"/>
<circle class="dd-dot" cx="88.0" cy="243.7" r="3.5"/>
<circle class="dd-dot" cx="196.0" cy="226.2" r="3.5"/>
<circle class="dd-dot" cx="304.0" cy="214.4" r="3.5"/>
<circle class="dd-dot" cx="520.0" cy="172.2" r="3.5"/>
<text class="t-name" x="530.0" y="176.2">2 节点</text>
<path class="dd-edge-acc" d="M88.0 233.7 L196.0 210.4 L304.0 171.8 L520.0 95.4" stroke-width="2"/>
<circle class="dd-fill-acc" cx="88.0" cy="233.7" r="3.5"/>
<circle class="dd-fill-acc" cx="196.0" cy="210.4" r="3.5"/>
<circle class="dd-fill-acc" cx="304.0" cy="171.8" r="3.5"/>
<circle class="dd-fill-acc" cx="520.0" cy="95.4" r="3.5"/>
<text class="t-name t-acc" x="530.0" y="99.4">4 节点</text>
<text class="t-mono t-acc" x="514.0" y="85.4" text-anchor="end">214.9</text>
<text class="t-name" x="608" y="48">斜率（RTT / token）</text>
<text class="t-mono" x="608" y="80">1 节点</text>
<text class="t-val" x="768" y="80" text-anchor="end">1.05</text>
<line class="dd-hair" x1="608" y1="89" x2="768" y2="89"/>
<text class="t-mono" x="608" y="106">2 节点</text>
<text class="t-val" x="768" y="106" text-anchor="end">1.90</text>
<line class="dd-hair" x1="608" y1="115" x2="768" y2="115"/>
<text class="t-mono t-acc" x="608" y="132">4 节点</text>
<text class="t-mono t-acc" x="768" y="132" text-anchor="end">3.79</text>
<text class="t-say" x="608" y="186">每多一个节点，</text>
<text class="t-say" x="608" y="208">每个 token 多付</text>
<text class="t-say" x="608" y="230">大约一次往返。</text>
<text class="t-note" x="608" y="270">4 个节点在同一台机器上，</text>
<text class="t-note" x="608" y="288">时延由转发程序注入，</text>
<text class="t-note" x="608" y="306">算力始终是同一份。</text>
</svg>
</div>
<figcaption>图 3 · 按层切开后，每个 token 对每个节点付一次往返：4 个节点、RTT 40 毫秒时，每 token 从 38 毫秒涨到 215 毫秒（llama.cpp 实测）</figcaption>
</figure>

三条线几乎都是直线，斜率分别是 1.05、1.90、3.79：**每多一个节点，每个 token 就多付大约一次往返时延**。即使时延为零，每多一个节点也要多几毫秒的固定开销：4 个节点每个 token 要 66.8 毫秒，比本机多了七成多。

### 原因：流水线上的每一跳都要排队等

道理很简单。下一个 token 要等这一个 token 算完才知道是什么，所以一个请求里的 token 只能一个接一个地生成，每个 token 又要按顺序穿过每一段。一个 token 的耗时大约是：

$$
t_{\text{token}} \approx \sum_{i=1}^{N} t_i + N \cdot \text{RTT}
$$

$t_i$ 是第 $i$ 台机器算自己那几层的时间，$N$ 是段数。前一项和整个模型放在一台机器上时差不多：总共要读的权重一样多，只是分给了好几台轮流读。后一项是拆开以后多出来的。llama.cpp 的 RPC 是星形连接，激活每次都要回到主进程再发给下一台；Petals 让服务器直接传给下一段，但段数一多，往返的次数照样跟着涨。

这就解释了开头的数字。在同一个城市里，RTT 几毫秒，三五台机器多出来的不过十几毫秒，Llama 2 70B 还能有每秒两步左右；跨大洲 RTT 上百毫秒，再加几段，每个 token 就要一两秒。也解释了 Jeff Geerling 在 4 台 Mac Studio 上看到的现象：llama.cpp RPC 加机器反而变慢[^geerling]。按层切能**装下**更大的模型，但不会让单个请求更快。

**有没有办法让几台机器同时算，把单个请求也变快？**

## 四、层内切开：张量并行

### 让每一层都由所有机器一起算

另一种切法是把每一层都拆开。一层里最大的是几个矩阵乘法，把矩阵按列切成 4 份，4 台机器各算一份，再把结果拼起来，这一层就快了将近 4 倍。这叫**张量并行**。distributed-llama 和 exo 走的都是这条路[^dllama][^exo]。

代价是同步。每台只算出了部分结果，必须和其他机器交换、合并，才能进入下一步。常见的切法是每层同步两次，注意力之后一次，MLP 之后一次[^megatron]。Llama 2 70B 有 80 层，生成一个 token 就要全员同步 160 次。流水线并行一个 token 只要 $N$ 跳，张量并行要 160 次。

<figure class="dd dd-figure">
<div class="dd-scroll">
<svg class="dd-svg" viewBox="0 0 800 420" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="dif4-title dif4-desc">
<title id="dif4-title">生成一个 token：流水线并行与张量并行</title>
<desc id="dif4-desc">上半：流水线并行，4 台机器轮流算，每台算完把激活传给下一台，一个 token 4 跳，同一时刻只有一台在忙。下半：张量并行，4 台同时算每一层的一部分，每层同步两次，80 层共 160 次同步。</desc>
<rect class="dd-frame" width="800" height="420" rx="10"/>
<rect class="dd-zone" x="16" y="16" width="768" height="184" rx="8"/>
<text class="t-zone" x="32" y="44">流水线并行 · 按层切（Petals、llama.cpp RPC）</text>
<rect class="dd-zone" x="16" y="216" width="768" height="188" rx="8"/>
<text class="t-zone" x="32" y="244">张量并行 · 层内切（distributed-llama、exo）</text>
<text class="t-name" x="100" y="74" text-anchor="end">机器 1</text>
<rect class="dd-track" x="112" y="60" width="640" height="18" rx="3"/>
<rect class="dd-dot" x="122.0" y="60" width="140.0" height="18" rx="3"/>
<text class="t-small" x="192.0" y="73" text-anchor="middle" style="fill:var(--dd-node)">层 0–19</text>
<path class="dd-edge-acc" d="M262.0 78 V95"/>
<circle class="dd-fill-acc" cx="262.0" cy="78" r="3"/>
<text class="t-name" x="100" y="100" text-anchor="end">机器 2</text>
<rect class="dd-track" x="112" y="86" width="640" height="18" rx="3"/>
<rect class="dd-dot" x="282.0" y="86" width="140.0" height="18" rx="3"/>
<text class="t-small" x="352.0" y="99" text-anchor="middle" style="fill:var(--dd-node)">层 20–39</text>
<path class="dd-edge-acc" d="M422.0 104 V121"/>
<circle class="dd-fill-acc" cx="422.0" cy="104" r="3"/>
<text class="t-name" x="100" y="126" text-anchor="end">机器 3</text>
<rect class="dd-track" x="112" y="112" width="640" height="18" rx="3"/>
<rect class="dd-dot" x="442.0" y="112" width="140.0" height="18" rx="3"/>
<text class="t-small" x="512.0" y="125" text-anchor="middle" style="fill:var(--dd-node)">层 40–59</text>
<path class="dd-edge-acc" d="M582.0 130 V147"/>
<circle class="dd-fill-acc" cx="582.0" cy="130" r="3"/>
<text class="t-name" x="100" y="152" text-anchor="end">机器 4</text>
<rect class="dd-track" x="112" y="138" width="640" height="18" rx="3"/>
<rect class="dd-dot" x="602.0" y="138" width="140.0" height="18" rx="3"/>
<text class="t-small" x="672.0" y="151" text-anchor="middle" style="fill:var(--dd-node)">层 60–79</text>
<text class="t-say" x="112" y="180">一个 token 4 跳；同一时刻只有一台在算，其余三台在等。</text>
<text class="t-name" x="100" y="274" text-anchor="end">机器 1</text>
<rect class="dd-track" x="112" y="260" width="640" height="18" rx="3"/>
<rect class="dd-dot" x="115.0" y="260" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="145.0" y="260" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="175.0" y="260" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="205.0" y="260" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="235.0" y="260" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="265.0" y="260" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="295.0" y="260" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="325.0" y="260" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="355.0" y="260" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="385.0" y="260" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="415.0" y="260" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="445.0" y="260" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="475.0" y="260" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="505.0" y="260" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="535.0" y="260" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="565.0" y="260" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="595.0" y="260" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="625.0" y="260" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="655.0" y="260" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="685.0" y="260" width="22.0" height="18" rx="2"/>
<text class="t-name" x="100" y="300" text-anchor="end">机器 2</text>
<rect class="dd-track" x="112" y="286" width="640" height="18" rx="3"/>
<rect class="dd-dot" x="115.0" y="286" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="145.0" y="286" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="175.0" y="286" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="205.0" y="286" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="235.0" y="286" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="265.0" y="286" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="295.0" y="286" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="325.0" y="286" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="355.0" y="286" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="385.0" y="286" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="415.0" y="286" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="445.0" y="286" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="475.0" y="286" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="505.0" y="286" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="535.0" y="286" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="565.0" y="286" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="595.0" y="286" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="625.0" y="286" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="655.0" y="286" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="685.0" y="286" width="22.0" height="18" rx="2"/>
<text class="t-name" x="100" y="326" text-anchor="end">机器 3</text>
<rect class="dd-track" x="112" y="312" width="640" height="18" rx="3"/>
<rect class="dd-dot" x="115.0" y="312" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="145.0" y="312" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="175.0" y="312" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="205.0" y="312" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="235.0" y="312" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="265.0" y="312" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="295.0" y="312" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="325.0" y="312" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="355.0" y="312" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="385.0" y="312" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="415.0" y="312" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="445.0" y="312" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="475.0" y="312" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="505.0" y="312" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="535.0" y="312" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="565.0" y="312" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="595.0" y="312" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="625.0" y="312" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="655.0" y="312" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="685.0" y="312" width="22.0" height="18" rx="2"/>
<text class="t-name" x="100" y="352" text-anchor="end">机器 4</text>
<rect class="dd-track" x="112" y="338" width="640" height="18" rx="3"/>
<rect class="dd-dot" x="115.0" y="338" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="145.0" y="338" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="175.0" y="338" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="205.0" y="338" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="235.0" y="338" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="265.0" y="338" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="295.0" y="338" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="325.0" y="338" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="355.0" y="338" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="385.0" y="338" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="415.0" y="338" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="445.0" y="338" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="475.0" y="338" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="505.0" y="338" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="535.0" y="338" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="565.0" y="338" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="595.0" y="338" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="625.0" y="338" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="655.0" y="338" width="22.0" height="18" rx="2"/>
<rect class="dd-dot" x="685.0" y="338" width="22.0" height="18" rx="2"/>
<line class="dd-edge-acc" x1="139.5" y1="254" x2="139.5" y2="362"/>
<line class="dd-edge-acc" x1="169.5" y1="254" x2="169.5" y2="362"/>
<line class="dd-edge-acc" x1="199.5" y1="254" x2="199.5" y2="362"/>
<line class="dd-edge-acc" x1="229.5" y1="254" x2="229.5" y2="362"/>
<line class="dd-edge-acc" x1="259.5" y1="254" x2="259.5" y2="362"/>
<line class="dd-edge-acc" x1="289.5" y1="254" x2="289.5" y2="362"/>
<line class="dd-edge-acc" x1="319.5" y1="254" x2="319.5" y2="362"/>
<line class="dd-edge-acc" x1="349.5" y1="254" x2="349.5" y2="362"/>
<line class="dd-edge-acc" x1="379.5" y1="254" x2="379.5" y2="362"/>
<line class="dd-edge-acc" x1="409.5" y1="254" x2="409.5" y2="362"/>
<line class="dd-edge-acc" x1="439.5" y1="254" x2="439.5" y2="362"/>
<line class="dd-edge-acc" x1="469.5" y1="254" x2="469.5" y2="362"/>
<line class="dd-edge-acc" x1="499.5" y1="254" x2="499.5" y2="362"/>
<line class="dd-edge-acc" x1="529.5" y1="254" x2="529.5" y2="362"/>
<line class="dd-edge-acc" x1="559.5" y1="254" x2="559.5" y2="362"/>
<line class="dd-edge-acc" x1="589.5" y1="254" x2="589.5" y2="362"/>
<line class="dd-edge-acc" x1="619.5" y1="254" x2="619.5" y2="362"/>
<line class="dd-edge-acc" x1="649.5" y1="254" x2="649.5" y2="362"/>
<line class="dd-edge-acc" x1="679.5" y1="254" x2="679.5" y2="362"/>
<line class="dd-edge-acc" x1="709.5" y1="254" x2="709.5" y2="362"/>
<text class="t-note" x="732" y="274" text-anchor="middle">…</text>
<text class="t-note" x="732" y="326" text-anchor="middle">…</text>
<text class="t-say" x="112" y="384">每层同步 2 次，80 层就是 <tspan class="t-acc">160 次</tspan>全员同步；四台一起算，但一刻也离不开网络。</text>
</svg>
</div>
<figcaption>图 4 · 生成一个 token，流水线并行要传 4 次，张量并行要全员同步 160 次（Llama 2 70B，4 台机器，图中只画出前 10 层）</figcaption>
</figure>

### 实测：局域网里能变快

distributed-llama 有一组 4 台 Mac mini M4 Pro（每台 24 GB）的实测[^dllama-147]：

<figure class="dd dd-figure dt-figure">
<div class="dt-panel">
<div class="dt-title" id="dit1-title">distributed-llama · Mac mini M4 Pro 24 GB · 纯 CPU</div>
<div class="dt-scroll">
<table class="dt" aria-labelledby="dit1-title">
<colgroup><col style="width:34%"><col><col><col></colgroup>
<thead>
<tr><th scope="col">配置</th><th scope="col">tok/s</th><th scope="col">推理 ms/token</th><th scope="col">传输 ms/token</th></tr>
</thead>
<tbody>
<tr class="dt-group"><th colspan="4" scope="rowgroup">Llama 3.1 8B · 4 位（单台放得下）</th></tr>
<tr><th scope="row">1 台</th><td class="mono num">16.91</td><td class="mono num">58.70</td><td class="mono num">0.06</td></tr>
<tr><th scope="row">2 台 · 雷雳 5</th><td class="mono num acc">30.27</td><td class="mono num">25.81</td><td class="mono num">6.94</td></tr>
<tr><th scope="row">4 台 · 雷雳 5</th><td class="mono num acc">38.83</td><td class="mono num">20.73</td><td class="mono num">4.61</td></tr>
<tr><th scope="row">4 台 · 10G 以太网</th><td class="mono num">34.61</td><td class="mono num">14.34</td><td class="mono num">14.25</td></tr>
<tr class="dt-group"><th colspan="4" scope="rowgroup">Llama 3.3 70B · 4 位（单台 24 GB 放不下）</th></tr>
<tr><th scope="row">2 台 · 雷雳 5</th><td class="mono num">3.31</td><td class="mono num">285.05</td><td class="mono num">16.28</td></tr>
<tr><th scope="row">4 台 · 雷雳 5</th><td class="mono num acc">6.51</td><td class="mono num">116.64</td><td class="mono num">36.62</td></tr>
<tr><th scope="row">4 台 · 10G 以太网</th><td class="mono num">6.04</td><td class="mono num">130.47</td><td class="mono num">34.41</td></tr>
</tbody>
</table>
</div>
<p class="dt-note">数据来自 distributed-llama 讨论区 #147（v0.11.1，生成 64 个 token）。</p>
</div>
<figcaption>表 1 · 张量并行在局域网里能让单个请求变快：8B 模型 4 台快 2.3 倍；70B 要 4 台才装得下，每个 token 有约 37 毫秒花在传输上</figcaption>
</figure>

Llama 3.1 8B 一台就放得下，2 台快了 1.79 倍，4 台到 2.3 倍。Llama 3.3 70B 单台装不下，4 台时每个 token 里 116 毫秒在算，37 毫秒在传。exo 在 4 台 Mac Studio 上用 Thunderbolt 5 的 RDMA，把设备之间的时延从 300 微秒压到 50 微秒以内，Qwen3 235B 跑到每秒 32 个 token[^geerling]。

### 不足：同步次数太多，离不开一个房间

160 次同步，每次哪怕只要 1 毫秒，一个 token 就是 160 毫秒；放到 RTT 50 毫秒的公网上，就是 8 秒。所以张量并行的机器都要靠网线、雷雳线直连，最好在同一张桌子上。exo 和 distributed-llama 都是“把家里几台机器连起来”，而不是“把全世界的机器连起来”。

两种切法各有边界：**按层切能跨公网，但单个请求不会更快；层内切能更快，但走不出局域网。** 不管哪一种，单个请求的速度都不比一台装得下整个模型的机器快多少。那么换个角度：**单个请求慢，能不能靠同时服务很多请求，把总吞吐做上去？**

## 五、靠并发把机器填满

### 现象：流水线上大部分机器在等

回到按层切。一个请求在跑的时候，同一时刻只有一台机器在算，其他机器都在等激活传过来。4 段流水线，每台机器有四分之三的时间是空的。

### 解决：让多个请求错开排队

如果同时有好几个请求，机器 A 算完请求 1 的第一段交出去以后，马上可以接请求 2 的第一段。只要请求够多，每台机器都能一直有活干。

<figure class="dd dd-figure">
<div class="dd-scroll">
<svg class="dd-svg" viewBox="0 0 800 372" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="dif5-title dif5-desc">
<title id="dif5-title">流水线上的并发</title>
<desc id="dif5-desc">上半：只有一个请求时，3 段流水线上每台机器三分之二的时间在等。下半：3 个请求错开进入流水线，从第三个时间格起每台机器都在忙。</desc>
<rect class="dd-frame" width="800" height="372" rx="10"/>
<rect class="dd-zone" x="16" y="16" width="768" height="156" rx="8"/>
<text class="t-zone" x="32" y="44">只有 1 个请求</text>
<rect class="dd-zone" x="16" y="188" width="768" height="168" rx="8"/>
<text class="t-zone" x="32" y="216">3 个请求错开排队</text>
<text class="t-name" x="100" y="74" text-anchor="end">机器 A</text>
<rect class="dd-track" x="112" y="60" width="630" height="18" rx="3"/>
<text class="t-name" x="100" y="100" text-anchor="end">机器 B</text>
<rect class="dd-track" x="112" y="86" width="630" height="18" rx="3"/>
<text class="t-name" x="100" y="126" text-anchor="end">机器 C</text>
<rect class="dd-track" x="112" y="112" width="630" height="18" rx="3"/>
<rect class="dd-fill-acc" x="115" y="60" width="64" height="18" rx="3"/>
<text class="t-small" x="147.0" y="73" text-anchor="middle" style="fill:var(--dd-node)">甲·1</text>
<rect class="dd-fill-acc" x="185" y="86" width="64" height="18" rx="3"/>
<text class="t-small" x="217.0" y="99" text-anchor="middle" style="fill:var(--dd-node)">甲·1</text>
<rect class="dd-fill-acc" x="255" y="112" width="64" height="18" rx="3"/>
<text class="t-small" x="287.0" y="125" text-anchor="middle" style="fill:var(--dd-node)">甲·1</text>
<rect class="dd-fill-acc" x="325" y="60" width="64" height="18" rx="3"/>
<text class="t-small" x="357.0" y="73" text-anchor="middle" style="fill:var(--dd-node)">甲·2</text>
<rect class="dd-fill-acc" x="395" y="86" width="64" height="18" rx="3"/>
<text class="t-small" x="427.0" y="99" text-anchor="middle" style="fill:var(--dd-node)">甲·2</text>
<rect class="dd-fill-acc" x="465" y="112" width="64" height="18" rx="3"/>
<text class="t-small" x="497.0" y="125" text-anchor="middle" style="fill:var(--dd-node)">甲·2</text>
<rect class="dd-fill-acc" x="535" y="60" width="64" height="18" rx="3"/>
<text class="t-small" x="567.0" y="73" text-anchor="middle" style="fill:var(--dd-node)">甲·3</text>
<rect class="dd-fill-acc" x="605" y="86" width="64" height="18" rx="3"/>
<text class="t-small" x="637.0" y="99" text-anchor="middle" style="fill:var(--dd-node)">甲·3</text>
<rect class="dd-fill-acc" x="675" y="112" width="64" height="18" rx="3"/>
<text class="t-small" x="707.0" y="125" text-anchor="middle" style="fill:var(--dd-node)">甲·3</text>
<text class="t-say" x="112" y="156">每台机器只有三分之一的时间在算：下一个 token 要等这一个走完全程。</text>
<text class="t-name" x="100" y="246" text-anchor="end">机器 A</text>
<rect class="dd-track" x="112" y="232" width="630" height="18" rx="3"/>
<text class="t-name" x="100" y="272" text-anchor="end">机器 B</text>
<rect class="dd-track" x="112" y="258" width="630" height="18" rx="3"/>
<text class="t-name" x="100" y="298" text-anchor="end">机器 C</text>
<rect class="dd-track" x="112" y="284" width="630" height="18" rx="3"/>
<rect class="dd-fill-acc" x="115" y="232" width="64" height="18" rx="3"/>
<text class="t-small" x="147.0" y="245" text-anchor="middle" style="fill:var(--dd-node)">甲·1</text>
<rect class="dd-dot" x="185" y="232" width="64" height="18" rx="3"/>
<text class="t-small" x="217.0" y="245" text-anchor="middle" style="fill:var(--dd-node)">乙·1</text>
<rect class="dd-dot" x="255" y="232" width="64" height="18" rx="3"/>
<text class="t-small" x="287.0" y="245" text-anchor="middle" style="fill:var(--dd-node)">丙·1</text>
<rect class="dd-fill-acc" x="185" y="258" width="64" height="18" rx="3"/>
<text class="t-small" x="217.0" y="271" text-anchor="middle" style="fill:var(--dd-node)">甲·1</text>
<rect class="dd-dot" x="255" y="258" width="64" height="18" rx="3"/>
<text class="t-small" x="287.0" y="271" text-anchor="middle" style="fill:var(--dd-node)">乙·1</text>
<rect class="dd-dot" x="325" y="258" width="64" height="18" rx="3"/>
<text class="t-small" x="357.0" y="271" text-anchor="middle" style="fill:var(--dd-node)">丙·1</text>
<rect class="dd-fill-acc" x="255" y="284" width="64" height="18" rx="3"/>
<text class="t-small" x="287.0" y="297" text-anchor="middle" style="fill:var(--dd-node)">甲·1</text>
<rect class="dd-dot" x="325" y="284" width="64" height="18" rx="3"/>
<text class="t-small" x="357.0" y="297" text-anchor="middle" style="fill:var(--dd-node)">乙·1</text>
<rect class="dd-dot" x="395" y="284" width="64" height="18" rx="3"/>
<text class="t-small" x="427.0" y="297" text-anchor="middle" style="fill:var(--dd-node)">丙·1</text>
<rect class="dd-fill-acc" x="325" y="232" width="64" height="18" rx="3"/>
<text class="t-small" x="357.0" y="245" text-anchor="middle" style="fill:var(--dd-node)">甲·2</text>
<rect class="dd-dot" x="395" y="232" width="64" height="18" rx="3"/>
<text class="t-small" x="427.0" y="245" text-anchor="middle" style="fill:var(--dd-node)">乙·2</text>
<rect class="dd-dot" x="465" y="232" width="64" height="18" rx="3"/>
<text class="t-small" x="497.0" y="245" text-anchor="middle" style="fill:var(--dd-node)">丙·2</text>
<rect class="dd-fill-acc" x="395" y="258" width="64" height="18" rx="3"/>
<text class="t-small" x="427.0" y="271" text-anchor="middle" style="fill:var(--dd-node)">甲·2</text>
<rect class="dd-dot" x="465" y="258" width="64" height="18" rx="3"/>
<text class="t-small" x="497.0" y="271" text-anchor="middle" style="fill:var(--dd-node)">乙·2</text>
<rect class="dd-dot" x="535" y="258" width="64" height="18" rx="3"/>
<text class="t-small" x="567.0" y="271" text-anchor="middle" style="fill:var(--dd-node)">丙·2</text>
<rect class="dd-fill-acc" x="465" y="284" width="64" height="18" rx="3"/>
<text class="t-small" x="497.0" y="297" text-anchor="middle" style="fill:var(--dd-node)">甲·2</text>
<rect class="dd-dot" x="535" y="284" width="64" height="18" rx="3"/>
<text class="t-small" x="567.0" y="297" text-anchor="middle" style="fill:var(--dd-node)">乙·2</text>
<rect class="dd-dot" x="605" y="284" width="64" height="18" rx="3"/>
<text class="t-small" x="637.0" y="297" text-anchor="middle" style="fill:var(--dd-node)">丙·2</text>
<rect class="dd-fill-acc" x="535" y="232" width="64" height="18" rx="3"/>
<text class="t-small" x="567.0" y="245" text-anchor="middle" style="fill:var(--dd-node)">甲·3</text>
<rect class="dd-dot" x="605" y="232" width="64" height="18" rx="3"/>
<text class="t-small" x="637.0" y="245" text-anchor="middle" style="fill:var(--dd-node)">乙·3</text>
<rect class="dd-dot" x="675" y="232" width="64" height="18" rx="3"/>
<text class="t-small" x="707.0" y="245" text-anchor="middle" style="fill:var(--dd-node)">丙·3</text>
<rect class="dd-fill-acc" x="605" y="258" width="64" height="18" rx="3"/>
<text class="t-small" x="637.0" y="271" text-anchor="middle" style="fill:var(--dd-node)">甲·3</text>
<rect class="dd-dot" x="675" y="258" width="64" height="18" rx="3"/>
<text class="t-small" x="707.0" y="271" text-anchor="middle" style="fill:var(--dd-node)">乙·3</text>
<rect class="dd-fill-acc" x="675" y="284" width="64" height="18" rx="3"/>
<text class="t-small" x="707.0" y="297" text-anchor="middle" style="fill:var(--dd-node)">甲·3</text>
<text class="t-note" x="112" y="322">时间 →</text>
<text class="t-say" x="112" y="340">请求甲每个 token 的速度没变，但三台机器从第三格起一直在忙，总吞吐变成三倍。</text>
</svg>
</div>
<figcaption>图 5 · 单个请求填不满流水线；多个请求错开排队，每台机器都有活干（“甲·2”表示请求甲的第 2 个 token）</figcaption>
</figure>

另一种做法是把多个请求拼成一批，一起穿过流水线。一批 16 个请求，一次往返的时间就由 16 个 token 分摊。我们用 llama.cpp 的批量基准在同一套环境里试了一下，4 个节点、RTT 20 毫秒：

<figure class="dd dd-figure dt-figure">
<div class="dt-panel">
<div class="dt-title" id="dit2-title">生成吞吐（tok/s）· Qwen2.5-1.5B 4 位 · llama-batched-bench</div>
<div class="dt-scroll">
<table class="dt" aria-labelledby="dit2-title">
<colgroup><col style="width:22%"><col><col class="hl"><col></colgroup>
<thead>
<tr><th scope="col">同时请求数</th><th scope="col">本机</th><th scope="col" class="acc">4 节点 · RTT 20 ms</th><th scope="col">4 节点 / 本机</th></tr>
</thead>
<tbody>
<tr><th scope="row">1</th><td class="mono num">23.07</td><td class="mono num acc">7.06</td><td class="mono num">31%</td></tr>
<tr><th scope="row">4</th><td class="mono num">58.95</td><td class="mono num acc">20.96</td><td class="mono num">36%</td></tr>
<tr><th scope="row">16</th><td class="mono num">168.80</td><td class="mono num acc">68.38</td><td class="mono num">41%</td></tr>
</tbody>
</table>
</div>
<p class="dt-note">每个请求 32 个输入 token、生成 32 个 token；4 个节点与本机是同一台 4 核机器。</p>
</div>
<figcaption>表 2 · 请求拼成一批以后，一次往返由整批 token 分摊：16 个请求时，4 节点的总吞吐是单请求的 9.7 倍</figcaption>
</figure>

单个请求时，4 节点只有本机的 31%；16 个请求一起跑，总吞吐升到本机的 41%，是单请求的 9.7 倍。并发确实能把网络的代价摊薄，但摊不掉：批越大，每一步算得越久，网络占的比例越小，可每一步该付的往返一次也没少。

Petals 也是这样设计的：多个客户端同时使用时，各自的请求在服务器之间交错进行。论文里 3 台 T4 同时服务 3 个客户端，每个客户端每秒 2.02 步，合起来大约每秒 6 个 token；10 台 RTX 3090 同时服务 10 个客户端，合起来大约每秒 11.7 个 token[^petals-paper]。

到这里，拆模型这条路的技术轮廓已经清楚了。**io.net 这类号称几十万张 GPU 的项目，也是这样拆的吗？**

## 六、io.net 拆的是什么

### 现象：推理容器只能跑在一台机器上

翻 io.net 的文档，会发现它的推理部署有一条硬性要求：容器“必须运行在单个节点内”[^ionet-caas]。用户部署一个模型时，选的是 GPU 型号、数量和地点，比如一台 8 卡 H100 的机器，整个模型放在这一台机器里跑。

也就是说，io.net 没有把一个模型拆到天南海北的机器上。它拆的是**市场**：GPU 来自独立数据中心、加密矿工和其他算力网络，io.net 把它们登记、验证、撮合给需要的人[^ionet-network]。

<figure class="dd dd-figure">
<div class="dd-scroll">
<svg class="dd-svg" viewBox="0 0 800 444" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="dif6-title dif6-desc">
<title id="dif6-title">两种“去中心化”：拆模型与拆市场</title>
<desc id="dif6-desc">左：Petals 把 Llama 2 70B 的 80 层分给三个家庭里的 GPU，一个请求的每个 token 都要跨公网依次经过三台。右：io.net 把来自独立数据中心和矿工的 GPU 登记、每小时 PoW 验证、撮合给用户；一个推理容器只跑在一台节点上，比如一台 8×H100，整个模型放在里面，供应方另得 IO 代币区块奖励。</desc>
<defs>
<marker id="dif6-arrow" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head" d="M1 1 L7 4 L1 7"/></marker>
<marker id="dif6-arrow-acc" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head-acc" d="M1 1 L7 4 L1 7"/></marker>
</defs>
<rect class="dd-frame" width="800" height="444" rx="10"/>
<rect class="dd-zone" x="16" y="16" width="376" height="412" rx="8"/>
<text class="t-zone" x="32" y="44">拆模型 · Petals</text>
<rect class="dd-zone" x="408" y="16" width="376" height="412" rx="8"/>
<text class="t-zone" x="424" y="44">拆市场 · io.net</text>
<path class="dd-edge-acc" d="M112 112 V140" marker-end="url(#dif6-arrow-acc)"/>
<path class="dd-edge-acc" d="M112 176 V204" marker-end="url(#dif6-arrow-acc)"/>
<path class="dd-edge-acc" d="M112 240 V268" marker-end="url(#dif6-arrow-acc)"/>
<path class="dd-edge-acc" d="M112 304 V332" marker-end="url(#dif6-arrow-acc)"/>
<path class="dd-edge-acc" d="M192 350 H352 V94 H200" marker-end="url(#dif6-arrow-acc)"/>
<rect class="dd-card" x="32" y="76" width="160" height="36" rx="6"/>
<text class="t-name" x="44" y="99">客户端</text><text class="t-mono" x="180" y="99" text-anchor="end">嵌入 / 输出</text>
<text class="t-note" x="124" y="131">公网 · RTT 几十毫秒</text>
<rect class="dd-card" x="32" y="140" width="160" height="36" rx="6"/>
<text class="t-name" x="44" y="163">家庭 GPU</text><text class="t-mono" x="180" y="163" text-anchor="end">层 0–26</text>
<rect class="dd-card" x="32" y="204" width="160" height="36" rx="6"/>
<text class="t-name" x="44" y="227">实验室 GPU</text><text class="t-mono" x="180" y="227" text-anchor="end">层 27–53</text>
<rect class="dd-card" x="32" y="268" width="160" height="36" rx="6"/>
<text class="t-name" x="44" y="291">家庭 GPU</text><text class="t-mono" x="180" y="291" text-anchor="end">层 54–79</text>
<rect class="dd-derived" x="32" y="332" width="160" height="36" rx="6"/>
<text class="t-name" x="44" y="355">下一个 token</text>
<text class="t-say" x="32" y="396">一个请求跨 3 台机器、3 段公网。</text>
<text class="t-say" x="32" y="416">虚线框：每个 token 重复一圈。</text>
<path class="dd-edge" d="M520 112 V140" marker-end="url(#dif6-arrow)"/>
<path class="dd-edge" d="M520 196 V224" marker-end="url(#dif6-arrow)"/>
<path class="dd-edge-acc" d="M704 112 V224" marker-end="url(#dif6-arrow-acc)"/>
<path class="dd-edge-acc" d="M704 284 V312" marker-end="url(#dif6-arrow-acc)"/>
<path class="dd-edge" d="M520 280 V296 Q520 304 528 304 H640" />
<rect class="dd-card" x="424" y="76" width="192" height="36" rx="6"/>
<text class="t-name" x="436" y="99">供给方</text><text class="t-mono" x="604" y="99" text-anchor="end">IDC / 矿工</text>
<rect class="dd-card" x="640" y="76" width="128" height="36" rx="6"/>
<text class="t-name" x="652" y="99">用户</text><text class="t-mono" x="756" y="99" text-anchor="end">部署</text>
<rect class="dd-card" x="424" y="140" width="192" height="58" rx="6"/>
<path class="dd-card-head" d="M424.6 172 V146 Q424.6 140.6 430 140.6 H610 Q615.4 140.6 615.4 146 V172 Z"/>
<text class="t-name" x="436" y="161">登记与验证</text>
<text class="t-mono" x="604" y="161" text-anchor="end">每小时</text>
<text class="t-mono" x="436" y="189">设备须空闲</text>
<text class="t-val" x="604" y="189" text-anchor="end">GPU ≤ 5%</text>
<rect class="dd-card" x="424" y="224" width="192" height="58" rx="6"/>
<path class="dd-card-head" d="M424.6 256 V230 Q424.6 224.6 430 224.6 H610 Q615.4 224.6 615.4 230 V256 Z"/>
<text class="t-name" x="436" y="245">奖励</text>
<text class="t-mono" x="604" y="245" text-anchor="end">IO 代币</text>
<text class="t-mono" x="436" y="273">20 年共发</text>
<text class="t-val" x="604" y="273" text-anchor="end">3 亿枚</text>
<rect class="dd-card" x="640" y="224" width="128" height="58" rx="6"/>
<path class="dd-card-head" d="M640.6 256 V230 Q640.6 224.6 646 224.6 H762 Q767.4 224.6 767.4 230 V256 Z"/>
<text class="t-name" x="652" y="245">调度</text>
<text class="t-mono" x="756" y="245" text-anchor="end">撮合</text>
<text class="t-mono" x="652" y="273">选</text>
<text class="t-val" x="756" y="273" text-anchor="end">型号/数量/地点</text>
<text class="t-val" x="756" y="273" text-anchor="end"></text>
<rect class="dd-card" x="640" y="312" width="128" height="58" rx="6"/>
<path class="dd-card-head" d="M640.6 344 V318 Q640.6 312.6 646 312.6 H762 Q767.4 312.6 767.4 318 V344 Z"/>
<text class="t-name" x="652" y="333">单个节点</text>
<text class="t-mono" x="756" y="333" text-anchor="end">8×H100</text>
<text class="t-mono" x="652" y="361">整个模型</text>
<text class="t-mono t-acc" x="756" y="361" text-anchor="end">在这一台</text>
<text class="t-say" x="424" y="396">一个请求只进一台机器；</text>
<text class="t-say" x="424" y="416">去中心化的是供给、验证和结算。</text>
</svg>
</div>
<figcaption>图 6 · Petals 把一个模型拆给公网上的几台机器；io.net 把整台机器撮合给用户，推理容器只跑在单个节点上</figcaption>
</figure>

### 原因：去中心化放在供给和结算上

io.net 的“去中心化”体现在几处：

- **供给**：谁都可以把机器挂上来，但挂着待租的设备必须空闲，CPU 利用率不超过 30%，GPU 不超过 5%（部分消费级卡 20%）[^ionet-idle]。
- **验证**：每小时跑一次工作量证明，确认这张卡真的存在、显存真的有那么大[^ionet-idle]。
- **激励**：总量 8 亿枚 IO 代币，其中 3 亿枚在 20 年里按小时发给供应方，作为区块奖励[^ionet-token]。

AkashML（Akash 网络上的推理服务）也类似：供给来自 65 个以上的数据中心，对外是一个和 OpenAI 兼容的接口[^akashml]。

为什么不像 Petals 那样拆模型？前面三节已经给出了答案：拆开以后单个请求更慢，吞吐也更低。要卖 API，就得和数据中心里的整机比速度和价格，只有让整个模型留在一台机器里，才比得过。

**把模型留在一台机器里，去中心化省下的又是什么钱？我们来算一笔账。**

## 七、闲置硬件能不能更便宜

### 每个 token 的成本从哪来

每百万 token 的成本，就是每小时的成本除以每小时产出的 token 数。去中心化推理赌的是分子：机器本来就闲着，不算折旧，只算电费，分子就小得多。拆模型的方案却在分母上吃亏：单个请求慢，并发又有限，每小时产出的 token 比数据中心的整机少几个数量级。

我们按每度电 0.15 美元，只算电费，看看前面几组实测每百万 token 要花多少：

<figure class="dd dd-figure dt-figure">
<div class="dt-panel">
<div class="dt-title" id="dit3-title">只算电费 · 每度 0.15 美元</div>
<div class="dt-scroll">
<table class="dt" aria-labelledby="dit3-title">
<colgroup><col style="width:46%"><col><col><col></colgroup>
<thead>
<tr><th scope="col">配置</th><th scope="col">功耗</th><th scope="col">总吞吐 tok/s</th><th scope="col">美元 / 百万 token</th></tr>
</thead>
<tbody>
<tr><th scope="row">H100 · Llama 3.3 70B · 最大负载</th><td class="mono num">700 W</td><td class="mono num">2,209</td><td class="mono num good">0.013</td></tr>
<tr><th scope="row">H100 · 同上 · 线上可卖（推算）</th><td class="mono num">700 W</td><td class="mono num">约 450</td><td class="mono num good">0.065</td></tr>
<tr class="dt-group"><th colspan="4" scope="rowgroup">拆开跑</th></tr>
<tr><th scope="row">3×T4 · Llama 2 70B · Petals，3 个客户端</th><td class="mono num">210 W</td><td class="mono num">6.06</td><td class="mono num bad">1.44</td></tr>
<tr><th scope="row">4×Mac mini · Llama 3.3 70B · 张量并行</th><td class="mono num">260 W</td><td class="mono num">6.51</td><td class="mono num bad">1.66</td></tr>
<tr><th scope="row">10×RTX 3090 · BLOOM-176B · Petals，10 个客户端</th><td class="mono num">3,500 W</td><td class="mono num">11.7</td><td class="mono num bad">12.5</td></tr>
</tbody>
</table>
</div>
<p class="dt-note">功耗按 H100 SXM 700 W、T4 70 W、RTX 3090 350 W、Mac mini 每台 65 W 计，没有算主板、网络和客户端。吞吐来自 TensorRT-LLM 性能表、Petals 论文表 2 / 表 3 和 distributed-llama #147；“线上可卖”是推算。参照：Llama 3.3 70B 市场最低价是每百万输出 token 0.32 美元。</p>
</div>
<figcaption>表 3 · 拆开跑的方案光电费就超过每百万 token 1 美元，比 70B 模型的市场最低价还贵；差距来自吞吐，不来自电价</figcaption>
</figure>

一张 H100 满载跑 70B[^trtllm]，每百万 token 的电费约 1 美分。4 台 Mac mini、3 台 T4 这样拆开跑，光电费就超过 1 美元，比 Llama 3.3 70B 的市场最低价（每百万输出 token 0.32 美元，DeepInfra）还贵四五倍[^openrouter]。差距不在电价，在吞吐：H100 一张卡同时服务几百个请求，每秒两千多个 token；拆开的消费级机器每秒只有几个。

也就是说，对拆模型的方案，“硬件不要钱”这个前提还不够，连电费都打不平。

### 卖 API 的去中心化供应商卖多少钱

那些把整个模型留在一台机器里的去中心化供应商呢？OpenRouter 上能看到 io.net、AkashML、Chutes（Bittensor 子网 64）的报价，和同一个模型的最低价放在一起：

<figure class="dd dd-figure dt-figure">
<div class="dt-panel">
<div class="dt-title" id="dit4-title">OpenRouter 报价 · 美元 / 百万输出 token · 2026-10-20</div>
<div class="dt-scroll">
<table class="dt" aria-labelledby="dit4-title">
<colgroup><col style="width:20%"><col style="width:36%"><col class="hl"><col></colgroup>
<thead>
<tr><th scope="col">供应商</th><th scope="col">模型</th><th scope="col" class="acc">报价</th><th scope="col">该模型最低价</th></tr>
</thead>
<tbody>
<tr><th scope="row">AkashML</th><td>gpt-oss-120b</td><td class="mono num acc">0.187</td><td class="mono num">0.15</td></tr>
<tr><th scope="row">AkashML</th><td>gpt-oss-20b</td><td class="mono num acc">0.10</td><td class="mono num">0.09</td></tr>
<tr><th scope="row">AkashML</th><td>Llama 3.3 70B</td><td class="mono num acc">0.52</td><td class="mono num">0.32</td></tr>
<tr><th scope="row">Io Net</th><td>Gemma 4 26B A4B</td><td class="mono num acc">0.31</td><td class="mono num">0.22</td></tr>
<tr><th scope="row">Io Net</th><td>Mistral Nemo</td><td class="mono num acc">0.022</td><td class="mono num">0.022</td></tr>
<tr><th scope="row">Chutes</th><td>Gemma 4 31B</td><td class="mono num acc">0.37</td><td class="mono num">0.32</td></tr>
</tbody>
</table>
</div>
<p class="dt-note">通过 OpenRouter 公开接口读取各供应商价格；“最低价”是同一模型所有供应商里最便宜的一家。</p>
</div>
<figcaption>表 4 · 去中心化供应商的报价贴着市场走，没有一家低于同模型的最低价</figcaption>
</figure>

没有一家比最低价便宜，大多数比最低价贵一些。它们用的是数据中心级的 GPU，成本结构和普通云厂商差不多，价格也就跟着市场走。区别在于供给从哪来、怎么结算：一部分供给是别人闲置的卡，供应方的一部分收入来自代币奖励。

## 八、回到 20 台小机器

现在回头看开头那个念头：20 台各有 10 GB 内存的小机器，跑 Qwen3-30B-A3B。下面的数字是按前面的实测和模型结构推算的，没有实际搭过 20 台。

**装得下。** 4 位量化后约 17 GB[^dllama]，20 台合计 200 GB，按层切成 20 段，每台不到 1 GB。

**单个请求会变慢。** Qwen3-30B-A3B 是混合专家模型，每个 token 只激活约 3B 参数，4 位下每个 token 要读约 2 GB 权重。一台 32 GB 内存、带宽每秒 50 GB 左右的普通电脑独自跑，大约每秒 20–30 个 token。切成 20 段，读权重的总时间不变，还要多 20 次往返和 20 份固定开销。按我们实验里每个节点几毫秒的开销估算，大约每秒 5–10 个 token。

**要靠并发才能打平电费。** 按每台满载 25 瓦、每度电 0.15 美元，20 台每小时电费约 0.075 美元。这个模型在 OpenRouter 上的输出价是每百万 token 0.50 美元，打平电费需要每秒约 42 个 token；如果再把每台 200 美元的机器按 3 年摊进来，需要每秒约 125 个 token。单个请求只有 5–10 个，得同时跑十几到几十个请求，把 20 段流水线一直填满。

所以这个方案能跑通，能不能省钱取决于两件事：机器是不是本来就闲着，以及有没有足够多的并发请求。同样这笔钱，买一台 64 GB 内存的机器单独跑，速度更快，也省掉了网络和 20 台机器的运维。

## 小结

1. **模型为什么要拆？** 每个 token 都要把所有权重读一遍，放在显卡外面来回搬太慢，只能让每台机器常驻一部分。
2. **按层切传什么？** 每一跳只传一个 token 的激活，Llama 2 70B 是 16 KB；KV 缓存留在各台服务器上。带宽不是问题，掉线靠客户端缓存的输入重放恢复。
3. **为什么慢？** 每个 token 都要按顺序穿过每一段，每一段多一次往返。我们的实验里，斜率几乎正好是每个节点一个 RTT。按层切能装下更大的模型，不会让单个请求更快。
4. **层内切呢？** 每层同步两次，70B 一个 token 要 160 次，只能在局域网甚至雷雳线上做；做到了，单个请求能快 2–3 倍。
5. **并发有用吗？** 有用。错开排队或拼批能把空转的机器填满，4 节点 16 个请求时总吞吐是单请求的 9.7 倍，但网络的代价摊不掉。
6. **io.net 拆的是什么？** 拆的是市场，不是模型。推理容器只能跑在单个节点上；去中心化在供给、验证和代币激励上。
7. **能更便宜吗？** 拆模型的方案吞吐太低，只算电费每百万 token 就要 1 美元以上，高于市场最低价；整机的去中心化供应商价格贴着市场走。“闲置硬件不要钱”只有在机器装得下整个模型、又有足够并发的时候才成立。

如果想自己验证第三节的结论，只要一台机器：编译 llama.cpp 时打开 `-DGGML_RPC=ON`，在本机起几个 `ggml-rpc-server`，用 `--rpc` 把它们都连上，比较节点数不同时每个 token 的耗时；想模拟远程，就在中间加一个延迟转发。

## 参考资料
