> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mirobody.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# 本地小模型：1.5.4 走到哪里，下一步怎么走

> 逐一实测的本地模型、GLM-OCR 能从照片里读出与读不出的内容，以及小型后训练模型组合的计划。

export const OssSource = ({path, lang = "en"}) => {
  const href = "https://github.com/thetahealth/mirobody/blob/43892ca6d85e3df1203fca8ceb547debd7947e74/" + path;
  return <p className="text-sm text-gray-500 dark:text-gray-400">
      {lang === "zh" ? "对应 mirobody " : "For mirobody "}
      <code>1.5.4</code>
      {lang === "zh" ? " · 源文件 " : " · source "}
      <a href={href}>
        <code>{path}</code>
      </a>
    </p>;
};

<OssSource path="docs/local-models-roadmap.zh-CN.md" lang="zh" />

Mirobody 不填任何 API key 也能跑：一个模型回答问题，一个模型读文档，都在同一台机器上
（怎么跑见 [local-models.md](/zh/local-models)）。这一页记录 2026-09-30 的实测结果、它对「看图」
意味着什么，以及让整套东西装进一台普通电脑的计划。

<h2 id="结论">
  结论
</h2>

* **现在的默认**（1.5.4）是小号：**MiniCPM5-2B**（Q4\_K\_M，1.6 GB）回答问题，**GLM-OCR-0.9B**（1.4 GB）读文档，
  合计 3.0 GB，跑在 llama.cpp 上，16 GB 内存、没有显卡就行。在现已发布的
  [`benchmarks/local_models/`](https://github.com/thetahealth/mirobody/blob/43892ca6d85e3df1203fca8ceb547debd7947e74/benchmarks/local_models/README.md) 评测上，它 24 道题过了 19 道（Claude Code 评分 248 分得 215），12 份文档的 140 个印刷行全部存对，31 条日记条目写出 22 条；
  在 1.5.4 改 harness（[见下](#先改-harness)）之前，24 道题过了 16 道，140 个印刷行存对 45 个，31 条日记条目一条也没写出来。
* **大号**是 Qwen3.8-27B（GSQ-RCO IQ3\_S，13 GB），实测见下：在我们的题目上答得和云端模型一样对，也能看照片，
  但要约 20 GB 内存：32 GB 内存的 Mac，或 24 GB 显存的显卡。
* **1.6.0 交付目标**：把这两个小模型针对 Mirobody 做后训练，作为 Mirobody 自己的模型发布（[再训练](#再训练)），
  合计约 3 GB，和今天的小号一样，16 GB 内存、没有显卡就能跑。是两个模型，不合并成一个。只有通过了和被替换的模型同样的评测，才替换默认。
* **看图**：GLM-OCR 只读印刷的文字和表格，别的一概不会。看懂一张照片拍的是什么，比如一盘菜的热量、一块皮疹，
  需要能看图的模型：大号，或云端模型。这对小模型做不到，Mirobody 会直说，而不是去猜。
* **和云端模型比**：[model-choice.zh-CN.md](/zh/model-choice) 把小号和五个云端模型（DeepSeek V4.1 Flash、Claude Sonnet 5.5、
  Claude Opus 5.5、Gemini 3.8 Flash、GPT-6 Luna）放在同一套题目上比，也写了各自的费用和谁会读到数据。

<h2 id="实测了什么">
  实测了什么
</h2>

Apple M4 Pro 48 GB，llama.cpp b11269，demo 数据。八道题各问两遍：三个月趋势图、最新值、
变化统计、用药、化验单、基因、别人共享给我的记录、常识题。一次运行算通过：调对工具、用对视图、
要图就画图、用提问的语言作答、并且答完。然后逐个核对回答里的每个数是否在库里有来处，
每个「偏高 / 正常」是否按报告上印的范围判断。

| 模型 | 大小 | 通过 | 记录里没有的数 | 按报告印的范围判断 | 每次回答 中位 / p90 | 能看图 |
| - | - | - | - | - | - | - |
| Qwen3.8-27B GSQ-RCO IQ3\_S（大号） | 13.0 GB | 16/16 | 0 | 有一次说报告没印范围（现在读数已带范围） | 134 / 216 s | 能 |
| Qwen3.8-27B GSQ-RCO IQ2\_S | 9.6 GB | 16/16 | 0 | 是 | 109 / 224 s（机器当时负载高） | 带 mmproj 时能（未测） |
| Qwen3.8-27B Q4\_K\_M（Ollama） | 17 GB | 16/16 | 0 | 是 | 99 / 234 s | 能 |
| Ternary-Bonsai-2-27B（PrismML 的 llama.cpp 分支） | 6.6 GB | 16/16 | 0 | 是 | 132 / 410 s | 未测 |
| Qwen3.5-9B Q4\_K\_M | 5.7 GB | 16/16 | 0 | 否：把报告印的范围说成「通常建议」，漏了报告标的「偏高」 | 40 / 76 s | 能 |
| MiniCPM5-2B Q4\_K\_M | 1.6 GB | 14/16（同一题两次都没答完） | 两次都整张图是编的（见下） | 没走到 | 17 / 375 s | 不能 |
| MiniCPM5-1B Q8\_0 | 1.1 GB | 2/16 | 把 BMI 29.4 当成体重 kg | 没走到 | 3 / 7 s | 不能 |
| MiMo-V2.6-Distill-Qwen-9B Q4\_K\_M | 5.4 GB | 10/16 | 10 个有数据的回答里 4 个 | 没走到 | 23 / 736 s | 未测 |
| 云端（Claude Sonnet 5、GPT-5.6、Qwen3.8-flash） | | 各 16/16 | | | 9–23 s | 能 |

耗时只在同一台机器、同一时段内可比。产出这些数字的评测工具没有发布；取代它的评测是
[`benchmarks/local_models/`](https://github.com/thetahealth/mirobody/blob/43892ca6d85e3df1203fca8ceb547debd7947e74/benchmarks/local_models/README.md)：通过产品自己的 API 问 24 道题、读 12 份文档和 15 句日记，
云端参照和本地两种大小一起跑。

<h3 id="minicpm5-2b-细看">
  MiniCPM5-2B 细看
</h3>

它快（大多 2–70 秒一答），结构也对，毛病很集中：

* 2026-09-30 问「过去三个月」，它没传日期，拿到一整年真实的每日读数，回答的却是 2026 年 10 月到
  12 月：92 个点和三个月均值，全都不存在。
* 有一道题（共享记录里的胆固醇）两次都没答出来（594 秒、375 秒）。
* 要它按 JSON 输出一个读数，它把数值写进了名字。

MiniCPM5-1B 用的是 Q8\_0，排除量化的影响，16 次里 12 次一个工具都不调，反过来问用户要哪种「view」、什么时区，所以后训练的底座选 2B。

这些都不是缺知识，是日期、算数、照工具的格式来，正是后训练擅长修的，也是 harness 可以整个
从模型手里拿走的。

<h2 id="看图">
  看图
</h2>

GLM-OCR 官方只有四种提示：`Text Recognition:`、`Table Recognition:`、`Formula Recognition:`，
以及按 JSON 模板做信息抽取。四张图（显示 128/91、心率 76 的监护仪，中文营养成分表，FDA 样例标签，
一盘菜）：

| 图 | GLM-OCR 文字提示 | GLM-OCR JSON 模板 | Qwen3.8-27B 直接看 |
| - | - | - | - |
| 监护仪 128/91、心率 76 | 128、91、76（时钟读错） | 收缩压 76、心率空：错 | 「128/91 mmHg，心率 76」 |
| 中文营养成分表 | 每一行都对；表格提示也对 | 能量写成 NRV% 那一列的「3」：错 | |
| FDA 标签 | 每一行都对 | 脂肪 8 g 写成 0 g：错 | |
| 一盘菜，没有字 | 什么也没有，这是对的 | 编出「Noodle dish」 | 给出热量区间和依据，但菜名认错 |

所以 Mirobody 只给 GLM-OCR 发它的文字和表格两种提示（`config.llm.yaml` 里 `local-ocr` 条目），
从不发 JSON 模板，也从不问开放问题；128 和 91 是血压，由读这段文字的推理模型来判断。
从餐食照片估热量，即使模型能看图，也只能给个区间。

从 1.5.4 起，agent 会问模型服务它的模型能不能看图（llama.cpp 的 `/props`、Ollama 的 `/api/show`；
`mirobody/utils/config/served.py`）。不能看图的模型收到的是照片的 OCR 文字而不是图片，并被告知
这只是印刷文字；问题问的是图里的东西时，它要说自己看不到这张图。

<h2 id="计划">
  计划
</h2>

<h3 id="先改-harness">
  先改 harness
</h3>

这些对所有模型都有用，云端的也一样，而且让小模型少出错的地方。1.5.4 做到了哪一步：

1. **算数交给工具。** 均值、月均值、差值、趋势由工具算好返回，模型只负责转述。*已具备*：`view="stats"` 返回条数、
   最小、最大、均值、第一条和最后一条读数及其日期、两者之差，`view="month"` 返回月均值；1.5.4 让 stats 的日期
   成为读数自己的本地日期，日、周、月视图也说明一天怎么计。还差的是模型去用它们：2026-10-06 的评测里，小号有两道题
   （`p002-rhr-monthly`、`p003-weight-change`）是自己拿原始行算的平均。
2. **日期交给工具。** 「过去三个月」变成一个由服务端解析的参数，模型从不自己算日期。*还没做。*
3. **图表引用工具的行。** 图表的点直接取自工具结果，不再由模型一个个手写；那 92 个假点就是手写出来的。
   *还没做*：点仍由模型写。1.5.4 只是让网页端把多了一个括号的图画出来，画不了就明说。
4. **每个数显示前都要核对。** 回答里的数如果在任何工具结果和文档里都找不到，就标出来并重新生成。
   *还没做*：评测的 `score.py` 事后数这样的数，产品本身不核对。
5. **给小模型一个更短的提示词**，按 `benchmarks/local_agent/` 里那份提示词的量法来量。agent 的提示词*还没做*。
   日记请求现在先给两个示范回答；两次评测之间，MiniCPM5-2B 的日记从 31 条写出 0 条变成最终版代码上的 22 条（第一轮改动之后是 24 条）。

1.5.4 还做了这些，不在上面的清单里（每条都在 [CHANGELOG](https://github.com/thetahealth/mirobody/blob/43892ca6d85e3df1203fca8ceb547debd7947e74/CHANGELOG.md) 里）：

* 表格按表头读、不用模型，不管配置的是什么模型；原生 PDF 的文字层、印刷页和 OCR 模型给出的表格都行；文本模型只拿到规则读剩的部分，并被告知那是一份医学报告的其余内容；
* 长报告逐页读，记录和日志带上每行自己的日期给出读数；印在两页上的同一个读数只存一次，页面的打印日期不再当作读数的日期；
* 循环输出的提取以文本长度为上限，并保留已完整的部分；
* 没指定指标就要某种视图时返回指标目录；分钟到月的视图只保留最新的 92 个点，不再撑爆上下文；
* 关键词不管单位和拼写都能找到读数（`FER`、`haemoglobin`、复数）；
* 所有 JSON schema 都是封闭的，OpenAI 的模型也能读上传的文件和日记；
* 一个模型的两个槽共用一个 KV 池，一道题可以用满整个上下文。

<h3 id="再训练">
  再训练
</h3>

这就是 1.6.0 要交付的，作为 Mirobody 自己的模型发布（[model-choice.zh-CN.md](/zh/model-choice#160-预告mirobody-自己的模型)）。
1.5.4 把 P0 做到了这里：上面的 harness 改动；评测发布在 [`benchmarks/local_models/`](https://github.com/thetahealth/mirobody/blob/43892ca6d85e3df1203fca8ceb547debd7947e74/benchmarks/local_models/README.md) 和
[`benchmarks/local_ocr/`](https://github.com/thetahealth/mirobody/blob/43892ca6d85e3df1203fca8ceb547debd7947e74/benchmarks/local_ocr/README.md)，MiniCPM5-2B 的基线已记下（2026-10-06），云端参照也一起跑了。
还没做的：200–500 道题，并分成训练、验证、测试三份。

| 阶段 | 做什么 | 算通过 |
| - | - | - |
| P0 | 上面的 harness 改动。评测从 16 次扩到 200–500 道题（多语言、多个人、各种日期窗口、照片），分成训练、验证、测试三份，测试集留真实报告；并发布到 `benchmarks/` | 记下 MiniCPM5-2B 的基线 |
| P1 | Agent：让大号 27B 模型在真实 harness 里答训练题，只留每项检查都过的运行，用它们对 MiniCPM5-2B 做 LoRA 微调 | 测试集上达到 27B 的水平：每次都通过、没有记录外的数、按报告印的范围判断 |
| P2 | 文档：用渲染出来的报告加上手机拍摄的畸变（透视、光照、模糊）微调 GLM-OCR，输出「名称、值、单位、范围、标记、日期」的行 | 在留出的真实报告上的字段准确率和漏读行数 |
| P3 | 对还没修好的问题做强化学习（GRPO），以数字核对作为奖励 | 测试集上没有记录外的数 |

已经具备的：

* **许可**：MiniCPM5-2B 是 Apache-2.0，GLM-OCR 权重是 MIT，两家都给了官方微调路径
  （MiniCPM：TRL + PEFT、LLaMA-Factory、ms-swift、unsloth；GLM-OCR：LLaMA-Factory 教程）。
* **老师模型**：大号 27B 每次都通过，并按报告印的范围判断。
* **能算出来的奖励**：回答里每个数在不在记录里，可以机械地核对，也能用来筛老师的运行。
* **零标注成本的数据**：demo 生成器能造任意多的人和时间序列；从已知数值渲染出的报告，天生带标注。

算力不大：2B 模型的 LoRA 一张 24–48 GB 的卡就够，0.9B 的 OCR 模型更少。只有训练出的模型
通过了和被替换的模型同样的检查，默认才会换。

<h3 id="暂不纳入">
  暂不纳入
</h3>

* **看懂照片。** MiniCPM5-2B 是纯文本模型，GLM-OCR 只读字。用这对小模型时，餐食照片得到的回答是
  「请描述一下吃了什么」；看懂照片仍交给能看图的模型（大号 27B，或云端 key）。小的视觉模型另立项。
* **合并成一个模型。** 两个任务要的数据和测试都不同，合并后出了回归也难定位。Mirobody 本来就把 agent
  和文档分给不同的条目，两个模型直接插上即可。等两个都各自达标，再量一量一个小视觉模型同时做两件事值不值。
* **保持更新。** 工具的格式一改就要再训练一次，所以评测每晚对训练出的模型跑一遍。


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.