Llama 3.1 模型应用指南:从部署到微调的全面教程
Llama 3.1 模型应用指南:从部署到微调的全面教程
在人工智能的潮流中,大型语言模型(LLMs)已成为推动技术进步的关键力量。Meta公司最新开源的Llama 3.1模型的发布,标志着开源AI领域的一次重大飞跃。Llama 3.1凭借其卓越的性能和广泛的应用潜力,成为开发者和研究者探索复杂AI应用的重要工具。
本文将详细介绍Llama 3.1模型的各个方面,从性能评估到模型推理和微调,为读者提供一份全面的实用指南。
Llama 3.1模型概述
Llama 3.1于7月23日正式推出,包含8B、70B和405B三种参数规模的多语言预训练模型。这些模型支持八种语言,并具备最高128K的上下文长度,使其在处理长文本方面表现出色。更重要的是,Llama 3.1的性能与业界领先的闭源模型相当,同时提供了开源的灵活性和可定制性。
Llama 3.1的主要特点
- 参数规模:Llama 3.1提供三种规格:80亿、700亿和4050亿参数,4050亿参数为系列中最强大,具备广泛的知识、数学计算、多语言翻译和工具操作能力,提升了模型处理复杂任务的能力。
- 上下文长度:支持128K的上下文长度,适合长文本摘要、复杂对话与多步骤问题解决,显著提升了长文本处理的效率。
- 多语言支持:模型支持包括英语、中文、西班牙语、法语、德语、日语、韩语和阿拉伯语在内的八种语言,增强了其全球应用性,适合多语言翻译及跨语言处理。
- 模型下载与定制:Llama 3.1模型可以从Meta官方网站和Hugging Face平台下载,允许开发者进行自定义训练和微调,以适应多样化的应用场景,推动AI技术的普及与创新。
- 高性能与高效训练:模型在超过15万亿个标记上训练,使用超过16,000个H100 GPU进行优化,确保其高性能与高效能,预训练数据截至到2023年12月。
- 量化技术:为了应对405B模型的运行需求,Meta将模型数据从16位(BF16)量化至8位(FP8),显著降低了计算资源的需求,使模型可以在单一服务器节点上运行。
- 安全与防护措施:提供Llama Guard 3和Prompt Guard等安全工具,以及Llama Stack API的评论请求,旨在促进第三方项目更容易地利用Llama模型。
- 生态系统支持:Meta改进了模型的训练与微调流程,以及推理与部署方式,以更广泛地支持开发者和平台提供商,包括AWS、NVIDIA、Google Cloud等25个合作伙伴提供的即用服务,确保无缝的开发与部署体验。
Llama 3.1模型性能评估
Llama 3.1在150多个涵盖多种语言的基准数据集上进行了性能评估,此外还进行了广泛的人工评估,将Llama 3.1与其他竞争模型进行了比较。实验表明,Llama 3.1的旗舰模型在多项任务中与领先的基础模型如GPT-4和Claude 3.5 Sonnet相媲美。同时,Llama 3.1的小型模型也与相似参数的闭源与开源模型表现相当。
Llama 3.1模型推理实践
1. 环境准备
首先,我们需要确保服务器具备足够的硬件配置来支持Llama 3.1模型的运行。我们选择了一台配备4090 GPU(24G显存)的服务器,基础镜像信息如下:ubuntu 22.04、python 3.12、cuda 12.1、pytorch 2.3.0。
2. 安装依赖
首先通过pip更换源以加速下载并安装依赖包
# 升级pip
python -m pip install --upgrade pip
# 更换pypi源以加速库的安装
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
pip install fastapi==0.111.1
pip install uvicorn==0.30.3
pip install modelscope==1.16.1
pip install transformers==4.42.4
pip install accelerate==0.32.1
安装完成如下:
3. 模型下载
使用modelscope中的snapshot_download函数下载模型。第一个参数为模型名称,cache_dir用于指定模型的下载路径。在/root/autodl-tmp路径下新建d.py文件,并输入以下内容:
import torch
from modelscope import snapshot_download, AutoModel, AutoTokenizer
import os
model_dir = snapshot_download('LLM-Research/Meta-Llama-3.1-8B-Instruct', cache_dir='/root/autodl-tmp', revision='master')
运行python /root/autodl-tmp/d.py
执行下载。注意,模型大小约为15GB,下载时间可能需要20分钟,请耐心等待。
4. 模型推理
1)推理测试
# 导入所需的库
from transformers import AutoTokenizer, AutoModelForCausalLM
import torch
# 加载预训练的分词器和模型
model_name_or_path = '/root/autodl-tmp/LLM-Research/Meta-Llama-3___1-8B-Instruct'
tokenizer = AutoTokenizer.from_pretrained(model_name_or_path, use_fast=False)
model = AutoModelForCausalLM.from_pretrained(model_name_or_path, device_map="auto", torch_dtype=torch.bfloat16)
# 定义对话消息列表
messages = [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": "Who are you?"}]
# 使用分词器将对话消息转换为模型输入格式
input_ids = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
# 将输入转换为PyTorch张量并移动到GPU设备上
model_inputs = tokenizer([input_ids], return_tensors="pt").to('cuda')
# 使用模型生成回复
generated_ids = model.generate(model_inputs.input_ids, max_new_tokens=512)
# 从生成的ID中提取回复部分
generated_ids = [output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)]
# 使用分词器将生成的ID解码为文本
response = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
执行成功如下:
查看响应结果:
response
输出结果如下:
"I'm an artificial intelligence model designed to assist and communicate with users in a helpful and informative way. I'm a type of chatbot, and my primary function is to provide information, answer questions, and engage in conversation to the best of my abilities.
2)中文测试一
# 定义对话消息列表
messages = [{"role": "user", "content": "你会讲中文么?"}]
# 使用分词器将对话消息转换为模型输入格式
input_ids = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
# 将输入转换为PyTorch张量并移动到GPU设备上
model_inputs = tokenizer([input_ids], return_tensors="pt").to('cuda')
# 使用模型生成回复
generated_ids = model.generate(model_inputs.input_ids, max_new_tokens=512)
# 从生成的ID中提取回复部分
generated_ids = [output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)]
# 使用分词器将生成的ID解码为文本
response = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
response
输出如下:
3)中文测试二
# 定义对话消息列表
messages = [{"role": "user", "content": "请以“夜晚”为题写一首诗"}]
# 使用分词器将对话消息转换为模型输入格式
input_ids = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
# 将输入转换为PyTorch张量并移动到GPU设备上
model_inputs = tokenizer([input_ids], return_tensors="pt").to('cuda')
# 使用模型生成回复
generated_ids = model.generate(model_inputs.input_ids, max_new_tokens=512)
# 从生成的ID中提取回复部分
generated_ids = [output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)]
# 使用分词器将生成的ID解码为文本
response = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
response
输出如下:
注意事项
如果推理时出现以下错误:
File ~/miniconda3/lib/python3.10/site-packages/transformers/models/llama/configuration_llama.py:182, in LlamaConfig._rope_scaling_validation(self)
则需要升级transformers:
pip install --upgrade transformers
资源消耗如下:
Llama 3.1模型微调实践
1. 数据集准备
微调大型语言模型(LLM)通常涉及指令微调,这是一种特定的数据准备和训练过程。在指令微调中,数据集由一系列包含指令、输入和输出的条目组成,例如:
{
"instruction": "回答以下用户问题,仅输出答案。","input": "1+1等于几?","output": "2"}
在这个例子中,instruction
是给予模型的任务指令,指定模型需完成的任务;input
是完成任务所需的用户提问或信息;而 output
则是模型应产生的预期回答。
我们的目标是训练模型,使其能够准确理解并遵循用户的指令。因此,在构建指令集时,必须针对特定的应用目标精心设计。例如,如果目标是创建一个能够模仿特定对话风格的个性化LLM,则需构建与之相应的指令集。
以使用开源的甄嬛传对话数据集为例,如果希望模型模拟甄嬛的对话风格,可以构造如下的指令:
在此示例中,省略了 input
字段,因为模型的回答基于预设角色背景知识,而非用户的直接提问。这种方式可使模型学习并模仿特定角色的语言风格和对话模式,从而在实际应用中提供更加个性化和情景化的交互体验。
2. 导入依赖包
from datasets import Dataset
import pandas as pd
from transformers import AutoTokenizer, AutoModelForCausalLM, DataCollatorForSeq2Seq, TrainingArguments, Trainer, GenerationConfig
3. 读取数据集
# 将JSON文件转换为CSV文件
df = pd.read_json('huanhuan.json')
ds = Dataset.from_pandas(df)
ds[:3]
输出:
{'instruction': ['小姐,别的秀女都在求中选,唯有咱们小姐想被撂牌子,菩萨一定记得真真儿的——','这个温太医啊,也是古怪,谁不知太医不得皇命不能为皇族以外的人请脉诊病,他倒好,十天半月便往咱们府里跑。','嬛妹妹,刚刚我去府上请脉,听甄伯母说你来这里进香了。'],'input': ['', '', ''],'output': ['嘘——都说许愿说破是不灵的。', '你们俩话太多了,我该和温太医要一剂药,好好治治你们。', '出来走走,也是散心。']}
4. 处理数据集
1)定义分词器
tokenizer = AutoTokenizer.from_pretrained('/root/autodl-tmp/LLM-Research/Meta-Llama-3___1-8B-Instruct', use_fast=False, trust_remote_code=True)
tokenizer.pad_token = tokenizer.eos_token
2)消息格式查看
messages = [{"role": "system", "content": "现在你要扮演皇帝身边的女人--甄嬛"},{"role": "user", "content": '你好呀'},{"role": "assistant", "content": "你好,我是甄嬛,你有什么事情要问我吗?"},]
print(tokenizer.apply_chat_template(messages, tokenize=False))
输出:
<|begin_of_text|><|start_header_id|>system<|end_header_id|>现在你要扮演皇帝身边的女人--甄嬛<|eot_id|><|start_header_id|>user<|end_header_id|>你好呀<|eot_id|><|start_header_id|>assistant<|end_header_id|>你好,我是甄嬛,你有什么事情要问我吗?<|eot_id|><|start_header_id|>assistant<|end_header_id|>
3)数据处理函数
def process_func(example):
MAX_LENGTH = 384 # Llama分词器会将一个中文字切分为多个token,因此需要放宽一些最大长度,确保数据的完整性
input_ids, attention_mask, labels = [], [], []
instruction = tokenizer(f"<|begin_of_text|><|start_header_id|>system<|end_header_id|>\n\n现在你要扮演皇帝身边的女人--甄嬛<|eot_id|><|start_header_id|>user<|end_header_id|>\n\n{example['instruction'] + example['input']}<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n\n", add_special_tokens=False) # add_special_tokens 不在开头加 special_tokens
response = tokenizer(f"{example['output']}<|eot_id|>", add_special_tokens=False)
input_ids = instruction["input_ids"] + response["input_ids"] + [tokenizer.pad_token_id]
attention_mask = instruction["attention_mask"] + response["attention_mask"] + [1] # eos token也需要关注,因此补充为1
labels = [-100] * len(instruction["input_ids"]) + response["input_ids"] + [tokenizer.pad_token_id]
if len(input_ids) > MAX_LENGTH: # 做一个截断
input_ids = input_ids[:MAX_LENGTH]
attention_mask = attention_mask[:MAX_LENGTH]
labels = labels[:MAX_LENGTH]
return {
"input_ids": input_ids,
"attention_mask": attention_mask,
"labels": labels
}
4)数据处理
tokenized_id = ds.map(process_func, remove_columns=ds.column_names)
tokenized_id
输出:
5)解码查看input_ids
tokenizer.decode(tokenized_id[0]['input_ids'])
输出:
'<|begin_of_text|><|start_header_id|>system<|end_header_id|>\n\n现在你要扮演皇帝身边的女人--甄嬛<|eot_id|><|start_header_id|>user<|end_header_id|>\n\n小姐,别的秀女都在求中选,唯有咱们小姐想被撂牌子,菩萨一定记得真真儿的——<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n\n嘘——都说许愿说破是不灵的。<|eot_id|><|eot_id|>'
6)解码查看labels
tokenizer.decode(list(filter(lambda x: x != -100, tokenized_id[1]["labels"])))
输出:
'你们俩话太多了,我该和温太医要一剂药,好好治治你们。<|eot_id|><|eot_id|>'
5. 定义模型
import torch
model = AutoModelForCausalLM.from_pretrained('/root/autodl-tmp/LLM-Research/Meta-Llama-3___1-8B-Instruct', device_map="auto",torch_dtype=torch.bfloat16)
输出如下:
model.enable_input_require_grads() # 启用梯度检查点时,需执行该方法
查看模型加载的精度:
model.dtype
输出:
torch.bfloat16
6. Lora配置
LoraConfig类中可以设置多个参数,主要参数如下:
- task_type:模型类型
- target_modules:需要训练的模型层名称,主要是attention部分的层
- r:Lora的秩
- lora_alpha:Lora alpha,具体作用参见Lora原理
Lora的缩放是lora_alpha/r,这里的缩放为4倍。
from peft import LoraConfig, TaskType, get_peft_model
config = LoraConfig(
task_type=TaskType.CAUSAL_LM,
target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"],
inference_mode=False, # 训练模式
r=8, # Lora 秩
lora_alpha=32, # Lora alpha
lora_dropout=0.1 # Dropout比例
)
输出:
LoraConfig(peft_type=<PeftType.LORA: 'LORA'>, auto_mapping=None, base_model_name_or_path=None, revision=None, task_type=<TaskType.CAUSAL_LM: 'CAUSAL_LM'>, inference_mode=False, r=8, target_modules={'k_proj', 'v_proj', 'up_proj', 'o_proj', 'down_proj', 'gate_proj', 'q_proj'}, lora_alpha=32, lora_dropout=0.1, fan_in_fan_out=False, bias='none', use_rslora=False, modules_to_save=None, init_lora_weights=True, layers_to_transform=None, layers_pattern=None, rank_pattern={}, alpha_pattern={}, megatron_config=None, megatron_core='megatron.core', loftq_config={}, use_dora=False, layer_replication=None)
加载微调配置:
model = get_peft_model(model, config)
输出:
LoraConfig(peft_type=<PeftType.LORA: 'LORA'>, auto_mapping=None, base_model_name_or_path='/root/autodl-tmp/LLM-Research/Meta-Llama-3___1-8B-Instruct', revision=None, task_type=<TaskType.CAUSAL_LM: 'CAUSAL_LM'>, inference_mode=False, r=8, target_modules={'k_proj', 'v_proj', 'up_proj', 'o_proj', 'down_proj', 'gate_proj', 'q_proj'}, lora_alpha=32, lora_dropout=0.1, fan_in_fan_out=False, bias='none', use_rslora=False, modules_to_save=None, init_lora_weights=True, layers_to_transform=None, layers_pattern=None, rank_pattern={}, alpha_pattern={}, megatron_config=None, megatron_core='megatron.core', loftq_config={}, use_dora=False, layer_replication=None)
查看可训练的参数:
model.print_trainable_parameters()
输出:
trainable params: 20,971,520 || all params: 8,051,232,768 || trainable%: 0.2605
7. 配置训练参数
TrainingArguments类的源码介绍了每个参数的作用,常用参数如下:
- output_dir:模型输出路径
- per_device_train_batch_size:batch_size
- gradient_accumulation_steps: 梯度累加
- logging_steps:多少步输出一次log
- num_train_epochs:训练的周期数
- gradient_checkpointing:启用梯度检查
args = TrainingArguments(
output_dir="./output/llama3_1_instruct_lora",
per_device_train_batch_size=4,
gradient_accumulation_steps=4,
logging_steps=10,
num_train_epochs=3,
save_steps=100, # 为了快速演示,这里设置为10,建议设置为100
learning_rate=1e-4,
save_on_each_node=True,
gradient_checkpointing=True
)
8. 开始Trainer训练
trainer = Trainer(
model=model,
args=args,
train_dataset=tokenized_id,
data_collator=DataCollatorForSeq2Seq(tokenizer=tokenizer, padding=True),
)
trainer.train()
训练完成如下:
9. 合并模型
将训练后的权重文件合并到基础模型中,产生新的模型文件。
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
from peft import PeftModel
mode_path = '/root/autodl-tmp/LLM-Research/Meta-Llama-3___1-8B-Instruct'
lora_path = '/root/autodl-tmp/output/llama3_1_instruct_lora/checkpoint-100' # 这里改为你的lora输出对应checkpoint地址
# 加载tokenizer
tokenizer = AutoTokenizer.from_pretrained(mode_path, trust_remote_code=True)
# 加载模型
model = AutoModelForCausalLM.from_pretrained(mode_path, device_map="auto", torch_dtype=torch.bfloat16, trust_remote_code=True).eval()
# 加载lora权重
model = PeftModel.from_pretrained(model, model_id=lora_path)
合并完成如下:
10. 模型推理
prompt = "你是谁?"
messages = [
{"role": "system", "content": "假设你是皇帝身边的女人--甄嬛。"},
{"role": "user", "content": prompt}
]
input_ids = tokenizer.apply_chat_template(messages, tokenize=False)
model_inputs = tokenizer([input_ids], return_tensors="pt").to('cuda')
generated_ids = model.generate(model_inputs.input_ids, max_new_tokens=512)
generated_ids = [output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)]
response = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
print(response)
推理结果输出:
我是甄嬛,家父是大理寺少卿甄远道。
Llama 3.1模型API部署与调用
为了将Llama 3.1模型的能力分享给其他用户,我们采用FastAPI框架发布API服务。FastAPI是一个现代、快速(高性能)的Web框架,便于构建API与Python类型提示相结合。
首先,我们创建一个名为fastapi-test.py的文件,包含启动和运行API服务所需的代码。
1. 代码准备
from fastapi import FastAPI, Request
from transformers import AutoTokenizer, AutoModelForCausalLM
import uvicorn
import json
import datetime
import torch
# 设置设备参数
DEVICE = "cuda" # 使用CUDA
DEVICE_ID = "0" # CUDA设备ID
CUDA_DEVICE = f"{DEVICE}:{DEVICE_ID}" if DEVICE_ID else DEVICE # 组合CUDA设备信息
# 清理GPU内存函数
def torch_gc():
if torch.cuda.is_available(): # 检查是否可用CUDA
with torch.cuda.device(CUDA_DEVICE): # 指定CUDA设备
torch.cuda.empty_cache() # 清空CUDA缓存
torch.cuda.ipc_collect() # 收集CUDA内存碎片
# 创建FastAPI应用
app = FastAPI()
# 处理POST请求的端点
@app.post("/")
async def create_item(request: Request):
global model, tokenizer # 声明全局变量以便在函数内部使用模型和分词器
json_post_raw = await request.json() # 获取POST请求的JSON数据
json_post = json.dumps(json_post_raw) # 将JSON数据转换为字符串
json_post_list = json.loads(json_post) # 将字符串转换为Python对象
prompt = json_post_list.get('prompt') # 获取请求中的提示
messages = [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": prompt}
]
# 调用模型进行对话生成
input_ids = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
model_inputs = tokenizer([input_ids], return_tensors="pt").to('cuda')
generated_ids = model.generate(model_inputs.input_ids, max_new_tokens=512)
generated_ids = [
output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)
]
response = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
now = datetime.datetime.now() # 获取当前时间
time = now.strftime("%Y-%m-%d %H:%M:%S") # 格式化时间为字符串
# 构建响应JSON
answer = {
"response": response,
"status": 200,
"time": time
}
# 构建日志信息
log = "[" + time + "] " + '", prompt:"' + prompt + '", response:"' + repr(response) + '"'
print(log) # 打印日志
torch_gc() # 执行GPU内存清理
return answer # 返回响应
# 主函数入口
if __name__ == '__main__':
# 加载预训练的分词器和模型
model_name_or_path = '/root/autodl-tmp/LLM-Research/Meta-Llama-3___1-8B-Instruct'
tokenizer = AutoTokenizer.from_pretrained(model_name_or_path, use_fast=False)
model = AutoModelForCausalLM.from_pretrained(model_name_or_path, device_map="auto", torch_dtype=torch.bfloat16)
# 启动FastAPI应用
uvicorn.run(app, host='0.0.0.0', port=6006, workers=1) # 在指定端口和主机上启动应用
2. 启动API服务
在终端输入以下命令启动API服务:
python fastapi-test.py
加载完毕后出现如下信息说明成功。
3. curl命令调用API
默认部署在6006端口,通过POST方法进行调用,可以使用curl命令:
curl -X POST "http://127.0.0.1:6006" -H 'Content-Type: application/json' -d '{"prompt": "什么是AI大模型?"}'
输出:
{"response":"AI大模型(Large Language Model, LLM)是一种基于深度学习的计算机模型,它能够处理和理解自然语言的能力。它可以理解和生成人类语言的不同方面,如语法、语义、语调等。","status":200,"time":"2024-07-30 10:37:36"}
4. Python代码调用API
也可以使用Python中的requests库进行API调用,如下所示:
import requests
import json
def get_completion(prompt):
headers = {'Content-Type': 'application/json'}
data = {"prompt": prompt}
response = requests.post(url='http://127.0.0.1:6006', headers=headers, data=json.dumps(data))
return response.json()['response']
get_completion('什么是机器学习?')
返回值如下:
'机器学习是一种人工智能的分支,研究如何让计算机能够通过数据和经验学习和改进其性能。它涉及使用算法和统计模型来分析数据,自动化决策和预测任务。'
结语
在本文中,我们深入探讨了Llama 3.1模型的推理过程、微调技巧以及API部署调用,旨在帮助读者提升对AI大型模型的实践技能。Llama 3.1的开源精神不仅赋予了AI社区一个功能强大的工具,更激发了技术共享与创新的活力。随着越来越多的开发者与企业深入挖掘Llama 3.1的潜力,未来将涌现出更多令人振奋的应用成果和技术创新。