CoolFace
Modelpublic

Inkdrop/gpt2-property-classifier

sourceHugging Facemitupdated 5y agoView on Hugging Face
0likes29downloads
zero_to_fp32.py454 linesDownload Raw Back to root
1#!/usr/bin/env python2 3# This script extracts fp32 consolidated weights from a zero 2 and 3 DeepSpeed checkpoints. It gets4# copied into the top level checkpoint dir, so the user can easily do the conversion at any point in5# the future. Once extracted, the weights don't require DeepSpeed and can be used in any6# application.7#8# example: python zero_to_fp32.py . pytorch_model.bin9 10import argparse11import torch12import glob13import math14import os15from collections import OrderedDict16 17# while this script doesn't use deepspeed to recover data, since the checkpoints are pickled with18# DeepSpeed data structures it has to be available in the current python environment.19import deepspeed20from deepspeed.utils import logger21 22debug = 023 24# load to cpu25device = torch.device('cpu')26 27 28def get_model_state_file(checkpoint_dir, zero_stage):29    if not os.path.isdir(checkpoint_dir):30        raise FileNotFoundError(f"Directory '{checkpoint_dir}' doesn't exist")31 32    # there should be only one file33    if zero_stage == 2:34        file = os.path.join(checkpoint_dir, "mp_rank_00_model_states.pt")35    elif zero_stage == 3:36        file = os.path.join(checkpoint_dir, "zero_pp_rank_0_mp_rank_00_model_states.pt")37 38    if not os.path.exists(file):39        raise FileNotFoundError(f"can't find model states file at '{file}'")40 41    return file42 43 44def get_optim_files(checkpoint_dir):45    # XXX: need to test that this simple glob rule works for multi-node setup too46    optim_files = sorted(glob.glob(os.path.join(checkpoint_dir, "*_optim_states.pt")))47 48    if len(optim_files) == 0:49        raise FileNotFoundError(50            f"can't find '*_optim_states.pt' files in directory '{checkpoint_dir}'")51 52    return optim_files53 54 55def parse_model_state(file):56    state_dict = torch.load(file, map_location=device)57 58    if "buffer_names" not in state_dict:59        raise ValueError(f"{file} is not a model state checkpoint")60    buffer_names = state_dict["buffer_names"]61    if debug:62        print("Found buffers:", buffer_names)63 64    # recover just the buffers while restoring them to fp32 if they were saved in fp1665    buffers = {66        k: v.float()67        for k,68        v in state_dict["module"].items() if k in buffer_names69    }70    return buffers71 72 73def parse_optim_states(files, ds_checkpoint_dir):74 75    total_files = len(files)76    state_dicts = []77    for f in files:78        state_dicts.append(torch.load(f, map_location=device))79 80    if not "zero_stage" in state_dicts[0]['optimizer_state_dict']:81        raise ValueError(f"{files[0]} is not a zero checkpoint")82    zero_stage = state_dicts[0]['optimizer_state_dict']["zero_stage"]83    world_size = state_dicts[0]['optimizer_state_dict']["partition_count"]84    param_shapes = state_dicts[0]["param_shapes"]85    # For ZeRO-2 each param group can have different partition_count as data parallelism for expert86    # parameters can be different from data parallelism for non-expert parameters. So we can just87    # use the max of the partition_count to get the dp world_size.88 89    if type(world_size) is list:90        world_size = max(world_size)91 92    if world_size != total_files:93        raise ValueError(94            f"Expected {world_size} of '*_optim_states.pt' under '{ds_checkpoint_dir}' but found {total_files} files. "95            "Possibly due to an overwrite of an old checkpoint, or a checkpoint didn't get saved by one or more processes."96        )97 98    # the groups are named differently in each stage99    if zero_stage == 2:100        fp32_groups_key = "single_partition_of_fp32_groups"101    elif zero_stage == 3:102        fp32_groups_key = "fp32_flat_groups"103    else:104        raise ValueError(f"unknown zero stage {zero_stage}")105 106    if zero_stage == 2:107        fp32_flat_groups = [108            state_dicts[i]['optimizer_state_dict'][fp32_groups_key]109            for i in range(len(state_dicts))110        ]111    elif zero_stage == 3:112        # if there is more than one param group, there will be multiple flattened tensors - one113        # flattened tensor per group - for simplicity merge them into a single tensor114        #115        # XXX: could make the script more memory efficient for when there are multiple groups - it116        # will require matching the sub-lists of param_shapes for each param group flattened tensor117 118        fp32_flat_groups = [119            torch.cat(state_dicts[i]['optimizer_state_dict'][fp32_groups_key],120                      0) for i in range(len(state_dicts))121        ]122 123    return zero_stage, world_size, param_shapes, fp32_flat_groups124 125 126def _get_fp32_state_dict_from_zero_checkpoint(ds_checkpoint_dir):127    """128    Returns fp32 state_dict reconstructed from ds checkpoint129 130    Args:131        - ``ds_checkpoint_dir``: path to the deepspeed checkpoint folder (where the optimizer files are)132 133    """134    print(f"Processing zero checkpoint '{ds_checkpoint_dir}'")135 136    optim_files = get_optim_files(ds_checkpoint_dir)137    zero_stage, world_size, param_shapes, fp32_flat_groups = parse_optim_states(optim_files, ds_checkpoint_dir)138    print(139        f"Detected checkpoint of type zero stage {zero_stage}, world_size: {world_size}")140 141    model_file = get_model_state_file(ds_checkpoint_dir, zero_stage)142    buffers = parse_model_state(model_file)143 144    if zero_stage == 2:145        return _get_fp32_state_dict_from_zero2_checkpoint(world_size,146                                                          param_shapes,147                                                          fp32_flat_groups,148                                                          buffers)149    elif zero_stage == 3:150        return _get_fp32_state_dict_from_zero3_checkpoint(world_size,151                                                          param_shapes,152                                                          fp32_flat_groups,153                                                          buffers)154 155 156def _get_fp32_state_dict_from_zero2_checkpoint(world_size,157                                               param_shapes,158                                               fp32_flat_groups,159                                               buffers):160 161    # Reconstruction protocol:162    #163    # XXX: document this164 165    if debug:166        for i in range(world_size):167            for j in range(len(fp32_flat_groups[0])):168                print(f"fp32_flat_groups[{i}][{j}].shape={fp32_flat_groups[i][j].shape}")169 170    # XXX: memory usage doubles here (zero2)171    num_param_groups = len(fp32_flat_groups[0])172    merged_single_partition_of_fp32_groups = []173    for i in range(num_param_groups):174        merged_partitions = [sd[i] for sd in fp32_flat_groups]175        full_single_fp32_vector = torch.cat(merged_partitions, 0)176        merged_single_partition_of_fp32_groups.append(full_single_fp32_vector)177    avail_numel = sum([178        full_single_fp32_vector.numel()179        for full_single_fp32_vector in merged_single_partition_of_fp32_groups180    ])181 182    if debug:183        wanted_params = sum([len(shapes) for shapes in param_shapes])184        wanted_numel = sum(185            [sum(shape.numel() for shape in shapes.values()) for shapes in param_shapes])186        # not asserting if there is a mismatch due to possible padding187        print(f"Have {avail_numel} numels to process.")188        print(f"Need {wanted_numel} numels in {wanted_params} params.")189 190    state_dict = OrderedDict()191 192    # buffers193    state_dict.update(buffers)194    if debug:195        print(f"added {len(buffers)} buffers")196 197    # params198    # XXX: for huge models that can't fit into the host's RAM we will have to recode this to support199    # out-of-core computing solution200    total_numel = 0201    total_params = 0202    for shapes, full_single_fp32_vector in zip(param_shapes, merged_single_partition_of_fp32_groups):203        offset = 0204        avail_numel = full_single_fp32_vector.numel()205        for name, shape in shapes.items():206 207            unpartitioned_numel = shape.numel()208            total_numel += unpartitioned_numel209            total_params += 1210 211            if debug:212                print(213                    f"{name} full shape: {shape} unpartitioned numel {unpartitioned_numel} "214                )215            state_dict[name] = full_single_fp32_vector.narrow(216                0,217                offset,218                unpartitioned_numel).view(shape)219            offset += unpartitioned_numel220 221        # Z2 started to align to 2*world_size to improve nccl performance. Therefore both offset and222        # avail_numel can differ by anywhere between 0..2*world_size. Due to two unrelated complex223        # paddings performed in the code it's almost impossible to predict the exact numbers w/o the224        # live optimizer object, so we are checking that the numbers are within the right range225        align_to = 2 * world_size226 227        def zero2_align(x):228            return align_to * math.ceil(x / align_to)229 230        if debug:231            print(f"original offset={offset}, avail_numel={avail_numel}")232 233        offset = zero2_align(offset)234        avail_numel = zero2_align(avail_numel)235 236        if debug:237            print(f"aligned  offset={offset}, avail_numel={avail_numel}")238 239        # Sanity check240        if offset != avail_numel:241            raise ValueError(242                f"consumed {offset} numels out of {avail_numel} - something is wrong")243 244    print(245        f"Reconstructed fp32 state dict with {total_params} params {total_numel} elements"246    )247 248    return state_dict249 250 251def zero3_partitioned_param_info(unpartitioned_numel, world_size):252    remainder = unpartitioned_numel % world_size253    padding_numel = (world_size - remainder) if remainder else 0254    partitioned_numel = math.ceil(unpartitioned_numel / world_size)255    return partitioned_numel, padding_numel256 257 258def _get_fp32_state_dict_from_zero3_checkpoint(world_size,259                                               param_shapes,260                                               fp32_flat_groups,261                                               buffers):262 263    # Reconstruction protocol: For zero3 we need to zip the partitions together at boundary of each264    # param, re-consolidating each param, while dealing with padding if any265 266    avail_numel = fp32_flat_groups[0].numel() * world_size267    # merge list of dicts, preserving order268    param_shapes = {k: v for d in param_shapes for k, v in d.items()}269 270    if debug:271        for i in range(world_size):272            print(f"fp32_flat_groups[{i}].shape={fp32_flat_groups[i].shape}")273 274        wanted_params = len(param_shapes)275        wanted_numel = sum(shape.numel() for shape in param_shapes.values())276        # not asserting if there is a mismatch due to possible padding277        print(f"Have {avail_numel} numels to process.")278        print(f"Need {wanted_numel} numels in {wanted_params} params.")279 280    state_dict = OrderedDict()281 282    # buffers283    state_dict.update(buffers)284    if debug:285        print(f"added {len(buffers)} buffers")286 287    # params288    # XXX: for huge models that can't fit into the host's RAM we will have to recode this to support289    # out-of-core computing solution290    offset = 0291    total_numel = 0292    total_params = 0293    for name, shape in param_shapes.items():294 295        unpartitioned_numel = shape.numel()296        total_numel += unpartitioned_numel297        total_params += 1298 299        partitioned_numel, partitioned_padding_numel = zero3_partitioned_param_info(unpartitioned_numel, world_size)300 301        if debug:302            print(303                f"{total_params} {name} full shape: {shape} partition0 numel={partitioned_numel} partitioned_padding_numel={partitioned_padding_numel}"304            )305 306        # XXX: memory usage doubles here307        state_dict[name] = torch.cat(308            tuple(fp32_flat_groups[i].narrow(0,309                                             offset,310                                             partitioned_numel)311                  for i in range(world_size)),312            0).narrow(0,313                      0,314                      unpartitioned_numel).view(shape)315        offset += partitioned_numel316 317    offset *= world_size318 319    # Sanity check320    if offset != avail_numel:321        raise ValueError(322            f"consumed {offset} numels out of {avail_numel} - something is wrong")323 324    print(325        f"Reconstructed fp32 state dict with {total_params} params {total_numel} elements"326    )327 328    return state_dict329 330 331def get_fp32_state_dict_from_zero_checkpoint(checkpoint_dir, tag=None):332    """333    Convert ZeRO 2 or 3 checkpoint into a single fp32 consolidated state_dict that can be loaded with334    ``load_state_dict()`` and used for training without DeepSpeed or shared with others, for example335    via a model hub.336 337    Args:338        - ``checkpoint_dir``: path to the desired checkpoint folder339        - ``tag``: checkpoint tag used as a unique identifier for checkpoint. If not provided will attempt to load tag in 'latest' file. e.g., ``global_step14``340 341    Returns:342        - pytorch ``state_dict``343 344    Note: this approach may not work if your application doesn't have sufficient free CPU memory and345    you may need to use the offline approach using the ``zero_to_fp32.py`` script that is saved with346    the checkpoint.347 348    A typical usage might be ::349 350        from deepspeed.utils.zero_to_fp32 import get_fp32_state_dict_from_zero_checkpoint351        # do the training and checkpoint saving352        state_dict = get_fp32_state_dict_from_zero_checkpoint(checkpoint_dir) # already on cpu353        model = model.cpu() # move to cpu354        model.load_state_dict(state_dict)355        # submit to model hub or save the model to share with others356 357    In this example the ``model`` will no longer be usable in the deepspeed context of the same358    application. i.e. you will need to re-initialize the deepspeed engine, since359    ``model.load_state_dict(state_dict)`` will remove all the deepspeed magic from it.360 361    If you want it all done for you, use ``load_state_dict_from_zero_checkpoint`` instead.362 363    """364    if tag is None:365        latest_path = os.path.join(checkpoint_dir, 'latest')366        if os.path.isfile(latest_path):367            with open(latest_path, 'r') as fd:368                tag = fd.read().strip()369        else:370            raise ValueError(f"Unable to find 'latest' file at {latest_path}")371 372    ds_checkpoint_dir = os.path.join(checkpoint_dir, tag)373 374    if not os.path.isdir(ds_checkpoint_dir):375        raise FileNotFoundError(f"Directory '{ds_checkpoint_dir}' doesn't exist")376 377    return _get_fp32_state_dict_from_zero_checkpoint(ds_checkpoint_dir)378 379 380def convert_zero_checkpoint_to_fp32_state_dict(checkpoint_dir, output_file, tag=None):381    """382    Convert ZeRO 2 or 3 checkpoint into a single fp32 consolidated ``state_dict`` file that can be383    loaded with ``torch.load(file)`` + ``load_state_dict()`` and used for training without DeepSpeed.384 385    Args:386        - ``checkpoint_dir``: path to the desired checkpoint folder. (one that contains the tag-folder, like ``global_step14``)387        - ``output_file``: path to the pytorch fp32 state_dict output file (e.g. path/pytorch_model.bin)388        - ``tag``: checkpoint tag used as a unique identifier for checkpoint. If not provided will attempt to load tag in the file named ``latest`` in the checkpoint folder, e.g., ``global_step14``389    """390 391    state_dict = get_fp32_state_dict_from_zero_checkpoint(checkpoint_dir, tag)392    print(f"Saving fp32 state dict to {output_file}")393    torch.save(state_dict, output_file)394 395 396def load_state_dict_from_zero_checkpoint(model, checkpoint_dir, tag=None):397    """398    1. Put the provided model to cpu399    2. Convert ZeRO 2 or 3 checkpoint into a single fp32 consolidated ``state_dict``400    3. Load it into the provided model401 402    Args:403        - ``model``: the model object to update404        - ``checkpoint_dir``: path to the desired checkpoint folder. (one that contains the tag-folder, like ``global_step14``)405        - ``tag``: checkpoint tag used as a unique identifier for checkpoint. If not provided will attempt to load tag in the file named ``latest`` in the checkpoint folder, e.g., ``global_step14``406 407    Returns:408        - ``model`: modified model409 410    Make sure you have plenty of CPU memory available before you call this function. If you don't411    have enough use the ``zero_to_fp32.py`` utility to do the conversion. You will find it412    conveniently placed for you in the checkpoint folder.413 414    A typical usage might be ::415 416        from deepspeed.utils.zero_to_fp32 import load_state_dict_from_zero_checkpoint417        model = load_state_dict_from_zero_checkpoint(trainer.model, checkpoint_dir)418        # submit to model hub or save the model to share with others419 420    Note, that once this was run, the ``model`` will no longer be usable in the deepspeed context421    of the same application. i.e. you will need to re-initialize the deepspeed engine, since422    ``model.load_state_dict(state_dict)`` will remove all the deepspeed magic from it.423 424    """425    logger.info(f"Extracting fp32 weights")426    state_dict = get_fp32_state_dict_from_zero_checkpoint(checkpoint_dir, tag)427 428    logger.info(f"Overwriting model with fp32 weights")429    model = model.cpu()430    model.load_state_dict(state_dict, strict=False)431 432    return model433 434 435if __name__ == "__main__":436 437    parser = argparse.ArgumentParser()438    parser.add_argument(439        "checkpoint_dir",440        type=str,441        help="path to the desired checkpoint folder, e.g., path/checkpoint-12")442    parser.add_argument(443        "output_file",444        type=str,445        help=446        "path to the pytorch fp32 state_dict output file (e.g. path/checkpoint-12/pytorch_model.bin)"447    )448    parser.add_argument("-d", "--debug", action='store_true', help="enable debug")449    args = parser.parse_args()450 451    debug = args.debug452 453    convert_zero_checkpoint_to_fp32_state_dict(args.checkpoint_dir, args.output_file)454