推理与性能开发工具

自建大模型:多卡为什么卡,以及为什么还是比 API 贵

我桌下那台机器插了 3 张显卡。

只跑一张,单卡 decode(生成回复的出字环节)吞吐就有 200 TPS 以上。

可几张卡一起上,情况就变了。我让一个 agent 同时开几条分支去读文件、查资料、调工具。按理说 3 张卡正好分开使,结果终端里的输出一顿一顿,几条分支全挤在了一起。切到监控面板,3 张卡都没跑满:一张卡忙得冒烟,另外两张闲在旁边。

慢下来不是因为哪张卡算不过来,是这些分支被分到了同一张卡上。几张卡一起用,总要有东西决定每件活落到哪张卡,这个「怎么分」就是关键。

这台机器是 3 张 5090,每张 32GB 显存,卡间没有 NVLink,只能走主板 PCIe。跨卡通信慢,我没把模型切开摊到多张卡上,而是让每张卡各装一份完整权重,常驻跑一份 4-bit 量化的 Qwen3.8-27B,后端是本地引擎 NInfer,配 MTP 投机解码;关掉思考模式跑对话和代码,单条 decode 在 150 到 256 TPS。

一张卡跑得快,不代表几张一起用也快

社区里比显卡快慢,习惯喂一批互相独立、长度固定的题目,算完一道清一道,看每秒出多少 token。这种标准化测速就是跑分。我这台机器同时喂 1 到 8 条独立请求,3 张卡合起来能到 268 到 962 TPS,纸面成绩很亮眼。但 agent 真干活时是另一回事:一个任务要连着聊很多轮,每轮都由上一轮触发,带着全部历史像滚雪球一样越滚越长;中间调工具还会有停顿;主 agent 还会随时分叉出几条并行分支,开头全都重叠着同一段 system prompt 和背景。正因为这样,最近几套专门冲着 agent 流量设计的评测,都把「跨轮次历史复用」列成必考项(例),固定长度的独立跑分已经不够用了。

长对话会把几张卡搞乱,根子在显卡处理长文本的两步工序。模型记不住上一轮说了什么,每次新追问,程序都要把前面所有轮次的历史和新这句话拼成上万 token 的长文,重新喂给显卡。显卡分两步:第一步把整篇输入从头通读、算出各层的上下文关联,这叫 prefill,吃算力;第二步照着算好的状态一个 token 一个 token 生成,这叫 decode,吃显存带宽。聊到第 20 轮,前 19 轮其实一字未改。为了省掉每轮重读,显卡 prefill 时会把各层的中间状态留在显存里备查,这份缓存就是 KV cache;下一轮发来,只要开头那一大段历史和显存里存着的一模一样,显卡就直接调出 KV cache 跳过重读、只算末尾新增的几句,这叫 prefix reuse。一份公开实测测过:单实例下 1 万 token 的提示词,第一次没有缓存、要从头 prefill,首字延迟(TTFT,第一个字出来的耗时)4.3 秒;第二次命中 KV cache、完成 prefix reuse,降到 0.6 秒。有的 agent 团队干脆说,缓存命中率是生产级 agent 最重要的一项指标。

只有一张卡时,请求自然都落回同一张卡,显存装得下就能顺带复用。扩到 3 张独立卡后,前门就得有调度器决定每条新任务去哪张卡。普通网站后端无状态,轮流派单或挑最闲的机器即可;但大模型服务里每张卡各自攒着不同对话的 KV cache,把长对话随手切给一张空卡,空卡没有前面的缓存,就得从头重算上万 token 的 prefill。这种既要看哪张卡有空、又要认哪张卡存着旧缓存的派单机制,就是 routing。

routing 难的地方在于两个目标互相打架:想复用旧缓存,相同开头的请求就得一直送到存着这段缓存的那张卡;想不排队,又得把同时涌来的任务平摊到 3 张卡。前者叫 cache affinity,后者叫 load balancing,一个要求把请求往同一张卡上送,一个要求往多张卡上分散。几条分支同时启动,开头往往共用同一段 system prompt,一旦某张卡先存下这段公共开头,偏向 cache affinity 的调度就把后续分支全塞给它,造成一张卡排队、其他卡闲置。业界把这种扎堆叫 request herding(分析)。也有研究证明,单一映射规则没法同时兼顾 cache affinity 和 load balancing,一味追高命中率必然压垮热门前缀所在的实例。它给的补救办法是:给每个请求同时算两个候选卡位,再按当前的负载和延迟目标挑更合适的那个,相当于在两个目标之间取个折中。

缓存亲和想让同前缀请求都粘在同一张卡上省掉重算,负载均衡却要把请求打散以防排队,两个目标天生对立

开头那几条分支为什么全挤在一张卡上,我在自己机器上看得更清楚。前门挂的是官方 sglang-router,开默认的 cache-aware 策略。这几条分支开头共用同一段 system prompt,第一张卡先存下这段公共开头之后,调度器就认准了它,把后面的分支接着往那儿送,另外两张卡空着也不管。它这么做,是因为它只能看请求文本,默认偏向让同前缀的请求复用缓存;只有当两张卡的排队差超过某个阈值,它才会换卡,差得不够就继续粘着。

还有一种情况更麻烦:长对话聊到接近上下文上限时,客户端会压缩改写前面的历史,把中间几句换掉。NInfer 判断「这段前缀能不能复用」,靠的是历史完全没变;中间一改,它就只能把整段历史当成新的、从头 prefill 一遍,卡顿立刻出现。

而 agent 干活的节奏又是突发式的:上一秒集群还闲着,下一秒主 agent 可能就并发拉起好几条分支,把 3 张卡一次占满。要让这种突发不至于直接排起队,我平时只好让集群常态留出一半以上的算力余量,宁可空着卡换顺滑。

大厂已经解决了多卡分发,但个人玩家用不上

前门调度器为什么放着两张空卡不用,非把两条长对话挤在一张卡上硬抢?因为它在门外派单,看不见显卡显存里到底存了什么、手头还压着多少活,只能隔着门缝瞎猜。传统负载均衡默认后端无状态、请求独立、节点可互换,而 LLM 推理把这三条全打破了(综述)。数据中心要把活派准,调度器得随时掌握每张卡的两类信息。第一类是显存里真实的缓存明细:显卡每新存或删掉一块 KV cache,引擎就发一个事件,调度器据此维护一张全集群缓存地图,记清哪段文本在哪张卡上。第二类是每张卡手头积压的工作量:光看能省多少重读不够,还得算清这张卡还有多少输入 token 没消化完、多少在途的 decode 在排队,这种按待算 token 衡量的忙碌程度就叫 token load。两类信息合起来,调度器把要重读的 token 加上现有 token load 折算成预计等待时间,挑最快算完的那张卡。

有了引擎实时上报缓存变动和 token load 这两类信息,数据中心的多卡调度已经相当成熟。典型做法是引擎主动报事件、调度器按综合开销挑卡,实测能把首字延迟拉快 57 倍、吞吐翻倍(架构)。

但这套东西离个人玩家很远。它要求底层是 vLLM 或 SGLang、部署在 Kubernetes 上、引擎实时上报 KV cache 事件。个人多卡玩家为了保住单卡速度,往往选定制引擎:我用的 NInfer 对外只有 /health,查不了 cache 状态,也问不出在途负载;另一款常见的开源引擎(例)多卡时走的是单机内按层切分,缓存由单个节点自管。开头那几条分支为什么全被吸到一张卡上,谜底就在这:前门调度器只能靠记住外面飞过的请求文本,自己拼一棵近似的 prefix tree,看不到显存里真实的缓存,也判断不准哪张卡才是当下更该用的,只好一路认死最像有缓存的那张卡。社区里已经有面向自建集群的路由器,尝试在网关层用自带 tokenizer 维护前缀索引,把命中率提高了几倍、中位 TTFT 降低近 30%(项目);但只要底层引擎还是黑盒,站在门外猜状态就有上限。

就算分发做到最好,自建还是比买 API 贵

顺着 routing 往下想一步:即便把多卡分发做得严丝合缝,桌下这台机器的单位成本,真能低过按 token 计费的云端 API 吗?很多人买卡攒机,直觉是硬件只花一次钱,总比长期交接口费便宜。可一旦把硬件折旧和电费摊到产出的 token 上,算出每百万 token 的成本,再跟云端账单比,这个直觉很快会落空。

先看商业接口把价格压到了什么程度:DeepSeek V4.1-Flash 是 552B 的混合专家模型,激活 8B(输入)和 16B(输出)。按官方定价,闲时输出 0.60 美元每百万 token;输入命中云端 KV cache、完成 prefix reuse 后自动减免 98%,低至 0.003 美元。重复的计算能省到这个程度,价格自然压得下来。

再算我这台三卡主机。5090 早就不是发布时那个价了:2026 年 10 月,全新卡在美国要 5000 美元上下,二手也要 3800 左右(价格追踪),3 张卡加机箱电源差不多 1.7 万美元。按 4 年折旧,每月固定成本约 350 美元,满载功耗约 1.7kW。只有 24 小时满载,按 3 张卡合计 600 TPS 折算,每百万 token 成本才压到 0.4 美元左右;按个人每天真跑 1 到 2 小时的节奏摊下来,立刻涨到 4 到 6 美元,反倒比调 API 贵出近 10 倍。

每百万 token 成本放在对数轴上:自建全天满载约 0.4 美元才低于 DeepSeek 输出价 0.6 美元,每天只跑一两小时就涨到 4 到 6 美元,而命中缓存的 API 只要 0.003 美元

同样一台机器,为什么理想单价 0.4 美元、日常却要四五美元?关键不在显卡那几秒算得多快,而在机器一天 24 小时里有多少小时真在干活,这个比例就是利用率。买卡或租云主机按时间付钱,调 API 按实际生成的 token 付钱。一份自建成本测算显示,AWS 上常开的 H100 每月固定约 5000 美元,利用率要到 9% 才和 API 打平;另一份报告也指出,不少集群日均利用率低至 5%(数据)。落到个人多卡机器上还要再折两层:一天只跑 1 到 2 小时,机器大部分时间空着,这是第一层;就算人在电脑前干活,为防止 routing 撞车,我还得故意空出一半以上算力,这是第二层。两层叠起来,前面为体验留出的余量,最后全记进了不划算那本账。

把工程门槛和这笔账都摊开,自建大模型的定位反而清楚了:在桌下塞一台多卡机器,就像家里备一台柴油发电机。没人买发电机是为了省电费,真把折旧和油钱摊进每一度电,自家发电怎么算都比不过电网。我们甘愿折腾多张卡、调推理引擎、抠 routing 策略,买回来的本来就是 API 给不了的三样东西:代码和数据不出本地、断网或限流也能照跑、以及亲手拆开系统看清 cache 与调度如何咬合的控制欲和乐趣。

鸭哥每日手记

日更的深度AI新闻和分析