Commit 9de8092d 9de8092d5982e71e52d18fd3247b5eb11d2ca4eb by cnb.bofCdSsphPA

Freeze the production encoder before scaling the music index

Document the production decision to stabilize the embedding space before onboarding a 300k-song catalog, and record the migration rules for future encoder upgrades.

Constraint: 300k-song production rollout makes embedding churn expensive and risky
Rejected: keep iterating encoder before defining a production embedding version | would force repeated full-vector rebuilds and unstable rollout criteria
Confidence: high
Scope-risk: narrow
Directive: Treat encoder changes as versioned index migrations, not in-place model swaps
Tested: reviewed rendered markdown content, docs index link, changelog entry, and git diff for the three touched docs
Not-tested: git push / remote sync outcome depends on repository remote state
1 parent 73d28fae
### Stage: production encoder freeze FAQ and rollout guidance
完成项:
- 新增文档:
- `docs/production-encoder-freeze-and-embedding-strategy.md`
- 文档内容覆盖:
- 为什么当前应先冻结 encoder
- 当前结构的泛化能力边界
- 外置模型权重后如何给其他歌曲直接使用
- wav/mp3/flac/ogg 集合如何快速进入 manifest -> build-index -> evaluate 链路
- 30 万首生产曲库下 embedding/version/index 的治理建议
- encoder 升级后哪些数据必须重建、哪些元数据可以保留
- docs 入口已补充:
- `docs/README.md` 新增该答疑文档链接
结论:
- 当前阶段先冻结 `encoder v1` 是更稳妥的生产决策
- 生产环境应把模型文件、embedding 版本、reference 索引与评测报告解耦管理
- 后续新 encoder 应走“离线 shadow build -> A/B -> 切换”的升级路径,而不是直接覆盖旧 embedding 库
## 2026-06-02 16:11 UTC / hum_guard fresh eval did not beat hum_focus
-`/tmp/dualaxis_sweep/hum_guard/eval.json` 做了最新复核
......
......@@ -91,6 +91,7 @@ cd /workspace/acr-engine
- [数据规范](./dataset-spec.md)
- [开放数据工作流](./open-dataset-workflow.md)
- [训练数据与 pgvector 指南](./training-data-and-pgvector-guide.md)
- [生产 Encoder 冻结与 Embedding 策略答疑](./production-encoder-freeze-and-embedding-strategy.md)
- [数据来源与接入](./dataset-sources-and-licensing.md)
- [工业评测规范](./industrial-benchmark-spec.md)
......
# Production Encoder Freeze & Embedding Strategy / 生产 Encoder 冻结与 Embedding 策略答疑
> 更新:2026-06-03
> 关联文档:[持续开发交接文档](./session-handoff.md) · [训练数据与 pgvector 指南](./training-data-and-pgvector-guide.md) · [开放数据工作流](./open-dataset-workflow.md) · [服务接口](./service-api.md)
## 一页结论
围绕你当前最关心的生产问题,可以先压缩成 6 句话:
1. **当前先冻结 encoder 是正确决定。** 对 30 万首生产曲库来说,先稳定 embedding 空间,比继续频繁改模型更重要。
2. **当前结构具备泛化能力。** 线上识别主路径是“固定 encoder 抽 embedding + reference 检索”,不是只能识别训练时见过的 closed-set 分类标签。
3. **模型权重可以外置复用。** 你可以把当前 `best_model.pt` 当成独立 encoder,用在别的 wav/mp3/flac/ogg 歌曲集合上。
4. **如果 encoder 变了,向量库就必须重建。** 歌曲元数据不用重做,但所有旧 embedding / ANN index / pgvector 向量都应视为旧版本。
5. **30 万首场景下必须做 embedding 版本化。** 不建议“直接覆盖旧库”,而应并行维护 `encoder_version=v1/v2/...`
6. **最稳的上线策略是:先冻结 v1,上线使用;新模型只做离线 shadow build + A/B,收益足够大再切换。**
---
## 1. 当前项目进度:已经走到哪里
## 1.1 当前状态
根据 [持续开发交接文档](./session-handoff.md),当前项目已经从“原型是否能跑通”进入“真实数据验证 + 工程化推进”阶段:
- synthetic 数据链路已跑通:生成、训练、建索引、识别、评测都已具备
- 开放数据链路已闭环:`inspect-local -> prepare-local -> validate-local -> train -> build-index -> evaluate -> generate_artifacts`
- 当前最佳候选方向已收敛到 `hum_focus`
- 真实 FMA smoke 已跨过训练,进入了 `build-index` 阶段
这说明当前不是“从零开始想方案”,而是已经具备:
1. **一个可以独立抽 embedding 的 encoder**
2. **一个可以对 reference 曲库建索引的 pipeline**
3. **一个可以对 query 做识别的 hybrid 检索链路**
## 1.2 当前最适合的策略
在这个阶段,最重要的不是继续频繁换 encoder,而是:
```mermaid
flowchart TD
A[已有可运行模型与索引链] --> B[冻结生产 Encoder v1]
B --> C[完成 30 万首建库与使用]
C --> D[收集真实线上/离线问题]
D --> E[离线评估新 Encoder v2]
E --> F[证明显著收益后再迁移]
```
也就是:**先让一版可用的 embedding 体系稳定下来,再做后续升级。**
---
## 2. 当前结构是否有泛化能力
## 2.1 简短回答
**有。**
但这里的“泛化”要分成两层理解:
| 泛化层次 | 当前是否支持 | 说明 |
|---|---|---|
| 新歌曲入库后可被识别 | 支持 | 只要用同一 encoder 生成 reference embedding 即可 |
| 完全未知分布上保持稳定高精度 | 尚未完全证明 | 当前更多是结构可行、真实大规模效果还需继续验证 |
## 2.2 为什么说当前结构具备泛化能力
当前识别主链路不是:
- “输入 query -> 分类头直接输出某个固定 song class”
而是:
- “输入 query -> encoder 抽 embedding -> 在 reference embedding 库中做相似度检索”
结构图如下:
```mermaid
flowchart LR
Q[query wav/mp3 片段] --> E1[冻结 Encoder v1]
E1 --> QE[query embedding]
QE --> S[similarity search]
R[30 万首 reference 曲库] --> E2[同一个冻结 Encoder v1]
E2 --> RE[reference embeddings]
RE --> S
S --> O[候选歌曲 + 分数]
```
这种结构天然支持:
- 新歌曲加入 reference 曲库
- 不重训模型的情况下扩歌库
- 针对 query 做检索而不是固定类分类
## 2.3 但泛化能力的上限受什么影响
当前真实效果主要还受这些因素制约:
1. **训练数据分布**
- 目前 hard case 已关注 `humming_like` / `confused`
- 但你的线上 30 万首曲库是否和当前训练/验证分布一致,仍需要真实数据验证
2. **reference 建库方式**
- 当前 reference 端是 **5 秒窗口 + 2.5 秒 stride** 的重叠滑窗
- 这对片段识别是好的,但代价是窗口数上升、建库成本变高
3. **query 形式差异**
- 无损整曲、压缩 mp3、录音片段、截短片段、混响/手机采样等都会影响效果
4. **混合检索策略**
- 当前不是纯 embedding 检索,而是 `chromaprint + embedding + melody rerank` 的 hybrid
- 这对鲁棒性是加分项,但也意味着线上治理时要同时考虑指纹索引和向量索引
---
## 3. 为什么当前先冻结 Encoder
## 3.1 业务原因
你现在的生产环境里有 **30 万首歌曲**。这会带来一个决定性的工程事实:
> **一旦 encoder 改了,就不是“换个模型文件”这么简单,而是整套向量空间都可能变。**
如果还没冻结 encoder,就会出现这些问题:
- 今天建的 30 万 embedding,明天可能全部作废
- pgvector / Faiss / Milvus / 自研 ANN 索引都要全量重建
- 离线评测和线上灰度无法稳定对齐
- 回滚困难:你很难知道 query 是按哪个模型算的,reference 又是按哪个模型建的
## 3.2 工程原因
冻结 encoder 后,你才能稳定以下这些对象:
| 资产 | 是否应随 encoder 冻结 | 原因 |
|---|---|---|
| `best_model.pt` | 是 | 它决定向量空间 |
| `n_mels/sample_rate/window/stride` | 是 | 这些决定 embedding 分布 |
| `embedding_dim` | 是 | 向量表结构和索引依赖它 |
| reference embeddings | 是 | 必须和 query embedding 同版本 |
| ANN index / pgvector 索引 | 是 | 建立在具体 embedding 空间上 |
## 3.3 当前建议的冻结定义
建议把当前生产 encoder 冻结为一组明确配置,而不是只记一个文件名。
推荐至少记录这些字段:
```yaml
encoder_version: ecapa_hum_focus_v1
checkpoint_path: /abs/path/to/best_model.pt
embedding_dim: 192
sample_rate: 16000
n_mels: 128
n_fft: 512
hop_length: 160
reference_window_sec: 5.0
reference_stride_sec: 2.5
query_runtime_window_sec: 5.0
feature_family: mel
index_family: hybrid_chromaprint_ecapa
status: frozen_for_production
```
---
## 4. 模型权重能否外置,给其他歌曲使用
## 4.1 简短回答
**可以,而且应该这样做。**
你可以把当前冻结好的 `best_model.pt` 视为一个独立的音乐 embedding encoder,用来处理:
- 新增歌曲入库
- 旧歌库全量建 embedding
- 任意 query 音频片段识别
- 后续 pgvector / ANN 向量服务对接
## 4.2 外置使用时的推荐组织方式
推荐把“模型”和“索引产物”分开管理:
```mermaid
flowchart TD
A[models/] --> A1[ecapa_hum_focus_v1/best_model.pt]
A --> A2[ecapa_hum_focus_v1/encoder_manifest.yaml]
B[indexes/] --> B1[ecapa_hum_focus_v1/chromaprint.pkl]
B --> B2[ecapa_hum_focus_v1/reference_embs.npy]
B --> B3[ecapa_hum_focus_v1/reference_ids.npy]
B --> B4[ecapa_hum_focus_v1/index_metadata.json]
C[metadata/] --> C1[song_catalog.csv]
C --> C2[manifests/catalog.json]
```
## 4.3 外置后能做什么
### 场景 A:直接给新歌曲建库
- 你有一批新歌(wav/mp3/flac/ogg)
- 不重训
- 用冻结 encoder 直接建 reference embedding
- 加入曲库识别
### 场景 B:给 query 片段做识别
- 你有 5~10 秒左右的查询片段
- 用同一 encoder 抽 query embedding
- 在 reference 库做相似度匹配
### 场景 C:离线批量生成 pgvector/ANN 数据
- 你可以把 reference embeddings 作为离线产物导出
- 再灌入 PostgreSQL + pgvector / Faiss / Milvus / 自研检索服务
---
## 5. 你手头有无损、压缩、片段等 wav/mp3 文件集合,应该怎么直接使用
## 5.1 总体原则
你手头的文件不需要先人工区分“是不是训练专用格式”。
当前最重要的是把它们统一进入这套结构:
```mermaid
flowchart LR
A[原始 wav/mp3/flac/ogg] --> B[标准化目录]
B --> C[manifest]
C --> D[reference 建库]
C --> E[query 验证]
D --> F[线上识别/检索]
```
## 5.2 建议先分三类素材
| 类别 | 作用 | 建议处理方式 |
|---|---|---|
| 完整歌曲 / 主版本 | 做 reference | 全部入库 |
| 截断片段 / 业务 query 样本 | 做评测 query | 固定留作测试集 |
| 低码率/手机录音/混响压缩片段 | 做 hard case query | 用来验证鲁棒性 |
## 5.3 最短直接使用流程
### 第 1 步:冻结 encoder v1
先不要继续换模型,先把当前决定好的 checkpoint 固定下来。
### 第 2 步:准备音频目录
例如:
```text
input_music/
song_a.flac
song_b.mp3
song_c.wav
...
```
### 第 3 步:检查目录是否适合进入当前链路
```bash
cd /root/vprecog/acr-engine
/usr/local/miniconda3/bin/python src/data/manifest_tools.py inspect-audio-dir \
/abs/path/to/input_music \
--query-duration 8.0 \
--eval-ratio 0.2
```
### 第 4 步:生成统一 manifest
```bash
cd /root/vprecog/acr-engine
/usr/local/miniconda3/bin/python src/data/manifest_tools.py audio-dir-to-splits \
/abs/path/to/input_music \
/abs/path/to/output_dataset \
--source-dataset prod_music \
--eval-ratio 0.2 \
--query-duration 8.0 \
--query-strategy hybrid \
--query-stride 2.5
```
建议说明:
- `query-duration=8.0`:适合作为线下验证 query 长度
- `query-strategy=hybrid`:更贴近当前项目已有的音乐感知切片方向
- `query-stride=2.5`:如果你希望验证覆盖率更高,可以生成更多 query
### 第 5 步:用冻结 encoder 建 reference index
```bash
cd /root/vprecog/acr-engine
/usr/local/miniconda3/bin/python run_demo.py build-index \
--data /abs/path/to/output_dataset/manifests \
--model /abs/path/to/frozen/best_model.pt \
--output /abs/path/to/output_index \
--device cpu
```
### 第 6 步:做离线识别验证
```bash
cd /root/vprecog/acr-engine
/usr/local/miniconda3/bin/python evaluate.py \
--data /abs/path/to/output_dataset/manifests \
--model /abs/path/to/frozen/best_model.pt \
--index-prefix /abs/path/to/output_index/reference \
--split test \
--device cpu \
--fast-eval \
--output-json /abs/path/to/output_reports/eval.json
```
### 第 7 步:确认后再推到生产向量库
当离线评测满足最低要求后,再把 reference embedding 导入生产检索系统,而不是一上来直接刷 30 万首正式库。
---
## 6. 如果要快速微调,应该怎么做
## 6.1 原则
**“快速微调”不等于“马上用 30 万首全量重训”。**
对你当前场景,最合理的是:
1. 先冻结生产 encoder v1
2. 用一个代表性子集训练/微调候选 v2
3. 只在离线环境评估 v2
4. 证明收益大于迁移成本,才考虑升级生产 encoder
## 6.2 推荐的微调子集组成
建议优先抽一个 **几千到几万首规模的代表性集合**,覆盖:
- 无损高质量版本
- 常见压缩版本(mp3/aac 等)
- 短片段
- 开头/中段/结尾片段
- 旋律近似、编曲相似的易混淆歌曲
- 手机采样 / 录屏 / 二次压缩音频
- 业务上最常见失败样本
## 6.3 推荐微调流程
```mermaid
flowchart TD
A[冻结 Encoder v1] --> B[抽代表性子集]
B --> C[生成 manifests]
C --> D[训练 v2 候选]
D --> E[建 v2 索引]
E --> F[固定测试集评测]
F --> G{收益显著?}
G -- 否 --> H[继续使用 v1]
G -- 是 --> I[准备 30 万首离线重刷 v2]
```
## 6.4 微调时的判断标准
不要只看训练 loss,至少要看:
| 指标 | 作用 |
|---|---|
| top1 / topk | 基本识别率 |
| hard-case 命中率 | 看压缩/片段/混淆样本提升是否真实 |
| 新增模型对旧强项的回退 | 防止“补一个洞,漏一大片” |
| 全量建库速度 | 看新模型是否导致生产成本显著上升 |
| 线上 query 延迟 | 看推理成本是否可接受 |
## 6.5 微调后的发布原则
微调完成后不要立刻替换 v1,而要:
1. 标记为 `encoder_version=v2_candidate`
2. 只做离线建库和评测
3. 在真实样本上和 v1 做 A/B
4. 显著更优后再升级
---
## 7. 如果 embedding 变了,哪些数据必须重建
## 7.1 要区分“元数据”和“向量数据”
| 数据类型 | 是否需要重建 | 说明 |
|---|---|---|
| 歌曲主数据(song_id/路径/业务标签) | 通常不需要 | 这些不依赖向量空间 |
| manifest | 通常不需要全重做 | 除非你的切片策略/数据治理规则也变了 |
| reference embeddings | 必须重建 | 因为 encoder 变了 |
| query embeddings 缓存 | 必须重建 | 否则和 reference 不同空间 |
| pgvector 行数据 | 必须重建 | 向量本体变了 |
| ANN 索引(Faiss/Milvus/HNSW/IVF 等) | 必须重建 | 建立在旧向量之上 |
| chromaprint 索引 | 不一定 | 只要指纹算法不变,可独立复用 |
## 7.2 为什么必须重建
因为向量相似度检索默认假设:
> query embedding 和 reference embedding 来自同一个特征空间。
如果 query 用的是 `encoder v2`,reference 还停留在 `encoder v1`,就会出现:
- 分数不可比
- recall 明显下降
- 线上结果随机波动
- A/B 结论失真
## 7.3 推荐的迁移策略
不要“原地覆盖旧 embedding”,而应采用双版本并行:
```mermaid
flowchart LR
A[Encoder v1] --> B[index_v1]
C[Encoder v2] --> D[index_v2]
E[Query] --> F{使用哪个版本?}
F --> B
F --> D
```
推荐步骤:
1. 保留 `v1` 生产库不动
2. 离线刷 `v2` 的 30 万 embedding
3.`v2` 的 ANN / pgvector 索引
4. 用相同 query 集对比 v1/v2
5. 确认收益后切主流量
6. 回滚时直接切回 v1
---
## 8. 30 万首生产环境推荐的版本化方案
## 8.1 推荐最小字段
### 歌曲元数据表
| 字段 | 说明 |
|---|---|
| `song_id` | 稳定歌曲 ID |
| `audio_uri` | 原始音频路径/对象存储地址 |
| `duration_sec` | 时长 |
| `codec/container` | 格式信息 |
| `catalog_status` | 是否可入 reference 库 |
| `business_tags` | 业务标签 |
### embedding 表
| 字段 | 说明 |
|---|---|
| `song_id` | 对应歌曲 |
| `encoder_version` | 如 `ecapa_hum_focus_v1` |
| `window_index` | 第几个 reference window |
| `offset_sec` | 窗口起点 |
| `embedding_dim` | 例如 192 |
| `embedding` | 向量本体 |
| `built_at` | 构建时间 |
| `source_audio_hash` | 原音频指纹/摘要,便于查重与失效控制 |
### index manifest
| 字段 | 说明 |
|---|---|
| `encoder_version` | 当前索引对应的 encoder |
| `checkpoint_path` | 模型文件 |
| `feature_config` | mel/n_mels/sample_rate 等 |
| `reference_window_sec` | 例如 5.0 |
| `reference_stride_sec` | 例如 2.5 |
| `catalog_size` | 曲库规模 |
| `num_reference_windows` | 总窗口数 |
| `built_at` | 构建时间 |
## 8.2 推荐目录结构
```text
prod_artifacts/
models/
ecapa_hum_focus_v1/
best_model.pt
encoder_manifest.yaml
indexes/
ecapa_hum_focus_v1/
chromaprint.pkl
chromaprint_progress.json
reference_embs.npy
reference_ids.npy
index_metadata.json
reports/
ecapa_hum_focus_v1/
eval.json
hard_case_eval.json
```
---
## 9. 当前建议的生产操作手册
## 9.1 目标
当前目标不是继续改模型,而是先完成:
1. 冻结 encoder v1
2. 用 v1 支撑第一版 30 万首曲库建库
3. 建立版本化规范
4. 为后续 v2 升级预留迁移机制
## 9.2 分步操作
### Phase 1:冻结与归档
- [ ] 选定当前生产 checkpoint
- [ ] 为该 checkpoint 生成 `encoder_manifest.yaml`
- [ ] 记录 `encoder_version`
- [ ] 固定 reference window / stride / mel 配置
- [ ] 禁止直接覆盖此 checkpoint
### Phase 2:小规模真实集验证
- [ ] 抽 1k~10k 首真实歌曲做第一次建库
- [ ] 抽真实 query 集做评测
- [ ] 统计 top1/topk/hard-case 结果
- [ ] 验证索引构建速度、磁盘占用、查询延迟
### Phase 3:30 万首全量离线建库
- [ ] 清洗 song_id 与元数据
- [ ] 生成标准 catalog/manifests
- [ ] 用冻结 encoder v1 批量生成 reference embeddings
- [ ] 生成 chromaprint / 向量索引
- [ ] 导入生产检索服务
### Phase 4:上线与观测
- [ ] 上线 v1
- [ ] 记录 query 失败样本
- [ ] 归档 hard-case
- [ ] 为 v2 微调准备离线样本集
### Phase 5:未来升级
- [ ] 训练 `v2_candidate`
- [ ] 离线全量重刷 `index_v2`
- [ ] 做离线 A/B
- [ ] 收益显著后再切换主版本
---
## 10. 常见问题 FAQ
## 10.1 我现在有一大堆 wav/mp3,可以直接用吗?
**可以。**
先不要纠结格式本身,先把它们组织成统一音频目录,再生成 manifests,再用冻结 encoder 建 reference index。
## 10.2 无损和压缩版本要不要分开?
**建议保留来源信息,但 reference 主库优先保留“主版本”。**
如果同一首歌有多个编码版本:
- 主版本作为 reference 主资产
- 其他压缩版本优先作为评测 query / hard case
这样更利于真实评估鲁棒性。
## 10.3 片段文件可以直接拿来做 query 吗?
**可以。**
尤其适合作为:
- clean query
- compressed query
- hard case query
但如果要把它当训练样本,最好仍然能回溯到稳定的 `song_id` 和原 reference。
## 10.4 如果后面发现 encoder 不够好怎么办?
不要直接替换现网。正确做法是:
1. 保持 v1 不动
2. 离线训练 v2
3. 离线重刷 v2 的全量 embedding
4. 对比 v1/v2
5. 确认收益后再切
## 10.5 现在有没有必要立刻上全量 30 万首?
**建议先做一轮中等规模验证,再上全量。**
推荐先做:
- 1k 首小验证
- 1w~5w 首中等规模验证
- 验证速度/精度/存储后,再上 30 万
## 10.6 现阶段最重要的一句话建议是什么?
> **先冻结 encoder v1,把 embedding/version/index 治理做好,再讨论 v2。**
---
## 11. 最终建议
对你现在的阶段,我的建议优先级是:
1. **冻结当前 encoder**
2. **建立 embedding 版本化规范**
3. **先做小到中规模真实集建库验证**
4. **再推进 30 万首全量建库**
5. **把新模型升级变成“离线重刷 + A/B + 切换”的标准动作**
这样做的好处是:
- 你现在就能开始用
- 后面也不会因为继续调模型把生产库拖乱
- 未来升级有明确路径,不会出现“模型变了,数据全乱了”的问题
## Sources
- [持续开发交接文档](./session-handoff.md)
- [训练数据与 pgvector 指南](./training-data-and-pgvector-guide.md)
- [开放数据工作流](./open-dataset-workflow.md)
- [服务接口](./service-api.md)
- [acr-engine/train.py](../acr-engine/train.py)
- [acr-engine/run_demo.py](../acr-engine/run_demo.py)
- [acr-engine/src/engines/ecapa_embedder.py](../acr-engine/src/engines/ecapa_embedder.py)
- [acr-engine/src/data/manifest_tools.py](../acr-engine/src/data/manifest_tools.py)