mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-23 10:28:06 +00:00
feat(memory): 新增 chineseclip provider —— text+image 的小体积可商用向量空间
## 为什么 用户决定「本轮不覆盖 video,先支持 text+image」。这一刀正好解锁了此前 「小 + 可商用 + 覆盖视频」三者不可兼得的僵局:不要求视频后,唯一同时满足 **小、可商用、中文原生** 的选项是 Chinese-CLIP ViT-B/16。 实测对比(同机、真实跑出来的数字): | | Chinese-CLIP | jina-v5-omni-nano | Qwen3-VL-Emb-2B | |---|---|---|---| | 参数量 | 188M | 1.04B | 2B | | 产物 / 常驻内存 | 754MB / **1.15GB** | ~2GB / 2.23GB | 8GB / 9.4GB | | 维度 | 512 | 768 | 2048 | | 许可 | **Apache-2.0** | CC BY-NC(不可商用) | Apache-2.0 | | 视频 | 无 | 有 | 有 | 本机可用内存只有 5.3GB,Qwen 的 9.4GB 无法进程内使用;而 ORT format + mmap 那条路被证实当前不通(转换器对三段图段错误;走通还需同时升 ORT 运行时与 Go 绑定,v1.36 要求 API 29 而本机只有 28)。1.15GB 则可以直接进程内跑。 **代价已写进包注释与文档**:CLIP 是双塔对比学习,text↔image 是强项,但纯文本 语义明显弱于 MLLM 型嵌入器;文本检索仍由既有词向量/TF-IDF 路径兜底。 需要更强文本语义或视频时切回 qwen3vl。 ## 内容 - `providers/chineseclip/`:按公共 SPI 实现的 provider(注册名 `chineseclip`), 含 BERT WordPiece 分词器、图像预处理、ONNX 双塔推理、无标签 stub。 - `scripts/export_chineseclip_onnx.py`:从官方权重导出规范产物 + 冻结参考, 自带逐用例 PyTorch 对比与覆盖度断言(计划集合≠执行集合即非零退出)。 - `cmd/homed/main.go`:空白导入两个 provider,由配置选其一。 - `go.mod`:`golang.org/x/text` 由间接依赖转为直接依赖(删音标需要 NFD)。 ## 实现要点 - **分词器逐 token 对齐官方**。第一版探针自己拼 BertTokenizer(只给 vocab.txt、 没删音标、中文没逐字切),中文被整体切成 [UNK],三个不同句子产出几乎相同的 向量(余弦 0.98)——差点把「模型坏了」当成结论。官方配置是 do_lower_case=true + 删音标生效 + 中文逐字切分;`TestTokenizerMatchesOfficialReference` 钉住 逐 token 一致。 - **图像缩放自写 bicubic**(复刻 PIL 的 precompute_coeffs + a=-0.5 核),不引 golang.org/x/image:它未进本机模块缓存,且最新版要求把整个工具链升到 Go 1.26, 为一个缩放函数动工具链不划算。 - **归一化在 provider 侧**(两个塔的图里都没归一化),检索按余弦。 - **指纹覆盖全部影响语义的产物**:两个 ONNX 图 + vocab.txt + embed_config.json, 读不到就写 MISSING(跳过等于对缺件不敏感)。 - 会话 Run 用 runMu 串行化(ORT 会话不保证并发安全),创建/销毁用 mu。 ## 模态范围 只声明 `text` 与 `image`;`audio`/`video` 明确返回 `ErrUnsupportedModality`, 绝不用别的模型向量冒充(这是「音频明确 unsupported」纪律的落地)。 ## 验证(实测) 导出侧:10 个用例(5 文本 + 5 图像)ONNX vs 官方 PyTorch 全部 `cos = 1.000000000`,覆盖度断言 10/10 通过。 Go 侧(`CHINESECLIP_MODEL_DIR=... go test -tags onnxruntime ./providers/chineseclip/ -v`): 11/11 通过,其中 - 文本 5 用例 `cos = 1.000000000000`(逐位一致) - 图像 4 纯色用例 `cos = 1.000000`(与官方预处理在 6 位小数内一致) - 跨模态判别:红图对「红色」文本高于「蓝色」文本 - 模态拒绝 / 空输入 / 指纹稳定 / 产物缺失报错 顺带修掉测试自身的一个假通过:参考向量是**未归一化**的原始输出(模长 10~36), 原先「点积当余弦 + 单侧下界」会让 13.6 也判过,已改为真余弦 + 双侧容差。 构建矩阵:`go build/vet ./...` 与 `-tags onnxruntime` 两种都过; `providers/... pkg/... internal/config/... internal/memory/vector/...` 回归通过 (qwen3vl 的 TestVideoModelInputMRope 需要 QWEN_ONNX_MODEL_DIR 指向含视频档的 v3 目录,缺该环境变量时用的是只有文本+图像的目录,与本改动无关)。 ## 未做(明确记录) - 发行版默认 provider 与构建标签变更:留下一提交(涉及打包与模型分发策略)。 - 模型产物(754MB)不进仓库,由导出脚本生成。
This commit is contained in:
@ -1,6 +1,18 @@
|
||||
# 统一多模态向量空间(Qwen3-VL-Embedding-2B)
|
||||
# 统一多模态向量空间
|
||||
|
||||
文本、图像、**视频帧** 在同一模型、同一 2048 维、同一 fingerprint 空间里被编码。
|
||||
核心不绑定任何具体模型:它按 provider 名从公共注册表(`pkg/embedding`)打开一个
|
||||
向量空间。仓库内自带两个:
|
||||
|
||||
| provider | 模态 | 维度 | 实测常驻 | 许可 | 适用 |
|
||||
|---|---|---|---|---|---|
|
||||
| `chineseclip` | text + image | 512 | **1.15 GB** | Apache-2.0 | 默认(内存受限 / 中文图文) |
|
||||
| `qwen3vl` | text + image(视频已实现未纳入契约) | 2048 | 9.4 GB | Apache-2.0 | 内存充足 / 需要更强文本语义或视频 |
|
||||
| `http` | 由外部服务决定 | 由外部服务决定 | 由外部服务决定 | — | 侧车部署(如 jina-v5-omni-nano,注意其 CC BY-NC 许可) |
|
||||
|
||||
下面第一节是 Qwen3-VL(2048 维,最强但最重),第二节是 Chinese-CLIP(512 维,
|
||||
默认推荐)。两者互斥启用,改配置后重启生效。
|
||||
|
||||
文本、图像、**视频帧** 在同一模型、同一维度、同一 fingerprint 空间里被编码。
|
||||
记忆系统用它做三件事:多模态图记忆的跨模态召回、multimodal doc 的向量融合、
|
||||
multimodal context 的相关性裁剪/淘汰。
|
||||
|
||||
@ -76,6 +88,94 @@ axis,实际却只能用导出的那个长度运行。
|
||||
|
||||
「能加载」不等于「算得对」:形状错、输入名错、池化位置错的图都能正常 load。
|
||||
|
||||
## 一·补、text+image 默认空间:Chinese-CLIP ViT-B/16
|
||||
|
||||
**为什么它是默认**:text+image 只需要一个向量空间时,同时满足「小、可商用、中文原生」
|
||||
的选项只有一个。
|
||||
|
||||
| | Chinese-CLIP | jina-v5-omni-nano | Qwen3-VL-Emb-2B |
|
||||
|---|---|---|---|
|
||||
| 参数量 | 188M | 1.04B | 2B |
|
||||
| 产物 / 实测常驻 | **721MB / 1.15GB** | ~2GB / 2.23GB | 8GB / 9.4GB |
|
||||
| 维度 | 512 | 768 | 2048 |
|
||||
| 许可 | **Apache-2.0** | CC BY-NC(不可商用) | Apache-2.0 |
|
||||
| 中文 | 原生(~2 亿中文图文对) | 多语言 | 多语言 |
|
||||
| 文本语义 | 弱(双塔对比) | 好 | 最好 |
|
||||
| 视频 | 无 | 有 | 有 |
|
||||
|
||||
**要诚实记录的代价**:CLIP 是双塔对比学习,text↔image 是强项,但**纯文本语义
|
||||
(text↔text)明显弱于 MLLM 型嵌入器**。文本检索仍由既有词向量/TF-IDF 路径兜底,
|
||||
本空间主要用于跨模态召回与相关性裁剪。需要更强文本语义或视频时切回 `qwen3vl`。
|
||||
|
||||
### 产物与获取
|
||||
|
||||
产物约 754MB,**不进仓库**;用导出脚本从官方权重导出(脚本入库,保证可复现):
|
||||
|
||||
```bash
|
||||
python3 scripts/export_chineseclip_onnx.py \
|
||||
--model-dir /path/to/chinese-clip-vit-base-patch16 \
|
||||
--out /home/newqqagent/models/chinese-clip-vit-b16-onnx
|
||||
```
|
||||
|
||||
国内下载:本机 `huggingface.co` 走代理会被 reset,用 `hf-mirror.com` 且**不设代理**:
|
||||
|
||||
```bash
|
||||
curl -4 -L --retry 3 -o vocab.txt \
|
||||
https://hf-mirror.com/OFA-Sys/chinese-clip-vit-base-patch16/resolve/main/vocab.txt
|
||||
```
|
||||
|
||||
### 产物契约(Go 侧按此读取)
|
||||
|
||||
| 文件 | 输入 | 输出 |
|
||||
|---|---|---|
|
||||
| `TextEncoder.onnx` | `input_ids` int64 `[B,52]`、`attention_mask` int64 `[B,52]` | `text_features` float `[B,512]` |
|
||||
| `VisionEncoder.onnx` | `pixel_values` float `[B,3,224,224]` | `image_features` float `[B,512]` |
|
||||
|
||||
外加 `embed_config.json`(维度/预处理/分词超参/文件名——provider 的唯一权威)、
|
||||
`vocab.txt`、`reference.json`(冻结参考:逐文本 token id + 逐样本向量)、`SHA256SUMS`。
|
||||
|
||||
图像预处理:缩放到 224×224(双三次,复刻 PIL 系数)→ `(x/255 - mean) / std`,
|
||||
不裁剪。文本:BERT WordPiece,`max_length=52`,补 `[PAD]`,超长截断尾部。
|
||||
两个塔的输出**都没有在图中归一化**,归一化由 provider 负责(检索按余弦)。
|
||||
|
||||
### 启用
|
||||
|
||||
```bash
|
||||
core.memory.multimodal_space.provider = chineseclip
|
||||
core.memory.multimodal_space.options.model_dir = /home/newqqagent/models/chinese-clip-vit-b16-onnx
|
||||
```
|
||||
|
||||
同样要求 `homed` 带 `onnxruntime` build tag。
|
||||
|
||||
### 模态范围
|
||||
|
||||
只声明 `text` 与 `image`。`audio`/`video` **明确返回 `ErrUnsupportedModality`**——
|
||||
本空间没有它们的原生编码器,用别的模型向量冒充会污染整个向量空间
|
||||
(这正是「音频明确 unsupported」那条纪律的落地)。
|
||||
|
||||
### 验证
|
||||
|
||||
Go 侧回归对着官方 PyTorch 参考(`reference.json`),模型目录由
|
||||
`CHINESECLIP_MODEL_DIR` 指定,缺失时 skip:
|
||||
|
||||
```bash
|
||||
CHINESECLIP_MODEL_DIR=/home/newqqagent/models/chinese-clip-vit-b16-onnx \
|
||||
go test -tags onnxruntime ./providers/chineseclip/ -v
|
||||
```
|
||||
|
||||
实测结果:文本 5 个用例 `cos = 1.000000000000`(与官方逐位一致);
|
||||
图像 4 个纯色用例 `cos = 1.000000`(自写 bicubic 与 PIL 在 6 位小数内一致);
|
||||
另有跨模态判别、模态拒绝、指纹稳定性、产物缺失报错等用例。
|
||||
|
||||
### 两个已踩过的坑(都在测试里钉住了)
|
||||
|
||||
1. **分词器不能自己拼**。第一版探针用 `BertTokenizer(vocab_file=..., do_lower_case=True)`
|
||||
手工分词,中文被整体切成 `[UNK]`,三个不同句子产出几乎相同的向量(余弦 0.98),
|
||||
差点把「模型坏了」当成结论。官方配置是 `do_lower_case=true` + **删音标生效** +
|
||||
**中文逐字切分**;Go 侧实现必须与官方**逐 token** 对齐(`TestTokenizerMatchesOfficialReference`)。
|
||||
2. **参考向量是未归一化的原始输出**(模长 10~36)。用「点积当余弦 + 单侧下界」判定
|
||||
会得到 13.6 而「通过」——测试里因此改成真余弦 + 双侧容差。
|
||||
|
||||
## 二、启用
|
||||
|
||||
核心不识别任何具体模型:它只按配置里的 **provider 名**从公共注册表
|
||||
|
||||
Reference in New Issue
Block a user