单张 3090 每秒 381 token,是真的吗:拆解大模型的照抄式加速

单张 3090 每秒 381 token,是真的吗:拆解大模型的照抄式加速

文中标注来源的第三方与官方数字来自公开资料,其余速度与上下文数字均为本机实测,测试在同一台双 RTX 3090 机器、同一张显卡上完成。速度单位是 token/s,即每秒生成的 token 数。
前两天社交平台上流传一条帖子,说一张消费级显卡把 27B 的大模型推到了 381 token/s。显卡是 2020 年发布的那款 24GB 显存 3090,测试时把功耗限制在 250W。发帖的人自己先补了一句,这不是普通聊天的成绩。
看到这类数字,第一反应通常是营销。但顺着帖子给出的线索,我找到了它背后那套开源实现,把文档和参数读了一遍,然后在自己的机器上装起同样的机制重测。结果是 372 token/s。
所以这个数字是真的,只是在一种特定的活儿上成立。而那个条件比数字本身更值得说清楚,因为它决定了这套东西对你的工作到底有没有用。

先说清楚一件事:模型一次只能吐一个 token

要理解 381 意味着什么,得先知道不加速时能跑多少。
大模型生成文字的过程,每一步都要把整个模型的权重从显存里读一遍,算出下一个 token。这张卡的显存带宽是 936 GB/s,而我们要跑的模型权重压到 4 位精度之后大约 15GB。两者一除,理论上限就在 60 token/s 上下,实际能到 40 多个。
我们用的这套引擎,官方公布的裸跑成绩是 47.3 token/s。这个数字跟上面那道除法严丝合缝,说明它撞的是硬墙:不是算法不够聪明,是显存带宽就这么宽。
所以 381 不可能是”算得更快”。每秒 60 的墙还在那里,唯一的出路是让每次读完权重之后多吐出几个 token 来。

投机解码:先猜后验

这正是投机解码要做的事。它的思路很直白:既然读一次权重很贵,那就别一次只算一个。先用一个很小的模型或者一个额外的预测头,一口气猜出接下来 7 到 8 个 token,然后让大模型一次性核对。猜对了,这一轮权重读取就摊到了七八个 token 上;猜错了,从第一个错的地方截断重来。
这套机制在推理引擎里已经很成熟,我们跑的这个模型自带一个多 token 预测头,猜得还不错,一轮通常能多吐出 3 到 6 个 token。但它的天花板很明显:草稿能提几个位置,验证块就只能核对几个位置。逐字复述材料的时候,答案明明就摆在输入里、随手能抄 15 个 token,一轮也最多只能过 7 个。
帖子里的 381,是在这之上又动了两步。

那个数字的巧劲,藏在”照抄”两个字里

第一步,换个地方找草稿。
原本草稿是由那个小预测头猜的。但有一类任务,模型要写的内容根本不在”未来”,而在”过去”:让它改写一个文件,输出里大半是原文;让它复述一段材料,输出基本就是输入;让它引用一段工具返回的结果,也是照搬。这些字已经明明白白地躺在输入里了,让预测头去猜,既浪费又猜不准。
于是有了第二种草稿来源:在模型这次请求的完整文本里,找”刚刚写出去的最后十几个 token”上一次出现在什么地方,然后把那一次后面跟着的内容提出来,作为这一轮要验证的候选。这就是所谓 n-gram 复制草稿。它不花任何额外算力,因为要抄的东西本来就在手边。
第二步,把验证的长度和草稿的长度拆开。
原来的实现里,验证块的长度是被草稿模型定死的,草稿模型只会猜 7 个,验证块就只能是 8 个位置,逐字复述时每轮最多过 7 个 token,一直顶在天花板上。改动之后,草稿模型照旧猜它的 7 个,但目标模型可以去核对一个更长的块,多出来的位置由复制草稿填。这样就变成了 16 个位置一次核对。
两个改动叠起来,按对方项目公布的数字,逐字复述一个 2 万多 token 的文档,每轮平均能过 15 个 token,速度从 159 涨到 381 token/s。

为什么抄错了也不会写错

这是整个机制里最容易被忽略、但最关键的一点:它不改变模型的输出。
复制草稿提出的只是”候选”。每一个位置上的 token,最终都要由目标模型自己点头才算数。如果抄错了,或者上下文里那句话本来就该改写,那这一步就是白猜一次,这一轮少过几个 token,速度慢一点,但写出来的内容跟不加速时逐个 token 生成的结果一样。它不是一个”用质量换速度”的开关。
我们复现之后也做了个朴素的质量抽查:往长文里埋一个暗号让模型检索,换成这一路之前和之后都能准确答出来。

我们把它装到自己机器上

我们本地跑的是一个叫 NInfer 的推理引擎,它是从零写起的 C++/CUDA 项目,专门把单张显卡跑一个模型这件事做到极致。它的最新版里有跟上面完全同构的两个开关:一个负责让草稿来自上下文,一个负责把验证块放宽到 16 个位置。
我们用的那组参数大致是:草稿模型选它自带的块状草稿器、草稿数 7、复制草稿的上限 15,另外把读文档的分块调小一档、把一部分权重转码压到 4 位,把上下文从 155,648 拉回 207,872(这张卡换草稿器之前是 262,144)。这些参数的意思在文末会交代。
然后在编码那张卡上先测三组负载,分别代表三种真实工作:凭空写一段新代码、把一个文件改完之后原样返回、以及解释一个概念;另外单独做了一次按帖子同形任务的复述测试。同一张卡、同一个模型,结果一并列在下面。

负载 速度(token/s) 输出里来自”照抄”的比例
改文件并原样返回 479.2 高
逐字复述 24K token 文档片段 371.9 高
凭空写新代码 187.3 低
解释一个概念 129.0 极低

其中逐字复述那一行,是我们按帖子描述的同一种任务重做的:输入 23,797 个 token,让它逐字复述其中一段,输出在 1,500 个 token 处被上限截断。客户端从首字时间自己算出来的速度是 372,引擎自己报的是 371.9,两边对得上。对比帖子里 25K token 文档跑出的 381,差距在 2.4%,属于同一档。

同一个模型,三种活儿差 3.7 倍

上表里最值得看的其实不是最高那行,而是最高和最低那行之间的差距:3.7 倍。

同一张显卡、同一个模型、同一套参数,仅仅是换了一种活儿,速度就从 129 窜到 479。这足以说明 381 不是这个模型的能力值,而是某一种活儿的成绩。把”照抄型”任务的速度当通用速度去理解,就会得出完全错误的结论。

帖子本身其实没有隐瞒这一点,它明确写了普通聊天大约在 133。这个数字跟我们的解释概念那一行(129)几乎重合。

它在什么场合真有价值

判断标准只有一条:你让它写出来的内容里,有多少是它刚才读进去的东西。

按这条标准,真正吃得上的场景是:检索问答里让它引用原文作答、把一份长材料改写成要点、代码助手修改文件并把整个文件写回、翻译时对照原文、从长文档里抽取字段、把工具返回的结果整理成结论。它们的共同点是输出与输入高度重合,模型干的活更像”整理”而不是”创作”。

这类场景在本地部署里尤其值钱。因为本地的硬约束就是显存带宽,而这个机制恰好是少数能绕开带宽墙的手段:它没有让计算变少,而是让每一次权重读取多负责十几个 token。对手里只有一张卡、买不起更大显存的人来说,这比任何精度上的小修小补都实在。

什么时候它帮不上忙

有几条边界必须说,否则很容易被这类数字误导。

自由创作、解释概念、开放讨论这类任务,输出与输入关系不大,复制草稿基本找不到可抄的东西。按对方项目自己的实测,这个机制在散文上的增益只有两三个百分点,个别任务甚至略微为负,因为长验证块本身也有调度开销。要说明的是,我们表里解释概念那一行看着比基线快约 25%,那部分可能主要来自换了草稿器和腾出显存,我们并没有把复制草稿这一项单独拆出来测。

真实负载和合成基准之间还有一道鸿沟。我们自己的合成文档测试里,复制草稿的接受率接近 80%;但在真实的长请求上,引擎日志记录的接受率只有 48.8%。原因不难猜:合成测试用的填充文本天然重复,真实工作的文本没那么配合。所以看到一个漂亮的合成数字时,最好先问一句它测的是什么文本。

还有代价。这条路线在我们编码那张卡上只能单并发,同时上下文从 262,144 降到了 207,872。想要两个并发或者满上下文,就得退回到普通投机解码,放弃这份提速。速度和容量在这一张卡上是此消彼长的。

如果你也想在自己机器上试

机制本身不神秘,落到配置上就是几个开关:把投机解码的草稿器切成块状草稿器,打开上下文复制草稿并把上限设到 15,再把预填充的分块调小以省出显存。上下文能拉到多少由引擎在启动时按剩余显存自己算,不是想要多少就有多少,所以每次调完都得核对一遍启动日志里的实际容量。

需要提醒的是,这套参数是围绕我们这张卡和这份模型产物抠出来的。换一个模型,权重大小和存储格式都会变,能拉到的上下文也就不一样。另外这些开关更新很快,跨版本升级前务必先读一遍破坏性变更清单。

回到那个数字

381 和 372 的差别其实不重要,两套不同的引擎、两份不同的实现,落到同一档上,说明这个机制是成立的、可复现的。

真正值得带走的是另一个问题:你每天让模型干的活里,有多大比例的输出,其实是在把它刚读进去的东西再说一遍?如果你的答案是”很大一部分”,那么这份提速就基本是白捡的;如果你的答案是”很少”,那 381 对你就是一个好看但与己无关的数字。

本地推理的账,从来不是靠一个通用速度跑分算清楚的。


皖ICP备2025105865号-2|皖公网安备34010402704739号