最近在 Hacker News 上看到一个很有反差的标题:Show HN: Kimi K3 inference on about 100k PS3 nodes。十年前的 PlayStation 3 游戏机和大模型推理放在一起,天然就有一种“赛博考古”的味道。如果你平时关注 LLM 部署、分布式推理或者异构算力池,应该能感受到这类实验背后其实藏着不少值得拆解的工程问题:模型权重怎么切、通信怎么做、节点故障怎么处理、功耗怎么算、10 万台设备的集群到底可不可行。
这篇文章会围绕这个标题展开,但不是去复刻一个真实部署报告(这类 Show HN 很多时候是概念验证或模拟推演),而是把“PS3 集群跑大模型推理”这件事拆成几层来讲:先看 PS3 的硬件底子为什么特殊,再看大模型推理在分布式环境下会碰到的显存、通信、调度问题,然后给出一套可运行的调度模拟代码和单节点计算伪代码,最后讨论 10 万节点规模的现实代价,以及这类实验对常规分布式推理的启发。无论你是想了解分布式推理的入门读者,还是正在做模型部署的工程师,都可以从这篇文章里找到可参考的思路。
1. 这个项目在讲什么:用 PS3 跑大模型推理?
1.1 Show HN 与 Kimi K3 的背景
“Show HN”是 Hacker News 上的一个固定栏目,开发者可以在上面展示自己的作品,通常以链接、Demo 或一篇技术说明的形式出现。这类内容不一定是生产级系统,很多时候是“我做了个东西,给大家看看思路”的概念验证。
标题里的 Kimi K3,从公开资料看并不是一个我能查证的官方正式版本。更合理的理解是:它在这里作为一个命名代号,代表“用类 Kimi 系列模型思路,或者一个开源 LLM 推理任务”跑在 PS3 节点上。考虑到社区里 DeepSeek V4、Qwen3.8 这类命名经常出现在讨论中,模型版本的更迭很快,所以本文不纠结于 K3 具体指哪个权重,而是把它当成一个大模型推理任务的占位。换句话说,真正的主角不是某个模型,而是PS3 节点 + 集群推理这一整套架构设计。
1.2 为什么选择 PS3 作为推理节点
PS3 之所以会被这类实验盯上,有几个客观原因:
- 价格便宜:PS3 已经停产多年,二手市场的整机价格很低,甚至很多人家里还闲置着一台。
- 硬件特殊:PS3 搭载的 Cell Broadband Engine 架构和普通 PC 差别很大,它不是传统 x86 CPU,而是异构多核处理器,这正好和“用非标准设备做并行计算”的实验需求对上了。
- 可改机运行 Linux:早期 PS3 支持安装 OtherOS,后来虽然被官方移除,但民间仍然有不少方式可以运行 Linux 系统。这意味着 PS3 可以被改造成一个“能跑程序的 Linux 节点”。
- 网络化能力:PS3 有网口,可以接入局域网或互联网,理论上可以组成分布式集群。
但这里有一个很关键的现实:单个 PS3 的内存只有约 256MB 的 XDR 内存,显存也只有 256MB 量级,连一个 1B 参数规模的量化模型都塞不下。所以“在单个 PS3 上跑大模型”是伪命题,真正的思路一定是把模型切成很多份,分散到大量节点上,再通过网络协同完成推理。这也解释了标题里的 100k nodes 是怎么来的:单节点算力和内存都不够,只能靠数量堆。
2. 先搞清楚硬件:PS3 到底能干什么
2.1 Cell Broadband Engine 核心特性
要理解 PS3 集群推理,先要理解 Cell 处理器。它并不是一颗普通的 8 核 CPU,而是一颗异构处理器:
- PPE(PowerPC Processor Element):一个通用 64 位 PowerPC 核心,负责运行操作系统、任务调度和常规逻辑。
- SPE(Synergistic Processor Element):共有 8 个协处理器核心,每个 SPE 拥有独立的256KB Local Store(本地存储),没有缓存,也不能直接访问主内存。
- DMA 传输:SPE 要读取主内存数据,必须通过 DMA(直接内存访问)显式搬运到自己的 Local Store,计算完成后再用 DMA 把结果写回主内存。
这个架构非常像现在的GPU 异构计算模型:主 CPU 负责控制流,计算单元负责大量并行运算,数据要显式拷贝到计算单元的显存里。只不过 Cell 的本地存储只有 256KB,比现代 GPU 的显存小了好几个数量级。
对大模型推理来说,这个架构意味着两件事:
- 模型权重不可能常驻在 SPE 的 Local Store 里,必须按层、按块切分,用时搬运。
- 每一个计算步骤都伴随着大量的 DMA 传输,数据传输开销会远超计算本身。
2.2 与大模型推理相关的硬件指标
我们把 PS3 节点和今天的 GPU 服务器放到一张表里对比,能更直观地看出差距:
| 硬件指标 | 单台 PS3(约) | 单张消费级 GPU(约) | 单张数据中心 GPU(约) |
|---|---|---|---|
| 主内存 | 256MB XDR | 16GB~32GB | 32GB~80GB |
| 显存 | 256MB GDDR3 | 8GB~24GB 显存 | 24GB~80GB HBM |
| 计算核心 | 1 PPE + 7~8 SPE | 数千 CUDA 核心 | 数万核心 |
| 单节点算力 | 相对有限 | 高 | 很高 |
| 功耗 | 整机约 150W~200W 量级 | 单卡 200W~350W | 单卡 300W~700W |
从这个表能看出,PS3 节点本质上是一个内存极小、算力有限、但具有独立网络能力的嵌入式节点。它和“显卡”的定位完全不同,更像一个微控制器级别的运算单元。
所以,10 万个 PS3 节点的集群,等价于一个万卡规模的弱算力池。它的聚合算力和总内存可能不少,但是每一个节点的局部能力太低,通信和调度成本会被无限放大。
3. 大模型推理在 PS3 集群上要过哪些关
3.1 模型参数放不下:从单机到分布式
大模型推理的第一步,是解决“参数放不下”的问题。
假设一个模型的权重是 7B(70 亿参数),用 FP16 存储,每个参数需要 2 字节,总大小是:
7B × 2B = 14GB单台 PS3 的内存才 256MB,差了 50 倍以上。即便量化到 INT4,每个参数只占 0.5 字节,也要 3.5GB,仍然是 256MB 的十几倍。
因此,唯一可行的路线就是分布式推理。具体来说,模型需要被切分成多个分片,每个 PS3 节点只负责其中一小部分权重对应的一小段计算,节点之间通过网络传输中间结果。
3.2 张量并行、流水线并行、专家并行怎么选
分布式推理有几种常见的模型切分方式,处理思路是不同的:
- 流水线并行(Pipeline Parallelism):按层切分。第 1~2 层放在节点 A,第 3~4 层放在节点 B,数据依次流过每个节点。这种方式通信量相对可控,但延迟会随层数增加而累加。
- 张量并行(Tensor Parallelism):按矩阵维度切分。一个 Transformer 层里的 Attention 和 MLP 矩阵被拆到多个节点上,节点之间需要频繁同步,通信开销非常大。
- 专家并行(Expert Parallelism):只适用于 MoE(Mixture of Experts)结构模型。不同的专家子网络放在不同节点上,每个 token 只激活部分专家。
对 PS3 这种“内存极小、网络带宽一般”的节点来说,流水线并行是最容易落地的。因为每个节点只需要存放连续几层的权重,节点的本地存储压力最小。张量并行虽然能把单层切得更细,但每一层计算都需要多次集合通信,在 10 万个节点的规模下基本不可用。
3.3 10 万节点规模下的通信问题
假设采用流水线并行,把一个 30 层左右的模型切到 30 个节点一组,每组完成一次完整的 forward 推理。10 万节点可以同时服务几千组请求,吞吐量看起来不低。
但问题在于,流水线并行中节点之间是链式依赖的:
节点 0 -> 节点 1 -> 节点 2 -> ... -> 节点 29每一层推理都需要等上一层的结果,通过网络传输过来。如果单个节点的单次传输延迟是 1ms,30 层串行就是 30ms,这还不算排队和计算时间。如果网络带宽不够或者交换机拥塞,延迟会进一步恶化。
10 万节点还意味着网络拓扑非常深,不可能所有节点都在同一台交换机下,至少是几十台机柜、多级交换网络。跨机柜通信的延迟和丢包率会显著上升。所以,这个实验如果真正落地,网络架构会比软件调度更早遇到瓶颈。
4. 推理调度系统的架构设想
4.1 主从架构与任务拆分
既然单节点放不下完整模型,我们需要一个调度系统来统一管理任务。一个比较自然的架构是主从模式:
- Master(调度器):负责接收推理请求,把请求按“模型分片”拆成多个子任务,分配给对应的 Worker 节点。
- Worker(计算节点):每个 PS3 节点持有模型的一部分权重,负责执行某一层或某几层的计算。
- KV Cache 管理:对于生成式模型,每个 token 生成都需要读取之前所有 token 的 KV Cache。在 PS3 上,KV Cache 也要切分存储。
调度器还需要处理失败重试。10 万节点的集群,假设单节点每小时的故障概率是 0.1%,那么整体平均无故障时间会非常短。所以集群调度器必须有心跳检测、任务超时重试、失败节点隔离等机制。
4.2 一个可运行的 Python 模拟框架
下面用 Python 写一个简化版的主从调度模拟器,用来演示任务拆分、心跳检测和失败重试的基本逻辑。这不是生产代码,但能帮你理解大型推理集群的核心流程。
# 文件路径:simulate_ps3_cluster.py import random import time from collections import deque class WorkerNode: """模拟一个 PS3 节点""" def __init__(self, node_id, layer_range): self.node_id = node_id self.layer_range = layer_range # 本节点负责的层区间 self.alive = True self.current_task = None def execute(self, task, simulate_time=0.1): """模拟执行一次推理子任务,随机故障""" if random.random() < 0.15: # 15% 概率模拟节点故障 self.alive = False raise RuntimeError(f"node {self.node_id} crashed") time.sleep(simulate_time) return { "task_id": task["task_id"], "node_id": self.node_id, "layers": self.layer_range, "status": "done", } class Scheduler: """主调度器:负责给节点派发任务、检测存活、重试""" def __init__(self, workers): self.workers = workers self.task_queue = deque() self.results = [] def submit(self, task): self.task_queue.append(task) def dispatch_once(self): if not self.task_queue: return False task = self.task_queue.popleft() target_layer = task["target_layer"] # 找到负责该层的存活节点 candidates = [ w for w in self.workers if w.alive and w.layer_range[0] <= target_layer <= w.layer_range[1] ] if not candidates: print(f"[skip] task {task['task_id']} -> no alive worker for layer {target_layer}") # 放回队列,等待节点恢复(简化处理) self.task_queue.append(task) return True worker = random.choice(candidates) try: result = worker.execute(task) self.results.append(result) print(f"[ok] task {task['task_id']} -> node {worker.node_id}, layers {worker.layer_range}") except RuntimeError as e: print(f"[fail] {e}, task {task['task_id']} will retry") task["retry_count"] = task.get("retry_count", 0) + 1 if task["retry_count"] < 3: self.task_queue.append(task) return True def run_loop(self, max_rounds=20): for _ in range(max_rounds): if not self.task_queue: break self.dispatch_once() return self.results if __name__ == "__main__": # 创建 10 个节点,每个节点负责 1 个 layer workers = [WorkerNode(node_id=i, layer_range=(i, i)) for i in range(10)] scheduler = Scheduler(workers) # 模拟一次需要经过 0~9 层的请求 for layer_id in range(10): scheduler.submit({ "task_id": layer_id, "target_layer": layer_id, "retry_count": 0, }) results = scheduler.run_loop() print("\n=== final results ===") print(f"finished tasks: {len(results)} / 10")运行方式:
python simulate_ps3_cluster.py预期输出类似:
[ok] task 0 -> node 0, layers (0, 0) [ok] task 1 -> node 1, layers (1, 1) [fail] node 3 crashed, task 3 will retry ...这段代码模拟了一个最朴素的“按层调度”逻辑:每个请求的每一层对应一个 Task,调度器找到负责该层的存活节点,执行并返回结果;如果节点故障,任务会回到队列里重试,重试次数限制为 3 次。
真实系统中的调度器远比这个复杂,比如需要维护分布式 KV Cache、需要把多个连续层合并到同一个节点、需要动态负载均衡,但核心的心跳检测、失败重试和任务队列思想是一样的。
4.3 单节点上的 SPE 计算伪代码
如果真的要在一个 PS3 的 SPE 上跑推理,代码风格会接近下面的示意。注意这只是一个结构示意,并不是可以直接在 PS3 上编译的完整程序,实际开发还需要依赖特定的 SDK 和库:
/* spe_worker.c - 仅供理解 SPE 计算流程示意 */ void compute_layer(float *input, float *weights, float *output, unsigned int weight_size) { /* 1. 把权重从主存搬到 SPE Local Store */ dma_transfer(weights, local_store, weight_size); /* 2. 在 Local Store 里做矩阵乘加 */ for (int i = 0; i < weight_size; i++) { local_result[i] = local_input[i] * local_store[i]; } /* 3. 把结果写回主存 */ dma_transfer_back(local_result, output, weight_size); }可以看到,核心计算代码本身并不复杂,复杂的是 DMA 的时机和 Local Store 空间的规划。因为 Local Store 只有 256KB,一次能放进来的权重块非常小,一个稍微大一点的权重矩阵就要拆成几十次甚至上百次传输。
5. 从“能跑”到“能看”:量化与 KV cache 优化
5.1 为什么必须量化到 INT4/INT8
如果 PS3 集群的目标是跑大模型推理,量化不是优化项,而是必选项。先看一个简单计算:
一个 1B 参数的模型,FP16 需要 2GB,INT8 需要 1GB,INT4 需要 0.5GB。
PS3 节点内存约 256MB,如果把 1B 模型切成 8 份,每份只有 125MB,勉强放进 FP16 或者 INT8。但注意,模型权重还需要和 KV Cache 以及中间激活值共存,所以实际可用空间更紧。
因此,在 PS3 集群上,INT4 基本是唯一现实的选择。量化会带来精度损失,但对于 LLM 生成任务来说,合理的量化方案通常能把损失控制在可接受范围内。
5.2 把权重切到 Local Store 的思路
即便模型权重量化到 INT4,一个节点要处理的层权重依然可能超过 256KB。所以在单节点内部,还需要再做一次“块级切分”:
- 每个 SPE 的 Local Store 只保存当前计算需要的权重块。
- 计算完一块后,通过 DMA 换入下一块。
- 这样虽然牺牲了访存效率,但保证了计算不会因为空间不足而失败。
这种思路和 GPU 上的 Kernel Fusion、分块矩阵乘法有很多相似之处,区别只是 PS3 的 Local Store 比 GPU 显存小太多,以至于块切分要更细。
5.3 模拟量化权重切分的 Python 示例
下面用 Python + NumPy 模拟一个简单的 INT4 量化和分块切分过程,方便你直观理解模型权重在部署前会被怎么处理:
# 文件路径:quantize_chunk.py import numpy as np def quantize_int4(tensor): """把 FP16 张量量化到 INT4 范围,并记录 scale""" tensor = tensor.astype(np.float32) scale = np.max(np.abs(tensor)) / 7.0 # INT4 有效范围 [-7, 7] quantized = np.round(tensor / scale).astype(np.int8) quantized = np.clip(quantized, -7, 7) return quantized, scale def chunk_weights(quantized, chunk_size): """把量化权重按 chunk_size 切块,模拟 SPE Local Store 空间限制""" chunks = [] for start in range(0, len(quantized), chunk_size): end = min(start + chunk_size, len(quantized)) chunks.append(quantized[start:end]) return chunks if __name__ == "__main__": # 模拟一个 256 维的权重向量 np.random.seed(0) weights = np.random.randn(256).astype(np.float16) q_weights, scale = quantize_int4(weights) chunks = chunk_weights(q_weights, chunk_size=64) print(f"原始权重数量: {len(weights)}") print(f"量化后 chunk 数量: {len(chunks)}") print(f"每个 chunk 大小: {chunks[0].shape}") print(f"scale: {scale}") # 反量化示例 dequantized = chunks[0].astype(np.float32) * scale print(f"第一个 chunk 反量化后前 5 个值: {dequantized[:5]}")运行结果输出示例:
原始权重数量: 256 量化后 chunk 数量: 4 每个 chunk 大小: (64,) scale: 0.432... 第一个 chunk 反量化后前 5 个值: [ ... ]这段代码演示了部署时最重要的三步:量化、切块、反量化校验。在真实场景中,权重会在服务启动前做好量化并分片,节点启动时只加载自己需要的那部分分片。
6. 10 万节点的现实账单:功耗、故障与延迟
6.1 功耗与散热估算
标题里的约 100k nodes 是一个非常大的数。我们粗略估算一下电费。
假设单台 PS3 整机功耗在 150W 到 200W 之间,取中位数 175W:
单台年功耗 = 0.175kW × 24h × 365 ≈ 1533 kWh 10 万台年功耗 = 1533 × 100000 ≈ 1.53 亿 kWh这个量级的电力消耗,相当于一个中型城市的居民用电规模。即使按工业电价 0.5 元/kWh 计算,一年的电费也接近 8000 万元。如果再算上空调散热、网络设备、机柜空间,运营成本会远远超过硬件购置成本。
所以,10 万 PS3 节点跑推理,在现实世界里几乎不可能长期稳定运营,更像是一种极限推演。
6.2 故障率与检查点
大规模集群最麻烦的问题之一是故障率。即使每个节点非常稳定,故障也会随着数量增加变成常态。
假设单节点每小时故障概率为 0.1%,看起来很低,但在 10 万个节点下:
- 每小时期望故障节点数:100000 × 0.001 = 100 个。
- 也就是说,平均每小时会有 100 个节点挂掉。
这也是调度器必须有失败重试、自动隔离、任务重新分配的原因。在真实的分布式推理系统中,还需要定期保存中间状态,也就是 checkpoint,这样节点挂掉后不需要从最开始重新算。
6.3 推理延迟的现实估计
最后看推理延迟。假设一个 30 层的模型被切到 30 个 PS3 节点上,每个节点只负责一层:
- 每层的 DMA 传输:几十毫秒级别。
- 每层的实际计算:也可能需要几十毫秒。
- 节点间的网络传输:1ms 到 10ms 不等。
这样算下来,生成第一个 token 的 prefill 延迟可能达到秒级甚至几十秒,后续每次生成一个 token,也需要串行经过 30 个节点,延迟同样非常高。作为对比,现代 GPU 上跑一个同规模模型,单 token 生成延迟通常是几十毫秒到几百毫秒。
结论很明确:PS3 集群跑 LLM 推理,吞吐量也许能靠节点数量撑起来,但每个请求的端到端延迟会非常感人。它适合那些对延迟不敏感、但想验证分布式推理框架的场景,并不适合在线业务。
7. 这类实验带来的工程启发
7.1 异构算力池的调度思路
虽然 PS3 集群本身并不实用,但这类实验背后的“异构算力池”思想是很有价值的。今天很多公司内部会有闲置的消费级显卡、旧服务器、甚至小型设备,通过统一调度框架把它们组织起来,按任务类型分配给不同节点,本身就是一种降本增效的手段。
调度器需要关心的核心问题是一致的:
- 如何把一个大任务切分成能在小节点上运行的子任务。
- 如何感知每个节点的实时负载和存活状态。
- 如何在节点故障时快速重试和迁移任务。
- 如何在数据量大的场景下减少跨节点通信。
这些问题和 PS3 实验中的问题完全一样,只是换了更现代的硬件。
7.2 小模型 + 大集群的性价比讨论
结合社区里 DeepSeek V4、Qwen3.8 这类讨论热词来看,今年开源模型的一个趋势是小模型能力越来越强,参数量在变小,推理部署成本在下沉。在这种情况下,“用大量低配节点跑小模型”并不是完全没意义。
比如一个 1B~3B 的量化模型,如果切成几十份,跑在一批低功耗 ARM 节点或者老旧游戏机改装的节点上,虽然单请求延迟高,或者只能做离线批量推理,但依然能解决一部分实验性需求。这类实践更偏教育意义,用来理解分布式推理的机制,而不是真的替代 GPU 集群。
7.3 容错和调度思想可迁移到常规分布式推理
即便你完全不用 PS3,这套实验里体现的模型切分、心跳检测、重试机制、DMA 与显存搬运等概念,在常规分布式推理框架中也同样适用。
现在主流的推理框架会在多卡环境下做张量并行,会在多机环境下做流水线并行,会使用 KV Cache 管理来减少重复计算。这些技术本质上和 PS3 集群要解决的问题是一样的,只是硬件资源更充裕,实现细节不同。理解了一个极受限环境下的推理调度,再回头看 GPU 集群上的推理框架,很多配置项和日志信息会变得容易理解得多。
8. 常见问题与学习路线
8.1 常见疑问速查
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 单个节点内存不足 | 模型参数超过 256MB | 按层/按块切分权重,必须量化到 INT4 级别 |
| 推理延迟很高 | 节点间串行传输 + DMA 搬运 | 减少流水线深度,增加并行组,优化网络拓扑 |
| 节点频繁故障 | 硬件老化,设备数量过多 | 调度器增加心跳检测、失败重试、节点隔离 |
| KV Cache 放不下 | 长序列生成的缓存占用大 | 使用 paged KV cache 或限制最大生成长度 |
| 10 万节点总功耗过高 | 单机功耗叠加 | 用低功耗设备、休眠调度,或改用模拟实验验证 |
8.2 如果你想复现或模拟,可以从哪里开始
如果你对这个方向感兴趣,不建议一上来就买二手 PS3 组集群。更务实的路径是:
- 先跑通本文的 Python 调度模拟器,理解任务队列、失败重试、节点分配的基本逻辑。
- 用 Docker 在一台机器上模拟多节点,每个容器相当于一个“弱算力节点”,尝试部署一个小模型做分布式推理。
- 学习真实推理框架的并行策略,重点关注张量并行和流水线并行。
- 如果条件允许,再考虑用树莓派、旧电脑等设备做小规模物理集群实验,规模从 4 节点、8 节点开始,先验证通信瓶颈。
- 最后,如果你真的想去碰 PS3,优先研究系统安装方式、网络配置和 SPE 编程环境,同时接受它“玩具性质大于生产价值”的定位。
从“能跑”到“跑得好”,中间隔着非常多的工程问题:权重分片、KV Cache 管理、量化损失评估、网络拥塞控制、节点扩缩容、检查点恢复。这些能力并不是靠一台高端 GPU 就能学会的,反而是在受限环境下更容易把原理吃透。
如果你也想尝试类似的实验,建议先从软件模拟开始,跑通一个最小的调度器,再逐步加节点、加模型层数。真正的收获不在 10 万台 PS3,而在把模型切碎、调度、容错、量化这一整套思路。本文就分享到这里,欢迎在评论区聊聊你的分布式推理实验进展。