diff --git a/profiler/MANIFEST.in b/profiler/MANIFEST.in new file mode 100644 index 0000000000000000000000000000000000000000..0550da458f399209a4002b47706e5d741c990af3 --- /dev/null +++ b/profiler/MANIFEST.in @@ -0,0 +1,7 @@ +recursive-include profiler/advisor/ * +recursive-include profiler/cli/ * +recursive-include profiler/prof_common/ * +recursive-include profiler/compare_tools/ * +recursive-include profiler/cluster_analyse/ * +global-exclude */__pycache__/* +global-exclude *.pyc diff --git a/profiler/cli/__init__.py b/profiler/cli/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/profiler/cli/cluster_cli.py b/profiler/cli/cluster_cli.py new file mode 100644 index 0000000000000000000000000000000000000000..62c06c2e21d54ee4540ad5e747efefabb65ed762 --- /dev/null +++ b/profiler/cli/cluster_cli.py @@ -0,0 +1,37 @@ +# Copyright (c) 2024, Huawei Technologies Co., Ltd. +# All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import click +import os +import sys + +sys.path.append( + os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "cluster_analyse")) + +from profiler.prof_common.constant import Constant +from profiler.cluster_analyse.cluster_analysis import ALL_FEATURE_LIST +from cluster_analysis import Interface + + +@click.command(context_settings=Constant.CONTEXT_SETTINGS, name="cluster", + short_help='Analyze cluster data to locate slow nodes and slow links.') +@click.option('--profiling_path', '-d', type=click.Path(), required=True, + help='path of the profiling data') +@click.option('--mode', '-m', type=click.Choice(ALL_FEATURE_LIST), default='all') +def cluster_cli(profiling_path, mode) -> None: + parameter = { + Constant.COLLECTION_PATH: profiling_path, + Constant.ANALYSIS_MODE: mode + } + Interface(parameter).run() diff --git a/profiler/cli/compare_cli.py b/profiler/cli/compare_cli.py new file mode 100644 index 0000000000000000000000000000000000000000..a781986d819542d27fe4453dd2cfd01b2046a8fd --- /dev/null +++ b/profiler/cli/compare_cli.py @@ -0,0 +1,47 @@ +# Copyright (c) 2024, Huawei Technologies Co., Ltd. +# All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import click +import os +import sys + +sys.path.append( + os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "cluster_analyse")) +sys.path.append( + os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "compare_tools")) + +from profiler.prof_common.analyze_dict import AnalyzeDict +from profiler.prof_common.constant import Constant +from compare_backend.comparison_generator import ComparisonGenerator + + +@click.command(context_settings=Constant.CONTEXT_SETTINGS, name="compare", + short_help='Compare the performance differences between GPUs and NPUs.') +@click.option('--profiling_path', '-d', 'base_profiling_path', type=click.Path(), required=True, + help='path of the profiling data') +@click.option('--benchmark_profiling_path', '-bp', 'comparison_profiling_path', type=click.Path(), required=True) +@click.option('--enable_profiling_compare', is_flag=True) +@click.option('--enable_operator_compare', is_flag=True) +@click.option('--enable_memory_compare', is_flag=True) +@click.option('--enable_communication_compare', is_flag=True) +@click.option('--output_path', '-o', 'output_path', type=click.Path()) +@click.option('--max_kernel_num', 'max_kernel_num', type=int, help="The number of kernels per torch op is limited.") +@click.option('--op_name_map', type=dict, default={}, + help="The mapping of operator names equivalent to GPUs and NPUs in the form of dictionaries.", + required=False) +@click.option('--use_input_shape', is_flag=True) +@click.option('--gpu_flow_cat', type=str, default='', help="Identifier of the GPU connection.") +def compare_cli(**kwargs) -> None: + args = AnalyzeDict(kwargs) + ComparisonGenerator(args).run() diff --git a/profiler/cli/entrance.py b/profiler/cli/entrance.py new file mode 100644 index 0000000000000000000000000000000000000000..96fc008f58f3081ac569da7825d8536003af74e3 --- /dev/null +++ b/profiler/cli/entrance.py @@ -0,0 +1,57 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright (c) 2024, Huawei Technologies Co., Ltd. +# All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import logging +import click + +from profiler.cli.cluster_cli import cluster_cli +from profiler.cli.compare_cli import compare_cli + +logger = logging.getLogger() +CONTEXT_SETTINGS = dict(help_option_names=['-H', '-h', '--help'], + max_content_width=160) + +COMMAND_PRIORITY = { + "cluster": 1, + "compare": 2 +} + + +class SpecialHelpOrder(click.Group): + + def __init__(self, *args, **kwargs): + super(SpecialHelpOrder, self).__init__(*args, **kwargs) + + def list_commands_for_help(self, ctx): + """ + reorder the list of commands when listing the help + """ + commands = super(SpecialHelpOrder, self).list_commands(ctx) + return [item[1] for item in sorted((COMMAND_PRIORITY.get(command, float('INF')), + command) for command in commands)] + + def get_help(self, ctx): + self.list_commands = self.list_commands_for_help + return super(SpecialHelpOrder, self).get_help(ctx) + + +@click.group(context_settings=CONTEXT_SETTINGS, cls=SpecialHelpOrder) +def msprof_analyze_cli(): + pass + + +msprof_analyze_cli.add_command(cluster_cli, name="cluster") +msprof_analyze_cli.add_command(compare_cli, name="compare") diff --git a/profiler/cluster_analyse/cluster_kernels_analysis/__init__.py b/profiler/cluster_analyse/cluster_kernels_analysis/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/profiler/prof_common/__init__.py b/profiler/prof_common/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/profiler/prof_common/analyze_dict.py b/profiler/prof_common/analyze_dict.py new file mode 100644 index 0000000000000000000000000000000000000000..a06577e8fb49436f7b867e8e74495cc76a6a58b2 --- /dev/null +++ b/profiler/prof_common/analyze_dict.py @@ -0,0 +1,29 @@ +# Copyright (c) 2024, Huawei Technologies Co., Ltd. +# All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +class AnalyzeDict(dict): + def __getstate__(self): + return self.__dict__ + + def __setstate__(self, d): + self.__dict__.update(d) + + def __getattr__(self, key: str): + if key not in self: + return {} + + value = self[key] + if isinstance(value, dict): + value = AnalyzeDict(value) + return value diff --git a/profiler/prof_common/constant.py b/profiler/prof_common/constant.py new file mode 100644 index 0000000000000000000000000000000000000000..5789b89cb1a248977b64839339395acc5288b2ab --- /dev/null +++ b/profiler/prof_common/constant.py @@ -0,0 +1,18 @@ +# Copyright (c) 2024, Huawei Technologies Co., Ltd. +# All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +class Constant(object): + COLLECTION_PATH = "collection_path" + ANALYSIS_MODE = "analysis_mode" + CONTEXT_SETTINGS = dict(help_option_names=['-H', '-h', '--help']) \ No newline at end of file diff --git a/profiler/requirements.txt b/profiler/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..6bf2069a60788587f8e4e0129bbc031b1d1daaaf --- /dev/null +++ b/profiler/requirements.txt @@ -0,0 +1,2 @@ +-r requirements/build.txt +-r requirements/tests.txt \ No newline at end of file diff --git a/profiler/requirements/build.txt b/profiler/requirements/build.txt new file mode 100644 index 0000000000000000000000000000000000000000..c750ff83dedf2f6b6823f45a747c95d395e1ccb5 --- /dev/null +++ b/profiler/requirements/build.txt @@ -0,0 +1,12 @@ +click +tabulate +networkx +jinja2 +PyYaml +tqdm +prettytable +ijson +requests +xlsxwriter +sqlalchemy +urllib3<2.0 \ No newline at end of file diff --git a/profiler/requirements/test.txt b/profiler/requirements/test.txt new file mode 100644 index 0000000000000000000000000000000000000000..bab89704aa267e69a0fca03d99e855d5b47f9d5b --- /dev/null +++ b/profiler/requirements/test.txt @@ -0,0 +1,5 @@ +pytest==6.2.4 +pytest-cov==2.12.0 +pytest-mock==3.6.1 +pytest-cookies==0.6.1 +mock==4.0.3 \ No newline at end of file diff --git a/profiler/setup.cfg b/profiler/setup.cfg new file mode 100644 index 0000000000000000000000000000000000000000..26f243faa5199ade53fc6f42959d475ac5539f29 --- /dev/null +++ b/profiler/setup.cfg @@ -0,0 +1,32 @@ +[isort] +line_length = 120 +multi_line_output = 0 +known_standard_library = setuptools +no_lines_before = STDLIB,LOCALFOLDER +default_section = THIRDPARTY +include_trailing_comma = true +force_grid_wrap = 0 +use_parentheses = true + +[flake8] +exclude = tests/* +max-line-length = 120 + +[pycodestyle] +max-line-length = 120 +exclude = tests/* + +[yapf] +BASED_ON_STYLE = pep8 +BLANK_LINE_BEFORE_NESTED_CLASS_OR_DEF = true +SPLIT_BEFORE_EXPRESSION_AFTER_OPENING_PAREN = true +COLUMN_LIMIT = 120 + +[aliases] +test=pytest + +[mypy] +ignore_missing_imports = True + +[mypy-tests.*] +ignore_errors = True diff --git a/profiler/setup.py b/profiler/setup.py new file mode 100644 index 0000000000000000000000000000000000000000..835164c8046909207944881da41361a3542b04cf --- /dev/null +++ b/profiler/setup.py @@ -0,0 +1,44 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +import os.path + +from setuptools import find_packages, setup # type: ignore + +extras = { + "test": [ + "pytest==6.2.4", + "pytest-cookies==0.6.1", + "pytest-cov==2.12.0", + "mock==4.0.3", + ] +} + +with open('requirements/build.txt', 'r') as f: + requires = f.read().splitlines() + +with open('requirements/test.txt', 'r') as f: + tests_requires = f.read().splitlines() +tests_requires.extend(set(requires)) + +with open('version.txt', 'r') as f: + version = f.read().strip() + +root_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), "../") +setup( + name="msprof-analyze", + version=version, + description="MindStudio Profiler Analyze Tools", + package_dir={"": root_path}, + packages=find_packages(root_path), + include_package_data=False, + python_requires='>=3.7', + install_requires=requires, + package_data={'': ['*.json', '*.ini', '*.txt', '*.yaml', '*.html']}, + tests_require=tests_requires, + entry_points=""" + [console_scripts] + msprof-analyze=profiler.cli.entrance:msprof_analyze_cli + """ +) + +# build cmd: pip install --editable . diff --git a/profiler/version.txt b/profiler/version.txt new file mode 100644 index 0000000000000000000000000000000000000000..9f8e9b69a33f4e8067d5b21661a35d8856758aba --- /dev/null +++ b/profiler/version.txt @@ -0,0 +1 @@ +1.0 \ No newline at end of file