16 KiB
MVP v3.5 详细设计方案(进一步精简版,基于 v3.0)
背景:v3.0 已具备 WebUI + API server + 用户/任务隔离 + SFTPGo 数据管理 + Stateless Ray cluster(head + worker node pool)。
v3.5 本轮 只做 2 件事:
- Advanced Task:支持用户提交自定义训练命令(command)
- Custom Reward:支持用户通过 VERL 原生
custom_reward_function.*方式注入 reward(仅方式 A:用户自己写命令)明确不做(从上一版设计中移除):(3) 自定义 verl 版本/代码路径、(4) 断点续训、(5) IB/RoCEv2 网络支持、(6) Model Serving。
0. 继承 v3.0 的不变点(重要约束)
- Node management 不变
- v3.5 不新增/不修改 node management 机制;仍按 v3.0 现状运行(head 写 discovery、worker watchdog 自动 join、自愈)。
- Head 不跑训练
- 所有训练/Serving driver 通过 Ray entrypoint placement 强制落在 worker(例如
entrypoint_resources={"worker_node": 1})。
- 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 TaskSpec(ppo/grpo/sft)通过平台模板生成固定 overrides,适合“快速跑通”。
但科研/调参场景需要更高自由度:用户希望直接写 python3 -m verl.trainer.main_ppo ... 并自行控制每个 override。
2.2 Advanced TaskSpec(建议 schema)
建议新增一种 TaskSpec 类型,通过 kind: advanced 区分:
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)
nnodes、n_gpus_per_node必须存在(用于队列/资源预检查/placement)command必须包含一个明确的 python entry:- 建议最低要求:包含
python3且包含-m verl.trainer.(防止随意执行系统命令)
- 建议最低要求:包含
- 路径隔离校验(字符串/正则级别):
- 展开
$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.pathcustom_reward_function.namecustom_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 里加:
custom_reward_function.path=$HOME/code/reward.py \
custom_reward_function.name=compute_score
函数签名建议(与 naive reward manager 参数对齐):
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 的服务层需要做两件事:
- 用户侧语义(写 TaskSpec/command)
- 共享 datasets(只读):
$HOME/common/datasets/... - 共享 hf cache(只读):
$HOME/common/hf/...
- 运行时真实路径(提交到 Ray 前展开)
$HOME/common/datasets/...→/private/datasets/...$HOME/common/hf/...→/private/hf/...
同时保留用户自有目录:
- 用户 datasets:
$HOME/datasets/... - 用户 models:
$HOME/models/... - 用户 code(reward):
$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 command(train/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. 待确认问题(需要你拍板/补充)
-
Advanced command 的“强约束”是否需要更严格?
- 目前建议要求包含
python3 -m verl.trainer.,否则拒绝。 - 你是否允许用户跑非 verl 的命令(例如自定义评估脚本)?
- 目前建议要求包含
-
$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 需要新增
- 新增
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)
- 必填:
- 新增 “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 需要新增
- 新增
build_advanced_argv(command: str) -> list[str]- 推荐实现:返回
["bash", "-lc", "<expanded_command>"] - 原因:用户 command 允许
ENV=... python3 ... \多行以及 shell 语法,bash -lc兼容性最好。
- 推荐实现:返回
- 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
- runtime_env 的
v3.5 需要新增
- 扩展 submit 支持 AdvancedTaskSpec:
- 方案 A(最小侵入):新增
submit_advanced(...)方法,参数为command+job_dir+submission_id+nnodes/n_gpus... - 方案 B(统一接口):新增内部抽象
SubmitPlan(包含runtime_env+entrypoint+artifacts),Basic/Advanced 都生成 plan,再走同一 submit 逻辑。
- 方案 A(最小侵入):新增
- 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 统一读取。
- 因 v3.5 本轮不做“自定义 verl code_path”,建议仍固定使用公共快照(例如
- 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 传入),属于小改动但要注意兼容性。
- VERL 的自定义 reward 函数是通过
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 需要新增/修改
POST /api/v2/tasks分流:kind != advanced:走原 Basic 流程(兼容 v3.0)kind == advanced:走 Advanced 解析 + 校验
- Advanced command 宏替换与映射(核心):
- 实现
expand_command(user_id, command):- 先把
$HOME/common/datasets→/private/datasets - 再把
$HOME/common/hf→/private/hf - 再把其余
$HOME→/private/users/<user>
- 先把
- 校验使用 “展开后的 command”
- 实现
- reward 注入检查(仅方式 A):
- 若发现
custom_reward_function.path=...:- 校验展开后的 path 前缀必须是
/private/users/<me>/code/
- 校验展开后的 path 前缀必须是
- 若发现
/api/v2/tasks/{task_id}/spec:- 需要支持返回 AdvancedTaskSpec 的 resolved 版本:
- 展示时可选择“原始 command”(含
$HOME)或“展开后的 command”(建议都展示:raw + expanded)
- 展示时可选择“原始 command”(含
- 需要支持返回 AdvancedTaskSpec 的 resolved 版本:
7.5 Scheduler(队列与提交)
现状
src/mvp/py/argus/service/scheduler.py假定 jobspec_yaml 一定是 Basic JobSpec,并调用tool.submit(spec2, ...)。
v3.5 需要新增
- Scheduler 的
_parse_jobspec替换为parse_taskspec(支持 Basic/Advanced)。 _submit_one根据 spec 类型调用:- Basic:保持现状
tool.submit(JobSpec, ...) - Advanced:调用
tool.submit_advanced(...)(或统一 SubmitPlan)
- Basic:保持现状
7.6 WebUI(最小改动)
现状
src/mvp/py/argus/service/ui.py的 New Task 页面只提供 Basic YAML 模板。
v3.5 需要新增
- 增加 “Advanced Task” 模板按钮:
kind: advancedworkload: ppo|grpo|sft(用于 UI 分类与 task_id)nnodes/n_gpus_per_nodecommand: | ...(带中文注释)
- 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/...(新/真实路径)
- Basic 继续接受
- 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字段(可选增量迁移)。