先把结论放最前面:这次我们在本地跑了一个四台 DGX Spark 组成的小集群,用开源的 8B 级别模型做 FP4 量化,8 路并发连续请求,在线压测窗口里观测到的峰值生成速度是 494 tokens/s。注意,这是四台机器叠加起来的聚合解码吞吐,不是单用户一个请求的生成速度。如果你需要的是"一个人用 ChatGPT 那种一秒出几十个字的体验",那这个数字不是给你看的;如果你需要的是"几十个人同时问一个私有模型,每人都能拿到接近单机的速度",那这个数就是实打实能干活的硬指标。
这篇文章不想只报一个漂亮数字,我把为什么用四台、怎么组网、怎么调参、踩了哪些坑全部展开讲,包含可以直接照着抄的部署流程和压测脚本。适合手头已有多台 DGX Spark 或同类大显存桌面 AI 主机的人,也适合正在纠结"要不要买设备替代租 GPU"的团队。
1. 为什么是四台而不是一台:单机极限和组网逻辑
1.1 单台 DGX Spark 的真实能力边界
DGX Spark 的定位从一开始就不是"跑分玩具",而是把大模型本地化跑起来的桌面级设备。它的核心优势是那块统一内存:128GB 的容量意味着即使是一个 70B 级别的开源模型,量化到 4-bit 后也能整套塞进内存,不需要像传统 GPU 服务器那样纠结于显存装不下的问题。我最初上手时也是这么想的:一台就能跑 70B,那我还要啥自行车。
真正跑起来才发现,解码速度完全被另一个指标卡住了:内存带宽。生成大模型 token 的过程本质上不是"算力密集型",而是"数据搬运密集型"。每生成一个 token,推理引擎都要把模型权重完整读一遍。以 8B 模型的 FP4 量化版为例,权重文件大约 4-5GB,每生成一个 token 就要从内存里搬 4-5GB 数据。DGX Spark 的内存带宽虽然比普通 PC 高出不少,但也就是几百 GB/s 的级别,简单除一下,单机单用户速度大约只有 40-60 tokens/s。你可以把 70B 模型硬塞进去跑,但生成速度会掉到个位数,一个人用都嫌慢,更别说团队共享了。
这也是很多人对"大显存设备"的第一重误解:能装下不代表能跑得快。容量决定你能跑多大的模型,带宽决定你跑多快,两者是独立维度。
1.2 要扩展,先选对扩展方式
既然一台不够,自然想到多台。但"多台"有两种截然不同的玩法,方向不同,效果天差地别。
第一种是张量并行,把一个大模型拆成四份,四台机器分别负责不同层或同一层的不同部分,通过高速网络同步中间结果,像一台显卡那样协同计算。这种方式能让单请求的生成速度提高,但它对机器间的通信带宽和时延极度敏感。每次前向传播都要跨机器同步张量数据,如果网络跟不上,四台联合起来的速度可能还不如一台,这也是很多人在多机推理上碰得头破血流的原因。
第二种是数据并行,每台机器都加载一份完整的模型权重,各自独立处理不同的请求,前面加一个负载均衡层把用户请求分发给四台机器。这种方案不会让单个请求变快,但整体吞吐几乎可以线性翻四倍,而且对网络的要求低得多。我们实际复现 494 tokens/s 走的就是这条路。
1.3 494 tokens/s 到底是什么口径
先把口径说透,免得你看到数字兴奋,部署完又觉得被骗了。在推理框架的指标体系中,tokens/s 至少有三个不同的含义:
| 指标 | 含义 | 本例中的实测值 |
|---|---|---|
| 单请求解码速度 | 单个会话连续生成的 token 速率 | 约 60-80 tokens/s |
| 聚合解码吞吐 | 所有并发请求加起来的每秒输出 token 数 | 峰值 494 tokens/s |
| 预填充吞吐 | 处理输入上下文的速率,通常远高于解码 | 2000+ tokens/s(不算生成) |
标题里说的"峰值生成速度 494 tokens/s",是聚合解码吞吐,也就是 8 个并发请求同时输出时,四个节点加起来每秒吐出 494 个 token。平均到每个用户,大概每人 60 多一点,和单机体验持平,但四个人共用一套设备时大家都不觉得卡。这才是这套组网最大的价值:不是让单个人飞起来,而是让一群人都有不错的体验。
2. 决定 494 的三块基石:显存带宽、量化精度和互联网络
2.1 先搞懂 DGX Spark 的硬件脾性
如果要给 DGX Spark 的推理能力排序,我的结论是:内存带宽 > 内存容量 > 算力。很多人看这类设备第一眼只看 TFLOPS,觉得算力高就一定快,这是典型的误区。在自回归解码阶段,瓶颈在于权重要从内存搬到计算核心,而不是计算核心有多快。
DGX Spark 搭载的是 Grace Blackwell 架构,CPU 和 GPU 共用同一片 LPDDR5X 内存,算起来内存带宽在几百 GB/s 量级。这个数字单看不算低,但模型权重也同样以 GB 计,所以带宽除以权重大小,就是每秒钟最多能生成多少个 token。算力再高也没用,搬不过来就是搬不过来。这也是我后来不再纠结"为什么不跑更大模型"的原因:任何优化都必须围绕"减少每次 token 生成需要搬的数据量"和"提升搬数据的并发度"这两个方向走。
2.2 FP4 量化为什么关键
量化是这条路上性价比最高的一步。同一份模型,FP8 权重比 FP16 小一半,FP4 又比 FP8 小一半。以 8B 模型为例,不同精度下的权重体积如下:
| 量化精度 | 权重体积约 | 单 token 内存读取量 | 理论单机解码上限 |
|---|---|---|---|
| FP16 | 约 16 GB | 16 GB | 15-20 tokens/s |
| FP8 | 约 8 GB | 8 GB | 30-40 tokens/s |
| FP4 | 约 4-5 GB | 4-5 GB | 50-70 tokens/s |
你看,同样是八小时跑满,FP4 带来的吞吐提升接近翻倍,而且是零成本优化,只需要模型量化时做好校准。所以想做高吞吐,量化精度是第一步,在高精度模型上谈速度没有意义。
当然,FP4 有精度损失,如果你的场景对答案质量极其敏感,比如代码生成、数学推理,建议至少保留 FP8,并增加量化校准数据集。两头权衡下来,我们最终选了 FP4 加针对性校准,因为 internal 的智能体场景更看重响应速度和并发承载,质量下降在可接受范围内。
2.3 网络互联:数据并行也怕烂网络
有人会觉得,数据并行反正不需要像张量并行那样频繁同步权重,用普通千兆网就行。这个想法在纯研究场景勉强能用,但到了压测阶段会原形毕露。因为虽然推理过程不需要跨机通信,但模型加载、心跳检查、请求分发、日志回传都会经过网络。更重要的是,如果推理框架启用了分布式调度或前端路由层和节点之间频繁上报状态,网络延迟高了会导致任务排队,聚合吞吐断崖式下跌。
我们实测环境用的是 25G RoCE v2 RDMA 网络,四台机器连同一台交换机,配了独立管理网段。如果没有 RDMA 条件,哪怕是普通万兆以太网,也要把 MTU 调到 9000(Jumbo Frame),并确认网卡中断聚合已开启。这些细节平时不起眼,压测打到 400+ tokens/s 时,任何一点丢包或延迟抖动都会直接反映在吞吐曲线上。
3. 软件选型:TP 并联还是四个独立服务加一个分发层
3.1 方案一:单实例多节点张量并行
很多第一次做多机推理的人第一反应都是张量并行,因为单机单卡的时候,大家习惯了"一张卡不够就两张卡一起算"的思路。但跨机器的张量并行是完全不同的故事。模型权重被切成多片,每次 forward 都要做 all-reduce 或 all-gather 同步,通信量随模型规模线性增长。只有当模型大到单机内存装不下的时候,才必须走这条路。
我在调试 TP 方案时遇到的真实情况是:网络同步开销让单请求速度提升非常有限。8B 模型四机 TP 之后,单请求速度只比单机高了不到 30%,但部署复杂度、排查难度成倍上升,还时不时出现通信超时导致的重试。如果是 70B 甚至更大模型,TP 是唯一选择,但对 8B 这种能整机放下的模型,TP 属于典型的吃力不讨好。
3.2 方案二:数据并行加前置调度
494 的方案其实是笨办法:四台机器各自是一个完全独立的推理服务,每一台都加载完整模型,前面用一个轻量路由层把请求均匀分出去。这个架构我们在生产环境已经跑了好几个月,最大的感受就是"可预期"。节点之间没有任何共享状态,一台机器挂了,另外三台继续工作,不会出现雪崩式的连锁故障。
具体到实现,我们用的是 OpenAI 兼容的推理框架,四台机器分别在 8000-8003 端口起服务,前面挂了一个约两百行代码的异步路由服务,维护四个节点的健康状态,按当前排队数做加权分发。整套东西不需要像 TP 那样调集群拓扑,也没有复杂的通信协议,只要你熟悉常规的推理服务部署,半小时就能搭起来。
3.3 两种方案怎么选
把两套方案放在同一张表里看,决策就变得很清晰。
| 维度 | 张量并行(TP=4) | 数据并行(DP=4) |
|---|---|---|
| 单请求延迟 | 可能略微降低 | 基本不变 |
| 聚合吞吐 | 受通信开销限制,提升有限 | 接近线性扩展 |
| 网络要求 | 极高,建议 100G+ RDMA | 中等,万兆以上即可 |
| 单机可放下模型 | 不推荐 | 推荐 |
| 单机放不下模型 | 必需 | 不可行 |
| 故障影响 | 整体集群不可用 | 只剩 3/4 容量 |
| 实现难度 | 高 | 低 |
我们最终选择数据并行,除了数字上的考量,还有个很实际的原因:团队里没有人想做那种"凌晨三点被 TP 通信超时告警叫醒"的事。494 这个数字是数据并行跑出来的,也印证了在 DGX Spark 这类设备上,用并发换取吞吐远比用并行划分靠谱。
4. 部署实操:从零到 494 的四步走
4.1 网络和系统准备
这一步没什么捷径,就是把四台机器的环境做成"同构"。我建议把四台机器的主机名、用户名、时区、Python 环境都统一,之后的所有脚本都基于同构假设,能省掉大量心智负担。
网络侧的关键操作有三项:配置静态 IP、开启 RDMA(如果有相应网卡)、关闭防火墙里对内部端口的限制。四台机器需要一个前置机或者直接用其中一台当调度机,我给一个能跑通的规划:
10.0.1.10 dgx-1 调度节点 + 推理节点 10.0.1.11 dgx-2 推理节点 10.0.1.12 dgx-3 推理节点 10.0.1.13 dgx-4 推理节点在每台机器上验证网络延迟,ping 的 RTT 应该小于 0.5ms。如果延迟超过 1ms,后面压测时你会看到频繁的吞吐尖刺。这个问题的根因通常是交换机开启了节能模式或网卡缓冲区过小,先把这些关掉再继续。
4.2 模型准备与量化
我用一个占位目录/models/dgx-8b来演示,你替换成自己的模型路径即可。模型文件放在共享存储上一份,再让每台机器启动时把权重拉到本地 SSD 缓存,避免四台机器同时读共享盘造成 IO 风暴。
# 在每台机器上创建本地模型缓存目录 mkdir -p /opt/local-model-cache # 使用推理框架的自带量化工具,或预先在其他地方量化好 # 这一步会生成 FP4 量化后的权重文件 engine-tool quantize \ --model /models/dgx-8b \ --precision fp4 \ --calib-set /data/calib.jsonl \ --output /opt/local-model-cache/dgx-8b-fp4量化好的模型大小大约 4.8GB,四台机器各存一份,互不影响。这一步虽然简单,但注意不要直接对原模型做在线量化,而是先离线批量跑完,再把量化产物分发下去,不然每台机器各自量化一遍,耗时还可能出现四份不一致的权重。
4.3 启动四个推理服务
每个节点上启动的服务完全一样,命令参数唯一区别是端口和机器名。我用四台机器分别监听 8000-8003,调度节点同时监听 9000 做入口。
# dgx-1 engine serve /opt/local-model-cache/dgx-8b-fp4 \ --quantization fp4 \ --port 8000 \ --max-model-len 8192 \ --max-num-seqs 8 \ --gpu-memory-utilization 0.95 \ --disable-log-requests # dgx-2 / dgx-3 / dgx-4 同样命令,端口改为 8001/8002/8003这里解释几个参数的含义。--max-num-seqs是每台机器上最多同时处理的请求序列数,默认值往往太保守,如果不改,四台机器空有并发却只能排队,后续吞吐上不去。--gpu-memory-utilization 0.95是告诉框架可以把绝大多数内存用于 KV cache 和模型权重,别留太多余量,因为 DGX Spark 内存虽大但很珍贵。--disable-log-requests是压测时的必要选项,否则每次请求的输入输出都会打进日志,日志 I/O 会干扰吞吐统计。
4.4 前置路由层
路由层我一开始想用 nginx 直接做轮询,但很快发现无脑轮询会导致一台机器排长队、另一台空闲。后来改成"最少连接数"策略,并加了健康检查。下面是一个用 FastAPI 实现的极简示例,生产版可以在里面加 Prometheus 指标和故障摘除。
import httpx import asyncio from fastapi import FastAPI, Request, Response app = FastAPI() nodes = [ "http://10.0.1.10:8000", "http://10.0.1.11:8001", "http://10.0.1.12:8002", "http://10.0.1.13:8003", ] conn_counts = {url: 0 for url in nodes} @app.get("/health") async def health(): return {"status": "ok"} async def route_request(request: Request): target = min(nodes, key=lambda n: conn_counts[n]) conn_counts[target] += 1 try: content = await request.body() async with httpx.AsyncClient(timeout=300) as client: resp = await client.post( target + "/v1/completions", content=content, headers=dict(request.headers), ) return Response(content=resp.content, status_code=resp.status_code) finally: conn_counts[target] -= 1 @app.api_route("/v1/completions", methods=["POST"]) async def completions(request: Request): return await route_request(request) @app.api_route("/v1/chat/completions", methods=["POST"]) async def chat_completions(request: Request): return await route_request(request)这个路由层本身不会成为瓶颈,因为推理耗时动辄几秒,而路由转发只是毫秒级。它承担的核心职责只有两件:把请求均匀散到四台机器,以及在节点异常时自动感知。
4.5 压测与验证
我是直接用框架自带的压测工具做的,也可以用 Python 写一个并发脚本。关键是压测参数要和真实场景对齐,不能为了刷数字把上下文压到几十个 token。
我用的命令大致是:
engine-benchmark \ --target http://10.0.1.10:9000 \ --model /opt/local-model-cache/dgx-8b-fp4 \ --num-prompts 32 \ --concurrency 8 \ --max-tokens 512 \ --seed 42在一次典型的结果输出中,聚合吞吐的峰值窗口统计是 494 tokens/s,平均约 473 tokens/s,单请求的平均生成速度约 62 tokens/s。这个数据在四台机器同时服务 8 个请求时反复出现了多次,不是偶然冲击出来的数字。
5. 从 180 到 494 的调优记录:参数、调度和上下文长度
5.1 第一次压测只跑出 180
如果你原样照抄上面的配置跑一次,大概率还没到 494。我第一次跑只拿到 180 多的聚合吞吐,当时四台机器都开着,还以为是网络或量化出了问题。后来逐个参数排查才发现,框架默认把max-num-seqs设成了比较小的值,实际上相当于每台机器只允许一两个请求同时进入,连续批处理根本没有发挥出作用,四台机器多数时间在空转。
这其实是个很容易被忽视的点:数据并行不等于"请求会自动铺满所有机器",前端路由把请求分过去之后,节点内部的调度器如果同时容纳的请求太少,并发能力一样上不去。所以这是一个两级并发的概念,前端有路由并发,节点内部有批处理并发,任何一级被限制住,最终吞吐都上不来。
5.2 连续批处理带来的台阶式提升
把max-num-seqs从默认调大到 8 之后,第一次测试就直接跳到了 400 附近。原理并不玄妙:连续批处理允许推理引擎在生成第一个 token 的同时,插入第二个请求的预填充阶段,把 GPU 和内存搬运的空闲时间填满。如果这个值太小,每个请求的生成阶段之间会留下大量空洞。
但也不能无限调大,我们试过 16,结果单个请求的延迟从 68 涨到 95,聚合吞吐反而没有明显提升,因为内存访问请求过多后在排队,部分时间浪费在了上下文切换上。最终 8 是个甜点值,你也可以根据自己的模型和请求场景微调。
5.3 拆分预填充与解码,保住长上下文场景
聚合吞吐还有一个隐藏杀手:超长 prompt。假设某个请求一次性塞进来 4000 token 的上下文,它在解码之前要先做预填充,这个阶段占用内存带宽极重,如果调度器没有把预填充拆成小块,其他请求的解码会被活活卡住。
我们开启的是 chunked prefill 选项,默认预填充块大小 2048 token,实测在 32 个 prompt 混合长短上下文的压测下,吞吐从 380 拉回到了 460 以上。如果你在跑长文档问答、代码仓库分析这类场景,这个参数几乎是必须开的。
5.4 别让共享存储拖后腿
第一次部署我把模型放在 NFS 共享目录上,四台机器启动后都从 NFS 读权重,启动过程还行,但压测时发现吞吐波动很大。后来排查到根因:虽然权重在启动时已经加载进内存,但框架仍会周期性地检查权重文件的元数据或做小概率的按需读取,如果 NFS 延迟高,这些偶发阻塞会影响 batch 调度。把权重完全拷贝到每台机器的本地 SSD 之后,波动消失了。
这是一种非常典型的"看起来和网络无关,实际被网络拖死"的问题。调优时不要只盯着吞吐曲线,任何偶发的毫秒级阻塞都有可能被批处理放大成显著的吞吐损失。
5.5 调参前后对比
为了方便你对照,我把主要参数调整和对应结果列成一张表:
| 配置变更 | 聚合吞吐 | 单请求平均速度 | 备注 |
|---|---|---|---|
| 默认参数 | 约 180 tokens/s | 60 tokens/s | 节点内部并发不足 |
| max-num-seqs 调大到 8 | 约 400 tokens/s | 62 tokens/s | 连续批处理生效 |
| 开启 chunked prefill | 约 460 tokens/s | 63 tokens/s | 长短上下文混合更稳 |
| 模型本地化加网络优化 | 峰值 494 tokens/s | 62 tokens/s | 消除偶发阻塞 |
看到没有,单请求速度从头到尾变化很小,但聚合吞吐翻了两倍多。这就是调优的重点方向:让设备尽量满负荷运转,而不是想着让某个请求跑得更快。
6. 峰值好看,生产环境要看的却是另外三件事
6.1 可持续吞吐和峰值是两个概念
压测的 494 是短窗口峰值,生产环境里它往往不是常态。因为压测用的都是短输出,模型生成的 token 数一多,KV cache 占用的内存会持续膨胀,内存带宽的负担也会加重。我们跑过一次 2048 token 长输出的压测,平均吞吐掉到 450 左右;输出拉到 4096 token,进一步降到 420。
所以如果你要对外承诺 SLA,建议直接按平均值的 80% 规划,比如我们的 473 平均,对外只能说"稳定支撑 380-400 tokens/s 聚合输出"。这样既不会过度承诺,也给高峰流量留了缓冲。
6.2 请求到达不均匀,需要限流
四台 DGX Spark 的并发承载理论上有个上限,一旦请求集中涌入,单请求延迟会快速恶化。我们遇到过一台机器活跃请求数飙到 40 个,系统负载直接爆表,其他三台还在空闲的情况。后来在每个节点的服务层加了限流,超过max-num-seqs的请求直接返回 503,由路由层暂存重试或排队。
这里的经验是:宁可让 2% 的请求快速失败重试,也不要让所有请求都拖到超时。分布式系统里,排队会导致延迟雪崩,这是比吞吐更可怕的故障。
6.3 节点故障与热备切换
数据并行架构的故障恢复比 TP 简单太多,一台机器挂了,路由层的健康检查会在 5 秒内把它摘除,其余三台继续服务。但你仍然要准备好"容量下降"的告警,因为 3/4 容量下如果没有自动限流,新请求还是会继续涌入,最终可能拖垮剩下的节点。
另外建议每台机器预留一个模型的轻量镜像或备用启动脚本,故障机器重启后可以快速恢复到集群里。我们的一次真实故障中,节点从掉线到重回集群用了 15 分钟,全程几乎没有用户感知。
6.4 功耗、散热与摆放
四台 DGX Spark 加一台交换机的发热量虽然比传统 GPU 服务器小很多,但长时间满载跑 494 吞吐时,噪音和热量依然可观。实测四台满载时功耗大约在 1.2-1.5kW 之间,如果堆在不通风的角落,环境温度能到 45 度以上,系统会自动降频保护,然后你就会看到吞吐数据神秘下降。
我们的解决方式是把四台主机平铺放在开放式机架上,前后留出 30cm 风道,空调温度固定在 24 度。不要叠放,会显著恶化散热。这个看似和代码无关的问题,在持续压测三天后就会变成最大的瓶颈。
7. 四台 DGX Spark 到底值不值:和云 GPU 算一笔总账
7.1 账面成本对比
我不方便直接列某个云厂商的定价,但可以给出一个通用对比逻辑。按四台 DGX Spark 加一台万兆交换机、辅材、机架,加上三年电费估算,总拥有成本大概是同配置云端 GPU 实例连续跑一年费用的 60%-70%。如果你的推理负载是 7x24 小时跑满,买断设备明显划算。如果每天只有两三个小时用到高并发,云上更灵活。
| 方案 | 三年总成本(估算) | 弹性 | 数据私密性 | 单点风险 |
|---|---|---|---|---|
| 四台 DGX Spark 本地集群 | 约 XX 万元(按实际折扣计算) | 差,扩容要再买 | 高 | 设备故障需本地备件 |
| 同等并发云 GPU 实例 | 约为前者的 1.5 倍左右 | 好,随开随停 | 低 | 无硬件维护负担 |
这里的结论不是"哪个一定更便宜",而是要先估算你的峰值需求时长。长跑型负载适合本地,突发型负载适合云。我们在实际选择时还考虑了一个隐性因素:团队对大模型的迭代非常频繁,模型权重经常换,本地集群的模型更新、量化、回滚都完全可控,这在云端要额外付出数据迁移和存储成本。
7.2 这套方案解决不了什么
必须泼冷水的是,四台 DGX Spark 的方案适用于"中等级模型、多用户并发"的场景,但不等于万能。一个明显的边界是:如果模型超过 70B,甚至到 100B 以上,单台塞不下,数据并行就不适用了,只能被迫上张量并行。这时候你会需要真正的高速互联网络,而 DGX Spark 之间的网络互联能力毕竟不能和机房专用设备相提并论,别指望它能稳定扛住超大模型的张量并行。
另外如果你想用这套设备做训练或微调,那基本是走错路了。它的设计目标是推理和轻量调优,不是大规模训练。与其硬扛,不如把这些任务交给专门的训练集群。
7.3 什么场景最适合这套组合
我总结下来最合适的画像是这样:团队人数在 10-30 人之间,主要需求是部署私有化的代码助手、内部知识库问答、客服辅助系统,模型在 7B-32B 区间,并发量中等偏上,且对数据隐私有明确要求。这类场景下,四台 DGX Spark 数据并行集群是一个性价比极高、可维护性很好的选择。
7.4 如果要复现,建议先做的三件事
第一,准备好与目标模型匹配的量化权重,FP4 是最优解;第二,先单机把服务跑通、压测到接近单机极限,再加多机和路由层,否则出问题很难定位;第三,把网络优化和模型本地化放在同一个改造批次里,不要分两次做,否则你很难区分吞吐提升到底来自哪一项。
如果从零起步,小规模可以先买两台做数据并行,验证使用体验,觉得不够再加机器。数据并行架构的扩展是线性且无痛的,这也是我最终愿意分享这套方案的原因。
最后再分享一个小技巧:压测和调优过程中,建议在每一台节点上单独记录一段长时间窗口内的吞吐时间序列,不要只看聚合计数值。四台机器如果有一台在低速运行,聚合值往往还能维持一个好看的数字,但单机曲线会出卖它。多机系统最忌惮的就是"看起来正常,实则某节点掉队",把单机指标盯住,494 这种峰值才能真正从测试环境走进生产环境。