# MVP v3.5(精简版)开发计划(TDD) > 目标:在 v3.0 已有能力基础上,仅新增两项能力: > 1) **Advanced TaskSpec(自定义 command)** > 2) **Custom Reward(方式 A:用户自己在 command 里写 `custom_reward_function.*`)** > > 设计依据:`specs/mvp/v3.5/v3.5_design.md`(本计划不再扩展 scope)。 --- ## 0. 范围与约束 ### 0.1 In scope - 新增 `kind: advanced` 的 TaskSpec:用户提供 `command`,平台做 `$HOME` 宏替换与 best-effort 校验,再提交 Ray Job。 - Custom Reward:平台仅做 **reward path 校验**(方式 A),不新增结构化字段。 - `$HOME/common/*` 路径语义支持(关键):用户在 SFTPGo/WebClient 看到的路径能被训练进程正确读取。 ### 0.2 Out of scope(本轮不做) - 自定义 verl 版本/代码路径(多版本共存) - 断点续训(resume from checkpoint) - IB/RoCEv2/NCCL 专项支持 - Model Serving - Node management 改造(v3.0 的 stateless head/worker/watchdog/supervisor 机制保持不变) ### 0.3 关键路径映射(必须保持一致) > 说明:SFTPGo 的 `$HOME/common/...` 是 **virtual folder**,训练进程看不到该虚拟路径。 提交 Advanced command 前必须展开/映射: - `$HOME/common/datasets` → `/private/datasets`(只读语义) - `$HOME/common/hf` → `/private/hf`(只读语义) - 其余 `$HOME` → `/private/users/` 并且为兼容历史用法(v3.0): - Basic TaskSpec 仍接受 `/private/common/datasets/...`、`/private/common/hf/...`(不强制迁移)。 --- ## 1. 测试策略(TDD) ### 1.1 单元测试优先级 1) **解析与兼容**:`kind: advanced` 能解析;无 `kind` 仍按 Basic 解析,旧用法不破坏。 2) **宏替换正确性**:`$HOME` / `$HOME/common/*` 映射严格按约定展开。 3) **best-effort 校验**:拒绝明显危险/跨用户路径;对 reward path 做 allowlist。 4) **提交链路**:Scheduler 能识别 Advanced spec 并调用对应的提交方法,确保 submission_id/目录规范不变。 5) **WebUI/API**:New Task 模板与 `/spec` 展示完整 resolved spec(包含展开后的 command)。 ### 1.2 本地运行方式 - 复用已有 `.venv`,执行:`.venv/bin/python -m pytest` - 若环境没有 pip,使用 uv 的方式参考 v3.0 约定(不在本计划重复)。 --- ## 2. 里程碑划分(每个里程碑可独立验证) > 约定:每个里程碑先写测试(失败),再实现代码使测试通过;里程碑结束跑一遍 `pytest`。 ### M1 — TaskSpec 模型与解析(兼容优先) **目标** - 引入 AdvancedTaskSpec 数据结构与 union parser,同时保证 v3.0 Basic 行为不变。 **新增/修改(建议位置)** - `src/mvp/py/argus/ray/models.py` - 新增 `AdvancedTaskSpec` - 新增 `parse_taskspec(obj: dict) -> JobSpec | AdvancedTaskSpec` - 兼容策略:缺省 `kind` → 走 `JobSpec.from_dict` **测试(先写)** - `src/mvp/py/tests/test_models.py` - `test_parse_taskspec_basic_no_kind_compat()` - `test_parse_taskspec_advanced_smoke()` - `test_parse_taskspec_advanced_requires_command_nnodes_gpus()` **验收** - `pytest -q` 通过;旧测试不修改或仅做最小必要更新。 --- ### M2 — Advanced command 展开与校验(核心能力) **目标** - 实现 command 展开(含 `$HOME/common/*` 映射)与 best-effort 强约束校验。 **实现点(建议新增模块)** - `src/mvp/py/argus/service/command_expand.py`(或放在 `argus/service/validation.py`) - `expand_advanced_command(user_id: str, command: str) -> str` - `validate_advanced_command(user_id: str, expanded_command: str) -> None`(失败抛 `ValueError`) **强约束(与设计文档一致)** - 必须包含 `python3` 且包含 `-m verl.trainer.`(否则 400) - 禁止出现 `/private/users//...`(跨用户路径) - 若检测到 `data.train_files=`/`data.val_files=`: - 只允许 `/private/users//datasets/...` 或 `/private/datasets/...` - (兼容)允许 `/private/common/datasets/...`(旧路径) - 若检测到 `custom_reward_function.path=`: - 只允许 `/private/users//code/...`(展开后校验) **测试(先写)** - 新增:`src/mvp/py/tests/test_advanced_command.py` - `test_expand_maps_home_common_datasets_to_private_datasets()` - `test_expand_maps_home_common_hf_to_private_hf()` - `test_expand_maps_home_to_private_users()` - `test_validate_rejects_cross_user_paths()` - `test_validate_requires_verl_trainer_entry()` - `test_validate_allows_reward_path_under_user_code()` - `test_validate_rejects_reward_path_outside_user_code()` **验收** - 单测覆盖映射/校验的正反例;错误信息可读(用于 API 400 detail)。 --- ### M3 — Ray 提交链路支持 Advanced(Builder/Tool/Scheduler) **目标** - Advanced spec 能进入 scheduler 队列并提交为 Ray job(driver 仍落 worker)。 **代码改动点(建议)** - `src/mvp/py/argus/ray/builders.py` - 新增 `build_advanced_argv(command: str)`:返回 `["bash","-lc", expanded_command]` - `src/mvp/py/argus/ray/ray_job_tool.py` - 新增 `submit_advanced(...)`(或统一成内部 submit plan) - runtime_env:继续注入公共 verl code path(本轮不支持用户自定义 verl 代码) - 可选:把 `/private/users//code` 加入 `PYTHONPATH`,提升 reward 代码 `import` 体验 - `src/mvp/py/argus/service/scheduler.py` - 使用 `parse_taskspec` 分流 Basic/Advanced - Advanced 调用 `tool.submit_advanced(...)` **测试(先写)** - `src/mvp/py/tests/test_builders.py` - `test_build_advanced_argv_uses_bash_lc()` - `src/mvp/py/tests/test_scheduler.py` - 新增一个 `kind: advanced` 的任务,断言 scheduler 调用了 `submit_advanced` - 断言 job_dir/submission_id 规则不变(仍按 `/private/users//jobs/`) - `src/mvp/py/tests/test_ray_job_tool.py` - 断言 advanced 提交时 entrypoint 是 driver_entrypoint + `bash -lc ...` **验收** - 单测跑通;Scheduler tick 能完成 Advanced 任务从 QUEUED → SUBMITTED(mock Ray)。 --- ### M4 — API & WebUI(最小功能闭环) **目标** - WebUI/HTTP API 能提交 Advanced Task,并在详情页看到 resolved spec(含完整 command)。 **API 改动点** - `src/mvp/py/argus/service/app.py` - `POST /api/v2/tasks`:支持 `kind: advanced` - 保存 raw YAML(保持与 Basic 一致) - 对 Advanced:展开 command + 校验(失败返回 400) - `GET /api/v2/tasks/{task_id}/spec`: - 返回 resolved spec(建议同时返回 raw + expanded,或 YAML 中直接给 expanded) **WebUI 改动点** - `src/mvp/py/argus/service/ui.py` - New Task 页面新增 Advanced 模板(含中文注释) - 文案强调共享目录:`$HOME/common/datasets`、`$HOME/common/hf` **测试(先写)** - `src/mvp/py/tests/test_app.py` - `test_create_task_advanced_ok()`(最小 valid command) - `test_create_task_advanced_rejects_invalid_command()` - `test_task_spec_endpoint_includes_expanded_command()` - `src/mvp/py/tests/test_ui.py` - 断言页面包含 Advanced 示例块 **验收** - `pytest` 通过;浏览器可提交 Advanced YAML 并看到 expanded command。 --- ### M5 — 端到端验证(远端 argus@h1) **目标** - 在真实 Ray cluster + VERL 环境下验证 Advanced 与 Custom Reward(方式 A)。 **步骤(手工验收脚本化可选)** 1) 启动 v3.0/v3.5 统一的 compose + API(沿用现有 `run_all` 脚本体系) 2) 用户(如 `alice`)通过 SFTP 上传 reward 代码到: - `$HOME/code/reward.py`(真实路径 `/private/users/alice/code/reward.py`) 3) 通过 WebUI 或 curl 提交 Advanced task: - `command` 中包含: - `custom_reward_function.path=$HOME/code/reward.py` - `custom_reward_function.name=compute_score` - `data.train_files=$HOME/common/datasets/gsm8k/train.parquet` - `data.val_files=$HOME/common/datasets/gsm8k/test.parquet` 4) 检查: - 任务状态从 QUEUED → RUNNING → SUCCEEDED/FAILED(有日志) - driver 不在 head 上跑(dashboard 验证) - 日志出现 “custom reward” 生效的提示(按 VERL 实际日志关键字确认) 5) 回归:提交 Basic ppo/grpo/sft 任务仍可运行(确保兼容性) **验收** - Advanced task 能跑至少若干 step,且 reward 注入生效。 - Basic 任务兼容不回退。 --- ## 3. 风险点与边界(明确写进 PR/变更说明) - Advanced command 只做 best-effort 校验,不做完整 shell AST 解析;复杂命令可能存在漏检/误判(后续可扩展)。 - `$HOME/common/*` 是“用户侧语义”,服务层必须映射到真实路径,否则训练必然 FileNotFound。 - 校验策略(强约束)如果后续要允许非 VERL 命令,需要调整规则并补测试(本轮默认拒绝)。