本地 AI 桌面端今年冒出来一堆,大多走同一条路:给你一个空对话框,让你自己去挑模型、调参数、装 Ollama。Hob 反着来——装完什么都不问,先扫你的内存和显卡,然后自己决定下哪个模型。
所以这篇不写它「有多好用」。我要拆的是它替我做的那个决定:在 16GB 的 M4 上,它挑了什么、为什么挑它、猜的速度准不准。为了不被界面上的文案带跑,我直接把它选模型的代码抽出来,用这台机器的真实参数跑了一遍,再拿它内核指定的同一份权重、同一套提示词真跑了几轮。
先把事实摆清楚
Hob 是 Frigade 做的,10 月 26 日发 v0.2.1,MIT 许可,仓库在 FrigadeHQ/hob,官网 hob.chat,同一天上了 Hacker News 的 Show HN。下面这些是我从安装包里读出来的,不是抄官网文案:
| 项目 | 实测值 |
|---|---|
| 安装包 | Hob-mac-arm64.dmg 193,261,519 字节(184MB),装完 444MB |
| 应用主体 | app.asar 85MB,Electron 打包 |
| 签名 | Developer ID: Christian Mathiesen(2JQNSVN3N8),已公证(Notarization Ticket=stapled) |
| 推理内核 | node-llama-cpp 3.22.1,macOS 走 Metal,Windows/Linux 走 CUDA 或 Vulkan |
| 模型目录 | catalog/catalog.json,当前 8 个模型,全部 Apache-2.0 |
| 官方插件 | pdf、code、web、calendar-mail 四个,web 搜索和代码执行本身就是插件 |
环境:macOS 26.5.2,Apple M4(10 核),16GB 内存,Node 22.22.2。装起来三条命令:
curl -LO https://github.com/FrigadeHQ/hob/releases/latest/download/Hob-mac-arm64.dmg
hdiutil attach -nobrowse Hob-mac-arm64.dmg
cp -R "/Volumes/Hob/Hob.app" ~/Applications/
codesign -dv --verbose=2 ~/Applications/Hob.app 2>&1 | grep -E "Authority|TeamIdentifier|Notarization"最后一行是我自己的习惯:装任何会读你文件目录的东西之前,先确认签名和公证。Windows 用 Hob-windows-x64.exe,Linux 是 AppImage。
用它的代码,替它做一次选择
Hob 的选模型逻辑被拆成独立包 packages/picker,纯函数:输入硬件参数,输出推荐项、更快的选项、以及哪些模型在这台机器上不可用。把它拉下来跑一遍,比看界面里那句「已为你选择最佳模型」有信息量得多:
mkdir hob-pick && cd hob-pick && mkdir -p src
curl -o catalog.json https://raw.githubusercontent.com/FrigadeHQ/hob/main/catalog/catalog.json
for f in index.ts pick.ts bandwidth.ts types.ts; do
curl -o src/$f https://raw.githubusercontent.com/FrigadeHQ/hob/main/packages/picker/src/$f
done
echo "export { pick } from './src/pick'" > entry.ts
npm i -D esbuild && npx esbuild entry.ts --bundle --format=esm --platform=node --outfile=picker.mjs再喂给它这台机器的真实数字(内存、CPU、可用磁盘都从系统读,不手填):
import fs from 'node:fs'
import { execSync } from 'node:child_process'
import { pick } from './picker.mjs'
const hw = {
os: 'darwin', arch: process.arch,
cpuModel: execSync('sysctl -n machdep.cpu.brand_string').toString().trim(),
totalRamGB: Math.round(Number(execSync('sysctl -n hw.memsize')) / 1e9 * 10) / 10,
freeDiskGB: Number(execSync("df -g / | awk 'NR==2 {print $4}'")),
gpu: { kind: 'unified', name: 'M4', vramGB: 0 },
}
const r = pick(hw, JSON.parse(fs.readFileSync('catalog.json', 'utf8')).models)
const f = (c) => c && `${c.model.name} ${c.build.quant} ${c.build.sizeGB}GB ctx${c.contextSize} 预测${c.estTokPerSec}tok/s`
console.log('推荐', f(r.recommended))
console.log('更快', f(r.faster))
console.log('不可用', r.unavailable.map(u => `${u.model.name}(${u.reason})`).join(', '))我这台机器的输出是三行:
| 结果 | |
|---|---|
| 推荐 | Gemma 4 E4B,Q4_0 5.15GB,上下文 16384,预测 35 tok/s |
| 更快 | Gemma 4 E2B,Q4_0 3.35GB,上下文 16384,预测 59.6 tok/s |
| 不可用 | gpt-oss 20B(内存)、Gemma 4 26B(内存)、gpt-oss 120B(内存) |
| 磁盘 | 需腾出 2.15GB(当时只剩 13GB 可用) |
把内存当成唯一变量再跑一遍,它给不同机器的答案是这样:
| 内存 | 推荐模型 | 上下文 | 预测速度 |
|---|---|---|---|
| 8GB | Gemma 4 E2B(3.35GB) | 8192 | 59.6 tok/s |
| 16GB | Gemma 4 E4B(5.15GB) | 16384 | 35 tok/s |
| 24GB 及以上 | Gemma 4 26B Q4_0(14.44GB) | 8192→16384 | 40.5 tok/s |
| 128GB | gpt-oss 120B(63.39GB) | 16384 | 32.6 tok/s |
它算速度的那条公式,和三个后果
pick() 里的速度不是测出来的,是算的。核心三行:
每 token 要读的字节 = 权重体积 × (激活参数 / 总参数)
每 token 耗时 = 每 token 要读的字节 ÷ 内存带宽
预测速度 = (1 ÷ 每 token 耗时) × 0.75M4 在它的带宽表里写死 120 GB/s。E4B 是 8B 总参、4B 激活,5.15 × 0.5 = 2.575GB,120 ÷ 2.575 = 46.6,乘 0.75 就是 35。逻辑很干净:MoE 只读激活的那部分权重,所以「激活参数」这个数比「总参数」重要。
但这条公式只看「每 token 读多少权重」,于是有三个后果。
第一,2-bit 的大模型会显得比正常的小模型更快。 目录里有个 gemma-4-26b-a4b-compact,IQ2_XXS 量化、9.66GB、26B 总参 4B 激活,每 token 只读 1.49GB,算出来 60.6 tok/s,比推荐的 E4B 高出 73%。它在我这台机器的可选列表里是真的存在的——因为它的母模型(14.44GB)在 16GB 上跑不动,Hob 才把它放出来。速度是真的,质量不是:IQ2_XXS 是 2-bit 量化,26B 被压到这个体积,中文长句和数字推理都会掉。要省内存就选 E2B,别点这个被压扁的大家伙。
第二,gpt-oss 20B 在 16GB 上是「不存在」,不是「慢」。 它的内存预算是物理内存的 70%——这台 16GB 机器算出 12.04GB。而 gpt-oss 20B 要 12.11GB 权重,加上 16384 上下文的 KV 缓存 1.28GB 和 1.5GB 固定开销,一共 14.89GB,直接被判 memory。同一套账也是 26B 进不来的原因。这跟我在 LFM2.5 那篇里踩的内存坑是同一件事,只不过那次是 llama.cpp 当场 OOM,这次是应用提前把它挡在门外——提前挡掉体验好得多。
第三,官方文档比代码慢半拍。 文档写「8GB Mac 用 E4B Compact」,但按现在的 pick() 实跑,8GB 机器给的是 E2B:compact 是 3.86GB,加 8192 上下文的 KV 0.66GB 和 1.5GB 开销,一共 6.02GB,超过 8 × 0.7 = 5.6GB 的预算。这种偏差不看代码发现不了,也不影响使用(E2B 更小更快),但说明「文档里的推荐配置」永远值得自己跑一遍。
真跑一轮:同一份权重、同一套提示词
选型只是纸面推演,接下来要验证它猜得准不准。
先核对权重。目录里给了 sha256,我下完对了一遍:676c3507…03e35901,5,154,941,280 字节,与目录声明一致(权重来自 Hugging Face 的 gemma-4-E4B-it-qat-q4_0-gguf,QAT 量化版)。然后有个小障碍:应用内那份 node-llama-cpp 被打进 app.asar,外部进程 require 不到它的依赖(lifecycle-utils 找不到)。所以我用同版本 3.22.1 的 npm 包,配上它指定的同一份 GGUF、它自己的 systemPrompt() 和 samplingFor('gemma4')——采样参数是 temperature 1、topP 0.95、topK 64、presencePenalty 0.5,这些不是我拍的,是它源码里按架构写死的。
实测过程:
- llama.cpp 探测到 Metal,统一内存 12.71GB
- 加载 5.1 秒,arch=gemma4,训练上下文 131072
- 预热(先读一个 Hello 把权重从磁盘换进内存)8.7 秒——和源码注释里说的「别让用户等第一次提问」对得上
- 系统提示词 3543 字符 / 890 token,每次对话先交这么多过路费
三题结果:
| 题目 | 首字延迟 | 输出 | 解码速度 |
|---|---|---|---|
| A 中文短问答 | 7.4s | 26 tok | 30.7 tok/s |
| B 表格求和并比较 | 9.3s | 73 tok | 27.5 tok/s |
| C 按格式输出 JSON | 9.5s | 341 tok | 26.6 tok/s |
预测 35,实测 26.6–30.7,低了 12% 到 24%。也就是说 0.75 那个效率系数,在这台机器上的真实值只有 0.57 到 0.66。估算模型的方向是对的(它确实把 E4B 排在 E2B 后面、把 20B 挡在外面),但「35 tok/s」这种数字别当承诺看。
质量上两个细节值得记下来:
B 题它先答「华东 271500 最高」,紧接着自己发现华南 327300 更大,改口并算出差 52200。答案对了,过程摇了一次。让它做跨行求和,最好要求先列明细再下结论,不然中间那一步错得很自然。
C 题我明确写了「只输出 JSON,不要解释」,它回的是带 LaTeX 公式的 Markdown。4B 级模型的指令遵循,中文加结构化输出是最容易破的一环——这也是我把这篇文章的判断定在「能处理文件,不能替代思考」的原因。
工具调用:单步稳,多步现原形
桌面 Agent 的价值全在工具链上,所以我又用它的 toolFunctions()(自带乱码参数检测和 30 次调用上限)跑了同一个任务:读 sales.csv、算合计、说第一比第二高多少。三次,三种结果:
| 第几次 | 模型干了什么 | 用户看到什么 |
|---|---|---|
| 1 | read_file 用绝对路径,把同一个文件读了两遍 | 正文漏出内心独白,末尾带一个 <channel|> 控制标记 |
| 2 | 改用 run_python,代码里 try: 后面没缩进 | 又一次独白,没有最终答案 |
| 3 | list_files 把文件当目录传进去,接着 run_python 给了相对路径 | 清洗后正文为空 |
第 2、3 次我给 run_python 塞的是固定输出(模拟沙箱返回),所以「算」这一步不是真算,只验证调用链。但三次里没有一次老老实实把结果说清楚,这个是能确认的。
顺便说个加载时的告警:llama.cpp 报 <|tool_response> 不是 control 类型、并把 </s> 从 EOG 列表里移除。这是模型文件自身的小毛病,不影响跑,但正好解释了它为什么偶尔会把控制标记当正文吐出来。
这不全是 Hob 的锅。它的兜底其实做得很细:工具参数里出现控制标记就判定为「乱码调用」要求重调,单轮最多 30 次工具调用,思考预算 256 token,流式清洗专门维护了一长串 Gemma 自制标签。源码注释里写得很实在——两个工具调用粘成一次,「seen on Gemma 4」。作者知道这模型会怎么坏。
兜不住的是:模型自己不想算。
这也是我现在的偏好:宁可让一个小模型只干一件事。像之前测过的 S1-mini 那种 462MB 的文稿清洗模型,任务窄到只有一个输入一个输出,小模型的短板就不会暴露;一旦任务变成「读文件→算数→改回去」,每一步都要模型自己判断下一步,4B 级的分歧点就指数级放大。
装之前该知道的四件事
磁盘要留 10GB 余量。 公式是「权重 + 10GB − 可用空间」,我 13GB 可用时它算出来要腾 2.15GB。官方文档还写着:主模型下完会再下一个 Qwen3-VL-4B 专门看图,所以实际占用会比模型目录里那个数字更大(这一条来自文档,我没有走到那一步)。
别把 Chromium 的启动开关扔给它。 Hob --remote-debugging-port=9333 直接回 bad option: --remote-debugging-port=9333;从终端起进程还会看到 Node.js environment variables are disabled because this process is invoked by other apps。它有自己的参数解析,不接受 Chromium 那套。
GPU 崩过会自动降级。 源码里 probe 或 load 阶段进程崩掉,下一次启动会带 HOB_ENGINE_GPU=off。想手动省内存也可以直接用这个环境变量。
界面环节我测不了。 这台机器没有可截屏的显示会话(screencapture 报 could not create image from display),首次引导、PDF 表单填写、图表渲染、语音听写、MCP 插件、审批卡片和 Undo,全部未实测,需要你在真机上看。我能确认的只有安装包、选型逻辑、推理与工具链路这四层。
谁该装
适合:手里有 16GB 以上 Mac,想把合同、报表、发票这类文件交给本地模型;要的是一个能动手改文件的助手,不是又一个聊天框;能接受每秒十来个汉字的输出速度。
不适合:写长文、做复杂推理的——4B 级模型在结构化输出上会漏;要多步自动化的——读、算、改这条链在 E4B 上三次跑出三种结果;想拿它替代云端模型的——能替代的是「处理文件」这部分,不是「想清楚一件事」。如果你只是想要一个本地的问答入口,Open Dots 那种 30 行代码接 Ollama 的方案更轻;如果想要的是端侧小模型本身的手感,可以先看 Spark-X2.6-4B 那篇,同样是 3GB 级权重在 16GB 机器上的真实速度。
收尾
Hob 最值钱的地方不是免费,是它把「这台机器能跑什么」算清楚了,而且把理由写进了代码里:内存拿 70%、KV 按每 1K 上下文 0.08GB、固定开销 1.5GB、速度按带宽乘 0.75。这个思路比多支持一个模型重要——本地 AI 的门槛从来不是模型不够好,是普通人不知道该下哪个。
至于我,会把它留在机器上,专门处理那些不方便上传的表格和 PDF。真要动脑子,还是回云端。顺便记一笔给下次的自己:看到 2-bit 的「更快」选项,先想想它省下的是字节还是质量。这个坑,比装不上的坑更难发现。