Press "Enter" to skip to content

转译结果:在一台16GB的GPU上训练QLoRA:一个大规模语言模型

让我们探索量化是如何工作的,并提供在Google Colab中使用T4 16GB GPU对7亿参数的Bloom模型进行微调的例子。

本文是免费的大型语言模型课程的一部分,可在GitHub上获取。

由作者使用Dall-E2生成的图片

我们将把一种模型的权重减少技术,如量化,与一种参数高效的微调技术,如LoRA,结合起来。这种组合的结果是QLoRA,它可以让我们以非常资源高效的方式对大型模型进行微调。

在之前的一篇文章中,我们探讨了如何使用LoRA对大型语言模型进行微调。

使用LoRA进行高效微调。大型语言模型的最佳训练。

LoRA是适用于大型语言模型的最有效和最有效的微调技术之一。在这篇文章中…

levelup.gitconnected.com

在这篇文章中,我们将添加量化到这个组合中,从而使我们能够对更大且更强大的模型进行微调。

我们将能够在仅拥有16GB显存的GPU上对Lllama-2 7B或Bloom 7B等70亿参数的模型进行微调。这使得QLoRA成为目前最有效的用于实际应用的模型微调方法之一。

而且,我们将通过最小的努力实现这一点,利用了Hugging Face提供的PEFT库,这样我们只需要几行代码就可以享受到所有这些好处。

量化是如何工作的?

主要思想很简单:我们将把浮点数的精度从通常占用32位的部分降低到8位甚至4位的整数。

这种降低发生在模型的参数上,也就是神经层的权重,以及在流经模型层的激活值上。

这意味着我们不仅在模型的存储大小和内存消耗方面取得了改进,而且在计算速度方面也更加敏捷

当然,精度会有所损失,但特别是在8位量化的情况下,这种损失是很小的。

让我们看一个小例子。

我将创建一个用于量化和反量化(或者叫其他名字)的函数。

实际上,我想看到的是从32位数到量化的8/4位数再返回到原始32位值时发生的精度损失。

#导入必要的库import numpy as npimport mathimport matplotlib.pyplot as plt#量化和反量化的函数def quantize(value, bits=4):    quantized_value = np.round(value * (2**(bits - 1) - 1))    return int(quantized_value)def unquantize(quantized_value, bits=4):    value = quantized_value / (2**(bits - 1) - 1)    return float(value)quant_4 = quantize(0.622, 4)print (quant_4)quant_8 = quantize(0.622, 8)print(quant_8)

当量化0.622时,我们得到以下结果:

  • 4位: 4。
  • 8位: 79

让我们将这些值恢复到它们的原始精度,并看看我们得到了什么。

unquant_4 = unquantize(quant_4, 4)print(unquant_4)unquant_8 = unquantize(quant_8, 8)print(unquant_8)
  • 4位未量化: 0.57142
  • 8位未量化: 0.62204

如果我们考虑原始数字为0.622,可以说8位量化几乎不会损失精度,而4位量化的损失是可以接受的。

始终考虑量化模型的预期用途非常重要。对于文本生成或源代码生成等任务,精度损失可能并不是非常重要。但是,在用于疾病诊断的图像识别模型中,对精度的显著损失可能不太令人满意。

让我们绘制一条余弦函数的未量化值曲线。

x = np.linspace(-1, 1, 50)y = [math.cos(val) for val in x]y_quant_8bit = np.array([quantize(val, bits=8) for val in y])y_unquant_8bit = np.array([unquantize(val, bits=8) for val in y_quant_8bit])y_quant_4bit = np.array([quantize(val, bits=4) for val in y])y_unquant_4bit = np.array([unquantize(val, bits=4) for val in y_quant_4bit])

plt.figure(figsize=(10, 12))plt.subplot(4, 1, 1)plt.plot(x, y, label="原始")plt.plot(x, y_unquant_8bit, label="未量化_8位")plt.plot(x, y_unquant_4bit, label="未量化_4位")plt.legend()plt.title("比较图表")plt.grid(True)

转译结果:在一台16GB的GPU上训练QLoRA:一个大规模语言模型 四海 第2张

从图表中可以看出,代表8位值的未量化线条几乎与代表原始值的线条完全重叠。相比之下,代表未量化4位值的线条存在一些明显的跳跃。8位量化和4位量化之间的精度差异非常显著。这是在决定量化我们的模型时必须考虑的一个重要因素。

话虽如此,我们将使用4位量化,因为正如前面提到的,对于文本生成来说,我们不太会注意到差异,并且我们需要在一张16GB的GPU上加载模型。

QLoRA: 使用 LoRA 对4位量化模型进行微调。

要与本文一起使用笔记本,请访问 Github:

Large-Language-Model-Notebooks-Course/5-Fine Tuning/QLoRA_Tuning_PEFT.ipynb at main ·…

关于大型语言模型的实践课程。访问 peremartra/Large-Language-Model-Notebooks-Course 进行贡献。

github.com

我将使用的模型是 Bloom 7B。它是 Hugging Face 的众多知名模型之一,非常强大,并且在 LLAMA 的水平上表现。这是一个我们无法在16GB的GPU上加载而不进行量化的模型。

以前的文章中,我训练了一个来自同一系列但规模更小的模型,以便您可以研究这两个笔记本之间的差异。

我们可以开始加载必要的库。

!pip -q install accelerate!pip -q install datasets!pip -q install trl

trlaccelerate 库是 HuggingFace 生态系统的一部分,它们允许我们进行模型微调。

数据集 库包含大量预处理的数据集,包括我们在示例中要使用的数据集。

您可能已经注意到两个主要的库缺失: transformerspeft。第一个用作 Hugging Face 模型的主要接口,第二个包含各种微调技术的实现。PEFT 代表参数高效微调。

让我们以特殊方式安装这些库。

#安装建议的 peft & transformers 库的最新版本,如果您想使用最新的模型!pip install -q git+https://github.com/huggingface/peft.git!pip install -q git+https://github.com/huggingface/transformers.git

通过这种方式,我们直接从项目的 GitHub 安装了这些库的最新版本,其中包括最新模型(如 Mistral 或 LLAMA-2)的实现。在我们的情况下,这可能不是严格必要的,因为 Bloom 模型系列在这些库的可用版本中已经得到支持了一段时间。

让我们导入各种必要的类。

from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfigfrom trl import SFTTrainerimport torch

加载模型。

#使用您想要的任何模型,如果您想进行一些快速测试,只需使用最小的一个。#model_name = "bigscience/bloomz-560m"#model_name="bigscience/bloom-1b1"model_name = "bigscience/bloom-7b1"target_modules = ["query_key_value"]

所选模型是 Bloom 7B,但如果您正在进行测试,我建议使用其中一个较小的模型以最小化训练时间和资源使用。一旦您对结果满意,您可以尝试 7B 模型并查看结果。

要加载模型,我们需要一个指定量化操作方式的配置类。我们将使用 Transformers 库中的 BitesAndBytesConfig 实现这一目标。

bnb_config = BitsAndBytesConfig(    load_in_4bit=True,    bnb_4bit_use_double_quant=True,    bnb_4bit_quant_type="nf4",    bnb_4bit_compute_dtype=torch.bfloat16)

我们指定了使用 4 位量化,并启用了双重量化以减少精度损失。

对于 bnb_4bit_quant_type 参数,我在论文 ‘QLoRA: Efficient Finetuning of Quantized LLMs.‘ 中使用了推荐值

现在,我们可以继续加载模型。

device_map = {"": 0}foundation_model = AutoModelForCausalLM.from_pretrained(model_name,                    quantization_config=bnb_config,                    device_map=device_map,                    use_cache = False)

通过这样做,我们将在内存中得到模型的量化版本。如果您愿意,可以尝试加载没有量化的模型,只需简单地删除 quantization 参数。由于内存限制,您很可能无法加载它。

现在,让我们加载分词器,所有准备工作都将完成,可以测试模型了。

tokenizer = AutoTokenizer.from_pretrained(model_name)tokenizer.pad_token = tokenizer.eos_token

测试未经微调的模型。

为了确定微调模型是否有任何积极影响,最好在进行任何修改之前对最近加载的模型进行测试。

为此,我将创建一个函数,该函数接受模型、用户输入和最大响应长度。

#此函数返回接收到的模型、输入的输出。def get_outputs(model, inputs, max_new_tokens=100):    outputs = model.generate(        input_ids=inputs["input_ids"],        attention_mask=inputs["attention_mask"],        max_new_tokens=max_new_tokens,        repetition_penalty=1.5, #避免重复。        early_stopping=False, #模型可以在达到最大长度之前停止        eos_token_id=tokenizer.eos_token_id,    )    return outputs

现在我们可以向模型发送请求并检查其响应,从而可以将其与微调后提供的响应进行比较。

#推理原始模型input_sentences = tokenizer("我希望你扮演一位激励教练。", return_tensors="pt").to('cuda')foundational_outputs_sentence = get_outputs(foundation_model, input_sentences, max_new_tokens=50)print(tokenizer.batch_decode(foundational_outputs_sentence, skip_special_tokens=True))

[“我希望你扮演一位激励教练。我不是指告诉别人他们应该做什么,而是鼓励他们并帮助他们激发自己的行动。\n你可以从以下问题开始:\n\n你的目标是什么?\n这如何帮助实现这些目标?\n\n然后”]

响应非常好。 Bloom 是一个经过良好训练的模型,能够在各种情况下提供准确的响应。我用 560M 的 Bloom 进行了同样的测试,响应完全不同:[“我希望你扮演一位激励教练。不要害怕被挑战。”]。

准备数据集。

要使用的数据集是 datasets 库中的一个可用数据集:fka/awesome-chatgpt-prompts

让我们看一下数据集中包含的一些提示:

  • 我希望你扮演 JavaScript 控制台。我将输入命令,你将回复控制台应该显示的内容。你应该只在一个唯一的代码块中回复终端输出,其他内容都不需要包括。不要给出解释。除非我指示你这样做,否则不要输入命令。当我需要用英语告诉你一些事情时,我会在花括号中放置文本 {像这样}。我的第一个命令是 console.log(“Hello World”);
  • 我希望你扮演一位旅行指南。我会告诉你我的位置,你会建议我在我所在位置附近参观的地方。有些情况下,我还会告诉你我将要参观的地方的类型。你还会建议我附近与我第一个位置相似类型的地方。我的第一个请求是“我在伊斯坦布尔/贝约卢,我只想参观博物馆。”
  • 我希望你扮演一位编剧。你将为一部长篇电影或一部能够吸引观众的网络系列剧开发一个引人入胜且富有创意的剧本。首先要创作出有趣的角色、故事背景、人物之间的对话等。完成角色发展后,创建一个充满转折和悬念的精彩故事情节,让观众一直保持紧张,直到结尾。我的第一个请求是“我需要写一部设定在巴黎的浪漫爱情剧电影。”

现在我们对期望从微调模型中获得的响应风格有了清晰的想法。让我们看看我们是否可以实现这一目标。

from datasets import load_datasetdataset = "fka/awesome-chatgpt-prompts"#创建数据集以创建提示data = load_dataset(dataset)data = data.map(lambda samples: tokenizer(samples["prompt"]), batched=True)train_sample = data["train"].select(range(50))del datatrain_sample = train_sample.remove_columns('act')display(train_sample)

Dataset({ features: [‘prompt’, ‘input_ids’, ‘attention_mask’], num_rows: 50 })

数据集包含两列。我选择保留只包含提示的一列,因为我认为另一列不提供有用的信息。

然而,这只是我做出的一个设计决策,我鼓励您对删除 act 列的代码行进行评论,看看微调模型是否表现更好。

使用 QLoRA 进行微调。

现在我们拥有了所需的一切:模型、分词器和已下载的数据集。

我们可以开始使用 QLoRA 进行微调,以生成一个能够生成与数据集中所含提示相似的提示的新模型。

第一步将是创建一个 LoRA 配置对象,我们将在其中设置指定微调过程特征的变量。

# TARGET_MODULES# https://github.com/huggingface/peft/blob/39ef2546d5d9b8f5f8a7016ec10657887a867041/src/peft/utils/other.py#L220import peftfrom peft import LoraConfig, get_peft_modellora_config = LoraConfig(r=16, lora_alpha=16, target_modules=target_modules, lora_dropout=0.05, bias="none", task_type="CAUSAL_LM")

让我们来检查我们设置的值:

  • r: 这表示重新参数化的大小。请记住,数值越小,训练的参数越少。训练更多的参数可以更好地学习输入和输出之间的关系,但也会增加计算开销。取16是一个合理的折衷,既可以控制参数,又可以获得正确的结果。
  • lora_alpha: 这个因子调整了权重矩阵的大小。在较小的模型中,它通常不会产生显著影响,但在较大的模型中,它有助于使微调对其余未更改的权重起更大的作用。
  • target_modules: 这表示我们想要训练的模块。这可能看起来是一个具有挑战性的决定,主要是因为你需要了解模型中的内部模块名称。幸运的是,你可以参考 Hugging Face 提供的文档,链接在这里指定了每个模型系列所使用的可用模块。
  • lora_dropout: 如果你之前训练过深度学习模型,你可能对dropout很熟悉。它用于防止过拟合。在这种情况下,考虑到训练时间短和数据有限,你可以尝试将dropout的值设为0。
  • bias: 有三个选项 – none、all 和 lora_only。对于文本分类,通常使用 none。对于更复杂的任务,你可以在 all 和 lora_only 之间进行选择。

现在,让我们创建一个目录,用来存放新微调的模型,这需要作为 TrainingArguments 类的一个参数进行指定。

# 创建一个用于存放模型的目录
import os
working_dir = './'
output_directory = os.path.join(working_dir, "peft_lab_outputs")

# 创建 TrainingArgs
import transformers
from transformers import TrainingArguments

training_args = TrainingArguments(
    output_dir=output_directory,
    auto_find_batch_size=True, 
    learning_rate=2e-4, 
    num_train_epochs=5
)

TrainingArguments 类接受我们所有熟悉的参数,如训练轮数和学习率。

现在我们有了训练模型所需的一切。

  • 模型。
  • TrainingArgs。
  • 数据集。
  • LoRA 配置。
tokenizer.pad_token = tokenizer.eos_tokentrainer = SFTTrainer(
    model=foundation_model,
    args=training_args,
    train_dataset=train_sample,
    peft_config=lora_config,
    dataset_text_field="prompt",
    tokenizer=tokenizer,
    data_collator=transformers.DataCollatorForLanguageModeling(tokenizer, mlm=False)
)
trainer.train()

TrainOutput(global_step=65, training_loss=2.7377777099609375, metrics={‘train_runtime’: 404.0462, ‘train_samples_per_second’: 0.619, ‘train_steps_per_second’: 0.161, ‘total_flos’: 966262938697728.0, ‘train_loss’: 2.7377777099609375, ‘epoch’: 5.0})

现在我们可以保存这个正确工作的模型。

# 保存模型
peft_model_path = os.path.join(output_directory, f"lora_model")
trainer.model.save_pretrained(peft_model_path)

测试微调后的模型。

# 导入 peft
from peft import AutoPeftModelForCausalLM, PeftConfig

device_map = {"": 0}
working_dir = './'
output_directory = os.path.join(working_dir, "peft_lab_outputs")
peft_model_path = os.path.join(output_directory, f"lora_model")

# 加载模型
loaded_model = AutoPeftModelForCausalLM.from_pretrained(
    peft_model_path,
    torch_dtype=torch.bfloat16,
    is_trainable=False,
    load_in_4bit=True,
    device_map='auto'
)

# 微调模型输入
input_sentences = tokenizer("I want you to act as a motivational coach. ", return_tensors="pt").to('cuda')
foundational_outputs_sentence = get_outputs(loaded_model, input_sentences, max_new_tokens=50)
print(tokenizer.batch_decode(foundational_outputs_sentence, skip_special_tokens=True))

[“我希望你能充当一名激励教练。你将与一位在职业生涯中挣扎并且未能取得成功的个人合作。这个人可能有一些以前的经验,但现在他们正在寻找可以帮助他们取得更多成就的新机会。\n客户的当前情况”]

我喜欢这个回答!

结论。

让我们来比较这些回答:

  • 预训练模型:我希望你能充当一名激励教练。我的意思不是告诉别人他们应该做什么,而是鼓励他们,帮助他们激发自己的行动。\n你可以从以下问题开始提问:
  • 微调模型:我希望你能充当一名激励教练。我将提供一些关于需要提高自信心的个人的细节,你的目标是“帮助某人提高自信心的思路”。你的第一个建议应该是“在他们最需要时提供鼓励”;我的回复

毋庸置疑,微调过程对回答的结构产生了积极影响。微调模型生成的回答更接近我们预期的提示。 我认为这个实验是成功的。

我相信通过更长时间的训练,可以实现更好的结果。

您可以通过修改训练变量并得出自己的结论来进行测试。如果您追求一个巨大的挑战,可以尝试通过微调 Mistral 7B 模型来重复这个练习!

资源。

关于 大型语言模型 的全套课程可在 GitHub 上获得。若要及时获得新文章的更新,请考虑跟随该存储库或为其点赞。

GitHub – peremartra/Large-Language-Model-Notebooks-Course:关于大型语言模型的实践课程

关于大型语言模型的实践课程。. 参与到 peremartra/Large-Language-Model-Notebooks-Course…

github.com

如果您想获取关于 QLoRA 如何工作的更多信息: https://arxiv.org/abs/2305.14314

本文是系列文章的一部分,我们将探讨大型语言模型的实际应用。您可以在以下列表中找到其他文章:

Pere Martra

Pere Martra

大型语言模型实践课程

查看列表10篇文章转译结果:在一台16GB的GPU上训练QLoRA:一个大规模语言模型 四海 第4张转译结果:在一台16GB的GPU上训练QLoRA:一个大规模语言模型 四海 第5张转译结果:在一台16GB的GPU上训练QLoRA:一个大规模语言模型 四海 第6张

我经常写关于深度学习和人工智能的文章。请考虑 在VoAGI上关注我,以获取关于新文章的更新。当然,您也可以与我连接在LinkedIntwitter上。

Leave a Reply

Your email address will not be published. Required fields are marked *