中文 | English
该研究的动机是构建一个分词器,该分词器在中文、英文和代码数据上具有更高的压缩率,并覆盖100%的汉字。鉴于InternLM分词器较高的压缩率,我们决定对InternLM分词器进行了以下改进:
- 补充100%的常见汉字以训练我们自己的分词器
- 根据InternLM分词器校准我们分词器的score
- 补充InternLM分词器中没有而我们训练的分词器中有的token,如一些常见汉字和用户定义的符号
- 转换成Llama format的分词器
您可以在这里找到更多关于我们如何构建分词器的详细信息。
我们的分词器位于这里,它包含
105789
个标记,实现了100%
的汉字覆盖。
为了揭示我们分词器的有效性,我们使用以下指标将其与一些著名开源LLM的分词器进行比较:
-
**压缩率:**我们将分词器的两种压缩率与一些开源LLM的分词器进行比较:
请参考这个pthon脚本获取更多细节。 我们在中文、英文和代码测试集上评估分词器以计算压缩率,数据来源如下:
- 中文:Skywork/ChineseDomainModelingEval
- 英文:EleutherAI/pile的测试集
- 代码:来自Pile-GitHub的分割
这些测试数据在这个链接上公开可用。
-
**汉字覆盖率:**一个好的中文LLM应该覆盖更多的汉字。在我们的工作中,我们使用vocab-coverage来计算汉字的覆盖率,包括:
- 一级汉字覆盖率(FCC)包含3500个广泛使用的汉字。
- 二级汉字覆盖率(SCC)包含3000个汉字。
- 三级汉字覆盖率(TCC)包含1605个不常见的汉字。
为了获得实验结果,请运行以下命令:
cd analyze_tokenizer
# compute the compression rate
python compression_analyze.py
# compute the Chinese character coverage
python coverage_analyze.py
实验结果如下所示:
Tokenizer | FCC | SCC | TCC | Byte per Token |
Comparied Compression |
---|---|---|---|---|---|
Chatglm-6b | 99.97% | 57.47% | 2.99% | 4.2911 | 0.5303 |
Chatglm2/3-6b | 100.00% | 77.83% | 13.89% | 4.0329 | 0.5642 |
Baichuan2-7b/14b | 100.00% | 99.8% | 86.48% | 4.1827 | 0.5440 |
Internlm-7b/20b | 100.00% | 65.93% | 5.67% | 4.3133 | 0.5276 |
Qwen-7b/14b/72b | 100.00% | 100.00% | 100.00% | 4.1326 | 0.5506 |
Llama-2-7b/13b/70b | 17.29% | 0.13% | 0.00% | 2.2755 | 1.00 |
Ours | 100.00% | 100.00% | 100.00% | 4.3143 | 0.5274 |
实验结果揭示了我们的分词器在压缩率(对中文、英文和代码数据)和汉字覆盖率方面,相较于现有流行LLM的分词器的优势。
我们的模型采用了基础的Llama架构,以下是关键参数:
Setting | Description |
---|---|
parameters | 1.4B |
attention | Grouped-query Attention (GQA) |
num layers | 22 |
num attention heads | 32 |
query groups | 4 |
hidden size | 2048 |
intermediate size | 5632 |
activation func | SiLU |
max sequence length | 2048 |
我们的训练过程经过精心规划和执行,以确保模型的健壮性和灵活性:
Setting | Description |
---|---|
block size | 2048 |
per-device batch size | 8 |
gradient accumulation steps | 16 |
num device | 8 |
total batch size | 2M tokens |
max learning rate | 5e-4 |
min learning rate | 5e-5 |
warmup steps | 2000 |
learning rate schedule | cosine |
我们修改了已有的learning scheduler,更多细节可以在这里找到。
我们的模型在精心挑选的中文、英文和编程数据集上进行训练,旨在培养广泛的中英文语言理解能力:
Dataset | Split | Token (Billion) | Domain |
---|---|---|---|
ChineseWebText | Chinese | 142 | Chinese |
RefinedWeb | English | 128 | English |
Pile-arXiv | English | 38 | English |
Pile-Wiki | English | 12 | English |
Pile-GitHub | Code | 30 | Coding |
在以下两种策略的帮助下,我们能够实现per second per GPU 16k token的高吞吐量进行训练:
计算吞吐量的设置如下:
- ZeRO-1
- block_size: 2048
- per_device_train_batch_size: 8
- gradient_accumulation_steps: 16
Settings | tokens per GPU per second |
---|---|
None | CUDA OOM |
Flash Attention 2 | 13k |
torch.compile | CUDA OOM |
Flash Attention 2 + torch.compile | 16k |
为了覆盖更多的汉字,我们的分词器比TinyLlama大得多(105789 > 32000),导致吞吐量低于TinyLlama(16k < 24k)。
然而,当分词器大小相同时,我们的吞吐量与TinyLlama相当(为此将per_device_train_batch_size设置为20)。
Settings | tokens per GPU per second |
---|---|
Flash Attention 2 + torch.compile | 24k |
与利用一些复杂操作融合的TinyLlama不同,我们只通过结合torch.compile
和flash attention 2
即可实现了这一吞吐量。
我们的预训练是在配备1TB CPU内存的8x80G A100服务器上运行的。 您可以使用较少的GPU卡和内存,通过减小批量大小来适配您的硬件条件。
我们已经为预训练准备了Docker环境,该环境已经集成了flash attention 2
和torch.compile
以实现高效的预训练。Docker文件位于这里。
请参考我们的数据准备指南,了解更多关于我们如何预处理预训练数据集的细节,例如数据集的重新格式化和充分的洗牌。
Start Training (train.sh)
一旦您准备好了运行环境和数据,您可以通过以下方式启动预训练:
set -ex
export WANDB_PROJECT=hammerllm
BASE_DIR="$PWD"
DATE=$(TZ=Asia/Shanghai date +'%Y%m%d%H%M%S')
CONFIG_PATH=${BASE_DIR}/configs/hammerllm
RUN_NAME=hammerllm_torch_compile_flash_attn_2
OUTPUT_DIR=${BASE_DIR}/checkpoint/${RUN_NAME}
DATA_SEED=3407
MODEL_SEED=3407
export CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7
export TOKENIZERS_PARALLELISM=false
export WANDB_MODE=online
if [ ! -d ${OUTPUT_DIR} ]
then
mkdir -p ${OUTPUT_DIR}
fi
echo "Setting checkpoint directory to ${OUTPUT_DIR}"
MASTER_PORT=$(shuf -n 1 -i 60000-65535)
torchrun --nproc_per_node=8 --master_port ${MASTER_PORT} train.py \
--model_name_or_path ${CONFIG_PATH} \
--use_flash_attention_2 \
--use_torch_compile \
--train_file /path/to/your/tokenized/train/dataset \
--validation_files /path/to/your/tokenized/validation/dataset_1 /path/to/your/tokenized/validation/dataset_2 ... \
--preprocessing_num_workers 100 \
--block_size 2048 \
--do_train \
--do_eval \
--per_device_train_batch_size 8 \
--per_device_eval_batch_size 8 \
--gradient_accumulation_steps 16 \
--logging_steps 10 \
--max_steps 1000000 \
--warmup_steps 2000 \
--eval_steps 500 \
--save_steps 500 \
--evaluation_strategy steps \
--save_strategy steps \
--greater_is_better false \
--load_best_model_at_end false \
--ddp_find_unused_parameters false \
--remove_unused_columns false \
--save_total_limit 50 \
--learning_rate 5e-4 \
--lr_scheduler_type cosine \
--output_dir ${OUTPUT_DIR} \
--report wandb \
--run_name ${RUN_NAME} \
--bf16 \
--seed ${MODEL_SEED} \
--data_seed ${DATA_SEED} \
--deepspeed ${BASE_DIR}/configs/zero_1.json
上述shell代码是用于预训练的启动脚本,关于我们预训练代码库的详细信息可以在这里找到。
checkpoint中包含由torch.compile
引入的一些不寻常的key name,这会导致模型参数加载失败。我们已经在这里提供了转换脚本,对这些key name进行校准。
python convert_checkpoint.py --input-path <path of saved checkpoint> --output-path <path of converted transformers checkpoint>
如下代码展示了如何使用HuggingFace transformers
与我们的模型进行交互:
import torch
from transformers import AutoTokenizer, AutoModelForCausalLM
model_name = 'DataHammer/hammerllm-1.4b-222k'
text = '北京理工大学是'
tokenizer = AutoTokenizer.from_pretrained(model_name, use_fast=False)
# if your device donot support the bfloat16, you could remove it
model = AutoModelForCausalLM.from_pretrained(model_name, torch_dtype=torch.bfloat16)
input_ids = tokenizer(text, return_tensors='pt').input_ids
output = model.generate(
input_ids=input_ids.cuda(),
max_length=min(int(len(input_ids) + 100), 1024),
do_sample=True,
top_p=0.95
).tolist()
generation = tokenizer.decode(output[0])
print(generation)