first commit

This commit is contained in:
xxl 2024-11-12 09:40:44 +08:00
parent 0bf35dd5de
commit 48c76c3ff4
13 changed files with 180717 additions and 2 deletions

304
README.md
View File

@ -1,3 +1,303 @@
# MiniCPM3-4B_a13567660624834560972296 ---
license: apache-2.0
language:
- zh
- en
pipeline_tag: text-generation
---
<div align="center">
<img src="https://github.com/OpenBMB/MiniCPM/blob/main/assets/minicpm_logo.png?raw=true" width="500em" ></img>
</div>
MiniCPM3-4B <p align="center">
<a href="https://github.com/OpenBMB/MiniCPM/" target="_blank">MiniCPM Repo</a> |
<a href="https://arxiv.org/abs/2404.06395" target="_blank">MiniCPM Paper</a> |
<a href="https://github.com/OpenBMB/MiniCPM-V/" target="_blank">MiniCPM-V Repo</a> |
Join us in <a href="https://discord.gg/3cGQn9b3YM" target="_blank">Discord</a> and <a href="https://github.com/OpenBMB/MiniCPM/blob/main/assets/wechat.jpg" target="_blank">WeChat</a>
</p>
## Introduction
MiniCPM3-4B is the 3rd generation of MiniCPM series. The overall performance of MiniCPM3-4B surpasses Phi-3.5-mini-Instruct and GPT-3.5-Turbo-0125, being comparable with many recent 7B~9B models.
Compared to MiniCPM1.0/MiniCPM2.0, MiniCPM3-4B has a more powerful and versatile skill set to enable more general usage. MiniCPM3-4B supports function call, along with code interpreter. Please refer to [Advanced Features](https://github.com/OpenBMB/MiniCPM/tree/main?tab=readme-ov-file#%E8%BF%9B%E9%98%B6%E5%8A%9F%E8%83%BD) for usage guidelines.
MiniCPM3-4B has a 32k context window. Equipped with LLMxMapReduce, MiniCPM3-4B can handle infinite context theoretically, without requiring huge amount of memory.
## Usage
### Inference with Transformers
```python
from modelscope import AutoModelForCausalLM, AutoTokenizer
import torch
path = "OpenBMB/MiniCPM3-4B"
device = "cuda"
tokenizer = AutoTokenizer.from_pretrained(path, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(path, torch_dtype=torch.bfloat16, device_map=device, trust_remote_code=True)
messages = [
{"role": "user", "content": "推荐5个北京的景点。"},
]
model_inputs = tokenizer.apply_chat_template(messages, return_tensors="pt", add_generation_prompt=True).to(device)
model_outputs = model.generate(
model_inputs,
max_new_tokens=1024,
top_p=0.7,
temperature=0.7
)
output_token_ids = [
model_outputs[i][len(model_inputs[i]):] for i in range(len(model_inputs))
]
responses = tokenizer.batch_decode(output_token_ids, skip_special_tokens=True)[0]
print(responses)
```
### Inference with [vLLM](https://github.com/vllm-project/vllm)
For now, you need to install our forked version of vLLM.
```bash
pip install git+https://github.com/OpenBMB/vllm.git@minicpm3
```
```python
from transformers import AutoTokenizer
from vllm import LLM, SamplingParams
model_name = "openbmb/MiniCPM3-4B"
prompt = [{"role": "user", "content": "推荐5个北京的景点。"}]
tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
input_text = tokenizer.apply_chat_template(prompt, tokenize=False, add_generation_prompt=True)
llm = LLM(
model=model_name,
trust_remote_code=True,
tensor_parallel_size=1
)
sampling_params = SamplingParams(top_p=0.7, temperature=0.7, max_tokens=1024, repetition_penalty=1.02)
outputs = llm.generate(prompts=input_text, sampling_params=sampling_params)
print(outputs[0].outputs[0].text)
```
## Evaluation Results
<table>
<tr>
<td>Benchmark</td>
<td>Qwen2-7B-Instruct</td>
<td>GLM-4-9B-Chat</td>
<td>Gemma2-9B-it</td>
<td>Llama3.1-8B-Instruct</td>
<td>GPT-3.5-Turbo-0125</td>
<td>Phi-3.5-mini-Instruct(3.8B)</td>
<td>MiniCPM3-4B </td>
</tr>
<tr>
<td colspan="15" align="left"><strong>English</strong></td>
</tr>
<tr>
<td>MMLU</td>
<td>70.5</td>
<td>72.4</td>
<td>72.6</td>
<td>69.4</td>
<td>69.2</td>
<td>68.4</td>
<td>67.2 </td>
</tr>
<tr>
<td>BBH</td>
<td>64.9</td>
<td>76.3</td>
<td>65.2</td>
<td>67.8</td>
<td>70.3</td>
<td>68.6</td>
<td>70.2 </td>
</tr>
<tr>
<td>MT-Bench</td>
<td>8.41</td>
<td>8.35</td>
<td>7.88</td>
<td>8.28</td>
<td>8.17</td>
<td>8.60</td>
<td>8.41 </td>
</tr>
<tr>
<td>IFEVAL (Prompt Strict-Acc.)</td>
<td>51.0</td>
<td>64.5</td>
<td>71.9</td>
<td>71.5</td>
<td>58.8</td>
<td>49.4</td>
<td>68.4 </td>
</tr>
<tr>
<td colspan="15" align="left"><strong>Chinese</strong></td>
</tr>
<tr>
<td>CMMLU</td>
<td>80.9</td>
<td>71.5</td>
<td>59.5</td>
<td>55.8</td>
<td>54.5</td>
<td>46.9</td>
<td>73.3 </td>
</tr>
<tr>
<td>CEVAL</td>
<td>77.2</td>
<td>75.6</td>
<td>56.7</td>
<td>55.2</td>
<td>52.8</td>
<td>46.1</td>
<td>73.6 </td>
</tr>
<tr>
<td>AlignBench v1.1</td>
<td>7.10</td>
<td>6.61</td>
<td>7.10</td>
<td>5.68</td>
<td>5.82</td>
<td>5.73</td>
<td>6.74 </td>
</tr>
<tr>
<td>FollowBench-zh (SSR)</td>
<td>63.0</td>
<td>56.4</td>
<td>57.0</td>
<td>50.6</td>
<td>64.6</td>
<td>58.1</td>
<td>66.8 </td>
</tr>
<tr>
<td colspan="15" align="left"><strong>Math</strong></td>
</tr>
<tr>
<td>MATH</td>
<td>49.6</td>
<td>50.6</td>
<td>46.0</td>
<td>51.9</td>
<td>41.8</td>
<td>46.4</td>
<td>46.6 </td>
</tr>
<tr>
<td>GSM8K</td>
<td>82.3</td>
<td>79.6</td>
<td>79.7</td>
<td>84.5</td>
<td>76.4</td>
<td>82.7</td>
<td>81.1 </td>
</tr>
<tr>
<td>MathBench</td>
<td>63.4</td>
<td>59.4</td>
<td>45.8</td>
<td>54.3</td>
<td>48.9</td>
<td>54.9</td>
<td>65.6 </td>
</tr>
<tr>
<td colspan="15" align="left"><strong>Code</strong></td>
</tr>
<tr>
<td>HumanEval+</td>
<td>70.1</td>
<td>67.1</td>
<td>61.6</td>
<td>62.8</td>
<td>66.5</td>
<td>68.9</td>
<td>68.3 </td>
</tr>
<tr>
<td>MBPP+</td>
<td>57.1</td>
<td>62.2</td>
<td>64.3</td>
<td>55.3</td>
<td>71.4</td>
<td>55.8</td>
<td>63.2 </td>
</tr>
<tr>
<td>LiveCodeBench v3</td>
<td>22.2</td>
<td>20.2</td>
<td>19.2</td>
<td>20.4</td>
<td>24.0</td>
<td>19.6</td>
<td>22.6 </td>
</tr>
<tr>
<td colspan="15" align="left"><strong>Function Call</strong></td>
</tr>
<tr>
<td>BFCL v2</td>
<td>71.6</td>
<td>70.1</td>
<td>19.2</td>
<td>73.3</td>
<td>75.4</td>
<td>48.4</td>
<td>76.0 </td>
</tr>
<tr>
<td colspan="15" align="left"><strong>Overall</strong></td>
</tr>
<tr>
<td>Average</td>
<td>65.3</td>
<td>65.0</td>
<td>57.9</td>
<td>60.8</td>
<td>61.0</td>
<td>57.2</td>
<td><strong>66.3</strong></td>
</tr>
</table>
## Statement
* As a language model, MiniCPM3-4B generates content by learning from a vast amount of text.
* However, it does not possess the ability to comprehend or express personal opinions or value judgments.
* Any content generated by MiniCPM3-4B does not represent the viewpoints or positions of the model developers.
* Therefore, when using content generated by MiniCPM3-4B, users should take full responsibility for evaluating and verifying it on their own.
## LICENSE
* This repository is released under the [Apache-2.0](https://github.com/OpenBMB/MiniCPM/blob/main/LICENSE) License.
* The usage of MiniCPM3-4B model weights must strictly follow [MiniCPM Model License.md](https://github.com/OpenBMB/MiniCPM/blob/main/MiniCPM%20Model%20License.md).
* The models and weights of MiniCPM3-4B are completely free for academic research. after filling out a ["questionnaire"](https://modelbest.feishu.cn/share/base/form/shrcnpV5ZT9EJ6xYjh3Kx0J6v8g) for registration, are also available for free commercial use.
## Citation
```
@article{hu2024minicpm,
title={MiniCPM: Unveiling the Potential of Small Language Models with Scalable Training Strategies},
author={Hu, Shengding and Tu, Yuge and Han, Xu and He, Chaoqun and Cui, Ganqu and Long, Xiang and Zheng, Zhi and Fang, Yewei and Huang, Yuxiang and Zhao, Weilin and others},
journal={arXiv preprint arXiv:2404.06395},
year={2024}
}
```

10
added_tokens.json Normal file
View File

@ -0,0 +1,10 @@
{
"<|execute_end|>": 73444,
"<|execute_start|>": 73443,
"<|fim_middle|>": 73446,
"<|fim_prefix|>": 73445,
"<|fim_suffix|>": 73447,
"<|im_end|>": 73440,
"<|im_start|>": 73441,
"<|tool_call|>": 73442
}

41
config.json Normal file
View File

@ -0,0 +1,41 @@
{
"_name_or_path": "openbmb/MiniCPM3-4B",
"architectures": [
"MiniCPM3ForCausalLM"
],
"auto_map": {
"AutoConfig": "configuration_minicpm.MiniCPM3Config",
"AutoModel": "modeling_minicpm.MiniCPM3Model",
"AutoModelForCausalLM": "modeling_minicpm.MiniCPM3ForCausalLM",
"AutoModelForSeq2SeqLM": "modeling_minicpm.MiniCPM3ForCausalLM",
"AutoModelForSequenceClassification": "modeling_minicpm.MiniCPM3ForSequenceClassification"
},
"bos_token_id": 1,
"eos_token_id": [2, 73440],
"hidden_act": "silu",
"initializer_range": 0.1,
"hidden_size": 2560,
"num_hidden_layers": 62,
"intermediate_size": 6400,
"max_position_embeddings": 32768,
"num_attention_heads": 40,
"num_key_value_heads": 40,
"qk_nope_head_dim": 64,
"qk_rope_head_dim": 32,
"q_lora_rank": 768,
"kv_lora_rank": 256,
"rms_norm_eps": 1e-05,
"rope_scaling": {
"type": "longrope",
"long_factor": [1.0591234137867171, 1.1241891283591912, 1.2596935748670968, 1.5380380402321725, 2.093982484148734, 3.1446935121267696, 4.937952647693647, 7.524541999994549, 10.475458000005451, 13.062047352306353, 14.85530648787323, 15.906017515851266, 16.461961959767827, 16.740306425132907, 16.87581087164081, 16.940876586213285],
"short_factor": [1.0591234137867171, 1.1241891283591912, 1.2596935748670968, 1.5380380402321725, 2.093982484148734, 3.1446935121267696, 4.937952647693647, 7.524541999994549, 10.475458000005451, 13.062047352306353, 14.85530648787323, 15.906017515851266, 16.461961959767827, 16.740306425132907, 16.87581087164081, 16.940876586213285],
"original_max_position_embeddings": 32768
},
"torch_dtype": "bfloat16",
"transformers_version": "4.41.0",
"use_cache": true,
"vocab_size": 73448,
"scale_emb": 12,
"dim_model_base": 256,
"scale_depth": 1.4
}

1
configuration.json Normal file
View File

@ -0,0 +1 @@
{"framework":"Pytorch","task":"text-generation"}

195
configuration_minicpm.py Normal file
View File

@ -0,0 +1,195 @@
# coding=utf-8
# Copyright 2022 EleutherAI and the HuggingFace Inc. team. All rights reserved.
#
# This code is based on EleutherAI's GPT-NeoX library and the GPT-NeoX
# and OPT implementations in this library. It has been modified from its
# original forms to accommodate minor architectural differences compared
# to GPT-NeoX and OPT used by the Meta AI team that trained the model.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
""" MiniCPM model configuration"""
from transformers.configuration_utils import PretrainedConfig
from transformers.utils import logging
logger = logging.get_logger(__name__)
MINICPM_PRETRAINED_CONFIG_ARCHIVE_MAP = {}
class MiniCPM3Config(PretrainedConfig):
r"""
This is the configuration class to store the configuration of a [`MiniCPMModel`]. It is used to instantiate an MiniCPM
model according to the specified arguments, defining the model architecture. Instantiating a configuration with the
defaults will yield a similar configuration to that of the MiniCPM-7B.
Configuration objects inherit from [`PretrainedConfig`] and can be used to control the model outputs. Read the
documentation from [`PretrainedConfig`] for more information.
Args:
vocab_size (`int`, *optional*, defaults to 32000):
Vocabulary size of the MiniCPM model. Defines the number of different tokens that can be represented by the
`inputs_ids` passed when calling [`MiniCPMModel`]
hidden_size (`int`, *optional*, defaults to 4096):
Dimension of the hidden representations.
intermediate_size (`int`, *optional*, defaults to 11008):
Dimension of the MLP representations.
num_hidden_layers (`int`, *optional*, defaults to 32):
Number of hidden layers in the Transformer decoder.
num_attention_heads (`int`, *optional*, defaults to 32):
Number of attention heads for each attention layer in the Transformer decoder.
num_key_value_heads (`int`, *optional*):
This is the number of key_value heads that should be used to implement Grouped Query Attention. If
`num_key_value_heads=num_attention_heads`, the model will use Multi Head Attention (MHA), if
`num_key_value_heads=1 the model will use Multi Query Attention (MQA) otherwise GQA is used. When
converting a multi-head checkpoint to a GQA checkpoint, each group key and value head should be constructed
by meanpooling all the original heads within that group. For more details checkout [this
paper](https://arxiv.org/pdf/2305.13245.pdf). If it is not specified, will default to
`num_attention_heads`.
hidden_act (`str` or `function`, *optional*, defaults to `"silu"`):
The non-linear activation function (function or string) in the decoder.
max_position_embeddings (`int`, *optional*, defaults to 2048):
The maximum sequence length that this model might ever be used with. MiniCPM 1 supports up to 2048 tokens,
MiniCPM 2 up to 4096, CodeMiniCPM up to 16384.
initializer_range (`float`, *optional*, defaults to 0.02):
The standard deviation of the truncated_normal_initializer for initializing all weight matrices.
rms_norm_eps (`float`, *optional*, defaults to 1e-06):
The epsilon used by the rms normalization layers.
use_cache (`bool`, *optional*, defaults to `True`):
Whether or not the model should return the last key/values attentions (not used by all models). Only
relevant if `config.is_decoder=True`.
pad_token_id (`int`, *optional*):
Padding token id.
bos_token_id (`int`, *optional*, defaults to 1):
Beginning of stream token id.
eos_token_id (`int`, *optional*, defaults to 2):
End of stream token id.
pretraining_tp (`int`, *optional*, defaults to 1):
Experimental feature. Tensor parallelism rank used during pretraining. Please refer to [this
document](https://huggingface.co/docs/transformers/parallelism) to understand more about it. This value is
necessary to ensure exact reproducibility of the pretraining results. Please refer to [this
issue](https://github.com/pytorch/pytorch/issues/76232).
tie_word_embeddings (`bool`, *optional*, defaults to `False`):
Whether to tie weight embeddings
rope_theta (`float`, *optional*, defaults to 10000.0):
The base period of the RoPE embeddings.
rope_scaling (`Dict`, *optional*):
Dictionary containing the scaling configuration for the RoPE embeddings. Currently supports two scaling
strategies: linear and dynamic. Their scaling factor must be a float greater than 1. The expected format is
`{"type": strategy name, "factor": scaling factor}`. When using this flag, don't update
`max_position_embeddings` to the expected new maximum. See the following thread for more information on how
these scaling strategies behave:
https://www.reddit.com/r/LocalMiniCPM/comments/14mrgpr/dynamically_scaled_rope_further_increases/. This is an
experimental feature, subject to breaking API changes in future versions.
attention_bias (`bool`, defaults to `False`, *optional*, defaults to `False`):
Whether to use a bias in the query, key, value and output projection layers during self-attention.
attention_dropout (`float`, *optional*, defaults to 0.0):
The dropout ratio for the attention probabilities.
```python
>>> from transformers import MiniCPMModel, MiniCPMConfig
>>> # Initializing a MiniCPM minicpm-7b style configuration
>>> configuration = MiniCPMConfig()
>>> # Initializing a model from the minicpm-7b style configuration
>>> model = MiniCPMModel(configuration)
>>> # Accessing the model configuration
>>> configuration = model.config
```"""
model_type = "minicpm3"
keys_to_ignore_at_inference = ["past_key_values"]
def __init__(
self,
vocab_size=32000,
hidden_size=4096,
intermediate_size=11008,
num_hidden_layers=32,
num_attention_heads=32,
num_key_value_heads=None,
qk_nope_head_dim=64,
qk_rope_head_dim=32,
q_lora_rank=768,
kv_lora_rank=256,
v_head_dim=None,
head_dim=None,
hidden_act="silu",
max_position_embeddings=2048,
initializer_range=0.02,
rms_norm_eps=1e-6,
use_cache=True,
pad_token_id=None,
bos_token_id=1,
eos_token_id=2,
pretraining_tp=1,
tie_word_embeddings=True,
rope_theta=10000.0,
rope_scaling=None,
attention_bias=False,
attention_dropout=0.0,
scale_emb=1,
dim_model_base=1,
scale_depth=1,
**kwargs,
):
self.vocab_size = vocab_size
self.max_position_embeddings = max_position_embeddings
self.hidden_size = hidden_size
self.intermediate_size = intermediate_size
self.num_hidden_layers = num_hidden_layers
self.num_attention_heads = num_attention_heads
self.qk_nope_head_dim = qk_nope_head_dim
self.qk_rope_head_dim = qk_rope_head_dim
self.q_lora_rank = q_lora_rank
self.kv_lora_rank = kv_lora_rank
if v_head_dim is None:
v_head_dim = qk_nope_head_dim
self.v_head_dim = v_head_dim
# for backward compatibility
if num_key_value_heads is None:
num_key_value_heads = num_attention_heads
self.num_key_value_heads = num_key_value_heads
self.hidden_act = hidden_act
self.initializer_range = initializer_range
self.rms_norm_eps = rms_norm_eps
self.pretraining_tp = pretraining_tp
self.use_cache = use_cache
self.rope_theta = rope_theta
self.rope_scaling = rope_scaling
self.attention_bias = attention_bias
self.attention_dropout = attention_dropout
self.scale_emb = scale_emb
self.dim_model_base = dim_model_base
self.scale_depth = scale_depth
self.head_dim = self.qk_nope_head_dim + self.qk_rope_head_dim
super().__init__(
pad_token_id=pad_token_id,
bos_token_id=bos_token_id,
eos_token_id=eos_token_id,
tie_word_embeddings=tie_word_embeddings,
**kwargs,
)
try:
import flash_attn
self._attn_implementation = "flash_attention_2"
except:
pass

7
generation_config.json Normal file
View File

@ -0,0 +1,7 @@
{
"do_sample": true,
"top_p": 0.8,
"temperature": 0.8,
"bos_token_id": 1,
"eos_token_id": [2, 73440]
}

1572
modeling_minicpm.py Normal file

File diff suppressed because it is too large Load Diff

BIN
pytorch_model.bin (Stored with Git LFS) Normal file

Binary file not shown.

81
special_tokens_map.json Normal file
View File

@ -0,0 +1,81 @@
{
"additional_special_tokens": [
{
"content": "<|im_end|>",
"lstrip": false,
"normalized": false,
"rstrip": false,
"single_word": false
},
{
"content": "<|im_start|>",
"lstrip": false,
"normalized": false,
"rstrip": false,
"single_word": false
},
{
"content": "<|tool_call|>",
"lstrip": false,
"normalized": false,
"rstrip": false,
"single_word": false
},
{
"content": "<|execute_start|>",
"lstrip": false,
"normalized": false,
"rstrip": false,
"single_word": false
},
{
"content": "<|execute_end|>",
"lstrip": false,
"normalized": false,
"rstrip": false,
"single_word": false
},
{
"content": "<|fim_prefix|>",
"lstrip": false,
"normalized": false,
"rstrip": false,
"single_word": false
},
{
"content": "<|fim_middle|>",
"lstrip": false,
"normalized": false,
"rstrip": false,
"single_word": false
},
{
"content": "<|fim_suffix|>",
"lstrip": false,
"normalized": false,
"rstrip": false,
"single_word": false
}
],
"bos_token": {
"content": "<s>",
"lstrip": false,
"normalized": false,
"rstrip": false,
"single_word": false
},
"eos_token": {
"content": "</s>",
"lstrip": false,
"normalized": false,
"rstrip": false,
"single_word": false
},
"unk_token": {
"content": "<unk>",
"lstrip": false,
"normalized": false,
"rstrip": false,
"single_word": false
}
}

431
tokenization_minicpm.py Normal file
View File

@ -0,0 +1,431 @@
import ast
import json
import keyword
import traceback
import uuid
from collections import deque
from copy import deepcopy
from logging import getLogger
from typing import Any, Dict, List, Optional, Union
from datamodel_code_generator import DataModelType
from datamodel_code_generator.format import PythonVersion
from datamodel_code_generator.model import get_data_model_types
from datamodel_code_generator.parser.jsonschema import JsonSchemaParser
from jsonschema import Draft202012Validator, exceptions, validate
from transformers import LlamaTokenizerFast
from transformers.tokenization_utils_base import BatchEncoding
from transformers.utils import TensorType
logger = getLogger(__name__)
class MiniCPMTokenizer(LlamaTokenizerFast):
def apply_chat_template(
self,
conversation: Union[List[Dict[str, str]], List[List[Dict[str, str]]]],
tools: Optional[List[Dict]] = None,
documents: Optional[List[Dict[str, str]]] = None,
chat_template: Optional[str] = None,
add_generation_prompt: bool = False,
tokenize: bool = True,
padding: bool = False,
truncation: bool = False,
max_length: Optional[int] = None,
return_tensors: Optional[Union[str, TensorType]] = None,
return_dict: bool = False,
return_assistant_tokens_mask: bool = False,
tokenizer_kwargs: Optional[Dict[str, Any]] = None,
**kwargs,
) -> Union[str, List[int], List[str], List[List[int]], BatchEncoding]:
if tools is None:
tools = []
check_messages(conversation, tools)
functions = [tool["function"] for tool in tools]
conversation = self.reorder_tool_response(conversation)
input_messages = input_format(conversation, functions, add_to_system=True)
return super().apply_chat_template(
input_messages,
tools=None,
documents=documents,
chat_template=chat_template,
add_generation_prompt=add_generation_prompt,
tokenize=tokenize,
padding=padding,
truncation=truncation,
max_length=max_length,
return_tensors=return_tensors,
return_dict=return_dict,
return_assistant_tokens_mask=return_assistant_tokens_mask,
tokenizer_kwargs=tokenizer_kwargs,
**kwargs,
)
def reorder_tool_response(self, conversation: List[Dict[str, str]]):
tool_call_ids = deque()
tool_responses = deque()
new_conversation = []
for message in conversation:
if (
message["role"] == "assistant"
and "tool_calls" in message
and message["tool_calls"] is not None
and len(message["tool_calls"]) > 0
):
for tool_call in message["tool_calls"]:
tool_call_ids.append(tool_call["id"])
new_conversation.append(message)
elif message["role"] == "tool":
tool_call_id = message.get("tool_call_id", None)
if tool_call_id == tool_call_ids[0]:
new_conversation.append(message)
tool_call_ids.popleft()
while (
len(tool_call_ids) > 0
and len(tool_responses) > 0
and tool_call_ids[0] == tool_responses[0]["tool_call_id"]
):
new_conversation.append(tool_responses.popleft())
tool_call_ids.popleft()
else:
tool_responses.append(message)
else:
new_conversation.append(message)
if len(tool_call_ids) != 0:
raise ValueError(f"Message error, not all tool calls have responses: {tool_call_ids}")
if len(tool_responses) != 0:
raise ValueError(f"Message error, too many tool responses: {tool_responses}")
return new_conversation
def decode_function_call(
self,
sequence: str,
tool_call_start="<|tool_call_start|>",
tool_call_end="<|tool_call_end|>",
thought_start="<|thought_start|>",
thought_end="<|thought_end|>",
):
if thought_end in sequence and thought_start in sequence:
thought_string, sequence = sequence.rsplit(thought_end, 1)
thought_string = thought_string.split(thought_start, 1)[1]
else:
thought_string = ""
if tool_call_start in sequence and tool_call_end in sequence:
tool_call_string, content = sequence.rsplit(tool_call_end, 1)
tool_call_string = tool_call_string.split(tool_call_start, 1)[1]
try:
tool_calls = []
tool_call_string = tool_call_string.strip()
if tool_call_string.startswith("```"):
tool_call_string = tool_call_string.lstrip("```").strip()
if tool_call_string.startswith("python"):
tool_call_string = tool_call_string.lstrip("python").strip()
if tool_call_string.endswith("```"):
tool_call_string = tool_call_string.rstrip("```").strip()
for kw in keyword.kwlist:
tool_call_string = tool_call_string.replace("," + kw + "=", "," + kw + "_=")
tool_call_string = tool_call_string.replace(" " + kw + "=", " " + kw + "_=")
tool_call_string = tool_call_string.replace("(" + kw + "=", "(" + kw + "_=")
parsed = ast.parse(tool_call_string)
for elem in parsed.body:
assert isinstance(elem.value, ast.Call)
calls = resolve_ast_call(elem.value)
for func_name, func_args in calls.items():
new_args = {}
for k, v in func_args.items():
for kw in keyword.kwlist:
if k == kw + "_":
k = kw
new_args[k] = v
this_one = {"name": func_name, "arguments": new_args}
tool_calls.append(this_one)
return {
"content": content.strip(),
"tool_calls": [
{"type": "function", "function": tool_call, "id": "call_" + uuid.uuid4().hex}
for tool_call in tool_calls
],
"role": "assistant",
}
except:
logger.error(traceback.format_exc())
return {
"content": content.strip(),
"role": "assistant",
"thought": thought_string,
}
else:
return {
"content": sequence.strip(),
"role": "assistant",
"thought": thought_string,
}
def check_messages(conversation: List[Dict[str, str]], tools: List[Dict]):
if tools is not None:
for tool in tools:
if "type" not in tool or tool["type"] != "function":
raise ValueError(f"Tool {tool} is not valid")
if "name" not in tool["function"]:
raise ValueError(f"Tool {tool} is not valid")
if "parameters" not in tool["function"] or not check_tool(tool["function"]["parameters"]["properties"]):
raise ValueError(f"Tool {tool} is not valid")
for message in conversation:
if message["role"] == "assistant" and "tool_calls" in message and len(message["tool_calls"]) > 0:
for tool_call in message["tool_calls"]:
if "id" not in tool_call:
raise ValueError(f"Tool call {tool_call} is not valid")
if tool_call["type"] != "function":
raise ValueError(f"Tool call {tool_call} is not valid")
if "function" not in tool_call:
raise ValueError(f"Tool call {tool_call} is not valid")
if not check_tool(tool_call["function"]):
raise ValueError(f"Tool call function {tool_call['function']} is not valid")
elif message["role"] == "tool":
if "tool_call_id" not in message:
raise ValueError(f"Tool message {message['content']} is not valid")
def check_tool(tool_schema):
try:
Draft202012Validator.check_schema(tool_schema)
return True
except exceptions.SchemaError as e:
print(f"SchemaError: {e}")
return False
def check_args(args, tool_schema):
try:
validate(instance=args, schema=tool_schema)
return True
except exceptions.ValidationError as e:
print(f"Data failed validation: {e}")
return False
def message_format(msg, system_suffix="", user_prefix=""):
if "thought" in msg and msg["thought"] is not None and len(msg["thought"]) > 0:
thought_prefix = f"<|thought_start|>\n{msg['thought']}\n<|thought_end|>\n"
else:
thought_prefix = ""
if msg["role"] == "assistant":
content = msg.get("content", "")
if content is None:
content = ""
if "tool_calls" in msg and msg["tool_calls"] is not None and len(msg["tool_calls"]) > 0:
def add_quotes(variable):
if isinstance(variable, str):
return repr(variable)
else:
return str(variable)
tool_calls = []
for _tool_call in msg["tool_calls"]:
if _tool_call is None:
continue
tool_call = _tool_call["function"]
tool_name = tool_call["name"]
if "arguments" not in tool_call or tool_call["arguments"] is None:
continue
if isinstance(tool_call["arguments"], str):
try:
tool_call["arguments"] = json.loads(tool_call["arguments"])
except:
continue
args = ",".join([k + "=" + add_quotes(v) for k, v in tool_call["arguments"].items()])
tool_calls.append(f"{tool_name}({args})")
content = (
thought_prefix
+ "<|tool_call_start|>\n```python\n"
+ "\n".join(tool_calls).strip()
+ "\n```\n<|tool_call_end|>\n"
+ content
)
# msg["tool_call_string"] = "\n".join(tool_calls).strip()
msg["content"] = content
else:
content = thought_prefix + content
msg["content"] = content
elif msg["role"] == "user":
msg["content"] = user_prefix + "\n" + msg["content"]
elif msg["role"] == "system":
msg["content"] = msg["content"] + "\n" + system_suffix
msg["content"] = msg["content"].strip()
return msg
def jsonschema_to_code(jsonschema: dict) -> str:
input_text = json.dumps(jsonschema)
data_model_types = get_data_model_types(
DataModelType.PydanticBaseModel,
PythonVersion.PY_310,
)
parser = JsonSchemaParser(
source=input_text,
data_model_type=data_model_types.data_model,
data_model_root_type=data_model_types.root_model,
data_model_field_type=data_model_types.field_model,
data_type_manager_type=data_model_types.data_type_manager,
target_python_version=PythonVersion.PY_310,
dump_resolve_reference_action=data_model_types.dump_resolve_reference_action,
field_constraints=True,
)
results = parser.parse()
return results
def transform_function(function: dict):
"""turn json format of function into signature"""
params, default_params = [], []
for prop_name, prop in function["parameters"]["properties"].items():
if "default" in prop:
default_params.append(f'{prop_name}={repr(prop["default"])}')
elif prop_name not in function["parameters"].get("required", []):
default_params.append(f"{prop_name}={repr(None)}")
else:
params.append(prop_name)
ps = ", ".join(params + default_params)
res = "def {f_name}({ps}):\n".format(f_name=function["name"], ps=ps)
f_des = function.get("description", "")
content = jsonschema_to_code(function["parameters"])
if "class" in content:
i = content.index("class")
# print(content[:i])
content = content[i:]
classes, args = content.split("class Model(BaseModel):", 1)
lint_msg = f' """\n {f_des}\n Args:\n{args}\n """\n'
res += lint_msg
if len(classes) > 0:
res = classes + res
return res
def input_format(messages: List[Dict], tools: List[Dict], add_to_system=True):
"""
Process the input messages, global_arguments, tools, tool_choice,
and convert it into a input string.
The global arguments and tools can not be both empty.
parameters:
messages: List[Dict]
the input messages
For example:
tools: List[Dict]
the tools list you can use
For example:
"""
messages = deepcopy(messages)
tools = deepcopy(tools)
if tools is not None and len(tools) > 0:
header = (
"from enum import Enum\nfrom typing import List, Dict, Optional\nfrom pydantic import BaseModel, Field\n\n"
)
tools_string = header
for tool in tools:
try:
tools_string += "\n\n" + transform_function(tool)
except:
pass
tools_template = """# Functions
Here is a list of functions that you can invoke:
```python
{tools}
```
# Function Call Rule and Output Format
- If the user's question can be answered without calling any function, please answer the user's question directly. In this situation, you should return your thought and answer the user's question directly.
- If the user cannot be answered without calling any function, and the user does not provide enough information to call functions, please ask the user for more information. In this situation, you should return your thought and ask the user for more information.
- If the user's question cannot be answered without calling any function, and the user has provided enough information to call functions to solve it, you should call the functions. In this situation, the assistant should return your thought and call the functions.
- Use default parameters unless the user has specified otherwise.
- You should answer in the following format:
<|thought_start|>
{{explain why the user's question can be answered without calling a function or why you should ask the user for more information or why you should call one or more functions and your plan to solve the user's question.}}
<|thought_end|>
<|tool_call_start|>
```python
func1(params_name=params_value, params_name2=params_value2...)
func2(params)
```
<|tool_call_end|>
{{answer the user's question directly or ask the user for more information}}
"""
tools_string = tools_template.format(tools=tools_string).strip()
else:
tools_string = ""
if add_to_system:
return [message_format(msg, system_suffix=tools_string, user_prefix="") for msg in messages]
else:
return [message_format(msg, system_suffix="", user_prefix=tools_string) for msg in messages]
# This is a modified version of
# https://github.com/ShishirPatil/gorilla/blob/main/berkeley-function-call-leaderboard/bfcl/model_handler/utils.py
# Thanks to the gorilla team for the original implementation
def resolve_ast_call(elem):
# Handle nested attributes for deeply nested module paths
func_parts = []
func_part = elem.func
while isinstance(func_part, ast.Attribute):
func_parts.append(func_part.attr)
func_part = func_part.value
if isinstance(func_part, ast.Name):
func_parts.append(func_part.id)
func_name = ".".join(reversed(func_parts))
args_dict = {}
for arg in elem.keywords:
output = resolve_ast_by_type(arg.value)
args_dict[arg.arg] = output
return {func_name: args_dict}
def resolve_ast_by_type(value):
if isinstance(value, ast.Constant):
if value.value is Ellipsis:
output = "..."
else:
output = value.value
elif isinstance(value, ast.UnaryOp):
output = -value.operand.value
elif isinstance(value, ast.List):
output = [resolve_ast_by_type(v) for v in value.elts]
elif isinstance(value, ast.Dict):
output = {resolve_ast_by_type(k): resolve_ast_by_type(v) for k, v in zip(value.keys, value.values)}
elif isinstance(value, ast.NameConstant): # Added this condition to handle boolean values
output = value.value
elif isinstance(value, ast.BinOp): # Added this condition to handle function calls as arguments
output = eval(ast.unparse(value))
elif isinstance(value, ast.Name):
output = value.id
elif isinstance(value, ast.Call):
if len(value.keywords) == 0:
output = ast.unparse(value)
else:
output = resolve_ast_call(value)
elif isinstance(value, ast.Tuple):
output = tuple(resolve_ast_by_type(v) for v in value.elts)
elif isinstance(value, ast.Lambda):
output = eval(ast.unparse(value.body[0].value))
elif isinstance(value, ast.Ellipsis):
output = "..."
elif isinstance(value, ast.Subscript):
try:
output = ast.unparse(value.body[0].value)
except:
output = ast.unparse(value.value) + "[" + ast.unparse(value.slice) + "]"
else:
raise Exception(f"Unsupported AST type: {type(value)}")
return output

177952
tokenizer.json Normal file

File diff suppressed because it is too large Load Diff

BIN
tokenizer.model (Stored with Git LFS) Normal file

Binary file not shown.

119
tokenizer_config.json Normal file
View File

@ -0,0 +1,119 @@
{
"auto_map": {
"AutoTokenizer": ["tokenization_minicpm.MiniCPMTokenizer", null]
},
"add_bos_token": true,
"add_eos_token": false,
"added_tokens_decoder": {
"0": {
"content": "<unk>",
"lstrip": false,
"normalized": false,
"rstrip": false,
"single_word": false,
"special": true
},
"1": {
"content": "<s>",
"lstrip": false,
"normalized": false,
"rstrip": false,
"single_word": false,
"special": true
},
"2": {
"content": "</s>",
"lstrip": false,
"normalized": false,
"rstrip": false,
"single_word": false,
"special": true
},
"73440": {
"content": "<|im_end|>",
"lstrip": false,
"normalized": false,
"rstrip": false,
"single_word": false,
"special": true
},
"73441": {
"content": "<|im_start|>",
"lstrip": false,
"normalized": false,
"rstrip": false,
"single_word": false,
"special": true
},
"73442": {
"content": "<|tool_call|>",
"lstrip": false,
"normalized": false,
"rstrip": false,
"single_word": false,
"special": true
},
"73443": {
"content": "<|execute_start|>",
"lstrip": false,
"normalized": false,
"rstrip": false,
"single_word": false,
"special": true
},
"73444": {
"content": "<|execute_end|>",
"lstrip": false,
"normalized": false,
"rstrip": false,
"single_word": false,
"special": true
},
"73445": {
"content": "<|fim_prefix|>",
"lstrip": false,
"normalized": false,
"rstrip": false,
"single_word": false,
"special": true
},
"73446": {
"content": "<|fim_middle|>",
"lstrip": false,
"normalized": false,
"rstrip": false,
"single_word": false,
"special": true
},
"73447": {
"content": "<|fim_suffix|>",
"lstrip": false,
"normalized": false,
"rstrip": false,
"single_word": false,
"special": true
}
},
"additional_special_tokens": [
"<|im_end|>",
"<|im_start|>",
"<|tool_call|>",
"<|execute_start|>",
"<|execute_end|>",
"<|fim_prefix|>",
"<|fim_middle|>",
"<|fim_suffix|>"
],
"bos_token": "<s>",
"clean_up_tokenization_spaces": false,
"eos_token": "<|im_end|>",
"legacy": true,
"model_max_length": 1000000000000000019884624838656,
"pad_token": null,
"sp_model_kwargs": {},
"spaces_between_special_tokens": false,
"tokenizer_class": "MiniCPMTokenizer",
"unk_token": "<unk>",
"use_default_system_prompt": false,
"chat_template": "{% for message in messages %}{{'<|im_start|>' + message['role'] + '\n' + message['content'] + '<|im_end|>' + '\n'}}{% endfor %}{% if add_generation_prompt %}{{ '<|im_start|>assistant\n' }}{% endif %}"
}