融入qLoRA微调避免灾难性遗忘:LLaMA2(-chat)模型案例分析

2023年09月12日 由 alex 发表 917 0

这是关于什么的,为什么它很重要?


大型语言模型(LLM)如Anthropic的Claude或Meta的LLaMA2在各种自然语言任务上展示出令人印象深刻的能力。然而,它们的知识和任务特定技能相对通用——如果你·想执行需要明确知识的更专门化的领域特定任务,你需要找到方法来向模型注入知识并教导它们任务特定的行为。LLM驱动的应用程序需要在目标领域内正常工作,提供准确的答案而不是产生幻觉,并确保安全、隐私和适当的内容。


这些挑战通常被称为“三个H”,即帮助性、诚实性和无害性。在设计高品质的FM驱动应用程序时,克服这些挑战特别重要。


将领域知识传授给基础模型有几种选择:


1


1. 源知识:你可以通过提示工程、基于示例或上下文融入的生成方式来动态地提供知识。这种选项以相对简单的方式呈现,如零样本或少样本提示。然而,也有更复杂的方法来整合不断发展的外部知识源。通过动态提示组合注入知识,从各种外部“工具”(检索增强生成、数据库检索器、数据湖检索器、通过RESTful API的实时系统等)中获取信息,这种方法越来越受欢迎。


2. 参数化知识:你可以通过微调直接更新模型参数。通过将经过预训练的权重调整到一个较小(与预训练相比)的特定领域数据集上,从而形成一个领域特定的知识库或任务。


正如Heiko Hotz在博文中所解释的那样,选择正确的方法(或两种方法的结合)存在权衡。在本文中,我们将专注于参数化方法,并演示如何使用Amazon SageMaker上的PEFT(参数高效微调)对LLaMA2模型进行微调。


我们的目标是将LLaMA2调整到特定领域,获取最新的知识,以解决模型缺乏对最近信息的认识的“知识截断”问题,这些信息不是它们的训练数据的一部分。与特定任务的微调相比,这对于很多从业者来说是一个更可实现的任务,因为他们可以简单地使用包含特定领域信息的文本语料库作为训练数据集,而不是手动制作或收集特定任务的数据集,如对话型或指令型数据集。


由于对于许多相关的LLM驱动用例来说,特定任务的模型是有益的,我们还将展示所提出的设置同样适用于已经经过特定任务微调的模型,例如LLaMA2-chat,而不会丧失其特定任务的特性(如按照指令操作、对话行为等)。


通过演示这种知识注入的端到端工作流程,我们为你调整基础模型以满足特定需求提供了实用指南。


模型


LLaMA2模型于2023年7月发布,并伴随着一篇研究论文。在论文中,Touvron等人指出,LLaMA2是“一个规模从70亿到700亿参数的预训练和微调的大型语言模型(LLM)的集合。我们的微调LLM模型称为Llama 2-Chat,针对对话使用案例进行了优化。我们的模型在我们测试的大多数基准测试中都优于开源聊天模型,并且根据我们对其帮助性和安全性的人工评估,可能是封闭源模型的合适替代品。”


如上所述,LLaMA2模型的大小分为7B、13B和70B。它们既可以作为纯粹的完成模型使用,也可以针对对话使用案例进行优化。经过大约2万亿个令牌的预训练,它们支持最长4096个令牌的上下文长度。对于对话使用案例的微调是通过超过10万个示例进行的,并使用超过100万个代表人类偏好的训练样本进行了进一步优化。


在AWS中,使用SageMaker JumpStart可以轻松部署LLaMA2模型,也可以通过AWSxHuggingFace LLM DLC从HuggingFace模型库中获取模型。


微调


微调通过迁移学习将专业知识有效注入到像LLaMA2这样的基础模型中。该过程通过在特定领域数据上进行持续的预训练来更新模型的权重,同时保持整体网络结构不变。与需要大规模数据集和计算资源的完全预训练相比,微调具有较高的样本和计算效率。参数高效微调(PEFT)技术,如(q)LoRA方法,可以在最小开销的情况下轻量级注入专业知识到像LLaMA2这样的通用语言模型中。


在谈论微调时,有两种不同的方法:


1. 通过使用本地自回归因果语言建模(CLM)方法,对LLaMA2的仅解码器架构进行领域特定语料库的微调,可以将专业知识或特定语言或词汇(例如医学研究术语/语音)注入到LLM中,同时保留其通用语言能力。


2. 另一方面,通过使用特定任务的语料库,可以将模型微调为特定任务,例如遵循指令或对话行为。


这些模型利用自监督的训练方法,通过优化特定于语言建模(LM)的损失函数。像LLaMA2这样的仅解码器模型采用了因果语言建模(CLM)方法,带有单向上下文。简单来说,这意味着它们通过基于前面的令牌作为语义上下文来自我回归地预测后续的令牌进行训练。


如上所述,PEFT技术可以通过更新模型的一部分参数来轻量级地注入专业知识,开销最小。低秩适应(LoRA)或量化低秩适应(QLoRA)的方法可以冻结预训练模型的权重,并将可训练的秩分解矩阵注入到Transformer架构的每个层中,从而大大减少下游任务的可训练参数数量。


场景


在2023年7月26日,AWS在纽约举行的AWS峰会上宣布了关于生成式人工智能领域的各种功能。


考虑到LLaMA2的知识截止日期,遗憾的是,纯粹的LLaMA2模型无法提供有关这些公告的任何信息。我们希望通过利用微调将这些知识注入到基础模型(FM)中,并提升其知识水平,使其能够做到这一点。


由于我们并不打算将模型微调到特定任务,而只是想吸收特定领域的知识,我们可以采用经典的基于CLM的方法。


为了负责任地使用资源,我们将对LLaMA2-13b和LLaMA2-13b-chat模型进行微调。微调7b和70b版本也将根据调整后的训练集群配置进行操作。微调完成后,我们将部署基础模型以及经过微调的模型,并进行高级性能比较。


随后,我们将逐步介绍各个微调模型步骤的详细操作。


数据加载


我们使用LangChain的WebBaseLoader来加载一个由URL标识的网站数组的数据。


loader = WebBaseLoader(["https://aws.amazon.com/blogs/aws/preview-enable-foundation-models-to-complete-tasks-with-agents-for-amazon-bedrock/", "https://aws.amazon.com/blogs/aws/aws-entity-resolution-match-and-link-related-records-from-multiple-applications-and-data-stores/", "https://aws.amazon.com/blogs/database/the-role-of-vector-datastores-in-generative-ai-applications/", "https://aws.amazon.com/blogs/big-data/introducing-the-vector-engine-for-amazon-opensearch-serverless-now-in-preview/", "https://aws.amazon.com/blogs/big-data/build-data-integration-jobs-with-ai-companion-on-aws-glue-studio-notebook-powered-by-amazon-codewhisperer/", "https://aws.amazon.com/blogs/aws/new-amazon-ec2-p5-instances-powered-by-nvidia-h100-tensor-core-gpus-for-accelerating-generative-ai-and-hpc-applications/"])
data = loader.load()


许可协议和HuggingFace登录


2


在开始数据准备和训练之前,我们需要接受LLaMA2模型的许可协议。这包括在Meta网站上注册电子邮件地址与你的HuggingFace帐户匹配。


然后,我们在运行时使用HuggingFace中心进行身份验证。


huggingface-cli login --token ***enter huggingface token***


数据准备


首先,我们对原始网站进行一些预处理。在实际应用中,可以更加重视这个阶段。为了演示目的,我们只是简单地去除所有较大的空格连接,以便获得一个连贯和流畅的文本语料库。然后,我们将文档列表加载到HuggingFace的数据集中。


from datasets import Dataset
def strip_spaces(doc):
    return {"text": doc.page_content.replace("  ", "")}
  
stripped_data = list(map(strip_spaces, data))
dataset = Dataset.from_list(stripped_data)


在下一步中,我们对文本语料库进行标记化,使其适合LLM处理。为此,我们使用从HuggingFace Hub加载的LLaMA2标记器。根据模型的上下文窗口(2048个令牌),加载的标记器可以用于标记化创建的批次。


from itertools import chain
from functools import partial
from transformers import AutoTokenizer
model_id = "meta-llama/Llama-2–13b-chat-hf" # alternatively "meta-llama/Llama-2–13b-hf"
tokenizer = AutoTokenizer.from_pretrained(model_id,use_auth_token=True) # previous authentication with HuggingFace needed, see here https://huggingface.co/docs/huggingface_hub/main/en/package_reference/login
tokenizer.pad_token = tokenizer.eos_token
# empty list to save remainder from batches to use in next batch
remainder = {"input_ids": [], "attention_mask": [], "token_type_ids": []}
def chunk(sample, chunk_length=2048):
  # define global remainder variable to save remainder from batches to use in next batch
  global remainder
  # Concatenate all texts and add remainder from previous batch
  concatenated_examples = {k: list(chain(*sample[k])) for k in sample.keys()}
  concatenated_examples = {k: remainder[k] + concatenated_examples[k] for k in concatenated_examples.keys()}
  # get total number of tokens for batch
  batch_total_length = len(concatenated_examples[list(sample.keys())[0]])
  # get max number of chunks for batch
  if batch_total_length >= chunk_length:
  batch_chunk_length = (batch_total_length // chunk_length) * chunk_length
  # Split by chunks of max_len.
  result = {
  k: [t[i : i + chunk_length] for i in range(0, batch_chunk_length, chunk_length)]
  for k, t in concatenated_examples.items()
  }
  # add remainder to global variable for next batch
  remainder = {k: concatenated_examples[k][batch_chunk_length:] for k in concatenated_examples.keys()}
  # prepare labels
  result["labels"] = result["input_ids"].copy()
  return result
# tokenize and chunk dataset
lm_dataset = dataset.map(
    lambda sample: tokenizer(sample["text"]), batched=True, remove_columns=list(dataset.features)
).map(
    partial(chunk, chunk_length=2048),
    batched=True,
)
# Print total number of samples
print(f"Total number of samples: {len(lm_dataset)}")


然后我们将数据集保存在S3上,以便在训练作业中使用。


# save train_dataset to s3
training_input_path = f's3://{sess.default_bucket()}/processed/llama/genai-nyc-summit/train'
lm_dataset.save_to_disk(training_input_path)
print("uploaded data to:")
print(f"training dataset to: {training_input_path}")


训练


现在,我们可以触发一个Amazon SageMaker训练作业来执行基于预处理数据的CLM-tied QLoRA微调脚本。


超参数和训练脚本是根据Philipp的博客文章进行调整的。唯一的例外是执行的训练时期数量,这里我们选择了一个相对较高的时期数量,即20。这是因为我们的训练数据集相对较小(约32k个令牌)。在实际应用中,建议使用更大的数据集进行微调。


然后,我们可以执行训练作业,它将执行微调并将我们的模型文件保存到S3中。


from sagemaker.huggingface import HuggingFace, HuggingFaceModel
from huggingface_hub import HfFolder
# define Training Job Name
job_name = f'huggingface-qlora-{model_id.replace("/", "-")}-{time.strftime("%Y-%m-%d-%H-%M-%S", time.localtime())}'
# hyperparameters, which are passed into the training job
hyperparameters ={
  'model_id': model_id,                             # pre-trained model
  'dataset_path': '/opt/ml/input/data/training',    # path where sagemaker will save training dataset
  'epochs': 20,                                      # number of training epochs
  'per_device_train_batch_size': 2,                 # batch size for training
  'lr': 2e-4,                                       # learning rate used during training
  'hf_token': HfFolder.get_token(),                 # huggingface token to access llama 2
  'merge_weights': True,                            # wether to merge LoRA into the model (needs more memory)
}
# create the Estimator
huggingface_estimator = HuggingFace(
    entry_point          = 'run_clm.py',      # train script
    source_dir           = 'scripts',         # directory which includes all the files needed for training
    instance_type        = 'ml.g5.4xlarge',   # instances type used for the training job
    instance_count       = 1,                 # the number of instances used for training
    base_job_name        = job_name,          # the name of the training job
    role                 = role,              # Iam role used in training job to access AWS ressources, e.g. S3
    volume_size          = 300,               # the size of the EBS volume in GB
    transformers_version = '4.28',            # the transformers version used in the training job
    pytorch_version      = '2.0',             # the pytorch_version version used in the training job
    py_version           = 'py310',           # the python version used in the training job
    hyperparameters      =  hyperparameters,  # the hyperparameters passed to the training job
    environment          = { "HUGGINGFACE_HUB_CACHE": "/tmp/.cache" }, # set env variable to cache models in /tmp
)
# define a data input dictonary with our uploaded s3 uris
data = {'training': training_input_path}
# starting the train job with our uploaded datasets as input
huggingface_estimator.fit(data, wait=False)


部署


两个基础模型LLaMA2-13b和LLaMA2-13b-chat可以通过SageMaker JumpStart进行方便部署。


两个微调模型可以通过使用AWSxHuggingFace LLM DLC进行部署。为此,我们将HuggingFaceModel类的model_data参数指向模型文件的S3路径。然后,我们可以将在托管容器中设置的环境变量HF_MODEL_ID指向DLC中的默认模型路径(“/opt/ml/model”)。


from sagemaker.huggingface import get_huggingface_llm_image_uri
# retrieve the llm image uri
llm_image = get_huggingface_llm_image_uri(
"huggingface",
version="0.8.2"
)
# print ecr image uri
print(f"llm image uri: {llm_image}")
# sagemaker config
instance_type = "ml.g5.12xlarge"
number_of_gpu = 4
health_check_timeout = 300
# TGI config
config = {
'HF_MODEL_ID': "/opt/ml/model", # path to where sagemaker stores the model
'SM_NUM_GPUS': json.dumps(number_of_gpu), # Number of GPU used per replica
'MAX_INPUT_LENGTH': json.dumps(1024),  # Max length of input text
'MAX_TOTAL_TOKENS': json.dumps(2048),  # Max length of the generation (including input text),
# 'HF_MODEL_QUANTIZE': "bitsandbytes", # comment in to quantize
}
# create HuggingFaceModel
llm_model = HuggingFaceModel(
role=role,
image_uri=llm_image,
model_data="s3://sagemaker-us-east-1-308819823671/huggingface-qlora-llama2-13b-chat-2023--2023-08-02-08-54-16-604/output/model.tar.gz",
env=config
)
# Deploy model to an endpoint
# https://sagemaker.readthedocs.io/en/stable/api/inference/model.html#sagemaker.model.Model.deploy
llm = llm_model.deploy(
endpoint_name="llama-2-13b-chat-hf-nyc-finetuned", # alternatively "llama-2-13b-hf-nyc-finetuned"
initial_instance_count=1,
instance_type=instance_type,
# volume_size=400, # If using an instance with local SSD storage, volume_size must be None, e.g. p4 but not p3
container_startup_health_check_timeout=health_check_timeout, # 10 minutes to be able to load the model
)


测试


在部署了所有四个模型之后,我们希望在一个示例问题上测试它们的性能。Amazon EC2 P5实例的公告博文指出:“P5实例提供8个NVIDIA H100 Tensor Core GPU,拥有640 GB的高带宽GPU内存,第三代AMD EPYC处理器,2 TB的系统内存以及30 TB的本地NVMe存储。P5实例还提供3200 Gbps的聚合网络带宽,支持GPUDirect RDMA,通过绕过节点间的CPU通信,实现低延迟和有效的规模外性能。”


我们提出以下问题:“Amazon EC2 P5实例是什么?它们配备了哪种类型的GPU?”聊天模型使用推理配置max_new_tokens = 200,top_p = 0.9,temperature = 0.01给出以下回答:


1. LLaMA2-13b-chat:“Amazon Elastic Compute Cloud(EC2)P5实例是一种提供高性能计算资源的实例,适用于需要强大计算能力的应用程序。这些实例配备了NVIDIA Tesla V100 GPU,专为机器学习、科学模拟和其他高性能计算工作负载而设计。P5实例中的NVIDIA Tesla V100 GPU基于Volta架构,并具有多项适用于高要求工作负载的功能,包括:5120个用于高性能计算的CUDA核心,640个用于加速深度学习和其他基于矩阵的工作负载的张量核,16 GB的GDDR6内存,用于快速数据传输和处理,支持NVIDIA的GPU加速库和框架,例如CU...”


2. LLaMA2-13b-chat-nyc-finetuned:“Amazon EC2 P5实例是通用实例,为需要大规模图形处理和复杂机器学习(ML)模型的应用程序提供极端性能。它们由NVIDIA H100 Tensor Core GPU和第三代AMD EPYC处理器提供动力。H100 GPU提供640 GB的高带宽GPU内存,使你能够在实时中使用海量数据集运行最要求的应用程序。P5实例还提供2000 GB的高速CPU内存,允许你在内存中处理大量数据,而无需访问磁盘存储。这减少了处理时间并改善了响应时间。你可以将这些实例用于计算机视觉、视频编码、基因组分析和语言模型训练等应用。”


我们可以清楚地看到,基础模型中的模型产生了关于GPU类型(V100)的错误信息,而经过微调的模型则给出了正确的答案(H100)。我们还可以看到,通过qLoRA,我们可以保留基础模型的聊天微调特性,从而缓解了灾难性的遗忘问题。这意味着我们可以将知识注入到LLM中,而无需在此之后进行全面的指令/聊天微调,只需使用相应的任务微调模型作为基础模型即可。这种方法之所以奏效,是因为LoRA的本质是神经网络的每一层的大部分部分保持不变,同时对层进行扩展。


对于纯自动完成版本的模型,鉴于这些模型不能理解指令或以对话方式进行交互,我们需要重新构造问题。相反,它们只是通过自回归的下一个令牌预测来完成标记序列。


我们提出以下问题:“Amazon EC2 P5实例配备了哪种类型的GPU?”自动完成模型在推理配置max_new_tokens = 200,top_p = 0.9,temperature = 0.01下给出以下回答:


1. LLaMA2-13b:“NVIDIA Tesla V100。NVIDIA Tesla V100是一款专为高性能计算而设计的GPU。它基于NVIDIA Volta架构,配备32GB的HBM2内存。V100能够提供高达100万亿次的性能,并设计用于数据中心和超级计算机。NVIDIA Tesla V100是一款功能强大的GPU,适用于高性能计算。它基于NVIDIA Volta架构,配备32GB的HBM2内存。V100能够提供高达100万亿次的性能,并设计用于数据中心和超级计算机。


2. LLaMA2-13b-nyc-finetuned:“适用于计算密集型工作负载的NVIDIA A100,例如生成式人工智能、高性能计算(HPC)和数据分析。P5实例非常适合在拥有数万亿参数的模型中进行训练和运行推断,并对庞大数据集进行数据分析。与P4实例相比,它们在GPU内存方面提供了8倍的容量和在浮点操作(FLOPs)方面提供了64倍的计算性能。你可以在Amazon Elastic Container Service (ECS)中使用P5实例,并使用常见的容器工具和框架,如Docker、PyTorch、TensorFlow、Jupyter Notebook、R Studio和Venv,或者你可以通过Amazon Elastic MapReduce (EMR) 或Amazon DeepSense使用P5实例,使用Amazon提供的ML和AI框架和工具,也可以使用其他公司提供的框架和工具。为了在P5实例上更轻松地构建生成式AI应用程序...”


basic_endpoint = 'jumpstart-dft-meta-textgeneration-llama-2-13b' 
basic_endpoint_ft = 'llama-2-13b-hf-nyc-finetuned'
chat_endpoint = 'jumpstart-dft-meta-textgeneration-llama-2-13b-f'
chat_endpoint_ft = 'llama-2-13b-chat-hf-nyc-finetuned'
def query_endpoint(payload, endpoint_name):
    client = boto3.client("sagemaker-runtime")
    response = client.invoke_endpoint(
        EndpointName=endpoint_name,
        ContentType="application/json",
        Body=json.dumps(payload),
        CustomAttributes="accept_eula=true",
    )
    response = response["Body"].read().decode("utf8")
    response = json.loads(response)
    return response
print(f'{basic_endpoint}: {query_endpoint({"inputs": "Amazon EC2 P5 instances are equipped with GPUs of the type", "parameters": {"max_new_tokens": 200, "top_p": 0.9, "temperature": 0.01, "return_full_text": False}}, basic_endpoint)}')
print(f'{basic_endpoint_ft}: {query_endpoint({"inputs": "Amazon EC2 P5 instances are equipped with GPUs of the type", "parameters": {"max_new_tokens": 200, "top_p": 0.9, "temperature": 0.01, "return_full_text": False}}, basic_endpoint_ft)}')
print(f'{chat_endpoint}: {query_endpoint({"inputs": [[{"role": "user", "content": "What are Amazon EC2 P5 instances? Which kind of GPUs are they equipped with?"}]], "parameters": {"max_new_tokens": 200, "top_p": 0.9, "temperature": 0.01}}, chat_endpoint)}')
print(f'{chat_endpoint_ft}: {query_endpoint({"inputs": json.dumps([[{"role": "user", "content": "What are Amazon EC2 P5 instances? Which kind of GPUs are they equipped with?"}]]), "parameters": {"max_new_tokens": 200, "top_p": 0.9, "temperature": 0.01, "return_full_text": False}}, chat_endpoint_ft)}')


尽管在微调聊天微调模型时,使用这么少的数据已经消除了模型中的幻觉,但在我们的设置中似乎并没有起作用。这可能是因为我们的微调数据集规模有限。此外,更复杂的提示工程和优化推理配置参数设置可能会有所帮助。


结论


总之,本文深入探讨了将领域特定知识注入到大型语言模型(LLM)如LLaMA2中的关键过程,强调了在为面向企业级质量的LLM驱动应用程序设计时解决有关帮助性、诚实性和无害性挑战的重要性。这里的主要重点是参数化微调方法,它可以在不损害模型通用语言能力的情况下高效地向基础模型注入专业知识。


本文强调了使用参数高效微调技术(如qLoRA方法)微调LLaMA2的步骤,并展示了如何在Amazon SageMaker上进行此过程。通过采用这种方法,从业者可以将LLaMA2调整到特定领域,确保模型即使超出原始训练数据范围,也能与最新知识保持同步。


本文还强调了这种方法的灵活性,展示了它适用于已经经历了特定任务微调的模型,如LLaMA2-chat。这为将知识注入到LLM提供了机会,而无需进行广泛的指令或基于对话的微调,从而保持其特定任务的特性。

文章来源:https://towardsdatascience.com/leveraging-qlora-for-fine-tuning-of-task-fine-tuned-models-without-catastrophic-forgetting-d9bcd594cff4
欢迎关注ATYUN官方公众号
商务合作及内容投稿请联系邮箱:bd@atyun.com
评论 登录
写评论取消
回复取消