2. 单卡训练BERT模型
任务目标
本案例使用 Megatron-LM 框架在单张 GPU 上训练一个小型 BERT 模型,目标是跑通 Megatron-LM 的基本训练流程,包括数据预处理、模型构建、训练启动与日志验证。通过本案例,你将理解 Megatron-LM 与 HuggingFace Transformers 在训练脚本编写上的核心差异。
环境与依赖
- NVIDIA GPU(显存 ≥ 8GB),CUDA 11.8+,PyTorch 1.13+
- Megatron-LM 源码:
git clone https://github.com/NVIDIA/Megatron-LM.git - 依赖安装:
pip install ninja pybind11(apex 如需使用可另行安装,本案例不强制)
数据准备
Megatron-LM 使用二进制格式的索引数据集,不能直接读取纯文本。先准备一个小的示例语料,并转换为 Megatron 所需的 .bin/.idx 格式。以下命令在 Megatron-LM 仓库根目录下执行:
# 1. 创建小型语料文件(约2000条短文本)
mkdir -p /tmp/bert_data
python -c "
import json, random
random.seed(42)
lines = []
for i in range(2000):
words = ['the', 'model', 'train', 'data', 'gpu', 'batch', 'layer', 'attention'] * random.randint(3, 8)
lines.append(' '.join(words[:random.randint(10, 30)]))
with open('/tmp/bert_data/sample.txt', 'w') as f:
f.write('\n'.join(lines))
print('raw text ready')
"
# 2. 转换为 Megatron 二进制格式
python tools/preprocess_data.py \
--input /tmp/bert_data/sample.txt \
--output-prefix /tmp/bert_data/bert_sample \
--vocab-file /path/to/bert-base-uncased-vocab.txt \
--tokenizer-type BertWordPieceLowerCase \
--split-sentences \
--workers 4
需要准备 BERT 词表文件(bert-base-uncased-vocab.txt),可从 HuggingFace 的 bert-base-uncased 仓库下载,或使用 Megatron-LM 仓库中 vocab/bert-base-uncased-vocab.txt 自带的词表。
核心训练代码
以下脚本是单卡 BERT 训练的最小完整实现,保存为 pretrain_bert_single_gpu.py,放在 Megatron-LM 仓库根目录下运行:
import os
import torch
from megatron.initialize import initialize_megatron
from megatron import get_args
from megatron.training import train
from megatron.model import BertModel
from megatron.utils import get_ltor_masks_and_position_ids
def model_provider(pre_process=True, post_process=True):
args = get_args()
model = BertModel(
num_tokentypes=2,
add_binary_head=True,
parallel_output=True,
pre_process=pre_process,
post_process=post_process
)
return model
def forward_step(data_iterator, model):
args = get_args()
tokens, labels, loss_mask, attention_mask, position_ids = next(data_iterator)
output_tensor = model(tokens, position_ids, attention_mask, labels=labels)
return output_tensor, loss_mask
def get_batch(data_iterator):
args = get_args()
keys = ['text', 'labels', 'loss_mask', 'attention_mask', 'position_ids']
dataloader = data_iterator
batch = next(dataloader)
tokens = batch['text'].cuda()
labels = batch['labels'].cuda()
loss_mask = batch['loss_mask'].cuda()
attention_mask = batch['attention_mask'].cuda()
position_ids = batch['position_ids'].cuda()
return tokens, labels, loss_mask, attention_mask, position_ids
def train_valid_test_datasets_provider(train_val_test_num_samples):
from megatron.data.bert_dataset import build_train_valid_test_datasets
args = get_args()
train_ds, valid_ds, test_ds = build_train_valid_test_datasets(
data_prefix=args.data_path,
data_impl='mmap',
splits_string='980,10,10',
train_valid_test_num_samples=train_val_test_num_samples,
max_seq_length=args.seq_length,
masked_lm_prob=0.15,
short_seq_prob=0.1,
seed=args.seed,
skip_warmup=True,
binary_head=True)
return train_ds, valid_ds, test_ds
if __name__ == '__main__':
os.environ['CUDA_VISIBLE_DEVICES'] = '0'
initialize_megatron(allow_no_cuda=False)
args = get_args()
train(train_valid_test_datasets_provider, model_provider, forward_step, get_batch)
启动训练
在 Megatron-LM 仓库根目录执行以下命令(注意替换词表路径):
bash pretrain_bert.sh # 或直接运行下方命令
python pretrain_bert_single_gpu.py \
--tensor-model-parallel-size 1 \
--pipeline-model-parallel-size 1 \
--num-layers 4 \
--hidden-size 128 \
--num-attention-heads 8 \
--seq-length 128 \
--max-position-embeddings 512 \
--micro-batch-size 4 \
--global-batch-size 16 \
--train-iters 200 \
--lr 1e-4 \
--lr-decay-iters 100 \
--lr-warmup-fraction 0.1 \
--min-lr 1e-5 \
--optimizer adam \
--data-path /tmp/bert_data/bert_sample \
--tokenizer-type BertWordPieceLowerCase \
--vocab-file /path/to/bert-base-uncased-vocab.txt \
--split 980,10,10 \
--distributed-backend nccl \
--init-method-std 0.02 \
--attention-dropout 0.1 \
--hidden-dropout 0.1 \
--weight-decay 0.01 \
--clip-grad 1.0 \
--log-interval 10 \
--save-interval 100 \
--eval-interval 50 \
--eval-iters 10 \
--fp16
验证方法与通过标准
- 训练启动验证:终端输出包含
building train/valid/test datasets ...和starting training日志,无报错退出。 - Loss 下降验证:观察
lm loss值,初始约 10~11,训练 200 步后应降至 6~7 区间(因数据为随机拼接文本,loss 不会降到极低,但趋势必须下降)。 - 检查点保存:训练完成后
--save-interval指定的目录下生成iter_0000100/等子目录,内含模型权重文件。 - 资源占用确认:通过
nvidia-smi观察 GPU 显存占用约 2~4GB,确认训练确实在 GPU 上进行。
常见问题
- 报错
KeyError: 'text':数据预处理时未加--split-sentences参数,导致数据格式不完整,重新运行预处理命令。 - OOM 显存不足:降低
--micro-batch-size至 2 或 1,或减小--hidden-size至 64。 - 词表路径错误:确保
--vocab-file指向真实存在的词表文件,否则 tokenizer 初始化失败。