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

1448 lines
56 KiB
Python

# Copyright 2020-present the HuggingFace Inc. team.
# Copyright (c) 2022 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.
# This file is modified from
# https://github.com/huggingface/transformers/blob/main/src/transformers/trainer_utils.py
"""
Utilities for the Trainer class.
"""
import datetime
import gc
import inspect
import json
import math
import os
import random
import threading
import time
from contextlib import contextmanager
from enum import Enum
from pathlib import Path
from typing import Dict, List, NamedTuple, Optional, Tuple, Union
import numpy as np
import paddle
import paddle.distributed as dist
from paddle.distributed import fleet
from paddle.distributed.fleet.meta_optimizers.dygraph_optimizer.dygraph_sharding_optimizer import (
DygraphShardingOptimizer,
DygraphShardingOptimizerV2,
)
from paddle.distributed.fleet.meta_parallel import get_rng_state_tracker
from paddle.io import IterableDataset
from paddle.optimizer.lr import LambdaDecay
from paddlenlp.ops import Topology
from ..trainer.argparser import strtobool
from ..transformers import get_gpt_pp_schedule, get_llama_pp_schedule
from ..transformers.tokenizer_utils_base import BatchEncoding
from ..utils.env import PREFIX_CHECKPOINT_DIR, _re_checkpoint # noqa for compatibility
from ..utils.fault_tolerance import PDC_DOWNLOAD_ERROR
from ..utils.import_utils import is_paddle_cuda_available, is_psutil_available
from ..utils.log import logger
from ..utils.pdc_sdk import PDCErrorCode, PDCErrorMessageMap, pdc_tool
from .utils.helper import distributed_file
__all__ = [
"TrainOutput",
"PredictionOutput",
"EvalPrediction",
"IntervalStrategy",
"SchedulerType",
"set_seed",
"speed_metrics",
"get_last_checkpoint",
"get_scheduler",
"set_hyrbid_parallel_seed",
"log_trainer_start",
]
def log_trainer_start():
if "MAIN_PROCESS_STARTED" not in os.environ:
start_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
logger.info(f"The Training Main Process Started Successfully. time: {start_time}, pid: {os.getpid()}")
os.environ["MAIN_PROCESS_STARTED"] = "1"
def _get_distributed_seeds(seed: int = 1234, topo: Topology = None):
"""
Get the seeds from distributed environment strategy.
Args:
seed (:obj:`int`, `optional`, defaults to 1234): The seeds for initializing distributed training.
topo (:obj:`Topology`, `optional`, defaults to None): The topology of hybrid parallel in semi-auto mode.
Returns:
Tuple[int, int]: The global seed and local seed respectively.
"""
# NOTE: For parameter init seed:
# seed: dp/mp_undistributed_parameter/sharding is same; others is different
# For compute seed(dropout):
# global seed: only mp group is same.
# local seed: all groups are different
hcg = None
if hasattr(fleet.fleet, "_hcg") and topo is None:
hcg = fleet.get_hybrid_communicate_group()
if topo is not None and paddle.distributed.get_world_size() > 1:
dp_rank = topo.dp_info.rank
dp_size = topo.dp_info.size
pp_rank = topo.pp_info.rank
pp_size = topo.pp_info.size
mp_rank = topo.mp_info.rank
mp_size = topo.mp_info.size
sep_rank = topo.sep_info.rank
sep_size = topo.sep_info.size
sharding_rank = topo.sharding_info.rank
elif hcg is not None and paddle.distributed.get_world_size() > 1:
# obtain rank message of hybrid parallel
mp_rank = hcg.get_model_parallel_rank()
mp_size = hcg.get_model_parallel_world_size()
if hasattr(hcg, "get_sep_parallel_rank"):
sep_rank = hcg.get_sep_parallel_rank()
sep_size = hcg.get_sep_parallel_world_size()
else:
sep_rank, sep_size = 0, 1
pp_rank = hcg.get_stage_id()
pp_size = hcg.get_pipe_parallel_world_size()
dp_rank = hcg.get_data_parallel_rank()
dp_size = hcg.get_data_parallel_world_size()
sharding_rank = hcg.get_sharding_parallel_rank()
else:
mp_rank, mp_size = 0, 1
sep_rank, sep_size = 0, 1
pp_rank, pp_size = 0, 1
dp_rank, dp_size = 0, 1
sharding_rank, _ = 0, 1
seed_offset = seed
global_seed = (
seed_offset
+ sep_rank * (mp_size)
+ pp_rank * (mp_size * sep_size)
+ dp_rank * (mp_size * sep_size * pp_size)
+ sharding_rank * (mp_size * sep_size * pp_size * dp_size)
)
seed_offset += paddle.distributed.get_world_size()
local_seed = (
seed_offset
+ mp_rank
+ sep_rank * (mp_size)
+ pp_rank * (mp_size * sep_size)
+ dp_rank * (mp_size * sep_size * pp_size)
+ sharding_rank * (mp_size * sep_size * pp_size * dp_size)
)
# NOTE: the commented seeds are set only for precision validation
random_seed = seed + 100 * pp_rank
return global_seed, local_seed, random_seed
def set_seed(seed: int = 1234, topo=None):
global_seed, local_seed, random_seed = _get_distributed_seeds(seed, topo)
tracker = get_rng_state_tracker()
if "global_seed" not in tracker.states_ and global_seed not in tracker.seeds_:
tracker.add("global_seed", global_seed)
if "local_seed" not in tracker.states_ and local_seed not in tracker.seeds_:
tracker.add("local_seed", local_seed)
paddle.seed(global_seed)
random.seed(random_seed)
np.random.seed(random_seed)
logger.info(
"The global seed is set to {}, local seed is set to {} and "
"random seed is set to {}.".format(global_seed, local_seed, random_seed)
)
def _switch_mode(mode="dynamic"):
assert mode in ["dynamic", "static"]
if mode == "dynamic":
paddle.disable_static()
else:
paddle.enable_static()
@contextmanager
def _exec_mode_guard(mode="dynamic"):
origin_mode = "dynamic" if paddle.in_dynamic_mode() else "static"
_switch_mode(mode)
try:
yield
finally:
_switch_mode(origin_mode)
class ExplicitEnum(Enum):
"""
Enum with more explicit error message for missing values.
"""
@classmethod
def _missing_(cls, value):
raise ValueError(
f"{value} is not a valid {cls.__name__}, please select one of {list(cls._value2member_map_.keys())}"
)
class EvalPrediction(NamedTuple):
"""
Evaluation output (always contains labels), to be used to compute metrics.
Parameters:
predictions (`np.ndarray`): Predictions of the model.
label_ids (`np.ndarray`): Targets to be matched.
"""
predictions: Union[np.ndarray, Tuple[np.ndarray]]
label_ids: Union[np.ndarray, Tuple[np.ndarray]]
class EvalLoopOutput(NamedTuple):
predictions: Union[np.ndarray, Tuple[np.ndarray]]
label_ids: Optional[Union[np.ndarray, Tuple[np.ndarray]]]
metrics: Optional[Dict[str, float]]
num_samples: Optional[int]
class PredictionOutput(NamedTuple):
predictions: Union[np.ndarray, Tuple[np.ndarray]]
label_ids: Optional[Union[np.ndarray, Tuple[np.ndarray]]]
metrics: Optional[Dict[str, float]]
class TrainOutput(NamedTuple):
global_step: int
training_loss: float
metrics: Dict[str, float]
def _check_checkpoint_files(
folder_path, world_size, ignore_save_lr_and_optim, skip_save_model_weight, remove_master_weight
):
files = os.listdir(folder_path)
model_weight_files = [f for f in files if f.startswith(".model_weight")]
a = len(model_weight_files) == world_size
if not ignore_save_lr_and_optim:
b = True
if not skip_save_model_weight or not remove_master_weight:
master_weight_file = [f for f in files if f.startswith(".master_weight")]
b = len(master_weight_file) == world_size
optimizer_file = [f for f in files if f.startswith(".optimizer_weight")]
c = len(optimizer_file) == world_size
return a and b and c
else:
return a
def get_last_checkpoint(folder, signal_folder=None, uc_async_save=False):
content = os.listdir(folder)
checkpoints = [
path
for path in content
if _re_checkpoint.search(path) is not None and os.path.isdir(os.path.join(folder, path))
]
if len(checkpoints) == 0:
return
if uc_async_save:
assert signal_folder is not None
if strtobool(os.getenv("FLAG_LLM_PDC", "False")):
for i in sorted(checkpoints, key=lambda x: int(_re_checkpoint.search(x).groups()[0]), reverse=True):
current_path = os.path.join(folder, i)
# make sure the checkpoint is valid
if not uc_async_save:
if os.path.exists(os.path.join(current_path, ".checkpoint_done")):
return current_path
else:
saving_info = paddle.load(distributed_file(os.path.join(current_path, ".saving_info")))
current_signal_path = os.path.join(signal_folder, i)
pre_world_size = saving_info.get("world_size", 1)
ignore_save_lr_and_optim = saving_info.get("ignore_save_lr_and_optim", False)
skip_save_model_weight = saving_info.get("skip_save_model_weight", False)
remove_master_weight = saving_info.get("remove_master_weight", False)
if _check_checkpoint_files(
current_signal_path,
pre_world_size,
ignore_save_lr_and_optim,
skip_save_model_weight,
remove_master_weight,
):
return current_path
return
else:
return os.path.join(folder, max(checkpoints, key=lambda x: int(_re_checkpoint.search(x).groups()[0])))
class IntervalStrategy(ExplicitEnum):
NO = "no"
STEPS = "steps"
EPOCH = "epoch"
class EvaluationStrategy(ExplicitEnum):
NO = "no"
STEPS = "steps"
EPOCH = "epoch"
class OptimizerNames(ExplicitEnum):
"""
Stores the acceptable string identifiers for optimizers.
"""
ADAMW = "adamw"
ADAFACTOR = "adafactor"
ADAMW_MINI = "adamw_mini"
ADAMW_CUSTOM = "adamw_custom"
class ShardingOption(ExplicitEnum):
"""
Sharding Option
OP for sharding optimizer state
GRAD for sharding gradients
FULL_SHARD for sharding optimizer gradient and parameter
OFFLOAD means offload to cpu.
"""
SHARD_OP = "stage1"
SHARD_GRAD_OP = "stage2"
FULL_SHARD = "stage3"
# NO_SHARD = "no"
OFFLOAD = "offload"
def is_main_process(local_rank):
"""
Whether or not the current process is the local process, based on `xm.get_ordinal()` (for TPUs) first, then on
`local_rank`.
"""
return local_rank in [-1, 0]
def total_processes_number(local_rank):
"""
Return the number of processes launched in parallel. Works with `paddle.distributed` and TPUs.
"""
if local_rank != -1:
import paddle
return paddle.distributed.get_world_size()
return 1
def speed_metrics(split, start_time, num_samples=None, num_steps=None, seq_length=None, model_flops_per_token=None):
"""
Measure and return speed performance metrics.
This function requires a time snapshot `start_time` before the operation to be measured starts and this function
should be run immediately after the operation to be measured has completed.
Args:
- split: name to prefix metric (like train, eval, test...)
- start_time: operation start time
- num_samples: number of samples processed
"""
runtime = time.time() - start_time
result = {f"{split}_runtime": round(runtime, 4)}
if num_samples is not None:
samples_per_second = num_samples / runtime
result[f"{split}_samples_per_second"] = round(samples_per_second, 4)
if seq_length is not None:
tokens_per_second_per_device = samples_per_second * seq_length / paddle.distributed.get_world_size()
result[f"{split}_tokens_per_second_per_device"] = round(tokens_per_second_per_device, 4)
if model_flops_per_token is not None:
result[f"{split}_hardware_tflops_per_device"] = round(
tokens_per_second_per_device * model_flops_per_token / 2**40, 2
)
if num_steps is not None:
steps_per_second = num_steps / runtime
result[f"{split}_steps_per_second"] = round(steps_per_second, 4)
return result
class SchedulerType(ExplicitEnum):
LINEAR = "linear"
COSINE = "cosine"
CONSTANT = "constant"
CONSTANT_WITH_WARMUP = "constant_with_warmup"
POLYNOMIAL = "polynomial"
def get_constant_schedule(learning_rate: float, last_epoch: int = -1):
"""
Create a schedule with a constant learning rate, using the learning rate set in optimizer.
Args:
learning_rate (float)
The initial learning rate. It is a python float number.
last_epoch (`int`, *optional*, defaults to -1):
The index of the last epoch when resuming training.
Return:
`paddle.optimizer.lr.LambdaDecay` with the appropriate schedule.
"""
return LambdaDecay(learning_rate, lambda _: 1, last_epoch=last_epoch)
def get_constant_schedule_with_warmup(learning_rate: float, num_warmup_steps: int, last_epoch: int = -1):
"""
Create a schedule with a constant learning rate preceded by a warmup period during which the learning rate
increases linearly between 0 and the initial lr set in the optimizer.
Args:
learning_rate (float)
The initial learning rate. It is a python float number.
num_warmup_steps (`int`):
The number of steps for the warmup phase.
last_epoch (`int`, *optional*, defaults to -1):
The index of the last epoch when resuming training.
Return:
`paddle.optimizer.lr.LambdaDecay` with the appropriate schedule.
"""
def lr_lambda(current_step: int):
if current_step < num_warmup_steps:
return float(current_step) / float(max(1.0, num_warmup_steps))
return 1.0
return LambdaDecay(learning_rate, lr_lambda, last_epoch=last_epoch)
def get_linear_schedule_with_warmup(learning_rate: float, num_warmup_steps, num_training_steps, last_epoch=-1):
"""
Create a schedule with a learning rate that decreases linearly from the initial lr set in the optimizer to 0, after
a warmup period during which it increases linearly from 0 to the initial lr set in the optimizer.
Args:
learning_rate (float)
The initial learning rate. It is a python float number.
num_warmup_steps (`int`):
The number of steps for the warmup phase.
num_training_steps (`int`):
The total number of training steps.
last_epoch (`int`, *optional*, defaults to -1):
The index of the last epoch when resuming training.
Return:
`paddle.optimizer.lr.LambdaDecay` with the appropriate schedule.
"""
def lr_lambda(current_step: int):
if current_step < num_warmup_steps:
return float(current_step) / float(max(1, num_warmup_steps))
return max(
0.0, float(num_training_steps - current_step) / float(max(1, num_training_steps - num_warmup_steps))
)
return LambdaDecay(learning_rate, lr_lambda, last_epoch)
def get_cosine_schedule_with_warmup(
learning_rate: float,
num_warmup_steps: int,
num_training_steps: int,
num_cycles: float = 0.5,
last_epoch: int = -1,
min_lr: float = 0.0,
):
"""
Create a schedule with a learning rate that decreases following the values of the cosine function between the
initial lr set in the optimizer to 0, after a warmup period during which it increases linearly between 0 and the
initial lr set in the optimizer.
Args:
learning_rate (float)
The initial learning rate. It is a python float number.
num_warmup_steps (`int`):
The number of steps for the warmup phase.
num_training_steps (`int`):
The total number of training steps.
num_cycles (`float`, *optional*, defaults to 0.5):
The number of waves in the cosine schedule (the defaults is to just decrease from the max value to 0
following a half-cosine).
last_epoch (`int`, *optional*, defaults to -1):
The index of the last epoch when resuming training.
Return:
`paddle.optimizer.lr.LambdaDecay` with the appropriate schedule.
"""
def lr_lambda(current_step):
if current_step < num_warmup_steps:
return float(current_step) / float(max(1, num_warmup_steps))
progress = float(current_step - num_warmup_steps) / float(max(1, num_training_steps - num_warmup_steps))
ratio = max(0.0, 0.5 * (1.0 + math.cos(math.pi * float(num_cycles) * 2.0 * progress)))
return ratio * (1 - min_lr / learning_rate) + min_lr / learning_rate
return LambdaDecay(learning_rate, lr_lambda, last_epoch)
def get_polynomial_decay_schedule_with_warmup(
learning_rate: float,
num_warmup_steps: int,
num_training_steps: int,
lr_end: float = 1e-7,
power: float = 1.0,
last_epoch: int = -1,
):
"""
Create a schedule with a learning rate that decreases as a polynomial decay from the initial lr set in the
optimizer to end lr defined by *lr_end*, after a warmup period during which it increases linearly from 0 to the
initial lr set in the optimizer.
Args:
learning_rate (`float`):
The base learning rate. It is a python float number.
num_warmup_steps (`int`):
The number of steps for the warmup phase.
num_training_steps (`int`):
The total number of training steps.
lr_end (`float`, *optional*, defaults to 1e-7):
The end LR.
power (`float`, *optional*, defaults to 1.0):
Power factor.
last_epoch (`int`, *optional*, defaults to -1):
The index of the last epoch when resuming training.
Note: *power* defaults to 1.0 as in the fairseq implementation, which in turn is based on the original BERT
implementation at
https://github.com/google-research/bert/blob/f39e881b169b9d53bea03d2d341b31707a6c052b/optimization.py#L37
Return:
`paddle.optimizer.lr.LambdaDecay` with the appropriate schedule.
"""
lr_init = learning_rate
if not (lr_init > lr_end):
raise ValueError(f"lr_end ({lr_end}) must be be smaller than initial lr ({lr_init})")
def lr_lambda(current_step: int):
if current_step < num_warmup_steps:
return float(current_step) / float(max(1, num_warmup_steps))
elif current_step > num_training_steps:
return lr_end / lr_init # as LambdaLR multiplies by lr_init
else:
lr_range = lr_init - lr_end
decay_steps = num_training_steps - num_warmup_steps
pct_remaining = 1 - (current_step - num_warmup_steps) / decay_steps
decay = lr_range * pct_remaining**power + lr_end
return decay / lr_init # as LambdaLR multiplies by lr_init
return LambdaDecay(learning_rate, lr_lambda, last_epoch)
TYPE_TO_SCHEDULER_FUNCTION = {
SchedulerType.LINEAR: get_linear_schedule_with_warmup,
SchedulerType.COSINE: get_cosine_schedule_with_warmup,
SchedulerType.CONSTANT: get_constant_schedule,
SchedulerType.POLYNOMIAL: get_polynomial_decay_schedule_with_warmup,
SchedulerType.CONSTANT_WITH_WARMUP: get_constant_schedule_with_warmup,
}
def get_scheduler(
name: Union[str, SchedulerType],
learning_rate: float,
num_warmup_steps: Optional[int] = None,
num_training_steps: Optional[int] = None,
num_cycles: Optional[float] = 0.5,
lr_end: Optional[float] = 1e-7,
power: Optional[float] = 1.0,
min_lr: Optional[float] = 0.0,
):
"""
Unified API to get any scheduler from its name.
Args:
name (`str` or `SchedulerType`):
The name of the scheduler to use.
learning_rate (float)
The initial learning rate. It is a python float number.
num_warmup_steps (`int`, *optional*):
The number of warmup steps to do. This is not required by all schedulers (hence the argument being
optional), the function will raise an error if it's unset and the scheduler type requires it.
num_training_steps (`int``, *optional*):
The number of training steps to do. This is not required by all schedulers (hence the argument being
optional), the function will raise an error if it's unset and the scheduler type requires it.
num_cycles (``float``, *optional*):
The number of waves in the cosine scheduler (the defaults is to just decrease from the max value to 0
following a half-cosine). This is not required by all schedulers (hence the argument being optional)
lr_end (``float``, *optional*):
The end LR in the polynomial scheduler. This is not required by all schedulers (hence the argument
being optional).
power (``float``, *optional*):
The power factor in the polynomial scheduler. This is not required by all schedulers (hence the argument
being optional).
min_lr (``float``, *optional*):
The minimum LR in the cosine scheduler. This is not required by all schedulers (hence the argument
being optional).
"""
name = SchedulerType(name)
schedule_func = TYPE_TO_SCHEDULER_FUNCTION[name]
if name == SchedulerType.CONSTANT:
return schedule_func(learning_rate)
# All other schedulers require `num_warmup_steps`
if num_warmup_steps is None:
raise ValueError(f"{name} requires `num_warmup_steps`, please provide that argument.")
if name == SchedulerType.CONSTANT_WITH_WARMUP:
return schedule_func(learning_rate, num_warmup_steps=num_warmup_steps)
# All other schedulers require `num_training_steps`
if num_training_steps is None:
raise ValueError(f"{name} requires `num_training_steps`, please provide that argument.")
if name == SchedulerType.COSINE:
return schedule_func(
learning_rate,
num_warmup_steps=num_warmup_steps,
num_training_steps=num_training_steps,
num_cycles=num_cycles,
min_lr=min_lr,
)
if name == SchedulerType.POLYNOMIAL:
return schedule_func(
learning_rate,
num_warmup_steps=num_warmup_steps,
num_training_steps=num_training_steps,
lr_end=lr_end,
power=power,
)
return schedule_func(learning_rate, num_warmup_steps=num_warmup_steps, num_training_steps=num_training_steps)
def _secs2timedelta(secs):
"""
convert seconds to hh:mm:ss.msec, msecs rounded to 2 decimals
"""
msec = int(abs(secs - int(secs)) * 100)
return f"{datetime.timedelta(seconds=int(secs))}.{msec:02d}"
def metrics_format(self, metrics: Dict[str, float]) -> Dict[str, float]:
"""
Reformat Trainer metrics values to a human-readable format
Args:
metrics (`Dict[str, float]`):
The metrics returned from train/evaluate/predict
Returns:
metrics (`Dict[str, float]`): The reformatted metrics
"""
metrics_copy = metrics.copy()
for k, v in metrics_copy.items():
if "_mem_" in k:
metrics_copy[k] = f"{ v >> 20 }MB"
elif "_runtime" in k:
metrics_copy[k] = _secs2timedelta(v)
elif k == "total_flos":
metrics_copy[k] = f"{ int(v) >> 30 }GF"
elif isinstance(metrics_copy[k], float):
metrics_copy[k] = round(v, 4)
return metrics_copy
def log_metrics(self, split, metrics):
"""
Log metrics in a specially formatted way
Under distributed environment this is done only for a process with rank 0.
Args:
split (`str`):
Mode/split name: one of `train`, `eval`, `test`
metrics (`Dict[str, float]`):
The metrics returned from train/evaluate/predictmetrics: metrics dict
"""
if not self.is_world_process_zero():
return
logger.info(f"***** {split} metrics *****")
metrics_formatted = self.metrics_format(metrics)
k_width = max(len(str(x)) for x in metrics_formatted.keys())
v_width = max(len(str(x)) for x in metrics_formatted.values())
for key in sorted(metrics_formatted.keys()):
logger.info(f" {key: <{k_width}} = {metrics_formatted[key]:>{v_width}}")
def save_metrics(self, split, metrics, combined=True):
"""
Save metrics into a json file for that split, e.g. `train_results.json`.
Under distributed environment this is done only for a process with rank 0.
Args:
split (`str`):
Mode/split name: one of `train`, `eval`, `test`, `all`
metrics (`Dict[str, float]`):
The metrics returned from train/evaluate/predict
combined (`bool`, *optional*, defaults to `True`):
Creates combined metrics by updating `all_results.json` with metrics of this call
To understand the metrics please read the docstring of [`~Trainer.log_metrics`]. The only difference is that raw
unformatted numbers are saved in the current method.
"""
if not self.is_world_process_zero():
return
path = os.path.join(self.args.output_dir, f"{split}_results.json")
with open(path, "w") as f:
json.dump(metrics, f, indent=4, sort_keys=True)
if combined:
path = os.path.join(self.args.output_dir, "all_results.json")
if os.path.exists(path):
with open(path, "r") as f:
all_metrics = json.load(f)
else:
all_metrics = {}
all_metrics.update(metrics)
with open(path, "w") as f:
json.dump(all_metrics, f, indent=4, sort_keys=True)
def save_state(self):
"""
Saves the Trainer state, since Trainer.save_model saves only the tokenizer with the model
Under distributed environment this is done only for a process with rank 0.
"""
if not self.is_world_process_zero():
return
path = os.path.join(self.args.output_dir, "trainer_state.json")
self.state.save_to_json(path)
def has_length(dataset):
"""
Checks if the dataset implements __len__() and it doesn't raise an error
"""
try:
return len(dataset) is not None
except (TypeError, ValueError, RuntimeError):
# TypeError: len() of unsized object
return False
class TrainerMemoryTracker:
"""
A helper class that tracks cpu and gpu memory.
This class will silently skip unless `psutil` is available. Install with `pip install psutil`.
When a stage completes, it can pass metrics dict to update with the memory metrics gathered during this stage.
Example :
```python
self._memory_tracker = TrainerMemoryTracker(self.args.skip_memory_metrics)
self._memory_tracker.start()
# code ...
metrics = {"train_runtime": 10.5}
self._memory_tracker.stop_and_update_metrics(metrics)
```
At the moment GPU tracking is only for `paddle`.
# To understand this class' intricacies please read the documentation of [`~Trainer.log_metrics`].
"""
# map trainer methods to metrics prefix
stages = {
"__init__": "init",
"train": "train",
"_inner_training_loop": "train",
"evaluate": "eval",
"predict": "test",
}
def __init__(self, skip_memory_metrics=False):
self.skip_memory_metrics = skip_memory_metrics
if not is_psutil_available():
# soft dependency on psutil
self.skip_memory_metrics = True
if self.skip_memory_metrics:
return
import psutil # noqa
if is_paddle_cuda_available():
import paddle
self.paddle = paddle
self.gpu = {}
else:
self.paddle = None
self.process = psutil.Process()
self.cur_stage = None
self.cpu = {}
self.init_reported = False
def derive_stage(self):
"""derives the stage/caller name automatically"""
caller = inspect.currentframe().f_back.f_back.f_code.co_name
if caller in self.stages:
return self.stages[caller]
else:
raise ValueError(
f"was called from {caller}, but only expect to be called from one of {self.stages.keys()}"
)
def cpu_mem_used(self):
"""get resident set size memory for the current process"""
return self.process.memory_info().rss
def peak_monitor_func(self):
self.cpu_mem_used_peak = -1
while True:
self.cpu_mem_used_peak = max(self.cpu_mem_used(), self.cpu_mem_used_peak)
# can't sleep or will not catch the peak right (this comment is here on purpose)
# time.sleep(0.001) # 1msec
if not self.peak_monitoring:
break
def start(self):
"""start tracking for the caller's stage"""
if self.skip_memory_metrics:
return
stage = self.derive_stage()
# deal with nested calls of eval during train - simply ignore those
if self.cur_stage is not None and self.cur_stage != stage:
return
self.cur_stage = stage
gc.collect()
if self.paddle is not None:
# self.paddle.cuda.reset_peak_memory_stats()?
self.paddle.device.cuda.empty_cache()
# gpu
if self.paddle is not None:
self.gpu_mem_used_at_start = self.paddle.device.cuda.memory_allocated()
# cpu
self.cpu_mem_used_at_start = self.cpu_mem_used()
self.peak_monitoring = True
peak_monitor_thread = threading.Thread(target=self.peak_monitor_func)
peak_monitor_thread.daemon = True
peak_monitor_thread.start()
def stop(self, stage):
"""stop tracking for the passed stage"""
# deal with nested calls of eval during train - simply ignore those
if self.cur_stage is not None and self.cur_stage != stage:
return
# this sends a signal to peak_monitor_func to complete its loop
self.peak_monitoring = False
# first ensure all objects get collected and their memory is freed
gc.collect()
if self.paddle is not None:
self.paddle.device.cuda.empty_cache()
# concepts:
# - alloc_delta: the difference of allocated memory between the end and the start
# - peaked_delta: the difference between the peak memory and the current memory
# in order to know how much memory the measured code consumed one needs to sum these two
# gpu
if self.paddle is not None:
self.gpu_mem_used_now = self.paddle.device.cuda.memory_allocated()
self.gpu_mem_used_peak = self.paddle.device.cuda.max_memory_allocated()
self.gpu[self.cur_stage] = dict(
begin=self.gpu_mem_used_at_start,
end=self.gpu_mem_used_now,
alloc=(self.gpu_mem_used_now - self.gpu_mem_used_at_start),
peaked=max(0, self.gpu_mem_used_peak - self.gpu_mem_used_now),
)
# cpu
self.cpu_mem_used_now = self.cpu_mem_used()
self.cpu[self.cur_stage] = dict(
begin=self.cpu_mem_used_at_start,
end=self.cpu_mem_used_now,
alloc=(self.cpu_mem_used_now - self.cpu_mem_used_at_start),
peaked=max(0, self.cpu_mem_used_peak - self.cpu_mem_used_now),
)
# reset - cycle finished
self.cur_stage = None
def update_metrics(self, stage, metrics):
"""updates the metrics"""
if self.skip_memory_metrics:
return
# deal with nested calls of eval during train - simply ignore those
if self.cur_stage is not None and self.cur_stage != stage:
return
if hasattr(self, "gpu_mem_used_peak"):
metrics["gpu_mem_max_memory_allocated"] = self.gpu_mem_used_peak
metrics["gpu_mem_max_memory_reserved"] = self.paddle.device.cuda.max_memory_reserved()
# since we don't have a way to return init metrics, we push them into the first of train/val/predict
stages = [stage]
if not self.init_reported:
stages.insert(0, "init")
self.init_reported = True
for stage in stages:
for t in ["alloc", "peaked"]:
if stage in self.cpu and t in self.cpu[stage]:
metrics[f"{stage}_mem_cpu_{t}_delta"] = self.cpu[stage][t]
if self.paddle is not None and stage in self.gpu and t in self.gpu[stage]:
metrics[f"{stage}_mem_gpu_{t}_delta"] = self.gpu[stage][t]
# if we need additional debug info, enable the following
# for t in ["begin", "end"]:
# if stage in self.cpu and t in self.cpu[stage]:
# metrics[f"{stage}_mem_cpu_{t}"] = self.cpu[stage][t]
# if self.paddle is not None and stage in self.gpu and t in self.gpu[stage]:
# metrics[f"{stage}_mem_gpu_{t}"] = self.gpu[stage][t]
# since memory can be allocated before init, and it might be difficult to track overall
# memory usage, in particular for GPU, let's report memory usage at the point init was called
if stages[0] == "init":
metrics["before_init_mem_cpu"] = self.cpu["init"]["begin"]
if self.paddle is not None:
metrics["before_init_mem_gpu"] = self.gpu["init"]["begin"]
# if we also wanted to report any additional memory allocations in between init and
# whatever the next stage was we could also report this:
# if self.cpu["init"]["end"] != self.cpu[stage]["begin"]:
# metrics[f"after_init_mem_cpu_delta"] = self.cpu[stage]["begin"] - self.cpu["init"]["end"]
# if self.paddle is not None and self.gpu["init"]["end"] != self.gpu[stage]["begin"]:
# metrics[f"after_init_mem_gpu_delta"] = self.gpu[stage]["begin"] - self.gpu["init"]["end"]
def stop_and_update_metrics(self, metrics=None):
"""combine stop and metrics update in one call for simpler code"""
if self.skip_memory_metrics:
return
stage = self.derive_stage()
self.stop(stage)
# init doesn't have metrics to update so we just save that data for later stages to retrieve
if metrics is not None:
self.update_metrics(stage, metrics)
class IterableDatasetShard(IterableDataset):
"""
Wraps a Paddle `IterableDataset` to generate samples for one of the processes only. Instances of this class will
always yield a number of samples that is a round multiple of the actual batch size (which is `batch_size x
num_processes`). Depending on the value of the `drop_last` attribute, it will either stop the iteration at the
first batch that would be too small or loop with indices from the beginning.
On two processes with an iterable dataset yielding of `[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]` with a batch size of
2:
- the shard on process 0 will yield `[0, 1, 4, 5, 8, 9]` so will see batches `[0, 1]`, `[4, 5]`, `[8, 9]`
- the shard on process 1 will yield `[2, 3, 6, 7, 10, 11]` so will see batches `[2, 3]`, `[6, 7]`, `[10, 11]`
Args:
dataset (`paddle.io.IterableDataset`):
The batch sampler to split in several shards.
batch_size (`int`, *optional*, defaults to 1):
The size of the batches per shard.
drop_last (`bool`, *optional*, defaults to `False`):
Whether or not to drop the last incomplete batch or complete the last batches by using the samples from the
beginning.
num_processes (`int`, *optional*, defaults to 1):
The number of processes running concurrently.
process_index (`int`, *optional*, defaults to 0):
The index of the current process.
seed (`int`, *optional*, defaults to 0):
A random seed that will be used for the random number generation in
[`~trainer_utils.IterableDatasetShard.set_epoch`].
"""
def __init__(
self,
dataset: IterableDataset,
batch_size: int = 1,
drop_last: bool = False,
num_processes: int = 1,
process_index: int = 0,
seed: int = 0,
):
self.dataset = dataset
self.batch_size = batch_size
self.drop_last = drop_last
self.num_processes = num_processes
self.process_index = process_index
self.seed = seed
self.epoch = 0
self.num_examples = 0
def set_epoch(self, epoch):
self.epoch = epoch
if hasattr(self.dataset, "set_epoch"):
self.dataset.set_epoch(epoch)
def __iter__(self):
self.num_examples = 0
# TODO: support generator seed in sampling.
#
# if (
# not hasattr(self.dataset, "set_epoch")
# and hasattr(self.dataset, "generator")
# and isinstance(self.dataset.generator, paddle.fluid.Generator)
# ):
# self.dataset.generator.manual_seed(self.seed + self.epoch)
real_batch_size = self.batch_size * self.num_processes
process_slice = range(self.process_index * self.batch_size, (self.process_index + 1) * self.batch_size)
first_batch = None
current_batch = []
for element in self.dataset:
self.num_examples += 1
current_batch.append(element)
# Wait to have a full batch before yielding elements.
if len(current_batch) == real_batch_size:
for i in process_slice:
yield current_batch[i]
if first_batch is None:
first_batch = current_batch.copy()
current_batch = []
# Finished if drop_last is True, otherwise complete the last batch with elements from the beginning.
if not self.drop_last and len(current_batch) > 0:
if first_batch is None:
first_batch = current_batch.copy()
while len(current_batch) < real_batch_size:
current_batch += first_batch
for i in process_slice:
yield current_batch[i]
def __len__(self):
# Will raise an error if the underlying dataset is not sized.
if self.drop_last:
return (len(self.dataset) // (self.batch_size * self.num_processes)) * self.batch_size
else:
return math.ceil(len(self.dataset) / (self.batch_size * self.num_processes)) * self.batch_size
class LastBatchPaddingSampler(paddle.io.DistributedBatchSampler):
"""The sampler which pads the first batch to the last batch"""
def __iter__(self):
local_batch_size = self.batch_size * self._acc_steps
num_samples = len(self.dataset)
indices = np.arange(num_samples).tolist()
global_eval_batch_size = self.batch_size * self.nranks
last_batch_size = num_samples % global_eval_batch_size
# Padding the first batch if the last batch is not full
if last_batch_size > 0:
padding_size = global_eval_batch_size - last_batch_size
# Select the first batch of indices for padding
if global_eval_batch_size <= len(indices):
first_batch_idx = indices[:global_eval_batch_size]
else:
first_batch_idx = indices.copy()
while padding_size > 0:
# Repeatedly pad the indices until the padding size is fulfilled
if padding_size > len(first_batch_idx):
indices += first_batch_idx
padding_size -= len(first_batch_idx)
else:
indices += first_batch_idx[:padding_size]
padding_size = 0
# Update the total number of indices
self.total_size = len(indices)
if self.shuffle:
np.random.RandomState(self.epoch).shuffle(indices)
self.epoch += 1
# subsample
def _get_indices_by_batch_size(indices):
subsampled_indices = []
# Iterate over the indices and extract batches that belong to the current device
for i in range(
self.local_rank * self.batch_size,
len(indices),
self.batch_size * self.nranks,
):
subsampled_indices.extend(indices[i : i + self.batch_size])
return subsampled_indices
if self.nranks > 1:
indices = _get_indices_by_batch_size(indices)
_sample_iter = iter(indices)
batch_indices = []
for idx in _sample_iter:
batch_indices.append(idx)
if len(batch_indices) == local_batch_size:
yield batch_indices
batch_indices = []
# Ensure that there are no leftover indices after batching
assert len(batch_indices) == 0
def find_batch_size(tensors):
"""
Find the first dimension of a tensor in a nested list/tuple/dict of tensors.
"""
if isinstance(tensors, (list, tuple)):
for t in tensors:
result = find_batch_size(t)
if result is not None:
return result
elif isinstance(tensors, (dict, BatchEncoding)):
for key, value in tensors.items():
result = find_batch_size(value)
if result is not None:
return result
elif isinstance(tensors, paddle.Tensor):
return tensors.shape[0] if len(tensors.shape) >= 1 else None
elif isinstance(tensors, np.ndarray):
return tensors.shape[0] if len(tensors.shape) >= 1 else None
class RemoveColumnsCollator:
"""Wrap the data collator to remove unused columns before they are passed to the collator."""
def __init__(
self,
data_collator,
signature_columns,
logger=None,
model_name: Optional[str] = None,
description: Optional[str] = None,
):
self.data_collator = data_collator
self.signature_columns = signature_columns
self.logger = logger
self.description = description
self.model_name = model_name
self.message_logged = False
def _remove_columns(self, feature: dict) -> dict:
if not isinstance(feature, dict):
return feature
if not self.message_logged and self.logger and self.model_name:
ignored_columns = list(set(feature.keys()) - set(self.signature_columns))
if len(ignored_columns) > 0:
dset_description = "" if self.description is None else f"in the {self.description} set"
self.logger.info(
f"The following columns {dset_description} don't have a corresponding argument in "
f"`{self.model_name}.forward` and have been ignored: {', '.join(ignored_columns)}."
f" If {', '.join(ignored_columns)} are not expected by `{self.model_name}.forward`, "
" you can safely ignore this message."
)
self.message_logged = True
return {k: v for k, v in feature.items() if k in self.signature_columns}
def __call__(self, features: List[dict]):
features = [self._remove_columns(feature) for feature in features]
return self.data_collator(features)
def set_hyrbid_parallel_seed(basic_seed, dataset_rank, tp_rank, pp_rank=0):
from paddle.distributed.fleet.meta_parallel import get_rng_state_tracker
random.seed(basic_seed + dataset_rank)
np.random.seed(basic_seed + dataset_rank)
paddle.seed(basic_seed + dataset_rank)
# local_seed/ global_seed is used to control dropout in ModelParallel
local_seed = basic_seed + 59999 + tp_rank * 10 + pp_rank * 1000
global_seed = basic_seed + 100003 + dataset_rank
tracker = get_rng_state_tracker()
if "global_seed" not in tracker.states_ and global_seed not in tracker.seeds_:
tracker.add("global_seed", global_seed)
if "local_seed" not in tracker.states_ and local_seed not in tracker.seeds_:
tracker.add("local_seed", local_seed)
def should_skip_data(global_step, skip_data_intervals):
"""Whether to skip current step data"""
if skip_data_intervals is None:
return False
skip_flag = False
for interval in skip_data_intervals:
if len(interval) != 2 or interval[0] > interval[1] or interval[0] <= 0:
raise ValueError(f"Please check your skip interval {interval}")
start_global_step, end_global_step = interval[0], interval[1]
# start_global_step and end_global_step start from 1, while global_step start from 0
if start_global_step <= global_step + 1 <= end_global_step:
skip_flag = True
break
return skip_flag
def split_parallel_config(parallel_config):
if "," in parallel_config:
parallel_config = set(parallel_config.split(","))
else:
parallel_config = set(parallel_config.split(" "))
return parallel_config
def download_recovery_ckpt_from_pdc(recovery_checkpoint_path, timeout):
"""Download checkpoint from PDC for resuming training after failover. Longjob environment is necessary.
Args:
recovery_checkpoint_path (`str`):
local path to load checkpoint for training recovery
timeout (`int`):
max wait time for download
"""
try:
base_dir, download_dir = os.path.split(os.path.normpath(recovery_checkpoint_path))
if not os.path.exists(base_dir) and base_dir != "":
os.makedirs(base_dir, exist_ok=True)
download_step = int(_re_checkpoint.search(download_dir).groups()[0])
except Exception as e:
raise RuntimeError(f"{PDC_DOWNLOAD_ERROR}; Failed to parse checkpoint path, details: {e}")
start_time = time.time()
# TODO(@gexiao): temporary workaround for environment variable conflicts.
original_trainer_id = os.getenv("PADDLE_TRAINER_ID")
original_trainers_num = os.getenv("PADDLE_TRAINERS_NUM")
cards_per_node = int(os.getenv("PADDLE_LOCAL_SIZE", "8"))
os.environ["PADDLE_TRAINER_ID"] = str(dist.get_rank() // cards_per_node)
os.environ["PADDLE_TRAINERS_NUM"] = str(dist.get_world_size() // cards_per_node)
result = pdc_tool.pdc_download_checkpoint(download_step, timeout)
os.environ["PADDLE_TRAINER_ID"] = original_trainer_id
os.environ["PADDLE_TRAINERS_NUM"] = original_trainers_num
end_time = time.time()
if result == PDCErrorCode.Success:
logger.info(f"Successfully downloaded checkpoint from PDC, total time cost: {end_time - start_time} seconds.")
elif result == PDCErrorCode.LocalPathExist:
logger.warning(
f"Skipping download checkpoint since file exists at local, total time cost: {end_time - start_time} seconds."
)
else:
raise RuntimeError(
f"{PDC_DOWNLOAD_ERROR}; Error occurred when trying to download checkpoint from PDC, recovery_checkpoint_path: {recovery_checkpoint_path}, timeout: {timeout}; error details: {PDCErrorMessageMap[result]}"
)
def check_auto_parallel_pipeline_support(model_type=None):
support_types = ["llama_pp", "gpt_pp"]
return model_type in support_types
def get_pp_schedule(model, model_type, n_microbatches, loss_fn, mode, pp_degree, group):
assert check_auto_parallel_pipeline_support(model_type)
if model_type == "llama_pp":
return get_llama_pp_schedule(model, n_microbatches, loss_fn, mode, pp_degree, group)
elif model_type == "gpt_pp":
return get_gpt_pp_schedule(model, n_microbatches, loss_fn, mode, pp_degree, group)
def parse_nccl_config_file(config_dir):
json_file = Path(config_dir)
if json_file.exists():
with open(json_file, "r") as file:
data = json.load(file)
def get_full_config_from_dict(comm_config):
assert type(comm_config) is dict
min_val = {
"ll_buffsize": 2**15, # 32KB
"ll128_buffsize": 2**17, # 128KB
"simple_buffsize": 2**17, # 128KB
}
final_config = {}
# if user does not set group name, use the default name set by Paddle
if comm_config.get("name", None) is not None:
final_config["commName"] = comm_config["name"]
final_config["buffsize_align"] = comm_config.get("buffsize_align", 1024)
final_config["algoStr"] = comm_config.get("algo", "")
final_config["protoStr"] = comm_config.get("proto", "")
final_config["nchannels"] = comm_config.get("n_channels", -1)
# ll part
# -1 means using the default value
final_config["ll_buffsize"] = comm_config.get("ll_buffsize", -1)
# keep the buffsize > the min value
if final_config["ll_buffsize"] != -1:
final_config["ll_buffsize"] = max(final_config["ll_buffsize"], min_val["ll_buffsize"])
# ll128 part
final_config["ll128_buffsize"] = comm_config.get("ll128_buffsize", -1)
if final_config["ll128_buffsize"] != -1:
final_config["ll128_buffsize"] = max(final_config["ll128_buffsize"], min_val["ll128_buffsize"])
# simple part
final_config["simple_buffsize"] = comm_config.get("simple_buffsize", -1)
if final_config["simple_buffsize"] != -1:
final_config["simple_buffsize"] = max(final_config["simple_buffsize"], min_val["simple_buffsize"])
# set the buffer size of unused protocols to the minimum value
if final_config["protoStr"] != "":
protos = split_parallel_config(final_config["protoStr"].lower())
for proto in ["ll", "ll128", "simple"]:
if proto not in protos:
final_config[(proto + "_buffsize")] = min_val[(proto + "_buffsize")]
return final_config
for key in data.keys():
data[key] = get_full_config_from_dict(data[key])
return data
else:
raise FileNotFoundError(f"The argument file {json_file} does not exist.")
def init_nccl_config(nccl_comm_group_config, strategy):
nccl_config = parse_nccl_config_file(nccl_comm_group_config)
def set_comm_config(configs, attr, dict_obj):
if strategy.hybrid_configs.get(configs, None) is None or dict_obj is None:
return
if not hasattr(strategy.hybrid_configs[configs], attr):
return
attr_obj = getattr(strategy.hybrid_configs[configs], attr)
for key, value in dict_obj.items():
if hasattr(attr_obj, key):
setattr(attr_obj, key, value)
set_comm_config("pp_configs", "coll_nccl_config", nccl_config.get("pp", None))
set_comm_config("pp_configs", "p2p_nccl_config", nccl_config.get("pp_p2p", None))
set_comm_config("pp_configs", "shared_nccl_config", nccl_config.get("pp_shared", None))
set_comm_config("mp_configs", "nccl_config", nccl_config.get("tp", None))
set_comm_config("sharding_configs", "nccl_config", nccl_config.get("sharding", None))
set_comm_config("sharding_configs", "check_nccl_config", nccl_config.get("sharding_check", None))
set_comm_config("dp_configs", "nccl_config", nccl_config.get("dp", None))
set_comm_config("dp_configs", "check_nccl_config", nccl_config.get("dp_check", None))
set_comm_config("sep_configs", "nccl_config", nccl_config.get("sep", None))
set_comm_config("dp_sep_configs", "nccl_config", nccl_config.get("dp_sep", None))
set_comm_config("pp_tp_configs", "nccl_config", nccl_config.get("pp_tp", None))
set_comm_config("ep_configs", "nccl_config", nccl_config.get("ep", None))
set_comm_config("ep_configs", "grad_nccl_config", nccl_config.get("ep_grad", None))
set_comm_config("moe_sharding_configs", "nccl_config", nccl_config.get("moe_sharding", None))
set_comm_config("moe_sharding_configs", "check_nccl_config", nccl_config.get("moe_sharding_check", None))
set_comm_config("default_comm_group_configs", "nccl_config", nccl_config.get("default", None))
return strategy
def init_optimizer(optimizer, model_sharded_state_dict, state_dict_metadata):
"""
Initialize the optimizer's states according to its type.
For DygraphShardingOptimizer (V1), initializes accumulators for local parameters.
For DygraphShardingOptimizerV2, manually initializes master weights and state dict for sharded parameters.
For other cases, initializes accumulators for all parameters.
Args:
optimizer: The optimizer instance to be initialized.
"""
optimizer_state_names = [".moment1_0", ".moment2_0", ".beta1_pow_acc_0", ".beta2_pow_acc_0", ".w_0"]
inner_opt = getattr(optimizer, "_inner_opt", None)
static_to_struct_mapping = {}
model_sharded_state_dict = dict(sorted(model_sharded_state_dict.items()))
for k, v in model_sharded_state_dict.items():
if v.local_tensor.name not in static_to_struct_mapping:
static_to_struct_mapping[v.local_tensor.name] = k
if isinstance(inner_opt, DygraphShardingOptimizer):
local_params = optimizer._rank2params[optimizer._sharding_rank]
param_list = []
for param in local_params:
param_name = param.name
struct_name = static_to_struct_mapping[param_name]
if not any(struct_name + state_name in state_dict_metadata for state_name in optimizer_state_names):
continue
param_list.append(param)
optimizer._create_accumulators(paddle.base.framework.default_main_program().global_block(), param_list)
return
elif isinstance(inner_opt, DygraphShardingOptimizerV2):
def init_param_optimizer_states(param_iter):
master_weights = {}
state_dict = {}
moments = ("moment1_0", "moment2_0")
betas = ("beta1_pow_acc_0", "beta2_pow_acc_0")
for static_name, shape, no_need_master_weights in param_iter:
if not no_need_master_weights:
master_weights[static_name] = paddle.zeros(shape, dtype="float32")
prefix = f"{static_name}_fp32_master_0_"
else:
prefix = f"{static_name}_"
for moment in moments:
key = f"{prefix}{moment}"
state_dict[key] = paddle.zeros(shape, dtype="float32")
for beta in betas:
key = f"{prefix}{beta}"
state_dict[key] = paddle.zeros((1,), dtype="float32")
return master_weights, state_dict
def buffer_params():
for buffer in optimizer._comm_buffer_list:
for param_name, grad_view in buffer._sharding_param_grad_view.items():
struct_name = static_to_struct_mapping[param_name]
if not any(
struct_name + state_name in state_dict_metadata for state_name in optimizer_state_names
):
continue
param_begin = grad_view._param_begin
param_end = grad_view._param_end
shape = (param_end - param_begin,)
no_need_master_weights = grad_view._param.dtype == paddle.float32
if shape[0] > 0:
yield param_name, shape, no_need_master_weights
master_weights, state_dict = init_param_optimizer_states(buffer_params())
state_dict["master_weights"] = master_weights
state_dict["LR_Scheduler"] = {"last_epoch": 1, "last_lr": 5e-06}
optimizer.set_state_dict(state_dict)
return
param_list = []
for param in optimizer._parameter_list:
param_name = param.name
struct_name = static_to_struct_mapping[param_name]
if not any(struct_name + state_name in state_dict_metadata for state_name in optimizer_state_names):
continue
param_list.append(param)
optimizer._create_accumulators(paddle.base.framework.default_main_program().global_block(), param_list)