news 2026/9/12 3:48:33

Qwen-7B-Chat P-Tuning 微调实战:基于 self-llm 冻结主干、只训练 Prompt 嵌入层的高效微调方案

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Qwen-7B-Chat P-Tuning 微调实战:基于 self-llm 冻结主干、只训练 Prompt 嵌入层的高效微调方案

Qwen-7B-Chat P-Tuning 微调实战:基于 self-llm 冻结主干、只训练 Prompt 嵌入层的高效微调方案

【免费下载链接】self-llm《开源大模型食用指南》针对中国宝宝量身打造的基于Linux环境快速微调(全参数/Lora)、部署国内外开源大模型(LLM)/多模态大模型(MLLM)教程项目地址: https://gitcode.com/GitHub_Trending/se/self-llm

本文是《开源大模型食用指南》self-llm 项目中 Qwen 系列微调教程的 P-Tuning 篇,讲解如何基于 transformers、peft 等框架对 Qwen-7B-Chat 进行 P-Tuning(前缀微调)——冻结主模型全部参数,仅训练一小段自动学习的 Soft Prompt 嵌入层,以远低于全参微调的显存开销完成中文对话风格的定制。读完本文,你将掌握 P-Tuning 与 Prompt-Tuning 的原理差异、PromptEncoderConfig各核心参数的语义与取值约束,并能直接运行仓库提供的微调脚本完成从数据格式化、模型加载到 Trainer 训练与推理的完整流程。

P-Tuning 原理:为什么只训练 Prompt,不碰主模型

P-Tuning 属于参数高效微调(PEFT)家族。与上一节 Lora 微调 中往注意力矩阵旁挂低秩旁路不同,P-Tuning 的基本思路是:冻结主模型的全部参数,在训练数据前加入一小段 Prompt,只训练这段 Prompt 对应的嵌入层。也就是说,整个训练过程中主模型权重一动不动,需要学习的只有新增的那一小段可训练参数,显存占用与可训练参数量都极小。

P-Tuning 是在 Prompt-Tuning 基础上的演进,两者的区别可以精炼地概括为两点:

  • Prompt-Tuning:只在输入序列前面增加一个Prompt Embedding(可训练的 prompt 嵌入向量),直接拼接到输入嵌入上;
  • P-Tuning:把简单的Prompt Embedding替换为Prompt Encoder,即新引入LSTM/MLP + Embedding结构,先对可训练的 prompt 参数做一次编码,再拼接进输入序列。

在 P-Tuning 中,这段 Prompt 是自动学习的(即 Soft Prompt),不需要人工设计具体的提示语文本,因此不存在手工构造 prompt 的负担。下图清晰展示了 Prompt Embedding 与 Prompt Encoder(LSTM/MLP)的组合关系以及后续进入 Transformer Blocks 的整体流程:

引入编码器的动机在于加速收敛:直接训练离散的 prompt 嵌入在早期往往收敛缓慢,而经过 LSTM/MLP 重参数化(reparameterization)后的 Soft Prompt 携带了更强的归纳偏置,训练更稳定、收敛更快。

环境与数据准备

数据加载与模型配置与 Lora 微调 完全一致,这里不再赘述,只需按 Lora 篇完成基础环境搭建并安装以下依赖即可:

pip install transformers==4.35.2 pip install peft==0.4.0 pip install datasets==2.10.1 pip install accelerate==0.20.3 pip install tiktoken pip install transformers_stream_generator

微调数据集沿用仓库根目录下的 dataset/huanhuan.json,每一条样本均为{"instruction": ..., "input": ..., "output": ...}形式的指令数据。本教程的目标是构建一个能模拟甄嬛对话风格的个性化 LLM,例如:

{ "instruction": "现在你要扮演皇帝身边的女人--甄嬛", "input": "你是谁?", "output": "家父是大理寺少卿甄远道。" }

在微调脚本中,通过pd.read_json('./dataset/huanhuan.json')将 JSON 读入 DataFrame,再经Dataset.from_pandas(df)转为 HuggingFaceDataset对象供后续map使用。运行脚本前请将os.chdir('/root/self-llm')改为自己 self-llm 项目的绝对路径。

数据格式化:拼接 Qwen 原生 Chat 模板并构造 labels

P-Tuning 与 Lora 共用同一套process_func数据预处理逻辑。核心要点是遵循 Qwen 原生的 Chat 指令格式进行拼接——因为在原本模型指令微调的形式上进行高效微调效果最好。每条样本会被组织成如下形态:

<|im_start|>system 现在你要扮演皇帝身边的女人--甄嬛.<|im_end|> <|im_start|>user 小姐,别的秀女都在求中选,唯有咱们小姐想被撂牌子,菩萨一定记得真真儿的——<|im_end|> <|im_start|>assistant 嘘——都说许愿说破是不灵的。<|im_end|> <|endoftext|>

对应的预处理函数如下(与 Lora 脚本 中完全一致):

def process_func(example): MAX_LENGTH = 384 # 中文按 token 切分较碎,需放开最大长度保证数据完整性 input_ids, attention_mask, labels = [], [], [] instruction = tokenizer("\n".join(["<|im_start|>system", "现在你要扮演皇帝身边的女人--甄嬛.<|im_end|>" + "\n<|im_start|>user\n" + example["instruction"] + example["input"] + "<|im_end|>\n"]).strip(), add_special_tokens=False) response = tokenizer("<|im_start|>assistant\n" + example["output"] + "<|im_end|>\n", add_special_tokens=False) input_ids = instruction["input_ids"] + response["input_ids"] + [tokenizer.pad_token_id] attention_mask = instruction["attention_mask"] + response["attention_mask"] + [1] # eos token 也要关注,故补 1 labels = [-100] * len(instruction["input_ids"]) + response["input_ids"] + [tokenizer.pad_token_id] # Qwen 的特殊构造 if len(input_ids) > MAX_LENGTH: input_ids = input_ids[:MAX_LENGTH] attention_mask = attention_mask[:MAX_LENGTH] labels = labels[:MAX_LENGTH] return {"input_ids": input_ids, "attention_mask": attention_mask, "labels": labels}

几点说明:

  • add_special_tokens=False:不额外添加特殊 token,保证序列完全由手动拼接的 Chat 模板控制;
  • labels中系统与用户部分填充-100(在交叉熵损失中被忽略),只有assistant回答部分参与 loss 计算,这正是指令微调"只学回答"的标准做法;
  • 最后补tokenizer.pad_token_id作为序列结束符,attention_mask对应位置补 1。

之后用ds.map(process_func, remove_columns=ds.column_names)将原始样本批量编码为input_idsattention_masklabels三个字段。

加载 Tokenizer 与半精度模型

模型以半精度torch.half形式加载;若显卡较新也可改用torch.bfloat16。自定义模型必须设置trust_remote_code=True。Qwen 的eod_idpad_token_id相同,但需要显式指定:

tokenizer = AutoTokenizer.from_pretrained('/root/autodl-tmp/qwen/Qwen-7B-Chat', use_fast=False, trust_remote_code=True) tokenizer.pad_token_id = tokenizer.eod_id model = AutoModelForCausalLM.from_pretrained('/root/autodl-tmp/qwen/Qwen-7B-Chat', trust_remote_code=True, torch_dtype=torch.half, device_map="auto")

脚本中通过os.environ['CUDA_VISIBLE_DEVICES'] = '1'指定使用第 1 块 GPU。与 Lora 篇不同的是,P-Tuning 脚本默认不开启gradient_checkpointing,因此无需调用model.enable_input_require_grads()(该行在脚本中处于注释状态)。

Ptuning PEFT 模块:PromptEncoderConfig 参数详解

P-Tuning 的核心配置类是 peft 库中的PromptEncoderConfig。其关键的取舍点由PromptEncoderReparameterizationType决定——到底用 LSTM 还是 MLP 来做 Prompt Encoder 的重参数化

from peft import PromptEncoderConfig, TaskType, get_peft_model, PromptEncoderReparameterizationType config = PromptEncoderConfig(task_type=TaskType.CAUSAL_LM, num_virtual_tokens=10, encoder_reparameterization_type=PromptEncoderReparameterizationType.MLP, encoder_dropout=0.1, encoder_num_layers=5, encoder_hidden_size=1024)

可调参数及其含义如下表:

参数取值示例含义
task_typeTaskType.CAUSAL_LM模型类型,因果语言模型
num_virtual_tokens10虚拟 token 数量,即插入到输入前的 Soft Prompt 长度
encoder_reparameterization_typePromptEncoderReparameterizationType.MLP重参数化编码器类型,LSTM 或 MLP
encoder_dropout0.1编码器的 Dropout 比例,用于抑制过拟合
encoder_num_layers5编码器层数
encoder_hidden_size1024编码器隐藏层维度

这里有一个非常容易踩坑的注意点encoder_num_layers设置的层数只在选用 LSTM 时生效;若选用 MLP,其层数固定为 2,该参数不会起作用。读者在调参时应结合自己选择的编码器类型来判断该参数是否真正生效。

构建config后打印可见其完整字段:

PromptEncoderConfig(peft_type=<PeftType.P_TUNING: 'P_TUNING'>, auto_mapping=None, base_model_name_or_path=None, revision=None, task_type=<TaskType.CAUSAL_LM: 'CAUSAL_LM'>, inference_mode=False, num_virtual_tokens=10, token_dim=None, num_transformer_submodules=None, num_attention_heads=None, num_layers=None, encoder_reparameterization_type=<PromptEncoderReparameterizationType.MLP: 'MLP'>, encoder_hidden_size=1024, encoder_num_layers=5, encoder_dropout=0.1)

其中peft_type=<PeftType.P_TUNING: 'P_TUNING'>表明这是 P-Tuning 适配器。与 Lora 不同,P-Tuning不需要指定target_modules——因为它不向任何既有模块注入低秩旁路,而是整体替换/新增输入侧的可训练嵌入,因此无需关心c_attnc_projw1w2等注意力层命名。

随后通过get_peft_model(model, config)将普通模型包装为 P-Tuning 模型:此时主模型全部参数被冻结,唯一可训练的是 prompt encoder 相关参数,参数量远小于 Lora 方案。

配置训练参数并使用 Trainer 训练

训练参数仍使用TrainingArguments配置:

args = TrainingArguments( output_dir="./output/Qwen", per_device_train_batch_size=2, gradient_accumulation_steps=2, logging_steps=10, num_train_epochs=3, # gradient_checkpointing=True, save_steps=100, learning_rate=1e-4, save_on_each_node=True )

与 Lora 篇(per_device_train_batch_size=8且开启gradient_checkpointing)相比,P-Tuning 脚本将单卡 batch size 调低至 2,且默认不开启梯度检查点,在显存占用上更加宽裕——这正是 P-Tuning 只训练少量 prompt 参数带来的红利。各参数作用:output_dir为模型输出路径;gradient_accumulation_steps做梯度累加(显存小时可减小 batch、增大累加步数);logging_steps控制日志打印频率;num_train_epochs为训练轮数;save_steps控制 checkpoint 保存频率;learning_rate=1e-4为学习率;save_on_each_node=True表示每个节点都保存权重。

训练流程与 Lora 完全一致,把模型、参数与数据集交给Trainer即可:

trainer = Trainer( model=model, args=args, train_dataset=tokenized_id, data_collator=DataCollatorForSeq2Seq(tokenizer=tokenizer, padding=True), ) trainer.train() # 开始训练

DataCollatorForSeq2Seq负责在 batch 内将不等长序列 padding 对齐。训练完成后,微调得到的 prompt encoder 参数会与主模型一起保存于output_dir

模型推理

训练结束后可直接调用 Qwen 自带的model.chat接口验证微调效果:

response, history = model.chat(tokenizer, "你是谁", history=[], system="现在你要扮演皇帝身边的女人--甄嬛.") print(response)

若模型已收敛,模型应能以甄嬛的口吻回答"你是谁"这类问题,验证 P-Tuning 确实将对话风格注入到了可训练的 Soft Prompt 中。

总结:P-Tuning vs Lora 的选型建议

  • 原理层面:Lora 通过低秩矩阵近似权重的增量,可训练参数分布在注意力与前馈层的旁路中;P-Tuning 则完全冻结主模型,只训练输入侧一段由 LSTM/MLP 编码的 Soft Prompt。
  • 参数取舍:P-Tuning 的encoder_num_layers仅对 LSTM 生效,MLP 固定 2 层;Lora 则需要针对具体模型设置target_modules(Qwen-7B-Chat 为["c_attn", "c_proj", "w1", "w2"])。
  • 显存与效果权衡:P-Tuning 可训练参数更少、显存占用更低,适合硬件资源紧张、追求低成本风格定制的场景;Lora 可训练参数量更大,在复杂任务上通常具有更高的效果上限。
  • 数据与流程共性:两者的数据格式化、tokenizer/模型加载、Trainer 训练流程完全一致,切换方法时只需替换configget_peft_model的调用方式,这也正是本仓库将两篇教程并列编排的用意所在。

如需进一步深入,可对照阅读 04-Qwen-7B-Chat Lora 微调 及同级目录下的 微调脚本,并结合 dataset/huanhuan.json 实际跑通完整流程。

【免费下载链接】self-llm《开源大模型食用指南》针对中国宝宝量身打造的基于Linux环境快速微调(全参数/Lora)、部署国内外开源大模型(LLM)/多模态大模型(MLLM)教程项目地址: https://gitcode.com/GitHub_Trending/se/self-llm

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/12 3:47:40

局部线性嵌入LLE:流形学习的原理、推导与NumPy实现

做流形学习的相关研究或课程作业时&#xff0c;局部线性嵌入&#xff08;Locally Linear Embedding&#xff0c;LLE&#xff09;这个名字总是绕不过去。它和 Isomap 一起被认为是流形学习领域的开山之作&#xff0c;2000 年发表在Science上时&#xff0c;给当时被 PCA 这类线性…

作者头像 李华
网站建设 2026/9/12 3:45:23

python的图论工业场景模拟第一百三十二篇:动态物料分配与通道失效韧性分析,任务:模拟通道随机失效,追踪最大流衰减输出韧性报告,图建模说明:动态有向图,删边与最大流迭代,核心点:动态失效韧性仿真。

⚠️ 前置说明&#xff1a;本篇是“网络流问题&#xff08;第 7 章&#xff09;”的韧性工程篇。核心目标是&#xff1a;在最大流算完之后&#xff0c;模拟传送带/管路/通信链路随机失效&#xff0c;观察最大流怎么掉、掉多少、掉在哪&#xff0c;输出一份“系统抗打击能力”报…

作者头像 李华
网站建设 2026/9/12 3:43:42

Smart Form跨系统传输与俄语多语言落地实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华