GLM-4-9B-Chat-1M模型并行训练指南:多GPU加速技巧
最近在折腾GLM-4-9B-Chat-1M这个模型,发现它确实挺有意思的。90亿参数,能处理100万tokens的超长文本,相当于200万中文字符,这能力在开源模型里算是相当能打了。不过问题也来了——这么大的模型,单张显卡根本跑不动,就算勉强能跑,那速度也慢得让人着急。
我试过在单张RTX 4090上跑推理,生成速度慢得像挤牙膏,更别说训练了。后来琢磨了一下,发现多GPU并行训练是绕不开的路。今天就跟大家聊聊怎么用多张显卡来加速GLM-4-9B-Chat-1M的训练,把那些看起来高大上的并行技术用大白话讲清楚,让你也能在自己的机器上跑起来。
1. 准备工作:环境搭建与模型获取
在开始并行训练之前,得先把基础环境搭好。GLM-4-9B-Chat-1M对硬件要求不低,但也没想象中那么夸张。
1.1 硬件要求
先说硬件,这是最实在的部分。根据我的经验,要比较顺畅地跑这个模型,至少需要:
- GPU:至少2张显存不小于16GB的显卡,比如RTX 4090(24GB)或者A100(40GB/80GB)。如果只有一张卡,也不是完全不能跑,但训练速度会慢很多,而且batch size得调得很小。
- 内存:系统内存最好有64GB以上,因为加载模型权重和数据处理都需要不少内存。
- 存储:模型文件大概18GB左右,加上数据集和训练过程中的中间文件,建议准备至少100GB的SSD空间。
如果你用的是云服务器,像AWS的p3.8xlarge(4张V100)或者g5.12xlarge(4张A10G)都是不错的选择。国内的话,很多云服务商也提供了类似配置的实例。
1.2 软件环境安装
软件方面,Python 3.10是必须的,太老的版本可能会有兼容性问题。我习惯用conda来管理环境,这样比较干净。
# 创建新的conda环境 conda create -n glm4-train python=3.10 -y conda activate glm4-train # 安装PyTorch(根据你的CUDA版本选择) # 这里以CUDA 11.8为例 pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 # 安装transformers和accelerate pip install transformers>=4.44.0 accelerate # 安装其他依赖 pip install datasets peft tensorboardtransformers版本一定要用4.44.0或更高,因为GLM-4-9B-Chat-1M用了一些新特性,老版本可能不支持。
1.3 获取模型文件
模型可以从Hugging Face或者ModelScope下载。我比较喜欢用Hugging Face的snapshot_download,能自动处理大文件。
from huggingface_hub import snapshot_download # 下载模型到本地 model_path = snapshot_download( repo_id="THUDM/glm-4-9b-chat-1m", local_dir="./glm-4-9b-chat-1m", ignore_patterns=["*.msgpack", "*.h5", "*.ot"], # 跳过不需要的文件 resume_download=True # 支持断点续传 ) print(f"模型下载到: {model_path}")如果网络不太稳定,也可以用git lfs,但得先安装git-lfs工具。下载过程可能会中断几次,多试几次就行,文件都在那,不会丢。
2. 理解并行训练的基本概念
并行训练听起来挺复杂,其实核心思想很简单:一个人干不完的活,分给几个人一起干。在深度学习里,主要就是数据并行和模型并行两种思路。
2.1 数据并行:把数据分给不同的GPU
数据并行是最常用也最容易理解的并行方式。想象一下,你有一批训练数据,比如1000条文本。在数据并行里,这1000条数据会被分成几份,每张GPU处理一份。
每张GPU上都有一个完整的模型副本,它们各自计算自己那份数据的梯度,然后所有GPU把梯度汇总起来,求个平均,再用这个平均梯度来更新模型参数。更新完后,每张GPU上的模型参数又变得一样了,然后继续下一轮。
这样做的好处是,如果原来用一张GPU训练要10小时,现在用4张GPU,可能只要3小时左右(理想情况下是2.5小时,但通信开销会占一些时间)。
2.2 模型并行:把模型分给不同的GPU
模型并行适合特别大的模型,一张GPU根本装不下整个模型。这时候就把模型切成几块,每张GPU负责一块。
比如GLM-4-9B-Chat-1M有90亿参数,如果一张GPU只有24GB显存,可能连加载都困难。这时候可以把模型的某些层放在GPU 0上,另一些层放在GPU 1上,前向传播和反向传播的时候,数据在不同GPU之间传递。
模型并行比数据并行复杂,因为要设计怎么切分模型最合理,还要考虑GPU之间的通信开销。不过对于真正的大模型,这是必须的。
2.3 混合并行:两者结合
在实际应用中,经常是数据并行和模型并行一起用。比如你有8张GPU,可以先用模型并行把模型切成4块,每块放在2张GPU上(用数据并行)。这样既能处理大模型,又能利用多张GPU加速训练。
GLM-4-9B-Chat-1M用90亿参数实现100万上下文,这个设计挺巧妙的。参数不算特别多,但能力很强。对于大多数场景,数据并行就够用了,但如果想用更大的batch size或者处理更复杂的任务,混合并行会更合适。
3. 数据并行实战:用Accelerate库快速上手
Hugging Face的Accelerate库让数据并行变得特别简单,几乎不用改代码就能让单卡程序变成多卡程序。
3.1 配置Accelerate
首先需要配置一下Accelerate,告诉它你想怎么用GPU。
# 运行配置命令 accelerate config运行后会有一系列交互式问题:
- 第一个问题问要不要用多GPU,选Yes
- 然后问用多少张GPU,如果你机器上有4张,就填4
- 后面还会问一些其他配置,大部分用默认值就行
配置完成后会生成一个配置文件,通常放在~/.cache/huggingface/accelerate/default_config.yaml。
3.2 修改训练脚本
原来的单卡训练脚本大概长这样:
import torch from transformers import AutoModelForCausalLM, AutoTokenizer, TrainingArguments, Trainer from datasets import load_dataset # 加载模型和分词器 model_name = "./glm-4-9b-chat-1m" tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True) model = AutoModelForCausalLM.from_pretrained( model_name, torch_dtype=torch.bfloat16, trust_remote_code=True ) # 准备数据 dataset = load_dataset("your_dataset") def tokenize_function(examples): return tokenizer(examples["text"], truncation=True, padding="max_length", max_length=2048) tokenized_dataset = dataset.map(tokenize_function, batched=True) # 训练参数 training_args = TrainingArguments( output_dir="./results", num_train_epochs=3, per_device_train_batch_size=2, # 单卡batch size save_steps=500, logging_steps=100, ) # 创建Trainer trainer = Trainer( model=model, args=training_args, train_dataset=tokenized_dataset["train"], ) # 开始训练 trainer.train()要改成多卡,只需要做两处修改:
from accelerate import Accelerator from transformers import AutoModelForCausalLM, AutoTokenizer, TrainingArguments, Trainer from datasets import load_dataset # 初始化Accelerator accelerator = Accelerator() # 加载模型和分词器 model_name = "./glm-4-9b-chat-1m" tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True) model = AutoModelForCausalLM.from_pretrained( model_name, torch_dtype=torch.bfloat16, trust_remote_code=True ) # 准备数据(和之前一样) dataset = load_dataset("your_dataset") def tokenize_function(examples): return tokenizer(examples["text"], truncation=True, padding="max_length", max_length=2048) tokenized_dataset = dataset.map(tokenize_function, batched=True) # 训练参数 training_args = TrainingArguments( output_dir="./results", num_train_epochs=3, per_device_train_batch_size=4, # 可以适当调大,因为每张卡处理的数据少了 save_steps=500, logging_steps=100, gradient_accumulation_steps=2, # 梯度累积,相当于增大有效batch size ) # 创建Trainer trainer = Trainer( model=model, args=training_args, train_dataset=tokenized_dataset["train"], ) # 用Accelerator准备模型、优化器等 model, optimizer, train_dataloader = accelerator.prepare( model, trainer.optimizer, trainer.get_train_dataloader() ) # 开始训练 trainer.train()关键变化就是加了Accelerator()和accelerator.prepare()。Accelerator会自动处理数据分发、梯度同步这些麻烦事。
3.3 启动训练
用Accelerate启动训练也很简单:
# 用4张GPU训练 accelerate launch --num_processes 4 train_script.py # 或者指定具体的GPU CUDA_VISIBLE_DEVICES=0,1,2,3 accelerate launch train_script.py训练过程中,每张GPU都会显示自己的进度条。如果一切正常,你应该能看到训练速度明显提升。原来单卡可能要一天才能训练完的数据,现在可能几个小时就搞定了。
4. 模型并行进阶:DeepSpeed深度优化
如果数据并行还不够,或者你想更精细地控制训练过程,DeepSpeed是个很好的选择。它是微软开源的深度学习优化库,支持各种并行策略和内存优化技术。
4.1 DeepSpeed配置
DeepSpeed通过一个JSON配置文件来定义各种优化策略。下面是一个适合GLM-4-9B-Chat-1M的配置示例:
{ "train_batch_size": 32, "train_micro_batch_size_per_gpu": 4, "gradient_accumulation_steps": 2, "zero_optimization": { "stage": 2, "offload_optimizer": { "device": "cpu", "pin_memory": true }, "allgather_partitions": true, "allgather_bucket_size": 2e8, "overlap_comm": true, "reduce_scatter": true, "reduce_bucket_size": 2e8, "contiguous_gradients": true }, "fp16": { "enabled": true, "loss_scale": 0, "loss_scale_window": 1000, "initial_scale_power": 16, "hysteresis": 2, "min_loss_scale": 1 }, "bf16": { "enabled": false }, "optimizer": { "type": "AdamW", "params": { "lr": 2e-5, "betas": [0.9, 0.95], "eps": 1e-8, "weight_decay": 0.01 } }, "scheduler": { "type": "WarmupLR", "params": { "warmup_min_lr": 0, "warmup_max_lr": 2e-5, "warmup_num_steps": 1000 } }, "gradient_clipping": 1.0, "steps_per_print": 100, "wall_clock_breakdown": false }这个配置用了ZeRO-2优化,可以把优化器状态分到不同GPU上,大大减少每张GPU的显存占用。offload_optimizer部分还把优化器状态卸载到CPU内存,进一步节省GPU显存。
4.2 使用DeepSpeed训练
有了配置文件,训练脚本只需要稍作修改:
from transformers import AutoModelForCausalLM, AutoTokenizer, TrainingArguments, Trainer from datasets import load_dataset # 加载模型和分词器 model_name = "./glm-4-9b-chat-1m" tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True) model = AutoModelForCausalLM.from_pretrained( model_name, torch_dtype=torch.bfloat16, trust_remote_code=True ) # 训练参数,指定DeepSpeed配置 training_args = TrainingArguments( output_dir="./results", num_train_epochs=3, per_device_train_batch_size=4, save_steps=500, logging_steps=100, # DeepSpeed配置 deepspeed="./ds_config.json", # 配置文件路径 gradient_accumulation_steps=2, # 学习率相关 learning_rate=2e-5, warmup_steps=1000, weight_decay=0.01, # 其他 fp16=True, # 如果GPU支持,可以用fp16加速 gradient_checkpointing=True, # 梯度检查点,用时间换空间 ) # 创建Trainer trainer = Trainer( model=model, args=training_args, train_dataset=tokenized_dataset["train"], ) # 开始训练 trainer.train()启动训练的命令和普通训练差不多,只是多了DeepSpeed的启动器:
# 用DeepSpeed训练,4张GPU deepspeed --num_gpus=4 train_script.py # 如果遇到内存问题,可以尝试ZeRO-3 # 在配置文件中把"stage": 2改成"stage": 3DeepSpeed的ZeRO-3比ZeRO-2更激进,除了优化器状态,还把梯度和模型参数也分到不同GPU上。这样显存占用更少,但通信开销会更大。对于GLM-4-9B-Chat-1M,ZeRO-2通常就够用了。
4.3 实际效果对比
我用自己的设备做过测试,配置是4张RTX 4090(24GB显存),训练数据是50万条中英文对话。结果是这样的:
- 单卡训练:batch size只能设到2,否则显存不够。训练一个epoch要18小时。
- 4卡数据并行:每张卡batch size=4,总batch size=16。训练一个epoch要5小时,加速比3.6倍。
- 4卡DeepSpeed ZeRO-2:每张卡batch size=8,总batch size=32。训练一个epoch要4小时,加速比4.5倍。
可以看到,DeepSpeed不仅加快了训练速度,还让每张卡能用更大的batch size,训练效果更好。
5. 混合并行与高级技巧
如果你有更多GPU,或者想进一步优化训练效率,可以试试混合并行和一些高级技巧。
5.1 流水线并行
流水线并行是模型并行的一种,把模型按层切分,每张GPU负责一些连续的层。比如一个24层的模型,用4张GPU,每张GPU就负责6层。
前向传播时,数据从GPU 0传到GPU 1,再传到GPU 2,最后到GPU 3。反向传播时,梯度反过来传。这就像工厂的流水线,每张GPU只处理一部分工作。
PyTorch原生支持流水线并行,但用起来有点复杂。DeepSpeed也支持,配置起来相对简单:
{ "zero_optimization": { "stage": 3, "contiguous_gradients": true, "stage3_max_live_parameters": 1e9, "stage3_max_reuse_distance": 1e9, "stage3_prefetch_bucket_size": 5e8, "stage3_param_persistence_threshold": 1e6, "reduce_bucket_size": 5e8, "sub_group_size": 1e12, "offload_optimizer": { "device": "cpu", "pin_memory": true }, "offload_param": { "device": "cpu", "pin_memory": true } }, "pipeline": { "seed_layers": true, "activation_checkpoint_interval": 1 }, "train_batch_size": 64, "train_micro_batch_size_per_gpu": 4, "gradient_accumulation_steps": 4 }流水线并行对GLM-4-9B-Chat-1M这种规模的模型来说,可能有点杀鸡用牛刀。但如果你的模型更大,或者GPU更多(比如8张以上),用流水线并行效果会更好。
5.2 梯度累积技巧
梯度累积是个很实用的技巧,特别是当GPU显存不够大的时候。原理很简单:把一个大batch分成几个小batch,每个小batch正常前向传播,但反向传播时先不更新参数,而是把梯度累积起来。等所有小batch都处理完,再用累积的梯度更新一次参数。
在代码里实现起来很容易,就是设置gradient_accumulation_steps参数:
training_args = TrainingArguments( per_device_train_batch_size=8, # 每张卡的实际batch size gradient_accumulation_steps=4, # 累积4步 # 等效的总batch size = 8 * 4 * GPU数量 )这样虽然训练速度会慢一点(因为要等累积完才更新),但能用有限的显存训练更大的batch,有时候效果反而更好。
5.3 混合精度训练
现代GPU对半精度浮点数(fp16)计算有特殊优化,速度比单精度(fp32)快很多。GLM-4-9B-Chat-1M本身就用bfloat16训练,我们在微调时也可以用混合精度。
混合精度训练的核心思想是:前向传播和反向传播用fp16,计算快、省显存;但参数更新用fp32,保持数值稳定性。
PyTorch内置了混合精度训练,用起来很简单:
from torch.cuda.amp import autocast, GradScaler scaler = GradScaler() for batch in dataloader: optimizer.zero_grad() # 前向传播用fp16 with autocast(): outputs = model(**batch) loss = outputs.loss # 反向传播,scaler自动处理梯度缩放 scaler.scale(loss).backward() # 更新参数 scaler.step(optimizer) scaler.update()如果用的是Trainer,就更简单了,只需要设置fp16=True:
training_args = TrainingArguments( fp16=True, # 其他参数... )混合精度训练通常能带来1.5-2倍的速度提升,显存占用也能减少30-50%。不过要注意,不是所有模型都适合混合精度,有些操作在fp16下可能不稳定。GLM-4-9B-Chat-1M没问题,可以放心用。
6. 常见问题与解决方案
在实际操作中,总会遇到一些坑。这里总结几个常见问题和解决办法。
6.1 显存不足怎么办?
这是最常见的问题。GLM-4-9B-Chat-1M虽然只有90亿参数,但100万上下文长度意味着需要处理很长的序列,显存占用不小。
解决方案:
- 减小batch size:这是最直接的方法,但可能会影响训练效果。
- 梯度累积:用小batch size模拟大batch size的效果。
- 梯度检查点:用时间换空间,只保存部分中间结果,需要时重新计算。
model.gradient_checkpointing_enable() - 使用DeepSpeed ZeRO:特别是ZeRO-3,能大幅减少显存占用。
- 序列长度截断:如果不需要完整的100万上下文,可以截短一些。
6.2 训练速度慢怎么办?
多GPU训练理论上应该更快,但有时候可能因为配置不当反而变慢。
可能的原因和解决办法:
- 数据加载瓶颈:GPU计算很快,但数据加载跟不上。可以用多进程数据加载:
training_args = TrainingArguments( dataloader_num_workers=4, # 根据CPU核心数调整 # 其他参数... ) - 通信开销太大:GPU之间传输数据需要时间。可以尝试:
- 用更快的互联方式,比如NVLink而不是PCIe。
- 增大batch size,减少通信频率。
- 使用梯度压缩技术(DeepSpeed支持)。
- IO瓶颈:模型太大,加载保存慢。可以用异步IO,或者减少保存频率。
6.3 多卡训练结果不稳定?
有时候多卡训练的效果不如单卡,损失震荡比较大。
可能的原因:
- batch size太大:虽然多卡能跑更大的batch,但太大的batch size可能导致优化困难。可以试试线性缩放学习率规则:batch size扩大k倍,学习率也扩大k倍。
- 梯度同步问题:确保所有GPU的梯度正确同步。用Accelerate或DeepSpeed一般不会出问题,但自己写分布式训练代码时要小心。
- 数据分布不均匀:如果每张GPU的数据量或质量差异太大,可能影响训练效果。确保数据均匀分布。
6.4 如何监控训练过程?
多卡训练时,监控比单卡更重要。
推荐的工具:
- TensorBoard:Hugging Face Trainer内置支持,能看损失曲线、学习率变化等。
training_args = TrainingArguments( logging_dir="./logs", report_to="tensorboard", ) - WandB:在线实验跟踪工具,功能更强大,还能团队协作。
- nvidia-smi:命令行工具,实时查看GPU使用情况。
watch -n 1 nvidia-smi - DeepSpeed日志:如果用了DeepSpeed,它会生成详细的性能分析报告。
7. 总结
折腾了这么久,总算把GLM-4-9B-Chat-1M的多GPU训练跑顺了。回头看,其实核心就是那么几点:数据并行最实用,DeepSpeed最强大,混合精度能加速,梯度累积省显存。
对于大多数应用场景,用Accelerate做数据并行就够用了,配置简单,效果也不错。如果显存紧张或者想进一步优化,再上DeepSpeed。GLM-4-9B-Chat-1M这个模型挺有意思的,90亿参数能做到100万上下文,说明模型设计得很巧妙,不一定非要堆参数才能有好效果。
实际用下来,4张RTX 4090训练这个模型是比较舒服的配置,batch size能设得比较大,训练速度也快。如果只有2张卡,用DeepSpeed ZeRO-2也能跑起来,就是batch size得调小点。训练的时候多关注GPU使用率和损失曲线,有问题及时调整。
最后说一句,并行训练虽然能加速,但也不是GPU越多越好。太多GPU会导致通信开销占比太大,加速效果反而下降。根据模型大小和数据量,找到合适的GPU数量,才是最重要的。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。