307 lines
13 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 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.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+)。
- gang`nnodes` + `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-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
---
## 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`
- `workload``ppo|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 侧输出目录天然可读、可追溯
- `status`Ray 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
- `SUBMITTED`Ray 已接受 submission拿到 `ray_submission_id`
- `RUNNING`Ray Job RUNNING
- `SUCCEEDED`:任务成功(终态)
- `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
- 我们认为不够 → 实际够了(浪费)
策略(建议):
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_gpus``PENDING_RESOURCES`
注意v2.0 先只做总量判断;节点级分配(保证每个 node 恰好 n_gpus_per_node可作为 v2.1+(资源池/标签/节点纳管)增强点。
### 6.2 排队与并发
- 默认 FIFO。
- 并发度:允许同时跑多个任务,但必须保证资源足够。
- 简化实现:如果任务默认都吃满 8 卡,则 scheduler 实际上一次只能跑一个。
- 若未来支持小任务1*1、1*4可以自然并发。
### 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 默认单实例服务(单 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_type`STATE_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_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
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.md`API 详细定义(请求/响应/字段/错误码)
代码建议位置(后续实现时):
- `src/mvp/v2.0/`
- `py/`API server + scheduler
- `scripts/`:启动/停止/查看状态(仍沿用 v1.1 的 compose/cluster 逻辑)