原创教学拆解 · 核对:2026-10-11。接着资料搜索系统往下走;本页没有宣称训练出新模型。
用户搜不到一篇相关资料,可能是资料没入库,也可能是切块丢了标题、向量不适合这个领域,或者评估只收了很好搜的题。先把这些情况分开,才知道该修 pipeline 还是训练模型。
1 · 接入:先留下能追溯的记录
文档至少带 document_id、revision、来源、内容时间、到达时间和访问范围。图片还需要处理后的尺寸、processor 版本等信息;行为事件则要有 event ID、request ID 与发生时间。具体保留哪些原文和身份字段,受权限、用途与保留期限约束,不是记录越多越好。
| 情况 | 要处理什么 | 不处理会怎样 |
|---|---|---|
| 同一事件被重试发送 | 按稳定 event ID 幂等去重 | 一次点击被当成多次反馈 |
| 文档更新 | revision 与替换关系 | 搜索结果和引用落到不同版本 |
| 删除或撤回权限 | tombstone / 权限事件及传播确认 | 主库删了,旧索引或 cache 还在返回 |
| 解析失败 | 错误类型、重试与隔离队列 | 静默丢掉某类文档,数据分布改变 |
小项目里一张带版本的表就能做很多事。引入消息队列后,也仍要定义重复、乱序、晚到与重放,不能靠“用了 Kafka”代替数据语义。
2 · 特征:当时真的知道吗?
假设 10:00 要预测用户是否会点开资料。一条行为发生于 09:58,却到 10:02 才进入系统。离线用今天的完整日志回放(replay)时,不能让 10:00 的样本用上这条当时不可用的信息。
下面把时间写成分钟整数,便于手算;真实系统还要统一时区、时钟和晚到规则。示例查询同一用户的一项特征,选择事件时间和可用时间都不晚于查询时刻的最新记录:
def feature_as_of(records, entity, query_time):
eligible = [
record for record in records
if record["entity"] == entity
and record["event_at"] <= query_time
and record["available_at"] <= query_time
]
return max(eligible, key=lambda record: (record["event_at"], record["available_at"]), default=None)
records = [
{"entity": "user-a", "event_at": 590, "available_at": 591, "value": 2},
{"entity": "user-a", "event_at": 598, "available_at": 602, "value": 3},
]
assert feature_as_of(records, "user-a", 600)["value"] == 2
assert feature_as_of(records, "user-a", 603)["value"] == 3
示例假定上游已经去重,且这是单项特征;相同时间的修订、TTL 和缺失策略另需定义。Feast 的 point-in-time join可帮助理解按历史时点取特征,但“当时何时可用”需要数据本身记录并由 pipeline 正确处理,不是打开一个开关就自动解决。
3 · 样本:日志不等于标签
用户没点一篇文章,可能没看到、标题没吸引他、已经知道答案,也可能确实不相关。负例怎么构建,要配合曝光(exposure)、位置、观察窗口和任务定义。没有曝光的候选(candidate)与展示后明确拒绝的候选,不宜直接混成同一种负例。
| 样本类型 | 能提供什么信息 | 仍然缺什么 |
|---|---|---|
| 点击与停留 | 对展示内容的行为反应 | 是否真正解决问题;位置与展示偏差 |
| 人工相关性标注 | 按 rubric 判断 query–document | 标注一致性与覆盖范围 |
| 合成 query / teacher 分数 | 扩大候选监督 | teacher 偏差、数据重复、分数校准(calibration) |
| 明确“不相关”反馈 | 相对清楚的拒绝信号 | 用户是否误操作,反馈是否晚到 |
对训练、开发和测试按文档来源、时间或 query family 做合适隔离。同一 PDF 的相邻段落随机分到两边,可能只是测“记没记住文档”。去重后的样本量、标签比例、缺失情况和来源变化,应该成为每次数据交付的一部分。
4 · 模型:先做能定位问题的 baseline
先检查关键词检索(retrieval),再加 dense retrieval;两者的错误可能互补。带截图的问题可以试 SigLIP 这类图文 encoder,但不是把图片向量硬塞进任意文本向量空间。各路先产生候选,再以明确方法融合或重排。
训练 retriever 时,检查 positive、negative 与 false negative;训练引用行为的 SFT,则检查 chat template、target mask 和有效 token 权重。它们不是同一种监督。分别见双塔和数据到 batch。
GPU 放不下时,先把权重、optimizer、激活和临时内存分别列出来,再比较 DDP / FSDP2 / ZeRO。扩卡之前,用固定小 batch 比较 loss 与一次更新;扩卡之后,再检查数据重复、分母和梯度累积。先把错的目标跑得更快,最后只会更快得到一个不合适的模型。
5 · Checkpoint:继续训练和上线不是一回事
| 用途 | 要保存 / 关联的内容 | 验收动作 |
|---|---|---|
| 继续这次训练 | 模型、optimizer、scheduler、RNG、数据游标、更新步、配置与数据版本 | 中断后恢复,比较样本与更新轨迹 |
| 推理部署 | 权重 / adapter、基座、模型配置、tokenizer / processor、模板、精度、运行依赖 | 在实际加载路径做固定输入回归 |
| 复现实验判断 | 代码 revision、环境、数据划分、指标定义、选择 checkpoint 的依据 | 别人能重跑同一项比较 |
训练 checkpoint 可能是分片(sharding)文件,推理引擎(inference engine)未必能直接加载;adapter 也要匹配基座。导出、量化或合并以后,要重新验收实际部署产物,不能只引用训练时那份分数。更细的恢复测试见保存与恢复。
6 · 索引:向量对得上版本吗?
假设旧 encoder A 和新 encoder B 都输出 768 维。维度相同,只说明形状能接上,不说明坐标含义相同。用 B 编 query、拿 A 的文档向量做 ANN,可能完全没有预期的匹配关系。
因此一次发布要关联模型与 index:
{
"release": "search-demo-v2",
"query_encoder": "encoder-b",
"document_encoder": "encoder-b",
"processor": "processor-b",
"index": "index-b-snapshot-17",
"document_snapshot": "documents-17",
"ranking_config": "ranking-4",
"evaluation_report": "eval-search-demo-v2"
}
这是教学 manifest,不是任何 registry 的标准配置;实际版本应能解析到不可变 revision 或内容 hash。某些双塔(dual-encoder)本来就使用不同的 query / document encoders,这时记录的是经过共同训练或验证的兼容组合,不是强制两边文件相同。
重建可以在旁路完成,抽查覆盖率(coverage)、删除、权限与检索质量,再切换读流量。回滚时把兼容组合一起退回;只退 query encoder,往往是不完整的回滚。
7 · 评估:模型分数和系统结果分别看
固定 query 和候选资格规则,再分开检查:相关文档有没有入库、是否被召回、是否被排到前面、摘要是否用对证据。只给最后一个答案分数,没法判断该修哪个模块。
| 层次 | 最小检查 | 不该怎么解释 |
|---|---|---|
| 数据 / index | 覆盖率、新鲜度(freshness)、删除与权限 | 覆盖更多不等于相关性更高 |
| 召回 | 固定预算下的 Recall@k,按语言、query 类型切片(slice) | 不能靠无限增大 k 获得免费收益 |
| 排序 | NDCG 等排序指标、失败案例 | 标签和候选池变了,分数不可直接横比 |
| 生成 | 支持性、引用、拒答与任务完成 | 格式正确不等于答案有依据 |
| 成本 | 延迟、显存、编码量、训练和索引更新成本 | 不只数一次模型 forward |
先约定发布门槛,再看结果;测试集留给最终比较。样本小就报告不确定性与失败明细,不把多答对一道包装成稳定提升。下一篇继续看如何上线、监控与回流。
本页术语 11
- 回放 replay
- 用固定输入重新执行一段数据或模型链路,检查结果是否一致
- 曝光 exposure
- 内容真的被展示给用户这件事,和「被检索到」不是一回事
- 候选 candidate
- 召回阶段捞出来、等待精排挑选的内容
- 校准 calibration
- 模型给出的置信度是否等于真实正确率
- 检索 retrieval
- 从外部语料里找出相关内容
- 分片 sharding
- 把参数、梯度或优化器状态切开分给多张卡,每张只存其中一份
- 推理引擎 inference engine
- 负责高吞吐生成或模型服务的执行系统
- 双塔 dual-encoder
- 用户和内容各一个编码器,映到同一向量空间靠内积召回
- 覆盖率 coverage
- 结果覆盖了多少目标内容或类别,计算前需要说清范围和分母
- 新鲜度 freshness
- 内容产生到可被检索之间的延迟
- 切片 slice
- 按内容或用户属性划分的子群体,用来检查平均值掩盖了什么