131 lines
5.4 KiB
Python
131 lines
5.4 KiB
Python
# Copyright (c) DeepSpeed Team.
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
# DeepSpeed Team
|
|
|
|
import math
|
|
|
|
import pytest
|
|
import torch
|
|
|
|
import deepspeed.comm as dist
|
|
from deepspeed.accelerator import get_accelerator
|
|
from deepspeed.compile.config import CompileConfig
|
|
from deepspeed.compile.util import get_deepcompile_handle, is_deepcompile_supported
|
|
from unit.common import DistributedTest
|
|
|
|
pytestmark = pytest.mark.skipif(not is_deepcompile_supported(),
|
|
reason="DeepCompile requires CUDA and supported PyTorch")
|
|
|
|
|
|
class TestDeepCompileZ3ReleaseStorage(DistributedTest):
|
|
world_size = 2
|
|
non_daemonic_procs = True
|
|
|
|
def _device(self):
|
|
return torch.device(get_accelerator().current_device_name())
|
|
|
|
def _init_dc(self):
|
|
dc = get_deepcompile_handle()
|
|
dc.init(dist.get_world_group(), CompileConfig(deepcompile=True), 1024)
|
|
return dc
|
|
|
|
def _register_param(self, dc, graph_id, ds_id, shape, persistent=False):
|
|
device = self._device()
|
|
world_size = dist.get_world_size()
|
|
true_numel = math.prod(shape)
|
|
shard_numel = math.ceil(true_numel / world_size)
|
|
rank = dist.get_rank()
|
|
values = torch.arange(rank * shard_numel, (rank + 1) * shard_numel, device=device, dtype=torch.float32)
|
|
grad_buffer = torch.zeros_like(values)
|
|
dc.register_z3_param(ds_id, list(shape), values, grad_buffer, persistent, values.dtype)
|
|
dc.register_graph_z3(graph_id, [ds_id])
|
|
return values
|
|
|
|
def _gather_view_and_storage(self, shard, graph_id, ds_id):
|
|
gathered = torch.ops.dc.allgather_param.default(shard, graph_id, ds_id)
|
|
gathered = torch.ops.dc.wait_allgather.default(gathered, graph_id, ds_id)
|
|
view = gathered.reshape(-1).narrow(0, 0, gathered.numel() - 1)
|
|
assert view.untyped_storage().data_ptr() == gathered.untyped_storage().data_ptr()
|
|
storage = view.untyped_storage()
|
|
assert storage.nbytes() >= gathered.numel() * gathered.element_size()
|
|
return view, storage
|
|
|
|
def _release(self, view, graph_id, ds_id, n_users, synchronize=True):
|
|
torch.ops.dc.release_param.default(view, graph_id, ds_id, n_users)
|
|
if synchronize:
|
|
get_accelerator().synchronize()
|
|
|
|
def _expected_view_sum(self, shape):
|
|
world_size = dist.get_world_size()
|
|
shard_numel = math.ceil(math.prod(shape) / world_size)
|
|
values = torch.arange(0, world_size * shard_numel, dtype=torch.float32, device=self._device())
|
|
values = values[:math.prod(shape)].reshape(-1)
|
|
return values.narrow(0, 0, values.numel() - 1).sum()
|
|
|
|
def test_storage_resized_to_zero_after_release_single_use(self):
|
|
graph_id, ds_id = 9010, 9011
|
|
dc = self._init_dc()
|
|
try:
|
|
shard = self._register_param(dc, graph_id, ds_id, [4097])
|
|
view, storage = self._gather_view_and_storage(shard, graph_id, ds_id)
|
|
self._release(view, graph_id, ds_id, 1)
|
|
assert storage.nbytes() == 0
|
|
finally:
|
|
dc.cleanup()
|
|
|
|
def test_storage_nonzero_until_final_release_when_multi_use(self):
|
|
graph_id, ds_id = 9020, 9021
|
|
dc = self._init_dc()
|
|
try:
|
|
shard = self._register_param(dc, graph_id, ds_id, [3])
|
|
view, storage = self._gather_view_and_storage(shard, graph_id, ds_id)
|
|
before_release_nbytes = storage.nbytes()
|
|
self._release(view, graph_id, ds_id, 2)
|
|
assert storage.nbytes() == before_release_nbytes
|
|
self._release(view, graph_id, ds_id, 2)
|
|
assert storage.nbytes() == 0
|
|
finally:
|
|
dc.cleanup()
|
|
|
|
def test_persistent_param_storage_unchanged_across_release(self):
|
|
graph_id, ds_id = 9030, 9031
|
|
dc = self._init_dc()
|
|
try:
|
|
shard = self._register_param(dc, graph_id, ds_id, [4], persistent=True)
|
|
view, storage = self._gather_view_and_storage(shard, graph_id, ds_id)
|
|
before_ptr = storage.data_ptr()
|
|
before_nbytes = storage.nbytes()
|
|
self._release(view, graph_id, ds_id, 1)
|
|
assert storage.data_ptr() == before_ptr
|
|
assert storage.nbytes() == before_nbytes
|
|
finally:
|
|
dc.cleanup()
|
|
|
|
def test_consumer_stream_can_finish_before_storage_reuse(self):
|
|
graph_id, ds_id = 9040, 9041
|
|
if not hasattr(torch.cuda, "_sleep"): #ignore-cuda
|
|
pytest.skip("CUDA sleep helper is unavailable")
|
|
dc = self._init_dc()
|
|
try:
|
|
shard = self._register_param(dc, graph_id, ds_id, [4097])
|
|
view, storage = self._gather_view_and_storage(shard, graph_id, ds_id)
|
|
padded_bytes = storage.nbytes()
|
|
result = torch.empty((), device=self._device(), dtype=view.dtype)
|
|
consumer_stream = get_accelerator().Stream()
|
|
with get_accelerator().stream(consumer_stream):
|
|
torch.cuda._sleep(int(1e8)) #ignore-cuda
|
|
result.copy_(view.sum())
|
|
self._release(view, graph_id, ds_id, 1, synchronize=False)
|
|
|
|
scratch = torch.empty((padded_bytes // view.element_size()) + 1024,
|
|
device=self._device(),
|
|
dtype=view.dtype)
|
|
scratch.fill_(17)
|
|
get_accelerator().synchronize()
|
|
assert torch.allclose(result, self._expected_view_sum([4097]))
|
|
assert storage.nbytes() == 0
|
|
del scratch
|
|
finally:
|
|
dc.cleanup()
|