mvp 2.0 验收通过,实现基本API提交,查询,取消任务,并且有简单的FIFO排队
This commit is contained in:
194
specs/mvp/v2.0/v2_api.md
Normal file
194
specs/mvp/v2.0/v2_api.md
Normal file
@@ -0,0 +1,194 @@
|
||||
# MVP v2.0 API 设计(最小可用)
|
||||
|
||||
v2.0 的 API 目标是:把 v1.1 的“脚本提交”变成“服务化提交”,并在服务侧实现队列/重试/状态聚合。
|
||||
|
||||
约束:
|
||||
- 内部 token 鉴权(简单即可)。
|
||||
- Ray Job 提交必须使用 **Ray Python SDK**(`JobSubmissionClient`),不使用 `requests` 手写 HTTP。
|
||||
- 输出与状态必须落盘到 NFS(容器内 `/private`)。
|
||||
|
||||
---
|
||||
|
||||
## 1. 鉴权
|
||||
|
||||
- Header:`Authorization: Bearer <INTERNAL_TOKEN>`
|
||||
- v2.0 不做用户体系与权限隔离;token 只是“防误用”。
|
||||
- 配置建议:复用 `src/mvp/v1.1/py/configs/dev.yaml` 并在 `v2.auth.token_env` 指定 token 环境变量名。
|
||||
|
||||
## 1.1 运行位置(dev 示例)
|
||||
|
||||
- 服务进程运行在 **Ray head 容器**(便于访问 Ray Job server)。
|
||||
- 宿主机侧用脚本控制(`docker exec`):
|
||||
- `src/mvp/v2.0/scripts/20_start_api.sh`
|
||||
- `src/mvp/v2.0/scripts/21_stop_api.sh`
|
||||
- `src/mvp/v2.0/scripts/22_status_api.sh`
|
||||
- 远程机目录约定(示例):`argus@h1:/home2/argus/infra/mvp/v2/`,容器内挂载到 `/workspace/mvp/v2/`。
|
||||
|
||||
---
|
||||
|
||||
## 2. 资源与 ID 约定
|
||||
|
||||
### 2.1 task_id(服务层主 ID)
|
||||
|
||||
- 格式建议:`mvp2-<workload>-<YYYYMMDD>-<HHMMSS>-<suffix>`
|
||||
- 示例:`mvp2-ppo-20251223-143201-7f3a`
|
||||
|
||||
### 2.2 ray_submission_id(attempt 级 ID)
|
||||
|
||||
- 由 service 派生:`<task_id>--a<NN>`
|
||||
- 示例:`mvp2-ppo-20251223-143201-7f3a--a01`
|
||||
|
||||
好处:
|
||||
- Ray 的 submission id 自带 task_id,可直接从 Ray dashboard 反查到服务侧任务。
|
||||
- `/private/jobs/<ray_submission_id>/...` 目录天然隔离且可读。
|
||||
|
||||
---
|
||||
|
||||
## 3. JobSpec(请求体)
|
||||
|
||||
v2.0 **要求 JobSpec 使用 v1.1 同款 YAML**(字段与语义保持一致),服务端接收 YAML 文本并解析后入库(同时原样保存 `jobspec_yaml` 便于审计/复现)。
|
||||
|
||||
最小字段(示例 YAML):
|
||||
|
||||
```yaml
|
||||
workload: "ppo"
|
||||
submission_id: "" # v2.0 服务端会忽略/覆盖(由 task_id 派生 ray_submission_id)
|
||||
code_path: "/private/common/code/verl/verl_repo"
|
||||
model_id: "Qwen/Qwen2.5-0.5B-Instruct"
|
||||
train_file: "/private/datasets/gsm8k/train.parquet"
|
||||
val_file: "/private/datasets/gsm8k/test.parquet"
|
||||
nnodes: 2
|
||||
n_gpus_per_node: 4
|
||||
total_epochs: 1
|
||||
total_training_steps: 10
|
||||
save_freq: 10
|
||||
test_freq: -1
|
||||
trainer_device: null # 仅 sft 使用(通常 "cpu")
|
||||
```
|
||||
|
||||
说明:
|
||||
- `trainer_device` 仅对 `sft` 生效(通常为 `cpu`,避免 driver 无 GPU)。
|
||||
- `val_file` 可为 `null`(例如 SFT)。
|
||||
|
||||
---
|
||||
|
||||
## 4. API 端点
|
||||
|
||||
### 4.1 提交任务
|
||||
|
||||
`POST /api/v2/tasks`
|
||||
|
||||
Request body:
|
||||
- **raw JobSpec YAML**(与 v1.1 jobspec YAML 结构一致)
|
||||
|
||||
Headers:
|
||||
- `Content-Type: application/yaml`(或 `text/yaml`)
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"task_id": "mvp2-ppo-20251223-143201-7f3a",
|
||||
"state": "QUEUED"
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 查询任务(聚合状态)
|
||||
|
||||
`GET /api/v2/tasks/{task_id}`
|
||||
|
||||
Response(示例):
|
||||
```json
|
||||
{
|
||||
"task_id": "mvp2-ppo-20251223-143201-7f3a",
|
||||
"workload": "ppo",
|
||||
"state": "RUNNING",
|
||||
"desired_resources": {"nnodes": 2, "n_gpus_per_node": 4, "total_gpus": 8},
|
||||
"latest_attempt": {
|
||||
"attempt_no": 1,
|
||||
"ray_submission_id": "mvp2-ppo-20251223-143201-7f3a--a01",
|
||||
"ray_status": "RUNNING",
|
||||
"start_time": "2025-12-23T14:32:10+08:00"
|
||||
},
|
||||
"error_summary": null
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3 列出 attempts
|
||||
|
||||
`GET /api/v2/tasks/{task_id}/attempts`
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"task_id": "mvp2-ppo-20251223-143201-7f3a",
|
||||
"attempts": [
|
||||
{
|
||||
"attempt_no": 1,
|
||||
"ray_submission_id": "mvp2-ppo-20251223-143201-7f3a--a01",
|
||||
"ray_status": "FAILED",
|
||||
"failure_kind": "INSUFFICIENT_RESOURCES",
|
||||
"message": "Total available GPUs 0 is less than total desired GPUs 8",
|
||||
"start_time": "...",
|
||||
"end_time": "..."
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 4.4 取消任务
|
||||
|
||||
`POST /api/v2/tasks/{task_id}:cancel`
|
||||
|
||||
行为:
|
||||
- 若 task 处于 `SUBMITTED/RUNNING`:调用 Ray Jobs SDK `stop_job(ray_submission_id)` 并标记 `CANCELED`
|
||||
- 若处于 `QUEUED/PENDING_RESOURCES`:直接标记 `CANCELED`(不提交)
|
||||
|
||||
Response:
|
||||
```json
|
||||
{"task_id":"...","state":"CANCELED"}
|
||||
```
|
||||
|
||||
### 4.5 获取日志
|
||||
|
||||
`GET /api/v2/tasks/{task_id}/logs?attempt=latest&tail=2000`
|
||||
|
||||
返回:
|
||||
- `text/plain`(直接透传 Ray Job logs tail)
|
||||
|
||||
说明:
|
||||
- v2.0 先用 Ray SDK `get_job_logs()`。
|
||||
- 若需要更稳定的归档,可在 scheduler 定期抓取并落盘(v2.1+)。
|
||||
|
||||
### 4.6 列出队列(运维/调试)
|
||||
|
||||
`GET /api/v2/queue`
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"pending": [{"task_id":"...","state":"PENDING_RESOURCES","next_run_at":"..."}],
|
||||
"running": [{"task_id":"...","ray_submission_id":"..."}]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 错误码(最小)
|
||||
|
||||
- `400`:jobspec 缺字段/非法
|
||||
- `401`:token 不正确
|
||||
- `404`:task 不存在
|
||||
- `409`:状态冲突(例如已终态又 cancel)
|
||||
- `500`:服务内部错误
|
||||
|
||||
---
|
||||
|
||||
## 6. SQLite 持久化(API 可见性)
|
||||
|
||||
v2.0 服务端使用 SQLite 持久化保存:
|
||||
- tasks(`task_id`、`state`、`jobspec_yaml`、`next_run_at`、`latest_attempt_no` 等)
|
||||
- attempts(`ray_submission_id`、`ray_status`、失败原因等)
|
||||
|
||||
因此:
|
||||
- `GET /api/v2/tasks/{task_id}` 的数据来自 SQLite(再叠加 Ray 状态同步的结果)。
|
||||
- 进程重启后,队列可恢复,`PENDING_RESOURCES` 的任务会在 `next_run_at` 到期后继续尝试提交。
|
||||
306
specs/mvp/v2.0/v2_plan.md
Normal file
306
specs/mvp/v2.0/v2_plan.md
Normal file
@@ -0,0 +1,306 @@
|
||||
# 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)
|
||||
|
||||
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+)。
|
||||
- 完整 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
|
||||
|
||||
---
|
||||
|
||||
## 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 默认单实例服务(单 writer),SQLite 足够。
|
||||
- 生产环境若 NFS 上的 SQLite 有锁/性能风险,v2.1+ 再演进到 Postgres/Redis;v2.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 逻辑)
|
||||
Reference in New Issue
Block a user