6.5 KiB
6.5 KiB
MVP v3.8(变更)开发计划:Per-Model Serve App(TDD)
目标:按
specs/mvp/v3.8/v3.8_per_model_app.md将 v3.8 从“单 app 多模型(全量重建)”改为“每个模型一个 Ray Serve app + 独立 route_prefix”,实现增删/缩放某个模型不触发其它模型重启与重调度。
约束与结论
- 推理端口固定:
8000 - 推理 endpoint 不做鉴权
model_id前缀规则:<user_id>-<YYYYMMDDHHMM>-<suffix>LLMConfig.accelerator_type由configs/dev.yaml决定(dev/h1:H20)- 路由方案(本迭代固定):
app_name = model_keyroute_prefix = /serve/<model_key>openai_base_url = http://<host>:8000/serve/<model_key>/v1
非目标(明确不做)
- 不提供统一
/v1的“跨模型聚合路由”(如要,需要额外 router 层;可在后续迭代做) - 不改 ServingSpec 语义(输入仍为
model_id/model_source/num_replicas/gpus_per_replica/engine_kwargs)
M0 - 基线回归与分支保护
目的:确保切换架构前训练/现有功能不回退。
测试
- 本地:
.venv/bin/python -m pytest全绿(coverage ≥ 90%)
验收
- 基线可用;进入 M1
M1 - API 输出与 endpoint 语义调整(单测驱动)
目的:API/DB/前端都统一 per-model 的 openai_base_url 语义;避免 UI/脚本继续使用 /v1 根路径。
变更点
GET /api/v2/serve/models:- 保持返回
items[],但每个 item 的endpoint.openai_base_url必须是 per-model base url openai_base_url(列表层级字段)处理策略二选一:- A(推荐):移除该字段(breaking,需同步 UI/脚本)
- B(兼容):保留但改为
null或提示字符串(不再保证可用)
- 保持返回
GET /api/v2/serve/models/{model_key}:model.endpoint.openai_base_url改为 per-model base url
单测(先写)
- 更新/新增
src/mvp/py/tests/test_app_serving_api.py- 断言
endpoint.openai_base_url包含/serve/<model_key>/v1 - 断言多条 models 的 base_url 不相同(随 model_key 变化)
- 断言
实现
- 更新
src/mvp/py/argus/service/app.py:_serve_model_public()的endpoint.openai_base_url拼接 per-model prefix- 如选择移除/调整 list 层的
openai_base_url,同步实现
验收
- API 单测通过;返回结构可被 UI/脚本消费
M2 - ServeClient 扩展(delete_app)+ Reconciler 改造成 per-model(单测驱动)
目的:核心行为变更:每次 tick 只部署/删除一个模型对应的 app,不重建全量 app。
变更点(行为)
QUEUED:- 对该
model_key执行serve.run(app, name=model_key, route_prefix=/serve/<model_key>) - 状态:
DEPLOYING → RUNNING
- 对该
DELETING:- 对该
model_key执行serve.delete(model_key) - 状态:
DELETED
- 对该
- 资源预检查从“全量 needed_total_gpus”改为“本次变更模型所需 GPU”
单测(先写)
- 更新
src/mvp/py/tests/test_serving_reconciler.pycreate A后,reconciler 只apply_app(app_name=A.model_key, route_prefix=/serve/A)create B后,reconciler 只apply_app(app_name=B.model_key, route_prefix=/serve/B)(不再对 A 触发 apply)delete B后,reconciler 只delete_app(B.model_key)(不触发 A)- GPU 不足时:保持
QUEUED且 event=SERVE_PENDING_RESOURCES
实现
src/mvp/py/argus/service/serve_client.py- 增加
delete_app(app_name: str)(封装serve.delete)
- 增加
src/mvp/py/argus/service/serving_reconciler.py- 移除“全量 app apply”逻辑
- 每个 model_key 独立部署:
app_name=model_key、route_prefix=/serve/<model_key> - 删除路径走
delete_app
验收
- reconciler 单测全绿;逻辑可解释(events/state 正确)
M3 - WebUI Serving 页面适配 per-model base_url(单测驱动)
目的:用户从 UI 复制的示例命令必须可用;不再指向根 /v1。
变更点
/ui/serving列表:- “OpenAI /v1/models”按钮改为:
- A:移除(因为没有聚合
/v1/models) - B:保留但文案改为“OpenAI base 需进入详情页”
- A:移除(因为没有聚合
- “OpenAI /v1/models”按钮改为:
/ui/serving/{model_key}详情页:curl示例使用 per-modelopenai_base_url- 增加一键打开:
/serve/<model_key>/v1/models
单测(先写)
- 更新/新增
src/mvp/py/tests/test_ui_serving.py- 断言页面包含
/serve/前缀 - 断言详情页示例里包含
/serve/<model_key>/v1/chat/completions
- 断言页面包含
实现
src/mvp/py/argus/service/ui.py更新 Serving UI
验收
- UI 单测全绿;页面内容与 API 语义一致
M4 - E2E 脚本更新(v3.8 serving)
目的:在 dev/h1 一键验证 per-model app:A/B 增删不互相影响,且推理可用。
变更点
- 更新
src/mvp/scripts/run_all_v38_serving.sh:/v1/models与chat/completions均改用 per-model base_url(/serve/<model_key>/v1)- 增加“隔离验证”步骤:
- 部署 A → 记录 A 的 serve replica actor_id/node_id
- 部署 B → 再次记录 A 的 actor_id/node_id,必须一致
- 删除 B → 再次记录 A 的 actor_id/node_id,必须一致
- 最后删除 A
验收
- E2E 脚本能跑通且输出明确断言(一致/不一致)
M5 - h1 端到端验证与回归
目的:确认实际 Ray Serve 行为满足“其它模型不滚动更新”的核心目标。
操作
- 同步代码到:
argus@h1:/home2/argus/infra/mvp/src/mvp - 重启 API:
scripts/61_stop_api.sh && scripts/60_start_api.sh - 执行:
MVP_INTERNAL_TOKEN=... scripts/run_all_v38_serving.sh
验收标准(必须满足)
- 新增/删除 B 时,A 的
LLMServerreplica actor_id/node_id 不变 - A/B 的 OpenAI endpoint 均可完成
chat/completions - 删除 B 后 A 仍可推理
M6 - 文档与迁移说明
目的:明确“路由语义变化”和“如何使用”。
- 更新
src/mvp/README.md:- 新增 per-model base_url 说明(
/serve/<model_key>/v1) - 提示不再提供聚合
/v1/models
- 新增 per-model base_url 说明(
- 更新
specs/mvp/v3.8/v3.8_progress.md:- 记录 per-model app 变更与验收结论
风险与缓解
- 风险:旧
argus_llm_app残留- 缓解:在 E2E/迁移步骤里增加一次 best-effort
serve.delete("argus_llm_app")(可选)
- 缓解:在 E2E/迁移步骤里增加一次 best-effort
- 风险:用户仍按旧方式访问
/v1- 缓解:UI/文档/脚本统一切换到 per-model base_url,并在列表页给出明显提示