你的本地模型也许根本没用上 GPU:一个 Token 速度数字为什么经常骗人
OC 原创示意图。
林岚
OC 原创示意图。
据开源项目 picchio 的作者介绍,这个单文件诊断工具会同时读取 llama.cpp 或 Ollama 的运行日志、系统 GPU 指标和实际耗时,专门寻找两类常见问题:模型悄悄回退到 CPU,以及一个看似正确的 tok/s 数字被放错了语境。
一句话结论: 本地模型“每秒多少 Token”如果没有说明是在读提示词还是生成答案、是否包含加载时间、用了多少 GPU 层,往往不是性能数据,只是一段容易被记错的数字。
picchio 的起点就是一次很典型的测速困惑。作者记得同一模型直接运行 llama.cpp 能到 36 tok/s,放进自己的应用却只有 11.5 tok/s。后来他跑了 32 种 CPU/GPU、冷启动/热启动组合,36 这个数字没有在任何生成场景中重现,真正发现的反而是某个构建发生了无提示的 CPU 回退。
为什么这种误会如此常见?因为大模型运行至少有三段速度。prefill 是模型读取提示词的速度,长上下文时可能达到每秒数百甚至数千 Token;decode 才是逐字生成回答的速度;wall clock 还会把模型加载、预热和网络等待算进去。有人截到一个 500 tok/s 的画面,很可能是在看 prefill,却把它记成了回答速度。

OC 原创解释图:GPU 回退对提示词处理的打击,可能远大于对逐 Token 生成的影响。
项目给出的 Apple M5 实测很好地说明了差别。同一个 Qwen3.5-9B Q4KM 文件,GPU 正常工作时 prefill 为 588 tok/s、decode 为 21.1 tok/s;强制 CPU 后分别变成 26.8 和 12.2。前者慢了约 22 倍,后者还不到两倍。如果只盯着生成速度,你甚至可能没意识到 GPU 已经掉线,直到换成长提示词或计算完整耗时才发现系统明显变慢。
Ollama 社区此前也有人提交过 无声 CPU 回退问题:当显存放不下模型、没有任何层能进入 GPU,或只能部分卸载时,关键信息可能只存在于调试日志中。picchio 做的不是让模型变快,而是把引擎自报、操作系统观察到的 GPU 活动和时间特征放在一起。如果三者互相打架,它就拒绝给出“正常”结论。
另一个容易误读的是量化名称。作者测试了四个都标成 Q4_K_M 的 Qwen3.5-9B 文件,实际有效位宽在每权重 5.02 到 5.27 bit 之间。Q4 并不保证整个文件严格以 4 bit 保存,不同张量、缩放参数、词嵌入和附加头部都可能采用更高精度。拿标签相同但内部组成不同的文件比较速度和体积,本来就不是严格对照。
先别急着把 picchio 当成新的跨平台跑分标准。项目目前的完整验证主要覆盖一台 Apple M5 和一台租用的 RTX 4090;Linux 上的 Ollama、Vulkan 和更多硬件仍缺少实机数据。它更像一支故障定位探针,而不是用来宣布“哪张卡赢了”的排行榜。
对本地模型用户,最实用的做法是每次记录同一个模型文件、上下文长度、生成长度、引擎版本和 GPU 层数,把 prefill、decode、首 Token 延迟和总耗时分开。Ollama 用户至少先看 ollama ps 的 CPU/GPU 分配;需要更完整证据时再用 picchio 复测。这个对开发者到底省了哪一步?它省的是你花几个晚上调提示词和线程数,最后才发现模型压根没有跑在以为的硬件上。
参考来源
- GitHub:logxio/picchio:项目说明、测量协议与原始测试数据。
- Ollama Issue #14258:GPU-to-CPU fallback happens silently:用于核对无提示回退在真实用户中的表现。
- llama.cpp:性能测试工具文档:用于理解专用 benchmark 与完整应用耗时的区别。
评论
围绕这篇文章补充信息、提出问题或分享观察。