argus-cluster/specs/mvp/v3.8/v3.8_dev_plan.md

10 KiB
Raw Blame History

MVP v3.8 开发计划TDD细化版

目标:在 v3.7 基础上引入 Ray ServevLLM模型动态部署与管理多模型单 app并提供 WebUI + API 管理闭环。
约束(已确认):

  • 推理端口固定 8000Serve HTTP
  • 推理侧不接入现有 token 鉴权(对外 OpenAI endpoint 无鉴权)。
  • 对外 model_id 统一加前缀:<user_id>-<YYYYMMDDHHMM>-<suffix>(用户只填 suffix
  • LLMConfig.accelerator_typedev.yaml 读取dev/h1: H20)。

本计划按“测试先行 → 实现 → 回归”的节奏拆分到可验证粒度;每个 milestone 都能单独验收。


M0 - 基线与依赖探测(不改行为)

目的:确认 v3.7 baseline 稳定,并明确 Ray Serve LLM 依赖是否已具备(否则后续会卡在镜像/依赖)。

M0.1 本地回归

  • .venv/bin/python -m pytest 通过coverage ≥ 90%

M0.2 远端回归h1

  • src/mvp/scripts/run_all_v30_api.sh 可跑通(确认训练闭环未回退)

M0.3 head 容器内依赖探测(记录结论)

  • python3 -c "import ray; import ray.serve; print(ray.__version__)"
  • python3 -c "from ray.serve.llm import LLMConfig, build_openai_app; print('serve_llm_ok')"
  • 若失败(例如缺 gymnasium):记录缺失项,并在 M6 通过补齐 ray[llm] 解决

M0.4 配置探测

  • configs/dev.yaml 中存在:
    • serving.llm.accelerator_type: H20
    • serving.serve.http_port: 8000
    • serving.serve.proxy_location: HeadOnly

验收

  • baseline 无回退;依赖探测结论明确(可用/不可用)

M1 - ServingSpec解析/校验/宏替换/路径校验)(单测驱动)

目的先把“输入”这层彻底固化API/UI 复用),避免后期反复改 schema。

M1.1 新增/扩展数据模型

  • ServingSpec(输入)
    • model_idsuffix
    • model_source(支持 $HOME 宏)
    • num_replicasdefault=1
    • gpus_per_replicadefault=1
    • engine_kwargs(可选 dict先原样存 DB实现阶段再做白名单/黑名单)
  • ResolvedServingSpec(内部)
    • model_id_suffix
    • model_id_prefix(由平台生成:user_id-YYYYMMDDHHMM
    • model_id(对外:<prefix>-<suffix>
    • model_sourceresolved path

M1.2 规则(写成纯函数,便于测)

  • validate_model_id_suffix(suffix):长度/字符集限制(建议:[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}
  • $HOME 宏替换:$HOME$HOME/common/hf$HOME/common/datasets
  • 路径校验(强制本地路径):
    • 允许:/private/hf/.../private/users/<user_id>/...
    • 拒绝:..、空、其它用户路径、非 /private 路径
  • make_model_id_prefix(user_id, now_utc)YYYYMMDDHHMMUTC+ user_id

M1.3 单测(先写失败用例,再补实现)

  • test_serving_spec_validation.py
    • suffix 合法/非法
    • replicas/gpus 边界0、负数、小数、超大值按实现决定是否限制上限
  • test_serving_spec_paths.py
    • $HOME 替换正确
    • 越权路径返回 403/ValueError按接口层映射
    • /private/hf/private/users/<user> 均可
  • test_serving_model_id_prefix.py
    • 固定时间输入 → prefix 输出一致(避免时区/格式问题)

验收

  • 输入 spec 规则稳定;核心校验/替换均有单测覆盖

M2 - SQLite 表结构与 Db 接口(单测驱动)

目的Serving 的声明式状态必须持久化,可审计、可恢复。

M2.1 DB schema

  • serve_models
    • 主键:model_key(平台生成)
    • unique(user_id, model_id_suffix)(实现 upsert
    • 存储resolved spec包含 prefix/full model_id、resolved model_source
    • 状态:QUEUED/DEPLOYING/RUNNING/FAILED/DELETING/DELETED
    • error_summary
  • serve_eventsappend-only

M2.2 Db 方法

  • upsert_serve_model(user_id, spec_yaml, now) → (model_key, state)
  • list_serve_models(user_id, include_deleted=False, limit/offset?)
  • get_serve_model(model_key)
  • set_serve_model_state(model_key, state, error_summary=None)
  • append_serve_event(model_key, event_type, payload_json=None)
  • pick_next_runnable_serve_change()(给 reconciler 用)

M2.3 单测

  • test_db_serving.py
    • upsert 行为(同 suffix 更新不产生新 model_key 或产生新版本——此处需在实现前明确策略)
    • state 流转 + 事件记录
    • list 的过滤与排序(按 updated_at

验收

  • DB 行为可预测upsert/unique 语义确定并测试覆盖

M3 - Serving 管理 APIFastAPI单测驱动

目的:先把管理 API 跑通Ray Serve 先不接真实reconciler 之后再接)。

M3.1 API 路由(用户)

  • POST /api/v2/serve/modelsContent-Type: application/yaml
    • 入参ServingSpec YAML
    • 出参:{model_key,state}202
  • GET /api/v2/serve/models
    • 返回 items + openai_base_url=http://<host>:8000/v1
  • GET /api/v2/serve/models/{model_key}
    • 返回 model + resolved_spec_yaml + events分页可后置+ serve_status先空/占位)
  • PATCH /api/v2/serve/models/{model_key}JSON
    • 支持 num_replicas(最小闭环)
  • DELETE /api/v2/serve/models/{model_key}

M3.2 API 路由admin可选

  • GET /api/v2/serve/status(仅 admin token

M3.3 错误映射(必须测试)

  • YAML 解析失败400
  • spec 校验失败422
  • 越权路径403
  • 不存在 model_key404

M3.4 单测

  • test_app_serving_api.py
    • happy pathcreate → list → get → patch → delete
    • 多用户隔离:用户只能看到自己的 model
    • 错误码覆盖400/403/404/422

验收

  • API reference (v3.8_api.md) 中所有管理接口可返回预期结构Serve 未接入也能工作)

M4 - ServeClient 抽象 + LLMConfig builder单测驱动

目的:将“如何从 ResolvedServingSpec 构造 LLMConfig”固化并把 Ray Serve 的依赖隔离到 client 里,便于 mock。

M4.1 ServeClient 接口(可 mock

  • ensure_started(http_port=8000, proxy_location="HeadOnly")
  • apply_app(app_name, llm_configs)multi-model
  • get_status()serve.status 摘要)

M4.2 build_llm_config(resolved_spec, accelerator_type, runtime_env_defaults) 纯函数

  • 写入 LLMConfig.accelerator_type(来自 dev.yamlH20
  • deployment_config.num_replicas
  • engine_kwargs.tensor_parallel_size = gpus_per_replica
  • placement_group_config bundles 按 GPU 张数生成
  • runtime_env.env_vars 注入(至少包含 HF cache + HF_HUB_OFFLINE=1

M4.3 单测

  • test_llm_config_builder.py
    • gpus_per_replica=1/2/4 → tensor_parallel_size 与 bundles 数量正确
    • accelerator_type 注入正确
    • runtime_env 含 HF_HUB_OFFLINE 等关键 env

验收

  • 从平台 spec 到 Ray Serve LLMConfig 的映射规则稳定,有单测锁定

M5 - Serving Reconciler状态机 + 资源预检查)(单测驱动)

目的实现声明式对齐DB → Serve同时提供可解释的 QUEUED/FAILED 状态。

M5.1 状态机(最小闭环)

  • QUEUED:等待 apply
  • DEPLOYING:已触发 apply等待 Serve running/healthy
  • RUNNINGServe status running
  • FAILEDapply 或 status 失败(写 error_summary + event
  • DELETING:等待从 app 中移除
  • DELETED:完成删除(可选保留记录)

M5.2 资源预检查

  • needed_total_gpus = sum(num_replicas*gpus_per_replica)(最小可用预检查)
  • ray.available_resources()["GPU"](或更稳健的 per-node 统计)不足时:
    • 保持 QUEUED
    • 记录 PENDING_RESOURCES event

M5.3 reconcile 策略multi-model app

  • tick 读取 active models构建全量 llm_configs
  • 处理 deleting从 configs 中移除对应 model再 apply

M5.4 单测mock ServeClient + mock ray resources

  • test_serving_reconciler.py
    • 新增模型apply_app 被调用state 进入 DEPLOYING
    • 删除模型apply_app configs 不包含该模型
    • GPU 不足:不 applystate 仍 QUEUEDevent 写入
    • apply 抛异常state FAILEDerror_summary 写入

验收

  • reconciler 行为在纯单测环境可验证;失败可解释

M6 - 真实集成h1Ray Serve 启动 + 推理闭环E2E

目的:在 dev/h1 环境真正跑通:部署模型 → /v1/models 可见 → chat/completions 成功 → 删除后消失。

M6.1 compose/端口

  • src/mvp/docker-compose.yamlray_head 增加 8000:8000

M6.2 镜像依赖(若 M0 发现缺失)

  • argus-ray-node 镜像中补齐 ray[serve,llm](版本与现有 Ray 对齐,避免升级 Ray 导致不兼容)
    • 推荐优先补齐 ray[llm](包含 ray.serve.llm 依赖闭包,如 gymnasium),再按需补 ray[serve]
    • 验证点:python3 -c "from ray.serve.llm import LLMConfig, build_openai_app; print('serve_llm_ok')"

M6.3 E2E 脚本(幂等)

  • 新增 scripts/run_all_v38_serving.sh
    • 起 compose确保 Serve 端口可用)
    • 起 API
    • 创建 user + token
    • POST /api/v2/serve/models 创建 1GPU 模型
    • 轮询模型 state 到 RUNNING
    • curl http://127.0.0.1:8000/v1/models 验证包含 <prefix>-<suffix>
    • curl http://127.0.0.1:8000/v1/chat/completions 进行最小推理
    • DELETE /api/v2/serve/models/{model_key} 下线
    • 再轮询 /v1/models 不包含

验收

  • E2E 可重复跑通(至少两次连续跑不需要人工清理)

M7 - WebUIServing 页面)(单测驱动)

目的:给用户可视化的模型管理页面(最小必要功能)。

M7.1 页面

  • Sidebar 增加 Serving
  • /ui/serving:列表 + 状态 + 操作delete/scale
  • /ui/serving/newYAML 输入 + submit
  • /ui/serving/{model_key}详情resolved spec、events、OpenAI 调用示例)

M7.2 单测

  • test_ui_serving.py:路由 200、关键链接存在、包含 openai_base_url=8000

验收

  • WebUI 覆盖 create/list/detail/scale/delete 的主链路

M8 - 文档与验收用例(交付)

目的:给用户/运维一套可复用的运行方式与排障路径。

  • 更新 specs/mvp/v3.8/v3.8_progress.md(按 milestone 记录)
  • 补充 README可选端口说明、推理 API 无鉴权警示、模型路径约定
  • 验收清单checklist
    • 单测通过
    • h1 E2E 通过
    • UI 主链路可操作