重建索引与向量模型迁移。
向量模型就是一份 schema,而且是你技术栈里唯一一份没有原地迁移路径的 schema:两个模型产出的向量身处不同的空间,所以没有部分切换、没有双读、没有百分之五的灰度——你要么拥有一个完整的第二套索引,要么什么都没有。仅这一条性质,就作废了你团队已经熟悉的每一种发布模式;也正因如此,第一次向量模型迁移通常就是那次发现“检索流水线从一开始就不可复现”的迁移。
为什么这和换一个生成模型不是一回事。
换掉那个写答案的模型是一次发布:把百分之一的流量导过去、对比、放量或回滚,而两个版本能和平共存,因为它们吃的是同样的输入。模型弃用与迁移讲的是那种情形,而那里的经验一条也搬不过来。
向量模型定义的是一套坐标系。模型 A 产出的向量与模型 B 产出的向量不可比、也不近似可比,更不是用你一个下午拟合出来的投影就能修好的——在一个混合索引上做最近邻检索,返回的是笃定的胡话而不是一个错误,那是所有失败模式里最糟的一种。所以这次迁移只有一种形状:建一套完整的第二索引,离线评估,然后原子切换。
这就逼出三件多数团队没有编入预算的事:
- 整个期间的双倍存储与双倍写入容量,因为两套索引必须同时存在。如果新模型维度更大,第二套索引的每个向量也更大;而维度对内存、索引构建时间与成本的驱动,比语料规模还要直接。
- 是一次切换,不是一次放量。爆炸半径是所有依赖检索的功能,一次全中。你可以对新索引做影子读并做对比,但你不能让一半用户走这边、一半走那边,然后管它叫灰度——那两拨人拿到的不是同一个系统的两个变体,而是两个不同的系统。
- 回滚只有在你留着旧索引时才免费。切换后第二天就把它删掉是个错误,因为真正要紧的回归出现在查询分布的长尾上,要好几天才浮出来。留着它,直到一个你事先定好的评估窗口结束。
触发时机通常不由你决定。
团队规划的是自愿那种情形——出了个更好的模型、检索质量能提升、你选择搬家。而现实中日程由别人定,而且历史上给的通知窗口是以月计而不是以年计:
- 供应商下线了那个端点,或者把它挪进“遗留”状态:还能应答,但不再改进,最终不再支持。你现在是在按他们的日程迁移。
- 你的切块或解析变了。换了文档解析器、改了块大小、加了元数据——这些都改变了被嵌入的内容,也就意味着索引与那段查询它的代码不再匹配。这是人们压根不会认作“迁移”的那个触发因素,也是最常见的一个。
- 合规把数据挪走了。一条数据驻留要求或者一个自托管决定,意味着换一个模型,因为原来那个在该地区或私有化环境里没有。
- 语料变了形状。你从英文帮助文档起步,如今索引里有了代码、通话转写和另外三种语言。当初对的那个模型,如今不再是对的那个。
由此得出的真正运维要求,并不是“要有一份迁移方案”,而是:能够按需、不做考古地从源头重建索引。如果一次完整重建是一个牵涉到某位已离职同事的两周项目,那你拥有的不是一个检索系统,而是一件出土文物——而当那封弃用邮件到来时,你照样得做这次重建,只是在时间压力之下、判断力更差。
重建出来的绝不是“同一份语料配新向量”,而这毁掉了你的归因能力。
下面这个失败,才是真正让团队赔掉一个季度的那个。你换了向量模型、重建、评估,然后质量掉了。你据此断定新模型更差。它通常并不更差。
在第一套索引建成的那天和你重建的这天之间,流水线漂移了。切块器被调过。HTML 清洗学会了丢掉导航栏。有人给每个块加了标题前缀以改善上下文。三百份文档在源头被删了,却还躺在旧索引里,因为从来没有任何东西把删除传播下去。旧索引是由一段已经不存在的代码,基于一份已经不存在的语料快照建出来的——所以一次重建同时改掉了模型和切块和清洗和文档集合,于是那个质量差值没法归因给其中任何一项。
防住这件事的纪律很普通,也不光鲜:
- 给流水线打版本,而不只是给模型打版本。解析器版本、切块器版本与参数、清洗规则、元数据 schema、模型——合成一个标识,盖在每一个向量上。如果同一套索引里两个向量带着不同的流水线版本,那这套索引早就不一致了,而你并不知道。
- 留着原始源文件,而不只是留着块。你没法从块里重新切块。重新嵌入需要原始文档,这意味着源语料的留存是一项硬性运维依赖,不是锦上添花——它约束的那些解析决策在面向 RAG 的文档解析里。
- 每次重建只改一个变量。如果你在迁模型,那就让切块保持一模一样,哪怕你明知它是错的。切块的问题留到之后单独重建一次去修。两次重建花的是算力;一次无法归因的回归花掉的是一个月的争论。
- 把重建当例行演练。每季度从源头重建一次到一个临时索引,再和生产环境做 diff。凡是有差异的地方,都是你此前不知道的漂移。这与可复现性对“运行”提出的论证是同一条,只是用在了索引上。
成本与时间由语料主导,而且你会付不止一次。
人们写下来的估算,是按供应商每 token 嵌入单价对语料跑一遍。真实账单还有另外四项,而最后一项才是疼的:
- 切块膨胀。你不是把语料嵌入一遍;你嵌入的是每一个块,而重叠意味着 token 总量会超过语料 token 数,常常超出不少。
- 抽取,而不只是嵌入。如果你的流水线要跑模型来解析 PDF、给块写摘要、或者为图抽取实体,那通常才是更大的那一笔——嵌入本身在现代推理里属于便宜的那一端。
- 索引构建与存储。在几千万条向量上构建向量索引是一个实打实的计算任务,有实打实的墙钟时间;而正是这一项决定了切换是一个下午还是一个周末。
- 重跑。第一次重建要么跑到一半挂掉、要么跑完了评估很难看、要么暴露出切块的改动混了进来。按三遍编预算,不是一遍。一条不能从检查点续跑的流水线,会把每一次失败都变成一次全额重付。
有两根杠杆能实质改变这道算术。批量推理端点比同步端点明显便宜,而一次批量重新嵌入正是理想的批处理负载——没有用户在等。另外,如果你的模型支持维度截断,更短的向量能砍掉存储与索引构建时间;但要在你自己的评测集上验证质量代价,而不是照搬公布的数字,因为这个损失是随语料而变的。把两者都并进预测里,见智能体支出预测。
智能体记忆没有安静的时刻,所以迁移必须是双写。
文档语料可以从一份快照重建:冻结、重建、切换,接受几小时的更新排在后面。智能体记忆不给你这个窗口。它在每一轮、由每一个会话、持续不断地被写入,而且这些写入不是只追加的——它们会合并、作废、改写既有条目。基于快照的重建,从快照之后的第一轮起就开始变陈旧。
行得通的模式,就是任何在线数据库迁移里的那一套,稍作调整:
- 从迁移开始的那一刻起就双写。每一条新记忆都同时写入新旧两套索引、落在两个向量空间里。这就是全部诀窍,代价是整个期间每次写入多一次嵌入调用。
- 在后台回填历史,从最旧的开始,带检查点,让失败能续跑而不是重来。
- 在回填被确认完成之前一直读旧的,然后把读切过去,再在回滚窗口关闭之后停掉双写。三个独立决策、三次独立发布——把它们压成一次,正是丢数据的地方。
- 切换前先对账。计数并抽查:旧索引里的每一条,新索引里都有对应的吗?回填过程中的静默丢失很常见也很隐蔽,因为一条检索不到的记忆,看起来和一条智能体从未有过的记忆一模一样。
按用户切分的记忆让这件事更容易而不是更难:一个租户一个租户地迁,出错的爆炸半径就是一个客户。只要架构允许,就选这条路,见智能体的多租户。
用一套在开工前就冻结好的评测集来决定要不要切换。
这次迁移会产出一个质量数字,而“要不要上”的争论是打不赢的——除非判据是事先写下来的。在重建开工之前就把评测集和阈值定死,因为一旦开工,你之后的推理都会被沉没算力牵着走。
- 用一套从生产流量里冻结出来的查询集,包含长尾——那些罕见的、冗长的、多语言的、拼错的查询。检索回归恰恰集中在那里,而一套精心整理的规范问句只会给你看一个平局。构造方法在评估 RAG里。
- 直接给检索打分,而不是给端到端答案打分。对着已知相关文档算 recall@k,能把真正变化的那一环隔离出来。一个擅长从平庸上下文里挽救局面的生成模型,会一直掩盖一次真实的检索回归——直到你把生成模型也换掉的那天。
- 按分段报告,别报聚合值。向量模型迁移的典型结果是“整体更好、代码更差”或者“英文更好、其余全差”。一个聚合层面的改进,可以藏住一个大幅变差的分段,而那个分段是有用户的。
- 切换前先做一周影子读。在真实流量上同时查两套索引、把两组结果都记下来、哪一组都不上线。这是这次迁移所能允许的、最接近灰度的东西,代价是每次请求多一次查询——怎么读这份对比,见质量回归检测。
在你需要它之前就该建好的那一件事:把“从源头重建整套索引”做成一个例行的、带检查点的、一条命令的任务,并且不管你迁不迁移都按计划跑它。向量模型迁移里每一个难的部分——双倍存储、原子切换、双写、评估——都是可解的工程问题。真正把它变成一个季度级项目的,是发现没人能复现出当前那套索引:切块器变过了、源快照没了、质量差值归不了因。一个每季度重建一次的团队,早已把这笔代价拆成小块付掉了,因而可以把供应商弃用当成一项排期任务来处理。而一个从没重建过的团队,会在供应商的时间表上发现:自家的检索质量原来是一场没人能复现的意外。