v3.0 开发验证完成,满足webui和最小数据管理需求, 任务模版目前偏简单,可以扩展一下,再release开放使用

This commit is contained in:
2025-12-30 17:29:50 +08:00
parent 7b89d60b3b
commit 6d3fefc7a6
30 changed files with 3764 additions and 16 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

15
specs/mvp/v3.0/README.md Normal file
View File

@@ -0,0 +1,15 @@
# MVP v3.0(Design)— WebUI + 用户数据上传/下载(SFTPGo)→ 首个可发布版本
本目录基于:
- `specs/mvp/mvp_roadmap_v2.md`(总体路线图)
- `specs/mvp/image/roadmap_v3.0.png`(v3.0 迭代图)
- 当前已落地的 v2.5(User Mgmt + Stateless Ray Node Pool)
目标是在 v2.5 的基础上补齐 **用户数据闭环**(上传→训练可见→产物下载)以及最小可用的 **WebUI**,形成“可发布”的 v3.0 版本。
文档:
- `specs/mvp/v3.0/v3.0_design.md`:总体架构与关键机制(WebUI、SFTPGo、数据/权限模型、任务流)。
- `specs/mvp/v3.0/v3.0_api.md`:v3.0 API 扩展设计(UI、数据、SFTPGo 管理、权限约束)。
- `specs/mvp/v3.0/v3.0_acceptance.md`:部署/升级/验收流程与可验证标准(含故障注入与回归清单)。
- `specs/mvp/v3.0/v3.0_dev_plan.md`:TDD 驱动的工程化开发计划(里程碑拆分、测试分层、E2E 验收)。
- `specs/mvp/v3.0/v3.0_progress.md`:实施进展记录(每个里程碑完成后追加记录)。

View File

@@ -0,0 +1,55 @@
# MVP v3.0 — 部署与验收流程(草案)
## 0) 环境前提
- Ray 集群:延续 v2.5 的 head + stateless worker(自动 join)
- 共享存储:容器内挂载 `/private`(dev/prod 对齐)
- API server:宿主机代码挂载到 head 容器,在 head 容器内启动
- 新增:SFTPGo 服务(建议容器化部署)
## 1) 部署步骤(高层)
1) 部署/升级 Ray 节点镜像(沿用 v2.5 的 `argus/argus-ray-node:v2.5` 或更高版本)
2) 启动 Ray 集群(compose 或平台创建容器)
3) 启动/配置 SFTPGo(挂载 `/private`)
4) 启动 API server(head 容器内)
5) 启动 WebUI(由 API server 托管)
## 2) 验收用例(必须通过)
### A. 用户与凭据
1) admin 创建用户 `alice`,签发 API token
2) 系统联动在 SFTPGo 创建 `alice`(home=/private/users/alice)
3) `alice` 使用 token 登录 WebUI(或调用 `/api/v2/me` 成功)
### B. 上传数据闭环(核心)
1) `alice` 通过 SFTP 上传数据集到 `/private/users/alice/datasets/...`
2) `alice` 通过 WebUI/API 提交任务,TaskSpec 引用该路径
3) Ray worker 读取该数据,任务 RUNNING 并最终 SUCCEEDED
### C. 下载产物闭环
1) 训练完成后,产物落到 `/private/users/alice/jobs/<submission_id>/...`
2) `alice` 通过 SFTP 下载 checkpoints/logs 成功
3) (新增)`alice` 将需要长期保留的权重从 `jobs/<submission_id>/...` 移动到 `models/`,确认移动后可长期存在
### C2. Jobs 回收站与自动清理(3 天移入回收站,7 天后永久删除)
1) 将 `jobs_trash_after_days`/`jobs_purge_after_days` 配置为较小值(例如分钟级,用于验证)
2) 训练完成进入 terminal 状态
3) 等待 API server 内置 janitor 扫描周期后,确认对应 `jobs/<submission_id>` 被移动到 `trash/jobs/<submission_id>`
4) 在回收站窗口内,把某个文件从 `trash/jobs/<submission_id>` 移动到 `models/`,确认移动成功
5) 等待超过 `jobs_purge_after_days` 后,确认 `trash/jobs/<submission_id>` 被永久删除
6) 确认已移动到 `models/` 的文件不被删除
### D. 安全隔离(必须)
1) `bob` 不能通过 API 查询 `alice` 的 task(404)
2) `bob` 不能提交引用 `/private/users/alice/...` 的 TaskSpec(400/403)
3) `bob` 通过 SFTP 无法访问 `/private/users/alice/...`(chroot 生效)
## 3) 故障注入(推荐通过)
1) kill worker watchdog 或 raylet → worker 自动恢复并重新加入集群
2) 重启 head 容器 → head 重新写 `head.json`,worker 自动重连
3) SFTPGo 重启 → 不影响 Ray 集群;用户可重新连接上传/下载
## 4) 回归清单(与 v2.5 一致)
- 任务队列、重试(INSUFFICIENT_RESOURCES → PENDING_RESOURCES → retry)
- PPO/GRPO/SFT 三种 workload 均可跑通
- head 不跑训练(driver 强制落 worker)

109
specs/mvp/v3.0/v3.0_api.md Normal file
View File

@@ -0,0 +1,109 @@
# MVP v3.0 — API 扩展设计(基于 v2.5)
v3.0 的原则是:**尽量复用 v2.5 API**,只增量增加 “数据闭环” 与 “WebUI 支持” 所需的最小接口。
## 1) 认证与权限
沿用 v2.5:
- Header:`Authorization: Bearer <token>`
- admin token:来自 `MVP_INTERNAL_TOKEN`
- 普通用户 token:由 admin 颁发并持久化在 SQLite
权限规则:
- 非 admin:只能访问自己的 task、自己的数据空间(`/private/users/<user_id>/...`)。
- 跨用户访问返回 404(不泄露存在性)。
## 2) 用户与 SFTPGo 联动(管理员接口)
### 2.1 创建用户(复用 v2.5)
`POST /api/v2/users`
- v3.0 行为:成功后,**可选**联动创建 SFTPGo 用户
- v3.0 默认启用联动:创建 SFTPGo 用户 + 生成一次性密码(password 认证)
- v3.0 仅保留该方案(方案 A):不做外部认证/SSO 集成(留到更后续版本)
- `data.sftpgo.admin_api_base` 推荐形如:`http://argus-sftpgo:8080/api/v2`(包含 `/api/v2` 前缀)
### 2.2 下发 token(复用 v2.5)
`POST /api/v2/users/{user_id}/tokens`
### 2.3 禁用用户(复用 v2.5)
`POST /api/v2/users/{user_id}:disable`
- v3.0 行为:联动禁用 SFTPGo 用户(可选)
### 2.4 SFTP 凭据管理(新增,管理员或用户自助)
(具体由你确认 v3.0 需要“用户自助”还是“管理员操作”)
#### 重置 SFTP 密码(管理员)
`POST /api/v2/users/{user_id}/sftp:reset_password`
- 返回:一次性密码(只返回一次,服务端不保存明文)
> v3.0 先只做 password 方案;SSH public key 作为后续版本可选增强(不在 v3.0 范围)。
## 3) 用户自助信息(新增)
### 3.1 获取当前用户信息
`GET /api/v2/me`
- 返回示例:
```json
{
"user_id": "alice",
"is_admin": false,
"paths": {
"home": "/private/users/alice",
"datasets": "/private/users/alice/datasets",
"models": "/private/users/alice/models",
"code": "/private/users/alice/code",
"jobs": "/private/users/alice/jobs",
"trash_jobs": "/private/users/alice/trash/jobs"
},
"retention": {
"jobs_trash_after_days": 3,
"jobs_purge_after_days": 7
},
"sftp": {
"host": "h1.example.internal",
"port": 2022,
"username": "alice"
}
}
```
## 3.2 Jobs Retention 提示(新增)
为了支撑 WebUI 展示与用户预期管理,可在 `/api/v2/me` 或单独接口返回:
- `jobs_trash_after_days`:默认 3
- `jobs_purge_after_days`:默认 7
- `jobs_root`:`/private/users/<me>/jobs`
- `trash_jobs_root`:`/private/users/<me>/trash/jobs`
- `recommendations`:提示用户把需要长期保存的产物移动到 `models/` 或 `datasets/`
## 4) 数据浏览/下载(可选,v3.0 最小化)
说明:上传/下载主通道仍是 SFTP。
WebUI 如果要提供“快速浏览/查看”,可实现只读接口(避免实现大文件上传/断点等复杂逻辑)。
### 4.1 列目录
`GET /api/v2/files?path=/private/users/alice`
- 权限:path 必须在 `/private/common/` 或 `/private/users/<me>/` 下
- 返回:文件列表(name/type/size/mtime)
### 4.2 下载文件(小文件为主)
`GET /api/v2/files:download?path=/private/users/alice/jobs/.../logs/...`
- 返回:流式下载
- 大文件仍建议走 SFTP
## 5) TaskSpec 路径校验升级(v3.0 关键)
v2.5:仅允许 `/private/common/...`
v3.0:允许 `/private/common/...` 与 `/private/users/<me>/...`
应用字段(至少):
- `train_file` / `val_file`
- `code_path`:仍仅允许 `/private/common/...`(v3.0 不支持执行用户 code)
- 本地模型路径字段(如果引入):允许 `/private/users/<me>/models/...`
## 6) WebUI 路由(新增)
由 API server 托管:
- `GET /ui`:主页面
- `GET /ui/login`:token 登录页
- 静态资源:`/ui/static/...`
WebUI 的所有操作均调用同源 API(不额外开 CORS)。

View File

@@ -0,0 +1,358 @@
# MVP v3.0 详细设计方案(基于 v2.5)
## 0. 结论摘要(v3.0 要交付什么)
v3.0 = v2.5 + **WebUI** + **用户数据上传/下载(SFTPGo)**,形成第一个可对外发布的版本:
- 用户可以通过 **SFTP** 上传数据/模型/代码(至少数据),落到 GPFS(容器内 `/private`)并对 Ray worker 可见。
- 用户可以通过 API/WebUI 提交训练任务,任务读取自己上传的数据。
- 用户可以下载训练产物(checkpoints/logs 等),最小闭环跑通。
## 1. 范围与原则
### 1.1 继承 v2.5 的前提(不回退)
- **Stateless Ray Node Pool**:head 写 `head.json`,worker watchdog 自动 join/自愈。
- **User Management**:token 鉴权、任务可见性隔离(跨用户 404 不泄漏)。
- **作业产物隔离**:Ray job 目录落到 `/private/users/<user_id>/jobs/<ray_submission_id>/...`。
- **API server 短期运行方式**:代码在宿主机,挂载到 head 容器,在 head 容器内启动(保持现状)。
### 1.2 v3.0 新增目标
1) **Data Management(SFTPGo)**
- 提供用户上传/下载入口(SFTP 为主)。
- 数据落到 GPFS(dev 环境 NFS/GPFS,生产环境 GPFS),训练 job 在 worker 容器内可直接读取。
2) **WebUI**
- 用户可视化创建任务、查看队列/状态/日志、查看“数据路径约定”和自己的 SFTP 信息。
- 目标是 “可用而非豪华”,支持核心工作流。
3) **权限闭环**
- 用户只能使用自己目录下的数据(`/private/users/<user_id>/...`)或公共目录(`/private/common/...`)。
- 防止用户提交任务读取其他用户的文件路径。
### 1.3 v3.0 明确不做(留给 v3.5)
- 不做 “自定义 reward function / 自定义 verl 代码 / 多版本 verl 共存”(路线图 v3.5)。
- 不做复杂 Serving/训推一体(路线图 v3.5)。
- 不做 IB 网络/拓扑优化(路线图 v3.5)。
- 不做系统级可观测性平台(路线图 v4.0)。
## 2. 架构概览
参考 `roadmap_v3.0.png`,v3.0 的控制面与数据面:
### 2.1 控制面(Control Plane)
- **API Server(FastAPI)**
- v2.5 的任务队列/调度/重试 + 用户管理能力继续复用
- 新增:数据管理能力(与 SFTPGo 对接) + WebUI
- **WebUI**
- 通过 API 使用 token 登录
- 提供任务/日志/数据入口(不直接运行训练)
- **Ray Head(状态节点)**
- 仍在 head 容器内(或单独节点)
- job server/dashbaord 提供 job submit/status/logs 能力
### 2.2 数据面(Data Plane)
- **GPFS(容器内挂载 `/private`)**
- 存放 common 与 users 两大根目录
- **Ray Worker Node(无状态)**
- 自动连接 head,执行训练
- 读取 `/private/users/<user>/...` 的数据
### 2.3 新增组件:SFTPGo(Data Management)
- 作为独立服务运行(容器化优先),后端存储使用 **filesystem**(GPFS 挂载路径)。
- 用户的 home directory 指向 `/private/users/<user_id>`(或其子目录)。
## 3. 存储与目录规范(v3.0 统一约定)
### 3.1 目录层级
统一以容器内 `/private` 作为根路径(dev/prod 对齐):
- `/private/common/`:公共资源
- `hf/`:HF cache
- `datasets/`:公共数据集(可选)
- `code/`:公共代码(例如公共 verl repo snapshot)
- `db/`:SQLite(队列、用户、token)
- `logs/`:API/supervisor/watchdog 日志
- `/private/users/<user_id>/`:用户空间(v3.0 重点)
- `datasets/`:用户上传的数据集(推荐)
- `models/`:用户保存/上传的本地模型(允许;也用于“把 job 产物移动到长期保存目录”)
- `code/`:用户上传的代码(v3.0 **不支持执行**;仅存放/下载)
- `jobs/`:训练任务产物(已在 v2.5 落地)
- `tmp/`:临时文件(可选)
### 3.2 Jobs Retention(两段式:3 天移入回收站,7 天后永久删除)
v3.0 引入 **jobs 目录两段式保留策略**:
- 第 1 阶段(soft-delete):job 结束后 **3 天**,将该 job 目录从 `jobs/` **移动到用户回收目录**;
- 第 2 阶段(hard-delete):进入回收目录后再过 **7 天**,从回收目录 **永久删除**。
目录约定(建议):
- jobs 根目录:`/private/users/<user_id>/jobs/<ray_submission_id>/...`
- 回收目录:`/private/users/<user_id>/trash/jobs/<ray_submission_id>/...`
计时规则:
- 以 job 进入 terminal 状态(SUCCEEDED/FAILED/CANCELED)的结束时间为起点;
- “3 天”用于从 `jobs/` 移入 `trash/jobs/`;
- “7 天”用于从 `trash/jobs/` 永久删除(即总共最多 10 天窗口)。
用户保留关键产物的方式(无需 keep 标记):
- 在 “3 天窗口”内把需要长期保存的文件从 `jobs/<submission_id>/...` **移动/复制**到 `models/`(例如权重)或 `datasets/`(例如评估输出数据);
- 即便已被移动到回收目录,用户仍可在 “7 天窗口”内从 `trash/jobs/<submission_id>/...` 把需要的文件移到 `models/` / `datasets/`;
- janitor 只管理 `jobs/` 与 `trash/jobs/`,不会触碰 `models/` 与 `datasets/`。
这里的“清理程序”我们称为 **janitor**:
- 定义:一个后台清理执行器,按固定周期扫描“已结束且已过期”的 job 目录并删除
- v3.0 目标:实现“3 天移入回收站 + 7 天后删除”这一条产品规则(不提供 keep/延长保留标记)
实现建议(按你的偏好):
- **janitor 作为 API server 内置后台线程**运行:
- 优点:天然可访问 SQLite(任务状态、结束时间、user_id、ray_submission_id),并能把清理结果写回 events 表用于审计
- 部署更简单:不额外引入 cronjob/独立服务
- 删除/移动动作建议 **直接在 GPFS/NFS 文件系统上操作**(API server 运行在 head 容器,已挂载 `/private`):
- 第 1 阶段:`os.rename`(同文件系统原子移动)把 `jobs/<sid>` 移到 `trash/jobs/<sid>`;
- 若跨文件系统(理论上不应发生),则降级为 copy+delete;
- 移动前做严格路径前缀校验(必须在 `.../users/<u>/jobs/` 下)。
- 第 2 阶段:对 `trash/jobs/<sid>` 执行递归删除(例如 `shutil.rmtree`),同样做路径前缀校验(必须在 `.../users/<u>/trash/jobs/` 下)。
- 为什么不依赖 SFTPGo API:SFTPGo 只是用户访问协议层(SFTP/Web),目录物理就在同一份文件系统;文件系统直连更简单、也不依赖 SFTPGo 在线。
- 如果你强烈希望“通过 SFTPGo API 删除”:
- 可以作为可选实现/补充(例如用于统一审计或未来接入配额/策略),但不建议作为唯一手段(SFTPGo 停机不应阻塞清理)。
### 3.3 用户在 SFTPGo 内移动/整理文件(确认点)
支持用户在 SFTPGo 中进行“移动/重命名/整理”(例如把权重从 `jobs/` 移动到 `models/`):
- 前提:SFTPGo 用户权限允许对其 home 目录进行 `rename/mkdir/remove` 等操作(v3.0 默认可写)。
- 行为:用户可以把 `jobs/` 下某些文件移动到 `models/` 或 `datasets/`,用于长期保存权重/评估产物等。
- 与 retention 的关系:只要文件被移动出 `jobs/`,就不会被 jobs 清理逻辑删除。
### 3.4 路径权限规则(API 侧校验)
v2.5 约束是 “只允许 `/private/common/...`”。
v3.0 需要升级为:
- 允许:
- `/private/common/...`
- `/private/users/<current_user_id>/...`
- 禁止:
- 任何其他绝对路径(例如 `/private/users/other/...`、`/etc/...`)
并把该规则应用到 TaskSpec 的相关字段(至少):
- `train_file` / `val_file`
- `code_path`:仍仅允许 `/private/common/...`(v3.0 不支持执行用户 code)
- 本地模型路径字段:允许 `/private/users/<me>/models/...`(确认:v3.0 允许)
## 4. SFTPGo 方案设计(Data Management)
### 4.1 运行形态
推荐用容器运行 SFTPGo(与 Ray/API 解耦),挂载同一份 `/private`:
- `sftpgo` 容器挂载 `../../shared:/private`
- 对外暴露:
- SFTP 端口(建议 2022)
- WebAdmin/API 端口(建议 8081,仅内网或管理员访问)
#### 4.1.1 镜像来源(现成 Docker 镜像)
SFTPGo 有现成可用的 Docker 镜像(无需自建):
- v3.0 推荐优先使用官方/上游发布的 `sftpgo` 镜像作为运行基座
- 我们在 v3.0 里不需要定制 SFTPGo 代码,只需要:
- 正确挂载 GPFS/NFS(容器内 `/private`)
- 配置管理员账号(用于 API server 联动创建/禁用用户、重置密码)
- 配置每用户 home/chroot
> 注意:具体镜像名/tag 在不同环境可能有差异(官方/镜像仓库策略会变动)。落地时建议在 `argus@h1` 上 `docker search sftpgo` 或由你们内部镜像仓库提供固定版本;v3.0 设计只要求“使用现成镜像”,不强依赖某个 tag。
#### 4.1.2 docker-compose 服务草案(示意)
下面给出一个**示意**(最终以实际镜像名/tag 与你们端口规划为准):
```yaml
services:
sftpgo:
image: sftpgo/sftpgo:latest # 示例:使用现成镜像
container_name: argus-sftpgo
ports:
- "2022:2022" # SFTP
- "8081:8080" # WebAdmin/API(建议仅内网/管理员)
volumes:
- ../../shared:/private
- ../../shared/common/sftpgo:/var/lib/sftpgo # 持久化 SFTPGo 元数据(可选/建议)
environment:
# 管理员账号/密码(示意,具体变量名以镜像文档为准)
SFTPGO_ADMIN_USERNAME: "admin"
SFTPGO_ADMIN_PASSWORD: "${SFTPGO_ADMIN_PASSWORD}"
```
与 v3.0 的配合点:
- API server 使用 `data.sftpgo.admin_api_base` + admin 凭据联动创建用户
- 用户 home/chroot 统一指向 `/private/users/<user_id>`
### 4.2 用户隔离
每个用户在 SFTPGo 中的 home dir 绑定到:
- `/private/users/<user_id>`(chroot),用户只能读写自己的目录。
### 4.3 用户创建与凭据管理(两种实现,建议先做 A)
**方案 A(v3.0 推荐):API Server 负责“联动创建 SFTPGo 用户”**
- 在 v2.5 的 `POST /api/v2/users` 成功后:
- API server 调用 SFTPGo 管理 API 创建同名用户
- 设置 home dir = `/private/users/<user_id>`
- 设置权限(默认可写;是否只读可配置)
- 认证方式:
- v3.0 最小可用:用户名+密码(确认:v3.0 先 password;API 生成一次性密码,用户首次登录后要求改密)
- 或:SSH public key(WebUI 允许上传 public key,API 写入 SFTPGo)
**方案 B(更强但复杂):SFTPGo 外部认证**
- SFTPGo 把认证委托给 API server(token/SSO),SFTP 也走内部 token。
- 复杂度高,建议 v3.0 不做,放到 v3.5 或更后。
### 4.4 用户上传/下载体验
用户通过 SFTP 上传:
- `datasets/...`(训练数据)
- `models/...`(本地模型,可选)
下载:
- `jobs/<ray_submission_id>/...`(checkpoints/logs)
WebUI/文档提供 “路径如何写进 TaskSpec” 的指引。
## 5. WebUI 方案设计(最小可用)
### 5.1 目标页面
v3.0 WebUI 采用“**多子页面 + 侧边导航栏**”而不是把所有功能挤到单页:
- 原因:信息密度更可控,后续可扩展(v3.5+)且不会把一个页面做成“巨型表单/巨型列表”。
- 实现仍保持轻量:服务端渲染(或静态 HTML + 少量 JS),不引入复杂前端工程。
信息架构(IA)建议如下:
1) **登录页**(`/ui/login`)
- 用户粘贴 token(管理员发放),浏览器保存(localStorage/sessionStorage)
- 提供“退出登录/清空 token”
2) **任务列表页**(`/ui/tasks`)
- 默认列表:最近 N 条任务(按 created_at 倒序)
- 支持过滤:workload、state(QUEUED/RUNNING/SUCCEEDED/FAILED/CANCELED)、时间范围
- 支持快捷操作:进入详情、取消任务
3) **新建任务页**(`/ui/tasks/new`)
- 两种模式(二选一,均可实现):
- **YAML 直接提交**:上传/粘贴 TaskSpec YAML(最省开发)
- **表单生成 YAML**:选择 workload,填写核心字段(train/val/model/nnodes/gpus),生成 YAML 预览后提交
- 提交后跳转到任务详情页
4) **任务详情页**(`/ui/tasks/{task_id}`)
- 顶部:task_id、workload、state、created_at、updated_at、error_summary
- Attempt 卡片:latest attempt_no、ray_submission_id、ray_status、start/end
- 操作区:取消任务(若非 terminal)、刷新状态、复制路径/ID
- 链接到日志页与产物提示(SFTP 路径)
5) **任务日志页**(`/ui/tasks/{task_id}/logs`)
- 默认 tail=2000,可选 200/1000/5000
- 提供“自动刷新(每 3~5 秒)”开关(简单轮询即可)
6) **数据页**(`/ui/data`)
- 显示 SFTP 连接信息(host/port/username)
- 显示用户目录约定:
- home:`/private/users/<user_id>`
- datasets:`/private/users/<user_id>/datasets`
- models:`/private/users/<user_id>/models`
- jobs:`/private/users/<user_id>/jobs`
- trash/jobs:`/private/users/<user_id>/trash/jobs`
- 明确 retention:jobs 结束后 3 天移入回收站,回收站 7 天后删除;重要文件请移到 `models/` 或 `datasets/`
7) **(仅管理员可见)用户管理页**(`/ui/admin/users`,可选但很有价值)
- 创建用户、禁用用户、签发 token、重置 SFTP 密码(方案 A)
### 5.2 页面组织与导航(建议)
侧边栏导航(普通用户):
- Tasks(列表)
- New Task(新建)
- Data(SFTP/目录说明)
管理员侧边栏额外增加:
- Admin / Users
### 5.3 大致示意图(wireframe)
下面是一个粗略示意(非最终 UI,仅表达信息结构与布局):
```
┌──────────────────────────────────────────────────────────────────────┐
│ Argus MVP v3.0 [user: alice] │
├───────────────┬──────────────────────────────────────────────────────┤
│ Side Nav │ /ui/tasks │
│ │ │
│ • Tasks │ [Filter] workload=all state=all [Search task_id] │
│ • New Task │ │
│ • Data │ Task List │
│ • Admin(*) │ ┌────────────────────────────────────────────────┐ │
│ │ │ task_id workload state ... │ │
│ │ │ mvp2-alice-ppo-... ppo RUNNING ... │ │
│ │ │ mvp2-alice-sft-... sft SUCCEEDED... │ │
│ │ └────────────────────────────────────────────────┘ │
│ │ [View] [Cancel] │
└───────────────┴──────────────────────────────────────────────────────┘
```
任务详情页(示意):
```
┌──────────────────────────────────────────────────────────────────────┐
│ /ui/tasks/{task_id} │
├──────────────────────────────────────────────────────────────────────┤
│ task_id: mvp2-alice-ppo-... state: RUNNING workload: ppo │
│ created_at: ... updated_at: ... │
│ error_summary: (empty) │
│ │
│ latest_attempt: a01 ray_submission_id: ...--a01 ray_status: RUNNING │
│ [Open Logs] [Cancel Task] [Refresh] │
│ │
│ Artifacts (SFTP paths): │
│ jobs/: /private/users/alice/jobs/<ray_submission_id>/ │
│ trash/: /private/users/alice/trash/jobs/<ray_submission_id>/ │
│ tip: move important files to /private/users/alice/models/ │
└──────────────────────────────────────────────────────────────────────┘
```
### 5.2 技术取舍(建议:不引入 Node 构建)
为了降低部署复杂度,建议 v3.0 WebUI 以 “服务端渲染 + 少量 JS/HTMX” 或 “纯静态 HTML+fetch” 实现:
- 由 API server 提供静态资源(FastAPI StaticFiles)
- 页面调用同源 API,避免跨域与复杂前端构建链
## 6. API 扩展设计(概览)
v3.0 可以保持 `/api/v2/...` 不变,增量加:
- SFTPGo 集成管理端点(管理员):
- 创建/禁用用户时联动 SFTPGo
- 重置 SFTP 密码 / 更新 SSH key
- 用户数据端点(可选,最小化):
- `/api/v2/me`:返回 user_id、SFTP 信息(host/port/home)
- `/api/v2/files`:仅用于浏览/下载(上传仍走 SFTP)
详细见 `specs/mvp/v3.0/v3.0_api.md`。
## 7. 配置与部署(v3.0 新增项)
在 `configs/dev.yaml` 基础上扩展一组 `data` 配置(示意):
```yaml
data:
shared_root: "/private" # 通常与 ray.shared_root 一致
user_root: "/private/users" # 用户空间根目录
allow_common_prefix: "/private/common/"
allow_user_prefix_template: "/private/users/{user_id}/"
sftpgo:
enabled: true
host: "127.0.0.1"
sftp_port: 2022
admin_api_base: "http://127.0.0.1:8081/api/v2"
admin_user: "admin"
admin_password_env: "SFTPGO_ADMIN_PASSWORD" # 仅 head 容器内可读
retention:
jobs_trash_after_days: 3
jobs_purge_after_days: 7
trash_root_template: "/private/users/{user_id}/trash/jobs"
janitor_interval_s: 3600 # 每小时扫一次(可配置)
```
## 8. 风险点与对策
1) **路径逃逸/越权读取**
- 必须在 API 提交任务时校验路径前缀
- SFTPGo 必须 chroot 到用户 home
2) **大文件上传稳定性**
- 优先用 SFTP(断点续传/可靠性更好)
3) **用户 token 与 SFTP 凭据的生命周期**
- token 走 v2.5 SQLite
- SFTP 凭据建议独立(密码/SSH key),并提供 reset 流程
4) **GPFS/NFS 权限**
- 确保 `/private/users/<user>` 目录权限可被 SFTPGo 写入且 worker 可读
## 9. 已确认结论(来自你的反馈)
1) 允许用户上传并在训练时使用自定义数据集:允许(`/private/users/<u>/datasets/...`)。
2) 允许用户上传并在训练时使用本地模型路径:允许(`/private/users/<u>/models/...`)。
3) v3.0 不允许执行用户自定义代码(不注入 `PYTHONPATH` 作为可执行 code path)。
4) SFTPGo 认证方式:v3.0 先 password。
5) WebUI:按“简单最小必要功能”做(token 粘贴登录优先)。
## 10. 待确认问题(需要你给结论)
(已确认)jobs 清理执行主体:v3.0 采用 **API server 内置 janitor 后台线程**。

View File

@@ -0,0 +1,232 @@
# MVP v3.0 开发计划(TDD 驱动)
本文是 v3.0 的**工程化开发计划**,强调“先写测试,再写实现”(TDD),并将每个里程碑拆成**可独立验收**的小闭环。
输入依据:
- 路线图:`specs/mvp/mvp_roadmap_v2.md`
- v3.0 设计:`specs/mvp/v3.0/v3.0_design.md`
- v3.0 API:`specs/mvp/v3.0/v3.0_api.md`
- v3.0 验收:`specs/mvp/v3.0/v3.0_acceptance.md`
- 现状基线:v2.5(Task queue + User mgmt + Stateless ray pool + 单镜像节点守护)
v3.0 已确认约束:
- 允许用户数据集路径:`/private/users/<me>/datasets/...`
- 允许用户本地模型路径:`/private/users/<me>/models/...`
- **不允许执行用户自定义代码**(不注入 user code 到 PYTHONPATH;`code_path` 仍只允许 `/private/common/...`)
- SFTPGo 先用 **password** 方案(方案 A:API 联动创建/管理 SFTPGo 用户)
- jobs retention:**3 天移入回收站(trash/jobs),再 7 天永久删除**;不提供 keep/延长保留标记
- janitor:**API server 内置后台线程**;删除/移动采用**文件系统直接操作**(不依赖 SFTPGo API)
---
## 0. TDD 规范(所有功能都遵循)
### 0.1 测试分层
1) **单元测试(fast)**
- 纯 Python 逻辑:路径策略、SFTPGo client、retention 计算、文件移动/删除策略(用临时目录)。
- 不依赖真实 Ray、不依赖 docker、不依赖网络。
2) **组件测试(中等)**
- FastAPI 路由(含 WebUI 路由):`fastapi.testclient.TestClient`
- mock/stub SFTPGo client 与 ray client
3) **端到端(慢)**
- 在 `argus@h1` 通过 docker compose + scripts:
- Ray 集群自动起来(head+2 worker)
- SFTPGo 服务可用
- 上传数据 → 提交训练 → 下载产物 → jobs 回收站/清理
### 0.2 代码与测试约定
- 测试目录:`src/mvp/py/tests/`
- 新功能必须先补齐测试用例,并让其在未实现时失败(红)
- 最小实现让测试变绿(绿)
- 再做重构(重构)
- 覆盖率:继续沿用当前阈值(>= 90%)
---
## 1. 里程碑拆分(v3.0 = 5 个可验证闭环)
### M1:TaskSpec 路径策略升级(允许 user datasets/models;code_path 仍仅 common)
**目标**
- API submit 时的路径校验从 v2.5 的 “仅 `/private/common/`” 升级为:
- `train_file` / `val_file`:允许 `/private/common/...` 与 `/private/users/<me>/...`
- 本地模型路径:允许 `/private/users/<me>/models/...`(不改变 YAML 结构,见实现建议)
- `code_path`:仍仅允许 `/private/common/...`
- 阻止越权路径(`/private/users/other/...`)与非 `/private/...` 路径。
**实现建议(不扩展 TaskSpec)**
- `model_id` 字段保持不变:
- 若 `model_id` 以 `/private/` 开头 → 视作本地模型路径
- 否则视作 HuggingFace repo id(如 `Qwen/...`)
**TDD 用例(先写测试)**
- 单测:
- `test_paths_allow_common_and_own_user_prefix()`
- `test_paths_reject_other_user_prefix()`
- `test_model_id_local_path_allowed_only_under_users_models()`
- `test_code_path_still_common_only()`
- API 测试:
- `test_submit_accepts_user_datasets_paths()`
- `test_submit_rejects_cross_user_paths_404_or_400()`(按约定返回 400/403)
**验收点**
- `v3.0_acceptance.md` 的 D 类安全隔离用例可由 API 测试覆盖。
---
### M2:SFTPGo 集成(方案 A:用户联动创建 + password)
**目标**
- 引入 `data management (SFTPGo)`:
- admin 创建用户时联动创建 SFTPGo 用户(home=/private/users/<user_id>,chroot)
- password 模式:生成一次性密码(reset/create)并返回给 admin(明文只返回一次)
- 提供用户自助信息:
- `GET /api/v2/me` 返回 SFTP 连接信息、目录约定、retention 提示。
**实现建议**
- 新增 `SFTPGoAdminClient`(同步调用):
- 通过 `urllib` 或 `httpx`(建议 `urllib`,减少依赖;禁止 hard-code requests 使用)
- 支持:create user / disable user / reset password(最小集合)
- API server 启动时校验配置(enabled 时必须具备 admin 密码 env)。
- 同步创建用户目录结构(文件系统):
- `/private/users/<u>/{datasets,models,code,jobs,trash/jobs}`(最小必需)
**TDD 用例(先写测试)**
- 单测:
- `test_sftpgo_client_builds_correct_requests()`(不发真实网络;mock urlopen)
- `test_user_dirs_created_on_user_create()`(tmp dir 断言目录存在)
- API 测试:
- `test_create_user_calls_sftpgo_client()`(stub client,断言调用参数)
- `test_me_returns_sftp_info_and_paths()`(含 trash/jobs 与 TTL 字段)
**验收点**
- `v3.0_acceptance.md` 的 A 类(用户/凭据)与 B 类(上传闭环前置)覆盖。
---
### M3:WebUI(最小可用,多页面 + 侧边栏)
**目标**
- WebUI 由 API server 托管(同源,无额外 CORS):
- `/ui/login`:token 粘贴登录(localStorage)
- `/ui/tasks`:任务列表 + 过滤(最小)
- `/ui/tasks/new`:YAML 提交(优先)+(可选)表单生成 YAML
- `/ui/tasks/{task_id}`:详情页
- `/ui/tasks/{task_id}/logs`:日志 tail + 可选自动刷新
- `/ui/data`:SFTP 信息 + 目录/retention 提示
- (可选)`/ui/admin/users`:管理员用户管理(若时间允许,强烈建议)
**实现建议**
- 先不引入 Node 构建:
- HTML 模板可用最简单的字符串拼接或 Jinja2(若引入 jinja2,则补齐依赖与测试)
- 页面通过 fetch 调用 `/api/v2/...`,并复用 token header
**TDD 用例(先写测试)**
- 组件测试(TestClient):
- `test_ui_routes_render_200()`
- `test_ui_contains_sidebar_links()`(简单断言文本包含导航链接)
- `test_ui_tasks_detail_shows_ids()`(包含 task_id、state、ray_submission_id)
**验收点**
- WebUI 能完成:登录→创建任务→查看任务→查看日志→看到 data 页提示。
---
### M4:Jobs Retention janitor(3 天移入 trash,7 天后 purge)
**目标**
- API server 内置 janitor 后台线程:
- 周期性扫描 DB 中 terminal tasks
- 到期后执行:
- move:`/private/users/<u>/jobs/<sid>` → `/private/users/<u>/trash/jobs/<sid>`
- purge:递归删除 `/private/users/<u>/trash/jobs/<sid>`
- 全程严格 path 校验,禁止越界删除
- 清理操作记录到 DB events(审计)
**实现建议(数据与状态)**
- 需要稳定的时间锚点与幂等:
- 使用 attempts.end_time 作为 job 结束时间(latest attempt)
- 在 tasks 表新增字段(或新表)记录:
- `trashed_at`(首次成功 move 时间)
- `purged_at`(成功删除时间)
- `trash_path`(可选)
- 幂等:重复运行不会报错(目录不存在视为已处理)
**TDD 用例(先写测试)**
- 单测(用 tmpdir 构造 jobs/trash 目录):
- `test_janitor_moves_job_to_trash_after_threshold()`
- `test_janitor_purges_trash_after_threshold()`
- `test_janitor_never_touches_models_or_datasets()`
- `test_janitor_path_escape_rejected()`(恶意 path 不可删)
- API/组件测试:
- `test_me_includes_retention_fields()`(jobs_trash_after_days/jobs_purge_after_days)
**验收点**
- `v3.0_acceptance.md` 的 C2 用例可按“把阈值调小到分钟级”完成验证。
---
### M5:端到端(h1)— SFTP 上传→训练→产物下载→回收站/清理
**目标**
- 在 `argus@h1` 落一个一键脚本(或手册)跑通:
1) `docker compose up -d` 拉起 Ray(head+2 worker)+ SFTPGo
2) admin 创建用户 alice(联动创建 SFTPGo 用户 + password)
3) alice 通过 SFTP 上传:
- 数据集到 `/private/users/alice/datasets/...`
- (可选)本地模型到 `/private/users/alice/models/...`
4) alice 通过 API/WebUI 提交任务引用上述路径
5) 任务成功后:
- 从 `jobs/<sid>` 下载 logs/checkpoints
- 把权重移动到 `models/`,验证不会被清理
6) 把 retention 配置调小,验证 jobs→trash→purge
**交付建议**
- 新增脚本(命名示例):
- `scripts/run_all_v30_api.sh`
- `scripts/run_e2e_v30_cases.sh`
- 新增 `docker-compose.yaml` 中的 `sftpgo` service(或 `docker-compose.v30.yaml` 叠加文件)
**验收点**
- `v3.0_acceptance.md` 全部 MUST 用例通过。
---
## 2. 风险与测试关注点
1) **权限与路径逃逸**
- path policy 必须覆盖:train/val/model_id(local)/output dirs(jobs/trash)
- 所有删除/移动必须做 prefix 校验
2) **并发与竞态**
- janitor 只处理 terminal tasks,避免清理正在写入的目录
- move 使用同文件系统 `os.replace`(原子)
3) **SFTPGo 可用性**
- SFTPGo 不在线不应影响训练与 API 核心功能(除了用户创建联动)
- janitor 不依赖 SFTPGo(文件系统直连)
---
## 3. 交付清单(代码/配置/脚本/文档)
### 3.1 代码
- Path policy(v3.0)
- SFTPGoAdminClient + user create/disable/reset password 联动
- `/api/v2/me` 扩展(SFTP/目录/retention)
- WebUI 路由与静态资源
- janitor(trash+purge)后台线程 + DB 记录
### 3.2 配置
- `configs/dev.yaml` 增加 `data.sftpgo`、`data.retention` 段(详见设计文档)
### 3.3 scripts / compose
- compose 增加 `sftpgo`(或新增 overlay compose 文件)
- v3.0 e2e 脚本(上传/下载/清理验证)
### 3.4 文档
- 更新 `specs/mvp/v3.0/*` 与 `src/mvp/README.md`(运行方式、路径约定、SFTP 操作、retention 解释)

View File

@@ -0,0 +1,154 @@
# MVP v3.0 进展记录(milestone log)
本文档用于记录 v3.0 按 `specs/mvp/v3.0/v3.0_dev_plan.md` 实施过程中的里程碑完成情况。
约定:每完成一个里程碑,追加一条记录,包含**日期**、**完成内容**、**涉及文件**、**验证方式/结果**、**待办/风险**。
---
## M1:Path policy + tests(已完成)
- 日期:2025-12-30
- 范围:按 v3.0 路径策略升级 API submit 的路径校验(不扩展 TaskSpec YAML 结构)。
- 完成内容:
- `code_path`:仍只允许 `/private/common/...`(v3.0 不执行 user code)。
- `train_file`/`val_file`:允许 `/private/common/datasets/...` 或 `/private/users/<me>/datasets/...`。
- `model_id`:若以 `/private/` 开头则视为本地路径,仅允许:
- `/private/common/models/...` 或
- `/private/users/<me>/models/...`
否则仍按 HuggingFace repo id(如 `Qwen/...`)处理。
- 拒绝跨用户路径(例如 `bob` 提交 `/private/users/alice/datasets/...`)。
- 拒绝本地模型路径不在 `models/`(例如指向 `jobs/`)。
- 涉及文件:
- `src/mvp/py/argus/service/app.py`
- `src/mvp/py/tests/test_users.py`
- 验证方式与结果:
- 本地单测:`.venv/bin/python -m pytest -q`
- 结果:全部通过(`54 passed`),覆盖率阈值保持 `>= 90%`。
- 待办/风险:
- `model_id=/private/...` 的“本地模型路径语义”需要在用户文档/WebUI 中明确提示(避免误用)。
- 后续 M2/M3 需要把该路径策略同步到 UI 表单/提示文本(避免用户填错路径)。
---
## M2:SFTPGo 集成(方案 A:用户联动创建 + password)(已完成)
- 日期:2025-12-30
- 范围:SFTPGo(Data Management)最小集成 + 用户自助信息 `/api/v2/me` + 用户目录结构落盘。
- 完成内容:
- 新增 `data` 配置段:
- `data.user_root`:用户数据根目录(默认 `/private/users`)
- `data.sftpgo`:SFTPGo 可选联动(enabled/host/sftp_port/admin_api_base/admin_user/admin_password_env)
- `data.retention`:jobs 过期策略配置(3 天移入 trash,7 天 purge;janitor 在 M4 实现)
- 新增 `SFTPGoAdminClient`(`urllib` 实现,不使用 `requests`):
- `create_user` / `disable_user` / `reset_password`(最小集合)
- API server 增强:
- `POST /api/v2/users`:创建 DB user + 同步创建目录结构(`datasets/models/code/jobs/trash/jobs`)
- 当 `data.sftpgo.enabled=true` 时,创建用户会联动调用 SFTPGo admin API,并返回一次性密码(明文仅返回一次,服务端不保存)
- `POST /api/v2/users/{user_id}:disable`:禁用用户(SFTPGo 禁用 best-effort)
- `POST /api/v2/users/{user_id}/sftp:reset_password`:管理员重置一次性密码(SFTPGo enabled 才允许)
- `GET /api/v2/me`:返回当前用户的目录约定、retention 提示,以及(可选)SFTP 连接信息
- 同步更新 `src/mvp/configs/dev.yaml`:补齐 v3.0 相关 `data.*` 配置(默认关闭 sftpgo)。
- 涉及文件:
- `src/mvp/py/argus/service/config.py`
- `src/mvp/py/argus/service/sftpgo.py`
- `src/mvp/py/argus/service/app.py`
- `src/mvp/py/tests/test_sftpgo.py`
- `src/mvp/py/tests/test_users.py`
- `src/mvp/py/tests/test_app.py`
- `src/mvp/py/tests/test_service_config.py`
- `src/mvp/configs/dev.yaml`
- `specs/mvp/v3.0/v3.0_api.md`
- 验证方式与结果:
- 本地单测:`.venv/bin/python -m pytest -q`
- 结果:全部通过(`62 passed`),覆盖率 `90.11%`(阈值 `>= 90%`)。
- 待办/风险:
- M2 仅做了“API 侧联动 + 单测”,未在真实 SFTPGo 容器上端到端验证(按计划在 M5 完成)。
- 目录创建依赖文件系统权限:生产部署时需确保 API/head 容器对 `/private/users` 可写。
---
## M3:WebUI(最小可用,多页面 + 侧边栏)(已完成)
- 日期:2025-12-30
- 范围:API server 托管最小 WebUI(同源,不引入 Node 构建),用于登录/提交/查看任务与日志、查看 data 信息。
- 完成内容:
- 新增 UI 路由(HTML+少量 JS):
- `/ui`(重定向到 tasks)
- `/ui/login`:token 粘贴并写入浏览器 localStorage(key=`mvp_token`)
- `/ui/tasks`:任务队列列表(调用 `/api/v2/queue`)
- `/ui/tasks/new`:提交 TaskSpec YAML(POST `/api/v2/tasks`)
- `/ui/tasks/{task_id}`:任务详情(GET `/api/v2/tasks/{task_id}`,支持 cancel)
- `/ui/tasks/{task_id}/logs`:日志查看(GET `/api/v2/tasks/{task_id}/logs`,可选自动刷新)
- `/ui/data`:展示 `/api/v2/me` 返回的路径/SFTP/retention 信息
- 统一侧边栏导航:Tasks / New Task / Data / Login。
- UI 不做服务端 session:所有 API 调用均由浏览器带 `Authorization: Bearer <token>`(localStorage 注入)。
- 涉及文件:
- `src/mvp/py/argus/service/ui.py`
- `src/mvp/py/argus/service/app.py`
- `src/mvp/py/tests/test_ui.py`
- 验证方式与结果:
- 本地单测:`.venv/bin/python -m pytest -q`
- 结果:全部通过(`65 passed`),覆盖率 `90.53%`(阈值 `>= 90%`)。
- 待办/风险:
- WebUI 当前为“骨架+API 驱动”,不做复杂交互与大文件下载;上传/下载仍以 SFTP 为主(按设计)。
- Starlette TestClient 的 `allow_redirects` 有弃用告警(不影响功能,可在后续清理)。
---
## M4:Jobs Retention janitor(3 天移入 trash,7 天后 purge)(已完成)
- 日期:2025-12-30
- 范围:API server 内置后台线程,对“已结束 attempt”的 job 目录执行保留策略(文件系统直连,不依赖 SFTPGo)。
- 完成内容:
- 新增 `JobsJanitor`:
- 以 `attempts.end_time` 为基准计算 TTL(从 job 结束开始算)
- `>= 3 天 && < 7 天`:把目录从 `.../jobs/<ray_submission_id>` 移动到 `.../trash/jobs/<ray_submission_id>`
- `>= 7 天`:确保目录进入 trash 后删除(`shutil.rmtree`)
- 对缺失目录、异常移动/删除为 best-effort(不影响服务主流程)
- DB 增强:新增查询 `list_ended_attempts_before()`,用于 janitor 扫描候选 attempt。
- API server 启动时启动 janitor 线程(可通过 `data.retention.janitor_interval_s` 控制;<=0 视为关闭)。
- 涉及文件:
- `src/mvp/py/argus/service/janitor.py`
- `src/mvp/py/argus/service/db.py`
- `src/mvp/py/argus/service/app.py`
- `src/mvp/py/tests/test_janitor.py`
- 验证方式与结果:
- 本地单测:`.venv/bin/python -m pytest -q`
- 结果:全部通过(`75 passed`),覆盖率 `90.72%`(阈值 `>= 90%`)。
- 待办/风险:
- M4 只做“逻辑 + 单测”,实际 `/private/users/...` 的权限与在 `argus@h1` 的行为验证放到 M5(端到端)。
---
## M5:端到端(h1)— SFTPGo compose + v3.0 E2E 脚本(已完成:交付脚本/配置)
- 日期:2025-12-30
- 范围:补齐 h1 端到端所需的 compose/service、配置与一键脚本(实际运行/验收由你在 `argus@h1` 执行)。
- 完成内容:
- SFTPGo 集成到 `docker compose`:
- 新增 `argus-sftpgo` service(SFTP 2022;Admin API/UI 8080→host 8081,避免与 MVP API 8080 冲突)
- 同挂载 `../../shared:/private`,并持久化元数据到 `../../shared/common/sftpgo`
- SFTPGoAdminClient 实装(对齐 upstream OpenAPI):
- `GET /api/v2/token`(BasicAuth)获取 admin token
- `POST /api/v2/users` 创建用户(含 `permissions: {"/":["*"]}`)
- `PUT /api/v2/users/{username}` 禁用/重置密码
- 新增 v3.0 dev 配置:`configs/dev_v30.yaml`(启用 `data.sftpgo` 并配置 `admin_api_base=http://argus-sftpgo:8080/api/v2`)
- 新增 v3.0 一键脚本:
- `scripts/run_all_v30_api.sh`:起 Ray+SFTPGo、启动 API、创建用户并提交 PPO/GRPO/SFT(引用 user dataset 路径)
- `scripts/run_e2e_v30_cases.sh`:最小 E2E runner(HP-1)
- API 启动脚本增强:`scripts/60_start_api.sh` 支持透传 `SFTPGO_ADMIN_PASSWORD` 到 head 容器内的 API 进程。
- 涉及文件:
- `src/mvp/docker-compose.yaml`
- `src/mvp/configs/dev_v30.yaml`
- `src/mvp/scripts/run_all_v30_api.sh`
- `src/mvp/scripts/run_e2e_v30_cases.sh`
- `src/mvp/scripts/60_start_api.sh`
- `src/mvp/py/argus/service/sftpgo.py`
- `src/mvp/py/tests/test_sftpgo.py`
- `src/mvp/README.md`
- `specs/mvp/v3.0/v3.0_api.md`
- 验证方式与结果:
- 本地单测:`.venv/bin/python -m pytest -q`
- 结果:全部通过(`75 passed`),覆盖率 `90.35%`(阈值 `>= 90%`)。
- 待办/风险:
- 需要你在 `argus@h1` 实跑 `scripts/run_all_v30_api.sh` 完成真正的 SFTP 上传/下载与 retention 验收(按 `v3.0_acceptance.md`)。

View File

@@ -0,0 +1,166 @@
# MVP v3.0 迭代总结(Ray + SFTPGo + API + WebUI)
本文总结 v3.0 迭代最终落地的功能、架构、运行方式、验收点与已知限制,便于后续评审、交接与继续迭代。
相关更详细文档:
- `specs/mvp/v3.0/v3.0_design.md`
- `specs/mvp/v3.0/v3.0_api.md`
- `specs/mvp/v3.0/v3.0_dev_plan.md`
- `specs/mvp/v3.0/v3.0_acceptance.md`
- `specs/mvp/v3.0/v3.0_progress.md`
---
## 1. 目标与范围
v3.0 作为“第一版可发布”的最小闭环,主要新增:
- **WebUI**:最小可用的人机界面(登录、任务提交与查看、数据入口、管理员入口)。
- **用户管理**:基于内部 token 的用户体系(admin 与普通用户),支持创建用户与签发 token。
- **数据管理入口(SFTPGo)**:用户通过 SFTP/WebClient 上传下载自己的数据;同时暴露只读的共享数据/缓存目录(common)用于复用。
- **保持训练闭环**:仍通过 Ray Job 提交到集群执行(PPO/GRPO/SFT 三类 workload 都验证)。
明确不做(本迭代保持最小):
- 不支持用户自定义训练代码(TaskSpec 的 `code_path` 固定走 common 下的 verl snapshot 策略)。
- 不做复杂资源排队优化/多集群/多租隔离策略(目前隔离粒度主要在用户 jobs 目录层)。
---
## 2. 系统架构(最终形态)
核心组件:
- **Ray 集群(容器)**
- `argus-ray-head`:head 节点(无 GPU/不跑训练),提供 Ray Dashboard 与 Job Server。
- `argus-ray-worker-0/1`:worker 节点(有 GPU),承载训练任务。
- worker 以 “stateless + watchdog 自动连接 head” 的方式加入集群。
- **API Server(运行在 head 容器内)**
- 读取 YAML 配置(dev/prod),维护任务队列(sqlite),并周期性调度将任务提交到 Ray。
- 同时承载 WebUI(`/ui`)。
- **SFTPGo(容器)**
- 提供 SFTP(端口 `2022`)与 Web Client/Admin(端口 `8081` 映射到容器 8080)。
- 用户 home 为 `/private/users/<user>`,默认可读写。
- 额外提供 `/common/*` 共享只读入口(见第 4 节)。
- **共享存储(NFS/GPFS 等挂载到容器内 `/private`)**
- `/private/common`:共享缓存(hf、datasets、models、db、logs 等)。
- `/private/users/<user>`:用户隔离目录(jobs/datasets/models/code/trash 等)。
---
## 3. 任务与调度(Task / Ray Job)
### 3.1 Task(平台概念)
- 用户向 API 提交 TaskSpec(YAML),平台分配 `task_id`(可读、包含用户名)。
- `task_id` 对应内部状态机与重试逻辑;底层每次提交 Ray Job 会产生 attempt 与 `ray_submission_id`。
### 3.2 Ray Job(Ray 概念)
- 真正执行训练的 driver 通过 Ray Job 运行在集群 worker 上(避免 head 承载训练)。
- head 节点通过 `--num-cpus=0` / 自定义资源等策略避免调度到 head。
### 3.3 VERL 资源预检查的处理
- VERL 在创建资源池时会做 fail-fast 资源预检查(如“可用 GPU 不足”直接报错退出)。
- v3.0 延续 v2.x 的策略:服务端识别失败原因并按策略重试/回退(具体见 scheduler 实现与 v2.5/3.0 文档)。
---
## 4. 数据管理(SFTPGo)与 common 只读目录
### 4.1 用户目录(读写)
- 用户通过 SFTP/WebClient 访问自己的 home:`/private/users/<user>`
- 目录结构(至少):`datasets/ models/ code/ jobs/ trash/ common/`
### 4.2 common 只读(方案 A:Virtual Folder)
本迭代采用 SFTPGo 的 Virtual Folder + 路径权限覆盖,实现用户可读共享目录但不可写。
最终对外暴露为:
- `/common/datasets`(只读)
- **mapped_path 指向真实目录 `/private/datasets`**(避免 `/private/common/datasets` 中大量 symlink 导致的 WebClient “权限不足/越界”问题)
- `/common/hf`(只读)
- mapped_path 指向 `/private/hf`
备注:
- `/private/common/datasets` 内部存在 symlink(如 `gsm8k -> /private/datasets/gsm8k`),如果虚拟目录映射到 symlink 根目录,SFTPGo 会把 symlink 跳转视为“逃逸 root”,导致点击进入时报权限不足;因此选择直接映射到真实目录根。
---
## 5. WebUI(最小可用)
入口:
- `/ui/login`:粘贴 token(存 browser `localStorage`)
- `/ui/tasks`:任务列表(Running/Pending/Completed),Completed 支持分页
- `/ui/tasks/new`:提交任务(PPO/GRPO/SFT 三套样例可一键填充)
- `/ui/data`:展示当前用户名、支持重置 SFTPGo 密码并复制;提供跳转到 SFTPGo WebClient;提示 FileZilla 等客户端用法
- `/ui/admin`:管理员入口(创建用户、签发 token、用户列表)
- 导航栏提供 Ray Dashboard 快捷跳转(当前 IP 的 `:8265`)
关于 admin 页面权限:
- admin 页面本身可访问,但其数据请求必须携带 admin token;否则会在页面内显示 401/403/错误信息(满足“需要先提供 admin token 才能看到内容”)。
---
## 6. API(v3.0 新增/强化点)
核心接口(节选):
- 认证:
- Bearer token:`MVP_INTERNAL_TOKEN`(admin)或用户 token(由 admin 签发)
- 用户管理(admin):
- `POST /api/v2/users` 创建用户(并初始化用户目录)
- `GET /api/v2/users` 获取用户列表(包含最新 token、创建/更新时间等)
- `POST /api/v2/users/{user_id}/tokens` 签发用户 token
- 任务:
- `POST /api/v2/tasks` 提交 TaskSpec(YAML)
- `GET /api/v2/tasks` 任务列表(支持 states/limit/offset,用于 Completed 分页)
- `GET /api/v2/tasks/{task_id}`、`POST /api/v2/tasks/{task_id}:cancel`、`GET /api/v2/tasks/{task_id}/logs`
- `GET /api/v2/queue`(运行中/待调度概览)
- 数据/SFTP:
- `GET /api/v2/me` 返回用户路径信息、SFTP 连接信息,并 best-effort 对齐 SFTPGo 用户配置
- `POST /api/v2/me/sftp:reset_password` 用户自助重置 SFTPGo 密码(一次性返回明文)
安全取舍说明(当前为内网/开发优先):
- 为了 Admin WebUI “可查看并复制 token”,数据库持久化存储了 `token_plain`(明文 token)。
- 这在生产场景通常不建议;未来可改为只展示“重置/重新签发”而不回显明文,或只回显一次。
---
## 7. 持久化与清理
- 任务队列:sqlite(WAL 模式)
- SFTPGo:自带 sqlite db(容器挂载持久化目录)
- Jobs 目录清理策略(服务端 janitor):
- job 结束后 3 天移动到回收目录(trash)
- 回收目录再保留 7 天后删除
---
## 8. 运行方式与脚本
开发/验收脚本:
- `src/mvp/scripts/run_all_v30_api.sh`:端到端拉起(Ray + SFTPGo + API),并通过 API 提交 PPO/GRPO/SFT,等待完成并验收
- 其他脚本用于启动/停止 API、准备数据与模型、探测服务就绪等(详见 scripts 目录与 README)
典型端到端(示例参数):
- `MVP_INTERNAL_TOKEN=my-dev-token`
- `SFTPGO_ADMIN_PASSWORD=my-dev-sftpgo-admin`
- 支持 `RESET_DB/RESET_SFTPGO` 用于测试环境重置
---
## 9. 验证结果(已跑通)
在 `argus@h1` 环境中已完成端到端验证:
- Ray 集群可用(head + 2 worker)
- API server + WebUI 可用
- SFTPGo(admin + 普通用户)可用
- 通过 API 连续提交 PPO/GRPO/SFT 三种任务均能完成(SUCCEEDED)
- 用户可以登录 SFTPGo WebClient/SFTP,访问自己的目录,并访问 `/common/datasets`、`/common/hf` 的只读内容
同时本地单测通过:
- pytest 全绿
- 覆盖率阈值 >= 90%
---
## 10. 已知限制 & 后续可改进
- WebUI 当前为最小版,交互与权限提示仍偏“工程化”而非产品化(后续可增强错误提示、搜索筛选、任务详情聚合等)。
- token 明文持久化仅适合内网/开发场景;生产建议改为一次性展示或支持撤销/轮换策略。
- SFTPGo 虚拟目录目前保留了历史遗留映射(例如 `/common/models` 可能残留),后续可在升级脚本中做一次性清理与迁移。