ARTICLE · 1100425
我终于让本地模型驱动起了 AI 助手,但官网的数字得先打对折
上个月我写过一篇《我用 64GB M5 Pro 跑 Qwen3.8 :能跑,但没法用》。当时的结论很干脆:本地模型在我这台机器上跑得起来,但跑得起来和能用之间,隔着一整个工作流的距离。
这篇文章我要把那个结论收回一半。
一条推文,两行命令
事情的起点是一条推文。一家做数据中心推理的公司发了个新东西,说在 M5 Max 上, 270 亿参数的模型能跑到每秒 144 个 token 。
推文里最扎眼的是安装方式——两行命令:
brew install incoai/tap/splash splash serve --model incoai/Qwen3.8-27B-Splash 没有配置文件,没有网络图,没有"先装 Python 环境再装依赖"。这种东西值得花点时间拆开看。
先说它到底在卖什么。这家公司的产品和主流做法是反的。别人做推理引擎是"一个引擎尽量能跑所有模型",它反过来:引擎围绕某一个模型重新造一遍。每个模型配自己的一套计算核、自己训练的一个小"草稿模型"、一套按这台机器算出来的内存预算。共享的只有调度、缓存和对外接口。
代价写在同一页上:这个引擎只支持两个模型,而且模型包是它自己的格式,别的地方加载不了。
这条代价我记下了,因为后面会变成真正的问题。
我是怎么测的
我没有跑个 demo 就下结论。这次的规则定得很死:
两个引擎轮流起在同一台机器上,用的是同一份测试脚本、同一批提示词、同样的输出长度上限、同样关闭"思考模式"的设置。测四档内容——普通说明文字、代码、代码加思考、高度重复的文本——再加两组压力测试:两万六千 token 的长上下文,和四个请求同时打进来。
有一条容易被忽略的公平性要求:两个 270 亿模型同时驻留要吃 32GB 内存,会互相抢资源。所以必须一个测完完全停掉,再起另一个。测速这件事,仪器脏了,数字就全是假的。
还要交个底:这台机器上一直有另一个后台服务在跑,占着一个 CPU 核心。我没关它,因为两端面对的条件一样,这才是你我都真实的工作环境。
最后一个差异必须说清楚:两边用的量化版本其实不完全一样。一个用的是社区做的混合精度版,另一个用的是标准 4-bit 版。严格来说,最干净的对比应该让两个引擎吃同一个文件——但那做不到,因为其中一个引擎只认自己的包格式。所以下面这组数字是"两台引擎各自的最佳可用配置"的对比,不是纯粹的单一变量对比。这个差异会往哪边偏,我在最后一节说。
结果:单请求全面领先,但不是碾压
四档内容的解码速度,单位是每秒 token :
倍数在一倍八到两倍七之间。长上下文的两组数据更能说明问题:两万六千 token 冷读进去,新引擎 66 秒,旧引擎 81 秒——两个都很慢,这不是引擎的问题,是长提示词的物理成本,谁都躲不掉。但同一段上下文第二次用的时候,缓存命中的首 token 延迟是 264 毫秒对 502 毫秒。
这组数字是整篇文章里最有用的,我放到后面讲为什么。
最反直觉的一节:官网那个数字,我得打对折
官网说 270 亿模型在这个档次的机器上能跑每秒 74 个 token 。我照着代码类任务复测,得到 68.6——对得上,官方没吹牛。
但同一套设置,我把内容换成普通的说明文字,数字掉到 41.5 。
差了一倍。
原因不复杂:这类引擎的核心加速手段是"投机解码"——让一个小模型先猜一串 token ,大模型一次性验证。猜得准就快,猜不准就白猜。而代码是最好猜的内容,缩进、括号、命名规律、重复的语法结构,小模型几乎能提前把下一段背出来。自由写作是最难猜的。
我把内容可预测性从低到高排了一下:
普通写作 41.5 → 代码 68.6 → 高度重复文本 95.6
一条很干净的正相关。
所以官网那个数字不是假的,它是一个"在最好猜的内容上"的数字。这件事对你有两个完全不同的含义:如果你的 AI 助手主要在改代码、读仓库、写脚本,你拿到的是高档位;如果它主要在写中文长文,你拿到的是低档位——而低档位和旧引擎的差距,其实还是那么大,因为旧引擎同样依赖这套机制,只是弱一些。
这也是我这次最想留下的一条方法:拿到任何引擎的宣传速度,先问它测的是什么内容。
真正的分水岭在并发,差了将近六倍
如果这篇文章只让你记住一个数字,记这个:
四个请求同时打进来,新引擎的墙钟吞吐是每秒 149.2 个 token ,27.5 秒跑完;旧引擎是每秒 25.1 个,163 秒。
将近六倍。
有意思的是差距的来源。我把两边的单个请求速率都记下来了:旧引擎那四个请求,每一个自己都保持在每秒 25 个左右——也就是每个请求都是全速的,只是它们排了队,一个跑完下一个才开始。新引擎那边四个请求各自每秒 38 个,同时推进。
顺带一提,官网上写的是"并发场景下接近四倍"。实际测出来比官网说的还多。宣传数字偶尔也会保守。
为什么这组数字比单请求速度更重要?因为一个 agent 干活时候的流量形状跟聊天完全不一样。
你问一句它答一句,那是聊天。 agent 是:读一段仓库、改几个文件、跑一次测试、把结果塞回对话,而这个对话每轮都在变长;再往下,它会同时派出好几个子任务去并行处理。它绝大多数请求,都是"同一大段上下文,加上一点点新内容"。
这意味着 agent 场景真正的瓶颈根本不是"模型每秒吐多少字",而是"同一段上下文第二次用的时候,要等多久才吐出第一个字"——也就是我前面埋着的那个 264 毫秒对 502 毫秒,以及几个请求能不能真的同时跑。
这两个指标里,旧引擎单请求其实不弱;它是输在"再来一次"和"一起来"上。
至于"第一次用"要等多久——我当时觉得那是次要的。后面会证明我错了。
但有一个反方,必须先讲
如果你根本不跑 agent ,也不并发——就是偶尔问一句、它答一句——那上面这些差距对你几乎不存在。单请求速度两边都落在"能用"的范围里,四档内容的差别在你眼里就是"快一点"和"慢一点",值不值得为它换一套工具,另说。
这篇文章的结论只在你让模型干连续的活时才成立。
代价:它把选择权收走了
好的地方说完了,该说代价。
第一,模型被锁死。 官方只提供两个模型包,一个 270 亿稠密模型、一个 350 亿的混合专家模型。我从旧的引擎换过来的时候,我在用的另外几个模型——一个无审查版本、一个带特殊推理头的版本——一个都带不过去。
第二,格式也被锁死。 模型包不是标准的格式,官方在模型卡上写得很直白:只能在它自己的引擎里加载。想换回去,得把那十几个 G 重新下一遍。
第三,硬件门槛不低。 要求 M3 或更新的芯片、比较新的系统版本、至少 36GB 统一内存,官方推荐 48GB 以上。这一条把大量存量机器直接排除在外。
第四,长上下文冷读依然很贵。 两万六千 token 进去要 66 秒。它比对手快,但快得不足以让人忘记这件事本身有多慢。
所以这不是"换个引擎就起飞"的故事,是用模型选择的自由,换来了实际可用性。
我最后是怎么用的
我没有把旧的引擎删掉。两者装在机器上不同端口,各跑各的,实测可以同时开着互不干扰——而且我之前遇到的那个"某个监控进程每隔两秒报一次接口不存在"的噪音,也随着端口分开彻底消失了。
新的那个我设成了开机自启。冷启动到能用大概七八秒。我特意做了个破坏性测试:直接把进程杀掉,系统在五秒内把它拉了起来,重新加载完成。做自动化的人都知道这个测试意味着什么——一个不会自己爬起来的后台服务,等于没有。
然后是最关键的一步:我把它接进了我日常在用的那个 AI 助手,用的是它自己的配置文件,没有动我的默认模型。我不想让一个本地模型悄悄接管我所有的工作。
跑通的那一刻我才确认这件事。
我问它:本次响应由哪个模型生成。它答:由本地的这个 270 亿模型生成。
听起来像个废话测试。但在这之前,我试过的所有本地组合,都会在这个位置上露馅——要么速度慢到没法调工具,要么输出格式不对导致工具调用失败。这次它真的把工具调起来了,然后把活干完了。
我当时的原话是:好像我终于找到一个可以驱动 agent 的本地模型了。
但"好像"这两个字,我自己看着不舒服。我说了不算,得它说了算。
它到底能不能干活:我跑了三轮
不跑 demo 。我给它三个真任务,全部走完整的 agent 循环——调什么工具它自己定,命令它自己写,结果它自己读。
第一个任务是硬指标:查出三个数,并且严格按一行格式输出。
它写了一条 shell 命令,调工具跑了两轮,最后给出:
目录数=55 文件数=379 合计=434 三个数都对。而且比我事先自己算的基线更严谨——我算出来是 54 和 278 ,它主动把隐藏目录算进了目录数、把文件按递归计数,思考过程里还专门讨论了"到底要不要包含隐藏项"这个歧义点。
另外两轮:读一个文件再总结,内容准确;换一套更小的工具集重跑,也过。
三轮全过。没有一次工具调用格式失败,没有一个编造的数字。
在这之前,我试过的所有本地组合都会在工具调用这一步露馅:要么慢得跑不动,要么输出格式不对导致调用失败。这次没有。
但有一件事,我一开始判断错了
会话内的表现非常好。同一段上下文第二次、第三次用,首字等待是 14.6 秒和 1.3 秒。缓存一旦命中,快得让人忘了上一句话等了多久。
问题出在"第二句话之前"。
第一次把一条完整的 agent 提示词喂进去,引擎自己报的读入量是四万五千多个 token。首字等待, 150 秒。
两分半。你敲完回车去泡了杯咖啡,回来它才刚开口。
但这个数字是"每开一次新会话都得付一遍",还是"只付一次"?我连着跑了五遍同一个查询。
第一次,一百五十秒。
第二次,二十一秒之后——三秒。
再往后三次,间隔从半分钟到五分钟不等: 2.5 秒、 2.6 秒、 3.1 秒。
引擎自己的日志把话说得很直白:第一次 cached 64,之后每一次都是 cached 44608。四万五千个 token 的提示词,它复用了四万四千六百个。
所以这一百五十秒不是每会话必付——是"第一次见这段提示词"付一次,之后它就躺在缓存里。
那我最早测出来的"每次都要重付"是从哪来的?查下去发现,是我自己把它挤掉的。做那组测试的时候,本机同时在跑别的本地压测,几组长提示词并发打进来。这台机器上只有一个引擎,那些请求照样要占同一份缓存,把我刚预热好的那一段挤了出去。
冷启动的价钱是真的,但它不是"每次新会话"的价钱,是"缓存被清掉之后第一次"的价钱。
顺手还摸到它缓存的一条脾气:改结尾免费,改中间重置。
同一段约七千个 token 的提示词,一字不改重发,首字等待从九点五秒掉到零点二秒。只改结尾最后三十个字,还是零点二秒——前面那七千个 token 照样复用。但把改动挪到中间,立刻回到九点五秒,从改动那一行往后全部重算。
这条脾气刚好解释了为什么我的 AI 助手不会每次都变冷:它每次新会话写进去的新内容(比如记忆),都挂在提示词最尾巴上。只要改动落在尾部,前面那四万多个 token 就一直有效。
瓶颈不在吐字,在"读题"
我回去把数字拆开看,原因很清楚。
它吐字的速度是每秒 35 到 52 个 token ,这个没问题。但它读提示词的速度,只有每秒三百出头。
四万五千个 token 除以三百,正好一百五十秒。
两个数字只差九倍。这个比例本身就不对劲——读题是最该快的一步,因为它可以整段并行处理,不像吐字必须一个接一个。一台机器读题只比吐字快九倍,说明它把力气花在了后头,前头那块板是短的。
而 agent 这个用法,恰恰是拿巨长的提示词去换一点点输出——它每干一件事,都要把整个对话历史和几十个工具的说明书重新读一遍。
这个用法,正正踩在它最短的那块板上。
好消息:这个数字我能调
提示词多大,是我说了算的。
我把那条 AI 助手的工具集从三十二个削到只剩一个,重跑同一类任务:
| 24 秒 | 34 秒 |
提示词缩到不足七分之一,首字等待降到约六分之一。基本是线性的:提示词砍多少,等待就砍多少。
二十四秒的首字等待,跟云端模型比还是慢,但它已经落在"坐下来能干活"的范围里了。
要让它变快,削提示词比换引擎管用。 同一台机器上,我原来用的那个引擎在同规模提示词下读得更慢——换回去只会更糟。
真正值得带走的,不是"哪个引擎快"
这次实验最有价值的产出不是一张速度表,是四件事。
第一,本地模型能不能用,分水岭不在单请求速度,在 prefill 成本和缓存复用。 缓存决定多轮会话里每一轮等多久; prefill 成本乘上你的提示词有多大,决定你每次开口要等多久。这两个指标在厂商的宣传页上通常排在后面,因为它们不好看——但它们是 agent 场景的全部。
第二,官方数字必须在自己的内容上复测。 方法很简单,三档内容加一次并发:一段你自己常写的普通文字、一段代码、一段高度重复的文本,再加四个请求同时打进去。半小时的事,能省掉你后面几个月的误判。
第三,本地和云不是替代关系,是分工。 我把本地的这个留着跑那些"数据不想出门"和"量大到心疼 API 费"的活,复杂的判断还是交给云端的强模型。这次我没改默认模型,就是这个意思。
第四,冷启动的钱只付一次,但会被你自己的别的活挤掉。 引擎读题慢、提示词又长,第一次开口可能要等两分半;可一旦进了缓存,后面每次新会话都只要三秒。真正让它重新变冷的,是本机同时在跑的大任务——它们抢的是同一份缓存。所以想让本地模型当日常入口,先管好本机在同时跑什么,比算单次速度有用。
最后补上开头留的那个尾巴。两边量化版本不一样,那个差异往哪边偏?说实话,单独看没法定论——混合精度版通常更准也更慢,但它同时带了一个专门加速的推理头,两个因素方向相反。
真正能回答这个问题的是另一组对照:官方发布时用的是同一套权重跑出来的对比,得到的倍数是两倍;我在代码内容上测到的倍数是二点二七倍。两个数字很接近。说明这组倍数主要来自引擎,不是来自权重版本。
最后点名那个最反直觉的结论——
那个"每秒 74 个 token"是真的。但它说的不是"这台机器能跑多快",而是"在这台机器上,当你让模型写代码时,它能跑多快"。同一台机器、同一个模型、同一套设置,换成让我写文章,数字就是 41.5 。
宣传页上的每一个速度数字,背后都藏着一个没说出口的"在什么内容上"。找到那个前提,比记住那个数字有用得多。
本文由 AI 辅助创作,作者进行了实测验证和编辑修改。