Appearance
第9章 Embedding:文本如何变成可检索的向量
"An embedding is a lossy summary that preserves the axis of meaning you care about." — Information Retrieval 课堂总结
本章要点
- Embedding 模型的核心任务:把文本压缩成低维稠密向量,让语义相近的文本在向量空间中距离相近
- 三大训练范式:对比学习(SimCSE/BGE 主流)、蒸馏(MiniLM 小模型常用)、指令微调(E5-mistral 新路径)
- 选型六维度:语言覆盖 / 领域 / 输入长度 / 向量维度 / 推理成本 / License
- 2024-2026 新趋势:Matryoshka 可截断向量(短向量在线+长向量精排)、late-interaction(ColBERT v2,多向量精度但大)、多向量 per chunk
- 生产部署的关键是吞吐和稳定性——批处理 + 动态 batch + 熔断 / 降级
9.1 Embedding 要解决的本质问题
RAG 的检索阶段问一个核心问题:"库里哪些 chunk 和这个 query 最相关?" 把这个问题工程化需要一个度量——"相关性"必须是可计算的数字。
最朴素的度量是 BM25(第 12 章)——按词项重叠打分。但"新版企业版 SSO 功能"和"最新产品套餐 单点登录"这两句在词项上几乎不重叠、语义上完全等价。BM25 给低分、语义检索给高分——这是 Embedding 在 RAG 里的不可替代价值。
Embedding 模型把这种"语义距离"学成一个向量空间的几何距离:把一段文本映射到 R^d 空间中的一个点(d 通常 384-1536),让语义相近的文本在空间中靠近。检索时只需要算 query 向量和 chunk 向量的余弦相似度,取 top-k。
这个思路简单,但实现细节决定了效果:
- 空间怎么训出来?(9.2)
- 向量维度怎么选?(9.3)
- 领域不适配怎么办?(9.4-9.5)
- 推理效率怎么撑起生产流量?(9.6)
9.2 三大训练范式
对比学习:主流中的主流
SimCSE(Gao et al., arXiv:2104.08821)开启的路线。核心思路:让 (anchor, positive) 对的向量靠近、让 (anchor, negative) 对的向量远离。
训练数据形如 (query, relevant_doc, irrelevant_doc_1, ..., irrelevant_doc_k)。loss 用 InfoNCE:
text
loss = -log( exp(sim(q, pos)) / (exp(sim(q, pos)) + Σ exp(sim(q, neg_i))) )1
BGE(BAAI)、E5(Microsoft)、GTE(Alibaba)、Voyage、Cohere v3 都是这条路线的成熟产品。
关键工程细节:
- 难负例(hard negatives):纯随机负例学不到细粒度语义。要从 BM25 召回的"表面相似但真正无关"的候选里挑
- in-batch negatives:同 batch 里其他样本的 positive 互为负例——免费的负例,batch_size 越大效果越好
- 多阶段训练:先在大规模弱监督数据(网页对、搜索 log)预训练,再在高质量人工标注数据上微调
蒸馏:小模型的出路
大模型(如 BGE-large 335M)效果好但推理慢。蒸馏把大模型的向量空间"教给"小模型:
- 学生模型 (如 MiniLM 33M) 的输出向量 MSE-loss 对齐教师 (BGE-large)
- 推理速度 5-10 倍提升,精度损失 2-5%
生产场景的经典权衡:对冷门长尾文档用大模型精细 embedding,对高频查询走小模型快速通道——第 11 章讨论缓存时会回到这个设计。
指令微调:让 Embedding 听懂任务
E5-mistral-7b(Microsoft 2024)走了另一条路:用 LLM 做 embedding、通过 prompt 告诉模型"我要检索什么"。
text
Instruct: Given a web search query, retrieve relevant passages that answer the query
Query: how to train a llama1
2
2
优点:同一个模型可以做检索、分类、聚类等多种任务——靠改 instruction 切换。Cohere embed-v3、Voyage-3 都加了类似的 instruction input。
缺点:模型大(7B+)、推理慢、显存占用高。适合质量优先场景(专业知识库、科研文献);不适合通用高并发场景。
9.3 选型六维度
给定一个 RAG 项目,选 Embedding 模型要看六个维度。
语言覆盖
- 中文为主:BGE 系列(bge-m3、bge-large-zh-v1.5)、M3E、智源的 PIXIU
- 英文为主:E5、text-embedding-3、Voyage、Cohere embed-v3
- 多语言:bge-m3 / paraphrase-multilingual-mpnet / multilingual-e5
MTEB 是基准 leaderboard(huggingface.co/spaces/mteb/leaderboard)——针对语言和任务有独立排名。选前查自己目标语言的子榜。
领域
通用 embedding 在专业领域会水土不服:
- 医疗:
PubMedBERT、BioASQ训练的模型 - 法律:
LegalBERT、阿里的lawformer - 代码:
voyage-code-2、nomic-embed-code、jina-embeddings-v2-code - 金融:
FinBERT系列
如果业务在垂直领域且有标注数据,微调 比直接换模型效果好(9.5)。
输入长度
Embedding 模型的最大输入 token 数:
| 模型 | 最大 token | 备注 |
|---|---|---|
| bge-small-zh-v1.5 | 512 | 短文本优化 |
| bge-m3 | 8192 | 长文本友好 |
| text-embedding-3-small | 8192 | OpenAI |
| text-embedding-3-large | 8192 | OpenAI |
| voyage-3 | 16000 | 长文档王者 |
| jina-embeddings-v3 | 8192 | 多语言长文本 |
注意 "最大" ≠ "最佳"。多数模型在 200-500 token 样本上训练最充分,远超训练分布的长 chunk 表现会下降。bge-m3 的 8192 是工程可用、但 4000 token 的 chunk 比 200 token 的 chunk 在检索质量上没有优势。
向量维度
常见维度:
- 384:bge-small、MiniLM 系列
- 768:bge-base、multilingual-e5-base
- 1024:bge-large、bge-m3、jina-v3、voyage-3
- 1536:text-embedding-3-small、ada-002
- 3072:text-embedding-3-large
维度影响两件事:
- 索引内存:1M chunk × 1024 dim × 4 bytes = 4 GB RAM。维度翻倍直接翻倍存储
- 检索延迟:距离计算 O(d),维度越大越慢
Matryoshka 向量(Kusupati et al., arXiv:2205.13147)让同一向量可以截断使用——完整 1024 维最精、截到 384 维仍可用。text-embedding-3、nomic-embed 都支持。生产常见用法:ANN 检索用短维度快速召回、top-k 精排用完整维度打分。
推理成本
| 模型 | 吞吐(单 A100) | 美元/1M tokens |
|---|---|---|
| bge-small | 3000+ chunk/s | 自托管 ~$0.02 |
| bge-m3 | 500-800 chunk/s | 自托管 ~$0.15 |
| text-embedding-3-small | API | $0.02 |
| text-embedding-3-large | API | $0.13 |
| voyage-3 | API | $0.06 |
| cohere embed-v3 | API | $0.10 |
API 便利但并发有限;自托管延迟低但运维成本。混合策略:日常索引走 API 按量付费,大批量全量重建走自托管 GPU pool。
License
开源可商用的 Embedding 模型:bge-* (MIT)、nomic-embed (Apache 2.0)、jina-v3 (non-commercial for v3、v2 Apache)。闭源 API:OpenAI、Voyage、Cohere。
注意"开源可商用"≠"可微调再发布"。部分模型用了有限制的训练数据,商用前查清 license。
9.4 Query 和 Document 非对称编码
BGE、E5 这类模型在训练时区分 query 和 document——查询加 "Represent this sentence for searching relevant passages: " 前缀、文档不加。推理时要严格对应:
python
# 正确
q_vec = embed(query, mode="query") # 带 prefix
d_vec = embed(chunk, mode="document") # 不带 prefix
# 错误——都用 document 模式
q_vec = embed(query, mode="document") # 匹配度下降 5-15%1
2
3
4
5
6
2
3
4
5
6
这个细节在 quickstart 教程里常被省略。生产代码必须严格区分 query/document mode。模型卡里会说明各自的 prompt/prefix 格式。
E5-mistral 走了一步更远——用完整的 instruction prompt 描述任务、query 和 document 分别加不同的 instruction。
9.5 领域微调:什么时候值得做
什么时候不用微调
- 通用领域(百科、一般产品文档):开箱 bge-m3 / voyage-3 已足够
- 标注数据不够(< 5000 条相关对):微调数据不够反而伤泛化
- 团队没有 ML 工程师:维护微调模型的运维成本高
什么时候值得微调
- 专业领域(法律条文、医学术语、代码)且标注数据充足
- 开箱模型在本领域 gold set 上 recall@10 < 70%
- 有长期 ML 团队能维护训练和发布流程
微调的两条路
路径 1:全参数微调。拿开源模型(bge-base / E5-base)当起点、在领域对比学习数据上继续训练。效果好但成本高(几小时 GPU × 几次迭代)、模型完整部署。
路径 2:LoRA / QLoRA。只训练 adapter 参数、base 模型冻结。训练成本降 10 倍、部署时 adapter 动态加载、可以针对不同子领域维护多个 adapter。2024 年后这条路主流化。
训练数据从哪来
- 业务 log:用户点击、采纳的 (query, chunk) 是天然正样本
- 人工标注:少量高质量标注校准方向
- 合成数据:用 LLM 根据 chunk 生成问题,
(generated_q, chunk)作为正样本——Llama-index 和 InPars 都有工具链 - 难负例挖掘:BM25 或老版 embedding 召回的"高分但实际无关"的 chunk 作为负样本
实操比例:30% 人工 + 50% 合成 + 20% 业务 log。纯合成容易过拟合合成模式、纯人工量不够。
9.6 推理部署:吞吐、延迟、稳定性
生产 embedding 服务的三个工程要点。
动态 batch
单次 embedding 调用固定 batch=1 会让 GPU 利用率只有 10-20%。动态 batch 聚合多个请求:
- 队列里等 0-20ms 积累请求、最多 64 条、到就 flush
- batch=32 时 GPU 利用率 >80%、吞吐 10 倍于 batch=1
- 延迟代价:P50 延迟 +10-20ms、换 10 倍吞吐值
开源方案:Triton Inference Server、vLLM 的 embedding backend、TEI(Text Embeddings Inference,Hugging Face 官方)。
Prefix caching
query/document prefix 相同、每次推理都重算 prefix 浪费。推理引擎支持 prefix cache 时复用 prefix 的 KV/attention——节省 5-10% FLOPs。小收益但免费。
熔断和降级
外部 API embedding 服务偶尔抖动(OpenAI limit、网络、服务端故障)。熔断策略:
- P99 延迟或错误率超阈值:自动切到 fallback 模型(自托管 bge-small)
- 业务继续运转、索引延迟略增但不中断
- 恢复后自动切回主模型
在线查询侧比离线索引侧更需要熔断——离线可以等恢复后补、在线等不了。
多模型混合部署
不同场景选不同模型,一套服务对外:
- 召回模型(bge-small 快):低延迟、覆盖面
- 精排模型(bge-m3 准):top-k 精调
- 领域模型(微调 adapter):专业查询
服务端按 request header 路由到不同 model。这种混合部署让单个 RAG 系统同时享受快和准。
9.7 Embedding 服务的容量规划
一个 RAG 项目在 Embedding 这一环的容量规划要算清楚两条需求:
离线索引吞吐。假设知识库 500 万 chunk、每月增量 50 万 chunk。
- 全量重建:500 万 / 800 chunk/s = ~1.7 小时(单 A100 + bge-m3)
- 每日增量:50 万 / 30 天 ≈ 1.7 万/日 → 30 分钟(宽裕)
- 结论:1 张 A100 完全覆盖离线需求
在线查询吞吐。假设日活 10 万、人均 5 次查询、QPS 峰值约 10。每次查询 1 个 embedding 调用。
- 峰值 10 QPS、单次 < 50ms,1 张 A100 撑得住
- 但实际要考虑每次查询可能有多次 embedding 调用(query rewrite 生成 3 个子查询)
- 3 倍 QPS = 30 → 仍然 1 张 A100 够
双活方案:主备各 1 张 A100 跑 embedding 服务、主挂备顶。2 张 A100 覆盖 500 万 chunk 知识库 + 10 万日活的 RAG 足够。
如果查询 QPS 上 100+、或知识库上亿 chunk,需要多 GPU 水平扩展——走 Triton / TEI 的多实例部署。
9.8 评估 Embedding 的方法
选定候选模型后怎么验证?三层评估。
MTEB 排行榜
快速初筛。针对目标语言和任务(Retrieval / STS / Classification)看分数。但 MTEB 是公开数据集、不完全代表你的业务。
业务 gold set
200-1000 条业务真实 QA,每条标注 gold chunk。跑每个候选模型测 recall@10 / MRR。这是最重要的评估——排行榜再高,业务 gold set 上低就没用。
在线 A/B
候选模型上线 10% 流量、观察 CTR、refusal rate、转人工率。最准但最慢。候选模型必须先通过前两层才值得做 A/B。
实操顺序:MTEB 选 3-5 个候选 → gold set 选 1-2 个 top → A/B 验证选最终 1 个。
9.9 Embedding 的十个常见坑
生产 Embedding 服务里反复出现的坑,列出来避免踩:
- 坑 1:tokenizer 不一致。训练和推理用不同 tokenizer(版本、vocab)导致 embedding 漂移。统一锁 tokenizer 版本
- 坑 2:padding 策略不一致。训练时
right_pad推理时left_pad——pooling 层取 last_token 拿到的是 pad token。pooling 策略要和训练对齐(CLS / mean / last) - 坑 3:正则化遗忘。训练时对 embedding L2 正则化、推理忘了做——余弦相似度计算错误
- 坑 4:FP16 精度损失。大向量维度(1024+)用 FP16 存储、距离计算累积误差。保留 FP32 或用 BF16
- 坑 5:温度系数忘记标定。对比学习的 temperature 在推理时不相关,但若做 softmax 重排、得用标定温度
- 坑 6:text truncation 静默。输入超限被 tokenizer 静默截断、后半段丢失。生产必须显式检查长度并告警
- 坑 7:多语言混文。中英混文按哪种语言 tokenize 影响大。多语言模型选用对齐 tokenizer(xlm-roberta 系)
- 坑 8:embedding 不归一化。向量库有的算 cosine 有的算 inner-product——不归一化结果不一致。入库前统一归一化
- 坑 9:更新模型不回灌。换了新 Embedding 模型,老 chunk 没重 embed,新老向量空间不兼容。必须全量重 embed
- 坑 10:数值不稳定。某些模型在 long input + 异常字符上产出 NaN 向量。入库前 validator 检查
all(finite(vec))
这十个坑几乎每个生产 RAG 团队都踩过至少三个。前期把 validator 写好、CI 跑好,能躲掉大部分。
9.10 2024-2026 的三条新路线
Embedding 领域不是静止的。最近三年出现的新路线:
Matryoshka:可截断向量
同一模型同一次推理产生一个 1024 维向量、但前 64/128/256/512 维各自可独立用作检索向量。实现靠训练时 loss 同时优化多个截断点。text-embedding-3、nomic-embed-1.5 都是 Matryoshka。
生产用法:ANN 召回用 128 维快速跑大集合、精排再用 1024 维打分。延迟降低 60% 不损精度。
Late-interaction:ColBERT v2
ColBERT v2 不给 chunk 一个向量,给每个 token 一个向量。检索时 query 的每个 token 和 chunk 的每个 token 求 max-sim 再求和。
优点:精度比 single-vector 显著高(BEIR 平均 +5-10 个点)。缺点:存储膨胀 30-100 倍(per-token 向量数)。
应用:作为精排阶段的打分器,而非召回阶段的索引——精排只对 top-50 做、存储压力可控。
三条路线的选型
生产实战:大部分项目选 Matryoshka——成本低、收益显著、无需大改架构。ColBERT v2 适合"成本不敏感但一定要拿到精度上限"的项目(法律、医疗、科研)。多向量 per chunk 是商业 API 提供的"中间档",无需自托管。
未来趋势预判
三条路线可能融合——一个模型同时支持:
- Matryoshka 可截断主向量
- 额外产生若干辅助向量(多向量)
- 在特殊场景下退化成 late-interaction 做精排
2026 年已有模型(jina-v3)部分实现了这种"多模式"架构。预计 2027-2028 年会成为主流 embedding 模型的标配能力。RAG 工程需要跟进这种演进——但稳定生产系统不要追最新模型,等一个模型有 6+ 月的社区验证再换。
多向量 per chunk
极简版 ColBERT——给每个 chunk 产生多个向量(比如 3-5 个,分别代表 chunk 的不同语义角度)。存储膨胀适中(3-5 倍)、精度介于 single-vector 和 ColBERT 之间。商业 embedding API(Jina v3、Voyage)开始提供。
9.11 Embedding 的量化与压缩:int8 与 binary 向量
2024 年后 embedding 领域另一条实用路线——向量本身的量化压缩。这和第 10 章 PQ 不是一回事:PQ 是ANN 索引内部的压缩技巧,向量进索引后再压;而 int8 / binary 量化发生在embedding 输出阶段——模型直接产出低精度向量、内存和传输都省。两种技术可以叠加。
三档精度的权衡
1024 维向量的存储:
- float32:4096 bytes/向量。100 万向量 = 4 GB
- int8:1024 bytes/向量。100 万向量 = 1 GB(4× 压缩、recall 保留 ~99%)
- binary:128 bytes/向量。100 万向量 = 128 MB(32× 压缩、recall 保留 ~90%)
binary 把每个维度压成 1 bit——用 sign(x) 或阈值切分。距离从 cosine 变成 Hamming 距离(异或后数 1)——XOR + popcount 指令比 float 乘加快 10-100 倍。
为什么 recall 损失可控
int8 量化精度保留 ~99% 的秘密:embedding 分布接近高斯,极值稀有、动态范围不大。把 [-1, 1] 的 float32 映射到 [-128, 127] 的 int8、精度损失在小数点后几位、对 cosine 排序几乎无影响。
binary 更激进但仍能工作的原因:高维空间里方向比具体数值更重要。1024 维 float 向量的"方向"大部分由每个维度的符号决定、幅度是次要信息。binary 保留了最核心的符号信息。
两阶段检索模式
binary 最成功的生产用法是两阶段:
- 粗召回:用 binary 向量 + Hamming 距离在全库跑 top-500。速度比 float32 快 30-50×
- 精排 rescoring:对 top-500 加载对应的 float32 向量、算真实 cosine、选 top-k
粗召回阶段扩大 K(如从 top-50 扩到 top-500)吸收 binary 的精度损失、精排阶段用 float32 把排序修正回来。整体 recall 接近纯 float32、延迟和存储接近纯 binary。
支持矩阵
2026 年主流模型的量化支持:
| 模型 | int8 | binary | 备注 |
|---|---|---|---|
| bge-m3 | ✓ | ✓(社区版) | 原生支持 int8、binary 需后处理 |
| mxbai-embed-large | ✓ | ✓ | 官方提供 binary 和 int8 权重 |
| Cohere embed-v3 | ✓ | ✓ | API 参数 embedding_types: ["int8", "binary"] |
| nomic-embed | ✓ | ✓ | 支持 Matryoshka + binary 叠加 |
| text-embedding-3 | ✓ | - | 通过客户端量化 |
| voyage-3 | ✓ | - | 官方 int8 |
无原生支持的模型也能客户端量化——但会损失一些精度,因为训练时没考虑量化感知。
量化感知训练 vs 事后量化
两种获得量化向量的路径:
- 事后量化(post-hoc quantization):用 float32 模型跑推理、对输出向量做量化。简单、零改动。精度损失典型 0.5-2%
- 量化感知训练(quantization-aware training, QAT):训练时就让模型输出 int8 / binary 向量、loss 反向传播包含量化误差。精度损失可低到 < 0.5%、但需要重训
生产场景里事后量化已经够用——0.5-2% 的 recall 损失在 hybrid + rerank 链路里基本看不出。
何时用量化
| 场景 | 推荐 |
|---|---|
| 千万级以上向量 + 内存紧张 | binary 粗召 + float 精排 |
| 百万级 + 延迟敏感 | int8 |
| 百万级以下 + 有 rerank 兜底 | int8 或 float32(看成本) |
| 高精度硬需求(法律/医疗) | float32 + int8 备份路径 |
常见坑
- 归一化顺序错:量化前要对 float 向量做 L2 归一化、否则 int8 动态范围失控
- Hamming 距离和 cosine 不同量纲:不要直接拿 binary 分数当 cosine 分数用——要么都转一致单位、要么用 rank 融合(ch13 RRF)
- 数据漂移后量化参数失效:int8 的缩放系数(scale/zero_point)按训练分布定、分布变后量化误差爆增。定期校准
- 多版本混用:int8 向量和 float32 向量不能直接比——要么统一升到 float32 算、要么分别维护索引
- 粗召回 K 设太小:粗召回 top-50 + 精排 top-5——binary 的精度损失让 top-5 之外的真相关 chunk 漏掉。粗召回 K 通常 10× 最终 k
和 Matryoshka 的叠加
Matryoshka 降维度、量化降每维度精度——两者正交、可叠加:
text
原始: 1024 dim × 4 bytes = 4096 bytes/向量
+ Matryoshka 截到 256 dim: 256 × 4 = 1024 bytes (4× 压缩)
+ int8 量化: 256 × 1 = 256 bytes (16× 压缩)
+ binary: 256 / 8 = 32 bytes (128× 压缩)1
2
3
4
2
3
4
128× 总压缩——1 亿向量从 400 GB 降到 3.2 GB 单机可装。recall 用两阶段 rescoring 补回来。2025 年后部分 SOTA RAG 系统就是这种组合。
9.12 多模态 Embedding:文本之外的检索
前 11 节的 Embedding 都假设输入是文本。但 2024-2026 年真实企业文档里、纯文本只占 60-70%——剩下是截图、示意图、产品照、嵌入 PDF 的表格、产品视频里的演示片段、录音会议纪要。这些非文本内容用传统 text embedding 漏召、用 OCR+文本 embedding 又损失结构——多模态 Embedding 是这个空白的答案。
多模态 Embedding 的三条主线
- 文本 embedding:前面 11 节
- 图像-文本联合(CLIP 家族):图像和文本映射到同一向量空间——用文本 query 能召回图像、反之亦然
- 音频-文本联合(CLAP、LAION-CLAP):类似 CLIP、但替换成音频
- 统一多模态:2024 年后兴起的"一个模型处理所有模态"——Cohere Embed v3 multimodal、Voyage-multimodal-3、Jina-clip-v2
CLIP 的工作原理和局限
CLIP(OpenAI 2021)和它的变体(SigLIP、EVA-CLIP)是多模态 embedding 的基础。核心思路:
- 训练时大量 (image, caption) 对、用对比学习拉近同一对的距离、拉远不同对
- 推理时图像过 image encoder、文本过 text encoder、各自输出同维度向量
- cosine 距离直接跨模态对比
生产里 CLIP 的几个实际局限:
- 文本端能力弱:CLIP 的 text encoder 比 BGE/E5 短不少、长句理解差。"文本 + 文本"的 RAG 不要用 CLIP 当通用 embedding
- 领域泛化有限:CLIP 预训练数据偏自然图像、企业文档里的流程图、截图、PDF 版式表现一般
- 分辨率限制:典型 224×224 或 336×336、小字识别不行
生产补救:
- 文本 embedding 继续用 BGE/Voyage——只把图像走 CLIP 独立索引
- 业务领域多的话、fine-tune CLIP 或用领域模型(MedCLIP 医疗、FashionCLIP 时尚)
统一多模态:2024 年后的方向
Cohere Embed v3 multimodal、Voyage-multimodal-3、Jina-clip-v2 的核心卖点:一个 embedding 模型同时支持文本、图像、PDF 版面——不用分别维护 text 和 image 索引。
python
# Voyage 风格 API
text_vec = embed("企业版 SSO 配置界面")
image_vec = embed(png_bytes) # 同一个 embed 函数
pdf_vec = embed(pdf_bytes) # PDF 原始字节(内部处理版面)
# 都在同一向量空间、可直接对比
similarity = cosine(text_vec, image_vec)1
2
3
4
5
6
7
2
3
4
5
6
7
统一多模态对 RAG 工程的简化明显:
- 单一向量索引、不需要 chunk 是图还是文
- 检索跨模态自然、"找和这张产品图类似的文档"一次查询搞定
- 只维护一个 embedding 服务、成本 / 运维都省
但 2026 年的统一多模态模型在纯文本 retrieval 精度上通常仍不如专用文本 embedding(BGE-m3 等)——权衡是"一处简单 vs 两处最优"。
多模态 RAG 的工程模式
生产多模态 RAG 的典型架构:
- 解析阶段(第 5 章):PDF 解析出文本 + 图像 + 表格、分别产生独立 chunk
- 嵌入阶段:
- 文本 chunk → text embedding
- 图像 chunk → CLIP / 统一模型 embedding
- 表格 chunk → 转成 markdown 后 text embedding(结构化保留)
- 音频 chunk → CLAP embedding 或先 ASR 转文本
- 索引阶段:所有 embedding 进同一向量库(同维度或按类型分 collection)
- 检索阶段:query 先判断类型(文本 query 还是图像 query)、选对应 embedding 路径
图像 chunk 的 metadata 要保留文档来源 + 所在页码 + 描述文本——这样答案可以显示"见《产品手册》第 5 页的架构图"、而不是只给个图片 URL。
多模态 RAG 的评估挑战
多模态评估比纯文本复杂:
- 跨模态相关性:"这张图是否回答了这个问题"——需要人工标注、LLM-as-judge 在视觉任务上稳定性差
- 图像质量是信号:同一内容的清晰图 vs 模糊图在召回时应有差异——单纯 embedding 不直接体现
- 答案引用的视觉指代:答案说"如图所示"、UI 要能跳转展示——测试 retrieval 不够、还要测 UI 对齐
生产方案:文本 RAG 的 gold set 保持独立测试、图像 RAG 建独立 gold set(问题 + 期望图像)、分别评估、避免相互干扰。统一报告里分 modality 拆指标。
什么时候该上多模态 RAG
不是所有 RAG 都需要:
| 场景 | 是否值得上多模态 |
|---|---|
| 纯文本 FAQ / 文档问答 | 不需要 |
| 产品手册含大量示意图 | 值得 |
| 代码库 RAG(含 diagrams) | 中等、取决于 diagram 重要性 |
| 电商 / 时尚 / 视觉检索产品 | 必需 |
| 医疗影像 / 法律证据图 | 必需、且需要领域专用模型 |
判断标准:如果 30%+ 的有效信息藏在图像 / 表格 / 音视频里、就值得上多模态。否则 OCR + 文本 RAG 成本效益更高。
多模态的成本
多模态 Embedding 的成本典型比纯文本高 3-10 倍:
- Image embedding:CLIP 类单图约 50-100ms、每千图几美分(API)或自托管 GPU
- Audio embedding:CLAP 每分钟音频约 100-200ms
- 统一多模态:Voyage/Cohere 按 token + image token 混合计费
生产考虑离线索引时批量计算(GPU 利用率高、成本降 50%+)——在线检索只做 query 侧 embedding、就和纯文本 RAG 成本相当。
9.13 Embedding 模型升级的迁移工程
§9.9 的坑 9 提到"换新 embedding 模型、老 chunk 没重 embed、新老向量空间不兼容"——这是 embedding 升级最危险的陷阱。实际生产每 6-12 月就可能遇到一次 embedding 升级(模型厂商出新版、自托管模型迭代、业务换垂直模型)——这一整节讲清楚升级的工程路径、避免踩第二次坑。
升级的三种触发
每种触发性质不同:
- 厂商换版:被动、必须跟(老版本可能下架)
- 自托管更新:主动、按需跟进
- 垂直换模型:主动、ROI 驱动
核心挑战:向量空间不兼容
Embedding 升级最严重的问题:新老模型的向量不在同一空间。直觉检验:
python
# 新老模型对同一文本的 embedding
v_old = old_model("企业版 SSO") # 例 [0.12, -0.34, ...]
v_new = new_model("企业版 SSO") # 例 [0.21, 0.05, ...]
# 两个向量的 cosine 相似度可能只有 0.3-0.5
# 就算内容完全相同、模型视角不同1
2
3
4
5
6
2
3
4
5
6
这意味着:
- 新 query 用新模型 embed、拿去老索引检索 → 距离完全错位、recall 崩溃
- 老 chunk 用老模型 embed、新 query 用新模型 → 同样不行
- 必须全量重 embed所有 chunk——不能混用
三种迁移策略
策略 1:停机重建。停服务、全量重 embed、重建索引、切换、启服务。
- 优点:代码路径不变、简单
- 缺点:百万 chunk 重 embed 几小时、用户感受明显
- 适合:小项目、非关键业务、低活跃时段
策略 2:蓝绿双索引。老索引(蓝)继续服务、新模型并行建新索引(绿)。建完后切换路由到绿索引、蓝索引保留 N 天作回滚。
- 优点:零停机、回滚快
- 缺点:存储翻倍一段时间、应用层需要支持 index 切换
- 适合:中大型项目、推荐方案
策略 3:渐进双写。新增 chunk 同时用新老模型 embed 写两份、历史 chunk 后台逐步重 embed。全部完成后切换。
- 优点:迁移周期最长但对系统冲击最小
- 缺点:工程复杂、期间可能部分不一致
- 适合:超大规模项目、不能有任何停机窗口
多数项目选策略 2(蓝绿双索引)——平衡简单和零停机。
迁移的验证 checklist
切换前必须验证的项:
- [ ] 向量格式兼容:新模型维度、数值范围和向量库 schema 匹配
- [ ] gold set recall 不退化:新索引在历史 gold set 上 recall 至少不低于老版
- [ ] 新能力测试:新模型的优势(如长 context)在对应 gold set 上体现
- [ ] 延迟测试:新模型推理延迟在 SLA 内
- [ ] 成本估算:新模型 + 新索引的月成本和预算对比
- [ ] badcase 回归:历史 badcase 在新索引上仍然修复(不回退)
- [ ] 权限和 metadata 完整:全量重 embed 不能丢任何 chunk 的元信息
任一不过关 → 不切换。带着问题上线后恢复代价 10× 预防。
Shadow 验证阶段
切换前、让新索引先跑 shadow 流量:
python
# 线上请求 shadow 到新索引
async def retrieve(query):
# 主路径:老索引
primary_result = await old_index.search(query)
# shadow 路径:新索引(异步、不阻塞用户)
asyncio.create_task(shadow_check(query, primary_result))
return primary_result
async def shadow_check(query, primary_result):
new_result = await new_index.search(query)
# 对比 recall 交集、记录 delta
log_delta(query, primary_result, new_result)1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
Shadow 一周、积累几万条真实 query 的对比数据——看新索引在真实流量下是否达标。用 gold set 不够、用真实流量才稳。
回滚策略
切换后发现问题、能分钟级回滚是必备:
- 应用配置里
active_index = "green"改回"blue"即可 - 不涉及数据库变更、纯流量切换
- 演练过的团队、回滚 < 5 分钟
蓝索引保留期建议 至少 2 周——某些问题只在长尾流量里暴露、短期内没发现。2 周后确认稳定再清理蓝索引释放存储。
升级的成本
100 万 chunk 规模的升级典型成本:
- 重 embed:100 万 × 单价(如 $0.00001/chunk)≈ $10-50
- 新索引构建:几 GB 内存、几十分钟
- 存储翻倍(蓝绿期):2 周 × 双倍存储费 ≈ $100-200
- 人工:验证 + shadow + 切换 → 2-3 人周
总计千元美金级——相对 RAG 基础设施成本是小数目。但不做可能花百倍修复 recall 漂移事故。
渐进式升级:Matryoshka 和快照
两种可以减少升级风险的新趋势:
- Matryoshka embedding(§9.10):新老模型如果都支持 Matryoshka、可以从高维截到相同低维、兼容性可能保留一部分(虽然不完美)
- Embedding 空间对齐(学术):训一个映射模型
f: V_old → V_new、近似把老向量迁到新空间——避免重 embed。2025-2026 年在研究、生产还不稳
两者都是减负、但不替代完整重建——追求正确性还是要全量重 embed。
常见迁移反模式
- 增量 embed 新 chunk、不动老 chunk:新老混用、新 query embed 后检索结果漂移、recall 崩
- 没做 shadow 验证:直接切换、出问题时已经线上用户遭殃
- 不保留老索引:切换后立刻删老、回滚不可能
- Gold set 和训练数据混:新模型在 gold 上分高、上线垮——gold 污染了
- 升级时机错:业务高峰、版本迭代高频期间升级——错上加错
升级频率建议
- 商用 API:跟厂商主版本、一年 1-2 次
- 自托管开源:半年评估一次、看是否有明显更优选择
- 业务模型:有明确 badcase 时才升、不盲目追新
过频升级是工程浪费——每次重建索引、验证、可能事故——ROI 通常只在有明确问题驱动时才好。
9.14 Embedding 的调试与可解释性
前 13 节讨论了 embedding 的训练、选型、部署、升级——但遇到问题怎么定位是单独一门工程手艺。embedding 是 1024 维黑盒、"这两条为什么检索不到"、"这个 chunk 的向量为什么异常"——没有专门的调试工具、工程师只能靠猜。这节把 embedding 调试的常用方法整理出来、让黑盒变灰盒。
调试的典型场景
每种场景都有不同的调试切入点。
场景 1:召回不到已知相关 chunk
用户问 "企业版 SSO 配置"、系统里明明有一份讲这个的文档、但没被召回到 top-10。排查:
python
def debug_not_found(query, expected_chunk_id):
# 1. 查 chunk 是否在索引里
chunk = vector_db.get(expected_chunk_id)
if not chunk:
return "chunk not indexed"
# 2. 算 query 和 chunk 的 cosine 相似度
q_vec = embed(query)
similarity = cosine(q_vec, chunk.vec)
print(f"Similarity: {similarity}") # 如果 < 0.5 说明 embedding 把两者分开了
# 3. chunk 在 top-K 的排名
results = vector_db.search(q_vec, top_k=100)
ranks = [r.id for r in results].index(expected_chunk_id) if expected_chunk_id in [r.id for r in results] else None
print(f"Rank in top-100: {ranks}")
# 4. 找比 chunk 更相似的"抢位"chunk
for r in results[:20]:
if r.id != expected_chunk_id:
print(f"Rank {r.rank}: {r.text[:100]} (sim={r.score})")1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
这个脚本能区分:
- 根本没索引:chunk 不在 DB 里
- 相似度过低:embedding 把 query 和 chunk 分开了
- 被别的 chunk 抢位:有更相似但不相关的干扰项
不同原因对应不同修复——瞎猜浪费时间。
相似度可视化:UMAP / t-SNE
想看 chunk 的 embedding 空间整体分布——用降维可视化:
python
from umap import UMAP
import matplotlib.pyplot as plt
def visualize_embeddings(chunks, sample_n=1000):
# 随机采样避免图太密
sample = random.sample(chunks, sample_n)
vectors = np.array([c.vec for c in sample])
labels = [c.metadata["category"] for c in sample]
# UMAP 降到 2D
reducer = UMAP(n_components=2, random_state=42)
coords = reducer.fit_transform(vectors)
# 按 category 着色
for cat in set(labels):
mask = [l == cat for l in labels]
plt.scatter(coords[mask, 0], coords[mask, 1], label=cat, alpha=0.5)
plt.legend()
plt.savefig("embedding_viz.png")1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
这种图能肉眼看出:
- 聚类分布:同类 chunk 是否在空间里聚成团?
- 异常点:孤立的 chunk 可能是异常(embedding 坏了 / 内容异常)
- 类别重叠:两个"不同"类别的 chunk 混在一起、说明 embedding 分不出
2D 图不严格、但能发现大问题。做重大 embedding 迁移前(§9.13)必做一次可视化对比。
异常 embedding 的识别
有些 chunk 的 embedding 本身就有问题——识别方法:
python
def detect_anomalous_embeddings(chunks):
vectors = np.array([c.vec for c in chunks])
norms = np.linalg.norm(vectors, axis=1)
# 1. norm 异常(应该都是 1 如果归一化了)
wrong_norm = (norms < 0.95) | (norms > 1.05)
# 2. 全 0 或全非常小
dead = norms < 0.01
# 3. NaN / Inf
invalid = ~np.isfinite(vectors).all(axis=1)
# 4. 离群(和均值距离极远)
mean_vec = vectors.mean(axis=0)
dist_to_mean = np.linalg.norm(vectors - mean_vec, axis=1)
outlier = dist_to_mean > dist_to_mean.mean() + 3 * dist_to_mean.std()
return {
"wrong_norm": wrong_norm.sum(),
"dead": dead.sum(),
"invalid": invalid.sum(),
"outlier": outlier.sum(),
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
每周跑一次、发现异常及时处理。
相似度分布的健康检查
健康的 embedding 系统、cosine 相似度应该满足:
- 跨 chunk 平均相似度:0.3-0.5(太高说明 embedding 塌缩、所有 chunk 都相似)
- 同 doc 邻近 chunk 相似度:0.6-0.8(高于跨 doc)
- top-10 检索结果的分数分布:递减、不是平坦
监控:
python
def cosine_stats(chunks, sample=10000):
sample_chunks = random.sample(chunks, min(sample, len(chunks)))
vectors = np.array([c.vec for c in sample_chunks])
# 随机对的相似度
pairs = random.sample(range(len(vectors)), 1000)
sims = []
for i in range(0, len(pairs), 2):
sim = cosine(vectors[pairs[i]], vectors[pairs[i+1]])
sims.append(sim)
return {"mean": np.mean(sims), "std": np.std(sims)}1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
分布漂移(均值从 0.4 升到 0.6)——embedding 开始塌缩、质量下降。
跨模型对比
评估一个新 embedding 候选时、不只跑 benchmark 分数——还要和现有模型对比同一份数据的相似度结构:
python
def compare_embeddings(chunks, model_a, model_b):
for chunk in chunks[:100]:
v_a = model_a.embed(chunk.text)
v_b = model_b.embed(chunk.text)
# 两模型眼中的"最相似 chunk" 是否一致?
top_a = find_nearest(v_a, all_vectors_a, top_k=5)
top_b = find_nearest(v_b, all_vectors_b, top_k=5)
overlap = len(set(top_a) & set(top_b)) / 5
print(f"Overlap: {overlap:.2f}")1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
Overlap < 30%——两模型的"相关概念"差距大、升级要慎重。
具体 chunk 的语义解释
想知道 "为什么模型觉得 chunk A 和 chunk B 相似"——技术上很难解释黑盒、但几个 proxy 方法:
- attention 可视化(自托管模型可用):看两个 chunk 的 encoder attention 重点对应哪些 token
- 删词实验:逐个删除 chunk 里的关键词、看相似度变化——下降最多的词是"关键"
- 换词实验:把关键词替换成同义词、看相似度是否保留——保留说明 embedding 捕捉了语义
这些 proxy 不严格、但比完全黑盒好。
Embedding 测试的 gold set
除了检索评估 gold set、embedding 本身也要 gold set:
text
测试项 1:同义句应该接近
- "企业版支持 SSO"
- "Enterprise 版本包含单点登录"
→ 期望 cosine > 0.85
测试项 2:反义句应该远
- "企业版支持 SSO"
- "基础版不支持 SSO"
→ 期望 cosine < 0.6
测试项 3:不相关应该远
- "企业版支持 SSO"
- "天气预报今天晴天"
→ 期望 cosine < 0.31
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
200-500 条这样的三元组作为 embedding unit gold set、每次升级跑。
生产 debug 工具链
生产环境的 debug 工具配置:
- cosine similarity explorer:UI 里输入两段文本、立刻看相似度
- neighbor lookup:任一 chunk → 看其 top-20 邻居(调试检索异常的直观方式)
- embedding health dashboard:norm / 分布 / 异常数
- A/B compare UI:同 query 同时跑两个模型、看差异
这些工具不需要花哨——简单 Streamlit / Gradio 应用即可。每周几次省几天人工排查。
Debug 的思维方式
Embedding debug 的核心是:假设 → 验证 → 迭代。
- 不要"瞎试参数"——每次改都要有假设
- 不要"只看一个案例"——每个改动至少测 50 条、看 aggregate 效果
- 不要"跳过基础"——先验证 norm / 编码 / 版本正确、再深入调优
这套思维方式不只 embedding——所有 ML 系统 debug 通用。
Debug 工具的长期投资
团队应该长期投入维护这套工具:
- 初版:2-3 人周
- 每季度扩展:0.5 人天
- Badcase 库持续积累
Debug 工具是团队生产力的乘数——有比没有效率差 3-5 倍。尤其 embedding 升级 / 向量库迁移这种大事、工具准备好的团队能几天搞定、没准备的拖几周。
9.15 Embedding 的隐私风险:从向量能反推文本吗
Embedding 看起来是 "1024 维数字、看不懂"——所以很多工程师认为存 embedding 比存原文更安全。这是严重误解——学术界已证明 embedding 可以反推出原文(至少部分)。这对 RAG 的隐私含义重大、尤其是合规场景。这节把 embedding inversion 的研究现状、对 RAG 的实际威胁、防御措施讲清楚。
Embedding inversion 攻击
攻击者拿到 embedding 向量(没有原文)——能部分或完全恢复原文。2023-2025 年多篇论文证明可行:
- Text Embeddings Reveal (Almost) As Much As Text(2023):从 OpenAI ada-002 embedding 恢复出 92% 的原文内容
- Vec2Text(2024):多步迭代攻击、恢复精度进一步提升
- TextObfuscator / Inversion defense(2025):研究防御
结论:embedding 不是"脱敏"形式的原文——是另一种表达、可以还原。
对 RAG 的实际威胁
什么时候这个威胁真实存在:
- 向量库被入侵:攻击者拿到 embedding 文件、反推得到所有 chunk 的内容。等同于原文泄漏
- Embedding 被导出 / 共享:给第三方分析、以为匿名、实际不匿名
- Train 数据泄漏:用户数据用来训 embedding 模型、模型被他人使用——可能反推训练数据
威胁场景在 RAG 里具体化:
- 企业 RAG 索引被黑客拖走:内部机密信息可恢复
- 和外部合作方共享 "embedding 数据":觉得不是原文所以安全——错
- 用公有 embedding API(OpenAI):API 能看到你的文本(被 embed 时)
威胁的严重程度
不同场景的实际威胁:
| 场景 | 威胁等级 | 原因 |
|---|---|---|
| 公开知识(维基百科 embed) | 低 | 本来就是公开的 |
| 企业内部知识 RAG | 高 | Embedding 文件泄露 = 数据泄露 |
| 医疗 / 金融合规 | 极高 | 法规明确、赔偿罚款大 |
| 用户个人 memory embed | 高 | 隐私泄露严重 |
不能假设"embedding 泄漏"就安全——要按原文泄漏同等级别对待。
攻击的实际难度
不是说"随便拿到向量就能恢复"——需要条件:
- 知道用的是哪个 embedding 模型:不同模型的 inversion 方法不同
- 有模型本身的访问:攻击用相同模型的知识做迭代
- 计算资源:反推每条 embedding 可能要几秒到几分钟 GPU 时间
所以不是"黑客拿到向量文件立即看到明文"——是"经过几天的工作恢复大部分"。但对高价值数据来说、这个门槛不高。
防御措施
防御 1:加密存储
向量库文件 at-rest 加密(磁盘级或应用级)——和原文一样保护。常识但很多团队忽略。
防御 2:访问控制
向量库 API 权限严控:
- 只有 RAG 服务自身能查询
- 定期审计访问日志
- 任何大规模 export 触发告警(防撞库式下载)
防御 3:不必要时不存明文
如果某 chunk 的 metadata 里存了原文 text——和 embedding 一起存会双重暴露。考虑:
- Metadata 里只存 text_id、原文存独立加密存储
- 或 metadata 里只存 text 的哈希、检索后回查原文
防御 4:差分隐私 embedding
研究方向:在 embedding 时加噪声、让 inversion 恢复的内容"近似但不准"。代价是检索精度降——工程上还不成熟、但值得关注。
防御 5:不发出 embedding
API 给外部只返回 "chunk_id 列表"、不返回 embedding 向量。即使 API 被滥用、也得不到向量、无法反推。
外部 API 的隐私风险
用 OpenAI / Voyage / Cohere 的 embedding API:
- 你的文本离开你的环境、到厂商服务器
- 厂商是否存储、存多久——看合同和 ToS
- 厂商被入侵的话、你的数据也受影响
合规场景(医疗 / 金融)——尽量自托管、不发敏感文本给第三方 API。
一个妥协:敏感部分脱敏后才 embed。比如把 "张三的薪资 5 万" 脱敏为 "<人名> 的薪资 <金额>"、embed 脱敏后的文本——语义保留但没隐私。
隐私和检索能力的权衡
完全保护隐私 vs 保留检索能力的取舍:
- 完全加密 检索时无法用 embedding——失去 RAG
- 差分隐私 加噪、降精度 5-10 点——成本高
- 脱敏 + 正常 embed—— 丢失部分细节信息
- 自托管 + 加密存储—— 相对均衡
多数企业 RAG 选 自托管 + 加密存储 + 访问控制 组合——既保留检索、又不让数据出境。
合规的具体要求
GDPR 第 4 条:pseudonymization(匿名化)的要求——embedding 是否算匿名?法律尚无明确定义、但研究证明可反推——趋势是按"伪匿名"对待、和原文同级保护。
HIPAA(美国医疗):要求 PHI(protected health information)不泄漏。Embedding 含可反推的 PHI——也在保护范围内。
中国 PIPL:个人信息保护法、embedding 若可反推识别个人——同受保护。
各地法规仍在演化——保守处理:按原文同级保护、不要赌"embedding 不是原文"。
向量库的 at-rest 加密实现
主流向量库的加密支持:
- Qdrant:支持 disk 加密、Qdrant Cloud 强制启用
- Milvus:支持 storage-layer 加密
- pgvector:继承 Postgres 的 TDE(Transparent Data Encryption)
- Pinecone:自带加密、不可选
自托管时 at-rest 加密用 LUKS / dm-crypt 等系统层加密——和普通敏感 DB 一样。
传输层加密
向量库和应用之间的网络也要加密:
- TLS 是必需——不是可选
- 内部网络也不要明文(vs "内部可信"的错误假设)
- 敏感场景用 mTLS、双向认证
审计和合规证明
合规审查时要能证明已采取保护措施:
- 证书:加密算法 / 密钥管理
- 访问日志:谁查了什么向量
- 审计报告:定期第三方审计
- 事故响应 plan:embedding 泄漏时的通知机制
这些是合规的 baseline——不是 "有就好"、是"必须要"。
新兴威胁:member inference
除了 inversion、还有 member inference——攻击者不恢复原文、只判断"某条数据是否在你的训练集 / 索引里"。对隐私敏感场景同样威胁:
- "某患者是否在这家医院" → 泄漏就业信息
- "某交易是否在数据库" → 泄漏商业信息
防御类似——限制模型输出的信息:
- 检索返回不带具体分数(只给 yes/no)
- 限制查询频率(防拖库)
这是新兴领域
Embedding 隐私是2023+ 的新研究领域——实践标准仍在形成。建议:
- 关注学术进展(arXiv 上的 embedding inversion 研究)
- 跟合规团队同步、不闭门造车
- 按"比当前法规严一点"的标准做——等法规落定时已经合规
对 RAG 工程的启示
别把 embedding 当"看不见的数据"——把它当原文等级的敏感数据对待:
- 存储加密
- 传输加密
- 访问控制严格
- 不必要时不共享
- 审计完整
这些加起来、增加的工程复杂度不大、但避免未来的隐私事故——小投资大回报。
9.16 跨书关联:Embedding 和预训练模型
Embedding 模型通常是预训练 encoder + 对比学习 finetune 的产物。encoder 骨架常用 BERT、RoBERTa、XLM-R、或最近的 Mistral/Gemma 裁剪。训练机制和原 LLM 预训练有重叠——了解 Transformer encoder 的 attention 和 pooling 层有助于理解 embedding 怎么产生。
《Rust 编译器与运行时揭秘》讨论 async 状态机时用的"把任意函数编译成状态机"的思路,在 Embedding 里有平行——把任意文本编码成固定维向量。两者都是"把多样输入压缩到固定形式"的工程抽象,只是一个在编译期、一个在推理期。
9.17 本章小结
- Embedding 把文本编码成向量让语义检索可计算——这是 RAG 检索的核心工具
- 三大训练范式:对比学习(主流)、蒸馏(小模型)、指令微调(LLM 级)
- 选型六维度:语言 / 领域 / 长度 / 维度 / 成本 / license
- Query / Document 非对称编码是必须注意的细节——生产代码严格区分 mode
- 微调 在专业领域值得——LoRA/QLoRA 成本低、效果显著
- 部署靠 动态 batch + prefix cache + 熔断
- 新趋势:Matryoshka / ColBERT v2 / 多向量——分层检索和精排
下一章讲向量索引——拿到几百万向量如何支持毫秒级 ANN 搜索——Flat、HNSW、IVF、PQ 的工程直觉。