draven-alg commited on
Commit
78bb712
·
verified ·
1 Parent(s): 928c954

Upload folder using huggingface_hub

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. joint_50ksteps_bs128_qwen3vl2b_end_padding_mask/ds_checkpoint_50000/global_step50000/bf16_zero_pp_rank_0_mp_rank_00_optim_states.pt +3 -0
  2. joint_50ksteps_bs128_qwen3vl2b_end_padding_mask/ds_checkpoint_50000/global_step50000/bf16_zero_pp_rank_1_mp_rank_00_optim_states.pt +3 -0
  3. joint_50ksteps_bs128_qwen3vl2b_end_padding_mask/ds_checkpoint_50000/global_step50000/bf16_zero_pp_rank_2_mp_rank_00_optim_states.pt +3 -0
  4. joint_50ksteps_bs128_qwen3vl2b_end_padding_mask/ds_checkpoint_50000/global_step50000/bf16_zero_pp_rank_3_mp_rank_00_optim_states.pt +3 -0
  5. joint_50ksteps_bs128_qwen3vl2b_end_padding_mask/ds_checkpoint_50000/global_step50000/mp_rank_00_model_states.pt +3 -0
  6. joint_50ksteps_bs128_qwen3vl2b_end_padding_mask/ds_checkpoint_50000/latest +1 -0
  7. joint_50ksteps_bs128_qwen3vl2b_end_padding_mask/ds_checkpoint_50000/zero_to_fp32.py +790 -0
  8. joint_50ksteps_bs128_qwen3vl2b_end_padding_mask/ds_checkpoint_final/global_step50000/bf16_zero_pp_rank_0_mp_rank_00_optim_states.pt +3 -0
  9. joint_50ksteps_bs128_qwen3vl2b_end_padding_mask/ds_checkpoint_final/global_step50000/bf16_zero_pp_rank_1_mp_rank_00_optim_states.pt +3 -0
  10. joint_50ksteps_bs128_qwen3vl2b_end_padding_mask/ds_checkpoint_final/global_step50000/bf16_zero_pp_rank_2_mp_rank_00_optim_states.pt +3 -0
  11. joint_50ksteps_bs128_qwen3vl2b_end_padding_mask/ds_checkpoint_final/global_step50000/bf16_zero_pp_rank_3_mp_rank_00_optim_states.pt +3 -0
  12. joint_50ksteps_bs128_qwen3vl2b_end_padding_mask/ds_checkpoint_final/global_step50000/mp_rank_00_model_states.pt +3 -0
  13. joint_50ksteps_bs128_qwen3vl2b_end_padding_mask/ds_checkpoint_final/latest +1 -0
  14. joint_50ksteps_bs128_qwen3vl2b_end_padding_mask/ds_checkpoint_final/zero_to_fp32.py +790 -0
  15. joint_50ksteps_bs128_qwen3vl2b_end_padding_mask/train.log +0 -0
  16. joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16/ds_checkpoint_50000/global_step50000/bf16_zero_pp_rank_0_mp_rank_00_optim_states.pt +3 -0
  17. joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16/ds_checkpoint_50000/global_step50000/bf16_zero_pp_rank_1_mp_rank_00_optim_states.pt +3 -0
  18. joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16/ds_checkpoint_50000/global_step50000/bf16_zero_pp_rank_2_mp_rank_00_optim_states.pt +3 -0
  19. joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16/ds_checkpoint_50000/global_step50000/bf16_zero_pp_rank_3_mp_rank_00_optim_states.pt +3 -0
  20. joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16/ds_checkpoint_50000/global_step50000/mp_rank_00_model_states.pt +3 -0
  21. joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16/ds_checkpoint_50000/latest +1 -0
  22. joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16/ds_checkpoint_50000/zero_to_fp32.py +790 -0
  23. joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16/ds_checkpoint_final/global_step50000/bf16_zero_pp_rank_0_mp_rank_00_optim_states.pt +3 -0
  24. joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16/ds_checkpoint_final/global_step50000/bf16_zero_pp_rank_1_mp_rank_00_optim_states.pt +3 -0
  25. joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16/ds_checkpoint_final/global_step50000/bf16_zero_pp_rank_2_mp_rank_00_optim_states.pt +3 -0
  26. joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16/ds_checkpoint_final/global_step50000/bf16_zero_pp_rank_3_mp_rank_00_optim_states.pt +3 -0
  27. joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16/ds_checkpoint_final/global_step50000/mp_rank_00_model_states.pt +3 -0
  28. joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16/ds_checkpoint_final/latest +1 -0
  29. joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16/ds_checkpoint_final/zero_to_fp32.py +790 -0
  30. joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16/train.log +0 -0
  31. joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16_new_instruction/ds_checkpoint_50000/global_step50000/bf16_zero_pp_rank_0_mp_rank_00_optim_states.pt +3 -0
  32. joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16_new_instruction/ds_checkpoint_50000/global_step50000/bf16_zero_pp_rank_1_mp_rank_00_optim_states.pt +3 -0
  33. joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16_new_instruction/ds_checkpoint_50000/global_step50000/bf16_zero_pp_rank_2_mp_rank_00_optim_states.pt +3 -0
  34. joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16_new_instruction/ds_checkpoint_50000/global_step50000/bf16_zero_pp_rank_3_mp_rank_00_optim_states.pt +3 -0
  35. joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16_new_instruction/ds_checkpoint_50000/global_step50000/bf16_zero_pp_rank_4_mp_rank_00_optim_states.pt +3 -0
  36. joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16_new_instruction/ds_checkpoint_50000/global_step50000/bf16_zero_pp_rank_5_mp_rank_00_optim_states.pt +3 -0
  37. joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16_new_instruction/ds_checkpoint_50000/global_step50000/bf16_zero_pp_rank_6_mp_rank_00_optim_states.pt +3 -0
  38. joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16_new_instruction/ds_checkpoint_50000/global_step50000/bf16_zero_pp_rank_7_mp_rank_00_optim_states.pt +3 -0
  39. joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16_new_instruction/ds_checkpoint_50000/global_step50000/mp_rank_00_model_states.pt +3 -0
  40. joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16_new_instruction/ds_checkpoint_50000/latest +1 -0
  41. joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16_new_instruction/ds_checkpoint_50000/zero_to_fp32.py +790 -0
  42. joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16_new_instruction/ds_checkpoint_final/global_step50000/bf16_zero_pp_rank_0_mp_rank_00_optim_states.pt +3 -0
  43. joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16_new_instruction/ds_checkpoint_final/global_step50000/bf16_zero_pp_rank_1_mp_rank_00_optim_states.pt +3 -0
  44. joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16_new_instruction/ds_checkpoint_final/global_step50000/bf16_zero_pp_rank_2_mp_rank_00_optim_states.pt +3 -0
  45. joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16_new_instruction/ds_checkpoint_final/global_step50000/bf16_zero_pp_rank_3_mp_rank_00_optim_states.pt +3 -0
  46. joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16_new_instruction/ds_checkpoint_final/global_step50000/bf16_zero_pp_rank_4_mp_rank_00_optim_states.pt +3 -0
  47. joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16_new_instruction/ds_checkpoint_final/global_step50000/bf16_zero_pp_rank_5_mp_rank_00_optim_states.pt +3 -0
  48. joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16_new_instruction/ds_checkpoint_final/global_step50000/bf16_zero_pp_rank_6_mp_rank_00_optim_states.pt +3 -0
  49. joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16_new_instruction/ds_checkpoint_final/global_step50000/bf16_zero_pp_rank_7_mp_rank_00_optim_states.pt +3 -0
  50. joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16_new_instruction/ds_checkpoint_final/global_step50000/mp_rank_00_model_states.pt +3 -0
joint_50ksteps_bs128_qwen3vl2b_end_padding_mask/ds_checkpoint_50000/global_step50000/bf16_zero_pp_rank_0_mp_rank_00_optim_states.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e26ef621a6f96c9ccd801372a6cd50cc75821cbae86df7af3e1f2606f12c7e65
3
+ size 8066685992
joint_50ksteps_bs128_qwen3vl2b_end_padding_mask/ds_checkpoint_50000/global_step50000/bf16_zero_pp_rank_1_mp_rank_00_optim_states.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2b0001ad209d57997615dc6d599f56431095d8de041fef41e957783713a6dace
3
+ size 8066667688
joint_50ksteps_bs128_qwen3vl2b_end_padding_mask/ds_checkpoint_50000/global_step50000/bf16_zero_pp_rank_2_mp_rank_00_optim_states.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ba3297ec1f26b63937fb039139826a18cacf5ec9cca2f970cd973cab620b575f
3
+ size 8066672104
joint_50ksteps_bs128_qwen3vl2b_end_padding_mask/ds_checkpoint_50000/global_step50000/bf16_zero_pp_rank_3_mp_rank_00_optim_states.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:171c4ccaf28af4d0b5bc310e5cae47ae0baa35de536fdbe595bfc1a03b8bef9d
3
+ size 8066671208
joint_50ksteps_bs128_qwen3vl2b_end_padding_mask/ds_checkpoint_50000/global_step50000/mp_rank_00_model_states.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:031244614fb75859c3799bf9a2e44171af0ad9597422d557c09b4201f56aec24
3
+ size 5378167768
joint_50ksteps_bs128_qwen3vl2b_end_padding_mask/ds_checkpoint_50000/latest ADDED
@@ -0,0 +1 @@
 
 
1
+ global_step50000
joint_50ksteps_bs128_qwen3vl2b_end_padding_mask/ds_checkpoint_50000/zero_to_fp32.py ADDED
@@ -0,0 +1,790 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python
2
+
3
+ # Copyright (c) Microsoft Corporation.
4
+ # SPDX-License-Identifier: Apache-2.0
5
+
6
+ # DeepSpeed Team
7
+
8
+ # This script extracts fp32 consolidated weights from a zero 1, 2 and 3 DeepSpeed checkpoints. It gets
9
+ # copied into the top level checkpoint dir, so the user can easily do the conversion at any point in
10
+ # the future. Once extracted, the weights don't require DeepSpeed and can be used in any
11
+ # application.
12
+ #
13
+ # example:
14
+ # python zero_to_fp32.py . output_dir/
15
+ # or
16
+ # python zero_to_fp32.py . output_dir/ --safe_serialization
17
+
18
+ import argparse
19
+ import torch
20
+ import glob
21
+ import math
22
+ import os
23
+ import re
24
+ import gc
25
+ import json
26
+ import numpy as np
27
+ from tqdm import tqdm
28
+ from collections import OrderedDict
29
+ from dataclasses import dataclass
30
+
31
+ # while this script doesn't use deepspeed to recover data, since the checkpoints are pickled with
32
+ # DeepSpeed data structures it has to be available in the current python environment.
33
+ from deepspeed.utils import logger
34
+ from deepspeed.checkpoint.constants import (DS_VERSION, OPTIMIZER_STATE_DICT, SINGLE_PARTITION_OF_FP32_GROUPS,
35
+ FP32_FLAT_GROUPS, ZERO_STAGE, PARTITION_COUNT, PARAM_SHAPES, BUFFER_NAMES,
36
+ FROZEN_PARAM_SHAPES, FROZEN_PARAM_FRAGMENTS, AUTOEP_LAYERS_KEY,
37
+ AUTOEP_LAYERS_KEY_LEGACY, AUTOEP_ZERO3_EXPERT_STATE_FORMAT_KEY,
38
+ AUTOEP_ZERO3_PARTITIONED_EXPERT_STATE_FORMAT)
39
+
40
+
41
+ @dataclass
42
+ class zero_model_state:
43
+ buffers: dict()
44
+ param_shapes: dict()
45
+ shared_params: list
46
+ ds_version: int
47
+ frozen_param_shapes: dict()
48
+ frozen_param_fragments: dict()
49
+
50
+
51
+ debug = 0
52
+
53
+ # load to cpu
54
+ device = torch.device('cpu')
55
+
56
+
57
+ def atoi(text):
58
+ return int(text) if text.isdigit() else text
59
+
60
+
61
+ def natural_keys(text):
62
+ '''
63
+ alist.sort(key=natural_keys) sorts in human order
64
+ http://nedbatchelder.com/blog/200712/human_sorting.html
65
+ (See Toothy's implementation in the comments)
66
+ '''
67
+ return [atoi(c) for c in re.split(r'(\d+)', text)]
68
+
69
+
70
+ def get_model_state_file(checkpoint_dir, zero_stage):
71
+ if not os.path.isdir(checkpoint_dir):
72
+ raise FileNotFoundError(f"Directory '{checkpoint_dir}' doesn't exist")
73
+
74
+ # there should be only one file
75
+ if zero_stage <= 2:
76
+ file = os.path.join(checkpoint_dir, "mp_rank_00_model_states.pt")
77
+ elif zero_stage == 3:
78
+ file = os.path.join(checkpoint_dir, "zero_pp_rank_0_mp_rank_00_model_states.pt")
79
+
80
+ if not os.path.exists(file):
81
+ raise FileNotFoundError(f"can't find model states file at '{file}'")
82
+
83
+ return file
84
+
85
+
86
+ def get_checkpoint_files(checkpoint_dir, glob_pattern):
87
+ # XXX: need to test that this simple glob rule works for multi-node setup too
88
+ ckpt_files = sorted(glob.glob(os.path.join(checkpoint_dir, glob_pattern)), key=natural_keys)
89
+
90
+ if len(ckpt_files) == 0:
91
+ raise FileNotFoundError(f"can't find {glob_pattern} files in directory '{checkpoint_dir}'")
92
+
93
+ return ckpt_files
94
+
95
+
96
+ def get_optim_files(checkpoint_dir):
97
+ return get_checkpoint_files(checkpoint_dir, "*_optim_states.pt")
98
+
99
+
100
+ def get_model_state_files(checkpoint_dir):
101
+ return get_checkpoint_files(checkpoint_dir, "*_model_states.pt")
102
+
103
+
104
+ def _has_autoep_zero3_partitioned_metadata(state_dict):
105
+ autoep_layers = state_dict.get(AUTOEP_LAYERS_KEY)
106
+ if autoep_layers is None:
107
+ autoep_layers = state_dict.get(AUTOEP_LAYERS_KEY_LEGACY)
108
+ if not isinstance(autoep_layers, list):
109
+ return False
110
+ return any(
111
+ isinstance(entry, dict)
112
+ and entry.get(AUTOEP_ZERO3_EXPERT_STATE_FORMAT_KEY) == AUTOEP_ZERO3_PARTITIONED_EXPERT_STATE_FORMAT
113
+ for entry in autoep_layers)
114
+
115
+
116
+ def _raise_if_autoep_zero3_partitioned_state(state_dict):
117
+ if _has_autoep_zero3_partitioned_metadata(state_dict):
118
+ raise NotImplementedError("zero_to_fp32 does not support AutoEP ZeRO-3 partition-native checkpoints. "
119
+ "AutoEP expert parameters are partitioned over expert replica groups, so "
120
+ "global data-parallel consolidation would produce incomplete expert tensors. "
121
+ "Use ds_to_universal.py for expert-aware conversion.")
122
+
123
+
124
+ def _raise_if_autoep_zero3_partitioned_checkpoint(model_files):
125
+ for file in model_files:
126
+ state_dict = torch.load(file, map_location=device, weights_only=False)
127
+ _raise_if_autoep_zero3_partitioned_state(state_dict)
128
+
129
+
130
+ def parse_model_states(files):
131
+ zero_model_states = []
132
+ for file in files:
133
+ state_dict = torch.load(file, map_location=device, weights_only=False)
134
+ _raise_if_autoep_zero3_partitioned_state(state_dict)
135
+
136
+ if BUFFER_NAMES not in state_dict:
137
+ raise ValueError(f"{file} is not a model state checkpoint")
138
+ buffer_names = state_dict[BUFFER_NAMES]
139
+ if debug:
140
+ print("Found buffers:", buffer_names)
141
+
142
+ # recover just the buffers while restoring them to fp32 if they were saved in fp16
143
+ buffers = {k: v.float() for k, v in state_dict["module"].items() if k in buffer_names}
144
+ param_shapes = state_dict[PARAM_SHAPES]
145
+
146
+ # collect parameters that are included in param_shapes
147
+ param_names = []
148
+ for s in param_shapes:
149
+ for name in s.keys():
150
+ param_names.append(name)
151
+
152
+ # update with frozen parameters
153
+ frozen_param_shapes = state_dict.get(FROZEN_PARAM_SHAPES, None)
154
+ if frozen_param_shapes is not None:
155
+ if debug:
156
+ print(f"Found frozen_param_shapes: {frozen_param_shapes}")
157
+ param_names += list(frozen_param_shapes.keys())
158
+
159
+ # handle shared params
160
+ shared_params = [[k, v] for k, v in state_dict["shared_params"].items()]
161
+
162
+ ds_version = state_dict.get(DS_VERSION, None)
163
+
164
+ frozen_param_fragments = state_dict.get(FROZEN_PARAM_FRAGMENTS, None)
165
+
166
+ z_model_state = zero_model_state(buffers=buffers,
167
+ param_shapes=param_shapes,
168
+ shared_params=shared_params,
169
+ ds_version=ds_version,
170
+ frozen_param_shapes=frozen_param_shapes,
171
+ frozen_param_fragments=frozen_param_fragments)
172
+ zero_model_states.append(z_model_state)
173
+
174
+ return zero_model_states
175
+
176
+
177
+ def parse_optim_states(files, ds_checkpoint_dir):
178
+ total_files = len(files)
179
+ state_dicts = []
180
+ for f in tqdm(files, desc='Loading checkpoint shards'):
181
+ state_dict = torch.load(f, map_location=device, mmap=True, weights_only=False)
182
+ # immediately discard the potentially huge 2 optimizer states as we only care for fp32 master weights
183
+ # and also handle the case where it was already removed by another helper script
184
+ state_dict["optimizer_state_dict"].pop("optimizer_state_dict", None)
185
+ state_dicts.append(state_dict)
186
+
187
+ if ZERO_STAGE not in state_dicts[0][OPTIMIZER_STATE_DICT]:
188
+ raise ValueError(f"{files[0]} is not a zero checkpoint")
189
+ zero_stage = state_dicts[0][OPTIMIZER_STATE_DICT][ZERO_STAGE]
190
+ world_size = state_dicts[0][OPTIMIZER_STATE_DICT][PARTITION_COUNT]
191
+
192
+ # For ZeRO-2 each param group can have different partition_count as data parallelism for expert
193
+ # parameters can be different from data parallelism for non-expert parameters. So we can just
194
+ # use the max of the partition_count to get the dp world_size.
195
+
196
+ if type(world_size) is list:
197
+ world_size = max(world_size)
198
+
199
+ if world_size != total_files:
200
+ raise ValueError(
201
+ f"Expected {world_size} of '*_optim_states.pt' under '{ds_checkpoint_dir}' but found {total_files} files. "
202
+ "Possibly due to an overwrite of an old checkpoint, or a checkpoint didn't get saved by one or more processes."
203
+ )
204
+
205
+ # the groups are named differently in each stage
206
+ if zero_stage <= 2:
207
+ fp32_groups_key = SINGLE_PARTITION_OF_FP32_GROUPS
208
+ elif zero_stage == 3:
209
+ fp32_groups_key = FP32_FLAT_GROUPS
210
+ else:
211
+ raise ValueError(f"unknown zero stage {zero_stage}")
212
+
213
+ fp32_flat_groups = [state_dicts[i][OPTIMIZER_STATE_DICT][fp32_groups_key] for i in range(len(state_dicts))]
214
+ return zero_stage, world_size, fp32_flat_groups
215
+
216
+
217
+ def _get_fp32_state_dict_from_zero_checkpoint(ds_checkpoint_dir, exclude_frozen_parameters):
218
+ """
219
+ Returns fp32 state_dict reconstructed from ds checkpoint
220
+
221
+ Args:
222
+ - ``ds_checkpoint_dir``: path to the deepspeed checkpoint folder (where the optimizer files are)
223
+
224
+ """
225
+ print(f"Processing zero checkpoint '{ds_checkpoint_dir}'")
226
+
227
+ # parse_model_states rejects AutoEP ZeRO-3 partition-native checkpoints
228
+ # before the expensive optimizer-shard load below.
229
+ model_files = get_model_state_files(ds_checkpoint_dir)
230
+ zero_model_states = parse_model_states(model_files)
231
+ print(f'Parsing checkpoint created by deepspeed=={zero_model_states[0].ds_version}')
232
+
233
+ optim_files = get_optim_files(ds_checkpoint_dir)
234
+ zero_stage, world_size, fp32_flat_groups = parse_optim_states(optim_files, ds_checkpoint_dir)
235
+ print(f"Detected checkpoint of type zero stage {zero_stage}, world_size: {world_size}")
236
+
237
+ if zero_stage <= 2:
238
+ return _get_fp32_state_dict_from_zero2_checkpoint(world_size, fp32_flat_groups, zero_model_states,
239
+ exclude_frozen_parameters)
240
+ elif zero_stage == 3:
241
+ return _get_fp32_state_dict_from_zero3_checkpoint(world_size, fp32_flat_groups, zero_model_states,
242
+ exclude_frozen_parameters)
243
+
244
+
245
+ def _zero2_merge_frozen_params(state_dict, zero_model_states):
246
+ if zero_model_states[0].frozen_param_shapes is None or len(zero_model_states[0].frozen_param_shapes) == 0:
247
+ return
248
+
249
+ frozen_param_shapes = zero_model_states[0].frozen_param_shapes
250
+ frozen_param_fragments = zero_model_states[0].frozen_param_fragments
251
+
252
+ if debug:
253
+ num_elem = sum(s.numel() for s in frozen_param_shapes.values())
254
+ print(f'rank 0: {FROZEN_PARAM_SHAPES}.numel = {num_elem}')
255
+
256
+ wanted_params = len(frozen_param_shapes)
257
+ wanted_numel = sum(s.numel() for s in frozen_param_shapes.values())
258
+ avail_numel = sum([p.numel() for p in frozen_param_fragments.values()])
259
+ print(f'Frozen params: Have {avail_numel} numels to process.')
260
+ print(f'Frozen params: Need {wanted_numel} numels in {wanted_params} params')
261
+
262
+ total_params = 0
263
+ total_numel = 0
264
+ for name, shape in frozen_param_shapes.items():
265
+ total_params += 1
266
+ unpartitioned_numel = shape.numel()
267
+ total_numel += unpartitioned_numel
268
+
269
+ state_dict[name] = frozen_param_fragments[name]
270
+
271
+ if debug:
272
+ print(f"{name} full shape: {shape} unpartitioned numel {unpartitioned_numel} ")
273
+
274
+ print(f"Reconstructed Frozen fp32 state dict with {total_params} params {total_numel} elements")
275
+
276
+
277
+ def _has_callable(obj, fn):
278
+ attr = getattr(obj, fn, None)
279
+ return callable(attr)
280
+
281
+
282
+ def _zero2_merge_trainable_params(state_dict, world_size, fp32_flat_groups, zero_model_states):
283
+ param_shapes = zero_model_states[0].param_shapes
284
+
285
+ # Reconstruction protocol:
286
+ #
287
+ # XXX: document this
288
+
289
+ if debug:
290
+ for i in range(world_size):
291
+ for j in range(len(fp32_flat_groups[0])):
292
+ print(f"{FP32_FLAT_GROUPS}[{i}][{j}].shape={fp32_flat_groups[i][j].shape}")
293
+
294
+ # XXX: memory usage doubles here (zero2)
295
+ num_param_groups = len(fp32_flat_groups[0])
296
+ merged_single_partition_of_fp32_groups = []
297
+ for i in range(num_param_groups):
298
+ merged_partitions = [sd[i] for sd in fp32_flat_groups]
299
+ full_single_fp32_vector = torch.cat(merged_partitions, 0)
300
+ merged_single_partition_of_fp32_groups.append(full_single_fp32_vector)
301
+ avail_numel = sum(
302
+ [full_single_fp32_vector.numel() for full_single_fp32_vector in merged_single_partition_of_fp32_groups])
303
+
304
+ if debug:
305
+ wanted_params = sum([len(shapes) for shapes in param_shapes])
306
+ wanted_numel = sum([sum(shape.numel() for shape in shapes.values()) for shapes in param_shapes])
307
+ # not asserting if there is a mismatch due to possible padding
308
+ print(f"Have {avail_numel} numels to process.")
309
+ print(f"Need {wanted_numel} numels in {wanted_params} params.")
310
+
311
+ # params
312
+ # XXX: for huge models that can't fit into the host's RAM we will have to recode this to support
313
+ # out-of-core computing solution
314
+ total_numel = 0
315
+ total_params = 0
316
+ for shapes, full_single_fp32_vector in zip(param_shapes, merged_single_partition_of_fp32_groups):
317
+ offset = 0
318
+ avail_numel = full_single_fp32_vector.numel()
319
+ for name, shape in shapes.items():
320
+
321
+ unpartitioned_numel = shape.numel() if _has_callable(shape, 'numel') else math.prod(shape)
322
+ total_numel += unpartitioned_numel
323
+ total_params += 1
324
+
325
+ if debug:
326
+ print(f"{name} full shape: {shape} unpartitioned numel {unpartitioned_numel} ")
327
+ state_dict[name] = full_single_fp32_vector.narrow(0, offset, unpartitioned_numel).view(shape)
328
+ offset += unpartitioned_numel
329
+
330
+ # Z2 started to align to 2*world_size to improve nccl performance. Therefore both offset and
331
+ # avail_numel can differ by anywhere between 0..2*world_size. Due to two unrelated complex
332
+ # paddings performed in the code it's almost impossible to predict the exact numbers w/o the
333
+ # live optimizer object, so we are checking that the numbers are within the right range
334
+ align_to = 2 * world_size
335
+
336
+ def zero2_align(x):
337
+ return align_to * math.ceil(x / align_to)
338
+
339
+ if debug:
340
+ print(f"original offset={offset}, avail_numel={avail_numel}")
341
+
342
+ offset = zero2_align(offset)
343
+ avail_numel = zero2_align(avail_numel)
344
+
345
+ if debug:
346
+ print(f"aligned offset={offset}, avail_numel={avail_numel}")
347
+
348
+ # Sanity check
349
+ if offset != avail_numel:
350
+ raise ValueError(f"consumed {offset} numels out of {avail_numel} - something is wrong")
351
+
352
+ print(f"Reconstructed fp32 state dict with {total_params} params {total_numel} elements")
353
+
354
+
355
+ def _get_fp32_state_dict_from_zero2_checkpoint(world_size, fp32_flat_groups, zero_model_states,
356
+ exclude_frozen_parameters):
357
+ state_dict = OrderedDict()
358
+
359
+ # buffers
360
+ buffers = zero_model_states[0].buffers
361
+ state_dict.update(buffers)
362
+ if debug:
363
+ print(f"added {len(buffers)} buffers")
364
+
365
+ if not exclude_frozen_parameters:
366
+ _zero2_merge_frozen_params(state_dict, zero_model_states)
367
+
368
+ _zero2_merge_trainable_params(state_dict, world_size, fp32_flat_groups, zero_model_states)
369
+
370
+ # recover shared parameters
371
+ for pair in zero_model_states[0].shared_params:
372
+ if pair[1] in state_dict:
373
+ state_dict[pair[0]] = state_dict[pair[1]]
374
+
375
+ return state_dict
376
+
377
+
378
+ def zero3_partitioned_param_info(unpartitioned_numel, world_size):
379
+ remainder = unpartitioned_numel % world_size
380
+ padding_numel = (world_size - remainder) if remainder else 0
381
+ partitioned_numel = math.ceil(unpartitioned_numel / world_size)
382
+ return partitioned_numel, padding_numel
383
+
384
+
385
+ def _zero3_merge_frozen_params(state_dict, world_size, zero_model_states):
386
+ if zero_model_states[0].frozen_param_shapes is None or len(zero_model_states[0].frozen_param_shapes) == 0:
387
+ return
388
+
389
+ if debug:
390
+ for i in range(world_size):
391
+ num_elem = sum(s.numel() for s in zero_model_states[i].frozen_param_fragments.values())
392
+ print(f'rank {i}: {FROZEN_PARAM_SHAPES}.numel = {num_elem}')
393
+
394
+ frozen_param_shapes = zero_model_states[0].frozen_param_shapes
395
+ wanted_params = len(frozen_param_shapes)
396
+ wanted_numel = sum(s.numel() for s in frozen_param_shapes.values())
397
+ avail_numel = sum([p.numel() for p in zero_model_states[0].frozen_param_fragments.values()]) * world_size
398
+ print(f'Frozen params: Have {avail_numel} numels to process.')
399
+ print(f'Frozen params: Need {wanted_numel} numels in {wanted_params} params')
400
+
401
+ total_params = 0
402
+ total_numel = 0
403
+ for name, shape in zero_model_states[0].frozen_param_shapes.items():
404
+ total_params += 1
405
+ unpartitioned_numel = shape.numel()
406
+ total_numel += unpartitioned_numel
407
+
408
+ param_frags = tuple(model_state.frozen_param_fragments[name] for model_state in zero_model_states)
409
+ state_dict[name] = torch.cat(param_frags, 0).narrow(0, 0, unpartitioned_numel).view(shape)
410
+
411
+ partitioned_numel, partitioned_padding_numel = zero3_partitioned_param_info(unpartitioned_numel, world_size)
412
+
413
+ if debug:
414
+ print(
415
+ f"Frozen params: {total_params} {name} full shape: {shape} partition0 numel={partitioned_numel} partitioned_padding_numel={partitioned_padding_numel}"
416
+ )
417
+
418
+ print(f"Reconstructed Frozen fp32 state dict with {total_params} params {total_numel} elements")
419
+
420
+
421
+ class GatheredTensor:
422
+ """
423
+ A pseudo tensor that collects partitioned weights.
424
+ It is more memory efficient when there are multiple groups.
425
+ """
426
+
427
+ def __init__(self, flat_groups, flat_groups_offset, offset, partitioned_numel, shape):
428
+ self.flat_groups = flat_groups
429
+ self.flat_groups_offset = flat_groups_offset
430
+ self.offset = offset
431
+ self.partitioned_numel = partitioned_numel
432
+ self.shape = shape
433
+ self.dtype = self.flat_groups[0][0].dtype
434
+
435
+ def contiguous(self):
436
+ """
437
+ Merge partitioned weights from flat_groups into a single tensor.
438
+ """
439
+ end_idx = self.offset + self.partitioned_numel
440
+ world_size = len(self.flat_groups)
441
+ pad_flat_param_chunks = []
442
+
443
+ for rank_i in range(world_size):
444
+ # for each rank, we need to collect weights from related group/groups
445
+ flat_groups_at_rank_i = self.flat_groups[rank_i]
446
+ start_group_id = None
447
+ end_group_id = None
448
+ for group_id in range(len(self.flat_groups_offset)):
449
+ if self.flat_groups_offset[group_id] <= self.offset < self.flat_groups_offset[group_id + 1]:
450
+ start_group_id = group_id
451
+ if self.flat_groups_offset[group_id] < end_idx <= self.flat_groups_offset[group_id + 1]:
452
+ end_group_id = group_id
453
+ break
454
+ # collect weights from related group/groups
455
+ for group_id in range(start_group_id, end_group_id + 1):
456
+ flat_tensor = flat_groups_at_rank_i[group_id]
457
+ start_offset = self.offset - self.flat_groups_offset[group_id]
458
+ end_offset = min(end_idx, self.flat_groups_offset[group_id + 1]) - self.flat_groups_offset[group_id]
459
+ pad_flat_param_chunks.append(flat_tensor[start_offset:end_offset])
460
+
461
+ # collect weights from all ranks
462
+ pad_flat_param = torch.cat(pad_flat_param_chunks, dim=0)
463
+ param = pad_flat_param[:self.shape.numel()].view(self.shape).contiguous()
464
+ return param
465
+
466
+
467
+ def _zero3_merge_trainable_params(state_dict, world_size, fp32_flat_groups, zero_model_states):
468
+ param_shapes = zero_model_states[0].param_shapes
469
+ avail_numel = sum([flat_group.numel() for flat_group in fp32_flat_groups[0]]) * world_size
470
+
471
+ # Reconstruction protocol: For zero3 we need to zip the partitions together at boundary of each
472
+ # param, re-consolidating each param, while dealing with padding if any
473
+
474
+ # merge list of dicts, preserving order
475
+ param_shapes = {k: v for d in param_shapes for k, v in d.items()}
476
+
477
+ if debug:
478
+ for i in range(world_size):
479
+ print(f"{FP32_FLAT_GROUPS}[{i}].shape={fp32_flat_groups[i].shape}")
480
+
481
+ wanted_params = len(param_shapes)
482
+ wanted_numel = sum(shape.numel() for shape in param_shapes.values())
483
+ # not asserting if there is a mismatch due to possible padding
484
+ avail_numel = fp32_flat_groups[0].numel() * world_size
485
+ print(f"Trainable params: Have {avail_numel} numels to process.")
486
+ print(f"Trainable params: Need {wanted_numel} numels in {wanted_params} params.")
487
+
488
+ # params
489
+ # XXX: for huge models that can't fit into the host's RAM we will have to recode this to support
490
+ # out-of-core computing solution
491
+ offset = 0
492
+ total_numel = 0
493
+ total_params = 0
494
+ flat_groups_offset = [0] + list(np.cumsum([flat_tensor.numel() for flat_tensor in fp32_flat_groups[0]]))
495
+ for name, shape in tqdm(param_shapes.items(), desc='Gathering sharded weights'):
496
+ unpartitioned_numel = shape.numel()
497
+ total_numel += unpartitioned_numel
498
+ total_params += 1
499
+ partitioned_numel, partitioned_padding_numel = zero3_partitioned_param_info(unpartitioned_numel, world_size)
500
+
501
+ if debug:
502
+ print(
503
+ f"Trainable params: {total_params} {name} full shape: {shape} partition0 numel={partitioned_numel} partitioned_padding_numel={partitioned_padding_numel}"
504
+ )
505
+
506
+ # memory efficient tensor
507
+ tensor = GatheredTensor(fp32_flat_groups, flat_groups_offset, offset, partitioned_numel, shape)
508
+ state_dict[name] = tensor
509
+ offset += partitioned_numel
510
+
511
+ offset *= world_size
512
+
513
+ # Sanity check
514
+ if offset != avail_numel:
515
+ raise ValueError(f"consumed {offset} numels out of {avail_numel} - something is wrong")
516
+
517
+ print(f"Reconstructed Trainable fp32 state dict with {total_params} params {total_numel} elements")
518
+
519
+
520
+ def _get_fp32_state_dict_from_zero3_checkpoint(world_size, fp32_flat_groups, zero_model_states,
521
+ exclude_frozen_parameters):
522
+ state_dict = OrderedDict()
523
+
524
+ # buffers
525
+ buffers = zero_model_states[0].buffers
526
+ state_dict.update(buffers)
527
+ if debug:
528
+ print(f"added {len(buffers)} buffers")
529
+
530
+ if not exclude_frozen_parameters:
531
+ _zero3_merge_frozen_params(state_dict, world_size, zero_model_states)
532
+
533
+ _zero3_merge_trainable_params(state_dict, world_size, fp32_flat_groups, zero_model_states)
534
+
535
+ # recover shared parameters
536
+ for pair in zero_model_states[0].shared_params:
537
+ if pair[1] in state_dict:
538
+ state_dict[pair[0]] = state_dict[pair[1]]
539
+
540
+ return state_dict
541
+
542
+
543
+ def to_torch_tensor(state_dict, return_empty_tensor=False):
544
+ """
545
+ Convert state_dict of GatheredTensor to torch tensor
546
+ """
547
+ torch_state_dict = {}
548
+ converted_tensors = {}
549
+ for name, tensor in state_dict.items():
550
+ tensor_id = id(tensor)
551
+ if tensor_id in converted_tensors: # shared tensors
552
+ shared_tensor = torch_state_dict[converted_tensors[tensor_id]]
553
+ torch_state_dict[name] = shared_tensor
554
+ else:
555
+ converted_tensors[tensor_id] = name
556
+ if return_empty_tensor:
557
+ torch_state_dict[name] = torch.empty(tensor.shape, dtype=tensor.dtype)
558
+ else:
559
+ torch_state_dict[name] = tensor.contiguous()
560
+ return torch_state_dict
561
+
562
+
563
+ def get_fp32_state_dict_from_zero_checkpoint(checkpoint_dir,
564
+ tag=None,
565
+ exclude_frozen_parameters=False,
566
+ lazy_mode=False):
567
+ """
568
+ Convert ZeRO 2 or 3 checkpoint into a single fp32 consolidated state_dict that can be loaded with
569
+ ``load_state_dict()`` and used for training without DeepSpeed or shared with others, for example
570
+ via a model hub.
571
+
572
+ Args:
573
+ - ``checkpoint_dir``: path to the desired checkpoint folder
574
+ - ``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``
575
+ - ``exclude_frozen_parameters``: exclude frozen parameters
576
+ - ``lazy_mode``: get state_dict in lazy mode. It returns a dict of pesduo tensor instead of torch tensor, which is more memory efficient.
577
+ Convert the pesduo tensor to torch tensor by ``.contiguous()``
578
+
579
+ Returns:
580
+ - pytorch ``state_dict``
581
+
582
+ A typical usage might be ::
583
+
584
+ from deepspeed.utils.zero_to_fp32 import get_fp32_state_dict_from_zero_checkpoint
585
+ # do the training and checkpoint saving
586
+ state_dict = get_fp32_state_dict_from_zero_checkpoint(checkpoint_dir) # already on cpu
587
+ model = model.cpu() # move to cpu
588
+ model.load_state_dict(state_dict)
589
+ # submit to model hub or save the model to share with others
590
+
591
+ In this example the ``model`` will no longer be usable in the deepspeed context of the same
592
+ application. i.e. you will need to re-initialize the deepspeed engine, since
593
+ ``model.load_state_dict(state_dict)`` will remove all the deepspeed magic from it.
594
+
595
+ If you want it all done for you, use ``load_state_dict_from_zero_checkpoint`` instead.
596
+
597
+ Note: the above usage may not work if your application doesn't have sufficient free CPU memory.
598
+ You may need to use the offline approach using the ``zero_to_fp32.py`` script that is saved with
599
+ the checkpoint. Or you can load state_dict in lazy mode ::
600
+
601
+ from deepspeed.utils.zero_to_fp32 import get_fp32_state_dict_from_zero_checkpoint
602
+ state_dict = get_fp32_state_dict_from_zero_checkpoint(checkpoint_dir, lazy_mode=True) # not on cpu
603
+ for name, lazy_tensor in state_dict.item():
604
+ tensor = lazy_tensor.contiguous() # to cpu
605
+ print(name, tensor)
606
+ # del tensor to release memory if it no longer in use
607
+ """
608
+ if tag is None:
609
+ latest_path = os.path.join(checkpoint_dir, 'latest')
610
+ if os.path.isfile(latest_path):
611
+ with open(latest_path, 'r') as fd:
612
+ tag = fd.read().strip()
613
+ else:
614
+ raise ValueError(f"Unable to find 'latest' file at {latest_path}")
615
+
616
+ ds_checkpoint_dir = os.path.join(checkpoint_dir, tag)
617
+
618
+ if not os.path.isdir(ds_checkpoint_dir):
619
+ raise FileNotFoundError(f"Directory '{ds_checkpoint_dir}' doesn't exist")
620
+
621
+ state_dict = _get_fp32_state_dict_from_zero_checkpoint(ds_checkpoint_dir, exclude_frozen_parameters)
622
+ if lazy_mode:
623
+ return state_dict
624
+ else:
625
+ return to_torch_tensor(state_dict)
626
+
627
+
628
+ def convert_zero_checkpoint_to_fp32_state_dict(checkpoint_dir,
629
+ output_dir,
630
+ max_shard_size="5GB",
631
+ safe_serialization=False,
632
+ tag=None,
633
+ exclude_frozen_parameters=False):
634
+ """
635
+ Convert ZeRO 2 or 3 checkpoint into a single fp32 consolidated ``state_dict`` file that can be
636
+ loaded with ``torch.load(file)`` + ``load_state_dict()`` and used for training without DeepSpeed.
637
+
638
+ Args:
639
+ - ``checkpoint_dir``: path to the desired checkpoint folder. (one that contains the tag-folder, like ``global_step14``)
640
+ - ``output_dir``: directory to the pytorch fp32 state_dict output files
641
+ - ``max_shard_size``: the maximum size for a checkpoint before being sharded, default value is 5GB
642
+ - ``safe_serialization``: whether to save the model using `safetensors` or the traditional PyTorch way (that uses `pickle`).
643
+ - ``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``
644
+ - ``exclude_frozen_parameters``: exclude frozen parameters
645
+ """
646
+
647
+ # Dependency pre-check
648
+ if safe_serialization:
649
+ try:
650
+ from safetensors.torch import save_file
651
+ except ImportError:
652
+ print('If you want to use `safe_serialization`, please `pip install safetensors`')
653
+ raise
654
+ if max_shard_size is not None:
655
+ try:
656
+ from huggingface_hub import split_torch_state_dict_into_shards
657
+ except ImportError:
658
+ print('If you want to use `max_shard_size`, please `pip install huggingface_hub`')
659
+ raise
660
+
661
+ # Convert zero checkpoint to state_dict
662
+ state_dict = get_fp32_state_dict_from_zero_checkpoint(checkpoint_dir,
663
+ tag,
664
+ exclude_frozen_parameters,
665
+ lazy_mode=True)
666
+
667
+ # Shard the model if it is too big.
668
+ weights_name = "model.safetensors" if safe_serialization else "pytorch_model.bin"
669
+ if max_shard_size is not None:
670
+ filename_pattern = weights_name.replace(".bin", "{suffix}.bin").replace(".safetensors", "{suffix}.safetensors")
671
+ # an memory-efficient approach for sharding
672
+ empty_state_dict = to_torch_tensor(state_dict, return_empty_tensor=True)
673
+ state_dict_split = split_torch_state_dict_into_shards(empty_state_dict,
674
+ filename_pattern=filename_pattern,
675
+ max_shard_size=max_shard_size)
676
+ else:
677
+ from collections import namedtuple
678
+ StateDictSplit = namedtuple("StateDictSplit", ["is_sharded", "filename_to_tensors"])
679
+ state_dict_split = StateDictSplit(is_sharded=False,
680
+ filename_to_tensors={weights_name: list(state_dict.keys())})
681
+
682
+ # Save the model by shard
683
+ os.makedirs(output_dir, exist_ok=True)
684
+ filename_to_tensors = state_dict_split.filename_to_tensors.items()
685
+ for shard_file, tensors in tqdm(filename_to_tensors, desc="Saving checkpoint shards"):
686
+ shard_state_dict = {tensor_name: state_dict[tensor_name] for tensor_name in tensors}
687
+ shard_state_dict = to_torch_tensor(shard_state_dict)
688
+ output_path = os.path.join(output_dir, shard_file)
689
+ if safe_serialization:
690
+ save_file(shard_state_dict, output_path, metadata={"format": "pt"})
691
+ else:
692
+ torch.save(shard_state_dict, output_path)
693
+ # release the memory of current shard
694
+ for tensor_name in list(shard_state_dict.keys()):
695
+ del state_dict[tensor_name]
696
+ del shard_state_dict[tensor_name]
697
+ del shard_state_dict
698
+ gc.collect()
699
+
700
+ # Save index if sharded
701
+ if state_dict_split.is_sharded:
702
+ index = {
703
+ "metadata": state_dict_split.metadata,
704
+ "weight_map": state_dict_split.tensor_to_filename,
705
+ }
706
+ save_index_file = "model.safetensors.index.json" if safe_serialization else "pytorch_model.bin.index.json"
707
+ save_index_file = os.path.join(output_dir, save_index_file)
708
+ with open(save_index_file, "w", encoding="utf-8") as f:
709
+ content = json.dumps(index, indent=2, sort_keys=True) + "\n"
710
+ f.write(content)
711
+
712
+
713
+ def load_state_dict_from_zero_checkpoint(model, checkpoint_dir, tag=None):
714
+ """
715
+ 1. Put the provided model to cpu
716
+ 2. Convert ZeRO 2 or 3 checkpoint into a single fp32 consolidated ``state_dict``
717
+ 3. Load it into the provided model
718
+
719
+ Args:
720
+ - ``model``: the model object to update
721
+ - ``checkpoint_dir``: path to the desired checkpoint folder. (one that contains the tag-folder, like ``global_step14``)
722
+ - ``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``
723
+
724
+ Returns:
725
+ - ``model`: modified model
726
+
727
+ Make sure you have plenty of CPU memory available before you call this function. If you don't
728
+ have enough use the ``zero_to_fp32.py`` utility to do the conversion. You will find it
729
+ conveniently placed for you in the checkpoint folder.
730
+
731
+ A typical usage might be ::
732
+
733
+ from deepspeed.utils.zero_to_fp32 import load_state_dict_from_zero_checkpoint
734
+ model = load_state_dict_from_zero_checkpoint(trainer.model, checkpoint_dir)
735
+ # submit to model hub or save the model to share with others
736
+
737
+ Note, that once this was run, the ``model`` will no longer be usable in the deepspeed context
738
+ of the same application. i.e. you will need to re-initialize the deepspeed engine, since
739
+ ``model.load_state_dict(state_dict)`` will remove all the deepspeed magic from it.
740
+
741
+ """
742
+ logger.info("Extracting fp32 weights")
743
+ state_dict = get_fp32_state_dict_from_zero_checkpoint(checkpoint_dir, tag)
744
+
745
+ logger.info("Overwriting model with fp32 weights")
746
+ model = model.cpu()
747
+ model.load_state_dict(state_dict, strict=False)
748
+
749
+ return model
750
+
751
+
752
+ if __name__ == "__main__":
753
+ parser = argparse.ArgumentParser()
754
+ parser.add_argument("checkpoint_dir",
755
+ type=str,
756
+ help="path to the desired checkpoint folder, e.g., path/checkpoint-12")
757
+ parser.add_argument("output_dir",
758
+ type=str,
759
+ help="directory to the pytorch fp32 state_dict output files"
760
+ "(e.g. path/checkpoint-12-output/)")
761
+ parser.add_argument(
762
+ "--max_shard_size",
763
+ type=str,
764
+ default="5GB",
765
+ help="The maximum size for a checkpoint before being sharded. Checkpoints shard will then be each of size"
766
+ "lower than this size. If expressed as a string, needs to be digits followed by a unit (like `5MB`"
767
+ "We default it to 5GB in order for models to be able to run easily on free-tier google colab instances"
768
+ "without CPU OOM issues.")
769
+ parser.add_argument(
770
+ "--safe_serialization",
771
+ default=False,
772
+ action='store_true',
773
+ help="Whether to save the model using `safetensors` or the traditional PyTorch way (that uses `pickle`).")
774
+ parser.add_argument("-t",
775
+ "--tag",
776
+ type=str,
777
+ default=None,
778
+ help="checkpoint tag used as a unique identifier for checkpoint. e.g., global_step1")
779
+ parser.add_argument("--exclude_frozen_parameters", action='store_true', help="exclude frozen parameters")
780
+ parser.add_argument("-d", "--debug", action='store_true', help="enable debug")
781
+ args = parser.parse_args()
782
+
783
+ debug = args.debug
784
+
785
+ convert_zero_checkpoint_to_fp32_state_dict(args.checkpoint_dir,
786
+ args.output_dir,
787
+ max_shard_size=args.max_shard_size,
788
+ safe_serialization=args.safe_serialization,
789
+ tag=args.tag,
790
+ exclude_frozen_parameters=args.exclude_frozen_parameters)
joint_50ksteps_bs128_qwen3vl2b_end_padding_mask/ds_checkpoint_final/global_step50000/bf16_zero_pp_rank_0_mp_rank_00_optim_states.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e26ef621a6f96c9ccd801372a6cd50cc75821cbae86df7af3e1f2606f12c7e65
3
+ size 8066685992
joint_50ksteps_bs128_qwen3vl2b_end_padding_mask/ds_checkpoint_final/global_step50000/bf16_zero_pp_rank_1_mp_rank_00_optim_states.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2b0001ad209d57997615dc6d599f56431095d8de041fef41e957783713a6dace
3
+ size 8066667688
joint_50ksteps_bs128_qwen3vl2b_end_padding_mask/ds_checkpoint_final/global_step50000/bf16_zero_pp_rank_2_mp_rank_00_optim_states.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ba3297ec1f26b63937fb039139826a18cacf5ec9cca2f970cd973cab620b575f
3
+ size 8066672104
joint_50ksteps_bs128_qwen3vl2b_end_padding_mask/ds_checkpoint_final/global_step50000/bf16_zero_pp_rank_3_mp_rank_00_optim_states.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:171c4ccaf28af4d0b5bc310e5cae47ae0baa35de536fdbe595bfc1a03b8bef9d
3
+ size 8066671208
joint_50ksteps_bs128_qwen3vl2b_end_padding_mask/ds_checkpoint_final/global_step50000/mp_rank_00_model_states.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:031244614fb75859c3799bf9a2e44171af0ad9597422d557c09b4201f56aec24
3
+ size 5378167768
joint_50ksteps_bs128_qwen3vl2b_end_padding_mask/ds_checkpoint_final/latest ADDED
@@ -0,0 +1 @@
 
 
1
+ global_step50000
joint_50ksteps_bs128_qwen3vl2b_end_padding_mask/ds_checkpoint_final/zero_to_fp32.py ADDED
@@ -0,0 +1,790 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python
2
+
3
+ # Copyright (c) Microsoft Corporation.
4
+ # SPDX-License-Identifier: Apache-2.0
5
+
6
+ # DeepSpeed Team
7
+
8
+ # This script extracts fp32 consolidated weights from a zero 1, 2 and 3 DeepSpeed checkpoints. It gets
9
+ # copied into the top level checkpoint dir, so the user can easily do the conversion at any point in
10
+ # the future. Once extracted, the weights don't require DeepSpeed and can be used in any
11
+ # application.
12
+ #
13
+ # example:
14
+ # python zero_to_fp32.py . output_dir/
15
+ # or
16
+ # python zero_to_fp32.py . output_dir/ --safe_serialization
17
+
18
+ import argparse
19
+ import torch
20
+ import glob
21
+ import math
22
+ import os
23
+ import re
24
+ import gc
25
+ import json
26
+ import numpy as np
27
+ from tqdm import tqdm
28
+ from collections import OrderedDict
29
+ from dataclasses import dataclass
30
+
31
+ # while this script doesn't use deepspeed to recover data, since the checkpoints are pickled with
32
+ # DeepSpeed data structures it has to be available in the current python environment.
33
+ from deepspeed.utils import logger
34
+ from deepspeed.checkpoint.constants import (DS_VERSION, OPTIMIZER_STATE_DICT, SINGLE_PARTITION_OF_FP32_GROUPS,
35
+ FP32_FLAT_GROUPS, ZERO_STAGE, PARTITION_COUNT, PARAM_SHAPES, BUFFER_NAMES,
36
+ FROZEN_PARAM_SHAPES, FROZEN_PARAM_FRAGMENTS, AUTOEP_LAYERS_KEY,
37
+ AUTOEP_LAYERS_KEY_LEGACY, AUTOEP_ZERO3_EXPERT_STATE_FORMAT_KEY,
38
+ AUTOEP_ZERO3_PARTITIONED_EXPERT_STATE_FORMAT)
39
+
40
+
41
+ @dataclass
42
+ class zero_model_state:
43
+ buffers: dict()
44
+ param_shapes: dict()
45
+ shared_params: list
46
+ ds_version: int
47
+ frozen_param_shapes: dict()
48
+ frozen_param_fragments: dict()
49
+
50
+
51
+ debug = 0
52
+
53
+ # load to cpu
54
+ device = torch.device('cpu')
55
+
56
+
57
+ def atoi(text):
58
+ return int(text) if text.isdigit() else text
59
+
60
+
61
+ def natural_keys(text):
62
+ '''
63
+ alist.sort(key=natural_keys) sorts in human order
64
+ http://nedbatchelder.com/blog/200712/human_sorting.html
65
+ (See Toothy's implementation in the comments)
66
+ '''
67
+ return [atoi(c) for c in re.split(r'(\d+)', text)]
68
+
69
+
70
+ def get_model_state_file(checkpoint_dir, zero_stage):
71
+ if not os.path.isdir(checkpoint_dir):
72
+ raise FileNotFoundError(f"Directory '{checkpoint_dir}' doesn't exist")
73
+
74
+ # there should be only one file
75
+ if zero_stage <= 2:
76
+ file = os.path.join(checkpoint_dir, "mp_rank_00_model_states.pt")
77
+ elif zero_stage == 3:
78
+ file = os.path.join(checkpoint_dir, "zero_pp_rank_0_mp_rank_00_model_states.pt")
79
+
80
+ if not os.path.exists(file):
81
+ raise FileNotFoundError(f"can't find model states file at '{file}'")
82
+
83
+ return file
84
+
85
+
86
+ def get_checkpoint_files(checkpoint_dir, glob_pattern):
87
+ # XXX: need to test that this simple glob rule works for multi-node setup too
88
+ ckpt_files = sorted(glob.glob(os.path.join(checkpoint_dir, glob_pattern)), key=natural_keys)
89
+
90
+ if len(ckpt_files) == 0:
91
+ raise FileNotFoundError(f"can't find {glob_pattern} files in directory '{checkpoint_dir}'")
92
+
93
+ return ckpt_files
94
+
95
+
96
+ def get_optim_files(checkpoint_dir):
97
+ return get_checkpoint_files(checkpoint_dir, "*_optim_states.pt")
98
+
99
+
100
+ def get_model_state_files(checkpoint_dir):
101
+ return get_checkpoint_files(checkpoint_dir, "*_model_states.pt")
102
+
103
+
104
+ def _has_autoep_zero3_partitioned_metadata(state_dict):
105
+ autoep_layers = state_dict.get(AUTOEP_LAYERS_KEY)
106
+ if autoep_layers is None:
107
+ autoep_layers = state_dict.get(AUTOEP_LAYERS_KEY_LEGACY)
108
+ if not isinstance(autoep_layers, list):
109
+ return False
110
+ return any(
111
+ isinstance(entry, dict)
112
+ and entry.get(AUTOEP_ZERO3_EXPERT_STATE_FORMAT_KEY) == AUTOEP_ZERO3_PARTITIONED_EXPERT_STATE_FORMAT
113
+ for entry in autoep_layers)
114
+
115
+
116
+ def _raise_if_autoep_zero3_partitioned_state(state_dict):
117
+ if _has_autoep_zero3_partitioned_metadata(state_dict):
118
+ raise NotImplementedError("zero_to_fp32 does not support AutoEP ZeRO-3 partition-native checkpoints. "
119
+ "AutoEP expert parameters are partitioned over expert replica groups, so "
120
+ "global data-parallel consolidation would produce incomplete expert tensors. "
121
+ "Use ds_to_universal.py for expert-aware conversion.")
122
+
123
+
124
+ def _raise_if_autoep_zero3_partitioned_checkpoint(model_files):
125
+ for file in model_files:
126
+ state_dict = torch.load(file, map_location=device, weights_only=False)
127
+ _raise_if_autoep_zero3_partitioned_state(state_dict)
128
+
129
+
130
+ def parse_model_states(files):
131
+ zero_model_states = []
132
+ for file in files:
133
+ state_dict = torch.load(file, map_location=device, weights_only=False)
134
+ _raise_if_autoep_zero3_partitioned_state(state_dict)
135
+
136
+ if BUFFER_NAMES not in state_dict:
137
+ raise ValueError(f"{file} is not a model state checkpoint")
138
+ buffer_names = state_dict[BUFFER_NAMES]
139
+ if debug:
140
+ print("Found buffers:", buffer_names)
141
+
142
+ # recover just the buffers while restoring them to fp32 if they were saved in fp16
143
+ buffers = {k: v.float() for k, v in state_dict["module"].items() if k in buffer_names}
144
+ param_shapes = state_dict[PARAM_SHAPES]
145
+
146
+ # collect parameters that are included in param_shapes
147
+ param_names = []
148
+ for s in param_shapes:
149
+ for name in s.keys():
150
+ param_names.append(name)
151
+
152
+ # update with frozen parameters
153
+ frozen_param_shapes = state_dict.get(FROZEN_PARAM_SHAPES, None)
154
+ if frozen_param_shapes is not None:
155
+ if debug:
156
+ print(f"Found frozen_param_shapes: {frozen_param_shapes}")
157
+ param_names += list(frozen_param_shapes.keys())
158
+
159
+ # handle shared params
160
+ shared_params = [[k, v] for k, v in state_dict["shared_params"].items()]
161
+
162
+ ds_version = state_dict.get(DS_VERSION, None)
163
+
164
+ frozen_param_fragments = state_dict.get(FROZEN_PARAM_FRAGMENTS, None)
165
+
166
+ z_model_state = zero_model_state(buffers=buffers,
167
+ param_shapes=param_shapes,
168
+ shared_params=shared_params,
169
+ ds_version=ds_version,
170
+ frozen_param_shapes=frozen_param_shapes,
171
+ frozen_param_fragments=frozen_param_fragments)
172
+ zero_model_states.append(z_model_state)
173
+
174
+ return zero_model_states
175
+
176
+
177
+ def parse_optim_states(files, ds_checkpoint_dir):
178
+ total_files = len(files)
179
+ state_dicts = []
180
+ for f in tqdm(files, desc='Loading checkpoint shards'):
181
+ state_dict = torch.load(f, map_location=device, mmap=True, weights_only=False)
182
+ # immediately discard the potentially huge 2 optimizer states as we only care for fp32 master weights
183
+ # and also handle the case where it was already removed by another helper script
184
+ state_dict["optimizer_state_dict"].pop("optimizer_state_dict", None)
185
+ state_dicts.append(state_dict)
186
+
187
+ if ZERO_STAGE not in state_dicts[0][OPTIMIZER_STATE_DICT]:
188
+ raise ValueError(f"{files[0]} is not a zero checkpoint")
189
+ zero_stage = state_dicts[0][OPTIMIZER_STATE_DICT][ZERO_STAGE]
190
+ world_size = state_dicts[0][OPTIMIZER_STATE_DICT][PARTITION_COUNT]
191
+
192
+ # For ZeRO-2 each param group can have different partition_count as data parallelism for expert
193
+ # parameters can be different from data parallelism for non-expert parameters. So we can just
194
+ # use the max of the partition_count to get the dp world_size.
195
+
196
+ if type(world_size) is list:
197
+ world_size = max(world_size)
198
+
199
+ if world_size != total_files:
200
+ raise ValueError(
201
+ f"Expected {world_size} of '*_optim_states.pt' under '{ds_checkpoint_dir}' but found {total_files} files. "
202
+ "Possibly due to an overwrite of an old checkpoint, or a checkpoint didn't get saved by one or more processes."
203
+ )
204
+
205
+ # the groups are named differently in each stage
206
+ if zero_stage <= 2:
207
+ fp32_groups_key = SINGLE_PARTITION_OF_FP32_GROUPS
208
+ elif zero_stage == 3:
209
+ fp32_groups_key = FP32_FLAT_GROUPS
210
+ else:
211
+ raise ValueError(f"unknown zero stage {zero_stage}")
212
+
213
+ fp32_flat_groups = [state_dicts[i][OPTIMIZER_STATE_DICT][fp32_groups_key] for i in range(len(state_dicts))]
214
+ return zero_stage, world_size, fp32_flat_groups
215
+
216
+
217
+ def _get_fp32_state_dict_from_zero_checkpoint(ds_checkpoint_dir, exclude_frozen_parameters):
218
+ """
219
+ Returns fp32 state_dict reconstructed from ds checkpoint
220
+
221
+ Args:
222
+ - ``ds_checkpoint_dir``: path to the deepspeed checkpoint folder (where the optimizer files are)
223
+
224
+ """
225
+ print(f"Processing zero checkpoint '{ds_checkpoint_dir}'")
226
+
227
+ # parse_model_states rejects AutoEP ZeRO-3 partition-native checkpoints
228
+ # before the expensive optimizer-shard load below.
229
+ model_files = get_model_state_files(ds_checkpoint_dir)
230
+ zero_model_states = parse_model_states(model_files)
231
+ print(f'Parsing checkpoint created by deepspeed=={zero_model_states[0].ds_version}')
232
+
233
+ optim_files = get_optim_files(ds_checkpoint_dir)
234
+ zero_stage, world_size, fp32_flat_groups = parse_optim_states(optim_files, ds_checkpoint_dir)
235
+ print(f"Detected checkpoint of type zero stage {zero_stage}, world_size: {world_size}")
236
+
237
+ if zero_stage <= 2:
238
+ return _get_fp32_state_dict_from_zero2_checkpoint(world_size, fp32_flat_groups, zero_model_states,
239
+ exclude_frozen_parameters)
240
+ elif zero_stage == 3:
241
+ return _get_fp32_state_dict_from_zero3_checkpoint(world_size, fp32_flat_groups, zero_model_states,
242
+ exclude_frozen_parameters)
243
+
244
+
245
+ def _zero2_merge_frozen_params(state_dict, zero_model_states):
246
+ if zero_model_states[0].frozen_param_shapes is None or len(zero_model_states[0].frozen_param_shapes) == 0:
247
+ return
248
+
249
+ frozen_param_shapes = zero_model_states[0].frozen_param_shapes
250
+ frozen_param_fragments = zero_model_states[0].frozen_param_fragments
251
+
252
+ if debug:
253
+ num_elem = sum(s.numel() for s in frozen_param_shapes.values())
254
+ print(f'rank 0: {FROZEN_PARAM_SHAPES}.numel = {num_elem}')
255
+
256
+ wanted_params = len(frozen_param_shapes)
257
+ wanted_numel = sum(s.numel() for s in frozen_param_shapes.values())
258
+ avail_numel = sum([p.numel() for p in frozen_param_fragments.values()])
259
+ print(f'Frozen params: Have {avail_numel} numels to process.')
260
+ print(f'Frozen params: Need {wanted_numel} numels in {wanted_params} params')
261
+
262
+ total_params = 0
263
+ total_numel = 0
264
+ for name, shape in frozen_param_shapes.items():
265
+ total_params += 1
266
+ unpartitioned_numel = shape.numel()
267
+ total_numel += unpartitioned_numel
268
+
269
+ state_dict[name] = frozen_param_fragments[name]
270
+
271
+ if debug:
272
+ print(f"{name} full shape: {shape} unpartitioned numel {unpartitioned_numel} ")
273
+
274
+ print(f"Reconstructed Frozen fp32 state dict with {total_params} params {total_numel} elements")
275
+
276
+
277
+ def _has_callable(obj, fn):
278
+ attr = getattr(obj, fn, None)
279
+ return callable(attr)
280
+
281
+
282
+ def _zero2_merge_trainable_params(state_dict, world_size, fp32_flat_groups, zero_model_states):
283
+ param_shapes = zero_model_states[0].param_shapes
284
+
285
+ # Reconstruction protocol:
286
+ #
287
+ # XXX: document this
288
+
289
+ if debug:
290
+ for i in range(world_size):
291
+ for j in range(len(fp32_flat_groups[0])):
292
+ print(f"{FP32_FLAT_GROUPS}[{i}][{j}].shape={fp32_flat_groups[i][j].shape}")
293
+
294
+ # XXX: memory usage doubles here (zero2)
295
+ num_param_groups = len(fp32_flat_groups[0])
296
+ merged_single_partition_of_fp32_groups = []
297
+ for i in range(num_param_groups):
298
+ merged_partitions = [sd[i] for sd in fp32_flat_groups]
299
+ full_single_fp32_vector = torch.cat(merged_partitions, 0)
300
+ merged_single_partition_of_fp32_groups.append(full_single_fp32_vector)
301
+ avail_numel = sum(
302
+ [full_single_fp32_vector.numel() for full_single_fp32_vector in merged_single_partition_of_fp32_groups])
303
+
304
+ if debug:
305
+ wanted_params = sum([len(shapes) for shapes in param_shapes])
306
+ wanted_numel = sum([sum(shape.numel() for shape in shapes.values()) for shapes in param_shapes])
307
+ # not asserting if there is a mismatch due to possible padding
308
+ print(f"Have {avail_numel} numels to process.")
309
+ print(f"Need {wanted_numel} numels in {wanted_params} params.")
310
+
311
+ # params
312
+ # XXX: for huge models that can't fit into the host's RAM we will have to recode this to support
313
+ # out-of-core computing solution
314
+ total_numel = 0
315
+ total_params = 0
316
+ for shapes, full_single_fp32_vector in zip(param_shapes, merged_single_partition_of_fp32_groups):
317
+ offset = 0
318
+ avail_numel = full_single_fp32_vector.numel()
319
+ for name, shape in shapes.items():
320
+
321
+ unpartitioned_numel = shape.numel() if _has_callable(shape, 'numel') else math.prod(shape)
322
+ total_numel += unpartitioned_numel
323
+ total_params += 1
324
+
325
+ if debug:
326
+ print(f"{name} full shape: {shape} unpartitioned numel {unpartitioned_numel} ")
327
+ state_dict[name] = full_single_fp32_vector.narrow(0, offset, unpartitioned_numel).view(shape)
328
+ offset += unpartitioned_numel
329
+
330
+ # Z2 started to align to 2*world_size to improve nccl performance. Therefore both offset and
331
+ # avail_numel can differ by anywhere between 0..2*world_size. Due to two unrelated complex
332
+ # paddings performed in the code it's almost impossible to predict the exact numbers w/o the
333
+ # live optimizer object, so we are checking that the numbers are within the right range
334
+ align_to = 2 * world_size
335
+
336
+ def zero2_align(x):
337
+ return align_to * math.ceil(x / align_to)
338
+
339
+ if debug:
340
+ print(f"original offset={offset}, avail_numel={avail_numel}")
341
+
342
+ offset = zero2_align(offset)
343
+ avail_numel = zero2_align(avail_numel)
344
+
345
+ if debug:
346
+ print(f"aligned offset={offset}, avail_numel={avail_numel}")
347
+
348
+ # Sanity check
349
+ if offset != avail_numel:
350
+ raise ValueError(f"consumed {offset} numels out of {avail_numel} - something is wrong")
351
+
352
+ print(f"Reconstructed fp32 state dict with {total_params} params {total_numel} elements")
353
+
354
+
355
+ def _get_fp32_state_dict_from_zero2_checkpoint(world_size, fp32_flat_groups, zero_model_states,
356
+ exclude_frozen_parameters):
357
+ state_dict = OrderedDict()
358
+
359
+ # buffers
360
+ buffers = zero_model_states[0].buffers
361
+ state_dict.update(buffers)
362
+ if debug:
363
+ print(f"added {len(buffers)} buffers")
364
+
365
+ if not exclude_frozen_parameters:
366
+ _zero2_merge_frozen_params(state_dict, zero_model_states)
367
+
368
+ _zero2_merge_trainable_params(state_dict, world_size, fp32_flat_groups, zero_model_states)
369
+
370
+ # recover shared parameters
371
+ for pair in zero_model_states[0].shared_params:
372
+ if pair[1] in state_dict:
373
+ state_dict[pair[0]] = state_dict[pair[1]]
374
+
375
+ return state_dict
376
+
377
+
378
+ def zero3_partitioned_param_info(unpartitioned_numel, world_size):
379
+ remainder = unpartitioned_numel % world_size
380
+ padding_numel = (world_size - remainder) if remainder else 0
381
+ partitioned_numel = math.ceil(unpartitioned_numel / world_size)
382
+ return partitioned_numel, padding_numel
383
+
384
+
385
+ def _zero3_merge_frozen_params(state_dict, world_size, zero_model_states):
386
+ if zero_model_states[0].frozen_param_shapes is None or len(zero_model_states[0].frozen_param_shapes) == 0:
387
+ return
388
+
389
+ if debug:
390
+ for i in range(world_size):
391
+ num_elem = sum(s.numel() for s in zero_model_states[i].frozen_param_fragments.values())
392
+ print(f'rank {i}: {FROZEN_PARAM_SHAPES}.numel = {num_elem}')
393
+
394
+ frozen_param_shapes = zero_model_states[0].frozen_param_shapes
395
+ wanted_params = len(frozen_param_shapes)
396
+ wanted_numel = sum(s.numel() for s in frozen_param_shapes.values())
397
+ avail_numel = sum([p.numel() for p in zero_model_states[0].frozen_param_fragments.values()]) * world_size
398
+ print(f'Frozen params: Have {avail_numel} numels to process.')
399
+ print(f'Frozen params: Need {wanted_numel} numels in {wanted_params} params')
400
+
401
+ total_params = 0
402
+ total_numel = 0
403
+ for name, shape in zero_model_states[0].frozen_param_shapes.items():
404
+ total_params += 1
405
+ unpartitioned_numel = shape.numel()
406
+ total_numel += unpartitioned_numel
407
+
408
+ param_frags = tuple(model_state.frozen_param_fragments[name] for model_state in zero_model_states)
409
+ state_dict[name] = torch.cat(param_frags, 0).narrow(0, 0, unpartitioned_numel).view(shape)
410
+
411
+ partitioned_numel, partitioned_padding_numel = zero3_partitioned_param_info(unpartitioned_numel, world_size)
412
+
413
+ if debug:
414
+ print(
415
+ f"Frozen params: {total_params} {name} full shape: {shape} partition0 numel={partitioned_numel} partitioned_padding_numel={partitioned_padding_numel}"
416
+ )
417
+
418
+ print(f"Reconstructed Frozen fp32 state dict with {total_params} params {total_numel} elements")
419
+
420
+
421
+ class GatheredTensor:
422
+ """
423
+ A pseudo tensor that collects partitioned weights.
424
+ It is more memory efficient when there are multiple groups.
425
+ """
426
+
427
+ def __init__(self, flat_groups, flat_groups_offset, offset, partitioned_numel, shape):
428
+ self.flat_groups = flat_groups
429
+ self.flat_groups_offset = flat_groups_offset
430
+ self.offset = offset
431
+ self.partitioned_numel = partitioned_numel
432
+ self.shape = shape
433
+ self.dtype = self.flat_groups[0][0].dtype
434
+
435
+ def contiguous(self):
436
+ """
437
+ Merge partitioned weights from flat_groups into a single tensor.
438
+ """
439
+ end_idx = self.offset + self.partitioned_numel
440
+ world_size = len(self.flat_groups)
441
+ pad_flat_param_chunks = []
442
+
443
+ for rank_i in range(world_size):
444
+ # for each rank, we need to collect weights from related group/groups
445
+ flat_groups_at_rank_i = self.flat_groups[rank_i]
446
+ start_group_id = None
447
+ end_group_id = None
448
+ for group_id in range(len(self.flat_groups_offset)):
449
+ if self.flat_groups_offset[group_id] <= self.offset < self.flat_groups_offset[group_id + 1]:
450
+ start_group_id = group_id
451
+ if self.flat_groups_offset[group_id] < end_idx <= self.flat_groups_offset[group_id + 1]:
452
+ end_group_id = group_id
453
+ break
454
+ # collect weights from related group/groups
455
+ for group_id in range(start_group_id, end_group_id + 1):
456
+ flat_tensor = flat_groups_at_rank_i[group_id]
457
+ start_offset = self.offset - self.flat_groups_offset[group_id]
458
+ end_offset = min(end_idx, self.flat_groups_offset[group_id + 1]) - self.flat_groups_offset[group_id]
459
+ pad_flat_param_chunks.append(flat_tensor[start_offset:end_offset])
460
+
461
+ # collect weights from all ranks
462
+ pad_flat_param = torch.cat(pad_flat_param_chunks, dim=0)
463
+ param = pad_flat_param[:self.shape.numel()].view(self.shape).contiguous()
464
+ return param
465
+
466
+
467
+ def _zero3_merge_trainable_params(state_dict, world_size, fp32_flat_groups, zero_model_states):
468
+ param_shapes = zero_model_states[0].param_shapes
469
+ avail_numel = sum([flat_group.numel() for flat_group in fp32_flat_groups[0]]) * world_size
470
+
471
+ # Reconstruction protocol: For zero3 we need to zip the partitions together at boundary of each
472
+ # param, re-consolidating each param, while dealing with padding if any
473
+
474
+ # merge list of dicts, preserving order
475
+ param_shapes = {k: v for d in param_shapes for k, v in d.items()}
476
+
477
+ if debug:
478
+ for i in range(world_size):
479
+ print(f"{FP32_FLAT_GROUPS}[{i}].shape={fp32_flat_groups[i].shape}")
480
+
481
+ wanted_params = len(param_shapes)
482
+ wanted_numel = sum(shape.numel() for shape in param_shapes.values())
483
+ # not asserting if there is a mismatch due to possible padding
484
+ avail_numel = fp32_flat_groups[0].numel() * world_size
485
+ print(f"Trainable params: Have {avail_numel} numels to process.")
486
+ print(f"Trainable params: Need {wanted_numel} numels in {wanted_params} params.")
487
+
488
+ # params
489
+ # XXX: for huge models that can't fit into the host's RAM we will have to recode this to support
490
+ # out-of-core computing solution
491
+ offset = 0
492
+ total_numel = 0
493
+ total_params = 0
494
+ flat_groups_offset = [0] + list(np.cumsum([flat_tensor.numel() for flat_tensor in fp32_flat_groups[0]]))
495
+ for name, shape in tqdm(param_shapes.items(), desc='Gathering sharded weights'):
496
+ unpartitioned_numel = shape.numel()
497
+ total_numel += unpartitioned_numel
498
+ total_params += 1
499
+ partitioned_numel, partitioned_padding_numel = zero3_partitioned_param_info(unpartitioned_numel, world_size)
500
+
501
+ if debug:
502
+ print(
503
+ f"Trainable params: {total_params} {name} full shape: {shape} partition0 numel={partitioned_numel} partitioned_padding_numel={partitioned_padding_numel}"
504
+ )
505
+
506
+ # memory efficient tensor
507
+ tensor = GatheredTensor(fp32_flat_groups, flat_groups_offset, offset, partitioned_numel, shape)
508
+ state_dict[name] = tensor
509
+ offset += partitioned_numel
510
+
511
+ offset *= world_size
512
+
513
+ # Sanity check
514
+ if offset != avail_numel:
515
+ raise ValueError(f"consumed {offset} numels out of {avail_numel} - something is wrong")
516
+
517
+ print(f"Reconstructed Trainable fp32 state dict with {total_params} params {total_numel} elements")
518
+
519
+
520
+ def _get_fp32_state_dict_from_zero3_checkpoint(world_size, fp32_flat_groups, zero_model_states,
521
+ exclude_frozen_parameters):
522
+ state_dict = OrderedDict()
523
+
524
+ # buffers
525
+ buffers = zero_model_states[0].buffers
526
+ state_dict.update(buffers)
527
+ if debug:
528
+ print(f"added {len(buffers)} buffers")
529
+
530
+ if not exclude_frozen_parameters:
531
+ _zero3_merge_frozen_params(state_dict, world_size, zero_model_states)
532
+
533
+ _zero3_merge_trainable_params(state_dict, world_size, fp32_flat_groups, zero_model_states)
534
+
535
+ # recover shared parameters
536
+ for pair in zero_model_states[0].shared_params:
537
+ if pair[1] in state_dict:
538
+ state_dict[pair[0]] = state_dict[pair[1]]
539
+
540
+ return state_dict
541
+
542
+
543
+ def to_torch_tensor(state_dict, return_empty_tensor=False):
544
+ """
545
+ Convert state_dict of GatheredTensor to torch tensor
546
+ """
547
+ torch_state_dict = {}
548
+ converted_tensors = {}
549
+ for name, tensor in state_dict.items():
550
+ tensor_id = id(tensor)
551
+ if tensor_id in converted_tensors: # shared tensors
552
+ shared_tensor = torch_state_dict[converted_tensors[tensor_id]]
553
+ torch_state_dict[name] = shared_tensor
554
+ else:
555
+ converted_tensors[tensor_id] = name
556
+ if return_empty_tensor:
557
+ torch_state_dict[name] = torch.empty(tensor.shape, dtype=tensor.dtype)
558
+ else:
559
+ torch_state_dict[name] = tensor.contiguous()
560
+ return torch_state_dict
561
+
562
+
563
+ def get_fp32_state_dict_from_zero_checkpoint(checkpoint_dir,
564
+ tag=None,
565
+ exclude_frozen_parameters=False,
566
+ lazy_mode=False):
567
+ """
568
+ Convert ZeRO 2 or 3 checkpoint into a single fp32 consolidated state_dict that can be loaded with
569
+ ``load_state_dict()`` and used for training without DeepSpeed or shared with others, for example
570
+ via a model hub.
571
+
572
+ Args:
573
+ - ``checkpoint_dir``: path to the desired checkpoint folder
574
+ - ``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``
575
+ - ``exclude_frozen_parameters``: exclude frozen parameters
576
+ - ``lazy_mode``: get state_dict in lazy mode. It returns a dict of pesduo tensor instead of torch tensor, which is more memory efficient.
577
+ Convert the pesduo tensor to torch tensor by ``.contiguous()``
578
+
579
+ Returns:
580
+ - pytorch ``state_dict``
581
+
582
+ A typical usage might be ::
583
+
584
+ from deepspeed.utils.zero_to_fp32 import get_fp32_state_dict_from_zero_checkpoint
585
+ # do the training and checkpoint saving
586
+ state_dict = get_fp32_state_dict_from_zero_checkpoint(checkpoint_dir) # already on cpu
587
+ model = model.cpu() # move to cpu
588
+ model.load_state_dict(state_dict)
589
+ # submit to model hub or save the model to share with others
590
+
591
+ In this example the ``model`` will no longer be usable in the deepspeed context of the same
592
+ application. i.e. you will need to re-initialize the deepspeed engine, since
593
+ ``model.load_state_dict(state_dict)`` will remove all the deepspeed magic from it.
594
+
595
+ If you want it all done for you, use ``load_state_dict_from_zero_checkpoint`` instead.
596
+
597
+ Note: the above usage may not work if your application doesn't have sufficient free CPU memory.
598
+ You may need to use the offline approach using the ``zero_to_fp32.py`` script that is saved with
599
+ the checkpoint. Or you can load state_dict in lazy mode ::
600
+
601
+ from deepspeed.utils.zero_to_fp32 import get_fp32_state_dict_from_zero_checkpoint
602
+ state_dict = get_fp32_state_dict_from_zero_checkpoint(checkpoint_dir, lazy_mode=True) # not on cpu
603
+ for name, lazy_tensor in state_dict.item():
604
+ tensor = lazy_tensor.contiguous() # to cpu
605
+ print(name, tensor)
606
+ # del tensor to release memory if it no longer in use
607
+ """
608
+ if tag is None:
609
+ latest_path = os.path.join(checkpoint_dir, 'latest')
610
+ if os.path.isfile(latest_path):
611
+ with open(latest_path, 'r') as fd:
612
+ tag = fd.read().strip()
613
+ else:
614
+ raise ValueError(f"Unable to find 'latest' file at {latest_path}")
615
+
616
+ ds_checkpoint_dir = os.path.join(checkpoint_dir, tag)
617
+
618
+ if not os.path.isdir(ds_checkpoint_dir):
619
+ raise FileNotFoundError(f"Directory '{ds_checkpoint_dir}' doesn't exist")
620
+
621
+ state_dict = _get_fp32_state_dict_from_zero_checkpoint(ds_checkpoint_dir, exclude_frozen_parameters)
622
+ if lazy_mode:
623
+ return state_dict
624
+ else:
625
+ return to_torch_tensor(state_dict)
626
+
627
+
628
+ def convert_zero_checkpoint_to_fp32_state_dict(checkpoint_dir,
629
+ output_dir,
630
+ max_shard_size="5GB",
631
+ safe_serialization=False,
632
+ tag=None,
633
+ exclude_frozen_parameters=False):
634
+ """
635
+ Convert ZeRO 2 or 3 checkpoint into a single fp32 consolidated ``state_dict`` file that can be
636
+ loaded with ``torch.load(file)`` + ``load_state_dict()`` and used for training without DeepSpeed.
637
+
638
+ Args:
639
+ - ``checkpoint_dir``: path to the desired checkpoint folder. (one that contains the tag-folder, like ``global_step14``)
640
+ - ``output_dir``: directory to the pytorch fp32 state_dict output files
641
+ - ``max_shard_size``: the maximum size for a checkpoint before being sharded, default value is 5GB
642
+ - ``safe_serialization``: whether to save the model using `safetensors` or the traditional PyTorch way (that uses `pickle`).
643
+ - ``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``
644
+ - ``exclude_frozen_parameters``: exclude frozen parameters
645
+ """
646
+
647
+ # Dependency pre-check
648
+ if safe_serialization:
649
+ try:
650
+ from safetensors.torch import save_file
651
+ except ImportError:
652
+ print('If you want to use `safe_serialization`, please `pip install safetensors`')
653
+ raise
654
+ if max_shard_size is not None:
655
+ try:
656
+ from huggingface_hub import split_torch_state_dict_into_shards
657
+ except ImportError:
658
+ print('If you want to use `max_shard_size`, please `pip install huggingface_hub`')
659
+ raise
660
+
661
+ # Convert zero checkpoint to state_dict
662
+ state_dict = get_fp32_state_dict_from_zero_checkpoint(checkpoint_dir,
663
+ tag,
664
+ exclude_frozen_parameters,
665
+ lazy_mode=True)
666
+
667
+ # Shard the model if it is too big.
668
+ weights_name = "model.safetensors" if safe_serialization else "pytorch_model.bin"
669
+ if max_shard_size is not None:
670
+ filename_pattern = weights_name.replace(".bin", "{suffix}.bin").replace(".safetensors", "{suffix}.safetensors")
671
+ # an memory-efficient approach for sharding
672
+ empty_state_dict = to_torch_tensor(state_dict, return_empty_tensor=True)
673
+ state_dict_split = split_torch_state_dict_into_shards(empty_state_dict,
674
+ filename_pattern=filename_pattern,
675
+ max_shard_size=max_shard_size)
676
+ else:
677
+ from collections import namedtuple
678
+ StateDictSplit = namedtuple("StateDictSplit", ["is_sharded", "filename_to_tensors"])
679
+ state_dict_split = StateDictSplit(is_sharded=False,
680
+ filename_to_tensors={weights_name: list(state_dict.keys())})
681
+
682
+ # Save the model by shard
683
+ os.makedirs(output_dir, exist_ok=True)
684
+ filename_to_tensors = state_dict_split.filename_to_tensors.items()
685
+ for shard_file, tensors in tqdm(filename_to_tensors, desc="Saving checkpoint shards"):
686
+ shard_state_dict = {tensor_name: state_dict[tensor_name] for tensor_name in tensors}
687
+ shard_state_dict = to_torch_tensor(shard_state_dict)
688
+ output_path = os.path.join(output_dir, shard_file)
689
+ if safe_serialization:
690
+ save_file(shard_state_dict, output_path, metadata={"format": "pt"})
691
+ else:
692
+ torch.save(shard_state_dict, output_path)
693
+ # release the memory of current shard
694
+ for tensor_name in list(shard_state_dict.keys()):
695
+ del state_dict[tensor_name]
696
+ del shard_state_dict[tensor_name]
697
+ del shard_state_dict
698
+ gc.collect()
699
+
700
+ # Save index if sharded
701
+ if state_dict_split.is_sharded:
702
+ index = {
703
+ "metadata": state_dict_split.metadata,
704
+ "weight_map": state_dict_split.tensor_to_filename,
705
+ }
706
+ save_index_file = "model.safetensors.index.json" if safe_serialization else "pytorch_model.bin.index.json"
707
+ save_index_file = os.path.join(output_dir, save_index_file)
708
+ with open(save_index_file, "w", encoding="utf-8") as f:
709
+ content = json.dumps(index, indent=2, sort_keys=True) + "\n"
710
+ f.write(content)
711
+
712
+
713
+ def load_state_dict_from_zero_checkpoint(model, checkpoint_dir, tag=None):
714
+ """
715
+ 1. Put the provided model to cpu
716
+ 2. Convert ZeRO 2 or 3 checkpoint into a single fp32 consolidated ``state_dict``
717
+ 3. Load it into the provided model
718
+
719
+ Args:
720
+ - ``model``: the model object to update
721
+ - ``checkpoint_dir``: path to the desired checkpoint folder. (one that contains the tag-folder, like ``global_step14``)
722
+ - ``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``
723
+
724
+ Returns:
725
+ - ``model`: modified model
726
+
727
+ Make sure you have plenty of CPU memory available before you call this function. If you don't
728
+ have enough use the ``zero_to_fp32.py`` utility to do the conversion. You will find it
729
+ conveniently placed for you in the checkpoint folder.
730
+
731
+ A typical usage might be ::
732
+
733
+ from deepspeed.utils.zero_to_fp32 import load_state_dict_from_zero_checkpoint
734
+ model = load_state_dict_from_zero_checkpoint(trainer.model, checkpoint_dir)
735
+ # submit to model hub or save the model to share with others
736
+
737
+ Note, that once this was run, the ``model`` will no longer be usable in the deepspeed context
738
+ of the same application. i.e. you will need to re-initialize the deepspeed engine, since
739
+ ``model.load_state_dict(state_dict)`` will remove all the deepspeed magic from it.
740
+
741
+ """
742
+ logger.info("Extracting fp32 weights")
743
+ state_dict = get_fp32_state_dict_from_zero_checkpoint(checkpoint_dir, tag)
744
+
745
+ logger.info("Overwriting model with fp32 weights")
746
+ model = model.cpu()
747
+ model.load_state_dict(state_dict, strict=False)
748
+
749
+ return model
750
+
751
+
752
+ if __name__ == "__main__":
753
+ parser = argparse.ArgumentParser()
754
+ parser.add_argument("checkpoint_dir",
755
+ type=str,
756
+ help="path to the desired checkpoint folder, e.g., path/checkpoint-12")
757
+ parser.add_argument("output_dir",
758
+ type=str,
759
+ help="directory to the pytorch fp32 state_dict output files"
760
+ "(e.g. path/checkpoint-12-output/)")
761
+ parser.add_argument(
762
+ "--max_shard_size",
763
+ type=str,
764
+ default="5GB",
765
+ help="The maximum size for a checkpoint before being sharded. Checkpoints shard will then be each of size"
766
+ "lower than this size. If expressed as a string, needs to be digits followed by a unit (like `5MB`"
767
+ "We default it to 5GB in order for models to be able to run easily on free-tier google colab instances"
768
+ "without CPU OOM issues.")
769
+ parser.add_argument(
770
+ "--safe_serialization",
771
+ default=False,
772
+ action='store_true',
773
+ help="Whether to save the model using `safetensors` or the traditional PyTorch way (that uses `pickle`).")
774
+ parser.add_argument("-t",
775
+ "--tag",
776
+ type=str,
777
+ default=None,
778
+ help="checkpoint tag used as a unique identifier for checkpoint. e.g., global_step1")
779
+ parser.add_argument("--exclude_frozen_parameters", action='store_true', help="exclude frozen parameters")
780
+ parser.add_argument("-d", "--debug", action='store_true', help="enable debug")
781
+ args = parser.parse_args()
782
+
783
+ debug = args.debug
784
+
785
+ convert_zero_checkpoint_to_fp32_state_dict(args.checkpoint_dir,
786
+ args.output_dir,
787
+ max_shard_size=args.max_shard_size,
788
+ safe_serialization=args.safe_serialization,
789
+ tag=args.tag,
790
+ exclude_frozen_parameters=args.exclude_frozen_parameters)
joint_50ksteps_bs128_qwen3vl2b_end_padding_mask/train.log ADDED
The diff for this file is too large to render. See raw diff
 
joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16/ds_checkpoint_50000/global_step50000/bf16_zero_pp_rank_0_mp_rank_00_optim_states.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3b2c5c69dce98a2cf8335980444c2e348684320fd7a08cb7d2ba33081a6629eb
3
+ size 8066685992
joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16/ds_checkpoint_50000/global_step50000/bf16_zero_pp_rank_1_mp_rank_00_optim_states.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:995a00042643cb0d40d29c38865a508786566428a556377230f7e5e3610ff24c
3
+ size 8066667688
joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16/ds_checkpoint_50000/global_step50000/bf16_zero_pp_rank_2_mp_rank_00_optim_states.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b94be9c4a3abf6fe10e95d05fff3bb235f87b5a312b1aafead647a779a4073d2
3
+ size 8066672104
joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16/ds_checkpoint_50000/global_step50000/bf16_zero_pp_rank_3_mp_rank_00_optim_states.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6c1e5e3f1a08be7d7f38723be85783df028bf8f74192559eb3b29570df5bc62f
3
+ size 8066671208
joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16/ds_checkpoint_50000/global_step50000/mp_rank_00_model_states.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:db17858086cb75834dae5bc85e09a99012b0dbcbead2318014177a6c2f2f9a08
3
+ size 5378167768
joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16/ds_checkpoint_50000/latest ADDED
@@ -0,0 +1 @@
 
 
1
+ global_step50000
joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16/ds_checkpoint_50000/zero_to_fp32.py ADDED
@@ -0,0 +1,790 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python
2
+
3
+ # Copyright (c) Microsoft Corporation.
4
+ # SPDX-License-Identifier: Apache-2.0
5
+
6
+ # DeepSpeed Team
7
+
8
+ # This script extracts fp32 consolidated weights from a zero 1, 2 and 3 DeepSpeed checkpoints. It gets
9
+ # copied into the top level checkpoint dir, so the user can easily do the conversion at any point in
10
+ # the future. Once extracted, the weights don't require DeepSpeed and can be used in any
11
+ # application.
12
+ #
13
+ # example:
14
+ # python zero_to_fp32.py . output_dir/
15
+ # or
16
+ # python zero_to_fp32.py . output_dir/ --safe_serialization
17
+
18
+ import argparse
19
+ import torch
20
+ import glob
21
+ import math
22
+ import os
23
+ import re
24
+ import gc
25
+ import json
26
+ import numpy as np
27
+ from tqdm import tqdm
28
+ from collections import OrderedDict
29
+ from dataclasses import dataclass
30
+
31
+ # while this script doesn't use deepspeed to recover data, since the checkpoints are pickled with
32
+ # DeepSpeed data structures it has to be available in the current python environment.
33
+ from deepspeed.utils import logger
34
+ from deepspeed.checkpoint.constants import (DS_VERSION, OPTIMIZER_STATE_DICT, SINGLE_PARTITION_OF_FP32_GROUPS,
35
+ FP32_FLAT_GROUPS, ZERO_STAGE, PARTITION_COUNT, PARAM_SHAPES, BUFFER_NAMES,
36
+ FROZEN_PARAM_SHAPES, FROZEN_PARAM_FRAGMENTS, AUTOEP_LAYERS_KEY,
37
+ AUTOEP_LAYERS_KEY_LEGACY, AUTOEP_ZERO3_EXPERT_STATE_FORMAT_KEY,
38
+ AUTOEP_ZERO3_PARTITIONED_EXPERT_STATE_FORMAT)
39
+
40
+
41
+ @dataclass
42
+ class zero_model_state:
43
+ buffers: dict()
44
+ param_shapes: dict()
45
+ shared_params: list
46
+ ds_version: int
47
+ frozen_param_shapes: dict()
48
+ frozen_param_fragments: dict()
49
+
50
+
51
+ debug = 0
52
+
53
+ # load to cpu
54
+ device = torch.device('cpu')
55
+
56
+
57
+ def atoi(text):
58
+ return int(text) if text.isdigit() else text
59
+
60
+
61
+ def natural_keys(text):
62
+ '''
63
+ alist.sort(key=natural_keys) sorts in human order
64
+ http://nedbatchelder.com/blog/200712/human_sorting.html
65
+ (See Toothy's implementation in the comments)
66
+ '''
67
+ return [atoi(c) for c in re.split(r'(\d+)', text)]
68
+
69
+
70
+ def get_model_state_file(checkpoint_dir, zero_stage):
71
+ if not os.path.isdir(checkpoint_dir):
72
+ raise FileNotFoundError(f"Directory '{checkpoint_dir}' doesn't exist")
73
+
74
+ # there should be only one file
75
+ if zero_stage <= 2:
76
+ file = os.path.join(checkpoint_dir, "mp_rank_00_model_states.pt")
77
+ elif zero_stage == 3:
78
+ file = os.path.join(checkpoint_dir, "zero_pp_rank_0_mp_rank_00_model_states.pt")
79
+
80
+ if not os.path.exists(file):
81
+ raise FileNotFoundError(f"can't find model states file at '{file}'")
82
+
83
+ return file
84
+
85
+
86
+ def get_checkpoint_files(checkpoint_dir, glob_pattern):
87
+ # XXX: need to test that this simple glob rule works for multi-node setup too
88
+ ckpt_files = sorted(glob.glob(os.path.join(checkpoint_dir, glob_pattern)), key=natural_keys)
89
+
90
+ if len(ckpt_files) == 0:
91
+ raise FileNotFoundError(f"can't find {glob_pattern} files in directory '{checkpoint_dir}'")
92
+
93
+ return ckpt_files
94
+
95
+
96
+ def get_optim_files(checkpoint_dir):
97
+ return get_checkpoint_files(checkpoint_dir, "*_optim_states.pt")
98
+
99
+
100
+ def get_model_state_files(checkpoint_dir):
101
+ return get_checkpoint_files(checkpoint_dir, "*_model_states.pt")
102
+
103
+
104
+ def _has_autoep_zero3_partitioned_metadata(state_dict):
105
+ autoep_layers = state_dict.get(AUTOEP_LAYERS_KEY)
106
+ if autoep_layers is None:
107
+ autoep_layers = state_dict.get(AUTOEP_LAYERS_KEY_LEGACY)
108
+ if not isinstance(autoep_layers, list):
109
+ return False
110
+ return any(
111
+ isinstance(entry, dict)
112
+ and entry.get(AUTOEP_ZERO3_EXPERT_STATE_FORMAT_KEY) == AUTOEP_ZERO3_PARTITIONED_EXPERT_STATE_FORMAT
113
+ for entry in autoep_layers)
114
+
115
+
116
+ def _raise_if_autoep_zero3_partitioned_state(state_dict):
117
+ if _has_autoep_zero3_partitioned_metadata(state_dict):
118
+ raise NotImplementedError("zero_to_fp32 does not support AutoEP ZeRO-3 partition-native checkpoints. "
119
+ "AutoEP expert parameters are partitioned over expert replica groups, so "
120
+ "global data-parallel consolidation would produce incomplete expert tensors. "
121
+ "Use ds_to_universal.py for expert-aware conversion.")
122
+
123
+
124
+ def _raise_if_autoep_zero3_partitioned_checkpoint(model_files):
125
+ for file in model_files:
126
+ state_dict = torch.load(file, map_location=device, weights_only=False)
127
+ _raise_if_autoep_zero3_partitioned_state(state_dict)
128
+
129
+
130
+ def parse_model_states(files):
131
+ zero_model_states = []
132
+ for file in files:
133
+ state_dict = torch.load(file, map_location=device, weights_only=False)
134
+ _raise_if_autoep_zero3_partitioned_state(state_dict)
135
+
136
+ if BUFFER_NAMES not in state_dict:
137
+ raise ValueError(f"{file} is not a model state checkpoint")
138
+ buffer_names = state_dict[BUFFER_NAMES]
139
+ if debug:
140
+ print("Found buffers:", buffer_names)
141
+
142
+ # recover just the buffers while restoring them to fp32 if they were saved in fp16
143
+ buffers = {k: v.float() for k, v in state_dict["module"].items() if k in buffer_names}
144
+ param_shapes = state_dict[PARAM_SHAPES]
145
+
146
+ # collect parameters that are included in param_shapes
147
+ param_names = []
148
+ for s in param_shapes:
149
+ for name in s.keys():
150
+ param_names.append(name)
151
+
152
+ # update with frozen parameters
153
+ frozen_param_shapes = state_dict.get(FROZEN_PARAM_SHAPES, None)
154
+ if frozen_param_shapes is not None:
155
+ if debug:
156
+ print(f"Found frozen_param_shapes: {frozen_param_shapes}")
157
+ param_names += list(frozen_param_shapes.keys())
158
+
159
+ # handle shared params
160
+ shared_params = [[k, v] for k, v in state_dict["shared_params"].items()]
161
+
162
+ ds_version = state_dict.get(DS_VERSION, None)
163
+
164
+ frozen_param_fragments = state_dict.get(FROZEN_PARAM_FRAGMENTS, None)
165
+
166
+ z_model_state = zero_model_state(buffers=buffers,
167
+ param_shapes=param_shapes,
168
+ shared_params=shared_params,
169
+ ds_version=ds_version,
170
+ frozen_param_shapes=frozen_param_shapes,
171
+ frozen_param_fragments=frozen_param_fragments)
172
+ zero_model_states.append(z_model_state)
173
+
174
+ return zero_model_states
175
+
176
+
177
+ def parse_optim_states(files, ds_checkpoint_dir):
178
+ total_files = len(files)
179
+ state_dicts = []
180
+ for f in tqdm(files, desc='Loading checkpoint shards'):
181
+ state_dict = torch.load(f, map_location=device, mmap=True, weights_only=False)
182
+ # immediately discard the potentially huge 2 optimizer states as we only care for fp32 master weights
183
+ # and also handle the case where it was already removed by another helper script
184
+ state_dict["optimizer_state_dict"].pop("optimizer_state_dict", None)
185
+ state_dicts.append(state_dict)
186
+
187
+ if ZERO_STAGE not in state_dicts[0][OPTIMIZER_STATE_DICT]:
188
+ raise ValueError(f"{files[0]} is not a zero checkpoint")
189
+ zero_stage = state_dicts[0][OPTIMIZER_STATE_DICT][ZERO_STAGE]
190
+ world_size = state_dicts[0][OPTIMIZER_STATE_DICT][PARTITION_COUNT]
191
+
192
+ # For ZeRO-2 each param group can have different partition_count as data parallelism for expert
193
+ # parameters can be different from data parallelism for non-expert parameters. So we can just
194
+ # use the max of the partition_count to get the dp world_size.
195
+
196
+ if type(world_size) is list:
197
+ world_size = max(world_size)
198
+
199
+ if world_size != total_files:
200
+ raise ValueError(
201
+ f"Expected {world_size} of '*_optim_states.pt' under '{ds_checkpoint_dir}' but found {total_files} files. "
202
+ "Possibly due to an overwrite of an old checkpoint, or a checkpoint didn't get saved by one or more processes."
203
+ )
204
+
205
+ # the groups are named differently in each stage
206
+ if zero_stage <= 2:
207
+ fp32_groups_key = SINGLE_PARTITION_OF_FP32_GROUPS
208
+ elif zero_stage == 3:
209
+ fp32_groups_key = FP32_FLAT_GROUPS
210
+ else:
211
+ raise ValueError(f"unknown zero stage {zero_stage}")
212
+
213
+ fp32_flat_groups = [state_dicts[i][OPTIMIZER_STATE_DICT][fp32_groups_key] for i in range(len(state_dicts))]
214
+ return zero_stage, world_size, fp32_flat_groups
215
+
216
+
217
+ def _get_fp32_state_dict_from_zero_checkpoint(ds_checkpoint_dir, exclude_frozen_parameters):
218
+ """
219
+ Returns fp32 state_dict reconstructed from ds checkpoint
220
+
221
+ Args:
222
+ - ``ds_checkpoint_dir``: path to the deepspeed checkpoint folder (where the optimizer files are)
223
+
224
+ """
225
+ print(f"Processing zero checkpoint '{ds_checkpoint_dir}'")
226
+
227
+ # parse_model_states rejects AutoEP ZeRO-3 partition-native checkpoints
228
+ # before the expensive optimizer-shard load below.
229
+ model_files = get_model_state_files(ds_checkpoint_dir)
230
+ zero_model_states = parse_model_states(model_files)
231
+ print(f'Parsing checkpoint created by deepspeed=={zero_model_states[0].ds_version}')
232
+
233
+ optim_files = get_optim_files(ds_checkpoint_dir)
234
+ zero_stage, world_size, fp32_flat_groups = parse_optim_states(optim_files, ds_checkpoint_dir)
235
+ print(f"Detected checkpoint of type zero stage {zero_stage}, world_size: {world_size}")
236
+
237
+ if zero_stage <= 2:
238
+ return _get_fp32_state_dict_from_zero2_checkpoint(world_size, fp32_flat_groups, zero_model_states,
239
+ exclude_frozen_parameters)
240
+ elif zero_stage == 3:
241
+ return _get_fp32_state_dict_from_zero3_checkpoint(world_size, fp32_flat_groups, zero_model_states,
242
+ exclude_frozen_parameters)
243
+
244
+
245
+ def _zero2_merge_frozen_params(state_dict, zero_model_states):
246
+ if zero_model_states[0].frozen_param_shapes is None or len(zero_model_states[0].frozen_param_shapes) == 0:
247
+ return
248
+
249
+ frozen_param_shapes = zero_model_states[0].frozen_param_shapes
250
+ frozen_param_fragments = zero_model_states[0].frozen_param_fragments
251
+
252
+ if debug:
253
+ num_elem = sum(s.numel() for s in frozen_param_shapes.values())
254
+ print(f'rank 0: {FROZEN_PARAM_SHAPES}.numel = {num_elem}')
255
+
256
+ wanted_params = len(frozen_param_shapes)
257
+ wanted_numel = sum(s.numel() for s in frozen_param_shapes.values())
258
+ avail_numel = sum([p.numel() for p in frozen_param_fragments.values()])
259
+ print(f'Frozen params: Have {avail_numel} numels to process.')
260
+ print(f'Frozen params: Need {wanted_numel} numels in {wanted_params} params')
261
+
262
+ total_params = 0
263
+ total_numel = 0
264
+ for name, shape in frozen_param_shapes.items():
265
+ total_params += 1
266
+ unpartitioned_numel = shape.numel()
267
+ total_numel += unpartitioned_numel
268
+
269
+ state_dict[name] = frozen_param_fragments[name]
270
+
271
+ if debug:
272
+ print(f"{name} full shape: {shape} unpartitioned numel {unpartitioned_numel} ")
273
+
274
+ print(f"Reconstructed Frozen fp32 state dict with {total_params} params {total_numel} elements")
275
+
276
+
277
+ def _has_callable(obj, fn):
278
+ attr = getattr(obj, fn, None)
279
+ return callable(attr)
280
+
281
+
282
+ def _zero2_merge_trainable_params(state_dict, world_size, fp32_flat_groups, zero_model_states):
283
+ param_shapes = zero_model_states[0].param_shapes
284
+
285
+ # Reconstruction protocol:
286
+ #
287
+ # XXX: document this
288
+
289
+ if debug:
290
+ for i in range(world_size):
291
+ for j in range(len(fp32_flat_groups[0])):
292
+ print(f"{FP32_FLAT_GROUPS}[{i}][{j}].shape={fp32_flat_groups[i][j].shape}")
293
+
294
+ # XXX: memory usage doubles here (zero2)
295
+ num_param_groups = len(fp32_flat_groups[0])
296
+ merged_single_partition_of_fp32_groups = []
297
+ for i in range(num_param_groups):
298
+ merged_partitions = [sd[i] for sd in fp32_flat_groups]
299
+ full_single_fp32_vector = torch.cat(merged_partitions, 0)
300
+ merged_single_partition_of_fp32_groups.append(full_single_fp32_vector)
301
+ avail_numel = sum(
302
+ [full_single_fp32_vector.numel() for full_single_fp32_vector in merged_single_partition_of_fp32_groups])
303
+
304
+ if debug:
305
+ wanted_params = sum([len(shapes) for shapes in param_shapes])
306
+ wanted_numel = sum([sum(shape.numel() for shape in shapes.values()) for shapes in param_shapes])
307
+ # not asserting if there is a mismatch due to possible padding
308
+ print(f"Have {avail_numel} numels to process.")
309
+ print(f"Need {wanted_numel} numels in {wanted_params} params.")
310
+
311
+ # params
312
+ # XXX: for huge models that can't fit into the host's RAM we will have to recode this to support
313
+ # out-of-core computing solution
314
+ total_numel = 0
315
+ total_params = 0
316
+ for shapes, full_single_fp32_vector in zip(param_shapes, merged_single_partition_of_fp32_groups):
317
+ offset = 0
318
+ avail_numel = full_single_fp32_vector.numel()
319
+ for name, shape in shapes.items():
320
+
321
+ unpartitioned_numel = shape.numel() if _has_callable(shape, 'numel') else math.prod(shape)
322
+ total_numel += unpartitioned_numel
323
+ total_params += 1
324
+
325
+ if debug:
326
+ print(f"{name} full shape: {shape} unpartitioned numel {unpartitioned_numel} ")
327
+ state_dict[name] = full_single_fp32_vector.narrow(0, offset, unpartitioned_numel).view(shape)
328
+ offset += unpartitioned_numel
329
+
330
+ # Z2 started to align to 2*world_size to improve nccl performance. Therefore both offset and
331
+ # avail_numel can differ by anywhere between 0..2*world_size. Due to two unrelated complex
332
+ # paddings performed in the code it's almost impossible to predict the exact numbers w/o the
333
+ # live optimizer object, so we are checking that the numbers are within the right range
334
+ align_to = 2 * world_size
335
+
336
+ def zero2_align(x):
337
+ return align_to * math.ceil(x / align_to)
338
+
339
+ if debug:
340
+ print(f"original offset={offset}, avail_numel={avail_numel}")
341
+
342
+ offset = zero2_align(offset)
343
+ avail_numel = zero2_align(avail_numel)
344
+
345
+ if debug:
346
+ print(f"aligned offset={offset}, avail_numel={avail_numel}")
347
+
348
+ # Sanity check
349
+ if offset != avail_numel:
350
+ raise ValueError(f"consumed {offset} numels out of {avail_numel} - something is wrong")
351
+
352
+ print(f"Reconstructed fp32 state dict with {total_params} params {total_numel} elements")
353
+
354
+
355
+ def _get_fp32_state_dict_from_zero2_checkpoint(world_size, fp32_flat_groups, zero_model_states,
356
+ exclude_frozen_parameters):
357
+ state_dict = OrderedDict()
358
+
359
+ # buffers
360
+ buffers = zero_model_states[0].buffers
361
+ state_dict.update(buffers)
362
+ if debug:
363
+ print(f"added {len(buffers)} buffers")
364
+
365
+ if not exclude_frozen_parameters:
366
+ _zero2_merge_frozen_params(state_dict, zero_model_states)
367
+
368
+ _zero2_merge_trainable_params(state_dict, world_size, fp32_flat_groups, zero_model_states)
369
+
370
+ # recover shared parameters
371
+ for pair in zero_model_states[0].shared_params:
372
+ if pair[1] in state_dict:
373
+ state_dict[pair[0]] = state_dict[pair[1]]
374
+
375
+ return state_dict
376
+
377
+
378
+ def zero3_partitioned_param_info(unpartitioned_numel, world_size):
379
+ remainder = unpartitioned_numel % world_size
380
+ padding_numel = (world_size - remainder) if remainder else 0
381
+ partitioned_numel = math.ceil(unpartitioned_numel / world_size)
382
+ return partitioned_numel, padding_numel
383
+
384
+
385
+ def _zero3_merge_frozen_params(state_dict, world_size, zero_model_states):
386
+ if zero_model_states[0].frozen_param_shapes is None or len(zero_model_states[0].frozen_param_shapes) == 0:
387
+ return
388
+
389
+ if debug:
390
+ for i in range(world_size):
391
+ num_elem = sum(s.numel() for s in zero_model_states[i].frozen_param_fragments.values())
392
+ print(f'rank {i}: {FROZEN_PARAM_SHAPES}.numel = {num_elem}')
393
+
394
+ frozen_param_shapes = zero_model_states[0].frozen_param_shapes
395
+ wanted_params = len(frozen_param_shapes)
396
+ wanted_numel = sum(s.numel() for s in frozen_param_shapes.values())
397
+ avail_numel = sum([p.numel() for p in zero_model_states[0].frozen_param_fragments.values()]) * world_size
398
+ print(f'Frozen params: Have {avail_numel} numels to process.')
399
+ print(f'Frozen params: Need {wanted_numel} numels in {wanted_params} params')
400
+
401
+ total_params = 0
402
+ total_numel = 0
403
+ for name, shape in zero_model_states[0].frozen_param_shapes.items():
404
+ total_params += 1
405
+ unpartitioned_numel = shape.numel()
406
+ total_numel += unpartitioned_numel
407
+
408
+ param_frags = tuple(model_state.frozen_param_fragments[name] for model_state in zero_model_states)
409
+ state_dict[name] = torch.cat(param_frags, 0).narrow(0, 0, unpartitioned_numel).view(shape)
410
+
411
+ partitioned_numel, partitioned_padding_numel = zero3_partitioned_param_info(unpartitioned_numel, world_size)
412
+
413
+ if debug:
414
+ print(
415
+ f"Frozen params: {total_params} {name} full shape: {shape} partition0 numel={partitioned_numel} partitioned_padding_numel={partitioned_padding_numel}"
416
+ )
417
+
418
+ print(f"Reconstructed Frozen fp32 state dict with {total_params} params {total_numel} elements")
419
+
420
+
421
+ class GatheredTensor:
422
+ """
423
+ A pseudo tensor that collects partitioned weights.
424
+ It is more memory efficient when there are multiple groups.
425
+ """
426
+
427
+ def __init__(self, flat_groups, flat_groups_offset, offset, partitioned_numel, shape):
428
+ self.flat_groups = flat_groups
429
+ self.flat_groups_offset = flat_groups_offset
430
+ self.offset = offset
431
+ self.partitioned_numel = partitioned_numel
432
+ self.shape = shape
433
+ self.dtype = self.flat_groups[0][0].dtype
434
+
435
+ def contiguous(self):
436
+ """
437
+ Merge partitioned weights from flat_groups into a single tensor.
438
+ """
439
+ end_idx = self.offset + self.partitioned_numel
440
+ world_size = len(self.flat_groups)
441
+ pad_flat_param_chunks = []
442
+
443
+ for rank_i in range(world_size):
444
+ # for each rank, we need to collect weights from related group/groups
445
+ flat_groups_at_rank_i = self.flat_groups[rank_i]
446
+ start_group_id = None
447
+ end_group_id = None
448
+ for group_id in range(len(self.flat_groups_offset)):
449
+ if self.flat_groups_offset[group_id] <= self.offset < self.flat_groups_offset[group_id + 1]:
450
+ start_group_id = group_id
451
+ if self.flat_groups_offset[group_id] < end_idx <= self.flat_groups_offset[group_id + 1]:
452
+ end_group_id = group_id
453
+ break
454
+ # collect weights from related group/groups
455
+ for group_id in range(start_group_id, end_group_id + 1):
456
+ flat_tensor = flat_groups_at_rank_i[group_id]
457
+ start_offset = self.offset - self.flat_groups_offset[group_id]
458
+ end_offset = min(end_idx, self.flat_groups_offset[group_id + 1]) - self.flat_groups_offset[group_id]
459
+ pad_flat_param_chunks.append(flat_tensor[start_offset:end_offset])
460
+
461
+ # collect weights from all ranks
462
+ pad_flat_param = torch.cat(pad_flat_param_chunks, dim=0)
463
+ param = pad_flat_param[:self.shape.numel()].view(self.shape).contiguous()
464
+ return param
465
+
466
+
467
+ def _zero3_merge_trainable_params(state_dict, world_size, fp32_flat_groups, zero_model_states):
468
+ param_shapes = zero_model_states[0].param_shapes
469
+ avail_numel = sum([flat_group.numel() for flat_group in fp32_flat_groups[0]]) * world_size
470
+
471
+ # Reconstruction protocol: For zero3 we need to zip the partitions together at boundary of each
472
+ # param, re-consolidating each param, while dealing with padding if any
473
+
474
+ # merge list of dicts, preserving order
475
+ param_shapes = {k: v for d in param_shapes for k, v in d.items()}
476
+
477
+ if debug:
478
+ for i in range(world_size):
479
+ print(f"{FP32_FLAT_GROUPS}[{i}].shape={fp32_flat_groups[i].shape}")
480
+
481
+ wanted_params = len(param_shapes)
482
+ wanted_numel = sum(shape.numel() for shape in param_shapes.values())
483
+ # not asserting if there is a mismatch due to possible padding
484
+ avail_numel = fp32_flat_groups[0].numel() * world_size
485
+ print(f"Trainable params: Have {avail_numel} numels to process.")
486
+ print(f"Trainable params: Need {wanted_numel} numels in {wanted_params} params.")
487
+
488
+ # params
489
+ # XXX: for huge models that can't fit into the host's RAM we will have to recode this to support
490
+ # out-of-core computing solution
491
+ offset = 0
492
+ total_numel = 0
493
+ total_params = 0
494
+ flat_groups_offset = [0] + list(np.cumsum([flat_tensor.numel() for flat_tensor in fp32_flat_groups[0]]))
495
+ for name, shape in tqdm(param_shapes.items(), desc='Gathering sharded weights'):
496
+ unpartitioned_numel = shape.numel()
497
+ total_numel += unpartitioned_numel
498
+ total_params += 1
499
+ partitioned_numel, partitioned_padding_numel = zero3_partitioned_param_info(unpartitioned_numel, world_size)
500
+
501
+ if debug:
502
+ print(
503
+ f"Trainable params: {total_params} {name} full shape: {shape} partition0 numel={partitioned_numel} partitioned_padding_numel={partitioned_padding_numel}"
504
+ )
505
+
506
+ # memory efficient tensor
507
+ tensor = GatheredTensor(fp32_flat_groups, flat_groups_offset, offset, partitioned_numel, shape)
508
+ state_dict[name] = tensor
509
+ offset += partitioned_numel
510
+
511
+ offset *= world_size
512
+
513
+ # Sanity check
514
+ if offset != avail_numel:
515
+ raise ValueError(f"consumed {offset} numels out of {avail_numel} - something is wrong")
516
+
517
+ print(f"Reconstructed Trainable fp32 state dict with {total_params} params {total_numel} elements")
518
+
519
+
520
+ def _get_fp32_state_dict_from_zero3_checkpoint(world_size, fp32_flat_groups, zero_model_states,
521
+ exclude_frozen_parameters):
522
+ state_dict = OrderedDict()
523
+
524
+ # buffers
525
+ buffers = zero_model_states[0].buffers
526
+ state_dict.update(buffers)
527
+ if debug:
528
+ print(f"added {len(buffers)} buffers")
529
+
530
+ if not exclude_frozen_parameters:
531
+ _zero3_merge_frozen_params(state_dict, world_size, zero_model_states)
532
+
533
+ _zero3_merge_trainable_params(state_dict, world_size, fp32_flat_groups, zero_model_states)
534
+
535
+ # recover shared parameters
536
+ for pair in zero_model_states[0].shared_params:
537
+ if pair[1] in state_dict:
538
+ state_dict[pair[0]] = state_dict[pair[1]]
539
+
540
+ return state_dict
541
+
542
+
543
+ def to_torch_tensor(state_dict, return_empty_tensor=False):
544
+ """
545
+ Convert state_dict of GatheredTensor to torch tensor
546
+ """
547
+ torch_state_dict = {}
548
+ converted_tensors = {}
549
+ for name, tensor in state_dict.items():
550
+ tensor_id = id(tensor)
551
+ if tensor_id in converted_tensors: # shared tensors
552
+ shared_tensor = torch_state_dict[converted_tensors[tensor_id]]
553
+ torch_state_dict[name] = shared_tensor
554
+ else:
555
+ converted_tensors[tensor_id] = name
556
+ if return_empty_tensor:
557
+ torch_state_dict[name] = torch.empty(tensor.shape, dtype=tensor.dtype)
558
+ else:
559
+ torch_state_dict[name] = tensor.contiguous()
560
+ return torch_state_dict
561
+
562
+
563
+ def get_fp32_state_dict_from_zero_checkpoint(checkpoint_dir,
564
+ tag=None,
565
+ exclude_frozen_parameters=False,
566
+ lazy_mode=False):
567
+ """
568
+ Convert ZeRO 2 or 3 checkpoint into a single fp32 consolidated state_dict that can be loaded with
569
+ ``load_state_dict()`` and used for training without DeepSpeed or shared with others, for example
570
+ via a model hub.
571
+
572
+ Args:
573
+ - ``checkpoint_dir``: path to the desired checkpoint folder
574
+ - ``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``
575
+ - ``exclude_frozen_parameters``: exclude frozen parameters
576
+ - ``lazy_mode``: get state_dict in lazy mode. It returns a dict of pesduo tensor instead of torch tensor, which is more memory efficient.
577
+ Convert the pesduo tensor to torch tensor by ``.contiguous()``
578
+
579
+ Returns:
580
+ - pytorch ``state_dict``
581
+
582
+ A typical usage might be ::
583
+
584
+ from deepspeed.utils.zero_to_fp32 import get_fp32_state_dict_from_zero_checkpoint
585
+ # do the training and checkpoint saving
586
+ state_dict = get_fp32_state_dict_from_zero_checkpoint(checkpoint_dir) # already on cpu
587
+ model = model.cpu() # move to cpu
588
+ model.load_state_dict(state_dict)
589
+ # submit to model hub or save the model to share with others
590
+
591
+ In this example the ``model`` will no longer be usable in the deepspeed context of the same
592
+ application. i.e. you will need to re-initialize the deepspeed engine, since
593
+ ``model.load_state_dict(state_dict)`` will remove all the deepspeed magic from it.
594
+
595
+ If you want it all done for you, use ``load_state_dict_from_zero_checkpoint`` instead.
596
+
597
+ Note: the above usage may not work if your application doesn't have sufficient free CPU memory.
598
+ You may need to use the offline approach using the ``zero_to_fp32.py`` script that is saved with
599
+ the checkpoint. Or you can load state_dict in lazy mode ::
600
+
601
+ from deepspeed.utils.zero_to_fp32 import get_fp32_state_dict_from_zero_checkpoint
602
+ state_dict = get_fp32_state_dict_from_zero_checkpoint(checkpoint_dir, lazy_mode=True) # not on cpu
603
+ for name, lazy_tensor in state_dict.item():
604
+ tensor = lazy_tensor.contiguous() # to cpu
605
+ print(name, tensor)
606
+ # del tensor to release memory if it no longer in use
607
+ """
608
+ if tag is None:
609
+ latest_path = os.path.join(checkpoint_dir, 'latest')
610
+ if os.path.isfile(latest_path):
611
+ with open(latest_path, 'r') as fd:
612
+ tag = fd.read().strip()
613
+ else:
614
+ raise ValueError(f"Unable to find 'latest' file at {latest_path}")
615
+
616
+ ds_checkpoint_dir = os.path.join(checkpoint_dir, tag)
617
+
618
+ if not os.path.isdir(ds_checkpoint_dir):
619
+ raise FileNotFoundError(f"Directory '{ds_checkpoint_dir}' doesn't exist")
620
+
621
+ state_dict = _get_fp32_state_dict_from_zero_checkpoint(ds_checkpoint_dir, exclude_frozen_parameters)
622
+ if lazy_mode:
623
+ return state_dict
624
+ else:
625
+ return to_torch_tensor(state_dict)
626
+
627
+
628
+ def convert_zero_checkpoint_to_fp32_state_dict(checkpoint_dir,
629
+ output_dir,
630
+ max_shard_size="5GB",
631
+ safe_serialization=False,
632
+ tag=None,
633
+ exclude_frozen_parameters=False):
634
+ """
635
+ Convert ZeRO 2 or 3 checkpoint into a single fp32 consolidated ``state_dict`` file that can be
636
+ loaded with ``torch.load(file)`` + ``load_state_dict()`` and used for training without DeepSpeed.
637
+
638
+ Args:
639
+ - ``checkpoint_dir``: path to the desired checkpoint folder. (one that contains the tag-folder, like ``global_step14``)
640
+ - ``output_dir``: directory to the pytorch fp32 state_dict output files
641
+ - ``max_shard_size``: the maximum size for a checkpoint before being sharded, default value is 5GB
642
+ - ``safe_serialization``: whether to save the model using `safetensors` or the traditional PyTorch way (that uses `pickle`).
643
+ - ``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``
644
+ - ``exclude_frozen_parameters``: exclude frozen parameters
645
+ """
646
+
647
+ # Dependency pre-check
648
+ if safe_serialization:
649
+ try:
650
+ from safetensors.torch import save_file
651
+ except ImportError:
652
+ print('If you want to use `safe_serialization`, please `pip install safetensors`')
653
+ raise
654
+ if max_shard_size is not None:
655
+ try:
656
+ from huggingface_hub import split_torch_state_dict_into_shards
657
+ except ImportError:
658
+ print('If you want to use `max_shard_size`, please `pip install huggingface_hub`')
659
+ raise
660
+
661
+ # Convert zero checkpoint to state_dict
662
+ state_dict = get_fp32_state_dict_from_zero_checkpoint(checkpoint_dir,
663
+ tag,
664
+ exclude_frozen_parameters,
665
+ lazy_mode=True)
666
+
667
+ # Shard the model if it is too big.
668
+ weights_name = "model.safetensors" if safe_serialization else "pytorch_model.bin"
669
+ if max_shard_size is not None:
670
+ filename_pattern = weights_name.replace(".bin", "{suffix}.bin").replace(".safetensors", "{suffix}.safetensors")
671
+ # an memory-efficient approach for sharding
672
+ empty_state_dict = to_torch_tensor(state_dict, return_empty_tensor=True)
673
+ state_dict_split = split_torch_state_dict_into_shards(empty_state_dict,
674
+ filename_pattern=filename_pattern,
675
+ max_shard_size=max_shard_size)
676
+ else:
677
+ from collections import namedtuple
678
+ StateDictSplit = namedtuple("StateDictSplit", ["is_sharded", "filename_to_tensors"])
679
+ state_dict_split = StateDictSplit(is_sharded=False,
680
+ filename_to_tensors={weights_name: list(state_dict.keys())})
681
+
682
+ # Save the model by shard
683
+ os.makedirs(output_dir, exist_ok=True)
684
+ filename_to_tensors = state_dict_split.filename_to_tensors.items()
685
+ for shard_file, tensors in tqdm(filename_to_tensors, desc="Saving checkpoint shards"):
686
+ shard_state_dict = {tensor_name: state_dict[tensor_name] for tensor_name in tensors}
687
+ shard_state_dict = to_torch_tensor(shard_state_dict)
688
+ output_path = os.path.join(output_dir, shard_file)
689
+ if safe_serialization:
690
+ save_file(shard_state_dict, output_path, metadata={"format": "pt"})
691
+ else:
692
+ torch.save(shard_state_dict, output_path)
693
+ # release the memory of current shard
694
+ for tensor_name in list(shard_state_dict.keys()):
695
+ del state_dict[tensor_name]
696
+ del shard_state_dict[tensor_name]
697
+ del shard_state_dict
698
+ gc.collect()
699
+
700
+ # Save index if sharded
701
+ if state_dict_split.is_sharded:
702
+ index = {
703
+ "metadata": state_dict_split.metadata,
704
+ "weight_map": state_dict_split.tensor_to_filename,
705
+ }
706
+ save_index_file = "model.safetensors.index.json" if safe_serialization else "pytorch_model.bin.index.json"
707
+ save_index_file = os.path.join(output_dir, save_index_file)
708
+ with open(save_index_file, "w", encoding="utf-8") as f:
709
+ content = json.dumps(index, indent=2, sort_keys=True) + "\n"
710
+ f.write(content)
711
+
712
+
713
+ def load_state_dict_from_zero_checkpoint(model, checkpoint_dir, tag=None):
714
+ """
715
+ 1. Put the provided model to cpu
716
+ 2. Convert ZeRO 2 or 3 checkpoint into a single fp32 consolidated ``state_dict``
717
+ 3. Load it into the provided model
718
+
719
+ Args:
720
+ - ``model``: the model object to update
721
+ - ``checkpoint_dir``: path to the desired checkpoint folder. (one that contains the tag-folder, like ``global_step14``)
722
+ - ``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``
723
+
724
+ Returns:
725
+ - ``model`: modified model
726
+
727
+ Make sure you have plenty of CPU memory available before you call this function. If you don't
728
+ have enough use the ``zero_to_fp32.py`` utility to do the conversion. You will find it
729
+ conveniently placed for you in the checkpoint folder.
730
+
731
+ A typical usage might be ::
732
+
733
+ from deepspeed.utils.zero_to_fp32 import load_state_dict_from_zero_checkpoint
734
+ model = load_state_dict_from_zero_checkpoint(trainer.model, checkpoint_dir)
735
+ # submit to model hub or save the model to share with others
736
+
737
+ Note, that once this was run, the ``model`` will no longer be usable in the deepspeed context
738
+ of the same application. i.e. you will need to re-initialize the deepspeed engine, since
739
+ ``model.load_state_dict(state_dict)`` will remove all the deepspeed magic from it.
740
+
741
+ """
742
+ logger.info("Extracting fp32 weights")
743
+ state_dict = get_fp32_state_dict_from_zero_checkpoint(checkpoint_dir, tag)
744
+
745
+ logger.info("Overwriting model with fp32 weights")
746
+ model = model.cpu()
747
+ model.load_state_dict(state_dict, strict=False)
748
+
749
+ return model
750
+
751
+
752
+ if __name__ == "__main__":
753
+ parser = argparse.ArgumentParser()
754
+ parser.add_argument("checkpoint_dir",
755
+ type=str,
756
+ help="path to the desired checkpoint folder, e.g., path/checkpoint-12")
757
+ parser.add_argument("output_dir",
758
+ type=str,
759
+ help="directory to the pytorch fp32 state_dict output files"
760
+ "(e.g. path/checkpoint-12-output/)")
761
+ parser.add_argument(
762
+ "--max_shard_size",
763
+ type=str,
764
+ default="5GB",
765
+ help="The maximum size for a checkpoint before being sharded. Checkpoints shard will then be each of size"
766
+ "lower than this size. If expressed as a string, needs to be digits followed by a unit (like `5MB`"
767
+ "We default it to 5GB in order for models to be able to run easily on free-tier google colab instances"
768
+ "without CPU OOM issues.")
769
+ parser.add_argument(
770
+ "--safe_serialization",
771
+ default=False,
772
+ action='store_true',
773
+ help="Whether to save the model using `safetensors` or the traditional PyTorch way (that uses `pickle`).")
774
+ parser.add_argument("-t",
775
+ "--tag",
776
+ type=str,
777
+ default=None,
778
+ help="checkpoint tag used as a unique identifier for checkpoint. e.g., global_step1")
779
+ parser.add_argument("--exclude_frozen_parameters", action='store_true', help="exclude frozen parameters")
780
+ parser.add_argument("-d", "--debug", action='store_true', help="enable debug")
781
+ args = parser.parse_args()
782
+
783
+ debug = args.debug
784
+
785
+ convert_zero_checkpoint_to_fp32_state_dict(args.checkpoint_dir,
786
+ args.output_dir,
787
+ max_shard_size=args.max_shard_size,
788
+ safe_serialization=args.safe_serialization,
789
+ tag=args.tag,
790
+ exclude_frozen_parameters=args.exclude_frozen_parameters)
joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16/ds_checkpoint_final/global_step50000/bf16_zero_pp_rank_0_mp_rank_00_optim_states.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3b2c5c69dce98a2cf8335980444c2e348684320fd7a08cb7d2ba33081a6629eb
3
+ size 8066685992
joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16/ds_checkpoint_final/global_step50000/bf16_zero_pp_rank_1_mp_rank_00_optim_states.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:995a00042643cb0d40d29c38865a508786566428a556377230f7e5e3610ff24c
3
+ size 8066667688
joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16/ds_checkpoint_final/global_step50000/bf16_zero_pp_rank_2_mp_rank_00_optim_states.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b94be9c4a3abf6fe10e95d05fff3bb235f87b5a312b1aafead647a779a4073d2
3
+ size 8066672104
joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16/ds_checkpoint_final/global_step50000/bf16_zero_pp_rank_3_mp_rank_00_optim_states.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6c1e5e3f1a08be7d7f38723be85783df028bf8f74192559eb3b29570df5bc62f
3
+ size 8066671208
joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16/ds_checkpoint_final/global_step50000/mp_rank_00_model_states.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:db17858086cb75834dae5bc85e09a99012b0dbcbead2318014177a6c2f2f9a08
3
+ size 5378167768
joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16/ds_checkpoint_final/latest ADDED
@@ -0,0 +1 @@
 
 
1
+ global_step50000
joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16/ds_checkpoint_final/zero_to_fp32.py ADDED
@@ -0,0 +1,790 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python
2
+
3
+ # Copyright (c) Microsoft Corporation.
4
+ # SPDX-License-Identifier: Apache-2.0
5
+
6
+ # DeepSpeed Team
7
+
8
+ # This script extracts fp32 consolidated weights from a zero 1, 2 and 3 DeepSpeed checkpoints. It gets
9
+ # copied into the top level checkpoint dir, so the user can easily do the conversion at any point in
10
+ # the future. Once extracted, the weights don't require DeepSpeed and can be used in any
11
+ # application.
12
+ #
13
+ # example:
14
+ # python zero_to_fp32.py . output_dir/
15
+ # or
16
+ # python zero_to_fp32.py . output_dir/ --safe_serialization
17
+
18
+ import argparse
19
+ import torch
20
+ import glob
21
+ import math
22
+ import os
23
+ import re
24
+ import gc
25
+ import json
26
+ import numpy as np
27
+ from tqdm import tqdm
28
+ from collections import OrderedDict
29
+ from dataclasses import dataclass
30
+
31
+ # while this script doesn't use deepspeed to recover data, since the checkpoints are pickled with
32
+ # DeepSpeed data structures it has to be available in the current python environment.
33
+ from deepspeed.utils import logger
34
+ from deepspeed.checkpoint.constants import (DS_VERSION, OPTIMIZER_STATE_DICT, SINGLE_PARTITION_OF_FP32_GROUPS,
35
+ FP32_FLAT_GROUPS, ZERO_STAGE, PARTITION_COUNT, PARAM_SHAPES, BUFFER_NAMES,
36
+ FROZEN_PARAM_SHAPES, FROZEN_PARAM_FRAGMENTS, AUTOEP_LAYERS_KEY,
37
+ AUTOEP_LAYERS_KEY_LEGACY, AUTOEP_ZERO3_EXPERT_STATE_FORMAT_KEY,
38
+ AUTOEP_ZERO3_PARTITIONED_EXPERT_STATE_FORMAT)
39
+
40
+
41
+ @dataclass
42
+ class zero_model_state:
43
+ buffers: dict()
44
+ param_shapes: dict()
45
+ shared_params: list
46
+ ds_version: int
47
+ frozen_param_shapes: dict()
48
+ frozen_param_fragments: dict()
49
+
50
+
51
+ debug = 0
52
+
53
+ # load to cpu
54
+ device = torch.device('cpu')
55
+
56
+
57
+ def atoi(text):
58
+ return int(text) if text.isdigit() else text
59
+
60
+
61
+ def natural_keys(text):
62
+ '''
63
+ alist.sort(key=natural_keys) sorts in human order
64
+ http://nedbatchelder.com/blog/200712/human_sorting.html
65
+ (See Toothy's implementation in the comments)
66
+ '''
67
+ return [atoi(c) for c in re.split(r'(\d+)', text)]
68
+
69
+
70
+ def get_model_state_file(checkpoint_dir, zero_stage):
71
+ if not os.path.isdir(checkpoint_dir):
72
+ raise FileNotFoundError(f"Directory '{checkpoint_dir}' doesn't exist")
73
+
74
+ # there should be only one file
75
+ if zero_stage <= 2:
76
+ file = os.path.join(checkpoint_dir, "mp_rank_00_model_states.pt")
77
+ elif zero_stage == 3:
78
+ file = os.path.join(checkpoint_dir, "zero_pp_rank_0_mp_rank_00_model_states.pt")
79
+
80
+ if not os.path.exists(file):
81
+ raise FileNotFoundError(f"can't find model states file at '{file}'")
82
+
83
+ return file
84
+
85
+
86
+ def get_checkpoint_files(checkpoint_dir, glob_pattern):
87
+ # XXX: need to test that this simple glob rule works for multi-node setup too
88
+ ckpt_files = sorted(glob.glob(os.path.join(checkpoint_dir, glob_pattern)), key=natural_keys)
89
+
90
+ if len(ckpt_files) == 0:
91
+ raise FileNotFoundError(f"can't find {glob_pattern} files in directory '{checkpoint_dir}'")
92
+
93
+ return ckpt_files
94
+
95
+
96
+ def get_optim_files(checkpoint_dir):
97
+ return get_checkpoint_files(checkpoint_dir, "*_optim_states.pt")
98
+
99
+
100
+ def get_model_state_files(checkpoint_dir):
101
+ return get_checkpoint_files(checkpoint_dir, "*_model_states.pt")
102
+
103
+
104
+ def _has_autoep_zero3_partitioned_metadata(state_dict):
105
+ autoep_layers = state_dict.get(AUTOEP_LAYERS_KEY)
106
+ if autoep_layers is None:
107
+ autoep_layers = state_dict.get(AUTOEP_LAYERS_KEY_LEGACY)
108
+ if not isinstance(autoep_layers, list):
109
+ return False
110
+ return any(
111
+ isinstance(entry, dict)
112
+ and entry.get(AUTOEP_ZERO3_EXPERT_STATE_FORMAT_KEY) == AUTOEP_ZERO3_PARTITIONED_EXPERT_STATE_FORMAT
113
+ for entry in autoep_layers)
114
+
115
+
116
+ def _raise_if_autoep_zero3_partitioned_state(state_dict):
117
+ if _has_autoep_zero3_partitioned_metadata(state_dict):
118
+ raise NotImplementedError("zero_to_fp32 does not support AutoEP ZeRO-3 partition-native checkpoints. "
119
+ "AutoEP expert parameters are partitioned over expert replica groups, so "
120
+ "global data-parallel consolidation would produce incomplete expert tensors. "
121
+ "Use ds_to_universal.py for expert-aware conversion.")
122
+
123
+
124
+ def _raise_if_autoep_zero3_partitioned_checkpoint(model_files):
125
+ for file in model_files:
126
+ state_dict = torch.load(file, map_location=device, weights_only=False)
127
+ _raise_if_autoep_zero3_partitioned_state(state_dict)
128
+
129
+
130
+ def parse_model_states(files):
131
+ zero_model_states = []
132
+ for file in files:
133
+ state_dict = torch.load(file, map_location=device, weights_only=False)
134
+ _raise_if_autoep_zero3_partitioned_state(state_dict)
135
+
136
+ if BUFFER_NAMES not in state_dict:
137
+ raise ValueError(f"{file} is not a model state checkpoint")
138
+ buffer_names = state_dict[BUFFER_NAMES]
139
+ if debug:
140
+ print("Found buffers:", buffer_names)
141
+
142
+ # recover just the buffers while restoring them to fp32 if they were saved in fp16
143
+ buffers = {k: v.float() for k, v in state_dict["module"].items() if k in buffer_names}
144
+ param_shapes = state_dict[PARAM_SHAPES]
145
+
146
+ # collect parameters that are included in param_shapes
147
+ param_names = []
148
+ for s in param_shapes:
149
+ for name in s.keys():
150
+ param_names.append(name)
151
+
152
+ # update with frozen parameters
153
+ frozen_param_shapes = state_dict.get(FROZEN_PARAM_SHAPES, None)
154
+ if frozen_param_shapes is not None:
155
+ if debug:
156
+ print(f"Found frozen_param_shapes: {frozen_param_shapes}")
157
+ param_names += list(frozen_param_shapes.keys())
158
+
159
+ # handle shared params
160
+ shared_params = [[k, v] for k, v in state_dict["shared_params"].items()]
161
+
162
+ ds_version = state_dict.get(DS_VERSION, None)
163
+
164
+ frozen_param_fragments = state_dict.get(FROZEN_PARAM_FRAGMENTS, None)
165
+
166
+ z_model_state = zero_model_state(buffers=buffers,
167
+ param_shapes=param_shapes,
168
+ shared_params=shared_params,
169
+ ds_version=ds_version,
170
+ frozen_param_shapes=frozen_param_shapes,
171
+ frozen_param_fragments=frozen_param_fragments)
172
+ zero_model_states.append(z_model_state)
173
+
174
+ return zero_model_states
175
+
176
+
177
+ def parse_optim_states(files, ds_checkpoint_dir):
178
+ total_files = len(files)
179
+ state_dicts = []
180
+ for f in tqdm(files, desc='Loading checkpoint shards'):
181
+ state_dict = torch.load(f, map_location=device, mmap=True, weights_only=False)
182
+ # immediately discard the potentially huge 2 optimizer states as we only care for fp32 master weights
183
+ # and also handle the case where it was already removed by another helper script
184
+ state_dict["optimizer_state_dict"].pop("optimizer_state_dict", None)
185
+ state_dicts.append(state_dict)
186
+
187
+ if ZERO_STAGE not in state_dicts[0][OPTIMIZER_STATE_DICT]:
188
+ raise ValueError(f"{files[0]} is not a zero checkpoint")
189
+ zero_stage = state_dicts[0][OPTIMIZER_STATE_DICT][ZERO_STAGE]
190
+ world_size = state_dicts[0][OPTIMIZER_STATE_DICT][PARTITION_COUNT]
191
+
192
+ # For ZeRO-2 each param group can have different partition_count as data parallelism for expert
193
+ # parameters can be different from data parallelism for non-expert parameters. So we can just
194
+ # use the max of the partition_count to get the dp world_size.
195
+
196
+ if type(world_size) is list:
197
+ world_size = max(world_size)
198
+
199
+ if world_size != total_files:
200
+ raise ValueError(
201
+ f"Expected {world_size} of '*_optim_states.pt' under '{ds_checkpoint_dir}' but found {total_files} files. "
202
+ "Possibly due to an overwrite of an old checkpoint, or a checkpoint didn't get saved by one or more processes."
203
+ )
204
+
205
+ # the groups are named differently in each stage
206
+ if zero_stage <= 2:
207
+ fp32_groups_key = SINGLE_PARTITION_OF_FP32_GROUPS
208
+ elif zero_stage == 3:
209
+ fp32_groups_key = FP32_FLAT_GROUPS
210
+ else:
211
+ raise ValueError(f"unknown zero stage {zero_stage}")
212
+
213
+ fp32_flat_groups = [state_dicts[i][OPTIMIZER_STATE_DICT][fp32_groups_key] for i in range(len(state_dicts))]
214
+ return zero_stage, world_size, fp32_flat_groups
215
+
216
+
217
+ def _get_fp32_state_dict_from_zero_checkpoint(ds_checkpoint_dir, exclude_frozen_parameters):
218
+ """
219
+ Returns fp32 state_dict reconstructed from ds checkpoint
220
+
221
+ Args:
222
+ - ``ds_checkpoint_dir``: path to the deepspeed checkpoint folder (where the optimizer files are)
223
+
224
+ """
225
+ print(f"Processing zero checkpoint '{ds_checkpoint_dir}'")
226
+
227
+ # parse_model_states rejects AutoEP ZeRO-3 partition-native checkpoints
228
+ # before the expensive optimizer-shard load below.
229
+ model_files = get_model_state_files(ds_checkpoint_dir)
230
+ zero_model_states = parse_model_states(model_files)
231
+ print(f'Parsing checkpoint created by deepspeed=={zero_model_states[0].ds_version}')
232
+
233
+ optim_files = get_optim_files(ds_checkpoint_dir)
234
+ zero_stage, world_size, fp32_flat_groups = parse_optim_states(optim_files, ds_checkpoint_dir)
235
+ print(f"Detected checkpoint of type zero stage {zero_stage}, world_size: {world_size}")
236
+
237
+ if zero_stage <= 2:
238
+ return _get_fp32_state_dict_from_zero2_checkpoint(world_size, fp32_flat_groups, zero_model_states,
239
+ exclude_frozen_parameters)
240
+ elif zero_stage == 3:
241
+ return _get_fp32_state_dict_from_zero3_checkpoint(world_size, fp32_flat_groups, zero_model_states,
242
+ exclude_frozen_parameters)
243
+
244
+
245
+ def _zero2_merge_frozen_params(state_dict, zero_model_states):
246
+ if zero_model_states[0].frozen_param_shapes is None or len(zero_model_states[0].frozen_param_shapes) == 0:
247
+ return
248
+
249
+ frozen_param_shapes = zero_model_states[0].frozen_param_shapes
250
+ frozen_param_fragments = zero_model_states[0].frozen_param_fragments
251
+
252
+ if debug:
253
+ num_elem = sum(s.numel() for s in frozen_param_shapes.values())
254
+ print(f'rank 0: {FROZEN_PARAM_SHAPES}.numel = {num_elem}')
255
+
256
+ wanted_params = len(frozen_param_shapes)
257
+ wanted_numel = sum(s.numel() for s in frozen_param_shapes.values())
258
+ avail_numel = sum([p.numel() for p in frozen_param_fragments.values()])
259
+ print(f'Frozen params: Have {avail_numel} numels to process.')
260
+ print(f'Frozen params: Need {wanted_numel} numels in {wanted_params} params')
261
+
262
+ total_params = 0
263
+ total_numel = 0
264
+ for name, shape in frozen_param_shapes.items():
265
+ total_params += 1
266
+ unpartitioned_numel = shape.numel()
267
+ total_numel += unpartitioned_numel
268
+
269
+ state_dict[name] = frozen_param_fragments[name]
270
+
271
+ if debug:
272
+ print(f"{name} full shape: {shape} unpartitioned numel {unpartitioned_numel} ")
273
+
274
+ print(f"Reconstructed Frozen fp32 state dict with {total_params} params {total_numel} elements")
275
+
276
+
277
+ def _has_callable(obj, fn):
278
+ attr = getattr(obj, fn, None)
279
+ return callable(attr)
280
+
281
+
282
+ def _zero2_merge_trainable_params(state_dict, world_size, fp32_flat_groups, zero_model_states):
283
+ param_shapes = zero_model_states[0].param_shapes
284
+
285
+ # Reconstruction protocol:
286
+ #
287
+ # XXX: document this
288
+
289
+ if debug:
290
+ for i in range(world_size):
291
+ for j in range(len(fp32_flat_groups[0])):
292
+ print(f"{FP32_FLAT_GROUPS}[{i}][{j}].shape={fp32_flat_groups[i][j].shape}")
293
+
294
+ # XXX: memory usage doubles here (zero2)
295
+ num_param_groups = len(fp32_flat_groups[0])
296
+ merged_single_partition_of_fp32_groups = []
297
+ for i in range(num_param_groups):
298
+ merged_partitions = [sd[i] for sd in fp32_flat_groups]
299
+ full_single_fp32_vector = torch.cat(merged_partitions, 0)
300
+ merged_single_partition_of_fp32_groups.append(full_single_fp32_vector)
301
+ avail_numel = sum(
302
+ [full_single_fp32_vector.numel() for full_single_fp32_vector in merged_single_partition_of_fp32_groups])
303
+
304
+ if debug:
305
+ wanted_params = sum([len(shapes) for shapes in param_shapes])
306
+ wanted_numel = sum([sum(shape.numel() for shape in shapes.values()) for shapes in param_shapes])
307
+ # not asserting if there is a mismatch due to possible padding
308
+ print(f"Have {avail_numel} numels to process.")
309
+ print(f"Need {wanted_numel} numels in {wanted_params} params.")
310
+
311
+ # params
312
+ # XXX: for huge models that can't fit into the host's RAM we will have to recode this to support
313
+ # out-of-core computing solution
314
+ total_numel = 0
315
+ total_params = 0
316
+ for shapes, full_single_fp32_vector in zip(param_shapes, merged_single_partition_of_fp32_groups):
317
+ offset = 0
318
+ avail_numel = full_single_fp32_vector.numel()
319
+ for name, shape in shapes.items():
320
+
321
+ unpartitioned_numel = shape.numel() if _has_callable(shape, 'numel') else math.prod(shape)
322
+ total_numel += unpartitioned_numel
323
+ total_params += 1
324
+
325
+ if debug:
326
+ print(f"{name} full shape: {shape} unpartitioned numel {unpartitioned_numel} ")
327
+ state_dict[name] = full_single_fp32_vector.narrow(0, offset, unpartitioned_numel).view(shape)
328
+ offset += unpartitioned_numel
329
+
330
+ # Z2 started to align to 2*world_size to improve nccl performance. Therefore both offset and
331
+ # avail_numel can differ by anywhere between 0..2*world_size. Due to two unrelated complex
332
+ # paddings performed in the code it's almost impossible to predict the exact numbers w/o the
333
+ # live optimizer object, so we are checking that the numbers are within the right range
334
+ align_to = 2 * world_size
335
+
336
+ def zero2_align(x):
337
+ return align_to * math.ceil(x / align_to)
338
+
339
+ if debug:
340
+ print(f"original offset={offset}, avail_numel={avail_numel}")
341
+
342
+ offset = zero2_align(offset)
343
+ avail_numel = zero2_align(avail_numel)
344
+
345
+ if debug:
346
+ print(f"aligned offset={offset}, avail_numel={avail_numel}")
347
+
348
+ # Sanity check
349
+ if offset != avail_numel:
350
+ raise ValueError(f"consumed {offset} numels out of {avail_numel} - something is wrong")
351
+
352
+ print(f"Reconstructed fp32 state dict with {total_params} params {total_numel} elements")
353
+
354
+
355
+ def _get_fp32_state_dict_from_zero2_checkpoint(world_size, fp32_flat_groups, zero_model_states,
356
+ exclude_frozen_parameters):
357
+ state_dict = OrderedDict()
358
+
359
+ # buffers
360
+ buffers = zero_model_states[0].buffers
361
+ state_dict.update(buffers)
362
+ if debug:
363
+ print(f"added {len(buffers)} buffers")
364
+
365
+ if not exclude_frozen_parameters:
366
+ _zero2_merge_frozen_params(state_dict, zero_model_states)
367
+
368
+ _zero2_merge_trainable_params(state_dict, world_size, fp32_flat_groups, zero_model_states)
369
+
370
+ # recover shared parameters
371
+ for pair in zero_model_states[0].shared_params:
372
+ if pair[1] in state_dict:
373
+ state_dict[pair[0]] = state_dict[pair[1]]
374
+
375
+ return state_dict
376
+
377
+
378
+ def zero3_partitioned_param_info(unpartitioned_numel, world_size):
379
+ remainder = unpartitioned_numel % world_size
380
+ padding_numel = (world_size - remainder) if remainder else 0
381
+ partitioned_numel = math.ceil(unpartitioned_numel / world_size)
382
+ return partitioned_numel, padding_numel
383
+
384
+
385
+ def _zero3_merge_frozen_params(state_dict, world_size, zero_model_states):
386
+ if zero_model_states[0].frozen_param_shapes is None or len(zero_model_states[0].frozen_param_shapes) == 0:
387
+ return
388
+
389
+ if debug:
390
+ for i in range(world_size):
391
+ num_elem = sum(s.numel() for s in zero_model_states[i].frozen_param_fragments.values())
392
+ print(f'rank {i}: {FROZEN_PARAM_SHAPES}.numel = {num_elem}')
393
+
394
+ frozen_param_shapes = zero_model_states[0].frozen_param_shapes
395
+ wanted_params = len(frozen_param_shapes)
396
+ wanted_numel = sum(s.numel() for s in frozen_param_shapes.values())
397
+ avail_numel = sum([p.numel() for p in zero_model_states[0].frozen_param_fragments.values()]) * world_size
398
+ print(f'Frozen params: Have {avail_numel} numels to process.')
399
+ print(f'Frozen params: Need {wanted_numel} numels in {wanted_params} params')
400
+
401
+ total_params = 0
402
+ total_numel = 0
403
+ for name, shape in zero_model_states[0].frozen_param_shapes.items():
404
+ total_params += 1
405
+ unpartitioned_numel = shape.numel()
406
+ total_numel += unpartitioned_numel
407
+
408
+ param_frags = tuple(model_state.frozen_param_fragments[name] for model_state in zero_model_states)
409
+ state_dict[name] = torch.cat(param_frags, 0).narrow(0, 0, unpartitioned_numel).view(shape)
410
+
411
+ partitioned_numel, partitioned_padding_numel = zero3_partitioned_param_info(unpartitioned_numel, world_size)
412
+
413
+ if debug:
414
+ print(
415
+ f"Frozen params: {total_params} {name} full shape: {shape} partition0 numel={partitioned_numel} partitioned_padding_numel={partitioned_padding_numel}"
416
+ )
417
+
418
+ print(f"Reconstructed Frozen fp32 state dict with {total_params} params {total_numel} elements")
419
+
420
+
421
+ class GatheredTensor:
422
+ """
423
+ A pseudo tensor that collects partitioned weights.
424
+ It is more memory efficient when there are multiple groups.
425
+ """
426
+
427
+ def __init__(self, flat_groups, flat_groups_offset, offset, partitioned_numel, shape):
428
+ self.flat_groups = flat_groups
429
+ self.flat_groups_offset = flat_groups_offset
430
+ self.offset = offset
431
+ self.partitioned_numel = partitioned_numel
432
+ self.shape = shape
433
+ self.dtype = self.flat_groups[0][0].dtype
434
+
435
+ def contiguous(self):
436
+ """
437
+ Merge partitioned weights from flat_groups into a single tensor.
438
+ """
439
+ end_idx = self.offset + self.partitioned_numel
440
+ world_size = len(self.flat_groups)
441
+ pad_flat_param_chunks = []
442
+
443
+ for rank_i in range(world_size):
444
+ # for each rank, we need to collect weights from related group/groups
445
+ flat_groups_at_rank_i = self.flat_groups[rank_i]
446
+ start_group_id = None
447
+ end_group_id = None
448
+ for group_id in range(len(self.flat_groups_offset)):
449
+ if self.flat_groups_offset[group_id] <= self.offset < self.flat_groups_offset[group_id + 1]:
450
+ start_group_id = group_id
451
+ if self.flat_groups_offset[group_id] < end_idx <= self.flat_groups_offset[group_id + 1]:
452
+ end_group_id = group_id
453
+ break
454
+ # collect weights from related group/groups
455
+ for group_id in range(start_group_id, end_group_id + 1):
456
+ flat_tensor = flat_groups_at_rank_i[group_id]
457
+ start_offset = self.offset - self.flat_groups_offset[group_id]
458
+ end_offset = min(end_idx, self.flat_groups_offset[group_id + 1]) - self.flat_groups_offset[group_id]
459
+ pad_flat_param_chunks.append(flat_tensor[start_offset:end_offset])
460
+
461
+ # collect weights from all ranks
462
+ pad_flat_param = torch.cat(pad_flat_param_chunks, dim=0)
463
+ param = pad_flat_param[:self.shape.numel()].view(self.shape).contiguous()
464
+ return param
465
+
466
+
467
+ def _zero3_merge_trainable_params(state_dict, world_size, fp32_flat_groups, zero_model_states):
468
+ param_shapes = zero_model_states[0].param_shapes
469
+ avail_numel = sum([flat_group.numel() for flat_group in fp32_flat_groups[0]]) * world_size
470
+
471
+ # Reconstruction protocol: For zero3 we need to zip the partitions together at boundary of each
472
+ # param, re-consolidating each param, while dealing with padding if any
473
+
474
+ # merge list of dicts, preserving order
475
+ param_shapes = {k: v for d in param_shapes for k, v in d.items()}
476
+
477
+ if debug:
478
+ for i in range(world_size):
479
+ print(f"{FP32_FLAT_GROUPS}[{i}].shape={fp32_flat_groups[i].shape}")
480
+
481
+ wanted_params = len(param_shapes)
482
+ wanted_numel = sum(shape.numel() for shape in param_shapes.values())
483
+ # not asserting if there is a mismatch due to possible padding
484
+ avail_numel = fp32_flat_groups[0].numel() * world_size
485
+ print(f"Trainable params: Have {avail_numel} numels to process.")
486
+ print(f"Trainable params: Need {wanted_numel} numels in {wanted_params} params.")
487
+
488
+ # params
489
+ # XXX: for huge models that can't fit into the host's RAM we will have to recode this to support
490
+ # out-of-core computing solution
491
+ offset = 0
492
+ total_numel = 0
493
+ total_params = 0
494
+ flat_groups_offset = [0] + list(np.cumsum([flat_tensor.numel() for flat_tensor in fp32_flat_groups[0]]))
495
+ for name, shape in tqdm(param_shapes.items(), desc='Gathering sharded weights'):
496
+ unpartitioned_numel = shape.numel()
497
+ total_numel += unpartitioned_numel
498
+ total_params += 1
499
+ partitioned_numel, partitioned_padding_numel = zero3_partitioned_param_info(unpartitioned_numel, world_size)
500
+
501
+ if debug:
502
+ print(
503
+ f"Trainable params: {total_params} {name} full shape: {shape} partition0 numel={partitioned_numel} partitioned_padding_numel={partitioned_padding_numel}"
504
+ )
505
+
506
+ # memory efficient tensor
507
+ tensor = GatheredTensor(fp32_flat_groups, flat_groups_offset, offset, partitioned_numel, shape)
508
+ state_dict[name] = tensor
509
+ offset += partitioned_numel
510
+
511
+ offset *= world_size
512
+
513
+ # Sanity check
514
+ if offset != avail_numel:
515
+ raise ValueError(f"consumed {offset} numels out of {avail_numel} - something is wrong")
516
+
517
+ print(f"Reconstructed Trainable fp32 state dict with {total_params} params {total_numel} elements")
518
+
519
+
520
+ def _get_fp32_state_dict_from_zero3_checkpoint(world_size, fp32_flat_groups, zero_model_states,
521
+ exclude_frozen_parameters):
522
+ state_dict = OrderedDict()
523
+
524
+ # buffers
525
+ buffers = zero_model_states[0].buffers
526
+ state_dict.update(buffers)
527
+ if debug:
528
+ print(f"added {len(buffers)} buffers")
529
+
530
+ if not exclude_frozen_parameters:
531
+ _zero3_merge_frozen_params(state_dict, world_size, zero_model_states)
532
+
533
+ _zero3_merge_trainable_params(state_dict, world_size, fp32_flat_groups, zero_model_states)
534
+
535
+ # recover shared parameters
536
+ for pair in zero_model_states[0].shared_params:
537
+ if pair[1] in state_dict:
538
+ state_dict[pair[0]] = state_dict[pair[1]]
539
+
540
+ return state_dict
541
+
542
+
543
+ def to_torch_tensor(state_dict, return_empty_tensor=False):
544
+ """
545
+ Convert state_dict of GatheredTensor to torch tensor
546
+ """
547
+ torch_state_dict = {}
548
+ converted_tensors = {}
549
+ for name, tensor in state_dict.items():
550
+ tensor_id = id(tensor)
551
+ if tensor_id in converted_tensors: # shared tensors
552
+ shared_tensor = torch_state_dict[converted_tensors[tensor_id]]
553
+ torch_state_dict[name] = shared_tensor
554
+ else:
555
+ converted_tensors[tensor_id] = name
556
+ if return_empty_tensor:
557
+ torch_state_dict[name] = torch.empty(tensor.shape, dtype=tensor.dtype)
558
+ else:
559
+ torch_state_dict[name] = tensor.contiguous()
560
+ return torch_state_dict
561
+
562
+
563
+ def get_fp32_state_dict_from_zero_checkpoint(checkpoint_dir,
564
+ tag=None,
565
+ exclude_frozen_parameters=False,
566
+ lazy_mode=False):
567
+ """
568
+ Convert ZeRO 2 or 3 checkpoint into a single fp32 consolidated state_dict that can be loaded with
569
+ ``load_state_dict()`` and used for training without DeepSpeed or shared with others, for example
570
+ via a model hub.
571
+
572
+ Args:
573
+ - ``checkpoint_dir``: path to the desired checkpoint folder
574
+ - ``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``
575
+ - ``exclude_frozen_parameters``: exclude frozen parameters
576
+ - ``lazy_mode``: get state_dict in lazy mode. It returns a dict of pesduo tensor instead of torch tensor, which is more memory efficient.
577
+ Convert the pesduo tensor to torch tensor by ``.contiguous()``
578
+
579
+ Returns:
580
+ - pytorch ``state_dict``
581
+
582
+ A typical usage might be ::
583
+
584
+ from deepspeed.utils.zero_to_fp32 import get_fp32_state_dict_from_zero_checkpoint
585
+ # do the training and checkpoint saving
586
+ state_dict = get_fp32_state_dict_from_zero_checkpoint(checkpoint_dir) # already on cpu
587
+ model = model.cpu() # move to cpu
588
+ model.load_state_dict(state_dict)
589
+ # submit to model hub or save the model to share with others
590
+
591
+ In this example the ``model`` will no longer be usable in the deepspeed context of the same
592
+ application. i.e. you will need to re-initialize the deepspeed engine, since
593
+ ``model.load_state_dict(state_dict)`` will remove all the deepspeed magic from it.
594
+
595
+ If you want it all done for you, use ``load_state_dict_from_zero_checkpoint`` instead.
596
+
597
+ Note: the above usage may not work if your application doesn't have sufficient free CPU memory.
598
+ You may need to use the offline approach using the ``zero_to_fp32.py`` script that is saved with
599
+ the checkpoint. Or you can load state_dict in lazy mode ::
600
+
601
+ from deepspeed.utils.zero_to_fp32 import get_fp32_state_dict_from_zero_checkpoint
602
+ state_dict = get_fp32_state_dict_from_zero_checkpoint(checkpoint_dir, lazy_mode=True) # not on cpu
603
+ for name, lazy_tensor in state_dict.item():
604
+ tensor = lazy_tensor.contiguous() # to cpu
605
+ print(name, tensor)
606
+ # del tensor to release memory if it no longer in use
607
+ """
608
+ if tag is None:
609
+ latest_path = os.path.join(checkpoint_dir, 'latest')
610
+ if os.path.isfile(latest_path):
611
+ with open(latest_path, 'r') as fd:
612
+ tag = fd.read().strip()
613
+ else:
614
+ raise ValueError(f"Unable to find 'latest' file at {latest_path}")
615
+
616
+ ds_checkpoint_dir = os.path.join(checkpoint_dir, tag)
617
+
618
+ if not os.path.isdir(ds_checkpoint_dir):
619
+ raise FileNotFoundError(f"Directory '{ds_checkpoint_dir}' doesn't exist")
620
+
621
+ state_dict = _get_fp32_state_dict_from_zero_checkpoint(ds_checkpoint_dir, exclude_frozen_parameters)
622
+ if lazy_mode:
623
+ return state_dict
624
+ else:
625
+ return to_torch_tensor(state_dict)
626
+
627
+
628
+ def convert_zero_checkpoint_to_fp32_state_dict(checkpoint_dir,
629
+ output_dir,
630
+ max_shard_size="5GB",
631
+ safe_serialization=False,
632
+ tag=None,
633
+ exclude_frozen_parameters=False):
634
+ """
635
+ Convert ZeRO 2 or 3 checkpoint into a single fp32 consolidated ``state_dict`` file that can be
636
+ loaded with ``torch.load(file)`` + ``load_state_dict()`` and used for training without DeepSpeed.
637
+
638
+ Args:
639
+ - ``checkpoint_dir``: path to the desired checkpoint folder. (one that contains the tag-folder, like ``global_step14``)
640
+ - ``output_dir``: directory to the pytorch fp32 state_dict output files
641
+ - ``max_shard_size``: the maximum size for a checkpoint before being sharded, default value is 5GB
642
+ - ``safe_serialization``: whether to save the model using `safetensors` or the traditional PyTorch way (that uses `pickle`).
643
+ - ``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``
644
+ - ``exclude_frozen_parameters``: exclude frozen parameters
645
+ """
646
+
647
+ # Dependency pre-check
648
+ if safe_serialization:
649
+ try:
650
+ from safetensors.torch import save_file
651
+ except ImportError:
652
+ print('If you want to use `safe_serialization`, please `pip install safetensors`')
653
+ raise
654
+ if max_shard_size is not None:
655
+ try:
656
+ from huggingface_hub import split_torch_state_dict_into_shards
657
+ except ImportError:
658
+ print('If you want to use `max_shard_size`, please `pip install huggingface_hub`')
659
+ raise
660
+
661
+ # Convert zero checkpoint to state_dict
662
+ state_dict = get_fp32_state_dict_from_zero_checkpoint(checkpoint_dir,
663
+ tag,
664
+ exclude_frozen_parameters,
665
+ lazy_mode=True)
666
+
667
+ # Shard the model if it is too big.
668
+ weights_name = "model.safetensors" if safe_serialization else "pytorch_model.bin"
669
+ if max_shard_size is not None:
670
+ filename_pattern = weights_name.replace(".bin", "{suffix}.bin").replace(".safetensors", "{suffix}.safetensors")
671
+ # an memory-efficient approach for sharding
672
+ empty_state_dict = to_torch_tensor(state_dict, return_empty_tensor=True)
673
+ state_dict_split = split_torch_state_dict_into_shards(empty_state_dict,
674
+ filename_pattern=filename_pattern,
675
+ max_shard_size=max_shard_size)
676
+ else:
677
+ from collections import namedtuple
678
+ StateDictSplit = namedtuple("StateDictSplit", ["is_sharded", "filename_to_tensors"])
679
+ state_dict_split = StateDictSplit(is_sharded=False,
680
+ filename_to_tensors={weights_name: list(state_dict.keys())})
681
+
682
+ # Save the model by shard
683
+ os.makedirs(output_dir, exist_ok=True)
684
+ filename_to_tensors = state_dict_split.filename_to_tensors.items()
685
+ for shard_file, tensors in tqdm(filename_to_tensors, desc="Saving checkpoint shards"):
686
+ shard_state_dict = {tensor_name: state_dict[tensor_name] for tensor_name in tensors}
687
+ shard_state_dict = to_torch_tensor(shard_state_dict)
688
+ output_path = os.path.join(output_dir, shard_file)
689
+ if safe_serialization:
690
+ save_file(shard_state_dict, output_path, metadata={"format": "pt"})
691
+ else:
692
+ torch.save(shard_state_dict, output_path)
693
+ # release the memory of current shard
694
+ for tensor_name in list(shard_state_dict.keys()):
695
+ del state_dict[tensor_name]
696
+ del shard_state_dict[tensor_name]
697
+ del shard_state_dict
698
+ gc.collect()
699
+
700
+ # Save index if sharded
701
+ if state_dict_split.is_sharded:
702
+ index = {
703
+ "metadata": state_dict_split.metadata,
704
+ "weight_map": state_dict_split.tensor_to_filename,
705
+ }
706
+ save_index_file = "model.safetensors.index.json" if safe_serialization else "pytorch_model.bin.index.json"
707
+ save_index_file = os.path.join(output_dir, save_index_file)
708
+ with open(save_index_file, "w", encoding="utf-8") as f:
709
+ content = json.dumps(index, indent=2, sort_keys=True) + "\n"
710
+ f.write(content)
711
+
712
+
713
+ def load_state_dict_from_zero_checkpoint(model, checkpoint_dir, tag=None):
714
+ """
715
+ 1. Put the provided model to cpu
716
+ 2. Convert ZeRO 2 or 3 checkpoint into a single fp32 consolidated ``state_dict``
717
+ 3. Load it into the provided model
718
+
719
+ Args:
720
+ - ``model``: the model object to update
721
+ - ``checkpoint_dir``: path to the desired checkpoint folder. (one that contains the tag-folder, like ``global_step14``)
722
+ - ``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``
723
+
724
+ Returns:
725
+ - ``model`: modified model
726
+
727
+ Make sure you have plenty of CPU memory available before you call this function. If you don't
728
+ have enough use the ``zero_to_fp32.py`` utility to do the conversion. You will find it
729
+ conveniently placed for you in the checkpoint folder.
730
+
731
+ A typical usage might be ::
732
+
733
+ from deepspeed.utils.zero_to_fp32 import load_state_dict_from_zero_checkpoint
734
+ model = load_state_dict_from_zero_checkpoint(trainer.model, checkpoint_dir)
735
+ # submit to model hub or save the model to share with others
736
+
737
+ Note, that once this was run, the ``model`` will no longer be usable in the deepspeed context
738
+ of the same application. i.e. you will need to re-initialize the deepspeed engine, since
739
+ ``model.load_state_dict(state_dict)`` will remove all the deepspeed magic from it.
740
+
741
+ """
742
+ logger.info("Extracting fp32 weights")
743
+ state_dict = get_fp32_state_dict_from_zero_checkpoint(checkpoint_dir, tag)
744
+
745
+ logger.info("Overwriting model with fp32 weights")
746
+ model = model.cpu()
747
+ model.load_state_dict(state_dict, strict=False)
748
+
749
+ return model
750
+
751
+
752
+ if __name__ == "__main__":
753
+ parser = argparse.ArgumentParser()
754
+ parser.add_argument("checkpoint_dir",
755
+ type=str,
756
+ help="path to the desired checkpoint folder, e.g., path/checkpoint-12")
757
+ parser.add_argument("output_dir",
758
+ type=str,
759
+ help="directory to the pytorch fp32 state_dict output files"
760
+ "(e.g. path/checkpoint-12-output/)")
761
+ parser.add_argument(
762
+ "--max_shard_size",
763
+ type=str,
764
+ default="5GB",
765
+ help="The maximum size for a checkpoint before being sharded. Checkpoints shard will then be each of size"
766
+ "lower than this size. If expressed as a string, needs to be digits followed by a unit (like `5MB`"
767
+ "We default it to 5GB in order for models to be able to run easily on free-tier google colab instances"
768
+ "without CPU OOM issues.")
769
+ parser.add_argument(
770
+ "--safe_serialization",
771
+ default=False,
772
+ action='store_true',
773
+ help="Whether to save the model using `safetensors` or the traditional PyTorch way (that uses `pickle`).")
774
+ parser.add_argument("-t",
775
+ "--tag",
776
+ type=str,
777
+ default=None,
778
+ help="checkpoint tag used as a unique identifier for checkpoint. e.g., global_step1")
779
+ parser.add_argument("--exclude_frozen_parameters", action='store_true', help="exclude frozen parameters")
780
+ parser.add_argument("-d", "--debug", action='store_true', help="enable debug")
781
+ args = parser.parse_args()
782
+
783
+ debug = args.debug
784
+
785
+ convert_zero_checkpoint_to_fp32_state_dict(args.checkpoint_dir,
786
+ args.output_dir,
787
+ max_shard_size=args.max_shard_size,
788
+ safe_serialization=args.safe_serialization,
789
+ tag=args.tag,
790
+ exclude_frozen_parameters=args.exclude_frozen_parameters)
joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16/train.log ADDED
The diff for this file is too large to render. See raw diff
 
joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16_new_instruction/ds_checkpoint_50000/global_step50000/bf16_zero_pp_rank_0_mp_rank_00_optim_states.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:fb57463db6681ab559a9e0b030bd157c04abf57e5bfacb192e148b78acf23199
3
+ size 4033352816
joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16_new_instruction/ds_checkpoint_50000/global_step50000/bf16_zero_pp_rank_1_mp_rank_00_optim_states.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:89c83da0743e8ce8f22fbed0244ad21b3dbcadda98dd3cc00f39606dcec73b89
3
+ size 4033337776
joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16_new_instruction/ds_checkpoint_50000/global_step50000/bf16_zero_pp_rank_2_mp_rank_00_optim_states.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4e10e86185e718185399b9dbd6a036b336e02cab90c6acd482717d45fbe51b54
3
+ size 4033334576
joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16_new_instruction/ds_checkpoint_50000/global_step50000/bf16_zero_pp_rank_3_mp_rank_00_optim_states.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4511211dee6519f0c3150e5a8ea9e49a8d06ab26c52c4695bd89e67ca2678c95
3
+ size 4033337712
joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16_new_instruction/ds_checkpoint_50000/global_step50000/bf16_zero_pp_rank_4_mp_rank_00_optim_states.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b71e24b4619369a89a79eb6fb83a992dcf1d87dd111ececc6f4114232050f6bd
3
+ size 4033337904
joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16_new_instruction/ds_checkpoint_50000/global_step50000/bf16_zero_pp_rank_5_mp_rank_00_optim_states.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ff7e6813e02e60ae4b967a3a9fd846639287aa5c49109cc7c59f8e12430fc765
3
+ size 4033338800
joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16_new_instruction/ds_checkpoint_50000/global_step50000/bf16_zero_pp_rank_6_mp_rank_00_optim_states.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3b6dd680795df187258a98e7aed6c12f110ce1cd5cd0a8f22e5c2863bf5e44db
3
+ size 4033338160
joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16_new_instruction/ds_checkpoint_50000/global_step50000/bf16_zero_pp_rank_7_mp_rank_00_optim_states.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:df0d6d89874ce952498ade8b1a15954fc5e0f52d54c780c822b85e951d333eb7
3
+ size 4033337712
joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16_new_instruction/ds_checkpoint_50000/global_step50000/mp_rank_00_model_states.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2e15cb0a8696a86b003201a9d5776156e3c72651e5d14a25285276fb2427beb3
3
+ size 5378167768
joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16_new_instruction/ds_checkpoint_50000/latest ADDED
@@ -0,0 +1 @@
 
 
1
+ global_step50000
joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16_new_instruction/ds_checkpoint_50000/zero_to_fp32.py ADDED
@@ -0,0 +1,790 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python
2
+
3
+ # Copyright (c) Microsoft Corporation.
4
+ # SPDX-License-Identifier: Apache-2.0
5
+
6
+ # DeepSpeed Team
7
+
8
+ # This script extracts fp32 consolidated weights from a zero 1, 2 and 3 DeepSpeed checkpoints. It gets
9
+ # copied into the top level checkpoint dir, so the user can easily do the conversion at any point in
10
+ # the future. Once extracted, the weights don't require DeepSpeed and can be used in any
11
+ # application.
12
+ #
13
+ # example:
14
+ # python zero_to_fp32.py . output_dir/
15
+ # or
16
+ # python zero_to_fp32.py . output_dir/ --safe_serialization
17
+
18
+ import argparse
19
+ import torch
20
+ import glob
21
+ import math
22
+ import os
23
+ import re
24
+ import gc
25
+ import json
26
+ import numpy as np
27
+ from tqdm import tqdm
28
+ from collections import OrderedDict
29
+ from dataclasses import dataclass
30
+
31
+ # while this script doesn't use deepspeed to recover data, since the checkpoints are pickled with
32
+ # DeepSpeed data structures it has to be available in the current python environment.
33
+ from deepspeed.utils import logger
34
+ from deepspeed.checkpoint.constants import (DS_VERSION, OPTIMIZER_STATE_DICT, SINGLE_PARTITION_OF_FP32_GROUPS,
35
+ FP32_FLAT_GROUPS, ZERO_STAGE, PARTITION_COUNT, PARAM_SHAPES, BUFFER_NAMES,
36
+ FROZEN_PARAM_SHAPES, FROZEN_PARAM_FRAGMENTS, AUTOEP_LAYERS_KEY,
37
+ AUTOEP_LAYERS_KEY_LEGACY, AUTOEP_ZERO3_EXPERT_STATE_FORMAT_KEY,
38
+ AUTOEP_ZERO3_PARTITIONED_EXPERT_STATE_FORMAT)
39
+
40
+
41
+ @dataclass
42
+ class zero_model_state:
43
+ buffers: dict()
44
+ param_shapes: dict()
45
+ shared_params: list
46
+ ds_version: int
47
+ frozen_param_shapes: dict()
48
+ frozen_param_fragments: dict()
49
+
50
+
51
+ debug = 0
52
+
53
+ # load to cpu
54
+ device = torch.device('cpu')
55
+
56
+
57
+ def atoi(text):
58
+ return int(text) if text.isdigit() else text
59
+
60
+
61
+ def natural_keys(text):
62
+ '''
63
+ alist.sort(key=natural_keys) sorts in human order
64
+ http://nedbatchelder.com/blog/200712/human_sorting.html
65
+ (See Toothy's implementation in the comments)
66
+ '''
67
+ return [atoi(c) for c in re.split(r'(\d+)', text)]
68
+
69
+
70
+ def get_model_state_file(checkpoint_dir, zero_stage):
71
+ if not os.path.isdir(checkpoint_dir):
72
+ raise FileNotFoundError(f"Directory '{checkpoint_dir}' doesn't exist")
73
+
74
+ # there should be only one file
75
+ if zero_stage <= 2:
76
+ file = os.path.join(checkpoint_dir, "mp_rank_00_model_states.pt")
77
+ elif zero_stage == 3:
78
+ file = os.path.join(checkpoint_dir, "zero_pp_rank_0_mp_rank_00_model_states.pt")
79
+
80
+ if not os.path.exists(file):
81
+ raise FileNotFoundError(f"can't find model states file at '{file}'")
82
+
83
+ return file
84
+
85
+
86
+ def get_checkpoint_files(checkpoint_dir, glob_pattern):
87
+ # XXX: need to test that this simple glob rule works for multi-node setup too
88
+ ckpt_files = sorted(glob.glob(os.path.join(checkpoint_dir, glob_pattern)), key=natural_keys)
89
+
90
+ if len(ckpt_files) == 0:
91
+ raise FileNotFoundError(f"can't find {glob_pattern} files in directory '{checkpoint_dir}'")
92
+
93
+ return ckpt_files
94
+
95
+
96
+ def get_optim_files(checkpoint_dir):
97
+ return get_checkpoint_files(checkpoint_dir, "*_optim_states.pt")
98
+
99
+
100
+ def get_model_state_files(checkpoint_dir):
101
+ return get_checkpoint_files(checkpoint_dir, "*_model_states.pt")
102
+
103
+
104
+ def _has_autoep_zero3_partitioned_metadata(state_dict):
105
+ autoep_layers = state_dict.get(AUTOEP_LAYERS_KEY)
106
+ if autoep_layers is None:
107
+ autoep_layers = state_dict.get(AUTOEP_LAYERS_KEY_LEGACY)
108
+ if not isinstance(autoep_layers, list):
109
+ return False
110
+ return any(
111
+ isinstance(entry, dict)
112
+ and entry.get(AUTOEP_ZERO3_EXPERT_STATE_FORMAT_KEY) == AUTOEP_ZERO3_PARTITIONED_EXPERT_STATE_FORMAT
113
+ for entry in autoep_layers)
114
+
115
+
116
+ def _raise_if_autoep_zero3_partitioned_state(state_dict):
117
+ if _has_autoep_zero3_partitioned_metadata(state_dict):
118
+ raise NotImplementedError("zero_to_fp32 does not support AutoEP ZeRO-3 partition-native checkpoints. "
119
+ "AutoEP expert parameters are partitioned over expert replica groups, so "
120
+ "global data-parallel consolidation would produce incomplete expert tensors. "
121
+ "Use ds_to_universal.py for expert-aware conversion.")
122
+
123
+
124
+ def _raise_if_autoep_zero3_partitioned_checkpoint(model_files):
125
+ for file in model_files:
126
+ state_dict = torch.load(file, map_location=device, weights_only=False)
127
+ _raise_if_autoep_zero3_partitioned_state(state_dict)
128
+
129
+
130
+ def parse_model_states(files):
131
+ zero_model_states = []
132
+ for file in files:
133
+ state_dict = torch.load(file, map_location=device, weights_only=False)
134
+ _raise_if_autoep_zero3_partitioned_state(state_dict)
135
+
136
+ if BUFFER_NAMES not in state_dict:
137
+ raise ValueError(f"{file} is not a model state checkpoint")
138
+ buffer_names = state_dict[BUFFER_NAMES]
139
+ if debug:
140
+ print("Found buffers:", buffer_names)
141
+
142
+ # recover just the buffers while restoring them to fp32 if they were saved in fp16
143
+ buffers = {k: v.float() for k, v in state_dict["module"].items() if k in buffer_names}
144
+ param_shapes = state_dict[PARAM_SHAPES]
145
+
146
+ # collect parameters that are included in param_shapes
147
+ param_names = []
148
+ for s in param_shapes:
149
+ for name in s.keys():
150
+ param_names.append(name)
151
+
152
+ # update with frozen parameters
153
+ frozen_param_shapes = state_dict.get(FROZEN_PARAM_SHAPES, None)
154
+ if frozen_param_shapes is not None:
155
+ if debug:
156
+ print(f"Found frozen_param_shapes: {frozen_param_shapes}")
157
+ param_names += list(frozen_param_shapes.keys())
158
+
159
+ # handle shared params
160
+ shared_params = [[k, v] for k, v in state_dict["shared_params"].items()]
161
+
162
+ ds_version = state_dict.get(DS_VERSION, None)
163
+
164
+ frozen_param_fragments = state_dict.get(FROZEN_PARAM_FRAGMENTS, None)
165
+
166
+ z_model_state = zero_model_state(buffers=buffers,
167
+ param_shapes=param_shapes,
168
+ shared_params=shared_params,
169
+ ds_version=ds_version,
170
+ frozen_param_shapes=frozen_param_shapes,
171
+ frozen_param_fragments=frozen_param_fragments)
172
+ zero_model_states.append(z_model_state)
173
+
174
+ return zero_model_states
175
+
176
+
177
+ def parse_optim_states(files, ds_checkpoint_dir):
178
+ total_files = len(files)
179
+ state_dicts = []
180
+ for f in tqdm(files, desc='Loading checkpoint shards'):
181
+ state_dict = torch.load(f, map_location=device, mmap=True, weights_only=False)
182
+ # immediately discard the potentially huge 2 optimizer states as we only care for fp32 master weights
183
+ # and also handle the case where it was already removed by another helper script
184
+ state_dict["optimizer_state_dict"].pop("optimizer_state_dict", None)
185
+ state_dicts.append(state_dict)
186
+
187
+ if ZERO_STAGE not in state_dicts[0][OPTIMIZER_STATE_DICT]:
188
+ raise ValueError(f"{files[0]} is not a zero checkpoint")
189
+ zero_stage = state_dicts[0][OPTIMIZER_STATE_DICT][ZERO_STAGE]
190
+ world_size = state_dicts[0][OPTIMIZER_STATE_DICT][PARTITION_COUNT]
191
+
192
+ # For ZeRO-2 each param group can have different partition_count as data parallelism for expert
193
+ # parameters can be different from data parallelism for non-expert parameters. So we can just
194
+ # use the max of the partition_count to get the dp world_size.
195
+
196
+ if type(world_size) is list:
197
+ world_size = max(world_size)
198
+
199
+ if world_size != total_files:
200
+ raise ValueError(
201
+ f"Expected {world_size} of '*_optim_states.pt' under '{ds_checkpoint_dir}' but found {total_files} files. "
202
+ "Possibly due to an overwrite of an old checkpoint, or a checkpoint didn't get saved by one or more processes."
203
+ )
204
+
205
+ # the groups are named differently in each stage
206
+ if zero_stage <= 2:
207
+ fp32_groups_key = SINGLE_PARTITION_OF_FP32_GROUPS
208
+ elif zero_stage == 3:
209
+ fp32_groups_key = FP32_FLAT_GROUPS
210
+ else:
211
+ raise ValueError(f"unknown zero stage {zero_stage}")
212
+
213
+ fp32_flat_groups = [state_dicts[i][OPTIMIZER_STATE_DICT][fp32_groups_key] for i in range(len(state_dicts))]
214
+ return zero_stage, world_size, fp32_flat_groups
215
+
216
+
217
+ def _get_fp32_state_dict_from_zero_checkpoint(ds_checkpoint_dir, exclude_frozen_parameters):
218
+ """
219
+ Returns fp32 state_dict reconstructed from ds checkpoint
220
+
221
+ Args:
222
+ - ``ds_checkpoint_dir``: path to the deepspeed checkpoint folder (where the optimizer files are)
223
+
224
+ """
225
+ print(f"Processing zero checkpoint '{ds_checkpoint_dir}'")
226
+
227
+ # parse_model_states rejects AutoEP ZeRO-3 partition-native checkpoints
228
+ # before the expensive optimizer-shard load below.
229
+ model_files = get_model_state_files(ds_checkpoint_dir)
230
+ zero_model_states = parse_model_states(model_files)
231
+ print(f'Parsing checkpoint created by deepspeed=={zero_model_states[0].ds_version}')
232
+
233
+ optim_files = get_optim_files(ds_checkpoint_dir)
234
+ zero_stage, world_size, fp32_flat_groups = parse_optim_states(optim_files, ds_checkpoint_dir)
235
+ print(f"Detected checkpoint of type zero stage {zero_stage}, world_size: {world_size}")
236
+
237
+ if zero_stage <= 2:
238
+ return _get_fp32_state_dict_from_zero2_checkpoint(world_size, fp32_flat_groups, zero_model_states,
239
+ exclude_frozen_parameters)
240
+ elif zero_stage == 3:
241
+ return _get_fp32_state_dict_from_zero3_checkpoint(world_size, fp32_flat_groups, zero_model_states,
242
+ exclude_frozen_parameters)
243
+
244
+
245
+ def _zero2_merge_frozen_params(state_dict, zero_model_states):
246
+ if zero_model_states[0].frozen_param_shapes is None or len(zero_model_states[0].frozen_param_shapes) == 0:
247
+ return
248
+
249
+ frozen_param_shapes = zero_model_states[0].frozen_param_shapes
250
+ frozen_param_fragments = zero_model_states[0].frozen_param_fragments
251
+
252
+ if debug:
253
+ num_elem = sum(s.numel() for s in frozen_param_shapes.values())
254
+ print(f'rank 0: {FROZEN_PARAM_SHAPES}.numel = {num_elem}')
255
+
256
+ wanted_params = len(frozen_param_shapes)
257
+ wanted_numel = sum(s.numel() for s in frozen_param_shapes.values())
258
+ avail_numel = sum([p.numel() for p in frozen_param_fragments.values()])
259
+ print(f'Frozen params: Have {avail_numel} numels to process.')
260
+ print(f'Frozen params: Need {wanted_numel} numels in {wanted_params} params')
261
+
262
+ total_params = 0
263
+ total_numel = 0
264
+ for name, shape in frozen_param_shapes.items():
265
+ total_params += 1
266
+ unpartitioned_numel = shape.numel()
267
+ total_numel += unpartitioned_numel
268
+
269
+ state_dict[name] = frozen_param_fragments[name]
270
+
271
+ if debug:
272
+ print(f"{name} full shape: {shape} unpartitioned numel {unpartitioned_numel} ")
273
+
274
+ print(f"Reconstructed Frozen fp32 state dict with {total_params} params {total_numel} elements")
275
+
276
+
277
+ def _has_callable(obj, fn):
278
+ attr = getattr(obj, fn, None)
279
+ return callable(attr)
280
+
281
+
282
+ def _zero2_merge_trainable_params(state_dict, world_size, fp32_flat_groups, zero_model_states):
283
+ param_shapes = zero_model_states[0].param_shapes
284
+
285
+ # Reconstruction protocol:
286
+ #
287
+ # XXX: document this
288
+
289
+ if debug:
290
+ for i in range(world_size):
291
+ for j in range(len(fp32_flat_groups[0])):
292
+ print(f"{FP32_FLAT_GROUPS}[{i}][{j}].shape={fp32_flat_groups[i][j].shape}")
293
+
294
+ # XXX: memory usage doubles here (zero2)
295
+ num_param_groups = len(fp32_flat_groups[0])
296
+ merged_single_partition_of_fp32_groups = []
297
+ for i in range(num_param_groups):
298
+ merged_partitions = [sd[i] for sd in fp32_flat_groups]
299
+ full_single_fp32_vector = torch.cat(merged_partitions, 0)
300
+ merged_single_partition_of_fp32_groups.append(full_single_fp32_vector)
301
+ avail_numel = sum(
302
+ [full_single_fp32_vector.numel() for full_single_fp32_vector in merged_single_partition_of_fp32_groups])
303
+
304
+ if debug:
305
+ wanted_params = sum([len(shapes) for shapes in param_shapes])
306
+ wanted_numel = sum([sum(shape.numel() for shape in shapes.values()) for shapes in param_shapes])
307
+ # not asserting if there is a mismatch due to possible padding
308
+ print(f"Have {avail_numel} numels to process.")
309
+ print(f"Need {wanted_numel} numels in {wanted_params} params.")
310
+
311
+ # params
312
+ # XXX: for huge models that can't fit into the host's RAM we will have to recode this to support
313
+ # out-of-core computing solution
314
+ total_numel = 0
315
+ total_params = 0
316
+ for shapes, full_single_fp32_vector in zip(param_shapes, merged_single_partition_of_fp32_groups):
317
+ offset = 0
318
+ avail_numel = full_single_fp32_vector.numel()
319
+ for name, shape in shapes.items():
320
+
321
+ unpartitioned_numel = shape.numel() if _has_callable(shape, 'numel') else math.prod(shape)
322
+ total_numel += unpartitioned_numel
323
+ total_params += 1
324
+
325
+ if debug:
326
+ print(f"{name} full shape: {shape} unpartitioned numel {unpartitioned_numel} ")
327
+ state_dict[name] = full_single_fp32_vector.narrow(0, offset, unpartitioned_numel).view(shape)
328
+ offset += unpartitioned_numel
329
+
330
+ # Z2 started to align to 2*world_size to improve nccl performance. Therefore both offset and
331
+ # avail_numel can differ by anywhere between 0..2*world_size. Due to two unrelated complex
332
+ # paddings performed in the code it's almost impossible to predict the exact numbers w/o the
333
+ # live optimizer object, so we are checking that the numbers are within the right range
334
+ align_to = 2 * world_size
335
+
336
+ def zero2_align(x):
337
+ return align_to * math.ceil(x / align_to)
338
+
339
+ if debug:
340
+ print(f"original offset={offset}, avail_numel={avail_numel}")
341
+
342
+ offset = zero2_align(offset)
343
+ avail_numel = zero2_align(avail_numel)
344
+
345
+ if debug:
346
+ print(f"aligned offset={offset}, avail_numel={avail_numel}")
347
+
348
+ # Sanity check
349
+ if offset != avail_numel:
350
+ raise ValueError(f"consumed {offset} numels out of {avail_numel} - something is wrong")
351
+
352
+ print(f"Reconstructed fp32 state dict with {total_params} params {total_numel} elements")
353
+
354
+
355
+ def _get_fp32_state_dict_from_zero2_checkpoint(world_size, fp32_flat_groups, zero_model_states,
356
+ exclude_frozen_parameters):
357
+ state_dict = OrderedDict()
358
+
359
+ # buffers
360
+ buffers = zero_model_states[0].buffers
361
+ state_dict.update(buffers)
362
+ if debug:
363
+ print(f"added {len(buffers)} buffers")
364
+
365
+ if not exclude_frozen_parameters:
366
+ _zero2_merge_frozen_params(state_dict, zero_model_states)
367
+
368
+ _zero2_merge_trainable_params(state_dict, world_size, fp32_flat_groups, zero_model_states)
369
+
370
+ # recover shared parameters
371
+ for pair in zero_model_states[0].shared_params:
372
+ if pair[1] in state_dict:
373
+ state_dict[pair[0]] = state_dict[pair[1]]
374
+
375
+ return state_dict
376
+
377
+
378
+ def zero3_partitioned_param_info(unpartitioned_numel, world_size):
379
+ remainder = unpartitioned_numel % world_size
380
+ padding_numel = (world_size - remainder) if remainder else 0
381
+ partitioned_numel = math.ceil(unpartitioned_numel / world_size)
382
+ return partitioned_numel, padding_numel
383
+
384
+
385
+ def _zero3_merge_frozen_params(state_dict, world_size, zero_model_states):
386
+ if zero_model_states[0].frozen_param_shapes is None or len(zero_model_states[0].frozen_param_shapes) == 0:
387
+ return
388
+
389
+ if debug:
390
+ for i in range(world_size):
391
+ num_elem = sum(s.numel() for s in zero_model_states[i].frozen_param_fragments.values())
392
+ print(f'rank {i}: {FROZEN_PARAM_SHAPES}.numel = {num_elem}')
393
+
394
+ frozen_param_shapes = zero_model_states[0].frozen_param_shapes
395
+ wanted_params = len(frozen_param_shapes)
396
+ wanted_numel = sum(s.numel() for s in frozen_param_shapes.values())
397
+ avail_numel = sum([p.numel() for p in zero_model_states[0].frozen_param_fragments.values()]) * world_size
398
+ print(f'Frozen params: Have {avail_numel} numels to process.')
399
+ print(f'Frozen params: Need {wanted_numel} numels in {wanted_params} params')
400
+
401
+ total_params = 0
402
+ total_numel = 0
403
+ for name, shape in zero_model_states[0].frozen_param_shapes.items():
404
+ total_params += 1
405
+ unpartitioned_numel = shape.numel()
406
+ total_numel += unpartitioned_numel
407
+
408
+ param_frags = tuple(model_state.frozen_param_fragments[name] for model_state in zero_model_states)
409
+ state_dict[name] = torch.cat(param_frags, 0).narrow(0, 0, unpartitioned_numel).view(shape)
410
+
411
+ partitioned_numel, partitioned_padding_numel = zero3_partitioned_param_info(unpartitioned_numel, world_size)
412
+
413
+ if debug:
414
+ print(
415
+ f"Frozen params: {total_params} {name} full shape: {shape} partition0 numel={partitioned_numel} partitioned_padding_numel={partitioned_padding_numel}"
416
+ )
417
+
418
+ print(f"Reconstructed Frozen fp32 state dict with {total_params} params {total_numel} elements")
419
+
420
+
421
+ class GatheredTensor:
422
+ """
423
+ A pseudo tensor that collects partitioned weights.
424
+ It is more memory efficient when there are multiple groups.
425
+ """
426
+
427
+ def __init__(self, flat_groups, flat_groups_offset, offset, partitioned_numel, shape):
428
+ self.flat_groups = flat_groups
429
+ self.flat_groups_offset = flat_groups_offset
430
+ self.offset = offset
431
+ self.partitioned_numel = partitioned_numel
432
+ self.shape = shape
433
+ self.dtype = self.flat_groups[0][0].dtype
434
+
435
+ def contiguous(self):
436
+ """
437
+ Merge partitioned weights from flat_groups into a single tensor.
438
+ """
439
+ end_idx = self.offset + self.partitioned_numel
440
+ world_size = len(self.flat_groups)
441
+ pad_flat_param_chunks = []
442
+
443
+ for rank_i in range(world_size):
444
+ # for each rank, we need to collect weights from related group/groups
445
+ flat_groups_at_rank_i = self.flat_groups[rank_i]
446
+ start_group_id = None
447
+ end_group_id = None
448
+ for group_id in range(len(self.flat_groups_offset)):
449
+ if self.flat_groups_offset[group_id] <= self.offset < self.flat_groups_offset[group_id + 1]:
450
+ start_group_id = group_id
451
+ if self.flat_groups_offset[group_id] < end_idx <= self.flat_groups_offset[group_id + 1]:
452
+ end_group_id = group_id
453
+ break
454
+ # collect weights from related group/groups
455
+ for group_id in range(start_group_id, end_group_id + 1):
456
+ flat_tensor = flat_groups_at_rank_i[group_id]
457
+ start_offset = self.offset - self.flat_groups_offset[group_id]
458
+ end_offset = min(end_idx, self.flat_groups_offset[group_id + 1]) - self.flat_groups_offset[group_id]
459
+ pad_flat_param_chunks.append(flat_tensor[start_offset:end_offset])
460
+
461
+ # collect weights from all ranks
462
+ pad_flat_param = torch.cat(pad_flat_param_chunks, dim=0)
463
+ param = pad_flat_param[:self.shape.numel()].view(self.shape).contiguous()
464
+ return param
465
+
466
+
467
+ def _zero3_merge_trainable_params(state_dict, world_size, fp32_flat_groups, zero_model_states):
468
+ param_shapes = zero_model_states[0].param_shapes
469
+ avail_numel = sum([flat_group.numel() for flat_group in fp32_flat_groups[0]]) * world_size
470
+
471
+ # Reconstruction protocol: For zero3 we need to zip the partitions together at boundary of each
472
+ # param, re-consolidating each param, while dealing with padding if any
473
+
474
+ # merge list of dicts, preserving order
475
+ param_shapes = {k: v for d in param_shapes for k, v in d.items()}
476
+
477
+ if debug:
478
+ for i in range(world_size):
479
+ print(f"{FP32_FLAT_GROUPS}[{i}].shape={fp32_flat_groups[i].shape}")
480
+
481
+ wanted_params = len(param_shapes)
482
+ wanted_numel = sum(shape.numel() for shape in param_shapes.values())
483
+ # not asserting if there is a mismatch due to possible padding
484
+ avail_numel = fp32_flat_groups[0].numel() * world_size
485
+ print(f"Trainable params: Have {avail_numel} numels to process.")
486
+ print(f"Trainable params: Need {wanted_numel} numels in {wanted_params} params.")
487
+
488
+ # params
489
+ # XXX: for huge models that can't fit into the host's RAM we will have to recode this to support
490
+ # out-of-core computing solution
491
+ offset = 0
492
+ total_numel = 0
493
+ total_params = 0
494
+ flat_groups_offset = [0] + list(np.cumsum([flat_tensor.numel() for flat_tensor in fp32_flat_groups[0]]))
495
+ for name, shape in tqdm(param_shapes.items(), desc='Gathering sharded weights'):
496
+ unpartitioned_numel = shape.numel()
497
+ total_numel += unpartitioned_numel
498
+ total_params += 1
499
+ partitioned_numel, partitioned_padding_numel = zero3_partitioned_param_info(unpartitioned_numel, world_size)
500
+
501
+ if debug:
502
+ print(
503
+ f"Trainable params: {total_params} {name} full shape: {shape} partition0 numel={partitioned_numel} partitioned_padding_numel={partitioned_padding_numel}"
504
+ )
505
+
506
+ # memory efficient tensor
507
+ tensor = GatheredTensor(fp32_flat_groups, flat_groups_offset, offset, partitioned_numel, shape)
508
+ state_dict[name] = tensor
509
+ offset += partitioned_numel
510
+
511
+ offset *= world_size
512
+
513
+ # Sanity check
514
+ if offset != avail_numel:
515
+ raise ValueError(f"consumed {offset} numels out of {avail_numel} - something is wrong")
516
+
517
+ print(f"Reconstructed Trainable fp32 state dict with {total_params} params {total_numel} elements")
518
+
519
+
520
+ def _get_fp32_state_dict_from_zero3_checkpoint(world_size, fp32_flat_groups, zero_model_states,
521
+ exclude_frozen_parameters):
522
+ state_dict = OrderedDict()
523
+
524
+ # buffers
525
+ buffers = zero_model_states[0].buffers
526
+ state_dict.update(buffers)
527
+ if debug:
528
+ print(f"added {len(buffers)} buffers")
529
+
530
+ if not exclude_frozen_parameters:
531
+ _zero3_merge_frozen_params(state_dict, world_size, zero_model_states)
532
+
533
+ _zero3_merge_trainable_params(state_dict, world_size, fp32_flat_groups, zero_model_states)
534
+
535
+ # recover shared parameters
536
+ for pair in zero_model_states[0].shared_params:
537
+ if pair[1] in state_dict:
538
+ state_dict[pair[0]] = state_dict[pair[1]]
539
+
540
+ return state_dict
541
+
542
+
543
+ def to_torch_tensor(state_dict, return_empty_tensor=False):
544
+ """
545
+ Convert state_dict of GatheredTensor to torch tensor
546
+ """
547
+ torch_state_dict = {}
548
+ converted_tensors = {}
549
+ for name, tensor in state_dict.items():
550
+ tensor_id = id(tensor)
551
+ if tensor_id in converted_tensors: # shared tensors
552
+ shared_tensor = torch_state_dict[converted_tensors[tensor_id]]
553
+ torch_state_dict[name] = shared_tensor
554
+ else:
555
+ converted_tensors[tensor_id] = name
556
+ if return_empty_tensor:
557
+ torch_state_dict[name] = torch.empty(tensor.shape, dtype=tensor.dtype)
558
+ else:
559
+ torch_state_dict[name] = tensor.contiguous()
560
+ return torch_state_dict
561
+
562
+
563
+ def get_fp32_state_dict_from_zero_checkpoint(checkpoint_dir,
564
+ tag=None,
565
+ exclude_frozen_parameters=False,
566
+ lazy_mode=False):
567
+ """
568
+ Convert ZeRO 2 or 3 checkpoint into a single fp32 consolidated state_dict that can be loaded with
569
+ ``load_state_dict()`` and used for training without DeepSpeed or shared with others, for example
570
+ via a model hub.
571
+
572
+ Args:
573
+ - ``checkpoint_dir``: path to the desired checkpoint folder
574
+ - ``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``
575
+ - ``exclude_frozen_parameters``: exclude frozen parameters
576
+ - ``lazy_mode``: get state_dict in lazy mode. It returns a dict of pesduo tensor instead of torch tensor, which is more memory efficient.
577
+ Convert the pesduo tensor to torch tensor by ``.contiguous()``
578
+
579
+ Returns:
580
+ - pytorch ``state_dict``
581
+
582
+ A typical usage might be ::
583
+
584
+ from deepspeed.utils.zero_to_fp32 import get_fp32_state_dict_from_zero_checkpoint
585
+ # do the training and checkpoint saving
586
+ state_dict = get_fp32_state_dict_from_zero_checkpoint(checkpoint_dir) # already on cpu
587
+ model = model.cpu() # move to cpu
588
+ model.load_state_dict(state_dict)
589
+ # submit to model hub or save the model to share with others
590
+
591
+ In this example the ``model`` will no longer be usable in the deepspeed context of the same
592
+ application. i.e. you will need to re-initialize the deepspeed engine, since
593
+ ``model.load_state_dict(state_dict)`` will remove all the deepspeed magic from it.
594
+
595
+ If you want it all done for you, use ``load_state_dict_from_zero_checkpoint`` instead.
596
+
597
+ Note: the above usage may not work if your application doesn't have sufficient free CPU memory.
598
+ You may need to use the offline approach using the ``zero_to_fp32.py`` script that is saved with
599
+ the checkpoint. Or you can load state_dict in lazy mode ::
600
+
601
+ from deepspeed.utils.zero_to_fp32 import get_fp32_state_dict_from_zero_checkpoint
602
+ state_dict = get_fp32_state_dict_from_zero_checkpoint(checkpoint_dir, lazy_mode=True) # not on cpu
603
+ for name, lazy_tensor in state_dict.item():
604
+ tensor = lazy_tensor.contiguous() # to cpu
605
+ print(name, tensor)
606
+ # del tensor to release memory if it no longer in use
607
+ """
608
+ if tag is None:
609
+ latest_path = os.path.join(checkpoint_dir, 'latest')
610
+ if os.path.isfile(latest_path):
611
+ with open(latest_path, 'r') as fd:
612
+ tag = fd.read().strip()
613
+ else:
614
+ raise ValueError(f"Unable to find 'latest' file at {latest_path}")
615
+
616
+ ds_checkpoint_dir = os.path.join(checkpoint_dir, tag)
617
+
618
+ if not os.path.isdir(ds_checkpoint_dir):
619
+ raise FileNotFoundError(f"Directory '{ds_checkpoint_dir}' doesn't exist")
620
+
621
+ state_dict = _get_fp32_state_dict_from_zero_checkpoint(ds_checkpoint_dir, exclude_frozen_parameters)
622
+ if lazy_mode:
623
+ return state_dict
624
+ else:
625
+ return to_torch_tensor(state_dict)
626
+
627
+
628
+ def convert_zero_checkpoint_to_fp32_state_dict(checkpoint_dir,
629
+ output_dir,
630
+ max_shard_size="5GB",
631
+ safe_serialization=False,
632
+ tag=None,
633
+ exclude_frozen_parameters=False):
634
+ """
635
+ Convert ZeRO 2 or 3 checkpoint into a single fp32 consolidated ``state_dict`` file that can be
636
+ loaded with ``torch.load(file)`` + ``load_state_dict()`` and used for training without DeepSpeed.
637
+
638
+ Args:
639
+ - ``checkpoint_dir``: path to the desired checkpoint folder. (one that contains the tag-folder, like ``global_step14``)
640
+ - ``output_dir``: directory to the pytorch fp32 state_dict output files
641
+ - ``max_shard_size``: the maximum size for a checkpoint before being sharded, default value is 5GB
642
+ - ``safe_serialization``: whether to save the model using `safetensors` or the traditional PyTorch way (that uses `pickle`).
643
+ - ``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``
644
+ - ``exclude_frozen_parameters``: exclude frozen parameters
645
+ """
646
+
647
+ # Dependency pre-check
648
+ if safe_serialization:
649
+ try:
650
+ from safetensors.torch import save_file
651
+ except ImportError:
652
+ print('If you want to use `safe_serialization`, please `pip install safetensors`')
653
+ raise
654
+ if max_shard_size is not None:
655
+ try:
656
+ from huggingface_hub import split_torch_state_dict_into_shards
657
+ except ImportError:
658
+ print('If you want to use `max_shard_size`, please `pip install huggingface_hub`')
659
+ raise
660
+
661
+ # Convert zero checkpoint to state_dict
662
+ state_dict = get_fp32_state_dict_from_zero_checkpoint(checkpoint_dir,
663
+ tag,
664
+ exclude_frozen_parameters,
665
+ lazy_mode=True)
666
+
667
+ # Shard the model if it is too big.
668
+ weights_name = "model.safetensors" if safe_serialization else "pytorch_model.bin"
669
+ if max_shard_size is not None:
670
+ filename_pattern = weights_name.replace(".bin", "{suffix}.bin").replace(".safetensors", "{suffix}.safetensors")
671
+ # an memory-efficient approach for sharding
672
+ empty_state_dict = to_torch_tensor(state_dict, return_empty_tensor=True)
673
+ state_dict_split = split_torch_state_dict_into_shards(empty_state_dict,
674
+ filename_pattern=filename_pattern,
675
+ max_shard_size=max_shard_size)
676
+ else:
677
+ from collections import namedtuple
678
+ StateDictSplit = namedtuple("StateDictSplit", ["is_sharded", "filename_to_tensors"])
679
+ state_dict_split = StateDictSplit(is_sharded=False,
680
+ filename_to_tensors={weights_name: list(state_dict.keys())})
681
+
682
+ # Save the model by shard
683
+ os.makedirs(output_dir, exist_ok=True)
684
+ filename_to_tensors = state_dict_split.filename_to_tensors.items()
685
+ for shard_file, tensors in tqdm(filename_to_tensors, desc="Saving checkpoint shards"):
686
+ shard_state_dict = {tensor_name: state_dict[tensor_name] for tensor_name in tensors}
687
+ shard_state_dict = to_torch_tensor(shard_state_dict)
688
+ output_path = os.path.join(output_dir, shard_file)
689
+ if safe_serialization:
690
+ save_file(shard_state_dict, output_path, metadata={"format": "pt"})
691
+ else:
692
+ torch.save(shard_state_dict, output_path)
693
+ # release the memory of current shard
694
+ for tensor_name in list(shard_state_dict.keys()):
695
+ del state_dict[tensor_name]
696
+ del shard_state_dict[tensor_name]
697
+ del shard_state_dict
698
+ gc.collect()
699
+
700
+ # Save index if sharded
701
+ if state_dict_split.is_sharded:
702
+ index = {
703
+ "metadata": state_dict_split.metadata,
704
+ "weight_map": state_dict_split.tensor_to_filename,
705
+ }
706
+ save_index_file = "model.safetensors.index.json" if safe_serialization else "pytorch_model.bin.index.json"
707
+ save_index_file = os.path.join(output_dir, save_index_file)
708
+ with open(save_index_file, "w", encoding="utf-8") as f:
709
+ content = json.dumps(index, indent=2, sort_keys=True) + "\n"
710
+ f.write(content)
711
+
712
+
713
+ def load_state_dict_from_zero_checkpoint(model, checkpoint_dir, tag=None):
714
+ """
715
+ 1. Put the provided model to cpu
716
+ 2. Convert ZeRO 2 or 3 checkpoint into a single fp32 consolidated ``state_dict``
717
+ 3. Load it into the provided model
718
+
719
+ Args:
720
+ - ``model``: the model object to update
721
+ - ``checkpoint_dir``: path to the desired checkpoint folder. (one that contains the tag-folder, like ``global_step14``)
722
+ - ``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``
723
+
724
+ Returns:
725
+ - ``model`: modified model
726
+
727
+ Make sure you have plenty of CPU memory available before you call this function. If you don't
728
+ have enough use the ``zero_to_fp32.py`` utility to do the conversion. You will find it
729
+ conveniently placed for you in the checkpoint folder.
730
+
731
+ A typical usage might be ::
732
+
733
+ from deepspeed.utils.zero_to_fp32 import load_state_dict_from_zero_checkpoint
734
+ model = load_state_dict_from_zero_checkpoint(trainer.model, checkpoint_dir)
735
+ # submit to model hub or save the model to share with others
736
+
737
+ Note, that once this was run, the ``model`` will no longer be usable in the deepspeed context
738
+ of the same application. i.e. you will need to re-initialize the deepspeed engine, since
739
+ ``model.load_state_dict(state_dict)`` will remove all the deepspeed magic from it.
740
+
741
+ """
742
+ logger.info("Extracting fp32 weights")
743
+ state_dict = get_fp32_state_dict_from_zero_checkpoint(checkpoint_dir, tag)
744
+
745
+ logger.info("Overwriting model with fp32 weights")
746
+ model = model.cpu()
747
+ model.load_state_dict(state_dict, strict=False)
748
+
749
+ return model
750
+
751
+
752
+ if __name__ == "__main__":
753
+ parser = argparse.ArgumentParser()
754
+ parser.add_argument("checkpoint_dir",
755
+ type=str,
756
+ help="path to the desired checkpoint folder, e.g., path/checkpoint-12")
757
+ parser.add_argument("output_dir",
758
+ type=str,
759
+ help="directory to the pytorch fp32 state_dict output files"
760
+ "(e.g. path/checkpoint-12-output/)")
761
+ parser.add_argument(
762
+ "--max_shard_size",
763
+ type=str,
764
+ default="5GB",
765
+ help="The maximum size for a checkpoint before being sharded. Checkpoints shard will then be each of size"
766
+ "lower than this size. If expressed as a string, needs to be digits followed by a unit (like `5MB`"
767
+ "We default it to 5GB in order for models to be able to run easily on free-tier google colab instances"
768
+ "without CPU OOM issues.")
769
+ parser.add_argument(
770
+ "--safe_serialization",
771
+ default=False,
772
+ action='store_true',
773
+ help="Whether to save the model using `safetensors` or the traditional PyTorch way (that uses `pickle`).")
774
+ parser.add_argument("-t",
775
+ "--tag",
776
+ type=str,
777
+ default=None,
778
+ help="checkpoint tag used as a unique identifier for checkpoint. e.g., global_step1")
779
+ parser.add_argument("--exclude_frozen_parameters", action='store_true', help="exclude frozen parameters")
780
+ parser.add_argument("-d", "--debug", action='store_true', help="enable debug")
781
+ args = parser.parse_args()
782
+
783
+ debug = args.debug
784
+
785
+ convert_zero_checkpoint_to_fp32_state_dict(args.checkpoint_dir,
786
+ args.output_dir,
787
+ max_shard_size=args.max_shard_size,
788
+ safe_serialization=args.safe_serialization,
789
+ tag=args.tag,
790
+ exclude_frozen_parameters=args.exclude_frozen_parameters)
joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16_new_instruction/ds_checkpoint_final/global_step50000/bf16_zero_pp_rank_0_mp_rank_00_optim_states.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:fb57463db6681ab559a9e0b030bd157c04abf57e5bfacb192e148b78acf23199
3
+ size 4033352816
joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16_new_instruction/ds_checkpoint_final/global_step50000/bf16_zero_pp_rank_1_mp_rank_00_optim_states.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:89c83da0743e8ce8f22fbed0244ad21b3dbcadda98dd3cc00f39606dcec73b89
3
+ size 4033337776
joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16_new_instruction/ds_checkpoint_final/global_step50000/bf16_zero_pp_rank_2_mp_rank_00_optim_states.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4e10e86185e718185399b9dbd6a036b336e02cab90c6acd482717d45fbe51b54
3
+ size 4033334576
joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16_new_instruction/ds_checkpoint_final/global_step50000/bf16_zero_pp_rank_3_mp_rank_00_optim_states.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4511211dee6519f0c3150e5a8ea9e49a8d06ab26c52c4695bd89e67ca2678c95
3
+ size 4033337712
joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16_new_instruction/ds_checkpoint_final/global_step50000/bf16_zero_pp_rank_4_mp_rank_00_optim_states.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b71e24b4619369a89a79eb6fb83a992dcf1d87dd111ececc6f4114232050f6bd
3
+ size 4033337904
joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16_new_instruction/ds_checkpoint_final/global_step50000/bf16_zero_pp_rank_5_mp_rank_00_optim_states.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ff7e6813e02e60ae4b967a3a9fd846639287aa5c49109cc7c59f8e12430fc765
3
+ size 4033338800
joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16_new_instruction/ds_checkpoint_final/global_step50000/bf16_zero_pp_rank_6_mp_rank_00_optim_states.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3b6dd680795df187258a98e7aed6c12f110ce1cd5cd0a8f22e5c2863bf5e44db
3
+ size 4033338160
joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16_new_instruction/ds_checkpoint_final/global_step50000/bf16_zero_pp_rank_7_mp_rank_00_optim_states.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:df0d6d89874ce952498ade8b1a15954fc5e0f52d54c780c822b85e951d333eb7
3
+ size 4033337712
joint_50ksteps_bs128_qwen3vl2b_end_padding_mask_chunk16_new_instruction/ds_checkpoint_final/global_step50000/mp_rank_00_model_states.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2e15cb0a8696a86b003201a9d5776156e3c72651e5d14a25285276fb2427beb3
3
+ size 5378167768