Hugging Face 教程展示如何在一天内快速构建任意领域的嵌入模型。适合程序员直接应用到实际项目,降低 AI 集成成本。
只需一块 GPU、不到一天的训练时间,你就可以把一个通用嵌入模型转变为真正理解你所在领域的模型,而且无须手动标注。为了帮助你快速上手,我们还发布了一个可直接使用的合成训练数据集,该数据集采用这套完整流程,基于 NVIDIA 的公开文档生成。使用这些数据和本教程的方法后,我们观察到 Recall@10 和 NDCG@10 均提升了 10% 以上。Atlassian 使用这套方法在其 JIRA 数据集上进行微调,将 Recall@60 从 0.751 提升至 0.951,增幅达到 26%,而且仅使用了一块 GPU。
🔗 数据集与代码快速链接:
基于 NVIDIA 公开文档生成的合成数据集
🧑💻 本教程集成的开源项目:
NeMo Data Designer:用于生成合成数据
NeMo Automodel:用于训练嵌入模型
BEIR:用于信息检索评估
NeMo Export-Deploy:用于 ONNX/TensorRT 转换
NVIDIA NIM:用于提供生产环境推理服务
一个包含领域文档的目录(文本文件,如 .txt、.md 或类似格式)
一个有效的 NVIDIA API 密钥(可在 build.nvidia.com 免费获取)
一块显存至少为 80GB 的 NVIDIA Ampere 或更新架构 GPU(Compute Capability >= 8.0)。本教程已在 1xA100(80GB)和 1xH100(80GB)上完成测试
本教程已在 1xA100(80GB)和 1xH100(80GB)上完成测试
阅读完本文后,你将学会:📄 如何在没有标注数据的情况下从领域文档生成训练数据🎯 如何使用困难负样本挖掘进行有效的对比学习🔗 如何通过多跳问题提升嵌入质量⚙️ 如何微调双编码器嵌入模型📊 如何评估微调是否改善了检索效果🚀 如何在你的流水线中部署微调后的模型
在本教程中,我们将微调基础模型 Llama-Nemotron-Embed-1B-v2——这是一个拥有 10 亿参数的嵌入模型,在模型质量与推理成本之间实现了平衡。要开始使用,请按照此设置指南操作。
📚 第 1 步:从文档生成训练数据
微调嵌入模型需要数千组(查询,相关文档)数据对。大多数用例并没有现成的此类数据。手动创建这些数据不仅成本高、速度慢,而且往往会受到标注者对“相关性”的个人理解影响。你可以使用 LLM(nvidia/nemotron-3-nano-30b-a3b)读取文档并自动生成高质量的合成问答对,无须手动标注数据。
nemotron embed sdg -c default corpus_dir=./data/my_domain_docs
在底层,该命令会运行一个由 NeMo Data Designer 驱动的四阶段合成数据生成(SDG)流水线:
输出是什么样的?
源文档片段:
H100 GPU 在 SXM 外形规格下的热设计功耗(TDP)为 700W。在持续工作负载下,冷却方案必须将结温维持在 83°C 以下。对于每个节点部署超过 4 块 GPU 的高密度场景,建议采用液冷,因为标准 2U 机箱中的风冷无法耗散足够的热量。
{
"question": "What cooling approach is recommended when deploying more than 4 H100 GPUs per server node?",
"answer": "Liquid cooling is recommended for dense deployments exceeding 4 GPUs per node, as air cooling cannot dissipate sufficient heat in standard 2U chassis configurations.",
"query_type": "contextual",
"reasoning_type": "factual",
"question_complexity": 3,
"segment_ids": [1],
"quality_score": 8.5
}
{
"question": "How does the 700W TDP of the H100 SXM constrain the choice between air and liquid cooling in multi-GPU configurations?",
"answer": "The 700W TDP generates substantial heat that must be dissipated to keep junction temperatures below 83°C. In dense configurations exceeding 4 GPUs per node, air cooling in standard 2U chassis cannot handle this thermal load, making liquid cooling necessary.",
"query_type": "multi_hop",
"reasoning_type": "causal",
"question_complexity": 4,
"segment_ids": [1, 2],
"hop_count": 2,
"quality_score": 9.0
}
请注意其中的区别:第一个问题只是简单的事实查询;第二个问题则需要进行多跳因果推理。该流水线可以生成这两类问题,并支持配置复杂度等级(2~5)和跳数(1~3)。随后,每个问答对都会接受质量评估,获得相关性、准确性、上下文支持度和清晰度等分项评分,以及一个综合评分。只有达到阈值的问答对才会被纳入训练。
⛏️ 第 2 步:挖掘困难负样本(以及它们为何重要)
如果仅使用正样本对(查询 + 正确文档)训练嵌入模型,模型会学会区分明显不同的文档,却无法应对困难情况——那些看似相关、实际却不是正确答案的段落。在真实的检索系统中,正是这些差一点命中的文档导致了错误答案。困难负样本挖掘可以找出这些容易混淆的段落,让模型学会区分它们。
nemotron embed prep -c default
上述命令会自动执行三个子步骤:
生成的问答对会被划分为训练集(80%)和测试集(20%)。测试集会被格式化为兼容 BEIR 的基准数据集,以便在第 5 步中进行标准化评估。
流水线使用基础嵌入模型执行以下操作:
对语料库中的每个查询和每个段落进行嵌入。
计算每个查询与所有段落之间的相似度。
屏蔽每个查询已标注的正样本文档。
应用边界过滤器:对于任何非正样本文档,如果其得分高于最低正样本得分的 95%,则将其排除。这个排除区间能够防止产生假负样本——某些未标注段落与正样本过于接近,实际上可能也是相关文档。
从剩余候选项中选取得分最高的 top-k 文档作为困难负样本(默认每个查询选取 5 个)。
最终得到的困难负样本,是与查询最相似、但得分仍安全地低于正样本得分上限的非正样本段落。当前模型认为这些段落高度相关,但它们并不是已标注的答案。
这种方法为何有效:使用简单负样本(完全不相关的段落)进行训练,无法让模型学到任何新东西。使用困难负样本训练,则迫使模型学习所在领域中至关重要的细微差异。例如,在医学语料库中,对于“二型糖尿病患者的二甲双胍剂量”这一问题,困难负样本可能涉及“二甲双胍的副作用”或“一型糖尿病患者的胰岛素剂量”——两者看起来接近,却存在至关重要的区别。95% 的边界上限可以防止挖掘器选择与正样本过于接近的段落,因为这些段落可能本身就是正确答案,只是在 SDG 期间没有被标注。
多跳问题会引用多个正样本文档。例如,“第 3.2 节中的热管理系统与第 5.1 节描述的功率限制有何关系?”这样的问题包含两个正样本段落。
展开操作会为每个(查询,正样本文档)对创建一个训练样本,使对比损失能够独立处理每个正样本。包含两个正样本文档的问题会生成两个训练样本,每个样本使用相同的困难负样本,但正样本不同。
最终输出是一个可直接用于训练的 JSON 文件:
{
"question_id": "q42_0",
"question": "How does the thermal management system in Section 3.2 relate to the power constraints described in Section 5.1?",
"pos_doc": [{"id": "Section 3.2"}],
"neg_doc": [{"id": "d_x7y8z9"}, {"id": "d_m4n5o6"}, {"id": "d_p1q2r3"}, {"id": "d_s4t5u6"}, {"id": "d_v7w8x9"}]
},
{
"question_id": "q42_1",
"question": "How does the thermal management system in Section 3.2 relate to the power constraints described in Section 5.1?",
"pos_doc": [{"id": "Section 5.1"}],
"neg_doc": [{"id": "d_x7y8z9"}, {"id": "d_m4n5o6"}, {"id": "d_p1q2r3"}, {"id": "d_s4t5u6"}, {"id": "d_v7w8x9"}]
}
🔍 第 3 步:理解多跳问题及其改善检索效果的原因
标准的嵌入模型微调会为每个段落生成一个问题,并训练模型将二者匹配起来。这种方法适用于简单的事实查询,但真实用户提出的问题通常较为复杂,可能横跨多个文档或章节。如果模型只接触过单跳训练数据,就很难为这些复杂查询检索出所有相关段落。
SDG 流水线默认生成跳数为 1~3 的问题:
单跳:“H100 SXM 的 TDP 是多少?”——仅凭一个段落即可回答。
双跳:“H100 的 TDP 与高密度部署中的冷却要求有何关系?”——需要关联两个段落中的信息。
三跳:“考虑到 TDP、冷却限制和机架密度上限,在标准数据中心的一排机架中最多可以部署多少块 H100 GPU?”——需要综合三个段落中的信息。
每一跳都通过自己的上下文摘要和段落 ID 跟踪,所以训练数据保留了完整的推理链。展开后(步骤 2c),每个(问题、相关段落)对都变成一个独立的训练信号,教会模型这些段落都与多跳查询相关。
微调后的模型学会检索上下文相关的文档,而不仅仅是词汇相似的文档。
🧠 步骤 4:微调嵌入模型
nemotron embed finetune -c default
对比学习的工作原理
训练使用具有对比损失的双编码器架构。
温度参数 0.02 被设计得很激进,它产生了非常尖锐的概率分布。这之所以有效是因为步骤 2 中的硬负例质量很高:它们是真正令人困惑的段落,模型需要强梯度来学会区分它们。
如果从配置中省略 ckpt_every_steps,检查点频率会自动设置:
映射风格数据集(已知长度):默认每个 epoch 一次。
可迭代数据集(未知长度):默认在训练期间两次。
这意味着你可以从小语料库(50-100 个文档)开始进行快速概念验证,然后稍后扩展,而无需手动调整检查点设置。
📈 步骤 5:衡量改进
微调真的有帮助吗?让我们通过运行标准化评估来找出答案,比较基础模型和微调检查点在保留测试集上的表现:
nemotron embed eval -c default
评估使用 BEIR 框架,并计算 k = 1、5、10 和 100 处的四个标准信息检索指标:
nDCG@k:排名质量 — 最好的文档是否排名最高?
Recall@k:覆盖率 — 相关文档在前 k 名中出现的比例是多少?
Precision@k:准确率 — 前 k 个结果中有多大比例是真正相关的?
MAP@k:所有查询的平均精准率
成功的微调通常会在一天内使 nDCG@10 和 Recall@10 提升约 10%。
使用检索合成 NVDocs 的结果:
📊 对比(基础模型 → 微调模型)
============================================================
NDCG:
NDCG@1: 0.55178 → 0.60796 (+0.05618, +10.2%)
NDCG@5: 0.51894 → 0.57689 (+0.05795, +11.2%)
NDCG@10: 0.55506 → 0.61559 (+0.06053, +10.9%)
NDCG@100: 0.60617 → 0.66567 (+0.05950, +9.8%)
Recall:
Recall@1: 0.28478 → 0.31547 (+0.03069, +10.8%)
Recall@5: 0.54486 → 0.60288 (+0.05802, +10.6%)
Recall@10: 0.62979 → 0.69296 (+0.06317, +10.0%)
Recall@100: 0.81421 → 0.87020 (+0.05599, +6.9%)
如果数字没有改进怎么办?
该管道使迭代变得容易:
SDG 中质量分数低?检查你的文档质量 — 干净、格式良好的文本会产生更好的合成数据。尝试一个更大且更强大的 LLM。
训练数据不足?向你的语料库添加更多文档并重新运行阶段 0。
过拟合?默认的 3 个 epoch 是为小示例数据集校准的;对于大多数真实数据,1-2 个 epoch 就够了。还可以考虑提高质量阈值,以仅保留最佳训练示例。
学习率错误?尝试两倍或一半的默认学习率。
🏆 真实世界结果:Atlassian
这个方案已被 Atlassian 在真实企业数据上验证。他们应用此管道在公共 Jira 数据集上微调 Llama-Nemotron-Embed-1B-v2,使用单个 NVIDIA A100 80GB GPU,按照上面描述的相同阶段进行。
Recall@60 从 0.751 跃升至 0.951 — 增长 26.7%。微调后的模型能在 95.1% 的查询中在前 60 个结果内检索到正确的文档,而基础模型为 75.1%。对于支撑 Jira 搜索的检索系统,这直接意味着为数百万用户提供了更相关的结果。在他们的博客文章《为数百万 Rovo 用户推进语义搜索》中找到更多详情。
🚀 步骤 6:导出和部署
PyTorch 检查点对于评估来说很好,但对于生产来说太慢了。最后两个阶段转换模型并通过 API 提供服务。
导出到 ONNX / TensorRT
nemotron embed export -c default
这将微调检查点导出到 ONNX(opset 17)。可选地,它编译一个 TensorRT 引擎以获得最大推理吞吐量,具有可配置的批大小(1-64)和序列长度(3-256)的优化配置文件:
# 仅 ONNX(在任何地方运行)
nemotron embed export -c default export_to_trt=false
# FP8 量化以进一步加速
nemotron embed export -c default quant_cfg=fp8
使用 NVIDIA NIM 部署
导出的模型部署在 NVIDIA NIM 容器内 — 一个生产就绪的推理微服务,公开一个与 OpenAI 兼容的 /v1/embeddings 端点:
nemotron embed deploy -c default
运行后,任何客户端都可以调用它:
curl -X POST http://localhost:8000/v1/embeddings \
-H "Content-Type: application/json" \
-d '{"input": ["What cooling is needed for 8 H100 GPUs in a 2U chassis?"],
"model": "custom",
"input_type": "query"}'
因为 NIM 提供了与 OpenAI 兼容的 API,你可以将其插入任何使用嵌入 API 格式的现有 RAG 管道中 — 无需代码更改。
验证部署准确性
该管道包括一个 NIM 准确性验证步骤,针对已部署的端点运行相同的 BEIR 评估:
nemotron embed eval -c default eval_nim=true eval_base=false
这会捕捉任何 ONNX/TensorRT 转换导致的精准率损失。公差范围内的指标(@1 为 0.03,@5+ 为 0.01)标记为通过;超出转换噪声的偏差被标记。
将所有部分整合在一起
完整的嵌入微调管道可以用六个命令运行,从原始文档到已部署的模型。
# 1. 从你的文档生成合成训练数据
nemotron embed sdg -c default corpus_dir=./data/my_docs
# 2. 准备训练数据(分割数据、挖掘硬负例、展开)
nemotron embed prep -c default
# 3. 微调嵌入模型
nemotron embed finetune -c default
# 4. 评估基础模型与微调模型
nemotron embed eval -c default
# 5. 导出优化后的模型
nemotron embed export -c default
# 6. 部署模型
nemotron embed deploy -c default
预期时间和资源
总计:不到一天,大部分时间是无需干预的训练。对于小语料库(约 500 个文档),整个管道在约 2-3 小时内完成。
该管道可以端到端运行,但每个阶段也可以根据你的起点独立执行。例如,如果你有原始文档,你可以从合成数据生成(SDG)开始,而已经包含硬负例的数据集可以跳过较早的步骤,直接进行微调。由于每个阶段都使用 JSON、BEIR 和 ONNX 等标准格式,很容易集成自定义组件或在其他工作流中重用中间输出。该方案在运行方式上也很灵活,支持在本地机器、Docker 容器内或基于 Slurm 的集群上执行。
如果你有领域文档和一些时间,你可以今天就生成你的第一批合成训练数据!完整的管道 - 从文档到已部署的、领域自适应的嵌入模型 - 在单个 GPU 上不到一天就能运行。你可以从我们现成的 nvidia/Retrieval-Synthetic-NVDocs-v1 数据集开始立即尝试该管道。让我们知道你构建了什么。如果你觉得有用的话,给 Nemotron、NeMo Data Designer 和 NeMo Automodel 的仓库点星。
本文提到的模型 1
本文提到的数据集 1
更多来自该作者
NVIDIA Cosmos-H-Dreams:将实时生成模拟引入手术机器人
物理 AI 仿真状态:概述
感谢 Nvidia。你能否允许在 SDG 阶段调用"本地"/内部部署的 LLM(OpenAI 兼容端点),即使这不是默认选项?自托管微调嵌入模型的关键点,除了准确性之外,是公司的数据永远不离开公司场所。谢谢
感谢反馈。我们将在方案的下一个版本中添加支持。
· 注册或登录以评论
本文提到的模型 1
本文提到的数据集 1