From 3374e7dbd93d681c7a7e874393b931e2b338e5f7 Mon Sep 17 00:00:00 2001 From: JianFeeeee Date: Sun, 20 Sep 2026 19:56:49 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E6=9B=B4=E6=AD=A3=E4=B8=89=E5=A4=84?= =?UTF-8?q?=E6=97=A0=E5=AE=9E=E6=B5=8B=E6=94=AF=E6=92=91=E7=9A=84=E6=80=A7?= =?UTF-8?q?=E8=83=BD=E6=96=AD=E8=A8=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 用户指出现有文档里的性能数字可疑。逐个实测后发现三类问题,都不加改原文地 标注更正(历史条目保留原文,仓内已有此惯例)。 ## ① 「崩溃到恢复 <1s」——从未成立 写于 v1.0.0 发版说明。但**当时的退避代码就已是 1s**(查 v1.0.0 tag 的 `procRestartBackoff = time.Second`),首次重启就要等 1s。 实测(新增临时测试测量 scheduleProcRestart 延迟): 第 1 次崩溃 → 1s 第 2 次 → 2.001s 第 3 次 → 3.002s 顺带纠正我自己刚在站点写错的阈值:并非「崩 3 次停下」。实测第 **4** 次 才停(`procMaxRestarts=3`,判定为 `n > 3`),前 3 次都会重启。 ## ② 「RPC 往返 p50 24.1µs」——量级对、数字不符 实测 `BenchmarkToolInvoke`:inline/small **30.4µs**、frame/small 51.5µs、 inline/large 767µs、frame/large 398µs。原文与实测同为几十微秒量级, 但具体值对不上,且未注明测的是哪种 payload。 ## ③ 「CLIP 实测常驻 1.15GB」——采样点不对(6 处) 实测加载 chineseclip 两塔,RSS 会**自己降下来**: 加载前 0.00 GB 两塔加载后 1.59 GB ← 峰值 GC + 静置 0.89 GB ← 稳态(内核回收未用页) 1.15GB 落在两者之间,既不代表峰值也不代表稳态。线上稳态实测 0.39~0.58GB (更长时间静置后更低)。同源问题:qwen3vl 的「常驻 9.4GB」实为**峰值**, 其视觉塔本就是按需加载(源码注释:每张图约 1.6GB,故按需)。 6 处全部改为「稳态 X(峰值 Y)」双值,消除口径歧义:README 中英、 docs/zh/multimodal-space.md、config/registry.go(2 处 + 1 处注释)、 providers/chineseclip/tokenizer.go。 ## 验证 - `go build`(含 `-tags onnxruntime` 与不带)与 `go vet` 均通过 - 全仓 `grep 1.15GB` 已清零 - 测量用的临时测试文件已删除,无残留 --- README.md | 10 +++++++++- README_EN.md | 13 ++++++++++++- docs/zh/multimodal-space.md | 2 +- internal/config/registry.go | 5 +++-- providers/chineseclip/tokenizer.go | 2 +- site/index.html | 2 +- 6 files changed, 27 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index b9f35ac..b58bc07 100644 --- a/README.md +++ b/README.md @@ -235,7 +235,7 @@ internal/ - **模型中立的统一向量空间**:内核不再适配任何具体模型,只提供公共 provider SPI (`pkg/embedding`:`Modality` / `Input{Data,MIME}` / `Info{Dimension,Fingerprint,Modalities}` + 名字注册表),实现在 `providers/*`。默认 **Chinese-CLIP ViT-B/16** —— text 与 image - 落在**同一空间**(512 维、指纹 `cd2a495cf990`、Apache-2.0、独立实测常驻约 1.15GB); + 落在**同一空间**(512 维、指纹 `cd2a495cf990`、Apache-2.0;实测加载峰值 1.59GB、静置回收后稳态约 0.89GB); `qwen3vl` 保留(2048 维、常驻约 9.4GB,供内存充足或将来要视频的机器切回)。 文本检索仍由既有词向量 / TF-IDF 兜底:CLIP 双塔的**纯文本语义弱于 MLLM 型嵌入器**, 这是已知并写进文档的代价。 @@ -256,6 +256,14 @@ internal/ > 以下历史条目保留原文以呈现演进,其中两条机制**已在 v1.2.0 移除**: > 「媒体以 `[ <短digest>] <描述>` 标记参与检索」(描述式索引)与「媒体引用计数式 GC」。 +> +> 另有**两项性能断言的量纲需要更正**(2026-09-20 实测): +> 「崩溃到恢复 <1s」不成立 —— 崩溃后是**线性退避重启**,即 1s / 2s / 3s(`procRestartBackoff=1s × 第 n 次`), +> 首次重启就要等 1s。且 5 分钟窗口内第 **4** 次崩溃即停止自动重启待人工介入(`procMaxRestarts=3`,判定为 `n > 3`)。 +> 该断言写下时(v1.0.0)退避值已是 1s,故从未成立。 +> 「RPC 往返 p50 24.1µs」与当前实测同量级但不吻合:本机 `BenchmarkToolInvoke` 实测 +> inline/small **30.4µs**、frame/small 51.5µs、inline/large 767µs、frame/large 398µs。 +> 保留原文不修改,以免伪造历史。 **v1.1.1** — 多模态贯通**插件边界**。v1.1.0 让记忆系统支持了二进制多媒体节点,但那条链路只对内核自己开放;本版打通到插件与模型。公开 SDK 新增媒体字段与三个媒体注入接口(配套 [SDK v1.1.0](https://gitcode.com/JianFeeeee/homeagent-sdk/releases/tag/v1.1.0),整条 1.1.x 线共用),内核实现对应四个 RPC。桥接层此前在**静默裁字段**:插件交进来的 `Confidence`/类型/`SentenceText` 全被丢弃、`Doc` 只留三个字段、`Remove` 不解引用(媒体永久算「被引用」,GC 收不掉)。`processTextInput`/`processMediaInput` 归一成一条 `processInput`,媒体路径由此获得它一直缺的去重、`no_memory`、通道 `Cleaner`、中断语义、`EventRawInput`。修掉三处真实缺陷:**用户发的图从来没出现在 WebUI 聊天记录里**(媒体路径发布 map 而订阅方断言 string)、**`memory_commit` 的 `sentence_text` 从未暴露给模型**(而它是媒体绑定链的必经环节)、**`PluginSDK` 两处并发竞态**(`-race` 实测 11 处,插件重载瞬间偶发 nil 解引用崩溃)。 diff --git a/README_EN.md b/README_EN.md index 3ca84fe..fae7216 100644 --- a/README_EN.md +++ b/README_EN.md @@ -236,7 +236,7 @@ External plugin development: see [homeagent-sdk](https://gitcode.com/JianFeeeee/ It exposes only a public provider SPI (`pkg/embedding`: `Modality` / `Input{Data,MIME}` / `Info{Dimension,Fingerprint,Modalities}` + a name registry), with implementations under `providers/*`. Default: **Chinese-CLIP ViT-B/16** — text and image land in the **same space** - (512-dim, fingerprint `cd2a495cf990`, Apache-2.0, ~1.15GB RSS measured standalone); + (512-dim, fingerprint `cd2a495cf990`, Apache-2.0; measured ~1.59GB peak on load, settling to ~0.89GB steady-state); `qwen3vl` is kept (2048-dim, ~9.4GB) for machines with headroom or future video. Text search still falls back to the existing word-vector / TF-IDF path — a CLIP dual tower's pure-text semantics are **weaker than an MLLM-style embedder**, a cost documented rather than hidden. @@ -263,6 +263,17 @@ External plugin development: see [homeagent-sdk](https://gitcode.com/JianFeeeee/ > The historical entries below are kept verbatim to show the evolution; two mechanisms in them > were **removed in v1.2.0**: text-description-based media indexing, and reference-counted media GC. +> +> **Two performance claims also need correcting** (measured 2026-09-20): +> "crash-to-recovery under 1s" does not hold — restarts are **linearly backed off**, i.e. +> 1s / 2s / 3s (`procRestartBackoff=1s × nth crash`). Even the *first* restart waits 1s. +> And the **4th** crash within a 5-minute window stops automatic restarts pending human +> intervention (`procMaxRestarts=3`, tested as `n > 3`). +> The backoff was already 1s when this claim was written (v1.0.0), so it never held. +> "RPC round-trip p50 24.1µs" is the right order of magnitude but does not match current +> measurements: `BenchmarkToolInvoke` on this machine gives inline/small **30.4µs**, +> frame/small 51.5µs, inline/large 767µs, frame/large 398µs. +> The original text is left unedited rather than rewritten, so the history isn't falsified. **v1.1.1** — Multimodal reaches the **plugin boundary**. v1.1.0 gave the memory system binary multimedia nodes, but that path was open only to the kernel itself; this release opens it to diff --git a/docs/zh/multimodal-space.md b/docs/zh/multimodal-space.md index ac7779d..260c3f4 100644 --- a/docs/zh/multimodal-space.md +++ b/docs/zh/multimodal-space.md @@ -96,7 +96,7 @@ axis,实际却只能用导出的那个长度运行。 | | Chinese-CLIP | jina-v5-omni-nano | Qwen3-VL-Emb-2B | |---|---|---|---| | 参数量 | 188M | 1.04B | 2B | -| 产物 / 实测常驻 | **721MB / 1.15GB** | ~2GB / 2.23GB | 8GB / 9.4GB | +| 产物 / 实测内存 | **721MB / 稳态 0.89GB(峰值 1.59GB)** | ~2GB / 2.23GB | 8GB / 峰值 9.4GB | | 维度 | 512 | 768 | 2048 | | 许可 | **Apache-2.0** | CC BY-NC(不可商用) | Apache-2.0 | | 中文 | 原生(~2 亿中文图文对) | 多语言 | 多语言 | diff --git a/internal/config/registry.go b/internal/config/registry.go index 7fd56db..8266f4b 100644 --- a/internal/config/registry.go +++ b/internal/config/registry.go @@ -723,7 +723,8 @@ func (r *ConfigRegistry) seedDBValues(dataDir string) { set("core.memory.documents", filepath.Join(dataDir, "memory", "documents")) set("core.memory.media.dir", filepath.Join(dataDir, "memory", "media")) // 发行版默认启用本地向量空间。用 chineseclip(text+image、512 维、实测 - // 常驻 1.15GB、Apache-2.0)而不是 qwen3vl(9.4GB):多数机器装不下后者。 + // 稳态约 0.89GB、Apache-2.0)而不是 qwen3vl(视觉塔按需加载,峰值约 9.4GB): + // 多数机器装不下后者。 // 产物不在仓库里,用 scripts/export_chineseclip_onnx.py 生成到这个路径; // 产物缺失时 homed 会打印明确错误并退回 fastText 文本路径(不静默假装启用)。 set("core.memory.multimodal_space.provider", "chineseclip") @@ -830,7 +831,7 @@ func (r *ConfigRegistry) seedCoreDefs(dataDir string) { reg(ConfigDef{Key: "core.memory.documents", Default: filepath.Join(dataDir, "memory", "documents"), Type: "string", DisplayName: "文档记忆路径", Description: "文档记忆存储目录", Category: "paths"}) reg(ConfigDef{Key: "core.memory.media.enabled", Default: "true", Type: "bool", DisplayName: "媒体记忆", Description: "把对话里出现的图片/音频变成一等记忆块,内容按 sha256 落盘去重。关闭后媒体仅在当前对话内可见,下一轮起只剩路径或 alt 文本", Category: "memory"}) reg(ConfigDef{Key: "core.memory.media.dir", Default: filepath.Join(dataDir, "memory", "media"), Type: "string", DisplayName: "媒体存储路径", Description: "媒体内容寻址存储目录(内含 media.db 与 blobs/)", Category: "paths"}) - reg(ConfigDef{Key: "core.memory.multimodal_space.provider", Default: "chineseclip", Type: "string", DisplayName: "多模态向量 provider", Description: "从公共 provider 注册表(pkg/embedding)按名字打开的多模态向量空间。内置:chineseclip(默认,text+image,512 维,实测常驻 1.15GB,Apache-2.0)、qwen3vl(text+image,2048 维,常驻 9.4GB;视频已实现但未纳入契约)、http(外部向量 API)。也可是第三方注册的名字。两者均需 onnxruntime 构建标签。留空禁用多模态向量检索,只保留 fastText 文本路径。provider 的模型文件、预处理与运行时全在 provider 内部,核心不做任何模型假设。修改后需重启生效。", Category: "memory"}) + reg(ConfigDef{Key: "core.memory.multimodal_space.provider", Default: "chineseclip", Type: "string", DisplayName: "多模态向量 provider", Description: "从公共 provider 注册表(pkg/embedding)按名字打开的多模态向量空间。内置:chineseclip(默认,text+image,512 维,实测稳态约 0.89GB(加载峰值 1.59GB),Apache-2.0)、qwen3vl(text+image,2048 维,峰值约 9.4GB,视觉塔按需加载;视频已实现但未纳入契约)、http(外部向量 API)。也可是第三方注册的名字。两者均需 onnxruntime 构建标签。留空禁用多模态向量检索,只保留 fastText 文本路径。provider 的模型文件、预处理与运行时全在 provider 内部,核心不做任何模型假设。修改后需重启生效。", Category: "memory"}) reg(ConfigDef{Key: "core.memory.multimodal_space.options.model_dir", Default: filepath.Join(dataDir, "models", "chinese-clip-vit-b16-onnx"), Type: "string", DisplayName: "provider 模型目录", Description: "provider 自定义选项(以 options. 开头的键会去掉前缀后原样传给 provider,核心不解释其含义)。对内置 chineseclip:Chinese-CLIP 产物目录(用 scripts/export_chineseclip_onnx.py 生成)。对内置 qwen3vl:Qwen3-VL ONNX 产物目录(用 scripts/export_qwen3vl_embedding_onnx.py 生成)。", Category: "memory"}) reg(ConfigDef{Key: "core.memory.multimodal_space.options.endpoint", Default: "", Type: "string", DisplayName: "provider 服务端点", Description: "provider 自定义选项。对内置 http:外部多模态向量服务的端点 URL(POST,接受 modality/side/text/data/mime,返回 embedding)。", Category: "memory"}) reg(ConfigDef{Key: "core.memory.multimodal_space.options.api_key", Default: "", Type: "password", DisplayName: "provider 服务密钥", Description: "provider 自定义选项。对内置 http:作为 Bearer token 发送。可选。", Category: "memory"}) diff --git a/providers/chineseclip/tokenizer.go b/providers/chineseclip/tokenizer.go index 1d48211..4894a2e 100644 --- a/providers/chineseclip/tokenizer.go +++ b/providers/chineseclip/tokenizer.go @@ -1,7 +1,7 @@ // Package chineseclip 提供 Chinese-CLIP ViT-B/16 的 text+image 向量空间 provider。 // // 为什么是它(而不是 Qwen3-VL-Embedding-2B / jina-v5-omni-nano): -// - 体积:721MB ONNX、实测常驻 1.15GB;Qwen 2B 需要 9.4GB,本机可用内存只有 5.3GB。 +// - 体积:721MB ONNX、实测稳态约 0.89GB(加载峰值 1.59GB);Qwen 2B 峰值约 9.4GB,本机可用内存只有 5.3GB。 // - 许可:Apache-2.0,可随发行版分发;jina-v5-omni-nano 是 CC BY-NC(不可商用)。 // - 中文:原生在 ~2 亿中文图文对上训练。 // diff --git a/site/index.html b/site/index.html index 2c81e11..2ed40ac 100644 --- a/site/index.html +++ b/site/index.html @@ -1724,7 +1724,7 @@ 隔离

崩一个,不赔上全部

插件不在内核里,是另一个进程。崩了就把它注册的东西一并摘掉,其余照跑。

- 退避重启 1s / 2s / 3s;5 分钟内崩 3 次则停下等人 + 退避重启 1s / 2s / 3s;5 分钟内第 4 次崩溃即停下等人