# 分布式网格计算部署指南(master 单向 SSH) > 把 CNO 网格计算分发到多台 Linux 服务器并行。适配"大部分机器异地、 > 只有 master 能 SSH 进 worker"的网络。 ## 1. 架构(master 单向 SSH 推/拉) ``` master(WSL2) 异地 worker tasks.json (本地 flock 安全) (无常驻进程) ├ 派任务: ssh worker "nohup run_one.py ─▶ 被动执行单点 │ --teff X ... &" 跑完写本地 conv.json + .7 ├ 取状态: ssh worker "python3 dist_check ◀─ 被动提供结果 │ .py --results results" └ 取种子: scp worker:.7 master(收敛点) ◀─ 汇聚到 master results/ 供后续点的 seed_step 查找 ``` **核心原则**: - **只需 master → worker 单向 SSH**(worker 在 NAT 后也行,不回连 master) - **任务队列只在 master 本地**(`fcntl.flock` 强一致,零并发风险) - **worker 无常驻进程**:master 直接 SSH 执行 `run_one.py`/`seed_step.py` 跑单点 - **冷启动零依赖**(seed=None,LTE grey 自建大气)→ 绝大多数点完美分布式 - **种子库就是 master 本地 `results/`**:收敛点的 `.7` 大气(393KB/点)由 master scp 取回汇聚于此,`run_grid.find_seed` 扫描该目录给后续冷启动失败的点做 seed_step 回退。无需任何外部对象存储。 - **大产物留 worker 本地**(`.spec` 等 13MB)→ 不跨网传 ## 2. 并发安全(重点) 任务队列 `tasks.json` **只在 master 本地文件**,用 `fcntl.flock` 排他锁保护。 master 多线程管理多台 worker,但所有 claim/report 都走本地 flock,**无跨机并发问题**。 worker 完全不碰 tasks.json。 ## 3. 一次性环境准备 ### 3.1 master(本机 WSL2) ```bash # pyyaml 可选(有则用,无则用内置极简解析器,零 pip 也能跑) pip3 install pyyaml 2>/dev/null || echo "用内置解析器,无需 pip" ``` ### 3.2 每台 worker(异地机器) 只需: - **python3**(系统自带) - **openssh-server**(让 master 能 SSH 进来) - **tlusty 树**(二进制 + 原子数据) ```bash sudo apt install python3 openssh-server ``` > **worker 不需要 curl、不需要 pip、不需要 SSH 回 master。** ### 3.3 SSH 免密配置(master → worker,单向即可) ```bash # 1. 生成密钥对(已有可跳过) ls ~/.ssh/id_ed25519.pub || ssh-keygen -t ed25519 -N "" -f ~/.ssh/id_ed25519 # 2. 推公钥到每台 worker(这一步会要求输一次密码) ssh-copy-id user@worker1 ssh-copy-id user@worker2 # 3. 验证免密(BatchMode 不弹交互提示,dist_master 用的就是这个模式) ssh -o BatchMode=yes user@worker1 "echo ok" ``` **localhost 也要配**(如果 workers 里包含 localhost): ```bash # WSL 默认没跑 sshd,先启动 sudo apt install openssh-server -y sudo service ssh start # localhost 的 host key 首次连接需要确认,BatchMode 下会报 # "Host key verification failed",必须预先加入 known_hosts: ssh-keyscan -H localhost >> ~/.ssh/known_hosts 2>/dev/null # 自己 SSH 自己也要公钥: ssh-copy-id localhost # 验证 ssh -o BatchMode=yes localhost "echo ok" ``` > **踩坑**:WSL 重启后 sshd 不会自启。加到 `~/.bashrc`: > ```bash > sudo service ssh start 2>/dev/null > ``` > 或者每次手动 `sudo service ssh start`。 **worker 不需要 SSH 回 master。** ### 3.4 分发 TLUSTY 树到每台 worker(首次,约 2GB) 在 master 上对每台 worker: ```bash TARGET=user@worker1 REMOTE_ROOT=~/DataSheel/tlusty # 记到 dist_config.yaml 的 tlusty_root # 优先用 rsync(支持 --exclude) rsync -avz --exclude='cno_grid/results' --exclude='*.log' --exclude='__pycache__' \ /home/dckj/program/tlusty/tl208-s54/ $TARGET:$REMOTE_ROOT/ ``` **没有 rsync 时用 scp 替代**(注意 scp 不支持 --exclude): ```bash # 只推必要子目录,不要推整个树(避免跟踪符号链接) ssh $TARGET "mkdir -p $REMOTE_ROOT/{tlusty,synspec,cno_grid}" scp /home/dckj/program/tlusty/tl208-s54/tlusty/tlusty.exe $TARGET:$REMOTE_ROOT/tlusty/ scp /home/dckj/program/tlusty/tl208-s54/synspec/synspec.exe $TARGET:$REMOTE_ROOT/synspec/ scp -r /home/dckj/program/tlusty/tl208-s54/data $TARGET:$REMOTE_ROOT/ scp -r /home/dckj/program/tlusty/tl208-s54/cno_grid/src \ /home/dckj/program/tlusty/tl208-s54/cno_grid/templates \ /home/dckj/program/tlusty/tl208-s54/cno_grid/seeds \ /home/dckj/program/tlusty/tl208-s54/cno_grid/config_dense.yaml \ $TARGET:$REMOTE_ROOT/cno_grid/ ``` > **踩坑:scp -r 会跟踪符号链接!** > 本地 `cno_grid/results -> /mnt/e/fmq/grid`(几百 MB 历史结果), > `scp -r cno_grid/` 会把链接指向的实际内容全推过去。 > **worker 不需要 master 的 results**(它自己建),所以: > - 用 rsync + `--exclude='results'`,或 > - 用 scp 时只推 `src/ templates/ seeds/ config*.yaml`,不推整个目录 ### 3.5 分发 cno_grid 代码到每台 worker(每次代码更新) ```bash rsync -avz --exclude='results' --exclude='*.log' --exclude='__pycache__' \ --exclude='tasks.json' \ /home/dckj/program/tlusty/tl208-s54/cno_grid/ \ $TARGET:$REMOTE_ROOT/cno_grid/ ``` 无 rsync 时: ```bash scp -r src/ templates/ seeds/ config_dense.yaml $TARGET:$REMOTE_ROOT/cno_grid/ ``` ### 3.6 验证 worker 部署完整性 ```bash ssh $TARGET "ls $REMOTE_ROOT/tlusty/tlusty.exe \ $REMOTE_ROOT/synspec/synspec.exe \ $REMOTE_ROOT/data/gfVIS99.dat \ $REMOTE_ROOT/cno_grid/src/run_one.py \ $REMOTE_ROOT/cno_grid/templates/cno_atmos.5.tpl" ``` 全部列出无报错即可。 ### 3.7 种子源(master 本地 results/) 种子(收敛点的 `.7` 大气)只存在 **master 本地的 `results/` 目录**: - 每个收敛模型在 `results//.7`,附带 `conv.json` - master 回收异地 worker 的收敛结果时,用 scp 把 `.7` 取回到这个目录 - 冷启动失败的点做 seed_step 回退时,`run_grid.find_seed` 扫描该目录, 按同 family (Teff/logg/logHe) 的 CNO 最近邻 → 全局最近邻选取种子 **无需任何外部对象存储(七牛/OSS 等)**。worker 端完全不碰种子库。 ### 3.8 从单机切分布式时(已有 results 里的收敛点) 如果之前用 `run_grid.py` 跑过一批,已收敛的 `.7` 已经在 `results//.7` 里——这正是分布式 master 查找种子的位置,**无需任何额外操作**。 确认种子可用: ```bash RESULTS=<你的 results 实际路径> # 如 /mnt/e/fmq/grid echo "收敛种子数: $(find $RESULTS -maxdepth 2 -name '*.7' | wc -l)" ``` > 注意:旧的 `.seeds_remote/` 目录(七牛时代的本地缓存)已不再使用, > 可以删除。master 现在直接扫 `results/`。 ## 4. 配置 dist_config.yaml ```yaml master: workdir: /home/dckj/program/tlusty/tl208-s54/cno_grid grid_config: /home/dckj/program/tlusty/tl208-s54/cno_grid/config_dense.yaml results: results workers: - host: localhost # 本机也算 worker tlusty_root: /home/dckj/program/tlusty/tl208-s54 nproc: 22 - host: dckj@192.168.7.102 # 异地机器 tlusty_root: ~/DataSheel/tlusty nproc: 35 # ... 每台机器一项 ``` ## 5. 运行 ### 5.1 预览(dry-run) ```bash cd /home/dckj/program/tlusty/tl208-s54/cno_grid python3 src/dist_master.py dist_config.yaml --dry-run # 输出:待计算 N 点,总并发 X 核,预计 ~Y 小时 + 部署检查清单 ``` ### 5.2 全量跑 ```bash nohup python3 src/dist_master.py dist_config.yaml > results/dist_run.log 2>&1 & ``` ### 5.3 监控 ```bash tail -f results/dist_run.log # master 视角(每 20s 打印状态计数) cat results/grid_status.json # 汇总 # 看某台 worker 的某个任务日志: ssh user@worker1 "tail ~/tlusty/tl208-s54/cno_grid/worker_jobs/<模型名>.log" ``` ### 5.4 断点续算 中断后重跑同一命令,已 `done` 的点自动跳过(tasks.json 持久化在 master)。 ## 6. 工作流程(master 内部) ``` 1. init tasks.json(5120 点;本地已 converged 标 done,其余 pending) - 按难度排序(CNO 总量升序 → Teff 升序 → logg 降序 → logHe 升序) - 每个 task 标注 wave 编号(同 CNO 总量归同一 wave) 2. 每台 worker 一个管理线程,并发跑: 循环: a. SSH 查 worker 正在跑的进程数 → 空闲槽 = nproc - running b. 每个空闲槽:本地 flock 领一个 pending→running(wave 门控: 只从最小未完成 wave 取任务,低金属 wave 全终态后才开放下一 wave) c. SSH 调 dist_check.py 查 worker 已完成的点: - 收敛 → scp .7 回 master results/ → 标 done - 未收敛 → 扫 master 本地 results/ 找邻居种子 → scp 种子到 worker → SSH 派 seed_step.py 重试 - seed_step 仍失败 → 标 failed(物理极限) 3. 全部终态 → 写 grid_status.json ``` **Wave 门控的意义**:低金属点易收敛,先算完产出 .7 种子;高金属点冷启动 困难,需要种子做 seed_step。如果无序派发,高金属点在种子还没产出时就被派出 去冷启动 → 失败 → 无种子可救 → 标 failed。wave 串行保证种子链从低金属向 高金属递进。实测:无序提交时 60K 收敛率 25%,wave 调度后期望 >50%。 ## 7. 文件清单 | 文件 | 角色 | 运行在哪 | |------|------|---------| | `src/dist_master.py` | master 调度器(推/拉/seed_step/汇总) | master | | `src/dist_check.py` | worker 状态查询(被 SSH 调用) | 每台 worker | | `src/claim_task.py` / `report_task.py` | 任务原子操作(master 本地调) | master | | `src/run_one.py` | 单点冷启动执行器(被 SSH 调用) | 每台 worker | | `src/seed_step.py` | 单点种子步进执行器(被 SSH 调用) | 每台 worker | | `dist_config.yaml` | 分布式配置 | master | | `tasks.json` | 任务队列(运行时生成) | master | > `dist_worker.py` 已废弃(旧双向模型残留),当前架构不用。 ## 8. 带宽与时间估算 | 项 | 大小 | 说明 | |----|------|------| | 单点计算 | 1200-2500s | 主耗时 | | SSH 派任务 | ~1KB/点 | 命令字符串,可忽略 | | 取 conv.json | 1.6KB/点 | 经 SSH,可忽略 | | 取种子 .7(收敛点) | 393KB/点 | scp 回 master,2Mbps≈1.6s | | seed_step 种子推送 | 393KB/点 | 仅失败点,少数 | | **跨网传输占比** | **<5%** | 相比计算可忽略 | 5120 点 / 2台×57核 ≈ **24 小时**(vs 单机 100+ 小时)。 ## 9. 从单机 run_grid.py 迁移到分布式 已有 `run_grid.py` 跑了一部分,想切换到 `dist_master.py` 续算: ```bash # 1. 停掉正在跑的 run_grid.py ps aux | grep run_grid kill # 2. 确保 dist_config.yaml 的 grid_config 指向同一个网格配置 # (如 config_dense.yaml),results 指向同一个结果目录 # 3. 预上传种子(见 3.8 节),否则高金属点 seed_step 无种子可用 # 4. 启动分布式(--force-init 重建 tasks.json,已收敛的自动标 done 跳过) python3 src/dist_master.py dist_config.yaml --force-init ``` 两边用同一个 `results/` 目录、同一套 `gen_input5.model_name()` 命名, `init_tasks` 会扫 `results//conv.json` 里 `converged=true` 的标为 done,完全兼容。 > **注意**:切换后不要再用 `run_grid.py` 跑同一个 results 目录, > 否则两边同时写同一个模型目录会冲突。 ## 10. 故障排查 | 现象 | 原因 | 解决 | |------|------|------| | `ssh: connect to host localhost port 22: Connection refused` | WSL 没跑 sshd | `sudo service ssh start` | | `Host key verification failed` | BatchMode 下首次连接不弹确认 | `ssh-keyscan -H localhost >> ~/.ssh/known_hosts` | | scp 把 results 全推到 worker 了 | `scp -r` 跟踪符号链接 | 用 rsync --exclude 或只推 src/templates/seeds | | `FileNotFoundError: '~/xxx/tlusty.exe'` | `tlusty_root` 用了 `~/`,Python 不展开 `~` | dist_config.yaml 改成绝对路径 `/home/user/...` | | worker 收到派发但 run_one 立刻退出 | 路径错 / 缺库 / 二进制架构不符 | 手动 SSH 跑一遍 `run_one.py` 看报错 | | master 派不出去 | SSH 免密没配好 | `ssh -o BatchMode=yes user@host "echo ok"` | | worker 算了但 master 没回收 | 看 `worker_jobs/.log` | `dist_check.py` 是否能列出 | | tasks.json 读到半截 JSON 崩溃 | 并发写时读方未加锁(已修复) | 升级 dist_master.py(用 `_read_tasks` + tmpfile rename) | | 任务一直 running | worker 进程崩了 | master 每 2 小时自动重派(stale_sec) | | 种子找不到(seed_step 失败) | master 本地 results/ 里还没有收敛的邻居点 | 等低金属 wave 先算完产出种子;或先单机跑一批易收敛点 | | 单机模式仍可用 | — | `python3 src/run_grid.py config_dense.yaml`(完全不受影响) | ## 11. 安全提示 - 本架构无需任何外部云服务凭证(不再使用七牛/OSS),无密钥泄露风险 - SSH 用密钥免密,不用密码