面壁小钢炮 3.0 正式开源!部署微调指南来啦
OpenBMB
2024年10月09日 15:12
收录于文集
共8篇

|原文章发布于2024年09月09日

端侧 ChatGPT 时刻到来!旗舰端侧模型面壁「小钢炮」系列进化为全新 MiniCPM 3.0 基座模型,再次以小博大,以 4B 参数,带来超越 GPT-3.5 的性能,强得不像端侧模型。并且,量化后仅 2GB 内存,端侧友好。

这还是一款瑞士军刀般全面开挂的基座模型,一口气带来:

·无限长文本,榜单性能超越 Kimi,超长文本也不崩;

·Function Calling,性能比肩 GPT-4o 的端侧最强 Function Calling;

·超强 RAG 外挂三件套,中文检索第一、生成超 Llama3-8B。

 ➤  MiniCPM 3.0 开源地址: 

🔗 https://github.com/OpenBMB/MiniCPM

🔗 https://huggingface.co/openbmb/MiniCPM3-4B

我们也为大家准备了部署微调教程,方便大家快速上手 MiniCPM 3.0

 ➤  MiniCPM 3.0 完整教程地址: 

🔗 https://modelbest.feishu.cn/wiki/LrdMwKKt3iZgoYkQlPRcvY1PnXc

MiniCPM-V 3.0 部署指南 

方法一:Transformers 部署

Chat 方法

代码块
JavaScript
自动换行
复制代码
from transformers import AutoTokenizer,AutoModelForCausalLM
tokenizer = AutoTokenizer.from_pretrained("/root/ld/ld_model_pretrained/minicpm3")
model=AutoModelForCausalLM.from_pretrained("/root/ld/ld_model_pretrained/minicpm3",trust_remote_code=True).cuda()
history=[]
query=input("user:")
response,history=model.chat(tokenizer, query=query,history=history)
print("model:",response)
query=input("user:")
response,history=model.chat(tokenizer, query=query,history=history)
print("model:",response)
#history是列表形式[{"role": "assistant", "content": answer1},{"role": "assistant", "content": response}。。。。]
复制成功

Generate 方法

1. 继续生成类(非问答)方法

代码块
JavaScript
自动换行
复制代码
from transformers import AutoTokenizer, AutoModelForCausalLM

model = AutoModelForCausalLM.from_pretrained("/root/ld/ld_model_pretrained/minicpm3",trust_remote_code=True).cuda()
tokenizer = AutoTokenizer.from_pretrained("/root/ld/ld_model_pretrained/minicpm3",trust_remote_code=True)

prompt = "Hey, are you conscious? Can you tell me "
inputs = tokenizer(prompt, return_tensors="pt")

# Generate
generate_ids = model.generate(inputs.input_ids.cuda(), max_length=300,do_sample=False)
output=tokenizer.batch_decode(generate_ids, skip_special_tokens=True, clean_up_tokenization_spaces=False)[0]print(output)
复制成功

2. 对话类(问答)方法

Function call 简易实现

注意:

1. tools列表中的每一个函数,需要定义一个同名的函数,确保能够调用

2. tools中的函数声明,应该严格按照示例中格式进行

3. 对于工具的实现函数,比如第五行get_delivery_date这个函数,要保证的是与tools中同名,同参数名,能够跑通,具体实现不重要。模型只管这个工具的输入和返回值,返回值应该设计成容易被大模型理解。

代码块
JavaScript
自动换行
复制代码
#!/usr/bin/env python
# encoding: utf-8import refrom transformers import AutoTokenizer,AutoModelForCausalLM
def get_delivery_date(order_id=None):# 应该定义下方Tools中每一个函数的实现方法if order_id == None:
return "没有订单号无法查询"
else:
       print("get_delivery_date:这里应该替换查询方法函数,结果用return返回")return "2024-09-02"
def get_response_call(tool_call_str):

# 正则表达式
   pattern = r'(?<=```python\n)(.*?)(?=\n```\n)'
# 使用正则表达式匹配
   match = re.search(pattern, tool_call_str)
if match:
       function_call = match.group(1)
return function_call
else:
return None

tools = [
   {
"type": "function",
"function": {
  "name": "get_delivery_date",# 函数名,需要定义一个一样的python函数
# description 是这个工具的描述,最好是英文,通俗易懂最好
"description": "Get the delivery date for a customer's order. Call this whenever you need to know the delivery date, for example when a customer asks 'Where is my package'",
"parameters": {
"type": "object",
"properties": {
"order_id": {#参数名
"type": "string",#参数类型
"description": "The customer's order ID.",# 参数实现
                   },
               },
"required": ["order_id"],# 哪些是必须要的
"additionalProperties": False,
           },
       },
   }
]
messages = [
   {
"role": "system",
"content": "You are a helpful customer support assistant. Use the supplied tools to assist the user.",
   }
]
query="Hi, can you tell me the delivery date for my order,my order id is 123456。"
tokenizer = AutoTokenizer.from_pretrained(
"/root/ld/ld_model_pretrained/minicpm3", trust_remote_code=True
)
prompt = tokenizer.apply_chat_template(
   messages, tools=tools, tokenize=False, add_generation_prompt=True
)
model=AutoModelForCausalLM.from_pretrained("/root/ld/ld_model_pretrained/minicpm3", trust_remote_code=True).cuda()
response,history=model.chat(tokenizer, query=query,history=messages,do_sample=False)#由于functioncall的精确性,这里建议将do_sample设置为False
call_str=get_response_call(response)
print(eval(call_str))
# output:"""2024-09-02"""
复制成功

方法二:vLLM 部署

vLLM 的安装

暂时在 OpenBMB 仓库,向官方提交了pr

代码块
JavaScript
自动换行
复制代码
git clone https://github.com/OpenBMB/vllm.git
cd vllm
git checkout minicpm3
pip install e .
复制成功

vLLM python 运行

代码块
JavaScript
自动换行
复制代码
from vllm import LLM, SamplingParams
import argparse

parser = argparse.ArgumentParser()

parser.add_argument("--model_path", type=str, default="/root/ld/ld_model_pretrained/minicpm3")
parser.add_argument("--prompt_path", type=str, default="")
parser.add_argument("--batch", type=int, default=2) #可以修改这个batch达到并发

args = parser.parse_args()

prompts=["你好啊","吃饭了没有","你好,今天天气怎么样?","孙悟空是谁?"]
prompt_template = "<|im_start|> user\n{} <|im_end|>"

prompts = [prompt_template.format(prompt.strip()) for prompt in prompts]

params_dict = {
   "n": 1,
   "best_of": 1,
   "presence_penalty": 1.0,
   "frequency_penalty": 0.0,
   "temperature": 0.5,
   "top_p": 0.8,
   "top_k": -1,
   "use_beam_search": False,
   "length_penalty": 1,
   "early_stopping": False,
   "stop": None,
   "stop_token_ids": None,
   "ignore_eos": False,
   "max_tokens": 1000,
   "logprobs": None, 
   "prompt_logprobs": None,
   "skip_special_tokens": True,
}
# Create a sampling params object.
sampling_params = SamplingParams(**params_dict)

# Create an LLM.
llm = LLM(model=args.model_path, tensor_parallel_size=1, dtype='bfloat16',trust_remote_code=True,max_model_len=2048,gpu_memory_utilization=0.5)
# Generate texts from the prompts. The output is a list of RequestOutput objects
# that contain the prompt, generated text, and other information.
batch_input=[]
for prompt in prompts:
   batch_input.append(prompt)
   if len(batch_input)==args.batch:
       outputs = llm.generate(batch_input, sampling_params)
       # Print the outputs.
       for output in outputs:
           prompt = output.prompt
           print("用户:{}".format(prompt))
           generated_text = output.outputs[0].text
           # find the first <用户> and remove the text before it.
           print("AI助手:{}".format(generated_text))
       batch_input=[]
复制成功

vLLM openapi

1.启动vllm的server

代码块
JavaScript
自动换行
复制代码
vllm serve /root/ld/ld_model_pretrained/minicpm3 --dtype auto --api-key token-abc123 --trust-remote-code --max_model_len 2048 --gpu_memory_utilization 0.
复制成功

2.使用python调用api接口

代码块
JavaScript
自动换行
复制代码
from openai import OpenAI
client = OpenAI(
   base_url="http://localhost:8000/v1",
   api_key="token-abc123",
)

completion = client.chat.completions.create(
 model="/root/ld/ld_model_pretrained/minicpm3",
 messages=[
   {"role": "user", "content": "hello,nice to meet you."}
 ]
)

print(completion.choices[0].message)
复制成功

方法三:llama.cpp 部署

设备:linux,mac

1. 下载llama.cpp的minicpm3分支

代码块
JavaScript
自动换行
复制代码
git clone https://github.com/OpenBMB/llama.cpp.gitgit checkout minicpm3
复制成功

2. 编译llama.cpp

代码块
JavaScript
自动换行
复制代码
cd lama.cpp/models
mkdir Minicpm3
复制成功

3. 获取MiniCPM的gguf模型

(1) 创建llama.cpp/models/Minicpm路径

代码块
JavaScript
自动换行
复制代码
cd lama.cpp/modelsmkdir Minicpm3
复制成功

(2) 下载[MiniCPM3模型]所有文件(也可以是训练后的模型)并保存到llama.cpp/models/Minicpm

(3) 将模型转换为gguf格式

代码块
JavaScript
自动换行
复制代码
python3 -m pip install -r requirements.txt
#将pytorch模型转化为fp16的gguf
python3 convert-hf-to-gguf.py models/Minicpm3/ --outfile /your/path/llama.cpp/models/Minicpm3/CPM-4B-F16.gguf
#完成以上步骤,llama.cpp/models/Minicpm3目录下有一个CPM-4B-F16.gguf的模型文件
复制成功

4. 将fp16的gguf文件进行量化

代码块
JavaScript
自动换行
复制代码
#使用本行代码执行成功后,/models/Minicpm/下将存在 ggml-model-Q4_K_M.gguf的4bit量化文件
./llama-quantize ./models/Minicpm3/CPM-4B-F16.gguf ./models/Minicpm3/ggml-model-Q4_K_M.gguf Q4_K_M
复制成功

5. 将fp16的gguf文件进行量化。

代码块
JavaScript
自动换行
复制代码
#使用本行代码执行成功后,/models/Minicpm/下将存在 ggml-model-Q4_K_M.gguf的4bit量化文件
./llama-quantize ./models/Minicpm3/CPM-4B-F16.gguf ./models/Minicpm3/ggml-model-Q4_K_M.gguf Q4_K_M
复制成功

代码块
JavaScript
自动换行
复制代码
#如果找不到llama-quantize,可以尝试以下方法
cd llama.cpp
make llama-quantize
复制成功

6. 开始推理

(1) 命令行推理

代码块
JavaScript
自动换行
复制代码
./llama-cli -c 1024 -m ./models/Minicpm/ggml-model-Q4_K_M.gguf -n 1024 --top-p 0.7 --temp 0.7 --prompt "<|im_start|>user\n请写一篇关于人工智能的文章,详细介绍人工智能的未来发展和隐患。<|im_end|>\n<|im_start|>assistant\n"
复制成功

(2) server服务

发起http服务,参数解析请见,llamacpp参数:

代码块
JavaScript
自动换行
复制代码
./llama-server  -m ./models/Minicpm3/CPM-2B-F16.gguf -c 2048
复制成功

使用python 调用服务,返回结果

代码块
JavaScript
自动换行
复制代码
import requests

url = "http://localhost:8080/completion"
headers = {
   "Content-Type": "application/json"
}
data = {
   "prompt": "MiniCPM3 是哪家公司发布的?",
   "n_predict": 128
}
response = requests.post(url, json=data, headers=headers)

if response.status_code == 200:
   result = response.json()
   print(result["content"])
else:
   print(f"Request failed with status code {response.status_code}: {response.text}")
复制成功

方法四:Ollama

1. 获取我们fork的分支代码

PR暂未合并,请务必使用我们的分支

代码块
JavaScript
自动换行
复制代码
git clone https://github.com/LDLINGLINGLING/ollama.git
cd ollama
git checkout minicpm
tar -zxvf ollama.tar.gz -C .
cd ollama
复制成功

2. 编译ollama

代码块
JavaScript
自动换行
复制代码
cd ollama
go generate ./... #这个过程需要访问google等网址
go build .
复制成功

3. 按照Llamacpp的教程获取量化后的gguf文件

4. 在ollama主路径下开启服务

代码块
JavaScript
自动换行
复制代码
./ollama serve
复制成功

5. 创建一个model.file文件

代码块
JavaScript
自动换行
复制代码
vim minicpm3.Modelfile
复制成功

From 后面写入量化后的gguf文件地址

代码块
JavaScript
自动换行
复制代码
FROM ./MiniCPM-V-2_6/ggml-model-Q4_K_M.ggufTEMPLATE """{{ if .System }}<|im_start|>system{{ .System }}<|im_end|>{{ end }}{{ if .Prompt }}<|im_start|>user{{ .Prompt }}<|im_end|>{{ end }}<|im_start|>assistant<|im_end|>{{ .Response }}<|im_end|>"""PARAMETER stop "<|endoftext|>"PARAMETER stop "<|im_end|>"PARAMETER num_ctx 4096
复制成功

6. 创建ollama实例

代码块
JavaScript
自动换行
复制代码
ollama create minicpm -f minicpm3.Modelfile
复制成功

7. 运行模型

代码块
JavaScript
自动换行
复制代码
ollama run minicpm3
复制成功

MiniCPM-V 3.0 微调指南 

在此教程中,我们使用llama_factory方法做微调训练

1. 首先安装llama_factory依赖

2. 处理数据

将数据集处理成Minicpm/finetune/llama_factory_example/llama_factory_data文件夹中的格式,示例包括dpo,kto,sft三种微调方式并放置到llama_factory/data目录下。

(1) dpo数据格式

对于一个"human"的answer,需要给一个chosen(好的答案),一个rejected(坏的答案)

代码块
JavaScript
自动换行
复制代码
[
 {
   "conversations": [
     {
       "from": "human",
       "value": "你吃饭了没有,吃的什么啊?"
   ],
   "chosen": {
     "from": "gpt",
     "value": "刚吃了啊,你去哪里玩?"
   },
   "rejected": {
     "from": "gpt",
     "value": "吃得茄子,西红柿,红烧肉。"
   }
 }
 ] 
复制成功

(2)kto数据格式:

  1. 其中每个messages代表一条数据,label代表是该条数据的回复是否合理

  2. 不需要和dpo那样相同问题不同答案,对于每个问题和答案对给予一个评价

  3. 每个问题和回复都可以有一个True(好的),或者false(不好的评价)

代码块
JavaScript
自动换行
复制代码
{
   "messages": [
     {
       "content": "minicpm3.0哪里最强?",
       "role": "user"
     },
     {
       "content": "哪里都强,六边形战士好吗",
       "role": "assistant"
     }
   ],
   "label": true
 },
    {
   "messages": [
     {
       "content": "minicpm3.0 有哪些优点。",
       "role": "user"
     },
     {
       "content": "1.长文本能力。2.function call能力 3.其他所有能力",
       "role": "assistant"
     },
     {
       "content": "细说长文本能力",
       "role": "user"
     },
     {
       "content": "反正很强,细说就去我们github吧。https://github.com/OpenBMB/MiniCPM.git",
       
       "role": "assistant"
     }
   ],
   "label": false
 }
 ]
复制成功

(3)sft数据格式如下,每个字典组成一条数据

代码块
JavaScript
自动换行
复制代码
[
 {
   "instruction": "你现在比牛顿还牛的科学家",
   "input": "识别并解释给定列表中的两个科学理论:细胞理论和日心说。",
   "output": "细胞理论是生物科学的一个理论,它认为所有生命体都是由微小的基本单元——细胞所构成。这是生物学的一个基础理论,认为细胞是所有生物的基本结构和功能单位,所有的生物都是由一个或多个细胞组成,细胞只能通过细胞分裂产生新的细胞。这一理论由薛定谔、施瓦内和雪莱于1839年首次提出。\n\n日心说是指太阳是太阳系的中心,也就是说,行星围绕太阳旋转的理论。这个理论打破了传统的地心说观点,认为地球并不是宇宙的中心。日心说的提出者是尼古拉·哥白尼,他在16世纪初发表了他的著作《天体运行论》,阐述了太阳系行星围绕太阳运行的模型,为天文学的发展做出了巨大贡献。"
 }
]
复制成功

3. 将数据信息添加到dataset_info.json

在llama_factory/data/dataset_info.json中添加数据集信息,保证dataset_info.json中能找到你的数据集,如下例:

  1. file_name为制作数据集的文件名

  2. 比如"sft_zh_demo"代表数据名称,在面配置文件yaml中会用到

  3. 需要修改的就只有两个一个是数据的键名,比如"sft_zh_demo","kto_en_demo.json"

代码块
JavaScript
自动换行
复制代码
{"identity": {
   "file_name": "identity.json"
 },
   "sft_zh_demo": {
     "file_name": "alpaca_zh_demo.json"
   },
   "kto_en_demo": {
     "file_name": "kto_en_demo.json",
     "formatting": "sharegpt",
     "columns": {
       "messages": "messages",
       "kto_tag": "label"
     },
          "tags": {
       "role_tag": "role",
       "content_tag": "content",
       "user_tag": "user",
       "assistant_tag": "assistant"
     }
   },
   "dpo_en_demo": {
     "file_name": "dpo_en_demo.json",
     "ranking": true,
     "formatting": "sharegpt",
     "columns": {
       "messages": "conversations",
       "chosen": "chosen",
       "rejected": "rejected"
     }
   }
 }
复制成功

4. 设置训练脚本

(1)将MiniCPM/finetune/llama_factory_example中文件复制到LLaMA-Factory/examples/minicpm目录下

代码块
JavaScript
自动换行
复制代码
cd LLaMA-Factory/examples
mkdir minicpm
#以下代码中的/your/path要改成你的MiniCPM代码和LLaMA-Factory路径
cp -r /your/path/MiniCPM/finetune/llama_factory_example/*  /your/path/LLaMA-Factory/examples/minicpm
复制成功

(2) 根据你需要微调的方式,以dpo为例

代码块
JavaScript
自动换行
复制代码
LLaMA-Factory/examples/minicpm/minicpm_dpo.yaml必须修改的配置参数如下
model_name_or_path: openbmb/MiniCPM3-4B # 或者你本地保存的地址
template: cpm3 #注意如果微调minicpm3就写cpm3,否则写cpm
dataset: dpo_en_demo # 这里写dataset_info.json中的键名
output_dir: your/finetune_minicpm/save/path # 你微调后模型的保存地址
bf16: true # 如果你的设备支持bf16,否则false
deepspeed: examples/deepspeed/ds_z2_config.json # 如果显存不够可以改成ds_z3_config.json
复制成功

(3) 修改LLaMA-Factory/examples/minicpm/single_node.sh文件中以下配置

代码块
JavaScript
自动换行
复制代码
#以下这行修改为你机器有多少张gpu
NPROC_PER_NODE=8
NNODES=1
RANK=0
MASTER_ADDR=127.0.0.1
MASTER_PORT=29500

#以下两行如果是A100,H100等以上的高端显卡可以删除
export NCCL_P2P_DISABLE=1
export NCCL_IB_DISABLE=1
#以下数字设置为你机器中参与训练的显卡,这里是0-7号卡都参与训练
CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 torchrun \
   --nproc_per_node $NPROC_PER_NODE \
   --nnodes $NNODES \
   --node_rank $RANK \
   --master_addr $MASTER_ADDR \
   --master_port $MASTER_PORT \
   
#以下这行需要修改成配置文件地址,
如/root/ld/ld_project/LLaMAFactory/examples/minicpm/minicpm_dpo.yaml
src/train.py /your/path/LLaMA-Factory/examples/minicpm/minicpm_dpo.yaml
复制成功

5. 开始训练

代码块
JavaScript
自动换行
复制代码
cd LLaMA-Factory
bash /your/path/LLaMA-Factory/examples/minicpm/single_node.sh
复制成功

更多模型部署FAQ、微调指南,请查阅知识库:

🔗 https://modelbest.feishu.cn/wiki/LrdMwKKt3iZgoYkQlPRcvY1PnXc

 ➤ 欢迎加入 OpenBMB 社区一起讨论 

官方网站

https://www.openbmb.org

GitHub

https://github.com/OpenBMB

https://github.com/thunlp

长期开放招聘|含实习

开发岗  |  算法岗  |  产品岗

交流QQ群

735930538