| @@ -1,11 +1,16 @@ | |||
| import os | |||
| from pathlib import Path | |||
| from typing import Optional, Union, Callable, Dict, Tuple, Sequence, List | |||
| from typing import Union, Dict, List | |||
| from .torch_driver import TorchDriver | |||
| from .ddp import TorchDDPDriver | |||
| from .utils import _create_default_config, _DDPWrappingModel | |||
| from fastNLP.core.utils import nullcontext | |||
| from fastNLP.core.log import logger | |||
| from fastNLP.envs.env import FASTNLP_DISTRIBUTED_CHECK | |||
| from fastNLP.envs import( | |||
| FASTNLP_DISTRIBUTED_CHECK, | |||
| FASTNLP_CHECKPOINT_FILENAME | |||
| ) | |||
| from fastNLP.envs.imports import _NEED_IMPORT_TORCH, _NEED_IMPORT_DEEPSPEED | |||
| if _NEED_IMPORT_TORCH: | |||
| @@ -79,6 +84,15 @@ class DeepSpeedDriver(TorchDDPDriver): | |||
| self._has_setup = False # 设置这一参数是因为 evaluator 中也会进行 setup 操作,但是显然是不需要的也不应该的; | |||
| self._has_ddpwrapped = False # 判断传入的模型是否经过 _has_ddpwrapped 包裹; | |||
| self.strategy = strategy | |||
| self.accumulation_steps = kwargs.get("accumulation_steps", 1) | |||
| # 获取 batch_size 以设置 train_micro_batch_size_per_gpu 参数 | |||
| train_dl = kwargs.get("train_dataloader", None) | |||
| if train_dl is not None: | |||
| self.train_micro_batch_size = self.get_dataloader_args(train_dl) | |||
| else: | |||
| logger.warn("No `train_dataloader` found, and we will set `train_micro_batch_size_per_gpu`" | |||
| "to 1 for deepspeed configuration.") | |||
| self.train_micro_batch_size = 1 | |||
| self._ds_kwargs = kwargs.get("deepspeed_kwargs", {}) | |||
| @@ -93,8 +107,8 @@ class DeepSpeedDriver(TorchDDPDriver): | |||
| raise ValueError("Multi optimizers is not supported for DeepSpeedDriver right now.") | |||
| if self._has_setup: | |||
| return | |||
| self.setup_config() | |||
| self._has_setup = True | |||
| self.setup_config() | |||
| # 如果用户需要使用多机模式,那么一定进入到这里; | |||
| if self.is_pull_by_torch_run: | |||
| # dist.get_world_size() 只能在 dist.init_process_group 初始化之后进行调用; | |||
| @@ -152,12 +166,14 @@ class DeepSpeedDriver(TorchDDPDriver): | |||
| # 设置 deepspeed | |||
| if not isinstance(self.model, deepspeed.DeepSpeedEngine): | |||
| model=_DDPWrappingModel(self.model) | |||
| model_parameters = filter(lambda p: p.requires_grad, model.parameters()) | |||
| self.model, ds_optimizer, _, _ = deepspeed.initialize( | |||
| model=_DDPWrappingModel(self.model), | |||
| model=model, | |||
| optimizer=self.optimizers[0], | |||
| config=self.config | |||
| model_parameters=model_parameters, | |||
| config=self.config, | |||
| ) | |||
| # TODO 是否有必要 | |||
| self._optimizers = [ds_optimizer] | |||
| if self.config.get("activation_checkpointing"): | |||
| @@ -174,6 +190,13 @@ class DeepSpeedDriver(TorchDDPDriver): | |||
| def setup_config(self): | |||
| self.config = self._ds_kwargs.get("config") | |||
| if self.config is not None: | |||
| # TODO 究竟哪些参数按照config,哪些按照trainer参数 | |||
| logger.warn("Notice that you have defined a configuration for deepspeed and parameters like" | |||
| "`optimizers`, `strategy` and `fp16` may not take effects.") | |||
| return | |||
| if self.strategy == "deepspeed": | |||
| self.config = _create_default_config(stage=2) | |||
| elif self.strategy == "deepspeed_stage_1": | |||
| @@ -202,13 +225,11 @@ class DeepSpeedDriver(TorchDDPDriver): | |||
| else: | |||
| raise ValueError(f"Unknown deepspeed strategy {self.strategy}.") | |||
| self.config.setdefault("train_micro_batch_size_per_gpu", 1) | |||
| # 设置成 max_int 防止 deepspeed 的输出干扰 fastnlp 的输出 | |||
| self.config.setdefault("steps_per_print", 2147483647) | |||
| self.config["gradient_accumulation_steps"] = self.accumulation_steps | |||
| self.config.setdefault("train_micro_batch_size_per_gpu", self.train_micro_batch_size) | |||
| # TODO 梯度裁剪的设置,这里需要用到trainer | |||
| # 从kwargs 获取 | |||
| # 精度设置 | |||
| # _format_precision_config | |||
| if self.fp16: | |||
| if "fp16" not in self.config: | |||
| # FP16 is a DeepSpeed standalone AMP implementation | |||
| @@ -238,6 +259,81 @@ class DeepSpeedDriver(TorchDDPDriver): | |||
| def unwrap_model(self): | |||
| r""" | |||
| :return: 返回原本的模型,例如没有被 ``DataParallel`` 包裹; | |||
| :return: 返回原本的模型; | |||
| """ | |||
| return self.model.module.model | |||
| def get_model_no_sync_context(self): | |||
| r""" | |||
| :return: 返回一个 ``context`` 上下文环境,用于关闭各个进程之间的同步;在 ``deepspeed`` 中,返回一个空的上下文 | |||
| """ | |||
| # 注意此时的 model 是 "DistributedDataParallel" 对象; | |||
| return nullcontext | |||
| def save_model(self, filepath: Union[str, Path], only_state_dict: bool = False, **kwargs): | |||
| """ | |||
| 保存当前 driver 的模型到 folder 下。 | |||
| :param filepath: 保存到哪个文件夹; | |||
| :param only_state_dict: 是否只保存权重; | |||
| :return: | |||
| """ | |||
| # deepspeed engine 要求在每个 rank 都调用 save_checkpoint,故去掉了 rank_zero_call 装饰器 | |||
| if self.zero_stage_3: | |||
| logger.rank_zero_warning( | |||
| "When saving the DeepSpeed Stage 3 checkpoint, " | |||
| "each worker will save a shard of the checkpoint within a directory. " | |||
| # TODO check一下 | |||
| # "If a single file is required after training, " | |||
| # "see https://pytorch-lightning.readthedocs.io/en/latest/advanced/advanced_gpu.html#" | |||
| # "deepspeed-zero-stage-3-single-file for instructions." | |||
| ) | |||
| if not only_state_dict: | |||
| logger.rank_zero_warning("Only saving state dict is not allowed for `DeepSpeedDriver`. We will save its " | |||
| "checkpoint for you instead.") | |||
| self.model.save_checkpoint(filepath, **kwargs) | |||
| def load_model(self, filepath: Union[Path, str], only_state_dict: bool = False, **kwargs): | |||
| """ | |||
| 从 folder 中加载权重并赋值到当前 driver 的模型上。 | |||
| :param filepath: 加载权重或模型的路径 | |||
| :param load_state_dict: 保存的内容是否只是权重。 | |||
| :param kwargs: | |||
| :return: | |||
| """ | |||
| if not only_state_dict: | |||
| logger.warn("Only loading state dict is not allowed for `DeepSpeedDriver`. We will load its " | |||
| "checkpoint for you instead.") | |||
| self.model.load_checkpoint(filepath, **kwargs) | |||
| def save_checkpoint(self, folder: Path, states: Dict, dataloader, only_state_dict: bool = True, should_save_model: bool = True, **kwargs): | |||
| # deepspeed engine 要求在每个 rank 都调用 save_checkpoint,故去掉了 rank_zero_call 装饰器 | |||
| # 1. 保存 sampler 的状态 | |||
| sampler_state_dict = self.get_sampler_state_dict() | |||
| # 2. 保存模型的状态; | |||
| if not should_save_model: | |||
| logger.rank_zero_warning("Saving checkpoint without model is not allowed for `DeepSpeedDriver`, " | |||
| "so we will still save the model for you.") | |||
| self.model.save_checkpoint(Path(folder).joinpath(FASTNLP_CHECKPOINT_FILENAME), | |||
| client_state=sampler_state_dict) | |||
| def load_checkpoint(self, folder: Path, dataloader, only_state_dict: bool = True, should_load_model: bool = True, **kwargs) -> Dict: | |||
| # 1. 加载模型状态; | |||
| if not should_load_model: | |||
| logger.rank_zero_warning("Loading checkpoint without model is not allowed for `DeepSpeedDriver`, " | |||
| "so we will still load the model for you.") | |||
| load_path, states = self.model.load_checkpoint(folder.joinpath(FASTNLP_CHECKPOINT_FILENAME)) | |||
| if load_path is None: | |||
| raise RuntimeError(f"Failed to load checkpoint from path: {str(folder)}") | |||
| # 2.恢复 sampler 的状态 | |||
| states = self.load_sampler_state_dict(states) | |||
| return states | |||
| @property | |||
| def stage_3(self) -> bool: | |||
| return self.config.get("zero_optimization") and self.config.get("zero_optimization").get("stage") == 3 | |||