| |
| """UMA demo YAML 配置解析器 - 把 YAML 转换为训练启动命令与相关变量。 |
| |
| 用法: |
| python _parse_config.py <config.yaml> <action> [hydra_config_path] |
| |
| action: |
| name 打印实验名称 |
| command 打印训练启动命令 (python/torchrun/srun 风格) |
| 需要额外传入 hydra_config_path,作为 train.py -c 的目标 |
| env 打印环境变量 export 语句 |
| env-args 打印 env_setup.sh 的参数 (conda_env module1 module2 ...) |
| data-files 打印 preflight_check.sh 要检查的路径 |
| (checkpoint_location + data.train_dataset.splits.*.src + data.val_dataset.splits.*.src) |
| slurm 打印 SLURM 配置变量 |
| hydra-config 把 YAML 剥离 demo meta 后的部分(纯 hydra 内容)打印到 stdout |
| run.sh 会把它重定向到 outputs/xxx_ts/hydra_config.yaml |
| |
| 顶层 meta 键(只给 demo 工具用,不进 hydra): |
| name, description, launch, env, slurm, nccl |
| |
| 其余顶层键(data, job, runner, reducer, train_dataset, ...)作为 hydra 内容。 |
| """ |
|
|
| import os |
| import sys |
|
|
| import yaml |
|
|
|
|
| |
| META_KEYS = {"name", "description", "launch", "env", "slurm", "nccl"} |
|
|
|
|
| def load_config(path): |
| with open(path) as f: |
| return yaml.safe_load(f) |
|
|
|
|
| def get_train_py_path(config_path): |
| """根据 demo/configs/xxx.yaml 的位置推算 UMA/train.py 路径。""" |
| |
| demo_dir = os.path.dirname(os.path.dirname(os.path.abspath(config_path))) |
| uma_dir = os.path.dirname(demo_dir) |
| return os.path.join(uma_dir, "train.py") |
|
|
|
|
| |
| |
| |
|
|
| def print_name(cfg): |
| print(cfg.get("name", "uma_train")) |
|
|
|
|
| def print_command(cfg, config_path, hydra_config_path): |
| """拼 python/torchrun/srun train.py -c <hydra_config_path>。""" |
| if not hydra_config_path: |
| print( |
| "[ERROR] command 动作需要提供 hydra_config_path 作为第三个参数", |
| file=sys.stderr, |
| ) |
| sys.exit(1) |
|
|
| launch = cfg.get("launch", {}) or {} |
| num_nodes = launch.get("num_nodes", 1) |
| num_gpus = launch.get("num_gpus", 1) |
| launcher = launch.get("launcher", "python") |
|
|
| train_py = get_train_py_path(config_path) |
|
|
| if num_nodes > 1: |
| |
| |
| |
| print( |
| "torchrun \\\n" |
| f" --nnodes={num_nodes} \\\n" |
| " --node_rank=${SLURM_NODEID} \\\n" |
| f" --nproc_per_node={num_gpus} \\\n" |
| " --rdzv_id=${SLURM_JOB_ID} \\\n" |
| " --rdzv_backend=c10d \\\n" |
| " --rdzv_endpoint=${MASTER_ADDR}:${MASTER_PORT} \\\n" |
| f" {train_py} \\\n" |
| f" -c {hydra_config_path}" |
| ) |
| elif launcher == "torchrun" or num_gpus > 1: |
| |
| print( |
| "torchrun \\\n" |
| " --nnodes=1 \\\n" |
| f" --nproc_per_node={num_gpus} \\\n" |
| f" {train_py} \\\n" |
| f" -c {hydra_config_path}" |
| ) |
| else: |
| |
| print(f"python {train_py} -c {hydra_config_path}") |
|
|
|
|
| def print_env(cfg): |
| """打印环境变量 export 语句。""" |
| launch = cfg.get("launch", {}) or {} |
| num_nodes = launch.get("num_nodes", 1) |
| num_gpus = launch.get("num_gpus", 1) |
|
|
| omp = launch.get("omp_num_threads", 1) |
| print(f"export OMP_NUM_THREADS={omp}") |
|
|
| if num_gpus > 1: |
| devices = ",".join(str(i) for i in range(num_gpus)) |
| print(f"export HIP_VISIBLE_DEVICES={devices}") |
| elif num_gpus == 1: |
| print("export HIP_VISIBLE_DEVICES=0") |
|
|
| |
| if num_nodes > 1: |
| nccl = cfg.get("nccl", {}) or {} |
| print("export HSA_FORCE_FINE_GRAIN_PCIE=1") |
| if nccl.get("socket_ifname"): |
| print(f"export NCCL_SOCKET_IFNAME={nccl['socket_ifname']}") |
| if nccl.get("ib_hca"): |
| print(f"export NCCL_IB_HCA={nccl['ib_hca']}") |
| if nccl.get("proto"): |
| print(f"export NCCL_PROTO={nccl['proto']}") |
|
|
|
|
| def print_env_args(cfg): |
| """打印 env_setup.sh 的参数: conda_env module1 module2 ...""" |
| env = cfg.get("env", {}) or {} |
| conda_env = env.get("conda_env", "chem") |
| modules = env.get("modules", []) or [] |
| parts = [conda_env] + list(modules) |
| print(" ".join(parts)) |
|
|
|
|
| def _dig_src_paths(node, out): |
| """递归找出 splits 下所有 src 字段。""" |
| if isinstance(node, dict): |
| if "splits" in node and isinstance(node["splits"], dict): |
| for split_name, split_val in node["splits"].items(): |
| if isinstance(split_val, dict) and "src" in split_val: |
| out.append(split_val["src"]) |
| for v in node.values(): |
| _dig_src_paths(v, out) |
| elif isinstance(node, list): |
| for item in node: |
| _dig_src_paths(item, out) |
|
|
|
|
| def _dig_checkpoint_locations(node, out): |
| """递归找出 checkpoint_location 字段。""" |
| if isinstance(node, dict): |
| if "checkpoint_location" in node and isinstance( |
| node["checkpoint_location"], str |
| ): |
| out.append(node["checkpoint_location"]) |
| for v in node.values(): |
| _dig_checkpoint_locations(v, out) |
| elif isinstance(node, list): |
| for item in node: |
| _dig_checkpoint_locations(item, out) |
|
|
|
|
| def print_data_files(cfg): |
| """打印 preflight 要检查的路径: checkpoint + 数据 src。""" |
| paths = [] |
| data = cfg.get("data", {}) |
| _dig_src_paths(data, paths) |
| _dig_checkpoint_locations(cfg, paths) |
| |
| seen = set() |
| for p in paths: |
| if p and p not in seen and not str(p).startswith("??"): |
| seen.add(p) |
| print(p) |
|
|
|
|
| def print_slurm(cfg): |
| """打印 SLURM 配置变量。""" |
| launch = cfg.get("launch", {}) or {} |
| slurm = cfg.get("slurm", {}) or {} |
| num_nodes = launch.get("num_nodes", 1) |
| num_gpus = launch.get("num_gpus", 1) |
|
|
| name = cfg.get("name", "uma_train") |
| partition = slurm.get("partition", "k100ai") |
| time_limit = slurm.get("time", "12:00:00") |
| cpus = slurm.get("cpus_per_task", 128) |
|
|
| |
| |
| ntasks = 1 |
| cpus_per_task = cpus |
| _ = num_gpus |
|
|
| print(f"JOB_NAME={name}") |
| print(f"PARTITION={partition}") |
| print(f"NODES={num_nodes}") |
| print(f"NTASKS_PER_NODE={ntasks}") |
| print(f"CPUS_PER_TASK={cpus_per_task}") |
| print(f"GPUS_PER_NODE={num_gpus}") |
| print(f"TIME={time_limit}") |
|
|
|
|
| def print_hydra_config(cfg): |
| """把 cfg 剥离 META_KEYS 后的部分用 YAML 输出。""" |
| hydra_only = {k: v for k, v in cfg.items() if k not in META_KEYS} |
| |
| yaml.safe_dump( |
| hydra_only, |
| sys.stdout, |
| sort_keys=False, |
| default_flow_style=False, |
| allow_unicode=True, |
| ) |
|
|
|
|
| |
| |
| |
|
|
| def main(): |
| if len(sys.argv) < 3: |
| print(__doc__, file=sys.stderr) |
| sys.exit(1) |
|
|
| config_path = sys.argv[1] |
| action = sys.argv[2] |
| hydra_config_path = sys.argv[3] if len(sys.argv) >= 4 else None |
|
|
| cfg = load_config(config_path) |
|
|
| actions = { |
| "name": lambda: print_name(cfg), |
| "command": lambda: print_command(cfg, config_path, hydra_config_path), |
| "env": lambda: print_env(cfg), |
| "env-args": lambda: print_env_args(cfg), |
| "data-files": lambda: print_data_files(cfg), |
| "slurm": lambda: print_slurm(cfg), |
| "hydra-config": lambda: print_hydra_config(cfg), |
| } |
|
|
| fn = actions.get(action) |
| if fn is None: |
| print(f"未知 action: {action}", file=sys.stderr) |
| print(f"可用: {', '.join(actions.keys())}", file=sys.stderr) |
| sys.exit(1) |
| fn() |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|