【FlexCheckpoint】adapter flex checkpoint (#11188)
* adapter flex checkpoint * opt bf16 save * fix multi moe_sharding_group load safetensors * fix
This commit is contained in:
@@ -121,9 +121,13 @@ from ..transformers.tokenizer_utils import PretrainedTokenizer
|
||||
from ..utils.batch_sampler import DistributedBatchSampler as NlpDistributedBatchSampler
|
||||
from ..utils.env import (
|
||||
DISLORA_WEIGHTS_NAME,
|
||||
EMA_STATE_DIC,
|
||||
LOKR_WEIGHTS_NAME,
|
||||
LORA_WEIGHTS_NAME,
|
||||
MASTER_WEIGHT_DIC,
|
||||
MODEL_META_NAME,
|
||||
MODEL_STATE_DIC,
|
||||
OPTIMIZER_STATE_DIC,
|
||||
PADDLE_MASTER_WEIGHTS_INDEX_NAME,
|
||||
PADDLE_OPTIMIZER_NAME,
|
||||
PADDLE_PEFT_WEIGHTS_INDEX_NAME,
|
||||
@@ -185,6 +189,8 @@ from .training_args import TrainingArguments
|
||||
from .unified_checkpoint import UnifiedCheckpointHandler
|
||||
from .utils import reshard as reshard_util
|
||||
from .utils.async_save import AsyncSaver
|
||||
from .utils.reshard import SHARDING_STRATEGY_V1, split_opt_state
|
||||
from .utils.sharding_io import GroupGetter, to_device
|
||||
|
||||
try:
|
||||
from .utils.zero_cost_checkpoint import (
|
||||
@@ -673,6 +679,248 @@ class Trainer:
|
||||
elif resume_from_checkpoint is not None:
|
||||
logger.info(f"not loading ckpt :{self.args.dataset_rank}")
|
||||
|
||||
def _load_flex_checkpoint(self, resume_from_checkpoint):
|
||||
def get_metadata_file_name(path):
|
||||
files = os.listdir(path)
|
||||
metadata_files = [f for f in files if f.endswith(".metadata")]
|
||||
assert len(metadata_files) > 0, f"Found no metadata files in {path}"
|
||||
assert len(metadata_files) == 1, f"Found multiple metadata files in {path}"
|
||||
return metadata_files[0]
|
||||
|
||||
model_sharded_state_dict = self.model.sharded_state_dict()
|
||||
hf_aoa_config = self.model._gen_aoa_config(self.model.config)
|
||||
master_weights_path = os.path.join(resume_from_checkpoint, MASTER_WEIGHT_DIC)
|
||||
opt_states_path = os.path.join(resume_from_checkpoint, OPTIMIZER_STATE_DIC)
|
||||
model_states_path = os.path.join(resume_from_checkpoint, MODEL_STATE_DIC)
|
||||
|
||||
if self.args.load_from_hf:
|
||||
hcg = dist.fleet.get_hybrid_communicate_group()
|
||||
assert (
|
||||
self.args.ignore_load_lr_and_optim
|
||||
), "Loading from HuggingFace format is only allowed when learning rate and optimizer state are ignored."
|
||||
try:
|
||||
moe_sharding_group = hcg.get_moe_sharding_parallel_group()
|
||||
except Exception:
|
||||
moe_sharding_group = None
|
||||
|
||||
if moe_sharding_group is None or moe_sharding_group.nranks <= 1:
|
||||
# when moe_sharding_group is None, we use the default process_group
|
||||
logger.info(f"Loading model weights from '{resume_from_checkpoint}' in safetensors format.")
|
||||
dist.load_state_dict(
|
||||
model_sharded_state_dict,
|
||||
resume_from_checkpoint,
|
||||
aoa_config=hf_aoa_config,
|
||||
offload=self.args.load_via_cpu,
|
||||
safetensors=True,
|
||||
process_group=None,
|
||||
comm_method=self.args.comm_method,
|
||||
)
|
||||
else:
|
||||
try:
|
||||
pp_group = hcg.get_pipe_parallel_group()
|
||||
if pp_group is None or pp_group.nranks < 1:
|
||||
raise NotImplementedError("Only support when pp_group is not None.")
|
||||
except Exception:
|
||||
raise RuntimeError("Only support when pp_group is not None.")
|
||||
|
||||
try:
|
||||
moe_group = hcg.get_expert_parallel_group()
|
||||
if moe_group is None or moe_group.nranks < 1:
|
||||
raise NotImplementedError("Only support when moe_group is not None.")
|
||||
except Exception:
|
||||
raise RuntimeError("Only support when moe_group is not None.")
|
||||
moe_sharding_rank = moe_sharding_group.rank
|
||||
cur_rank = dist.get_rank()
|
||||
if moe_sharding_rank == 0:
|
||||
moe_group_ranks = []
|
||||
dist.all_gather_object(moe_group_ranks, cur_rank, group=moe_group)
|
||||
pp_group_ranks = []
|
||||
dist.all_gather_object(pp_group_ranks, moe_group_ranks, group=pp_group)
|
||||
process_group_ranks = [rank for ranks in pp_group_ranks for rank in ranks]
|
||||
else:
|
||||
process_group_ranks = [0] * (pp_group.nranks * moe_group.nranks)
|
||||
src_rank = hcg.get_moe_sharding_parallel_group_src_rank()
|
||||
dist.broadcast_object_list(process_group_ranks, src=src_rank, group=moe_sharding_group)
|
||||
assert any(process_group_ranks), "process_group_ranks should not be all 0"
|
||||
logger.info(f"Creating a temporary process group with ranks: {process_group_ranks}")
|
||||
process_group = dist.new_group(process_group_ranks)
|
||||
|
||||
if moe_sharding_rank == 0:
|
||||
logger.info(f"Loading model weights from '{resume_from_checkpoint}' in safetensors format.")
|
||||
# Only the first moe_sharding process is allowed to load the model weights.
|
||||
dist.load_state_dict(
|
||||
model_sharded_state_dict,
|
||||
resume_from_checkpoint,
|
||||
aoa_config=hf_aoa_config,
|
||||
offload=self.args.load_via_cpu,
|
||||
safetensors=True,
|
||||
process_group=process_group,
|
||||
comm_method=self.args.comm_method,
|
||||
)
|
||||
|
||||
dist.barrier()
|
||||
logger.info("Destroying the temporary process group.")
|
||||
dist.destroy_process_group(process_group)
|
||||
# The first moe_sharding group loads the model weights and then broadcasts them to all other moe_sharding groups.
|
||||
logger.info(
|
||||
"First shard (moe_sharding_group) has loaded safetensors weights, starting broadcast on moe_sharding_groups."
|
||||
)
|
||||
for param_name, param in self.model.state_dict().items():
|
||||
dist.broadcast(param, src=src_rank, group=moe_sharding_group)
|
||||
logger.info("Safetensors format weights have been loaded successfully.")
|
||||
return
|
||||
|
||||
if not self.args.ignore_load_lr_and_optim:
|
||||
state_dict_metadata = {}
|
||||
metadata_paths = [
|
||||
os.path.join(model_states_path, get_metadata_file_name(model_states_path)),
|
||||
os.path.join(opt_states_path, get_metadata_file_name(opt_states_path)),
|
||||
os.path.join(master_weights_path, get_metadata_file_name(master_weights_path)),
|
||||
]
|
||||
|
||||
for metadata_file in metadata_paths:
|
||||
if not os.path.exists(metadata_file):
|
||||
raise FileNotFoundError(f"Metadata file not found: {metadata_file}")
|
||||
metadata = paddle.load(metadata_file)
|
||||
state_dict_metadata.update(metadata.state_dict_metadata)
|
||||
|
||||
init_optimizer(self.optimizer, model_sharded_state_dict, state_dict_metadata)
|
||||
|
||||
optimizer_sharded_state_dict = self.optimizer.sharded_state_dict(model_sharded_state_dict)
|
||||
|
||||
opt_states = {}
|
||||
master_weights = {}
|
||||
for k, v in optimizer_sharded_state_dict.items():
|
||||
if k.endswith(".w_0"):
|
||||
master_weights[k] = v
|
||||
else:
|
||||
opt_states[k] = v
|
||||
|
||||
dist.load_state_dict(
|
||||
opt_states,
|
||||
opt_states_path,
|
||||
aoa_config=self.args.aoa_config,
|
||||
offload=self.args.load_via_cpu,
|
||||
comm_method=self.args.comm_method,
|
||||
)
|
||||
|
||||
if not self.args.sharded_model_from_ema:
|
||||
dist.load_state_dict(
|
||||
master_weights,
|
||||
master_weights_path,
|
||||
aoa_config=self.args.aoa_config,
|
||||
offload=self.args.load_via_cpu,
|
||||
)
|
||||
|
||||
self._load_scheduler(resume_from_checkpoint)
|
||||
|
||||
should_load_stage1 = self.args.should_load_sharding_stage1_model
|
||||
if should_load_stage1 and self.args.sharded_model_from_ema:
|
||||
ema_states_path = os.path.join(resume_from_checkpoint, EMA_STATE_DIC, f"{dist.get_rank()}_0.distcp")
|
||||
ema_state_dict = paddle.load(ema_states_path)
|
||||
ema_master_weights = ema_state_dict.pop("master_weights", None)
|
||||
opt_master_weights = self.optimizer.state_dict()["master_weights"]
|
||||
for k, v in opt_master_weights.items():
|
||||
assert (
|
||||
k in ema_master_weights
|
||||
), f"{k} not in ema_master_weights, emas_master_weight keys {ema_master_weights.keys()}"
|
||||
paddle.assign(ema_master_weights[k], opt_master_weights[k])
|
||||
|
||||
ema_state_dict = reshard_util.all_gather_state_dict(ema_state_dict, lambda x: True, self.sharding_group)
|
||||
self.model.set_state_dict(ema_state_dict)
|
||||
else:
|
||||
dist.load_state_dict(
|
||||
model_sharded_state_dict,
|
||||
model_states_path,
|
||||
aoa_config=self.args.aoa_config,
|
||||
offload=self.args.load_via_cpu,
|
||||
)
|
||||
|
||||
if self.args.bf16 and (not self.args.ignore_load_lr_and_optim) and should_load_stage1:
|
||||
opt_state_dict = self.optimizer.state_dict()
|
||||
|
||||
def recover_params_from_master_weight(opt_state_dict, group):
|
||||
master_weights = opt_state_dict["master_weights"]
|
||||
tmp = OrderedDict()
|
||||
(master_weights, tmp) = (tmp, master_weights)
|
||||
# cast to before
|
||||
for (k, v) in tmp.items():
|
||||
name = v.name
|
||||
master_weights[k] = paddle.cast(to_device(v), paddle.bfloat16).cpu()
|
||||
master_weights[k].name = name
|
||||
|
||||
structure_name_map = {k: v.name for (k, v) in self.model.state_dict().items()}
|
||||
node_model_state = reshard_util.NodeModelState(group=group)
|
||||
node_model_state_tmp = reshard_util.NodeModelState(group=group)
|
||||
node_model_state_tmp.add_master_weights(master_weights)
|
||||
node_model_state_tmp.pack_keys(structure_name_map)
|
||||
node_model_state.merge_from(node_model_state_tmp, max(group.rank, 0))
|
||||
del node_model_state_tmp
|
||||
sharding_strategy = reshard_util.get_sharding_strategy(self.optimizer)
|
||||
logger.debug(f"sharding_strategy: {sharding_strategy}")
|
||||
restore_func = (
|
||||
reshard_util.sharding_v1.restore
|
||||
if sharding_strategy == SHARDING_STRATEGY_V1
|
||||
else reshard_util.sharding_v2.restore
|
||||
)
|
||||
node_model_state = restore_func(node_model_state, self.model, self.optimizer)
|
||||
node_model_state.unpack_keys()
|
||||
master_weights = node_model_state.master_weights
|
||||
|
||||
master_weights = reshard_util.all_gather_state_dict(master_weights, lambda x: True, group)
|
||||
|
||||
model_state_dict = self.model.state_dict()
|
||||
for key, param in model_state_dict.items():
|
||||
if param.name in master_weights:
|
||||
logger.debug(
|
||||
f"key {key}, convert master weights {param.name} shape {master_weights[param.name].shape} to param {param.name} shape{param.shape}"
|
||||
)
|
||||
assert (
|
||||
param.shape == master_weights[param.name].shape
|
||||
), f"got {param.shape} vs {master_weights[param.name].shape}"
|
||||
master_weight = paddle.reshape(master_weights[param.name], param.shape)
|
||||
paddle.assign(paddle.cast(to_device(master_weight), paddle.bfloat16), model_state_dict[key])
|
||||
|
||||
group_getter = GroupGetter(self.model)
|
||||
opt_state_dict = split_opt_state(opt_state_dict, group_getter)
|
||||
for gid in group_getter.get_group_ids():
|
||||
sub_opt_state_dict = opt_state_dict[gid]
|
||||
group = group_getter.get_group_by_id(gid)
|
||||
if self.args.bf16:
|
||||
recover_params_from_master_weight(sub_opt_state_dict, group)
|
||||
|
||||
def _save_flex_model_state(self, output_dir):
|
||||
model_sharded_state_dict = self.model.sharded_state_dict()
|
||||
model_state_dict_path = os.path.join(output_dir, MODEL_STATE_DIC)
|
||||
os.makedirs(model_state_dict_path, exist_ok=True)
|
||||
dist.save_state_dict(
|
||||
model_sharded_state_dict,
|
||||
model_state_dict_path,
|
||||
)
|
||||
|
||||
def _save_flex_optimizer_state(self, output_dir):
|
||||
optimizer_state_dict_path = os.path.join(output_dir, OPTIMIZER_STATE_DIC)
|
||||
optimizer_states = {}
|
||||
master_weights = {}
|
||||
model_sharded_state_dict = self.model.sharded_state_dict()
|
||||
optimizer_sharded_state_dict = self.optimizer.sharded_state_dict(model_sharded_state_dict)
|
||||
for k, v in optimizer_sharded_state_dict.items():
|
||||
if k.endswith(".w_0"):
|
||||
master_weights[k] = v
|
||||
else:
|
||||
optimizer_states[k] = v
|
||||
|
||||
dist.save_state_dict(
|
||||
optimizer_states,
|
||||
optimizer_state_dict_path,
|
||||
)
|
||||
|
||||
master_weights_path = os.path.join(output_dir, MASTER_WEIGHT_DIC)
|
||||
dist.save_state_dict(
|
||||
master_weights,
|
||||
master_weights_path,
|
||||
)
|
||||
|
||||
def _load_from_checkpoint(self, resume_from_checkpoint=None):
|
||||
"""load state_dict from_checkpoint, Only load model state dict.
|
||||
|
||||
@@ -1048,27 +1296,7 @@ class Trainer:
|
||||
if delay_optimizer_creation:
|
||||
self.create_optimizer_and_scheduler(num_training_steps=max_steps)
|
||||
|
||||
if resume_from_checkpoint is not None:
|
||||
if not self.args.ignore_load_lr_and_optim:
|
||||
model_sharded_state_dict = self.model.sharded_state_dict()
|
||||
accessible_files = os.listdir(resume_from_checkpoint)
|
||||
metadata_files = [file for file in accessible_files if file.endswith(".metadata")]
|
||||
assert len(metadata_files) == 1, "Only support one metadata file now."
|
||||
metadata = paddle.load(os.path.join(resume_from_checkpoint, metadata_files[0]))
|
||||
state_dict_metadata = metadata.state_dict_metadata
|
||||
init_optimizer(self.optimizer, model_sharded_state_dict, state_dict_metadata)
|
||||
optimizer_sharded_state_dict = self.optimizer.sharded_state_dict(model_sharded_state_dict)
|
||||
sharded_state_dict = {**model_sharded_state_dict, **optimizer_sharded_state_dict}
|
||||
dist.load_state_dict(
|
||||
sharded_state_dict, resume_from_checkpoint, aoa_config=self.args.aoa_config
|
||||
)
|
||||
self._load_scheduler(resume_from_checkpoint)
|
||||
else:
|
||||
model_sharded_state_dict = self.model.sharded_state_dict()
|
||||
sharded_state_dict = model_sharded_state_dict
|
||||
dist.load_state_dict(
|
||||
sharded_state_dict, resume_from_checkpoint, aoa_config=self.args.aoa_config
|
||||
)
|
||||
self._load_flex_checkpoint(resume_from_checkpoint)
|
||||
else:
|
||||
model = self._wrap_model(self.model_wrapped)
|
||||
# for the rest of this function `model` is the outside model, whether it was wrapped or not
|
||||
@@ -2867,8 +3095,7 @@ class Trainer:
|
||||
self.save_model(output_dir)
|
||||
|
||||
if self.args.save_checkpoint_format == "flex_checkpoint":
|
||||
model_sharded_state_dict = self.model.sharded_state_dict()
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
self._save_flex_model_state(output_dir)
|
||||
|
||||
# Determine the new best metric / best model checkpoint
|
||||
if metrics is not None and self.args.metric_for_best_model is not None:
|
||||
@@ -2932,11 +3159,7 @@ class Trainer:
|
||||
)
|
||||
else:
|
||||
if self.args.save_checkpoint_format == "flex_checkpoint":
|
||||
optimizer_sharded_state_dict = self.optimizer.sharded_state_dict(model_sharded_state_dict)
|
||||
dist.save_state_dict(
|
||||
{**model_sharded_state_dict, **optimizer_sharded_state_dict},
|
||||
output_dir,
|
||||
)
|
||||
self._save_flex_optimizer_state(output_dir)
|
||||
if self.args.should_save:
|
||||
if self.tokenizer is not None and self.args.save_tokenizer:
|
||||
self.tokenizer.save_pretrained(output_dir)
|
||||
@@ -2992,11 +3215,7 @@ class Trainer:
|
||||
signal_dir,
|
||||
)
|
||||
elif self.args.save_checkpoint_format == "flex_checkpoint":
|
||||
optimizer_sharded_state_dict = self.optimizer.sharded_state_dict(model_sharded_state_dict)
|
||||
dist.save_state_dict(
|
||||
{**model_sharded_state_dict, **optimizer_sharded_state_dict},
|
||||
output_dir,
|
||||
)
|
||||
self._save_flex_optimizer_state(output_dir)
|
||||
if self.args.should_save:
|
||||
if self.tokenizer is not None and self.args.save_tokenizer:
|
||||
self.tokenizer.save_pretrained(output_dir)
|
||||
@@ -3039,10 +3258,7 @@ class Trainer:
|
||||
self._offload_optimizer()
|
||||
else:
|
||||
if self.args.save_checkpoint_format == "flex_checkpoint":
|
||||
dist.save_state_dict(
|
||||
model_sharded_state_dict,
|
||||
output_dir,
|
||||
)
|
||||
self._save_flex_model_state(output_dir)
|
||||
if self.args.should_save:
|
||||
if self.tokenizer is not None and self.args.save_tokenizer:
|
||||
self.tokenizer.save_pretrained(output_dir)
|
||||
|
||||
@@ -1567,34 +1567,31 @@ def save_full_param(
|
||||
return total_size
|
||||
|
||||
|
||||
def replace_name_and_gen_index(path, cur_rank_total_size):
|
||||
def replace_name_and_gen_index(path, total_size):
|
||||
index_mapping = {}
|
||||
cur_rank = paddle.distributed.get_rank()
|
||||
safetensor_files = [fname for fname in os.listdir(path) if fname.endswith(".safetensors")]
|
||||
files_num = len(safetensor_files)
|
||||
all_files_num = []
|
||||
paddle.distributed.all_gather_object(all_files_num, files_num)
|
||||
if paddle.distributed.get_world_size() > 1:
|
||||
paddle.distributed.all_gather_object(all_files_num, files_num)
|
||||
else:
|
||||
all_files_num.append(files_num)
|
||||
total_files_num = sum(all_files_num)
|
||||
|
||||
all_sizes = []
|
||||
paddle.distributed.all_gather_object(all_sizes, cur_rank_total_size)
|
||||
total_size = sum(all_sizes)
|
||||
|
||||
start_idx = []
|
||||
acc = 1
|
||||
for files_num in all_files_num:
|
||||
start_idx.append(acc)
|
||||
acc += files_num
|
||||
|
||||
env_local_rank = int(os.environ.get("PADDLE_RANK_IN_NODE", -1))
|
||||
env_local_size = int(os.environ.get("PADDLE_LOCAL_SIZE", 8))
|
||||
assert env_local_rank >= 0
|
||||
env_local_rank = dist.get_rank() % env_local_size
|
||||
assert env_local_rank >= 0, f"expected positive local rank, got {env_local_rank}"
|
||||
|
||||
cur_file_index = start_idx[cur_rank] // env_local_size
|
||||
total_files_num = total_files_num // env_local_size
|
||||
|
||||
total_size = total_size // env_local_size
|
||||
|
||||
index_mapping = {}
|
||||
if env_local_rank == 0:
|
||||
for file in safetensor_files:
|
||||
@@ -1608,11 +1605,19 @@ def replace_name_and_gen_index(path, cur_rank_total_size):
|
||||
os.rename(file_path, new_file_path)
|
||||
|
||||
index_mapping_list = []
|
||||
paddle.distributed.all_gather_object(index_mapping_list, index_mapping)
|
||||
if paddle.distributed.get_world_size() > 1:
|
||||
paddle.distributed.all_gather_object(index_mapping_list, index_mapping)
|
||||
else:
|
||||
index_mapping_list.append(index_mapping)
|
||||
index_mapping = {}
|
||||
for mapping in index_mapping_list:
|
||||
index_mapping.update(mapping)
|
||||
|
||||
# Save signal file for each card
|
||||
saved_signal_path = os.path.join(path, f"saved_signal_{dist.get_rank()}")
|
||||
with open(saved_signal_path, mode="w+") as f:
|
||||
f.write("1")
|
||||
|
||||
if env_local_rank == 0:
|
||||
index_file_name = "model.safetensors.index.json"
|
||||
index_infos = {}
|
||||
@@ -1622,35 +1627,87 @@ def replace_name_and_gen_index(path, cur_rank_total_size):
|
||||
with open(os.path.join(path, index_file_name), "w") as f:
|
||||
json.dump(index_infos, f, indent=4)
|
||||
|
||||
# For PDC signal
|
||||
if strtobool(os.getenv("FLAG_LLM_PDC", "False")):
|
||||
for i in range(paddle.distributed.get_world_size()):
|
||||
saved_signal_path = os.path.join(path, f".model_weights.done.{i}")
|
||||
paddle.save(i, saved_signal_path)
|
||||
|
||||
def save_hf_checkpoint(
|
||||
model,
|
||||
aoa_config,
|
||||
h_group,
|
||||
v_group,
|
||||
num_splits,
|
||||
shard_idx,
|
||||
path,
|
||||
):
|
||||
|
||||
memory_growth_threshold = 8 * (2**30)
|
||||
itr = model.full(
|
||||
aoa_config=aoa_config,
|
||||
h_group=h_group,
|
||||
v_group=v_group,
|
||||
num_splits=num_splits,
|
||||
shard_idx=shard_idx,
|
||||
memory_growth_threshold=memory_growth_threshold,
|
||||
)
|
||||
num_saver_ranks = h_group.nranks * v_group.nranks
|
||||
rank = h_group.rank + v_group.rank * h_group.nranks
|
||||
total_saved_size = save_full_param(
|
||||
itr=itr,
|
||||
save_dir=path,
|
||||
rank=rank,
|
||||
moe_sharding_world_size=num_saver_ranks,
|
||||
max_shard_size="16GB",
|
||||
num_saver_ranks=num_saver_ranks,
|
||||
)
|
||||
paddle.distributed.barrier()
|
||||
replace_name_and_gen_index(path, total_saved_size)
|
||||
class HFFormatFullParamSaver:
|
||||
def __init__(
|
||||
self,
|
||||
model,
|
||||
aoa_config,
|
||||
h_group=None,
|
||||
v_group=None,
|
||||
num_splits=None,
|
||||
shard_idx=None,
|
||||
saved_in_one_node=False,
|
||||
memory_growth_threshold=8 * (2**30),
|
||||
):
|
||||
self.model = model
|
||||
self.aoa_config = aoa_config
|
||||
self.h_group = h_group
|
||||
self.v_group = v_group
|
||||
self.num_splits = num_splits
|
||||
self.shard_idx = shard_idx
|
||||
self.saved_in_one_node = saved_in_one_node
|
||||
self.memory_growth_threshold = memory_growth_threshold
|
||||
self.determin_saver_based_group()
|
||||
|
||||
def get_full_param_iter(self):
|
||||
assert (self.v_group and self.h_group) or not (
|
||||
self.v_group or self.h_group
|
||||
), f"both h_group and v_group are provided or none of them, but got {self.v_group} and {self.h_group}"
|
||||
if self.v_group and self.h_group:
|
||||
assert self.shard_idx is not None, "expected shard_idx is not None"
|
||||
assert self.num_splits is not None, "expected num_splits is not None"
|
||||
|
||||
param_iter = self.model.full(
|
||||
aoa_config=self.aoa_config,
|
||||
h_group=self.h_group,
|
||||
v_group=self.v_group,
|
||||
num_splits=self.num_splits,
|
||||
shard_idx=self.shard_idx,
|
||||
memory_growth_threshold=self.memory_growth_threshold,
|
||||
)
|
||||
else:
|
||||
param_iter = self.model.full(
|
||||
aoa_config=self.aoa_config, memory_growth_threshold=self.memory_growth_threshold
|
||||
)
|
||||
return param_iter
|
||||
|
||||
def determin_saver_based_group(self):
|
||||
self.num_saver_ranks = paddle.distributed.get_world_size()
|
||||
self.rank = paddle.distributed.get_rank()
|
||||
|
||||
if self.h_group and self.v_group:
|
||||
self.num_saver_ranks = self.h_group.nranks * self.v_group.nranks
|
||||
self.rank = self.h_group.rank + self.v_group.rank * self.h_group.nranks
|
||||
|
||||
if self.saved_in_one_node:
|
||||
local_world_size = int(os.environ.get("PADDLE_LOCAL_SIZE", 8))
|
||||
self.num_saver_ranks = min(local_world_size, self.num_saver_ranks)
|
||||
|
||||
def save_checkpoint(self, path, max_shard_size="16GB"):
|
||||
total_saved_size = save_full_param(
|
||||
itr=self.get_full_param_iter(),
|
||||
save_dir=path,
|
||||
rank=self.rank,
|
||||
moe_sharding_world_size=self.num_saver_ranks,
|
||||
max_shard_size=max_shard_size,
|
||||
num_saver_ranks=self.num_saver_ranks,
|
||||
)
|
||||
if paddle.distributed.get_world_size() > 1:
|
||||
paddle.distributed.barrier()
|
||||
|
||||
# TODO(): fix total size
|
||||
all_sizes = []
|
||||
if paddle.distributed.get_world_size() > 1:
|
||||
paddle.distributed.all_gather_object(all_sizes, total_saved_size)
|
||||
else:
|
||||
all_sizes.append(total_saved_size)
|
||||
total_size = sum(all_sizes)
|
||||
replace_name_and_gen_index(path, total_size)
|
||||
return total_saved_size
|
||||
|
||||
@@ -415,6 +415,17 @@ class TrainingArguments:
|
||||
The AoA configuration of FlexCheckpoint, used to describe the mapping between model weights and the checkpoint content. Default is None.
|
||||
save_hf_steps (`int`, *optional*, defaults to 500):
|
||||
Number of updates steps before two huggingface checkpoint saves if `save_strategy="steps"`.
|
||||
load_via_cpu (bool, optional):
|
||||
Whether to load checkpoint data into CPU memory first before transferring to GPU.
|
||||
This helps mitigate GPU memory shortage by staging data on the CPU and only moving required parts to the GPU on demand during communication.
|
||||
Defaults to False.
|
||||
load_from_hf (bool, optional):
|
||||
Whether to load a checkpoint in the HuggingFace format.
|
||||
Defaults to False.
|
||||
comm_method (str, optional):
|
||||
Communication method used for checkpoint resharding.
|
||||
Choices are "send_recv", "broadcast", "multi_group_broadcast", and "grouped_send_recv".
|
||||
Defaults to "broadcast".
|
||||
"""
|
||||
|
||||
output_dir: str = field(
|
||||
@@ -1146,6 +1157,29 @@ class TrainingArguments:
|
||||
|
||||
save_hf_steps: int = field(default=-1, metadata={"help": "Save huggingface checkpoint every X updates steps."})
|
||||
|
||||
load_via_cpu: Optional[bool] = field(
|
||||
default=False,
|
||||
metadata={
|
||||
"help": "If True, loads checkpoint data to CPU first, then transfers required parts to GPU on demand to reduce GPU memory usage. Defaults to False."
|
||||
},
|
||||
)
|
||||
|
||||
load_from_hf: Optional[bool] = field(
|
||||
default=False,
|
||||
metadata={"help": "Whether to load a checkpoint in the HuggingFace format."},
|
||||
)
|
||||
|
||||
comm_method: Optional[str] = field(
|
||||
default="broadcast",
|
||||
metadata={
|
||||
"help": (
|
||||
"Communication method for checkpoint resharding. "
|
||||
'Choices are "send_recv", "broadcast", "multi_group_broadcast", and "grouped_send_recv". '
|
||||
'Default is "broadcast".'
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
def __post_init__(self):
|
||||
world_size = paddle.distributed.get_world_size()
|
||||
if in_auto_parallel_align_mode():
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
import atexit
|
||||
import copy
|
||||
import functools
|
||||
import hashlib
|
||||
import json
|
||||
import multiprocessing
|
||||
@@ -51,9 +52,16 @@ from paddle.incubate.tensor.manipulation import (
|
||||
)
|
||||
from paddle.optimizer.fusion_utils import FusionStorageHelper
|
||||
|
||||
from paddlenlp.trainer.utils.sharding_io import GroupGetter
|
||||
|
||||
from ...transformers.model_utils import unwrap_optimizer
|
||||
from . import reshard as reshard_util
|
||||
from .reshard import SHARDING_STRATEGY_V1
|
||||
from .reshard import (
|
||||
SHARDING_STRATEGY_V1,
|
||||
merge_model_state,
|
||||
split_model_state,
|
||||
split_opt_state,
|
||||
)
|
||||
|
||||
try:
|
||||
from paddle.distributed.fleet.meta_optimizers.dygraph_optimizer.dygraph_sharding_optimizer import (
|
||||
@@ -123,6 +131,65 @@ def showmem(msg):
|
||||
)
|
||||
|
||||
|
||||
# the funciotn that accept state dict as input can be decorated with this function
|
||||
def sharded_state_dict_compatibility(func, *, return_sharded_state_dict=False):
|
||||
@functools.wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
def should_convert(maybe_sharded_state_dict):
|
||||
all_shared_weights = all(isinstance(value, ShardedWeight) for value in maybe_sharded_state_dict.values())
|
||||
any_shared_weights = any(isinstance(value, ShardedWeight) for value in maybe_sharded_state_dict.values())
|
||||
logger.debug(f"all sharded weight {all_shared_weights}, any shared weight {any_shared_weights}")
|
||||
if not any_shared_weights:
|
||||
logger.debug("this is not a sharded state dict, no need to convert.")
|
||||
return False
|
||||
|
||||
if any_shared_weights and (not all_shared_weights):
|
||||
logger.debug("this is a mixed state dict(normal and sharded), not support to convert.")
|
||||
return False
|
||||
logger.debug("this is a sharded state dict, will convert it to local tensor dict.")
|
||||
return True
|
||||
|
||||
original_sharded_state_dict = {}
|
||||
# process args
|
||||
new_args = list(args)
|
||||
for idx, arg in enumerate(new_args):
|
||||
if not isinstance(arg, dict):
|
||||
continue
|
||||
if should_convert(arg):
|
||||
local_tensor_state_dict = {}
|
||||
for k, v in arg.items():
|
||||
local_tensor_state_dict[k] = v.local_tensor
|
||||
|
||||
original_sharded_state_dict.update(arg)
|
||||
new_args[idx] = local_tensor_state_dict
|
||||
|
||||
# process kwargs
|
||||
for key, value in kwargs.items():
|
||||
if not isinstance(value, dict):
|
||||
continue
|
||||
if should_convert(value):
|
||||
local_tensor_state_dict = {}
|
||||
for k, v in value.items():
|
||||
local_tensor_state_dict[k] = v.local_tensor
|
||||
|
||||
kwargs[key] = local_tensor_state_dict
|
||||
original_sharded_state_dict.update(value)
|
||||
|
||||
# original function
|
||||
result = func(*new_args, **kwargs)
|
||||
|
||||
if return_sharded_state_dict:
|
||||
assert isinstance(result, dict), f"expected dict, but got {type(result)}"
|
||||
for k, v in result.items():
|
||||
sharded_sharded_weight = original_sharded_state_dict[k]
|
||||
sharded_sharded_weight.local_tensor = v
|
||||
result[k] = sharded_sharded_weight
|
||||
return result
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
@sharded_state_dict_compatibility
|
||||
def get_fused_param_mappings(optimizer, manipulated_state_dict):
|
||||
param_mappings = {}
|
||||
ipc_meta_mappings = {}
|
||||
@@ -1415,50 +1482,56 @@ class ZeroCostCheckpointCallbackFcBased(ZeroCostCheckpointCallback):
|
||||
self.sharding_group = self.hcg.get_sharding_parallel_group()
|
||||
|
||||
def _manipulate_state_dict_and_config(self, model_to_save, optimizer):
|
||||
# TODO(): at now accracy is wrong, need to fix later.
|
||||
return model_to_save.sharded_state_dict()
|
||||
# group_getter = GroupGetter(model_to_save)
|
||||
# gids = group_getter.get_group_ids()
|
||||
# from paddleformers.trainer.utils.sharding_io import filter_sharded_params, exclude_parameters_in_state_dict
|
||||
# return model_to_save.sharded_state_dict()
|
||||
|
||||
# filter_sharded_params = sharded_state_dict_compatibility(filter_sharded_params, return_sharded_state_dict=True)
|
||||
# exclude_parameters_in_state_dict = sharded_state_dict_compatibility(exclude_parameters_in_state_dict, return_sharded_state_dict=True)
|
||||
group_getter = GroupGetter(model_to_save)
|
||||
gids = group_getter.get_group_ids()
|
||||
from paddlenlp.trainer.utils.sharding_io import (
|
||||
exclude_parameters_in_state_dict,
|
||||
filter_sharded_params,
|
||||
)
|
||||
|
||||
# state_dict = model_to_save.sharded_state_dict()
|
||||
# if self.args.should_save_sharding_stage1_model:
|
||||
# state_dict = split_model_state(state_dict, group_getter)
|
||||
# for gid in gids:
|
||||
# state_dict[gid] = filter_sharded_params(
|
||||
# state_dict.get(gid, {}),
|
||||
# optimizer,
|
||||
# self.sharding_group,
|
||||
# self.args.save_sharding_stage1_model_include_freeze_params,
|
||||
# )
|
||||
# state_dict = merge_model_state(state_dict)
|
||||
filter_sharded_params = sharded_state_dict_compatibility(filter_sharded_params, return_sharded_state_dict=True)
|
||||
exclude_parameters_in_state_dict = sharded_state_dict_compatibility(
|
||||
exclude_parameters_in_state_dict, return_sharded_state_dict=True
|
||||
)
|
||||
|
||||
# if self.args.bf16 and self.args.should_save_sharding_stage1_model:
|
||||
# param_names_in_master_weights = []
|
||||
# optimzier_state_dict = optimizer.state_dict()
|
||||
# optimzier_state_dict = split_opt_state(optimzier_state_dict, group_getter)
|
||||
# state_dict = split_model_state(state_dict, group_getter)
|
||||
# for gid in gids:
|
||||
# sub_opt_state = optimzier_state_dict.get(gid, {})
|
||||
# param_names_in_master_weights = list(sub_opt_state.get("master_weights", {}).keys())
|
||||
# state_dict[gid] = exclude_parameters_in_state_dict(
|
||||
# state_dict.get(gid, {}),
|
||||
# param_names_in_master_weights,
|
||||
# group_getter.get_group_by_id(gid),
|
||||
# )
|
||||
# state_dict = merge_model_state(state_dict)
|
||||
# logger.info(
|
||||
# "param_names_in_master_weights len:{}, bf16 state_dict len:{}, :{}".format(
|
||||
# len(param_names_in_master_weights), len(state_dict), state_dict.keys()
|
||||
# )
|
||||
# )
|
||||
state_dict = model_to_save.sharded_state_dict()
|
||||
if self.args.should_save_sharding_stage1_model:
|
||||
state_dict = split_model_state(state_dict, group_getter)
|
||||
for gid in gids:
|
||||
state_dict[gid] = filter_sharded_params(
|
||||
state_dict.get(gid, {}),
|
||||
optimizer,
|
||||
self.sharding_group,
|
||||
self.args.save_sharding_stage1_model_include_freeze_params,
|
||||
)
|
||||
state_dict = merge_model_state(state_dict)
|
||||
|
||||
# return state_dict
|
||||
if self.args.bf16 and self.args.should_save_sharding_stage1_model:
|
||||
param_names_in_master_weights = []
|
||||
optimzier_state_dict = optimizer.state_dict()
|
||||
optimzier_state_dict = split_opt_state(optimzier_state_dict, group_getter)
|
||||
state_dict = split_model_state(state_dict, group_getter)
|
||||
for gid in gids:
|
||||
sub_opt_state = optimzier_state_dict.get(gid, {})
|
||||
param_names_in_master_weights = list(sub_opt_state.get("master_weights", {}).keys())
|
||||
state_dict[gid] = exclude_parameters_in_state_dict(
|
||||
state_dict.get(gid, {}),
|
||||
param_names_in_master_weights,
|
||||
group_getter.get_group_by_id(gid),
|
||||
)
|
||||
state_dict = merge_model_state(state_dict)
|
||||
logger.info(
|
||||
"param_names_in_master_weights len:{}, bf16 state_dict len:{}, :{}".format(
|
||||
len(param_names_in_master_weights), len(state_dict), state_dict.keys()
|
||||
)
|
||||
)
|
||||
|
||||
return state_dict
|
||||
|
||||
def _cache_meta_for_sharded_save(self, model, optimizer):
|
||||
# TODO(): fix later.
|
||||
logger.info("Start caching metas for sharded save...")
|
||||
(self.manipulated_state_dict) = self._manipulate_state_dict_and_config(model, optimizer)
|
||||
|
||||
|
||||
@@ -167,5 +167,11 @@ else:
|
||||
USE_FAST_TOKENIZER: bool = _get_bool_env("USE_FAST_TOKENIZER", "false")
|
||||
PREFILL_USE_SAGE_ATTN: bool = _get_bool_env("PREFILL_USE_SAGE_ATTN", "false")
|
||||
|
||||
# Folder names for FlexCheckpoint
|
||||
MODEL_STATE_DIC = "model_state"
|
||||
OPTIMIZER_STATE_DIC = "optimizer_state"
|
||||
MASTER_WEIGHT_DIC = "master_weight"
|
||||
EMA_STATE_DIC = "ema_state"
|
||||
|
||||
# hf checkpoint dir name
|
||||
PREFIX_HF_CHECKPOINT_DIR = "hf_checkpoint"
|
||||
|
||||
Reference in New Issue
Block a user