10 KiB
10 KiB
MVP v3.8 开发计划(TDD,细化版)
目标:在 v3.7 基础上引入 Ray Serve(vLLM)模型动态部署与管理(多模型单 app),并提供 WebUI + API 管理闭环。
约束(已确认):
- 推理端口固定
8000(Serve HTTP)。- 推理侧不接入现有 token 鉴权(对外 OpenAI endpoint 无鉴权)。
- 对外
model_id统一加前缀:<user_id>-<YYYYMMDDHHMM>-<suffix>(用户只填 suffix)。LLMConfig.accelerator_type从dev.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: H20serving.serve.http_port: 8000serving.serve.proxy_location: HeadOnly
验收:
- baseline 无回退;依赖探测结论明确(可用/不可用)
M1 - ServingSpec(解析/校验/宏替换/路径校验)(单测驱动)
目的:先把“输入”这层彻底固化(API/UI 复用),避免后期反复改 schema。
M1.1 新增/扩展数据模型
ServingSpec(输入)model_id(suffix)model_source(支持$HOME宏)num_replicas(default=1)gpus_per_replica(default=1)engine_kwargs(可选 dict,先原样存 DB;实现阶段再做白名单/黑名单)
ResolvedServingSpec(内部)model_id_suffixmodel_id_prefix(由平台生成:user_id-YYYYMMDDHHMM)model_id(对外:<prefix>-<suffix>)model_source(resolved 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):YYYYMMDDHHMM(UTC)+ 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_events(append-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 管理 API(FastAPI)(单测驱动)
目的:先把管理 API 跑通,Ray Serve 先不接真实(reconciler 之后再接)。
M3.1 API 路由(用户)
POST /api/v2/serve/models(Content-Type: application/yaml)- 入参:ServingSpec YAML
- 出参:
{model_key,state}(202)
GET /api/v2/serve/models- 返回 items +
openai_base_url=http://<host>:8000/v1
- 返回 items +
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_key:404
M3.4 单测
test_app_serving_api.py- happy path:create → 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.yaml:H20) deployment_config.num_replicasengine_kwargs.tensor_parallel_size = gpus_per_replicaplacement_group_configbundles 按 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:等待 applyDEPLOYING:已触发 apply,等待 Serve running/healthyRUNNING:Serve status runningFAILED:apply 或 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_RESOURCESevent
- 保持
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 不足:不 apply;state 仍 QUEUED;event 写入
- apply 抛异常:state FAILED;error_summary 写入
验收:
- reconciler 行为在纯单测环境可验证;失败可解释
M6 - 真实集成(h1):Ray Serve 启动 + 推理闭环(E2E)
目的:在 dev/h1 环境真正跑通:部署模型 → /v1/models 可见 → chat/completions 成功 → 删除后消失。
M6.1 compose/端口
src/mvp/docker-compose.yaml:ray_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 - WebUI(Serving 页面)(单测驱动)
目的:给用户可视化的模型管理页面(最小必要功能)。
M7.1 页面
- Sidebar 增加 Serving
/ui/serving:列表 + 状态 + 操作(delete/scale)/ui/serving/new:YAML 输入 + 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 主链路可操作