argus-cluster/specs/mvp/refactor/code_refactor.md

302 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 代码结构重构方案(按功能模块划分)
背景:当前 `src/mvp/` 下以 `v1.1/``v2.0/` 版本目录来组织代码。实际上 **v2.0 是在 v1.1 的 Ray Jobs SDK 提交链路基础上扩展了服务层**,并且为了让 v2.0 工作又对 v1.1 的 `docker-compose.yaml``dev.yaml` 做了修改(挂载 v2、开放 8080、增加 `v2:` 配置段)。因此“按版本分目录”会让依赖关系变得不清晰(谁是库、谁是应用、哪些配置是共享的)。
本方案目标:把 `src/mvp/` 重构为“按功能模块”划分ray 提交核心库 / service 服务层 / cli 工具 / TaskSpecs / configs / scripts并给出迁移后的验证与执行方案。
> 本文仅给出设计与迁移/验证方案,不直接改代码(待确认后再实施)。
---
## 1. 现状梳理(问题点)
### 1.1 代码重复与耦合
- `src/mvp/v2.0/py/mvp_v11/` 是从 `src/mvp/v1.1/py/mvp_v11/` 复制而来用于复用,但这导致:
- **库代码重复**(修 bug 要改两份)
- 谁是“权威实现”不明确
- v2 API`mvp_v2`)通过引用复制的 `mvp_v11.RayJobTool` 来提交 Ray Job本质上依赖 v1.1 提交链路作为“库”。
### 1.2 配置与部署目录不稳定
- v2.0 复用了 v1.1 config 文件并新增 `v2:` section这是合理的“向后兼容扩展”但它把
- “Ray submit 基础配置”
- “API 服务配置”
- “部署路径约定(/workspace/mvp/v1.1 vs /workspace/mvp/v2
混在一个文件里,不利于长期维护。
### 1.3 命名歧义jobspec 与 Ray job
- v1.1/v2.0 都使用 `jobspec.yaml` 指代“训练语义参数”PPO/GRPO/SFT 的训练字段)。
- 但 Ray 也有 “Ray Job” 概念submission_id、entrypoint、runtime_env 等),易造成沟通误解。
- 需要把训练侧 specs 改名为 **TaskSpecs**(代表平台级任务规范),与 Ray Job 区分。
---
## 2. 重构目标What good looks like
### 2.1 目录与职责清晰
- “提交 Ray Job 的 SDK 封装”是一个可复用模块(库)。
- “服务层API + scheduler + SQLite”是一个独立模块应用/服务)。
- “训练语义参数TaskSpecs”与 “Ray Job 提交参数RayConfig”分层清楚。
### 2.2 单一真源Single Source of Truth
- 只能有一份“Ray submitter core”实现不能复制一份到另一个版本目录
- API 与 CLI/脚本都复用同一份 core。
### 2.3 兼容现有运行方式(渐进迁移)
- 保留现有的脚本式启动/准备流程Ray 起集群、准备模型/数据仍用 scripts
- 允许在迁移期提供薄 wrapper 兼容旧路径(减少一次性 break
---
## 3. 目标结构(按功能模块划分)
建议把 `src/mvp/` 重构为下面的“功能分层”:
```
src/mvp/
py/
argus/ # 顶层包(避免与 Ray 的 `ray` 包冲突)
__init__.py
core/ # 通用yaml/模型定义/工具函数(纯库)
__init__.py
yaml_io.py
ids.py # task_id / attempt_id 生成规则
ray/ # Ray Job 提交“核心库”(由现成 mvp_v11 迁移而来)
__init__.py
models.py # RayConfig, TaskSpec(解析), Attempt, enums
builders.py # build_training_argv (ppo/grpo/sft)
driver_entrypoint.py # 仍然作为 Ray job entrypointworker 上执行)
ray_job_tool.py # Ray Jobs SDK 封装submit/status/stop/logs
runtime_env.py # 统一 PYTHONPATH/runtime_env 组装逻辑
service/ # 服务层FastAPI + scheduler + sqlite应用
__init__.py
app.py
scheduler.py
db.py
config.py # service 相关配置读取(从 configs 读取)
ray_resources.py
cli/ # 命令行/SDK 提交入口(由现成 v1.1 run.py 迁移而来)
__init__.py
run.py # submit/status/logs/stop 等 action
server.py # uvicorn 入口(导入 argus.service.*
configs/
dev.yaml # RayConfig + ServiceConfig按层次组织、可扩展
prod.yaml # (可选)生产配置模板
taskspecs/ # 原 jobspecs/,改名 TaskSpecs训练语义规范
ppo.yaml
grpo.yaml
sft.yaml
README.md # TaskSpec 字段解释、示例
scripts/ # 宿主机脚本docker exec/compose 编排)
lib.sh
00_prereq_check.sh
01_up.sh / 02_down.sh
20_start_head.sh / 21_start_workers.sh
30_prepare_data_and_model.sh
40_submit_cli.sh # 通过 cli/run.py 提交 TaskSpec
60_start_api.sh # 启动 APIservice
61_stop_api.sh
62_status_api.sh
docker-compose.yaml # dev 环境 compose从 v1.1 迁移到这里,路径稳定)
README.md # 总入口文档(运行方式、目录约定)
```
### 3.1 关键点:库 vs 应用边界
- `argus.ray` 是唯一的 Ray submitter 库(替代当前 v1.1/v2.0 的 `mvp_v11` 双份拷贝)。
- `argus.service` 依赖 `argus.ray`,不反向依赖。
- `argus.cli` 依赖 `argus.ray`,用于脚本化提交/调试。
### 3.2 TaskSpecs vs RayConfig
- `taskspecs/*.yaml`描述训练任务语义参数workload、nnodes、n_gpus_per_node、数据/模型路径、训练步数等)。
- `configs/*.yaml`:描述 Ray 提交环境address、entrypoint_resources、runtime_env 以及 service 配置)。
---
## 4. 配置策略(重构后如何组织 configs
### 4.1 建议的 config 分层
把当前 `dev.yaml` 的内容明确分为两段(按模块名分段):
1) `ray:`RayConfig
- job server address
- shared_root`/private`
- entrypoint resources强制 driver 落 worker
- runtime_env env_varsHF cache、PYTHONPATH 注入策略)
2) `service:`ServiceConfig
- api host/port
- auth token_env
- sqlite db_path
- scheduler tick/max_running/retry_interval
示例(结构示意):
```yaml
ray:
address: "http://127.0.0.1:8265"
shared_root: "/private"
entrypoint_num_cpus: 1
entrypoint_resources:
worker_node: 1
runtime_env:
env_vars:
HF_ENDPOINT: "https://hf-mirror.com"
PYTHONUNBUFFERED: "1"
user_code_path: "/private/user/code"
service:
api:
host: "0.0.0.0"
port: 8080
auth:
token_env: "MVP_INTERNAL_TOKEN"
sqlite:
db_path: "/private/common/db/mvp.sqlite3"
scheduler:
tick_s: 5
retry_interval_s: 60
max_running_tasks: 1
```
> 迁移期可以支持“旧格式”v1.1 顶层字段 + v2: 段与“新格式”ray:/service: 两段)并存:解析时兼容读取,降低一次性改动风险;但最终以新格式为准。
---
## 5. 迁移路径(推荐分两阶段实施)
### 阶段 A先拷贝/迁移现成文件,再做最小调整(保持行为不变)
目标:不改功能、不改 API 行为。优先通过“拷贝/迁移现成文件 + 修正包引用/路径”完成重构,避免重头重写逻辑(降低出错概率)。
建议步骤:
1) 抽出 `src/mvp/py/argus/ray/`(由现成代码迁移)
-`src/mvp/v1.1/py/mvp_v11/` 迁移到 `src/mvp/py/argus/ray/`,并把它作为 submitter core 的唯一真源(不再保留一份复制品在其它目录)。
- 只做机械化调整:修正 import、修正默认路径常量例如 tool code path / working dir、修正 scripts 的调用路径。
2) 抽出 `src/mvp/py/argus/service/`(由现成代码迁移)
-`src/mvp/v2.0/py/mvp_v2/` 迁移到 `src/mvp/py/argus/service/`
- service 侧对 submitter 的依赖统一改为 `src/mvp/py/argus/ray/`(不再引用 `src/mvp/v2.0/py/mvp_v11/` 的复制品)。
3) CLI 统一入口:`src/mvp/py/argus/cli/run.py`(由现成代码迁移)
-`src/mvp/v1.1/py/run.py` 迁移到 `src/mvp/py/argus/cli/run.py`,保留 action 语义submit/status/logs/stop
- 仅调整 import 与默认路径使其指向新目录configs/taskspecs/py
4) scripts 合并(以 v1.1 为基、合入 v2 API
-`src/mvp/v1.1/scripts/` 迁移到 `src/mvp/scripts/`Ray 集群编排最成熟)。
-`src/mvp/v2.0/scripts/` 的 API 启停脚本合入 `src/mvp/scripts/`,并统一命名与默认路径。
5) docker-compose / mounts 稳定化(你已确认要迁移)
-`src/mvp/v1.1/docker-compose.yaml` 迁移为 `src/mvp/docker-compose.yaml`
- 容器内挂载统一:宿主机 `.../src/mvp/` → 容器 `/workspace/mvp/`(包含 `py/ configs/ taskspecs/ scripts/`)。
- runtime_env 的 `PYTHONPATH` 注入统一指向 `/workspace/mvp/py`(不再出现 `/workspace/mvp/v1.1/py``/workspace/mvp/v2/...`)。
阶段 A 完成标准:
- 原来 v1.1 的 CLI 提交方式仍可用(提交 PPO/GRPO/SFT
- v2 API 仍可用(队列、取消、日志)。
- 不再存在 `mvp_v11` 的重复目录。
### 阶段 B配置格式升级按模块两段+ TaskSpecs 更名落地
目标:把 jobspec 真正改名为 TaskSpec并把 config 升级为按模块两段(`ray:`/`service:`)清晰分层。
建议步骤:
- `jobspecs/``taskspecs/`,并更新 README/脚本引用。
- `dev.yaml` 从“顶层字段 + v2:”迁移为“ray:/service:”两段。
- 保留一段时间的兼容解析逻辑(读取旧格式时发出 warning
- 完成验证后删除旧版本目录:`src/mvp/v1.1/``src/mvp/v2.0/`(以及远端对应目录),确保新结构成为唯一入口。
阶段 B 完成标准:
- docs 里不再出现 “jobspec” 词汇,统一称 “TaskSpec”。
- `configs/dev.yaml` 分层清晰(`ray:`/`service:` 两段按模块名),服务与 Ray 的配置互不干扰。
---
## 6. 重构后的验证与执行方案(验收/回归)
### 6.1 本地(仓库内)静态验证
1) import / 入口检查(在容器环境)
- `python3 -c "from argus.ray.ray_job_tool import RayJobTool"`
- `python3 -c "from argus.service.app import create_app"`
2) 目录结构检查
- 确保 `src/mvp/py` 是唯一 python 代码根
- 确保 `taskspecs/``configs/``scripts/` 都在 `src/mvp/`
### 6.2 dev 环境argus@h1端到端验证
前置:
- 远端目录:`argus@h1:/home2/argus/infra/mvp/`(维持不变)
- 共享目录:`/home2/argus/infra/mvp/shared` 挂载到容器 `/private`
验证步骤(推荐顺序):
1) 重新拉起容器 + 启动 Ray
- `scripts/01_up.sh`
- `scripts/20_start_head.sh`head `--num-cpus=0 --num-gpus=0`
- `scripts/21_start_workers.sh`workers 带 `worker_node` 资源)
- `ray status` / `ray list nodes` 确认 head 无 GPU、workers 各 4 GPU
2) CLI 提交回归(等价 v1.1
- 提交 `taskspecs/sft.yaml`,确认成功
- 提交 `taskspecs/grpo.yaml`,确认成功
- 提交 `taskspecs/ppo.yaml`,确认成功
3) API 服务回归(等价 v2.0
- `scripts/60_start_api.sh`
- `POST /api/v2/tasks`raw YAML TaskSpec
- `GET /api/v2/tasks/{task_id}`:确认返回 `created_at/updated_at`
- `POST /api/v2/tasks/{task_id}:cancel`:确认任务 `state=CANCELED` 且 attempt `ray_status=STOPPED`(服务侧语义一致)
4) 队列行为回归
-`service.scheduler.max_running_tasks=1` 下:
- 连续提交两个 8-GPU 任务:第二个应保持 `QUEUED/PENDING_RESOURCES`,直到第一个结束后自动提交。
验收标准:
- 三种 workloadPPO/GRPO/SFT都能通过 CLI 跑通(或至少能正确提交到 Ray 并进入 RUNNING
- API 提交/查询/取消/日志正常。
- “cancel 后 state=CANCELED 但 attempt 仍 RUNNING”的不一致问题不再出现。
---
## 7. 风险与注意事项
1) PYTHONPATH 注入路径变化
- 当前 runtime_env 里有 `MVP_TOOL_CODE_PATH=/workspace/mvp/v1.1/py` 的历史路径假设;
- 重构后需统一为 `/workspace/mvp/py`,并确保所有容器都挂载到相同路径。
2) SQLite WAL 在 NFS 上的稳定性
- 目前 db 使用 WAL生成 `-wal/-shm`NFS 下可能有一致性风险;
- 可作为后续优化:检测 NFS 时退回 `journal_mode=DELETE` 或换成单机本地盘。
3) 渐进迁移的兼容期
- 迁移期可以短暂保留旧路径(例如 `src/mvp/v1.1``src/mvp/v2.0` 仅保留 README 指向新路径)以减少一次性断裂;但你已确认最终会删除这两个目录,因此需要在 scripts/文档同步更新后尽快清理。
---
## 8. 已确认约束(将按此实施)
你已明确:
1) `docker-compose.yaml` 必须迁移到 `src/mvp/` 根;重构完成后 `src/mvp/v1.1``src/mvp/v2.0` 都会删除。
2) `configs/dev.yaml` 升级为两段,按模块名分段:`ray:``service:`;并保持纯 YAML 风格(不混用 JSON inline map
3) TaskSpec 字段先保持与现有 v1.1 YAML 完全一致(仅目录与命名变化),避免引入不必要的不兼容。