语言模型无监督多任务学习:零样本 Prompt 验证与实操指南
发布时间:2026/10/20 12:59:45 锦皓数字建站

“Language Models are Unsupervised Multitask Learners”这个标题翻译过来就是“语言模型是无监督多任务学习者”。我第一次读到这句话时心里其实是拒绝的——当时主流思路还是“先预训练、再针对任务微调”每个下游任务都得单独设计输出层、单独准备标注数据。但论文给出的结论远比标题更锋利一个在纯文本上训练、只学会“预测下一个词”的模型居然能在翻译、摘要、阅读理解、问答这些任务上不更新任何参数、不用任何任务样本直接生成答案。也就是说模型根本不区分“这是一个翻译任务”还是“这是一个问答任务”它只是读到一段提示词然后续写下去任务就在续写里完成了。这篇文章不打算复述论文里的每一张表而是想把“无监督”和“多任务”到底怎么在语言模型里统一起来这件事讲清楚然后给你一套可以直接上手的零样本多任务验证流程再把我自己复现过程中踩过的坑全部摊开。适合这么几类人读刚接触预训练语言模型的学生想搞懂 Prompt 为什么会生效的工程师以及所有对“不微调也能做任务”这件事持怀疑态度的实践者。1. 论文标题到底在说什么1.1 把“无监督”“多任务”“学习者”三个词拆开看“无监督”指的是训练数据没有人工标注。传统机器学习里要做翻译就需要中英对照语料要做问答就需要问题和标准答案。但语言模型不一样它只需要世界上已有的原始文本——网页、书籍、新闻、论坛帖子——然后做一件极其简单的事看到前几个词猜下一个词。这个目标不需要人工设计正确答案因为下一个词就在原文里天然存在。互联网上有多少文本就有多少免费的训练信号。“多任务”指的是一个模型同时学会处理多种任务。这个说法在2019年还是很反直觉的因为主流做法是每个任务一个小模型或者至少每个任务一个分类头。语言模型的逻辑完全不同不管是什么任务只要你能把任务的输入和输出写成一段连续的文本它就变成一个“文本续写”问题。翻译是“给出英文续写法语”摘要就是“给出一篇文章续写一句总结”问答就是“给出问题续写答案”。这些任务都不需要单独建模因为它们最终都落在同一个目标函数里。“学习者”就更有意思了。模型不是在每个任务上“学习”然后更新参数而是在预训练阶段就已经学会了大量任务的文本模式到了推理阶段它不学习只是被输入的提示词激活对应的能力。你可以把模型想象成一个参加过无数场开卷考试的人手里没有标准答案但只要给他一道题的题干他能顺着往下写出答案。1.2 这个结论在当年为什么反常识在它出现之前预训练模型的使用方式基本是“预训练微调”。你用一个通用的目标在语料上训一个底座比如让模型根据上下文填空或者预测共现词然后针对你的下游任务在这个底座上套一个分类层、回归层或者序列标注层再用带标签的数据微调整个模型。这么做有两个很难受的地方第一每个新任务都要重新准备标注数据成本极高第二任务越多要维护的模型副本就越多工程上非常笨重。而这篇论文里的模型走的是另一条路没有针对任务加任何结构没有用任何下游任务标注甚至没有微调阶段只靠“预测下一个词”这个原始目标在多个任务上做零样本生成。我当初看到实验表格时第一反应是“这怕不是测试数据被模型见过了吧”。后来认真读了一遍数据预处理流程发现他们在构造测试集时做了严格的去重尽量避免训练语料和测试文本重叠。也就是说这套能力很多情况下是模型自己在文本分布里归纳出来的不是死记硬背。表格对比一下传统方式和这个新思路会很清楚对比维度传统预训练微调无监督多任务语言模型预训练目标填空、预测共现词等辅助目标单向自回归预测下一个词下游适配方式加任务头、更新参数不加任务头不更新参数每个任务数据需要大量人工标注不需要任何任务标注任务统一方式每个任务结构不同单独设计全部转换为文本序列推理方式分类头输出概率或标签生成式续写采样得到答案这张表里的最后一行其实是最关键的任务被“生成”出来而不是被“分类”出来。这个区别决定了下游所有玩法的走向。2. 模型机制为什么“预测下一个词”能学到多任务2.1 单向 Transformer 与自回归条件概率要理解多任务能力从哪来得先看模型结构。它使用的是 Transformer 的解码器部分核心是带掩码的自注意力。所谓“掩码”简单说就是在计算某个词的表征时不允许它偷看右边未来的词。这样模型只能利用当前词之前的信息去预测当前词。给一个序列 (x_1, x_2, ..., x_t)模型建模的条件概率是[ P(x_t | x_1, x_2, ..., x_{t-1}) ]整个句子的概率就是这些条件概率的乘积。训练时用的损失就是最大化这个乘积。为什么单向这么重要因为“预测下一个词”本质上就是“给定左侧文本生成右侧文本”。而所有自然语言任务都可以重新描述成左侧右侧的文本对翻译任务的左侧是源语言句子右侧是目标语言句子摘要任务的左侧是正文加一个摘要引导符右侧是摘要内容问答任务的左侧是问题和引导符右侧是答案。模型只需要学会一件事根据左侧上下文给出右侧最合理的续写。不需要一个专门的“任务分类器”告诉它现在该用哪套规则因为左侧文本的格式已经隐含了规则。我当时为了形象化理解给朋友打过一个比方你给一个读了很多书的人看半页试卷——题目是英文句子旁边注着“法语翻译”再给他看另一半试卷——一篇新闻末尾写着“概括段落大意”。他不需要你大声宣布“现在做翻译题”或“现在做概括题”他看到版面格式就能自动切换做题模式。语言模型在做的事本质上就是根据提示文本的格式切换自己的续写模式。2.2 任务格式统一与提示词的作用既然所有任务都能变成文本续写那模型凭什么在推理时知道自己该续写哪种风格答案藏在预训练语料的自然分布里。互联网文本不是清一色的论文和新闻它天然混杂着各种“任务样例”网页里经常有英文句子紧跟着翻译论坛上有用户提问另一个用户回复书评里有人写长文、有人写总结新闻标题和正文也天然组成“摘要”。也就是说模型在预训练阶段见过海量的天然任务文本只不过没有人专门给它们打上“这是训练样本”的标签。推理阶段我们需要人为构造一个类似格式的左侧序列触发模型在预训练时学到的模式。这就是 Prompt提示词的本质它不是魔法而是把任务输入排列成“模型熟悉的任务文本形状”。下面几个模板是我实际用过的任务类型左侧输入示例期望输出机器翻译English: Language models are powerful.\nChinese:语言模型很强大摘要新闻正文...\nTL;DR:一句话总结问答问题什么是注意力机制\n回答注意力机制是一种...文本续写在一个安静的下午他推开那扇门。后续情节这里有一个很容易被忽视的细节换行符、冒号、语言名、TL;DR这类标记都很重要。同一个任务English: xxx\nChinese:和英文xxx\n中文两个模板放在同一个模型上输出质量可能差别很大。原因很简单模型预训练时“见过”的那些任务文本它们的格式和你给的模板越接近触发就越可靠。这不是玄学这是分布匹配。2.3 多任务能力是涌现还是训练分布的结果很多人喜欢用“涌现”这个词解释这种现象好像模型突然长出了超能力。我的理解没那么玄。所谓“多任务能力”其实是模型容量、训练数据规模、上下文长度三者共同作用下的结果。数据规模足够大模型就能看到同一任务的不同表达模型容量足够大就能把不同任务的模式分别存储训练目标足够统一所有任务就都能映射到同一个概率分布里。这样推理时给一个 prompt模型只是在搜索自己庞大的先验记忆里最匹配的续写模式。所以“零样本”听起来很神奇但拆开看并不神秘模型没有在新的任务样本上学习可它在训练时已经隐式地学过了大量类似任务。这跟人不一样人学会游泳后再学冲浪不需要重新学平衡但模型是把所有数据和任务一次性灌进权重里的。这也是为什么论文的结论不能随便拿小模型复现——模型规模不够时这些任务模式即使存在于语料中也没有足够的参数去记住和区分。3. 零样本多任务验证我的一套复现流程3.1 准备工作与模型选择论文中的原始实验成本很高普通个人电脑很难完整复现“数十亿参数在超大规模语料上预训练”这个过程。所以我们复现的重点不是“从零训练一个模型”而是“拿一个已经训练好的预训练语言模型去验证它有没有零样本多任务能力”。这样更现实也更贴近大多数人的硬件条件。准备工作中最重要的三样东西一个支持自回归生成的深度学习框架、一个预训练语言模型权重、一块足够容纳模型和你需要生成文本的显卡。如果显存比较紧张可以先拿模型的小版本验证流程确认 Prompt 设计合理后再上大版本。我自己的机器上 8G 显存跑几百兆参数的小模型很轻松但想跑更大规模就只能用半精度加分段生成。下面这组代码我故意没有绑定某个具体模型库而是把关键逻辑抽出来方便你在自己熟悉的框架里对应修改def load_tokenizer_and_model(model_name): # 加载分词器和预训练模型 # 不同框架的 API 不同但核心都是这两步 tokenizer load_tokenizer(model_name) model load_model(model_name) model.eval() return tokenizer, model def generate_text(tokenizer, model, prompt, max_new_tokens64, temperature0.8, top_p0.9): inputs tokenizer(prompt, return_tensorspt) with torch.no_grad(): outputs model.generate( inputs.input_ids, max_new_tokensmax_new_tokens, temperaturetemperature, top_ptop_p, do_sampleTrue, ) return tokenizer.decode(outputs[0], skip_special_tokensTrue)核心流程就三步分词器把 prompt 变成 token 序列模型逐步预测后面的 token分词器把生成的 token 还原成文本。不要被各种前后处理干扰理解这三步就够了。3.2 在三个任务上做零样本验证我用三个经典任务来验证多任务能力。第一个是文本续写这个最直白输入一个开头看看模型能不能自然往下写。第二个是机器翻译把源语言和目标语言标识符放进 prompt。第三个是摘要用TL;DR作为摘要引导符号。代码完全一样变的只是输入 prompt# 任务一文本续写 prompt 在一个安静的下午他推开那扇吱呀作响的木门 print(generate_text(tokenizer, model, prompt, max_new_tokens50)) # 任务二机器翻译 prompt English: Language models are unsupervised multitask learners.\nChinese: print(generate_text(tokenizer, model, prompt, max_new_tokens30)) # 任务三摘要 article (天空出现罕见的月全食整个过程持续了近三个小时。 众多天文爱好者聚集在郊外的观测点用望远镜和相机记录这一幕。 专家提醒下一次类似天象将在数年后出现。) prompt f{article}\nTL;DR: print(generate_text(tokenizer, model, prompt, max_new_tokens30))第一次跑完我的表情大概是“原来还可以这样”。模型确实能续写出完整的一段话翻译结果虽然偶有词不达意但大体意思在摘要也能提炼出核心事件。最让我意外的不是每个任务效果有多好而是同一个模型、同一套参数、同一个解码器什么结构都没改居然能在这三种任务之间自由切换。这就是“文本序列统一所有任务”最直观的验证。3.3 让实验可复现的关键参数零样本生成看似简单但如果不控制参数复现结果会很飘。我用到的几个关键参数如下参数建议值作用temperature0.7 ~ 1.0控制随机性越低越保守越高越发散top_p0.8 ~ 0.95从概率累积达到阈值的候选词里采样避免尾部噪声max_new_tokens任务相关限制生成长度注意不要设得太短导致半句话do_sampleTrue自己验证任务时建议开启效果比贪婪解码丰富repetition_penalty1.0 ~ 1.2降低重复文本太高会让语句生硬这里要特别说说max_new_tokens和早期常见实现的max_length的区别。max_length是 prompt 和生成内容的总长度如果你给了一个很长的 prompt再设一个很大的max_length实际能生成的新 token 比想象中少很多max_new_tokens则只控制新生成的长度建议优先用这个。不然你以为生成了几百字结果 prompt 就占了大半输出不完整。另一个影响可复现性的点是随机种子。虽然生成式采样本身带着随机性但为了对比不同 prompt 的效果最好固定随机种子不然你没法判断效果差异到底是 prompt 带来的还是纯随机噪声。4. 实操踩坑与排查笔记4.1 生成内容反复重复、卡在死循环最常遇到的问题是模型生成到一半开始不断重复同一个短语比如“他推开门推开门推开门……”循环。这在大模型和小模型上都可能出现尤其在长度超过一二百字时更明显。原因通常有两个采样温度太低让模型过度自信地选中高概率词或者模型在某个局部模式里陷了进去。我的排查顺序是先把 temperature 调高到 0.9 以上让分布更平滑如果还重复就把 repetition_penalty 调到 1.1 左右给已经出现过的 token 加一点惩罚。注意惩罚不是越高越好设到 1.3 以上模型会为了避重复而用不自然的词语句反而变别扭。另外top_p调得太小比如 0.5也可能加剧重复因为候选词范围变窄了模型更倾向复用高频词。4.2 Prompt 差一个换行结果就完全变了我在调试时发现对同一个任务只要改一下 Prompt 里的分隔符输出风格马上不同。比如“一句话总结”和“文章末尾加上 TL;DR:”前者的输出更像口语化总结后者的输出更像规范摘要。原因就是模型在预训练语料里看到过大量不同的任务写作模板模板风格决定了续写风格。所以当你发现“零样本能力很弱”的时候先别急着下结论试试换 Prompt 表达。我个人的习惯是用三套不同风格的模板测同一个任务一套带明确任务指令比如“请回答”一套带标签引导比如“问题: ... 回答:”一套带英文标记比如“Question: ... Answer:”。三套里面有两套效果不错就说明模型本身有能力问题在模板匹配三套全崩再考虑是不是模型规模不行或者任务太复杂。4.3 显存不足、推理速度太慢生成任务和分类任务不一样输出的是一个 token 序列每一步都要过一遍模型所以推理开销明显更大。显存不足时我最常用的三个降级手段一是把模型换成半精度加载显存占用直接砍半二是限制输入 prompt 长度别把整篇文章都塞进去三是商量批量大小调成 1逐个任务跑。速度太慢还有个隐藏原因很多时候瓶颈不在 GPU 计算而在“反复解码”。有些框架默认会缓存历史键值但如果你显存压力大导致缓存被清空或分批处理配置不当速度就会掉得厉害。验证方法很简单连续生成两次相同长度的文本观察第二次有没有明显变快如果没有就检查是不是每次请求都在重建缓存。这不是必须深究的原理但能帮你判断该不该在缓存配置上花时间。4.4 权重加载失败与设备不匹配本地复现时最容易卡住的其实是模型加载这一关。权重文件很大下载中途断掉、路径配错、本机框架版本和权重格式不兼容都会导致加载失败。另外如果本机没有可用的 GPU或者 GPU 显存不够但框架默认把模型放到 CUDA 上你会看到一堆莫名其妙的设备错误。遇到这类问题我的建议是先把问题拆开单独写几行代码只加载模型不做生成确认模型能不能成功载入再打印一下模型的参数量和 dtype确认显存预估。把“加载”和“生成”两件事分开排查能省很多时间。加载通过了再调 Prompt不要一边网络下载失败一边怀疑模型能力不行。5. 这篇论文撬动了什么5.1 Prompt 范式与少样本学习的直接推进这篇论文最有影响力的地方不是它发布了一个多大的模型而是证明了“不更新权重只修改输入文本也能控制模型做多种任务”。在这之后很长一段时间内用 Prompt 从预训练模型里“提取”能力成为主流玩法。你甚至在很多任务上不再需要一个精心设计的分类头和一层层微调管线只要把任务描述、示例和输入拼成一段文本模型就能输出结果。后来大家又发现如果在 Prompt 里附带几个任务示例哪怕完全不更新参数效果也能大幅上升。这种“少样本上下文学习”的方式本质上和论文里的零样本是同一个机制模型在预训练时已经学会了任务格式给它几个示例只是在强化并校准这个格式。所以后来的很多工作与其说是换了新范式不如说是沿着“把任务写作文本”这条路越走越深。5.2 多任务能力的局限仍然明显我不会把“无监督多任务学习”吹成万能答案。模型确实能零样本做很多任务但性能方差很大。它对 Prompt 措辞极其敏感同一个问题换个问法输出可能完全偏向另一个方向。生成式模型也容易出现事实性错误因为它并不是在“查询事实数据库”而是在“续写看起来合理的文本”。如果把这个能力直接用在高风险场景里没有人工校验翻车概率很高。评估上也有一个隐患叫“数据污染”如果训练语料里本来就有测试集的相近内容那看似漂亮的零样本成绩其实是记忆出来的不是泛化出来的。所以看到这类模型宣称的多任务能力第一反应应该是抓它的数据去重方法和测试集构造过程而不是无脑相信分数。5.3 对普通开发者和研究者的可复用思路抛开论文光环这套思路对普通做应用的人至少有三点启发。第一当你想做一个文本类小功能不一定要收集标注数据去做微调先用一个预训练语言模型试试零样本生成很多时候能快速出 Demo。第二同一个模型可以同时充当多个功能模块用 Prompt 切换即可省去部署一堆小模型和一堆推理管线的麻烦。第三“无监督多任务”的思想可以反向用在数据上与其在网上找现成的任务标注不如看看手头的原始文本里是不是已经天然包含大量“输入-输出”结构。我在实际使用中最常用到的一个做法先用零样本生成一批结果把它当作“伪标注”筛掉明显差的部分再拿剩下质量不错的数据去做有监督微调。这样既能省人工标注又能把模型自己的能力蒸馏到一个更小的、适合上线的模型里。这个流程本质上就是把“无监督多任务”当一个低成本的内容生产工具而不是终极答案。最后再分享一个小技巧。如果你也想验证手头语言模型的零样本多任务能力建议先固定一个你自己的“测试任务集”包含十几个不同任务、每个任务配三种不同风格的 Prompt统一跑一遍再比较。不要因为一个 Prompt 效果差就给模型判死刑也不要因为一个 Prompt 效果好就下结论说模型无所不能。我踩过太多次“模型太强了”和“模型太蠢了”两种极端最后发现大多数情况下只是 Prompt 模板匹配度过高或过低。把这个测试集留下来之后每次换模型或者换版本都用同一套 Prompt 跑一场你才能真正看到模型能力的变化。这种“固定提示词只换模型”的方法比零散地打字测试更能看出门道。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。