cann-cann 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 liggest
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,126 @@
1
+ Metadata-Version: 2.4
2
+ Name: cann-cann
3
+ Version: 0.1.0
4
+ Summary: 看看环境上的 NPU 参数
5
+ Author: liggest
6
+ License-Expression: MIT
7
+ License-File: LICENSE
8
+ Requires-Python: >=3.10
9
+ Project-URL: Homepage, https://github.com/liggest/cann-cann
10
+ Project-URL: Repository, https://github.com/liggest/cann-cann
11
+ Project-URL: Issues, https://github.com/liggest/cann-cann/issues
12
+ Description-Content-Type: text/markdown
13
+
14
+ <div align="center">
15
+
16
+ # cann-cann
17
+
18
+ 一行代码快速检查环境上的 NPU 与 CANN 参数
19
+
20
+ > 看看你的 UB 有多大😎
21
+ </div>
22
+
23
+ ## 安装
24
+
25
+ ```bash
26
+ pip install cann-cann
27
+ ```
28
+
29
+ > 支持 Python >= 3.10;需要安装 CANN 并加载环境变量
30
+
31
+ ## Python 接口
32
+
33
+ ```python
34
+ import cann_cann
35
+
36
+ cann_cann.ub_size() # 253952
37
+ cann_cann.ub_size(unit="kb") # 248
38
+ cann_cann.memory_size(unit="gb") # 128
39
+
40
+ cann_cann.npu_name() # "Ascend950PR_XXXX"
41
+ cann_cann.all_npu_names() # ["Ascend910A",...]
42
+
43
+ cann_cann.memory_size(npu_name="Ascend910_9392", unit="gb") # 64
44
+ ```
45
+
46
+ 也可先获取单独的配置对象,方便连续读取同一份配置中的多个参数:
47
+
48
+ ```python
49
+ config = cann_cann.platform_config(npu_name="Ascend910B2")
50
+
51
+ config.ub_size(unit="kb") # 192
52
+ config.l1_size(unit="kb") # 512
53
+ config.memory_size(unit="gb") # 64
54
+
55
+ # 读取配置文件中的原始值
56
+ config.ai_core_spec.ub_size # 196608
57
+ config.soc_info.ai_core_count # 24
58
+ config.soc_info.memory_size # 68719476736
59
+ ```
60
+
61
+ ## 命令行
62
+
63
+ 提供 `cann-cann` 与作为简称的 `ccann` 命令,使用任一命令均可
64
+
65
+ ```bash
66
+ cann-cann UB # 253952
67
+ cann-cann UB --kb # 248
68
+ ccann L0A --unit kb # 64
69
+ ccann L1 --kb --npu-name Ascend910_9392 # 512
70
+ ccann ai-cores # 24
71
+ ccann memory --gb # 128
72
+
73
+ cann-cann npu-name # Ascend950PR_XXXX
74
+ ccann all-npu-names # Ascend910A...
75
+ ```
76
+
77
+ > 查询名称不区分大小写
78
+ >
79
+ > 当前支持 `UB`、`L0A`、`L0B`、`L0C`、`L1`、`L2`、`memory` 和 `ai-cores`
80
+
81
+ <!-- ## 单位
82
+
83
+ 平台配置中的存储空间大小以字节记录,Section 的类型化属性会原样返回这些值
84
+ 便利函数和方法默认使用 `unit="bytes"`
85
+
86
+ 为符合常见加速器工具的使用习惯,本项目中的 `kb` 和 `kib` 都表示 1024 字节,
87
+ `mb` 和 `mib` 都表示 1024² 字节。CLI 可使用 `--kb`、`--kib`、`--mb` 和
88
+ `--mib` 快速选择单位
89
+
90
+ ## 异常处理
91
+
92
+ 找不到 CANN、无法探测 NPU 名称或无法读取平台配置时,Python API 会抛出
93
+ `CannCannError` 的子类。一般情况下只需捕获这个基类;需要采取不同恢复措施时,
94
+ 可以进一步捕获 `NPUDetectionError` 或 `PlatformConfigNotFoundError`。不支持的单位
95
+ 等参数错误使用标准 `ValueError`
96
+
97
+ CLI 会简洁地显示可预期的错误,并以非零状态码退出。如果无法自动探测 NPU,
98
+ 可显式指定名称:
99
+
100
+ ```bash
101
+ cann-cann UB --npu-name Ascend910_9392
102
+ ``` -->
103
+
104
+ ## 参与开发
105
+
106
+ 参与开发时,先安装开发依赖并启用提交前检查:
107
+
108
+ ```bash
109
+ uv sync --dev
110
+ uv run pre-commit install
111
+ ```
112
+
113
+ 启用后,每次提交前都会使用 Ruff 自动检查并格式化 Python 代码。也可手动检查所有文件:
114
+
115
+ ```bash
116
+ uv run pre-commit run --all-files
117
+ ```
118
+
119
+ 完整检查、测试与构建:
120
+
121
+ ```bash
122
+ uv run ruff format --check .
123
+ uv run ruff check .
124
+ uv run pytest
125
+ uv build
126
+ ```
@@ -0,0 +1,113 @@
1
+ <div align="center">
2
+
3
+ # cann-cann
4
+
5
+ 一行代码快速检查环境上的 NPU 与 CANN 参数
6
+
7
+ > 看看你的 UB 有多大😎
8
+ </div>
9
+
10
+ ## 安装
11
+
12
+ ```bash
13
+ pip install cann-cann
14
+ ```
15
+
16
+ > 支持 Python >= 3.10;需要安装 CANN 并加载环境变量
17
+
18
+ ## Python 接口
19
+
20
+ ```python
21
+ import cann_cann
22
+
23
+ cann_cann.ub_size() # 253952
24
+ cann_cann.ub_size(unit="kb") # 248
25
+ cann_cann.memory_size(unit="gb") # 128
26
+
27
+ cann_cann.npu_name() # "Ascend950PR_XXXX"
28
+ cann_cann.all_npu_names() # ["Ascend910A",...]
29
+
30
+ cann_cann.memory_size(npu_name="Ascend910_9392", unit="gb") # 64
31
+ ```
32
+
33
+ 也可先获取单独的配置对象,方便连续读取同一份配置中的多个参数:
34
+
35
+ ```python
36
+ config = cann_cann.platform_config(npu_name="Ascend910B2")
37
+
38
+ config.ub_size(unit="kb") # 192
39
+ config.l1_size(unit="kb") # 512
40
+ config.memory_size(unit="gb") # 64
41
+
42
+ # 读取配置文件中的原始值
43
+ config.ai_core_spec.ub_size # 196608
44
+ config.soc_info.ai_core_count # 24
45
+ config.soc_info.memory_size # 68719476736
46
+ ```
47
+
48
+ ## 命令行
49
+
50
+ 提供 `cann-cann` 与作为简称的 `ccann` 命令,使用任一命令均可
51
+
52
+ ```bash
53
+ cann-cann UB # 253952
54
+ cann-cann UB --kb # 248
55
+ ccann L0A --unit kb # 64
56
+ ccann L1 --kb --npu-name Ascend910_9392 # 512
57
+ ccann ai-cores # 24
58
+ ccann memory --gb # 128
59
+
60
+ cann-cann npu-name # Ascend950PR_XXXX
61
+ ccann all-npu-names # Ascend910A...
62
+ ```
63
+
64
+ > 查询名称不区分大小写
65
+ >
66
+ > 当前支持 `UB`、`L0A`、`L0B`、`L0C`、`L1`、`L2`、`memory` 和 `ai-cores`
67
+
68
+ <!-- ## 单位
69
+
70
+ 平台配置中的存储空间大小以字节记录,Section 的类型化属性会原样返回这些值
71
+ 便利函数和方法默认使用 `unit="bytes"`
72
+
73
+ 为符合常见加速器工具的使用习惯,本项目中的 `kb` 和 `kib` 都表示 1024 字节,
74
+ `mb` 和 `mib` 都表示 1024² 字节。CLI 可使用 `--kb`、`--kib`、`--mb` 和
75
+ `--mib` 快速选择单位
76
+
77
+ ## 异常处理
78
+
79
+ 找不到 CANN、无法探测 NPU 名称或无法读取平台配置时,Python API 会抛出
80
+ `CannCannError` 的子类。一般情况下只需捕获这个基类;需要采取不同恢复措施时,
81
+ 可以进一步捕获 `NPUDetectionError` 或 `PlatformConfigNotFoundError`。不支持的单位
82
+ 等参数错误使用标准 `ValueError`
83
+
84
+ CLI 会简洁地显示可预期的错误,并以非零状态码退出。如果无法自动探测 NPU,
85
+ 可显式指定名称:
86
+
87
+ ```bash
88
+ cann-cann UB --npu-name Ascend910_9392
89
+ ``` -->
90
+
91
+ ## 参与开发
92
+
93
+ 参与开发时,先安装开发依赖并启用提交前检查:
94
+
95
+ ```bash
96
+ uv sync --dev
97
+ uv run pre-commit install
98
+ ```
99
+
100
+ 启用后,每次提交前都会使用 Ruff 自动检查并格式化 Python 代码。也可手动检查所有文件:
101
+
102
+ ```bash
103
+ uv run pre-commit run --all-files
104
+ ```
105
+
106
+ 完整检查、测试与构建:
107
+
108
+ ```bash
109
+ uv run ruff format --check .
110
+ uv run ruff check .
111
+ uv run pytest
112
+ uv build
113
+ ```
@@ -0,0 +1,50 @@
1
+ [project]
2
+ name = "cann-cann"
3
+ version = "0.1.0"
4
+ description = "看看环境上的 NPU 参数"
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ license = "MIT"
8
+ license-files = ["LICENSE"]
9
+ dependencies = []
10
+
11
+ [[project.authors]]
12
+ name = "liggest"
13
+
14
+ [project.urls]
15
+ Homepage = "https://github.com/liggest/cann-cann"
16
+ Repository = "https://github.com/liggest/cann-cann"
17
+ Issues = "https://github.com/liggest/cann-cann/issues"
18
+
19
+ [project.scripts]
20
+ cann-cann = "cann_cann.cli:main"
21
+ ccann = "cann_cann.cli:main"
22
+
23
+ [dependency-groups]
24
+ dev = [
25
+ "pre-commit>=4.6.2",
26
+ "pytest>=8.0",
27
+ "ruff>=0.8",
28
+ ]
29
+
30
+ [tool.pytest.ini_options]
31
+ testpaths = ["tests"]
32
+
33
+ [tool.ruff]
34
+ target-version = "py310"
35
+ line-length = 140
36
+ exclude = [".agents"]
37
+
38
+ [tool.ruff.lint]
39
+ select = [
40
+ "E",
41
+ "F",
42
+ "I",
43
+ "UP",
44
+ "B",
45
+ "SIM",
46
+ ]
47
+
48
+ [build-system]
49
+ requires = ["uv_build>=0.12.2,<0.13.0"]
50
+ build-backend = "uv_build"
@@ -0,0 +1,43 @@
1
+ [project]
2
+ name = "cann-cann"
3
+ version = "0.1.0"
4
+ description = "看看环境上的 NPU 参数"
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ license = "MIT"
8
+ license-files = ["LICENSE"]
9
+ dependencies = []
10
+
11
+ [[project.authors]]
12
+ name = "liggest"
13
+
14
+ [project.urls]
15
+ Homepage = "https://github.com/liggest/cann-cann"
16
+ Repository = "https://github.com/liggest/cann-cann"
17
+ Issues = "https://github.com/liggest/cann-cann/issues"
18
+
19
+ [project.scripts]
20
+ cann-cann = "cann_cann.cli:main"
21
+ ccann = "cann_cann.cli:main"
22
+
23
+ [dependency-groups]
24
+ dev = [
25
+ "pre-commit>=4.6.2",
26
+ "pytest>=8.0",
27
+ "ruff>=0.8",
28
+ ]
29
+
30
+ [tool.pytest.ini_options]
31
+ testpaths = ["tests"]
32
+
33
+ [tool.ruff]
34
+ target-version = "py310"
35
+ line-length = 140
36
+ exclude = [".agents"]
37
+
38
+ [tool.ruff.lint]
39
+ select = ["E", "F", "I", "UP", "B", "SIM"]
40
+
41
+ [build-system]
42
+ requires = ["uv_build>=0.12.2,<0.13.0"]
43
+ build-backend = "uv_build"
@@ -0,0 +1,149 @@
1
+ """看看环境上的 NPU 参数"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from functools import cache, lru_cache
6
+ from pathlib import Path
7
+ from typing import TYPE_CHECKING
8
+
9
+ from . import environment as _environment
10
+ from .config import (
11
+ AICoreSpecSection,
12
+ CANNPlatformConfig,
13
+ PlatformConfigSection,
14
+ SoCInfoSection,
15
+ VersionSection,
16
+ )
17
+ from .errors import (
18
+ CannCannError,
19
+ CANNNotFoundError,
20
+ NPUDetectionError,
21
+ PlatformConfigError,
22
+ PlatformConfigNotFoundError,
23
+ )
24
+ from .units import SizeUnit
25
+
26
+ if TYPE_CHECKING:
27
+ __version__: str
28
+
29
+
30
+ def __getattr__(name: str) -> str:
31
+ """按需读取安装包版本,首次读取后缓存结果"""
32
+
33
+ if name != "__version__":
34
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
35
+
36
+ from importlib.metadata import version
37
+
38
+ package_version = version("cann-cann")
39
+ globals()[name] = package_version
40
+ return package_version
41
+
42
+
43
+ @lru_cache(maxsize=1)
44
+ def _default_platform_config_directory() -> Path:
45
+ cann_home = _environment.find_cann_home()
46
+ return _environment.platform_config_directory(cann_home)
47
+
48
+
49
+ @cache
50
+ def _platform_config_by_name(npu_name: str) -> CANNPlatformConfig:
51
+ if not npu_name or Path(npu_name).name != npu_name:
52
+ raise ValueError(f"NPU 名称无效:{npu_name!r}")
53
+ path = _default_platform_config_directory() / f"{npu_name}.ini"
54
+ return CANNPlatformConfig.from_file(path)
55
+
56
+
57
+ def npu_name() -> str:
58
+ """返回 ACL 探测到的 NPU 名称,并缓存首次成功的结果"""
59
+
60
+ return _environment.detect_npu_name()
61
+
62
+
63
+ def all_npu_names() -> list[str]:
64
+ """列出当前 CANN 安装中包含平台配置的 NPU 名称"""
65
+
66
+ directory = _default_platform_config_directory()
67
+ return sorted(path.stem for path in directory.glob("*.ini") if path.is_file())
68
+
69
+
70
+ def platform_config(npu_name: str | None = None) -> CANNPlatformConfig:
71
+ """返回 *npu_name* 对应的平台配置;不指定时自动探测 NPU 名称"""
72
+
73
+ resolved_name = _environment.detect_npu_name() if npu_name is None else npu_name.strip()
74
+ return _platform_config_by_name(resolved_name)
75
+
76
+
77
+ def ub_size(*, npu_name: str | None = None, unit: SizeUnit | str = "bytes") -> int | float:
78
+ """返回指定 NPU 的 AI Core UB 大小"""
79
+
80
+ return platform_config(npu_name).ub_size(unit)
81
+
82
+
83
+ def l0_a_size(*, npu_name: str | None = None, unit: SizeUnit | str = "bytes") -> int | float:
84
+ """返回指定 NPU 的 L0A 大小"""
85
+
86
+ return platform_config(npu_name).l0_a_size(unit)
87
+
88
+
89
+ def l0_b_size(*, npu_name: str | None = None, unit: SizeUnit | str = "bytes") -> int | float:
90
+ """返回指定 NPU 的 L0B 大小"""
91
+
92
+ return platform_config(npu_name).l0_b_size(unit)
93
+
94
+
95
+ def l0_c_size(*, npu_name: str | None = None, unit: SizeUnit | str = "bytes") -> int | float:
96
+ """返回指定 NPU 的 L0C 大小"""
97
+
98
+ return platform_config(npu_name).l0_c_size(unit)
99
+
100
+
101
+ def l1_size(*, npu_name: str | None = None, unit: SizeUnit | str = "bytes") -> int | float:
102
+ """返回指定 NPU 的 L1 大小"""
103
+
104
+ return platform_config(npu_name).l1_size(unit)
105
+
106
+
107
+ def l2_size(*, npu_name: str | None = None, unit: SizeUnit | str = "bytes") -> int | float:
108
+ """返回指定 NPU 的 L2 大小"""
109
+
110
+ return platform_config(npu_name).l2_size(unit)
111
+
112
+
113
+ def memory_size(*, npu_name: str | None = None, unit: SizeUnit | str = "bytes") -> int | float:
114
+ """返回指定 NPU 的显存大小"""
115
+
116
+ return platform_config(npu_name).memory_size(unit)
117
+
118
+
119
+ def ai_core_count(*, npu_name: str | None = None) -> int:
120
+ """返回指定 NPU 的 AI Core 数量"""
121
+
122
+ return platform_config(npu_name).ai_core_count()
123
+
124
+
125
+ __all__ = [
126
+ "__version__",
127
+ "AICoreSpecSection",
128
+ "CANNNotFoundError",
129
+ "CANNPlatformConfig",
130
+ "CannCannError",
131
+ "NPUDetectionError",
132
+ "PlatformConfigError",
133
+ "PlatformConfigNotFoundError",
134
+ "PlatformConfigSection",
135
+ "SizeUnit",
136
+ "SoCInfoSection",
137
+ "VersionSection",
138
+ "ai_core_count",
139
+ "all_npu_names",
140
+ "l0_a_size",
141
+ "l0_b_size",
142
+ "l0_c_size",
143
+ "l1_size",
144
+ "l2_size",
145
+ "memory_size",
146
+ "npu_name",
147
+ "platform_config",
148
+ "ub_size",
149
+ ]
@@ -0,0 +1,135 @@
1
+ """cann-cann 命令行入口"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import sys
7
+ from collections.abc import Sequence
8
+ from pathlib import Path
9
+ from typing import TypeAlias
10
+
11
+ from .errors import CannCannError
12
+
13
+ _SIZE_QUERIES = {
14
+ "l0_a": "l0_a_size",
15
+ "l0a": "l0_a_size",
16
+ "l0_b": "l0_b_size",
17
+ "l0b": "l0_b_size",
18
+ "l0_c": "l0_c_size",
19
+ "l0c": "l0_c_size",
20
+ "l1": "l1_size",
21
+ "l2": "l2_size",
22
+ "hbm": "memory_size",
23
+ "memory": "memory_size",
24
+ "ub": "ub_size",
25
+ }
26
+ _COUNT_QUERIES = {
27
+ "ai_core_count": "ai_core_count",
28
+ "ai_cores": "ai_core_count",
29
+ "cores": "ai_core_count",
30
+ }
31
+ _CLIResult: TypeAlias = str | list[str] | int | float
32
+
33
+
34
+ class _VersionAction(argparse.Action):
35
+ def __init__(self, option_strings: Sequence[str], dest: str, **kwargs) -> None:
36
+ super().__init__(option_strings, dest, nargs=0, **kwargs)
37
+
38
+ def __call__(
39
+ self,
40
+ parser: argparse.ArgumentParser,
41
+ namespace: argparse.Namespace,
42
+ values: object,
43
+ option_string: str | None = None,
44
+ ) -> None:
45
+ import cann_cann
46
+
47
+ print(f"{parser.prog} {cann_cann.__version__}")
48
+ parser.exit()
49
+
50
+
51
+ def _parser(prog: str = "cann-cann") -> argparse.ArgumentParser:
52
+ parser = argparse.ArgumentParser(
53
+ prog=prog,
54
+ description="看看环境上的 NPU 参数",
55
+ epilog="AI Core 数量可用 AI_CORE_COUNT、cores 或 ai-cores 查询",
56
+ add_help=False,
57
+ )
58
+ parser._positionals.title = "位置参数"
59
+ parser._optionals.title = "选项"
60
+ parser.add_argument("-h", "--help", action="help", help="显示帮助信息并退出")
61
+ parser.add_argument("-v", "--version", action=_VersionAction, help="显示版本信息并退出")
62
+ parser.add_argument(
63
+ "query",
64
+ help="例如 UB、L1、memory、cores、npu-name 或 all-npu-names",
65
+ )
66
+ parser.add_argument(
67
+ "--npu-name",
68
+ metavar="NAME",
69
+ help="读取指定 NPU 名称的配置,不再自动探测",
70
+ )
71
+ units = parser.add_mutually_exclusive_group()
72
+ units.add_argument(
73
+ "--unit",
74
+ choices=("bytes", "kb", "kib", "mb", "mib", "gb", "gib"),
75
+ help="存储空间大小的输出单位(默认:bytes)",
76
+ )
77
+ units.add_argument("--kb", action="store_const", const="kb", dest="short_unit", help="以 kb 输出")
78
+ units.add_argument("--kib", action="store_const", const="kib", dest="short_unit", help="同 --kb")
79
+ units.add_argument("--mb", action="store_const", const="mb", dest="short_unit", help="以 mb 输出")
80
+ units.add_argument("--mib", action="store_const", const="mib", dest="short_unit", help="同 --mb")
81
+ units.add_argument("--gb", action="store_const", const="gb", dest="short_unit", help="以 gb 输出")
82
+ units.add_argument("--gib", action="store_const", const="gib", dest="short_unit", help="同 --gb")
83
+ return parser
84
+
85
+
86
+ def _run(args: argparse.Namespace) -> _CLIResult:
87
+ # 运行时导入,确保 CLI 始终通过公开 Python API 完成查询
88
+ import cann_cann
89
+
90
+ query = args.query.lower().replace("-", "_")
91
+ unit = args.unit or args.short_unit or "bytes"
92
+
93
+ if query == "npu_name":
94
+ if args.npu_name is not None:
95
+ raise ValueError("查询 npu-name 时不能使用 --npu-name")
96
+ return cann_cann.npu_name()
97
+ if query == "all_npu_names":
98
+ if args.npu_name is not None:
99
+ raise ValueError("查询 all-npu-names 时不能使用 --npu-name")
100
+ return cann_cann.all_npu_names()
101
+ if query in _SIZE_QUERIES:
102
+ function = getattr(cann_cann, _SIZE_QUERIES[query])
103
+ return function(npu_name=args.npu_name, unit=unit)
104
+ if query in _COUNT_QUERIES:
105
+ if args.unit is not None or args.short_unit is not None:
106
+ raise ValueError("单位选项只能用于查询存储空间大小")
107
+ function = getattr(cann_cann, _COUNT_QUERIES[query])
108
+ return function(npu_name=args.npu_name)
109
+
110
+ raise ValueError(f"未知的查询项:{args.query}")
111
+
112
+
113
+ def _print_result(result: _CLIResult) -> None:
114
+ if isinstance(result, list):
115
+ for item in result:
116
+ print(item)
117
+ return
118
+ print(result)
119
+
120
+
121
+ def main(argv: Sequence[str] | None = None) -> int:
122
+ """运行命令行程序并返回退出码"""
123
+ prog = Path(sys.argv[0]).name if argv is None else "cann-cann"
124
+ parser = _parser(prog)
125
+ args = parser.parse_args(argv)
126
+ try:
127
+ _print_result(_run(args))
128
+ except (CannCannError, ValueError) as error:
129
+ print(f"错误:{error}", file=sys.stderr)
130
+ return 2
131
+ return 0
132
+
133
+
134
+ if __name__ == "__main__": # pragma: no cover
135
+ raise SystemExit(main())
@@ -0,0 +1,285 @@
1
+ """CANN 平台配置文件的只读对象模型"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import errno
6
+ import os
7
+ from collections.abc import Iterator, Mapping
8
+ from configparser import ConfigParser
9
+ from configparser import Error as ConfigParserError
10
+ from dataclasses import dataclass, field
11
+ from pathlib import Path
12
+ from types import MappingProxyType
13
+
14
+ from .errors import (
15
+ PlatformConfigError,
16
+ PlatformConfigNotFoundError,
17
+ )
18
+ from .units import SizeUnit, convert_size
19
+
20
+
21
+ def _optional_text(values: Mapping[str, str], key: str) -> str | None:
22
+ value = values.get(key)
23
+ return value if value not in (None, "") else None
24
+
25
+
26
+ def _optional_int(values: Mapping[str, str], key: str) -> int | None:
27
+ value = _optional_text(values, key)
28
+ return None if value is None else int(value, 0)
29
+
30
+
31
+ @dataclass(frozen=True, slots=True, kw_only=True)
32
+ class PlatformConfigSection:
33
+ """一个 INI Section 的只读快照
34
+
35
+ 已知的 Section 通过子类提供带类型提示的属性;同时保留完整原始数据,使新版
36
+ CANN 新增的字段在公共数据模型尚未适配时仍然可以读取
37
+ """
38
+
39
+ name: str
40
+ _raw: Mapping[str, str] = field(repr=False, compare=False)
41
+
42
+ def __post_init__(self) -> None:
43
+ object.__setattr__(self, "_raw", MappingProxyType(dict(self._raw)))
44
+
45
+ def raw(self, name: str) -> str | None:
46
+ """返回未经转换的 INI 字符串;字段不存在时返回 ``None``"""
47
+
48
+ return self._raw.get(name)
49
+
50
+ def raw_items(self) -> Mapping[str, str]:
51
+ """返回该 Section 未经转换的只读键值映射"""
52
+
53
+ return self._raw
54
+
55
+ def __iter__(self) -> Iterator[str]:
56
+ return iter(self._raw)
57
+
58
+ def __len__(self) -> int:
59
+ return len(self._raw)
60
+
61
+
62
+ @dataclass(frozen=True, slots=True, kw_only=True)
63
+ class VersionSection(PlatformConfigSection):
64
+ """``[version]`` 中的常用类型化字段"""
65
+
66
+ soc_version: str
67
+ short_soc_version: str | None
68
+ npu_arch: int | None
69
+
70
+
71
+ @dataclass(frozen=True, slots=True, kw_only=True)
72
+ class SoCInfoSection(PlatformConfigSection):
73
+ """``[SoCInfo]`` 中的常用类型化字段"""
74
+
75
+ ai_core_count: int | None
76
+ cube_core_count: int | None
77
+ vector_core_count: int | None
78
+ memory_size: int | None
79
+ l2_size: int | None
80
+
81
+
82
+ @dataclass(frozen=True, slots=True, kw_only=True)
83
+ class AICoreSpecSection(PlatformConfigSection):
84
+ """``[AICoreSpec]`` 中的常用类型化字段"""
85
+
86
+ l0_a_size: int | None
87
+ l0_b_size: int | None
88
+ l0_c_size: int | None
89
+ l1_size: int | None
90
+ ub_size: int | None
91
+ ub_block_size: int | None
92
+
93
+
94
+ def _version_section(name: str, raw: Mapping[str, str]) -> VersionSection:
95
+ soc_version = _optional_text(raw, "SoC_version")
96
+ if soc_version is None:
97
+ raise PlatformConfigError("[version] 缺少必要字段 SoC_version")
98
+ return VersionSection(
99
+ name=name,
100
+ _raw=raw,
101
+ soc_version=soc_version,
102
+ short_soc_version=_optional_text(raw, "Short_SoC_version"),
103
+ npu_arch=_optional_int(raw, "NpuArch"),
104
+ )
105
+
106
+
107
+ def _soc_info_section(name: str, raw: Mapping[str, str]) -> SoCInfoSection:
108
+ return SoCInfoSection(
109
+ name=name,
110
+ _raw=raw,
111
+ ai_core_count=_optional_int(raw, "ai_core_cnt"),
112
+ cube_core_count=_optional_int(raw, "cube_core_cnt"),
113
+ vector_core_count=_optional_int(raw, "vector_core_cnt"),
114
+ memory_size=_optional_int(raw, "memory_size"),
115
+ l2_size=_optional_int(raw, "l2_size"),
116
+ )
117
+
118
+
119
+ def _ai_core_spec_section(name: str, raw: Mapping[str, str]) -> AICoreSpecSection:
120
+ return AICoreSpecSection(
121
+ name=name,
122
+ _raw=raw,
123
+ l0_a_size=_optional_int(raw, "l0_a_size"),
124
+ l0_b_size=_optional_int(raw, "l0_b_size"),
125
+ l0_c_size=_optional_int(raw, "l0_c_size"),
126
+ l1_size=_optional_int(raw, "l1_size"),
127
+ ub_size=_optional_int(raw, "ub_size"),
128
+ ub_block_size=_optional_int(raw, "ubblock_size"),
129
+ )
130
+
131
+
132
+ @dataclass(frozen=True, slots=True, kw_only=True)
133
+ class CANNPlatformConfig:
134
+ """一份 CANN 平台配置的只读类型化视图"""
135
+
136
+ version: VersionSection
137
+ soc_info: SoCInfoSection
138
+ ai_core_spec: AICoreSpecSection
139
+ _sections: Mapping[str, PlatformConfigSection] = field(repr=False, compare=False)
140
+
141
+ def __post_init__(self) -> None:
142
+ sections = MappingProxyType(dict(self._sections))
143
+ object.__setattr__(self, "_sections", sections)
144
+ if sections.get("version") is not self.version:
145
+ raise ValueError("version 必须引用已注册的 [version] Section")
146
+ if sections.get("SoCInfo") is not self.soc_info:
147
+ raise ValueError("soc_info 必须引用已注册的 [SoCInfo] Section")
148
+ if sections.get("AICoreSpec") is not self.ai_core_spec:
149
+ raise ValueError("ai_core_spec 必须引用已注册的 [AICoreSpec] Section")
150
+
151
+ @property
152
+ def npu_name(self) -> str:
153
+ """CANN 在 ``SoC_version`` 中记录的 NPU 名称"""
154
+
155
+ return self.version.soc_version
156
+
157
+ @classmethod
158
+ def from_file(cls, path: str | Path) -> CANNPlatformConfig:
159
+ """解析 *path* 指向的 INI 文件,返回只读平台配置"""
160
+
161
+ config_path = Path(path)
162
+ if not config_path.is_file():
163
+ raise PlatformConfigNotFoundError(
164
+ errno.ENOENT,
165
+ "找不到平台配置文件",
166
+ os.fspath(config_path),
167
+ )
168
+
169
+ parser = ConfigParser(
170
+ interpolation=None,
171
+ strict=True,
172
+ empty_lines_in_values=False,
173
+ )
174
+ parser.optionxform = str
175
+ try:
176
+ with config_path.open(encoding="utf-8") as stream:
177
+ parser.read_file(stream)
178
+ except FileNotFoundError as error:
179
+ raise PlatformConfigNotFoundError(
180
+ errno.ENOENT,
181
+ "找不到平台配置文件",
182
+ os.fspath(config_path),
183
+ ) from error
184
+ except ConfigParserError as error:
185
+ raise PlatformConfigError(f"无法解析平台配置 {config_path}:{error}") from error
186
+
187
+ raw_sections = {name: dict(parser.items(name, raw=True)) for name in parser.sections()}
188
+ missing = [name for name in ("version", "SoCInfo", "AICoreSpec") if name not in raw_sections]
189
+ if missing:
190
+ names = ", ".join(f"[{name}]" for name in missing)
191
+ raise PlatformConfigError(f"平台配置缺少必要的 Section:{names}")
192
+
193
+ sections: dict[str, PlatformConfigSection] = {}
194
+ try:
195
+ for name, raw in raw_sections.items():
196
+ if name == "version":
197
+ section = _version_section(name, raw)
198
+ elif name == "SoCInfo":
199
+ section = _soc_info_section(name, raw)
200
+ elif name == "AICoreSpec":
201
+ section = _ai_core_spec_section(name, raw)
202
+ else:
203
+ section = PlatformConfigSection(name=name, _raw=raw)
204
+ sections[name] = section
205
+ except PlatformConfigError:
206
+ raise
207
+ except ValueError as error:
208
+ raise PlatformConfigError(f"平台配置 {config_path} 中存在无效值:{error}") from error
209
+
210
+ version = sections["version"]
211
+ soc_info = sections["SoCInfo"]
212
+ ai_core_spec = sections["AICoreSpec"]
213
+ assert isinstance(version, VersionSection)
214
+ assert isinstance(soc_info, SoCInfoSection)
215
+ assert isinstance(ai_core_spec, AICoreSpecSection)
216
+ return cls(
217
+ version=version,
218
+ soc_info=soc_info,
219
+ ai_core_spec=ai_core_spec,
220
+ _sections=sections,
221
+ )
222
+
223
+ def raw_section(self, name: str) -> Mapping[str, str] | None:
224
+ """返回 *name* 对应的原始只读映射;Section 不存在时返回 ``None``"""
225
+
226
+ section = self._sections.get(name)
227
+ return None if section is None else section.raw_items()
228
+
229
+ def section(self, name: str) -> PlatformConfigSection | None:
230
+ """按 INI 中的原始名称返回 Section;不存在时返回 ``None``"""
231
+
232
+ return self._sections.get(name)
233
+
234
+ @staticmethod
235
+ def _required_size(value: int | None, field_name: str) -> int:
236
+ if value is None:
237
+ raise PlatformConfigError(f"当前平台配置中没有字段 {field_name}")
238
+ return value
239
+
240
+ def ub_size(self, unit: SizeUnit | str = "bytes") -> int | float:
241
+ """按指定单位返回 AI Core UB 大小"""
242
+
243
+ value = self._required_size(self.ai_core_spec.ub_size, "AICoreSpec.ub_size")
244
+ return convert_size(value, unit)
245
+
246
+ def l0_a_size(self, unit: SizeUnit | str = "bytes") -> int | float:
247
+ """按指定单位返回 L0A 大小"""
248
+
249
+ value = self._required_size(self.ai_core_spec.l0_a_size, "AICoreSpec.l0_a_size")
250
+ return convert_size(value, unit)
251
+
252
+ def l0_b_size(self, unit: SizeUnit | str = "bytes") -> int | float:
253
+ """按指定单位返回 L0B 大小"""
254
+
255
+ value = self._required_size(self.ai_core_spec.l0_b_size, "AICoreSpec.l0_b_size")
256
+ return convert_size(value, unit)
257
+
258
+ def l0_c_size(self, unit: SizeUnit | str = "bytes") -> int | float:
259
+ """按指定单位返回 L0C 大小"""
260
+
261
+ value = self._required_size(self.ai_core_spec.l0_c_size, "AICoreSpec.l0_c_size")
262
+ return convert_size(value, unit)
263
+
264
+ def l1_size(self, unit: SizeUnit | str = "bytes") -> int | float:
265
+ """按指定单位返回 L1 大小"""
266
+
267
+ value = self._required_size(self.ai_core_spec.l1_size, "AICoreSpec.l1_size")
268
+ return convert_size(value, unit)
269
+
270
+ def l2_size(self, unit: SizeUnit | str = "bytes") -> int | float:
271
+ """按指定单位返回 L2 大小"""
272
+
273
+ value = self._required_size(self.soc_info.l2_size, "SoCInfo.l2_size")
274
+ return convert_size(value, unit)
275
+
276
+ def memory_size(self, unit: SizeUnit | str = "bytes") -> int | float:
277
+ """按指定单位返回 NPU 显存大小"""
278
+
279
+ value = self._required_size(self.soc_info.memory_size, "SoCInfo.memory_size")
280
+ return convert_size(value, unit)
281
+
282
+ def ai_core_count(self) -> int:
283
+ """返回 AI Core 数量"""
284
+
285
+ return self._required_size(self.soc_info.ai_core_count, "SoCInfo.ai_core_cnt")
@@ -0,0 +1,95 @@
1
+ """发现当前 CANN 安装及其中的平台配置"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import importlib
6
+ import os
7
+ import platform
8
+ from collections.abc import Mapping
9
+ from functools import lru_cache
10
+ from pathlib import Path
11
+
12
+ from .errors import CANNNotFoundError, NPUDetectionError
13
+
14
+ _CANN_HOME_VARIABLES = (
15
+ "ASCEND_HOME_PATH",
16
+ "ASCEND_TOOLKIT_HOME",
17
+ "ASCEND_TOOLKIT_LATEST_HOME",
18
+ )
19
+ _DEFAULT_CANN_HOMES = (Path("/usr/local/Ascend/cann"),)
20
+ _ARCHITECTURES = {
21
+ "aarch64": "aarch64",
22
+ "arm64": "aarch64",
23
+ "x86_64": "x86_64",
24
+ "amd64": "x86_64",
25
+ }
26
+
27
+
28
+ def normalize_architecture(machine: str | None = None) -> str:
29
+ """返回 CANN 安装路径中使用的架构名称"""
30
+
31
+ detected = platform.machine() if machine is None else machine
32
+ try:
33
+ return _ARCHITECTURES[detected.strip().lower()]
34
+ except KeyError as exc:
35
+ raise CANNNotFoundError(f"CANN 不支持当前主机架构:{detected!r}") from exc
36
+
37
+
38
+ def platform_config_directory(cann_home: str | os.PathLike[str], *, machine: str | None = None) -> Path:
39
+ """返回 CANN 安装中的平台配置目录"""
40
+
41
+ arch = normalize_architecture(machine)
42
+ return Path(cann_home).expanduser() / f"{arch}-linux" / "data" / "platform_config"
43
+
44
+
45
+ def find_cann_home(
46
+ *,
47
+ environ: Mapping[str, str] | None = None,
48
+ machine: str | None = None,
49
+ default_homes: tuple[Path, ...] = _DEFAULT_CANN_HOMES,
50
+ ) -> Path:
51
+ """按约定的环境变量优先级查找可用的 CANN 根目录"""
52
+
53
+ environment = os.environ if environ is None else environ
54
+ candidates = [Path(value).expanduser() for name in _CANN_HOME_VARIABLES if (value := environment.get(name))]
55
+ candidates.extend(default_homes)
56
+
57
+ for candidate in candidates:
58
+ if platform_config_directory(candidate, machine=machine).is_dir():
59
+ return candidate.resolve()
60
+
61
+ searched = ", ".join(str(path) for path in candidates) or "没有可供查找的路径"
62
+ raise CANNNotFoundError(f"找不到包含平台配置的 CANN 安装;已查找:{searched}")
63
+
64
+
65
+ @lru_cache(maxsize=1)
66
+ def detect_npu_name() -> str:
67
+ """通过 ACL 探测 NPU 名称,并缓存首次成功的结果"""
68
+
69
+ try:
70
+ acl = importlib.import_module("acl")
71
+ except (ImportError, OSError) as exc:
72
+ raise NPUDetectionError("无法导入 ACL,因而无法探测 NPU 名称;请显式指定 npu_name") from exc
73
+
74
+ try:
75
+ name = acl.get_soc_name()
76
+ except Exception as exc:
77
+ raise NPUDetectionError("ACL 探测 NPU 名称失败;请显式指定 npu_name") from exc
78
+
79
+ if not isinstance(name, str) or not name.strip():
80
+ raise NPUDetectionError("ACL 未返回有效的 NPU 名称;请显式指定 npu_name")
81
+ return name.strip()
82
+
83
+
84
+ def list_npu_names(
85
+ *,
86
+ cann_home: str | os.PathLike[str] | None = None,
87
+ machine: str | None = None,
88
+ ) -> list[str]:
89
+ """列出当前 CANN 安装中包含平台配置的 NPU 名称"""
90
+
91
+ home = find_cann_home(machine=machine) if cann_home is None else Path(cann_home)
92
+ directory = platform_config_directory(home, machine=machine)
93
+ if not directory.is_dir():
94
+ raise CANNNotFoundError(f"CANN 平台配置目录不存在:{directory}")
95
+ return sorted(path.stem for path in directory.glob("*.ini") if path.is_file())
@@ -0,0 +1,21 @@
1
+ """cann-cann 的公开异常"""
2
+
3
+
4
+ class CannCannError(Exception):
5
+ """cann-cann 公开异常的基类"""
6
+
7
+
8
+ class CANNNotFoundError(CannCannError):
9
+ """找不到可用的 CANN 安装时抛出"""
10
+
11
+
12
+ class NPUDetectionError(CannCannError):
13
+ """无法探测当前 NPU 名称时抛出"""
14
+
15
+
16
+ class PlatformConfigError(CannCannError):
17
+ """无法使用 CANN 平台配置时抛出"""
18
+
19
+
20
+ class PlatformConfigNotFoundError(PlatformConfigError, FileNotFoundError):
21
+ """找不到所需的平台配置文件时抛出"""
File without changes
@@ -0,0 +1,37 @@
1
+ """存储空间大小的单位转换"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Literal, TypeAlias
6
+
7
+ SizeUnit: TypeAlias = Literal["bytes", "kb", "kib", "mb", "mib", "gb", "gib"]
8
+
9
+ _UNIT_DIVISORS: dict[str, int] = {
10
+ "bytes": 1,
11
+ "kb": 1024,
12
+ "kib": 1024,
13
+ "mb": 1024**2,
14
+ "mib": 1024**2,
15
+ "gb": 1024**3,
16
+ "gib": 1024**3,
17
+ }
18
+
19
+
20
+ def convert_size(value: int, unit: SizeUnit | str = "bytes") -> int | float:
21
+ """将字节数转换为支持的二进制单位
22
+
23
+ 为方便使用,``kb`` 与 ``kib``、``mb`` 与 ``mib``、``gb`` 与 ``gib``
24
+ 含义相同,均按 1024 进制换算。结果能整除时返回整数,否则返回浮点数
25
+ """
26
+
27
+ normalized_unit = unit.lower()
28
+ try:
29
+ divisor = _UNIT_DIVISORS[normalized_unit]
30
+ except KeyError as error:
31
+ choices = ", ".join(_UNIT_DIVISORS)
32
+ raise ValueError(f"不支持的单位 {unit!r};可选值:{choices}") from error
33
+
34
+ quotient, remainder = divmod(value, divisor)
35
+ if remainder == 0:
36
+ return quotient
37
+ return value / divisor