Skip to content

Commit 737ccd1

Browse files
inkcherrydelockloadamstjruwase
authored andcommitted
Reduce the device bubble introduced by heavy loop synchronization in coalesced fetch/release(z3_leaf_module) (deepspeedai#6694)
depend on deepspeedai#6649 When performing fetch/release operations on Z3 leaf modules, the loop time is excessively long in fine-grained module. Compared to non-leaf modules, Z3 leaf modules may include a larger number of parameters. Although each loop unit does not consume much time, the overall loop length can be significant. ![image](https://github.com/user-attachments/assets/9891835a-2620-47f3-aba6-ea22b8905d1c) **The fetch time is impacted by:** Post-allgather operations (narrow, slice ,cat, difficult to avoid) Memory pressure(record_stream/fetch event create&sync) **The release time is impacted by:** slice Free parameter record_stream Considering the fine-grained leaf modules, where each parameter is relatively small, we can treat the parameters within each leaf module as a unified entity to handle memory pressure. This approach can approximately halve the CPU time required for fetch/release operations. --------- Co-authored-by: Ma, Guokai <guokai.ma@gmail.com> Co-authored-by: Logan Adams <114770087+loadams@users.noreply.github.com> Co-authored-by: Olatunji Ruwase <olruwase@microsoft.com>
1 parent 3489980 commit 737ccd1

4 files changed

Lines changed: 69 additions & 41 deletions

File tree

deepspeed/runtime/zero/mics.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ class MiCS_AllGatherCoalescedHandle(AllGatherCoalescedHandle):
3838
def __init__(self, allgather_handle, params: List[Parameter], partitions: List[Tensor], world_size: int) -> None:
3939
super().__init__(allgather_handle, params, partitions, world_size)
4040

41-
def wait(self) -> None:
41+
def wait(self, **kwargs) -> None:
4242
"""
4343
"""
4444
# let the current stream to op

deepspeed/runtime/zero/parameter_offload.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,16 @@ def __init__(
145145
module.ds_inflight_param_registry = InflightParamRegistry()
146146
self.__inflight_param_registry = module.ds_inflight_param_registry
147147

148+
self.fast_sharding_for_leaf_module = False
149+
150+
if zero_module_granularity_threshold > 0:
151+
self.min_granularity_value = sys.maxsize
152+
self.min_granularity_layer = None
153+
self.granularity_info = set()
154+
self.z3_leaf_layers = []
155+
self._set_z3_leaf_modules_by_threshold(module, zero_module_granularity_threshold)
156+
self.fast_sharding_for_leaf_module = True
157+
148158
self.param_coordinator = PartitionedParameterCoordinator(
149159
prefetch_bucket_sz=self._prefetch_bucket_sz,
150160
max_reuse_distance_in_numel=self._max_reuse_distance_in_numel,
@@ -155,14 +165,7 @@ def __init__(
155165
timers=self.timers,
156166
zero_quantized_weights=self.zero_quantized_weights,
157167
zero_quantized_nontrainable_weights=self.zero_quantized_nontrainable_weights,
158-
)
159-
160-
if zero_module_granularity_threshold > 0:
161-
self.min_granularity_value = sys.maxsize
162-
self.min_granularity_layer = None
163-
self.granularity_info = set()
164-
self.z3_leaf_layers = []
165-
self._set_z3_leaf_modules_by_threshold(module, zero_module_granularity_threshold)
168+
fast_sharding_for_leaf_module=self.fast_sharding_for_leaf_module)
166169

167170
self.forward_hooks = []
168171
self.backward_hooks = []

deepspeed/runtime/zero/partition_parameters.py

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def __init__(self, param: Parameter) -> None:
5555
non_blocking=True).view(param.ds_shape)
5656
self.__param = param
5757

58-
def wait(self) -> None:
58+
def wait(self, **kwargs) -> None:
5959
if not get_accelerator().resolves_data_dependency():
6060
get_accelerator().current_stream().synchronize()
6161
self.__param.ds_status = ZeroParamStatus.AVAILABLE
@@ -78,7 +78,7 @@ def __init__(self, params: List[Parameter]) -> None:
7878
non_blocking=True).view(param.ds_shape)
7979

8080
@instrument_w_nvtx
81-
def wait(self) -> None:
81+
def wait(self, **kwargs) -> None:
8282
if self.__complete:
8383
return
8484

@@ -639,7 +639,7 @@ def __init__(self, handle, param: Parameter, quantization=None) -> None:
639639
self.__param = param
640640
self.__quantization = quantization
641641

642-
def wait(self) -> None:
642+
def wait(self, handle_dependency=True) -> None:
643643
instrument_w_nvtx(self.__handle.wait)()
644644
if self.__quantization:
645645
instrument_w_nvtx(self.__quantization.quant_handle.wait)()
@@ -650,6 +650,8 @@ def wait(self) -> None:
650650

651651
class AllGatherCoalescedHandle:
652652

653+
data_buffer = []
654+
653655
def __init__(
654656
self,
655657
allgather_handle,
@@ -672,7 +674,7 @@ def __init__(
672674
raise RuntimeError(f"expected param {param.ds_summary()} to not be available")
673675

674676
@instrument_w_nvtx
675-
def wait(self) -> None:
677+
def wait(self, handle_dependency=True) -> None:
676678
if self.complete:
677679
return
678680

@@ -704,24 +706,30 @@ def wait(self) -> None:
704706
partitions.append(part_to_copy)
705707
param.data = instrument_w_nvtx(torch.cat)(partitions).view(param.ds_shape)
706708
param.ds_status = ZeroParamStatus.AVAILABLE
707-
708-
for part_to_copy in partitions:
709-
if not get_accelerator().is_synchronized_device():
709+
if not get_accelerator().is_synchronized_device() and handle_dependency:
710+
for part_to_copy in partitions:
710711
part_to_copy.record_stream(get_accelerator().current_stream())
711712

712713
param_offset += ds_tensor_numel
713714

714715
self.complete = True
716+
if not get_accelerator().is_synchronized_device() and not handle_dependency:
717+
# if the device needs to handle dependencies and opts for explicit processing outside the function.
718+
AllGatherCoalescedHandle.data_buffer.append(partitions)
719+
720+
@staticmethod
721+
def free_buffer():
722+
AllGatherCoalescedHandle.data_buffer = []
715723

716724

717725
class MultipleAllGatherHandles:
718726

719727
def __init__(self, handles: List[AllGatherCoalescedHandle]):
720728
self.handles = handles
721729

722-
def wait(self) -> None:
730+
def wait(self, handle_dependency=True) -> None:
723731
for handle in self.handles:
724-
handle.wait()
732+
handle.wait(handle_dependency)
725733

726734

727735
class AllReduceCoalescedHandle:
@@ -1377,13 +1385,13 @@ def all_gather_coalesced(params: Iterable[Parameter],
13771385
quantization=quant_info,
13781386
)
13791387

1380-
def partition(param_list=None, hierarchy=0, has_been_updated=False):
1388+
def partition(param_list=None, hierarchy=0, has_been_updated=False, free_data=True):
13811389
cls = param
13821390
print_rank_0(f"{'--'*hierarchy}----Partitioning param {debug_param2name_id_shape_device(cls)}",
13831391
force=False)
13841392
if param_list is None:
13851393
param_list = [cls]
1386-
self._partition(param_list, has_been_updated=has_been_updated)
1394+
self._partition(param_list, has_been_updated=has_been_updated, free_data=True)
13871395

13881396
def reduce_gradients_at_owner(param_list=None, hierarchy=0):
13891397
cls = param
@@ -1527,20 +1535,20 @@ def _all_gather(self, param_list, async_op=False, hierarchy=None):
15271535

15281536
return handles
15291537

1530-
def _partition(self, param_list, force=False, has_been_updated=False):
1538+
def _partition(self, param_list, force=False, has_been_updated=False, free_data=True):
15311539
for param in param_list:
15321540
print_rank_0(f"Before Partitioning Param {param.ds_id}", force=False)
15331541
if self.zero_param_process_group is not None:
15341542
self._partition_param_sec(param)
1535-
self._partition_param(param, has_been_updated=has_been_updated)
1543+
self._partition_param(param, has_been_updated=has_been_updated, free_data=True)
15361544

15371545
param.ds_status = ZeroParamStatus.NOT_AVAILABLE
15381546
# if param.ds_tensor is not None:
15391547
# assert id(param.data) == id(param.ds_tensor.data), \
15401548
# "After the parameters are initially partitioned, make sure we are not recreating the partition."
15411549
#print_rank_0(f"After Partitioning Param {param.ds_id} {param.ds_tensor.size()} {param.ds_tensor}",force=False)
15421550
@instrument_w_nvtx
1543-
def _partition_param(self, param, buffer=None, has_been_updated=False):
1551+
def _partition_param(self, param, buffer=None, has_been_updated=False, free_data=True):
15441552
assert param.ds_status is not ZeroParamStatus.INFLIGHT, f" {param} Cannot partition a param in flight"
15451553
global reuse_buffers
15461554
print_rank_0(f"Param id {param.ds_id} status is {param.ds_status}", force=False)
@@ -1565,7 +1573,8 @@ def _partition_param(self, param, buffer=None, has_been_updated=False):
15651573

15661574
see_memory_usage(f'Before partitioning param {param.ds_id} {param.shape}', force=False)
15671575
# param.data does not store anything meaningful in partitioned state
1568-
free_param(param)
1576+
if free_data:
1577+
free_param(param)
15691578
see_memory_usage(f'After partitioning param {param.ds_id} {param.shape}', force=False)
15701579

15711580
if param.ds_tensor.final_location == OffloadDeviceEnum.nvme:

deepspeed/runtime/zero/partitioned_param_coordinator.py

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -76,18 +76,17 @@ class __ParamInTrace:
7676
param: Parameter
7777
step_id_last_used_at: int
7878

79-
def __init__(
80-
self,
81-
prefetch_bucket_sz: int,
82-
max_reuse_distance_in_numel: int,
83-
max_available_parameters_in_numel: int,
84-
allgather_stream: get_accelerator().Stream,
85-
inflight_param_registry: InflightParamRegistry,
86-
prefetch_nvme: bool = False,
87-
timers=None,
88-
zero_quantized_weights=False,
89-
zero_quantized_nontrainable_weights=False,
90-
) -> None:
79+
def __init__(self,
80+
prefetch_bucket_sz: int,
81+
max_reuse_distance_in_numel: int,
82+
max_available_parameters_in_numel: int,
83+
allgather_stream: get_accelerator().Stream,
84+
inflight_param_registry: InflightParamRegistry,
85+
prefetch_nvme: bool = False,
86+
timers=None,
87+
zero_quantized_weights=False,
88+
zero_quantized_nontrainable_weights=False,
89+
fast_sharding_for_leaf_module=False) -> None:
9190
# mapping of param -> handle for each param that is currently in flight
9291
self.__inflight_param_registry = inflight_param_registry
9392
# keeps track of the number of submodules invoked so far.
@@ -130,6 +129,10 @@ def __init__(
130129
self.__max_ongoing_fetch_events: int = 2
131130
self.__profiler = PartitionedParameterProfiler(timers if ENABLE_PROFILER else None)
132131

132+
# whether to enable fast fetch for the z3 leaf module.
133+
# this will improve fetch speed but will not break down leaf module parameters to alleviate memory pressure.
134+
self.fast_sharding_for_leaf_module = fast_sharding_for_leaf_module
135+
133136
"""Tracing and Tracking
134137
TODO. consider performing trace before initializing PartitionedParameterCoordinator
135138
and passing trace results into constructor. This way all the code in here can
@@ -308,6 +311,7 @@ def fetch_sub_module(self, current_submodule: Module, forward: bool) -> None:
308311
wait_numel = 0
309312
wait_event_name = __class__.FORWARD_FETCH_WAIT if forward else __class__.BACKWARD_FETCH_WAIT
310313
self.__profiler.start_event(wait_event_name)
314+
fast_fetch = self.fast_sharding_for_leaf_module and z3_leaf_module(current_submodule)
311315
# wait for parameters in the immediately needed submodule to become available
312316
for param in params_to_fetch:
313317
param.ds_active_sub_modules.add(current_submodule.id)
@@ -321,16 +325,18 @@ def fetch_sub_module(self, current_submodule: Module, forward: bool) -> None:
321325
if len(self.__ongoing_fetch_events) > self.__max_ongoing_fetch_events:
322326
self.__ongoing_fetch_events.popleft().synchronize()
323327

324-
self.__inflight_param_registry.pop(param).wait()
328+
self.__inflight_param_registry.pop(param).wait(handle_dependency=not fast_fetch)
325329

326-
if not get_accelerator().handles_memory_backpressure():
330+
if not get_accelerator().handles_memory_backpressure() and not fast_fetch:
327331
event = get_accelerator().Event()
328332
event.record()
329333
self.__ongoing_fetch_events.append(event)
330334

331335
assert param.ds_status == ZeroParamStatus.AVAILABLE, param.ds_summary()
332336
if not get_accelerator().resolves_data_dependency():
333337
get_accelerator().current_stream().wait_stream(self.__allgather_stream)
338+
if fast_fetch:
339+
AllGatherCoalescedHandle.free_buffer()
334340
self.__profiler.stop_event(wait_event_name, wait_numel)
335341

336342
# kick off parameter prefetches for upcoming modules
@@ -412,10 +418,20 @@ def release_sub_module(self, submodule: Module) -> None:
412418
be released."""
413419
params_to_release = (self.__params_to_release(submodule, self.__step_id) if self.is_complete_trace() else set(
414420
p.ds_id for p in iter_params(submodule, recurse=z3_leaf_module(submodule))))
421+
422+
free_data = not z3_leaf_module(submodule) or not self.fast_sharding_for_leaf_module
423+
if not free_data:
424+
# wait for the computation to finish and launch as early as possible.
425+
empty_buffer = torch.empty(1, device=get_accelerator().current_device())
426+
415427
for param in iter_params(submodule, recurse=z3_leaf_module(submodule)):
416428
param.ds_active_sub_modules.discard(submodule.id)
417429
if param.ds_id in params_to_release and not param.is_external_param:
418-
self.__release_param(param)
430+
self.__release_param(param, free_data)
431+
if not free_data:
432+
if param.ds_id in params_to_release and not param.is_external_param:
433+
# empty buffer ensures that all computations are complete
434+
param.data = empty_buffer
419435

420436
@instrument_w_nvtx
421437
@torch.no_grad()
@@ -490,11 +506,11 @@ def __all_gather_params_(self, params: Set[Parameter], forward: bool, quantize:
490506

491507
@compiler.disable
492508
@instrument_w_nvtx
493-
def __release_param(self, param: Parameter) -> None:
509+
def __release_param(self, param: Parameter, free_data: bool = True) -> None:
494510
if param.ds_status == ZeroParamStatus.AVAILABLE and not param.ds_active_sub_modules:
495511
if logger.isEnabledFor(logging.DEBUG):
496512
debug_rank0(f"-release: {param.ds_summary()}")
497-
param.partition()
513+
param.partition(free_data=free_data)
498514
self.__n_available_params -= param.ds_numel
499515

500516
@instrument_w_nvtx

0 commit comments

Comments
 (0)