dsh-scnet 0.1.0 → 0.2.1
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.
- package/README.md +37 -15
- package/index.mjs +76 -1
- package/package.json +10 -3
- package/skills/scnet-hpc/SKILL.md +32 -2
- package/skills/scnet-hpc/clusters/_template.conf +6 -4
- package/skills/scnet-hpc/clusters/kseshell.conf +8 -6
- package/skills/scnet-hpc/clusters/wuzhshell.conf +60 -0
- package/skills/scnet-hpc/clusters/zzeshell.conf +10 -9
- package/skills/scnet-hpc/references/adding-cluster.md +13 -1
- package/skills/scnet-hpc/references/environment.md +14 -0
- package/skills/scnet-hpc/references/setup.md +3 -1
- package/skills/scnet-hpc/references/troubleshooting.md +29 -2
- package/skills/scnet-hpc/scripts/_common.sh +41 -1
- package/skills/scnet-hpc/scripts/compute-probe.py +114 -0
- package/skills/scnet-hpc/scripts/new-job.sh +12 -2
- package/skills/scnet-hpc/scripts/refresh-cluster.sh +277 -0
- package/skills/scnet-hpc/scripts/run-compute-probe.sh +132 -0
- package/skills/scnet-hpc/scripts/setup-ssh.sh +2 -0
package/README.md
CHANGED
|
@@ -1,26 +1,20 @@
|
|
|
1
1
|
<h1 align="center">dsh-scnet</h1>
|
|
2
2
|
|
|
3
|
-
<p align="center">在 DeepSeek Harness 里操作超算互联网(scnet.cn)及同类国产超算集群:配 SSH、生成 Slurm
|
|
3
|
+
<p align="center">在 DeepSeek Harness 里操作超算互联网(scnet.cn)及同类国产超算集群:配 SSH、生成 Slurm 作业、探测/刷新集群、运行计算节点探针、排查失败,附海光 DCU/DTK 知识。</p>
|
|
4
4
|
|
|
5
5
|
## 这是什么
|
|
6
6
|
|
|
7
|
-
一个 DeepSeek Harness
|
|
7
|
+
一个 DeepSeek Harness bundle 插件,把 `scnet-hpc` 的 Agent Skill 和 bash 脚本包装成 DSH 可安装包。装好后 DSH 模型在涉及超算集群、Slurm 作业、海光 DCU/DTK 时会加载对应 skill,并可调用工具完成确定性操作。
|
|
8
8
|
|
|
9
9
|
## 安装
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
dsh plugin --profile web add "github:lql341/dsh-scnet#main"
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
或本地目录:
|
|
11
|
+
推荐从 npm 安装:
|
|
16
12
|
|
|
17
13
|
```sh
|
|
18
|
-
|
|
19
|
-
cd dsh-scnet
|
|
20
|
-
dsh plugin --profile web add .
|
|
14
|
+
dsh plugin --profile web add dsh-scnet
|
|
21
15
|
```
|
|
22
16
|
|
|
23
|
-
|
|
17
|
+
git 源、本地目录安装以及验证步骤见 [INSTALL.md](./INSTALL.md)。
|
|
24
18
|
|
|
25
19
|
## 能力面
|
|
26
20
|
|
|
@@ -30,6 +24,14 @@ dsh plugin --profile web add .
|
|
|
30
24
|
|---|---|
|
|
31
25
|
| scnet-hpc | 国产超算使用规范:连接、提交/监控、调试、失败排查、海光 DCU/DTK 资源索引 |
|
|
32
26
|
|
|
27
|
+
### 内置集群
|
|
28
|
+
|
|
29
|
+
| 短名 | 描述 | 加速器 |
|
|
30
|
+
|---|---|---|
|
|
31
|
+
| zzeshell | 超算互联网 · 郑州 | 海光 BW1000 DCU (gfx936) ×8, 64GB/卡 |
|
|
32
|
+
| kseshell | 超算互联网 · 昆山 | 海光 Z100 DCU (gfx906) ×4, 16GB/卡 |
|
|
33
|
+
| wuzhshell | 超算互联网 · 乌镇 | 海光 Z100 DCU (gfx906) ×4, 16GB/卡 |
|
|
34
|
+
|
|
33
35
|
### Tools
|
|
34
36
|
|
|
35
37
|
| 工具 | 说明 |
|
|
@@ -39,6 +41,13 @@ dsh plugin --profile web add .
|
|
|
39
41
|
| scnet_generate_job | 按 profile 生成合规 Slurm 作业脚本 |
|
|
40
42
|
| scnet_setup_ssh | 配置本地到集群的 SSH 连接 |
|
|
41
43
|
| scnet_probe_cluster | 探测新集群并生成 profile |
|
|
44
|
+
| scnet_refresh_cluster | 动态刷新已有集群的规则缓存 |
|
|
45
|
+
| scnet_run_compute_probe | 在计算节点运行最小能力探针 |
|
|
46
|
+
|
|
47
|
+
## 文档
|
|
48
|
+
|
|
49
|
+
- [INSTALL.md](./INSTALL.md) — 安装与验证
|
|
50
|
+
- [TESTING.md](./TESTING.md) — 按风险排序的初步测试步骤
|
|
42
51
|
|
|
43
52
|
## 结构
|
|
44
53
|
|
|
@@ -46,10 +55,20 @@ dsh plugin --profile web add .
|
|
|
46
55
|
.
|
|
47
56
|
├── package.json # dsh.bundle + dsh.skills
|
|
48
57
|
├── cordis.patch.yml # bundle 组合层
|
|
49
|
-
├── index.mjs # Cordis 入口,注册
|
|
50
|
-
├── skills/scnet-hpc/
|
|
58
|
+
├── index.mjs # Cordis 入口,注册 7 个工具
|
|
59
|
+
├── skills/scnet-hpc/
|
|
60
|
+
│ ├── clusters/
|
|
61
|
+
│ │ ├── zzeshell.conf
|
|
62
|
+
│ │ ├── kseshell.conf
|
|
63
|
+
│ │ └── wuzhshell.conf
|
|
64
|
+
│ ├── scripts/
|
|
65
|
+
│ └── references/
|
|
51
66
|
├── sync.sh # 从 canonical 仓库同步
|
|
52
|
-
|
|
67
|
+
├── README.md # 项目说明
|
|
68
|
+
├── INSTALL.md # 安装与验证
|
|
69
|
+
├── TESTING.md # 初步测试步骤
|
|
70
|
+
├── LICENSE
|
|
71
|
+
└── .gitignore
|
|
53
72
|
```
|
|
54
73
|
|
|
55
74
|
## 同步
|
|
@@ -62,13 +81,16 @@ dsh plugin --profile web add .
|
|
|
62
81
|
```
|
|
63
82
|
|
|
64
83
|
只改 canonical,本仓库跑 sync 更新;不要把 `skills/scnet-hpc/` 当手工维护目录。
|
|
84
|
+
`sync.sh` 会自动排除 `scripts/install.sh` 和 `clusters/.cache/`,避免把安装脚本或本机动态缓存打进发布包。
|
|
65
85
|
|
|
66
86
|
## 隐私与安全
|
|
67
87
|
|
|
68
88
|
- 仓库不含私钥、token、用户名、密钥指纹。
|
|
69
89
|
- `scnet_setup_ssh` 会写入 `~/.ssh/`,调用前确认私钥路径和集群。
|
|
70
90
|
- `scnet_generate_job` 生成的 `.slurm` 可能含本机用户名,`.gitignore` 已排除。
|
|
71
|
-
-
|
|
91
|
+
- 集群主机名/端口是平台公开信息,由 canonical 仓库维护。
|
|
92
|
+
- `scnet_refresh_cluster` / `scnet_run_compute_probe` 会 SSH 到远端并可能提交作业;运行前确认集群和副作用。
|
|
93
|
+
- `clusters/.cache/` 是本机动态探测缓存,`.gitignore` 已排除,不应提交。
|
|
72
94
|
|
|
73
95
|
## License
|
|
74
96
|
|
package/index.mjs
CHANGED
|
@@ -134,6 +134,8 @@ export function apply(ctx) {
|
|
|
134
134
|
time: { type: 'string', description: '时长,默认 00:20:00,格式 HH:MM:SS 或 D-HH:MM:SS' },
|
|
135
135
|
cluster: { type: 'string', description: '集群短名;省略时若只有一个 profile 则自动选择' },
|
|
136
136
|
remote_user: { type: 'string', description: '远端用户名;与本地不同时填写(日志路径需要真实用户名)' },
|
|
137
|
+
refresh: { type: 'boolean', description: '是否在生成前运行 refresh-cluster.sh 刷新动态规则(默认否)' },
|
|
138
|
+
no_auto: { type: 'boolean', description: '是否忽略 clusters/.cache/*.auto.conf 动态缓存(默认否)' },
|
|
137
139
|
},
|
|
138
140
|
output: {
|
|
139
141
|
schema: { type: 'string' },
|
|
@@ -153,18 +155,51 @@ export function apply(ctx) {
|
|
|
153
155
|
if (cluster) argv.push('--cluster', cluster)
|
|
154
156
|
const remoteUser = String(args.remote_user || '').trim()
|
|
155
157
|
if (remoteUser) argv.push('--user', remoteUser)
|
|
158
|
+
if (args.refresh === true) argv.push('--refresh')
|
|
159
|
+
if (args.no_auto === true) argv.push('--no-auto')
|
|
156
160
|
argv.push(nameVal)
|
|
157
161
|
if (accelerators) argv.push(accelerators.value)
|
|
158
162
|
if (cpus) argv.push(cpus.value)
|
|
159
163
|
if (args.time && String(args.time).trim()) argv.push(String(args.time).trim())
|
|
160
164
|
|
|
161
|
-
const
|
|
165
|
+
const timeoutMs = args.refresh === true ? 240000 : 30000
|
|
166
|
+
const res = await runBash(join(SCRIPTS_DIR, 'new-job.sh'), argv, timeoutMs)
|
|
162
167
|
if (res.ok) return res.stdout.trim() || res.stderr.trim()
|
|
163
168
|
return `生成失败(exit ${res.code}):\n${res.stderr.trim() || res.stdout.trim()}`
|
|
164
169
|
},
|
|
165
170
|
}),
|
|
166
171
|
)
|
|
167
172
|
|
|
173
|
+
ctx.tools.register(
|
|
174
|
+
defineTool({
|
|
175
|
+
name: 'scnet_refresh_cluster',
|
|
176
|
+
description:
|
|
177
|
+
'动态刷新指定集群的规则缓存(分区、内存、GRES、网络、登录节点缺库等)。默认只做登录节点只读探测;compute=true 会额外提交一个约 10 分钟的计算节点能力探针,会 SSH 到远端并写 clusters/.cache/<短名>.auto.conf。',
|
|
178
|
+
parameters: {
|
|
179
|
+
cluster: { type: 'string', description: '集群短名;省略时若只有一个 profile 则自动选择' },
|
|
180
|
+
compute: { type: 'boolean', description: '是否额外提交计算节点能力探针(默认否)' },
|
|
181
|
+
dry_run: { type: 'boolean', description: '只输出将要写入的缓存,不落盘(默认否)' },
|
|
182
|
+
},
|
|
183
|
+
timeoutMs: 900000,
|
|
184
|
+
output: {
|
|
185
|
+
schema: { type: 'string' },
|
|
186
|
+
render: (_args, value) => textBlock(value),
|
|
187
|
+
},
|
|
188
|
+
async execute(args) {
|
|
189
|
+
const argv = []
|
|
190
|
+
const cluster = String(args.cluster || '').trim()
|
|
191
|
+
if (cluster) argv.push('--cluster', cluster)
|
|
192
|
+
if (args.compute === true) argv.push('--compute')
|
|
193
|
+
if (args.dry_run === true) argv.push('--dry-run')
|
|
194
|
+
|
|
195
|
+
const timeoutMs = args.compute === true ? 900000 : 240000
|
|
196
|
+
const res = await runBash(join(SCRIPTS_DIR, 'refresh-cluster.sh'), argv, timeoutMs)
|
|
197
|
+
if (res.ok) return res.stdout.trim() || res.stderr.trim()
|
|
198
|
+
return `刷新失败(exit ${res.code}):\n${res.stderr.trim() || res.stdout.trim()}`
|
|
199
|
+
},
|
|
200
|
+
}),
|
|
201
|
+
)
|
|
202
|
+
|
|
168
203
|
ctx.tools.register(
|
|
169
204
|
defineTool({
|
|
170
205
|
name: 'scnet_setup_ssh',
|
|
@@ -194,6 +229,46 @@ export function apply(ctx) {
|
|
|
194
229
|
}),
|
|
195
230
|
)
|
|
196
231
|
|
|
232
|
+
ctx.tools.register(
|
|
233
|
+
defineTool({
|
|
234
|
+
name: 'scnet_run_compute_probe',
|
|
235
|
+
description:
|
|
236
|
+
'在已配置 SSH 的集群计算节点上运行最小能力探针,输出 PROBE_ 开头的结果(加速器架构、FP8、Triton、bitsandbytes、外网等)。会提交一个 1 卡、4 核、10 分钟的小作业并等待完成。',
|
|
237
|
+
parameters: {
|
|
238
|
+
cluster: { type: 'string', required: true, description: '集群短名' },
|
|
239
|
+
accelerators: { type: 'string', description: '加速器数量,默认 1' },
|
|
240
|
+
cpus: { type: 'string', description: 'CPU 核数,默认 4' },
|
|
241
|
+
time: { type: 'string', description: '作业时长,默认 00:10:00' },
|
|
242
|
+
remote_user: { type: 'string', description: '远端用户名;与本地不同时填写' },
|
|
243
|
+
},
|
|
244
|
+
timeoutMs: 900000,
|
|
245
|
+
output: {
|
|
246
|
+
schema: { type: 'string' },
|
|
247
|
+
render: (_args, value) => textBlock(value),
|
|
248
|
+
},
|
|
249
|
+
async execute(args) {
|
|
250
|
+
const cluster = String(args.cluster || '').trim()
|
|
251
|
+
if (!cluster) return '缺少必填参数 cluster。'
|
|
252
|
+
|
|
253
|
+
const accelerators = positiveInt(args.accelerators, 'accelerators')
|
|
254
|
+
if (accelerators && accelerators.error) return accelerators.error
|
|
255
|
+
const cpus = positiveInt(args.cpus, 'cpus')
|
|
256
|
+
if (cpus && cpus.error) return cpus.error
|
|
257
|
+
|
|
258
|
+
const argv = ['--cluster', cluster]
|
|
259
|
+
const remoteUser = String(args.remote_user || '').trim()
|
|
260
|
+
if (remoteUser) argv.push('--user', remoteUser)
|
|
261
|
+
if (cpus) argv.push('--cpus', cpus.value)
|
|
262
|
+
if (args.time && String(args.time).trim()) argv.push('--time', String(args.time).trim())
|
|
263
|
+
if (accelerators) argv.push('--accelerators', accelerators.value)
|
|
264
|
+
|
|
265
|
+
const res = await runBash(join(SCRIPTS_DIR, 'run-compute-probe.sh'), argv, 900000)
|
|
266
|
+
if (res.ok) return res.stdout.trim()
|
|
267
|
+
return `计算节点探针失败(exit ${res.code}):\n${res.stderr.trim() || res.stdout.trim()}`
|
|
268
|
+
},
|
|
269
|
+
}),
|
|
270
|
+
)
|
|
271
|
+
|
|
197
272
|
ctx.tools.register(
|
|
198
273
|
defineTool({
|
|
199
274
|
name: 'scnet_probe_cluster',
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-scnet",
|
|
3
|
-
"version": "0.1
|
|
4
|
-
"description": "DeepSeek Harness bundle plugin for SCNet (scnet.cn) and similar domestic HPC clusters: SSH setup, Slurm job generation, cluster probing, and Hygon DCU/DTK guidance.",
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "DeepSeek Harness bundle plugin for SCNet (scnet.cn) and similar domestic HPC clusters: SSH setup, Slurm job generation, cluster probing/refresh, compute-node capability probes, and Hygon DCU/DTK guidance.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.mjs",
|
|
7
7
|
"exports": {
|
|
@@ -9,6 +9,13 @@
|
|
|
9
9
|
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
10
10
|
"./package.json": "./package.json"
|
|
11
11
|
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@deepseek-ai/dsh-tools": "^0.1.0-rc.6"
|
|
14
|
+
},
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/lql341/dsh-scnet.git"
|
|
18
|
+
},
|
|
12
19
|
"files": [
|
|
13
20
|
"index.mjs",
|
|
14
21
|
"cordis.patch.yml",
|
|
@@ -23,7 +30,7 @@
|
|
|
23
30
|
]
|
|
24
31
|
},
|
|
25
32
|
"engines": {
|
|
26
|
-
"node": ">=
|
|
33
|
+
"node": ">=22.19"
|
|
27
34
|
},
|
|
28
35
|
"keywords": [
|
|
29
36
|
"deepseek-harness",
|
|
@@ -24,6 +24,8 @@ cat clusters/<集群短名>.conf # 看具体参数
|
|
|
24
24
|
| `clusters/_template.conf` | 新增集群的模板 |
|
|
25
25
|
| `scripts/setup-ssh.sh` | 新电脑上配连接(macOS/Linux,幂等)|
|
|
26
26
|
| `scripts/probe-cluster.sh` | 探测新集群,自动生成 profile |
|
|
27
|
+
| `scripts/refresh-cluster.sh` | 动态刷新已有集群的规则缓存 |
|
|
28
|
+
| `scripts/run-compute-probe.sh` | 在计算节点运行最小能力探针 |
|
|
27
29
|
| `scripts/new-job.sh` | 按 profile 生成合规的作业脚本 |
|
|
28
30
|
| `references/setup.md` | 连接配置详解、平台差异、故障排查 |
|
|
29
31
|
| `references/environment.md` | 环境栈、装依赖、目录约定、迁移注意 |
|
|
@@ -34,12 +36,14 @@ cat clusters/<集群短名>.conf # 看具体参数
|
|
|
34
36
|
|
|
35
37
|
## 换机器
|
|
36
38
|
|
|
37
|
-
本插件已随 DeepSeek Harness 安装,换机器时用 `dsh plugin --profile web add` 重装即可。
|
|
39
|
+
本插件已随 DeepSeek Harness 安装,换机器时用 `dsh plugin --profile web add dsh-scnet` 重装即可。
|
|
38
40
|
配置集群连接:
|
|
39
41
|
|
|
40
42
|
```bash
|
|
41
|
-
./scripts/setup-ssh.sh <私钥文件>
|
|
43
|
+
./scripts/setup-ssh.sh --cluster <集群短名> <私钥文件> <用户名>
|
|
42
44
|
```
|
|
45
|
+
|
|
46
|
+
只有一个 profile 时可省略 `--cluster`;公开 profile 里的 `KEY_NAME_MARKER` 是占位符,所以需要显式传用户名。
|
|
43
47
|
## 新增集群
|
|
44
48
|
|
|
45
49
|
```bash
|
|
@@ -51,6 +55,30 @@ cat clusters/<集群短名>.conf # 看具体参数
|
|
|
51
55
|
|
|
52
56
|
完整步骤见 `references/adding-cluster.md`。
|
|
53
57
|
|
|
58
|
+
## 动态刷新已有集群规则
|
|
59
|
+
|
|
60
|
+
profile 里的 `SSH_HOST` / `SSH_PORT` 是固定连接参数,应写死在
|
|
61
|
+
`clusters/<集群短名>.conf`。其余容易随平台调整的规则
|
|
62
|
+
(`DEF_MEM_PER_CPU`、`MIN_GRES`、`PARTITION`、`NET_OK`、登录节点缺库等)
|
|
63
|
+
可以动态探测:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
./scripts/refresh-cluster.sh --cluster <集群短名> # 只做登录节点只读探测
|
|
67
|
+
./scripts/refresh-cluster.sh --cluster <集群短名> --compute # 再提交一个 10 分钟小作业
|
|
68
|
+
./scripts/refresh-cluster.sh --cluster <集群短名> --dry-run # 只看结果,不写缓存
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
结果写到 `clusters/.cache/<集群短名>.auto.conf`,后续脚本会自动加载并覆盖
|
|
72
|
+
profile 中的同名字段。删除该缓存文件即可回退到纯 profile。生成作业时可用
|
|
73
|
+
`./scripts/new-job.sh --refresh ...` 先刷新再生成,或用 `--no-auto` 忽略缓存。
|
|
74
|
+
|
|
75
|
+
默认只做登录节点探测,不提交作业;`--compute` 才会到计算节点实测加速器架构、
|
|
76
|
+
FP8、Triton、bitsandbytes 和外网状态。
|
|
77
|
+
|
|
78
|
+
分区节点数 `NODE_COUNT` 是例外:每次加载集群时都会实时查询当前
|
|
79
|
+
`PARTITION` 的 `TotalNodes`,查不到才回退到 cache/profile。需要关闭时设
|
|
80
|
+
`SCNET_HPC_LIVE_NODE_COUNT=no`。
|
|
81
|
+
|
|
54
82
|
## 通用硬约束
|
|
55
83
|
|
|
56
84
|
以下几条在国产超算上普遍存在,**具体数值查 profile**。
|
|
@@ -222,6 +250,8 @@ Triton 那条影响最大:**任何依赖 Triton kernel 的推理框架都跑
|
|
|
222
250
|
| `lib*.so: cannot open shared object file` | 没 `module load`,或在登录节点跑了 |
|
|
223
251
|
| `Name or service not known` / `offline mode` | 计算节点在访问外网 |
|
|
224
252
|
| `hipDrvLaunchKernelEx` | Triton 不可用,换纯 PyTorch 路线 |
|
|
253
|
+
| `libgcvm.so.17git: cannot open shared object file` | Triton 在当前 DTK/DCU 栈不可用 |
|
|
254
|
+
| `python3: command not found` | 昆山计算节点需 `module load python/3.8.10` |
|
|
225
255
|
| `sacct` COMPLETED 但结果不对 | 脚本缺 `exit $rc` |
|
|
226
256
|
|
|
227
257
|
完整手册见 `references/troubleshooting.md`。
|
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
# 新增集群:
|
|
4
4
|
# 1. cp _template.conf <集群短名>.conf
|
|
5
5
|
# 2. 填下面的值(不确定的用 scripts/probe-cluster.sh 探测)
|
|
6
|
-
# 3. scripts/setup-ssh.sh --cluster <集群短名> <私钥文件>
|
|
6
|
+
# 3. scripts/setup-ssh.sh --cluster <集群短名> <私钥文件> [用户名]
|
|
7
7
|
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
8
|
+
# CLUSTER_ID / SSH_HOST / SSH_PORT 是必填连接参数,应写死;其余可留空。
|
|
9
|
+
# 留空的项脚本会跳过、文档里显示为「未探测」。旧 profile 漏填 SSH_HOST 时,
|
|
10
|
+
# _common.sh 会临时从本机 ssh config 补全,但新集群仍建议在此显式填写。
|
|
10
11
|
|
|
11
12
|
# ---- 连接(必填)----
|
|
12
13
|
CLUSTER_ID="" # 短名,同时用作 ssh 别名,如 "zzeshell"
|
|
@@ -35,7 +36,7 @@ ACCEL_MEM_GB=""
|
|
|
35
36
|
CPUS_PER_NODE=""
|
|
36
37
|
CPU_MODEL=""
|
|
37
38
|
NODE_MEM_GB=""
|
|
38
|
-
NODE_COUNT=""
|
|
39
|
+
NODE_COUNT="" # 通常留空/作回退;运行时优先实时查 TotalNodes
|
|
39
40
|
LOGIN_NODES=""
|
|
40
41
|
HOME_QUOTA_GB=""
|
|
41
42
|
|
|
@@ -44,6 +45,7 @@ TOOLCHAIN="" # "DTK" / "CUDA" / "ROCm" 等
|
|
|
44
45
|
TOOLCHAIN_VERSION=""
|
|
45
46
|
TOOLCHAIN_PATH=""
|
|
46
47
|
MODULE_LOADS="" # 空格分隔的 module load 参数
|
|
48
|
+
PYTHON_MODULE="" # 计算节点缺少 python3 时加载的 Python module
|
|
47
49
|
|
|
48
50
|
DEFAULT_VENV="" # 相对家目录的 venv 路径
|
|
49
51
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# kseshell —— 海光 Z100 DCU
|
|
1
|
+
# kseshell —— 海光 Z100 DCU 集群(连接字段已按平台填写)
|
|
2
2
|
#
|
|
3
3
|
# SCNet 昆山集群。与郑州(zzeshell / BW1000 / gfx936)的主要差别:
|
|
4
4
|
# - 加速器是 Z100 (gfx906),单机 4 卡、每卡 16GB(BW1000 是 8 卡 × 64GB)
|
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
# ---- 连接 ----
|
|
10
10
|
CLUSTER_ID="kseshell"
|
|
11
11
|
CLUSTER_DESC="海光 Z100 DCU(昆山)"
|
|
12
|
-
SSH_HOST=""
|
|
13
|
-
SSH_PORT=""
|
|
12
|
+
SSH_HOST="cancon.hpccube.com"
|
|
13
|
+
SSH_PORT="65023"
|
|
14
14
|
HOME_BASE="/public/home"
|
|
15
15
|
|
|
16
|
-
KEY_NAME_MARKER=""
|
|
16
|
+
KEY_NAME_MARKER="_<CLUSTER_HOST>_"
|
|
17
17
|
NEEDS_UPDATE_HOSTKEYS_NO="no" # 实测无坏签名告警
|
|
18
18
|
|
|
19
19
|
# ---- 调度器 ----
|
|
@@ -46,8 +46,8 @@ ACCEL_TDP_W="300"
|
|
|
46
46
|
CPUS_PER_NODE="32"
|
|
47
47
|
CPU_MODEL="Hygon C86 7185 32-core"
|
|
48
48
|
NODE_MEM_GB="123"
|
|
49
|
-
NODE_COUNT="
|
|
50
|
-
LOGIN_NODES="
|
|
49
|
+
NODE_COUNT="1368" # 回退值;运行时优先实时查 TotalNodes
|
|
50
|
+
LOGIN_NODES="login01 / login02(示意)"
|
|
51
51
|
HOME_QUOTA_GB="2950" # /public 文件系统总量,非个人独占
|
|
52
52
|
|
|
53
53
|
# ---- 软件栈 ----
|
|
@@ -57,6 +57,7 @@ TOOLCHAIN_PATH=""
|
|
|
57
57
|
MODULE_LOADS="compiler/dtk/26.04"
|
|
58
58
|
# 可用版本很多:22.10 / 23.04 / 23.10 / 24.04.x / 25.04.x / 26.04
|
|
59
59
|
# 实测 26.04、25.04.4、24.04.3 都能正常识别 4 张 gfx906
|
|
60
|
+
PYTHON_MODULE="python/3.8.10" # 计算节点默认无 python3
|
|
60
61
|
|
|
61
62
|
DEFAULT_VENV="" # 尚未建 venv;系统 python3 是 3.8.5,无 torch
|
|
62
63
|
|
|
@@ -76,6 +77,7 @@ LOGIN_NODE_MISSING_LIBS="" # 登录节点无 torch(未装),
|
|
|
76
77
|
# - 单卡仅 16GB,大模型必须多卡切分或量化
|
|
77
78
|
# 具体的 torch/Triton 能力待建 venv 后实测,参考 references/adding-cluster.md
|
|
78
79
|
KNOWN_LIMITATIONS="gfx906 (Vega20) 无 FP8/BF16 硬件支持、无 MFMA Matrix Core;\
|
|
80
|
+
无 BF16 数据类型支持;计算节点默认无 python3,需 module load python/3.8.10;\
|
|
79
81
|
单卡显存仅 16GB;torch/Triton 可用性待实测;\
|
|
80
82
|
CentOS 7.6 / glibc 2.17 环境优先使用 manylinux_2_17 wheel,onnxruntime 通常锁定 1.16.3;\
|
|
81
83
|
MinerU 3.4.x 需 Python >=3.10,旧版 magika 兼容方案需单独验证 API;\
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# wuzhshell —— 乌镇超算(wuzh02.hpccube.com)
|
|
2
|
+
|
|
3
|
+
# ---- 连接 ----
|
|
4
|
+
CLUSTER_ID="wuzhshell"
|
|
5
|
+
CLUSTER_DESC="乌镇超算(wuzh02)"
|
|
6
|
+
SSH_HOST="wuzh02.hpccube.com"
|
|
7
|
+
SSH_PORT="65091"
|
|
8
|
+
HOME_BASE="/work/home"
|
|
9
|
+
|
|
10
|
+
KEY_NAME_MARKER="_<CLUSTER_HOST>_"
|
|
11
|
+
NEEDS_UPDATE_HOSTKEYS_NO="no"
|
|
12
|
+
|
|
13
|
+
# ---- 调度器 ----
|
|
14
|
+
SCHEDULER="slurm"
|
|
15
|
+
|
|
16
|
+
# 默认使用 DCU 队列;纯 CPU 作业改用 wzhcnormal。
|
|
17
|
+
PARTITION="wzhdnormal"
|
|
18
|
+
|
|
19
|
+
DEF_MEM_PER_CPU="3634"
|
|
20
|
+
MIN_GRES="dcu:1"
|
|
21
|
+
GRES_TYPE="dcu"
|
|
22
|
+
MAX_WALLTIME="333-08:00:00"
|
|
23
|
+
|
|
24
|
+
# CPU 队列和 DCU 队列。
|
|
25
|
+
PARTITION_CPU="wzhcnormal"
|
|
26
|
+
PARTITION_DCU="wzhdnormal"
|
|
27
|
+
|
|
28
|
+
# ---- 硬件 ----
|
|
29
|
+
ACCEL_NAME="海光 Z100 DCU (Z100SM)"
|
|
30
|
+
ACCEL_ARCH="gfx906"
|
|
31
|
+
ACCEL_PER_NODE="4"
|
|
32
|
+
ACCEL_MEM_GB="16"
|
|
33
|
+
CPUS_PER_NODE="32"
|
|
34
|
+
CPU_MODEL="Hygon C86 7285 32-core Processor"
|
|
35
|
+
NODE_MEM_GB="123"
|
|
36
|
+
NODE_COUNT="434"
|
|
37
|
+
LOGIN_NODES="login01 / login02(示意)"
|
|
38
|
+
HOME_QUOTA_GB="2000"
|
|
39
|
+
|
|
40
|
+
# ---- 软件栈 ----
|
|
41
|
+
TOOLCHAIN="DTK"
|
|
42
|
+
TOOLCHAIN_VERSION="26.04"
|
|
43
|
+
TOOLCHAIN_PATH=""
|
|
44
|
+
MODULE_LOADS="compiler/dtk/26.04"
|
|
45
|
+
PYTHON_MODULE="python/3.8.10"
|
|
46
|
+
|
|
47
|
+
DEFAULT_VENV=""
|
|
48
|
+
|
|
49
|
+
# ---- 网络 ----
|
|
50
|
+
NET_OK="pypi.org pypi.tuna.tsinghua.edu.cn modelscope.cn github.com"
|
|
51
|
+
NET_BLOCKED="huggingface.co"
|
|
52
|
+
PIP_INDEX="https://pypi.tuna.tsinghua.edu.cn/simple"
|
|
53
|
+
COMPUTE_NODE_OFFLINE="yes"
|
|
54
|
+
TMP_SHARED="no"
|
|
55
|
+
|
|
56
|
+
# ---- 已知问题 ----
|
|
57
|
+
LOGIN_NODE_MISSING_LIBS=""
|
|
58
|
+
KNOWN_LIMITATIONS="gfx906 (Vega20) 无 FP8/BF16 硬件支持、无 MFMA Matrix Core;\
|
|
59
|
+
单卡显存 16GB;计算节点默认无 torch(module load compiler/dtk/26.04 后仍无 torch,需建 venv);\
|
|
60
|
+
计算节点无外网(PROBE_NET_PYPI=OFFLINE);DCU FP8/Triton/bitsandbytes 待装 torch 后实测"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# zzeshell —— 海光 DCU
|
|
1
|
+
# zzeshell —— 海光 DCU 集群(连接字段已按平台填写)
|
|
2
2
|
#
|
|
3
3
|
# 这是一个集群 profile。新增集群时复制本文件改值即可,
|
|
4
4
|
# 脚本和文档都从这里读配置,不硬编码。
|
|
@@ -6,13 +6,13 @@
|
|
|
6
6
|
# ---- 连接 ----
|
|
7
7
|
CLUSTER_ID="zzeshell" # 短名,同时用作 ssh 别名
|
|
8
8
|
CLUSTER_DESC="海光 BW1000 DCU(郑州)"
|
|
9
|
-
SSH_HOST=""
|
|
10
|
-
SSH_PORT=""
|
|
9
|
+
SSH_HOST="zzeshell.scnet.cn"
|
|
10
|
+
SSH_PORT="65032"
|
|
11
11
|
HOME_BASE="/public/home" # 家目录父路径,实际家目录是 $HOME_BASE/$USER
|
|
12
12
|
|
|
13
13
|
# 私钥文件名里的主机标识,用于从文件名推断用户名
|
|
14
14
|
# 形如 <用户名>_<集群主机>_RsaKeyExpireTime_<日期>.txt
|
|
15
|
-
KEY_NAME_MARKER=""
|
|
15
|
+
KEY_NAME_MARKER="_<CLUSTER_HOST>_"
|
|
16
16
|
|
|
17
17
|
# 平台对 SSH 主机密钥轮换扩展返回错误签名,需要 UpdateHostKeys no 抑制告警
|
|
18
18
|
NEEDS_UPDATE_HOSTKEYS_NO="yes"
|
|
@@ -22,7 +22,7 @@ SCHEDULER="slurm"
|
|
|
22
22
|
PARTITION="hx1hdnormal"
|
|
23
23
|
|
|
24
24
|
# 每 CPU 默认内存(MB)。内存上限 = cpus-per-task × 此值
|
|
25
|
-
DEF_MEM_PER_CPU="3888
|
|
25
|
+
DEF_MEM_PER_CPU="3800" # 2026-08-16 实测;旧值 3888
|
|
26
26
|
|
|
27
27
|
# QOS 强制的最小 GRES。非空表示每个作业必须申请,纯 CPU 作业会被拒
|
|
28
28
|
MIN_GRES="dcu:1"
|
|
@@ -40,8 +40,8 @@ ACCEL_MEM_GB="64"
|
|
|
40
40
|
CPUS_PER_NODE="128"
|
|
41
41
|
CPU_MODEL="Hygon C86"
|
|
42
42
|
NODE_MEM_GB="498"
|
|
43
|
-
NODE_COUNT="
|
|
44
|
-
LOGIN_NODES="
|
|
43
|
+
NODE_COUNT="131" # 回退值;运行时优先实时查 TotalNodes
|
|
44
|
+
LOGIN_NODES="login01 / login02(有负载均衡)"
|
|
45
45
|
HOME_QUOTA_GB="1450" # /public 文件系统总量,非个人独占
|
|
46
46
|
|
|
47
47
|
# ---- 软件栈 ----
|
|
@@ -49,6 +49,7 @@ TOOLCHAIN="DTK" # 海光的 ROCm 发行版
|
|
|
49
49
|
TOOLCHAIN_VERSION="26.04"
|
|
50
50
|
TOOLCHAIN_PATH=""
|
|
51
51
|
MODULE_LOADS="compiler/gcc/9.3.0 compiler/dtk/26.04"
|
|
52
|
+
PYTHON_MODULE="" # 计算节点可用 python3,无需额外加载
|
|
52
53
|
|
|
53
54
|
# 默认 venv 路径(相对家目录)。因人而异,装完可改
|
|
54
55
|
DEFAULT_VENV="softwares/bitsandbytes/venv"
|
|
@@ -63,10 +64,10 @@ TMP_SHARED="no" # /tmp 是否跨节点共享
|
|
|
63
64
|
|
|
64
65
|
# ---- 已知的平台特有问题 ----
|
|
65
66
|
# 登录节点缺失的库(导致无法在登录节点 import 深度学习框架)
|
|
66
|
-
LOGIN_NODE_MISSING_LIBS="
|
|
67
|
+
LOGIN_NODE_MISSING_LIBS="libgalaxyhip.so.5"
|
|
67
68
|
|
|
68
69
|
# 加速器不支持的能力,写进文档避免重复踩坑
|
|
69
|
-
KNOWN_LIMITATIONS="Triton
|
|
70
|
+
KNOWN_LIMITATIONS="Triton 不可用(实测 libgcvm.so.17git 或 libamdhip64.so 缺 hipDrvLaunchKernelEx);\
|
|
70
71
|
FP8 无硬件计算支持(torch._scaled_mm 拒绝 gfx936);\
|
|
71
72
|
bitsandbytes 8-bit 不可用(hipBLASLt 无 gfx936 INT8 algo);\
|
|
72
73
|
纯 PyTorch 软件反量化可作为大模型推理退路,但单序列解码约 0.93–1.12 tok/s;\
|
|
@@ -45,6 +45,9 @@ EOF
|
|
|
45
45
|
|
|
46
46
|
profile 末尾的注释列出了还需确认的。主要是:
|
|
47
47
|
|
|
48
|
+
**`SSH_HOST` / `SSH_PORT`** —— 固定连接参数,应写死。本机已配置 SSH 别名时,
|
|
49
|
+
可用 `ssh -G <别名>` 查出 `hostname` 和 `port` 后填进 profile。
|
|
50
|
+
|
|
48
51
|
**加速器型号和架构** —— 在计算节点跑:
|
|
49
52
|
|
|
50
53
|
```bash
|
|
@@ -68,9 +71,12 @@ srun -p <分区> --gres=gpu:1 --time=00:05:00 nvidia-smi --query-gpu=name --form
|
|
|
68
71
|
删掉第 2 步的临时段,然后:
|
|
69
72
|
|
|
70
73
|
```bash
|
|
71
|
-
./scripts/setup-ssh.sh --cluster <集群短名> <私钥文件>
|
|
74
|
+
./scripts/setup-ssh.sh --cluster <集群短名> <私钥文件> <用户名>
|
|
72
75
|
```
|
|
73
76
|
|
|
77
|
+
公开 profile 使用占位符时,`<用户名>` 必须显式传入;如果已在本地私有 profile
|
|
78
|
+
里填了真实 `KEY_NAME_MARKER`,可以省略。
|
|
79
|
+
|
|
74
80
|
### 6. 实测加速器能力,写回 profile
|
|
75
81
|
|
|
76
82
|
**这一步别省。** 国产加速器的软件栈往往落后于上游,很多想当然的能力并不存在。
|
|
@@ -121,6 +127,10 @@ Triton 尤其值得测:**它不可用会连带否掉一大批推理框架**(
|
|
|
121
127
|
vLLM 的 ROCm MoE 后端、transformers 的 finegrained-fp8)。提前知道能避免在框架
|
|
122
128
|
适配上白费时间。
|
|
123
129
|
|
|
130
|
+
已有集群后续不必重跑完整流程,可用
|
|
131
|
+
`./scripts/refresh-cluster.sh --cluster <集群短名> --compute` 动态刷新调度器、
|
|
132
|
+
内存、分区、网络和计算节点能力,结果写到 `clusters/.cache/<集群短名>.auto.conf`。
|
|
133
|
+
|
|
124
134
|
## profile 字段速查
|
|
125
135
|
|
|
126
136
|
必填三项:`CLUSTER_ID`、`SSH_HOST`、`SSH_PORT`。其余留空则脚本跳过、文档显示为未探测。
|
|
@@ -133,7 +143,9 @@ vLLM 的 ROCm MoE 后端、transformers 的 finegrained-fp8)。提前知道能
|
|
|
133
143
|
| `GRES_TYPE` | `new-job.sh` | 生成的脚本不含 `--gres` |
|
|
134
144
|
| `DEF_MEM_PER_CPU` | `new-job.sh` | 生成的脚本不含 `--mem`,需手填 |
|
|
135
145
|
| `MODULE_LOADS` | `new-job.sh` | 生成的脚本不含 `module load` |
|
|
146
|
+
| `PYTHON_MODULE` | `run-compute-probe.sh` | 计算节点缺 python3 时可能找不到解释器 |
|
|
136
147
|
| `DEFAULT_VENV` | `new-job.sh` | 生成的脚本不含 venv 激活 |
|
|
148
|
+
| `NODE_COUNT` | 运行时回退 | 实时查 TotalNodes 失败时使用该值 |
|
|
137
149
|
| `KEY_NAME_MARKER` | `setup-ssh.sh` | 无法从文件名推断用户名,需显式传 |
|
|
138
150
|
| `NEEDS_UPDATE_HOSTKEYS_NO` | `setup-ssh.sh` | 不写 `UpdateHostKeys no` |
|
|
139
151
|
| `COMPUTE_NODE_OFFLINE` | `new-job.sh` | 不设 `HF_HUB_OFFLINE` |
|
|
@@ -16,6 +16,14 @@ ssh <集群> 'sinfo -p <分区> -o "%n %c %m %G %t"' # 每节点 CPU/内存/GRE
|
|
|
16
16
|
ssh <集群> 'sacctmgr show qos where name=<QOS名> format=Name,MinTRES%30,MaxTRES%30 -P'
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
+
也可以用 skill 自带的动态刷新,登录节点只读探测不会提交作业:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
./scripts/refresh-cluster.sh --cluster <集群短名> --dry-run
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
其中分区节点数 `NODE_COUNT` 默认每次加载集群时实时查询 `TotalNodes`。
|
|
26
|
+
|
|
19
27
|
节点状态含义:`idle` 空闲、`mix` 部分占用、`alloc` 全占、`drain`/`drng` 不可调度
|
|
20
28
|
(维护中)。实际可用节点常少于总数。
|
|
21
29
|
|
|
@@ -56,6 +64,12 @@ source ~/<DEFAULT_VENV>/bin/activate
|
|
|
56
64
|
ImportError: libgalaxyhip.so.5: cannot open shared object file
|
|
57
65
|
```
|
|
58
66
|
|
|
67
|
+
昆山计算节点默认没有 `python3` 命令,需额外加载:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
module load python/3.8.10
|
|
71
|
+
```
|
|
72
|
+
|
|
59
73
|
## 厂商预编译的 wheel
|
|
60
74
|
|
|
61
75
|
国产加速器的 torch 等框架需要厂商适配版,文件名带厂商标记。海光的例子:
|
|
@@ -87,7 +87,9 @@ ssh -O exit <集群> # 手工断开(网络切换后卡住时用)
|
|
|
87
87
|
`<用户名>_<主机>_RsaKeyExpireTime_2026-10-29_12-51-40.txt` 表示 2026-10-29 过期。
|
|
88
88
|
到期前从控制台重新下载并重跑 setup 脚本。
|
|
89
89
|
|
|
90
|
-
profile 里的 `KEY_NAME_MARKER`
|
|
90
|
+
profile 里的 `KEY_NAME_MARKER` 就是用来从这种文件名里提取用户名的;公开
|
|
91
|
+
`scnet-hpc` profile 使用 `_<CLUSTER_HOST>_` 占位符,所以安装时通常需要显式传
|
|
92
|
+
用户名,或在本地私有 profile 中补齐真实标记。
|
|
91
93
|
|
|
92
94
|
查看当前密钥指纹:
|
|
93
95
|
|
|
@@ -54,8 +54,9 @@ sbatch: error: QOSMinGRES
|
|
|
54
54
|
|
|
55
55
|
三种可能:
|
|
56
56
|
|
|
57
|
-
1. **在登录节点跑 torch** ——
|
|
58
|
-
之后也 import 不了。必须用
|
|
57
|
+
1. **在登录节点跑 torch** —— 郑州集群最近实测是缺 `libgalaxyhip.so.5`,旧环境
|
|
58
|
+
报过 `libmsgpackc.so.2`;即使 `module load` 之后也 import 不了。必须用
|
|
59
|
+
`srun`/`sbatch`。
|
|
59
60
|
2. **忘了 `module load`** —— venv 激活前必须先
|
|
60
61
|
`module load compiler/gcc/9.3.0 compiler/dtk/26.04`。
|
|
61
62
|
3. **顺序错了** —— 先激活 venv 再 module load 也会失败,顺序必须是 module 在前。
|
|
@@ -84,6 +85,14 @@ Triton。DTK 26.04 的 HIP 运行时缺上游 ROCm 7 的 API,Triton 的 AMD ba
|
|
|
84
85
|
即失败。**放弃这条路**,包括所有依赖 Triton kernel 的框架(SGLang FP8、vLLM ROCm
|
|
85
86
|
MoE、transformers finegrained-fp8)。
|
|
86
87
|
|
|
88
|
+
郑州集群最近还会先报另一种缺库:
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
ImportError: libgcvm.so.17git: cannot open shared object file
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
本质相同,都是 Triton 在当前 DTK/DCU 软件栈上不可用,不要继续调 Triton kernel。
|
|
95
|
+
|
|
87
96
|
### `torch._scaled_mm is only supported on CUDA devices with compute capability >= 9.0 or 8.9, or ROCm MI300+`
|
|
88
97
|
|
|
89
98
|
gfx936 不支持硬件 FP8 计算。需要软件反量化(把 FP8 权重转成 BF16 再算)。
|
|
@@ -127,6 +136,24 @@ ROCM_PATH=未设置
|
|
|
127
136
|
|
|
128
137
|
自检:作业里打印 `echo "ROCM_PATH=${ROCM_PATH:-未设置}"`,未设置就说明没生效。
|
|
129
138
|
|
|
139
|
+
### `python3: command not found`(昆山计算节点)
|
|
140
|
+
|
|
141
|
+
昆山计算节点 `module purge` 后没有 `python3` 命令,裸 `python` 是 Python 2,
|
|
142
|
+
会再报:
|
|
143
|
+
|
|
144
|
+
```
|
|
145
|
+
SyntaxError: Non-ASCII character '\xe5' ... no encoding declared
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
修法:作业脚本里显式加载 Python 3 module:
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
module load python/3.8.10
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
`scripts/run-compute-probe.sh` 会自动从 `module avail` 探测 `python/3.*` 模块,
|
|
155
|
+
但普通业务作业仍需在 `MODULE_LOADS` 或 `PYTHON_MODULE` 里带上。
|
|
156
|
+
|
|
130
157
|
### `TIMEOUT`
|
|
131
158
|
|
|
132
159
|
`--time` 不够。注意大模型加载本身就很慢(DeepSeek V4 加载 154B 权重约 27 分钟),
|
|
@@ -16,6 +16,25 @@ list_clusters() {
|
|
|
16
16
|
find "$CLUSTER_DIR" -maxdepth 1 -name '*.conf' ! -name '_*' -exec basename {} .conf \; 2>/dev/null | sort
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
refresh_live_node_count() {
|
|
20
|
+
[ "${SCNET_HPC_LIVE_NODE_COUNT:-yes}" != "no" ] || return 0
|
|
21
|
+
[ "${SCHEDULER:-slurm}" = "slurm" ] || return 0
|
|
22
|
+
[ -n "${CLUSTER_ID:-}" ] || return 0
|
|
23
|
+
[ -n "${PARTITION:-}" ] || return 0
|
|
24
|
+
|
|
25
|
+
local live
|
|
26
|
+
live=$(ssh -o BatchMode=yes -o ConnectTimeout=10 -o ServerAliveInterval=15 -o ServerAliveCountMax=2 \
|
|
27
|
+
"$CLUSTER_ID" "scontrol show partition ${PARTITION}" 2>/dev/null \
|
|
28
|
+
| tr ' ' '\n' | sed -n 's/^TotalNodes=//p' | head -1)
|
|
29
|
+
|
|
30
|
+
if [[ "$live" =~ ^[0-9]+$ ]] && [ "$live" -gt 0 ]; then
|
|
31
|
+
NODE_COUNT="$live"
|
|
32
|
+
NODE_COUNT_SOURCE="live"
|
|
33
|
+
else
|
|
34
|
+
NODE_COUNT_SOURCE="${NODE_COUNT_SOURCE:-profile/cache}"
|
|
35
|
+
fi
|
|
36
|
+
}
|
|
37
|
+
|
|
19
38
|
load_cluster() {
|
|
20
39
|
local want="${1:-}"
|
|
21
40
|
local available
|
|
@@ -45,11 +64,32 @@ $(printf '%s\n' "$available" | sed 's/^/ /')"
|
|
|
45
64
|
. "$conf"
|
|
46
65
|
|
|
47
66
|
[ -n "${CLUSTER_ID:-}" ] || die "$conf 里 CLUSTER_ID 为空"
|
|
48
|
-
[ -n "${SSH_HOST:-}" ] || die "$conf 里 SSH_HOST 为空"
|
|
49
67
|
: "${SSH_PORT:=22}"
|
|
50
68
|
: "${HOME_BASE:=/public/home}"
|
|
51
69
|
|
|
70
|
+
# SSH_HOST/SSH_PORT 应以 profile 为准。这里只作为兼容回退:
|
|
71
|
+
# 旧 profile 漏填时,从本机 ~/.ssh/config 动态补全,避免脚本直接失败。
|
|
72
|
+
if [ -z "${SSH_HOST:-}" ]; then
|
|
73
|
+
SSH_HOST="$(ssh -G "$CLUSTER_ID" 2>/dev/null | awk '/^hostname /{print $2; exit}')"
|
|
74
|
+
fi
|
|
75
|
+
if [ -z "${SSH_PORT:-}" ]; then
|
|
76
|
+
SSH_PORT="$(ssh -G "$CLUSTER_ID" 2>/dev/null | awk '/^port /{print $2; exit}')"
|
|
77
|
+
fi
|
|
78
|
+
REMOTE_USER_DETECTED="$(ssh -G "$CLUSTER_ID" 2>/dev/null | awk '/^user /{print $2; exit}')"
|
|
79
|
+
: "${REMOTE_USER_DETECTED:=}"
|
|
80
|
+
|
|
81
|
+
# 用于设置新连接的新集群仍需明确的 SSH_HOST/SSH_PORT;已有本机 ssh 别名时
|
|
82
|
+
# 这里会从 ssh -G 补齐。若仍为空,给一个可操作的错误。
|
|
83
|
+
[ -n "$SSH_HOST" ] || die "$conf 里 SSH_HOST 为空,且 ssh -G ${CLUSTER_ID} 也解析不到 HostName;请先配置 ~/.ssh/config 或补 profile。"
|
|
84
|
+
|
|
52
85
|
CLUSTER_CONF="$conf"
|
|
86
|
+
CLUSTER_AUTO_CONF="$CLUSTER_DIR/.cache/$want.auto.conf"
|
|
87
|
+
if [ "${SCNET_HPC_USE_AUTO:-yes}" != "no" ] && [ -f "$CLUSTER_AUTO_CONF" ]; then
|
|
88
|
+
# shellcheck disable=SC1090
|
|
89
|
+
. "$CLUSTER_AUTO_CONF"
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
refresh_live_node_count
|
|
53
93
|
}
|
|
54
94
|
|
|
55
95
|
# 从参数里摘出 --cluster <名>,剩下的放进 REST[]
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""在目标计算节点上做一次最小能力探针。
|
|
3
|
+
|
|
4
|
+
输出统一使用 PROBE_ 前缀的 KEY=value 行,方便 refresh-cluster.sh 解析。
|
|
5
|
+
每个检查都独立捕获异常,单项失败不会中断后续检查。
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import urllib.request
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def emit(key, value):
|
|
13
|
+
print("PROBE_{}={}".format(key, value), flush=True)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def short_error(exc, limit=180):
|
|
17
|
+
text = str(exc).replace("\n", " ")
|
|
18
|
+
return "{}:{}".format(type(exc).__name__, text[:limit])
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
import torch
|
|
23
|
+
emit("TORCH_VERSION", torch.__version__)
|
|
24
|
+
cuda_available = bool(torch.cuda.is_available())
|
|
25
|
+
emit("CUDA_AVAILABLE", "1" if cuda_available else "0")
|
|
26
|
+
|
|
27
|
+
if cuda_available:
|
|
28
|
+
count = torch.cuda.device_count()
|
|
29
|
+
emit("DEVICE_COUNT", count)
|
|
30
|
+
|
|
31
|
+
props = torch.cuda.get_device_properties(0)
|
|
32
|
+
emit("DEVICE_NAME", getattr(props, "name", "unknown").replace("\n", " "))
|
|
33
|
+
|
|
34
|
+
arch = getattr(props, "gcnArchName", None)
|
|
35
|
+
if arch is None:
|
|
36
|
+
arch = "{}.{}".format(getattr(props, "major", "?"), getattr(props, "minor", "?"))
|
|
37
|
+
emit("ARCH", arch)
|
|
38
|
+
|
|
39
|
+
total_memory = getattr(props, "total_memory", 0)
|
|
40
|
+
emit("MEM_MIB", int(total_memory // (1024 * 1024)))
|
|
41
|
+
|
|
42
|
+
have_dtypes = []
|
|
43
|
+
for dtype_name in ["float8_e4m3fn", "float8_e5m2", "float8_e8m0fnu", "bfloat16"]:
|
|
44
|
+
if hasattr(torch, dtype_name):
|
|
45
|
+
have_dtypes.append(dtype_name)
|
|
46
|
+
emit("DTYPES", ",".join(have_dtypes))
|
|
47
|
+
|
|
48
|
+
# 硬件 FP8 matmul
|
|
49
|
+
try:
|
|
50
|
+
if hasattr(torch, "float8_e4m3fn") and hasattr(torch, "_scaled_mm"):
|
|
51
|
+
a = torch.randn(64, 128).to(torch.float8_e4m3fn).cuda()
|
|
52
|
+
b = torch.randn(128, 64).to(torch.float8_e4m3fn).cuda().t().contiguous().t()
|
|
53
|
+
torch._scaled_mm(
|
|
54
|
+
a,
|
|
55
|
+
b,
|
|
56
|
+
scale_a=torch.tensor(1.0).cuda(),
|
|
57
|
+
scale_b=torch.tensor(1.0).cuda(),
|
|
58
|
+
out_dtype=torch.bfloat16,
|
|
59
|
+
)
|
|
60
|
+
torch.cuda.synchronize()
|
|
61
|
+
emit("SCALED_MM", "OK")
|
|
62
|
+
else:
|
|
63
|
+
emit("SCALED_MM", "SKIP:float8_e4m3fn_or_scaled_mm_missing")
|
|
64
|
+
except Exception as exc:
|
|
65
|
+
emit("SCALED_MM", "ERR:" + short_error(exc))
|
|
66
|
+
|
|
67
|
+
# Triton 内核
|
|
68
|
+
try:
|
|
69
|
+
import triton
|
|
70
|
+
import triton.language as tl
|
|
71
|
+
|
|
72
|
+
@triton.jit
|
|
73
|
+
def add_kernel(x, y, out, n, BLOCK: tl.constexpr):
|
|
74
|
+
idx = tl.program_id(0) * BLOCK + tl.arange(0, BLOCK)
|
|
75
|
+
mask = idx < n
|
|
76
|
+
tl.store(out + idx, tl.load(x + idx, mask=mask) + tl.load(y + idx, mask=mask), mask=mask)
|
|
77
|
+
|
|
78
|
+
n = 4096
|
|
79
|
+
x = torch.randn(n, device="cuda")
|
|
80
|
+
y = torch.randn(n, device="cuda")
|
|
81
|
+
out = torch.empty_like(x)
|
|
82
|
+
add_kernel[(triton.cdiv(n, 256),)](x, y, out, n, BLOCK=256)
|
|
83
|
+
torch.cuda.synchronize()
|
|
84
|
+
max_err = float((out - (x + y)).abs().max().item())
|
|
85
|
+
emit("TRITON", "OK err={:.3g}".format(max_err))
|
|
86
|
+
except Exception as exc:
|
|
87
|
+
emit("TRITON", "ERR:" + short_error(exc, 240))
|
|
88
|
+
|
|
89
|
+
# bitsandbytes:先看能否 import;是否真正可用仍需业务级测试
|
|
90
|
+
try:
|
|
91
|
+
import bitsandbytes as bnb
|
|
92
|
+
emit("BITSANDBYTES", "IMPORT_OK version={}".format(getattr(bnb, "__version__", "unknown")))
|
|
93
|
+
except Exception as exc:
|
|
94
|
+
emit("BITSANDBYTES", "IMPORT_ERR:" + short_error(exc))
|
|
95
|
+
else:
|
|
96
|
+
emit("DEVICE_COUNT", 0)
|
|
97
|
+
except Exception as exc:
|
|
98
|
+
emit("TORCH_IMPORT", "ERR:" + short_error(exc, 240))
|
|
99
|
+
|
|
100
|
+
# 计算节点外网
|
|
101
|
+
try:
|
|
102
|
+
urllib.request.urlopen("https://pypi.org", timeout=5)
|
|
103
|
+
emit("NET_PYPI", "ONLINE")
|
|
104
|
+
except Exception:
|
|
105
|
+
emit("NET_PYPI", "OFFLINE")
|
|
106
|
+
|
|
107
|
+
# /tmp 是否跨节点共享无法只靠单节点可靠判断,这里只记录文件系统信息
|
|
108
|
+
try:
|
|
109
|
+
emit("TMP_FSTYPE", str(os.statvfs("/tmp").f_fsid))
|
|
110
|
+
except Exception as exc:
|
|
111
|
+
emit("TMP_FSTYPE", "ERR:" + short_error(exc))
|
|
112
|
+
emit("TMP_SHARED", "UNKNOWN")
|
|
113
|
+
|
|
114
|
+
print("PROBE_EXIT=0", flush=True)
|
|
@@ -14,11 +14,15 @@ set -- "${REST[@]+${REST[@]}}"
|
|
|
14
14
|
|
|
15
15
|
# --user <名>:远端用户名与本地不同时指定(日志路径需要真实用户名)
|
|
16
16
|
REMOTE_USER=""
|
|
17
|
+
REFRESH=no
|
|
18
|
+
USE_AUTO=yes
|
|
17
19
|
ARGS=()
|
|
18
20
|
while [ $# -gt 0 ]; do
|
|
19
21
|
case "$1" in
|
|
20
22
|
--user) REMOTE_USER="${2:-}"; shift 2 ;;
|
|
21
23
|
--user=*) REMOTE_USER="${1#*=}"; shift ;;
|
|
24
|
+
--refresh) REFRESH=yes; shift ;;
|
|
25
|
+
--no-auto) USE_AUTO=no; shift ;;
|
|
22
26
|
*) ARGS+=("$1"); shift ;;
|
|
23
27
|
esac
|
|
24
28
|
done
|
|
@@ -31,7 +35,7 @@ TIME="${4:-00:20:00}"
|
|
|
31
35
|
|
|
32
36
|
if [ -z "$NAME" ]; then
|
|
33
37
|
cat >&2 <<EOF
|
|
34
|
-
用法: $0 [--cluster <集群短名>] [--user <远端用户名>] <作业名> [加速器数] [cpu数] [时长]
|
|
38
|
+
用法: $0 [--cluster <集群短名>] [--user <远端用户名>] [--refresh|--no-auto] <作业名> [加速器数] [cpu数] [时长]
|
|
35
39
|
|
|
36
40
|
例子:
|
|
37
41
|
$0 probe # 1 卡, 8 核, 20 分钟(探针)
|
|
@@ -42,6 +46,11 @@ EOF
|
|
|
42
46
|
exit 1
|
|
43
47
|
fi
|
|
44
48
|
|
|
49
|
+
if [ "$REFRESH" = yes ]; then
|
|
50
|
+
"$REPO_ROOT/scripts/refresh-cluster.sh" --cluster "${CLUSTER:-}"
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
export SCNET_HPC_USE_AUTO="$USE_AUTO"
|
|
45
54
|
load_cluster "$CLUSTER"
|
|
46
55
|
|
|
47
56
|
[ "${SCHEDULER:-slurm}" = "slurm" ] \
|
|
@@ -69,7 +78,7 @@ fi
|
|
|
69
78
|
# 远端用户名。#SBATCH 是注释行,Slurm 不做变量展开 —— 日志路径里写 $USER
|
|
70
79
|
# 会被当成字面量目录名,作业以 exit 53 失败且不产生日志。所以这里必须展开成
|
|
71
80
|
# 真实用户名。默认取本地用户名,不同时用 --user 覆盖。
|
|
72
|
-
REMOTE_USER="${REMOTE_USER:-$(whoami)}"
|
|
81
|
+
REMOTE_USER="${REMOTE_USER:-${REMOTE_USER_DETECTED:-$(whoami)}}"
|
|
73
82
|
HOME_DIR="${HOME_BASE}/${REMOTE_USER}"
|
|
74
83
|
|
|
75
84
|
# 脚本体(非 #SBATCH 行)里可以正常用 $USER
|
|
@@ -137,6 +146,7 @@ sed -i.bak '/^$/N;/^\n$/D' "$OUT" 2>/dev/null && rm -f "$OUT.bak"
|
|
|
137
146
|
|
|
138
147
|
echo "已生成 $OUT"
|
|
139
148
|
echo " 集群=${CLUSTER_ID} ${GRES_TYPE:-加速器}=${ACCEL} CPU=${CPUS} 内存=${MEM_NOTE} 时长=${TIME}"
|
|
149
|
+
echo " 分区=${PARTITION:-未设置} 节点数=${NODE_COUNT:-未探测}(来源:${NODE_COUNT_SOURCE:-profile/cache})"
|
|
140
150
|
echo " 日志目录=${HOME_DIR}/scripts/logs(用户名 ${REMOTE_USER},不对请加 --user)"
|
|
141
151
|
echo
|
|
142
152
|
echo "上传并提交:"
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# 动态刷新 clusters/.cache/<集群>.auto.conf。
|
|
3
|
+
#
|
|
4
|
+
# 用法:
|
|
5
|
+
# ./refresh-cluster.sh [--cluster <集群短名>] [--compute] [--dry-run]
|
|
6
|
+
#
|
|
7
|
+
# 默认只做登录节点上的只读探测;--compute 会提交一个 1 卡、4 核、10 分钟的小作业,
|
|
8
|
+
# 到计算节点实测加速器、Triton、FP8、外网等能力。生成的缓存会覆盖 profile 的同名字段。
|
|
9
|
+
|
|
10
|
+
set -euo pipefail
|
|
11
|
+
|
|
12
|
+
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_common.sh"
|
|
13
|
+
|
|
14
|
+
parse_cluster_arg "$@"
|
|
15
|
+
set -- "${REST[@]+${REST[@]}}"
|
|
16
|
+
|
|
17
|
+
COMPUTE=no
|
|
18
|
+
DRY_RUN=no
|
|
19
|
+
|
|
20
|
+
while [ $# -gt 0 ]; do
|
|
21
|
+
case "$1" in
|
|
22
|
+
--compute|-c) COMPUTE=yes; shift ;;
|
|
23
|
+
--dry-run|-n) DRY_RUN=yes; shift ;;
|
|
24
|
+
-h|--help) sed -n '2,14p' "$0" | sed 's/^# \?//'; exit 0 ;;
|
|
25
|
+
*) die "未知参数: $1" ;;
|
|
26
|
+
esac
|
|
27
|
+
done
|
|
28
|
+
|
|
29
|
+
load_cluster "$CLUSTER"
|
|
30
|
+
|
|
31
|
+
r() {
|
|
32
|
+
ssh -o BatchMode=yes -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=3 \
|
|
33
|
+
"$CLUSTER_ID" "$1" 2>/dev/null || true
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
probe_value() {
|
|
37
|
+
local key="$1"
|
|
38
|
+
printf '%s\n' "${COMPUTE_OUT:-}" | awk -F= -v k="$key" '$1 == k { sub(/^[^=]+=/,""); print; exit }'
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
add_known_limit() {
|
|
42
|
+
local item="$1"
|
|
43
|
+
[ -n "$item" ] || return 0
|
|
44
|
+
case "$item" in
|
|
45
|
+
*Triton*)
|
|
46
|
+
if printf '%s' "$KNOWN_LIMITS_NEW" | grep -q 'Triton'; then return 0; fi
|
|
47
|
+
;;
|
|
48
|
+
*FP8*)
|
|
49
|
+
if printf '%s' "$KNOWN_LIMITS_NEW" | grep -q 'FP8'; then return 0; fi
|
|
50
|
+
;;
|
|
51
|
+
*BF16*)
|
|
52
|
+
if printf '%s' "$KNOWN_LIMITS_NEW" | grep -q 'BF16'; then return 0; fi
|
|
53
|
+
;;
|
|
54
|
+
*bitsandbytes*)
|
|
55
|
+
if printf '%s' "$KNOWN_LIMITS_NEW" | grep -q 'bitsandbytes'; then return 0; fi
|
|
56
|
+
;;
|
|
57
|
+
esac
|
|
58
|
+
KNOWN_LIMITS_NEW="${KNOWN_LIMITS_NEW:+${KNOWN_LIMITS_NEW};}${item}"
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
info "==> 动态探测 ${CLUSTER_ID} (${CLUSTER_DESC:-$SSH_HOST})"
|
|
62
|
+
probe_ok=$(r 'echo yes')
|
|
63
|
+
[ "$probe_ok" = "yes" ] || die "无法通过 ssh 连接 ${CLUSTER_ID}"
|
|
64
|
+
|
|
65
|
+
# ---- 连接和登录节点基础信息 ----
|
|
66
|
+
DETECTED_HOST="$(ssh -G "$CLUSTER_ID" 2>/dev/null | awk '/^hostname /{print $2; exit}')"
|
|
67
|
+
DETECTED_PORT="$(ssh -G "$CLUSTER_ID" 2>/dev/null | awk '/^port /{print $2; exit}')"
|
|
68
|
+
DETECTED_USER="$(ssh -G "$CLUSTER_ID" 2>/dev/null | awk '/^user /{print $2; exit}')"
|
|
69
|
+
LOGIN_HOST=$(r 'hostname')
|
|
70
|
+
REMOTE_HOME=$(r 'echo $HOME')
|
|
71
|
+
HOME_BASE_NEW=$(dirname "$REMOTE_HOME")
|
|
72
|
+
CPU_MODEL_NEW=$(r "lscpu | sed -n 's/^Model name:[[:space:]]*//p' | head -1")
|
|
73
|
+
QUOTA_NEW=$(r "df -BG $REMOTE_HOME 2>/dev/null | awk 'NR==2{gsub(/[^0-9]/,\"\",\$2); print \$2}'")
|
|
74
|
+
|
|
75
|
+
# ---- 调度器 ----
|
|
76
|
+
SCHED_NEW="unknown"
|
|
77
|
+
if [ -n "$(r 'command -v sbatch')" ]; then
|
|
78
|
+
SCHED_NEW="slurm"
|
|
79
|
+
elif [ -n "$(r 'command -v qsub')" ]; then
|
|
80
|
+
SCHED_NEW="pbs"
|
|
81
|
+
fi
|
|
82
|
+
|
|
83
|
+
PART_NEW=""
|
|
84
|
+
DEFMEM_NEW=""
|
|
85
|
+
MAXWALL_NEW=""
|
|
86
|
+
MINGRES_NEW=""
|
|
87
|
+
GRESTYPE_NEW=""
|
|
88
|
+
NODECNT_NEW=""
|
|
89
|
+
CPUS_NEW=""
|
|
90
|
+
NODEMEM_NEW=""
|
|
91
|
+
ACCEL_NEW=""
|
|
92
|
+
|
|
93
|
+
if [ "$SCHED_NEW" = "slurm" ]; then
|
|
94
|
+
# 优先沿用 profile 现有分区,只在它已经不再 up 时才改选第一个 up 分区。
|
|
95
|
+
if [ -n "${PARTITION:-}" ]; then
|
|
96
|
+
PART_STATE=$(r "sinfo -h -p ${PARTITION} -o '%a' | head -1")
|
|
97
|
+
[ "$PART_STATE" = "up" ] && PART_NEW="$PARTITION"
|
|
98
|
+
fi
|
|
99
|
+
if [ -z "$PART_NEW" ]; then
|
|
100
|
+
PART_NEW=$(r "sinfo -h -o '%P %a' | awk '\$2==\"up\"{print \$1; exit}'" | tr -d '*')
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
if [ -n "$PART_NEW" ]; then
|
|
104
|
+
SCON=$(r "scontrol show partition $PART_NEW")
|
|
105
|
+
DEFMEM_NEW=$(printf '%s' "$SCON" | tr ' ' '\n' | sed -n 's/^DefMemPerCPU=//p' | head -1)
|
|
106
|
+
MAXWALL_NEW=$(printf '%s' "$SCON" | tr ' ' '\n' | sed -n 's/^MaxTime=//p' | head -1)
|
|
107
|
+
NODECNT_NEW=$(printf '%s' "$SCON" | tr ' ' '\n' | sed -n 's/^TotalNodes=//p' | head -1)
|
|
108
|
+
|
|
109
|
+
QOS_NEW=$(printf '%s' "$SCON" | tr ' ' '\n' | sed -n 's/^QoS=//p' | head -1)
|
|
110
|
+
if [ -n "$QOS_NEW" ] && [ "$QOS_NEW" != "N/A" ]; then
|
|
111
|
+
MINTRES=$(r "sacctmgr -nP show qos where name=$QOS_NEW format=MinTRES")
|
|
112
|
+
MINGRES_NEW=$(printf '%s' "$MINTRES" | sed -n 's|.*gres/\([a-z]*\)=\([0-9]*\).*|\1:\2|p')
|
|
113
|
+
fi
|
|
114
|
+
|
|
115
|
+
NODEINFO=$(r "sinfo -h -p $PART_NEW -o '%c|%m|%G' | head -1")
|
|
116
|
+
CPUS_NEW=$(printf '%s' "$NODEINFO" | cut -d'|' -f1)
|
|
117
|
+
NODEMEM_NEW=$(printf '%s' "$NODEINFO" | cut -d'|' -f2 | awk '{printf "%d", $1/1024}')
|
|
118
|
+
GRESRAW=$(printf '%s' "$NODEINFO" | cut -d'|' -f3)
|
|
119
|
+
GRESBASE=${GRESRAW%%(*}
|
|
120
|
+
GRESTYPE_NEW=$(printf '%s' "$GRESBASE" | cut -d: -f1)
|
|
121
|
+
ACCEL_NEW=$(printf '%s' "$GRESBASE" | awk -F: '{print $NF}' | tr -dc '0-9')
|
|
122
|
+
fi
|
|
123
|
+
fi
|
|
124
|
+
|
|
125
|
+
# ---- 登录节点网络 ----
|
|
126
|
+
NET_OK_NEW=""
|
|
127
|
+
NET_BLOCKED_NEW=""
|
|
128
|
+
for h in pypi.org pypi.tuna.tsinghua.edu.cn modelscope.cn huggingface.co github.com; do
|
|
129
|
+
reachable=no
|
|
130
|
+
for _ in 1 2; do
|
|
131
|
+
code=$(r "timeout 15 curl -sI https://$h -o /dev/null -w '%{http_code}'")
|
|
132
|
+
case "$code" in
|
|
133
|
+
2*|3*) reachable=yes; break ;;
|
|
134
|
+
esac
|
|
135
|
+
done
|
|
136
|
+
if [ "$reachable" = yes ]; then
|
|
137
|
+
NET_OK_NEW="${NET_OK_NEW:+$NET_OK_NEW }$h"
|
|
138
|
+
else
|
|
139
|
+
NET_BLOCKED_NEW="${NET_BLOCKED_NEW:+$NET_BLOCKED_NEW }$h"
|
|
140
|
+
fi
|
|
141
|
+
done
|
|
142
|
+
|
|
143
|
+
# ---- 登录节点 torch 缺库 ----
|
|
144
|
+
torch_login=$(r 'python3 -c "import torch" 2>&1 | tail -1')
|
|
145
|
+
MISSING_LIB_NEW=$(printf '%s' "$torch_login" | sed -n 's/^ImportError: \([^:]*\.so[^:]*\).*/\1/p')
|
|
146
|
+
|
|
147
|
+
# ---- module 候选(只记录,不自动覆盖 MODULE_LOADS)----
|
|
148
|
+
MODULE_CANDIDATES_NEW=$(r "bash -lc 'module avail 2>&1' | awk '/compiler\\/(dtk|rocm)/{print}' | sort -u | head -30")
|
|
149
|
+
|
|
150
|
+
# ---- 计算节点能力(可选)----
|
|
151
|
+
COMPUTE_OUT=""
|
|
152
|
+
ACCEL_NAME_NEW=""
|
|
153
|
+
ACCEL_ARCH_NEW=""
|
|
154
|
+
ACCEL_MEM_NEW=""
|
|
155
|
+
COMPUTE_OFFLINE_NEW=""
|
|
156
|
+
KNOWN_LIMITS_NEW=""
|
|
157
|
+
|
|
158
|
+
if [ "$COMPUTE" = yes ] && [ "$DRY_RUN" = no ]; then
|
|
159
|
+
info "==> 提交计算节点小探针(约 1 卡、4 核、10 分钟)"
|
|
160
|
+
COMPUTE_OUT="$( { "$REPO_ROOT/scripts/run-compute-probe.sh" --cluster "$CLUSTER_ID"; } 2>&1 )" || true
|
|
161
|
+
if ! printf '%s\n' "$COMPUTE_OUT" | grep -q '^PROBE_'; then
|
|
162
|
+
info "计算节点探针未产生有效 PROBE 输出:"
|
|
163
|
+
printf '%s\n' "$COMPUTE_OUT" | sed 's/^/ /' >&2
|
|
164
|
+
COMPUTE_OUT=""
|
|
165
|
+
fi
|
|
166
|
+
fi
|
|
167
|
+
|
|
168
|
+
if [ "$COMPUTE" = yes ] && [ -n "$COMPUTE_OUT" ]; then
|
|
169
|
+
CUDA_AVAILABLE=$(probe_value PROBE_CUDA_AVAILABLE)
|
|
170
|
+
ACCEL_NAME_NEW=$(probe_value PROBE_DEVICE_NAME)
|
|
171
|
+
case "$ACCEL_NAME_NEW" in
|
|
172
|
+
BW|BW1000) ACCEL_NAME_NEW="海光 BW1000 DCU" ;;
|
|
173
|
+
esac
|
|
174
|
+
ACCEL_ARCH_NEW=$(probe_value PROBE_ARCH)
|
|
175
|
+
|
|
176
|
+
MEM_MIB=$(probe_value PROBE_MEM_MIB)
|
|
177
|
+
if [[ "$MEM_MIB" =~ ^[0-9]+$ ]]; then
|
|
178
|
+
ACCEL_MEM_NEW=$(( (MEM_MIB + 1023) / 1024 ))
|
|
179
|
+
fi
|
|
180
|
+
|
|
181
|
+
case "$(probe_value PROBE_NET_PYPI)" in
|
|
182
|
+
OFFLINE) COMPUTE_OFFLINE_NEW=yes ;;
|
|
183
|
+
ONLINE) COMPUTE_OFFLINE_NEW=no ;;
|
|
184
|
+
esac
|
|
185
|
+
|
|
186
|
+
if [ "$CUDA_AVAILABLE" = "0" ]; then
|
|
187
|
+
KNOWN_LIMITS_NEW="torch.cuda 不可用"
|
|
188
|
+
fi
|
|
189
|
+
|
|
190
|
+
DTYPES=$(probe_value PROBE_DTYPES)
|
|
191
|
+
case ",$DTYPES," in
|
|
192
|
+
*,bfloat16,*) ;;
|
|
193
|
+
*) KNOWN_LIMITS_NEW="${KNOWN_LIMITS_NEW:+${KNOWN_LIMITS_NEW};}无 BF16 数据类型支持" ;;
|
|
194
|
+
esac
|
|
195
|
+
|
|
196
|
+
SCALED=$(probe_value PROBE_SCALED_MM)
|
|
197
|
+
case "$SCALED" in
|
|
198
|
+
ERR:*) KNOWN_LIMITS_NEW="${KNOWN_LIMITS_NEW:+${KNOWN_LIMITS_NEW};}硬件 FP8 torch._scaled_mm 不可用(${SCALED})" ;;
|
|
199
|
+
esac
|
|
200
|
+
|
|
201
|
+
TRITON=$(probe_value PROBE_TRITON)
|
|
202
|
+
case "$TRITON" in
|
|
203
|
+
ERR:*) KNOWN_LIMITS_NEW="${KNOWN_LIMITS_NEW:+${KNOWN_LIMITS_NEW};}Triton 不可用(${TRITON})" ;;
|
|
204
|
+
esac
|
|
205
|
+
|
|
206
|
+
BNB=$(probe_value PROBE_BITSANDBYTES)
|
|
207
|
+
case "$BNB" in
|
|
208
|
+
IMPORT_ERR:*) KNOWN_LIMITS_NEW="${KNOWN_LIMITS_NEW:+${KNOWN_LIMITS_NEW};}bitsandbytes 导入失败(${BNB})" ;;
|
|
209
|
+
esac
|
|
210
|
+
|
|
211
|
+
# 保留 profile 里已有的人工经验,动态结果放前面。
|
|
212
|
+
if [ -n "${KNOWN_LIMITATIONS:-}" ]; then
|
|
213
|
+
OLD_IFS="$IFS"
|
|
214
|
+
IFS=';'
|
|
215
|
+
for item in $KNOWN_LIMITATIONS; do
|
|
216
|
+
add_known_limit "$item"
|
|
217
|
+
done
|
|
218
|
+
IFS="$OLD_IFS"
|
|
219
|
+
fi
|
|
220
|
+
elif [ "$COMPUTE" = yes ]; then
|
|
221
|
+
info "计算节点探针没有返回结果,保留 profile 中的加速器和限制信息。"
|
|
222
|
+
fi
|
|
223
|
+
|
|
224
|
+
build_cache() {
|
|
225
|
+
echo "# 由 scripts/refresh-cluster.sh 自动生成于 $(date '+%Y-%m-%d %H:%M:%S')"
|
|
226
|
+
echo "# 登录节点: ${LOGIN_HOST:-未探测}"
|
|
227
|
+
echo "# 这是动态探测缓存,会覆盖 profile 中的同名字段;删除本文件即可回退到纯 profile。"
|
|
228
|
+
printf 'SSH_HOST=%q\n' "${DETECTED_HOST:-$SSH_HOST}"
|
|
229
|
+
printf 'SSH_PORT=%q\n' "${DETECTED_PORT:-$SSH_PORT}"
|
|
230
|
+
printf 'REMOTE_USER_DETECTED=%q\n' "${DETECTED_USER:-${REMOTE_USER_DETECTED:-}}"
|
|
231
|
+
printf 'HOME_BASE=%q\n' "${HOME_BASE_NEW:-$HOME_BASE}"
|
|
232
|
+
printf 'SCHEDULER=%q\n' "${SCHED_NEW:-${SCHEDULER:-}}"
|
|
233
|
+
printf 'PARTITION=%q\n' "${PART_NEW:-${PARTITION:-}}"
|
|
234
|
+
printf 'DEF_MEM_PER_CPU=%q\n' "${DEFMEM_NEW:-${DEF_MEM_PER_CPU:-}}"
|
|
235
|
+
printf 'MIN_GRES=%q\n' "${MINGRES_NEW:-${MIN_GRES:-}}"
|
|
236
|
+
printf 'GRES_TYPE=%q\n' "${GRESTYPE_NEW:-${GRES_TYPE:-}}"
|
|
237
|
+
printf 'MAX_WALLTIME=%q\n' "${MAXWALL_NEW:-${MAX_WALLTIME:-}}"
|
|
238
|
+
printf 'CPUS_PER_NODE=%q\n' "${CPUS_NEW:-${CPUS_PER_NODE:-}}"
|
|
239
|
+
printf 'NODE_MEM_GB=%q\n' "${NODEMEM_NEW:-${NODE_MEM_GB:-}}"
|
|
240
|
+
printf 'NODE_COUNT=%q\n' "${NODECNT_NEW:-${NODE_COUNT:-}}"
|
|
241
|
+
printf 'ACCEL_PER_NODE=%q\n' "${ACCEL_NEW:-${ACCEL_PER_NODE:-}}"
|
|
242
|
+
printf 'CPU_MODEL=%q\n' "${CPU_MODEL_NEW:-${CPU_MODEL:-}}"
|
|
243
|
+
printf 'LOGIN_NODES=%q\n' "${LOGIN_HOST:-${LOGIN_NODES:-}}"
|
|
244
|
+
printf 'HOME_QUOTA_GB=%q\n' "${QUOTA_NEW:-${HOME_QUOTA_GB:-}}"
|
|
245
|
+
printf 'NET_OK=%q\n' "${NET_OK_NEW:-${NET_OK:-}}"
|
|
246
|
+
printf 'NET_BLOCKED=%q\n' "${NET_BLOCKED_NEW:-${NET_BLOCKED:-}}"
|
|
247
|
+
printf 'LOGIN_NODE_MISSING_LIBS=%q\n' "${MISSING_LIB_NEW:-${LOGIN_NODE_MISSING_LIBS:-}}"
|
|
248
|
+
printf 'MODULE_CANDIDATES=%q\n' "${MODULE_CANDIDATES_NEW:-}"
|
|
249
|
+
|
|
250
|
+
if [ "$COMPUTE" = yes ]; then
|
|
251
|
+
printf 'ACCEL_NAME=%q\n' "${ACCEL_NAME_NEW:-${ACCEL_NAME:-}}"
|
|
252
|
+
printf 'ACCEL_ARCH=%q\n' "${ACCEL_ARCH_NEW:-${ACCEL_ARCH:-}}"
|
|
253
|
+
printf 'ACCEL_MEM_GB=%q\n' "${ACCEL_MEM_NEW:-${ACCEL_MEM_GB:-}}"
|
|
254
|
+
printf 'COMPUTE_NODE_OFFLINE=%q\n' "${COMPUTE_OFFLINE_NEW:-${COMPUTE_NODE_OFFLINE:-}}"
|
|
255
|
+
printf 'KNOWN_LIMITATIONS=%q\n' "${KNOWN_LIMITS_NEW:-${KNOWN_LIMITATIONS:-}}"
|
|
256
|
+
else
|
|
257
|
+
printf 'COMPUTE_NODE_OFFLINE=%q\n' "${COMPUTE_NODE_OFFLINE:-}"
|
|
258
|
+
printf 'KNOWN_LIMITATIONS=%q\n' "${KNOWN_LIMITATIONS:-}"
|
|
259
|
+
fi
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
mkdir -p "$CLUSTER_DIR/.cache"
|
|
263
|
+
if [ "$DRY_RUN" = yes ]; then
|
|
264
|
+
info "==> dry-run,不写缓存"
|
|
265
|
+
build_cache
|
|
266
|
+
else
|
|
267
|
+
AUTO_TMP="$CLUSTER_AUTO_CONF.tmp.$$"
|
|
268
|
+
build_cache > "$AUTO_TMP"
|
|
269
|
+
mv "$AUTO_TMP" "$CLUSTER_AUTO_CONF"
|
|
270
|
+
info "==> 已写入 $CLUSTER_AUTO_CONF"
|
|
271
|
+
|
|
272
|
+
if [ "$COMPUTE" = yes ] && [ -n "$COMPUTE_OUT" ]; then
|
|
273
|
+
PROBE_LOG="$CLUSTER_DIR/.cache/${CLUSTER_ID}.probe.log"
|
|
274
|
+
printf '%s\n' "$COMPUTE_OUT" > "$PROBE_LOG"
|
|
275
|
+
info "==> 原始探针输出已保存 $PROBE_LOG"
|
|
276
|
+
fi
|
|
277
|
+
fi
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# 在目标集群计算节点运行一次最小能力探针,输出 PROBE_ 开头的解析结果。
|
|
3
|
+
#
|
|
4
|
+
# 用法:
|
|
5
|
+
# ./run-compute-probe.sh [--cluster <集群短名>] [--cpus 4] [--time 00:10:00] [--accelerators 1]
|
|
6
|
+
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
|
|
9
|
+
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_common.sh"
|
|
10
|
+
|
|
11
|
+
parse_cluster_arg "$@"
|
|
12
|
+
set -- "${REST[@]+${REST[@]}}"
|
|
13
|
+
|
|
14
|
+
CPUS=4
|
|
15
|
+
TIME=00:10:00
|
|
16
|
+
ACCEL=1
|
|
17
|
+
REMOTE_USER=""
|
|
18
|
+
PYTHON_BIN=""
|
|
19
|
+
|
|
20
|
+
while [ $# -gt 0 ]; do
|
|
21
|
+
case "$1" in
|
|
22
|
+
--cpus) CPUS="${2:-$CPUS}"; shift 2 ;;
|
|
23
|
+
--cpus=*) CPUS="${1#*=}"; shift ;;
|
|
24
|
+
--time) TIME="${2:-$TIME}"; shift 2 ;;
|
|
25
|
+
--time=*) TIME="${1#*=}"; shift ;;
|
|
26
|
+
--accelerators) ACCEL="${2:-$ACCEL}"; shift 2 ;;
|
|
27
|
+
--accelerators=*) ACCEL="${1#*=}"; shift ;;
|
|
28
|
+
--user) REMOTE_USER="${2:-}"; shift 2 ;;
|
|
29
|
+
--user=*) REMOTE_USER="${1#*=}"; shift ;;
|
|
30
|
+
--python) PYTHON_BIN="${2:-}"; shift 2 ;;
|
|
31
|
+
--python=*) PYTHON_BIN="${1#*=}"; shift ;;
|
|
32
|
+
-h|--help) sed -n '2,10p' "$0" | sed 's/^# \?//'; exit 0 ;;
|
|
33
|
+
*) die "未知参数: $1" ;;
|
|
34
|
+
esac
|
|
35
|
+
done
|
|
36
|
+
|
|
37
|
+
[ -n "${CLUSTER:-}" ] || die "请用 --cluster <集群短名> 指定集群。"
|
|
38
|
+
load_cluster "$CLUSTER"
|
|
39
|
+
|
|
40
|
+
[ "${SCHEDULER:-slurm}" = "slurm" ] || die "${CLUSTER_ID} 不是 Slurm 调度器,暂不支持自动计算节点探针。"
|
|
41
|
+
[ -n "${PARTITION:-}" ] || die "${CLUSTER_ID} 的 PARTITION 为空,无法提交计算节点探针。"
|
|
42
|
+
|
|
43
|
+
REMOTE_USER="${REMOTE_USER:-${REMOTE_USER_DETECTED:-$(whoami)}}"
|
|
44
|
+
HOME_DIR="${HOME_BASE}/${REMOTE_USER}"
|
|
45
|
+
PROBE_BASE="${HOME_DIR}/scnet-hpc-probe"
|
|
46
|
+
|
|
47
|
+
ssh_cmd() { ssh -o BatchMode=yes -o ConnectTimeout=20 -o ServerAliveInterval=15 -o ServerAliveCountMax=3 "$@"; }
|
|
48
|
+
scp_cmd() { scp -o BatchMode=yes -o ConnectTimeout=20 "$@"; }
|
|
49
|
+
|
|
50
|
+
PYTHON_MODULE_LINE=""
|
|
51
|
+
if [ -z "${PYTHON_MODULE:-}" ]; then
|
|
52
|
+
PYTHON_MODULE_DETECTED="$(ssh_cmd "$CLUSTER_ID" "bash -lc 'module avail 2>&1'" 2>/dev/null \
|
|
53
|
+
| awk '/^(python|apps\/python)\/3\./{print $1; exit}')"
|
|
54
|
+
[ -n "$PYTHON_MODULE_DETECTED" ] && PYTHON_MODULE="$PYTHON_MODULE_DETECTED"
|
|
55
|
+
fi
|
|
56
|
+
[ -n "${PYTHON_MODULE:-}" ] && PYTHON_MODULE_LINE="module load ${PYTHON_MODULE}"
|
|
57
|
+
|
|
58
|
+
MEM_LINE=""
|
|
59
|
+
if [ -n "${DEF_MEM_PER_CPU:-}" ]; then
|
|
60
|
+
MEM_MB=$(( CPUS * DEF_MEM_PER_CPU ))
|
|
61
|
+
MEM_GB=$(( MEM_MB / 1024 ))
|
|
62
|
+
[ "$MEM_GB" -gt 1 ] && MEM_GB=$(( MEM_GB - 1 ))
|
|
63
|
+
MEM_LINE="#SBATCH --mem=${MEM_GB}gb"
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
GRES_LINE=""
|
|
67
|
+
if [ -n "${GRES_TYPE:-}" ]; then
|
|
68
|
+
GRES_LINE="#SBATCH --gres=${GRES_TYPE}:${ACCEL}"
|
|
69
|
+
fi
|
|
70
|
+
|
|
71
|
+
MODULE_LINE=""
|
|
72
|
+
[ -n "${MODULE_LOADS:-}" ] && MODULE_LINE="module load ${MODULE_LOADS}"
|
|
73
|
+
|
|
74
|
+
VENV_LINE=""
|
|
75
|
+
[ -n "${DEFAULT_VENV:-}" ] && VENV_LINE="source ${HOME_DIR}/${DEFAULT_VENV}/bin/activate"
|
|
76
|
+
|
|
77
|
+
TMPDIR_LOCAL=$(mktemp -d)
|
|
78
|
+
trap 'rm -rf "$TMPDIR_LOCAL"' EXIT
|
|
79
|
+
cp "$REPO_ROOT/scripts/compute-probe.py" "$TMPDIR_LOCAL/compute-probe.py"
|
|
80
|
+
|
|
81
|
+
cat > "$TMPDIR_LOCAL/probe.slurm" <<EOF
|
|
82
|
+
#!/bin/bash -l
|
|
83
|
+
#SBATCH --job-name=scnet_hpc_probe
|
|
84
|
+
#SBATCH --partition=${PARTITION}
|
|
85
|
+
#SBATCH --nodes=1
|
|
86
|
+
#SBATCH --ntasks-per-node=1
|
|
87
|
+
#SBATCH --cpus-per-task=${CPUS}
|
|
88
|
+
${GRES_LINE}
|
|
89
|
+
${MEM_LINE}
|
|
90
|
+
#SBATCH --time=${TIME}
|
|
91
|
+
#SBATCH --output=${HOME_DIR}/scripts/logs/scnet-hpc-probe_%j.out
|
|
92
|
+
#SBATCH --error=${HOME_DIR}/scripts/logs/scnet-hpc-probe_%j.err
|
|
93
|
+
|
|
94
|
+
module purge
|
|
95
|
+
${MODULE_LINE}
|
|
96
|
+
${PYTHON_MODULE_LINE}
|
|
97
|
+
${VENV_LINE}
|
|
98
|
+
export HF_HUB_OFFLINE=1
|
|
99
|
+
export TRANSFORMERS_OFFLINE=1
|
|
100
|
+
|
|
101
|
+
# 部分集群 module purge 后没有 python3 这个命令,按候选名兜底找 Python。
|
|
102
|
+
PYTHON_BIN="${PYTHON_BIN}"
|
|
103
|
+
if [ -z "\$PYTHON_BIN" ]; then
|
|
104
|
+
for candidate in python3 python3.8 python3.9 python3.10 python3.11; do
|
|
105
|
+
if command -v "\$candidate" >/dev/null 2>&1; then
|
|
106
|
+
PYTHON_BIN="\$candidate"
|
|
107
|
+
break
|
|
108
|
+
fi
|
|
109
|
+
done
|
|
110
|
+
fi
|
|
111
|
+
[ -n "\$PYTHON_BIN" ] || { echo "PYTHON_NOT_FOUND" >&2; exit 127; }
|
|
112
|
+
"\$PYTHON_BIN" ${PROBE_BASE}.py
|
|
113
|
+
rc=\$?
|
|
114
|
+
echo "PROBE_EXIT=\$rc"
|
|
115
|
+
exit \$rc
|
|
116
|
+
EOF
|
|
117
|
+
|
|
118
|
+
ssh_cmd "$CLUSTER_ID" "mkdir -p ${HOME_DIR}/scripts/logs"
|
|
119
|
+
scp_cmd -q "$TMPDIR_LOCAL/compute-probe.py" "${CLUSTER_ID}:${PROBE_BASE}.py"
|
|
120
|
+
scp_cmd -q "$TMPDIR_LOCAL/probe.slurm" "${CLUSTER_ID}:${PROBE_BASE}.slurm"
|
|
121
|
+
|
|
122
|
+
JOBID=$(ssh_cmd "$CLUSTER_ID" "cd ${HOME_DIR} && sbatch --parsable --wait ${PROBE_BASE}.slurm" | tail -1)
|
|
123
|
+
OUT=$(ssh_cmd "$CLUSTER_ID" "cat ${HOME_DIR}/scripts/logs/scnet-hpc-probe_${JOBID}.out" 2>/dev/null || true)
|
|
124
|
+
ERR=$(ssh_cmd "$CLUSTER_ID" "cat ${HOME_DIR}/scripts/logs/scnet-hpc-probe_${JOBID}.err" 2>/dev/null || true)
|
|
125
|
+
|
|
126
|
+
if [ -n "$OUT" ]; then
|
|
127
|
+
printf '%s\n' "$OUT"
|
|
128
|
+
else
|
|
129
|
+
printf '%s\n' "$ERR" >&2
|
|
130
|
+
fi
|
|
131
|
+
|
|
132
|
+
ssh_cmd "$CLUSTER_ID" "rm -f ${PROBE_BASE}.py ${PROBE_BASE}.slurm ${HOME_DIR}/scripts/logs/scnet-hpc-probe_${JOBID}.out ${HOME_DIR}/scripts/logs/scnet-hpc-probe_${JOBID}.err"
|