Chapter 10

训练循环与 checkpoint

📌 commit 2411b02DistributedTrainer 内部

trainer.py(约 800 行)是 nanotron 的"中枢神经"。本章把训练循环和 ckpt 流程过一遍。

10.1DistributedTrainer 核心

# src/nanotron/trainer.py
class DistributedTrainer:
    def __init__(self, config):
        self.config = config
        self.parallel_context = ParallelContext(
            data_parallel_size=config.parallelism.dp,
            pipeline_parallel_size=config.parallelism.pp,
            tensor_parallel_size=config.parallelism.tp,
        )

        self.model = self._init_model()
        self.optimizer, self.lr_scheduler = self._init_optimizer_and_scheduler()
        self.loaded_checkpoint = self._load_checkpoint_if_exists()

    def train(self, dataloader_builder):
        for stage in self.config.data_stages:
            if self.current_step < stage.start_training_step:
                continue
            dataloader = dataloader_builder(stage, self.parallel_context)
            self._train_stage(dataloader)

    def _train_stage(self, dataloader):
        for batch in dataloader:
            if self.current_step >= self.config.tokens.train_steps:
                return
            self._training_step(batch)
            self.current_step += 1
            self._log_metrics()
            if self.current_step % ckpt_interval == 0:
                self._save_checkpoint()

10.2_training_step

def _training_step(self, batch):
    # 1) Optionally split batch into micro_batches
    micro_batches = split_batch(batch, self.config.tokens.batch_accumulation_per_replica)

    # 2) Forward + backward
    if self.pp_size > 1:
        loss = self.pp_engine.step(self.model, micro_batches, self.optimizer)
    else:
        loss = 0
        for mb in micro_batches:
            with self.tp_context():
                output = self.model(mb)
                local_loss = self.loss_fn(output, mb.target)
                local_loss.backward()
                loss += local_loss.item()

    # 3) DP sync gradients
    if self.dp_size > 1:
        self.sync_gradients()

    # 4) Clip + step
    torch.nn.utils.clip_grad_norm_(self.model.parameters(), max_norm=clip_grad)
    self.optimizer.step()
    self.lr_scheduler.step()
    self.optimizer.zero_grad()

10.3checkpoint 设计

nanotron 的 ckpt 格式按 (pp, tp, dp_shard) 三维存:

./checkpoints/iteration_1000/
├── config.yaml
├── pp-0-of-2/
│   ├── tp-0-of-4/
│   │   ├── optimizer/
│   │   │   └── dp-0-of-2/...     # ZeRO-1 切到 dp_rank
│   │   ├── model_weights.safetensors
│   │   └── ...
│   └── tp-1-of-4/...
├── pp-1-of-2/...
└── metadata.json

save 是 distributed save(每 rank 写自己那份),快、不阻塞。

10.4save / load

# save
trainer._save_checkpoint()
# 内部:
#   1) 每 rank 把自己那份 weight 写到对应子目录
#   2) DP 范围内只 rank 0 写 weight(其他 rank 是副本)
#   3) optimizer state 每 dp_rank 各自写

# load
trainer._load_checkpoint_if_exists()
# 自动按当前 ParallelContext 决定从哪几个文件读

10.5resume 包含

子项是否
model state
optimizer state
lr scheduler state
current step
data sampler 位置
RNG state

10.6跨 mesh resume

不像 TorchTitan dcp 支持任意 reshard,nanotron 的 ckpt 跟 (pp, tp) 结构强绑定。要换 tp_size 必须先用 tools/converter 脚本重新切。

10.7训练循环中的 hooks

nanotron 没有 NeMo / MMEngine 那种 Hook 系统,所有"切点"都直接写在 trainer.py 里(更简单但灵活度低)。常见自定义点:

10.8这章你需要带走的