随着ChatGPT的快速崛起,大型模型的时代正在发生革命性变化。但对于很多人而言,进行大型模型的预训练或全面微调似乎是遥不可及的。
不过随着多种高效参数微调技术的涌现,科研人员和普通开发者都有机会尝试微调这些庞大的模型了。
本文我将分享了大模型微调技术的原理及代码案例 ,完整版代码,可在文末获取。
Prefix Tuning(论文:Prefix-Tuning: Optimizing Continuous Prompts for Generation),在输入token之前构造一段任务相关的virtual tokens作为Prefix;然后,在训练的时候只更新Prefix部分的参数,而 PLM 中的其他部分参数固定。
针对不同的模型结构,需要构造不同的 Prefix。
z = [PREFIX; x; y]
,合适的上文能够在固定 LM 的情况下去引导生成下文(比如:GPT3的上下文学习)。z = [PREFIX; x; PREFIX0; y]
。Encoder端增加前缀是为了引导输入部分的编码,Decoder 端增加前缀是为了引导后续token的生成。同时,为了防止直接更新 Prefix 的参数导致训练不稳定和性能下降的情况,在 Prefix 层前面加了 MLP 结构,训练完成后,只保留 Prefix 的参数。
除此之外,通过消融实验证实,只调整embedding层的表现力不够,将导致性能显著下降,因此,在每层都加了prompt的参数,改动较大。
P-Tuning v2(论文: P-Tuning v2: Prompt Tuning Can Be Comparable to Fine-tuning Universally Across Scales and Tasks),该方法在每一层都加入了Prompts tokens作为输入,而不是仅仅加在输入层,这带来两个方面的好处:
具体做法基本同Prefix Tuning,可以看作是将文本生成的Prefix Tuning技术适配到NLU任务中,然后做了一些改进:
为了不影响阅读体验,完整版代码在公众号:机器学习社区,回复:pref,即可获取,这里仅列出关键步骤。
第一步,引进必要的库,如:Prefix Tuning / P-Tuning v2 配置类 PrefixTuningConfig
。
from peft import get_peft_config, get_peft_model, PrefixTuningConfig, TaskType, PeftType
第二步,创建 Prefix Tuning / P-Tuning v2 微调方法对应的配置。
peft_config = PrefixTuningConfig(task_type=TaskType.CAUSAL_LM, num_virtual_tokens=30)
PrefixTuningConfig 配置类参数说明:
task_type:指定任务类型。如:条件生成任务(SEQ_2_SEQ_LM),因果语言建模(CAUSAL_LM)等。
num_virtual_tokens:虚拟token的数量,换句话说就是提示(prompt)。
inference_mode:是否在推理模式下使用Peft模型。
prefix_projection:是否投影前缀嵌入(token),默认值为false,表示使用P-Tuning v2, 如果为true,则表示使用 Prefix Tuning。
第三步,通过调用 get_peft_model
方法包装基础的 Transformer 模型。
model = AutoModelForCausalLM.from_pretrained(model_name_or_path)
model = get_peft_model(model, peft_config)
model.print_trainable_parameters()
通过 print_trainable_parameters 方法可以查看到 P-Tuning v2 可训练参数的数量(仅为1,474,560)以及占比(仅为0.2629%)。
trainable params: 1,474,560 || all params: 560,689,152 || trainable%: 0.26299064191632515
PEFT 中 Prefix Tuning 相关的代码是基于清华开源的P-tuning-v2 进行的重构;同时,我们可以在chatglm-6b和chatglm2-6b中看到类似的代码。PEFT 中源码如下所示。
class PrefixEncoder(torch.nn.Module):
def __init__(self, config):
super().__init__()
self.prefix_projection = config.prefix_projection
token_dim = config.token_dim
num_layers = config.num_layers
encoder_hidden_size = config.encoder_hidden_size
num_virtual_tokens = config.num_virtual_tokens
if self.prefix_projection andnot config.inference_mode:
# Use a two-layer MLP to encode the prefix
# 初始化重参数化的编码器
self.embedding = torch.nn.Embedding(num_virtual_tokens, token_dim)
self.transform = torch.nn.Sequential(
torch.nn.Linear(token_dim, encoder_hidden_size),
torch.nn.Tanh(),
torch.nn.Linear(encoder_hidden_size, num_layers * 2 * token_dim),
)
else:
self.embedding = torch.nn.Embedding(num_virtual_tokens, num_layers * 2 * token_dim)
def forward(self, prefix: torch.Tensor):
if self.prefix_projection:
prefix_tokens = self.embedding(prefix)
past_key_values = self.transform(prefix_tokens)
else:
past_key_values = self.embedding(prefix)
return past_key_values
从上面的源码也可以看到 Prefix Tuning 与 P-Tuning v2 最主要的差别就是是否进行重新参数化编码。
第四步,模型训练的其余部分均无需更改,当模型训练完成之后,保存高效微调部分的模型权重以供模型推理即可。
peft_model_id = f"{model_name_or_path}_{peft_config.peft_type}_{peft_config.task_type}"
model.save_pretrained(peft_model_id)
输出的模型权重文件如下所示:
/data/nfs/llm/model/bloomz-560m_PREFIX_TUNING_CAUSAL_LM
├── [ 390] adapter_config.json
├── [5.6M] adapter_model.bin
└── [ 93] README.md
0 directories, 3 files
注意:这里只会保存经过训练的增量 PEFT 权重。其中,adapter_config.json
为 P-Tuning v2 / Prefix Tuning 配置文件;adapter_model.bin
为 P-Tuning v2 / Prefix Tuning 权重文件。
第五步,加载微调后的权重文件进行推理。
from peft import PeftModel, PeftConfig
peft_model_id = f"{model_name_or_path}_{peft_config.peft_type}_{peft_config.task_type}"
config = PeftConfig.from_pretrained(peft_model_id)
# 加载基础模型
model = AutoModelForCausalLM.from_pretrained(config.base_model_name_or_path)
# 加载PEFT模型
model = PeftModel.from_pretrained(model, peft_model_id)
# 编码
inputs = tokenizer(f'{text_column} : {dataset["test"][i]["Tweet text"]} Label : ', return_tensors="pt")
# 模型推理
outputs = model.generate(
input_ids=inputs["input_ids"],
attention_mask=inputs["attention_mask"],
max_new_tokens=10,
eos_token_id=3
)
# 解码
print(tokenizer.batch_decode(outputs.detach().cpu().numpy(), skip_special_tokens=True))
至此,我们完成了 Prefix Tuning / P-Tuning v2 的训练及推理。
本文对 Prefix Tuning / P-Tuning v2 的基本原理进行了简述;同时,讲解了使用 Prefix Tuning / P-Tuning v2 技术进行模型训练及推理。下文将对 LoRA 微调技术进行实战讲解。
如果觉得我的文章能够能够给您带来帮助,期待您的点赞收藏加关注~~
完整版代码在公众号:机器学习社区,回复:pref,即可获取。
建了实战技术交流群!想要进交流群的同学,可以直接加微信号:mlc2060。加的时候备注一下:研究方向 +学校/公司+知乎,即可。然后就可以拉你进群了。
前沿技术资讯、算法交流、求职内推、算法竞赛、面试交流(校招、社招、实习)等、与 10000+来自港科大、北大、清华、中科院、CMU、腾讯、百度等名校名企开发者互动交流~