13 KiB
MVP v2.0 开发计划(服务化入口 + 队列调度 + Ray Jobs SDK)
目标:在 v1.1(脚本 + Ray Jobs SDK)已验收通过的基础上,交付一个可独立运行的最小“服务层”:
- 用户通过 HTTP API 提交训练任务(PPO/GRPO/SFT)。
- 服务层分配一个人类易读的任务 ID(
task_id),并把任务放入队列。 - 后台调度器在资源满足时再向 Ray 集群提交 Ray Job,并持续追踪 Ray Job 状态。
- 针对
verl的 fail-fast 资源预检查(资源不足直接ValueError失败)做“服务级重试/排队”,避免用户反复手工提交。
约束继承 v1.1:head 不跑训练;driver 必须落到 worker;共享存储只考虑 NFS(容器内
/private)。
1. 背景:为什么 v2.0 需要“服务层调度”
在 v1.1 中我们通过 Ray Job 提交 verl 训练任务。verl PPO/GRPO 在初始化 worker 时会创建资源池,并做一次 fail-fast 的资源检查:
- 触发点:
ResourcePoolManager.create_resource_pool()末尾调用_check_resource_available() _check_resource_available()使用ray._private.state.available_resources_per_node()统计“可用 GPU/NPU”,如果不足则直接抛异常:ValueError: Total available GPUs 0 is less than total desired GPUs 8
这是一种合理的选择(避免 Ray 层面无限 pending/卡死),但会带来一个平台侧问题:
- 当集群暂时没有足够资源时,用户提交会“立刻失败”,需要手动重试。
因此 v2.0 的服务层要提供:
- 队列 + gang 约束:资源不满足则任务在服务层 pending(不提交到 Ray)。
- 状态追踪:一旦提交到 Ray,持续获取 Ray Job 状态并回传给用户。
- 资源不足的“自动重试”:即使发生 race(提交时资源够、启动时被抢走),也能识别该类失败并延迟重试。
2. v2.0 交付范围(Scope)
2.1 必做(MVP v2.0)
- HTTP API(内部 token):
- 提交任务、查询任务、取消任务、拉取日志(最小可用)。
- 任务队列与调度器:
- FIFO(先到先服务),无配额/公平性(留给 v3+)。
- gang:按
nnodes+n_gpus_per_node的固定资源需求“全有才提交”。
- Ray Jobs SDK 集成(不使用
requests自己拼 HTTP):- 通过
ray.job_submission.JobSubmissionClientsubmit/status/stop/logs。
- 通过
- 可观测/可排障最小集:
- 每个 task/attempt 落盘配置、提交载荷、Ray 返回的
submission_id、关键日志。
- 每个 task/attempt 落盘配置、提交载荷、Ray 返回的
- 失败策略:
- 识别 “资源不足 fail-fast” 类失败 → 转为
PENDING_RESOURCES并延迟重试。 - 其他失败保持
FAILED(不自动重试,避免掩盖错误)。
- 识别 “资源不足 fail-fast” 类失败 → 转为
2.2 不做(v2.0 不实现)
- 多租户/配额/优先级/公平性调度(v3)。
- Pipeline(多 job 串联)(v3+)。
- 完整 UI(v3+,v2.0 可只提供 OpenAPI/Swagger)。
- K8s 编排(明确不做,仍是 Native Ray)。
2.3 工程原则(开闭原则 / 复用 v1.1)
v2.0 研发遵循开闭原则(Open/Closed Principle):
- 对扩展开放:新增“服务层(API + scheduler + SQLite)”能力以支持排队、重试、状态聚合。
- 对修改关闭:尽量不改动 v1.1 已经稳定可用的 Ray Jobs SDK 提交链路代码。
落地方式:
- 将
src/mvp/v1.1/py/mvp_v11/作为“成熟可用提交层”,原样拷贝到src/mvp/v2.0/py/mvp_v11/供 v2.0 复用。 - v2.0 的新增功能全部在新模块实现(例如
src/mvp/v2.0/py/mvp_v2/),通过组合/封装来调用mvp_v11,避免在旧代码中掺杂平台逻辑。
3. 总体架构(v2.0)
3.1 组件
-
mvp-api(HTTP Server)
- 接收 JobSpec(结构化字段保持与 v1.1 一致的语义)
- 生成
task_id并写入持久化 - 提供 query/cancel/logs
-
mvp-scheduler(后台调度器,可与 api 同进程也可拆进程)
- 轮询队列:对
PENDING_RESOURCES的任务做资源判断 - 资源满足 → 调用 Ray Jobs SDK 提交 → 记录
ray_submission_id - 对
SUBMITTED/RUNNING的任务持续同步 Ray Job 状态 - 如果 Ray Job 失败且命中资源不足模式 → 延迟重试
- 轮询队列:对
部署建议:v2.0 先在 head 容器内运行该服务(dev/prod 行为一致;生产环境只能 ssh 进入容器纳管)。
3.4 dev 环境目录约定(示例)
以当前远程开发机为例(argus@h1):
- 宿主机目录:
/home2/argus/infra/mvp/v2/ - 容器内挂载:
/workspace/mvp/v2/ - 共享 NFS:容器内统一为
/private/(与 v1.1 保持一致)
注意:服务脚本(
v2/scripts/*.sh)应在宿主机执行,通过docker exec控制 head 容器;训练 driver 仍通过 Ray entrypoint_resources 强制落到 worker。
3.2 与 Ray/容器的关系
- 服务进程运行在 head(或等价能访问 head 的 Job server 地址)。
- 提交时仍使用 v1.1 的强约束:
- head:
--num-cpus=0 --num-gpus=0 - worker:
--resources='{\"worker_node\": 100}' - job entrypoint:
entrypoint_resources={\"worker_node\": 1}强制 driver 落 worker
- head:
3.3 配置约定(复用 v1.1 dev.yaml 并扩展)
v2.0 的服务层(API + scheduler)建议复用 v1.1 已存在的 RayConfig 文件:
src/mvp/v1.1/py/configs/dev.yaml
原因:
- 其中已包含 v1.1 运行所需的 Ray 基础配置(Ray Job server address、entrypoint_resources、runtime_env 等),v2.0 也需要同样的信息来提交 Ray Jobs。
扩展方式:
- 在该 YAML 中新增一个顶层
v2:section,存放 v2 服务专属配置(API 监听、SQLite 路径、scheduler 间隔等)。 - v1.1 submitter 只读取
address/shared_root/entrypoint_* /runtime_env/user_code_path,会忽略v2:之类的额外字段;因此不会破坏 v1.1。
最小新增项建议(示例):
v2.api.host/v2.api.portv2.auth.token_env(内部 token 环境变量名)v2.sqlite.db_path(建议/private/common/db/mvp_v2.sqlite3)v2.scheduler.tick_s/v2.scheduler.retry_interval_s/v2.scheduler.max_running_tasks
4. 核心数据模型(Task / Attempt)
4.1 Task(用户视角的任务)
task_id:人类易读且唯一,例如:mvp2-ppo-20251223-143201-7f3a
workload:ppo|grpo|sftjobspec:提交参数(保持 v1.1 的 jobspec YAML 字段与语义;服务端解析 YAML 后入库)state:见第 5 节状态机created_at/updated_atlatest_attempt:指向当前 attemptattempts[]:历史尝试列表error_summary:面向用户的简短错误(最后一次失败原因)
4.2 Attempt(一次真实的 Ray Job 提交)
attempt_no:从 1 开始递增ray_submission_id:建议派生自 task_id:ray_submission_id = <task_id>--a01- 好处:Ray 侧输出目录天然可读、可追溯
status:Ray Job 状态(PENDING/RUNNING/SUCCEEDED/FAILED/STOPPED)start_time/end_timeexit_code(如可取)failure_kind(枚举):INSUFFICIENT_RESOURCES(匹配 “Total available GPUs … less than total desired …”)USER_ERROR(配置/数据路径错误等)RUNTIME_ERROR(代码异常)UNKNOWN
5. 状态机(服务侧)
建议最小状态集:
QUEUED:已入队,尚未进行资源判断PENDING_RESOURCES:资源不足,等待(服务侧 pending,不提交 Ray)SUBMITTING:正在向 Ray 提交 attemptSUBMITTED:Ray 已接受 submission(拿到ray_submission_id)RUNNING:Ray Job RUNNINGSUCCEEDED:任务成功(终态)FAILED:任务失败(终态,除非命中“资源不足重试策略”)CANCELED:用户取消(终态)
关键转换:
QUEUED -> PENDING_RESOURCES:资源不足QUEUED/PENDING_RESOURCES -> SUBMITTING:资源满足SUBMITTING -> SUBMITTED:提交成功SUBMITTED -> RUNNING:Ray 状态推进SUBMITTED/RUNNING -> SUCCEEDED|FAILED:Ray 终态FAILED (INSUFFICIENT_RESOURCES) -> PENDING_RESOURCES:进入延迟重试(attempt_no+1)
6. 调度策略(v2.0)
6.1 资源计算(对齐 verl 的“可用资源”口径)
由于 verl 使用 ray._private.state.available_resources_per_node() 做“可用资源”统计,
v2.0 的 scheduler 应该尽量使用相同口径,避免:
- 我们认为够了 → 实际 verl 认为不够(仍 fail-fast)
- 我们认为不够 → 实际够了(浪费)
策略(建议):
- scheduler 周期性获取 per-node 可用 GPU
- 计算 total_available_gpus = sum(node_gpu_available)
- 任务需求 total_required_gpus = nnodes * n_gpus_per_node
- 如果
total_available_gpus < total_required_gpus→PENDING_RESOURCES
注意:v2.0 先只做总量判断;节点级分配(保证每个 node 恰好 n_gpus_per_node)可作为 v2.1+(资源池/标签/节点纳管)增强点。
6.2 排队与并发
- 默认 FIFO。
- 并发度:允许同时跑多个任务,但必须保证资源足够。
- 简化实现:如果任务默认都吃满 8 卡,则 scheduler 实际上一次只能跑一个。
- 若未来支持小任务(11、14),可以自然并发。
6.3 重试策略(资源不足)
当出现下面模式时判定为 INSUFFICIENT_RESOURCES:
- Ray Job
status=FAILED JobDetails.message或job logs中匹配:Total available GPUs且less than total desired
处理:
- 将 task 置为
PENDING_RESOURCES next_run_at = now + 60s(固定间隔;v2.1 可改指数退避)- attempt_no++ 后重提(新 submission id)
7. SQLite 持久化(队列/状态/attempt)
v2.0 引入一个最小但可恢复的持久化层:使用 SQLite 保存任务队列与状态,确保:
- api/scheduler 进程重启后,队列不丢;
- task/attempt 历史可追溯;
- 能实现“服务侧 pending + 延迟重试”的确定性行为。
7.1 存放位置
建议路径(容器内):
DB_PATH=/private/common/db/mvp_v2.sqlite3
说明:
- v2.0 默认单实例服务(单 writer),SQLite 足够。
- 生产环境若 NFS 上的 SQLite 有锁/性能风险,v2.1+ 再演进到 Postgres/Redis;v2.0 先以“可回放/可恢复”为第一目标。
7.2 表设计(建议最小集合)
-
taskstask_id(PK)workloadstate(服务侧状态机)jobspec_yaml(原始 YAML 文本,原样落盘便于审计/复现)created_at,updated_atnext_run_at(用于PENDING_RESOURCES的延迟重试)error_summarylatest_attempt_no
-
attemptstask_id(FK)attempt_noray_submission_idray_statusfailure_kindmessage(截断后的关键信息)start_time,end_time
-
events(可选,但非常利于排障)id(PK)task_idtsevent_type(STATE_TRANSITION / SUBMIT / RAY_STATUS_SYNC / RETRY_SCHEDULED 等)payload_json
7.3 调度循环(与 SQLite 的交互)
scheduler 每个 tick 做三件事:
- 挑选可运行任务(FIFO + next_run_at):
state IN ('QUEUED','PENDING_RESOURCES') AND next_run_at <= now
- 资源判断(对齐 verl 的可用资源口径):
- 不满足:更新
state='PENDING_RESOURCES',并写入next_run_at=now+60s
- 不满足:更新
- 提交 Ray Job 并追踪:
- 提交成功:写入
attempts并更新tasks.latest_attempt_no、state='SUBMITTED' - 周期性同步 Ray 状态:
SUBMITTED/RUNNING -> SUCCEEDED/FAILED - 若失败命中资源不足模式:
FAILED -> PENDING_RESOURCES+ 计划下次重试
- 提交成功:写入
8. 接口与验收(DoD)
8.1 API 能力(最小集合)
详见 specs/mvp/v2.0/v2_api.md。
8.2 验收口径(DoD)
- API 提交 PPO/GRPO/SFT,返回
task_id,并在 NFS 上创建任务目录。 - 当集群忙(GPU 不足)时:
- task 状态为
PENDING_RESOURCES(不是 FAILED) - 一旦资源释放,任务自动变为
SUBMITTED/RUNNING
- task 状态为
- 当 race 导致触发 verl fail-fast:
- attempt 标记为
INSUFFICIENT_RESOURCES - task 回到
PENDING_RESOURCES,并在 60s 后自动重试
- attempt 标记为
- 通过 API 查询 task 能看到:
- 当前 state
- 最新 attempt 的
ray_submission_id - attempt 历史(至少包含开始/结束/失败原因)
- Cancel 能停止正在运行的 Ray Job(调用 Ray Jobs SDK stop)
9. v2.0 交付物建议(目录)
specs/mvp/v2.0/(本目录):
v2_plan.md:总体设计与开发计划(本文件)v2_api.md:API 详细定义(请求/响应/字段/错误码)
代码建议位置(后续实现时):
src/mvp/v2.0/py/:API server + schedulerscripts/:启动/停止/查看状态(仍沿用 v1.1 的 compose/cluster 逻辑)