Baichuan-7B 与 Baichuan-13B 梳理:开源可商用中英双语模型

梳理百川智能的 Baichuan 系列:基于 Transformer 结构、在约 1.2 万亿 token 上训练的 70 亿参数中英双语模型,在 C-Eval 与 MMLU 等基准上达到同尺寸最好效果,并给出数据处理方法、推理与量化部署方式。

🤗 Hugging Face • 🤖 ModelScope • 💬 WeChat

Baichuan-7B

简介

Baichuan-7B 是由百川智能开发的一个开源可商用的大规模预训练语言模型。基于 Transformer 结构,在大约 1.2 万亿 tokens 上训练的 70 亿参数模型,支持中英双语,上下文窗口长度为 4096。在标准的中文和英文 benchmark(C-Eval/MMLU)上均取得同尺寸最好的效果。

技术细节

数据

  • 原始数据包括开源的中英文数据和自行抓取的中文互联网数据,以及部分高质量知识性数据。
  • 参考相关数据工作,频率和质量是数据处理环节重点考虑的两个维度。 我们基于启发式规则和质量模型打分,对原始数据集进行篇章和句子粒度的过滤。在全量数据上,利用局部敏感哈希方法,对篇章和句子粒度做滤重。
    整体流程如下所示:

  • 经过不断的调整和多轮测试,最终确认了一个在下游任务上表现最好的中英文配比。
  • 我们使用了一个基于自动学习的数据权重策略,对不同类别的数据进行配比。

分词

我们参考学术界方案使用 SentencePiece 中的 Byte-Pair Encoding (BPE) 作为分词算法,并且进行了以下的优化:

  1. 目前大部分开源模型主要基于英文优化,因此对中文语料存在效率较低的问题。我们使用 2000 万条以中英为主的多语言语料训练分词模型,显著提升对于中文的压缩率。
  2. 对于数学领域,我们参考了 LLaMA 和 Galactica 中的方案,对数字的每一位单独分开,避免出现数字不一致的问题,对于提升数学能力有重要帮助。
  3. 对于罕见字词(如特殊符号等),支持 UTF-8 characters 的 byte 编码,因此做到未知字词的全覆盖。
  4. 我们分析了不同分词器对语料的压缩率,如下表,可见我们的分词器明显优于 LLaMA, Falcon 等开源模型,并且对比其他中文分词器在压缩率相当的情况下,训练和推理效率更高。

比较结果
| Model | Baichuan-7B | LLaMA | Falcon | mpt-7B | ChatGLM | moss-moon-003 |
| — | — | — | — | — | — | — |
| Compress Rate | 0.737 | 1.312 | 1.049 | 1.206 | 0.631 | 0.659 |
| Vocab Size | 64,000 | 32,000 | 65,024 | 50,254 | 130,344 | 106,029 |

模型结构

整体模型基于标准的 Transformer 结构,我们采用了和 LLaMA 一样的模型设计

  • 位置编码:rotary-embedding 是现阶段被大多模型采用的位置编码方案,具有更好的外延效果。虽然训练过程中最大长度为4096,但是实际测试中模型可以很好的扩展到 5000 tokens 以上,如下图:

  • 激活层:SwiGLU, Feedforward 变化为 8/3 倍的隐含层大小,即 11,008

  • Layer-Normalization: 基于 RMSNorm 的 Pre-Normalization

训练稳定性和吞吐

我们在原本的 LLaMA 框架上进行诸多修改以提升训练时的吞吐,具体包括:

  1. 算子优化技术:采用更高效算子,如 Flash-Attention,NVIDIA apex 的 RMSNorm 等。
  2. 算子切分技术:将部分计算算子进行切分,减小内存峰值。
  3. 混合精度技术:降低在不损失模型精度的情况下加速计算过程。
  4. 训练容灾技术:训练平台和训练框架联合优化,IaaS + PaaS 实现分钟级的故障定位和任务恢复。
  5. 通信优化技术,具体包括:
    基于上述的几个优化技术,我们在千卡 A800 显卡上达到了 7B 模型 182 TFLOPS 的吞吐,GPU 峰值算力利用率高达 58.3%。

最终的loss如下图:

推理方法

推理代码已经在官方 Huggingface 库

1
2
3
4
5
6
7
8
from transformers import AutoModelForCausalLM, AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("baichuan-inc/Baichuan-7B", trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained("baichuan-inc/Baichuan-7B", device_map="auto", trust_remote_code=True)
inputs = tokenizer('登鹳雀楼->王之涣\n夜雨寄北->', return_tensors='pt')
inputs = inputs.to('cuda:0')
pred = model.generate(**inputs, max_new_tokens=64,repetition_penalty=1.1)
print(tokenizer.decode(pred.cpu()[0], skip_special_tokens=True))

训练方法

安装依赖

pip install -r requirements.txt
deepspeed==0.9.2
numpy==1.23.5
sentencepiece==0.1.97
torch==2.0.0
transformers==4.29.1
xformers==0.0.20

准备数据

用户将训练语料按总rank数的倍数均匀切分成多个 UTF-8 文本文件,放置在语料目录(默认为 data_dir )下。各个rank进程将会读取语料目录下的不同文件,全部加载到内存后,开始后续训练过程。以上是简化的示范流程,建议用户在正式训练任务中,根据需求调整数据生产逻辑。

下载 tokenizer 模型

下载 tokenizer 模型文件 tokenizer.model ,放置在项目目录下。

配置 DeepSpeed

本示范代码采用 DeepSpeed 框架进行训练。用户需根据集群情况,修改 config/hostfile ,如果是多机多卡,需要修改 ssh 中各个节点的 IP 配置。具体可以参见 DeepSpeed 官方说明

执行训练

scripts/train.sh
#!/bin/bash
deepspeed –hostfile config/hostfile
–force_multi
train.py
–deepspeed
–deepspeed_config config/deepspeed.json

Baichuan-13B

Baichuan-13B 是由百川智能继 Baichuan-7B 之后开发的包含 130 亿参数的开源可商用的大规模语言模型,在权威的中文和英文 benchmark 上均取得同尺寸最好的效果。本次发布包含有预训练 (Baichuan-13B-Base) 和对齐 (Baichuan-13B-Chat) 两个版本。Baichuan-13B 有如下几个特点:

  1. 更大尺寸、更多数据:Baichuan-13B 在 Baichuan-7B 的基础上进一步扩大参数量到 130 亿,并且在高质量的语料上训练了 1.4 万亿 tokens,超过 LLaMA-13B 40%,是当前开源 13B 尺寸下训练数据量最多的模型。支持中英双语,使用 ALiBi 位置编码,上下文窗口长度为 4096。
  2. 同时开源预训练和对齐模型:预训练模型是适用开发者的『 基座 』,而广大普通用户对有对话功能的对齐模型具有更强的需求。因此本次开源我们同时发布了对齐模型(Baichuan-13B-Chat),具有很强的对话能力,开箱即用,几行代码即可简单的部署。
  3. 更高效的推理:为了支持更广大用户的使用,我们本次同时开源了 int8 和 int4 的量化版本,相对非量化版本在几乎没有效果损失的情况下大大降低了部署的机器资源门槛,可以部署在如 Nvidia 3090 这样的消费级显卡上。
  4. 开源免费可商用:Baichuan-13B 不仅对学术研究完全开放,开发者也仅需邮件申请并获得官方商用许可后,即可以免费商用。

    模型推理

1
2
3
4
5
6
7
8
9
10
11
>>> import torch
>>> from transformers import AutoModelForCausalLM, AutoTokenizer
>>> from transformers.generation.utils import GenerationConfig
>>> tokenizer = AutoTokenizer.from_pretrained("baichuan-inc/Baichuan-13B-Chat", use_fast=False, trust_remote_code=True)
>>> model = AutoModelForCausalLM.from_pretrained("baichuan-inc/Baichuan-13B-Chat", device_map="auto", torch_dtype=torch.float16, trust_remote_code=True)
>>> model.generation_config = GenerationConfig.from_pretrained("baichuan-inc/Baichuan-13B-Chat")
>>> messages = []
>>> messages.append({"role": "user", "content": "世界上第二高的山峰是哪座"})
>>> response = model.chat(tokenizer, messages)
>>> print(response)
乔戈里峰。世界第二高峰———乔戈里峰西方登山者称其为k2峰,海拔高度是8611米,位于喀喇昆仑山脉的中巴边境上

Baichuan-13B 使用了 ALiBi 线性偏置技术,相对于 Rotary Embedding 计算量更小,对推理性能有显著提升;与标准的 LLaMA-13B 相比,平均推理速度 (tokens/s) 实测提升 31.6%:

Modeltokens/s
LLaMA-13B19.4
Baichuan-13B25.4

测试环境和参数:GPU A100-SXM4-80G, PyTorch 2.0.0+cu117, transformers 4.29.1, batch size = 1, 生成长度 = 2048, 精度 fp16, 基于 Baichuan-13B-Base

量化部署

如需使用 int8 量化:

1
2
model = AutoModelForCausalLM.from_pretrained("baichuan-inc/Baichuan-13B-Chat", torch_dtype=torch.float16, trust_remote_code=True)
model = model.quantize(8).cuda()

同样的,如需使用 int4 量化:

1
2
model = AutoModelForCausalLM.from_pretrained("baichuan-inc/Baichuan-13B-Chat", torch_dtype=torch.float16, trust_remote_code=True)
model = model.quantize(4).cuda()

量化前后占用显存情况如下:

PrecisionGPU Mem (GB)
bf16 / fp1626.0
int815.8
int49.7

量化后在各个 benchmark 上的结果和原始版本对比如下:

Model 5-shotC-EvalMMLUCMMLU
Baichuan-13B-Base52.451.655.3
Baichuan-13B-Base-int851.249.954.5
Baichuan-13B-Base-int447.646.051.0

模型微调

可以对 Baichuan-13B-Base 或 Baichuan-13B-Chat 进行微调使用。在此我们测试了与 Baichuan-13B 兼容的微调工具 LLaMA Efficient Tuning,并给出全量微调LoRA微调的两种示范。

在开始之前,开发者需下载 LLaMA Efficient Tuning 项目并按其要求安装依赖

输入数据为放置在项目data目录下的 json 文件,用--dataset选项指定(参考下面示例),多个输入文件用,分隔。json 文件示例格式和字段说明如下:

1
2
3
4
5
6
7
8
[
{
"instruction": "What are the three primary colors?",
"input": "",
"output": "The three primary colors are red, blue, and yellow."
},
....
]

json 文件中存储一个列表,列表的每个元素是一个 sample。其中instruction代表用户输入,input是可选项,如果开发者同时指定了instructioninput,会把二者用\n连接起来代表用户输入;output代表期望的模型输出。

下面我们给出两种微调场景下测试跑通的示范脚本。

全量微调

我们在 8 * Nvidia A100 80 GB + deepspeed 的环境下进行了全量微调测试。

训练启动脚本示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
deepspeed --num_gpus=8 src/train_bash.py \
--stage sft \
--model_name_or_path baichuan-inc/Baichuan-13B-Base \
--do_train \
--dataset alpaca_gpt4_en,alpaca_gpt4_zh \
--finetuning_type full \
--output_dir path_to_your_sft_checkpoint \
--overwrite_cache \
--per_device_train_batch_size 4 \
--per_device_eval_batch_size 4 \
--gradient_accumulation_steps 8 \
--preprocessing_num_workers 16 \
--lr_scheduler_type cosine \
--logging_steps 10 \
--save_steps 100 \
--eval_steps 100 \
--learning_rate 5e-5 \
--max_grad_norm 0.5 \
--num_train_epochs 2.0 \
--dev_ratio 0.01 \
--evaluation_strategy steps \
--load_best_model_at_end \
--plot_loss \
--fp16 \
--deepspeed deepspeed.json

deep_speed.json 配置示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"train_micro_batch_size_per_gpu": "auto",
"zero_allow_untested_optimizer": true,
"fp16": {
"enabled": "auto",
"loss_scale": 0,
"initial_scale_power": 16,
"loss_scale_window": 1000,
"hysteresis": 2,
"min_loss_scale": 1
},
"zero_optimization": {
"stage": 2,
"allgather_partitions": true,
"allgather_bucket_size": 5e8,
"overlap_comm": false,
"reduce_scatter": true,
"reduce_bucket_size": 5e8,
"contiguous_gradients" : true
}
}

LoRA微调

我们在单张 Nvidia A100 80G 显卡上进行了 LoRA 微调测试。

训练启动脚本示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
CUDA_VISIBLE_DEVICES=0 python src/train_bash.py \
--stage sft \
--model_name_or_path baichuan-inc/Baichuan-13B-Base \
--do_train \
--dataset alpaca_gpt4_en,alpaca_gpt4_zh \
--finetuning_type lora \
--lora_rank 8 \
--lora_target W_pack \
--output_dir path_to_your_sft_checkpoint \
--overwrite_cache \
--per_device_train_batch_size 4 \
--per_device_eval_batch_size 4 \
--gradient_accumulation_steps 8 \
--preprocessing_num_workers 16 \
--lr_scheduler_type cosine \
--logging_steps 10 \
--save_steps 100 \
--eval_steps 100 \
--learning_rate 5e-5 \
--max_grad_norm 0.5 \
--num_train_epochs 2.0 \
--dev_ratio 0.01 \
--evaluation_strategy steps \
--load_best_model_at_end \
--plot_loss \
--fp16

关于使用 LLaMA Efficient Tuning 的更详细的用法,请参阅其项目主页说明。

Baichuan2

  • Baichuan 2 是百川智能推出的新一代开源大语言模型,采用 2.6 万亿 Tokens 的高质量语料训练。
  • Baichuan 2 在多个权威的中文、英文和多语言的通用、领域 benchmark 上取得同尺寸最佳的效果。
  • 本次发布包含有 7B13BBaseChat 版本,并提供了 Chat 版本的 4bits 量化

安装依赖

pip install -r requirements.txt
accelerate
colorama
bitsandbytes
sentencepiece
streamlit
transformers_stream_generator
cpm_kernels
xformers
scipy

文本生成

Chat 模型推理方法(文本生成)

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> import torch
>>> from transformers import AutoModelForCausalLM, AutoTokenizer
>>> from transformers.generation.utils import GenerationConfig
>>> tokenizer = AutoTokenizer.from_pretrained("baichuan-inc/Baichuan2-13B-Chat", use_fast=False, trust_remote_code=True)
>>> model = AutoModelForCausalLM.from_pretrained("baichuan-inc/Baichuan2-13B-Chat", device_map="auto", torch_dtype=torch.bfloat16, trust_remote_code=True)
>>> model.generation_config = GenerationConfig.from_pretrained("baichuan-inc/Baichuan2-13B-Chat")
>>> messages = []
>>> messages.append({"role": "user", "content": "解释一下“温故而知新”"})
>>> response = model.chat(tokenizer, messages)
>>> print(response)
"温故而知新"是一句中国古代的成语,出自《论语·为政》篇。这句话的意思是:通过回顾过去,我们可以发现新的知识和理解。换句话说,学习历史和经验可以让我们更好地理解现在和未来。

这句话鼓励我们在学习和生活中不断地回顾和反思过去的经验,从而获得新的启示和成长。通过重温旧的知识和经历,我们可以发现新的观点和理解,从而更好地应对不断变化的世界和挑战。

文本补全

Base 模型推理方法(文本补全)

1
2
3
4
5
6
7
8
9
>>> from transformers import AutoModelForCausalLM, AutoTokenizer
>>> tokenizer = AutoTokenizer.from_pretrained("baichuan-inc/Baichuan2-13B-Base", trust_remote_code=True)
>>> model = AutoModelForCausalLM.from_pretrained("baichuan-inc/Baichuan2-13B-Base", device_map="auto", trust_remote_code=True)
>>> inputs = tokenizer('登鹳雀楼->王之涣\n夜雨寄北->', return_tensors='pt')
>>> inputs = inputs.to('cuda:0')
>>> pred = model.generate(**inputs, max_new_tokens=64, repetition_penalty=1.1)
>>> print(tokenizer.decode(pred.cpu()[0], skip_special_tokens=True))
登鹳雀楼->王之涣
夜雨寄北->李商隐

在上述两段代码中,模型加载指定 device_map='auto',会使用所有可用显卡。如需指定使用的设备,可以使用类似 export CUDA_VISIBLE_DEVICES=0,1(使用了0、1号显卡)的方式控制。

模型量化

8bits 在线量化:

1
2
model = AutoModelForCausalLM.from_pretrained("baichuan-inc/Baichuan2-7B-Chat", torch_dtype=torch.float16, trust_remote_code=True)
model = model.quantize(8).cuda()

4bits 在线量化:

1
2
model = AutoModelForCausalLM.from_pretrained("baichuan-inc/Baichuan2-7B-Chat", torch_dtype=torch.float16, trust_remote_code=True)
model = model.quantize(4).cuda()

需要注意的是,在用 from_pretrained 接口的时候,用户一般会加上 device_map="auto",在使用在线量化时,需要去掉这个参数,否则会报错。

量化前后显存占用对比 (GPU Mem in GB):

PrecisionBaichuan2-7BBaichuan2-13B
bf16 / fp1615.327.5
8bits8.016.1
4bits5.18.6

量化后在各个 benchmark 上的结果和原始版本对比如下:

Model 5-shotC-EvalMMLUCMMLU
Baichuan2-13B-Chat56.7457.3259.68
Baichuan2-13B-Chat-4bits56.0556.2458.82
Baichuan2-7B-Chat54.3552.9354.99
Baichuan2-7B-Chat-4bits53.0451.7252.84

C-Eval 是在其 val set 上进行的评测

可以看到,4bits 相对 bfloat16 精度损失在 1 - 2 个百分点左右。

CPU 部署

Baichuan 2 模型支持 CPU 推理,但需要强调的是,CPU 的推理速度相对较慢。需按如下方式修改模型加载的方式:

1
2
# Taking Baichuan2-7B-Chat as an example
model = AutoModelForCausalLM.from_pretrained("baichuan-inc/Baichuan2-7B-Chat", torch_dtype=torch.float32, trust_remote_code=True)

Baichuan2模型微调

依赖安装

1
2
3
git clone https://github.com/baichuan-inc/Baichuan2.git
cd Baichuan2/fine-tune
pip install -r requirements.txt
  • 如需使用 LoRA 等轻量级微调方法需额外安装 peft
  • 如需使用 xFormers 进行训练加速需额外安装 xFormers

    单机训练

下面我们给一个微调 Baichuan2-7B-Base 的单机训练例子。

训练数据:data/belle_chat_ramdon_10k.json,该样例数据是从 multiturn_chat_0.8M 采样出 1 万条,并且做了格式转换。主要是展示多轮数据怎么训练,不保证效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
hostfile=""
deepspeed --hostfile=$hostfile fine-tune.py \
--report_to "none" \
--data_path "data/belle_chat_ramdon_10k.json" \
--model_name_or_path "baichuan-inc/Baichuan2-7B-Base" \
--output_dir "output" \
--model_max_length 512 \
--num_train_epochs 4 \
--per_device_train_batch_size 16 \
--gradient_accumulation_steps 1 \
--save_strategy epoch \
--learning_rate 2e-5 \
--lr_scheduler_type constant \
--adam_beta1 0.9 \
--adam_beta2 0.98 \
--adam_epsilon 1e-8 \
--max_grad_norm 1.0 \
--weight_decay 1e-4 \
--warmup_ratio 0.0 \
--logging_steps 1 \
--gradient_checkpointing True \
--deepspeed ds_config.json \
--bf16 True \
--tf32 True

多机训练

多机训练只需要给一下 hostfile ,内容类似如下:

1
2
3
4
5
ip1 slots=8
ip2 slots=8
ip3 slots=8
ip4 slots=8
....

同时在训练脚本里面指定 hosftfile 的路径:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
hostfile="/path/to/hostfile"
deepspeed --hostfile=$hostfile fine-tune.py \
--report_to "none" \
--data_path "data/belle_chat_ramdon_10k.json" \
--model_name_or_path "baichuan-inc/Baichuan2-7B-Base" \
--output_dir "output" \
--model_max_length 512 \
--num_train_epochs 4 \
--per_device_train_batch_size 16 \
--gradient_accumulation_steps 1 \
--save_strategy epoch \
--learning_rate 2e-5 \
--lr_scheduler_type constant \
--adam_beta1 0.9 \
--adam_beta2 0.98 \
--adam_epsilon 1e-8 \
--max_grad_norm 1.0 \
--weight_decay 1e-4 \
--warmup_ratio 0.0 \
--logging_steps 1 \
--gradient_checkpointing True \
--deepspeed ds_config.json \
--bf16 True \
--tf32 True

轻量化微调

代码已经支持轻量化微调如 LoRA,如需使用仅需在上面的脚本中加入以下参数:

1
--use_lora True

LoRA 具体的配置可见 fine-tune.py 脚本。

使用 LoRA 微调后可以使用下面的命令加载模型:

1
2
from peft import AutoPeftModelForCausalLM
model = AutoPeftModelForCausalLM.from_pretrained("output", trust_remote_code=True)

中间 Checkpoints

除了训练了 2.6 万亿 Tokens 的 Baichuan2-7B-Base 模型,我们还提供了在此之前的另外 11 个中间 checkpoints(分别对应训练了约 0.2 ~ 2.4 万亿 Tokens)供社区研究使用(下载地址)。下图给出了这些 checkpoints 在 C-Eval、MMLU、CMMLU 三个 benchmark 上的效果变化:

openai方式调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
from flask import Flask, request, jsonify
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from transformers.generation.utils import GenerationConfig

app = Flask(__name__)

# Load the pre-trained model and tokenizer
model_name = "baichuan-inc/Baichuan2-13B-Chat"
tokenizer = AutoTokenizer.from_pretrained(model_name, use_fast=False, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(model_name, device_map="auto", torch_dtype=torch.float16, trust_remote_code=True)
#这里的torch_dtype=torch.float16可改为torch_dtype=torch.bfloat16,仅支持30系及以上显卡
model.generation_config = GenerationConfig.from_pretrained(model_name)

@app.route('/v1/chat/completions', methods=['POST'])#URL: http://127.0.0.1:8000/v1/chat/completions
def chat_completion():
try:
# Parse incoming JSON data
data = request.get_json()
messages = data.get('messages', [])
is_streaming = data.get('stream', False)

# Check if streaming is enabled
if is_streaming:#这里暂时只支持非流式输出
return jsonify({"error": "Streaming is not supported."}), 400

# Generate response using the model
response_text = generate_response(messages)

# Calculate token counts
prompt_tokens = sum(len(tokenizer.encode(msg['content'])) for msg in messages)
# 暂时不支持调节其他参数
completion_tokens = len(response_text)
total_tokens = prompt_tokens + completion_tokens

# Build the response
response_data = {
"object": "chat.completion",
"model": model_name,
"choices": [{"message": {"role": "assistant", "content": response_text}, "index": 0, "finish_reason": "stop"}],
"usage": {
"prompt_tokens": prompt_tokens,
"completion_tokens": completion_tokens,
"total_tokens": total_tokens
}
}

return jsonify(response_data)

except Exception as e:
return jsonify({"error": str(e)}), 500

def generate_response(messages):
# Generate response using the model
response = model.chat(tokenizer, messages)
return response

if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000)
本文结束 感谢您的阅读