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

13 KiB
Raw Blame History

MVP 代码结构重构方案(按功能模块划分)

背景:当前 src/mvp/ 下以 v1.1/v2.0/ 版本目录来组织代码。实际上 v2.0 是在 v1.1 的 Ray Jobs SDK 提交链路基础上扩展了服务层,并且为了让 v2.0 工作又对 v1.1 的 docker-compose.yamldev.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 APImvp_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 注入策略)
  1. 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 行为。优先通过“拷贝/迁移现成文件 + 修正包引用/路径”完成重构,避免重头重写逻辑(降低出错概率)。

建议步骤:

  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"
  1. 目录结构检查
  • 确保 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.shhead --num-cpus=0 --num-gpus=0
  • scripts/21_start_workers.shworkers 带 worker_node 资源)
  • ray status / ray list nodes 确认 head 无 GPU、workers 各 4 GPU
  1. CLI 提交回归(等价 v1.1
  • 提交 taskspecs/sft.yaml,确认成功
  • 提交 taskspecs/grpo.yaml,确认成功
  • 提交 taskspecs/ppo.yaml,确认成功
  1. API 服务回归(等价 v2.0
  • scripts/60_start_api.sh
  • POST /api/v2/tasksraw 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(服务侧语义一致)
  1. 队列行为回归
  • 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,并确保所有容器都挂载到相同路径。
  1. SQLite WAL 在 NFS 上的稳定性
  • 目前 db 使用 WAL生成 -wal/-shmNFS 下可能有一致性风险;
  • 可作为后续优化:检测 NFS 时退回 journal_mode=DELETE 或换成单机本地盘。
  1. 渐进迁移的兼容期
  • 迁移期可以短暂保留旧路径(例如 src/mvp/v1.1src/mvp/v2.0 仅保留 README 指向新路径)以减少一次性断裂;但你已确认最终会删除这两个目录,因此需要在 scripts/文档同步更新后尽快清理。

8. 已确认约束(将按此实施)

你已明确:

  1. docker-compose.yaml 必须迁移到 src/mvp/ 根;重构完成后 src/mvp/v1.1src/mvp/v2.0 都会删除。
  2. configs/dev.yaml 升级为两段,按模块名分段:ray:service:;并保持纯 YAML 风格(不混用 JSON inline map
  3. TaskSpec 字段先保持与现有 v1.1 YAML 完全一致(仅目录与命名变化),避免引入不必要的不兼容。