13 KiB
Raw Blame History

MVP v2.0 开发计划(服务化入口 + 队列调度 + Ray Jobs SDK

目标:在 v1.1(脚本 + Ray Jobs SDK已验收通过的基础上交付一个可独立运行的最小“服务层”

  • 用户通过 HTTP API 提交训练任务PPO/GRPO/SFT
  • 服务层分配一个人类易读的任务 IDtask_id),并把任务放入队列。
  • 后台调度器在资源满足时再向 Ray 集群提交 Ray Job并持续追踪 Ray Job 状态。
  • 针对 verlfail-fast 资源预检查(资源不足直接 ValueError 失败)做“服务级重试/排队”,避免用户反复手工提交。

约束继承 v1.1head 不跑训练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

  1. HTTP API(内部 token
    • 提交任务、查询任务、取消任务、拉取日志(最小可用)。
  2. 任务队列与调度器
    • FIFO先到先服务无配额/公平性(留给 v3+)。
    • gangnnodes + n_gpus_per_node 的固定资源需求“全有才提交”。
  3. Ray Jobs SDK 集成(不使用 requests 自己拼 HTTP
    • 通过 ray.job_submission.JobSubmissionClient submit/status/stop/logs。
  4. 可观测/可排障最小集
    • 每个 task/attempt 落盘配置、提交载荷、Ray 返回的 submission_id、关键日志。
  5. 失败策略
    • 识别 “资源不足 fail-fast” 类失败 → 转为 PENDING_RESOURCES 并延迟重试。
    • 其他失败保持 FAILED(不自动重试,避免掩盖错误)。

2.2 不做v2.0 不实现)

  • 多租户/配额/优先级/公平性调度v3
  • Pipeline多 job 串联v3+)。
  • 完整 UIv3+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-apiHTTP 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 entrypointentrypoint_resources={\"worker_node\": 1} 强制 driver 落 worker

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.port
  • v2.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
  • workloadppo|grpo|sft
  • jobspec:提交参数(保持 v1.1 的 jobspec YAML 字段与语义;服务端解析 YAML 后入库)
  • state:见第 5 节状态机
  • created_at / updated_at
  • latest_attempt:指向当前 attempt
  • attempts[]:历史尝试列表
  • error_summary:面向用户的简短错误(最后一次失败原因)

4.2 Attempt一次真实的 Ray Job 提交)

  • attempt_no:从 1 开始递增
  • ray_submission_id:建议派生自 task_id
    • ray_submission_id = <task_id>--a01
    • 好处Ray 侧输出目录天然可读、可追溯
  • statusRay Job 状态PENDING/RUNNING/SUCCEEDED/FAILED/STOPPED
  • start_time / end_time
  • exit_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 提交 attempt
  • SUBMITTEDRay 已接受 submission拿到 ray_submission_id
  • RUNNINGRay Job RUNNING
  • SUCCEEDED:任务成功(终态)
  • FAILED:任务失败(终态,除非命中“资源不足重试策略”)
  • CANCELED:用户取消(终态)

关键转换:

  • QUEUED -> PENDING_RESOURCES:资源不足
  • QUEUED/PENDING_RESOURCES -> SUBMITTING:资源满足
  • SUBMITTING -> SUBMITTED:提交成功
  • SUBMITTED -> RUNNINGRay 状态推进
  • SUBMITTED/RUNNING -> SUCCEEDED|FAILEDRay 终态
  • 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
  • 我们认为不够 → 实际够了(浪费)

策略(建议):

  1. scheduler 周期性获取 per-node 可用 GPU
  2. 计算 total_available_gpus = sum(node_gpu_available)
  3. 任务需求 total_required_gpus = nnodes * n_gpus_per_node
  4. 如果 total_available_gpus < total_required_gpusPENDING_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.messagejob logs 中匹配:
    • Total available GPUsless 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 默认单实例服务(单 writerSQLite 足够。
  • 生产环境若 NFS 上的 SQLite 有锁/性能风险v2.1+ 再演进到 Postgres/Redisv2.0 先以“可回放/可恢复”为第一目标。

7.2 表设计(建议最小集合)

  • tasks

    • task_id (PK)
    • workload
    • state(服务侧状态机)
    • jobspec_yaml(原始 YAML 文本,原样落盘便于审计/复现)
    • created_at, updated_at
    • next_run_at(用于 PENDING_RESOURCES 的延迟重试)
    • error_summary
    • latest_attempt_no
  • attempts

    • task_id (FK)
    • attempt_no
    • ray_submission_id
    • ray_status
    • failure_kind
    • message(截断后的关键信息)
    • start_time, end_time
  • events(可选,但非常利于排障)

    • id (PK)
    • task_id
    • ts
    • event_typeSTATE_TRANSITION / SUBMIT / RAY_STATUS_SYNC / RETRY_SCHEDULED 等)
    • payload_json

7.3 调度循环(与 SQLite 的交互)

scheduler 每个 tick 做三件事:

  1. 挑选可运行任务FIFO + next_run_at
    • state IN ('QUEUED','PENDING_RESOURCES') AND next_run_at <= now
  2. 资源判断(对齐 verl 的可用资源口径):
    • 不满足:更新 state='PENDING_RESOURCES',并写入 next_run_at=now+60s
  3. 提交 Ray Job 并追踪
    • 提交成功:写入 attempts 并更新 tasks.latest_attempt_nostate='SUBMITTED'
    • 周期性同步 Ray 状态:SUBMITTED/RUNNING -> SUCCEEDED/FAILED
    • 若失败命中资源不足模式:FAILED -> PENDING_RESOURCES + 计划下次重试

8. 接口与验收DoD

8.1 API 能力(最小集合)

详见 specs/mvp/v2.0/v2_api.md

8.2 验收口径DoD

  1. API 提交 PPO/GRPO/SFT返回 task_id,并在 NFS 上创建任务目录。
  2. 当集群忙GPU 不足)时:
    • task 状态为 PENDING_RESOURCES(不是 FAILED
    • 一旦资源释放,任务自动变为 SUBMITTED/RUNNING
  3. 当 race 导致触发 verl fail-fast
    • attempt 标记为 INSUFFICIENT_RESOURCES
    • task 回到 PENDING_RESOURCES,并在 60s 后自动重试
  4. 通过 API 查询 task 能看到:
    • 当前 state
    • 最新 attempt 的 ray_submission_id
    • attempt 历史(至少包含开始/结束/失败原因)
  5. Cancel 能停止正在运行的 Ray Job调用 Ray Jobs SDK stop

9. v2.0 交付物建议(目录)

specs/mvp/v2.0/(本目录):

  • v2_plan.md:总体设计与开发计划(本文件)
  • v2_api.mdAPI 详细定义(请求/响应/字段/错误码)

代码建议位置(后续实现时):

  • src/mvp/v2.0/
    • py/API server + scheduler
    • scripts/:启动/停止/查看状态(仍沿用 v1.1 的 compose/cluster 逻辑)