《大语言模型开发:用开源模型开发本地系统》阅读报告(3)
[复制链接]
在前两周阅读《大语言模型开发》这本书时,发现书中提供了使用Llama2模型进行训练和微调的具体案例,具有很强的实践参考价值。书中说明Llama-2-7b模型的代码可通过在Hugging Face网站申请获得,于是我们在该网站提交了申请,认为其开源性质应该能够顺利使用。但由于期末考核任务繁重,一直没有关注申请进度。直到前两天准备更新报告时,才发现申请并未通过。
因此,本次报告我们只能基于书中的理论知识,纸上谈兵地向各位老师汇报我们的学习成果。另外,不知道其他朋友是否成功申请过,或者是否遇到过类似的情况,也请大家分享一下经验。
我们将按照书中介绍的模型训练过程对其中涉及的关键模块逐一介绍,整个训练流程的组成部分如下图所示:
1. 预训练模型
在模型训练过程中,预训练模型是不可或缺的一部分。我们通常不会从头开始构建一个大语言模型,因为这需要巨大的计算资源和数据。因此,我们主要通过Hugging Face的transformers库来引入预训练模型。transformers库是一个非常强大的工具,它提供了数以千计的预训练模型,支持多种语言的文本分类、信息抽取、问答、摘要、翻译、文本生成等任务。它提供了便于快速下载和使用的API,让你可以把预训练模型用在给定文本、在本地的数据集上微调。
在我们的案例中,我们使用了AutoModelForCausalLM类来加载预训练的因果语言模型(Causal Language Model)。这个类允许我们从预训练模型库中加载模型,并支持多种参数以自定义加载过程。具体到我们的代码示例中,我们使用了from_pretrained方法来加载'meta-llama/Llama-2-7b-hf'模型。这个方法通过from_pretrained字段配置我们需要的预训练模型,并且可以通过quantization_config参数来选择模型的量化精度。
model = AutoModelForCausalLM.from_pretrained(
'meta-llama/Llama-2-7b-hf',
cache_dir="/kaggle/working/",
trusr_remote_code=True,
device_map='auto',
quantization_config=bnb_config,
)
2. 量化技术
有关模型的量化技术,书中也有所介绍。量化技术是模型优化的重要手段之一,它通过减少模型参数的位宽来减少存储空间和加速计算,同时降低能耗。在我们的代码中,我们使用了load_in_4bit参数来决定是否使用4位量化精度的模型。
由此,我们便引入了第二个比较重要的成分,就是配置量化精度的BitsAndBytesConfig模型,用于设置模型的量化参数。通过这个类,我们可以灵活地配置模型的量化精度、分位数等超参数,从而在减少模型存储空间和加速计算的同时,尽量保持模型的性能。
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type='nf4',
bnb_4bit_compute_dtype='float16',
bnb_4bit_use_double_quant=True,
)
3. 分词器
除此之外,我们还需要使用分词器来处理自然语言任务。分词器(Tokenizer)在自然语言处理(NLP)中是一个关键组件,负责将文本字符串转换成模型可以处理的结构化数据形式,通常是将文本切分成“tokens”或单词、短语、子词等单位。这些tokens是模型理解文本的基础。分词器的类型和复杂性可以根据任务需求而变化,从简单的基于空格的分割到更复杂的基于规则或机器学习的分词方法。
分词器的工作流程通常包括两个关键步骤:
此外,分词器还会在序列的开始和结束添加特殊标记,如BERT中的CLS和SEP用于特定任务的序列分类或区分输入片段。有时为了确保输入序列的一致长度,分词器可以对较短的序列进行填充,对较长的序列进行截断。
我们主要是通过AutoTokenizer这个类来进行调用。AutoTokenizer 是 Hugging Face transformers 库中的一个实用工具类,主要用于自动加载与特定预训练模型相匹配的分词器。AutoTokenizer 能够自动识别出模型应该使用哪种类型的分词器,并加载对应的配置文件和词汇表。这使得开发者不需要手动指定分词器类型,简化了代码编写过程。一旦分词器被正确加载,它可以对输入文本进行预处理,包括分词、添加特殊标记、编码转换、填充和截断等操作。
tokenizer = AutoTokenizer.from_pretrained(
'meta-llama/Llama-2-7b-hf',
trust_remote_code=True,
)
tokenizer.pad_token = tokenizer.eos_token
我们也可以输入一些文本内容查看其分词的效果:
inputs = tokenizer("Hello, my dog is cute", return_tensors="pt")
labels = torch.tensor([1]).unsqueeze(0)
outputs = model(**inputs, labels=labels)
loss = outputs.loss
logits = outputs.logits
4. 训练方法
由于大模型参数量众多,我们即便在有预训练模型的基础上也不会去大量训练所有的模型参数,只会对其中部分进行微调。这里我们就会使用到LoRA这个方法,其只会对少量额外的模型参数进行训练,示意图如下:
LoRa(Low-Rank Adaptation)是一种用于对预训练大语言模型进行微调的高效方法。它通过引入两个低秩矩阵A和B来减少需要训练的参数数量,从而在保持模型性能的同时,显著降低训练成本。具体来说,如果原始参数矩阵W的大小为d×d,LoRA 会引入两个大小分别为d×r和r×d的矩阵,其中r是一个远小于d的秩。通过这种方式,LoRA大幅减少了需要训练的参数数量。
我们会从peft这个库来调用LoRa方法,peft(Parameter-Efficient Fine-Tuning)是一个用于高效微调大语言模型的库。它提供了多种参数高效的微调方法,包括 LoRa、Prefix-Tuning 等。peft 库的主要目标是通过减少需要训练的参数数量,提高微调的效率和效果。
以下是书中使用的LoRa的参数配置,其中lora_target_modules这个参数就是指出我们会对Llama模型的哪些部分进行训练,与上面的示意图也是对应的。
lora_alpha=16,
lora_dropout=0.1,
lora_r=16,
lora_bias='all',
model_type='llama',
lora_target_modules = [
'q_proj',
'k_proj',
'v_proj',
'o_proj',
'gate_proj',
'up_proj',
'down_proj'
]
我们可以通过LoRaConfig来应用这些参数设置:
peft_config = LoraConfig(
lora_alpha=lora_alpha,
lora_dropout=lora_dropout,
r=lora_r,
bias=lora_bias,
task_type='CAUSAL_LM',
inference_mode=False,
target_modules=lora_target_modules,
)
model = get_peft_model(model, peft_config)
5. 增量模型训练
接下来我们就可以使用LoRA方法来微调我们的模型参数,我们主要是通过调用tansforemres库中的TrainingArguments和Trainer这两个类。它们是Hugging Face中配置训练参数和用于训练和评估模型的类,封装了训练循环,支持多种功能,如分布式训练、混合精度训练、早停(early stopping)、学习率调度等。有关训练参数的配置主要包括:
output_dir = 'your_dir'
optim_type = 'adamw_8bit'
learning_rate = 0.0005
weight_decay = 0.002
per_device_train_batch_size = 1
per_device_eval_batch_size = 1
gradient_accumulation_steps = 16
warmup_steps = 5
save_steps = 100
logging_steps = 100
然后我们调用TraningArguments来对这些应用这些参数设置:
traing_args = TrainingArguments(
output_dir=output_dir,
evaluation_strategy='epoch',
optim=optim_type,
learning_rate=learning_rate,
weight_decay=weight_decay,
per_device_train_batch_size=per_device_train_batch_size,
per_device_eval_batch_size=per_device_eval_batch_size,
gradient_accumulation_steps=gradient_accumulation_steps,
do_train=True,
warmup_steps=warmup_steps,
save_steps=save_steps,
logging_steps=logging_steps,
)
trainer=Trainer(
model=model,
args=training_args,
train_dataset=data_train,
eval_dataset=data_test,
tokenizer=tokenizer,
)
可以看到这些参数的命名也还是比较一目了然的。最后使用如下代码就可以使用LoRA进行训练了。
trainer.train()
6. 模型合并
在使用 LoRa 方法进行微调后,我们通常需要将微调后的增量模型参数合并到原始的预训练模型中。这样,我们可以得到一个完整的、微调后的模型,才可以用于推理或其他任务。整个完整的训练流程我们总结如下:
-
加载预训练模型和分词器:首先,我们需要加载原始的预训练模型和分词器。
-
应用 LoRa 配置:将 LoRa 配置应用到预训练模型中,进行微调。
-
训练模型:使用训练数据对模型进行微调。
-
合并模型:将微调后的 LoRa 增量参数合并到原始模型中,得到一个完整的微调模型。
示意图如下:
合并模型的代码如下:
model = get_peft_model(model, lora_config)
model.save_pretrained('path_to_save_lora_model')
model = PeftModel.from_pretrained(model, 'path_to_save_lora_model')
model = model.merge_and_unload()
model.save_pretrained('path_to_save_merged_model')
tokenizer.save_pretrained('path_to_save_merged_model')
merged_model = AutoModelForCausalLM.from_pretrained('path_to_save_merged_model', trust_remote_code=True)
merged_tokenizer = AutoTokenizer.from_pretrained('path_to_save_merged_model', trust_remote_code=True)
通过上述步骤,我们成功完成了基于预训练模型和LoRa方法的大模型训练尝试。
7. 全书阅读总结
本次《大语言模型开发》的试读活动已接近尾声。本书不仅提供了详尽的理论阐述,还辅以实用的代码示例,极大地助力了我们的学习与实践,我愿意给予高度评价。然而,正如古语所言,“纸上得来终觉浅,绝知此事要躬行”,在实际操作过程中,我们深知还会遭遇诸多挑战与问题。我们也会再去积极获得Llama2的调用权限,或获取更多开源项目资源,以便进一步深化我们的研究与实践。活动结束后,我们仍将不懈努力,持续探索与学习,并及时向各位老师汇报我们的最新进展。
|