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

190 lines
6.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# v3.8 方案补充:每个模型一个 Ray Serve App隔离增删影响
## 背景与问题复现
当前 v3.8 的实现采用 **单 application + 多模型** 的方式:
- 服务层每次 reconcile 都会构造“全量 llm_configs”并调用一次 `serve.run(app, name="argus_llm_app", route_prefix="/")`
- **新增/删除一个模型**会触发对同一个 app 的“整体更新”
- Ray Serve 在 app 更新时会对该 app 内的 deployments/replicas 做滚动更新与重新调度
因此你在 Ray Dashboard 中观察到:
- 添加/删除一个模型时,其他模型的 Serve deployment 也进入更新状态
- 内存/显存占用重新变化,甚至出现 GPU 卡位变化replica 重新调度到不同 node/GPU
这与“其他未变更 model 不受影响”的期望不一致。
---
## 目标
将 serving 架构调整为:
- **每个模型一个 Serve App独立 app name**
- 每个模型一个独立 `route_prefix`
- 新增/删除/缩放某个模型只更新该模型对应的 app不影响其他模型 app
约束保持不变:
- 推理端口固定 `8000`
- 推理侧不接入现有 token 鉴权OpenAI endpoint 无鉴权)
- `model_id` 前缀规则:`<user_id>-<YYYYMMDDHHMM>-<suffix>`
- `LLMConfig.accelerator_type``configs/dev.yaml` 配置dev/h1: `H20`
---
## 总体设计
### 1) 命名与路由
为每个 model 生成:
- `app_name`:建议直接使用 `model_key`(天然唯一且 URL-safe例如
- `app_name = "mvp2-alice-serve-20260106-060203-aad8"`
- `route_prefix`:建议使用 model_key避免 model_id 中的 `.``_` 等带来的 URL/编码歧义:
- `route_prefix = f"/serve/{model_key}"`
于是该模型的 OpenAI base url 为:
- `openai_base_url = http://<host>:8000/serve/<model_key>/v1`
说明:
- 仍然是 **OpenAI-compatible**,只是 base_url 不再是根路径 `/v1`,而是每个模型一个前缀。
- 这样可以做到“每个模型的 OpenAI endpoint 互不影响”,也更容易做按模型的观测/下线。
### 2) 运行方式Ray Serve
单模型 app 的创建/更新:
- `app = build_openai_app({"llm_configs":[LLMConfig(...)]})`
- `serve.run(app, name=app_name, route_prefix=route_prefix)`
单模型 app 的删除:
- `serve.delete(app_name)`
关键点:
- **更新/删除只作用于对应 app_name**;其它 app 不会被 serve.run “整体重建”触发滚动更新。
### 3) 服务层Scheduler/Reconciler改造点高层
现状:`ServingReconciler.tick()` 每次对“全量模型集合” apply 一次 app。
目标:改成按 model_key 的“局部 reconcile”
- 对于状态 `QUEUED` 的 model
- 只构建该 model 的 `LLMConfig`
- `serve.run(app, name=model_key, route_prefix="/serve/<model_key>")`
- 状态:`DEPLOYING`probe 成功)→ `RUNNING`
- 对于状态 `DELETING` 的 model
- `serve.delete(model_key)`
- 状态:`DELETED`
资源预检查:
- 只需要预检查“本次变更模型”需要的 GPU`num_replicas * gpus_per_replica`
- 不需要把其他模型资源都算入 needed_total_gpus因为不再重建全量 app
### 4) API/UI 返回的 endpoint 结构
现状 API 返回:
- `endpoint.openai_base_url = http://<host>:8000/v1`
- `endpoint.model = <model_id>`
建议改为(字段不变,值变化):
- `endpoint.openai_base_url = http://<host>:8000/serve/<model_key>/v1`
- `endpoint.model = <model_id>`(保持)
UI 的示例 curl 也应使用上面的 base_url。
---
## 行为变化与兼容性影响
### 1) `/v1/models` 聚合能力变化(重要)
采用“每模型一个 route_prefix”后
- `http://<host>:8000/v1/models` **不再是“所有模型的总览”**(除非我们再提供一个聚合层)
- 每个模型的 models list 在它自己的前缀下:
- `http://<host>:8000/serve/<model_key>/v1/models`
如果仍然希望保留一个统一入口(可选增强,非本方案必做):
- 额外引入一个“稳定不重建”的 **OpenAI Router**(可以是:
- FastAPI(8080) 侧做反向代理;或
- 一个单独 Ray Serve app 只负责路由,不随模型变更重建)
- Router 读取 SQLite/内存缓存的 model 映射:
- `model_id -> route_prefix`
-`/v1/chat/completions` 转发到对应 model 的 prefix
这可以作为 v3.9+ 的增强项v3.8 的核心目标是“变更隔离”,优先保证稳定性。
### 2) 资源与调度稳定性
改为 per-app 后:
- 新增模型 B 不再引起模型 A 的 replica 重新调度 → **A 的 GPU/内存占用更稳定**
- 删除模型 B 也不会触发 A 的滚动更新
但仍需注意:
- 如果 Ray 集群发生节点故障/资源回收Serve 本身仍可能重启个别 replica这是系统层行为
---
## 验证与验收流程(建议)
### A. 功能验收API/UI
1. 通过 UI/或 API 创建模型 A等待 RUNNING
2. 记录 A 的:
- `model_key_A`
- `endpoint.openai_base_url_A`
3. 再创建模型 B等待 RUNNING
4. 确认:
- A 的 endpoint 仍可用(对 A 的 base_url 发 chat completion
- B 的 endpoint 可用
5. 删除模型 B确认
- B endpoint 404/不可用
- A endpoint 仍可用
### B. “不影响其它模型”的强验证Ray actor 级别)
在 Ray 上抓取 A 对应 `LLMServer` replica 的 actor_id/node_id
- 创建 B 前:`actor_id_A_before`
- 创建 B 后:`actor_id_A_after`
- 删除 B 后:`actor_id_A_after_delete`
预期:
- `actor_id_A_before == actor_id_A_after == actor_id_A_after_delete`
(允许 `LLMRouter` 变化,但 **LLMServer for A 不应变化**
---
## 需要修改的代码点(清单级)
> 这里只列“改哪里”,不展开具体实现(实现时按 TDD 补单测)。
- `argus.service.serving_reconciler`
- 从“全量 apply 单 app”改为“按 model_key 局部 apply/delete 单 app”
- GPU 预检查改为 per-model
- `argus.service.serve_client`
- 增加 `delete_app(app_name)`(封装 `serve.delete(app_name)`
- `apply_app` 传入 `app_name/route_prefix`(已存在,但将不再传固定 app_name
- `argus.service.app`Serving API 输出):
- `_serve_model_public().endpoint.openai_base_url` 改为 per-model 前缀
- `/api/v2/serve/models` list/get 的 openai_base_url 语义调整(可返回“该模型的 base_url”列表里每条都有
- `argus.service.ui`Serving 页面):
- “OpenAI /v1/models” 需要调整为“选择某个模型后打开该模型的 /v1/models”
- 详情页 curl 示例使用 per-model base_url