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

6.4 KiB
Raw Blame History

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_typeconfigs/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>")
    • 状态:DEPLOYINGprobe 成功)→ RUNNING
  • 对于状态 DELETING 的 model
    • serve.delete(model_key)
    • 状态:DELETED

资源预检查:

  • 只需要预检查“本次变更模型”需要的 GPUnum_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.appServing API 输出):
    • _serve_model_public().endpoint.openai_base_url 改为 per-model 前缀
    • /api/v2/serve/models list/get 的 openai_base_url 语义调整(可返回“该模型的 base_url”列表里每条都有
  • argus.service.uiServing 页面):
    • “OpenAI /v1/models” 需要调整为“选择某个模型后打开该模型的 /v1/models”
    • 详情页 curl 示例使用 per-model base_url