已合并
Add support of Execution Trace Observer object to torch_npu profiler (based on acl_prof). #44622
Add support of Execution Trace Observer object to torch_npu profiler (based on acl_prof). #44622
已合并
ilya_a创建于 18 天前
2 个文件变更+158-0
@@ -0,0 +1,151 @@
1+# Owner(s): ["oncall: profiler"]
2+ 
3+import json
4+import os
5+import tempfile
6+import glob
7+import gzip
8+from typing import Any
9+import torch_npu
10+import numpy as np
11+from torch_npu.testing.testcase import run_tests, TestCase
12+from torch.autograd import (
13+ _record_function_with_args_enter,
14+ _record_function_with_args_exit,
15+)
16+import torch
17+import torch.nn as nn
18+from torch.profiler import ExecutionTraceObserver
19+from torch.autograd.profiler import record_function
20+from unittest.mock import patch
21+ 
22+ 
23+worker_id = 1
24+Json = dict[str, Any]
25+ 
26+class TestNpuExecutionTrace(TestCase):
27+ """
28+ NestedTensor Layout on Ascend is not supported yet.
29+ """
30+ def trace_root(self, out_file_name) -> Json:
31+ nodes = []
32+ with (
33+ gzip.open(out_file_name)
34+ if out_file_name.endswith(".gz")
35+ else open(out_file_name)
36+ ) as f:
37+ et_graph = json.load(f)
38+ if "nodes" not in et_graph:
39+ raise AssertionError(f"Missing 'nodes' in execution trace: {et_graph}")
40+ nodes = et_graph["nodes"]
41+ return nodes
42+ 
43+ def workload(self):
44+ device = torch.device("npu:0")
45+ ut = torch.randn(3, 4, 5, requires_grad=True)
46+ with record_function("## TEST 1 ##", "1, 2, 3"):
47+ t1 = torch.randn(10, 10, device=device, requires_grad=True)
48+ t2 = torch.randn(10, 10, device=device, requires_grad=True)
49+ t3 = t1 + t2
50+ t3.backward(t3)
51+ gelu = nn.GELU()
52+ tm = torch.randn(2)
53+ _ = gelu(tm)
54+ t3 = t3.cpu()
55+ rec_fun_handler = _record_function_with_args_enter(
56+ "## TEST 2 ##",
57+ 1,
58+ False,
59+ 2.5,
60+ [ut, ut],
61+ (ut, ut),
62+ "hi",
63+ ut,
64+ float("inf"), float("-inf"), float("nan")
65+ )
66+ _record_function_with_args_exit(rec_fun_handler)
67+ 
68+ @property
69+ def worker_name(self):
70+ global worker_id
71+ worker_name = f"npu_profiler_test{worker_id}"
72+ worker_id += 1
73+ return worker_name
74+ 
75+ @patch.dict(
76+ os.environ,
77+ {"ENABLE_PYTORCH_EXECUTION_TRACE_SAVE_INTEGRAL_TENSOR_RANGE": "1"},
78+ )
79+ def test_npu_execution_trace_record_integral_tensor_range(self):
80+ device = torch.device("npu:0")
81+ x = torch.tensor([[1, 2], [3, 4]], device=device)
82+ y = torch.tensor([[0, 0], [1, 0]], device=device)
83+ with tempfile.NamedTemporaryFile("w+t", suffix=".et.json", delete=False) as trace_file:
84+ filename = trace_file.name
85+ et = ExecutionTraceObserver()
86+ et.register_callback(filename)
87+ with torch_npu.profiler.profile(
88+ activities=[torch_npu.profiler.ProfilerActivity.CPU,
89+ torch_npu.profiler.ProfilerActivity.NPU],
90+ schedule=torch_npu.profiler.schedule(
91+ skip_first=0, wait=0, warmup=0, active=1, repeat=1
92+ ),
93+ record_shapes=True,
94+ execution_trace_observer=et
95+ ) as prof:
96+ torch.gather(x, 1, y)
97+ prof.step()
98+ et.unregister_callback()
99+ nodes = self.trace_root(filename)
100+ os.remove(filename)
101+ for n in nodes:
102+ if "name" not in n:
103+ raise AssertionError(f"Expected node to have 'name': {n}")
104+ target_range = '{"0":[1,4],"1":[0,1]}'
105+ if "aten::gather" in n["name"]:
106+ for attr in n["attrs"]:
107+ if attr["name"] == "tensor_range" and attr["value"] != target_range:
108+ raise AssertionError(f"Expected tensor_range value to match {target_range}")
109+ 
110+ def test_npu_execution_trace_record_integral_tensor_data(self):
111+ with tempfile.TemporaryDirectory() as temp_dir:
112+ fp_name = os.path.join(temp_dir, "test.et.json")
113+ 
114+ os.environ["ENABLE_PYTORCH_EXECUTION_TRACE_SAVE_INTEGRAL_TENSOR_DATA"] = (
115+ "aten::gather"
116+ )
117+ et = ExecutionTraceObserver()
118+ et.register_callback(fp_name)
119+ et.set_extra_resource_collection(True)
120+ 
121+ device = torch.device("npu:0")
122+ t1 = torch.tensor([[1, 2], [3, 4]], device=device)
123+ t2 = torch.tensor([[0, 0], [1, 0]], device=device)
124+ with torch_npu.profiler.profile(
125+ activities=[torch_npu.profiler.ProfilerActivity.CPU,
126+ torch_npu.profiler.ProfilerActivity.NPU],
127+ schedule=torch_npu.profiler.schedule(
128+ skip_first=0, wait=0, warmup=0, active=1, repeat=1
129+ ),
atomgit-bot
atomgit-botatomgit-bot18 天前

🟡 Medium Priority

建议:将第 127 行的 schedule=torch.profiler.schedule(...) 改为 schedule=torch_npu.profiler.schedule(...),使用与第一个测试及 torch_npu profiler 内部约定一致的调度器。

改动建议
129
+ schedule=torch_npu.profiler.schedule(
130
+ skip_first=0, wait=0, warmup=0, active=1, repeat=1
129
131
  ),
应用建议
likedislike
130+ record_shapes=True,
131+ execution_trace_observer=et,
132+ ) as p:
133+ torch.gather(t1, 1, t2)
134+ p.step()
135+ et.unregister_callback()
136+ 
137+ resourceDir = fp_name.replace(".json", "_resources")
138+ dat_files = sorted(glob.glob(os.path.join(resourceDir, "*.dat")))
139+ if len(dat_files) < 2:
140+ raise AssertionError(f"Expected at least 2 .dat files in {resourceDir}, found {len(dat_files)}")
141+ 
142+ dumped_t1 = np.fromfile(dat_files[0], dtype=np.int64)
143+ dumped_t2 = np.fromfile(dat_files[1], dtype=np.int64)
144+ 
145+ if not (dumped_t1 == np.array([1, 2, 3, 4])).all():
146+ raise AssertionError("Expected t1 contents to match [1, 2, 3, 4]")
147+ if not (dumped_t2 == np.array([0, 0, 1, 0])).all():
148+ raise AssertionError("Expected t2 contents to match [0, 0, 1, 0]")
149+ 
150+if __name__ == "__main__":
151+ run_tests()
@@ -7,6 +7,7 @@ from typing import Any, Optional, Union
7 7 
8import torch.autograd.profiler as prof8import torch.autograd.profiler as prof
9import torch_npu.npu9import torch_npu.npu
10+from torch.profiler.profiler import _ITraceObserver
10from torch_npu.npu import current_stream, mstx11from torch_npu.npu import current_stream, mstx
11from torch_npu._C._profiler import (12from torch_npu._C._profiler import (
12 _disable_profiler_in_child_thread,13 _disable_profiler_in_child_thread,
@@ -211,6 +212,7 @@ class profile(_KinetoProfile):
211 with_flops: bool = False,212 with_flops: bool = False,
212 with_modules: bool = False,213 with_modules: bool = False,
213 experimental_config: Optional[_ExperimentalConfig] = None,214 experimental_config: Optional[_ExperimentalConfig] = None,
215+ execution_trace_observer: _ITraceObserver | None = None,
214 custom_trace_id_callback: Optional[Callable[[], str]] = None,216 custom_trace_id_callback: Optional[Callable[[], str]] = None,
215 # deprecated:217 # deprecated:
216 use_cuda: Optional[bool] = None,218 use_cuda: Optional[bool] = None,
@@ -249,6 +251,7 @@ class profile(_KinetoProfile):
249 metadata=self.metadata,251 metadata=self.metadata,
250 custom_trace_id_callback=custom_trace_id_callback,252 custom_trace_id_callback=custom_trace_id_callback,
251 )253 )
254+ self.execution_trace_observer = execution_trace_observer
252 self.on_trace_ready = on_trace_ready255 self.on_trace_ready = on_trace_ready
253 self.step_num = 0256 self.step_num = 0
254 self.current_action = self.schedule(self.step_num)257 self.current_action = self.schedule(self.step_num)
@@ -303,6 +306,8 @@ class profile(_KinetoProfile):
303 self.step_rec_fn = prof.record_function(step_name)306 self.step_rec_fn = prof.record_function(step_name)
304 self.step_rec_fn.__enter__()307 self.step_rec_fn.__enter__()
305 self._start_step_mstx_range(step_name)308 self._start_step_mstx_range(step_name)
309+ if self.execution_trace_observer:
310+ self.execution_trace_observer.start()
306 311 
307 @no_exception_func()312 @no_exception_func()
308 def stop(self):313 def stop(self):
@@ -311,6 +316,8 @@ class profile(_KinetoProfile):
311 self._end_step_mstx_range()316 self._end_step_mstx_range()
312 self.action_controller.transit_action(self.current_action, None)317 self.action_controller.transit_action(self.current_action, None)
313 self.stopped = True318 self.stopped = True
319+ if self.execution_trace_observer:
320+ self.execution_trace_observer.stop()
314 321 
315 @no_exception_func()322 @no_exception_func()
316 def step(self):323 def step(self):