194 lines
5.9 KiB
Python
194 lines
5.9 KiB
Python
# Copyright (c) DeepSpeed Team.
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
# DeepSpeed Team
|
|
|
|
import pytest
|
|
import torch
|
|
from torch.fx import Graph, GraphModule
|
|
|
|
import deepspeed.compile.profilers.graph_profile as graph_profile
|
|
|
|
|
|
class FakeRandom:
|
|
|
|
def fork_rng(self, devices):
|
|
return self
|
|
|
|
def __enter__(self):
|
|
return None
|
|
|
|
def __exit__(self, exc_type, exc, traceback):
|
|
return False
|
|
|
|
|
|
class FakeAccelerator:
|
|
|
|
def __init__(self):
|
|
self.event_count = 0
|
|
|
|
def current_device(self):
|
|
return "cpu"
|
|
|
|
def memory_allocated(self):
|
|
return 0
|
|
|
|
def max_memory_allocated(self):
|
|
return 0
|
|
|
|
def reset_peak_memory_stats(self):
|
|
return None
|
|
|
|
def Event(self, enable_timing=True):
|
|
event = FakeEvent(f"event-{self.event_count}")
|
|
self.event_count += 1
|
|
return event
|
|
|
|
def synchronize(self):
|
|
return None
|
|
|
|
def random(self):
|
|
return FakeRandom()
|
|
|
|
|
|
class FakeDeepCompileHandle:
|
|
|
|
def __init__(self):
|
|
self.events = []
|
|
|
|
def enable_profiling(self, enabled):
|
|
self.events.append(("enable", enabled))
|
|
|
|
def clear_all_gathered_params(self):
|
|
self.events.append(("clear", None))
|
|
|
|
|
|
class FakeEvent:
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
self.records = []
|
|
|
|
def record(self):
|
|
self.records.append(self.name)
|
|
|
|
def elapsed_time(self, end_event):
|
|
return 1.0
|
|
|
|
|
|
def _make_empty_graph_module():
|
|
graph = Graph()
|
|
graph.output(None)
|
|
return GraphModule(torch.nn.Module(), graph)
|
|
|
|
|
|
def test_profile_helpers_drop_warmup_and_intermediate_outputs():
|
|
deleted = []
|
|
|
|
class Output:
|
|
|
|
def __init__(self, index):
|
|
self.index = index
|
|
|
|
def __del__(self):
|
|
deleted.append(self.index)
|
|
|
|
outputs_created = []
|
|
|
|
def call_fn():
|
|
output = Output(len(outputs_created))
|
|
outputs_created.append(output.index)
|
|
return output
|
|
|
|
start_events = [FakeEvent(f"start-{i}") for i in range(3)]
|
|
end_events = [FakeEvent(f"end-{i}") for i in range(3)]
|
|
|
|
graph_profile._run_warmup_for_profile(call_fn, warmup=2)
|
|
out = graph_profile._run_repeatedly_for_profile(call_fn,
|
|
iteration=3,
|
|
start_events=start_events,
|
|
end_events=end_events)
|
|
|
|
assert out.index == 4
|
|
assert outputs_created == [0, 1, 2, 3, 4]
|
|
assert deleted == [0, 1, 2, 3]
|
|
assert [event.records for event in start_events] == [["start-0"], ["start-1"], ["start-2"]]
|
|
assert [event.records for event in end_events] == [["end-0"], ["end-1"], ["end-2"]]
|
|
|
|
|
|
def test_profiling_interpreter_wall_time_excludes_warmup(monkeypatch):
|
|
fake_handle = FakeDeepCompileHandle()
|
|
fake_accelerator = FakeAccelerator()
|
|
|
|
monkeypatch.setattr(graph_profile, "get_deepcompile_handle", lambda: fake_handle)
|
|
monkeypatch.setattr(graph_profile, "get_accelerator", lambda: fake_accelerator)
|
|
monkeypatch.setattr(graph_profile, "_get_mem_usage_out_of_torch", lambda: 0)
|
|
monkeypatch.setattr(graph_profile, "is_comm_op", lambda node: False)
|
|
monkeypatch.setattr(graph_profile, "is_release_node", lambda node: False)
|
|
monkeypatch.setattr(graph_profile.dist, "is_initialized", lambda: False)
|
|
monkeypatch.setattr(graph_profile.dist, "get_rank", lambda: 0)
|
|
|
|
timestamps = iter(range(20))
|
|
monkeypatch.setattr(graph_profile.time, "time", lambda: next(timestamps))
|
|
|
|
def timed_identity(x):
|
|
graph_profile.time.time()
|
|
return x
|
|
|
|
graph = Graph()
|
|
x = graph.placeholder("x")
|
|
y = graph.call_function(timed_identity, (x, ))
|
|
graph.output(y)
|
|
gm = GraphModule(torch.nn.Module(), graph)
|
|
|
|
interpreter = graph_profile.ProfilingInterpreter(gm, iteration=3, warmup=2)
|
|
interpreter.run(torch.ones(1))
|
|
|
|
call_node = next(node for node in gm.graph.nodes if node.op == "call_function")
|
|
assert call_node.meta["wall_time"] == pytest.approx((4 / 3) * 1000)
|
|
|
|
|
|
def test_memory_profiling_interpreter_clears_gathered_params_after_failure(monkeypatch):
|
|
fake_handle = FakeDeepCompileHandle()
|
|
|
|
monkeypatch.setattr(graph_profile, "get_deepcompile_handle", lambda: fake_handle)
|
|
monkeypatch.setattr(graph_profile, "get_accelerator", lambda: FakeAccelerator())
|
|
monkeypatch.setattr(graph_profile, "_all_real_if_tensor", lambda args: True)
|
|
monkeypatch.setattr(graph_profile, "_get_mem_usage_out_of_torch", lambda: 0)
|
|
|
|
def raise_from_run(self, *args):
|
|
raise RuntimeError("synthetic memory profile failure")
|
|
|
|
monkeypatch.setattr(graph_profile.Interpreter, "run", raise_from_run)
|
|
|
|
interpreter = graph_profile.MemoryProfilingInterpreter(_make_empty_graph_module())
|
|
interpreter.mem_record.append(("partial", 1, 1, 1))
|
|
|
|
assert interpreter.run() is None
|
|
assert not interpreter.profile_complete
|
|
assert interpreter.mem_record == []
|
|
assert fake_handle.events == [("enable", True), ("clear", None), ("enable", False)]
|
|
|
|
|
|
def test_memory_profiling_interpreter_disables_profiling_if_cleanup_fails(monkeypatch):
|
|
fake_handle = FakeDeepCompileHandle()
|
|
|
|
def fail_clear():
|
|
fake_handle.events.append(("clear", None))
|
|
raise RuntimeError("cleanup failed")
|
|
|
|
fake_handle.clear_all_gathered_params = fail_clear
|
|
|
|
monkeypatch.setattr(graph_profile, "get_deepcompile_handle", lambda: fake_handle)
|
|
monkeypatch.setattr(graph_profile, "get_accelerator", lambda: FakeAccelerator())
|
|
monkeypatch.setattr(graph_profile, "_all_real_if_tensor", lambda args: True)
|
|
monkeypatch.setattr(graph_profile, "_get_mem_usage_out_of_torch", lambda: 0)
|
|
monkeypatch.setattr(graph_profile.Interpreter, "run", lambda self, *args: None)
|
|
|
|
interpreter = graph_profile.MemoryProfilingInterpreter(_make_empty_graph_module())
|
|
|
|
with pytest.raises(RuntimeError, match="cleanup failed"):
|
|
interpreter.run()
|
|
|
|
assert fake_handle.events == [("enable", True), ("clear", None), ("enable", False)]
|