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.
- cann_cann-0.1.0/LICENSE +21 -0
- cann_cann-0.1.0/PKG-INFO +126 -0
- cann_cann-0.1.0/README.md +113 -0
- cann_cann-0.1.0/pyproject.toml +50 -0
- cann_cann-0.1.0/pyproject.toml.orig +43 -0
- cann_cann-0.1.0/src/cann_cann/__init__.py +149 -0
- cann_cann-0.1.0/src/cann_cann/cli.py +135 -0
- cann_cann-0.1.0/src/cann_cann/config.py +285 -0
- cann_cann-0.1.0/src/cann_cann/environment.py +95 -0
- cann_cann-0.1.0/src/cann_cann/errors.py +21 -0
- cann_cann-0.1.0/src/cann_cann/py.typed +0 -0
- cann_cann-0.1.0/src/cann_cann/units.py +37 -0
cann_cann-0.1.0/LICENSE
ADDED
|
@@ -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.
|
cann_cann-0.1.0/PKG-INFO
ADDED
|
@@ -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
|