跳到主要内容

深入实现、巧妙之处、边界

本章给要读源码/借鉴技术的人:快版多向量库怎么做、有哪些精华设计、会在哪崩,以及完整代码地图。

1. 深入:快版多向量库(TurboPuffer + MUVERA)

慢版库(MultiVectorStore,见 02 章)靠 pg 里 max_sim SQL 全表算 MaxSim——数据一大就慢。快版库 FastMultiVectorStore(core/vector_store/fast_multivector_store.py:295)用两阶段检索破局。

1.1 问题:多向量做不了标准 ANN

主流向量库(TurboPuffer 等)的 ANN 索引只认向量。一页上千个向量,没法直接建索引。

1.2 解法:MUVERA / FDE 把多向量塌成一个单向量

Morphik 引入 MUVERA(Fixed Dimensional Encoding,固定维度编码):用一组随机 SimHash 投影,把「一组不定长的多向量」确定性地映射成一个固定长度的单向量,且这个单向量的内积近似原多向量的 MaxSim。配置见 :325:

# core/vector_store/fast_multivector_store.py:325
self.fde_config = fde.FixedDimensionalEncodingConfig(
dimension=128, num_repetitions=20,
num_simhash_projections=5, projection_dimension=16,
projection_type="AMS_SKETCH",
)

摄入时对每个 chunk 的多向量算 fde.generate_document_encoding(...)(:451)存进 TurboPuffer;查询时对查询多向量算 fde.generate_query_encoding(...)(:524)。

1.3 两阶段:FDE 粗筛 → 真 MaxSim 精排

怎么读:先用「近似的单向量」快速筛出一小撮候选(便宜),再对这一小撮用「真正的多向量 MaxSim」精确重排(准)。

查询多向量
│ fde.generate_query_encoding

单向量 ──▶ TurboPuffer ANN(top_k = min(10*k, 75)) ← 阶段一:粗筛,快
│ (:529)

候选 chunk 的多向量(从对象存储下载)
│ processor.score_multi_vector(真 MaxSim) ← 阶段二:精排,准
▼ (:556)
torch.topk 取真正的 top-k(:559)

这是经典的「近似召回 + 精确重排」两级检索,把「多向量太重没法建索引」这个硬约束,用 FDE 降维绕过去了。

1.4 三种库,按需切换

services_init.pyMULTIVECTOR_STORE_PROVIDER 选库:morphik → 快版 FastMultiVectorStore(:159),否则 → 慢版 MultiVectorStore(:163);迁移期还能用 DualMultiVectorStore(:152)同时写两个库,平滑从慢版迁到快版。

2. 巧妙之处(可借鉴的精华)

  • 二值 MaxSim 塞进一个 SQL 函数。 把「多向量晚交互打分」整段用 bit_count(a # b) 汉明距离写成 pg 的 IMMUTABLE PARALLEL SAFE 函数(core/vector_store/multi_vector_store.py:290),让数据库一条 ORDER BY max_sim(...) 就完成 MaxSim 排序——不用把海量向量拉回应用层。

  • FDE 把「不能建索引的多向量」变成「能建索引的单向量」。 core/vector_store/fast_multivector_store.py:325/451/524,内积近似 MaxSim,粗筛阶段直接吃现成 ANN。

  • 内容外置 + storage key。 大 chunk(图片)不进向量行,只存 key,检索时按需下载,skip_image_content 还能在只要 key 时完全跳过下载(multi_vector_store.py:774)。

  • Excel 反直觉地不转图。 ColPali 主打「整页当图」,但 Excel 明确走文本路,因为表格文本检索更好(ingestion_service.py:1554)——不盲目套用招牌技术。

  • embedding 与鉴权并行、过量召回后重排。 检索热路径上把无依赖的两件事并发(document_service.py:353),reranker 前先 oversample(:394)提升重排质量。

  • 贵解析的多级兜底。 普通 Docling → 空则 LibreOffice 转 PDF + EasyOCR + 高精度表格的深度解析(morphik_parser.py:628),失败不崩、逐级降级。

3. 边界与局限(诚实)

  • ColPali 很吃资源。 ColQwen2.5-3B 要 GPU/MPS,cloud 模式 batch=8、本地 batch=1(colpali_embedding_model.py:61)。自托管跑视觉路对硬件要求高;README 也明说自托管支持有限

  • 摄入延迟高。 异步队列意味着「上传后不是马上可查」,得轮询 processing → completed

  • padding 会丢非图片 chunk。 开 padding 时,非图片 chunk 被直接过滤掉(document_service.py:594-600),混合文档要注意。

  • 删除用字符串拼接 SQL。 delete_chunks_by_document_iddocument_id 直接拼进 DELETE 语句(multi_vector_store.py:936);document_id 为内部生成的 external_id 而非用户直接输入,但这是个值得留意的模式。

  • 二值化有精度损失。 每维压成 1 bit 是有损的;慢版库靠它换存储/速度,极端精排场景可能不如全精度。

  • 图片查询上限 10MB(document_service.py:245),超了直接 400。

4. 横向对比(rag-retrieval 货架内)

维度Morphik 的取舍
文档表示整页当图片 + 多向量(而非纯文本单向量)
相似度MaxSim 晚交互(而非单向量余弦)
多向量落地二值 BIT + SQL MaxSim / 或 MUVERA FDE + ANN 两阶段
摄入异步 arq 队列 + 多级解析兜底
生成LiteLLM 多模态(图片直接进 LLM)

与「纯文本 chunk + 单向量 + 余弦」的经典 RAG 相比,Morphik 用更重的表示换视觉文档的召回质量,再用二值化/FDE 把重表示的成本压回可用范围。

5. 代码地图(导航索引)

主题文件路径符号名
摄入入队core/routes/ingest.pyingest_file / redis.enqueue_job("process_ingestion_job")
摄入后台任务core/workers/ingestion_worker.pyprocess_ingestion_job / WorkerSettings
摄入编排core/services/ingestion_service.pyingest_file_content / _process_chunks_and_embeddings / _process_colpali_embeddings
统一解析器core/parser/morphik_parser.pyMorphikParser / _parse_document / parse_file_to_text_deep
Excel 快路core/parser/morphik_parser.py_parse_excel_to_markdown
递归切分core/parser/morphik_parser.pyRecursiveCharacterTextSplitter._split_recursive
上下文切分core/parser/morphik_parser.pyContextualChunker._situate_context
ColPali embeddingcore/embedding/colpali_embedding_model.pyColpaliEmbeddingModel / embed_for_ingestion / generate_embeddings
PDF 转页图core/services/ingestion_service.py_create_chunks_multivector / _process_pdf_for_colpali
多向量库(慢)core/vector_store/multi_vector_store.pyMultiVectorStore / max_sim(SQL) / _binary_quantize / query_similar
多向量库(快)core/vector_store/fast_multivector_store.pyFastMultiVectorStore / fde_config / query_similar
库选择/服务装配core/services_init.pyMULTIVECTOR_STORE_PROVIDER 分支 / DualMultiVectorStore
检索总编排core/services/document_service.pyretrieve_chunks / _apply_padding_to_chunks
问答总编排core/services/document_service.pyquery
Rerankercore/reranker/flag_reranker.pyFlagReranker.rerank
多模态补全core/completion/litellm_completion.pycomplete / process_context_chunks / format_user_content
配置morphik.toml / core/config.pyregistered_models / [embedding] / [reranker]