雑談:这两周有六个数字骗了我
这两周我给家里那台跑本地 35B 的旧台式机加了两个能力:作曲和画图。都跑通了,手机上跟 Telegram 里的助手说一句就出东西。
但这篇不打算写怎么装。装的过程没多少可说的 —— 下权重、写个 MCP 工具、注册进 agent,就完了。
值钱的是另一件事。我数了一下,这两周至少有六个数字骗过我。 它们都看起来很权威:官方默认值、系统自报的硬件规格、别人转述的限制。但每一个都以不同的方式不可信。
202 秒
先说最打脸的。
我装了 FLUX.2 [klein],一个 40 亿参数的图像模型,能生成也能按指令改图。第一次测试跑通了两条路:生成一张图 22.6 秒,改一张图 202 秒。
我当时的反应是“改图比生成贵九倍,合理” —— 改图要先理解原图嘛。
然后我看日志,才发现原因蠢得多:
生成 我传了 num_inference_steps=4 4 步
改图 我没传这个参数,用了默认值 50 步
我只在生成那次写了步数,改图那次忘了写。默认值 50 是从上游库继承的。
于是我把 4 步和 8 步的改图结果跟 50 步的放在一起看。看不出差别。 猫的毛色、姿势、桌面阴影,三张图里完全一致。
4 步 ~22 秒
8 步 ~37 秒
50 步 202 秒 ← 默认值
这个模型是蒸馏过的 —— 它的设计点就是 4 步。日志里其实一直在提醒我:Guidance scale 3.5 is ignored for step-wise distilled models。它连引导强度参数都不接受,我却以为它会认真跑 50 步做出更好的图。
默认值不是为你的模型准备的,它是为上游那个通用库准备的。
72 小时
过了几天,我发现服务全挂了。
Ollama 没了,网页版没了,编程 agent 也没了。但机器没重启 —— 开机时间显示 84 小时。日志里没有崩溃痕迹,没有 OOM,没有堆栈。
这个症状很有迷惑性:服务在同一时刻集体消失,而机器活得好好的。 我第一反应是内存不够被杀了。
查任务状态才看到真相:
OllamaServe lastRun=9/3 22:10:03 lastResult=267014
OpenWebUIServe lastRun=9/3 22:10:03 lastResult=267014
DshServe lastRun=9/3 22:10:03 lastResult=267014
267014 是 SCHED_S_TASK_TERMINATED —— 被终止,不是异常退出。三个任务的启动时间完全相同,说明不是各自崩溃,是同一个外部计时器到点了。
开机 9/3 22:10,加 72 小时 = 9/6 22:10。
原因在任务定义里:我用 schtasks /create 建的任务,没有显式设置 ExecutionTimeLimit,这个字段就根本不会写进 XML。于是 Windows 套用默认值 —— 3 天。到点掐掉。
<!-- 我以为的:没写就是没限制 -->
<!-- 实际的:没写就用默认 P3D -->
这是第二种骗法:“没有配置”不等于“没有限制”。 缺失的字段有它自己的默认值,而那个默认值可能是个定时炸弹。
顺带说,当时我第一个怀疑的是另一行设置:
<StopIfGoingOnBatteries>true</StopIfGoingOnBatteries>
“切到电池就停止任务”。听起来很像。但查到这台机器无电池设备、机箱类型是台式机,这条就排除了 —— 它不可能触发。
这次排除很重要。 如果我没排除就动手改它,服务照样会在 72 小时后死,而我会以为自己修好了。
(不过我还是把它关了。因为我打算买 UPS —— UPS 接上之后 Windows 会把它识别成电池,停电那一刻这个默认值会主动停掉所有服务,正好是最不该停的时候。)
64 GB
然后我想加内存。先问机器自己能吃多少:
Win32_PhysicalMemoryArray.MaxCapacity = 67108864 KB = 64 GB
清楚了,64GB 上限。我就照这个给自己做了规划,还挺认真地对比了 4×16GB 和 2×32GB 两种买法。
结果是错的。联想官方的产品规格书(PSREF)写的是:
Max Memory: Up to 128GB DDR4-3200 Memory Slots: Four DDR4 UDIMM slots
128GB,是我查到的数字的两倍。
为什么会差这么多?因为那个 WMI 查询的原理不是“探测内存控制器的能力”,而是读 BIOS 里 SMBIOS 表的 Type 16 记录 —— 一个厂商写死的静态数值。
品牌机厂商在这里填的是“我验证过并且愿意承诺的容量”,不是“这块板子的物理上限”。2021 年这台机器出厂时最高配可能就是 32GB,研发测到 64GB 就停了,于是 BIOS 里填 64GB,规避未验证容量的售后风险。
这是第三种骗法:它报的是承诺,不是能力。 而我把一个保守的商业承诺当成了物理约束。
这个错误的代价是实际的 —— 差别正好卡在我想做的事上:
35B + 图像模型 + 系统 42.5 GB → 64GB 够
再加视频模型(权重 36GB) → 只有 128GB 撑得住
我前面因为内存不够放弃了视频模型。基于 64GB 这个假数,那个放弃是“没办法”;基于真实的 128GB,那是“可以重新考虑”。
300 秒
作曲那边也有一个。
MiniMax Music 3 的参数里有 duration_sec,我请求 300 秒。跑了 11 分钟,出来的音频是 144.6 秒。
不是失败,退出码 0,文件完整。就是只有一半长。
原因:duration_sec 是上限,不是目标。歌词唱完歌就结束,参数只管“最多多长”。我给了三段主歌三段副歌加一个桥段,它唱了 144.6 秒就没词了。
这是第二次撞上同一件事 —— 之前请求 60 秒出了 23.4 秒,我当时以为是模型不稳定。
参数名不会告诉你它是 cap 还是 target。 现在我的工具里改成了读输出文件的真实时长回报,而不是把请求值抄回去 —— 因为抄回去就是在对用户说谎。
120 秒
这个不是机器骗我,是人。
有人告诉我 MCP 协议的工具调用上限是 120 秒,超过会被截断或者不稳定。听起来很合理 —— 我的音乐生成动辄好几分钟,正好该担心这个。
我去读了源码:
_DEFAULT_TOOL_TIMEOUT = 300 # 默认 5 分钟,不是 2 分钟
def _resolve_tool_timeout(config):
per_server = config.get("timeout")
if per_server is not None:
return per_server # 直接返回,没有任何封顶
默认 300 秒,而且每服务的配置值原样生效不裁剪。全文搜不到 120,也没有任何截断逻辑。
而且实测直接证伪:那次 300 秒的音乐任务跑了 664 秒才返回,一切正常 —— 是 120 的五倍半。
有意思的是,“过长会不稳定”这半句其实有道理,但原因完全不同:真正的瓶颈是显存,不是超时。我之前遇到的中断是模型没卸载就生成导致的爆显存。
一个正确的现象配上一个错误的解释,比纯粹的错误更难纠正。 因为你会拿现象去印证解释,然后觉得自己验证过了。
2667 MT/s
最后一个,还没完全查清。
我的四条内存标称都是 3200,实际跑 2667。我当时的推论是:这是品牌机,BIOS 锁了,没有 XMP 选项。
后来看到联想官方文档明确提到 BIOS 里有 XMP 配置文件,还说:
若使用非 XMP 内存或 BIOS 中未开启内存超频选项,系统将默认以 2133MHz 或 2400MHz 等基础频率运行
2133 或 2400。我的机器跑的是 2667,两个都不是。
所以 2667 不是“XMP 没开”的特征。回头看内存配置:
ChannelA-DIMM0 Corsair ← 一套
ChannelA-DIMM1 Lenovo ← 另一套
ChannelB-DIMM0 Corsair
ChannelB-DIMM1 Lenovo
两套不同品牌混插,而且每通道插了两条。这两个条件各自都会压频率:混插导致 XMP 配置文件不一致无法套用,每通道两条(2DPC)则是内存控制器本身的降频。
我原来的推论 —— “读不到 BIOS 设置,所以大概没有这个功能” —— 是个很糟的推理。读不到只说明我用的那个接口是空壳(Lenovo 的 WMI 类在消费级主板上返回 255 个一模一样的占位记录),不说明功能不存在。
顺便:一个不是数字的教训
写工具的时候还撞到一件事,跟数字无关但更有意思。
音乐和图像是同一个问题:模型太大,和常驻的 35B 不能共存,必须轮换。我给音乐工具写的方案是每次调用都卸载 35B,算完再装回来 —— 切换要 28 秒,但音乐生成本身两分钟以上,这点开销无所谓。
给图像工具我就直接照搬了。然后发现很荒唐:
出一张图 22 秒
卸载+重载 35B 28 秒 ← 比干活本身还贵
连出十张图要付十次 28 秒。
所以图像这边必须反过来:常驻一个 worker 持有模型,空闲三分钟后自己退出并把 GPU 还给 35B。 连续出图只付一次切换。
同一个约束,同一台机器,两个相反的正确答案。差别只在一个比值:干活的时间 vs 切换的时间。
我当时差点没注意到,因为音乐那套“跑通了”。能跑通的方案是最难被质疑的方案。
所以
六个数字,五种骗法:
| 数字 | 它假装是 | 它其实是 |
|---|---|---|
| 50 步 | 这个模型的推荐值 | 上游通用库的默认值 |
| 72 小时 | (不存在的限制) | 缺失字段的默认值 |
| 64 GB | 硬件物理上限 | 厂商愿意承诺的容量 |
| 300 秒 | 目标时长 | 上限 |
| 120 秒 | 协议规定 | 别人的转述 |
| 2667 | XMP 缺失的证据 | 混插 + 2DPC 的结果 |
要说有什么共同点,大概是:这些数字都不是测出来的。
默认值是别人替我填的,自报规格是厂商替我承诺的,协议上限是别人替我转述的,而 2667 是我替自己解释的。六个里面没有一个来自“我在这台机器上跑了一遍看结果”。
我这两周真正做对的事只有一件:每次都去测了。4 步和 50 步的图放一起看,300 秒的请求去读输出文件的真实时长,120 秒的说法去读源码再跑一次 664 秒的任务。
测一次的成本通常很低 —— 对比两张图花了不到一分钟。而不测的代价是把一个错的数字写进配置,然后在它上面做几周的决策。