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

14 KiB
Raw Blame History

MVP v3.8 详细设计方案Ray ServevLLM模型动态部署与管理

基线:当前已具备 v3.7 能力(训练平台 + W&B + SFTPGo + WebUI/API + Ray stateless pool训练侧默认 rollout=vllm
v3.8 目标:在同一套 Ray 集群上,引入 Ray Serve LLM后端 vLLM 的模型推理服务能力,并通过 WebUI/API 动态管理模型生命周期。

0. 需求范围(来自 requirements.md

  1. 通过 Ray Serve后端 vLLM动态拉起 LLM支持多模型 application 部署
  2. 默认一个模型 1 个 replica用户可配置多个
  3. 用户可删除(下线)模型
  4. 用户可指定模型使用几张 GPU
  5. WebUI 可配置、查看模型列表、查看详情
  6. 模型路径可用 common也可用 user 路径(本地路径)

1. 总体架构

1.1 组件关系

v3.8 在现有“训练平台”之上新增一个 Serving 子系统

  • API server现有
    • 新增 Serving API模型部署/删除/扩缩容/状态)
    • 新增 Serving 后台线程reconciler周期性对齐 DB 与 Ray Serve 实际状态
  • SQLite现有
    • 新增 serve_modelsserve_events 等表,保存声明式配置与状态
  • Ray 集群(现有 stateless pool
    • 复用现有 head/worker 容器
    • 在集群内启动 Ray Servecontroller + proxy + deployments
  • Ray Serve LLM新增
    • 通过 ray.serve.llm.build_openai_app 构建一个 OpenAI-compatible app
    • app 内包含多个 LLMConfig(每个对应一个模型)

1.2 为什么选择“单个 multi-model application”

Ray Serve 支持 multi-app但在 dev/docker 场景下多个 app 的 route_prefix 管理更复杂;同时 requirements 要求“多模型 application 部署”,因此 v3.8 采用:

  • 一个固定的 appargus_llm_app(名字可配置)
  • route_prefix 固定为 /(对外暴露 /v1/... OpenAI 接口)
  • 每个模型对应一个 LLMConfig,通过 model_id 区分(即 OpenAI API 里的 model 字段)

这样对用户而言最直观:

  • base_url 固定:http://<host>:8000/v1
  • model= 选择不同模型(/v1/models 自动列出)

2. Ray Serve 部署策略dev/h1 约束)

2.1 HTTP 入口端口与 docker compose

Ray Serve 默认 HTTP 端口是 8000。v3.8 约定:

  • head 容器 映射 8000:8000
  • API server 仍在 8080
  • Ray Dashboard 在 8265

原因:在单机多容器 docker 环境里,如果让 proxy “每个节点都起”,会出现多个容器同时想绑定同一个 host 端口的问题(不可行)。因此 v3.8 推荐:

  • Serve proxy 位置设为 HeadOnly(只在 head 上提供 HTTP 入口)
  • GPU replica 仍运行在 worker 上proxy 只转发,不跑推理)

需要注意:

  • Serve 的 HTTP 配置host/port/proxy_locationRay 集群全局配置,启动后无法动态修改,因此应当在平台启动时一次性设定并持久化。
  • proxy Actor 需要 CPU 资源head 节点的 num-cpus=0 策略可能需要在 v3.8 做小幅调整(例如给 head 保留少量 CPU但仍通过 entrypoint_resources 确保训练 driver 不会被调度到 head。

2.1.1 compose 预期改动v3.8 实现时落地)

  • src/mvp/docker-compose.yamlray_head新增
    • ports: - "8000:8000"

worker 容器不暴露 8000避免 host 端口冲突),由 head proxy 统一对外提供入口。

2.2 启动/配置方式Python SDK 优先)

v3.8 采用 Ray Serve Python SDK

  • ray.init(address="auto")
  • serve.start(proxy_location="HeadOnly", http_options={"host":"0.0.0.0","port":8000})(一次性全局配置)
  • serve.run(app, name=<app_name>, route_prefix="/")
  • serve.delete(name=<app_name>)(必要时)
  • serve.status() 查询集群/应用状态

理由:

  • 避免在平台内部引入额外 REST client 依赖(并减少跨版本 REST schema 不稳定风险)
  • API server 本身运行在 head 容器内,可直接 ray.init(address="auto") 连接现有集群

Ray Dashboard 暴露 Serve REST APIPUT /api/serve/applications/ 等)可作为备选方案,但 v3.8 先不以它为主通路。

2.3 依赖与镜像假设

v3.8 依赖:

  • ray[serve]Serve Controller/Proxy
  • ray[llm]Ray Serve LLM 的 ray.serve.llm 模块)
  • vLLM推理引擎

由于 v3.7 已切换到 verlai/verl:vllm011.latest,预期镜像内包含 vLLMray.serve.llm 是否开箱即用需要在实现阶段确认。 若缺失v3.8 将在 argus-ray-node 镜像构建阶段补充 pip install "ray[serve,llm]"(或按官方建议的最小依赖)并做版本锁定。

2.4 Serving 配置dev.yaml

v3.8 新增一段 serving 配置,至少包含:

serving:
  serve:
    http_port: 8000              # 固定 8000
    proxy_location: HeadOnly     # dev/docker 下推荐
  llm:
    accelerator_type: H20        # dev 环境填写 H20对应 ray.serve.llm.LLMConfig.accelerator_type

说明:

  • accelerator_type 是 Ray Serve LLM 的 LLMConfig.accelerator_type 字段,用于表达“该模型运行在哪类加速卡上”。在 dev/h1 环境我们固定为 H20
  • v3.8 不把 accelerator_type 暴露给普通用户编辑(避免误配);由部署环境配置统一决定。

3. 模型配置与资源映射

3.1 关键配置对象:ray.serve.llm.LLMConfig

每个模型部署由一个 LLMConfig 描述关键字段v3.8 用到的子集):

  • model_loading_config
    • model_id: 对外展示/请求时用的模型名(唯一 key
    • model_source: HF repo id / S3 / local path
  • accelerator_type
    • dev.yamlserving.llm.accelerator_type 读取dev/h1: H20
  • deployment_config
    • num_replicasautoscaling_configv3.8 先用固定 num_replicas
    • ray_actor_optionsCPU/资源约束)
  • engine_kwargs
    • vLLM 相关参数(max_model_lengpu_memory_utilization 等)
  • placement_group_config
    • 控制 vLLM engine workers 使用的资源 bundle用于多 GPU / 跨节点)
  • runtime_env
    • 注入 HF cache、离线开关等环境变量

3.2 GPU 张数gpus_per_replica如何落到 LLMConfig

v3.8 把用户输入的:

  • gpus_per_replica = N

映射为:

  • engine_kwargs.tensor_parallel_size = N(单机/跨机张量并行Ray Serve LLM 官方示例写法)
  • placement_group_config = {"bundles": [{"GPU": 1, "CPU": <cpu_per_gpu>}] * N, "strategy": "PACK"}

并在 engine_kwargs 中保留 vLLM 其他参数(max_model_lengpu_memory_utilization 等)。

兼容性说明Ray Serve LLM/Serve LLM 仍处于快速演进阶段v3.8 会以我们线上实际 Ray 版本为准做最小适配与回归测试。

3.2.1 跨节点场景N > 单机 GPU

Ray Serve LLM 默认使用 PACK 策略,优先把 GPU worker 放在尽量少的节点上;如果单机放不下,会自动 spill 到其它节点从而支持跨节点张量并行TP部署。

3.3 replica 数num_replicas

v3.8 默认:

  • num_replicas = 1

允许用户在 UI 中设置为 >=1
多 replica 会线性消耗 GPUnum_replicas * gpus_per_replica),需要做资源预检查。

3.4 模型路径与宏替换common / user

v3.8 支持两类模型来源:

  1. common
  • 典型为 /private/hf/...(共享 HF cache / snapshot
  1. user
  • /private/users/<user_id>/models/...
  • 以及用户训练输出(例如 jobs/<sid>/checkpoints/.../huggingface

为保证 UI 易用,沿用平台已有的宏语义:

  • $HOME/private/users/<user_id>
  • $HOME/common/hf/private/hf

并进行路径校验:

  • 允许前缀:/private/hf/private/users/<user_id>/
  • 拒绝:越权访问其他用户目录、或访问系统敏感路径

3.5 离线模式(避免 HF mirror 429

v3.7 训练侧已验证 HF_HUB_OFFLINE=1 的必要性。v3.8 Serving 侧同样默认注入:

  • HF_HOME=/private/hf
  • HUGGINGFACE_HUB_CACHE=/private/hf/hub
  • TRANSFORMERS_CACHE=/private/hf/transformers
  • HF_HUB_OFFLINE=1
  • HF_ENDPOINT=https://hf-mirror.com(可保留,但离线模式下不应触发网络)

并建议用户在 ServingSpec 中尽量填写 local path 作为 model_source,而不是直接 repo id。

4. 平台数据模型SQLite

新增两张主表:

4.1 serve_models

每一行代表一个“声明式模型部署”:

  • model_key(平台内部唯一 ID便于重命名/去重)
  • user_id
  • model_id(对外 OpenAI model 名称,要求 per-app 唯一)
  • model_source(本地路径或 repo id存 resolved 后的结果)
  • num_replicas
  • gpus_per_replica
  • engine_kwargs_json(可选)
  • stateQUEUED | DEPLOYING | RUNNING | FAILED | DELETING | DELETED
  • serve_app_name(默认 argus_llm_app
  • created_at / updated_at
  • error_summary

4.2 serve_events

记录关键事件与排障信息(类似 task_events

  • id
  • model_key
  • event_typeDEPLOY_REQUESTED/DEPLOY_APPLIED/STATUS_SYNC/DELETE_REQUESTED/...
  • payload_json
  • created_at

5. API 设计(新增)

在现有 Authorization: Bearer <user_token> 的认证体系下,新增 Serving API路径仅示意具体在实现时与现有 api/v2 对齐)。

5.1 用户接口

  • POST /api/v2/serve/models
    • body: YAML 或 JSONv3.8 先用 YAML 与现有 TaskSpec 一致)
    • 创建/更新upsert一个模型配置进入 QUEUED
  • GET /api/v2/serve/models
    • 列出当前用户的模型列表(含 state、资源、endpoint
  • GET /api/v2/serve/models/{model_key}
    • 详情:完整 spec + 最近事件 + Serve status 摘要
  • PATCH /api/v2/serve/models/{model_key}
    • 修改 num_replicas、或 engine_kwargs可选
  • DELETE /api/v2/serve/models/{model_key}
    • 下线模型(进入 DELETING

5.2 系统接口admin

  • GET /api/v2/serve/statusadmin
    • 返回 serve.status() 的摘要(集群级 / app 级)

5.3 对外推理 endpoint

固定输出到 UI/接口中:

  • openai_base_url = http://<host>:8000/v1
  • 支持:
    • /v1/chat/completions
    • /v1/completions
    • /v1/embeddings
    • /v1/models

v3.8 不做额外网关与鉴权(保持与现有 dev 环境一致);若后续需要,可在 v3.9+ 引入 token 校验/反向代理。

5.4 model_id 前缀策略user_id-

为避免多用户冲突并保持可读性:

v3.8 采用“user_id + 日期小时分钟”作为稳定前缀,以降低冲突并便于快速定位创建时间:

  • 用户在 UI/API 中仅填写 model_id_suffix(或仍用字段名 model_id,但语义为 suffix
  • 平台计算实际对外 model_id
    • prefix = f"{user_id}-{YYYYMMDDHHMM}"
    • model_id = f"{prefix}-{model_id_suffix}"
  • 在列表/详情中同时展示:
    • model_id_suffix(用户输入)
    • model_id_prefix(平台生成,例如 alice-202601061235
    • model_id(对外 OpenAI 名称)

6. 后台执行模型Serving Reconciler

v3.8 参考任务 scheduler 的模式,引入一个轻量的 reconciler

  • tick 周期(例如 5s
  • 每次 tick
    1. 拉取 DB 中 QUEUED/DEPLOYING/RUNNING/DELETING 的模型
    2. 调用 serve.status() 读取当前 app 及 deployments 状态
  1. 若存在 QUEUED 或需要变更的模型:构建新的 multi-model app包含全部 RUNNING/DEPLOYING/QUEUED 的模型配置)并 serve.run(...)
  2. 若存在 DELETING:从 app 配置中移除对应模型,并 serve.run(...) 应用变更
  3. 更新每个模型的 state依据 Serve status

重要行为说明multi-model app 的代价):

  • 每次“新增/删除/改 replicas”都会触发对同一个 app 的一次 serve.run(...) 更新;
  • Ray Serve 会尽量做增量更新,但在某些版本/配置下可能导致 ingress/router 短暂重启;
  • v3.8 先接受该代价(满足需求闭环优先);若后续需要“删除某模型不影响其它模型”,可演进为“每模型一个 app + 单独 route_prefix”的方案。

资源预检查:

  • 在 apply 前使用 ray.available_resources() 做粗粒度 GPU 预检查:
    • 需要 GPU 总量 = sum(num_replicas * gpus_per_replica)(仅对“新增/扩容的差量”更精确)
  • 若不足:
    • 模型保持 QUEUED,记录事件 PENDING_RESOURCES
    • 用户 UI 显示“资源不足,等待释放”

v3.8 不引入更复杂的抢占/优先级。Serving 与 Training 会竞争 GPU用户需要自行规划资源或后续版本引入统一调度

7. WebUI 设计(新增 Serving 页面)

新增侧边栏入口:Serving

7.1 Serving 列表页

  • 展示字段:
    • model_id
    • user_id仅 admin 可见)
    • replicas / gpus_per_replica / total_gpus
    • stateRUNNING/DEPLOYING/QUEUED/FAILED
    • 操作Scale修改 replicas、Delete

7.2 Serving 创建/编辑页

两种模式(与 New Task 类似,先做 YAML 模式即可):

示例 YAMLv3.8

model_id: qwen-0.5b
model_source: $HOME/common/hf/hub/models--Qwen--Qwen2.5-0.5B-Instruct/snapshots/<sha>
num_replicas: 1
gpus_per_replica: 1
# engine_kwargs:
#   max_model_len: 8192
#   gpu_memory_utilization: 0.9

7.3 Serving 详情页

  • 完整配置resolved spec
  • Serve status 摘要deployments 状态、replica 健康)
  • OpenAI 调用示例python openai client

8. 验收标准v3.8

  1. 部署:
  • 一键部署一个模型1 replica、1 GPU成功状态变为 RUNNING
  • /v1/models 可列出该模型
  1. 扩缩容:
  • 修改 num_replicas 生效Serve status 看到副本数变化)
  1. 多模型:
  • 同一个 app 内能同时部署 2 个模型(不同 model_id
  • 通过 OpenAI 接口用不同 model= 请求可得到响应
  1. 下线:
  • 删除某模型后 /v1/models 不再出现
  1. 模型路径:
  • 支持 /private/hf/...common/private/users/<user>/...user两类本地路径
  1. 资源不足可解释:
  • 当 GPU 不足时,模型进入 QUEUED 并在 UI/详情中提示“资源不足”

9. 待确认点(请你评审时确认)

已确认(来自评审):

  1. 推理端口固定使用 8000Ray Serve 默认端口)。
  2. 对外暴露的 OpenAI 接口 不与现有 token 体系绑定v3.8 不做推理侧鉴权)。
  3. model_id 命名规则:平台统一加 user_id + 日期小时分钟 前缀,用户在 UI 里只填写后缀部分。

说明:这样可以避免跨用户 model_id 冲突,同时在 OpenAI API 的 model= 字段上自然可读。