五万字综述!Prompt-Tuning:一种新的微调范式
我对着BERT的[MASK]发了一下午呆,然后悟了
跟你说,去年有一天,我盯着BERT的输出层看了整整两个小时,脑子里只有一个念头:这玩意儿到底听懂我说的话没有?
事情是这样的。我做情感分类,老套路:把BERT拿出来,在它脑袋顶上扣个分类头,然后扔进去几千条标注数据,全参数微调。结果呢?效果还行,但总觉得不对劲。你想啊,BERT预训练的时候干的什么活?猜[MASK]是哪个词,判断两句话是不是挨着的。现在突然让它当判官——这句是正面还是负面?它心里不懵才怪!
所以当时我看到GPT-3和PET那些论文,第一反应是:加个模板就能少用数据?吹的吧?
但后来我亲手跑了一遍,服了。真的服了。
一个让你重新认识BERT的操作
你看,传统微调就像逼一个钢琴家去修水管——他再厉害,一时半会儿也转不过弯来。Prompt-Tuning干的什么事?把下游任务包装成它最擅长的填空题。
你没听错。就是填空题。
比如你说“I love this movie.”,传统做法是输出一个标签。Prompt-Tuning呢?它把这句子变成“I love this movie. It was [MASK].”,然后让BERT去猜那个空是“great”还是“terrible”。这是它老本行啊!猜的结果就是情感判断。
这个思想一旦打通,我后面两年的实验全部变了味。
我亲手踩过的三个坑(每个坑都值一顿烧烤)
第一次:手写模板,真香但也真累
最早我学PET,自己设计模板。情感分类就写“It was [MASK]”。在IMDB上,只用了10个样本,准确率就干到85%——只比全参数微调低不到5个点。我当时下巴都掉了:10条数据,5%的差距,你敢信?
但问题来了:换个任务就要重新想模板,跟写作文似的。更坑的是,同一个模板在BERT-base上好使,换到RoBERTa-large上就拉胯。我试过“It was [MASK]”在BERT上能打,放到ALBERT上直接崩——它参数共享,理解不了这种固定搭配。
所以啊,模板这东西,没有银弹。
第二次:连续模板,小模型上被骗了
为了省事,我开始玩“虚拟token”——不用手写,直接让模型自己学嵌入。这叫Prompt Tuning和Prefix Tuning。
先试Prompt Tuning:只在输入前面加几个可训练的虚拟token。在GPT-3那种175B的大模型上,效果炸裂。但换到BERT-base才110M,直接比全参数微调低5个点。论文《The Power of Scale》也说了同样的话:模型参数少于10B,别指望Prompt Tuning省钱还能保住效果。
这就好比给小孩一块黑板让他自己悟——黑板太小,他悟不出来。
再试Prefix Tuning:不仅在输入层加,还在Transformer每一层的Key和Value前面加虚拟token。效果确实好了,但显存直接起飞。我拿GPT-2 medium(355M)跑批大小1,序列长度512,prefix长度才10,显存就吃了4G。Prompt Tuning同样条件才1G。算过账:KV cache参数 = 10 × 2 × 12 × 768,18万多个float,加上梯度和优化器状态,8G卡稍微大点的模型就OOM。
更坑的是,训练时这些虚拟token的KV cache也是可训练参数,不像推理时只读。我同事就因为没注意这个,调了一个通宵的OOM。
后来发现Prefix Tuning的学习率得比Prompt Tuning低一个数量级。我用1e-4,loss原地爆炸;换到1e-5,才稳住。你以为的捷径,往往藏着最大的坑。
第三次:P-Tuning v2,终于找到对味儿的了
P-Tuning v1先用LSTM生成虚拟token的嵌入,其实就是做一层映射。在BERT-large上效果比固定好一丢丢,但训练慢得像蜗牛——LSTM有序列依赖,没法并行。
后来v2出来了,跟Prefix Tuning很像,但专门改进了在小模型上的表现。我在SuperGLUE的RTE任务上做过对比:P-Tuning v2平均90.2分,全参数微调91.5,只差1.3分!而Prompt Tuning只有87分。关键是v2只用了0.1%的参数:12层×20虚拟token×768维×2(K和V)≈36.8万,相比全量微调的3.3亿,省了近千倍。
不过显存还是会多一点点。我实测8G卡batch size=16,P-Tuning v2比全量微调多耗200M显存。
这里有个血的教训:虚拟token初始化太重要了! 我一开始随机初始,训练飘得像心电图。后来改成用预训练词汇表里“the”、“a”这些常见词的embedding做初始值,收敛速度直接翻倍。任务不同,最优token长度也不同:分类10-20够用,生成任务可能需要50以上。
这么多方法,到底在干什么?一句话点醒你
你可能会想:这些花样翻新的方法,本质是什么?
我琢磨了大半年,终于想明白:Prompt-Tuning就是任务格式转换。
BERT预训练学完形填空,GPT学下一个词预测。不管你让我做情感分析、关系抽取还是对话,你只要把问题包装成一个填空或者续写,我就能直接输出答案。根本不需要头顶上那个分类头。
所以GPT-3在few-shot下这么强——它就是做生成的,你给它几个例子,它自动续写下去就行了。
从工程角度,这些方法都是参数高效微调。全参数微调要保存整个模型几十GB,Prompt-Tuning可能只有几MB。我在生产环境里,一个BERT-base挂着6个不同的分类任务,每套Prompt参数才几MB,服务启动时全部加载,请求来了路由一下就行。一次训练,多任务共享,热切换无感。
到底选哪个?我的实战指南(兼踩坑合集)
如果你跟我一样是个实用主义者,直接看结论:
- **资源极少(卡小、数据少)**:先试Prompt Tuning。模型小于1B且任务简单,效果够用。也可以试试P-Tuning v1,但代码稍微复杂。
- **任务复杂(分类、NER、序列标注都来)**:直接上P-Tuning v2或Prefix Tuning。我强推v2,在不同规模模型和任务上更稳定,HuggingFace PEFT库里就有现成的。
- **生成任务(摘要、翻译、对话)**:选Prefix Tuning或LoRA。我拿T5-base做摘要,LoRA收敛快,但Prefix Tuning生成长文本更流畅。各有千秋。
- **懒人首选(不想调参)**:直接In-Context Learning,调个GPT-4 API,给几个示例完事。成本高,但省心。
但千万别以为PEFT库的默认参数能直接用! 我在BERT-base上用Prefix Tuning,默认prefix length=30。我的任务输入平均才20个token,加30个前缀,模型直接懵了——它更关注前缀,忽略了我真正想判断的句子。后来改成10,效果立刻提升2个点。
永远要根据实际输入长度调整虚拟token数量,一般不超过输入长度的1/3。
如果你想系统搞通,这几篇论文按顺序吃透
1. 《The Power of Scale for Parameter-Efficient Prompt Tuning》 —— Prompt Tuning的圣经。讲清楚了为什么大模型爽、小模型瓜。
2. 《Prefix-Tuning: Optimizing Continuous Prompts for Generation》 —— 原理和实现,必读。
3. 《P-Tuning v2: Prompt Tuning Can Be Comparable to Fine-tuning Universally Across Scales and Tasks》 —— v2为什么全能?里面消融实验多到你无法拒绝。
4. 《Finetuned Language Models Are Zero-Shot Learners》 —— 讲Instruction Tuning的,相当于Prompt Tuning在大模型下的进化版,需要人类指令数据。
代码方面,HuggingFace的PEFT库已经集成了。你想自己撸一遍理解原理,推荐苏神(bojone)的Pattern-Exploiting Training,GitHub上搜“bojone/Pattern-Exploiting Training”。代码简洁,注释清晰,适合新手。不过注意他实现基于TensorFlow 1.x,道理一样。
最后一句,我写在实验记录扉页上的话
写这玩意儿的时候,我翻了自己两年的实验记录,发现踩过的坑至少十个。但最让我感慨的是:Prompt-Tuning本质上不是一个新技术,而是对预训练模型的重新理解——你换个角度问问题,它就能给你惊喜。
现在大家都在聊ChatGPT、GPT-4,学会In-Context Learning和CoT,不用调任何参数。但在我看来,Prompt-Tuning仍然有不可替代的价值:当你想在自己的数据上精调一个模型,又不想烧钱烧卡时,它是最经济的方式。而且,从Prompt-Tuning到Instruction Tuning再到RLHF,这条路是通的——都是在教模型用人类的方式理解任务。
你别被“五万字综述”这种标题唬住。真正的关键就一句话:
把任务格式转化成模型最擅长的样子,剩下的让它自己搞定。
多试几个模板长度、学习率、初始化方式,结果不会差。加油干吧,踩坑难免,跑通一个实验你就爽了。
读者评论 5