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

1953 lines
85 KiB
Python

# Copyright (c) 2024 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 atexit
import copy
import functools
import hashlib
import json
import multiprocessing
import os
import random
import time
from abc import ABC, abstractmethod
from collections import OrderedDict, defaultdict
from dataclasses import replace
from enum import Enum
import numpy as np
import paddle
import paddle.autograd as imperative_base
import paddle.distributed as dist
from paddle.base import core
from paddle.distributed.communication.group import is_initialized
from paddle.distributed.fleet import fleet
from paddle.distributed.fleet.meta_parallel import PipelineLayer
from paddle.distributed.flex_checkpoint.dcp.metadata import (
LocalTensorIndex,
LocalTensorMetadata,
Metadata,
)
from paddle.distributed.flex_checkpoint.dcp.save_state_dict import dedup_key_in_dict
from paddle.distributed.flex_checkpoint.dcp.sharded_weight import ShardedWeight
from paddle.distributed.flex_checkpoint.dcp.utils import (
flatten_state_dict,
merge_state_dict_metadata,
)
from paddle.incubate.tensor.manipulation import (
async_offload_with_offset,
create_async_load,
)
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,
merge_model_state,
split_model_state,
split_opt_state,
)
try:
from paddle.distributed.fleet.meta_optimizers.dygraph_optimizer.dygraph_sharding_optimizer import (
DygraphShardingOptimizerV2,
)
except:
DygraphShardingOptimizerV2 = None
from paddle.distributed.fleet.meta_optimizers.dygraph_optimizer import (
DygraphShardingOptimizer,
)
from paddlenlp.trainer.trainer_callback import TrainerCallback
from paddlenlp.transformers.model_utils import (
_add_variant,
clean_model_class_name,
get_parameter_dtype,
unwrap_model,
)
from paddlenlp.transformers.utils import device_guard
from paddlenlp.utils.env import (
CONFIG_NAME,
MODEL_META_NAME,
PADDLE_OPTIMIZER_NAME,
PADDLE_WEIGHTS_NAME,
PREFIX_CHECKPOINT_DIR,
SCHEDULER_NAME,
TRAINER_STATE_NAME,
TRAINING_ARGS_NAME,
)
from paddlenlp.utils.fault_tolerance import FC_DUMP_ERROR, PC_DUMP_ERROR
from paddlenlp.utils.log import logger
from paddlenlp.utils.pdc_sdk import FLASH_DEVICE
def md5(tensor):
"""debug use"""
numpy_array = tensor.numpy()
array_bytes = numpy_array.tobytes()
return hashlib.md5(array_bytes).hexdigest()
class ZCCTaskType(Enum):
"""
TaskType defines the type of tasks that can be executed by the ZeroCostCheckpointWorker.
"""
UPDATE = 0
PREPARE = 1
OFFLOAD = 2
FINISH = 3
SET_EMA_STATE_DICT = 5
class ZCCWorkerStatus(Enum):
IDLE = 0
OFFLOADING = 1
DUMPING = 2
ERROR = 3
def showmem(msg):
return (
f"{msg} mem_alloc: {paddle.device.cuda.memory_allocated():.3e}"
f" Bytes/{paddle.device.cuda.max_memory_allocated():.3e} Bytes"
f"mem_reserv: {paddle.device.cuda.memory_reserved():.3e} "
f"Bytes/{paddle.device.cuda.max_memory_reserved():.3e} Bytes"
)
# 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 = {}
index = 0
sharding_comm_buffers = optimizer._comm_buffer_list
for buffer in sharding_comm_buffers:
ipc_meta_mappings[str(index)] = buffer.param_buffer_ipc_meta
for k, v in manipulated_state_dict.items():
logger.info(
f"check vname: {v.name}; buffer._sharding_param_grad_view: {buffer._sharding_param_grad_view.keys()}"
)
if v.name in buffer._sharding_param_grad_view:
assert k not in param_mappings, f"{k} has already been mapped, which is unexpected."
param_meta = {}
param_meta["buffer_index"] = str(index)
param_meta["shape"] = v.shape
param_meta["name"] = v.name
param_meta["start"] = buffer._sharding_param_grad_view[v.name]._index
param_meta["end"] = param_meta["start"] + v._numel()
param_mappings[k] = param_meta
index += 1
assert len(manipulated_state_dict) == len(
param_mappings
), f"manipulated state dict is not fully covered in param mappings, manipulated_state_dict:{manipulated_state_dict.keys()}, param_mappings:{param_mappings.keys()}"
return param_mappings, ipc_meta_mappings
class ZeroCostCheckpointEMAProcessor:
"""
生活在 ZCC Worker 里面的 EMA 处理模块.
通过 `optimizer_fusion_storage_helper` 以及 `param_fusion_storage_helper` 获取主模型的参数
"""
def __init__(self, optimizer_fusion_storage_helper, param_fusion_storage_helper, ema_coef):
self.optimizer_fusion_storage_helper = optimizer_fusion_storage_helper
self.param_fusion_storage_helper = param_fusion_storage_helper
self.ema_coef = ema_coef
(
self.ema_buffer,
self.ema_buffer_model_params,
self.master_min_offset,
self.master_max_offset,
) = self.build_ema_buffer()
def status(self):
if self.ema_buffer is None:
return "[EMA buffer] not initizied"
opt_md = md5(self.ema_buffer)
param_md = {k: md5(v) for k, v in self.ema_buffer_model_params.items()}
return f"[EMA buffer] opt:{opt_md}, param:{param_md}"
@imperative_base.no_grad()
def build_ema_buffer(self):
logger.info("[ZCC EMA] build ema buffer")
master_max_offset = max(
self.optimizer_fusion_storage_helper.master_weights_meta.values(), key=lambda i: i["end"]
)["end"]
master_min_offset = min(
self.optimizer_fusion_storage_helper.master_weights_meta.values(), key=lambda i: i["start"]
)["start"]
with device_guard("cpu"):
ema_buffer = paddle.zeros(
[master_max_offset - master_min_offset],
dtype="float32",
)
# ema model params, only works on float32 model weights (aka, moe gates)
ema_buffer_model_params = {
k: paddle.zeros_like(cpu_buf)
for k, (cuda_buf, cpu_buf) in self.param_fusion_storage_helper.inited_buffers.items()
if cuda_buf.dtype == paddle.float32
}
logger.info(f"[ZCCworker] build buffer done:{ema_buffer.dtype} {ema_buffer.place}")
return ema_buffer, ema_buffer_model_params, master_min_offset, master_max_offset
def ema_reset(self):
self.ema_buffer = None
self.ema_buffer_modele_params = None
@imperative_base.no_grad()
def ema_accumulate(self, global_step, loss, zcc_ema_loss_threshold):
"""
perform ema update : ` \alpha * EMA + (1-\alpha) + model`
buid `self.ema_buffer` if necessary
when loss < threshold, do ema update
"""
# logger.info(f'[ZCC EMA] wait all done, doing EMA w/ coef: {self.ema_coef}, status:{self.status()}')
# do update: ema = alpha * ema + (1-alpha) * model
logger.info(f"[ZCC EMA] accumulating, buffer type:{self.ema_buffer.place} {self.ema_buffer.dtype}")
with device_guard("cpu"):
cpu_master_weights = self.optimizer_fusion_storage_helper.cpu_buffer._slice(
self.master_min_offset, self.master_max_offset
).cpu()
if zcc_ema_loss_threshold is None or loss < zcc_ema_loss_threshold:
self.ema_buffer = self.ema_coef * self.ema_buffer + (1 - self.ema_coef) * cpu_master_weights
for index, ema_buf in self.ema_buffer_model_params.items():
_, cpu_buf = self.param_fusion_storage_helper.inited_buffers[index]
updated_ema = self.ema_coef * ema_buf + (1 - self.ema_coef) * cpu_buf.cpu()
self.ema_buffer_model_params[index] = updated_ema
logger.info(
f"[ZCC EMA] accmulating, buffer type:{self.ema_buffer.place} {self.ema_buffer.dtype}, done"
)
else:
logger.info(
f"[ZCC EMA] accmulating SKIP for global_step:{global_step}, because loss:{loss} > threshold:{zcc_ema_loss_threshold}"
)
@imperative_base.no_grad()
def ema_state_dict(self):
assert self.optimizer_fusion_storage_helper is not None
logger.info("[ZCC EMA] convert ema master weights state dict")
with device_guard("cpu"):
ema_state_dict = {}
for k, tensor_meta in self.param_fusion_storage_helper.model_weights_metas.items():
shape = tensor_meta["shape"]
name = tensor_meta["name"]
start = tensor_meta["start"]
end = tensor_meta["end"]
if tensor_meta["buffer_index"] not in self.ema_buffer_model_params:
continue # non fp32 has no `self.ema_buffer_model_params`
cpu_buffer = self.ema_buffer_model_params[tensor_meta["buffer_index"]]
tensor = cpu_buffer._slice(start, end).clone() # slice 出来的 tensor 在执行`paddle.save`会异常慢,此处必须clone
tensor.get_tensor()._set_dims(shape)
tensor.name = name
ema_state_dict[k] = tensor
ema_state_dict_master_weights = {}
for k, meta in self.optimizer_fusion_storage_helper.master_weights_meta.items():
s = meta["start"] - self.master_min_offset
e = meta["end"] - self.master_min_offset
t = self.ema_buffer._slice(s, e).clone()
t.get_tensor()._set_dims(meta["shape"])
t.name = meta["name"]
ema_state_dict_master_weights[k] = t
ema_state_dict["master_weights"] = ema_state_dict_master_weights
return ema_state_dict
def load_ema_state_dict(self, state_dict):
for k, tensor_meta in self.param_fusion_storage_helper.model_weights_metas.items():
logger.info(f"[ZCC EMA] load model weight key={k}")
start = tensor_meta["start"]
end = tensor_meta["end"]
if tensor_meta["buffer_index"] not in self.ema_buffer_model_params:
continue # non fp32 has no `self.ema_buffer_model_params`
if k in state_dict:
cpu_buffer = self.ema_buffer_model_params[tensor_meta["buffer_index"]]
tensor = state_dict[k].flatten()
cpu_buffer[start:end] = tensor
ema_master = state_dict["master_weights"]
for k, meta in self.optimizer_fusion_storage_helper.master_weights_meta.items():
logger.info(f"[ZCC EMA] load optimizer weight key={k}")
s = meta["start"] - self.master_min_offset
e = meta["end"] - self.master_min_offset
if k in ema_master: # state-dict is filtered
self.ema_buffer[s:e] = ema_master[k].flatten()
class ParamFusionStorageHelper:
def __init__(
self,
model_weights_metas,
buffer_ipc_metas,
):
self.async_loader = create_async_load()
self.inited_buffers = {}
self.all_param_numel = 0
self.model_weights_metas = OrderedDict()
self.current_offloaded_numel = 0
self.reset_meta(
model_weights_metas,
buffer_ipc_metas,
)
self.tasks = []
@imperative_base.no_grad()
def reset_meta(
self,
model_weights_metas,
buffer_ipc_metas,
):
self.inited_buffers = {}
self.all_param_numel = 0
self.model_weights_metas = OrderedDict()
if len(model_weights_metas) == 0:
logger.info("No model states need to save in current worker")
return
for k, v in model_weights_metas.items():
assert isinstance(v, dict), "model_weights_metas must be a dict"
buffer_index = v["buffer_index"]
if buffer_index not in self.inited_buffers.keys():
buffer_tuple = self.init_buffer(buffer_ipc_metas[buffer_index])
self.inited_buffers[buffer_index] = buffer_tuple
v["start"] = int(v["start"])
v["end"] = int(v["end"])
v["logical_start"] = self.all_param_numel
self.all_param_numel += v["end"] - v["start"]
v["logical_end"] = self.all_param_numel
self.model_weights_metas[k] = v
def init_buffer(self, meta):
cuda_buffer = paddle.to_tensor(paddle.base.core.LoDTensor._new_shared_cuda(meta))
cpu_buffer = cuda_buffer.pin_memory()
return (cuda_buffer, cpu_buffer)
@imperative_base.no_grad()
def sync_partial_param(self, numel_to_sync):
assert (
self.current_offloaded_numel + numel_to_sync <= self.all_param_numel
), f"numel_to_sync: {numel_to_sync}, current_offloaded_numel: {self.current_offloaded_numel}, all_param_numel: {self.all_param_numel}"
next_offload_index = 0
meta_keys_in_order = list(self.model_weights_metas.keys())
for i, k in enumerate(meta_keys_in_order):
if self.current_offloaded_numel >= self.model_weights_metas[k]["logical_end"]:
continue
next_offload_index = i
break
while numel_to_sync > 0:
offloading_param_key = meta_keys_in_order[next_offload_index]
offloading_param_meta = self.model_weights_metas[offloading_param_key]
logical_offload_param_start = self.current_offloaded_numel
logical_offload_param_end = min(
offloading_param_meta["logical_end"], logical_offload_param_start + numel_to_sync
)
actual_offload_start = (
logical_offload_param_start - offloading_param_meta["logical_start"]
) + offloading_param_meta["start"]
actual_offload_end = (
logical_offload_param_end - offloading_param_meta["logical_end"]
) + offloading_param_meta["end"]
actual_offload_size = actual_offload_end - actual_offload_start
current_param_buffer = self.inited_buffers[offloading_param_meta["buffer_index"]][0]
current_param_cpu_buffer = self.inited_buffers[offloading_param_meta["buffer_index"]][1]
task = async_offload_with_offset(
src_tensor=current_param_buffer,
dst_tensor=current_param_cpu_buffer,
src_offset=actual_offload_start,
dst_offset=actual_offload_start,
offload_size=actual_offload_size,
async_loader=self.async_loader,
)
self.tasks.append(task)
self.current_offloaded_numel += actual_offload_size
numel_to_sync -= actual_offload_size
next_offload_index += 1
def wait_all(self):
if len(self.tasks) == 0:
return
last_task = self.tasks.pop(-1)
while len(self.tasks) > 0:
task = self.tasks.pop(0)
task.cuda_wait()
last_task.cpu_wait()
self.current_offloaded_numel = 0
def state_dict(self):
state_dict = {}
for k, v in self.model_weights_metas.items():
state_dict[k] = self.restore_tensor_from_meta(v)
return state_dict
@imperative_base.no_grad()
def restore_tensor_from_meta(self, tensor_meta):
shape = tensor_meta["shape"]
name = tensor_meta["name"]
start = tensor_meta["start"]
end = tensor_meta["end"]
cpu_buffer = self.inited_buffers[tensor_meta["buffer_index"]][1]
tensor = cpu_buffer._slice(start, end)
tensor.get_tensor()._set_dims(shape)
tensor.name = name
return tensor
class ZeroCostCheckpointCallback(TrainerCallback):
"""
call ZeroCostCheckpointManager during training in following order:
on_step_end:
* call get_idle_worker_for_saving, set manager.current_worker
* call maybe_update_zcc_worker
* on_substep_end(call `gradient_accumulate` times): call zcc_pipeline_hook (in non-pp model)
* (when offload done, dump model)
on_optimizer_begin: call sync_offload_status, unset set manager.current_worker
maybe optimizer reload
maybe optimizer offload
"""
def __init__(self, args, zcc_manager, timer, sharding_io):
self.manager = zcc_manager
self.runtime_timer = timer
self.user_file_list = []
self.manipulated_state_dict = None
self.manipulated_config_to_save = None
self.manipulated_weight_suffix = None
self.model_meta = None
self.sharding_io = sharding_io
self.zcc_ema_interval = args.zcc_ema_interval
def on_substep_end(self, args, state, control, **kwargs):
self.manager.zcc_pipeline_hook(0) # only works in non-pp model
def on_optimizer_begin(self, args, state, control, **kwargs):
if args.enable_zero_cost_checkpoint and self.manager.current_worker is not None:
logger.info("[ZCC manager] Start syncing checkpoints")
assert self.manager.global_step != 0, "global_step should set, when calling `on_optimizer_begin`"
self.manager.sync_offload_status()
logger.info("[ZCC manager] Synced checkpoints.")
def on_step_end(self, args, state, control, model, lr_scheduler, optimizer, **kwargs):
if not control.should_save:
if args.zcc_save_ema_coef is not None and state.global_step % self.zcc_ema_interval == 0:
self.maybe_update_zcc_worker(args, model, optimizer, state.global_step)
self.manager.get_idle_worker_for_saving() # prepare for dumping
else:
self.runtime_timer.start("checkpoint saving time")
self.maybe_update_zcc_worker(args, model, optimizer, state.global_step)
checkpoint_folder = f"{PREFIX_CHECKPOINT_DIR}-{state.global_step}"
save_infos = self._get_save_infos_based_on_steps(state, args, checkpoint_folder)
non_cached_objects = (lr_scheduler.state_dict(), state, self.get_rng_states(args))
self.manager.get_idle_worker_for_saving((save_infos, non_cached_objects))
self.runtime_timer.stop()
if not isinstance(model, PipelineLayer):
self.manager.zcc_pipeline_hook(0)
def get_rng_states(self, args):
if not args.save_rng_states:
return None
rng_states = {
"python": random.getstate(),
"numpy": np.random.get_state(),
"cuda": paddle.get_rng_state(),
"cpu": paddle.framework.core.default_cpu_generator().get_state(),
"world_size": args.world_size,
}
if args.use_hybrid_parallel:
rng_states[
"hybrid_parallel_rng_state_tracker"
] = dist.fleet.meta_parallel.get_rng_state_tracker().get_states_tracker()
return rng_states
def _get_save_infos_based_on_steps(self, state, args, checkpoint_folder):
flash_device_checkpoint_dir = None
persistent_checkpoint_dir = None
if args.flash_device_save_steps > 0 and state.global_step % args.flash_device_save_steps == 0:
flash_device_checkpoint_dir = os.path.join(FLASH_DEVICE, checkpoint_folder)
if args.save_steps > 0 and state.global_step % args.save_steps == 0:
persistent_checkpoint_dir = os.path.join(args.output_dir, checkpoint_folder)
return (flash_device_checkpoint_dir, persistent_checkpoint_dir)
def _pack_dynamic_objects(self):
dynamic_objecs = {}
dynamic_objecs["optimizer_states_meta"] = self.optimizer_states_meta
dynamic_objecs["model_states_meta"] = self.model_states_meta
dynamic_objecs["optimizer_states_name_path"] = self.optimizer_states_name_path
dynamic_objecs["model_states_name_path"] = self.model_states_name_path
return dynamic_objecs
def _pack_static_objects(self, args):
static_objects = {}
static_objects["model_config"] = self.manipulated_config_to_save
static_objects["training_args"] = args
static_objects["model_meta"] = self.model_meta
static_objects["user_file"] = self.user_file_list
return static_objects
def maybe_update_zcc_worker(self, args, model, optimizer, global_step):
# logger.info(f"check should update :{optimizer.fused_buffer_version} vs {self.manager.cache_version}")
if optimizer.fused_buffer_version == self.manager.cache_version:
return
logger.info("ZCC checkpoint workers need upgrade.")
self._cache_meta_for_sharded_save(model, optimizer)
param_mappings, ipc_meta_mappings = get_fused_param_mappings(optimizer, self.manipulated_state_dict)
self.optimizer_states_meta = (
optimizer.fused_states_accumulators_meta,
optimizer.fused_states_master_weights_meta,
None,
optimizer.fused_states_buffer_ipc_meta,
)
self.model_states_meta = (param_mappings, ipc_meta_mappings)
self.optimizer_states_name_path = _add_variant(PADDLE_OPTIMIZER_NAME, args.optimizer_name_suffix)
self.model_states_name_path = _add_variant(PADDLE_WEIGHTS_NAME, self.manipulated_weight_suffix)
dynamic_objects = self._pack_dynamic_objects()
static_objects = self._pack_static_objects(args)
self.manager.update_zcc_workers(optimizer.fused_buffer_version, dynamic_objects, static_objects, global_step)
logger.info(f"[ZCC Callback] after first update:{optimizer.fused_states_buffer_ipc_meta}")
def _cache_meta_for_sharded_save(self, model, unused):
logger.info("Start caching metas for sharded save...")
(
self.manipulated_state_dict,
self.manipulated_config_to_save,
self.manipulated_weight_suffix,
) = self.sharding_io.manipulate_state_dict_and_config(model, merge_tensor_parallel=False)
logger.info("Cache manipulated static dict done.")
if self.manipulated_config_to_save is None:
model_to_save = unwrap_model(model)
dtype = get_parameter_dtype(model_to_save)
model_to_save.config.dtype = str(dtype).split(".")[1]
self.manipulated_config_to_save = copy.deepcopy(model_to_save.config)
self.manipulated_config_to_save.architectures = [clean_model_class_name(model_to_save.__class__.__name__)]
self.manipulated_config_to_save = self.manipulated_config_to_save.to_json_string(use_diff=True)
logger.info("Cache manipulated model config done")
self.model_meta = self.sharding_io.gather_distributed_model_meta()
logger.info("Cache distributed model meta done.")
class ZeroCostCheckpointManager:
def __init__(
self,
worker_num,
pipeline_hooks_capacity,
capacity_usage,
use_expert_parallel,
ema_coef=None,
zcc_worker_class=None,
):
assert worker_num > 0, "worker_num must be greater than 0"
assert capacity_usage <= 1.0, "capacity_usage must be less than or equal to 1.0"
self.cache_version = 0
self.worker_num = worker_num
self.workers = []
self.processes = []
self.current_worker = None
self.global_step = 0 # set `on-step-end`
self.device_id = int(os.getenv("FLAGS_selected_gpus"))
self.pipeline_hooks_steps = max(int(pipeline_hooks_capacity * capacity_usage), 1)
logger.info(
f"[ZCC manager] pipeline hooks capacity: {pipeline_hooks_capacity}; "
f"pipeline hooks steps for offloading: {self.pipeline_hooks_steps} "
f"ema coefficient: {ema_coef} "
)
self.current_pipeline_hook_step = 0
ctx = multiprocessing.get_context("spawn")
assert hasattr(fleet, "_hcg"), "ZeroCostCheckpoint Only support `use_hybrid_parallel`"
if zcc_worker_class is None:
zcc_worker_class = ZeroCostCheckpointWorker
for i in range(worker_num):
worker_task_queue = ctx.Queue()
worker_status = ctx.Value("i", ZCCWorkerStatus.IDLE.value)
worker_version = ctx.Value("i", 0)
worker_step = ctx.Value("i", 0)
worker = zcc_worker_class(
i,
self.device_id,
dist.get_rank(),
self.pipeline_hooks_steps,
worker_task_queue,
worker_status,
worker_step,
worker_version,
use_expert_parallel,
fleet.get_hybrid_communicate_group().get_data_parallel_rank(),
fleet.get_hybrid_communicate_group().get_model_parallel_rank(),
fleet.get_hybrid_communicate_group()._get_pipe_parallel_id(),
fleet.get_hybrid_communicate_group().get_sharding_parallel_rank(),
ema_coef,
)
p = ctx.Process(target=worker_loop, args=(worker,))
p.start()
self.workers.append(worker)
self.processes.append(p)
self.ready_to_save = False
atexit.register(self.terminate_workers)
def set_ema_state_dict(self, path):
logger.info(f"[ZCC manager] setting EMA state dict: {path}")
for worker in self.workers:
assert worker.status.value == ZCCWorkerStatus.IDLE.value, "[ZCC manager] worker should be idle, when "
worker.task_queue.put((ZCCTaskType.SET_EMA_STATE_DICT, path))
logger.info("[ZCC manager] done setting EMA state dict")
def update_zcc_workers(self, new_version, dynamic_objecs, static_object, global_step):
self.report_error_worker()
self.cache_version = new_version
self.global_step = global_step
assert self.current_worker is None, "[ZCC manager] current_worker must be None"
task = (ZCCTaskType.UPDATE, [self.cache_version, dynamic_objecs, static_object])
logger.info(f"[ZCC manager] updating zcc workers, version: {self.cache_version}")
for worker in self.workers:
worker.task_queue.put(task)
logger.info("[ZCC manager] waiting workers update done")
for worker in self.workers:
while worker.version.value != self.cache_version:
logger.info(
f"[ZCC manager] waiting worker{worker.worker_id} update. worker version: "
f"{worker.version.value}, expected version: {self.cache_version} "
f"step:{worker.global_step.value}"
)
time.sleep(1)
logger.info(
f"[ZCC manager] worker{worker.worker_id} updated. worker version: {worker.version.value}, "
f"expected version: {self.cache_version} "
f"global_step={worker.global_step.value} "
)
logger.info("[ZCC manager] update all zcc workers done")
self.ready_to_save = True
def get_idle_worker_for_saving(self, save_infos_and_non_cached_objects=None):
"""
if `save_infos_and_non_cached_objects` is None, do offload without dumping.
"""
self.report_error_worker()
assert self.current_worker is None, "[ZCC manager] current_worker must be None"
found_worker = False
while True:
for worker in self.workers:
if worker.status.value == ZCCWorkerStatus.IDLE.value:
self.current_worker = worker
found_worker = True
break
if found_worker:
break
logger.info(
"[ZCC manager] Waiting for idle worker..., consider increase `save-step` or `global-batch-size`"
)
time.sleep(1)
task = (ZCCTaskType.PREPARE, save_infos_and_non_cached_objects)
logger.info(
f"[ZCC manager] before putting task for prepare, dumping={save_infos_and_non_cached_objects is not None}"
)
self.current_worker.task_queue.put(task)
logger.info(
f"[ZCC manager] after putting task for prepare, dumping={save_infos_and_non_cached_objects is not None}"
)
def sync_offload_status(self):
self.report_error_worker()
assert self.current_worker is not None, "[ZCC manager] current_worker must not be None"
while True:
if self.current_worker.global_step.value != self.global_step:
logger.info(
f"[ZCC manager] Waiting current worker offloading done., "
f"worker_state:{self.current_worker.status.value}, "
f"worker_step:{self.current_worker.global_step.value}, manager_step:{self.global_step}"
)
time.sleep(1)
else:
logger.info(
f"[ZCC manager] Current worker offloading done "
f"worker_step:{self.current_worker.global_step.value}, manager_step:{self.global_step} "
)
break
self.current_pipeline_hook_step = 0
self.current_worker = None
def report_error_worker(self):
for worker in self.workers:
if worker.status.value == ZCCWorkerStatus.ERROR.value:
logger.error(f"[ZCC manager] Worker{worker.worker_id} encountered error.")
raise RuntimeError(f"{PC_DUMP_ERROR}")
def zcc_pipeline_hook(self, hook_id):
if self.current_worker is None:
return
if self.current_pipeline_hook_step == self.pipeline_hooks_steps:
return
if not self.ready_to_save:
return
task = (ZCCTaskType.OFFLOAD, self.global_step)
self.current_worker.task_queue.put(task)
self.current_pipeline_hook_step += 1
def finalize(self):
# clean up if the final step need to save
if self.current_worker is not None:
logger.info("[ZCC manager] clean up last step saving")
# trigger offload
for i in range(self.pipeline_hooks_steps):
self.zcc_pipeline_hook(i)
self.sync_offload_status()
self.ready_to_save = False
self.terminate_workers()
def terminate_workers(self):
for worker in self.workers:
task = (ZCCTaskType.FINISH, None)
worker.task_queue.put(task)
for p in self.processes:
p.join()
def worker_loop(worker):
worker.run()
class ZeroCostCheckpointWorker:
def __init__(
self,
worker_id,
device_id,
global_rank,
offload_chunks,
task_queue,
status,
global_step,
version,
use_expert_parallel,
dp_rank,
mp_rank,
pp_rank,
sd_rank,
ema_coef=None,
):
super().__init__()
self.worker_id = worker_id
self.device_id = device_id
self.global_rank = global_rank
self.offload_chunks = offload_chunks
self.task_queue = task_queue
self.status = status
self.global_step = global_step # state value
self.version = version
self.ema_coef = ema_coef
self.use_expert_parallel = use_expert_parallel
self.dp_rank = dp_rank
self.mp_rank = mp_rank
self.pp_rank = pp_rank
self.sd_rank = sd_rank
# for dynamic objects saving
self.optimizer_fusion_storage_helper = None
self.param_fusion_storage_helper = None
self.all_numel = 0
self.chunk_size_in_numel = 0
self.offloaded_numels = 0
self.optimizer_states_name_path = None
self.model_states_name_path = None
# for static objects saving
self.model_config_content = None
self.training_args_content = None
self.model_meta_content = None
self.user_file_list = None
# for non cached objects saving
# TODO(@gexiao): remove lr scheduler saves
self.lr_scheduler = None
self.trainer_state = None
self.rng_state = None
# for dumping
self.flash_device_save_dir = None
self.persistent_save_dir = None
self.zcc_ema_processor = None
def process_update_task(self, updates):
"""
sync operation, main process should wait
"""
version, dynamic_objecs, static_objects = updates
optimizer_states_meta = dynamic_objecs["optimizer_states_meta"]
model_states_meta = dynamic_objecs["model_states_meta"]
self.optimizer_states_name_path = dynamic_objecs["optimizer_states_name_path"]
self.model_states_name_path = dynamic_objecs["model_states_name_path"]
self.build_fusion_storage_helper(optimizer_states_meta, model_states_meta)
self.model_config_content = static_objects["model_config"]
self.training_args_content = static_objects["training_args"]
self.model_meta_content = static_objects["model_meta"]
self.user_file_list = static_objects["user_file"]
self.manage_offload_chunk()
self.version.value = version
def process_prepare_task(self, prepares):
self.offloaded_numels = 0
self.status.value = ZCCWorkerStatus.OFFLOADING.value
if prepares is None: # when `prepares` is None, not dumping
return
save_infos, non_cached_objects = prepares
self.flash_device_save_dir, self.persistent_save_dir = save_infos
self.lr_scheduler, self.trainer_state, self.rng_state = non_cached_objects
def process_offload_task(self, dump, global_step):
"""
call multipule times during model forward, return True if done dumpping
"""
actual_offload_size = (
min(self.offloaded_numels + self.chunk_size_in_numel, self.all_numel) - self.offloaded_numels
)
# Scene1: offload optimizer only
if self.offloaded_numels + actual_offload_size <= self.optimizer_fusion_storage_helper.buffer_length:
self.optimizer_fusion_storage_helper.sync_partial_param(
start=self.offloaded_numels, end=self.offloaded_numels + actual_offload_size
)
# Scene2: offload optimizer and param
elif self.offloaded_numels < self.optimizer_fusion_storage_helper.buffer_length:
self.optimizer_fusion_storage_helper.sync_partial_param(
start=self.offloaded_numels, end=self.optimizer_fusion_storage_helper.buffer_length
)
self.param_fusion_storage_helper.sync_partial_param(
numel_to_sync=(
actual_offload_size - (self.optimizer_fusion_storage_helper.buffer_length - self.offloaded_numels)
)
)
# Scene3: offload param only
else:
self.param_fusion_storage_helper.sync_partial_param(numel_to_sync=actual_offload_size)
self.offloaded_numels += actual_offload_size
# wait tasks done and change status to DUMPING at the last chunk
if self.offloaded_numels == self.all_numel:
self.optimizer_fusion_storage_helper.wait_all()
self.param_fusion_storage_helper.wait_all()
self.status.value = ZCCWorkerStatus.DUMPING.value
self.global_step.value = global_step
if self.ema_coef is not None:
self.zcc_ema_processor.ema_accumulate(
self.trainer_state.global_step,
self.trainer_state.loss,
self.training_args_content.zcc_ema_loss_threshold,
)
# continue to process dumping task at the last chunk
if self.offloaded_numels == self.all_numel:
if dump:
need_report_error = self.process_dump_task()
else:
need_report_error = False
self.offloaded_numels = 0
self.status.value = ZCCWorkerStatus.ERROR.value if need_report_error else ZCCWorkerStatus.IDLE.value
return True
return False
def process_dump_task(self):
"""
dump saved objects to either flash device or persistent device
Notice:
1. If dumping to flash device failed, the process will move on for other task
2. If dumping to persistent device failed, the process will change status to fail, and the main process will raise Error.
"""
need_report_error = False
if self.flash_device_save_dir:
try:
self.process_dump_task_impl(self.flash_device_save_dir)
logger.info(f"[ZCC Worker{self.worker_id}] Dumping to flash device done: {self.flash_device_save_dir}")
except Exception as e:
logger.error(f"{FC_DUMP_ERROR} [ZCC Worker{self.worker_id}] Failed to dump to flash device: {e}")
if self.persistent_save_dir:
try:
self.process_dump_task_impl(self.persistent_save_dir)
logger.info(
f"[ZCC Worker{self.worker_id}] Dumping to persistent device done: {self.persistent_save_dir}"
)
except Exception as e:
logger.error(f"[ZCC Worker{self.worker_id}] Failed to dump to persistent device: {e}")
need_report_error = True
return need_report_error
def _filter_moe_no_sync_optimizer_params(self, model_meta, optimzier_state_dict):
"""
filter optimizer params which should not sync, copy from paddlenlp.Trainer
"""
filter_optimzier_state_dict = OrderedDict()
assert "master_weights" in optimzier_state_dict, optimzier_state_dict.keys()
param_names_in_master_weights = list(optimzier_state_dict["master_weights"].keys())
filter_optimzier_state_dict["master_weights"] = OrderedDict()
suffix = f"tp{self.mp_rank:0>2d}_pp{self.pp_rank:0>2d}"
dyname_to_pname = model_meta["sharding_metas"][suffix]["structure_name_mapping"]
dyname_to_meta = model_meta["sharding_metas"][suffix]["param_meta"]
for k, pname in dyname_to_pname.items():
shape, dtype, is_dist, is_no_sync = dyname_to_meta[k]
if is_no_sync:
if pname in param_names_in_master_weights:
filter_optimzier_state_dict["master_weights"][pname] = optimzier_state_dict["master_weights"][
pname
]
else:
pass
# logger.info(f"filter out master weight:{pname} -> {k}")
for op_k, op_v in optimzier_state_dict.items():
if op_k.startswith(pname):
filter_optimzier_state_dict[op_k] = op_v
else:
# logger.info(f"filter out key={k}, when dp!=0")
pass
return filter_optimzier_state_dict
def _dump_static_objects(self, output_dir):
# Step1.1: save model config
json_file_path = os.path.join(output_dir, CONFIG_NAME)
with open(json_file_path, "w", encoding="utf-8") as writer:
writer.write(self.model_config_content)
# Step1.2: save training args
args_file_path = os.path.join(output_dir, TRAINING_ARGS_NAME)
paddle.save(self.training_args_content, args_file_path)
# Step1.3: save model meta
model_meta_path = os.path.join(output_dir, MODEL_META_NAME)
with open(model_meta_path, "w") as f:
json.dump(self.model_meta_content, f)
# Step1.4: save user files
for (file_name, file_content) in self.user_file_list:
file_path = os.path.join(output_dir, file_name)
with open(file_path, "w") as f:
f.write(file_content)
def _dump_states(self, output_dir):
# Step2.1: save model states
with device_guard("cpu"):
model_states_name_path = os.path.join(output_dir, self.model_states_name_path)
state_dict = self.param_fusion_storage_helper.state_dict()
# Step2.2: save optimizer states
optimizer_state_name_path = os.path.join(output_dir, self.optimizer_states_name_path)
opt_state_dict = self.optimizer_fusion_storage_helper.state_dict()
# logger.info(showmem(f"[ZCCworker{self.worker_id}] after build state-dict"))
if self.ema_coef is not None:
ema_name_path = os.path.join(output_dir, self.optimizer_states_name_path).replace("optimizer", "ema")
ema_state_dict = self.zcc_ema_processor.ema_state_dict()
if self.dp_rank <= 0 or self.use_expert_parallel:
if self.dp_rank > 0: # ep
opt_state_dict = self._filter_moe_no_sync_optimizer_params(self.model_meta_content, opt_state_dict)
if self.ema_coef is not None:
# non master-weights in `ema-state-dict` when dp >1 will be filtered, which is acceptable
ema_state_dict = self._filter_moe_no_sync_optimizer_params(self.model_meta_content, ema_state_dict)
paddle.save(state_dict, model_states_name_path)
paddle.save(opt_state_dict, optimizer_state_name_path)
if self.ema_coef is not None:
paddle.save(ema_state_dict, ema_name_path)
def _dump_args_and_state(self, output_dir):
# Step2.3: save LR Scheduler (To be removed)
lr_state_name_path = os.path.join(output_dir, SCHEDULER_NAME)
if self.device_id == 0:
paddle.save(self.lr_scheduler, lr_state_name_path)
# Step2.4: save TrainerState
trainer_state_name_path = os.path.join(output_dir, TRAINER_STATE_NAME)
if self.device_id == 0:
self.trainer_state.save_to_json(trainer_state_name_path)
# Step2.5: save RNG State
if self.rng_state is not None:
rng_state_name_path = os.path.join(output_dir, f"rng_state_{dist.get_rank()}.pth")
paddle.save(self.rng_state, rng_state_name_path)
def process_dump_task_impl(self, output_dir):
os.makedirs(output_dir, exist_ok=True)
# Step1: save static objects
if self.device_id == 0:
self._dump_static_objects(output_dir)
logger.info("[ZCC worker] dump static objec done.")
# Step2: save dynamic objects
self._dump_states(output_dir)
logger.info("[ZCC worker] dump model state done.")
self._dump_args_and_state(output_dir)
# Step3: dump save signals
saved_signal_path = os.path.join(output_dir, f"saved_signal_{self.global_rank}")
with open(saved_signal_path, mode="w+") as f:
f.write("1")
logger.info("[ZCC worker] dump save signal done.")
def run(self):
core.set_cuda_current_device_id(self.device_id)
paddle.set_device(f"gpu:{self.device_id}")
logger.info(f"[ZCC Worker{self.worker_id}] Worker{self.worker_id} started.")
ema_ckpt_path = None
save_info_tuple = None # save dir...
start_time = None
try:
while True:
# logger.info(f"[ZCC Worker{self.worker_id}] Wait for command")
task = self.task_queue.get()
task_type, task_body = task
# logger.info(f"[ZCC Worker{self.worker_id}] Received a new task of type {task_type}")
if task_type == ZCCTaskType.FINISH:
logger.info(f"[ZCC worker{self.worker_id}] exit")
break
elif task_type == ZCCTaskType.UPDATE:
self.process_update_task(task_body)
if self.ema_coef is not None:
self.zcc_ema_processor = ZeroCostCheckpointEMAProcessor( # 在 update task 后刷新 EMA buffer
self.optimizer_fusion_storage_helper, self.param_fusion_storage_helper, self.ema_coef
)
if ema_ckpt_path is not None: # update ema if needed
logger.info(f"[ZCC EMA] load state dict from {ema_ckpt_path}")
with device_guard("cpu"):
state_dict = paddle.load(ema_ckpt_path)
if self.use_expert_parallel and self.dp_rank > 0:
state_dict = self._filter_moe_no_sync_optimizer_params(
self.model_meta_content, state_dict
)
self.zcc_ema_processor.load_ema_state_dict(state_dict)
logger.info("[ZCC EMA] done loading")
ema_ckpt_path = None
elif task_type == ZCCTaskType.PREPARE:
start_time = time.time()
save_info_tuple = task_body
self.process_prepare_task(task_body)
elif task_type == ZCCTaskType.OFFLOAD:
dumped = self.process_offload_task(dump=save_info_tuple is not None, global_step=task_body)
if dumped:
used_time = time.time() - start_time
logger.info(f"[ZCC Worker{self.worker_id}] used time {used_time:.3f} sec")
elif task_type == ZCCTaskType.SET_EMA_STATE_DICT:
ema_ckpt_path = task_body # mark ema state dict path
else:
raise ValueError(f"[ZCC Worker{self.worker_id}] Unknown task type: {task_type}")
except Exception as e:
import traceback
logger.info(f"[ZCC Worker{self.worker_id}] failed!!, Exception:{e}\n Traceback:{traceback.format_exc()}\n")
raise e
def build_fusion_storage_helper(self, optimizer_states_meta, model_states_meta):
(
accumulators_meta,
master_weights_meta,
merged_model_params_meta,
buffer_ipc_meta,
) = optimizer_states_meta
if self.optimizer_fusion_storage_helper is None:
self.optimizer_fusion_storage_helper = FusionStorageHelper(
accumulators_meta,
master_weights_meta,
merged_model_params_meta,
buffer_ipc_meta,
)
else:
self.optimizer_fusion_storage_helper.reset_meta(
accumulators_meta,
master_weights_meta,
merged_model_params_meta,
buffer_ipc_meta,
)
model_param_mappings, model_ipc_meta_mappings = model_states_meta
if self.param_fusion_storage_helper is None:
self.param_fusion_storage_helper = ParamFusionStorageHelper(model_param_mappings, model_ipc_meta_mappings)
else:
self.param_fusion_storage_helper.reset_meta(model_param_mappings, model_ipc_meta_mappings)
def manage_offload_chunk(self):
# TODO(@gexiao): more precise slice for different dtype
optimizer_offload_numel = self.optimizer_fusion_storage_helper.buffer_length
param_offload_numel = self.param_fusion_storage_helper.all_param_numel
self.all_numel = optimizer_offload_numel + param_offload_numel
self.chunk_size_in_numel = (self.all_numel - 1) // self.offload_chunks + 1
logger.info(
f"[ZCC Worker{self.worker_id}] All numel: {self.all_numel}, Offload chunks: {self.offload_chunks}, Chunk size: {self.chunk_size_in_numel}]"
)
class EMABuffer(ABC):
def __init__(self, resume_from_checkpoint, args, offload=True):
self.master_weights = {}
self.model_params = {}
self.args = args
self.offload = offload
if resume_from_checkpoint is not None:
self._load(resume_from_checkpoint)
def _load(self, resume_from_checkpoint):
ema_path = self._ema_path(resume_from_checkpoint)
if not os.path.exists(ema_path):
return
success, err_msg = self._check_consistent_dist_strategy(resume_from_checkpoint)
if not success:
logger.info(f"Cannot load EMA because: {err_msg}")
return
logger.info(f"Loading EMA checkpoint from {resume_from_checkpoint} ...")
with device_guard("cpu"):
ema_state_dict = paddle.load(ema_path)
logger.info(f"Load EMA checkpoint from {resume_from_checkpoint} done")
self.master_weights = ema_state_dict.pop("master_weights")
self.model_params = ema_state_dict
def save(self, global_step):
base_path = os.path.join(self.args.output_dir, f"{PREFIX_CHECKPOINT_DIR}-{global_step}")
ema_path = self._ema_path(base_path)
ema_state_dict = {"master_weights": self.master_weights}
ema_state_dict.update(self.model_params)
os.makedirs(base_path, exist_ok=True)
logger.info(f"Saving EMA checkpoint to {base_path} ...")
paddle.save(ema_state_dict, ema_path)
logger.info(f"Save EMA checkpoint to {base_path} done")
def ema_accumulate(self, global_step, loss, ema_loss_threshold):
if ema_loss_threshold is None or loss < ema_loss_threshold:
logger.info(f"EMA accumulating for step {global_step} ...")
self._ema_impl(
state_dict=self._get_master_weight(),
ema_state_dict=self.master_weights,
)
self._ema_impl(
state_dict=self._get_model_state(),
ema_state_dict=self.model_params,
)
logger.info(f"EMA accumulate done for step {global_step}")
def _ema_impl(self, state_dict, ema_state_dict):
ema_coef = self.args.zcc_save_ema_coef
for k, v in state_dict.items():
if k in ema_state_dict:
ema_tensor = ema_state_dict[k]
ema_tensor = ema_coef * ema_tensor.cuda() + (1 - ema_coef) * v.cuda()
ema_tensor.name = v.name
v = ema_tensor
del ema_tensor
if self.offload:
v_pin = v.pin_memory()
v_pin.name = v.name
v = v_pin
ema_state_dict[k] = v
@abstractmethod
def _get_master_weight(self):
pass
@abstractmethod
def _get_model_state(self):
pass
@abstractmethod
def _check_consistent_dist_strategy(self, resume_from_checkpoint):
pass
class EMABufferShardingIOBased(EMABuffer):
def __init__(self, resume_from_checkpoint, args, sharding_io, offload=True):
assert sharding_io is not None, "EMA should be only enabled when save_sharded_model is True"
self.sharding_io = sharding_io
super().__init__(resume_from_checkpoint, args, offload)
def _ema_path(self, base_path):
path = _add_variant(PADDLE_OPTIMIZER_NAME, self.args.optimizer_name_suffix)
path = path.replace("optimizer", "ema")
return os.path.join(base_path, path)
def _get_model_state(self):
return self.sharding_io.manipulate_state_dict_and_config(
unwrap_model(self.sharding_io.model),
merge_tensor_parallel=False,
)[0]
def _get_master_weight(self):
return self.sharding_io.optimizer.state_dict()["master_weights"]
def _check_consistent_dist_strategy(self, resume_from_checkpoint):
return self.sharding_io.check_same_strategy(resume_from_checkpoint)
class EMABufferFcBased(EMABuffer):
def __init__(self, resume_from_checkpoint, args, offload=True, hcg=None, model=None, optimizer=None):
self.hcg = hcg
self.model = model
self.optimizer = optimizer
self.dist_info_collector_and_validator = DistInfoCollectorValidator(args, hcg)
self.device_id = int(os.getenv("FLAGS_selected_gpus"))
super().__init__(resume_from_checkpoint, args, offload)
def _get_model_meta(self):
return self.dist_info_collector_and_validator.gather_distributed_model_meta(self.model, self.optimizer)
def _ema_path(self, base_path):
return os.path.join(base_path, "ema_state", f"{dist.get_rank()}_0.distcp")
def _check_consistent_dist_strategy(self, resume_from_checkpoint):
return self.dist_info_collector_and_validator.check_same_strategy(resume_from_checkpoint)
def _get_model_state(self):
assert self.model is not None, "expected model is not None"
return self.model.state_dict()
def _get_master_weight(self):
assert self.optimizer is not None, "expected optimizer is not None"
return self.optimizer.state_dict()["master_weights"]
def save(self, global_step):
model_meta_content = self._get_model_meta()
base_path = os.path.join(self.args.output_dir, f"{PREFIX_CHECKPOINT_DIR}-{global_step}")
os.makedirs(base_path, exist_ok=True)
model_meta_path = os.path.join(base_path, MODEL_META_NAME)
if self.device_id == 0:
with open(model_meta_path, "w") as f:
json.dump(model_meta_content, f)
super().save(global_step)
class NonZCCEMACallback(TrainerCallback):
def __init__(self, ema_buffer: EMABuffer):
self.buffer = ema_buffer
@staticmethod
def create_nonzcc_callback(
args, resume_from_checkpoint, sharding_io=None, model=None, optimizer=None, hcg=None, offload=True
):
if args.save_checkpoint_format == "flex_checkpoint":
ema_buffer = EMABufferFcBased(
resume_from_checkpoint, args, offload=offload, hcg=hcg, model=model, optimizer=optimizer
)
else:
assert sharding_io is not None, "EMA should be only enabled when save_sharded_model is True"
ema_buffer = EMABufferShardingIOBased(resume_from_checkpoint, args, sharding_io, offload=offload)
return NonZCCEMACallback(ema_buffer)
def on_step_end(self, args, state, control, **kwargs):
if state.global_step % args.zcc_ema_interval == 0:
self.buffer.ema_accumulate(state.global_step, state.loss, args.zcc_ema_loss_threshold)
if control.should_save:
self.buffer.save(state.global_step)
class DistInfoCollectorValidator:
def __init__(self, args, hcg=None):
self.args = args
self.hcg = hcg
if self.hcg is None:
self.hcg = fleet.get_hybrid_communicate_group()
def _load_model_meta_impl(self, dir):
meta_path = os.path.join(dir, MODEL_META_NAME)
assert os.path.exists(meta_path), f"{meta_path} not exist"
with open(meta_path, "r") as handle:
model_dist_meta = json.load(handle)
assert "parallel_config" in model_dist_meta
self._check_distributed_strategy(model_dist_meta["parallel_config"])
return model_dist_meta
def _all_gather_simple_object(self, obj, group=None):
if group is None:
group = self.hcg.get_sharding_parallel_group()
res = []
if group.nranks < 2:
return [obj]
paddle.distributed.all_gather_object(res, obj, group)
return res
def _sharding_meta_suffix(self, tp_rank=None, pp_rank=None):
if tp_rank is None:
tp_rank = self.args.tensor_parallel_rank
if pp_rank is None:
pp_rank = self.args.pipeline_parallel_rank
suffix = f"tp{tp_rank:0>2d}_pp{pp_rank:0>2d}"
if self.args.expert_parallel_degree > 1:
ep_rank = self.args.expert_parallel_rank
return f"{suffix}_ep{ep_rank:0>2d}"
else:
return suffix
def _gather_sharding_metas(self, model, optimizer):
nranks = dist.get_world_size()
if not self.args.use_hybrid_parallel or nranks <= 1:
return None
if not reshard_util.is_sharding_opt(optimizer):
return None
sharding_strategy = reshard_util.get_sharding_strategy(optimizer)
param2rank = {}
pp_overlap = False
if sharding_strategy == SHARDING_STRATEGY_V1:
optimizer = unwrap_optimizer(optimizer, DygraphShardingOptimizer)
param2rank = {k: v for (k, v) in optimizer._param2rank.items()}
else:
pp_overlap = unwrap_optimizer(optimizer, DygraphShardingOptimizerV2).pp_overlap
structure_name_mapping = {}
param_meta = {}
for k, v in model.state_dict().items():
structure_name_mapping[k] = v.name
is_distributed = getattr(v, "is_distributed", False)
no_sync = getattr(v, "no_sync", False)
param_meta[k] = (v.shape, int(v.dtype), is_distributed, no_sync)
sharding_metas = {}
sharding_meta = {}
sharding_meta["param2rank"] = param2rank
sharding_meta["structure_name_mapping"] = structure_name_mapping
sharding_meta["param_meta"] = param_meta
sharding_meta["param_meta_keys"] = ["shape", "dtype", "is_distributed", "no_sync"]
sharding_meta["sharding_strategy"] = sharding_strategy
sharding_meta["enable_overlap"] = pp_overlap
suffix = self._sharding_meta_suffix()
sharding_metas[suffix] = sharding_meta
sharding_metas_list = self._all_gather_simple_object(sharding_metas, self.hcg.get_model_parallel_group())
sharding_metas = {k: v for e in sharding_metas_list for (k, v) in e.items()}
sharding_metas_list = self._all_gather_simple_object(sharding_metas, self.hcg.get_pipe_parallel_group())
sharding_metas = {k: v for e in sharding_metas_list for (k, v) in e.items()}
if self.args.expert_parallel_degree > 1:
sharding_metas_list = self._all_gather_simple_object(sharding_metas, self.hcg.get_expert_parallel_group())
sharding_metas = {k: v for e in sharding_metas_list for (k, v) in e.items()}
return sharding_metas
def _check_distributed_strategy(self, parallel_config):
ep_degree = parallel_config.get("ep_degree", 1)
if ep_degree > 1:
tp_degree = parallel_config["mp_degree"]
sharding_degree = parallel_config["sharding_degree"]
moe_sharding_degree = parallel_config.get("moe_sharding_degree", 1)
assert tp_degree * sharding_degree == ep_degree * moe_sharding_degree, "mismatch parallel degree settings"
def _get_distributed_strategy(self):
pp_degree = 1
mp_degree = 1
sharding_degree = 1
ep_degree = 1
moe_sharding_degree = 1
nranks = dist.get_world_size()
if self.args.use_hybrid_parallel and nranks > 1:
hcg = fleet.get_hybrid_communicate_group()
mp_degree = hcg.get_model_parallel_world_size()
pp_degree = hcg.get_pipe_parallel_world_size()
sharding_degree = hcg.get_sharding_parallel_world_size()
if hasattr(hcg, "get_expert_parallel_world_size"):
ep_degree = hcg.get_expert_parallel_world_size()
if hasattr(hcg, "get_moe_sharding_parallel_world_size"):
moe_sharding_degree = hcg.get_moe_sharding_parallel_world_size()
parallel_config = {
"pp_degree": pp_degree,
"mp_degree": mp_degree,
"sharding_degree": sharding_degree,
"ep_degree": ep_degree,
"moe_sharding_degree": moe_sharding_degree,
}
self._check_distributed_strategy(parallel_config)
return parallel_config
def gather_distributed_model_meta(self, model, optimizer):
if not self.args.use_hybrid_parallel:
return None
if not (self.args.should_save_sharding_stage1_model or self.args.save_checkpoint_format == "flex_checkpoint"):
return None
nranks = dist.get_world_size()
if nranks <= 1:
return None
model_meta = {}
model_meta["parallel_config"] = self._get_distributed_strategy()
model_meta["sharding_metas"] = self._gather_sharding_metas(model, optimizer)
return model_meta
def check_same_strategy(self, resume_from_checkpoint=None):
if resume_from_checkpoint:
cur_config = self._get_distributed_strategy()
old_config = self._load_model_meta_impl(resume_from_checkpoint)["parallel_config"]
keys = list(old_config.keys())
for key in keys:
if key not in cur_config:
return False, f"missing {key}"
else:
old_value = old_config[key]
cur_value = cur_config[key]
if old_value != cur_value:
return False, f"{key} not match: {old_value} vs {cur_value}"
return True, None
def saved_ckptmeta(state_dict, ckpt_file_name, process_group=None, replicate_saved_into_local=False):
with paddle.base.dygraph.guard():
assert isinstance(state_dict, dict), "The state_dict should be a dictionary."
flat_state_dict, mapping = flatten_state_dict(state_dict)
if len(flat_state_dict) > 0:
for val in flat_state_dict.values():
assert isinstance(
val, (paddle.Tensor, ShardedWeight)
), f"The value of state_dict should be a paddle.Tensor or ShardedWeight, but got: {val}."
use_dist = True if paddle.distributed.get_world_size() > 1 else False
if use_dist and process_group is None and not is_initialized():
# Init the default global process group
paddle.distributed.init_parallel_env()
metadata = Metadata()
local_state_dict_filter_map = {}
local_state_dict_metadata = {}
local_storage_metadata = {}
global_shape = None
for key, val in flat_state_dict.items():
assert isinstance(val, ShardedWeight), f"expected ShardedWeight, but got {type(val)}"
local_tensor = val.local_tensor
local_shape = val.local_shape
global_offset = val.global_offset
global_shape = val.global_shape
is_flattened = val.is_flattened
flattened_range = val.flattened_range
local_tensor_dtype = str(local_tensor.dtype).split(".")[1]
if flattened_range is not None:
flattened_range = (flattened_range.start, flattened_range.stop)
else:
flattened_range = None
local_state_dict_metadata[key] = LocalTensorMetadata(
tuple(global_offset),
tuple(local_shape),
local_tensor_dtype,
tuple(global_shape),
is_flattened,
flattened_range,
)
local_storage_metadata[
LocalTensorIndex(
key, tuple(global_offset), is_flattened, flattened_range, local_shape=tuple(local_shape)
)
] = ckpt_file_name
local_state_dict_filter_map[key] = False
global_state_dict_metadata = []
global_storage_metadata = []
global_flatten_mapping = []
if use_dist:
paddle.distributed.all_gather_object(
global_state_dict_metadata,
local_state_dict_metadata,
process_group,
)
paddle.distributed.all_gather_object(global_storage_metadata, local_storage_metadata, process_group)
paddle.distributed.all_gather_object(global_flatten_mapping, mapping, process_group)
else:
global_state_dict_metadata.append(local_state_dict_metadata)
global_storage_metadata.append(local_storage_metadata)
global_flatten_mapping.append(mapping)
def balanced_dedup_key_in_dict(global_storage_metadata):
lti_to_files = defaultdict(set)
for storage_metadata in global_storage_metadata:
for lti, fname in storage_metadata.items():
lti_to_files[lti].add(fname)
file_load = defaultdict(int)
out = {}
for lti, file_candidates in lti_to_files.items():
candidates = sorted(file_candidates)
selected_main_file = min(candidates, key=lambda f: file_load[f])
file_load[selected_main_file] += 1
if replicate_saved_into_local:
lti_main = replace(lti, replica_id=0)
out[lti_main] = selected_main_file
replica_id = 1
for fname in candidates:
if fname == selected_main_file:
continue
lti_replica = replace(lti, replica_id=replica_id)
out[lti_replica] = fname
replica_id += 1
else:
out[lti] = selected_main_file
return out
metadata.state_dict_metadata = merge_state_dict_metadata(global_state_dict_metadata)
metadata.storage_metadata = balanced_dedup_key_in_dict(global_storage_metadata)
metadata.flat_mapping = dedup_key_in_dict(global_flatten_mapping)
# logger.debug(f"metadata:{metadata}")
def _gen_filter_map():
for tensor_index, file_name in metadata.storage_metadata.items():
rank = int(file_name.split(".")[0].split("_")[0])
if tensor_index in local_storage_metadata and rank != paddle.distributed.get_rank():
# 'True' represents that this tensor is not needed by the current rank.
local_state_dict_filter_map[tensor_index.tensor_key] = True
_gen_filter_map()
# logger.debug(f"local_state_dict_filter_map:{local_state_dict_filter_map}")
return metadata, local_state_dict_filter_map
class ZeroCostCheckpointCallbackFcBased(ZeroCostCheckpointCallback):
def __init__(self, args, zcc_manager, timer, unused_arg):
self.manager = zcc_manager
self.runtime_timer = timer
self.user_file_list = []
self.model_meta = None
self.zcc_ema_interval = args.zcc_ema_interval
self.args = args
if paddle.distributed.get_world_size() > 1 and self.args.use_hybrid_parallel:
self.hcg = fleet.get_hybrid_communicate_group()
self.sharding_group = self.hcg.get_sharding_parallel_group()
def _manipulate_state_dict_and_config(self, model_to_save, optimizer):
group_getter = GroupGetter(model_to_save)
gids = group_getter.get_group_ids()
from paddlenlp.trainer.utils.sharding_io import exclude_parameters_in_state_dict
state_dict = model_to_save.state_dict()
if self.args.bf16:
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):
logger.info("Start caching metas for sharded save...")
(self.manipulated_state_dict) = self._manipulate_state_dict_and_config(model, optimizer)
def recover_sharded_state_dict():
filtered_sharded_state_dict = {}
model_sharded_state_dict = model.sharded_state_dict()
for k, v in self.manipulated_state_dict.items():
filtered_sharded_state_dict[k] = model_sharded_state_dict[k]
return filtered_sharded_state_dict
self.manipulated_state_dict = recover_sharded_state_dict()
logger.info("Cache manipulated static dict done.")
model_to_save = unwrap_model(model)
dtype = get_parameter_dtype(model_to_save)
model_to_save.config.dtype = str(dtype).split(".")[1]
self.manipulated_config_to_save = copy.deepcopy(model_to_save.config)
self.manipulated_config_to_save.architectures = [clean_model_class_name(model_to_save.__class__.__name__)]
self.manipulated_config_to_save = self.manipulated_config_to_save.to_json_string(use_diff=True)
logger.info("Cache manipulated model config done")
self.model_meta = DistInfoCollectorValidator(self.args, self.hcg).gather_distributed_model_meta(
model, optimizer
)
def create_ckpt_file_name():
data_file_name = f"{paddle.distributed.get_rank()}_0.distcp"
meta_file_name = "0.metadata"
return (data_file_name, meta_file_name)
# model state ckpt meta and filter
self.ckpt_data_name, self.ckpt_meta_name = create_ckpt_file_name()
# self.model_ckpt_meta, self.model_state_filter = saved_ckptmeta(model.sharded_state_dict(), self.ckpt_data_name)
self.model_ckpt_meta, self.model_state_filter = saved_ckptmeta(
self.manipulated_state_dict,
self.ckpt_data_name,
replicate_saved_into_local=self.args.replicate_saved_into_local,
)
# opt state dict ckpt meta and filter
opt_state_dict_tmp = optimizer.sharded_state_dict(model.sharded_state_dict())
opt_state_dict = {}
master_weights = {}
for k, v in opt_state_dict_tmp.items():
if k.endswith(".w_0"):
master_weights[k] = v
else:
opt_state_dict[k] = v
self.opt_ckpt_meta, self.opt_state_filter = saved_ckptmeta(
opt_state_dict, self.ckpt_data_name, replicate_saved_into_local=self.args.replicate_saved_into_local
)
self.master_weight_ckpt_meta, self.master_weights_filter = saved_ckptmeta(
master_weights, self.ckpt_data_name, replicate_saved_into_local=self.args.replicate_saved_into_local
)
# gen unified name mapping for optimzier
self.unified_name_mapping, self.param_slice_info = self._gen_unified_name(
optimizer, model.sharded_state_dict()
)
logger.info("Cache distributed model meta done.")
def _gen_unified_name(self, optimizer, model_sharded_state_dict):
param_slice_info = {}
padded_param = set()
for buffer in optimizer._comm_buffer_list:
for (
param_name,
grad_view,
) in buffer._sharding_param_grad_view.items():
numel = grad_view._param.numel().item()
param_begin = grad_view._param_begin
param_end = grad_view._param_end
index = grad_view._index
padding_begin = index + numel
flattened_range = slice(
param_begin - index,
max(
min(padding_begin - index, param_end - index),
param_begin - index,
),
)
if param_end > padding_begin:
padded_param.add(param_name)
param_slice_info[param_name] = flattened_range
_FP32_MASTER = "fp32_master_0"
_optimizer_scalar_name = [
"beta1_pow_acc_0",
"beta2_pow_acc_0",
]
_optimizer_non_scaler_name = [
"moment1_0",
"moment2_0",
"velocity_0",
]
def _generate_base_static_name(vname):
if _FP32_MASTER in vname:
return tuple(vname.split("_" + _FP32_MASTER + "_", 1))
for name in _optimizer_scalar_name + _optimizer_non_scaler_name:
if vname.endswith(name):
return vname[: -(len(name) + 1)], name
raise ValueError(f"Cannot split variable name: {vname}.")
model_sharded_state_dict = dict(sorted(model_sharded_state_dict.items()))
static_to_struct_mapping = {}
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
optimizer_state_dict = optimizer.state_dict()
optimizer_unified_name_mapping = {}
unified_slice_info = {}
master_weights = optimizer_state_dict.pop("master_weights", None)
optimizer_state_dict.pop("LR_Scheduler", None)
for key, _ in optimizer_state_dict.items():
static_name, optim_state_type = _generate_base_static_name(key)
struct_name = static_to_struct_mapping[static_name]
unified_name = f"{struct_name}.{optim_state_type}"
flattened_range = param_slice_info[static_name]
# if flattened_range.stop - flattened_range.start == 0:
# continue
optimizer_unified_name_mapping[key] = unified_name
unified_slice_info[unified_name] = flattened_range
if master_weights is not None:
for key, _ in master_weights.items():
struct_name = static_to_struct_mapping[key]
unified_name = f"{struct_name}.w_0"
flattened_range = param_slice_info[key]
# if flattened_range.stop - flattened_range.start == 0:
# continue
optimizer_unified_name_mapping[key] = unified_name
unified_slice_info[unified_name] = flattened_range
return optimizer_unified_name_mapping, unified_slice_info
def _pack_dynamic_objects(self):
dynamic_objecs = {}
dynamic_objecs["optimizer_states_meta"] = self.optimizer_states_meta
dynamic_objecs["model_states_meta"] = self.model_states_meta
dynamic_objecs["distcp_file_name"] = (self.ckpt_data_name, self.ckpt_meta_name)
dynamic_objecs["model_ckpt_meta"] = self.model_ckpt_meta
dynamic_objecs["model_state_filter"] = self.model_state_filter
dynamic_objecs["opt_ckpt_meta"] = self.opt_ckpt_meta
dynamic_objecs["opt_state_filter"] = self.opt_state_filter
dynamic_objecs["master_weight_ckpt_meta"] = self.master_weight_ckpt_meta
dynamic_objecs["master_weights_filter"] = self.master_weights_filter
dynamic_objecs["unified_name_mapping"] = self.unified_name_mapping
dynamic_objecs["param_slice_info"] = self.param_slice_info
return dynamic_objecs
def maybe_update_zcc_worker(self, args, model, optimizer, global_step):
# logger.info(f"check should update :{optimizer.fused_buffer_version} vs {self.manager.cache_version}")
if optimizer.fused_buffer_version == self.manager.cache_version:
return
logger.info("ZCC checkpoint workers need upgrade.")
self._cache_meta_for_sharded_save(model, optimizer)
param_mappings, ipc_meta_mappings = get_fused_param_mappings(optimizer, self.manipulated_state_dict)
self.optimizer_states_meta = (
optimizer.fused_states_accumulators_meta,
optimizer.fused_states_master_weights_meta,
None,
optimizer.fused_states_buffer_ipc_meta,
)
self.model_states_meta = (param_mappings, ipc_meta_mappings)
dynamic_objects = self._pack_dynamic_objects()
static_objects = self._pack_static_objects(args)
self.manager.update_zcc_workers(optimizer.fused_buffer_version, dynamic_objects, static_objects, global_step)
logger.info(f"[ZCC Callback] after first update:{optimizer.fused_states_buffer_ipc_meta}")
class ZeroCostCheckpointWorkerFcBased(ZeroCostCheckpointWorker):
def process_update_task(self, updates):
"""
sync operation, main process should wait
"""
version, dynamic_objecs, static_objects = updates
self.distcp_file_name = dynamic_objecs["distcp_file_name"]
self.model_ckpt_meta = dynamic_objecs["model_ckpt_meta"]
self.model_state_filter = dynamic_objecs["model_state_filter"]
self.opt_ckpt_meta = dynamic_objecs["opt_ckpt_meta"]
self.opt_state_filter = dynamic_objecs["opt_state_filter"]
self.master_weight_ckpt_meta = dynamic_objecs["master_weight_ckpt_meta"]
self.master_weights_filter = dynamic_objecs["master_weights_filter"]
self.unified_name_mapping = dynamic_objecs["unified_name_mapping"]
self.param_slice_info = dynamic_objecs["param_slice_info"]
optimizer_states_meta = dynamic_objecs["optimizer_states_meta"]
model_states_meta = dynamic_objecs["model_states_meta"]
self.build_fusion_storage_helper(optimizer_states_meta, model_states_meta)
self.model_config_content = static_objects["model_config"]
self.training_args_content = static_objects["training_args"]
self.model_meta_content = static_objects["model_meta"]
self.user_file_list = static_objects["user_file"]
self.manage_offload_chunk()
self.version.value = version
def _replace_pname_with_unified(self, state_dict):
new_state_dict = OrderedDict()
for key, value in state_dict.items():
assert key in self.unified_name_mapping, f"{key} not in {self.unified_name_mapping.keys()}"
new_key = self.unified_name_mapping[key]
new_state_dict[new_key] = value
return new_state_dict
@staticmethod
def _filter_state_dict(state_dict, filter_map):
need_remove_keys = []
for k, _ in state_dict.items():
# two case:
# 1. Mutliple key share the same tensor.
# 2. Don't need to be saved in current rank.
if k not in filter_map.keys():
logger.debug(f"[ZCC worker] {k} not exist in filter map.")
if (k not in filter_map.keys()) or filter_map[k]:
need_remove_keys.append(k)
for k in need_remove_keys:
state_dict.pop(k)
return state_dict
@staticmethod
def _slice_padded_tensor(static_dict, param_slice_info):
new_static_dict = {}
for k, v in static_dict.items():
if k in param_slice_info:
logger.info(f"[ZCC worker] Slice padded tensor of {k}")
flattened_range = param_slice_info[k]
new_static_dict[k] = paddle.slice(
v,
axes=[0],
starts=[0],
ends=[flattened_range.stop - flattened_range.start],
)
else:
new_static_dict[k] = v
return new_static_dict
def _save_model_state(self, output_dir):
data_file_name, meta_file_name = self.distcp_file_name
self.model_states_path = os.path.join(output_dir, "model_state", data_file_name)
self.model_states_meta_path = os.path.join(output_dir, "model_state", meta_file_name)
if self.dp_rank <= 0 or self.use_expert_parallel:
with device_guard("cpu"):
state_dict = self.param_fusion_storage_helper.state_dict()
logger.debug(f"model states key before filter is {state_dict.keys()}")
state_dict = self._filter_state_dict(state_dict, self.model_state_filter)
logger.debug(f"model states length is {len(state_dict)}")
paddle.save(state_dict, self.model_states_path)
if self.device_id == 0:
paddle.save(self.model_ckpt_meta, self.model_states_meta_path)
logger.info("[ZCC worker] Finish model states saved.")
def _save_opt_state(self, output_dir):
data_file_name, meta_file_name = self.distcp_file_name
self.opt_state_path = os.path.join(output_dir, "optimizer_state", data_file_name)
self.opt_state_meta_path = os.path.join(output_dir, "optimizer_state", meta_file_name)
self.master_weight_path = os.path.join(output_dir, "master_weight", data_file_name)
self.master_weight_meta_path = os.path.join(output_dir, "master_weight", meta_file_name)
if self.dp_rank <= 0 or self.use_expert_parallel:
with device_guard("cpu"):
opt_state_dict = self.optimizer_fusion_storage_helper.state_dict()
master_weights = opt_state_dict.pop("master_weights", {})
opt_state_dict = self._replace_pname_with_unified(opt_state_dict)
logger.info("[ZCC worker] opt state dict replace pname using unified name.")
master_weights = self._replace_pname_with_unified(master_weights)
logger.info("[ZCC worker] master weightsdict replace pname using unified name.")
opt_state_dict = self._slice_padded_tensor(opt_state_dict, self.param_slice_info)
logger.info("[ZCC worker] opt state dict slice padded tensor complete.")
master_weights = self._slice_padded_tensor(master_weights, self.param_slice_info)
logger.info("[ZCC worker] master weights slice padded tensor complete.")
if self.dp_rank > 0: # ep
opt_state_dict = self._filter_moe_no_sync_optimizer_params(self.model_meta_content, opt_state_dict)
opt_state_dict = self._filter_state_dict(opt_state_dict, self.opt_state_filter)
logger.info("[ZCC worker] opt state dict filter by opt_state_filter complete.")
master_weights = self._filter_state_dict(master_weights, self.master_weights_filter)
logger.info("[ZCC worker] master weights dict filter by master_weights_filter complete.")
logger.debug(f"opt states length is {len(opt_state_dict)}")
logger.debug(f"master weights length is {len(master_weights)}")
paddle.save(opt_state_dict, self.opt_state_path)
paddle.save(master_weights, self.master_weight_path)
if self.device_id == 0:
paddle.save(self.opt_ckpt_meta, self.opt_state_meta_path)
paddle.save(self.master_weight_ckpt_meta, self.master_weight_meta_path)
logger.info("[ZCC worker] Finish opt states and master weights saved.")
def _save_ema_state(self, output_dir):
data_file_name, meta_file_name = self.distcp_file_name
if (self.dp_rank <= 0 or self.use_expert_parallel) and self.ema_coef is not None:
self.ema_name_path = os.path.join(output_dir, "ema_state", data_file_name)
ema_state_dict = self.zcc_ema_processor.ema_state_dict()
if self.dp_rank > 0:
ema_state_dict = self._filter_moe_no_sync_optimizer_params(self.model_meta_content, ema_state_dict)
logger.debug(f"ema states length is {len(ema_state_dict)}")
paddle.save(ema_state_dict, self.ema_name_path)
logger.info("[ZCC worker] Finish ema states saved.")
def _dump_states(self, output_dir):
self._save_model_state(output_dir)
self._save_opt_state(output_dir)
self._save_ema_state(output_dir)