dsh-scnet 0.3.0 → 0.4.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.
@@ -0,0 +1,127 @@
1
+ # SCNet HPC English Quick Start
2
+
3
+ This guide covers the common workflow for SCNet clusters. Current repository profiles focus
4
+ mainly on Hygon DCU/DTK systems. Always read the target profile before using a partition,
5
+ memory value, accelerator type, module, or home-directory path.
6
+
7
+ The repository scripts support Linux and macOS natively. On Windows, use WSL2; native Windows
8
+ execution and Git Bash are not fully validated. This is separate from the official SCNet desktop
9
+ client, which supports Windows 10+ and macOS 12 Monterey+.
10
+
11
+ ## 1. Inspect the available profiles
12
+
13
+ ```bash
14
+ ls clusters/*.conf
15
+ sed -n '1,220p' clusters/<cluster>.conf
16
+ ```
17
+
18
+ Check at least `PARTITION`, `PARTITION_CPU`, `GRES_TYPE`, `MIN_GRES`,
19
+ `DEF_MEM_PER_CPU`, `MODULE_LOADS`, `COMPUTE_NODE_OFFLINE`, and
20
+ `KNOWN_LIMITATIONS`.
21
+
22
+ ## 2. Install the skill
23
+
24
+ ```bash
25
+ git clone https://github.com/lql341/scnet-hpc.git
26
+ cd scnet-hpc
27
+ ./scripts/install.sh
28
+ ```
29
+
30
+ The repository is designed to be shared by Codex, Claude Code, and OpenCode. Installation
31
+ paths depend on the local agent layout; `install.sh --link` is useful when developing the
32
+ skill from a shared source directory.
33
+
34
+ ## 3. Configure SSH
35
+
36
+ Download the private key from the SCNet console, then run:
37
+
38
+ ```bash
39
+ ./scripts/setup-ssh.sh --cluster <cluster> <private-key-file> <username>
40
+ ssh <cluster>
41
+ ```
42
+
43
+ The script installs the key under `~/.ssh`, adds an SSH config entry, and tests the
44
+ connection. Do not commit private keys, usernames, internal node names, or personal paths.
45
+
46
+ ## 4. Generate a Slurm job
47
+
48
+ Accelerator job:
49
+
50
+ ```bash
51
+ ./scripts/new-job.sh --cluster <cluster> myjob 1 8 00:20:00
52
+ ```
53
+
54
+ CPU-only job, when the profile defines `PARTITION_CPU`:
55
+
56
+ ```bash
57
+ ./scripts/new-job.sh --cluster <cluster> --cpu-only build 0 32 01:00:00
58
+ ```
59
+
60
+ Explicit partition override:
61
+
62
+ ```bash
63
+ ./scripts/new-job.sh --cluster <cluster> --partition <partition> probe 1 8 00:10:00
64
+ ```
65
+
66
+ The generator calculates a safe memory request from the profile, adds GRES only when
67
+ needed, configures a shared temporary directory, enables offline mode when appropriate,
68
+ and propagates the program exit code.
69
+
70
+ Create the remote log directory before submission:
71
+
72
+ ```bash
73
+ ssh <cluster> 'mkdir -p ~/scripts/logs'
74
+ ```
75
+
76
+ Upload the generated script and validate the request before queueing it:
77
+
78
+ ```bash
79
+ scp myjob.slurm <cluster>:~/scripts/
80
+ ssh <cluster> 'sbatch --test-only ~/scripts/myjob.slurm'
81
+ ssh <cluster> 'sbatch ~/scripts/myjob.slurm'
82
+ ```
83
+
84
+ ## 5. Monitor and debug
85
+
86
+ ```bash
87
+ squeue -u "$USER"
88
+ squeue -j <job-id> --start
89
+ sacct -j <job-id> --format=JobID,State%14,ExitCode,Elapsed,MaxRSS -P
90
+ scancel <job-id>
91
+ ```
92
+
93
+ Do not trust `COMPLETED` alone. Read both stdout and stderr, and make sure the job script
94
+ ends with the real program exit code.
95
+
96
+ For short experiments, request an interactive allocation with `srun`. Use the partition,
97
+ GRES type, CPU count, and memory limit from the target profile.
98
+
99
+ ## 6. SCNet environment rules
100
+
101
+ - Login nodes are for source management, dependency preparation, file operations, and
102
+ scheduler commands. Whether package indexes and model sites are reachable is cluster-specific.
103
+ - Compute nodes are often offline. Download dependencies, models, and kernels before the job.
104
+ - A DCU partition may require at least one accelerator even for a CPU-oriented command.
105
+ - Memory limits may be tied to `cpus-per-task × DefMemPerCPU`.
106
+ - Do not use system `/tmp` for files that must be shared across login and compute nodes.
107
+ - Loading a module on a login node does not prove that the runtime works on a compute node.
108
+ - Hygon DCU software capabilities depend on the exact DTK, driver, framework, and `gfx` target.
109
+
110
+ ## 7. Refresh and probe a cluster
111
+
112
+ ```bash
113
+ ./scripts/refresh-cluster.sh --cluster <cluster> --dry-run
114
+ ./scripts/refresh-cluster.sh --cluster <cluster>
115
+ ./scripts/refresh-cluster.sh --cluster <cluster> --compute
116
+ ```
117
+
118
+ The default refresh performs read-only login-node checks. `--compute` submits a small Slurm
119
+ job to test the accelerator runtime and therefore consumes cluster resources.
120
+
121
+ For detailed diagnostics, see the Chinese references:
122
+
123
+ - `references/setup.md`
124
+ - `references/environment.md`
125
+ - `references/troubleshooting.md`
126
+ - `references/software-compatibility.md`
127
+ - `references/hygon-dcu-development.md`
@@ -3,7 +3,7 @@
3
3
  集群的主机名、端口等参数来自 `clusters/<集群短名>.conf`。
4
4
  本文用 `<集群>`、`<主机名>`、`<端口>` 表示从 profile 读出的值。
5
5
 
6
- ## 手工配置(不用 setup-ssh.sh 时)
6
+ ## 手工配置
7
7
 
8
8
  ```bash
9
9
  # 1. 装私钥
@@ -42,14 +42,14 @@ Host <集群> <主机名>
42
42
  UpdateHostKeys no
43
43
  ```
44
44
 
45
- ## 各选项的理由
45
+ ## SSH 选项说明
46
46
 
47
47
  **`ControlPersist 10m` 而非 `yes`**:永久驻留的 master 在笔记本休眠或换网络后会
48
- 变成失效 socket,反而要手工清理。10 分钟覆盖一次交互操作的节奏,又能自然回收。
48
+ 变成失效 socket,可能留下需要清理的失效 socket。10 分钟覆盖一次交互操作的节奏,又能自然回收。
49
49
  实测复用后单条命令从约 2 秒降到 0.57 秒。
50
50
 
51
51
  **`ServerAliveInterval 60`**:应用层心跳,防止空闲 SSH 会话被中间网关静默切断。
52
- 跑长任务时关键。`TCPKeepAlive` 是 TCP 层的,两者互补。
52
+ 用于保持长时间空闲会话。`TCPKeepAlive` 是 TCP 层的,两者互补。
53
53
 
54
54
  **`UpdateHostKeys no`**:部分平台的服务端对主机密钥轮换扩展返回错误签名:
55
55
 
@@ -57,9 +57,7 @@ Host <集群> <主机名>
57
57
  client_global_hostkeys_prove_confirm: server gave bad signature for RSA key 0
58
58
  ```
59
59
 
60
- 这是平台侧实现问题,不影响安全性——首次记录的主机密钥仍在正常校验。关掉只为消除
61
- 告警噪音。**只在遇到这个告警的集群上加**(profile 里标
62
- `NEEDS_UPDATE_HOSTKEYS_NO=yes`)。
60
+ 该设置仅禁用服务端主机密钥轮换扩展;首次主机密钥仍按正常流程校验。仅在出现该告警且 profile 标记 `NEEDS_UPDATE_HOSTKEYS_NO=yes` 的集群上使用。
63
61
 
64
62
  **`IdentitiesOnly yes`**:只用指定的私钥。ssh-agent 里密钥多时,避免因尝试次数
65
63
  过多被服务端拒绝。
@@ -91,7 +89,7 @@ profile 里的 `KEY_NAME_MARKER` 就是用来从这种文件名里提取用户
91
89
  `scnet-hpc` profile 使用 `_<CLUSTER_HOST>_` 占位符,所以安装时通常需要显式传
92
90
  用户名,或在本地私有 profile 中补齐真实标记。
93
91
 
94
- 查看当前密钥指纹:
92
+ 查看密钥指纹:
95
93
 
96
94
  ```bash
97
95
  ssh-keygen -lf ~/.ssh/id_rsa_<集群>
@@ -113,7 +111,7 @@ ssh-keygen -p -f ~/.ssh/id_rsa_<集群>
113
111
  | `Connection timed out` | 端口被本地网络阻断(超算常用非 22 端口)|
114
112
  | `Bad owner or permissions on config` | `chmod 600 ~/.ssh/config` |
115
113
  | `WARNING: UNPROTECTED PRIVATE KEY` | `chmod 600 ~/.ssh/id_rsa_<集群>` |
116
- | 首次连接卡住不动 | 在等确认主机指纹,加 `-o StrictHostKeyChecking=accept-new` |
114
+ | 首次连接等待主机指纹确认 | 在等确认主机指纹,加 `-o StrictHostKeyChecking=accept-new` |
117
115
 
118
116
  多数超算平台有 **IP 白名单**,换网络(家里/公司/热点)后可能连不上,
119
117
  需要去控制台加当前 IP。
@@ -125,7 +123,7 @@ ssh -o BatchMode=yes <集群> '<命令>'
125
123
  ```
126
124
 
127
125
  `BatchMode=yes` 让认证失败立刻返回,而不是卡在密码提示上等输入。写自动化脚本时
128
- 必加,否则可能挂住。
126
+ 应启用,以避免自动化流程等待交互输入。
129
127
 
130
128
  ## 文件传输
131
129
 
@@ -135,4 +133,4 @@ scp <集群>:/public/home/$USER/scripts/logs/job_123.log .
135
133
  rsync -avz --progress ./dir/ <集群>:/public/home/$USER/dir/ # 大量文件
136
134
  ```
137
135
 
138
- 走同一个长连接,不需额外配置端口。
136
+ 上述命令复用 SSH profile 中的连接参数。
@@ -8,9 +8,38 @@
8
8
  4. **依赖路径**:区分 pip/conda 解析失败、运行时动态库失败、框架 kernel 不支持和模型/数据下载失败。
9
9
  5. **端到端结果**:记录成功条件、性能、显存/内存、失败日志和未验证范围。
10
10
 
11
+ ### 编译型软件的分层验证
12
+
13
+ - **把编译放到计算节点**:C++/HIP 大型构建的峰值内存可能远高于登录节点限额。
14
+ 编译器进程被 OOM kill 时,先换到有明确内存配额的计算作业,先排除资源限制,再判断源码问题。
15
+ - **只生成目标架构**:显式设置目标 `gfx` 架构。默认同时生成多个架构会成倍增加
16
+ 编译时间、产物体积和峰值内存。
17
+ - **升级工具链后重跑最小构建**:DTK/编译器升级可能改变 wrapper 头、host/device
18
+ 编译模式或默认链接路径。保存 verbose compile command,与上一版本逐项比较。
19
+ - **区分节点环境**:登录/CPU 节点可能加载得到 module,却缺少只在加速器节点完整
20
+ 可见的 HSA/HIP 库或符号链接。以目标计算节点解析到的真实库文件为准,必要时通过
21
+ CMake cache 变量显式传入,不创建全局假链接。
22
+ - **先串行能力、后并行能力**:先完成无 MPI 的单卡最小构建和功能验证,再开启 MPI,
23
+ 最后测试 GPU-aware 通信与多卡扩展,以隔离编译器、运行库和通信栈问题。
24
+
25
+ ### GPU-aware MPI 的验证边界
26
+
27
+ 系统 MPI 能启动多进程,不代表它带加速器感知能力。先记录 MPI 构建参数,并做
28
+ GPU-aware on/off 对照。单节点验证可先固定共享内存与 self 传输,减少 UCX/IB
29
+ 站点配置带来的变量;这只能证明单节点路径,不能外推到跨节点通信。
30
+
31
+ 多卡扩展通常同时依赖足够大的问题规模、较高的计算/通信比以及可用的 GPU-aware
32
+ 路径。小问题或低计算密度任务扩展不佳,不足以单独证明硬件或 MPI 配置错误。
33
+
34
+ ### 数值与性能验收
35
+
36
+ 加速器移植至少应同时报告:CPU/参考实现的关键数值偏差、守恒量或长期稳定性、
37
+ 单卡问题规模扫描、固定规模的 1/2/单节点全卡强扩展、GPU-aware on/off 对照、
38
+ 峰值内存/显存,以及每项测试的退出码和完整日志。性能通过不能替代正确性验证。
39
+
11
40
  ### 已验证的典型边界
12
41
 
13
- - **gfx936 / BW1000**:Triton、硬件 FP8 `torch._scaled_mm`、部分 bitsandbytes INT8 路径可能不可用;纯 PyTorch 软件反量化可作为推理兜底,但需要单独做性能和精度验证。
42
+ - **gfx936 / BW1000**:Triton、硬件 FP8 `torch._scaled_mm`、部分 bitsandbytes INT8 路径可能不可用;纯 PyTorch 软件反量化可作为兼容路径,但需要单独做性能和精度验证。
14
43
  - **gfx906 / Z100**:CentOS 7.6 的 glibc 2.17 会限制 manylinux wheel 版本;安装 Python 软件时优先选 manylinux2014 / manylinux_2_17 兼容包。MinerU 场景中,Python 3.10 + onnxruntime 1.16.3 是已验证方向,magika 版本/API 需要随源码适配。
15
44
  - **GROMACS HIP**:先验证 CMake 最低版本、DTK 的 `lib`/`lib64` CMake 路径、目标架构、FFTW 预置和 SIMD;CUDA 兼容层与原生 HIP 的性能不可直接互相推断。
16
45
 
@@ -26,18 +55,18 @@ echo "test_exit_code=$rc"
26
55
  exit "$rc"
27
56
  ```
28
57
 
29
- 不要只看 `sacct` 的 `COMPLETED`。脚本必须显式传递最后一个测试命令的退出码,并同时读取 `.out/.err`。
58
+ 不能只依据 `sacct` 的 `COMPLETED` 判定成功。脚本必须显式传递最后一个测试命令的退出码,并同时读取 `.out/.err`。
30
59
 
31
60
  ### 公开报告模板
32
61
 
33
- 标题采用“项目 + 加速器/平台 + 编译与性能/兼容性测试报告”。正文建议按以下顺序:
62
+ 标题采用“项目 + 加速器/平台 + 编译与性能/兼容性测试报告”。正文可按以下顺序:
34
63
 
35
64
  1. 环境信息
36
65
  2. 编译过程与主要难题
37
66
  3. 最终方案与验证结果
38
67
  4. 测试数据与资源占用
39
68
  5. 已知限制与注意事项
40
- 6. 经验总结与通用建议
69
+ 6. 适用范围与可复用结论
41
70
  7. 附录:快速复现命令
42
71
 
43
72
  发布前扫描:
@@ -1,6 +1,6 @@
1
1
  # 排查作业失败
2
2
 
3
- ## 第一原则:不要相信 `sacct` 的 State
3
+ ## 作业状态与实际退出码
4
4
 
5
5
  bash 脚本里 Python 崩了之后,bash 继续执行后面的行,作业以**最后一条命令**的退出码
6
6
  结束。真实案例:
@@ -10,10 +10,10 @@ $ sacct -j <JOB_ID> --format=JobID,JobName,State,ExitCode,Elapsed
10
10
  <JOB_ID> | example-job | COMPLETED | 0:0 | 00:34:32
11
11
  ```
12
12
 
13
- 看着完美,实际上 Python 在第 3 分钟就抛了 `ValueError`,日志末尾还打印了脚本里
13
+ 该记录不能证明 Python 工作负载成功。Python 可能已在第 3 分钟抛出 `ValueError`,而日志末尾仍打印脚本中
14
14
  写的 `===== COMPLETE =====`。
15
15
 
16
- **排查顺序永远是:读日志 → 看 State,不是反过来。**
16
+ 排查时先读取日志和工作负载退出码,再结合调度器 State 判断。
17
17
 
18
18
  预防:脚本末尾写
19
19
 
@@ -43,7 +43,7 @@ sbatch: error: min tres(gres/dcu) request 0 exceeds per-job max tres limit 1
43
43
  sbatch: error: QOSMinGRES
44
44
  ```
45
45
 
46
- 忘了 `--gres=dcu:N`。这个分区**强制**每个作业至少 1 张 DCU,纯 CPU 作业也要写。
46
+ 该分区要求每个作业至少申请 1 张 DCU。补充 `--gres=dcu:N`;纯 CPU 负载也受该分区策略约束。
47
47
 
48
48
  ### `too much memory was requested relative to the number of CPUs`
49
49
 
@@ -82,7 +82,7 @@ httpcore.ConnectError: [Errno -2] Name or service not known
82
82
  ### `cannot get address for 'hipDrvLaunchKernelEx' from libamdhip64.so`
83
83
 
84
84
  Triton。DTK 26.04 的 HIP 运行时缺上游 ROCm 7 的 API,Triton 的 AMD backend 加载
85
- 即失败。**放弃这条路**,包括所有依赖 Triton kernel 的框架(SGLang FP8、vLLM ROCm
85
+ 即失败。该软件栈不能使用此 Triton 路径,依赖对应 Triton kernel 的框架也不适用(SGLang FP8、vLLM ROCm
86
86
  MoE、transformers finegrained-fp8)。
87
87
 
88
88
  郑州集群最近还会先报另一种缺库:
@@ -91,7 +91,7 @@ MoE、transformers finegrained-fp8)。
91
91
  ImportError: libgcvm.so.17git: cannot open shared object file
92
92
  ```
93
93
 
94
- 本质相同,都是 Triton 在当前 DTK/DCU 软件栈上不可用,不要继续调 Triton kernel。
94
+ 本质相同,都是 Triton 在当前 DTK/DCU 软件栈上不可用,继续调整 Triton kernel 参数不能解决该运行时缺失。
95
95
 
96
96
  ### `torch._scaled_mm is only supported on CUDA devices with compute capability >= 9.0 or 8.9, or ROCm MI300+`
97
97
 
@@ -114,7 +114,7 @@ cat: No such file or directory ← 连日志都没有,因为写不进
114
114
  1. `--output` / `--error` 路径里有未展开的变量(`$USER`、`$HOME`)
115
115
  2. **日志目录不存在** —— Slurm 不会自动创建
116
116
 
117
- 修法:`#SBATCH` 里写完整字面量路径,提交前 `mkdir -p <日志目录>`。
117
+ 处理方法:`#SBATCH` 里写完整字面量路径,提交前 `mkdir -p <日志目录>`。
118
118
  `scripts/new-job.sh` 会展开好用户名并提示建目录。
119
119
 
120
120
  ### `module load` 静默失败:作业返回 0 但 `ROCM_PATH` 没设置
@@ -129,9 +129,9 @@ ROCM_PATH=未设置
129
129
  ### python exit=0 ← 作业「成功」了
130
130
  ```
131
131
 
132
- 这比直接报错更坑,因为退出码是 0。实测于昆山集群。
132
+ 该现象可能返回退出码 0,因此还需检查模块环境变量和探针输出。该行为已在昆山集群观测。
133
133
 
134
- 修法:shebang 用 `#!/bin/bash -l`,或在 heredoc 内包一层 `bash -l`。
134
+ 处理方法:shebang 用 `#!/bin/bash -l`,或在 heredoc 内包一层 `bash -l`。
135
135
  `scripts/new-job.sh` 生成的脚本已经用 `bash -l`。
136
136
 
137
137
  自检:作业里打印 `echo "ROCM_PATH=${ROCM_PATH:-未设置}"`,未设置就说明没生效。
@@ -145,7 +145,7 @@ ROCM_PATH=未设置
145
145
  SyntaxError: Non-ASCII character '\xe5' ... no encoding declared
146
146
  ```
147
147
 
148
- 修法:作业脚本里显式加载 Python 3 module:
148
+ 处理方法:作业脚本里显式加载 Python 3 module:
149
149
 
150
150
  ```bash
151
151
  module load python/3.8.10
@@ -186,7 +186,7 @@ for i in range(torch.cuda.device_count()):
186
186
 
187
187
  ### 作业一直 `PD (Priority)` 不动
188
188
 
189
- 排队等资源,正常现象。看预计启动时间:
189
+ 该状态表示等待调度资源。查询预计启动时间:
190
190
 
191
191
  ```bash
192
192
  squeue -j <ID> --start
@@ -197,15 +197,14 @@ sinfo -p <分区> -o "%n %t" # 有几台 idle
197
197
 
198
198
  ## 调试策略
199
199
 
200
- **验证要便宜。** 大模型加载动辄半小时,用大作业试错代价太高。分层验证:
200
+ 按成本递增的顺序执行验证,避免使用完整模型诊断基础环境问题:
201
201
 
202
202
  1. **纯逻辑**(不需要 GPU/torch)→ 本地 Python 直接跑
203
203
  2. **需要 torch/DCU 但数据量小** → `srun` 交互式,约 5 秒拿到节点
204
204
  3. **小规模真实数据** → 2 分钟的 `sbatch` 探针作业
205
205
  4. **完整模型** → 最后才做
206
206
 
207
- 真实教训:DeepSeek V4 的反量化实现,先写 16 项单元测试(2 分钟作业)验证数值正确,
208
- 再跑完整模型。如果跳过第 3 步直接跑,每轮试错 30 分钟。
207
+ 例如,反量化实现应先通过小规模数值测试,再运行完整模型。
209
208
 
210
209
  **探针作业模板**:只读 metadata 不加载权重,几秒就出结果:
211
210
 
@@ -222,11 +221,11 @@ with safe_open(path, framework="pt", device="cpu") as f:
222
221
  # 作业跑在哪个节点
223
222
  squeue -j <ID> -o "%N"
224
223
 
225
- # 登到那个节点看进程(作业运行期间可以)
224
+ # 在作业运行期间检查目标节点进程
226
225
  ssh <节点名> 'top -b -n1 -u $USER | head -20'
227
226
 
228
227
  # GPU 占用
229
228
  srun --jobid=<ID> --overlap rocm-smi
230
229
  ```
231
230
 
232
- `--overlap` 让新的 srun 步骤共享已有作业的资源分配,不用重新排队。
231
+ `--overlap` 让新的 srun 步骤共享已有作业的资源分配,避免为诊断步骤重新申请一份资源。
@@ -98,9 +98,17 @@ parse_cluster_arg() {
98
98
  REST=()
99
99
  while [ $# -gt 0 ]; do
100
100
  case "$1" in
101
- --cluster) CLUSTER="${2:-}"; shift 2 ;;
101
+ --cluster)
102
+ [ $# -ge 2 ] || die "--cluster 缺少参数"
103
+ CLUSTER="$2"
104
+ shift 2
105
+ ;;
102
106
  --cluster=*) CLUSTER="${1#*=}"; shift ;;
103
107
  *) REST+=("$1"); shift ;;
104
108
  esac
105
109
  done
110
+ if [ -n "$CLUSTER" ]; then
111
+ [[ "$CLUSTER" =~ ^[A-Za-z0-9][A-Za-z0-9._-]*$ ]] \
112
+ || die "集群短名包含不安全字符"
113
+ fi
106
114
  }
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env bash
2
2
  # 生成一个符合目标集群所有约束的作业脚本骨架。
3
3
  #
4
- # ./new-job.sh [--cluster <集群短名>] <作业名> [加速器数] [cpu数] [时长]
4
+ # ./new-job.sh [--cluster <集群短名>] [--cpu-only] [--partition <分区>] \
5
+ # <作业名> [加速器数] [cpu数] [时长]
5
6
  #
6
7
  # 内存按 cpus × DEF_MEM_PER_CPU 自动取安全值,避免手算出错。
7
8
 
@@ -16,11 +17,16 @@ set -- "${REST[@]+${REST[@]}}"
16
17
  REMOTE_USER=""
17
18
  REFRESH=no
18
19
  USE_AUTO=yes
20
+ CPU_ONLY=no
21
+ PARTITION_OVERRIDE=""
19
22
  ARGS=()
20
23
  while [ $# -gt 0 ]; do
21
24
  case "$1" in
22
- --user) REMOTE_USER="${2:-}"; shift 2 ;;
25
+ --user) [ $# -ge 2 ] || die "--user 缺少参数"; REMOTE_USER="$2"; shift 2 ;;
23
26
  --user=*) REMOTE_USER="${1#*=}"; shift ;;
27
+ --partition) [ $# -ge 2 ] || die "--partition 缺少参数"; PARTITION_OVERRIDE="$2"; shift 2 ;;
28
+ --partition=*) PARTITION_OVERRIDE="${1#*=}"; shift ;;
29
+ --cpu-only) CPU_ONLY=yes; shift ;;
24
30
  --refresh) REFRESH=yes; shift ;;
25
31
  --no-auto) USE_AUTO=no; shift ;;
26
32
  *) ARGS+=("$1"); shift ;;
@@ -35,17 +41,32 @@ TIME="${4:-00:20:00}"
35
41
 
36
42
  if [ -z "$NAME" ]; then
37
43
  cat >&2 <<EOF
38
- 用法: $0 [--cluster <集群短名>] [--user <远端用户名>] [--refresh|--no-auto] <作业名> [加速器数] [cpu数] [时长]
44
+ 用法: $0 [--cluster <集群短名>] [--user <远端用户名>] [--cpu-only]
45
+ [--partition <分区>] [--refresh|--no-auto] <作业名> [加速器数] [cpu数] [时长]
39
46
 
40
47
  例子:
41
48
  $0 probe # 1 卡, 8 核, 20 分钟(探针)
42
49
  $0 infer 8 32 01:30:00 # 8 卡, 32 核, 1.5 小时(大模型)
50
+ $0 --cpu-only build 0 32 01:00:00 # 使用 profile 的 CPU 分区,不申请加速器
43
51
 
44
52
  可用集群: $(list_clusters | tr '\n' ' ')
45
53
  EOF
46
54
  exit 1
47
55
  fi
48
56
 
57
+ [[ "$NAME" =~ ^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$ ]] \
58
+ || die "作业名只允许 1-64 个字母、数字、点、下划线或连字符,且必须以字母或数字开头"
59
+ [[ "$ACCEL" =~ ^[0-9]+$ ]] || die "加速器数必须是非负整数"
60
+ [[ "$CPUS" =~ ^[1-9][0-9]*$ ]] || die "CPU 数必须是正整数"
61
+ [[ "$TIME" =~ ^([0-9]+-)?[0-9]{1,2}:[0-9]{2}:[0-9]{2}$ ]] \
62
+ || die "时长格式应为 HH:MM:SS 或 D-HH:MM:SS"
63
+ if [ -n "$REMOTE_USER" ]; then
64
+ [[ "$REMOTE_USER" =~ ^[A-Za-z0-9._-]+$ ]] || die "远端用户名包含不安全字符"
65
+ fi
66
+ if [ -n "$PARTITION_OVERRIDE" ]; then
67
+ [[ "$PARTITION_OVERRIDE" =~ ^[A-Za-z0-9._-]+$ ]] || die "分区名包含不安全字符"
68
+ fi
69
+
49
70
  if [ "$REFRESH" = yes ]; then
50
71
  "$REPO_ROOT/scripts/refresh-cluster.sh" --cluster "${CLUSTER:-}"
51
72
  fi
@@ -56,6 +77,17 @@ load_cluster "$CLUSTER"
56
77
  [ "${SCHEDULER:-slurm}" = "slurm" ] \
57
78
  || die "暂只支持 slurm,${CLUSTER_ID} 用的是 ${SCHEDULER}"
58
79
 
80
+ if [ "$CPU_ONLY" = yes ]; then
81
+ [ -n "${PARTITION_CPU:-}" ] \
82
+ || die "${CLUSTER_ID} profile 未定义 PARTITION_CPU,不能使用 --cpu-only"
83
+ EFFECTIVE_PARTITION="${PARTITION_OVERRIDE:-$PARTITION_CPU}"
84
+ ACCEL=0
85
+ [ -n "${DEF_MEM_PER_CPU_CPU:-}" ] && DEF_MEM_PER_CPU="$DEF_MEM_PER_CPU_CPU"
86
+ else
87
+ EFFECTIVE_PARTITION="${PARTITION_OVERRIDE:-${PARTITION:-}}"
88
+ fi
89
+ [ -n "$EFFECTIVE_PARTITION" ] || die "目标分区为空,请补 profile 或使用 --partition"
90
+
59
91
  # 内存上限 = cpus × DEF_MEM_PER_CPU,留 1GB 余量避免边界抖动
60
92
  if [ -n "${DEF_MEM_PER_CPU:-}" ]; then
61
93
  MEM_MB=$(( CPUS * DEF_MEM_PER_CPU ))
@@ -70,15 +102,19 @@ fi
70
102
 
71
103
  # QOS 强制申请加速器时必须写 --gres
72
104
  GRES_LINE=""
73
- if [ -n "${GRES_TYPE:-}" ]; then
105
+ if [ "$CPU_ONLY" != yes ] && [ -n "${GRES_TYPE:-}" ] && [ "$ACCEL" -gt 0 ]; then
74
106
  GRES_LINE="#SBATCH --gres=${GRES_TYPE}:${ACCEL}"
75
107
  [ -n "${MIN_GRES:-}" ] && GRES_LINE="$GRES_LINE # 必须:QOS 要求最少 ${MIN_GRES}"
76
108
  fi
109
+ if [ "$CPU_ONLY" != yes ] && [ -n "${MIN_GRES:-}" ] && [ "$ACCEL" -eq 0 ]; then
110
+ die "默认/指定分区要求至少 ${MIN_GRES};请申请加速器或使用 --cpu-only"
111
+ fi
77
112
 
78
113
  # 远端用户名。#SBATCH 是注释行,Slurm 不做变量展开 —— 日志路径里写 $USER
79
114
  # 会被当成字面量目录名,作业以 exit 53 失败且不产生日志。所以这里必须展开成
80
115
  # 真实用户名。默认取本地用户名,不同时用 --user 覆盖。
81
116
  REMOTE_USER="${REMOTE_USER:-${REMOTE_USER_DETECTED:-$(whoami)}}"
117
+ [[ "$REMOTE_USER" =~ ^[A-Za-z0-9._-]+$ ]] || die "探测到的远端用户名包含不安全字符,请用 --user 显式指定"
82
118
  HOME_DIR="${HOME_BASE}/${REMOTE_USER}"
83
119
 
84
120
  # 脚本体(非 #SBATCH 行)里可以正常用 $USER
@@ -101,11 +137,11 @@ cat > "$OUT" <<EOF
101
137
  #!/bin/bash -l
102
138
  # 目标集群: ${CLUSTER_ID} (${CLUSTER_DESC:-$SSH_HOST})
103
139
  #
104
- # 注意 shebang 是 "bash -l"(login shell)。有些集群的 module 函数只在 login
105
- # shell 里定义,用普通 "#!/bin/bash" 会让 module load 静默失败 —— 作业照样
106
- # 返回 0,但 ROCM_PATH 不设置、rocminfo/rocm-smi 无输出。实测于昆山集群。
140
+ # 使用 "bash -l",因为部分集群仅在 login
141
+ # shell 中定义 module 函数。普通 "#!/bin/bash" 可能使 module load 静默失败,作业仍
142
+ # 返回 0,但 ROCM_PATH 未设置且能力工具无输出。该行为已在昆山集群观测。
107
143
  #SBATCH --job-name=${NAME}
108
- #SBATCH --partition=${PARTITION}
144
+ #SBATCH --partition=${EFFECTIVE_PARTITION}
109
145
  ${GRES_LINE}
110
146
  #SBATCH --nodes=1
111
147
  #SBATCH --ntasks-per-node=1
@@ -142,7 +178,7 @@ PYEOF
142
178
  rc=\$?
143
179
  echo "### python exit=\$rc"
144
180
  date +"%Y-%m-%d %H:%M:%S"
145
- # 必须传播退出码,否则 python 崩了 sacct 仍显示 COMPLETED(假成功)
181
+ # 传播工作负载退出码,避免调度器状态掩盖 Python 失败
146
182
  exit \$rc
147
183
  EOF
148
184
 
@@ -153,11 +189,11 @@ sed -i.bak '/^$/N;/^\n$/D' "$OUT" 2>/dev/null && rm -f "$OUT.bak"
153
189
 
154
190
  echo "已生成 $OUT"
155
191
  echo " 集群=${CLUSTER_ID} ${GRES_TYPE:-加速器}=${ACCEL} CPU=${CPUS} 内存=${MEM_NOTE} 时长=${TIME}"
156
- echo " 分区=${PARTITION:-未设置} 节点数=${NODE_COUNT:-未探测}(来源:${NODE_COUNT_SOURCE:-profile/cache})"
192
+ echo " 分区=${EFFECTIVE_PARTITION} 模式=$([ "$CPU_ONLY" = yes ] && echo CPU || echo accelerator) 节点数=${NODE_COUNT:-未探测}(来源:${NODE_COUNT_SOURCE:-profile/cache})"
157
193
  echo " 日志目录=${HOME_DIR}/scripts/logs(用户名 ${REMOTE_USER},不对请加 --user)"
158
194
  echo
159
195
  echo "上传并提交:"
160
196
  echo " scp $OUT ${CLUSTER_ID}:${HOME_DIR}/scripts/"
161
197
  echo " ssh ${CLUSTER_ID} 'mkdir -p ${HOME_DIR}/scripts/logs && sbatch --test-only ${HOME_DIR}/scripts/$OUT'"
162
198
  echo
163
- echo "注意:logs 目录必须先存在,否则作业会以 exit 53 失败且不产生任何日志。"
199
+ echo "要求:logs 目录必须先存在;否则作业会以 exit 53 失败且不产生日志。"
@@ -208,7 +208,7 @@ if [ "$COMPUTE" = yes ] && [ -n "$COMPUTE_OUT" ]; then
208
208
  IMPORT_ERR:*) KNOWN_LIMITS_NEW="${KNOWN_LIMITS_NEW:+${KNOWN_LIMITS_NEW};}bitsandbytes 导入失败(${BNB})" ;;
209
209
  esac
210
210
 
211
- # 保留 profile 里已有的人工经验,动态结果放前面。
211
+ # 保留 profile 中已有的人工验证结论,并将动态结果置于前面。
212
212
  if [ -n "${KNOWN_LIMITATIONS:-}" ]; then
213
213
  OLD_IFS="$IFS"
214
214
  IFS=';'
@@ -19,21 +19,32 @@ PYTHON_BIN=""
19
19
 
20
20
  while [ $# -gt 0 ]; do
21
21
  case "$1" in
22
- --cpus) CPUS="${2:-$CPUS}"; shift 2 ;;
22
+ --cpus) [ $# -ge 2 ] || die "--cpus 缺少参数"; CPUS="$2"; shift 2 ;;
23
23
  --cpus=*) CPUS="${1#*=}"; shift ;;
24
- --time) TIME="${2:-$TIME}"; shift 2 ;;
24
+ --time) [ $# -ge 2 ] || die "--time 缺少参数"; TIME="$2"; shift 2 ;;
25
25
  --time=*) TIME="${1#*=}"; shift ;;
26
- --accelerators) ACCEL="${2:-$ACCEL}"; shift 2 ;;
26
+ --accelerators) [ $# -ge 2 ] || die "--accelerators 缺少参数"; ACCEL="$2"; shift 2 ;;
27
27
  --accelerators=*) ACCEL="${1#*=}"; shift ;;
28
- --user) REMOTE_USER="${2:-}"; shift 2 ;;
28
+ --user) [ $# -ge 2 ] || die "--user 缺少参数"; REMOTE_USER="$2"; shift 2 ;;
29
29
  --user=*) REMOTE_USER="${1#*=}"; shift ;;
30
- --python) PYTHON_BIN="${2:-}"; shift 2 ;;
30
+ --python) [ $# -ge 2 ] || die "--python 缺少参数"; PYTHON_BIN="$2"; shift 2 ;;
31
31
  --python=*) PYTHON_BIN="${1#*=}"; shift ;;
32
32
  -h|--help) sed -n '2,10p' "$0" | sed 's/^# \?//'; exit 0 ;;
33
33
  *) die "未知参数: $1" ;;
34
34
  esac
35
35
  done
36
36
 
37
+ [[ "$CPUS" =~ ^[1-9][0-9]*$ ]] || die "CPU 数必须是正整数"
38
+ [[ "$ACCEL" =~ ^[1-9][0-9]*$ ]] || die "加速器数必须是正整数"
39
+ [[ "$TIME" =~ ^([0-9]+-)?[0-9]{1,2}:[0-9]{2}:[0-9]{2}$ ]] \
40
+ || die "时长格式应为 HH:MM:SS 或 D-HH:MM:SS"
41
+ if [ -n "$REMOTE_USER" ]; then
42
+ [[ "$REMOTE_USER" =~ ^[A-Za-z0-9._-]+$ ]] || die "远端用户名包含不安全字符"
43
+ fi
44
+ if [ -n "$PYTHON_BIN" ]; then
45
+ [[ "$PYTHON_BIN" =~ ^[A-Za-z0-9_./-]+$ ]] || die "Python 命令包含不安全字符"
46
+ fi
47
+
37
48
  [ -n "${CLUSTER:-}" ] || die "请用 --cluster <集群短名> 指定集群。"
38
49
  load_cluster "$CLUSTER"
39
50
 
@@ -41,6 +52,7 @@ load_cluster "$CLUSTER"
41
52
  [ -n "${PARTITION:-}" ] || die "${CLUSTER_ID} 的 PARTITION 为空,无法提交计算节点探针。"
42
53
 
43
54
  REMOTE_USER="${REMOTE_USER:-${REMOTE_USER_DETECTED:-$(whoami)}}"
55
+ [[ "$REMOTE_USER" =~ ^[A-Za-z0-9._-]+$ ]] || die "探测到的远端用户名包含不安全字符,请用 --user 显式指定"
44
56
  HOME_DIR="${HOME_BASE}/${REMOTE_USER}"
45
57
  PROBE_DIR="${HOME_DIR}/.scnet-hpc/probes"
46
58
  PROBE_BASE="${PROBE_DIR}/compute-probe"