# MVP v2.5 迭代总结（已落地）

本文档总结 v2.5 在 v2.0/v2.1/v2.2…基础上完成的能力、实现点、验收方式与已知限制，便于回顾与后续版本迭代对齐。

## 目标与边界

v2.5 的核心目标：
- 引入 **User Management（用户管理）**：基于 token 的鉴权与任务级隔离（“只隔离 jobs”）。
- 引入 **Stateless Ray Node Pool（无状态 Ray worker 池）**：worker 不依赖平台下发 head 地址，自动发现并连接/自愈。
- 保持 **TaskSpec（v1.1 同款 YAML 格式）不扩展**：本迭代不支持 reward function、自定义 verl 代码等。

明确不做（v2.5 约束）：
- 不支持 TaskSpec 扩展（例如 `reward_fn_path` 等）。
- 不支持用户自定义 verl/hf/dataset 的隔离或自定义路径：**统一使用 `/private/common/...`** 的公共资源。
- 用户隔离仅覆盖 **任务与产物目录**（jobs），不覆盖 HF cache、datasets 等公共缓存。

## 关键能力（对外表现）

### 1) 多用户鉴权与任务隔离
- API 仍使用内部 `Authorization: Bearer <token>` 方式：
  - 管理员 token 来自环境变量 `MVP_INTERNAL_TOKEN`（admin）。
  - 业务用户 token 由管理员通过 API 下发并持久化到 SQLite。
- 用户隔离策略：
  - 非管理员用户只能查询/取消/拉取日志 **自己的 task**；跨用户访问返回 404（不泄露存在性）。
  - 训练产物落盘隔离：Ray job 目录统一写入 `/private/users/<user_id>/jobs/<ray_submission_id>/...`。

### 2) task_id / submission_id 带用户名
- 新任务 ID 规则：`mvp2-<user>-<workload>-<YYYYMMDD-HHMMSS>-<suffix>`
- Ray submission id（attempt）规则：`<task_id>--aNN`，因此自然包含用户名。
- 作用：Dashboard/日志/落盘目录可读性更强，便于按用户追踪和审计。

### 3) “无状态 worker 池”与 head 地址发现
- Head 在共享存储写入 **head 地址文件**（例如 `head.json`），worker 通过 watchdog：
  - 轮询发现 head 地址
  - 自动 `ray start --address ...` 加入集群
  - 掉线后自动重连（watchdog 自愈）
- 达成效果：在生产环境中，即使 worker 容器由算力平台创建（只提供 SSH 纳管），也能通过共享存储实现连接与自愈。

### 4) 任务调度：队列 + Ray Job 提交 + 状态回传
- API 提交任务后进入 SQLite 队列，由后台 scheduler 逐个提交到 Ray（默认 `max_running_tasks=1`）。
- Scheduler 持续轮询 Ray job 状态并回写任务状态（RUNNING/SUCCEEDED/FAILED/CANCELED）。
- 资源不足的“可重试失败”处理：
  - 针对 VERL 的 fail-fast（`Total available GPUs ... is less than total desired GPUs ...`）或集群资源不足，
    任务进入 `PENDING_RESOURCES` 并设置 `next_run_at`，按 `retry_interval_s` 周期重试。

## 关键实现点（工程化落地）

### 存储与目录约定（容器内视角）
- 共享根路径统一为 `/private`（对齐生产挂载）。
- v2.5 强约束：TaskSpec 的以下字段必须以 `/private/common/` 开头：
  - `code_path` / `train_file` / `val_file`
- 公共目录（示例）：
  - `/private/common/hf`：HF 缓存
  - `/private/common/datasets`：训练数据（必要时通过 symlink 指向已有缓存目录复用下载）
  - `/private/common/db/mvp.sqlite3`：队列与用户信息（SQLite）
  - `/private/common/logs`：API / watchdog 日志
  - `/private/users/<uid>/jobs/...`：用户作业产物（隔离）

### Ray 拓扑与“head 不跑训练”
- Head 启动为管理节点（CPU/GPU=0），避免训练任务落到 head。
- Worker 节点具备 GPU（示例：2 个 worker * 每个 4 GPU）。
- driver 通过 `entrypoint_resources`（例如 `worker_node: 1`）强制落 worker。

### 部署脚本与可重复执行
提供完整脚本链路，覆盖：
- 清理 legacy 环境、起停容器、启动 Ray head
- head discovery publisher、worker watchdog 启动与状态检查
- 数据/模型/代码准备（幂等、可复用已有下载）
- 启动 API server（并支持 RESET_DB）
- API 方式连续提交 PPO/GRPO/SFT 并等待完成

代表性脚本：
- `src/mvp/scripts/run_all_v25_api.sh`：v2.5 happy-path 端到端（含重建集群、准备资源、起 API、提交 3 类任务）
- `src/mvp/scripts/run_e2e_v25_cases.sh`：在 happy-path 基础上增加鉴权/隔离/输入校验/资源不足/取消等用例

## 验收与测试（已通过）

### 单元测试（本机 venv）
- `.venv/bin/python -m pytest`
- 覆盖率阈值：>= 90%

### 远端端到端（h1）
- 在 `argus@h1:/home2/argus/infra/mvp/src/mvp/scripts` 执行：
  - `MVP_INTERNAL_TOKEN=mvp-dev-token RESET_DB=1 ./run_e2e_v25_cases.sh`
- 结果：happy-path（PPO/GRPO/SFT）完成，且异常/边界用例验证通过（鉴权、跨用户隔离、输入校验、资源不足转 PENDING_RESOURCES、取消任务等）。

## 已知问题与后续建议

- `max_running_tasks=1` 会让队列中的任务在前序 RUNNING 时保持 QUEUED，这在“资源不足”边界测试里需要显式清空/取消前序任务，或接受该行为作为设计的一部分。
- 当前仍是 SQLite 单点；后续若要 HA/水平扩展，可在 v2.6+ 引入更强的持久化与多副本（例如 Postgres/etcd）。
- API server / watchdog 目前以脚本方式守护；后续可进一步统一为 systemd/supervisor（或平台侧守护）并补齐健康检查与告警。

