坐标杭州,纵看大模型浪潮风起云涌,作为新时代的AI人才需要紧跟这波浪潮。

教员的思想是理论与实践结合,大模型的理论知识也喝了几壶了,但是本人至今依然难以相信自回归的训练可以让大模型拥有如此智能。

故本人想要眼见为实,使用两张A100 40G的显卡,想要训练一个0.24B左右模型参数的中文大模型,不求性能多强大,只要能涌现出智能即可。

1 基础准备

众所周知,大模型预训练需要的三板斧:数据、Tokenizer和Model。

1.1 训练数据

预训练数据:

sft数据:

1.2 Tokenizer

本来想要使用Qwen2的Tokenizer,由于Qwen2的词表15W+,这导致每一个token需要使用32位4字节进行存储,为了减少数据集的内存消耗,使用了ChatGLM3-6B的词典,大小为64798,使用np.uint16存储(减少一半内存消耗)。

1.3 Model

使用Qwen2的Model,具体设置如下:

{
  "architectures": [
    "Qwen2ForCausalLM"
  ],
  "attention_dropout": 0.0,
  "bos_token_id": 2,
  "eos_token_id": 2,
  "hidden_act": "silu",
  "hidden_size": 1024,
  "initializer_range": 0.02,
  "intermediate_size": 4096,
  "max_position_embeddings": 4096,
  "max_window_layers": 21,
  "model_type": "qwen2",
  "num_attention_heads": 8,
  "num_hidden_layers": 8,
  "num_key_value_heads": 4,
  "rms_norm_eps": 1e-06,
  "rope_theta": 1000000.0,
  "sliding_window": 4096,
  "tie_word_embeddings": false,
  "torch_dtype": "bfloat16",
  "transformers_version": "4.37.0",
  "use_cache": true,
  "use_sliding_window": false,
  "vocab_size": 64798,
  "use_flash_attn": "auto"
}

Notes:

  • 1 按需设置Tokenizer的参数,例如”bos_token_id”,”eos_token_id”, “vocab_size”需要替换为ChatGLM3-6B的token_id和vocab_size。
  • 2 使用GQA,两个q贡献同一个k,v。
  • 3 Rope base使用100W
  • 4 训练为混合精度 bf16
  • 5 tie_word_embedding: Embedding层和LM_head层不共享参数。

1.4 Trainer

Trainer的配置文件如下:

{
  "data_path": "/public/MountData/xcx/pretrained/sky_6_10",
  "data_batch": 1280,
  "sliding_window": 1024,
  "min_seq_length": 64,
  "max_seq_length": 1024,
  "cache_path": "/public/xcx/Item/Pre-Train/Qwen3/data/cache/sky_6_10.parquet",
  "eval_size": 100,


  "output_dir": "/public/xcx/Item/Pre-Train/Qwen3/outputs",
  "num_train_epochs": 1,
  "per_device_train_batch_size": 4,
  "warmup_steps": 2000,
  "weight_decay": 0.1,
  "logging_dir": "./logs",
  "logging_steps": 5,
  "save_steps": 100,
  "evaluation_strategy": "steps",
  "eval_steps": 20000000,
  "save_total_limit": 2,
  "gradient_accumulation_steps": 8,
  "seed": 42,
  "learning_rate": 3e-4,
  "lr_scheduler_type": "cosine",
  "lr_scheduler_kwargs": {"min_lr_ratio": 0.1},
  "adam_beta1": 0.9,
  "adam_beta2": 0.95,
  "bf16": true,
  "ddp_find_unused_parameters": false,
  "report_to": "tensorboard"
}

经典开源大模型的Trainer参数:

  • 1 warm_steps: 2000
  • 2 weight_decay: 0.1
  • 3 learning_rate: 3e-4
  • 4 lr_sheduler_type: cosine
  • 5 min_lr_ratio: 0.1
  • 6 AdamW beta1: 0.9 beta2: 0.95

2 训练数据处理

2.1 预训练数据处理

目的:将百度百科、wiki百科、天工数据集json格式处理为np.uint16类型大小的编码后的token parquet文件。

由于ChatGLM3没有FastTokener,故此处使用多进程编码弥补这个性能不足。

每次训练完成后,使用继续预训练,resume_from_checkpoint=True。需要将Trainer参数的num_train_epoch+1。

2.2 sft数据处理

从huggingface下载的dataset已经完成处理。

3 进度

—————————————–分割线 2024.6.26———————————————–

2024.6.26 18:00 : 直接处理baidu,wiki数据集后使用pd.read_parquet读取parquet报错为List index overflow,准备使用load_dataset进行读取.

obj = pd.read_parquet(file, engine='pyarrow').text.tolist()

2024.6.26 22:00 : load_dataset同样读取失败,故决定将原始baidu\wiki数据进行切分为3份parquet数据,具体如下:

2024.6.26 22:05 : 百科数据决定预训练顺序为baidu_0.parquet -> baidu_1.parquet -> baidu_2.parquet。

2024.6.26 22.10 : 发现4张A100 40G都为空,准备4张卡一起跑,accelerate配置文件如下:

2024.6.26 22.15 : 启动预训练程序,预训练语料为:baidu_0.parquet,baidu_0.parquet拥有1.4B左右的预训练语料,训练时间4卡大概为6~7h,等明天收菜。

—————————————–分割线 2024.6.27———————————————–

2024.6.27 10.05 : 收菜了收菜了,最低loss到2.9852,说明一开始的loss下降的是比较快的,后期需要几何倍的token数才能让loss继续下降。根据清华的理论。预训练loss才是涌现能力的关键,故准备将loss下降到2.0左右。

2024.6.27 10.10 : 4卡A100依旧空闲,开启baicu_1.parquet的预训练,token总数大概为1.3B,修改num_train_epochs为2trainer.train中设置resume_from_checkpoint=True

2024.6.27 16.40 : baidu_1.parquet文件训练完毕,发现没有设置学习率最多衰减到最大学习率的10%即3e-5,目前查看时学习率已经为1e-7,不过问题不大,之后的训练直接取消学习率衰减,直接设置为3e-5即可。有观察到loss还在继续下降,就是下降的比较缓慢,相比baidu_0.parquet 24k step从12下降到2.98baidu_1.parquet 18k从2.98下降到2.91

—————————————–分割线 2024.6.28———————————————–

2024.6.28 10:30 : 4卡A100空闲,开启baidu_2.parquet预训练,token总数大概1.3B,修改num_train_epochs为3.

2024.6.28 10:30 : 收菜了,有一些发现:

  • 1 warm_steps只作用于第一个epoch,之后的epoch wram_step不起作用。
  • 2 学习率衰减如果不需要,则请设置为lr_scheduler_type: constant,否则默认为linear.

通过学习率曲线可以看出之前我删除了lr_scheduler_type,但是默认的linear依然起作用。

loss下降情况如下,依旧观察到loss些许下降:

2024.6.28 11.00 : 开启sky_1_5.parquet的预训练,token总数大概1.35B,修改num_train_epoch=4,lr_scheduler_type:constant。

2024.6.28 20:00 : 收菜了,由于skywork的数据和百度百科数据分布不一致,出现loss的回升。此处联合MiniCPM的报告“MiniCPM:揭示端侧大语言模型的无限潜力报告读后记录”,感觉我的预料顺序设置错误,像百度百科这种优质的高质量数据集应该放在后半段进行学习,前期应该使用通用的web数据进行训练。

测试一下此时模型的性能:“介绍一下雷军?” 结果:已经能流畅生成逻辑通顺的语句了,虽然事实性很差。

2024.6.28 20.05 : 开启sky_6_10.parquet的预训练,token总数大概为1.3B,修改num_train_epoch=5。

—————————————–分割线 2024.6.29———————————————–

2024.6.29 10:45 : 收菜了,loss继续下降。

2024.6.29 11.05 : 开启sky_11_15.parquet的预训练,token总数大概为1.24B,修改num_train_epoch=6。

—————————————–分割线 2024.6.30———————————————–

2024.6.30 20:00 : 收菜了,loss下降情况如下:

2024.6.30 21:00 : 开启sky_16_20.parquet的预训练,token总数大概为1.15B,修改num_train_epoch=7.

—————————————–分割线 2024.7.1———————————————–

2024.7.1 10:00 : 收菜了,loss出现回升。

2024.7.1 10:30 : 开启sky_21_25.parquet的预训练,token总数大概为1.69B,修改num_train_epoch=8.

参考文献