# Copyright (c) Microsoft Corporation. # SPDX-License-Identifier: Apache-2.0 # DeepSpeed Team """ Functionality of swapping optimizer tensors to/from (NVMe) storage devices. """ from deepspeed.ops.op_builder import AsyncIOBuilder from deepspeed import comm as dist import torch from deepspeed.runtime.swap_tensor.constants import * from deepspeed.runtime.swap_tensor.utils import swap_in_tensors, swap_out_tensors, print_object from deepspeed.runtime.swap_tensor.async_swapper import AsyncTensorSwapper from deepspeed.runtime.swap_tensor.utils import get_sized_buffer from deepspeed.runtime.swap_tensor.optimizer_utils import OptimizerSwapper class OptimizerSwapOp(object): def __init__(self, aio_handle, read_op, param_info, allocated_buffers, state_buffers, num_ops): self.aio_handle = aio_handle self.read_op = read_op self.param_info = param_info self.allocated_buffers = allocated_buffers self.state_buffers = state_buffers self.wait_required = True self.num_ops = num_ops def is_parameter(self, parameter): return OptimizerSwapper.parameter_id(parameter) == self.param_info.param_id def wait(self): assert self.wait_required assert self.aio_handle.wait() == self.num_ops self.wait_required = False SYNC_SWAP_IN = 'sync_swap_in' ASYNC_SWAP_IN = 'async_swap_in' SYNC_SWAP_OUT = 'sync_swap_out' ASYNC_SWAP_OUT = 'async_swap_out' SWAP_IN_STATE_TIMER = 'swap_in_state' SWAP_OUT_STATE_TIMER = 'swap_out_state' SWAP_OUT_GRADIENT_TIMER = 'swap_out_gradient' ASYNC_SWAP_IN_STATE_TIMER = "async_swap_in_state" ASYNC_SWAP_OUT_STATE_TIMER = 'async_swap_out_state' class PipelinedOptimizerSwapper(OptimizerSwapper): def __init__(self, swap_config, aio_config, base_folder, optimizer, largest_numel, device, dtype, timers): super(PipelinedOptimizerSwapper, self).__init__(swap_config, aio_config, base_folder, optimizer, largest_numel, device, dtype, timers) aio_op = AsyncIOBuilder().load() self.write_aio_handle = aio_op.aio_handle(block_size=aio_config[AIO_BLOCK_SIZE], queue_depth=aio_config[AIO_QUEUE_DEPTH], single_submit=aio_config[AIO_SINGLE_SUBMIT], overlap_events=aio_config[AIO_OVERLAP_EVENTS], intra_op_parallelism=aio_config[AIO_INTRA_OP_PARALLELISM]) self.read_aio_handle = aio_op.aio_handle(block_size=aio_config[AIO_BLOCK_SIZE], queue_depth=aio_config[AIO_QUEUE_DEPTH], single_submit=aio_config[AIO_SINGLE_SUBMIT], overlap_events=aio_config[AIO_OVERLAP_EVENTS], intra_op_parallelism=aio_config[AIO_INTRA_OP_PARALLELISM]) # Overlap gradient swap out self.gradient_swapper = AsyncTensorSwapper(aio_handle=self.write_aio_handle, numel_alignment=self.numel_alignment, timers=self.timers) self.async_swap_in = swap_config.pipeline_read self.async_swap_out = swap_config.pipeline_write self.swap_ops = {SYNC_SWAP_IN: None, ASYNC_SWAP_IN: None, SYNC_SWAP_OUT: None, ASYNC_SWAP_OUT: None} self.print_exclude_list += [ 'gradient_swapper', 'read_aio_handle', 'write_aio_handle', 'swap_ops', 'print_exclude_list' ] if dist.get_rank() == 0: print_object(obj=self, name='PipelinedOptimizerSwapper', exclude_list=self.print_exclude_list) def initialize_parameters(self, parameters, src_tensors): self._initialize_parameters(parameters=parameters, src_tensors=src_tensors, aio_handle=self.write_aio_handle) def initialize_from_swapped_fp16_params(self, fp16_partitions_info, fp16_num_elems, fp16_pinned_buffers, fp32_parameters): self._initialize_from_swapped_fp16_params(aio_handle=self.write_aio_handle, fp16_partitions_info=fp16_partitions_info, fp16_num_elems=fp16_num_elems, fp16_pinned_buffers=fp16_pinned_buffers, fp32_parameters=fp32_parameters) def flush_gradients(self): self._flush_gradient_swapper(self.gradient_swapper) def swap_in_optimizer_state(self, parameter, async_parameter): assert parameter is not None assert self.swap_ops[SYNC_SWAP_IN] is None self._flush_gradient_swapper(self.gradient_swapper) self._start_timer(SWAP_IN_STATE_TIMER) if self.swap_ops[ASYNC_SWAP_IN]: assert self.swap_ops[ASYNC_SWAP_IN].is_parameter(parameter) self.swap_ops[SYNC_SWAP_IN] = self.swap_ops[ASYNC_SWAP_IN] self.swap_ops[ASYNC_SWAP_IN] = None else: self.swap_ops[SYNC_SWAP_IN] = self._swap_in_optimizer_state(aio_handle=self.read_aio_handle, parameter=parameter) if self.swap_ops[SYNC_SWAP_IN]: self.swap_ops[SYNC_SWAP_IN].wait() if self.async_swap_in and async_parameter is not None: assert self.swap_ops[ASYNC_SWAP_IN] is None self.swap_ops[ASYNC_SWAP_IN] = self._swap_in_optimizer_state(aio_handle=self.read_aio_handle, parameter=async_parameter) self._stop_timer(SWAP_IN_STATE_TIMER) self.timer_names.add(SWAP_IN_STATE_TIMER) def swap_out_optimizer_state(self, parameter, async_swap): self._start_timer(SWAP_OUT_STATE_TIMER) if self.swap_ops[ASYNC_SWAP_OUT]: self._start_timer(ASYNC_SWAP_OUT_STATE_TIMER) self._complete_swap_out(ASYNC_SWAP_OUT) self._stop_timer(ASYNC_SWAP_OUT_STATE_TIMER) self.timer_names.add(ASYNC_SWAP_OUT_STATE_TIMER) assert self.swap_ops[SYNC_SWAP_IN] is not None assert not self.swap_ops[SYNC_SWAP_IN].wait_required swap_op = self._swap_out_optimizer_state(aio_handle=self.write_aio_handle, parameter=parameter, swap_in_op=self.swap_ops[SYNC_SWAP_IN]) self.swap_ops[SYNC_SWAP_IN] = None if self.async_swap_out and async_swap: self.swap_ops[ASYNC_SWAP_OUT] = swap_op else: self.swap_ops[SYNC_SWAP_OUT] = swap_op self._complete_swap_out(SYNC_SWAP_OUT) self._stop_timer(SWAP_OUT_STATE_TIMER) self.timer_names.add(SWAP_OUT_STATE_TIMER) def swap_out_gradients(self, parameter, gradient_offsets, gradient_tensors): self._swap_out_gradients(parameter=parameter, gradient_offsets=gradient_offsets, gradient_tensors=gradient_tensors, gradient_swapper=self.gradient_swapper) def _complete_swap_out(self, swap_out_type): self.swap_ops[swap_out_type].wait() for buffer in self.swap_ops[swap_out_type].state_buffers: buffer = torch.Tensor() self.swap_buffer_manager.free(self.swap_ops[swap_out_type].allocated_buffers) self.swap_ops[swap_out_type] = None def _swap_out_optimizer_state(self, aio_handle, parameter, swap_in_op): assert swap_in_op.is_parameter(parameter) allocated_buffers = swap_in_op.allocated_buffers.copy() swap_buffers = swap_in_op.state_buffers.copy() param_info = swap_in_op.param_info self._update_param_state_info(param_info, parameter) unpinned_tensors = param_info.get_unpinned_state_tensors() if len(unpinned_tensors) > 0: new_alloc_buffers = self.swap_buffer_manager.allocate(num_elems=self._io_aligned_numel(param_info.numel()), count=len(unpinned_tensors), dtype=param_info.dtype()) assert new_alloc_buffers is not None allocated_buffers += new_alloc_buffers swap_buffers += new_alloc_buffers for pinned_dst, unpinned_src in zip(new_alloc_buffers, unpinned_tensors): dst = get_sized_buffer(pinned_dst, unpinned_src.numel()) dst.data.copy_(unpinned_src.data) swap_paths = param_info.get_swap_paths() assert len(swap_paths) == len(swap_buffers) swap_out_tensors(aio_handle, swap_buffers, swap_paths) swap_out_op = OptimizerSwapOp(aio_handle=aio_handle, param_info=param_info, read_op=False, allocated_buffers=allocated_buffers, state_buffers=swap_buffers, num_ops=len(swap_buffers)) return swap_out_op def _swap_in_optimizer_state(self, aio_handle, parameter): param_info = self._get_param_swap_info(parameter) if param_info is None: return None num_swap_tensors = param_info.num_tensors() required_buffer_count = num_swap_tensors + (1 if param_info.has_gradients() else 0) aligned_numel = self._io_aligned_numel(param_info.numel()) allocated_buffers = self.swap_buffer_manager.allocate(num_elems=aligned_numel, count=required_buffer_count, dtype=parameter.dtype) assert allocated_buffers is not None, \ "PipelinedOptimizerSwapper ran out of swap buffers, try increasing 'buffer_count'" state_buffers = allocated_buffers[:num_swap_tensors] param_info.set_swap_buffers(state_buffers, aligned_numel) swap_buffers = state_buffers.copy() swap_paths = param_info.get_swap_paths() if param_info.has_gradients(): parameter.grad = allocated_buffers[-1].narrow(0, 0, param_info.numel()) if param_info.swapped_gradients: swap_buffers += param_info.get_swap_gradient_buffers(parameter.grad) swap_paths += param_info.get_swap_gradient_paths() swap_in_tensors(aio_handle, swap_buffers, swap_paths) if param_info.unswapped_gradients: self._retrieve_unswapped_grad_partitions(swap_info=param_info, dest_buffer=parameter.grad) swap_in_op = OptimizerSwapOp(aio_handle=aio_handle, param_info=param_info, read_op=True, allocated_buffers=allocated_buffers, state_buffers=state_buffers, num_ops=len(swap_buffers)) return swap_in_op