Files
wehub-resource-sync 2aaeece67c
Codestyle Check / Lint (push) Has been cancelled
Codestyle Check / Check bypass (push) Has been cancelled
Pipelines-Test / Pipelines-Test (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:37:14 +08:00

1133 lines
54 KiB
Python

# Copyright (c) 2023 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import copy
import os
import random
import time
import types
from typing import Any, Dict, Optional, Union
import numpy as np
import paddle
import paddle.distributed as dist
import paddle.distributed.auto_parallel.intermediate.parallelize as parallelize
import paddle.nn as nn
from paddle.distributed import fleet
from paddle.distributed.auto_parallel._utils import _patch_grads_for_step
from paddle.profiler.utils import switch_job_schedule_profiler
from tqdm.auto import tqdm
from paddlenlp.trainer import Trainer
from ..transformers.context_parallel_utils import auto_split_sequence_dim_load_balance
from ..transformers.model_utils import clean_model_class_name, unwrap_model
from ..transformers.segment_parallel_utils import auto_split_inputs_sequence_dim
from ..utils.batch_sampler import DistributedBatchSampler as NlpDistributedBatchSampler
from ..utils.env import (
PREFIX_CHECKPOINT_DIR,
SCALER_NAME,
SCHEDULER_NAME,
TRAINER_STATE_NAME,
TRAINING_ARGS_NAME,
)
from ..utils.log import logger
from .argparser import strtobool
from .auto_training_args import AutoTrainingArguments
from .trainer_callback import TrainerState
from .trainer_utils import ( # set_hyrbid_parallel_seed,
ShardingOption,
TrainOutput,
_exec_mode_guard,
check_auto_parallel_pipeline_support,
get_last_checkpoint,
get_pp_schedule,
has_length,
speed_metrics,
)
from .utils.ckpt_converter import CheckpointConverter
from .utils.helper import distributed_file, distributed_isfile # nested_truncate,
try:
from ..quantization.quantization_linear import QuantizationLinear
except:
QuantizationLinear = None
MODEL_NAME = "model"
OPTIMIZER_NAME = "optimizer"
DIST_CKPT_PATH = "dist_ckpt"
DIST_MODEL_PATH = "dist_model"
FREE_SVAE_LOAD_KEY_PATTERNS = ["learning_rate_", "gradient_merge_", "@GRAD@MERG", "eager_tmp"]
class AutoTrainer(Trainer):
def __init__(self, *args, **kwargs):
if kwargs.get("args", None) is not None and kwargs["args"].to_static:
if kwargs.get("criterion", None) is None:
def loss_func(loss, outputs):
return loss
kwargs.update({"criterion": loss_func})
self.auto_dist_config = kwargs.pop("auto_dist_config", None)
model = kwargs.get("model", None)
self.model_type = kwargs.pop("model_type", None)
assert model is not None
if kwargs.get("args", None) is not None and kwargs["args"].use_intermediate_api:
if not parallelize.has_parallelized_model:
model, self.auto_dist_config = self.parallel_model(model, kwargs["args"])
kwargs["model"] = model
else:
assert kwargs.get(
"auto_dist_config", None
), "if use AutoTrainer.parallel_model , auto_dist_config obtained from parallel_model should be passed to AutoTrainer "
self.auto_dist_config = kwargs.pop("auto_dist_config")
model = kwargs["model"]
for param in model.parameters():
# NOTE(zhangwl):in pipeline mode , param may be initialized before while delete init_func, but param is still not is_initialized
if not param._is_initialized() and param._init_func is not None:
param.initialize()
kwargs["model"] = model
super().__init__(*args, **kwargs)
assert self.args.enable_auto_parallel
self.global_mesh = fleet.auto.get_mesh()
self.comm_group_in_pp = fleet.get_hybrid_communicate_group().get_pipe_parallel_group()
if self.args.pipeline_parallel_degree > 1 and check_auto_parallel_pipeline_support(self.model_type):
self.pp_schedule = get_pp_schedule(
model,
self.model_type,
self.args.n_microbatches,
self.criterion,
self.args.pipeline_schedule_mode,
self.args.pipeline_parallel_degree,
self.comm_group_in_pp,
)
self._in_pir_mode = paddle.base.framework.get_flags("FLAGS_enable_pir_api")["FLAGS_enable_pir_api"]
@classmethod
def parallel_model(cls, model, training_args: AutoTrainingArguments):
"""
Parallelize the model from a single card version to a distributed version.
Args:
model (paddle.nn.Layer): the model to be parallelized.
training_args (AutoTrainingArguments) : Training arguments which contain distributed information
Returns:
the model after parallelize and config contains distributed strategy
"""
if not training_args.use_intermediate_api:
return model, None
assert model is not None
for param in model.parameters():
if param._is_initialized():
logger.warning(
"intermediate_api needs lazy init because if param init before parallelize_model ,"
+ " param will be allocated the full amount of memory"
+ " We recommend reallocating memory after paralleliz-model to reduce the peak of memory allocation"
)
auto_dist_degree = {
"tensor_parallel": training_args.tensor_parallel_degree > 1,
"sequence_parallel": training_args.sequence_parallel,
"pipeline_parallel": training_args.pipeline_parallel_degree > 1,
"data_sharding_parallel": training_args.dataset_world_size > 1,
"sharding": training_args.sharding,
"sharding_mesh_dim": training_args.sharding_parallel_mesh_dimension,
"context_parallel": training_args.context_parallel_degree > 1 or training_args.sep_parallel_degree > 1,
}
auto_dist_config = model._generate_auto_dist_config(auto_dist_degree)
model = parallelize.parallelize_model(
model,
config=auto_dist_config,
)
return model, auto_dist_config
def _nested_gather(self, tensors):
"""
Gather value of `tensors` (tensor or list/tuple of nested tensors) and convert them to numpy before
concatenating them to `gathered`
"""
with _exec_mode_guard("dynamic"):
if isinstance(tensors, paddle.Tensor):
tr_loss = tensors._local_value() if tensors.is_dist() else tensors
else:
tr_loss = paddle.to_tensor([tensors])
if self.args.pipeline_parallel_degree <= 1:
return super()._nested_gather(tr_loss)
paddle.distributed.broadcast(tr_loss, src=self.comm_group_in_pp.ranks[-1], group=self.comm_group_in_pp)
return super()._nested_gather(tr_loss)
def _wrap_model(self, model, training=True):
return model
def _get_meshes_for_loader(self):
def _get_mesh(pp_idx=0):
return self.global_mesh.get_mesh_with_dim("pp")[pp_idx]
# Note(lizhiyu): If the values returned by `DataLoader` don't have the format `[images, labels]`,
# error may occurs here.
meshes = []
meshes.append(_get_mesh(0))
if self.args.pipeline_parallel_degree > 1:
meshes.append(_get_mesh(self.args.pipeline_parallel_degree - 1))
return meshes
def _wrap_for_dist_loader(self, train_dataloader, dense_tensor_idx=None):
self.dense_tensor_idx = dense_tensor_idx
dist_loader = dist.shard_dataloader(
dataloader=train_dataloader,
meshes=self._get_meshes_for_loader(),
shard_dims="dp",
dense_tensor_idx=dense_tensor_idx,
)
return dist_loader
def _wrap_for_auto(self, model, train_dataloader):
logger.info(f"Wrapping model for auto parallel using intermediate api {self.args.use_intermediate_api} ")
dist_loader = self._wrap_for_dist_loader(train_dataloader)
if self.args.use_intermediate_api:
assert self.auto_dist_config is not None
self.optimizer = parallelize.parallelize_optimizer(
self.optimizer,
config=self.auto_dist_config,
)
else:
sharding_parallel_mesh_dimension = self.args.sharding_parallel_mesh_dimension
if ShardingOption.SHARD_OP in self.args.sharding:
self.optimizer = dist.shard_optimizer(
self.optimizer,
dist.ShardingStage1(sharding_mesh_dim=sharding_parallel_mesh_dimension),
self.args.gradient_accumulation_steps,
)
elif ShardingOption.SHARD_GRAD_OP in self.args.sharding:
self.optimizer = dist.shard_optimizer(
self.optimizer,
dist.ShardingStage2(sharding_mesh_dim=sharding_parallel_mesh_dimension),
self.args.gradient_accumulation_steps,
)
elif ShardingOption.FULL_SHARD in self.args.sharding:
self.optimizer = dist.shard_optimizer(
self.optimizer,
dist.ShardingStage3(sharding_mesh_dim=sharding_parallel_mesh_dimension),
self.args.gradient_accumulation_steps,
)
else:
self.optimizer = dist.shard_optimizer(self.optimizer, None, self.args.gradient_accumulation_steps)
if (
hasattr(self.optimizer, "_enable_tensor_fusion")
and "enable_tensor_fusion" in self.args.sharding_parallel_config
):
self.optimizer._enable_tensor_fusion()
if (
hasattr(self.optimizer, "_enable_sharding_overlap")
and "enable_overlap" in self.args.sharding_parallel_config
):
self.optimizer._enable_sharding_overlap(model)
if self.args.to_static:
unified_strategy = dist.Strategy()
unified_strategy._from_legacy_strategy(self.args.strategy)
# same logic as autocast_smart_context_manager() in trainer.py
if self.enable_autocast_context_manager:
unified_strategy.amp.custom_black_list.extend(["reduce_sum", "c_softmax_with_cross_entropy"])
if self.args.fp16_opt_level == "O2":
unified_strategy.amp.custom_white_list.extend(["lookup_table", "lookup_table_v2"])
# dist.to_static() obtains the input spec information through next(dataloader), but this has side effects
# on the passed-in dataloader, altering the state of the sampler of the dataloader. In some cases, once
# the state of the sampler is changed, it cannot be reverted. Therefore, a temporary dataloader is
# constructed here to avoid side effects on the dataloader used for actual training.
temp_loader = self._wrap_for_dist_loader(self.get_train_dataloader())
model = dist.to_static(model, temp_loader, self.criterion, self.optimizer, strategy=unified_strategy)
self.model_wrapped = model
return model, dist_loader
def _wrap_amp_model(self, args, model):
logger.info("Using half precision")
self.amp_dtype = "float16" if self.args.fp16 else "bfloat16"
if self.args.fp16_opt_level == "O2":
paddle.amp.decorate(
models=model,
level=self.args.fp16_opt_level,
dtype=self.amp_dtype,
master_grad=self.args.amp_master_grad,
excluded_layers=QuantizationLinear,
)
self.enable_autocast_context_manager = True
if args.to_static:
return
self.do_grad_scaling = True if self.args.fp16 else False
self.scaler = dist.shard_scaler(paddle.amp.GradScaler(init_loss_scaling=self.args.scale_loss))
def _get_item_from_loss(self, loss):
if isinstance(loss, paddle.Tensor):
if loss.is_dist():
return loss._local_value().item() if loss._is_initialized() else 0.0
else:
return loss.item() if loss._is_initialized() else 0.0
else:
return loss
def _split_batches_for_accumulation(self, inputs):
if self.args.gradient_accumulation_steps == 1:
return [inputs]
if self.args.to_static and self.args.pipeline_parallel_degree > 1:
return [inputs]
if self.args.to_static and self._in_pir_mode and self.args.gradient_accumulation_steps > 1:
return [inputs]
global_micro_batchs = [{} for i in range(self.args.gradient_accumulation_steps)]
assert isinstance(inputs, dict)
def split_dtensor_by_axis(dtensor, axis=0):
if not dtensor._is_initialized():
return dtensor.split(self.args.gradient_accumulation_steps, axis=axis)
micro_batch_shape = dtensor.shape
micro_batch_shape[axis] = int(dtensor.shape[axis] / self.args.gradient_accumulation_steps)
global_micro_batchs = [
paddle.zeros(micro_batch_shape, dtype=dtensor.dtype)
for _ in range(self.args.gradient_accumulation_steps)
]
global_micro_batchs = [
dist.shard_tensor(b, dtensor.process_mesh, dtensor.placements) for b in global_micro_batchs
]
local_micro_batchs = dtensor._local_value().split(self.args.gradient_accumulation_steps, axis=axis)
for local_micro_batch, global_micro_batch in zip(local_micro_batchs, global_micro_batchs):
paddle.assign(local_micro_batch, global_micro_batch._local_value())
return global_micro_batchs
skip_next_i = False
for i, (key, dtensors) in enumerate(inputs.items()):
if skip_next_i:
skip_next_i = False
continue
if isinstance(dtensors, paddle.Tensor):
if self.dense_tensor_idx is not None and self.dense_tensor_idx[i] != []:
next_dtensor = dtensors[i + 1]
if isinstance(next_dtensor, paddle.Tensor):
next_dtensor_list = (
paddle.prod(next_dtensor, axis=-1) if len(next_dtensor.shape) != 1 else next_dtensor
)
global_datas = dtensors.split(next_dtensor_list.cast("int64").tolist(), axis=0)
for index in range(self.args.gradient_accumulation_steps):
tensor_list = []
for offset in range(self.args.per_device_train_batch_size):
tensor_list.append(
global_datas[index * self.args.per_device_train_batch_size + offset]
)
concat_tensor = paddle.concat(tensor_list, axis=0)
global_micro_batchs[index].update({key: [concat_tensor]})
global_datas_next = next_dtensor.split(self.args.gradient_accumulation_steps, axis=0)
for index, data in enumerate(global_datas):
global_micro_batchs[index].update({key: data})
elif isinstance(next_dtensor, int):
global_datas = dtensors.split(next_dtensor, axis=0)
for index, data in enumerate(global_datas):
global_micro_batchs[index].update({key: data})
for index in range(self.args.gradient_accumulation_steps):
global_micro_batchs[index].update({key: next_dtensor})
else:
raise ValueError(f"unsupported split dense_tensor with type: {type(next_dtensor)}")
skip_next_i = True
else:
mesh, placements = dtensors.process_mesh, dtensors.placements
global_datas = split_dtensor_by_axis(dtensors, 0)
for index, data in enumerate(global_datas):
global_micro_batchs[index].update({key: dist.reshard(data, mesh, placements)})
elif isinstance(dtensors, (list, tuple)):
if len(dtensors) == 0:
for j in range(self.args.gradient_accumulation_steps):
global_micro_batchs[j].update({key: []})
else:
skip_next_j = False
for j, dtensor in enumerate(dtensors):
if skip_next_j:
skip_next_j = False
continue
if isinstance(dtensor, paddle.Tensor):
if self.dense_tensor_idx is not None and j in self.dense_tensor_idx[i]:
next_dtensor = dtensors[j + 1]
if isinstance(next_dtensor, paddle.Tensor):
next_dtensor_list = (
paddle.prod(next_dtensor, axis=-1)
if len(next_dtensor.shape) != 1
else next_dtensor
)
global_datas = dtensor.split(next_dtensor_list.cast("int64").tolist(), axis=0)
for index in range(self.args.gradient_accumulation_steps):
tensor_list = []
for offset in range(self.args.per_device_train_batch_size):
tensor_list.append(
global_datas[index * self.args.per_device_train_batch_size + offset]
)
concat_tensor = paddle.concat(tensor_list, axis=0)
if key in global_micro_batchs[index].keys():
global_micro_batchs[index][key].append(concat_tensor)
else:
global_micro_batchs[index].update({key: [concat_tensor]})
global_datas_next = next_dtensor.split(
self.args.gradient_accumulation_steps, axis=0
)
for index, data in enumerate(global_datas_next):
if key in global_micro_batchs[index].keys():
global_micro_batchs[index][key].append(data)
else:
global_micro_batchs[index].update({key: [data]})
elif isinstance(next_dtensor, int):
global_datas = dtensor.split(next_dtensor, axis=0)
for index, data in enumerate(global_datas):
if key in global_micro_batchs[index].keys():
global_micro_batchs[index][key].append(data)
else:
global_micro_batchs[index].update({key: [data]})
for index in range(self.args.gradient_accumulation_steps):
if key in global_micro_batchs[index].keys():
global_micro_batchs[index][key].append(next_dtensor)
else:
global_micro_batchs[index].update({key: next_dtensor})
else:
raise ValueError(f"unsupported split dense_tensor with type: {type(next_dtensor)}")
skip_next_j = True
else:
mesh, placements = dtensor.process_mesh, dtensor.placements
global_datas = split_dtensor_by_axis(dtensor, 0)
for index, data in enumerate(global_datas):
if key in global_micro_batchs[index].keys():
global_micro_batchs[index][key].append(dist.reshard(data, mesh, placements))
else:
global_micro_batchs[index].update(
{key: [dist.reshard(data, mesh, placements)]}
)
else:
raise ValueError(f"unsupported type: {type(dtensor)}")
else:
raise ValueError(f"unsupported type: {type(dtensors)}")
return global_micro_batchs
def _inner_training_loop(
self,
args,
model,
train_dataloader,
len_dataloader,
max_steps,
num_train_epochs,
num_update_steps_per_epoch,
num_train_samples,
resume_from_checkpoint,
ignore_keys_for_eval,
):
start_time = time.time()
self._globalstep_last_start_time = time.time()
self.state.epoch = 0
epochs_trained = 0
steps_trained_in_current_epoch = 0
steps_trained_progress_bar = None
# Check if continuing training from a checkpoint
if (
resume_from_checkpoint is not None
and distributed_isfile(os.path.join(resume_from_checkpoint, TRAINER_STATE_NAME))
and not self.args.ignore_load_lr_and_optim
):
self.state = TrainerState.load_from_json(
distributed_file(os.path.join(resume_from_checkpoint, TRAINER_STATE_NAME))
)
if self.args.world_size > 1:
global_step_list = []
paddle.distributed.all_gather(
global_step_list, paddle.to_tensor([self.state.global_step], dtype="int64")
)
assert (
paddle.sum(paddle.stack(global_step_list) - global_step_list[0]) == 0
), f"Error, get different global step, please check! step list: {[x.item() for x in global_step_list]}"
epochs_trained = self.state.global_step // num_update_steps_per_epoch
if not args.ignore_data_skip:
steps_trained_in_current_epoch = self.state.global_step % (num_update_steps_per_epoch)
else:
steps_trained_in_current_epoch = 0
logger.info(" Continuing training from checkpoint, will skip to saved global_step")
logger.info(f" Continuing training from epoch {epochs_trained}")
logger.info(f" Continuing training from global step {self.state.global_step}")
if not args.ignore_data_skip:
logger.info(
f" Will skip the first {epochs_trained} epochs then the first {steps_trained_in_current_epoch} "
"batches in the first epoch. If this takes a lot of time, you can add the `--ignore_data_skip` "
"flag to your launch command, but you will resume the training on data already seen by your model."
)
if self.is_local_process_zero() and not args.disable_tqdm:
steps_trained_progress_bar = tqdm(total=steps_trained_in_current_epoch)
steps_trained_progress_bar.set_description("Skipping the first batches")
epoch_iterator = train_dataloader
# steps_in_epoch = len(epoch_iterator)
steps_in_epoch = (
len(epoch_iterator) if len_dataloader is not None else args.max_steps * args.gradient_accumulation_steps
)
if len_dataloader is not None:
if self.args.gradient_accumulation_steps > len(epoch_iterator):
logger.warning(
f"changing accumulation step from `{self.args.gradient_accumulation_steps}` to `{len(epoch_iterator)}` to avoid, cross epoch accumulate"
)
self.args.gradient_accumulation_steps = len(epoch_iterator)
self.callback_handler.model = self.model
self.callback_handler.optimizer = self.optimizer
self.callback_handler.lr_scheduler = self.lr_scheduler
self.callback_handler.train_dataloader = train_dataloader
self.state.max_steps = int(max_steps)
self.state.num_train_epochs = num_train_epochs
self.state.is_local_process_zero = self.is_local_process_zero()
self.state.is_world_process_zero = self.is_world_process_zero()
self.control = self.callback_handler.on_train_begin(args, self.state, self.control)
tr_loss = paddle.to_tensor(0.0)
self._total_loss_scalar = 0.0
self._globalstep_last_logged = self.state.global_step
if self.args.device == "npu" and self.args.flatten_param_grads:
from .plugins.npu_plugin import npu_accelerate_plugin
npu_accelerate_plugin(self.optimizer)
model, dist_loader = self._wrap_for_auto(model, train_dataloader)
if (
dist.in_auto_parallel_align_mode()
): # When in auto parallel align mode, patching the optimizer step function
orig_step = (
self.optimizer.step.__func__ if hasattr(self.optimizer.step, "__func__") else self.optimizer.step
)
decorator = _patch_grads_for_step(amp_master_grad=self.args.amp_master_grad)
new_step = decorator(orig_step)
self.optimizer.__dict__["step"] = types.MethodType(new_step, self.optimizer)
train_dataloader = dist_loader()
if resume_from_checkpoint is not None:
self._load_from_checkpoint(resume_from_checkpoint)
self.timers and self.timers("read-data").start()
for epoch in range(epochs_trained, num_train_epochs):
step_control = 0 # used in loop control, reset to 0 after every step
self.control = self.callback_handler.on_epoch_begin(args, self.state, self.control)
# read global-batch from dist_loader
for step, inputs in enumerate(train_dataloader):
self.timers and self.timers("read-data").stop()
os.environ["TRAINER_GLOBAL_STEP"] = str(self.state.global_step)
self.callback_handler.on_load_data_end(args, self.state, self.control, inputs=inputs)
# Skip past any already trained steps if resuming training
# We use consumed_samples to reset the status
if isinstance(train_dataloader._dataloader, paddle.io.DataLoader) and isinstance(
train_dataloader._dataloader.batch_sampler, NlpDistributedBatchSampler
):
if step == 0:
if steps_trained_progress_bar is not None:
steps_trained_progress_bar.update(steps_trained_in_current_epoch)
steps_trained_progress_bar.close()
steps_trained_progress_bar = None
self._load_rng_state(resume_from_checkpoint)
step += steps_trained_in_current_epoch
elif steps_trained_in_current_epoch > 0:
steps_trained_in_current_epoch -= 1
if steps_trained_progress_bar is not None:
steps_trained_progress_bar.update(1)
if steps_trained_in_current_epoch == 0:
self._load_rng_state(resume_from_checkpoint)
self.timers and self.timers("read-data").start()
continue
elif steps_trained_progress_bar is not None:
steps_trained_progress_bar.close()
steps_trained_progress_bar = None
inputs_list = self._split_batches_for_accumulation(inputs)
if self.args.to_static:
schedule_start_step = self.args.job_schedule_profiler_start
schedule_end_step = self.args.job_schedule_profiler_end
if schedule_start_step >= 0:
switch_job_schedule_profiler(model, step, schedule_start_step, schedule_end_step)
for inputs in inputs_list:
if step_control % args.gradient_accumulation_steps == 0:
self.control = self.callback_handler.on_step_begin(args, self.state, self.control)
self.timers and self.timers("forward-backward").start()
if self.args.sep_parallel_degree > 1 and self.args.split_inputs_sequence_dim:
inputs = auto_split_inputs_sequence_dim(inputs)
if self.args.context_parallel_degree > 1 and self.args.split_inputs_sequence_dim:
inputs = auto_split_sequence_dim_load_balance(inputs)
tr_loss_step = self.training_step(model, inputs)
with _exec_mode_guard("dynamic"):
tr_loss += tr_loss_step
disable_accumulation = False
if self.args.pipeline_parallel_degree > 1 and self.args.to_static:
disable_accumulation = True
if self.args.to_static and self._in_pir_mode and self.args.gradient_accumulation_steps > 1:
disable_accumulation = True
# disable_accumulation = self.args.to_static
if (step_control + 1) % args.gradient_accumulation_steps == 0 or (
# last step in epoch but step is always smaller than gradient_accumulation_steps
steps_in_epoch <= args.gradient_accumulation_steps
and (step + 1) == steps_in_epoch
or disable_accumulation
):
self.timers and self.timers("forward-backward").stop()
self.timers and self.timers("optimizer-step").start()
if self.args.gradient_accumulation_steps > 1 and self._enable_delay_scale_loss():
tr_loss /= self.args.gradient_accumulation_steps
# Optimizer step
self.callback_handler.on_optimizer_begin(
args, self.state, self.control, scaler=self.scaler if self.do_grad_scaling else None
)
self.optimizer_step()
self.timers and self.timers("optimizer-step").stop()
self.callback_handler.on_optimizer_end(
args, self.state, self.control, scaler=self.scaler if self.do_grad_scaling else None
)
self.state.global_step += 1
self.state.epoch = epoch + (step + 1) / steps_in_epoch
self.control = self.callback_handler.on_step_end(args, self.state, self.control)
self._maybe_log_save_evaluate(tr_loss, model, epoch, ignore_keys_for_eval, inputs=inputs)
self._print_timer()
step_control = 0
else:
self.control = self.callback_handler.on_substep_end(args, self.state, self.control)
step_control += 1
if self.control.should_epoch_stop or self.control.should_training_stop:
break
self.timers and self.timers("read-data").start()
if step < 0:
logger.warning(
f"There seems to be not a single sample in your epoch_iterator, stopping training at step"
f" {self.state.global_step}! This is expected if you're using an IterableDataset and set"
f" num_steps ({self.state.max_steps}) higher than the number of available samples."
)
self.control.should_training_stop = True
self.control = self.callback_handler.on_epoch_end(args, self.state, self.control)
self._maybe_log_save_evaluate(tr_loss, model, epoch, ignore_keys_for_eval, inputs=inputs)
if self.control.should_training_stop:
break
if args.past_index and hasattr(self, "_past"):
# Clean the state at the end of training
delattr(self, "_past")
logger.info("\nTraining completed. \n")
self._total_loss_scalar += self._get_item_from_loss(tr_loss)
train_loss = self._total_loss_scalar / self.state.global_step
metrics = speed_metrics("train", start_time, num_samples=num_train_samples, num_steps=self.state.max_steps)
metrics["train_loss"] = train_loss
self.is_in_train = False
self._memory_tracker.stop_and_update_metrics(metrics)
self.log(metrics)
self.control = self.callback_handler.on_train_end(args, self.state, self.control)
return TrainOutput(self.state.global_step, train_loss, metrics)
def _get_train_sampler(self) -> Optional[paddle.io.Sampler]:
if self.train_dataset is None or not has_length(self.train_dataset):
return None
total_batch_size_per_acc_step = self.args.per_device_train_batch_size * self.args.dataset_world_size
total_batch_size = total_batch_size_per_acc_step * self.args.gradient_accumulation_steps
return paddle.io.BatchSampler(
dataset=self.train_dataset,
shuffle=True,
batch_size=total_batch_size,
drop_last=self.args.dataloader_drop_last,
)
def compute_loss(self, model, inputs, return_outputs=False):
"""
How the loss is computed by Trainer. By default, all models return the loss in the first element.
Subclass and override for custom behavior.
"""
if self.criterion is not None:
if "labels" in inputs:
labels = inputs.pop("labels")
elif "start_positions" in inputs and "end_positions" in inputs:
labels = (inputs.pop("start_positions"), inputs.pop("end_positions"))
elif self.args.label_names is not None:
labels = []
for label in self.label_names:
labels.append(inputs.pop(label))
labels = tuple(labels)
elif "generator_labels" in inputs:
labels = inputs["generator_labels"]
else:
labels = None
outputs = model(**inputs)
if self.criterion is not None:
def to_list(value):
if value is None:
return value
if isinstance(value, (list, tuple)):
return list(value)
return [value]
criterion_inputs = to_list(outputs)
criterion_labels = to_list(labels)
loss = self.criterion(*(criterion_inputs + criterion_labels))
outputs = (loss, outputs)
# Save past state if it exists
# TODO: this needs to be fixed and made cleaner later.
if self.args.past_index >= 0:
self._past = outputs[self.args.past_index]
# We don't use .loss here since the model may return tuples instead of ModelOutput.
loss = outputs["loss"] if isinstance(outputs, dict) else outputs
if isinstance(outputs, dict):
loss = outputs["loss"]
elif isinstance(outputs, tuple):
loss = outputs[0]
else:
loss = outputs
return (loss, outputs) if return_outputs else loss
def compute_pipeline_loss(self, model, inputs, return_outputs=False):
"""
How the loss is computed by Trainer. By default, all models return the loss in the first element.
Subclass and override for custom behavior.
"""
if self.criterion is not None:
if "labels" in inputs:
labels = inputs.pop("labels")
elif "start_positions" in inputs and "end_positions" in inputs:
labels = (inputs.pop("start_positions"), inputs.pop("end_positions"))
elif self.args.label_names is not None:
labels = []
for label in self.label_names:
labels.append(inputs.pop(label))
labels = tuple(labels)
elif "generator_labels" in inputs:
labels = inputs["generator_labels"]
else:
labels = None
pp_rank = self.comm_group_in_pp.rank
losses = []
if pp_rank == 0: # 第一个pp_stage,参数传入数据流
self.pp_schedule.step(**inputs) # 最后的pp_stage,参数传入label, 并输出loss
elif pp_rank == self.args.pipeline_parallel_degree - 1:
self.pp_schedule.step(target=labels, losses=losses)
else:
self.pp_schedule.step()
final_loss = None
if len(losses) != 0:
final_loss = paddle.stack(losses).mean()
return final_loss
def dynamic_auto_parallel_pipeline_training(
self, model: nn.Layer, inputs: Dict[str, Union[paddle.Tensor, Any]]
) -> paddle.Tensor:
assert self.args.pipeline_parallel_degree > 1, "pipeline_parallel_degree must be greater than 1."
assert check_auto_parallel_pipeline_support(
self.model_type
), "dynamic auto_parallel pipeline only supports special models"
with self.autocast_smart_context_manager():
loss = self.compute_pipeline_loss(model, inputs)
return loss
def dynamic_training(self, model: nn.Layer, inputs: Dict[str, Union[paddle.Tensor, Any]]) -> paddle.Tensor:
if self.args.pipeline_parallel_degree > 1 and check_auto_parallel_pipeline_support(self.model_type):
return self.dynamic_auto_parallel_pipeline_training(model, inputs)
with self.autocast_smart_context_manager():
loss = self.compute_loss(model, inputs)
if loss is not None and self.args.gradient_accumulation_steps > 1 and not self._enable_delay_scale_loss():
loss = loss / self.args.gradient_accumulation_steps
if self.do_grad_scaling:
self.scaler.scale(loss).backward()
else:
loss.backward()
return loss
def static_training(self, model: nn.Layer, inputs: Dict[str, Union[paddle.Tensor, Any]]) -> paddle.Tensor:
input_ids, labels = tuple(inputs.values())
loss = model(input_ids, labels)
if loss is not None and self.args.gradient_accumulation_steps > 1 and not self._enable_delay_scale_loss():
loss = loss / self.args.gradient_accumulation_steps
return loss
def training_step(self, model: nn.Layer, inputs: Dict[str, Union[paddle.Tensor, Any]]) -> paddle.Tensor:
model.train()
inputs = self._prepare_inputs(inputs)
if not self.args.to_static:
loss = self.dynamic_training(model, inputs)
else:
loss = self.static_training(model, inputs)
if isinstance(loss, paddle.Tensor):
return loss.detach() if loss._is_initialized() else float(0.0)
elif isinstance(loss, np.ndarray):
return np.sum(loss)
elif loss is None:
return float(0.0)
else:
return float(loss)
def optimizer_step(self):
if not self.args.to_static:
optimizer_was_run = True
if self.do_grad_scaling:
scale_before = paddle.assign(self.scaler._scale)
self.scaler.step(self.optimizer)
self.scaler.update()
scale_after = self.scaler._scale
# Compatible with paddlepaddle 2.6.0 using typo word.
if hasattr(self.scaler, "_cache_founf_inf"):
optimizer_was_run = not self.scaler._cache_founf_inf
else:
optimizer_was_run = not self.scaler._cache_found_inf
if not optimizer_was_run:
scale_before_value = scale_before.cpu().numpy()
scale_after_value = scale_after.cpu().numpy()
logger.warning(
f"optimizer not run, scale_before: {scale_before_value[0]}, scale_after: {scale_after_value[0]}"
)
else:
self.optimizer.step()
if optimizer_was_run:
self.lr_scheduler.step()
self.optimizer.clear_grad()
else:
# TODO: support optimizer_was_run in static mode
self.lr_scheduler.step()
def _maybe_log_save_evaluate(self, tr_loss, model, epoch, ignore_keys_for_eval, **kwargs):
with _exec_mode_guard("dynamic"):
super()._maybe_log_save_evaluate(tr_loss, model, epoch, ignore_keys_for_eval, **kwargs)
def _save_model(self):
if not self.args.to_static:
return
with _exec_mode_guard("static"):
output_dir = f"{self.args.output_dir}/{DIST_MODEL_PATH}"
os.makedirs(output_dir, exist_ok=True)
logger.info(f"Saving model files into {output_dir}")
model_file = os.path.join(output_dir, "rank_" + str(paddle.distributed.get_rank()) + ".pd_dist_model")
if os.path.exists(model_file):
os.remove(model_file)
paddle.save(self.model_wrapped.dist_main_program("train"), model_file)
def _save_checkpoint(self, model, metrics=None):
# Save model checkpoint
checkpoint_folder = f"{PREFIX_CHECKPOINT_DIR}-{self.state.global_step}"
run_dir = self.args.output_dir
output_dir = f"{run_dir}/{checkpoint_folder}"
if self.args.should_save or self.args.should_save_model_state:
os.makedirs(output_dir, exist_ok=True)
if self.args.should_save:
logger.info(f"Saving checkpoinit files into {output_dir}")
if self.args.should_save_model_state:
if self.args.to_static:
opt_state_dict = {
key: value
for key, value in model.state_dict("opt").items()
if not any(keyword in key for keyword in FREE_SVAE_LOAD_KEY_PATTERNS)
}
model_state_dict = model.state_dict("param")
if self.args.should_save_model_with_tensor_fusion:
model_state_dict = self._convert_state_dict_for_saving_tensor_fusion_ckpt(model_state_dict)
opt_state_dict = self._convert_state_dict_for_saving_tensor_fusion_ckpt(opt_state_dict)
state_dict = {
MODEL_NAME: model_state_dict,
OPTIMIZER_NAME: opt_state_dict,
}
else:
optim_state_dict = self.optimizer.state_dict()
optim_state_dict.pop("LR_Scheduler", None)
opt_state_keys = ["_moment1_0", "_moment2_0", "_beta1_pow_acc_0", "_beta2_pow_acc_0"]
for p_name, p in model.state_dict().items():
if paddle.distributed.get_rank() not in p.process_mesh.process_ids:
var_name = p.name
for key in opt_state_keys:
if (
var_name + key in optim_state_dict
and not optim_state_dict[var_name + key].is_dist()
):
optim_state_dict.pop(var_name + key)
state_dict = {
MODEL_NAME: model.state_dict(),
OPTIMIZER_NAME: optim_state_dict,
}
self._save(output_dir=os.path.join(output_dir, DIST_CKPT_PATH), state_dict=state_dict)
# FIXME: maybe only save one copy
paddle.save(self.lr_scheduler.state_dict(), os.path.join(output_dir, SCHEDULER_NAME))
if self.do_grad_scaling:
paddle.save(self.scaler.state_dict(), os.path.join(output_dir, SCALER_NAME))
# Determine the new best metric / best model checkpoint
if metrics is not None and self.args.metric_for_best_model is not None:
metric_to_check = self.args.metric_for_best_model
if not metric_to_check.startswith("eval_"):
metric_to_check = f"eval_{metric_to_check}"
metric_value = metrics[metric_to_check]
operator = np.greater if self.args.greater_is_better else np.less
if (
self.state.best_metric is None
or self.state.best_model_checkpoint is None
or operator(metric_value, self.state.best_metric)
):
self.state.best_metric = metric_value
self.state.best_model_checkpoint = output_dir
# Save the Trainer state
if self.args.should_save:
self.state.save_to_json(os.path.join(output_dir, TRAINER_STATE_NAME))
# Save RNG state in non-distributed training
rng_states = {
"python": random.getstate(),
"numpy": np.random.get_state(),
"cuda": paddle.get_rng_state(),
"cpu": paddle.framework.core.default_cpu_generator().get_state(),
}
if self.args.world_size > 1:
rng_states_list = []
paddle.distributed.all_gather_object(rng_states_list, rng_states)
if self.args.should_save:
os.makedirs(output_dir, exist_ok=True)
paddle.save(rng_states_list, os.path.join(output_dir, f"rng_state_{self.args.world_size}.pth"))
else:
os.makedirs(output_dir, exist_ok=True)
paddle.save(rng_states, os.path.join(output_dir, "rng_state.pth"))
if strtobool(os.getenv("FLAG_LLM_PDC", "False")):
# save checkpoint_done file to ensure checkpoint is complete
if self.args.should_save_model_state and self.args.should_save:
# For ckpt integrity
paddle.save(self.state.global_step, os.path.join(output_dir, ".checkpoint_done"))
def _save(
self,
output_dir: Optional[str] = None,
state_dict=None,
merge_tensor_parallel=False,
):
output_dir = output_dir if output_dir is not None else self.args.output_dir
os.makedirs(output_dir, exist_ok=True)
logger.info(f"Saving model checkpoint to {output_dir}")
if self.args.should_save:
if self.tokenizer is not None:
self.tokenizer.save_pretrained(output_dir)
# Good practice: save your training arguments together with the trained model
paddle.save(self.args, os.path.join(output_dir, TRAINING_ARGS_NAME))
# Save the config
model_to_save = unwrap_model(self.model)
config_to_save = copy.deepcopy(model_to_save.config)
config_to_save.mp_degree = getattr(config_to_save, "config_to_save", 1)
# Attach architecture to the config
config_to_save.architectures = [clean_model_class_name(model_to_save.__class__.__name__)]
config_to_save.save_pretrained(output_dir)
if self.model.can_generate():
model_to_save.generation_config.save_pretrained(output_dir)
if self.args.should_save_model_state:
if state_dict is None:
self._save_ckpt_func(self.model.state_dict(), output_dir)
logger.info(f"Model weights saved in {output_dir}")
else:
self._save_ckpt_func(state_dict, output_dir)
logger.info(f"Model weights and optimizer states saved in {output_dir}")
def _load_from_checkpoint(self, resume_from_checkpoint=None):
resume_from_checkpoint = None if not resume_from_checkpoint else resume_from_checkpoint
# Load potential model checkpoint
if isinstance(resume_from_checkpoint, bool) and resume_from_checkpoint:
resume_from_checkpoint = get_last_checkpoint(self.args.output_dir)
if resume_from_checkpoint is None:
raise ValueError(f"No valid checkpoint found in output directory ({self.args.output_dir})")
if resume_from_checkpoint is not None:
logger.info(f"Loading model from {resume_from_checkpoint} .")
if not self.args.ignore_load_lr_and_optim:
with _exec_mode_guard("dynamic"):
if distributed_isfile(os.path.join(resume_from_checkpoint, SCHEDULER_NAME)):
self.lr_scheduler.set_state_dict(
paddle.load(distributed_file(os.path.join(resume_from_checkpoint, SCHEDULER_NAME)))
)
else:
raise ValueError(
f"scheduler-file not found, scheduler:{os.path.join(resume_from_checkpoint, SCHEDULER_NAME)}"
)
if self.do_grad_scaling and distributed_isfile(os.path.join(resume_from_checkpoint, SCALER_NAME)):
self.scaler.load_state_dict(
paddle.load(
distributed_file(os.path.join(resume_from_checkpoint, SCALER_NAME)), return_numpy=True
)
)
if self.args.to_static:
if self.model_wrapped._mode is None:
self.model_wrapped.train()
model_state_dict = {
key: value
for key, value in self.model_wrapped.state_dict("param").items()
if not any(keyword in key for keyword in FREE_SVAE_LOAD_KEY_PATTERNS)
}
optim_state_dict = {
key: value
for key, value in self.model_wrapped.state_dict("opt").items()
if not any(keyword in key for keyword in FREE_SVAE_LOAD_KEY_PATTERNS)
}
if self.args.should_load_model_with_tensor_fusion:
model_state_dict = self._convert_state_dict_for_loading_tensor_fusion_ckpt(model_state_dict)
optim_state_dict = self._convert_state_dict_for_loading_tensor_fusion_ckpt(optim_state_dict)
else:
model_state_dict = self.model_wrapped.state_dict()
optim_state_dict = self.optimizer.state_dict()
optim_state_dict.pop("LR_Scheduler", None)
if len(optim_state_dict) == 0:
self.optimizer._create_accumulators(
paddle.base.framework.default_main_program().global_block(), self.optimizer._parameter_list
)
optim_state_dict = self.optimizer.state_dict()
optim_state_dict.pop("LR_Scheduler", None)
state_dict = {
MODEL_NAME: model_state_dict,
OPTIMIZER_NAME: optim_state_dict,
}
parameter_to_structured_name = {}
if self.args.to_static:
parameter_to_structured_name = self.model_wrapped._parameter_to_structured_name
else:
for state_name, state_value in self.model_wrapped.state_dict().items():
parameter_to_structured_name[state_value.name] = state_name
if self.args.auto_parallel_resume_form_hybrid_parallel:
CheckpointConverter(
resume_from_checkpoint, state_dict, parameter_to_structured_name, self.args
).load_from_hybrid_parallel_checkpoint()
else:
ckpt_path = os.path.join(resume_from_checkpoint, DIST_CKPT_PATH)
if not os.path.isdir(ckpt_path):
raise ValueError(f"Can't find a valid checkpoint at {resume_from_checkpoint}")
self._load_ckpt_func(state_dict, ckpt_path)
if self.args.to_static:
if self.args.should_load_model_with_tensor_fusion:
model_state_dict = self._convert_state_dict_for_loading_model_with_tensor_fusion(model_state_dict)
optim_state_dict = self._convert_state_dict_for_loading_model_with_tensor_fusion(optim_state_dict)
self.model_wrapped.set_state_dict(model_state_dict)
self.model_wrapped.set_state_dict(optim_state_dict)
# release memory
del state_dict
def _convert_state_dict_for_loading_tensor_fusion_ckpt(self, state_dict):
if self.args.load_model_with_sharding_tensor_fusion:
logger.info("load sharding tensor fusion unbalanced model")
state_dict = self.model_wrapped._convert_state_dict_with_rank_unique_name(state_dict)
else:
logger.info("load sharding tensor fusion balanced model")
state_dict = self.model_wrapped._convert_state_dict_without_tensor_fusion_param(state_dict)
return state_dict
def _convert_state_dict_for_loading_model_with_tensor_fusion(self, state_dict):
if self.args.load_model_with_sharding_tensor_fusion:
state_dict = self.model_wrapped._convert_state_dict_with_origin_name(state_dict)
else:
state_dict = self.model_wrapped._convert_state_dict_with_tensor_fusion_param(state_dict)
return state_dict
def _convert_state_dict_for_saving_tensor_fusion_ckpt(self, state_dict):
if self.args.save_model_with_sharding_tensor_fusion:
logger.info("save sharding tensor fusion unbalanced model")
state_dict = self.model_wrapped._convert_state_dict_with_rank_unique_name(state_dict)
else:
logger.info("save sharding tensor fusion balanced model")
state_dict = self.model_wrapped._convert_state_dict_without_tensor_fusion_param(state_dict)
return state_dict