13 KiB
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 entrypoint(worker 上执行)
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 # 启动 API(service)
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 的内容明确分为两段(按模块名分段):
ray:(RayConfig)
- job server address
- shared_root(
/private) - entrypoint resources(强制 driver 落 worker)
- runtime_env env_vars(HF cache、PYTHONPATH 注入策略)
service:(ServiceConfig)
- api host/port
- auth token_env
- sqlite db_path
- scheduler tick/max_running/retry_interval
示例(结构示意):
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 行为。优先通过“拷贝/迁移现成文件 + 修正包引用/路径”完成重构,避免重头重写逻辑(降低出错概率)。
建议步骤:
-
抽出
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 的调用路径。
- 将
-
抽出
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/的复制品)。
- 将
-
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)。
- 将
-
scripts 合并(以 v1.1 为基、合入 v2 API)
- 将
src/mvp/v1.1/scripts/迁移到src/mvp/scripts/(Ray 集群编排最成熟)。 - 将
src/mvp/v2.0/scripts/的 API 启停脚本合入src/mvp/scripts/,并统一命名与默认路径。
- 将
-
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 本地(仓库内)静态验证
- import / 入口检查(在容器环境)
python3 -c "from argus.ray.ray_job_tool import RayJobTool"python3 -c "from argus.service.app import create_app"
- 目录结构检查
- 确保
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
验证步骤(推荐顺序):
- 重新拉起容器 + 启动 Ray
scripts/01_up.shscripts/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
- CLI 提交回归(等价 v1.1)
- 提交
taskspecs/sft.yaml,确认成功 - 提交
taskspecs/grpo.yaml,确认成功 - 提交
taskspecs/ppo.yaml,确认成功
- API 服务回归(等价 v2.0)
scripts/60_start_api.shPOST /api/v2/tasks(raw YAML TaskSpec)GET /api/v2/tasks/{task_id}:确认返回created_at/updated_atPOST /api/v2/tasks/{task_id}:cancel:确认任务state=CANCELED且 attemptray_status=STOPPED(服务侧语义一致)
- 队列行为回归
- 在
service.scheduler.max_running_tasks=1下:- 连续提交两个 8-GPU 任务:第二个应保持
QUEUED/PENDING_RESOURCES,直到第一个结束后自动提交。
- 连续提交两个 8-GPU 任务:第二个应保持
验收标准:
- 三种 workload(PPO/GRPO/SFT)都能通过 CLI 跑通(或至少能正确提交到 Ray 并进入 RUNNING)。
- API 提交/查询/取消/日志正常。
- “cancel 后 state=CANCELED 但 attempt 仍 RUNNING”的不一致问题不再出现。
7. 风险与注意事项
- PYTHONPATH 注入路径变化
- 当前 runtime_env 里有
MVP_TOOL_CODE_PATH=/workspace/mvp/v1.1/py的历史路径假设; - 重构后需统一为
/workspace/mvp/py,并确保所有容器都挂载到相同路径。
- SQLite WAL 在 NFS 上的稳定性
- 目前 db 使用 WAL(生成
-wal/-shm),NFS 下可能有一致性风险; - 可作为后续优化:检测 NFS 时退回
journal_mode=DELETE或换成单机本地盘。
- 渐进迁移的兼容期
- 迁移期可以短暂保留旧路径(例如
src/mvp/v1.1、src/mvp/v2.0仅保留 README 指向新路径)以减少一次性断裂;但你已确认最终会删除这两个目录,因此需要在 scripts/文档同步更新后尽快清理。
8. 已确认约束(将按此实施)
你已明确:
docker-compose.yaml必须迁移到src/mvp/根;重构完成后src/mvp/v1.1、src/mvp/v2.0都会删除。configs/dev.yaml升级为两段,按模块名分段:ray:与service:;并保持纯 YAML 风格(不混用 JSON inline map)。- TaskSpec 字段先保持与现有 v1.1 YAML 完全一致(仅目录与命名变化),避免引入不必要的不兼容。