Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions dace/codegen/instrumentation/data/data_dump.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@ def __init__(self):
self.uses_gpu = False
self.framecode: 'DaCeCodeGenerator' = None

def writes_to_report(self) -> bool:
# Data instrumentation dumps arrays to a separate directory and never touches
# __state->report.
return False

def on_sdfg_begin(self, sdfg: SDFG, local_stream: CodeIOStream, global_stream: CodeIOStream,
codegen: 'DaCeCodeGenerator'):
# Initialize serializer versioning object
Expand Down Expand Up @@ -202,6 +207,11 @@ def __init__(self):
self.uses_gpu = False
self.framecode: 'DaCeCodeGenerator' = None

def writes_to_report(self) -> bool:
# Data instrumentation dumps arrays to a separate directory and never touches
# __state->report.
return False

def _generate_report_setter(self, sdfg: SDFG) -> str:
return f'''
DACE_EXPORTED void __dace_set_instrumented_data_report({cpp.mangle_dace_state_struct_name(sdfg)} *__state, const char *dirpath) {{
Expand Down
3 changes: 3 additions & 0 deletions dace/codegen/instrumentation/gpu_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ def __init__(self):
self.backend = common.get_gpu_backend()
super().__init__()

def writes_to_report(self) -> bool:
return True

def on_sdfg_begin(self, sdfg: SDFG, local_stream: CodeIOStream, global_stream: CodeIOStream, codegen) -> None:
if self.backend == 'cuda':
header_name = 'cuda_runtime.h'
Expand Down
3 changes: 3 additions & 0 deletions dace/codegen/instrumentation/gpu_tx_markers.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ def __init__(self):
self.include_generated = False
super().__init__()

def writes_to_report(self) -> bool:
return False

def _print_include(self, sdfg: SDFG) -> None:
""" Prints the include statement for the NVTX/rocTX library for a given SDFG. """
if self.include_generated:
Expand Down
6 changes: 6 additions & 0 deletions dace/codegen/instrumentation/likwid.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,9 @@ def __init__(self):
except KeyError:
self._default_events = "CLOCK"

def writes_to_report(self) -> bool:
return True

def on_sdfg_begin(self, sdfg: SDFG, local_stream: CodeIOStream, global_stream: CodeIOStream, codegen) -> None:
if sdfg.parent is not None:
return
Expand Down Expand Up @@ -371,6 +374,9 @@ def __init__(self):
except KeyError:
self._default_events = "FLOPS_SP"

def writes_to_report(self) -> bool:
return True

def on_sdfg_begin(self, sdfg: SDFG, local_stream: CodeIOStream, global_stream: CodeIOStream, codegen) -> None:
if sdfg.parent is not None:
return
Expand Down
3 changes: 3 additions & 0 deletions dace/codegen/instrumentation/papi.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ def __init__(self):
PAPIInstrumentation._counters = PAPIInstrumentation._counters or set(
ast.literal_eval(Config.get('instrumentation', 'papi', 'default_counters')))

def writes_to_report(self) -> bool:
return True

def get_unique_number(self):
ret = self._unique_counter
self._unique_counter += 1
Expand Down
6 changes: 6 additions & 0 deletions dace/codegen/instrumentation/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ class types, given the currently-registered extensions of this class.

return result

def writes_to_report(self) -> bool:
""" Returns whether this provider writes to the runtime performance report
(``__state->report``). Every concrete provider must override this.
"""
raise NotImplementedError(f'{type(self).__name__} must implement writes_to_report()')

def _idstr(self, cfg: ControlFlowRegion, state: SDFGState, node: nodes.Node) -> str:
""" Returns a unique identifier string from a node or state. """
result = str(cfg.cfg_id)
Expand Down
3 changes: 3 additions & 0 deletions dace/codegen/instrumentation/timer.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ class TimerProvider(InstrumentationProvider):
""" Timing instrumentation that reports wall-clock time directly after
timed execution is complete. """

def writes_to_report(self) -> bool:
return True

def on_sdfg_begin(self, sdfg, local_stream, global_stream, codegen):
global_stream.write('#include <chrono>')

Expand Down
8 changes: 5 additions & 3 deletions dace/codegen/targets/framecode.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,9 @@ def generate_header(self, sdfg: SDFG, global_stream: CodeIOStream, callsite_stre
self.statestruct.extend(env.state_fields)

# Instrumentation preamble
if len(self._dispatcher.instrumentation) > 2:
# NOTE: Some instrumentation providers (e.g. GPU_TX_MARKERS) never write to
# __state->report, so skip the report machinery unless at least one active provider does.
if any(i is not None and i.writes_to_report() for i in self._dispatcher.instrumentation.values()):
self.statestruct.append('dace::perf::Report report;')
# Reset report if written every invocation
if config.Config.get_bool('instrumentation', 'report_each_invocation'):
Expand Down Expand Up @@ -252,7 +254,7 @@ def generate_footer(self, sdfg: SDFG, global_stream: CodeIOStream, callsite_stre

# Instrumentation saving
if (config.Config.get_bool('instrumentation', 'report_each_invocation')
and len(self._dispatcher.instrumentation) > 2):
and any(i is not None and i.writes_to_report() for i in self._dispatcher.instrumentation.values())):
callsite_stream.write(
'__state->report.save("%s", __HASH_%s);' % (pathlib.Path(sdfg.build_folder) / "perf", sdfg.name), sdfg)

Expand Down Expand Up @@ -362,7 +364,7 @@ def generate_footer(self, sdfg: SDFG, global_stream: CodeIOStream, callsite_stre

# Instrumentation saving
if (not config.Config.get_bool('instrumentation', 'report_each_invocation')
and len(self._dispatcher.instrumentation) > 2):
and any(i is not None and i.writes_to_report() for i in self._dispatcher.instrumentation.values())):
callsite_stream.write(
'__state->report.save("%s", __HASH_%s);' % (pathlib.Path(sdfg.build_folder) / "perf", sdfg.name), sdfg)

Expand Down
22 changes: 22 additions & 0 deletions tests/codegen/data_instrumentation_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,27 @@ def tester(A: dace.float64[20, 20]):
assert all('DeviceSynchronize' in chunk for chunk in before_each_save), code


def test_dump_no_report():
"""Data instrumentation (Save/RestoreProvider) never writes to __state->report, so the
performance report struct and its save calls must not appear when it's the only
instrumentation in use.
"""

@dace.program
def tester(A: dace.float64[20, 20]):
tmp = A + 1
return tmp + 5

sdfg = tester.to_sdfg(simplify=True)
_instrument(sdfg, dace.DataInstrumentationType.Save)

for each_invocation in (True, False):
with dace.config.set_temporary('instrumentation', 'report_each_invocation', value=each_invocation):
code = sdfg.generate_code()[0].clean_code
assert 'dace::perf::Report report;' not in code
assert '__state->report' not in code


@pytest.mark.datainstrument
def test_restore():

Expand Down Expand Up @@ -412,6 +433,7 @@ def dinstr(A: dace.float64[20]):
test_symbol_dump_conditional()
test_dump_gpu()
test_dump_gpu_synchronizes()
test_dump_no_report()
test_restore()
test_symbol_restore()
test_restore_gpu()
Expand Down
38 changes: 38 additions & 0 deletions tests/instrumentation_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@

N = dace.symbol('N')

# State struct field that carries the instrumentation report
REPORT_DECL = 'dace::perf::Report report;'


@dace.program
def slowmm(A: dace.float64[N, N], B: dace.float64[N, N], C: dace.float64[N, N]):
Expand Down Expand Up @@ -77,6 +80,15 @@ def onetest(instrumentation: dace.InstrumentationType, size=128):
range_pop = re.search(r'(nvtx|roctx)RangePop\b', code)
assert range_pop is not None

# GPU_TX_MARKERS never writes to the report, so it must not be declared nor used when it is
# the only instrumentation in use. Both save sites are checked, as they are guarded by the
# `instrumentation.report_each_invocation` configuration entry.
for each_invocation in (True, False):
with dace.config.set_temporary('instrumentation', 'report_each_invocation', value=each_invocation):
code = sdfg.generate_code()[0].clean_code
assert REPORT_DECL not in code
assert re.search(r'__state->report\b', code) is None


def test_timer():
onetest(dace.InstrumentationType.Timer)
Expand All @@ -98,9 +110,35 @@ def test_gpu_tx_markers():
onetest(dace.InstrumentationType.GPU_TX_MARKERS)


@pytest.mark.gpu
def test_gpu_tx_markers_with_timer():
""" The report is still needed when another instrumentation type is used next to GPU_TX_MARKERS. """
sdfg: dace.SDFG = slowmm.to_sdfg()
sdfg.name = 'instrumentation_test_GPU_TX_MARKERS_with_timer'
sdfg.simplify()

# Mark the map with GPU_TX_MARKERS and the state containing it with a timer
sdfg.instrument = dace.InstrumentationType.GPU_TX_MARKERS
for node, state in sdfg.all_nodes_recursive():
if isinstance(node, nodes.MapEntry) and node.map.label == 'mult':
node.map.instrument = dace.InstrumentationType.GPU_TX_MARKERS
state.instrument = dace.InstrumentationType.Timer

sdfg.apply_transformations(GPUTransformSDFG)

# Both providers are in use, so the ranges are emitted and the report is kept at either save site
for each_invocation in (True, False):
with dace.config.set_temporary('instrumentation', 'report_each_invocation', value=each_invocation):
code = sdfg.generate_code()[0].clean_code
assert re.search(r'(nvtx|roctx)RangePush\(', code) is not None
assert REPORT_DECL in code
assert re.search(r'__state->report\b', code) is not None


if __name__ == '__main__':
test_timer()
test_papi()
if len(sys.argv) > 1 and sys.argv[1] == 'gpu':
test_gpu_events()
test_gpu_tx_markers()
test_gpu_tx_markers_with_timer()
Loading