argus-cluster/specs/mvp/v3.5/v3.5_design.md

367 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# MVP v3.5 详细设计方案(进一步精简版,基于 v3.0
> 背景v3.0 已具备 WebUI + API server + 用户/任务隔离 + SFTPGo 数据管理 + Stateless Ray clusterhead + worker node pool
>
> v3.5 本轮 **只做 2 件事**
> 1) Advanced Task支持用户提交自定义训练命令command
> 2) Custom Reward支持用户通过 VERL 原生 `custom_reward_function.*` 方式注入 reward仅方式 A用户自己写命令
>
> 明确不做(从上一版设计中移除):(3) 自定义 verl 版本/代码路径、(4) 断点续训、(5) IB/RoCEv2 网络支持、(6) Model Serving。
---
## 0. 继承 v3.0 的不变点(重要约束)
1) **Node management 不变**
- v3.5 不新增/不修改 node management 机制;仍按 v3.0 现状运行head 写 discovery、worker watchdog 自动 join、自愈
2) **Head 不跑训练**
- 所有训练/Serving driver 通过 Ray entrypoint placement 强制落在 worker例如 `entrypoint_resources={"worker_node": 1}`)。
3) **SFTPGo 的 “common” 目录约定变更**
- 不再使用 `$COMMON` 宏。
- 在 SFTPGo 中,把共享只读资源映射到用户 home 下的固定目录(用户在 SFTP/WebClient 看到的是 `$HOME/common/...`
- `$HOME/common/datasets` → 容器内真实路径 `/private/datasets`(只读)
- `$HOME/common/hf` → 容器内真实路径 `/private/hf`(只读)
> 这里的 `$HOME` 指:`/private/users/<user_id>`(容器内路径)。
---
## 1. v3.5 需求范围(精简后)
### 1.1 In scope
**A. Advanced TaskSpec自定义命令**
- 用户提交 `command`(多行 shell 或单行)
- 平台做 `$HOME` 宏替换
- 平台做 best-effort 安全检查(路径/关键参数),然后提交为 Ray job
**B. Custom Reward仅方式 A**
- 用户在 `command` 里显式写 hydra overrides
- `custom_reward_function.path=...`
- `custom_reward_function.name=...`
- `custom_reward_function.reward_kwargs.*=...`(可选)
- 平台不提供结构化 reward 字段(不做方式 B只做检查校验 path 合法)
### 1.2 Out of scope本轮不做
- 自定义 verl 版本/代码路径(仍使用平台内置/公共 verl 代码快照)
- 断点续训resume from checkpoint
- IB/RoCEv2 网络专门支持NCCL/RDMA env 先不引入平台)
- Model Serving暂缓后续单独设计迭代
---
## 2. Advanced TaskSpec 设计
### 2.1 为什么需要 Advanced Task
v3.0 的 Basic TaskSpecppo/grpo/sft通过平台模板生成固定 overrides适合“快速跑通”。
但科研/调参场景需要更高自由度:用户希望直接写 `python3 -m verl.trainer.main_ppo ...` 并自行控制每个 override。
### 2.2 Advanced TaskSpec建议 schema
建议新增一种 TaskSpec 类型,通过 `kind: advanced` 区分:
```yaml
kind: advanced
# 资源(平台调度与预检查用;仍需要)
nnodes: 2
n_gpus_per_node: 4
# 自定义命令(用户负责写对 VERL 的参数/路径)
# 平台会对 $HOME 做宏替换;其余保持原样
command: |
PYTHONUNBUFFERED=1 python3 -m verl.trainer.main_ppo \
data.train_files=$HOME/datasets/gsm8k/train.parquet \
data.val_files=$HOME/datasets/gsm8k/test.parquet \
actor_rollout_ref.model.path=Qwen/Qwen2.5-0.5B-Instruct \
trainer.nnodes=2 \
trainer.n_gpus_per_node=4 \
trainer.total_epochs=1 \
trainer.save_freq=10 \
+ray_kwargs.ray_init.address=auto
```
### 2.3 `$HOME` 宏替换规则
仅支持 `$HOME`v3.5 移除 `$COMMON`
- `$HOME``/private/users/<user_id>`
用户如果要用共享数据/缓存:
- 共享数据:`$HOME/common/datasets/...`
- 共享 HF 缓存:`$HOME/common/hf/...`(通常不需要写进 command但可用于 debug
#### 2.3.1 重要说明SFTPGo “virtual folder” 与训练进程看到的“真实路径”
在 SFTPGo 中,`$HOME/common/datasets` / `$HOME/common/hf`**SFTP 虚拟目录映射**virtual folder它们映射到容器内真实路径
- `$HOME/common/datasets``/private/datasets`
- `$HOME/common/hf``/private/hf`
训练进程Ray worker 上的 python 进程)看到的是 **容器内真实文件系统**,它并不会理解 SFTPGo 的 virtual folder。
因此,为了让用户能沿用 WebClient 里看到的路径语义(写 `$HOME/common/...`),服务层在提交 Advanced command 前需要做 **路径宏映射**
- `"$HOME/common/datasets"``"/private/datasets"`
- `"$HOME/common/hf"``"/private/hf"`
- 其余 `"$HOME"``"/private/users/<user_id>"`
这样用户写的 command 能在训练进程里正确读到文件。
### 2.4 服务层检查best-effort强约束 + 弱约束)
> 目标:在不“解析完整 shell”的前提下尽可能避免跨用户读文件与明显错误的任务。
**强约束(必须通过,否则 400**
1) `nnodes``n_gpus_per_node` 必须存在(用于队列/资源预检查/placement
2) `command` 必须包含一个明确的 python entry
- 建议最低要求:包含 `python3` 且包含 `-m verl.trainer.`(防止随意执行系统命令)
3) 路径隔离校验(字符串/正则级别):
- 展开 `$HOME`(含 `$HOME/common/*` 映射到 `/private/*`)后:
- 禁止出现 `/private/users/` 下 “非当前用户”的路径(例如 `/private/users/bob/...`
-`data.train_files=...``data.val_files=...`(若出现)做 allowlist
- 允许(用户目录):`/private/users/<me>/datasets/...`
- 允许(共享目录):`/private/datasets/...`
-`custom_reward_function.path=...`(若出现)做 allowlist
- 允许:`/private/users/<me>/code/...`(用户自行上传)
**弱约束warning不阻塞**
- 未检测到 `data.train_files=`/`data.val_files=`(可能是用户写成了别的 key 或使用了 config file
- 未检测到 `+ray_kwargs.ray_init.address=auto`v3.0/v3.5 推荐加,但用户可自行负责)
> 说明Advanced command 本质上属于“内部可信用户”能力v3.5 不做强沙箱;安全检查以 best-effort 为主。
---
## 3. Custom Reward仅方式 A用户自己写
### 3.1 VERL 原生机制(本仓库 `verl/` 已调研)
VERL PPO trainer 配置里支持:
- `custom_reward_function.path`
- `custom_reward_function.name`
- `custom_reward_function.reward_kwargs`
对应实现位置:
- 配置模板:`verl/verl/trainer/config/ppo_trainer.yaml`
- 加载逻辑:`verl/verl/trainer/ppo/reward.py:get_custom_reward_fn`
- 典型 reward manager`verl/verl/workers/reward_manager/naive.py` 会调用 `compute_score(...)`
### 3.2 用户写法(示例)
用户上传 `$HOME/code/reward.py`,在 command 里加:
```bash
custom_reward_function.path=$HOME/code/reward.py \
custom_reward_function.name=compute_score
```
函数签名建议(与 `naive` reward manager 参数对齐):
```python
def compute_score(*, data_source: str, solution_str: str, ground_truth: str, extra_info=None, **kwargs):
...
```
### 3.3 平台侧只做检查(不做字段扩展)
v3.5 限定 reward 注入方式为 “用户写 command”平台只做
- 展开 `$HOME`
- 若检测到 `custom_reward_function.path=`,校验 path 在 `$HOME/code/`
- 不尝试解析/合并 reward_kwargs用户自己写
---
## 4. 服务层与 SFTPGo 的映射修改(你提出的关键点)
v3.0 时代平台允许用户引用:
- `/private/common/datasets/...`
- `/private/common/hf/...`
但现在 common 以 **SFTPGo virtual folder** 的形式呈现给用户(用户看到 `$HOME/common/...`,真实路径是 `/private/...`),因此 v3.5 的服务层需要做两件事:
1) **用户侧语义(写 TaskSpec/command**
- 共享 datasets只读`$HOME/common/datasets/...`
- 共享 hf cache只读`$HOME/common/hf/...`
2) **运行时真实路径(提交到 Ray 前展开)**
- `$HOME/common/datasets/...``/private/datasets/...`
- `$HOME/common/hf/...``/private/hf/...`
同时保留用户自有目录:
- 用户 datasets`$HOME/datasets/...`
- 用户 models`$HOME/models/...`
- 用户 codereward`$HOME/code/...`
> 这部分主要影响:
> - Advanced command 检查allowlist
> - WebUI/Data 页面文案(告诉用户共享数据在哪里)
> 兼容性建议:为了不影响 v3.0 期间已经习惯使用 `/private/common/datasets/...` 的用户/历史任务,
> v3.5 实现阶段建议 **同时接受**
> - `/private/common/datasets/...`(旧路径语义,仍可读)
> - `/private/datasets/...`(真实路径语义,推荐)
> - Advanced command 里写的 `$HOME/common/datasets/...` 会先映射到 `/private/datasets/...`
---
## 5. 验收标准(精简版)
### 5.1 Advanced command
- 提交一个 Advanced PPO commandtrain/val 使用 `$HOME/common/datasets/...``$HOME/datasets/...`
- 确认:
- 任务从 QUEUED → SUBMITTED/RUNNING
- driver 在 worker 上head 不跑训练)
- 训练能正常跑至少若干 step
### 5.2 Custom reward方式 A
- 用户上传 `$HOME/code/reward.py`
- 在 command 中设置 `custom_reward_function.path=$HOME/code/reward.py`
- 确认训练日志出现 `using customized reward function ...`
---
## 6. 待确认问题(需要你拍板/补充)
1) Advanced command 的“强约束”是否需要更严格?
- 目前建议要求包含 `python3 -m verl.trainer.`,否则拒绝。
- 你是否允许用户跑非 verl 的命令(例如自定义评估脚本)?
2) `$HOME/common/datasets``$HOME/common/hf` 两个映射目录在平台侧是否需要“强制只读”语义?
- 例如TaskSpec 校验允许读取但禁止写入(目前设计是 best-effort 字符串级校验)。
---
## 7. 基于现有源码的改动点分析(实现清单)
本节按当前 v3.0 已上线的源码结构(`src/mvp/py/argus/...`)逐文件列出 v3.5 需要的具体改动点,并评估对现有能力的影响面。
### 7.1 TaskSpec/模型层(解析与兼容)
**现状**
- Basic TaskSpec 由 `argus.ray.models.JobSpec.from_dict()` 解析:`src/mvp/py/argus/ray/models.py`
- API `/api/v2/tasks` 直接 `JobSpec.from_dict(obj)`,并基于字段做路径校验:`src/mvp/py/argus/service/app.py`
- Scheduler 同样假定 jobspec_yaml 能解析为 `JobSpec``src/mvp/py/argus/service/scheduler.py`
**v3.5 需要新增**
1) 新增 `AdvancedTaskSpec` 数据结构(建议放在 `src/mvp/py/argus/ray/models.py`
- 必填:`kind: advanced``workload`(建议仍要求 ppo/grpo/sft用于 task_id 命名与 UI 分类)、`nnodes``n_gpus_per_node``command`
- 可选:`submission_id`(由服务层 override
2) 新增 “union 解析”:
- 新增 `parse_taskspec(obj: dict) -> Basic(JobSpec) | Advanced(AdvancedTaskSpec)`
- 兼容策略:如果没有 `kind` 字段,则 **默认按 v3.0 Basic JobSpec 解析**(保证老客户端无感)。
### 7.2 Builder 层(把 TaskSpec 转为可执行 argv
**现状**
- `src/mvp/py/argus/ray/builders.py:build_training_argv(spec: JobSpec, ...)` 只支持模板化 PPO/GRPO/SFT。
**v3.5 需要新增**
1) 新增 `build_advanced_argv(command: str) -> list[str]`
- 推荐实现:返回 `["bash", "-lc", "<expanded_command>"]`
- 原因:用户 command 允许 `ENV=... python3 ... \` 多行以及 shell 语法,`bash -lc` 兼容性最好。
2) Driver entrypoint 复用:
- 仍通过 `argus.ray.driver_entrypoint` 执行(统一 job_dir、日志与退出码
### 7.3 RayJobTool 层runtime_env 与提交)
**现状**
- `src/mvp/py/argus/ray/ray_job_tool.py:RayJobTool.submit(spec: JobSpec, ...)`
- runtime_env 的 `PYTHONPATH``spec.code_path` 决定
- entrypoint 固定为 driver_entrypoint + builder 生成 argv
**v3.5 需要新增**
1) 扩展 submit 支持 AdvancedTaskSpec
- 方案 A最小侵入新增 `submit_advanced(...)` 方法,参数为 `command` + `job_dir` + `submission_id` + `nnodes/n_gpus...`
- 方案 B统一接口新增内部抽象 `SubmitPlan`(包含 `runtime_env` + `entrypoint` + `artifacts`Basic/Advanced 都生成 plan再走同一 submit 逻辑。
2) runtime_env 的 code path
- 因 v3.5 本轮不做“自定义 verl code_path”建议仍固定使用公共快照例如 `/private/common/code/verl/verl_repo`)。
- 为减少散落常量,建议在 config 增加 `ray.verl_code_path`(或 `service.verl_code_path`RayJobTool 统一读取。
3) runtime_env 的用户代码目录(可选增强):
- VERL 的自定义 reward 函数是通过 `custom_reward_function.path` 以“文件路径”动态 import 的,理论上不依赖 `PYTHONPATH`
- 但用户的 `reward.py` 可能会 `import` 自己目录下的其他模块;为了提升易用性,可将
`/private/users/<user>/code` 追加到 job 的 `PYTHONPATH`
- 这需要 RayJobTool.submit/submit_advanced 能感知 `user_id`(由 Scheduler 传入),属于小改动但要注意兼容性。
### 7.4 API Server提交校验、宏替换、spec 展示)
**现状**
- `POST /api/v2/tasks`:只支持 Basic JobSpec 且强校验 `code_path/train_file/val_file/model_id` 前缀:`src/mvp/py/argus/service/app.py`
- `/api/v2/tasks/{task_id}/spec`:返回 resolved 的 Basic JobSpec补默认值/补 submission_id`src/mvp/py/argus/service/app.py`
**v3.5 需要新增/修改**
1) `POST /api/v2/tasks` 分流:
- `kind != advanced`:走原 Basic 流程(兼容 v3.0
- `kind == advanced`:走 Advanced 解析 + 校验
2) Advanced command 宏替换与映射(核心):
- 实现 `expand_command(user_id, command)`
- 先把 `$HOME/common/datasets``/private/datasets`
- 再把 `$HOME/common/hf``/private/hf`
- 再把其余 `$HOME``/private/users/<user>`
- 校验使用 “展开后的 command”
3) reward 注入检查(仅方式 A
- 若发现 `custom_reward_function.path=...`
- 校验展开后的 path 前缀必须是 `/private/users/<me>/code/`
4) `/api/v2/tasks/{task_id}/spec`
- 需要支持返回 AdvancedTaskSpec 的 resolved 版本:
- 展示时可选择“原始 command”`$HOME`)或“展开后的 command”建议都展示raw + expanded
### 7.5 Scheduler队列与提交
**现状**
- `src/mvp/py/argus/service/scheduler.py` 假定 jobspec_yaml 一定是 Basic JobSpec并调用 `tool.submit(spec2, ...)`
**v3.5 需要新增**
1) Scheduler 的 `_parse_jobspec` 替换为 `parse_taskspec`(支持 Basic/Advanced
2) `_submit_one` 根据 spec 类型调用:
- Basic保持现状 `tool.submit(JobSpec, ...)`
- Advanced调用 `tool.submit_advanced(...)`(或统一 SubmitPlan
### 7.6 WebUI最小改动
**现状**
- `src/mvp/py/argus/service/ui.py` 的 New Task 页面只提供 Basic YAML 模板。
**v3.5 需要新增**
- 增加 “Advanced Task” 模板按钮:
- `kind: advanced`
- `workload: ppo|grpo|sft`(用于 UI 分类与 task_id
- `nnodes/n_gpus_per_node`
- `command: | ...`(带中文注释)
- Data 页面文案更新:
- 明确共享目录在 `$HOME/common/datasets``$HOME/common/hf`(并解释会映射到 `/private/datasets``/private/hf`
---
## 8. 对现有功能的兼容性影响评估
### 8.1 API/TaskSpec 兼容
- 兼容策略:**没有 `kind` 字段的 YAML 一律按 v3.0 Basic JobSpec 解析**。
- 现有脚本/客户端(提交 ppo/grpo/sft 的 YAML无需修改。
- AdvancedTaskSpec 是新增能力,不影响既有任务状态机/DB。
### 8.2 路径策略变更的影响
风险点v3.0 的 Basic 任务/模板大量使用 `/private/common/datasets/...`
建议:
- v3.5 实现阶段先保持 “双栈兼容”:
- Basic 继续接受 `/private/common/datasets/...`(旧)
- 同时接受 `/private/datasets/...`(新/真实路径)
- Advanced command 允许用户写 `$HOME/common/datasets/...`,服务层展开为 `/private/datasets/...`(避免虚拟目录不可见问题)。
### 8.3 任务执行/调度兼容
- Scheduler 队列/并发控制(`max_running_tasks`)保持不变。
- 资源预检查仍只依赖 `nnodes/n_gpus_per_node`AdvancedTaskSpec 不改变资源模型。
### 8.4 安全边界变化
- Advanced command 引入后,平台从“结构化参数”变成“执行用户命令”,安全边界变宽。
- 缓解措施best-effort
- 强约束要求命令包含 `python3 -m verl.trainer.`
- 基础路径隔离校验(禁止跨用户路径)
- reward 文件路径限制在 `$HOME/code`
### 8.5 数据库兼容
- DB schema 不强制变更:仍复用 `tasks.jobspec_yaml` 存储原始 YAML。
- 若后续需要更强查询/过滤,再考虑增加 `tasks.kind` 字段(可选增量迁移)。