简介:面向自然语言处理研究人员与技术人员的PDF指南,系统梳理大型语言模型、预训练模型与嵌入模型三类体系的定位差异。文档从对话系统、文本分类、语义聚类等典型场景切入,先分别阐述三者的训练方式、特性优势与最佳应用领域,并引入GPT-4、LLaMA、BERT、DistilBERT等模型进行参照;再依据性能指标、资源需求、可扩展性与定制能力做横向对比,结合三个真实案例说明选型逻辑,帮助读者避开“大模型万能”的常见误区。内容还包括任务复杂度评估、标注数据条件下的微调策略,以及以嵌入向量为基础搭建自定义分类管道的实施步骤与代码示例,兼顾理论深度与工程可操作性。资源共1个PDF文件,压缩包约606KB,当前已有106人学习,适合需要在算力受限条件下完成模型选型与部署的NLP从业者。
1. 选型不是选“最大”:三类模型对应三种完全不同的需求起点
大型语言模型、预训练模型与嵌入模型,这三类模型经常被混为一谈,但它们解决的问题根本不是一个层面。大型语言模型擅长的是开放式的文本生成与对话,预训练模型擅长的是从文本里提取结构化的特征用于分类、抽取,嵌入模型做的事情是把文本压缩成一个稠密向量,让相似内容在向量空间里彼此靠近。一个做对话产品的团队和一个做检索系统的团队,他们需要的“模型”可以是完全不同类别的东西,选错类别,后面的微调、部署、评测全都会跟着跑偏。
这篇选型指南想解决的,不是“哪个模型最强”,而是“你手里的任务到底应该由哪一类模型来承担”。我会按任务形态把常见应用场景拆开,给出判断标准、参数取舍、部署预算以及验证方法,最后落在生产环境的容量与回退策略上。适合正在建技术方案、准备做技术选型评审,或者已经踩了坑想搞清楚问题出在哪一层的工程师。
2. 三类模型的分工,先确认任务形态再谈参数
选型的一个常见误区是先把模型列表拉出来比参数大小,然后挑一个“看起来最厉害”的。实际上正确的顺序是先问任务形态:你要的是生成一段新文本,还是从一段文本里判断出类别,还是在一堆文本里找出最相关的那几条。三种形态对应三类模型,各自的能力边界和失败模式完全不同。
2.1 对话与生成型任务:大型语言模型的上下文与可控性边界
大型语言模型适合的是“输入一段指令,输出一段新文本”的任务。典型场景包括客服对话、文案生成、代码补全、摘要生成,以及需要多轮交互的助手类产品。它的强项在于理解复杂的自然语言指令,并且在开放域里生成流畅、上下文连贯的内容。
但它的弱点同样明显:输出不确定。同样一个 prompt,两次调用结果会不一样,温度参数调高之后尤其明显。对需要精确答案的场景,比如“这个订单是什么状态”,大模型可能给出很自然但完全编造的回答。这是它的“黑匣子”属性,不是靠调 prompt 能根除的。
选型时重点看三个参数:
- 上下文长度:决定单次请求能塞进多少背景资料。对话类和文档分析类任务,上下文长度往往比参数量更影响效果。
- 温度与采样策略:需要稳定的抽取式答案时,把温度调低甚至设为 0;需要创意文案时,才调高温度。
- 输出长度上限:生成任务要预留足够的 max_tokens,但也要考虑长输出对响应时延和成本的放大效应。
我一般会先做一组“可控性压测”:给定一批需要确定答案的问题,统计模型在低温下的答对率、拒答率和编造率。如果编造率压不下来,这个任务就不适合纯靠大模型硬扛,应该考虑检索增强或者换用判别式方案。
2.2 分类与抽取类任务:预训练模型微调的性价比判断
如果你的任务输出不是一个自然语言句子,而是几个标签、若干个实体,或者一个“是/否”的判断,那么预训练模型往往比大语言模型更省、更稳、更快。这类模型的核心价值在于它已经把通用语言知识预训练好了,你只需要在它的输出层接一个分类头或者序列标注头,在少量标注数据上做微调,就能获得一个专用模型。
与大型语言模型相比,预训练模型有两个非常实际的优势。第一是确定性:同一个输入永远给出同一个输出,适合对结果可解释性有要求的场景。第二是部署成本:一个百亿参数以内的编码器模型,单张消费级显卡就能跑推理,时延可以压到几十毫秒量级,远低于生成式模型动辄几秒的输出时间。
它不适合什么场景?开放式生成。预训练模型本质上是在做“理解”和“表征”,不是在写作文。硬要它生成一段自然语言,产出的往往是拼凑痕迹明显的句子。所以如果你发现自己在一个对话产品里反复用规则去约束大模型的输出格式,可以考虑是不是应该换成“预训练模型做意图识别 + 模板生成回答”这条更省钱的路径。
2.3 检索与匹配类任务:嵌入模型决定召回质量的三个指标
嵌入模型解决的是“找相似”的问题。它把一句 query、一段文档、一个商品标题各自映射成一个向量,然后用向量距离来衡量语义相似度。典型应用包括知识库检索、相似问题推荐、去重、聚类、多模态搜索的文本侧召回。
既然它只负责召回,就要用召回的指标来评价它,而不是用“答对率”这种生成式指标。我判断一个嵌入模型好不好用,只看三个东西:
- 召回率@K:前 K 个结果里有没有包含真正相关的文档。这是最核心的指标。
- 相似度分数分布:相关对的分数与不相关对的分数是否分得开。分不开的模型,阈值怎么调都难受。
- 领域一致性:在通用语料上表现好的模型,切到故障工单、法律文书、医疗记录这类垂直语料上,分数分布经常整个塌掉。
嵌入模型的技术参数也很直接:向量维度决定存储成本和比较速度;归一化决定你用余弦还是点积;文本窗口决定它一次能编码多长的内容。很多嵌入模型默认窗口只有 512 token,长文档超出部分被截断,召回效果立刻变差。这一点在评测时要特别留意。
在真实项目里,我见过不少团队先花大价钱做大模型的检索增强,最后发现检索召回那一环坏了,再贵的生成模型也救不回来。嵌入模型是整个检索链的地基,选型时值得单独花时间验证,而不是顺手拿一个通用模型顶上。
3. 把需求翻译成选型条件:任务形态、数据量、硬件预算与效果指标
上一章解决了“该用哪一类模型”,这一章解决的是“具体选到什么程度”。同一个类别里,模型之间的差距可能非常大。选型条件可以拆成四个维度:任务形态、数据量、硬件预算、效果指标。四个维度合在一起,基本能把候选范围锁到个位数。
3.1 从五个问题出发,确定模型类别和量级
没有标准答案的选型,可以通过一组问题快速缩小范围。这五个问题按优先级排序,先回答前两个,基本能定类别:
| 问题 | 答案 A | 答案 B | 对应选型 |
|---|---|---|---|
| 输出是文本还是判断? | 文本 | 标签/分数 | 答案 A 走向大语言模型,答案 B 走向预训练模型 |
| 是否高度依赖外部知识? | 是 | 否 | 依赖外部知识意味着必须引入检索链路,嵌入模型成为必选项 |
| 标注数据量有多少? | 低于 500 条 | 数千条以上 | 数据少走提示工程或零样本,数据足才谈微调 |
| 响应时延要求多严? | 秒级可接受 | 百毫秒级 | 严格时延直接排除生成式方案,优先判别式 |
| 部署环境有什么限制? | 有 GPU 服务器 | 只有 CPU 或边缘设备 | 受限环境必须选小参数量模型或量化方案 |
需要注意,答案之间会互相牵制。比如“输出是文本”和“时延要求百毫秒级”同时出现,说明纯大语言模型路径不成立,可以考虑用预训练模型做意图识别,再套模板生成回答,或者用极小的生成模型。
实际做法里,我习惯把这五个问题的答案写成一页纸的决策记录,连同候选模型、理由、风险一起评审。这样后续效果不好时,回头能看清楚是哪一步判断出了问题,而不是笼统地归咎于“模型不行”。
3.2 数据量决定路径:零样本、微调还是全量训练
同样的任务类别,数据量不同,技术路径完全不同。我按标注数据量分三档:
- 少于 500 条:不要微调。这个量级的样本不够模型学习新的语言模式,强行微调大概率过拟合。应该走提示工程、zero-shot 或者 few-shot,配合规则兜底。
- 500 到 1 万条:适合冻结大部分参数、只训练少量层或使用低秩适配的轻量微调。此时学习率要调低,训练轮次控制在 3 到 5 轮以内,并且用验证集早停。
- 1 万条以上:可以考虑全量微调。这时才有足够样本让所有权重发生有意义的变化。全量微调对显存和训练时间的要求跳一个量级,需提前做成本评估。
数据分布比数据量更值得关注。如果 5000 条数据里 90% 是同一个类别,模型学到的只是类别先验,真正判别性信息几乎没有。这种情况应该先做数据均衡和困难样本挖掘,而不是急着调参。
3.3 部署预算的硬账:显存、时延与并发怎么估算
选型阶段就要把部署账算清楚,否则效果达标的模型可能根本跑不起来。显存估算是第一步:推理时模型权重占用的显存约等于参数量乘以精度字节数,再叠加 KV Cache 或中间激活值。粗略估算单并发时,它们的关系大概如下:
| 模型规模 | 粗略推理显存区间 | 单次请求响应量级 | 适合场景 |
|---|---|---|---|
| 十亿参数以内 | 若干 GB | 毫秒到百毫秒 | 分类、抽取、小规模向量化 |
| 十亿到百亿参数 | 数十 GB | 秒级 | 对话、摘要、中等规模检索 |
| 百亿以上参数 | 百 GB 级或多个加速卡 | 数秒到数十秒 | 高质量生成、复杂推理、离线批处理 |
时延与并发的关系往往是反直觉的:并发上来后,显存带宽会成为瓶颈。一个单卡能跑百毫秒响应的模型,在 32 并发时 P95 可能直接翻十倍。这就是为什么选型阶段就要定好目标 QPS 和 P95 时延,而不是只测单请求。
我建议在选型文档里写死一组数字:目标并发、P95 响应时间、单请求平均输出长度、可用显存。用这组数字过滤候选模型,而不是拿一个很大的模型跑到爆显存再临时换小。
3.4 效果指标怎么定:离线评测集是选型的唯一裁判
选型阶段最大的风险是“凭感觉”。两个模型谁更好,必须用离线评测集说话。评测集有三个基本要求:第一,不能从训练数据里抽,尤其做微调时,否则指标虚高到你上线就傻眼。第二,要包含负样本,分类任务要覆盖难分的边界样本,检索任务要包含容易混淆的错误候选。第三,指标要跟线上行为对齐,线上关心的是用户找不找得到结果,评测就不能只看准确率。
对检索类任务,我常用召回率@K 和平均倒数排名来判断。对分类类任务,我除了看准确率和 F1,还要看混淆矩阵里的错误模式,判断模型是“稳定的错”还是“随机地错”。稳定的错通常能通过规则修正,随机地错就比较麻烦。
评测集一旦定好,整个选型过程就有了标尺。后续换模型、调阈值、做微调,都拿同一套数据说话,省掉大量无效争论。
4. 本地跑通三类验证:最小命令、评测脚本与效果检查
选型文档写得再漂亮,不如本地跑一组验证。这一章给出三类模型各自的最小验证方法,目标是在一两天内回答三个问题:模型能不能正常调用、效果达不达标、部署资源够不够。我会按“先调用、再评测、后调参”的顺序来。
4.1 生成类模型的最小调用与响应质量检查
先确认部署好的模型服务能从本地代码正常调用。常见做法是模型服务暴露一个 HTTP 接口,用下面的脚本做连通性测试和时延摸底:
import json import time import urllib.request def call_generate(prompt, port=8000, max_tokens=256, temperature=0.7): # 本地模型服务的生成接口,POST JSON 即可 payload = { "prompt": prompt, "max_tokens": max_tokens, "temperature": temperature, } req = urllib.request.Request( f"http://127.0.0.1:{port}/v1/generate", data=json.dumps(payload).encode("utf-8"), headers={"Content-Type": "application/json"}, ) # perf_counter 记录包含网络传输在内的完整响应时间 start = time.perf_counter() with urllib.request.urlopen(req, timeout=60) as resp: body = json.loads(resp.read().decode("utf-8")) elapsed = time.perf_counter() - start return body["text"], elapsed if __name__ == "__main__": # 同一 prompt 连测 5 次,观察输出的稳定性 for i in range(5): text, cost = call_generate("介绍一下向量检索的适用场景", port=8000) print(f"第 {i + 1} 次,时延 {cost:.2f}s,输出长度 {len(text)}")temperature 是采样随机性的控制旋钮:需要稳定答案时把它设为 0,文案场景酌情调高但不要超过 1。max_tokens 要按业务场景预判,给太少输出会被截断,给太多会拉高时延。timeout 设到 60 秒是为了区分“接口卡死”和“生成慢”。连测 5 次是为了看时延方差,生成模型的响应时间天然不稳定,单次测量没有参考意义。
如果同一 prompt 输出差异大,说明温度偏高或模型本身可控性差。这时可以先把温度降到 0 再测一轮,如果仍然飘,就要在选型记录里标记为风险项。
4.2 嵌入模型召回评测:用一个小脚本量化检索质量
嵌入模型的验证重点不是“它生成什么”,而是“它召回了什么”。下面这个脚本用余弦相似度计算 query 与候选文档的相似度,输出每个 query 的 top-K 结果和命中情况。
import json import math def cosine(a, b): # 余弦相似度,等价于归一化后的点积 dot = sum(x * y for x, y in zip(a, b)) norm_a = math.sqrt(sum(x * x for x in a)) norm_b = math.sqrt(sum(x * x for x in b)) if norm_a == 0 or norm_b == 0: return 0.0 return dot / (norm_a * norm_b) def get_embedding(text, port=8001): # 假设本地嵌入服务提供 /v1/embed 接口 payload = json.dumps({"text": text}).encode("utf-8") req = urllib.request.Request( f"http://127.0.0.1:{port}/v1/embed", data=payload, headers={"Content-Type": "application/json"}, ) with urllib.request.urlopen(req, timeout=30) as resp: return json.loads(resp.read().decode("utf-8"))["vector"] def evaluate_recall(queries, docs, gold, k=5): # queries: list[str],docs: list[str],gold: list[set],gold[i] 是第 i 个 query 的相关文档下标集合 hits = 0 for idx, query in enumerate(queries): q_vec = get_embedding(query) scored = [] for doc_idx, doc in enumerate(docs): d_vec = get_embedding(doc) scored.append((cosine(q_vec, d_vec), doc_idx)) scored.sort(reverse=True) top_k = {doc_idx for _, doc_idx in scored[:k]} if top_k & gold[idx]: hits += 1 return hits / len(queries)这段代码的核心逻辑是:拿每个 query 的向量去和所有候选文档的向量做余弦比较,排序后取前 K 个,再判断这 K 个里是否出现了人工标注过的相关文档。召回率@K 的含义是“真正的相关结果有没有被捞进前 K 名”,它回答的是“模型检索能力到底行不行”这个关键问题。
实际验证时要注意效率。上面脚本里每个 query 都重新对所有文档求向量,是一次非常慢的全量扫描。真实项目会用向量索引,但选型阶段用这小脚本足够,因为它能让你直接看到分数分布。
4.3 通过阈值分析看模型稳不稳
召回率只回答了“能不能找到”,另一个同样重要的问题是“找出来的结果可不可信”。这需要看相似度分数分布:把相关对和不相关对的分数分别画出来,观察两者之间有没有间隔。间隔越大,越容易设一个干净的阈值;完全重叠,说明这个嵌入模型在这个语料上分不清相关和不相关。
def score_distribution(queries, docs, gold, sample_size=200): # 分别收集相关对与不相关对的相似度分数 related_scores = [] unrelated_scores = [] for idx, query in enumerate(queries[:sample_size]): q_vec = get_embedding(query) for doc_idx, doc in enumerate(docs): d_vec = get_embedding(doc) score = cosine(q_vec, d_vec) if doc_idx in gold[idx]: related_scores.append(score) else: unrelated_scores.append(score) related_scores.sort() unrelated_scores.sort() # 观察两个序列的分布,例如打印中位数和 P90 def pct(arr, p): return arr[min(len(arr) - 1, int(len(arr) * p))] print(f"相关对: 中位数 {pct(related_scores, 0.5):.4f}, P90 {pct(related_scores, 0.9):.4f}") print(f"不相关对: 中位数 {pct(unrelated_scores, 0.5):.4f}, P10 {pct(unrelated_scores, 0.1):.4f}")输出里如果相关对 P90 大于不相关对 P10,说明存在一个可用的阈值区间。如果两条分布完全纠缠在一起,就别再调阈值了,问题出在嵌入模型本身,要么换更大更强的向量模型,要么做领域微调。
这一步非常容易踩坑:很多人上线之后发现检索结果有一堆“意思相近但事实无关”的内容,回头查才发现当时评测只看了召回率,没看分数分布。分数重叠的模型,阈值怎么调都会漏或误,这是选型阶段就能暴露出来的问题。
5. 选型避坑清单:五个常见翻车现场与处理办法
选型过程里的坑,十个里有八个不是出在模型能力上,而是出在评测方式、数据划分、部署预期这些“外围环节”。我把最常见的五个翻车现场按“现象 → 原因 → 解决”各写清楚,帮你提前排掉。
5.1 通用语料跑得很好,切到垂直领域召回就崩
现象:嵌入模型在公开数据集上召回率很不错,一换到专业语料库,query 和对应文档的相似度全面下降,相关结果沉到几十名开外。
原因:嵌入模型的语义空间是在训练语料里形成的。垂直领域的术语、行话、表达方式在通用语料里出现频率太低,模型根本没有学会它们之间的距离关系。
解决:不要直接换“更大的”通用模型,先把评测集切成领域内样本,确认问题边界。然后考虑用领域语料做无监督对比学习继续训练嵌入模型,或者在候选集层面加入关键词召回兜底,让向量召回和词法召回并行,最后做融合排序。
5.2 微调后训练集指标很高,线上效果反而更差
现象:训练集和验证集上 F1 值漂亮得惊人,上线后面对真实用户输入,效果还不如微调前的通用模型。
原因:微调数据里混进了与线上分布不一致的样本,模型学到的是数据集的“表面特征”,比如把某些高频词和标签绑定起来。这本质上是过拟合了训练数据分布。
解决:评测集必须按时间切分,线上之后的数据才允许进评测集,绝不从训练集抽样。同时训练时要在验证集上做早停,并检查训练集和验证集的指标差距,差距过大就降低训练轮次或增强数据多样性。
5.3 相似度分数很高,但检索结果“意思对、事实不对”
现象:向量召回的前几个结果语义上和 query 很接近,但要么是空话,要么是错误信息。比如查“退款流程”,召回了“退款政策说明”却是另一条失效规则。
原因:嵌入模型衡量的是语义相关,不是事实正确。相关和事实是两码事,向量空间里一句谣言和一条辟谣可能离得很近。
解决:检索链路里加一层重排或校验。常见做法是拿召回的 top-K 结果交给预训练模型做二分类判断,或者用规则把明显不满足条件的候选过滤掉。不要指望靠调阈值解决这个问题。
5.4 全量微调显存装不下,训练直接崩
现象:训练脚本一启动就爆显存,或者勉强启动但 batch size 太小,训练效率极低。
原因:全量微调时,模型权重、梯度、优化器状态都要驻留显存。某些优化器还要保存一阶和二阶动量,实际所需显存是模型权重的数倍,远超推理时的占用。
解决:选型阶段就按“显存 = 权重 + 梯度 + 优化器状态 × 参数规模”的公式估算。预算不足时优先考虑冻结大部分层、低秩适配等参数高效微调方式,而不是硬上全量训练。这类方案只用训练少量额外参数,显存需求低一个量级。
5.5 只测单请求时延,上线并发一高就超时
现象:本地压测单请求时延 200 毫秒,上线后 20 个并发请求,P95 直接到 5 秒。
原因:显存带宽和算力在所有请求之间共享,并发上升后吞吐饱和,单个请求的排队时间急剧增加。生成式模型尤其严重,因为每个请求都要循环逐 token 输出。
解决:选型阶段就做多轮并发压测,把目标并发下的 P95 作为硬性通过标准。同时确认服务的最大并发数和排队策略,超出容量时要有限流和降级方案,而不是让请求无限堆积。
6. 上线前的最后一步:容量规划、监控指标与回退策略
模型选型真正的终点不是“决定用哪个模型”,而是“决定这个模型上线出了问题怎么办”。我习惯在方案评审时就把容量、监控和回退三件事定下来。
容量规划先算显存。一个百亿参数模型在 FP16 精度下,权重约等于参数量乘以 2 字节,再加上每个并发请求的 KV Cache,单并发可能额外占几百兆到数 GB 不等。公式记一个大概:总显存需求 = 权重显存 + 平均并发数 × 单请求显存增量,再上浮 30% 余量。
监控指标方面,除了常规的时延和 QPS,我还会盯两个容易忽略的指标。第一个是响应长度,生成式模型的输出长度直接影响时延和成本,输出长度突然增长往往意味着提示词被攻击或温度参数异常。第二个是检索链路的召回率,线上要定期抽样人工评估,一个嵌入模型上线后如果语料增量没跟上,召回率会随时间缓慢下降,这类问题只有监控能看到。
回退策略要提前做,而且要能一键触发。我的做法是准备三个档位:主模型负责效果拉满,备选模型负责降级保服务,最底一层是规则兜底。比如对话场景:第一档是大语言模型生成完整回答;第二档降级为预训练模型做意图识别加模板回复;第三档是直接返回预设的兜底话术。这不是消极保底,而是给后面的优化留空间,让线上始终有可用服务。
我现在每做一个选型方案,都会在评审文档里强制写一页“回退方案”,把降级触发条件、回退路径、负责人这三栏填满。这个习惯救过我好几次:有一次新模型上线后检索召回率暴跌,因为线上语料里有大量新术语是旧嵌入模型没见过的,我直接触发第二档回退,用关键词检索顶住,再花一周做领域适配,没有造成业务中断。希望这套方法也能帮到你,选型时有依据,上线后有退路。
本文还有配套的精品资源,点击获取