diff --git a/CHECKLIST.md b/CHECKLIST.md index 6ddecda..cffc31f 100644 --- a/CHECKLIST.md +++ b/CHECKLIST.md @@ -347,22 +347,22 @@ - 偏差:0.1.9 的 vec0 **没有 ANN,逐条扫**,所以延迟与字节随规模线性——DENSE.md 把这条假设印在外推表旁边,不假装是对数。 - [x] p2-1.2 量化与截断:测了,没上 - `python -m bench.quant_probe` → `bench/reports/QUANT.md`(165,973 向量、200 探针、top 10,tie-aware 一致度;下面每个 p50 逐字抄自那张表):sqlite-vec float **41.2ms**/1.0、+holder 过滤 29.8/0.599、int8 35.5/**0.977**、int8+holder 22.3/0.595、bit **2.6ms**/0.728、bit+holder 4.1/0.513、numpy float32 全扫(真值)8.0/1.0、numpy 符号位 hamming 18.9/0.011、float32 前 192/128/64 维 7.5/0.722、7.9/0.57、4.0/0.43、**bit 粗排@200 → numpy 里 float32 精排 23.7/0.983**。每向量字节 f32 1536 / int8 384 / bit 48。 - - 结论:int8 省 4× 空间几乎不掉质量(0.977)但不省时间(35.5 vs 41.2);bit 省 32× 空间、省 **15.8×** 时间(41.2→2.6),只剩 0.728,只能当粗排;两级方案必须在 vec0 **外面**精排(在库里用 `rowid IN (…)` 回捞反而更慢)。维度截断是在**不是 MRL 训练**的 MiniLM 上测的,所以那是真实代价不是白拿:前 192 维 7.5ms、前 128 维 7.9ms,对全维 8.0ms 只快 1–6%,前 64 维才快一半但一致度掉到 0.43;切片必须是存下来的连续维,不是查询时切(非连续视图点集会掉速度)。 + - 结论:int8 省 4× 空间几乎不掉质量(0.977)但不省时间(35.5 vs 41.2);bit 省 32× 空间、省 **15.8×** 时间(41.2→2.6),只剩 0.728,只能当粗排;两级方案在 vec0 **外面**精排(QUANT.md 那行 23.7ms/0.983 就是这么测的;「在引擎里回捞候选再精排会更慢」这句我没测过,所以不写)。维度截断是在**不是 MRL 训练**的 MiniLM 上测的,所以那是真实代价不是白拿:前 192 维 7.5ms、前 128 维 7.9ms,对全维 8.0ms 只快 1–6%,前 64 维才快一半但一致度掉到 0.43。那三行是**查询时切片**测的(`bench/quant_probe.py:242` 用 `vectors[:, :dims]`,非连续视图),存成连续维只会更快、但没测过,所以不写。 - 偏差:精排腿(`bge-reranker-base`)早在基线消融里就有并测过(加了它 recall@10 从 0.565 掉到 0.453),所以这里没再为它编一套故事;粗排+精排这条链测出 0.983 一致度但**没接线**,因为 dense 腿默认关(见下)。 - - [x] p2-1.3 100k 实测 + 1M 外推:`python -m bench.dense_scale` → `bench/reports/DENSE.md`(stamp dff5977) + - [x] p2-1.3 100k 实测 + 1M 外推:`python -m bench.dense_scale` → `bench/reports/DENSE.md`(stamp 71733d86d) - 实测 **165,973 atom / 153,857 向量**(DENSE.md,stamp 71733d86d):建库 1444.9s(106 向量/秒)、**检索 p50 73.0ms / p95 75.7ms / max 85.6ms(300 查询)**、其中编码问题 p50 11.3ms / p95 24.0ms、与暴力扫描 tie-aware 一致度 **0.998**(40 探针)、**往满索引里写一条记忆 p50 508ms / p95 535ms(50 次写)**、失配扫描 1.3s(不在检索路径上)、**每向量 1,729 字节**(dbstat 数 vec0 影子表:266,059,776 B)、机器 load [3.53, 4.63, 4.85]。 - 1M 外推(**分母是向量数**,1,000,000 / 153,857,不是 atom 数——两者差 7.9%,所以报告把分母印在外推表旁边):p50 475ms / p95 492ms、向量表 1.6 GiB、首次建库 156.5 分钟、单条写 3303ms(vec0 按 id 删除也要扫表,所以写同样随规模线性),假设随行印出(逐条扫 ⇒ 线性)。 - tie-aware 的必要性:语料里有重复 atom,分数并列 ⇒ top-K 的 id 集合本身有歧义,两个都对引擎按 id 比会读出 0.827 的假召回。判定改成「返回的 atom 真实 cosine ≥ 第 K 名 cosine」。 - 验收:100k dense 腿 p50 <100ms → **165,973 atom(bar 的 1.66 倍)p50 73.0ms**(DENSE.md);新 atom 立即可查 → `pytest backend/tests/test_vector_index.py`(写进去当场能搜到、视图藏起来的 dense 腿也藏、备份带走向量恢复后照样应答)。 - [x] p2-1.4 每条腿值多少钱:`python -m bench.leg_check` → `bench/reports/LEGS.md`(stamp 见报告首行,300 条 holder 作用域探针,7 个配置,每臂带 n / MRR / Wilson 下界 / p50 / p95 / 分模态召回)。测的是 `bench/runs/dense_scale` 的副本:冻结基线自己**不带向量**,dense 腿在其上只会排一个空索引,所以报告首行就把来源 store 与向量数印出来。 - 只有词的 FTS 页:问 10 个 atom recall@10 **0.832**、MRR 0.683、p50 67.5ms;问产品配置的 24 个 **0.848** / 0.688 / 67.6ms;页放宽到 30 **0.855** / 0.690 / 72.0ms(放宽不要钱,10→24 就白捡 1.6 个点)。 - - 开 dense 腿:10 个 atom **0.845** / MRR 0.663 / p50 474.8ms;页 30 **0.858** / 0.669 / 416.0ms。多 1.3–2.6 个点召回、MRR 降、慢 6–7 倍。 - - 开图片腿。索引实物印在 LEGS.md 里(不是这里口头说的):**53,668 个 atom、512 维、109,912,192 字节**;编码速度也是实测的(200 张样本):**16.8–19.9 张/秒**,据此那 53,668 张 ≈ **53.3–45.0 分钟**——和我当时手工记的 2901.7 秒(48.4 分钟)落在同一个带里。结果:10 个 atom **0.815** / MRR 0.553 / 138.1ms;页 30 **0.825** / 0.551 / 166.7ms。分模态才看得见它在干什么:**video 0.583→0.708、pdf 0.849→0.907 涨,code 0.953→0.898、ui_screenshot 0.583→0.500 跌**——它救的正是金标是图的题,代价是稀释别的。 - - p50 是负载函数,不是性常数:两份已提交的 LEGS.md 里,words 腿 64.7–72.5ms、dense 腿 413.7–474.8ms、图片腿 132.5–166.7ms,**召回三跑到小数点后三位一模一样**(0.832 / 0.848 / 0.855 / 0.845 / 0.858 / 0.815 / 0.825)。所以比较只用召回,延迟看报告当次的 load。 + - 开 dense 腿:10 个 atom **0.845** / MRR 0.663 / p50 474.8ms;页 30 **0.858** / 0.669 / 416.0ms。同页比只多 **1.3** 个点(页 10:0.832→0.845)和 **0.3** 个点(页 30:0.855→0.858),MRR 降,慢 6–7 倍。 + - 开图片腿。索引实物印在 LEGS.md 里(不是这里口头说的):**53,668 个 atom、512 维、109,912,192 字节**;编码速度也是实测的(200 张样本):**16.8 张/秒**,据此那 53,668 张 ≈ **53.3 分钟**(报告里就这一个样本);我当时手工计时是 2901.7 秒(48.4 分钟),那个数只在我终端里、没留档,所以只当旁证不当证据。结果:10 个 atom **0.815** / MRR 0.553 / 138.1ms;页 30 **0.825** / 0.551 / 166.7ms。分模态才看得见它在干什么:**video 0.583→0.708、pdf 0.849→0.907 涨,code 0.953→0.898、ui_screenshot 0.583→0.500 跌**——它救的正是金标是图的题,代价是稀释别的。 + - p50 是负载函数,不是性常数:两份已提交的 LEGS.md(md5 `b9ea03ec` 在 `c4a8c36`/`6cc5640`/`71733d8`,`171a33d0` 在 `e80883c`)里,words 腿 64.7–72.5ms、dense 腿 413.7–474.8ms、图片腿 132.5–166.7ms,而**召回逐格一模一样**(0.832 / 0.848 / 0.855 / 0.845 / 0.858 / 0.815 / 0.825)。所以比较只用召回,延迟看报告当次的 load。 - 产品的图片腿不是开关:`retrieval.py:136` `if image is None and not thin: return {}`,稀疏页薄到 2 条以下才问图。所以这几行测的是「腿可用」,不是强行常开融合。 - - **没达到的验收线,写在这里**:用户定的验收是 recall@10 ≥0.90(n≥300)。测出来最好的一行是 **0.858**(dense 腿开、页 30、p50 416.0ms),默认配置 **0.848**(p50 67.6ms),CURRENT.md 里按 10 个 atom 问是 0.832;仓内 Mem0/Zep 式对照在 holder 作用域是 **0.866**。差距集中在图(image 0.750、ui_screenshot 0.500–0.583、video 0.583–0.708),文字的题(code 0.953、pdf 0.919)已经过了线。诚实结论:**延迟达标(281 倍于基线、p50 71.7ms),召回没达标**,剩下 4 个点是跨模态召回,不是索引规模问题。 + - **没达到的验收线,写在这里**:用户定的验收是 recall@10 ≥0.90(n≥300)。测出来最好的一行是 **0.858**(dense 腿开、页 30、p50 416.0ms),默认配置 **0.848**(p50 67.6ms),CURRENT.md 里按 10 个 atom 问是 0.832;仓内 Mem0/Zep 式对照在 holder 作用域是 **0.866**。差距集中在图(image 0.750、ui_screenshot 0.500–0.639、video 0.583–0.708),文字的题(code 0.953、pdf 0.919)已经过了线。诚实结论:**延迟达标(281 倍于基线、p50 71.7ms),召回没达标**,剩下 4 个点是跨模态召回,不是索引规模问题。 - 数字逼出来的决定:**dense 腿默认关**(`config.py:32 dense_embeddings_enabled=False`)。同一 300 条 holder 作用域探针上(LEGS.md),开腿 recall@10 0.832→0.845、MRR 0.683→0.663、p50 67.5ms→474.8ms。多 1.3 个点的召回、慢 7 倍、首位质量还降——默认值就该是关,腿留着(已增量、已入库)等更大的语料或更好的嵌入模型。 - - 门禁:`pytest -q` 全绿(**287 passed**);`ruff check` 干净;mypy 冷缓存 **24 errors / 9 files**(命令 `.venv/bin/python -m mypy --cache-dir <新目录> backend/src bench`),与 p1-6 基线(`9cb46c1` 同口径 24/9)**净新增 0**——p2-1 一度引入 +2(`vector_index.py` 与 `quant_probe.py` 的 `import sqlite_vec` 没有 stub),根因修法是在 `pyproject.toml` 的 `[[tool.mypy.overrides]]` 里声明 `sqlite_vec` 无 stub,而不是在代码里加 `# type: ignore`;修在 **`71733d8`**,所以 `6cc5640` 那棵树上量到的是 26/11(review 第一轮量的就是那棵树);`./node_modules/.bin/pyright` 对改动文件 0 errors;`bench/filesize_check` 干净;go / npm 未动。 + - 门禁:`pytest -q` 全绿(**287 passed**);`ruff check` 干净;mypy 冷缓存 **24 errors / 9 files**(命令 `.venv/bin/python -m mypy --cache-dir <新目录> backend/src bench`),与 p1-6 基线(`9cb46c1` 同口径 24/9)**净新增 0**——p2-1 一度引入 +2(`vector_index.py` 与 `quant_probe.py` 的 `import sqlite_vec` 没有 stub),根因修法是在 `pyproject.toml` 的 `[[tool.mypy.overrides]]` 里声明 `sqlite_vec` 无 stub,而不是在代码里加 `# type: ignore`;修在 **`71733d8`**,所以 `6cc5640` 那棵树上量到的是 26/11(review 第一轮量的就是那棵树);`./node_modules/.bin/pyright` 对 p2-1 范围内 **29 个 `.py`** → **0 errors**;同范围(`9cb46c1..fe538ac`,共 48 个文件)还改了 3 个前端文件(`frontend/src/components/MemoryPanel.tsx`、`frontend/src/components/SensesPanel.tsx`、`frontend/src/types.ts`)和 1 个 `.go`(`ely-eye-mcp/internal/domain/types.go`)——前端由 `npm run build`(220.81 kB)+ `npm run lint` exit 0 覆盖,go 由 `go build` / `go vet` / `go test`(ok)+ `gofmt -l` 空覆盖,四条都在 `fe538ac` 上重跑过(pyright 直接吃 `.ts/.tsx` 会报两千多条,那是没 tsconfig 上下文的噪声,不是门禁);`bench/filesize_check` 干净。 - 顺带修的测量缺陷:`copy_store()` 只拷 SQLite,所以任何要跑视觉腿的 harness 都得自己带上 `objects/`(25 GB,探针里用符号链接)与 `cache/clip_image_index.npy`;`bench/scale_check.py` 原本直接打开冻结基线库、把它**原地迁移**了,现改成在副本上测(`--scratch`)。产品 CLI 不在这条规则里:p2-1.4 建图片索引时 `ely-eye index-visual` 就把冻结基线从 schema 7 升到了 8,所以 p1-4.8 那两条钉的是**当前**的 mtime / sha256 / schema,并指名是哪一步改的。 - [ ] **p2-2** 安全与隐私 - [ ] p2-2.1 FastAPI 鉴权 + Origin 校验