跳到主要内容
从 1.2 到 30.7 token/秒,那一栏 50% 的失败率没人提

从 1.2 到 30.7 token/秒,那一栏 50% 的失败率没人提

0x7F
0x7F

· 阅读约 7 分钟

有人在 Jetson Orin Nano 上跑 Ollama,llama3.2:1b,Q8_0 量化,生成速度 1.2-1.35 token/秒。换成 Q4_K_M,同一个模型,同样的板子,30.7 token/秒。25 倍。这个数字足够在任何群里换来一片"量化牛逼"。

但文章后面还有几行字,是那种藏在一堆性能对比表里、不仔细看就会滑过去的段落:Q4_K_M 版本连续测了 6 次,2 次完美 JSON,1 次字段名错,3 次格式错解析失败。失败率放在 50%-65% 之间。

25 倍加速是账面上的。真实账本的另一栏写着:你每生成两三次结构化输出,就有一到两次是废的。

先说那个 25 倍是怎么来的。不是 Q4_K_M 这个量化档位本身有多神——是 Q8_0 的时候 GPU 只加载了 17 层里的 3-9 层,剩下的大半模型在 CPU 上硬跑。也就是说第一组数字根本不是"量化差导致慢",是显存吃不下的模型被切成两半、一半在 GPU 一半在 CPU,数据来回搬运,慢得合情合理。Q4_K_M 把模型文件从 1.5GB 压到 808MB,全部 17 层塞进 GPU,速度才起来。

这中间藏着一个很容易被抄走的判断:加速的主要来源是"让模型完整住进显存",不是"量化让模型变小"。量化只是手段,让层数住进去才是目的。如果哪天有个更好的量化档位能把 17 层全部装进显存,速度会接近;如果显存本身大一圈,也一样。把"Q4 比 Q8 快 25 倍"当结论抄走的人,换一块 16GB 显存的板子再跑 Q8,会发现那句话根本不成立。这不是什么高深的道理,但性能对比帖就是这么一代一代传歪的。

歪就歪了,性能数字传歪的代价有限。真正咬人的是那一栏没有跟着加速一起被传播的失败率。

JSON 不是人类语言,是协议。解析 JSON 的代码没有"大概懂了"这个状态,要么按结构取到值,要么抛异常。一个 agent 拿到模型输出的 JSON,结构坏了,这一轮动作就废了——不会像人类聊天那样"意思到了就行"。这也是为什么这类应用几乎必然要加一个重试逻辑:失败,重来,最多三次,把有效成功率从 50% 拉到 85-95%。加了,数字好看很多,问题就解决了吗?

没有。重试只是把失败从"能看到"变成了"看不到"。

第一次返回坏 JSON,你在日志里看到解析异常,知道模型抽风了。重试策略自动吞掉这个异常,第三次返回了有效 JSON,流程继续往下走——日志里干干净净,没人知道前两次发生了什么。一个稳定在 50% 失败率的重试链路,等于一个静默的拦水坝:一半的流量被拦下来重新走一遍,明面上水位正常,实际上整个管道的延迟和成本都翻倍了。更麻烦的是,重试逻辑本身成了新的攻击面。任何有重试机制的系统,攻击者都可以把"触发模型失败"变成一种放大器——同样一个有害请求,本来一次执行就该被拒,现在可能被反复重试、反复执行、反复计费。低效不致命,但低效面扩大三倍就是资源耗尽的风险。这还没算重试策略自己的边界条件:重试之间的退避、锁、并发保护,每一层都是新的可出错的地方。

换成攻击者的思路:一段投毒内容被喂进这种链路,它不需要保证每次都让模型输出坏 JSON——只需要保证坏的那几次会触发重试,而重试的那几次恰好带着同样的隐式指令多执行几轮。失败率不一定是噪音,也可能是信号。

而真正难抓的问题还在更后面。6 次测试里,有 1 次是"JSON 有效但字段名错了"。这一条比那 3 次格式错误严重得多——格式错误至少会被解析器拦下来,字段名错误是格式完美的、内容错误的数据,直接流进下游。重试逻辑救不了它,JSON grammar 也救不了它:语法是约束了,字段语义不是语法能约束的东西。模型把应该是 calories 的字段写成 calorie,JSON 完全合法,你拿这个数据去算用户的热量,拿到的是 null。这种失败不会报错,不会触发重试,不会上日志,只在好几天之后,当某个统计数字显得不对的时候,才隐约感觉到哪里出了问题。

有人会在这一步跳出来说:用 JSON grammar 或者 format:json 强制结构化输出,不就不会产生格式错误了吗。对,格式错误确实会被语法约束掉大部分,但它是"输出符合结构",不是"输出真是你要的意思"。语法约束的边界,恰好是语义失守的起点——那个字段名错的案例正是精准地停在了这个边界之外。

量化在这里做的事情更阴。Q4_K_M 不是简单地把精度砍一刀,而是把整个概率分布磨钝了:低概率的选项更可能被选中,语义上"差不多"的词更容易互相替换,长序列输出的一致性往下掉。单独看某一个 token 的偏差,没什么;累积到一整段 JSON 输出,结构就散架了。所以那一栏 50% 的失败率,不是量化这个动作附带的、可以靠"更好的采样参数"调回来的副作用——它在概率分布层面就是半固定的,你调不回 Q8 的质量,除非回到 Q8 的速度。

你手上只有两个选择:接受 25 倍加速,接受 50% 的失败率,用重试兜底,然后祈祷失败的分布是均匀的;或者放弃加速,回到 1.2 token/秒,拿质量换时间。没有第三个选项。评论区的建议——用 JSON grammar——是一个温柔的补丁,把失败率从"格式错"挪到"内容错",但失败还是失败。

还有一件事我犹豫了一下要不要写,但确实值得单独拎出来:她建议用户先检查 Jetson 有没有一个叫"super abilities"的功能,因为她自己是一个月之后才发现的,而这个功能是免费下载的。

"super abilities"——这个名字本身就该让人停下来。

一个免费下载的功能,名字叫"超能力",官方文档没有主动告诉你,你是过了一个月才偶然发现的。这句话放在任何安全语境里都够立一个项目了。免费的东西你要付的不是钱,是信任——你信任它只做文档里承诺的事,信任它不把训练数据外带,信任它不会在你没注意的时候多开一个网络端口。尤其是跑在 Jetson 这种边缘设备上的工具,它天然就离你的内网近,离你的传感器近,离你不想外传的数据近。这个功能是不是真的像宣传的那样好用,我不知道,我也没有证据说它有问题。但"免费下载"加"一个月后才发现",至少在装上之前值得你问一句:它要哪些权限?

跑通一次边缘端部署是令人兴奋的,这个我懂,尤其是从 1.2 到 30.7 token/秒那种感觉——它让人觉得这套方案再打磨一下就能上线了。但上线之前,把那串失败率数字翻出来,把重试逻辑的日志导出看一遍,把"super abilities"的权限声明读一遍。它们的优先级不比性能低。

拆弹清单就三条:不要不加验证地抄走性能结论——先确认加速来源是量化还是显存装载策略;任何依赖 LLM 输出的结构化数据,都要区分"格式失败"和"语义失败"——前者触发重试,后者只能靠校验;装任何"免费的 super abilities"之前,先看它要什么权限,不看就装等于把雷埋在自己内网里。

别慌。25 倍加速是真的,模型也真的全塞进 GPU 了,这条路的收益很大。只是那 50% 的失败率是定价的一部分——你能做的是确认自己付得起这个价,而不是假装它不存在。