黄浩文
资深开发者布道师
亚马逊云科技
专注于 AI/ML、Data Science 等。拥有 20 多年电信、移动互联网以及云计算等行业架构设计、技术及创业管理等丰富经验,曾就职于 Microsoft、Sun Microsystems、中国电信等企业,专注为游戏、电商、媒体和广告等企业客户提供 AI/ML、数据分析和企业数字化转型等解决方案咨询服务。

在上一期的文章中,我们一起梳理了大模型参数高效微调(PEFT)和 QLoRA 量化技术背后的理论基础。与标准的 16 位模型微调相比,QLoRA 减少了大模型微调的内存使用量,而无需权衡性能。探索完基本理论之后,我们就要开始动手实践了。

欢迎您加入亚马逊云科技(Amazon Web Services,AWS)!AWS是全球领先的云计算服务提供商,为个人、企业和组织提供稳定、可靠的云服务。通过AWS,您可以轻松构建网站、应用程序、存储数据、进行分析和机器学习等多种应用场景,快速响应业务需求。无论您是初学者还是资深开发者,AWS都能为您提供丰富的学习资源、技术支持和灵活的服务选择。立即注册亚马逊云科技账户,开启云计算之旅,探索无限的可能性!

本期文章,我们将探讨使用 Amazon SageMaker Studio 在交互式环境中,快速高效地微调大语言模型。我们将运用 QLoRA 和 4-bits的bitsandbtyes 量化技术原理,在 Amazon SageMaker Studio 上使用 Hugging Face PEFT 来微调 Falcon-40B 模型。

实践方法概述

Amazon SageMaker 提供了两个选项方法,用于启动完全托管的 notebook,用于探索数据和构建机器学习(ML)模型。

第一种选项是 Amazon SageMaker Studio。这是一个完全集成的机器学习开发环境(IDE),用户可以在 Amazon SageMaker Studio 中快速启动 notebook,在不中断工作的情况下向上或向下伸缩底层计算资源,甚至可以在 notebook 上实时共同编辑和协作。用户可以在 Amazon SageMaker Studio 的单一管理面板中执行所有机器学习开发步骤,包括:构建、训练、调试、跟踪、部署和监控模型等。

第二个选项是 Amazon SageMaker Notebook 实例。这是一个在云端运行 notebook 的完全托管的 ML 计算实例,这个方法可以帮助用户更好地控制 notebook 配置。

本例中我们将使用 Amazon SageMaker Studio。主要选择的原因有两点:

1) 可以利用 SageMaker Studio 的托管 TensorBoard 实验跟踪,以及 Hugging Face Transformer 对 TensorBoard 的支持;

2) Amazon SageMaker Studio 的 Amazon EFS 容量,可以无需预先预置 EBS 卷大小。鉴于 LLM 中模型权重较大,这在实践中很有帮助。

如果你想选择第二个选项,也是可行的。示例代码将同样适用于使用 conda_pytorch_p310 内核的 notebook 实例。

另外,在 Amazon SageMaker Studio 使用完 notebook 实例后,请将其关闭,以避免产生不必要的额外费用。本文最后有删除和清理资源的示例代码,可供参考。

本文的主要参考文档来自以下亚马逊云科技的官方博客。为阐述清楚其中的细节,本文增加了较多的章节扩展分析和代码对照的讲解:

https://aws.amazon.com/cn/blogs/machine-learning/interactively-fine-tune-falcon-40b-and-other-llms-on-amazon-sagemaker-studio-notebooks-using-qlora/

模型微调过程的拆解分析

1.启动 Amazon SageMaker JumpStart 环境

本实验的完整示例代码可参考:

https://github.com/aws-samples/amazon-sagemaker-generativeai/blob/main/studio-notebook-fine-tuning/falcon-40b-qlora-finetune-summarize.ipynb

示例代码的 notebook 在 Amazon SageMaker Studio 测试通过,内核为 Python 3(Data Science 3.0),实例为一台 ml.g5.12xlarge 实例。

2.安装模型微调所需的库

首先,安装所需的库,包括 Hugging Face 库,然后重新启动内核。

1
2
3
4
5
6
7
8
9
10
11
12
%pip install -q -U torch==2.0.1 bitsandbytes==0.40.2
%pip install -q -U transformers==4.31.0 peft==0.4.0 accelerate==0.21.0
%pip install -q -U datasets py7zr einops tensorboardX

# Add installed cuda runtime to path for bitsandbytes
import os
import nvidia

cuda_install_dir = '/'.join(nvidia.__file__.split('/')[:-1]) + '/cuda_runtime/lib/'
os.environ['LD_LIBRARY_PATH'] = cuda_install_dir

print("cuda_install_dir: ",cuda_install_dir)

我自己环境的 cuda_install_dir 路径如下输出所示:

3.输入文本的 Tokenizer 和大模型的量化

要训练模型,我们需要将输入文本转换为 token ID,这个工作可以交给 Hugging Face Transformers Tokenizer 完成。除了 QLoRA 之外,我们还将使用 bitsandbytes 的 4 位精度方法将 LLM 量化为 4 位,并遵照在上篇文章中介绍过的 QLoRA 论文中阐述的方法,在量化后的 LLM 上面配接 LoRA adapter。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import torch
from transformers import AutoTokenizer, AutoModelForCausalLM, BitsAndBytesConfig

model_id = "tiiuae/falcon-40b"
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_use_double_quant=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.bfloat16
)

tokenizer = AutoTokenizer.from_pretrained(model_id)

# Falcon requires you to allow remote code execution. This is because the model uses a new architecture that is not part of transformers yet.
# The code is provided by the model authors in the repo.
model = AutoModelForCausalLM.from_pretrained(model_id, trust_remote_code=True, quantization_config=bnb_config, device_map="auto")

这个 Falcon 40B LLM 量化到 4-bit 精度的过程,我自己实测大约需要用时 5 分钟左右。如上图所示。

另外,我们还需要设置代表句子结尾的特殊标记,如下图输出的 “|endoftext|”,就是代表句子结尾的特殊标记。关于 Tokenizer 的设置详情,可参考 Tokenizer的类说明文档

1
2
3
# Set the Falcon tokenizer
tokenizer.pad_token = tokenizer.eos_token
print("tokenizer.pad_token: ", tokenizer.pad_token)

输出如下图所示:

4.为 LoRA 微调训练方法准备模型

接下来要开始为 PEFT 工作准备模型了。

1
2
3
4
5
from peft import prepare_model_for_kbit_training

model.gradient_checkpointing_enable()
model = prepare_model_for_kbit_training(model)
print("model: ", model)

定义打印模型中可训练参数的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
def print_trainable_parameters(model):
"""
Prints the number of trainable parameters in the model.
"""
trainable_params = 0
all_param = 0
for _, param in model.named_parameters():
all_param += param.numel()
if param.requires_grad:
trainable_params += param.numel()
print(
f"trainable params: {trainable_params} || all params: {all_param} || trainable%: {100 * trainable_params / all_param}"
)

使用 PEFT 为 LoRA 微调训练方法准备模型和 LoRA 参数配置细节:

1
2
3
4
5
model, preprocess = clip.load("ViT-B/32")
model.cuda().eval()
input_resolution = model.visual.input_resolution
context_length = model.context_length
vocab_size = model.vocab_size

注意看输出的最后一行,即可训练参数的数量:

由以上输出可见,所有参数数量是 209 亿以上,可训练参数数量是 5500 万,只占全部参数的 0.26%。由于训练参数的大幅减少,可预计的训练时间也会大大缩短。

5.加载微调模型的数据集

要加载 samsum 数据集,我们使用 Hugging Face 数据集库中的 load_dataset () 方法。

1
2
3
4
5
6
7
from datasets import load_dataset

# Load dataset from the hub
dataset = load_dataset("samsum")

print(f"Train dataset size: {len(dataset['train'])}")
print(f"Test dataset size: {len(dataset['test'])}")

由以上输出可见,这个数据集并不大。训练数据 14732 条,测试数据 819 条。

接下来,我们需要创建提示词模板,并使用随机样本加载数据集做汇总测试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from random import randint

# custom instruct prompt start
prompt_template = f"Summarize the chat dialogue:\n{{dialogue}}\n---\nSummary:\n{{summary}}{{eos_token}}"

# template dataset to add prompt to each sample
def template_dataset(sample):
sample["text"] = prompt_template.format(dialogue=sample["dialogue"],
summary=sample["summary"],
eos_token=tokenizer.eos_token)
return sample

# apply prompt template per sample
train_dataset = dataset["train"].map(template_dataset, remove_columns=list(dataset["train"].features))

print(train_dataset[randint(0, len(dataset))]["text"])

提示词模版示例输出如下图所示。

以下代码将提示词模版应用到每一个 sample 里:

1
2
# apply prompt template per sample
test_dataset = dataset["test"].map(template_dataset, remove_columns=list(dataset["test"].features))

对数据集做 tokenize 和 chunk:

1
2
3
4
5
6
7
8
9
10
11
# tokenize and chunk dataset
lm_train_dataset = train_dataset.map(
lambda sample: tokenizer(sample["text"]), batched=True, batch_size=24, remove_columns=list(train_dataset.features)
)

lm_test_dataset = test_dataset.map(
lambda sample: tokenizer(sample["text"]), batched=True, remove_columns=list(test_dataset.features)
)

# Print total number of samples
print(f"Total number of train samples: {len(lm_train_dataset)}")

为了完成实时监控的任务,我们首先需要把训练过程的各项指标记录下来,比如:用一个 S3 桶来存放记录。因此,我们还需要为这个实验创建一个 S3 桶,以方便我们将训练中的各项指标完整地记录到 TensorBoard:

1
2
3
4
# bucket = <YOUR-S3-BUCKET>
bucket = "llm-demo-xxxxxx"
log_bucket = f"s3://{bucket}/falcon-40b-qlora-finetune"
log_bucket

如果你试着打印 log_bucket 的输出,将类似是如下这样的,它定义了一个 S3 桶:

1
's3://llm-demo-xxxxxx/falcon-40b-qlora-finetune'

6.微调训练过程指标的记录

然后将使用 Hugging Face Trainer 类对模型进行微调,定义要使用的超参数。我们还创建了一个 DataCollator 来填充我们的输入和标签。另外,因为要考虑做模型微调训练过程的监测,可以通过定义参数 logging_dir 和设置report_to=”tensorboard”,来请求 Hugging Face Transformer 把微调训练的日志记录到 TensorBoard。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import transformers

# We set num_train_epochs=1 simply to run a demonstration

trainer = transformers.Trainer(
model=model,
train_dataset=lm_train_dataset,
eval_dataset=lm_test_dataset,
args=transformers.TrainingArguments(
per_device_train_batch_size=8,
per_device_eval_batch_size=8,
logging_dir=log_bucket,
logging_steps=2,
num_train_epochs=1,
learning_rate=2e-4,
bf16=True,
save_strategy = "no",
output_dir="outputs",
report_to="tensorboard",
),
data_collator=transformers.DataCollatorForLanguageModeling(tokenizer, mlm=False),
)
model.config.use_cache = False # silence the warnings. Please re-enable for inference!

print("model:", model)

完成以上配置,就可以开始正式启动微调的训练了。启动的代码很简单,如下所示:

1
2
# Start training
trainer.train()

模型微调监控的拆解分析

1.实时监控 GPU 使用情况

前面的设置完成后,我们就可以实时监控微调过程了。为了实时监控 GPU 使用情况,我们可以直接从内核的容器运行 nvidia-smi 命令。要启动在镜像容器上运行的终端,只需选择笔记本顶部的终端图标即可。

我们可以使用 Linux watch 命令每半秒钟重复运行 nvidia-smi:

1
watch -n 0.5 nvidia-smi

在上面的动图中,我们可以看到模型权重分布在 4 个 GPU 上,随着层的串行处理,计算负载在这些 GPU 之间做分布计算。

2.实时监控模型微调的训练指标

为了监控模型微调过程中的训练指标,我们将把 TensorBoard 日志写入之前已经配置好的 S3 桶。可以从 SageMaker 控制台启动 SageMaker Studio 域用户的 TensorBoard,如下截图所示:

TensorBoard 加载完成后,可以指定 Hugging Face transformer 把训练日志写入指定的 S3 桶,以便查看训练和评估指标,如下图所示。

以上配置完成后,就可以通过 TensorBoard 的 “Time Series” 菜单,监控微调模型训练过程中各项指标随着训练时间的变化,包括:回合(epoch)、学习率(learning rate)、损失函数(loss)等等。

模型微调后的评估和生成结果

模型完成微调训练后,我们就可以对微调后的大模型进行系统评估或直接生成结果了。由于模型的评估又是另一个宏大的话题,本文会暂时略过这个话题,留待以后专题讨论;本文之后将主要聚焦微调后模型的结果生成示例。

首先,加载之前分拆出来的 samsum 测试数据集,并尝试使用随机样本进行 LLM 总结(Summary)测试:

1
2
3
4
5
6
7
8
9
10
11
12
# Load dataset from the hub
test_dataset = load_dataset("samsum", split="test")

# select a random test sample
sample = test_dataset[randint(0, len(test_dataset))]

# format sample
prompt_template = f"Summarize the chat dialogue:\n{{dialogue}}\n---\nSummary:\n"

test_sample = prompt_template.format(dialogue=sample["dialogue"])

print(test_sample)

接下来,把输入数据 tokenizer 化:

1
input_ids = tokenizer(test_sample, return_tensors="pt").input_ids

把 tokenizer 化后的输入数据传给微调后的 LLM,获取 LLM 总结(Summary)的输出结果:

1
2
3
4
5
6
7
#set the tokens for the summary evaluation
tokens_for_summary = 30
output_tokens = input_ids.shape[1] + tokens_for_summary

outputs = model.generate(inputs=input_ids, do_sample=True, max_length=output_tokens)
gen_text = tokenizer.batch_decode(outputs)[0]
print(gen_text)

有心的同学,可以通过模型微调前后,LLM 输出的总结(Summary)结果比较,来对比微调后的模型,是否在准确性和完整性上有了一定的改善。

如果你对模型的性能感到满意,可以把模型保存下来,如下代码所示:

1
trainer.save_model("path_to_save")

或者把模型部署到一个专门的 SageMaker 终端节点。部署终端节点的文档可参考:

https://aws.amazon.com/blogs/machine-learning/deploy-falcon-40b-with-large-model-inference-dlcs-on-amazon-sagemaker/

资源的删除和清理

实验完成后,请记得删除和清理资源,以避免不必要的额外费用。需要清理的资源分为三个部分,如下所示:

1)关闭 SageMaker Studio 实例

https://docs.aws.amazon.com/sagemaker/latest/dg/notebooks-run-and-manage-shut-down.html

2)关闭你的 TensorBoard 应用程序

https://docs.aws.amazon.com/sagemaker/latest/dg/tensorboard-on-sagemaker.html#debugger-htb-delete-app

3)清除 Hugging Face 缓存目录,参考命令如下所示:

1
rm -R ~/.cache/huggingface/hub

总结

在本文中,我们探讨了使用 Amazon SageMaker Studio在交互式环境中,快速高效地微调 Falcon 40B 大语言模型。我们运用了 QLoRA和 4-bits 的 bitsandbtyes 量化技术原理,在 Amazon SageMaker Studio 上使用 Hugging Face PEFT 微调了 Falcon-40B 模型。

本文做为 “Generative AI 新世界”的第十二篇文章,在不知不觉中已经伴随着各位热爱生成式 AI 领域知识的读者们,从初春走到了盛夏。随着我们一起对生成式 AI 知识的逐步深入学习,这个系列后面的文章内容会往更深度的专业领域做拓展。

目前计划有三个大方向:

1)代码深度实践方向。例如用代码完整诠释 Diffusion 模型的工作原理,或者 Transformer 的完整架构等;

2)模型部署和训练优化方向。例如尝试解读 LMI、DeepSpeed、Accelerate、FlashAttention 等不同模型优化方向的最新进展;

3)模型量化实践方向。例如 GPTQ、bitsandbtyes 等前沿模型量化原理和实践等。敬请期待。

关注亚马逊云科技官网,了解更多面向开发者的技术分享和云开发动态!