Skip to content

Commit 32d9797

Browse files
tjruwasejeffra
andauthored
Fix OOM and type mismatch (deepspeedai#1884)
Co-authored-by: Jeff Rasley <[email protected]>
1 parent 4575b2b commit 32d9797

4 files changed

Lines changed: 164 additions & 120 deletions

File tree

deepspeed/runtime/swap_tensor/partitioned_param_swapper.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"""
77

88
import os
9+
import shutil
910
from enum import Enum
1011
import torch
1112
import torch.distributed as dist
@@ -34,10 +35,11 @@ class PartitionedParamStatus(Enum):
3435

3536

3637
class AsyncPartitionedParameterSwapper(object):
37-
def __init__(self, ds_config):
38+
def __init__(self, ds_config, model_dtype):
3839

3940
aio_op = AsyncIOBuilder().load(verbose=False)
4041
self.aio_handle = aio_op.aio_handle
42+
self.dtype = model_dtype
4143

4244
#set swap buffers, create aio handles
4345
self._configure_aio(ds_config)
@@ -83,13 +85,15 @@ def available_swap_in_buffers(self):
8385

8486
def _configure_aio(self, ds_config):
8587
self.swap_config = ds_config.zero_config.offload_param
88+
torch_dtype_string = str(self.dtype).split(".")[1]
8689
self.swap_folder = os.path.join(self.swap_config[OFFLOAD_PARAM_NVME_PATH],
8790
'zero_stage_3',
88-
'fp16params',
91+
f'{torch_dtype_string}params',
8992
f'rank{dist.get_rank()}')
93+
shutil.rmtree(self.swap_folder, ignore_errors=True)
9094
os.makedirs(self.swap_folder, exist_ok=True)
9195

92-
self.swap_element_size = torch.tensor([], dtype=torch.half).element_size()
96+
self.swap_element_size = torch.tensor([], dtype=self.dtype).element_size()
9397

9498
self.aio_config = ds_config.aio_config
9599

@@ -107,7 +111,7 @@ def _configure_aio(self, ds_config):
107111
self.reserved_buffer_ids = []
108112
self.buffers = torch.empty(int(self.aligned_elements_per_buffer *
109113
self.param_buffer_count),
110-
dtype=torch.half,
114+
dtype=self.dtype,
111115
pin_memory=True,
112116
requires_grad=False)
113117

@@ -293,8 +297,9 @@ def swap_in(self, params, async_op=True, swap_in_buffers=None):
293297

294298
if swap_in_buffers is None:
295299
if len(self.available_buffer_ids) < len(swap_in_paths):
300+
ids = [p.ds_id for p in params]
296301
print_rank_0(
297-
f'Not enough swap in buffers {len(self.available_buffer_ids)} for params {len(swap_in_paths)}',
302+
f'Not enough swap in buffers {len(self.available_buffer_ids)} for {len(swap_in_paths)} params, ids = {ids}',
298303
force=True)
299304
print_rank_0(
300305
f'Num inflight: params {len(self.inflight_params)}, buffers {len(self.inflight_swap_in_buffers)}, numel = {self.inflight_numel}',
@@ -392,7 +397,7 @@ def reserve_partitioned_swap_space(self, partition_num_elems):
392397
[self._io_aligned_numel(numel) for numel in partition_num_elems])
393398
self.partitioned_swap_buffer = torch.zeros(aligned_numel,
394399
device='cpu',
395-
dtype=torch.half).pin_memory()
400+
dtype=self.dtype).pin_memory()
396401
self.partitioned_swap_pool = SwapBufferPool([self.partitioned_swap_buffer])
397402

398403
def swap_out_partitioned_params(self, dst_fp16_params, src_fp32_params):

deepspeed/runtime/zero/partition_parameters.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -699,7 +699,7 @@ def get_model():
699699

700700
# Enable fp16 param swapping to NVMe
701701
if self.remote_device == OFFLOAD_NVME_DEVICE:
702-
self.param_swapper = AsyncPartitionedParameterSwapper(_ds_config)
702+
self.param_swapper = AsyncPartitionedParameterSwapper(_ds_config, self.dtype)
703703
else:
704704
self.param_swapper = None
705705

@@ -877,7 +877,6 @@ def all_gather_coalesced(params: Iterable[Parameter],
877877
instrument_w_nvtx(torch.cat)(
878878
[p.ds_tensor.to(torch.cuda.current_device()) for p in params],
879879
out=partitions[self.rank])
880-
881880
handle = torch_allgather_fn(partitions[self.rank],
882881
flat_tensor,
883882
self.ds_process_group)

deepspeed/runtime/zero/partitioned_param_coordinator.py

Lines changed: 86 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,15 @@ def iter_params(module: Module, recurse=False) -> Iterable[Parameter]:
3333
return map(lambda pair: pair[1], get_all_parameters(module, recurse))
3434

3535

36+
class TraceMode(Enum):
37+
# Record trace of the network during a single forward+backward (for training) or forward (for inference)
38+
RECORD = 1
39+
# Use recorded network trace to optimize current forward+backward or forward
40+
COMPLETE = 2
41+
# Recorded trace does not match current forward+backward or forward pass.
42+
INVALID = 3
43+
44+
3645
class PartitionedParameterCoordinator:
3746
"""Handles partitioning and gathering of parameters."""
3847
class __InflightParamRegistry(UserDict):
@@ -65,9 +74,8 @@ def __init__(
6574
self.__inflight_param_registry = __class__.__InflightParamRegistry()
6675
# keeps track of the number of submodules invoked so far.
6776
self.__step_id: int = 0
68-
# whether or not we have completed a trace of the entire network. This should
69-
# always be true after the first forward pass + backward pass.
70-
self.trace_complete: bool = False
77+
# network tracing mode
78+
self.__trace_mode: TraceMode = TraceMode.RECORD
7179
# sequence of submodules/parameters in forward pass + backward pass
7280
self.__submodule_order: Iterable[Module] = []
7381
self.__param_order: Iterable[__class__.__ParamInTrace] = []
@@ -110,13 +118,46 @@ def __init__(
110118
Bookkeeping operations used to track where we are in the forward/backward pass
111119
"""
112120

113-
def record_trace(self, sub_module: Module) -> None:
121+
def _clear_trace_structures(self) -> None:
122+
self.__submodule_order = []
123+
self.__param_order = []
124+
self.__most_recent_step_id_param_fetched_for = collections.defaultdict(
125+
lambda: int(-1e10))
126+
self.__param_queue = None
127+
128+
def is_complete_trace(self) -> bool:
129+
return self.__trace_mode == TraceMode.COMPLETE
130+
131+
def is_invalid_trace(self) -> bool:
132+
return self.__trace_mode == TraceMode.INVALID
133+
134+
def is_record_trace(self) -> bool:
135+
return self.__trace_mode == TraceMode.RECORD
136+
137+
def _invalidate_trace(self) -> None:
138+
if self.is_invalid_trace():
139+
raise RuntimeError("attempted to invalidate already invalid trace")
140+
self.__trace_mode = TraceMode.INVALID
141+
self._clear_trace_structures()
142+
143+
def trace_prologue(self, sub_module: Module) -> None:
144+
if self.is_complete_trace():
145+
# sub_module must match expectation else invalidate trace cache
146+
if sub_module != self.__submodule_order[self.__step_id]:
147+
self._invalidate_trace()
148+
149+
def record_module(self, sub_module: Module) -> None:
114150
"""adds sub module to trace"""
115-
if self.trace_complete:
151+
if not self.is_record_trace():
116152
raise RuntimeError(
117-
"attempted to record trace when trace was already complete")
118-
153+
f"attempted to record trace when status = {self.__trace_mode}")
119154
self.__submodule_order.append(sub_module)
155+
156+
def record_parameters(self, sub_module: Module) -> None:
157+
"""adds sub module to trace"""
158+
if not self.is_record_trace():
159+
raise RuntimeError(
160+
f"attempted to record trace when status = {self.__trace_mode}")
120161
for param in sorted(set(iter_params(sub_module)), key=lambda p: p.ds_id):
121162
self.__param_order.append(
122163
__class__.__ParamInTrace(param=param,
@@ -129,19 +170,25 @@ def reset_step(self) -> None:
129170
f"still have inflight params "
130171
f"{[p.ds_summary for p in self.__inflight_param_registry.keys()]}")
131172

132-
if not self.trace_complete:
133-
# make sure that recorded parameter and submodule orders are
173+
if not self.is_complete_trace(): # not self.trace_complete:
174+
# Make sure that recorded parameter and submodule orders are
134175
# identical across ranks
135176
assert_ints_same_as_other_ranks([m.id for m in self.__submodule_order])
136177
assert_ints_same_as_other_ranks([p.param.ds_id for p in self.__param_order])
137178
assert_ints_same_as_other_ranks(
138179
[p.step_id_last_used_at for p in self.__param_order])
139180

140-
self.__submodule_order = tuple(self.__submodule_order) # freeze
141-
self.__param_order = tuple(self.__param_order) # freeze
142-
self.trace_complete = True
143-
print_rank_0(f"completed trace: {[m.id for m in self.__submodule_order]}",
144-
force=False)
181+
if self.is_record_trace():
182+
# Successfully recorded a trace
183+
self.__submodule_order = tuple(self.__submodule_order) # freeze
184+
self.__param_order = tuple(self.__param_order) # freeze
185+
self.__trace_mode = TraceMode.COMPLETE # self.trace_complete = True
186+
print_rank_0(
187+
f"completed trace: {[m.id for m in self.__submodule_order]}",
188+
force=False)
189+
else:
190+
# Enable trace recording for next forward/backward pass
191+
self.__trace_mode = TraceMode.RECORD
145192

146193
self.__param_queue = collections.deque(self.__param_order) # reset fetch queue
147194
self.__most_recent_step_id_param_fetched_for = collections.defaultdict(
@@ -199,9 +246,8 @@ def fetch_sub_module(self, current_submodule: Module) -> None:
199246
torch.cuda.current_stream().wait_stream(self.__allgather_stream)
200247

201248
# kick off parameter prefetches for upcoming modules
202-
# don't prefetch if we dont have a completed model trace, or if we aren't
203-
# training (throws off the tracing and don't want to prefetch modules for bwd)
204-
if self.trace_complete and current_submodule.training:
249+
# don't prefetch if we dont have a completed model trace
250+
if self.is_complete_trace():
205251
# go through the parameters we need for the current module and pop them
206252
# off the fetch queue so that they aren't prefetched later.
207253
# if params have already been popped off the fetch queue by earlier
@@ -228,24 +274,26 @@ def fetch_sub_module(self, current_submodule: Module) -> None:
228274
)
229275

230276
# kick off all gather for params in the next few submodules (prefetch)
231-
max_params_to_prefetch = min(
232-
self.__max_n_available_params - self.__n_available_params,
233-
self.__prefetch_bucket_sz)
234-
params_to_prefetch = set()
235-
numel_prefetching = 0
236-
while self.__param_queue and numel_prefetching < max_params_to_prefetch:
237-
param_in_trace: __class__.__ParamInTrace = self.__param_queue.popleft()
238-
self.__most_recent_step_id_param_fetched_for[
239-
param_in_trace.param] = param_in_trace.step_id_last_used_at
240-
if param_in_trace.param not in params_to_prefetch:
241-
params_to_prefetch.add(param_in_trace.param)
242-
numel_prefetching += param_in_trace.param.ds_numel
243-
for param in params_to_prefetch:
244-
debug_rank0(f"-prefetch: {param.ds_summary()}")
245-
self.__all_gather_params(params_to_prefetch)
246-
247-
if self.__prefetch_nvme:
248-
self.__prefetch_nvme_param_partitions()
277+
if self.__prefetch_bucket_sz > 0:
278+
max_params_to_prefetch = min(
279+
self.__max_n_available_params - self.__n_available_params,
280+
self.__prefetch_bucket_sz)
281+
params_to_prefetch = set()
282+
numel_prefetching = 0
283+
while self.__param_queue and numel_prefetching < max_params_to_prefetch:
284+
param_in_trace: __class__.__ParamInTrace = self.__param_queue.popleft(
285+
)
286+
self.__most_recent_step_id_param_fetched_for[
287+
param_in_trace.param] = param_in_trace.step_id_last_used_at
288+
if param_in_trace.param not in params_to_prefetch:
289+
params_to_prefetch.add(param_in_trace.param)
290+
numel_prefetching += param_in_trace.param.ds_numel
291+
for param in params_to_prefetch:
292+
debug_rank0(f"-prefetch: {param.ds_summary()}")
293+
self.__all_gather_params(params_to_prefetch)
294+
295+
if self.__prefetch_nvme:
296+
self.__prefetch_nvme_param_partitions()
249297

250298
self.__step_id += 1
251299

@@ -256,9 +304,8 @@ def release_sub_module(self, submodule: Module) -> None:
256304
be released."""
257305
params_to_release = (self.__params_to_release(submodule,
258306
self.__step_id)
259-
if self.trace_complete else set(
307+
if self.is_complete_trace() else set(
260308
p.ds_id for p in iter_params(submodule)))
261-
262309
for param in iter_params(submodule):
263310
param.ds_active_sub_modules.discard(submodule.id)
264311
if param.ds_id in params_to_release and not param.is_external_param:
@@ -311,7 +358,7 @@ def __release_param(self, param: Parameter) -> None:
311358
def __params_to_release(self,
312359
submodule_to_release: Module,
313360
step_id: int) -> Set[int]:
314-
if not self.trace_complete:
361+
if not self.is_complete_trace():
315362
raise RuntimeError("expected trace to be complete")
316363

317364
params_to_release = set(p.ds_id for p in iter_params(submodule_to_release)
@@ -335,7 +382,7 @@ def __prefetch_nvme_param_partitions(self) -> None:
335382
"""swap in parameter partitions from nvme for those parameters that will be used
336383
after the ones that are already being prefetched into full parameters
337384
"""
338-
if not self.trace_complete:
385+
if not self.is_complete_trace():
339386
return
340387

341388
numel_in_flight = sum(param.ds_numel for param in self.__inflight_param_registry)

0 commit comments

Comments
 (0)