提交 GPU 作业之前,需要确认各分区的空闲卡数。常规流程依次包括连接 VPN、SSH 登录、执行 scontrol 与 squeue,并对输出人工统计。Slurm GPU Widget 将上述流程封装为一个后台采集脚本,以 Übersicht 桌面卡片和 SwiftBar 菜单栏两种形式常驻显示各分区的占用情况;单次刷新耗时约 0.7–1.3 s,默认刷新周期为 15 s。项目以 MIT 协议开源:Yu-Xiang-Yang/slurm-gpu-widget。
1 背景与动机
GPU 集群通常以分区(partition)为单位组织资源,不同分区的 GPU 型号、节点数量与负载差异显著。提交作业前,需要回答以下三个问题:
- 哪个分区仍有空闲 GPU;
- 单个节点上最多有多少空闲 GPU,该数值决定多卡作业能否立即启动;
- 自身作业的运行时长、时间限制与排队状态。
对于需经 VPN 接入的集群,获取上述信息的常规流程存在三方面不便:
- 流程冗长:依次完成 VPN 连接、SSH 登录、命令执行三个步骤,每次查询均需重复;
- 原始输出不含汇总值:
scontrol show node按节点逐行输出,GPU 总数位于Gres字段,已分配数位于AllocTRES字段,需解析后累加; - 节点状态需剔除:处于
DOWN、DRAIN等状态的节点不可调度,若计入总量将高估可用资源。
Slurm GPU Widget 的目标是将该流程自动化,并以常驻界面呈现结果,使资源状态无需登录集群即可读取。
2 功能概述

图 1 Übersicht 桌面卡片界面(模拟数据,由仓库 mock/ 数据与前端源码渲染,启用 SHOW_MY_JOBS=1 与 JOB_GPU_USAGE=1)。上部为各分区的运行/排队/本人作业数、GPU 与内存占用条及单节点最大空闲卡数;下部“我的作业”列出作业 ID、名称、已运行时间/时限,以及各 GPU 的显存占用与利用率。
主要功能如下:
| 功能 | 说明 |
|---|---|
| VPN 检测与自动连接 | 通过 AppleScript 读取指定 Tunnelblick 配置的状态;非 CONNECTED 时发起连接,并轮询至多 CONNECT_TIMEOUT 秒 |
| 单次 SSH 往返 | scontrol show node -o 与 squeue 在同一非交互会话中执行,复用持久连接 |
| 分区级聚合 | GPU 已用/总数、CPU 已用/总数、空闲与不可用节点数、运行与排队作业数、本人作业数 |
| MIG 识别 | MIG 切分的 GPU(如 gpu:2g.20gb:12)以 MIG 实例数而非物理卡数计量,并加以标注 |
| 内存与型号 | 分区与节点级主机内存、可配置的 GPU 型号标签;可选通过 nvidia-smi 读取单卡型号、显存与利用率 |
| 本人作业列表 | 作业 ID、名称、已运行时间/时限、分区、节点与 GPU 数;启用 JOB_GPU_USAGE=1 后附各 GPU 的显存占用与利用率 |
| 节点列表(可选) | 在各分区下列出节点名称、状态及单节点 GPU/CPU 占用 |
| 双语界面 | 中文或英文,可显式指定或随 macOS 系统语言 |
| 可拖动卡片 | 桌面卡片可通过标题拖动,位置在刷新后保持,双击恢复默认位置 |
3 系统架构
项目由一个采集脚本与两个前端组成,数据流如下:
Tunnelblick(VPN 状态检测 / 自动连接)
│
▼
cluster-status.sh ── ssh(BatchMode, ControlMaster)──▶ 登录节点:scontrol / squeue
│ 本地 awk 聚合
├──── JSON ─────────────▶ Übersicht 桌面卡片
└──── --swiftbar-stream ▶ SwiftBar 菜单栏
| 前端 | 呈现位置 | 安装方式 |
|---|---|---|
| Übersicht | 桌面半透明卡片,每个分区一块 | brew install --cask ubersicht |
| SwiftBar | 菜单栏条目(如 GPU free 14/32),下拉菜单按分区展开 |
brew install --cask swiftbar |
两个前端调用同一采集脚本 cluster-status.sh,二者可单独或同时安装。采集脚本提供四种调用方式:
| 参数 | 输出 |
|---|---|
| (无) | 单个 JSON 对象,供 Übersicht 解析 |
--swiftbar |
SwiftBar 插件格式 |
--swiftbar-stream |
同上,作业已运行时间编码为占位符,供 SwiftBar 流式插件逐秒替换;该插件实际调用此模式 |
--connect |
仅触发 Tunnelblick 连接;对应 SwiftBar 菜单中的手动连接项 |
JSON 结构是采集层与展示层之间的接口约定,替换任一侧不影响另一侧。
4 数据采集方法
采集流程分为五个步骤:
- VPN 状态:
osascript -e 'tell application "Tunnelblick" to get state of first configuration where name = "…"'。 - 数据获取:
ssh -o BatchMode=yes $SSH_HOST "scontrol show node -o; squeue -h -o '%P|%T|%u'",两条命令共用一次 SSH 往返。 - GPU 明细(可选,
GPU_DETAIL=1):登录节点并行对各 GPU 节点执行nvidia-smi --query-gpu=index,name,memory.used,memory.total,utilization.gpu,单节点超时由GPU_DETAIL_TIMEOUT限定。在启用pam_slurm_adopt的集群上,仅用户有运行作业的节点接受连接,其余节点回退为 Slurm 给出的统计值。 - 本人作业:
squeue -u $USER -t R提供作业 ID、名称、已运行时间、时限、节点与每节点 GPU 数。 - 本地聚合:由
awk完成,规则见下。
聚合规则:
- CPU 使用量取自
CPUAlloc/CPUTot; - GPU 总数由
Gres字段解析,支持gpu:a100:8(S:0-1)、gpu:4、gpu:2g.20gb:12,gpu:1g.20gb:4等格式;已分配数取自AllocTRES中的gres/gpu=N(数量为零时该字段缺省); Partitions字段按逗号拆分,隶属多个分区的节点在各分区内分别计数,全局 GPU 总量中仅计一次;- 处于
DOWN、DRAIN、FAIL、MAINT、NOT_RESPONDING状态的节点不计入可用容量; - 主机内存使用量按
RealMemory − FreeMem计算,原因是未对内存调度的集群上AllocMem恒为 0。
4.1 单节点最大空闲卡数
输出中的 gpu_max_free_node 表示单个节点上空闲 GPU(或 MIG 实例)的最大数目,该值给出可立即启动的单节点作业规模上限。图 1 中 gpu 分区空闲 10/16,但单节点最多空闲 8 卡;gpu-v100 分区空闲 0/4,单节点最多空闲 0 卡。全局空闲数与单节点空闲数需结合判读:前者反映总量,后者决定多卡作业能否一次性获得所需节点。
4.2 作业 GPU 使用情况
JOB_GPU_USAGE=1 时,对每个运行中的作业(至多 8 个)在其既有资源分配内执行 srun --jobid=<id> --overlap -N1 -n1 nvidia-smi …。在 ConstrainDevices=yes 的集群上,该命令仅能看到作业持有的 GPU,无需额外的索引映射,也不会申请新的资源。该路径不受 pam_slurm_adopt 限制,但每次查询会在 sacct 中记录为一个作业步(<jobid>.0、<jobid>.1 等),因此默认关闭,并由 JOB_GPU_INTERVAL(默认 30 s)限制查询频率,使小组件产生的作业步数量有界。
图 1 下部为该功能的显示效果:作业 123456(train_llm,2 块 GPU)已运行 1:23:45,时限为 2 天;GPU0 显存占用 59.8/80 GB、利用率 97%,GPU1 显存占用 39.3/80 GB、利用率 88%。显存占用条按占比着色(低于 60% 为绿色,60%–90% 为黄色,90% 及以上为红色),已运行时间在两次集群查询之间由本地逐秒递增。
5 刷新机制与性能
| 组件 | 周期 | 说明 |
|---|---|---|
| Übersicht,集群数据 | 15 s | 由 index.jsx 顶部的 REFRESH_SECONDS 设定 |
| SwiftBar,集群数据 | 15 s | 由 swiftbar/cluster.sh 中的 REFRESH_SECONDS 设定(流式插件,文件名中不含周期) |
| 作业已运行时间 | 1 s | 本地按 elapsed_s + (now − epoch) 递增,并以 limit_s 为上限,不产生集群查询 |
| 单作业 GPU 显存/利用率 | 30 s | 仅 JOB_GPU_USAGE=1 时有效 |
性能方面采取三项措施:
- 连接复用:SSH 启用
ControlMaster=auto与ControlPersist=300,复用连接可省去握手开销,源码注释给出的量级为约 1 s 降至约 0.3 s;单次完整刷新耗时约 0.7–1.3 s。 - 查询合并:节点与队列信息在同一会话内一次获取。
- 重叠抑制:采集脚本以目录锁(
mkdir)防止并发运行;上一次采集(如 VPN 或 SSH 响应较慢)尚未结束时,直接返回上一次的结果。残留超过 90 s 的锁视为进程异常退出所致,将被清除。
6 安装与配置
6.1 前提条件
- 免密 SSH:小组件在后台运行,无法响应交互提示,须采用密钥认证。密钥带口令时,需在
~/.ssh/config中加入UseKeychain yes与AddKeysToAgent yes;跳板机可通过ProxyJump声明。 - 非交互 VPN:Tunnelblick 的凭据须存入 macOS 钥匙串。需要一次性口令的 VPN 无法自动连接,此时应设置
AUTO_CONNECT=0。 - 自动化权限:首次运行时 macOS 询问是否允许 Übersicht 或 SwiftBar 控制 Tunnelblick,自动重连需授予该权限。
6.2 安装步骤
安装任一前端并启动一次(以生成其插件目录),随后执行:
git clone https://github.com/Yu-Xiang-Yang/slurm-gpu-widget.git
cd slurm-gpu-widget
bash install.sh
open -e ~/.config/cluster-widget/config
编辑配置后验证采集脚本,输出 JSON 即表明配置有效:
~/.config/cluster-widget/cluster-status.sh
~/.config/cluster-widget/cluster-status.sh --swiftbar
重复执行 install.sh 会更新脚本与小组件,但不会覆盖已有的配置文件。
6.3 配置项
配置文件位于 ~/.config/cluster-widget/config(模板为 config.example),命令行给出的环境变量优先于文件设置。常用配置项如下:
| 配置项 | 默认值 | 说明 |
|---|---|---|
CLUSTER_NAME |
空 | 显示于标题与菜单栏的集群名称 |
TB_CONFIG |
空 | Tunnelblick 配置名;为空时跳过 VPN 检测,直接 SSH 连接 |
SSH_HOST |
空 | SSH 目标,建议使用 ~/.ssh/config 中的 Host 别名 |
AUTO_CONNECT |
1 |
VPN 断开时是否自动发起连接 |
CONNECT_TIMEOUT |
30 |
等待 VPN 连接的最长时间(s) |
SSH_TIMEOUT |
8 |
SSH 连接超时(s) |
PARTITIONS |
空 | 分区白名单(逗号分隔);为空时显示全部 |
GPU_ONLY |
1 |
1 仅显示 GPU 分区;0 增加 CPU 分区与 CPU 占用条 |
SHOW_NODES |
0 |
1 在各分区下列出节点 |
GPU_MODELS |
空 | 各分区的 GPU 型号标签,如 gpu=NVIDIA A100 80GB;compute2=NVIDIA V100 32GB;Gres 不含型号时需要设置 |
GPU_DETAIL |
0 |
1 通过 nvidia-smi 读取各 GPU 的型号、显存与利用率 |
SHOW_MY_JOBS |
1 |
1 列出本人运行中的作业,仅使用 squeue,不产生作业步 |
JOB_GPU_USAGE |
0 |
1 经 srun --overlap 读取各作业的 GPU 显存与利用率 |
UI_LANG |
auto |
界面语言:zh、en 或 auto |
临时查看全部分区及节点明细时,可在命令行覆盖配置,不影响配置文件:
GPU_ONLY=0 SHOW_NODES=1 PARTITIONS=gpu,compute ~/.config/cluster-widget/cluster-status.sh --swiftbar
分区规模较大(约 100 个节点)时,SHOW_NODES=1 宜与 PARTITIONS 联用,以限制卡片高度。
7 输出格式
默认模式输出的 JSON 对象结构如下(节选):
{
"lang": "en", "cluster": "Demo", "time": "13:58:32",
"vpn": "CONNECTED", "error": "",
"gpu_used": 10, "gpu_total": 20,
"partitions": [{
"name": "gpu", "nodes": 3, "idle_nodes": 1, "down_nodes": 1,
"cpu_used": 48, "cpu_total": 128, "gpu_used": 6, "gpu_total": 16,
"gpu_max_free_node": 8, "running": 2, "pending": 1, "mine": 0, "mig": false,
"model": "NVIDIA A100-SXM4-80GB", "mem_used": 1236992, "mem_total": 3072000
}]
}
字段约定如下:
- 内存数值单位为 MiB;
node_list仅在SHOW_NODES=1时出现;- 各节点的
gpus数组仅在GPU_DETAIL=1且该节点接受查询时非空; SHOW_MY_JOBS=1时,顶层对象附加my_jobs数组,每个作业含elapsed_s、limit_s(UNLIMITED记为 −1),顶层的epoch记录采集时刻,供本地计时使用。
8 无集群环境下的测试
仓库的 mock/ 目录提供合成的 scontrol 与 squeue 输出,可在没有集群的环境中验证采集与前端渲染:
MOCK_DIR=mock bash cluster-status.sh
MOCK_DIR=mock SHOW_NODES=1 CLUSTER_NAME=Demo bash cluster-status.sh --swiftbar
图 1 即由模拟数据渲染而成:采集脚本以 MOCK_DIR=mock、USER=testuser、SHOW_MY_JOBS=1、JOB_GPU_USAGE=1 与 PARTITIONS=gpu,gpu-v100 运行,输出的 JSON 交由仓库中的 Übersicht 前端源码渲染,因此图中的作业名称、显存与时间均为 mock/ 目录中的合成数值,不对应真实集群。
9 设计取舍
| 设计 | 依据 |
|---|---|
全程非交互(BatchMode=yes) |
后台进程无法响应口令提示,须以密钥认证为前提 |
| 单次 SSH 往返与连接复用 | 降低单次刷新耗时与登录节点负载 |
| 重查询默认关闭 | GPU_DETAIL 与 JOB_GPU_USAGE 开销较大,后者还会在 sacct 中留下作业步,故需显式启用并限频 |
| 作业计时本地递增 | 每秒更新显示而不增加集群查询 |
| 单一采集脚本、JSON 作为接口 | 两个前端共用同一数据源,避免解析逻辑重复 |
| 安装器不覆盖配置 | 升级脚本时保留用户设置 |
| 不可用节点剔除、多分区节点全局去重 | 使空闲数反映实际可调度的容量 |
10 常见问题
| 现象 | 原因与处理 |
|---|---|
install.sh 报告未找到任何前端 |
应用从未启动过,插件目录尚未生成;启动一次后重新运行 |
| Übersicht 桌面无显示 | 其渲染服务(端口 41416)未启动;退出并重新打开 Übersicht,可用 curl http://127.0.0.1:41416/ 验证 |
提示 ssh failed |
未配置密钥登录或主机别名有误;执行 ssh -o BatchMode=yes $SSH_HOST true 复现 |
| VPN 保持断开 | 钥匙串中无凭据,或自动化权限被拒绝(系统设置 → 隐私与安全性 → 自动化) |
11 向其他调度器的扩展
采集脚本中仅远程命令与 awk 解析部分与 Slurm 相关。PBS 对应 pbsnodes -a 与 qstat,LSF 对应 bhosts 与 bqueues。替换这两处后,JSON 结构与两个前端均无需改动。
12 项目地址
GitHub:https://github.com/Yu-Xiang-Yang/slurm-gpu-widget(MIT 协议,含中英文 README)。