dsh-scnet 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 dsh-scnet contributors
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.
package/README.md ADDED
@@ -0,0 +1,75 @@
1
+ <h1 align="center">dsh-scnet</h1>
2
+
3
+ <p align="center">在 DeepSeek Harness 里操作超算互联网(scnet.cn)及同类国产超算集群:配 SSH、生成 Slurm 作业、探测集群、排查失败,附海光 DCU/DTK 知识。</p>
4
+
5
+ ## 这是什么
6
+
7
+ 一个 DeepSeek Harness 官方 bundle 插件,把 `scnet-hpc` 的 Agent Skill 和 bash 脚本包装成 DSH 可安装包。装好后 DSH 模型在涉及超算集群、Slurm 作业、海光 DCU/DTK 时会加载对应 skill,并可调用工具完成确定性操作。
8
+
9
+ ## 安装
10
+
11
+ ```sh
12
+ dsh plugin --profile web add "github:lql341/dsh-scnet#main"
13
+ ```
14
+
15
+ 或本地目录:
16
+
17
+ ```sh
18
+ git clone https://github.com/lql341/dsh-scnet.git
19
+ cd dsh-scnet
20
+ dsh plugin --profile web add .
21
+ ```
22
+
23
+ > git 源安装会取源码而非构建产物;本插件入口是纯 JavaScript(无编译步骤),无需 allowBuilds。
24
+
25
+ ## 能力面
26
+
27
+ ### Skills
28
+
29
+ | Skill | 作用 |
30
+ |---|---|
31
+ | scnet-hpc | 国产超算使用规范:连接、提交/监控、调试、失败排查、海光 DCU/DTK 资源索引 |
32
+
33
+ ### Tools
34
+
35
+ | 工具 | 说明 |
36
+ |---|---|
37
+ | scnet_list_clusters | 列出已配置的集群 profile |
38
+ | scnet_show_cluster | 读取指定集群的完整参数 |
39
+ | scnet_generate_job | 按 profile 生成合规 Slurm 作业脚本 |
40
+ | scnet_setup_ssh | 配置本地到集群的 SSH 连接 |
41
+ | scnet_probe_cluster | 探测新集群并生成 profile |
42
+
43
+ ## 结构
44
+
45
+ ```
46
+ .
47
+ ├── package.json # dsh.bundle + dsh.skills
48
+ ├── cordis.patch.yml # bundle 组合层
49
+ ├── index.mjs # Cordis 入口,注册 5 个工具
50
+ ├── skills/scnet-hpc/ # Agent Skill + clusters/scripts/references
51
+ ├── sync.sh # 从 canonical 仓库同步
52
+ └── README.md
53
+ ```
54
+
55
+ ## 同步
56
+
57
+ 本仓库是发布面,知识内容(`skills/scnet-hpc/` 下的 SKILL.md、clusters、references、scripts)从 canonical 仓库 `lql341/scnet-hpc` 同步:
58
+
59
+ ```sh
60
+ ./sync.sh # 默认 ../skills/scnet-hpc
61
+ ./sync.sh --src /path/to/scnet-hpc
62
+ ```
63
+
64
+ 只改 canonical,本仓库跑 sync 更新;不要把 `skills/scnet-hpc/` 当手工维护目录。
65
+
66
+ ## 隐私与安全
67
+
68
+ - 仓库不含私钥、token、用户名、密钥指纹。
69
+ - `scnet_setup_ssh` 会写入 `~/.ssh/`,调用前确认私钥路径和集群。
70
+ - `scnet_generate_job` 生成的 `.slurm` 可能含本机用户名,`.gitignore` 已排除。
71
+ - 集群主机名/端口是平台公开信息;profile 中连接字段默认留空,由用户本地填写。
72
+
73
+ ## License
74
+
75
+ MIT
@@ -0,0 +1,3 @@
1
+ - insert:
2
+ - id: scnet
3
+ name: dsh-scnet
package/index.mjs ADDED
@@ -0,0 +1,221 @@
1
+ import { execFile } from 'node:child_process'
2
+ import { promises as fs } from 'node:fs'
3
+ import { dirname, join } from 'node:path'
4
+ import { fileURLToPath } from 'node:url'
5
+ import { defineTool } from '@deepseek-ai/dsh-tools'
6
+
7
+ export const name = 'scnet-hpc'
8
+ export const inject = ['tools']
9
+
10
+ const __dirname = dirname(fileURLToPath(import.meta.url))
11
+ const SKILL_DIR = join(__dirname, 'skills', 'scnet-hpc')
12
+ const SCRIPTS_DIR = join(SKILL_DIR, 'scripts')
13
+ const CLUSTERS_DIR = join(SKILL_DIR, 'clusters')
14
+
15
+ function runBash(script, args, timeoutMs = 120000) {
16
+ return new Promise((resolve) => {
17
+ execFile(
18
+ 'bash',
19
+ [script, ...args],
20
+ { cwd: SKILL_DIR, timeout: timeoutMs, maxBuffer: 16 * 1024 * 1024 },
21
+ (error, stdout, stderr) => {
22
+ const code = error ? (typeof error.code === 'number' ? error.code : 1) : 0
23
+ resolve({
24
+ ok: code === 0,
25
+ code,
26
+ stdout: String(stdout || ''),
27
+ stderr: String(stderr || ''),
28
+ })
29
+ },
30
+ )
31
+ })
32
+ }
33
+
34
+ function textBlock(value) {
35
+ return [{ type: 'text', text: value }]
36
+ }
37
+
38
+ async function listClusterIds() {
39
+ let entries = []
40
+ try {
41
+ entries = await fs.readdir(CLUSTERS_DIR)
42
+ } catch {
43
+ return []
44
+ }
45
+ return entries
46
+ .filter((entry) => entry.endsWith('.conf') && !entry.startsWith('_'))
47
+ .map((entry) => entry.replace(/\.conf$/, ''))
48
+ .sort()
49
+ }
50
+
51
+ async function readProfile(id) {
52
+ return fs.readFile(join(CLUSTERS_DIR, `${id}.conf`), 'utf8')
53
+ }
54
+
55
+ function fieldOf(text, key) {
56
+ const match = text.match(new RegExp(`^${key}="?(.*?)"?$`, 'm'))
57
+ return match ? match[1] : ''
58
+ }
59
+
60
+ function positiveInt(value, label) {
61
+ if (value === undefined || value === null || String(value).trim() === '') return null
62
+ if (!/^[0-9]+$/.test(String(value).trim())) {
63
+ return { error: `${label} 必须是正整数` }
64
+ }
65
+ return { value: String(value).trim() }
66
+ }
67
+
68
+ export function apply(ctx) {
69
+ ctx.tools.register(
70
+ defineTool({
71
+ name: 'scnet_list_clusters',
72
+ description:
73
+ '列出 scnet-hpc 插件中已配置的超算集群 profile(短名与描述)。生成作业或配 SSH 前用它确认可用的集群短名。',
74
+ parameters: {},
75
+ output: {
76
+ schema: { type: 'string' },
77
+ render: (_args, value) => textBlock(value),
78
+ },
79
+ async execute() {
80
+ const ids = await listClusterIds()
81
+ if (ids.length === 0) {
82
+ return '没有可用的集群 profile。请在 skills/scnet-hpc/clusters/ 下新增 <短名>.conf。'
83
+ }
84
+ const lines = []
85
+ for (const id of ids) {
86
+ const text = await readProfile(id)
87
+ lines.push(`${id}\t${fieldOf(text, 'CLUSTER_DESC') || '(未填写描述)'}`)
88
+ }
89
+ return lines.join('\n')
90
+ },
91
+ }),
92
+ )
93
+
94
+ ctx.tools.register(
95
+ defineTool({
96
+ name: 'scnet_show_cluster',
97
+ description:
98
+ '读取指定集群 profile 的完整参数(内存上限、分区、GRES、DTK 路径、已知限制等)。回答硬件规格或配额问题时必须读 profile,不要凭记忆。',
99
+ parameters: {
100
+ cluster: {
101
+ type: 'string',
102
+ description: '集群短名;省略时,若只有一个 profile 则自动选择,否则报错提示可选项。',
103
+ },
104
+ },
105
+ output: {
106
+ schema: { type: 'string' },
107
+ render: (_args, value) => textBlock(value),
108
+ },
109
+ async execute(args) {
110
+ let id = String(args.cluster || '').trim()
111
+ const ids = await listClusterIds()
112
+ if (!id && ids.length === 1) id = ids[0]
113
+ if (!id) {
114
+ return `请指定 cluster。可用集群:${ids.join(', ') || '(无)'}`
115
+ }
116
+ try {
117
+ return await readProfile(id)
118
+ } catch {
119
+ return `找不到 profile:${id}。可用集群:${ids.join(', ') || '(无)'}`
120
+ }
121
+ },
122
+ }),
123
+ )
124
+
125
+ ctx.tools.register(
126
+ defineTool({
127
+ name: 'scnet_generate_job',
128
+ description:
129
+ '按目标集群的约束生成合规的 Slurm 作业脚本(自动算 --mem、--gres、module load、离线环境变量,并在末尾写 exit $rc)。会写一个 <name>.slurm 到当前目录并返回上传/提交命令。',
130
+ parameters: {
131
+ name: { type: 'string', required: true, description: '作业名(slurm --job-name 和输出文件名)' },
132
+ accelerators: { type: 'string', description: '加速器数量,默认 1' },
133
+ cpus: { type: 'string', description: 'CPU 核数,默认 8' },
134
+ time: { type: 'string', description: '时长,默认 00:20:00,格式 HH:MM:SS 或 D-HH:MM:SS' },
135
+ cluster: { type: 'string', description: '集群短名;省略时若只有一个 profile 则自动选择' },
136
+ remote_user: { type: 'string', description: '远端用户名;与本地不同时填写(日志路径需要真实用户名)' },
137
+ },
138
+ output: {
139
+ schema: { type: 'string' },
140
+ render: (_args, value) => textBlock(value),
141
+ },
142
+ async execute(args) {
143
+ const nameVal = String(args.name || '').trim()
144
+ if (!nameVal) return '缺少必填参数 name。'
145
+
146
+ const accelerators = positiveInt(args.accelerators, 'accelerators')
147
+ if (accelerators && accelerators.error) return accelerators.error
148
+ const cpus = positiveInt(args.cpus, 'cpus')
149
+ if (cpus && cpus.error) return cpus.error
150
+
151
+ const argv = []
152
+ const cluster = String(args.cluster || '').trim()
153
+ if (cluster) argv.push('--cluster', cluster)
154
+ const remoteUser = String(args.remote_user || '').trim()
155
+ if (remoteUser) argv.push('--user', remoteUser)
156
+ argv.push(nameVal)
157
+ if (accelerators) argv.push(accelerators.value)
158
+ if (cpus) argv.push(cpus.value)
159
+ if (args.time && String(args.time).trim()) argv.push(String(args.time).trim())
160
+
161
+ const res = await runBash(join(SCRIPTS_DIR, 'new-job.sh'), argv, 30000)
162
+ if (res.ok) return res.stdout.trim() || res.stderr.trim()
163
+ return `生成失败(exit ${res.code}):\n${res.stderr.trim() || res.stdout.trim()}`
164
+ },
165
+ }),
166
+ )
167
+
168
+ ctx.tools.register(
169
+ defineTool({
170
+ name: 'scnet_setup_ssh',
171
+ description:
172
+ '在本地 macOS/Linux 上配置到超算集群的 SSH 连接:安装私钥到 ~/.ssh、写 ~/.ssh/config、测试连接。幂等,重复运行只补缺失项。会修改本机 SSH 配置,执行前应向用户确认私钥路径与集群。',
173
+ parameters: {
174
+ key_path: { type: 'string', required: true, description: '从超算平台控制台下载的私钥文件绝对路径(.txt 或 PEM 私钥)' },
175
+ cluster: { type: 'string', description: '集群短名;省略时若只有一个 profile 则自动选择' },
176
+ username: { type: 'string', description: '远端用户名;省略时尝试从私钥文件名推断' },
177
+ },
178
+ output: {
179
+ schema: { type: 'string' },
180
+ render: (_args, value) => textBlock(value),
181
+ },
182
+ async execute(args) {
183
+ const keyPath = String(args.key_path || '').trim()
184
+ if (!keyPath) return '缺少必填参数 key_path。'
185
+ const argv = []
186
+ const cluster = String(args.cluster || '').trim()
187
+ if (cluster) argv.push('--cluster', cluster)
188
+ argv.push(keyPath)
189
+ if (args.username && String(args.username).trim()) argv.push(String(args.username).trim())
190
+ const res = await runBash(join(SCRIPTS_DIR, 'setup-ssh.sh'), argv, 90000)
191
+ const out = `${res.stdout.trim()}\n${res.stderr.trim()}`.trim()
192
+ return out || `setup-ssh.sh 退出码 ${res.code},但没有输出。`
193
+ },
194
+ }),
195
+ )
196
+
197
+ ctx.tools.register(
198
+ defineTool({
199
+ name: 'scnet_probe_cluster',
200
+ description:
201
+ '探测一个已能 SSH 登录的集群(调度器、分区、内存、GRES、网络、登录节点 torch 可用性),生成可直接保存为 profile 的配置文本。结果需要写回 clusters/<短名>.conf 并补齐探测不到的项。',
202
+ parameters: {
203
+ target: { type: 'string', required: true, description: 'ssh 别名或主机名(须已能免密登录)' },
204
+ name: { type: 'string', description: '集群短名,默认与 target 相同' },
205
+ },
206
+ output: {
207
+ schema: { type: 'string' },
208
+ render: (_args, value) => textBlock(value),
209
+ },
210
+ async execute(args) {
211
+ const target = String(args.target || '').trim()
212
+ if (!target) return '缺少必填参数 target。'
213
+ const argv = [target]
214
+ if (args.name && String(args.name).trim()) argv.push(String(args.name).trim())
215
+ const res = await runBash(join(SCRIPTS_DIR, 'probe-cluster.sh'), argv, 180000)
216
+ if (res.ok) return res.stdout.trim()
217
+ return `探测失败(exit ${res.code}):\n${res.stderr.trim() || res.stdout.trim()}`
218
+ },
219
+ }),
220
+ )
221
+ }
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "dsh-scnet",
3
+ "version": "0.1.0",
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.",
5
+ "type": "module",
6
+ "main": "./index.mjs",
7
+ "exports": {
8
+ ".": "./index.mjs",
9
+ "./cordis.patch.yml": "./cordis.patch.yml",
10
+ "./package.json": "./package.json"
11
+ },
12
+ "files": [
13
+ "index.mjs",
14
+ "cordis.patch.yml",
15
+ "skills"
16
+ ],
17
+ "dsh": {
18
+ "bundle": {
19
+ "patch": "./cordis.patch.yml"
20
+ },
21
+ "skills": [
22
+ "./skills/scnet-hpc/SKILL.md"
23
+ ]
24
+ },
25
+ "engines": {
26
+ "node": ">=20"
27
+ },
28
+ "keywords": [
29
+ "deepseek-harness",
30
+ "dsh",
31
+ "dsh-plugin",
32
+ "dsh-bundle",
33
+ "slurm",
34
+ "hpc",
35
+ "hygon",
36
+ "dcu"
37
+ ],
38
+ "license": "MIT"
39
+ }
@@ -0,0 +1,235 @@
1
+ ---
2
+ name: scnet-hpc
3
+ description: 在超算互联网(scnet.cn)及同类国产超算集群上工作 —— 从个人电脑(macOS/Linux)配置 SSH 连接、提交和监控 Slurm 作业、交互式调试、排查作业失败、安装 Python 依赖,以及查找海光 DCU/DTK 的官方开发文档、数学库、通信库、分析调试和 CUDA 迁移工具。当任务涉及超算集群、Slurm 作业、海光 DCU / DTK / gfx9xx、DTK 工具库选型、或需要写 sbatch 脚本、判断该在登录节点还是计算节点执行某件事时使用。集群参数存在 clusters/*.conf,支持多集群。
4
+ ---
5
+
6
+ # 国产超算集群使用规范
7
+
8
+ 面向超算互联网(scnet.cn)系列集群。**集群参数不写在文档里,存在
9
+ `clusters/<集群短名>.conf`**,脚本从 profile 读取,所以同一套流程能用于多个集群。
10
+
11
+ ## 先确认在跟哪个集群打交道
12
+
13
+ ```bash
14
+ ls clusters/*.conf # 有哪些集群
15
+ cat clusters/<集群短名>.conf # 看具体参数
16
+ ```
17
+
18
+ 回答用户关于内存上限、分区名、硬件规格的问题时,**读 profile,不要凭记忆**。
19
+ 不同集群的 `DEF_MEM_PER_CPU`、`PARTITION`、`MIN_GRES` 都不一样。
20
+
21
+ | 文件 | 用途 |
22
+ |---|---|
23
+ | `clusters/*.conf` | 每个集群一个 profile(连接、调度器、硬件、已知问题)|
24
+ | `clusters/_template.conf` | 新增集群的模板 |
25
+ | `scripts/setup-ssh.sh` | 新电脑上配连接(macOS/Linux,幂等)|
26
+ | `scripts/probe-cluster.sh` | 探测新集群,自动生成 profile |
27
+ | `scripts/new-job.sh` | 按 profile 生成合规的作业脚本 |
28
+ | `references/setup.md` | 连接配置详解、平台差异、故障排查 |
29
+ | `references/environment.md` | 环境栈、装依赖、目录约定、迁移注意 |
30
+ | `references/troubleshooting.md` | 排查手册、调试策略 |
31
+ | `references/adding-cluster.md` | 新增集群的完整步骤 |
32
+ | `references/hygon-dcu-development.md` | 海光 DCU 开发流程、DTK 工具库与官方资源索引 |
33
+ | `references/software-compatibility.md` | 国产超算上的软件兼容性排查、验证分层与公开报告脱敏 |
34
+
35
+ ## 换机器
36
+
37
+ 本插件已随 DeepSeek Harness 安装,换机器时用 `dsh plugin --profile web add` 重装即可。
38
+ 配置集群连接:
39
+
40
+ ```bash
41
+ ./scripts/setup-ssh.sh <私钥文件> # 配连接(自动读唯一的 profile)
42
+ ```
43
+ ## 新增集群
44
+
45
+ ```bash
46
+ # 1. 手工确认能 ssh 上去(用平台给的私钥)
47
+ # 2. 探测,自动填大部分字段
48
+ ./scripts/probe-cluster.sh <ssh别名> <集群短名> > clusters/<集群短名>.conf
49
+ # 3. 补齐探测不到的项(加速器型号、module 名等),profile 里有提示
50
+ ```
51
+
52
+ 完整步骤见 `references/adding-cluster.md`。
53
+
54
+ ## 通用硬约束
55
+
56
+ 以下几条在国产超算上普遍存在,**具体数值查 profile**。
57
+
58
+ ### 1. 可能强制申请加速器
59
+
60
+ 若 profile 里 `MIN_GRES` 非空,该集群的 QOS 强制每个作业至少申请那么多加速器,
61
+ **纯 CPU 作业会被拒**:
62
+
63
+ ```
64
+ sbatch: error: min tres(gres/dcu) request 0 exceeds per-job max tres limit 1
65
+ sbatch: error: QOSMinGRES
66
+ ```
67
+
68
+ 哪怕作业根本不用 GPU(比如只读文件的探针),也要写 `--gres=<GRES_TYPE>:1`。
69
+
70
+ ### 2. 内存上限 = `cpus-per-task × DEF_MEM_PER_CPU`
71
+
72
+ 超了直接被拒(`too much memory was requested relative to the number of CPUs`)。
73
+ **要更多内存只能加 CPU 核数。**
74
+
75
+ 用 `scripts/new-job.sh` 生成脚本可避免手算。以 `DEF_MEM_PER_CPU=3888` 为例:
76
+
77
+ | `--cpus-per-task` | 上限 | 安全写法 |
78
+ |---|---|---|
79
+ | 8 | 30.38 GB | `--mem=30gb` |
80
+ | 16 | 60.75 GB | `--mem=60gb` |
81
+ | 32 | 121.50 GB | `--mem=121gb` |
82
+
83
+ 注意 `--mem` 只是请求量,cgroup 实际按 `cpus × DEF_MEM_PER_CPU` 给
84
+ ——所以 `MaxRSS` 可能超过你写的 `--mem` 值。
85
+
86
+ ### 3. 登录节点可能无法 import 深度学习框架
87
+
88
+ 若 profile 的 `LOGIN_NODE_MISSING_LIBS` 非空,登录节点缺那些库,
89
+ 即使 `module load` 之后也 import 不了:
90
+
91
+ ```
92
+ ImportError: libmsgpackc.so.2: cannot open shared object file
93
+ ```
94
+
95
+ **不要在登录节点试 torch 代码然后困惑为什么报错。** 哪怕只想看一眼 tensor 的
96
+ dtype,也要走 `srun` 或 `sbatch`。
97
+
98
+ 登录节点能做:`pip install`、`git`、读写文件、`sacct`/`squeue`、下载模型。
99
+
100
+ ### 4. 计算节点通常无外网 —— 依赖必须预装
101
+
102
+ profile 的 `NET_OK` / `NET_BLOCKED` 记录了登录节点的可达情况。计算节点若
103
+ `COMPUTE_NODE_OFFLINE=yes` 则完全无外网。
104
+
105
+ 任何**运行时从网上拉东西**的库都会在计算节点炸掉。典型的是 `transformers` 的
106
+ `kernels` 机制(从 HF Hub 下载预编译 kernel)。作业脚本里先声明离线,
107
+ 让失败快速且信息明确:
108
+
109
+ ```bash
110
+ export HF_HUB_OFFLINE=1
111
+ export TRANSFORMERS_OFFLINE=1
112
+ ```
113
+
114
+ ### 5. `/tmp` 通常不跨节点共享 —— 文件放家目录
115
+
116
+ `TMP_SHARED=no` 时,`/tmp` 在计算节点是本地的,作业之间不可见。
117
+
118
+ ### 6. 先验证二进制兼容性,再做完整安装
119
+
120
+ 遇到 Python/C++/GPU 软件安装问题时,先确认系统 ABI、manylinux 标签、编译器/DTK 版本和运行节点网络条件,再选择版本。不要只根据 pip/conda 能否解析依赖判断可用性;必须在目标计算节点完成最小 import、kernel、功能和端到端验证。
121
+
122
+ ## 提交作业
123
+
124
+ ```bash
125
+ ./scripts/new-job.sh <作业名> [加速器数] [cpu数] [时长]
126
+ ./scripts/new-job.sh probe # 1 卡, 8 核, 20 分钟
127
+ ./scripts/new-job.sh infer 8 32 01:30:00 # 8 卡, 32 核, 1.5 小时
128
+ ```
129
+
130
+ 上传后先验证配额而不真排队:
131
+
132
+ ```bash
133
+ ssh <集群> 'sbatch --test-only <脚本路径>'
134
+ ```
135
+
136
+ 返回 `Job N to start at <时间>` 表示配额合法;`Requested node configuration is
137
+ not available` 是当前无空闲节点(配额没问题);`too much memory` 才是超配额。
138
+
139
+ ### ★ 作业脚本末尾必须写 `exit $rc`
140
+
141
+ **这是最容易被骗的地方。** bash 里 Python 崩了之后 bash 继续执行后面的行,
142
+ 作业以最后一条命令的退出码结束:
143
+
144
+ ```
145
+ $ sacct -j <JOB_ID>
146
+ <JOB_ID> | example-job | COMPLETED | 0:0 | 00:34:32 ← 可能是假的!
147
+ ```
148
+
149
+ 而日志里 Python 早就抛异常了,甚至还打印了脚本末尾的 "COMPLETE" 字样。
150
+
151
+ **判断作业成功不能只看 `sacct` 的 State,必须读日志。**
152
+
153
+ ## 交互式调试(推荐)
154
+
155
+ 比 sbatch 快得多,适合试代码、查环境、验证假设。实测约 5 秒拿到节点:
156
+
157
+ ```bash
158
+ srun -p <分区> --gres=<类型>:1 --cpus-per-task=8 --mem=30gb --time=00:10:00 \
159
+ bash -lc 'module load <模块>
160
+ source ~/<venv>/bin/activate
161
+ python3 -c "import torch; print(torch.__version__)"'
162
+
163
+ # 交互 shell
164
+ srun -p <分区> --gres=<类型>:1 --cpus-per-task=8 --mem=30gb \
165
+ --time=00:30:00 --pty bash
166
+ ```
167
+
168
+ 节点忙时会排队,可以 Ctrl-C 放弃改用 sbatch。
169
+
170
+ **调试策略**:先用 2 分钟的小探针确认假设,再提交几十分钟的大作业。大模型加载
171
+ 动辄半小时,靠大作业试错代价太高。分层验证见 `references/troubleshooting.md`。
172
+
173
+ ## 常用命令
174
+
175
+ ```bash
176
+ squeue -u $USER # 我的队列
177
+ squeue -j <ID> --start # 预计启动时间
178
+ sacct -j <ID> --format=JobID,State%14,ExitCode,Elapsed,MaxRSS -P
179
+ scancel <ID>
180
+ sinfo -p <分区> -o "%n %c %m %G %t" # 节点资源和状态
181
+ ```
182
+
183
+ `MaxRSS` 只在作业结束后、且只在 `.batch` 子步骤那行有值。节点状态里
184
+ `drain`/`drng` 不可调度,实际可用常少于总数。
185
+
186
+ ## 加速器能力边界
187
+
188
+ 用户询问海光开发环境、HIP、数学库、通信库、性能分析、CUDA 迁移或容器部署时,
189
+ 先读 `references/hygon-dcu-development.md`。用它定位官方资料,再以目标集群的
190
+ module、计算节点探针和 profile 为准;不要把网页当前版本直接覆盖到集群环境。
191
+
192
+ **不要假设加速器支持什么。** 国产加速器(海光 DCU、寒武纪、昇腾)的软件栈往往
193
+ 落后于上游,很多 CUDA/ROCm 上想当然的能力并不存在。profile 的
194
+ `KNOWN_LIMITATIONS` 记录了已探明的缺口。
195
+
196
+ 以 zzeshell 的海光 gfx936 为例,实测结论:
197
+
198
+ | 能力 | 结果 |
199
+ |---|---|
200
+ | FP8 存储 + `.to(bfloat16)` 转换 | ✅ |
201
+ | `torch._scaled_mm`(硬件 FP8 matmul) | ❌ 需 SM89+/SM90+ 或 ROCm MI300+ |
202
+ | **Triton** | ❌ `libamdhip64.so` 缺 `hipDrvLaunchKernelEx` |
203
+ | bitsandbytes 4-bit / 8-bit | ✅ 需补丁 / ❌ 无 INT8 algo |
204
+
205
+ Triton 那条影响最大:**任何依赖 Triton kernel 的推理框架都跑不起来**
206
+ ——SGLang FP8 路径、vLLM ROCm MoE 后端、transformers `finegrained-fp8` 都在此列。
207
+ 遇到这类需求先想清楚有没有纯 PyTorch 的退路,别花时间在框架适配上。
208
+
209
+ 碰到新集群时,用一个探针作业实测这些能力,把结论写回 profile 的
210
+ `KNOWN_LIMITATIONS`,别让下一个人重踩。
211
+
212
+ ## 排查失败
213
+
214
+ 1. **先读日志,不要相信 `sacct` 的 State**
215
+ 2. `.err` 被 `Loading weights: ...%` 进度条刷屏时,`grep -v` 过滤掉再看
216
+ 3. 按报错分类:
217
+
218
+ | 报错 | 原因 |
219
+ |---|---|
220
+ | `QOSMinGRES` | 忘了 `--gres` |
221
+ | `too much memory` | 违反 `cpus × DEF_MEM_PER_CPU` |
222
+ | `lib*.so: cannot open shared object file` | 没 `module load`,或在登录节点跑了 |
223
+ | `Name or service not known` / `offline mode` | 计算节点在访问外网 |
224
+ | `hipDrvLaunchKernelEx` | Triton 不可用,换纯 PyTorch 路线 |
225
+ | `sacct` COMPLETED 但结果不对 | 脚本缺 `exit $rc` |
226
+
227
+ 完整手册见 `references/troubleshooting.md`。
228
+
229
+ ## 公开报告与知识沉淀
230
+
231
+ 将集群实测结果写入公开报告或仓库前,保留环境、方法、版本、结果和限制,移除或替换以下信息:用户名、SSH 主机/端口、内部域名、IP、节点名、作业编号、绝对家目录和私有镜像地址。可用 `<USER_HOME>`、`<DTK_ROOT>`、`<CLUSTER_HOST>`、`已脱敏作业` 等占位符。
232
+
233
+ 报告正文沿用已有 LAMMPS 报告的结构和风格:深色渐变卡片、青绿色主色、蓝色辅助色,以及“环境信息 → 编译过程与主要难题 → 最终方案 → 测试结果 → 已知限制 → 经验总结 → 附录”的叙述顺序。公开结论必须标明测试环境、验证范围和未验证边界。
234
+
235
+ 详细的兼容性排查和报告模板见 `references/software-compatibility.md`。
@@ -0,0 +1,59 @@
1
+ # 集群 profile 模板
2
+ #
3
+ # 新增集群:
4
+ # 1. cp _template.conf <集群短名>.conf
5
+ # 2. 填下面的值(不确定的用 scripts/probe-cluster.sh 探测)
6
+ # 3. scripts/setup-ssh.sh --cluster <集群短名> <私钥文件>
7
+ #
8
+ # 只有 CLUSTER_ID / SSH_HOST / SSH_PORT 是必填,其余可留空,
9
+ # 留空的项脚本会跳过、文档里显示为「未探测」。
10
+
11
+ # ---- 连接(必填)----
12
+ CLUSTER_ID="" # 短名,同时用作 ssh 别名,如 "zzeshell"
13
+ CLUSTER_DESC="" # 人类可读描述,如 "海光 DCU(郑州)"
14
+ SSH_HOST="" # 如 "xxx.scnet.cn"
15
+ SSH_PORT="22"
16
+ HOME_BASE="/public/home" # 家目录父路径
17
+
18
+ KEY_NAME_MARKER="" # 私钥文件名里的主机标识,如 "_xxx.scnet.cn_"
19
+ NEEDS_UPDATE_HOSTKEYS_NO="no" # 平台主机密钥签名有问题时设 yes
20
+
21
+ # ---- 调度器 ----
22
+ SCHEDULER="slurm"
23
+ PARTITION=""
24
+
25
+ DEF_MEM_PER_CPU="" # MB。scontrol show partition <名> 里的 DefMemPerCPU
26
+ MIN_GRES="" # QOS 强制的最小 GRES,如 "dcu:1";无则留空
27
+ GRES_TYPE="" # "dcu" / "gpu" / "npu" 等
28
+ MAX_WALLTIME=""
29
+
30
+ # ---- 硬件 ----
31
+ ACCEL_NAME="" # 如 "海光 BW1000 DCU" / "NVIDIA A100"
32
+ ACCEL_ARCH="" # 如 "gfx936" / "sm80"
33
+ ACCEL_PER_NODE=""
34
+ ACCEL_MEM_GB=""
35
+ CPUS_PER_NODE=""
36
+ CPU_MODEL=""
37
+ NODE_MEM_GB=""
38
+ NODE_COUNT=""
39
+ LOGIN_NODES=""
40
+ HOME_QUOTA_GB=""
41
+
42
+ # ---- 软件栈 ----
43
+ TOOLCHAIN="" # "DTK" / "CUDA" / "ROCm" 等
44
+ TOOLCHAIN_VERSION=""
45
+ TOOLCHAIN_PATH=""
46
+ MODULE_LOADS="" # 空格分隔的 module load 参数
47
+
48
+ DEFAULT_VENV="" # 相对家目录的 venv 路径
49
+
50
+ # ---- 网络 ----
51
+ NET_OK=""
52
+ NET_BLOCKED=""
53
+ PIP_INDEX="https://pypi.tuna.tsinghua.edu.cn/simple"
54
+ COMPUTE_NODE_OFFLINE="yes" # 多数超算的计算节点无外网
55
+ TMP_SHARED="no"
56
+
57
+ # ---- 已知问题 ----
58
+ LOGIN_NODE_MISSING_LIBS="" # 登录节点缺失的库
59
+ KNOWN_LIMITATIONS="" # 硬件/软件栈的能力缺口