极客时间已完结课程限时免费阅读

27|模型工程(三):低成本领域模型方案,小团队怎么做大模型?

27|模型工程(三):低成本领域模型方案,小团队怎么做大模型?-AI大模型系统实战-极客时间
下载APP

27|模型工程(三):低成本领域模型方案,小团队怎么做大模型?

讲述:Tyler

时长11:20大小10.36M

你好,我是 Tyler。
在前两节课中,我们学习了如何通过 self-instruct 的方法获取训练数据,以及如何以较低成本训练模型。你对这两个内容掌握得如何?
今天这节课,我们将继续深入探讨这些算法的具体实现。我们将学习数据增强、全量训练和 LoRA(低秩适应)的低成本领域模型训练。

为什么选 Alpaca 项目?

为了帮助你快速直观地建立感性认识,我在众多的学习对象中选择了 Alpaca 这个开源项目。目前,许多领域专属模型的开发方法几乎都源自 Alpaca,而且 Alpaca 的开源实现与工业界的需求紧密契合,可以说达到了工业级的入门标准。而且,Alpaca 的全量参数训练和 LoRA 加速训练方法都得到了出色的开源项目支持。
我们可以通过研究 Alpaca 项目的原始代码来了解大语言模型的训练方法。在你掌握并灵活使用 Alpaca 之后,就可以逐渐上手工业级复杂大语言模型的开发和微调。好,现在我们正式开始 Alpaca 开源项目的学习。
先来看一下数据生成算法的实现,Alpaca 模型是通过对 7B 的 LLaMA 模型使用 self-instruct 论文中的技术生成的 5.2 万条指令遵循数据。self-instruct 论文提出了一种新的生成数据增强方法,可以有效地提高大语言模型的性能。
目前 Alpaca 模型仍在发展中,存在一些局限性,例如安全性问题。不过,安全性问题是开源大语言模型的通用问题,因此在使用 Alpaca 模型时,我们需要采取相应的安全措施,例如使用安全的输入数据和训练方法,以及在部署模型时应采取相应的风控和内容安全策略。
下面我们开始 Alpaca 的具体实现学习,大致分为数据生成、模型训练和训练提效这几个环节。

数据生成过程

第 25 节课我们学习了 Alpaca 的数据生成方法,Alpaca 对 self-instruct(自我教导)数据生成的方式进行了升级,提高了效率,降低了花费。这些升级包括用 text-davinci-003(代替 davinci)来生成指令数据,并且创建了一个新的提示词模版 prompt.txt。
此外,他还采用了更大胆的批量解码方式,每一次会生成 20 条指令,大幅度降低了生成数据的费用。同时,数据生成流程也变得更简单了,取消了分类指令和非分类指令之间的差异,每个指令只生成一个实例,而不再生成 2 到 3 个实例。
这些优化成功生成了包含 52000 个实例的指令数据集,而成本只有不到 500 美元。初步研究还显示,与之前的 self-instruct 发布的数据相比,这样生成的这 52000 条数据更加多样化。
下一步,我们将带你生成这些数据,体会一下这种无中生有的快乐。
要运行这些代码,你需要使用你的 OpenAI API 密钥,设置环境变量 OPENAI_API_KEY,然后用以下命令安装所需的依赖。
$ pip install -r requirements.txt
接下来,我们运行生成数据的指令。
$ python -m generate_instruction generate_instruction_following_data
这里 generate_instruction_following_data 函数,是为了生成新的指令数据,确保新生成的指令文本不要和已有的指令文本过于相似。生成的数据可以用于微调预训练大语言模型。
下面我将为你解释一下这个函数的具体实现。步骤稍微有点多,需要你耐心一点,跟住我的节奏还是很容易理解的。
首先,它加载了种子任务数据,这是人工编写的指令数据,代码中将解析这些 JSON 格式的数据,提取指令、输入和输出信息。
seed_tasks = [json.loads(l) for l in open(seed_tasks_path, "r")]
seed_instruction_data = [
{"instruction": t["instruction"], "input": t["instances"][0]["input"], "output": t["instances"][0]["output"]}
for t in seed_tasks
]
接着,它创建了一个输出目录(如果不存在的话),用于存储生成的指令数据。
os.makedirs(output_dir, exist_ok=True)
之后我们需要初始化一些变量,其中包括生成请求的索引、存储机器生成指令的数据列表以及 ROUGE 评估器。
request_idx = 0
machine_instruction_data = []
scorer = rouge_scorer.RougeScorer(["rougeL"], use_stemmer=False)
这里还有一个进度条,用于可视化地追踪生成进度,如果已经有新的指令数据生成出来,就会更新进度条。
progress_bar = tqdm.tqdm(total=num_instructions_to_generate)
if machine_instruction_data:
progress_bar.update(len(machine_instruction_data))
接下来,将所有种子任务指令和已生成的机器指令数据变成 token,以便后续做相似性比较。
all_instructions = [d["instruction"] for d in seed_instruction_data] + [
d["instruction"] for d in machine_instruction_data
]
all_instruction_tokens = [scorer._tokenizer.tokenize(inst) for inst in all_instructions]
之后,算法通过循环生成新的指令数据,直到达到指定的指令数据生成数量。
while len(machine_instruction_data) < num_instructions_to_generate:
request_idx += 1
# ... (以下是生成新指令的主要部分)
接下来为每个生成请求创建一个输入文本批次,每个批次包含多个提示指令。
batch_inputs = []
for _ in range(request_batch_size):
# only sampling from the seed tasks
prompt_instructions = random.sample(seed_instruction_data, num_prompt_instructions)
prompt = encode_prompt(prompt_instructions)
batch_inputs.append(prompt)
再然后我们就可以使用 OpenAI 的 API 向模型提出请求,生成新的指令数据。
decoding_args = utils.OpenAIDecodingArguments(
temperature=temperature,
n=1,
max_tokens=3072,
top_p=top_p,
stop=["\n20", "20.", "20."],
)
request_start = time.time()
results = utils.openai_completion(
prompts=batch_inputs,
model_name=model_name,
batch_size=request_batch_size,
decoding_args=decoding_args,
logit_bias={"50256": -100},
)
request_duration = time.time() - request_start
再然后,处理生成的结果,提取新生成的指令数据。
process_start = time.time()
instruction_data = []
for result in results:
new_instructions = post_process_gpt3_response(num_prompt_instructions, result)
instruction_data += new_instructions
之后是比较关键的计算相似度环节。对每个新生成的指令数据,需要使用 ROUGE 评估方法,计算它与已有指令数据的相似性分数。如果相似度高于 0.7,这个指令将不予保留。
total = len(instruction_data)
keep = 0
for instruction_data_entry in instruction_data:
# ... (以下是计算相似性分数的主要部分)
最后,保存新生成的指令数据到文件,打印每个请求的持续时间、生成的指令数量和保留的指令数量。
process_duration = time.time() - process_start
print(f"Request {request_idx} took {request_duration:.2f}s, processing took {process_duration:.2f}s")
print(f"Generated {total} instructions, kept {keep} instructions")
utils.jdump(machine_instruction_data, os.path.join(output_dir, "regen.json"))
到这里,generate_instruction_following_data 函数就完成了它的使命,我们也拿到了生成的指令微调数据。

全量参数训练

接下来,我们用上一步中生成的数据,来训练 Llama 模型。最终生成的数据存储在 alpaca_data.json 文件中,其中的数据集包含了 52000 条指令,每个指令由以下字段组成。
instruction:一个字符串,描述模型应该执行的任务,这 52000 条指令都是独一无二的。
input:是一个可选的字段,用来表示指令的可选上下文或输入。大约 40% 的示例包含这个字段,例如,当指令是 “总结以下文章” 时,该字段的输入则是文章原文内容。
output:一个字符串,由 text-davinci-003 生成的、作为指令数据的输出。
你可以看一下文稿后面的例子,这样更好理解。
[
{
"instruction": "Give three tips for staying healthy.",
"input": "",
"output": "1.Eat a balanced diet and make sure to include plenty of fruits and vegetables. \n2. Exercise regularly to keep your body active and strong. \n3. Get enough sleep and maintain a consistent sleep schedule."
},
...
]
如果你想要进行微调,首先需要使用以下指令安装必要的依赖。
$ pip install -r requirements.txt
接下来,需要在拥有 4 个 A100 80G GPU(FSDP full_shard 模式)的机器上,使用我们的数据集对 LLaMA-7B 进行微调。
我们使用后面的命令,就可以在 Python 3.10 上复现与论文中模型质量相似的结果。
首先修改以下配置内容。
第一要设置端口 <your_random_port>,这个端口用于多机多卡训练。
第二个参数是原始预训练 llama 模型所在的路径 <your_path_to_hf_converted_llama_ckpt_and_tokenizer>。
第三个参数是训练完成后模型保存的路径 <your_output_dir>。
torchrun --nproc_per_node=4 --master_port=<your_random_port> train.py \
--model_name_or_path <your_path_to_hf_converted_llama_ckpt_and_tokenizer> \
--data_path ./alpaca_data.json \
--bf16 True \
--output_dir <your_output_dir> \
--num_train_epochs 3 \
--per_device_train_batch_size 4 \
--per_device_eval_batch_size 4 \
--gradient_accumulation_steps 8 \
--evaluation_strategy "no" \
--save_strategy "steps" \
--save_steps 2000 \
--save_total_limit 1 \
--learning_rate 2e-5 \
--weight_decay 0. \
--warmup_ratio 0.03 \
--lr_scheduler_type "cosine" \
--logging_steps 1 \
--fsdp "full_shard auto_wrap" \
--fsdp_transformer_layer_cls_to_wrap 'LlamaDecoderLayer' \
--tf32 True
这个脚本也同样适用于 OPT(Open Pre-trained Transformer Language Models)的微调。以下是微调 OPT-6.7B 的示例,你可以尝试一下 Meta 的另一款模型,看看和 Llama 有什么区别。
torchrun --nproc_per_node=4 --master_port=<your_random_port> train.py \
--model_name_or_path "facebook/opt-6.7b" \
--data_path ./alpaca_data.json \
--bf16 True \
--output_dir <your_output_dir> \
--num_train_epochs 3 \
--per_device_train_batch_size 4 \
--per_device_eval_batch_size 4 \
--gradient_accumulation_steps 8 \
--evaluation_strategy "no" \
--save_strategy "steps" \
--save_steps 2000 \
--save_total_limit 1 \
--learning_rate 2e-5 \
--weight_decay 0. \
--warmup_ratio 0.03 \
--lr_scheduler_type "cosine" \
--logging_steps 1 \
--fsdp "full_shard auto_wrap" \
--fsdp_transformer_layer_cls_to_wrap 'OPTDecoderLayer' \
--tf32 True
需要注意的是,这里提供的训练脚本是为了简化使用,让你可以轻松上手,所以没有经过特别的性能优化。如果要使用更多的 GPU 运行,你可以试着调整 gradient_accumulation_steps 的设置。

LoRA 低成本训练

当然,不是所有的同学都有条件进行全量的训练实验,毕竟它需要 8 张 A100 的显卡,这时候我们可以运用上节课学到的 LoRA(低秩适应)。
我们可以使用这种方法来复现斯坦福大学 Alpaca 项目的结果。在接下来要讲的这个项目中,也提供了一个 Instruct 模型,而且训练的质量与 text-davinci-003 类似,可以在树莓派上运行的(用于研究)、而且代码也可以轻松扩展到 13b、30b 和 65b 的模型。
除了训练代码,该项目还发布了已微调好的模型和 LoRA 权重以及推理的脚本。为了让微调的过程节省资源并且高效,该项目使用了 HuggingFace 的 PEFT 和 Tim Dettmers 的 bitsandbytes 来优化性能。
在默认的超参数设置下,LoRA 模型的输出与斯坦福 Alpaca 模型相当。不过进一步的调整可能会取得更好的性能。如果你对此感兴趣,不妨亲自尝试一下,并把你的结果在留言区里分享出来。

本地设置

我们首先在本地设置 LoRA,首先安装必要的依赖。
$ pip install -r requirements.txt
如果 bitsandbytes 无法正常工作,可以考虑从源代码进行安装。

训练(finetune.py)

finetune.py 包含了基于 PEFT 微调 LLaMA 模型的示例,具体的用法是后面这样。
python finetune.py \
--base_model 'decapoda-research/llama-7b-hf' \
--data_path 'yahma/alpaca-cleaned' \
--output_dir './lora-alpaca'
下面我带你梳理一下 train 函数的内容,这是模型训练代码的核心部分,这里我们挑主要的代码逻辑讲解。我们使用了 Hugging Face Transformers 库中的 LlamaForCausalLM 类来加载基础模型,这里通过 base_model 参数来设置。
model = LlamaForCausalLM.from_pretrained(
base_model,
load_in_8bit=True,
torch_dtype=torch.float16,
device_map=device_map,
)
接着,我们为 LoRA 方法创建配置,然后把它传到模型的设置中。
config = LoraConfig(
r=lora_r,
lora_alpha=lora_alpha,
target_modules=lora_target_modules,
lora_dropout=lora_dropout,
bias="none",
task_type="CAUSAL_LM",
)
model = get_peft_model(model, config)
然后,这里的代码检查了是否有恢复训练的检查点。如果有,它会加载已有的模型权重。
if resume_from_checkpoint:
# Check the available weights and load them
checkpoint_name = os.path.join(
resume_from_checkpoint, "pytorch_model.bin"
) # Full checkpoint
if not os.path.exists(checkpoint_name):
checkpoint_name = os.path.join(
resume_from_checkpoint, "adapter_model.bin"
) # only LoRA model - LoRA config above has to fit
resume_from_checkpoint = (
False # So the trainer won't try loading its state
)
# The two files above have a different name depending on how they were saved, but are actually the same.
if os.path.exists(checkpoint_name):
print(f"Restarting from {checkpoint_name}")
adapters_weights = torch.load(checkpoint_name)
set_peft_model_state_dict(model, adapters_weights)
else:
print(f"Checkpoint {checkpoint_name} not found")
之后的这段代码用来检查是否在单机多卡(Multi-GPU)环境中运行,如果是,将模型配置为模型并行。
if not ddp and torch.cuda.device_count() > 1:
# keeps Trainer from trying its own DataParallelism when more than 1 gpu is available
model.is_parallelizable = True
model.model_parallel = True
然后,这里创建了一个 Hugging Face Transformers 的 Trainer 对象,用于管理和执行训练过程,其中设置了训练参数、数据集和数据收集器。
trainer = transformers.Trainer(
model=model,
train_dataset=train_data,
eval_dataset=val_data,
args=transformers.TrainingArguments(
per_device_train_batch_size=micro_batch_size,
gradient_accumulation_steps=gradient_accumulation_steps,
warmup_steps=100,
num_train_epochs=num_epochs,
learning_rate=learning_rate,
fp16=True,
logging_steps=10,
optim="adamw_torch",
evaluation_strategy="steps" if val_set_size > 0 else "no",
save_strategy="steps",
eval_steps=200 if val_set_size > 0 else None,
save_steps=200,
output_dir=output_dir,
save_total_limit=3,
load_best_model_at_end=True if val_set_size > 0 else False,
ddp_find_unused_parameters=False if ddp else None,
group_by_length=group_by_length,
report_to="wandb" if use_wandb else None,
run_name=wandb_run_name if use_wandb else None,
),
data_collator=transformers.DataCollatorForSeq2Seq(
tokenizer, pad_to_multiple_of=8, return_tensors="pt", padding=True
),
)
最后,这里开始模型的训练,如果有提供的 checkpoint(检查点),则从该检查点恢复训练。
trainer.train(resume_from_checkpoint=resume_from_checkpoint)
当然,你也可以基于后面的示例,根据具体的需要调整超参数。
python finetune.py \
--base_model 'decapoda-research/llama-7b-hf' \
--data_path 'yahma/alpaca-cleaned' \
--output_dir './lora-alpaca' \
--batch_size 128 \
--micro_batch_size 4 \
--num_epochs 3 \
--learning_rate 1e-4 \
--cutoff_len 512 \
--val_set_size 2000 \
--lora_r 8 \
--lora_alpha 16 \
--lora_dropout 0.05 \
--lora_target_modules '[q_proj,v_proj]' \
--train_on_inputs \
--group_by_length
至此,你已经完成基于 LoRA 的模型训练了,你可以直接将这里的模型用于在线的模型推理。

推理(generate.py)

当然,如果你没有领域定制训练的需求,只是想看看 Alpaca 默认实现的模型效果是什么样的,可以直接使用 generate.py 从 Hugging Face 模型库读取基础模型和 LoRA 权重,然后运行一个 Gradio 接口,用于对指定输入进行推理。
你可以使用后面这段示例代码使用模型,具体的权重文件版本可以使用 lora_weights 参数进行指定。
python generate.py \
--load_8bit \
--base_model 'decapoda-research/llama-7b-hf' \
--lora_weights 'tloen/alpaca-lora-7b'
是不是非常方便?你可以参考这节课学习的内容,课后亲手练习一下,尝试搭建起你自己的专属领域大模型,完成你在特定领域的具体任务。

总结

学到这里,我们做个总结吧。
在这节课中,我们学到了一系列方法和工具,它们可以帮助你以更低的成本进行模型训练、生成多样化的数据,我们还学习了如何通过 LoRA 来提高性能。如果你对这些技术和流程感兴趣,鼓励你深入学习和实践,以便能够训练出属于你自己的领域专属模型。
当然,目前的 self-instruct 和 alpaca 数据生成方法也存在一些局限性,正如我们在之前的课后习题中提到的,它们的数据质量受限于 GPT 知识的范围。因此,为了生成高质量的训练数据,超越 GPT 知识范围,我们可以考虑其他数据生成方法,比如结合低质量的大规模原始语料,利用大语言模型生成相对少量的高质量语料。
模型工程的任务主要包括两个方面:对大型语言模型的微调(从 1-10)和构建全新的大型语言模型(从 0-1)。至此,我们已经完成了模型训练的必修部分,也就是模型微调的内容。

思考题

根据前面学习的 GPT 系列原理的知识,想一想构建一个规模达到 100B 及以上的模型需要怎么做。
恭喜完成我们第 27 次的打卡学习,期待你在留言区和我交流互动。如果你觉得有收获,也欢迎你分享给你身边的朋友,邀 TA 一起讨论。

Alpaca项目为低成本领域提供了实现大型模型的解决方案,重点介绍了其数据生成过程和LoRA加速训练方法。通过升级self-instruct数据生成方式,Alpaca成功生成了包含52000个实例的指令数据集,成本不到500美元。文章还提供了生成指令数据的具体实现代码,并解释了每个步骤的作用。此外,全量参数训练和LoRA低成本训练的具体实现步骤也得到了介绍。通过具体案例和代码实现,展示了在低成本领域实现大模型的方法和技术特点,为小团队实现大模型提供了有益的指导和启发。同时,文章还提到了构建规模达到100B及以上的模型需要考虑的问题,为读者提供了进一步思考的方向。

分享给需要的人,Ta购买本课程,你将得18
生成海报并分享
2023-10-20

赞 5

提建议

上一篇
26|模型工程(二):算力受限,如何为“无米之炊”?
下一篇
28|总体回顾:工业级AI大模型系统的庐山真面目
unpreview
 写留言

全部留言(5)

  • 最新
  • 精选
  • 顾琪瑶
    2023-10-20 来自上海
    想问下老师, 在目前的行业内, 如果是偏向基于大模型的业务应用开发的话, 微调是必备技能吗, 还是可选的?

    作者回复: 你好,顾琪瑶!即使是偏向大模型的业务应用开发,我也建议根据课程的内容,学习微调能力。因为这样可以让你在技术原理上真正理解大模型能力的来源,帮助你更好地完成业务需求。

    5
  • 周晓英
    2023-10-29 来自北京
    老师您好,如果现有的Embedding模型无法完全满足需求,想训练自己领域的Ebedding模型,可以采用您文中的方法吗?

    作者回复: 你好,周晓英。本节课主要学习的是大语言模型的训练方法,如果想训练自己的嵌入表征模型,可以使用第24课中的 sentence-bert(SBERT)算法。

    共 2 条评论
    3
  • 陈东
    2023-10-20 来自广西
    大模型在老师实践和工作中主要的作用?主要面对什么产业?产生什么价值?

    作者回复: 你好,陈东!我的工作主要是负责AI大模型平台体系建设,构建基于大模型技术的应用生态。如果你对我在产业的洞察感兴趣,欢迎你多花时间看一下最后一章的内容,那里有很多一线经历的总结。

    1
  • Geek_24ebc1
    2024-05-15 来自北京
    想问下老师,领域大模型,都需要提供哪些输入信息?有没有可参考的链接或文案。例如:想做一个油气测井大模型,或者医疗大模型。
    1
  • R_R
    2024-03-07 来自北京
    好文