两台迷你主机拼在一起跑本地大模型,这件事我从去年就开始折腾了。最开始的想法特别朴素——手头有一台小主机,显存不够,上下文一拉长就爆,于是想着再搞一台,两台凑一起,显存翻倍,是不是就能把长上下文这个老大难问题给啃下来?结果真上手之后才发现,事情远没有"1+1=2"那么简单。集群通信、显存分配、推理框架的并行策略、长上下文下的注意力计算开销,每一个环节都能让你卡上大半天。这篇就把我这两台机器跑本地智能体的完整过程摊开讲,重点回答一个很多人关心的问题:两台机器凑出来的算力,长上下文到底能不能用,能用到什么程度,代价是什么。
如果你手里正好有一到两台小主机、迷你主机或者带独显的机器,想跑本地智能体又不想上大显卡,那这篇内容应该能帮你少走不少弯路。我会从整体思路、硬件与框架选型、具体部署步骤、长上下文的实测表现,一直讲到踩过的坑和排查方法,尽量把每个"为什么这么选"都讲清楚,而不是只丢一堆命令让你照抄。
1. 整体思路与方案选型:为什么是两台而不是一张大卡
1.1 需求拆解:本地智能体到底吃的是什么资源
先把需求说清楚,不然选型就是瞎选。本地智能体(Agent)和单纯跑一个聊天模型不一样,它对资源的需求有几个很鲜明的特点。
第一是上下文长度。智能体要读工具返回结果、要维护多轮对话历史、要保留任务规划中间态,上下文动辄几万 token 起步。上下文一长,KV Cache 就跟着涨,显存占用是线性甚至更陡地往上走。这是本地跑智能体最要命的一环。
第二是并发与响应。智能体经常需要连续调用模型,比如规划一步、执行一步、再反思一步,中间还夹着工具调用。它对首 token 延迟和整体吞吐都有要求,不能慢到没法用。
第三是模型规模。想让智能体真的能干点活,模型不能太小,量化之后怎么也得是能稳定遵循指令、能调用工具的水平。参数量上去,权重占的显存也上去。
把这三点合起来看,你会发现瓶颈几乎全压在显存上。权重占一块,KV Cache 占一块,中间激活再占一块。单台小主机那点显存,权重一放就所剩无几,上下文稍微一拉就 OOM。这就是我想加第二台机器的直接原因。
1.2 两条路线对比:换大卡 vs 双机集群
面对显存不够,摆在面前其实就两条路。
一条是直接换一张大显存显卡。这条路最省心,插上就能用,软件层面几乎零改动,推理框架对单卡的支持也最成熟。但问题也很现实:大显存卡价格高、功耗高、对机箱电源散热都有要求,而且我手头这两台小主机的定位本来就是低功耗常开,换卡等于把整个方案推倒重来。
另一条是两台机器组集群,做分布式推理。这条路的好处是复用现有硬件,成本低,扩展性理论上更好——不够再加一台。坏处是复杂度陡增:要处理节点间通信、要选支持多机的推理框架、要调并行策略,而且长上下文场景下通信开销会被放大。
我最后选了第二条。原因很直接:我想验证的恰恰是"低成本的分布式方案在长上下文下到底能不能打",这本身就是个有价值的问题。如果只是换张大卡,那这篇就没啥可写的了。
1.3 分布式推理的三种并行策略,选错等于白干
决定走集群路线之后,第一个要搞明白的就是并行策略。分布式推理主流有三种切法,理解它们的区别非常关键,因为长上下文场景下选错策略,性能会差出一个数量级。
张量并行(Tensor Parallel,TP):把单个矩阵乘法拆到多台机器上算,每层都要通信。它的优点是能切权重、能降单卡显存压力,缺点是通信极其频繁,对节点间带宽和延迟要求极高。放在两台通过普通网络连接的机器上,TP 基本会被通信拖死。
流水线并行(Pipeline Parallel,PP):把模型按层切成几段,每台机器负责一段,数据像流水线一样流过。它的通信量比 TP 小得多,只在层与层交界处传激活值。缺点是会有流水线气泡,利用率受影响,而且对长上下文来说,激活值传输量也不小。
数据并行(Data Parallel,DP):每台机器都放一份完整模型,各自处理不同请求。它不解决单请求显存不够的问题,只提升吞吐。对"单条长上下文请求"这个场景基本没用。
我的场景是单请求、长上下文、两台机器,所以真正有意义的是 TP 和 PP 的组合。实际选型时,我倾向于优先 PP、谨慎用 TP,因为两台机器之间的网络带宽通常远不如卡内互联,TP 的通信压力扛不住。这个判断在后面实测里也得到了验证。
提示:如果你只有两台机器且网络是普通千兆或 2.5G,别一上来就开 TP,先试 PP,或者用框架提供的混合策略,把 TP 限制在单机内部。
2. 硬件与框架选型:把每一分算力用在刀刃上
2.1 两台机器的配置与互联方式
先交代一下我用的硬件,都是很普通的配置,没有堆料。
| 项目 | 机器 A | 机器 B |
|---|---|---|
| 角色 | 主节点 | 工作节点 |
| 显存 | 单卡 16GB 级 | 单卡 16GB 级 |
| 内存 | 64GB | 64GB |
| 互联 | 2.5G 有线直连 | 2.5G 有线直连 |
| 系统 | Linux | Linux |
两台机器用一根网线直连,走 2.5G。为什么直连而不是走交换机?因为直连能省掉一层转发,延迟更低、抖动更小,对分布式推理这种对延迟敏感的场景更友好。如果你家里有现成的 2.5G 或万兆交换机,走交换机也行,但直连是更稳的选择。
这里有个很多人忽略的点:互联带宽直接决定了你能不能用 TP。2.5G 换算下来大约 300MB/s 出头,这个带宽跑 PP 的激活传输勉强够用,跑 TP 就纯属自虐了。所以我的策略从一开始就很明确——PP 为主,TP 只在单机内部用。
2.2 推理框架怎么选:三个维度打分
框架选型我主要看三个维度:是否支持多机、是否支持长上下文优化、部署复杂度。
支持多机分布式推理的框架不算少,但真正对长上下文友好的不多。长上下文的核心优化点在于 KV Cache 的管理,比如分页注意力、KV Cache 量化、前缀缓存复用等。如果框架不支持这些,上下文一长显存就爆,多机也救不了。
我最后选的是一个支持多机 PP、并且带 KV Cache 分页管理的框架。选它的核心理由是:它把长上下文的显存问题当成一等公民来处理,而不是让你自己去抠。部署复杂度上,它提供了相对清晰的分布式启动参数,两台机器配置起来不算太痛苦。
注意:框架版本一定要两台机器完全一致,包括依赖库版本。我踩过一次坑,两台机器框架小版本差了一位,结果分布式初始化一直失败,报的错还特别隐晦,查了大半天才发现是版本不一致。
2.3 模型选择:量化等级与上下文窗口的权衡
模型这块,我选的是一个中等规模、指令遵循能力不错的开源模型,做了 4bit 量化。为什么是 4bit 而不是更高精度?因为显存实在紧张,4bit 能在两台机器上把权重压到可接受的范围,同时质量损失在可接受区间。
这里有个关键权衡:量化等级越低,权重占显存越少,但长上下文下的质量衰减可能越明显。尤其是长上下文场景,模型需要在很长的历史里保持注意力,量化带来的数值误差会被放大。我的做法是先用 4bit 跑通,再对比 8bit 在长上下文任务上的表现,看质量差距是否值得那部分显存。
上下文窗口的设置也要务实。模型标称支持多少 token 是一回事,你实际能稳定跑到多少是另一回事。我一开始把窗口设到模型标称上限,结果显存直接爆。后来逐步往下调,找到一个"显存不爆、质量还行"的平衡点,这个点才是你真正能用的窗口。
3. 实操部署:从零把两台机器跑起来
3.1 环境准备与依赖对齐
部署第一步不是敲命令,而是把两台机器的环境对齐。这一步做扎实,后面能省掉大量玄学问题。
具体要做的:
- 两台机器系统版本尽量一致,内核版本差异不要太大。
- 显卡驱动版本一致,CUDA 版本一致。
- 推理框架版本一致,Python 环境用虚拟环境隔离。
- 时间同步打开,分布式通信对时间戳敏感。
- 主机名和 hosts 配好,让两台机器能互相用名字解析。
时间同步这点特别容易被忽略。我有一次分布式启动卡在初始化阶段,日志里啥有用信息都没有,最后发现是两台机器时间差了十几秒,导致某些握手逻辑超时。把时间同步打开之后,问题直接消失。
3.2 网络配置与连通性验证
网络是分布式推理的命脉,必须先把连通性和带宽验证清楚。
先确认直连网段配好,两台机器能互相 ping 通。然后测实际带宽,别只看网卡标称速率。我用的测试方法是两台机器之间传一个大文件,看实际吞吐。2.5G 直连实测能跑到 280MB/s 左右,这个数字心里有底之后,后面调参就有依据了。
# 在机器 B 上启动一个简单的接收端,机器 A 发送测试数据 # 机器 B nc -l 5000 > /dev/null # 机器 A dd if=/dev/zero bs=1M count=2000 | nc 机器B地址 5000这个测试能直观告诉你实际带宽。如果实测远低于标称,先查网线质量、网卡协商速率,别急着上推理。
3.3 分布式启动参数详解
启动分布式推理,核心是几个参数:节点角色、节点数量、并行度配置、通信后端。
节点角色上,一台是主节点(rank 0),负责协调;另一台是工作节点。节点数量就是 2。并行度配置是重点:PP 设成 2,TP 设成 1,也就是把模型按层切成两段,每台机器一段,不做张量切分。这个配置在两台机器、普通网络的场景下是最稳的。
通信后端要选对。分布式推理通常需要一个通信库来传激活值和同步状态,选一个对网络延迟不敏感、支持 CPU 参与通信的后端。具体选哪个取决于框架,但原则是:优先选经过验证、社区用得多的后端,别用冷门的。
启动命令大致长这样(参数名以你用的框架为准):
# 主节点 python -m 框架.launch \ --nnodes 2 \ --node-rank 0 \ --master-addr 主节点地址 \ --master-port 29500 \ --pipeline-parallel-size 2 \ --tensor-parallel-size 1 \ --model 模型路径 \ --max-model-len 32768 # 工作节点 python -m 框架.launch \ --nnodes 2 \ --node-rank 1 \ --master-addr 主节点地址 \ --master-port 29500 \ --pipeline-parallel-size 2 \ --tensor-parallel-size 1 \ --model 模型路径 \ --max-model-len 32768两个节点的参数除了 node-rank 之外必须完全一致,尤其是并行度和模型路径。模型路径建议用共享存储或者两台机器都放一份,别用网络挂载,长上下文加载时网络挂载容易成为瓶颈。
3.4 首次启动与冒烟测试
第一次启动别急着上长上下文,先用一个短请求做冒烟测试,确认分布式链路通了。
冒烟测试要验证三件事:模型能正常加载、请求能正常返回、两台机器的显存都吃上了。如果只有一台机器显存涨了,说明 PP 没生效,模型全压在一台上了。如果请求返回但特别慢,可能是通信没走对路径。
我建议冒烟测试用一句特别简单的话,比如让它复述一句话,看返回是否正常。这一步过了,再逐步加长上下文。
4. 长上下文实测:到底能不能用
4.1 测试方法设计:怎么测才算数
测长上下文不能只测"能不能返回",要测三个维度:能不能装下、装下之后快不快、快的同时质量行不行。
我的测试设计是这样的:构造不同长度的输入,从 4K 一路加到 32K,每个长度测三件事——首 token 延迟、生成速度、显存峰值。同时准备一组需要长上下文才能答对的问题,比如把关键信息埋在几万 token 的中间位置,看模型能不能捞出来。这就是常说的"大海捞针"测试,能有效检验长上下文的真实可用性。
4.2 显存占用实测:KV Cache 是最大变量
实测下来,显存占用的大头确实是 KV Cache。权重是固定的,但 KV Cache 随上下文长度线性增长。下面是我实测的大致趋势(数值为示意,具体因模型和框架而异):
| 上下文长度 | 权重占用 | KV Cache 占用 | 总显存 | 状态 |
|---|---|---|---|---|
| 4K | 固定 | 小 | 低 | 轻松 |
| 8K | 固定 | 中 | 中 | 正常 |
| 16K | 固定 | 较大 | 较高 | 接近上限 |
| 32K | 固定 | 大 | 很高 | 需优化才能跑 |
关键结论:两台机器 PP 切分之后,权重被分摊了,但 KV Cache 的分摊取决于框架实现。如果框架把 KV Cache 也按层切分,那两台机器各存一半,长上下文就能撑得更久;如果 KV Cache 集中在某一台,那台就会先爆。这一点在选框架时一定要确认清楚。
4.3 速度实测:通信开销是隐形杀手
速度这块,PP 的通信开销在长上下文下会被放大。原因很简单:长上下文意味着每层要处理的 token 更多,层与层之间传的激活值也更大,2.5G 的带宽就成了瓶颈。
实测下来,短上下文时两台机器和单机差距不大,但上下文一长,双机的生成速度会明显低于单机同配置(如果单机能装下的话)。这就是分布式的代价——你用通信换来了显存,但通信本身要花时间。
所以我的结论是:双机 PP 适合"单机装不下、但你能接受速度下降"的场景。如果你的上下文长度单机就能装下,那双机纯属给自己找麻烦。
4.4 质量实测:大海捞针能不能捞到
质量测试是最有意思的部分。我把关键信息埋在不同位置,测模型能不能准确捞出来。
结果符合预期:在模型有效窗口内,捞针准确率不错;一旦超过某个长度,准确率开始明显下降。这个下降点往往早于显存爆掉的点。也就是说,显存还能装,但模型已经"记不住"了。这提醒我们:能装下不等于能用好,有效上下文窗口才是真正该关注的指标。
量化对长上下文质量的影响也在这里体现出来。4bit 在短上下文下和 8bit 差距不大,但在长上下文捞针任务上,4bit 的准确率下降更明显。如果你的智能体任务对长上下文精度要求高,可能得考虑牺牲一点显存换更高精度。
5. 常见问题与排查技巧实录
5.1 分布式启动失败排查表
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| 初始化卡住不动 | 时间不同步 / 网络不通 | 检查时间同步、ping 测试 |
| 报版本不匹配 | 框架或依赖版本不一致 | 逐项对比两台机器版本 |
| 只有一台显存涨 | PP 未生效 | 检查并行度参数 |
| 启动即 OOM | 上下文设太大 / 权重没分摊 | 调小 max-model-len,确认 PP |
| 通信超时 | 带宽不足 / 防火墙拦截 | 测带宽、查端口放行 |
5.2 长上下文 OOM 的三种解法
长上下文 OOM 是最常见的坑,我总结了三种解法,按优先级排:
第一,调小 max-model-len。这是最直接的,先让它能跑起来,再逐步往上加,找到稳定点。
第二,开 KV Cache 量化。把 KV Cache 也用低精度存,能省不少显存,代价是轻微的质量损失。这个开关很多框架都有,值得一试。
第三,开前缀缓存复用。如果智能体的系统提示词很长且固定,前缀缓存能避免重复计算,既省显存又提速。智能体场景下这个优化收益很大。
5.3 速度慢的排查思路
速度慢先分清是计算慢还是通信慢。判断方法:看显卡利用率。如果显卡利用率高但速度还是慢,那是计算瓶颈,考虑量化或换更小的模型;如果显卡利用率低,那多半在等通信,这时候要检查网络带宽、并行策略是否合理。
我踩过的一个坑是:PP 的切分点没选好,导致两台机器负载不均,一台忙死一台闲死。调整切分点让两层计算量接近,速度能明显改善。切分点怎么选?看模型各层的参数量和计算量,尽量让两段均衡。
提示:PP 切分不是随便切的,切在计算量相近的地方,流水线效率最高。切歪了,慢的那台会拖垮整体。
6. 智能体场景下的额外优化
6.1 工具调用与上下文管理
智能体跑起来之后,上下文增长比纯聊天快得多,因为工具返回结果往往很长。我的做法是对工具返回做截断和摘要,只把关键信息塞回上下文,而不是原样全塞。这一招能显著延长有效上下文的使用时间。
另外,智能体的系统提示词通常很长且固定,一定要用前缀缓存。前缀缓存能让这部分只算一次,后续请求直接复用,省显存又提速,是智能体场景下性价比最高的优化之一。
6.2 多轮对话的 KV Cache 复用
多轮对话里,历史部分的 KV Cache 其实是可以复用的。如果框架支持,尽量开启。这样每轮只需要计算新增部分,而不是把整个历史重算一遍。对长对话智能体来说,这个优化能把响应速度拉回可用区间。
6.3 任务拆分降低单次上下文压力
还有一个思路是从任务设计上降低上下文压力:把大任务拆成小任务,每个小任务用独立、较短的上下文。智能体不一定非要把所有历史都塞进一个上下文里,用外部记忆或者任务状态文件来存中间结果,需要时再取。这样单次请求的上下文就能控制住,双机的压力也小很多。
7. 我的实操心得与几个关键结论
折腾这么久,几个体会比较深。
第一,两台机器跑本地智能体,长上下文"能用",但有明确边界。边界取决于你的网络带宽、框架的 KV Cache 管理能力、以及模型的有效窗口。越过边界,要么 OOM,要么质量崩,要么慢到没法用。
第二,PP 是两台机器最务实的选择,TP 慎用。普通网络下 TP 的通信开销扛不住,PP 虽然也有通信,但量级小得多,是更稳的方案。
第三,有效上下文窗口比标称窗口重要得多。别被标称数字忽悠,实测捞针准确率下降的那个点,才是你真正能用的窗口。
第四,智能体场景下,前缀缓存和工具返回截断是两个必做的优化,收益立竿见影。
最后分享一个小技巧:如果你只是想验证方案可行性,先用小模型、短上下文把分布式链路跑通,确认两台机器能协同工作,再换大模型、拉长上下文。这样出问题时容易定位,不至于一上来就被一堆变量搞晕。分布式推理这东西,变量太多,一次只改一个,是最省时间的调试方式。