# MVP v1.1 工程化重构方案:Ray Python SDK 提交层(YAML Config + YAML JobSpec) 本文档把 v1.1 的“代码工程化”目标落到一个明确的设计:**保留现有 scripts**(Ray 集群构建、数据准备、模型准备、代码快照),将“任务提交机制”重构为 **Ray Python SDK**(`ray.job_submission.JobSubmissionClient`)驱动的 Python 工具层。 > 约束(已确认) > 1) 基础配置用 YAML,JobSpec 也用 YAML。 > 2) 工具必须在 **head 容器**执行(从 head 发起提交,满足“在 head 提交”的要求)。 > 3) 训练参数组织保持与现在一致:仍然使用 **Hydra overrides** 方式构造 entrypoint。 > 4) 不使用 `requests` 直连 HTTP API(只用 Ray SDK)。 --- ## 1. 当前 Ray SDK 能力验证(关键前提) 在 head 容器(`mvp11-ray-head`)中验证: - Ray 版本:`2.51.1` - `JobSubmissionClient.submit_job` 支持以下关键字段: - `submission_id` - `runtime_env` - `entrypoint_num_cpus` - `entrypoint_num_gpus` - `entrypoint_resources`(用于强制 driver 落 worker) 因此 v1.1 可以“纯 SDK”完成提交,不需要 `requests` fallback。 --- ## 2. 系统分层(不动 scripts,只重构提交层) ### 2.1 scripts(保留) `src/mvp/v1.1/scripts/` 继续负责: - 容器生命周期:`01_up.sh` / `02_down.sh` - Ray 启动:`20_start_head.sh` / `21_start_workers.sh` - 数据/模型准备:`30_prepare_data_and_model.sh` - 代码快照:`31_snapshot_verl_code.sh`(生成 `${SHARED_ROOT}/common/code/verl//`) scripts 可以新增一个“薄封装”脚本,负责 `docker exec` 进 head 容器并运行 Python 提交器,但 scripts 不再拼 `ray job submit ...` CLI 字符串。 ### 2.2 Python 工具层(新增) 在 `src/mvp/v1.1/py/` 新增提交工具层: - 读取 Ray 基础配置(YAML) - 读取训练 JobSpec(YAML) - 用 Ray Python SDK 提交/查询/停止/拉日志 - 将 job 级别产物落盘到:`${SHARED_ROOT}/jobs//...` --- ## 3. 输入定义:两份 YAML ### 3.1 Ray 基础配置(RayConfig YAML) 这份配置是“稳定可复用”的,描述 cluster 与 driver placement 等通用信息。 字段建议: - `address`: `http://127.0.0.1:8265`(从 head 容器内部视角) - `shared_root`: `/private` - `entrypoint_num_cpus`: `1` - `entrypoint_resources`: `{"worker_node": 1}`(强制 driver 使用 worker 才有的资源) - `runtime_env.env_vars`: HF cache / endpoint 等通用环境变量 - `user_code_path`: `${shared_root}/user/code`(可选,默认值也可) ### 3.2 训练 JobSpec(JobSpec YAML) 这份配置是“一次训练”语义,描述 workload + 训练参数 + code_path 多版本等。 字段建议: - `workload`: `ppo|grpo|sft` - `submission_id`: 可选(不填则生成;但最终必须显式传给 SDK) - `code_path`: `${shared_root}/common/code/verl/`(多版本关键字段) - `model_id` - 数据路径:`train_file` / `val_file`(按 workload) - 训练参数:`nnodes` / `n_gpus_per_node` / `total_training_steps` / `save_freq` / `test_freq` 注意(SFT 的 driver 设备选择): - Ray job 的 entrypoint(driver)默认不分配 GPU(我们通常不设置 `entrypoint_num_gpus`)。 - `sft_trainer_ray.py` 的 driver 会用 `trainer.device` 做张量统计;若设置为 `cuda` 且 driver 无 GPU,会报: - `RuntimeError: No CUDA GPUs are available` - 因此 v1.1 的 SFT JobSpec 默认应设置:`trainer.device=cpu`(训练 workers 仍会占用 GPU)。 --- ## 4. Python 提交器的职责(tool class) 建议实现 `RayJobTool`(或类似命名),能力: ### 4.1 submit(核心) 输入:`RayConfig + JobSpec` 输出:`submission_id` 实现要点: - `client = JobSubmissionClient(address)` - 生成/确定 `submission_id` - `runtime_env` 合并逻辑: - 合并 config 与 jobspec 的 `env_vars` - 强制注入多版本: - `PYTHONPATH = "::$PYTHONPATH"` - 构造 entrypoint(保持 hydra overrides 风格): - PPO/GRPO:`python3 -m verl.trainer.main_ppo ...` - SFT:`python3 -m verl.trainer.sft_trainer_ray ...` - 强制 driver 落 worker: - `entrypoint_resources=config.entrypoint_resources` - `entrypoint_num_cpus=config.entrypoint_num_cpus` - 落盘产物: - `${shared_root}/jobs//config/{ray_config.yaml,jobspec.yaml,submit_payload.json}` - `${shared_root}/jobs//logs/submit.out` - `${shared_root}/jobs//debug/{ray_status_pre,ray_job_list_post}.txt`(可用 SDK 或 `ray status` 采集) ### 4.2 status / stop / logs / list - `status(submission_id)` - `stop(submission_id)` - `logs(submission_id)`(可支持 tail) - `list()` --- ## 5. `run.py` 入口(必须在 head 容器执行) 建议入口: - `python3 /workspace/mvp/v1.1/py/run.py --config --jobspec --action submit` - `--action` 支持:`submit|status|stop|logs|list` host 侧执行方式(由 scripts 薄封装): - `docker exec mvp11-ray-head python3 /workspace/mvp/v1.1/py/run.py ...` --- ## 6. 验收口径(工程化部分) 1) **SDK 提交**:不使用 `ray job submit` CLI,改用 `JobSubmissionClient.submit_job`。 2) **driver 仍强制在 worker**:SDK 提交时 `entrypoint_resources={"worker_node":1}` 生效。 3) **多版本共存验证**: - 通过 `31_snapshot_verl_code.sh` 生成 `codeA/codeB` 两份 code_path - 通过两份 JobSpec 分别指向不同 `code_path` - 在 job logs 中看到不同的 marker(例如 `mvp_marker.MARKER`)