跟着,ChatGPT 敏捷爆火,引发了大模型的年代革新。但是关于一般群众来说,进行大模型的预训练或许全量微调遥不行及。由此,催生了各种参数高效微调技能,让科研人员或许一般开发者有时机尝试微调大模型。
因此,该技能值得咱们进行深入分析其背后的机理,之前分享了大模型参数高效微调技能原理总述的文章。下面给大家分享大模型参数高效微调技能实战系列文章,该系列共六篇文章,相关代码均放置在GitHub:llm-action。
- 大模型参数高效微调技能实战(一)-PEFT概述及环境搭建
- 大模型参数高效微调技能实战(二)-Prompt Tuning
- 大模型参数高效微调技能实战(三)-P-Tuning
- 大模型参数高效微调技能实战(四)-Prefix Tuning / P-Tuning v2
- 大模型参数高效微调技能实战(五)-LoRA
- 大模型参数高效微调技能实战(六)-IA3
本文为大模型参数高效微调技能实战的第二篇。
Prompt Tuning 简述
Prompt Tuning(论文:The Power of Scale for Parameter-Efficient Prompt Tuning),该办法能够看作是 Prefix Tuning 的简化版本,它给每个使命界说了自己的Prompt,然后拼接到数据上作为输入,但只在输入层参加prompt tokens,并且不需求参加 MLP 进行调整来解决难训练的问题。
更加详细的介绍可参阅之前的文章:大模型参数高效微调技能原理总述(二)-BitFit、Prefix Tuning、Prompt Tuning
Prompt Tuning 微调实战
为了不影响阅读体会,详细的代码放置在GitHub:llm-action 项目中 peft_prompt_tuning_clm.ipynb文件,这儿仅列出关键步骤。
第一步,引进必要的库,如:Prompt Tuning 配置类 PromptTuningConfig
。
from peft import get_peft_config, get_peft_model, PromptTuningInit, PromptTuningConfig, TaskType, PeftType
第二步,创建 Prompt Tuning 微调办法对应的配置。
peft_config = PromptTuningConfig(
task_type=TaskType.CAUSAL_LM,
prompt_tuning_init=PromptTuningInit.TEXT,
num_virtual_tokens=8,
prompt_tuning_init_text="Classify if the tweet is a complaint or not:",
tokenizer_name_or_path=model_name_or_path,
)
参数阐明:
- prompt_tuning_init:提示嵌入的初始化办法。PEFT支撑文本(TEXT)和随机(RANDOM)初始化。在原理篇中提到过 Prompttoken 的初始化办法和长度关于模型功能有影响。与随机初始化和运用样本词汇表初始化比较,Prompt Tuning 选用类标签初始化模型的作用更好。不过跟着模型参数规模的提高,这种gap最终会消失。因此,假如需求运用类标签和样本词汇表初始化需指定为TEXT。
- prompt_tuning_init_text:用于初始化提示嵌入的文本,在运用文本(TEXT)初始化办法时运用。
- task_type:指定使命类型。如:条件生成使命(SEQ_2_SEQ_LM),因果言语建模(CAUSAL_LM)等。
- num_virtual_tokens:指定虚拟Token数。在原理篇中,提到过提示虚拟 Token的长度在20左右时的表现已经不错(超过20之后,提高Prompt token长度,对模型的功能提高不明显了);相同的,这个gap也会跟着模型参数规模的提高而减小(即关于超大规模模型而言,即便提示虚拟 Token 长度很短,对功能也不会有太大的影响)。
第三步,经过调用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 办法能够检查可训练参数的数量(仅为8,192)以及占比(仅为0.00146%)。
trainable params: 8,192 || all params: 559,222,784 || trainable%: 0.0014648902430985358
Prompt Tuning 模型类结构如下所示:
PeftModelForCausalLM(
(base_model): BloomForCausalLM(
(transformer): BloomModel(
(word_embeddings): Embedding(250880, 1024)
(word_embeddings_layernorm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
(h): ModuleList(
...
)
(ln_f): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
)
(lm_head): Linear(in_features=1024, out_features=250880, bias=False)
)
(prompt_encoder): ModuleDict(
(default): PromptEmbedding(
(embedding): Embedding(8, 1024)
)
)
(word_embeddings): Embedding(250880, 1024)
)
从模型类结构能够看到,Prompt Tuning 只在输入层参加 prompt virtual tokens,其他地方均没有改变,详细可检查 PromptEmbedding 的源码。
class PromptEmbedding(torch.nn.Module):
def __init__(self, config, word_embeddings):
super().__init__()
total_virtual_tokens = config.num_virtual_tokens * config.num_transformer_submodules
# 初始化 embedding 层
self.embedding = torch.nn.Embedding(total_virtual_tokens, config.token_dim)
# 假如运用文本进行初始化,执行如下逻辑,PromptTuningConfig 配置类需求传入初始化文本。
if config.prompt_tuning_init == PromptTuningInit.TEXT:
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained(config.tokenizer_name_or_path)
init_text = config.prompt_tuning_init_text
init_token_ids = tokenizer(init_text)["input_ids"]
# Trim or iterate until num_text_tokens matches total_virtual_tokens
num_text_tokens = len(init_token_ids)
if num_text_tokens > total_virtual_tokens:
init_token_ids = init_token_ids[:total_virtual_tokens]
elif num_text_tokens < total_virtual_tokens:
num_reps = math.ceil(total_virtual_tokens / num_text_tokens)
init_token_ids = init_token_ids * num_reps
init_token_ids = init_token_ids[:total_virtual_tokens]
word_embedding_weights = word_embeddings(torch.LongTensor(init_token_ids)).detach().clone()
word_embedding_weights = word_embedding_weights.to(torch.float32)
# 初始化embedding层的权重
self.embedding.weight = torch.nn.Parameter(word_embedding_weights)
def forward(self, indices):
# Just get embeddings
prompt_embeddings = self.embedding(indices)
return prompt_embeddings
第四步,模型训练的其余部分均无需更改,当模型训练完结之后,保存高效微调部分的模型权重以供模型推理即可。
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_PROMPT_TUNING_CAUSAL_LM
├── [ 500] adapter_config.json
├── [ 33K] adapter_model.bin
└── [ 111] README.md
0 directories, 3 files
留意:这儿只会保存经过训练的增量 PEFT 权重。其中,adapter_config.json
为 Prompt Tuning 配置文件;adapter_model.bin
为 Prompt Tuning 权重文件。
第五步,加载微调后的权重文件进行推理。
from peft import PeftModel, PeftConfig
peft_model_id = f"{model_name_or_path}_{peft_config.peft_type}_{peft_config.task_type}"
# 加载PEFT配置
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)
# Tokenizer编码
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
)
# Tokenizer 解码
print(tokenizer.batch_decode(outputs.detach().cpu().numpy(), skip_special_tokens=True))
至此,咱们完结了Prompt Tuning的训练及推理。
结语
本文对 Prompt Tuning 的基本原理进行了简述;同时,解说了运用 Prompt Tuning 技能进行模型训练及推理。下文将对 P-Tuning 微调技能进行实战解说。
假如觉得我的文章能够能够给您带来协助,期待您的点赞收藏加关注~~