共计 16017 个字符,预计需要花费 41 分钟才能阅读完成。
应用 QLoRA 对 Llama 2 进行微调是咱们罕用的一个办法,然而在微调时会遇到各种各样的问题,所以在本文中,将尝试以具体正文的形式给出一些常见问题的答案。这些问题是特定于代码的,大多数正文都是针对所波及的开源库以及所应用的办法和类的问题。
导入库
对于大模型,第一件事是又多了一些不相熟的 Python 库。
!pip install -q peft==0.4.0 bitsandbytes==0.40.2 transformers==4.31.0 trl==0.4.7
咱们必须首先装置 accelerate, peft, bitsandbytes, transformers 和 trl。除了 transformers,其余的库都很生疏
transformers 是这里最古老的库,PyPI 上最早的版本 (2.0.0) 能够追溯到 2019 年。它是 huggingface 公布的库,能够快速访问文本,图像和音频 (从 hugs 的 API 下载) 的机器学习模型。它还提供训练和微调模型的性能,并能够 HuggingFace 模型核心共享这些模型。库没有像 Pytorch 或 Tensorflow 那样从头开始构建神经网络的形象层和模块,它提供了专门针对模型进行优化的训练和推理 api。transformer 是用于 LLM 微调的要害 Python 库之一,因为目前大部分的 LLM 都是能够通过它来加载应用。
bitsandbytes 是一个绝对较新的库,PyPI 上最早的版本时 2021 年公布的。它是 CUDA 自定义函数的轻量级包装,专门为 8 位优化器、矩阵乘法和量化而设计。它次要提供了优化和量化模型的性能,特地是对于 llm 和 transformers 模型。它还提供了 8 位 Adam/AdamW、SGD momentum、LARS、LAMB 等函数。bitsandbytes 的指标是通过 8 位操作实现高效的计算和内存应用从而使 llm 更易于拜访。通过利用 8 位优化和量化技术能够进步模型的性能和效率。在较小尺寸的消费类 gpu(如 RTX 3090)上运行 llm 存在内存瓶颈。所以人们始终对试图缩小运行 llm 的内存需要的权重量化技术进行钻研。bitsandbytes 的想法是量化模型权重的浮点精度,从较大的精度点 (如 FP32) 到较小的精度点 (如 Int8) (4×4 Float16)。有一些技术能够将 FP32 量化为 Int8,包含 abmax 和零点量化,但因为这些技术的局限性,bitsandbytes 库的创建者独特撰写了 LLM.int8() 论文以及 8 位优化器,为 llm 提供无效的量化办法。所以因为 bitsandbytes 库提供的量化技术,这在很大水平上让咱们在生产级的 GPU 上能够微调更大的模型。
Peft 容许咱们缩小将 LLM(或其局部)加载到工作内存中以进行微调的内存需要。与应用较小深度学习模型的迁徙学习技术不同,在迁徙学习技术中,咱们须要解冻像 AlexNet 这样的神经网络的较低层,而后在新工作上对分类层进行齐全微调,而应用 llm 进行这种微调的老本是微小的。Parameter Efficient Fine-Tuning(PEFT)办法是一组使 llm 适应上游工作的办法,例如在内存受限的设施 (如 T4GPU 提供 16GB VRAM) 上进行摘要或问答。通过 Peft 对 LLM 的局部进行微调,依然能够取得与齐全微调相比的后果。如 LoRA 和 Prefix Tuning 是相当胜利的。peft 库是一个 HuggingFace 库,它提供了这些微调办法,这是一个能够追溯到 2023 年 1 月的新库。在本文中咱们将应用 QLoRA,这是一种用于量化 llm 的低秩自适应或微调技术。
trl 是另一个 HuggingFace 库,trl 其实是自 2021 年公布的,然而在 2023 年 1 月才被人们热传。TRL 是 Transformer Reinforcement Learning 的缩写也就是 Transformer 强化学习。它提供了在训练和微调 LLM 的各个步骤中的不同算法的实现。包含监督微调步骤 (SFT),处分建模步骤(RM) 和近端策略优化 (PPO) 步骤。trl 也将 peft 作为一个依赖项,所以能够应用带有 peft 办法 (例如 LoRA) 的 SFT 训练器。
dataset 尽管没有蕴含在咱们之前的安装包列表中(这是因为它是 transformers 的一个依赖项),但 dataset 库是 huggingface 生态系统中的另一个重要局部。它能够不便的拜访 HuggingFace 托管的许多公共数据集,也就是说省去了咱们本人写 dataset 和 dataloader 的工夫。
下面这些库对于 LLM 的任何工作都是至关重要的。这里做了一个简略的图片来总结这些库是如何组合在一起的。
上面让咱们看一下导入
import os
import torch
from datasets import load_dataset
from transformers import (
AutoModelForCausalLM,
AutoTokenizer,
BitsAndBytesConfig,
TrainingArguments,
pipeline,
logging,
)
from peft import LoraConfig, PeftModel
from trl import SFTTrainer
咱们持续剖析导入
torch 是咱们很相熟的深度学习库,这里咱们不须要 torch 的那些低级性能,然而它是 transformers 和 trl 的依赖,在这里咱们须要应用 torch 来获取 dtypes(数据类型),比方 torch.Float16 以及查看 GPU 的工具函数。
load_dataset 所做的就是加载数据集,然而它从 HuggingFace 数据集中心下载到本地。所以这是一个在线加载程序,但它既高效又简略,只须要一行代码。
dataset = load_dataset(dataset_name, split="train")
因为模型很多所以 transformer 库提供了一组称为 Auto classes 的类,这些类给出了预训练模型的名称 / 门路,它能够主动推断出正确的构造并检索相干模型。这个 AutoModelForCausalLM 是一个通用的 Auto 类,用于加载用于因果语言建模的模型。
对于 transformers,HuggingFace 提供了两种类型的语言建模,因果和掩码掩蔽。因果语言模型包含;GPT- 3 和 Llama,这些模型预测标记序列中的下一个标记,以生成与输出数据语义类似的文本。AutoModelForCausalLM 类将从模型核心检索因果模型,并加载模型权重,从而初始化模型。from_pretrained()办法为咱们实现了这项工作。
model_name = "NousResearch/Llama-2-7b-chat-hf"
model = AutoModelForCausalLM.from_pretrained(
model_name,
device_map=device_map
)
AutoTokenizer 是对文本数据进行标记化。它提供了一种无需显式指定标记器类就能够初始化和应用不同模型的标记器的不便的办法。它也是一个通用的 Auto 类,所以它能够依据提供的模型名称或门路主动抉择适当的标记器。标记器将输出文本转换为标记,这些标记是 NLP 模型应用的根本文本单位。它还提供了额定的性能,如填充、截断和注意力掩码等。AutoTokenizer 简化了为 NLP 工作对文本数据进行标记的过程。咱们能够看到在上面初始化 AutoTokenizer,前面咱们会应用 SFTTrainer 将初始化的 AutoTokenizer 作为参数。
model_name = "NousResearch/Llama-2-7b-chat-hf"
# Load LLaMA tokenizer
tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
BitsAndBytesConfig,后面曾经说了咱们应用 bitsandbytes 进行量化。transformer 库最近增加了对 bitsandbytes 的全面反对,因而应用 BitsandBytesConfig 能够配置 bitsandbytes 提供的任何量化办法,例如 LLM.int8、FP4 和 NF4。将量化配置传递给 AutoModelForCausalLM 初始化器,这样在加载模型权重时就会间接应用量化的办法。
#bits and byte config
bnb_config = BitsAndBytesConfig(
load_in_4bit=use_4bit,
bnb_4bit_quant_type=bnb_4bit_quant_type,
bnb_4bit_compute_dtype=compute_dtype,
bnb_4bit_use_double_quant=use_nested_quant,
)
model = AutoModelForCausalLM.from_pretrained(
model_name,
quantization_config=bnb_config, #pass to AutoModelForCausalLM
device_map=device_map
)
TrainingArguments 非常简单。它用于存储 SFTTrainer 的所有训练参数。SFFTrainer 承受不同类型的参数,TrainingArguments 帮忙咱们将所有相干的训练参数组织到一个数据类中放弃代码的整洁和有组织。
还有一些很好的工具类能够与 TrainingArguments 一起应用,比方 HfArgumentParser 能够为 TrainingArguments 创立一个参数解析器,这对 CLI 应用程序很有用。
#TrainingArguments
training_arguments = TrainingArguments(
output_dir=output_dir,
num_train_epochs=num_train_epochs,
per_device_train_batch_size=per_device_train_batch_size,
gradient_accumulation_steps=gradient_accumulation_steps,
optim=optim,
save_steps=save_steps,
logging_steps=logging_steps,
learning_rate=learning_rate,
weight_decay=weight_decay,
fp16=fp16,
bf16=bf16,
max_grad_norm=max_grad_norm,
max_steps=max_steps,
warmup_ratio=warmup_ratio,
group_by_length=group_by_length,
lr_scheduler_type=lr_scheduler_type,
report_to="tensorboard"
)
在实现微调之后,咱们将应用 pipeline 进行推理。能够抉择各种管道工作的列表,像“图像分类”,“文本摘要”等。还能够为工作抉择要应用的模型。为了定制也能够增加一个参数来进行某种模式的预处理,如标记化或特征提取。
pipe = pipeline(task="text-generation", model=model, tokenizer=tokenizer, max_length=200)
从 transformer 导入的最初一个内容是 logging。这是一个日志零碎,这在调试代码时十分有用。
logging.set_verbosity(logging.CRITICAL)
从 peft 库中导入的 LoraConfig 数据类是一个配置类,它次要存储初始化 LoraModel 所需的配置,LoraModel 是 PeftTuner 的一个实例。咱们将此配置传递给 SFTTrainer,它将应用该配置初始化适当的 tuner。
# Load LoRA configuration
peft_config = LoraConfig(
lora_alpha=lora_alpha,
lora_dropout=lora_dropout,
r=lora_r,
bias="none",
task_type="CAUSAL_LM",
)
PeftModel,一旦咱们应用一种 peft 办法 (如 LoRA) 进行微调,就须要将 LoRA 适配器权重保留到磁盘并在应用时将它们加载回内存。PEFT 模块微调的权重,与根本模型权重是离开。应用 PeftModel,还能够抉择将将 base_model 权重与新微调的适配器权重合并 (调整),这样就失去了一个残缺的新模型。PeftModel.from_pretrained() 从内存中加载适配器权重,merge_and_unload()办法将它们与 base_model 合并。
# Reload base_model in FP16 and merge it with LoRA weights
base_model = AutoModelForCausalLM.from_pretrained(
model_name,
low_cpu_mem_usage=True,
return_dict=True,
torch_dtype=torch.float16,
device_map=device_map,
)
model = PeftModel.from_pretrained(base_model, new_model)
model = model.merge_and_unload()
最初一个导入是 SFTTrainer。SFTTrainer 是 transformer Trainer 类的子类。Trainer 是一个功模型训练的泛化 API。SFTTrainer 在此基础上减少了对参数微调的反对。有监督的微调步骤是训练因果语言模型 (如 Llama) 用于上游工作 (如指令遵循) 的关键步骤。
SFTTrainer 反对 PEFT,因而咱们将与 LoRA 一起应用 SFTTrainer。而后,SFTTrainer 将应用 LoRA 执行监督微调。而后咱们能够运行训练器 (train()) 并保留权重(save_pretrained())。
#Initialize the SFTTrainer object
trainer = SFTTrainer(
model=model,
train_dataset=dataset,
peft_config=peft_config,
dataset_text_field="text",
max_seq_length=max_seq_length,
tokenizer=tokenizer,
args=training_arguments,
packing=packing,
)
# Train model
trainer.train()
# Save trained model
trainer.model.save_pretrained(new_model)
对于援用,咱们也总结了一张图片
训练参数
当初咱们晓得了须要哪些库来调优 Llama 2(或任何 LLM),也晓得了这些库中须要的类,并且理解了这些类的性能。上面就是对后面导入的参数的介绍
模型和数据集名称:
# The model that you want to train from the Hugging Face hub
model_name = "NousResearch/Llama-2-7b-chat-hf"
# The instruction dataset to use
dataset_name = "mlabonne/guanaco-llama2-1k"
# Fine-tuned model name
new_model = "llama-2-7b-miniguanaco"
model_name、dataset_name 和 new_model。这些名称遵循 HuggingFace 模型及其 hub 上的数据集名称的格局。
birushuo 给一个名字“NousResearch/ Llama-2-7b-chat-hf”这个名字的第一局部 NousResearch 是一个钻研机构,也就是它 HuggingFace 账户的名称,第二局部是模型名称 lama-2 – 7b-chat-hf。模型命名的倡议是给模型提供描述性的名称,包含有用的信息,如独特的模型名称(lama-2),要害参数信息(7b),以及一些对于模型如何工作的其余有用信息(chat-hf)。咱们在 new_model 名称 llama-2-7b-miniguanaco 中看到了同样的规定,这是咱们调配给微调模型的名称。这里附加了在 miniguanaco 上进行微调的数据集的名称。
QLoRA 参数:
# LoRA attention dimension
lora_r = 64
# Alpha parameter for LoRA scaling
lora_alpha = 16
# Dropout probability for LoRA layers
lora_dropout = 0.1
咱们将应用的参数是 r (lora_r)、lora_alpha 和 lora_dropout。这些参数对于 LoRA 来说是最重要的,要了解其中的起因,必须深刻理解 LoRA 的论文,咱们只做简略的总结:
在神经网络中,反向流传算法计算期望值和理论值之间的误差,而后用这个误差来计算 delta,这是神经网络中权重对 e 的奉献。如果你有一个神经网络的初始权值 W0 那么对于误差 e,咱们计算 delta_W0 =∆W。而后应用∆W 来更新权重 W0 +∆W,以减小误差 e。LoRA 提出将∆W 合成为两组低秩矩阵 A 和 B,使 W0 +∆W = W0 + BA。而不是应用残缺的∆W 更新,咱们应用较小的低秩更新矩阵 BA,这就是咱们如何实现雷同效率和更低的计算需要。如果∆W 的大小为 (d × k) (W0 的大小),则咱们将∆W 合成为两个矩阵:B 和 A,维度别离为(d × r) 和(r × k),其中 r 为秩。
LoraConfig 中的参数 r (lora_r)是决定更新矩阵 BA 形态的秩。依据论文能够设置一个小的秩,并且失去很好的后果。当咱们更新 W0 时,能够通过应用缩放因子 α 来管制 BA 的影响,这个缩放因子作为学习率。比例因子是咱们的第二个参数(lora_alpha)。最初设置 lora_dropput,这是正则化的典型 dropput。
BitsandBytes 参数:
# Activate 4-bit precision base model loading
use_4bit = True
# Compute dtype for 4-bit base models
bnb_4bit_compute_dtype = "float16"
# Quantization type (fp4 or nf4)
bnb_4bit_quant_type = "nf4"
# Activate nested quantization for 4-bit base models (double quantization)
use_nested_quant = False
咱们正在应用一种称为 QLoRA 的量化版本的 LoRA,这意味着咱们心愿在 LoRA 微调中应用量化,将量化利用于咱们后面提到的更新权重(以及其余能够量化的操作)。
参数 use_4bit(第 6 行)设置为 True,以应用高保真的 4 位微调,这是起初在 QLoRA 论文中引入的,以实现比 LLM.int8 论文中引入的 8 位量化更低的内存要求。
设置 bnb_4bit_compute_dtype(第 9 行),这是执行计算的数据类型(float16)。也就是说尽管将权重通过 4 位量化存储,但计算还是产生在 16 位或 32 位。
应用 bnb_4bit_quant_type(第 12 行),nf4,依据 QLoRA 论文,nf4 显示了更好的实践和教训性能。
参数 use_nested_quant 设置为 False,并将其传递给 bnb_4bit_use_double_quant。模型在第一次量化之后启用第二次量化,从而为每个参数额定节俭 0.4 位。
下面一些参数都是 QLoRA 的论文提供,如果想深刻理解,请查看论文或咱们以前的文章
在本文中咱们抉择 NF4 量化 FP16 (float16)精度进行计算后,咱们应该对 Colab T4 GPU (16 GB VRAM)没有内存限度。咱们做个简略的计算:如果应用 Llama-2-7B(70 亿 params)和 FP16(没有量化),咱们失去 7B × 2 字节 = 14 GB(所需的 VRAM)。应用 4 位量化,咱们失去 7B × 0.5 字节 = ~ 4gb(所需的 VRAM)。
训练参数:
# Output directory where the model predictions and checkpoints will be stored
output_dir = "./results"
# Number of training epochs
num_train_epochs = 1
# Enable fp16/bf16 training (set bf16 to True with an A100)
fp16 = False
bf16 = False
# Batch size per GPU for training
per_device_train_batch_size = 4
# Batch size per GPU for evaluation
per_device_eval_batch_size = 4
# Number of update steps to accumulate the gradients for
gradient_accumulation_steps = 1
# Maximum gradient normal (gradient clipping)
max_grad_norm = 0.3
# Initial learning rate (AdamW optimizer)
learning_rate = 2e-4
# Weight decay to apply to all layers except bias/LayerNorm weights
weight_decay = 0.001
# Optimizer to use
optim = "paged_adamw_32bit"
# Learning rate schedule (constant a bit better than cosine)
lr_scheduler_type = "constant"
# Ratio of steps for a linear warmup (from 0 to learning rate)
warmup_ratio = 0.03
# Group sequences into batches with same length
# Saves memory and speeds up training considerably
group_by_length = True
# Save checkpoint every X updates steps
save_steps = 25
# Log every X updates steps
logging_steps = 25
Output_dir(第 6 行): 这是设置存储模型预测和检查点的地位,还包含日志
num_train_epochs(第 9 行): 训练的轮次
fp16 和 bf16(第 12 行和第 13 行): 咱们将它们都设置为 false,因为咱们不会应用混合精度训练,因为曾经有 QLoRA 了。
per_device_train_batch_size 和 per_device_eval_batch_size(第 16 行和第 19 行): 将它们都设置为 4。有足够的内存,能够设置更高的批处理大小(>8),这将放慢训练速度。
Gradient_accumulation_steps(第 22 行):“梯度累积”指的是在理论更新模型权重之前执行的向前和向后传递的次数(更新步骤)。在每一次向前和向后传递期间,梯度被计算并累积在一批数据上。在累积指定步数的梯度之后,而后执行反向传递,计算这些步骤的均匀梯度并相应地更新模型权重。这种办法有助于无效地模仿更少量大小,它缩小了每次向前和向后传递的内存需要。
max_gradient_norm(第 25 行): 如果梯度的范数 (幅度) 超过某个阈值(由 max_grad_norm 参数指定),则梯度裁剪放大梯度。如果梯度范数大于 max_grad_norm,则梯度将按比例放大,如果梯度标准曾经低于 max_grad_norm,则不利用缩放。倡议从 max_grad_norm 的较高值开始,而后在多个训练迭代中缓缓放大它。
learning_rate(第 28 行):AdamW 的学习率。AdamW 是风行的 Adam 优化器的一个变体。它联合了 Adam 优化器和权重衰减正则化的技术。
weight_decay(第 31 行): 权重衰减,也称为 L2 正则化或权重正则化,是机器学习和深度学习中罕用的一种正则化技术,用于避免模型对训练数据的过拟合。它的工作原理是在损失函数中增加一个惩办项。咱们应用 AdamW 和权重衰减是有意义的,因为权重衰减在微调期间特地有用,因为它有助于避免过拟合,并确保模型适应新工作,同时保留预训练中的一些常识。
optim(第 34 行): 应用 AdamW 优化器,“paged_adamw_32bit”仿佛是 AdamW 优化器的一个特定实现或变体,咱们找到任何对于他的信息,所以如果你有对于这方面的信息,请在评论中留下,谢谢!
lr_scheduler_type(第 37 行): 通常咱们在深度学习模型的训练期间应用学习率调度器,以随工夫调整学习率。
warmup_ratio(第 40 行): 这里咱们将“warmup_ratio”设置为 0.03。因为每个 epoch 有 250 个训练步骤,热身阶段将继续到前 8 步(250 的 3%),在此期间,学习率将从 0 线性减少到指定的初始值 2e-4。热身阶段通常用于稳固训练,避免梯度爆炸,并容许模型开始无效地学习。
group_by_length(第 44 行): 这个参数设置为 True,会放慢了训练速度。当 group_by_length 设置为 True 时,它将训练数据集中大致相同长度的样本分组到同一批中。这意味着具备类似长度的序列被分组在一起,缩小了所需的填充。也就是说批将具备更类似长度的序列,这将最小化所利用的填充量。当批处理具备统一的大小时 GPU 解决通常更无效,从而缩短训练工夫,这是从 LSTM 时代就开始的一个减速技巧。
save_steps 和 logging_steps(第 47 行和第 50 行): 这里将两个参数都设置为 25,以管制记录训练信息和保留检查点的距离步骤。
SFTTrainer 参数:
max_seq_length = None
# Pack multiple short examples in the same input sequence to increase efficiency
packing = False
最初参数是特定于 SFTTrainer 的。
max_seq_length: 将 max_seq_length 设置为 None 容许咱们不施加最大序列长度限度,咱们不想截断或填充它们到固定长度,因而将 max_seq_length 设置为 None 容许咱们应用数据中存在的全副序列长度。
packing: 依据文档,ConstantLengthDataset 应用这个参数来打包数据集的序列。在 ConstantLengthDataset 上下文中将 packing 设置为 False 能够在解决多个简短示例时提高效率,咱们的数据集就是这种状况。通过将 packing 设置为 False,容许 ConstantLengthDataset 将多个短示例打包到单个输出序列中,无效地组合它们。这缩小了对大量填充的需要,并进步了内存应用和计算的效率。
加载数据集、根本模型和标记器
device_map = {"": 0}
# Load dataset (you can process it here)
dataset = load_dataset(dataset_name, split="train")
# Load tokenizer and model with QLoRA configuration
compute_dtype = getattr(torch, bnb_4bit_compute_dtype)
bnb_config = BitsAndBytesConfig(
load_in_4bit=use_4bit,
bnb_4bit_quant_type=bnb_4bit_quant_type,
bnb_4bit_compute_dtype=compute_dtype,
bnb_4bit_use_double_quant=use_nested_quant,
)
# Check GPU compatibility with bfloat16
if compute_dtype == torch.float16 and use_4bit:
major, _ = torch.cuda.get_device_capability()
if major >= 8:
print("=" * 80)
print("Your GPU supports bfloat16: accelerate training with bf16=True")
print("=" * 80)
# Load base model
model = AutoModelForCausalLM.from_pretrained(
model_name,
quantization_config=bnb_config,
device_map=device_map
)
model.config.use_cache = False
model.config.pretraining_tp = 1
# Load LLaMA tokenizer
tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
tokenizer.add_special_tokens({'pad_token': '[PAD]'})
tokenizer.pad_token = tokenizer.eos_token
tokenizer.padding_side = "right"
# Load LoRA configuration
peft_config = LoraConfig(
lora_alpha=lora_alpha,
lora_dropout=lora_dropout,
r=lora_r,
bias="none",
task_type="CAUSAL_LM",
)
第 5 行加载数据集。而后在第 9 行应用 gettr 函数将 compute_dtype 设置为 torch.float16。在第 10 行,初始化 BitsandBytesConfig。
在第 17 行,咱们应用 torch.cuda.get_device_capability()函数查看 GPU 与 bfloat16 的兼容性。该函数返回反对 cuda 的 GPU 设施的计算能力。计算能力示意 GPU 反对的版本和个性。该函数返回一个由两个整数组成的元组,(major, minor),示意 GPU 的次要和主要计算能力版本。次要版本示意该计算能力的次要版本,主要版本示意该计算能力的主要版本。例如,如果函数返回(8,0),则示意 GPU 的计算能力为 8.0 版本,主要是 0。如果 GPU 是 bfloat16 兼容的,那么咱们将 compute_dtype 设置为 torch.Bfloat16,因为 Bfloat16 比 float16 更好的精度
而后就是应用 AutoModelForCausalLM.from_pretrained 加载根本模型,在第 31 行设置了 model.config。use_cache 为 False,当启用缓存时能够缩小变量。禁用缓存则在执行计算的程序方面引入了肯定水平的随机性,这在微调时十分有用。
在第 32 行设置了 model.config.pretraining_tp = 1 这里的 tp 代表张量并行性,依据这里的 Llama 2 的提醒:
设置 model.config. pretraining_tp = 1 不等于 1 的值将激活更精确但更慢的线性层计算,这应该更好地匹配原始概率。
而后就是应用 model_name 加载 Llama 标记器。如果你看一下 NousResearch/ lama- 2 的文件,你会留神到有一个 tokenizer. model 文件。应用 model_name, AutoTokenizer 能够下载该标记器。
在第 36 行,调用 add_special_tokens({‘ pad_token ‘: ‘ [PAD] ‘})这是另一个重要代码,因为咱们数据集中的文本长度能够变动,批处理中的序列可能具备不同的长度。为了确保批处理中的所有序列具备雷同的长度,须要将填充令牌增加到较短的序列中。这些填充标记通常是没有任何含意的标记,例如 <pad>。
在第 37 行,咱们设置 tokenizer. pad_token = tokenizer. eos_token。将 pad 令牌与 EOS 令牌对齐,并使咱们的令牌器配置更加统一。两个令牌 (pad_token 和 eos_token) 都有批示序列完结的作用。设置成一个简化了标记化和填充逻辑。
在第 38 行,设置填充边,将填充边设置为右能够修复溢出问题。
最初在第 41 行,咱们初始化了 LoraConfig
训练
# Set training parameters
training_arguments = TrainingArguments(
output_dir=output_dir,
num_train_epochs=num_train_epochs,
per_device_train_batch_size=per_device_train_batch_size,
gradient_accumulation_steps=gradient_accumulation_steps,
optim=optim,
save_steps=save_steps,
logging_steps=logging_steps,
learning_rate=learning_rate,
weight_decay=weight_decay,
fp16=fp16,
bf16=bf16,
max_grad_norm=max_grad_norm,
max_steps=max_steps,
warmup_ratio=warmup_ratio,
group_by_length=group_by_length,
lr_scheduler_type=lr_scheduler_type,
report_to="tensorboard"
)
# Set supervised fine-tuning parameters
trainer = SFTTrainer(
model=model,
train_dataset=dataset,
peft_config=peft_config,
dataset_text_field="text",
max_seq_length=max_seq_length,
tokenizer=tokenizer,
args=training_arguments,
packing=packing,
)
# Train model
trainer.train()
# Save trained model
trainer.model.save_pretrained(new_model)
在第 2 行应用后面具体探讨过的形参初始化 TrainingArguments。而后将 TrainingArguments 与探讨的其余相干参数一起传递到第 30 行上的 SFTTrainer 中。
这里新加的一个参数是第 27 行的 dataset_text_field= ” text “。dataset_text_field 参数用于批示数据集中哪个字段蕴含作为模型输出的文本数据。它使 datasets 库可能基于该字段中的文本数据主动创立 ConstantLengthDataset,简化数据筹备过程。
HuggingFace 生态系统是一个紧密结合的库生态系统,它在后盾为你自动化了很多工作。
推理
logging.set_verbosity(logging.CRITICAL)
# Run text generation pipeline with our next model
prompt = "What is a large language model?"
pipe = pipeline(task="text-generation", model=model, tokenizer=tokenizer, max_length=200)
result = pipe(f"<s>[INST] {prompt} [/INST]")
print(result[0]['generated_text'])
第 6 行,管道初始化。而后在第 7 行应用管道,传递应用第 5 行提示符结构的输出文本。咱们应用 <s> 来批示序列的开始,而增加 [INST] 和[/INST]作为管制令牌来批示用户音讯的开始和完结。
用适配器权重从新加载根本模型
# Reload model in FP16 and merge it with LoRA weights
base_model = AutoModelForCausalLM.from_pretrained(
model_name,
low_cpu_mem_usage=True,
return_dict=True,
torch_dtype=torch.float16,
device_map=device_map,
)
model = PeftModel.from_pretrained(base_model, new_model)
model = model.merge_and_unload()
# Reload tokenizer to save it
tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
tokenizer.add_special_tokens({'pad_token': '[PAD]'})
tokenizer.pad_token = tokenizer.eos_token
tokenizer.padding_side = "right"
在第 2 行应用 AutoModelForCausalLM.from_pretrained 来 (从新) 加载根本模型。咱们将在没有任何量化配置的状况下执行此操作,因为咱们不须要对其进行微调,只是想将其与适配器合并。还在第 13 行从新加载标记器,并进行与之前在第 13 – 14 行中所做的雷同的批改。
保留
最初咱们将刚刚通过微调的模型及其标记器保留到本地或者上传到 HuggingFace。
model.push_to_hub(new_model, use_temp_dir=False)
tokenizer.push_to_hub(new_model, use_temp_dir=False)
总结
peft,tramsformers 等库简化了咱们对于大模型开发的工作流程,并且不须要很多的专业知识也能够对大模型进行微调。然而要失去一个好的模型是一个漫长的过程,就像咱们下面的代码一样,看似简略实则简单,不仅要理解办法的原理,还要通过查看论文理解每一个参数的含意。
本文是一个良好的开始,因为能够把咱们在这里学到的大部分货色利用到微调任何 LLM 的工作中。对于微调 Llama 2,咱们的流程曾经介绍结束了,然而咱们如何能力正确地评估咱们的微调性能? 是否在不破费太多的状况下调整更大的模型(70B) ? 应用更大的数据集? 模型怎么部署呢? 咱们会在后续的文章中进行介绍。
https://avoid.overfit.cn/post/903a50f5e8ec469f890a1e8854d64716
作者:Ogban Ugot