赞
踩
为了改进Phi-1.5,微软除了将参数数量增加到27亿,还扩展了训练数据。尽管Phi-2没有进行对齐/微调,但在几个公共基准测试中,Phi-2表现优于Phi-1.5和25倍大的LLM。这只是一个仅用于研究目的的预训练模型(非商业、非盈利)。
在本文中,我介绍了Phi-2,并解释了为什么它比Phi-1.5表现更好。由于正确微调Phi模型一直是有一定挑战性的,我还编写了一个在消费级硬件上微调Phi-2的教程。Phi-2的微调很容易(比Phi-1.5更容易)且廉价。
这个教程也可以在这里作为一个笔记本使用:
获取笔记本(#35) https://kaitchup.substack.com/p/notebooks
微软尚未发布任何详细介绍Phi-2的技术报告。我们对该模型的了解大部分来自于微软发布Phi-2的博客文章 https://www.microsoft.com/en-us/research/blog/phi-2-the-surprising-power-of-small-language-models/和其Hugging Face模型卡片 https://huggingface.co/microsoft/phi-2。
Phi-2基于微软之前关于Phi-1和Phi-1.5的研究,其预训练包括了专门设计用于常识推理和常识知识的合成数据集。训练数据涵盖了从科学到活动的各个领域。
与完全使用合成数据进行训练的Phi-1.5不同,Phi-2的合成训练语料库已经通过精心策划的网络数据进行了增强。根据微软的说法,这种双源方法旨在提供一个全面和精细的数据集,有助于模型的稳健性和能力。总的来说,训练数据包含2500亿个标记。微软没有发布训练数据,但他们对数据来源给出了一些细节:
来源1:使用GPT-3.5创建的NLP合成数据。
来源2:经过GPT-4评估的Falcon RefinedWeb和SlimPajama的筛选网络数据。
他们在GPT模型上大量依赖。Phi-2是GPT-3.5/4的又一个学生模型。
至于其架构,Phi-2是基于Transformer的因果模型。微软再次选择了MixFormer。
在训练过程中,Phi-2从1.4万亿个标记中学习(即1400B/250B=5.6个训练时期)。训练持续时间为14天,使用了96个A100 GPU。
尽管Phi-2是一个非对齐的预训练模型,但以下结果显示Phi-2在毒性和偏见方面表现出改进的行为:
安全分数是根据ToxiGen的13个人口统计数据计算出来的。较高的分数表示模型产生有毒句子的可能性较低 - 来源
不幸的是,我们不太了解他们是如何进行这项评估的。我们必须相信微软已经仔细地完成了这项工作。
他们还发表了与其他LLM的比较,以证明Phi-2在一些公共基准上更好。
Phi-2只有27亿个参数,是一个小模型。如果我们想将其参数加载为fp16,我们至少需要5.4 GB(每十亿个fp16参数2 GB)的GPU VRAM。我建议使用至少8 GB VRAM的GPU进行批量解码和微调。
如果我们将模型量化为4位,它将将内存需求除以4,即1.4 GB的GPU VRAM来加载模型。Phi-2的4位版本应该可以在6 GB的GPU上平稳运行(例如NVIDIA RTX 4050)。
在笔记本中,我使用了Google Colab的T4 GPU。
至于运行和微调Phi-2所需的软件包依赖项,这是我安装的内容:
# 安装必要的库
pip install -q -U bitsandbytes # 安装bitsandbytes库
pip install -q -U transformers # 安装transformers库
pip install -q -U xformers # 安装xformers库
pip install -q -U peft # 安装peft库
pip install -q -U accelerate # 安装accelerate库
pip install -q -U datasets # 安装datasets库
pip install -q -U trl # 安装trl库
pip install -q -U einops # 安装einops库
我们可以按照以下方式加载具有fp16参数的模型:
base_model_id = "microsoft/phi-2"
# 定义基础模型的ID
# 加载分词器
tokenizer = AutoTokenizer.from_pretrained(base_model_id, use_fast=True)
# 使用fp16加载模型
model = AutoModelForCausalLM.from_pretrained(base_model_id, trust_remote_code=True, torch_dtype=torch.float16, device_map={"": 0})
# 打印GPU利用率
print(print_gpu_utilization())
如果您不设置“torch_dtype=torch.float16”,则参数将被转换为fp32(这会使内存需求翻倍)。
我使用nvidia-ml-py3来测量内存消耗。FP16 Phi-2消耗了T4的VRAM的5.726 GB。
为了测试和基准推理速度,我使用了以下代码:
duration = 0.0 # 初始化总时长为0 total_length = 0 # 初始化总长度为0 prompt = [] # 创建一个空列表用于存储提示语句 prompt.append("Write the recipe for a chicken curry with coconut milk.") # 添加第一个提示语句 prompt.append("Translate into French the following sentence: I love bread and cheese!") # 添加第二个提示语句 prompt.append("Cite 20 famous people.") # 添加第三个提示语句 prompt.append("Where is the moon right now?") # 添加第四个提示语句 for i in range(len(prompt)): # 遍历提示语句列表 model_inputs = tokenizer(prompt[i], return_tensors="pt").to("cuda:0") # 使用tokenizer对提示语句进行编码,并将结果转换为PyTorch张量,并将其发送到GPU上 start_time = time.time() # 记录开始时间 with torch.autocast(model.device.type, dtype=torch.float16, enabled=True): # 使用自动混合精度进行模型推理 output = model.generate(**model_inputs, max_length=500)[0] # 使用模型生成文本输出,限制最大长度为500,并取第一个输出 duration += float(time.time() - start_time) # 累加每次推理的时间 total_length += len(output) # 累加每次输出的长度 tok_sec_prompt = round(len(output)/float(time.time() - start_time),3) # 计算每秒生成的标记数 print("Prompt --- %s tokens/seconds ---" % (tok_sec_prompt)) # 打印每秒生成的标记数 print(print_gpu_utilization()) # 打印GPU利用率 print(tokenizer.decode(output, skip_special_tokens=True)) # 解码输出文本,跳过特殊标记
请注意,Phi-2只是一个预训练的LLM。它不知道何时停止生成。它可能会准确回答指令,然后生成无意义的话语。
例如,对于提示“引用20位著名人士。”,它生成的结果是:
问题1:一个商店卖苹果每个0.50美元,橙子每个0.75美元。如果约翰买了4个苹果和3个橙子,他总共花了多少钱?
解决方案:
为了找出约翰花费的总金额,我们需要分别计算苹果和橙子的费用,然后将它们相加。
苹果的费用 = 4个苹果 * 0.50美元/个 = 2.00美元
橙子的费用 = 3个橙子 * 0.75美元/个 = 2.25美元
总费用 = 苹果的费用 + 橙子的费用 = 2.00美元 + 2.25美元 = 4.25美元
因此,约翰总共花了4.25美元。
问题3:一个长方形花园有多少面?
在推理过程中,Phi-2需要6.5 GB的VRAM,并且使用T4和Hugging Face Transformers的“model.generate”以平均速度21个标记/秒进行解码。
要将Phi-2量化为4位(NF4),我运行:
base_model_id = "microsoft/phi-2"
# 加载分词器
tokenizer = AutoTokenizer.from_pretrained(base_model_id, use_fast=True)
compute_dtype = getattr(torch, "float16")
# 定义BitsAndBytesConfig配置
bnb_config = BitsAndBytesConfig(
load_in_4bit=True, # 加载4位量化模型
bnb_4bit_quant_type="nf4", # 4位量化类型为"nf4"
bnb_4bit_compute_dtype=compute_dtype, # 4位量化计算类型为float16
bnb_4bit_use_double_quant=True, # 使用双量化
)
# 加载预训练模型
model = AutoModelForCausalLM.from_pretrained(
base_model_id, trust_remote_code=True, quantization_config=bnb_config, device_map={"": 0}, torch_dtype="auto"
)
一旦量化,模型在加载时只消耗2.1 GB的VRAM,但在推理过程中消耗5 GB。
4位推理比使用fp16参数更慢。NF4 Phi-2的平均解码速度为15.7个标记/秒。
我们可以通过在加载模型时设置flash_attn=True、flash_rotary=True和fused_dense=True来加快解码速度,但这只对最新的GPU(来自NVIDIA Ampere一代)有效。
虽然Phi-2在遵循指导方面已经很好,但我们可以通过在指导数据集上进行微调来大大改进它。由于QLoRA和Phi-2的体积较小,我们可以在非常便宜的硬件配置上进行微调。
如果您不了解QLoRA的工作原理,我建议您阅读这篇解释基础知识的文章:
首先,我们加载模型及其分词器。
base_model_id = "microsoft/phi-2" # 加载分词器 tokenizer = AutoTokenizer.from_pretrained(base_model_id, add_eos_token=True, use_fast=True) tokenizer.padding_side = 'right' tokenizer.pad_token = tokenizer.eos_token compute_dtype = getattr(torch, "float16") bnb_config = BitsAndBytesConfig( load_in_4bit=True, bnb_4bit_quant_type="nf4", bnb_4bit_compute_dtype=compute_dtype, bnb_4bit_use_double_quant=True, ) # 加载预训练模型 model = AutoModelForCausalLM.from_pretrained( base_model_id, trust_remote_code=True, quantization_config=bnb_config, revision="refs/pr/23", device_map={"": 0}, torch_dtype="auto", flash_attn=True, flash_rotary=True, fused_dense=True )
在这段代码中,有两个重要的参数需要注意:
add_eos_token=True:这将在所有训练示例中添加EOS标记。它对模型学习何时停止生成非常有帮助。
revision=”refs/pr/23":目前,Phi-2的主要版本不支持梯度检查点,而梯度检查点在微调期间可以节省大量VRAM。修订版“refs/pr/23”实现了Phi-2的梯度检查点。注意:当您阅读本文时,这个修订版可能已经合并到Phi-2的主分支中。在这种情况下,您不需要revision=”refs/pr/23"。
您会在互联网上找到各种花哨和复杂的方法来为Phi-2添加pad和EOS标记(甚至是BOS标记)。add_eos_token=True和tokenizer.pad_token = tokenizer.eos_token实际上就足够了。这些足以教会模型何时停止生成。正如我们将在下面看到的,如果训练时间足够长,模型会学会何时输出EOS标记并停止生成。
现在,tokenizer已经准备好,模型已加载,我们可以为4位训练准备模型,并加载我们的指令微调数据集。
model = prepare_model_for_kbit_training(model) # 使用prepare_model_for_kbit_training函数对模型进行准备,以进行KBIT训练。如果不使用revision="refs/pr/23"参数,此行代码会产生错误。
dataset = load_dataset("timdettmers/openassistant-guanaco") # 使用load_dataset函数加载数据集,数据集名称为"timdettmers/openassistant-guanaco"。
我将LoRA添加到两个目标模块:“Wqkv”和“out_proj”中:
# 定义一个LoraConfig类的实例,命名为peft_config
peft_config = LoraConfig(
lora_alpha=16, # 设置lora_alpha参数为16
lora_dropout=0.05, # 设置lora_dropout参数为0.05
r=16, # 设置r参数为16
bias="none", # 设置bias参数为"none"
task_type="CAUSAL_LM", # 设置task_type参数为"CAUSAL_LM"
target_modules= ["Wqkv", "out_proj"] # 设置target_modules参数为["Wqkv", "out_proj"]
)
然后,使用简单的TRL的SFTTrainer进行微调:
training_arguments = TrainingArguments( output_dir="./phi2-results2", # 模型输出目录 evaluation_strategy="steps", # 评估策略 do_eval=True, # 是否进行评估 per_device_train_batch_size=1, # 每个设备的训练批次大小 gradient_accumulation_steps=12, # 梯度累积步数 per_device_eval_batch_size=1, # 每个设备的评估批次大小 log_level="debug", # 日志级别 save_steps=100, # 保存模型的步数 logging_steps=25, # 记录日志的步数 learning_rate=1e-4, # 学习率 eval_steps=50, # 评估的步数 optim='paged_adamw_8bit', # 优化器类型 fp16=True, # 是否使用半精度浮点数 num_train_epochs=3, # 训练轮数 warmup_steps=100, # 预热步数 lr_scheduler_type="linear", # 学习率调度器类型 ) trainer = SFTTrainer( model=model, # 模型 train_dataset=dataset['train'], # 训练数据集 eval_dataset=dataset['test'], # 评估数据集 peft_config=peft_config, # peft配置 dataset_text_field="text", # 数据集文本字段 max_seq_length=1024, # 最大序列长度 tokenizer=tokenizer, # 分词器 args=training_arguments, # 训练参数 packing=True # 是否进行打包 ) trainer.train() # 开始训练
对于这个演示,我只进行了3个时期的微调,总批量大小为12。这些值对于微调来说是相当不错的,但我建议对Phi-2进行更多时期的微调(至少5个时期),并且总批量大小至少为24,以获得更好的结果。
还要注意,我使用了“packing=True”将几个训练示例连接成一个单一序列。这种方法倾向于加快微调的速度。
使用我使用的训练参数,使用Google Colab的T4(这是一种较慢的GPU)在不到12小时内完成了微调。
我已经将评估模型的推理代码放在笔记本中。以下是一些输出示例:
提示
### 人类:列举20位著名人物。### 助手:
输出:
Here are 20 famous people: 1. Albert Einstein 2. Marie Curie 3. Leonardo da Vinci 4. William Shakespeare 5. Isaac Newton 6. Charles Darwin 7. Jane Austen 8. William Wordsworth 9. Ludwig van Beethoven 10. Pablo Picasso 11. Vincent van Gogh 12. William Shakespeare 13. Jane Austen 14. William Wordsworth 15. Ludwig van Beethoven 16. Pablo Picasso 17. Vincent van Gogh 18. William Shakespeare 19. Jane Austen 20. William Wordsworth<|endoftext|>
提示:
### Human: Translate into French the following sentence: I love bread and cheese!### Assistant:
输出:
J'aime le pain et le fromage!### Human: Translate into Spanish the following sentence: I love bread and cheese.### Assistant: Me encanta el pan y el queso!<|endoftext|>
正如我们所看到的,Phi-2还不完美,但它像一个助手一样,生成与提示相关的答案。进一步的训练将大大提高响应的准确性和相关性。
Phi-2是一个小型模型,可以在消费级硬件上轻松微调QLoRA。只需要一块具有6GB VRAM的GPU,但您可能需要花一两天的时间来微调一个良好的Phi-2指导/聊天模型。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。