OpenELM

对标题的评论会显示在这里

Qwen2-7B-Instruct FastApi 部署调用

对这一段的评论会显示在这里

环境准备

对这一段的评论会显示在这里

本文基础环境如下:

对这一段的评论会显示在这里
----------------
ubuntu 22.04
python 3.10
cuda 12.1
pytorch 2.1.0
----------------
对这一段的评论会显示在这里

本文默认学习者已安装好以上 Pytorch(cuda) 环境,如未安装请自行安装。

对这一段的评论会显示在这里

首先pip换源加速下载并安装依赖包

对这一段的评论会显示在这里
python -m pip install --upgrade pip
# 更换 pypi 源加速库的安装
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple

pip install modelscope==1.16.1
pip install transformers==4.42.4
pip install fastapi==0.111.1
pip install uvicorn==0.30.3
pip install SentencePiece==0.2.0
pip install accelerate==0.33.0
对这一段的评论会显示在这里

考虑到部分同学配置环境可能会遇到一些问题,我们在AutoDL平台准备了OpenELM的环境镜像,点击下方链接并直接创建Autodl实例即可。 https://www.codewithgpu.com/i/datawhalechina/self-llm/OpenELM-3B-Instruct

对这一段的评论会显示在这里

模型下载

对这一段的评论会显示在这里

使用 modelscope 命令行下载模型,参数model为模型名称,参数 local_dir 为模型的下载路径。 注:由于OpenELM使用的是Llama2的Tokenizer,所以我们在下载Llama2-7b时可将权重排除在外 打开终端输入以下命令下载模型和Tokenizer

对这一段的评论会显示在这里
modelscope download --model shakechen/Llama-2-7b-hf  --local_dir /root/autodl-tmp/Llama-2-7b-hf --exclude ".bin" "*.safetensors" "configuration.json" 
modelscope download --model LLM-Research/OpenELM-3B-Instruct --local_dir /root/autodl-tmp/OpenELM-3B-Instruct
对这一段的评论会显示在这里

代码准备

对这一段的评论会显示在这里

在 /root/autodl-tmp 路径下新建 api.py 文件并在其中输入以下内容,粘贴代码后请及时保存文件。 下面的代码有很详细的注释,大家如有不理解的地方,欢迎提出 issue。

对这一段的评论会显示在这里
from fastapi import FastAPI, Request
from transformers import AutoTokenizer, AutoModelForCausalLM, GenerationConfig
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')  # 获取请求中的提示

    # 调用模型进行对话生成
    model_inputs = tokenizer(prompt, add_special_tokens=True, return_tensors="pt")['input_ids'].cuda()
    generated_ids = model.generate(model_inputs, max_length=384)
    response = tokenizer.decode(generated_ids[0], skip_special_tokens=True)
    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_path = '/root/autodl-tmp/OpenELM-3B-Instruct'
    tokenizer_path = '/root/autodl-tmp/Llama-2-7b-hf'
    tokenizer = AutoTokenizer.from_pretrained(tokenizer_path, use_fast=False, trust_remote_code=True)
    model = AutoModelForCausalLM.from_pretrained(model_path, device_map="auto", torch_dtype=torch.bfloat16, trust_remote_code=True)

    # 启动FastAPI应用
    # 用6006端口可以将autodl的端口映射到本地,从而在本地使用api
    uvicorn.run(app, host='0.0.0.0', port=6006, workers=1)  # 在指定端口和主机上启动应用
对这一段的评论会显示在这里

Api 部署

对这一段的评论会显示在这里

在终端输入以下命令启动api服务:

对这一段的评论会显示在这里
cd /root/autodl-tmp
python api.py
对这一段的评论会显示在这里
python /root/api.py
对这一段的评论会显示在这里

加载完毕后出现如下信息说明成功。

对这一段的评论会显示在这里
加载模型
加载模型
对这一段的评论会显示在这里

默认部署在 6006 端口,通过 POST 方法进行调用,可以使用 curl 调用,如下所示:

对这一段的评论会显示在这里
curl -X POST "http://127.0.0.1:6006" \
     -H 'Content-Type: application/json' \
     -d '{"prompt": "Once upon a time there was"}'
对这一段的评论会显示在这里
模型调用
模型调用
对这一段的评论会显示在这里

也可以使用 python 中的 requests 库进行调用,如下所示:

对这一段的评论会显示在这里
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))
    print(json.dumps(response.json()))
    return response.json()['response']

if __name__ == '__main__':
    response = get_completion('Once upon a time there was')
对这一段的评论会显示在这里

得到的返回值如下所示:

对这一段的评论会显示在这里
{"response": "Once upon a time there was a little girl named Rosie. Rosie loved to play dress-up, and her favorite costume was a princess dress. Rosie's mommy and daddy dressed her up in her princess dress every chance they got. Rosie loved her princess dress so much, she even wore it to bed! Rosie's mommy and daddy loved Rosie very much, and they wanted Rosie to have the best life possible.\n\nOne day Rosie's mommy and daddy took Rosie to visit their friends, the Johnsons. Rosie's mommy and daddy told Rosie all about their friends, and Rosie couldn't wait to meet them. Rosie's mommy and daddy dropped Rosie off at the Johnsons' house, and Rosie ran inside to find her princess dress waiting for her. Rosie put on her princess dress and tiptoed upstairs to meet her friends.\n\nWhen Rosie walked into the Johnsons' living room, she saw her friends sitting on the couch, dressed in jeans and t-shirts. Rosie smiled and ran over to give her friends a hug. Rosie's friends were so happy to see her dressed up like a princess! They asked Rosie lots of questions about her princess dress, and Rosie told them all about her mommy and daddy's special tradition. Rosie's friends loved hearing all about her princess dress, and they promised to wear their princess dresses whenever Rosie visited them.\n\nAfter playing dress-up for a while, Rosie's friends asked Rosie if she wanted to play outside. Rosie loved playing with her friends, but she also loved playing princess dress-up.", "status": 200, "time": "2024-08-24 21:28:34"}
对这一段的评论会显示在这里

OpenELM-3B-Instruct Lora 微调

对这一段的评论会显示在这里

本节我们简要介绍如何基于 transformers、peft 等框架,对 OpenELM-3B-Instruc 模型进行 Lora 微调。Lora 是一种高效微调方法,深入了解其原理可参见博客:知乎|深入浅出Lora

对这一段的评论会显示在这里

这个教程会在同目录下给大家提供一个 notebook 文件,来让大家更好的学习。

对这一段的评论会显示在这里

环境准备

对这一段的评论会显示在这里

本文基础环境如下:

对这一段的评论会显示在这里
----------------
ubuntu 22.04
python 3.10
cuda 12.1
pytorch 2.1.0
----------------
对这一段的评论会显示在这里

本文默认学习者已安装好以上 Pytorch(cuda) 环境,如未安装请自行安装。

对这一段的评论会显示在这里

首先pip换源加速下载并安装依赖包

对这一段的评论会显示在这里
python -m pip install --upgrade pip
# 更换 pypi 源加速库的安装
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple

pip install modelscope==1.16.1
pip install transformers==4.42.4
pip install datasets==2.20.0
pip install peft==0.11.1
pip install fastapi==0.111.1
pip install uvicorn==0.30.3
pip install SentencePiece==0.2.0
pip install accelerate==0.33.0
对这一段的评论会显示在这里

考虑到部分同学配置环境可能会遇到一些问题,我们在AutoDL平台准备了OpenELM的环境镜像,点击下方链接并直接创建Autodl实例即可。 https://www.codewithgpu.com/i/datawhalechina/self-llm/OpenELM-3B-Instruct

对这一段的评论会显示在这里

模型下载

对这一段的评论会显示在这里

使用 modelscope 命令行下载模型,参数model为模型名称,参数 local_dir 为模型的下载路径。 注:由于OpenELM使用的是Llama2的Tokenizer,所以我们在下载Llama2-7b时可将权重排除在外 打开终端输入以下命令下载模型和Tokenizer

对这一段的评论会显示在这里
modelscope download --model shakechen/Llama-2-7b-hf  --local_dir /root/autodl-tmp/Llama-2-7b-hf --exclude ".bin" "*.safetensors" "configuration.json" 
modelscope download --model LLM-Research/OpenELM-3B-Instruct --local_dir /root/autodl-tmp/OpenELM-3B-Instruct
对这一段的评论会显示在这里

指令集构建

对这一段的评论会显示在这里

LLM 的微调一般指指令微调过程。所谓指令微调,是说我们使用的微调数据形如:

对这一段的评论会显示在这里
{
    "instruction":"回答以下用户问题,仅输出答案。",
    "input":"1+1等于几?",
    "output":"2"
}
对这一段的评论会显示在这里

其中,instruction 是用户指令,告知模型其需要完成的任务;input 是用户输入,是完成用户指令所必须的输入内容;output 是模型应该给出的输出。

对这一段的评论会显示在这里

数据集下载

对这一段的评论会显示在这里

我们使用alpaca-chinese-dataset作为我们的指令微调数据集 在终端打开/root/autodl-tmp目录输入以下命令下载数据集

对这一段的评论会显示在这里
cd /root/autodl-tmp
git clone https://mirror.ghproxy.com/https://github.com/open-chinese/alpaca-chinese-dataset
对这一段的评论会显示在这里

数据格式化

对这一段的评论会显示在这里

Lora 训练的数据是需要经过格式化、编码之后再输入给模型进行训练的,如果是熟悉 PyTorch 模型训练流程的同学会知道,我们一般需要将输入文本编码为 input_ids,将输出文本编码为 labels,编码之后的结果都是多维的向量。我们首先定义一个预处理函数,这个函数用于对每一个样本,编码其输入、输出文本并返回一个编码后的字典:

对这一段的评论会显示在这里
def process_func(example):
    MAX_LENGTH = 384
    
    instruction = tokenizer(f"{example['en_instruction'] + example['en_input']}<sep>", add_special_tokens=True)
    response = tokenizer(f"{example['en_output']}", 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]
    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
    }
对这一段的评论会显示在这里

加载tokenizer和半精度模型

对这一段的评论会显示在这里

模型以半精度形式加载,如果你的显卡比较新的话,可以用torch.bfolat形式加载。对于自定义的模型一定要指定trust_remote_code参数为True

对这一段的评论会显示在这里
from peft import LoraConfig, TaskType, get_peft_model
import torch
from transformers import AutoTokenizer, AutoModelForCausalLM, DataCollatorForSeq2Seq, TrainingArguments, Trainer, GenerationConfig

tokenizer = AutoTokenizer.from_pretrained(
    '/root/autodl-tmp/Llama-2-7b-hf',
    trust_remote_code=True
)
tokenizer.pad_token = tokenizer.eos_token
model = AutoModelForCausalLM.from_pretrained(
    '/root/autodl-tmp/OpenELM-3B-Instruct',
    device_map="auto",
    torch_dtype=torch.bfloat16,
    trust_remote_code=True
)
对这一段的评论会显示在这里

定义LoraConfig

对这一段的评论会显示在这里

LoraConfig这个类中可以设置很多参数,但主要的参数没多少,简单讲一讲,感兴趣的同学可以直接看源码。

对这一段的评论会显示在这里

task_type:模型类型
target_modules:需要训练的模型层的名字,主要就是attention部分的层,不同的模型对应的层的名字不同,可以传入数组,也可以字符串,也可以正则表达式。
rlora的秩,具体可以看Lora原理
lora_alphaLora alaph,具体作用参见 Lora 原理

对这一段的评论会显示在这里

Lora的缩放是啥嘞?当然不是r(秩),这个缩放就是lora_alpha/r, 在这个LoraConfig中缩放就是1倍。

对这一段的评论会显示在这里
config = LoraConfig(
    task_type=TaskType.CAUSAL_LM, 
    target_modules=['token_embeddings', "qkv_proj", "out_proj", "proj_1", "proj_2"],
    inference_mode=False,
    r=32, # Lora 秩
    lora_alpha=32, # Lora alaph,具体作用参见 Lora 原理
    lora_dropout=0.1 # Dropout 比例
)
对这一段的评论会显示在这里

自定义 TrainingArguments 参数

对这一段的评论会显示在这里

TrainingArguments这个类的源码也介绍了每个参数的具体作用,当然大家可以来自行探索,这里就简单说几个常用的。

对这一段的评论会显示在这里

output_dir:模型的输出路径
per_device_train_batch_size:顾名思义 batch_size
gradient_accumulation_steps: 梯度累加,如果你的显存比较小,那可以把 batch_size 设置小一点,梯度累加增大一些。
logging_steps:多少步,输出一次log
num_train_epochs:顾名思义 epoch
gradient_checkpointing:梯度检查,这个一旦开启,模型就必须执行model.enable_input_require_grads(),这个原理大家可以自行探索,这里就不细说了。

对这一段的评论会显示在这里
args = TrainingArguments(
    output_dir="autodl-tmp/output/openelm_3B_lora",
    per_device_train_batch_size=8,
    gradient_accumulation_steps=4,
    logging_steps=100,
    num_train_epochs=0.87,  # 为了快速掩饰,我们训练到约1200个iter作为测试,建议设为10个epochs
    save_steps=600,
    learning_rate=1e-4,
    save_on_each_node=True,
    gradient_checkpointing=True
)
对这一段的评论会显示在这里

使用 Trainer 训练

对这一段的评论会显示在这里
trainer = Trainer(
    model=peft_model,
    args=args,
    train_dataset=tokenized_id,
    data_collator=DataCollatorForSeq2Seq(tokenizer=tokenizer, padding=True),
)
trainer.train()
对这一段的评论会显示在这里

加载 lora 权重推理

对这一段的评论会显示在这里

训练好了之后可以使用如下方式加载lora权重进行推理:

对这一段的评论会显示在这里
from peft import LoraConfig, TaskType, get_peft_model, PeftModel
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch

mode_path = '/root/autodl-tmp/OpenELM-3B-Instruct'
lora_path = '/root/autodl-tmp/output/openelm_3B_lora/checkpoint-1200' # 这里改称你的 lora 输出对应 checkpoint 地址

# 加载tokenizer
tokenizer = AutoTokenizer.from_pretrained('/root/autodl-tmp/Llama-2-7b-hf', 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)

prompt = "How to be a good learner?<sep>"
instruction = tokenizer(prompt, add_special_tokens=True, return_tensors="pt")

generated_ids = model.generate(instruction['input_ids'].cuda(), max_length=384)
print(tokenizer.decode(generated_ids[0], skip_special_tokens=True).split('<sep>')[-1])

'''
To be a good learner, it is important to be motivated, organized, and have a positive attitude. 
Motivation is key to learning, as it helps to keep you focused and engaged. Organization is 
important to ensure that you have the materials you need and that you are able to stay on track. 
Finally, a positive attitude is essential to staying motivated and to help you stay focused on 
the task at hand.
'''
对这一段的评论会显示在这里

README

对这一段的评论会显示在这里

  1. 模型简介

对这一段的评论会显示在这里

OpenELM是由苹果公司开发的一款先进语言模型,通过一种新的层级缩放策略优化每个Transformer层的参数分配,从而提升模型的效率和准确性。OpenELM还提供了一个开放的训练和推理框架,包含数据集、训练日志和检查点等资源,支持研究的可重复性和透明性。

对这一段的评论会显示在这里

模型地址:https://huggingface.co/collections/apple/openelm-instruct-models-6619ad295d7ae9f868b759ca

对这一段的评论会显示在这里

项目地址:https://github.com/apple/corenet/tree/main/projects/openelm

对这一段的评论会显示在这里

官方报道:https://machinelearning.apple.com/research/openelm

对这一段的评论会显示在这里

论文链接:https://arxiv.org/abs/2404.14619

对这一段的评论会显示在这里

  1. 模型下载

对这一段的评论会显示在这里

OpenELM提供了Huggingface的模型格式,下载链接如下表所示:

对这一段的评论会显示在这里

| 模型 | 下载链接 |
| OpenELM-270M-Instruct | ModelScope | HuggingFace |
| OpenELM-450M-Instruct | ModelScope | HuggingFace |
| OpenELM-1_1B-Instruct | ModelScope | HuggingFace |
| OpenELM-3B-Instruct | ModelScope | HuggingFace |

对这一段的评论会显示在这里

  1. 教程简介

对这一段的评论会显示在这里

本教程以OpenELM-3B-Instruct为基础,介绍以下内容:

对这一段的评论会显示在这里

01-OpenELM-3B-Instruct FastApi 部署调用
02-OpenELM-3B-Instruct Lora 微调

对这一段的评论会显示在这里