diff --git a/profiler/compare_tools/README.md b/profiler/compare_tools/README.md
index 6b1da16bfa910c71a4adf7c1cd47c1b5bc0f8dcf..e307491f36686687d7a842f990193f58cff955ac 100644
--- a/profiler/compare_tools/README.md
+++ b/profiler/compare_tools/README.md
@@ -1,17 +1,57 @@
# 性能比对工具
-## 介绍
-性能比对工具支持比较GPU与NPU之间、NPU与NPU之间的性能差异,帮助用户更快的定位性能瓶颈。比对的结果主要分为4个维度展示:总体性能、算子性能、通信性能、算子内存。
+## 1.简介
+性能比对工具支持比较GPU与NPU之间、NPU与NPU之间的性能差异,通过对训练耗时和内存占用的比对分析,定位到具体劣化的算子,帮助用户提升性能调优的效率。工具将训练耗时拆分为算子、通信、调度3大维度,并针对算子和通信分别进行算子级别的比对;将训练占用的总内存,拆分成算子级别的内存占用进行比对。
-## 使用方式
-### 最简执行命令
+## 2.使用场景
+场景一:PyTorch训练工程从GPU迁移至NPU后出现性能劣化,通过工具分析出劣化点
+
+场景二:PyTorch训练工程在NPU上,不同版本之间存在性能差距,通过工具定位具体差异
+
+
+## 3.使用指导
+### 性能数据采集
+#### GPU性能数据采集
+通过PyTorch Profiler工具采集GPU的性能数据,参考链接:
+https://pytorch.org/docs/stable/profiler.html
+
+采集样例代码参考1:
```
-python performance_compare.py [基准性能数据的文件路径] [比较性能数据的文件路径]
+with torch.profiler.profile(
+ profile_memory=True, #内存数据采集的开关
+ record_shapes=True, #算子input shape信息采集的开关
+ schedule=torch.profiler.schedule(wait=10, warmup=0, active=1, repeat=1),
+ on_trace_ready=torch.profiler.tensorboard_trace_handler("./result_dir")
+) as prof:
+ for step in ranges(step_number):
+ train_one_step()
+ prof.step()
+```
+采集样例代码参考2:
+```
+prof = torch.profiler.profile(
+ profile_memory=True, #内存数据采集的开关
+ record_shapes=True, #算子input shape信息采集的开关
+ on_trace_ready=torch.profiler.tensorboard_trace_handler("./result_dir"))
+for step in range(step_number):
+ if step == 11:
+ prof.start()
+ train_one_step()
+ if step == 11:
+ prof.stop()
```
-#### 文件路径说明:
-GPU的性能数据文件路径:指定到以".pt.trace"结尾的json文件
-NPU的性能数据文件路径:可以指定以"_ascend_pt"结尾的文件,也可以指定到ASCEND_PROFILER_OUTPUT目录,也可以指定到trace_view.json,当指定到trace_view.json时不支持比对算子内存占用。
+pytorch profiler数据目录结构如下:
+
+```
+|- pytorch_profiling
+ |- **.pt.trace.json
+```
+
+#### NPU性能数据采集
+通过Ascend PyTorch Profiler工具采集NPU的性能数据,采集参数配置跟GPU一致,参考链接:
+https://www.hiascend.com/document/detail/zh/canncommercial/63RC2/modeldevpt/ptmigr/ptmigr_0066.html
+将GPU的性能数据采集代码中torch.profiler替换成torch_npu.profiler
ascend pytorch profiler数据目录结构如下:
@@ -25,32 +65,48 @@ ascend pytorch profiler数据目录结构如下:
|- **_ascend_pt
```
-#### 通用参数说明:
+### 性能数据比对
+#### 最简执行命令
+进入att代码仓的下载目录,cd att/profiler/compare_tools,执行以下命令:
+```
+python performance_compare.py [基准性能数据的文件路径] [比较性能数据的文件路径]
```
---disable_profiling_compare:设置该参数代表不进行总体性能比较
+工具将总体性能拆解为训练耗时和内存占用2个方面,其中训练耗时可拆分为算子、通信、调度3个维度,以打屏的形式输出总体指标,帮助用户定界劣化的方向。与此同时,工具还会生成performance_comparison_result_**.xlsl,里面具体到每个算子在执行耗时、通信耗时、内存占用的优劣,可通过DIFF列大于0筛选出劣化算子。
+
+#### 文件路径说明
+GPU的性能数据文件路径:指定到以".pt.trace"结尾的json文件
---disable_operator_compare:设置该参数代表不进行算子性能比较
+NPU的性能数据文件路径: 支持多种路径,①以"_ascend_pt"结尾的目录;②ASCEND_PROFILER_OUTPUT目录;③trace_view.json,该路径无法显示算子的内存占用
---disable_memory_compare:设置该参数代表不进行算子内存比较
+#### 通用参数说明
+```
+--enable_profiling_compare:开启总体性能比较。使用示例:--enable_profiling_compare
---disable_communication_compare:设置该参数代表不进行通信性能比较
+--enable_operator_compare:开启算子性能比较。使用示例:--enable_operator_compare
---output_path:性能比对结果存放的路径
+--enable_communication_compare:开启通信性能比较。使用示例:--enable_communication_compare
+
+--enable_memory_compare:开启算子内存比较。使用示例:--enable_memory_compare
+```
+说明:以上4个开关均不设置的情况下,工具默认开启所有的性能比较,当用户设置了以上开关,则按照用户设置的开关进行性能比对
+```
+--output_path:性能比对结果存放的路径。使用示例:--output_path=./result_dir
```
-#### 算子性能比对特有参数说明:
+#### 算子性能比对特有参数说明
```
---gpu_flow_cat:GPU trace中cpu侧算子与device kernel的连线标识,默认是async_gpu
+--gpu_flow_cat:配置GPU trace中cpu侧算子与device kernel的连线标识,当GPU的kernel均为空时设置。使用示例:--gpu_flow_cat=async_gpu
---use_input_shape:设置该参数代表算子精准匹配
+--use_input_shape:开启算子精准匹配。使用示例:--use_input_shape
---max_kernel_num:该参数设置cpu侧算子下发执行的最大kernel数量,当超过设定值时工具会自动找下层的子算子,直至满足条件
+--max_kernel_num:设置cpu侧算子下发的最大kernel数量,当超过设定值时工具会自动往下找子算子,直至满足条件。使用示例:--max_kernel_num=10
---op_name_map:该参数存放GPU与NPU等价的算子名称的映射关系,以字典形式存入
+--op_name_map:设置GPU与NPU等价的算子名称的映射关系,以字典形式存入。使用示例:--op_name_map='{"Optimizer.step#SGD.step":"Optimizer.step#NpuFusedSGD.step"}'
```
-## 比对内容
+## 4.比对结果说明
### 总体性能
+总体性能比对结果以打屏的形式呈现。
#### 算子耗时
```
包含cube算子耗时和vector算子耗时
@@ -79,24 +135,47 @@ npu上的内存使用可以使用npu-smi查看
profiling信息采集时打开profile_memory=True开关,即可从json文件中读出运行稳定后的memory信息
```
-#### 计算流e2e耗时
+#### E2E总耗时
```
计算流端到端耗时
```
### 算子性能
-DIFF以device耗时为比对指标
-#### device耗时
+算子性能比对结果在performance_comparison_result_**.xlsl中OperatorCompare的sheet页呈现。
+
+淡蓝色背景的记录行:算子的summary信息,包括算子名称、算子的Input Shape、算子的Input Type、算子在device上的总耗时(单位:us)
+
+无背景色的记录行:算子的detail信息,包含了这个算子下发到device侧的所有kernel的明细,包括kernel名称、kernel的信息(针对NPU)、device上的耗时(单位:us)
+
+DIFF列 = (比较算子在device上执行总耗时 - 基准算子在device上执行总耗时) / 基准算子在device上执行总耗时
+
+DIFF Filter列:红色代表劣化
+
+#### Device Duration(us)
```
-该算子下发到device上执行的所有kernel耗时的加总
+该算子下发到device上执行的所有kernel耗时的总和
```
### 通信性能
-DIFF以同一个类型的通信算子(如:allreduce)的总耗时为比对指标
+通信性能比对结果在performance_comparison_result_**.xlsl中CommunicationCompare的sheet页呈现。
-通信性能比对结果以通信算子的类型为粒度,展示该类型通信算子调用的总次数、平均耗时、总耗时、耗时最大值、耗时最小值。
+淡蓝色背景的记录行:通信算子的summary信息,包括通信算子名称、调用总次数、通信算子总耗时(单位:us)、通信算子平均耗时(单位:us)、通信算子最大耗时(单位:us)、通信算子最小耗时(单位:us)
+
+无背景色的记录行:通信算子的detail信息,仅支持NPU,包含了该通信算子下的所有Task信息,包括Task名称、Task调用次数、Task总耗时(单位:us)、Task平均耗时(单位:us)、Task最大耗时(单位:us)、Task最小耗时(单位:us)
+
+DIFF列 = (比较通信算子的总耗时 - 基准通信算子的总耗时) / 基准通信算子的总耗时
+
+DIFF Filter列:红色代表劣化
-NPU会下钻展示该类型通信算子下,不同通信小算子(如:Notify_Wait)的耗时占比,调用的总次数、平均耗时、总耗时、耗时最大值、耗时最小值。
### 算子内存
-DIFF以内存占用的大小为比对指标
+算子内存比对结果在performance_comparison_result_**.xlsl中MemoryCompare的sheet页呈现。
+
+淡蓝色背景的记录行:算子的summary信息,包括算子名称、算子的Input Shape、算子的Input Type、算子占用的总内存(单位:KB)
+
+无背景色的记录行:算子的detail信息,包含了这个算子下发到device侧执行的所有算子的内存占用,包括算子名称、内存持有时间(单位:us)、内存占用大小(单位:KB)
+
+DIFF列 = (比较算子占用的总内存 - 基准算子占用的总内存) / 基准算子占用的总内存
+
+DIFF Filter列:红色代表劣化
+
#### 内存占用大小
```
该算子占用的device内存大小,单位KB
diff --git a/profiler/compare_tools/comparator/op_comparator.py b/profiler/compare_tools/comparator/op_comparator.py
index 89bfc1a6921e2452e35fc2f491ad59e6ffdefea6..cb4b5bfa899ead63b995bacec5a85fa7d52575a3 100644
--- a/profiler/compare_tools/comparator/op_comparator.py
+++ b/profiler/compare_tools/comparator/op_comparator.py
@@ -90,11 +90,11 @@ class OpComparator:
root_node = TreeBuilder.build_tree(torch_op_data)
kernel_dict, memory_list = {}, []
- if not self._args.disable_operator_compare:
+ if self._args.enable_operator_compare:
kernel_dict = profiling_instance.kernel_dict
if not kernel_dict:
print(f"[WARNING] Can't find any flow event in the file: {profiling_instance.json_path}")
- if not self._args.disable_memory_compare:
+ if self._args.enable_memory_compare:
memory_list = profiling_instance.memory_list
if not memory_list:
print(f"[WARNING] Can't find any memory event in the file: {profiling_instance.file_path}")
diff --git a/profiler/compare_tools/generation/comparison_generator.py b/profiler/compare_tools/generation/comparison_generator.py
index f415262cd239cc282603520f5caaaf3c4819e2bd..fa3f9f8416ef380820321e7a477e4d8452496f1f 100644
--- a/profiler/compare_tools/generation/comparison_generator.py
+++ b/profiler/compare_tools/generation/comparison_generator.py
@@ -17,15 +17,15 @@ class ComparisonGenerator:
def create_excel(self, file_path: str):
wb = Workbook()
- if not self._args.disable_operator_compare or not self._args.disable_memory_compare:
+ if self._args.enable_operator_compare or self._args.enable_memory_compare:
op_compare_result = OpComparator(self._args).compare()
if op_compare_result:
- if not self._args.disable_operator_compare:
+ if self._args.enable_operator_compare:
OpComparisonGenerator(self._args, op_compare_result, Constant.OPERATOR_COMPARE).create_sheet(wb)
- if not self._args.disable_memory_compare:
+ if self._args.enable_memory_compare:
OpComparisonGenerator(self._args, op_compare_result, Constant.MEMORY_COMPARE).create_sheet(wb)
- if not self._args.disable_communication_compare:
+ if self._args.enable_communication_compare:
index_compare_result = IndexComparator(self._args).compare()
if not index_compare_result.empty:
CommunicationComparisonGenerator(self._args, index_compare_result).create_sheet(wb)
diff --git a/profiler/compare_tools/performance_compare.py b/profiler/compare_tools/performance_compare.py
index 885f9b44b75e02c305a027a6c201494191ea85ac..dec22a4ec28b5dfa32594e0ad9c04050b5ddea0b 100644
--- a/profiler/compare_tools/performance_compare.py
+++ b/profiler/compare_tools/performance_compare.py
@@ -12,7 +12,7 @@ from utils.constant import Constant
def performance_compare(args):
- if args.disable_profiling_compare:
+ if not args.enable_profiling_compare:
return
npu_path = ''
gpu_path = ''
@@ -30,21 +30,17 @@ def performance_compare(args):
def main():
sys.path.append(os.path.dirname(__file__))
parser = argparse.ArgumentParser(description="Compare trace of GPU and NPU")
- parser.add_argument("base_profiling_path", type=str, default='', help="base profiling file path")
- parser.add_argument("comparison_profiling_path", type=str, default='', help="comparison profiling file path")
- parser.add_argument("--disable_profiling_compare", default=False, action='store_true',
- help="不进行GPU与NPU的性能拆解")
- parser.add_argument("--disable_operator_compare", default=False, action='store_true',
- help="do not compare operator execution time")
- parser.add_argument("--disable_memory_compare", default=False, action='store_true',
- help="do not compare memory usage by operator dimensions")
- parser.add_argument("--disable_communication_compare", default=False, action='store_true',
- help="do not compare communication operator execution time")
+ parser.add_argument("base_profiling_path", type=str, default='', help="基准性能数据的文件路径")
+ parser.add_argument("comparison_profiling_path", type=str, default='', help="比较性能数据的文件路径")
+ parser.add_argument("--enable_profiling_compare", default=False, action='store_true', help="开启总体性能比较")
+ parser.add_argument("--enable_operator_compare", default=False, action='store_true', help="开启算子性能比较")
+ parser.add_argument("--enable_memory_compare", default=False, action='store_true', help="开启算子内存比较")
+ parser.add_argument("--enable_communication_compare", default=False, action='store_true', help="开启通信性能比较")
parser.add_argument("--output_path", type=str, default='', help="性能数据比对结果的存放路径")
parser.add_argument("--max_kernel_num", type=int, help="每个torch op的kernel数量限制")
parser.add_argument("--op_name_map", type=ast.literal_eval, default={},
- help="配置GPU OP与NPU OP等价的名称映射关系,以字典的形式传入")
- parser.add_argument("--use_input_shape", default=False, action='store_true', help="使用input shape作为匹配信息")
+ help="配置GPU与NPU等价的算子名称映射关系,以字典的形式传入")
+ parser.add_argument("--use_input_shape", default=False, action='store_true', help="开启算子的精准匹配")
parser.add_argument("--gpu_flow_cat", type=str, default='', help="gpu flow event的分类标识")
args = parser.parse_args()
@@ -54,14 +50,15 @@ def main():
except Exception:
print("[WARNING] Profiling failed to analyze.")
- print("[INFO] Start to compare performance data, please wait.")
- dir_path = args.output_path if args.output_path else "./"
- file_name = "performance_comparison_result_{}.xlsx".format(
- time.strftime("%Y%m%d%H%M%S", time.localtime(time.time())))
- result_file_path = os.path.realpath(os.path.join(dir_path, file_name))
+ if any([args.enable_operator_compare, args.enable_memory_compare, args.enable_communication_compare]):
+ print("[INFO] Start to compare performance data, please wait.")
+ dir_path = args.output_path if args.output_path else "./"
+ file_name = "performance_comparison_result_{}.xlsx".format(
+ time.strftime("%Y%m%d%H%M%S", time.localtime(time.time())))
+ result_file_path = os.path.realpath(os.path.join(dir_path, file_name))
- ComparisonGenerator(args).create_excel(result_file_path)
- print(f"[INFO] The comparison result file has been generated: {result_file_path}")
+ ComparisonGenerator(args).create_excel(result_file_path)
+ print(f"[INFO] The comparison result file has been generated: {result_file_path}")
if __name__ == "__main__":
diff --git a/profiler/compare_tools/utils/args_manager.py b/profiler/compare_tools/utils/args_manager.py
index eba55d72e362123011048fbcaa5cdc6977c176be..88c57b2f9e437a681243c9c33a7772cf6a4a6c23 100644
--- a/profiler/compare_tools/utils/args_manager.py
+++ b/profiler/compare_tools/utils/args_manager.py
@@ -112,6 +112,12 @@ class ArgsManager:
msg = f"Invalid param, --gpu_flow_cat exceeded the maximum value {Constant.MAX_FLOW_CAT_LEN}"
raise RuntimeError(msg)
+ if not any([self._args.enable_profiling_compare, self._args.enable_operator_compare,
+ self._args.enable_memory_compare, self._args.enable_communication_compare]):
+ self._args.enable_profiling_compare = True
+ self._args.enable_operator_compare = True
+ self._args.enable_memory_compare = True
+ self._args.enable_communication_compare = True
base_profiling_dict = self.parse_profiling_path(self._args.base_profiling_path)
comparison_profiling_dict = self.parse_profiling_path(self._args.comparison_profiling_path)
diff --git a/profiler/compare_tools/utils/compare_event.py b/profiler/compare_tools/utils/compare_event.py
index a994d8d6fc511292a349da60e529647f65434d8e..d80620a556fb276cde78b95a52f6af116b3866da 100644
--- a/profiler/compare_tools/utils/compare_event.py
+++ b/profiler/compare_tools/utils/compare_event.py
@@ -42,9 +42,12 @@ class MemoryEvent:
return self._event.get(Constant.SIZE, 0)
def get_record(self) -> list:
- if self._event.get(Constant.RELEASE_TIME):
- duration = float(self._event.get(Constant.RELEASE_TIME)) - self._event.get(Constant.ALLOCATION_TIME, 0)
- else:
+ if not self._event.get(Constant.ALLOCATION_TIME):
+ duration = Constant.NA
+ elif not self._event.get(Constant.RELEASE_TIME):
duration = Constant.NA
+ else:
+ duration = float(self._event.get(Constant.RELEASE_TIME)) - self._event.get(Constant.ALLOCATION_TIME, 0)
+
name = self._event.get(Constant.NAME, "") if self._event.get(Constant.NAME, "") else self._name
return [name, duration, self._event.get(Constant.SIZE, 0)]
diff --git a/profiler/compare_tools/utils/constant.py b/profiler/compare_tools/utils/constant.py
index 8c4d4a76810b708f276270c349f252ed57a7d01c..360c2ab44ae8f56c1708bb2c8213c357445ffcb4 100644
--- a/profiler/compare_tools/utils/constant.py
+++ b/profiler/compare_tools/utils/constant.py
@@ -66,8 +66,8 @@ class Constant(object):
OPERATOR_COMPARE = "OperatorCompare"
MEMORY_COMPARE = "MemoryCompare"
- DEFAULT_WIDTH = 25
- COLUMN_WIDTH = {OP_NAME: 45, INPUT_SHAPE + " / " + MEMORY_OP_NAME: 30, INPUT_SHAPE + " / " + KERNEL_NAME: 30}
+ DEFAULT_WIDTH = 20
+ COLUMN_WIDTH = {OP_NAME: 30, INPUT_SHAPE + " / " + MEMORY_OP_NAME: 30, INPUT_SHAPE + " / " + KERNEL_NAME: 30}
# communication
COMMUNICAT_OP = "Communication OP Name"
@@ -91,5 +91,5 @@ class Constant(object):
CMP_COMMUNICATION_HEADER = [COMMUNICAT_OP, TASK_NAME, CALLS, TOTAL_DURATION, AVG_DURATION, MAX_DURATION,
MIN_DURATION]
COLUMNS = [COMMUNICAT_OP, CALLS, TOTAL_DURATION, AVG_DURATION, MAX_DURATION, MIN_DURATION]
- COLUMN_WIDTH_CLL = {COMMUNICAT_OP: 25, TASK_NAME: 22, CALLS: 10, TOTAL_DURATION: 20, AVG_DURATION: 20,
- MAX_DURATION: 20, MIN_DURATION: 20, DIFF: 20}
+ COLUMN_WIDTH_CLL = {COMMUNICAT_OP: 22, TASK_NAME: 22, CALLS: 10, TOTAL_DURATION: 16, AVG_DURATION: 16,
+ MAX_DURATION: 16, MIN_DURATION: 16, DIFF: 16}
diff --git a/profiler/compare_tools/utils/profiling_parser.py b/profiler/compare_tools/utils/profiling_parser.py
index 8a94cb695df271c4f2d9493d948d77156a5262a0..aefcbade39ead9745741e4a95870bb1aa4da2709 100644
--- a/profiler/compare_tools/utils/profiling_parser.py
+++ b/profiler/compare_tools/utils/profiling_parser.py
@@ -1,7 +1,7 @@
from abc import ABCMeta, abstractmethod
from math import ceil
-from utils.compare_event import KernelEvent
+from utils.compare_event import KernelEvent, MemoryEvent
from utils.constant import Constant
from utils.file_reader import FileReader
from utils.trace_event_data import TraceEventData
@@ -53,7 +53,7 @@ class GPUProfilingParser(ProfilingParser):
return self._kernel_dict
@property
- def memory_list(self) -> dict:
+ def memory_list(self) -> list:
if self._memory_list is None:
self.get_memory_list()
return self._memory_list
@@ -83,13 +83,13 @@ class GPUProfilingParser(ProfilingParser):
flow_kernel_dict = {}
json_data = FileReader.read_trace_file(self._json_path)
total_events = json_data.get("traceEvents", [])
- flow_cat = self._args.gpu_flow_cat if self._args.gpu_flow_cat else "async_gpu"
+ flow_cat = (self._args.gpu_flow_cat,) if self._args.gpu_flow_cat else ("async_gpu", "async_cpu_to_gpu", "ac2g")
flow_start_dict, flow_end_dict, kernel_dict = {}, {}, {}
for event in total_events:
- if event.get("cat") == flow_cat and event.get("ph") == "s":
+ if event.get("cat") in flow_cat and event.get("ph") == "s":
flow_start_dict[event.get("id")] = event
- elif event.get("cat") == flow_cat and event.get("ph") == "f":
+ elif event.get("cat") in flow_cat and event.get("ph") == "f":
flow_end_dict[event.get("id")] = event
elif event.get("cat", "").capitalize() == "Kernel".capitalize():
kernel_dict["{}-{}-{}".format(event.get("pid"), event.get("tid"), event.get("ts"))] = event
@@ -175,7 +175,7 @@ class NPUProfilingParser(ProfilingParser):
return self._kernel_dict
@property
- def memory_list(self) -> dict:
+ def memory_list(self) -> list:
if self._memory_list is None:
self.get_memory_list()
return self._memory_list
@@ -239,6 +239,8 @@ class NPUProfilingParser(ProfilingParser):
return
memory_data = FileReader.read_csv_file(self._memory_data_path)
for data in memory_data:
+ if not data.get(Constant.ALLOCATION_TIME, 0):
+ continue
if "cann::" in data.get("Name", ""):
ts_time = float(data.get(Constant.ALLOCATION_TIME, 0))
match_dequeue_data = self._match_cann_memory_data(dequeue_data, ts_time)