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

372 lines
14 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.

# 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_models``serve_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 采用:
- 一个固定的 app`argus_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_location是 **Ray 集群全局配置**,启动后无法动态修改,因此应当在平台启动时一次性设定并持久化。
> - 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.yaml`ray_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 API`PUT /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`,预期镜像内包含 vLLM`ray.serve.llm` 是否开箱即用需要在实现阶段确认。
若缺失v3.8 将在 `argus-ray-node` 镜像构建阶段补充 `pip install "ray[serve,llm]"`(或按官方建议的最小依赖)并做版本锁定。
### 2.4 Serving 配置dev.yaml
v3.8 新增一段 serving 配置,至少包含:
```yaml
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.yaml``serving.llm.accelerator_type` 读取dev/h1: `H20`
- `deployment_config`
- `num_replicas``autoscaling_config`v3.8 先用固定 `num_replicas`
- `ray_actor_options`CPU/资源约束)
- `engine_kwargs`
- vLLM 相关参数(`max_model_len``gpu_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_len``gpu_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 会线性消耗 GPU`num_replicas * gpus_per_replica`),需要做资源预检查。
### 3.4 模型路径与宏替换common / user
v3.8 支持两类模型来源:
1) **common**
- 典型为 `/private/hf/...`(共享 HF cache / snapshot
2) **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`(可选)
- `state``QUEUED | 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_type`DEPLOY_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/status`admin
- 返回 `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 状态
3) 若存在 `QUEUED` 或需要变更的模型:构建新的 multi-model app包含全部 `RUNNING/DEPLOYING/QUEUED` 的模型配置)并 `serve.run(...)`
4) 若存在 `DELETING`:从 app 配置中移除对应模型,并 `serve.run(...)` 应用变更
5) 更新每个模型的 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
```yaml
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` 可列出该模型
2) 扩缩容:
- 修改 `num_replicas` 生效Serve status 看到副本数变化)
3) 多模型:
- 同一个 app 内能同时部署 2 个模型(不同 model_id
- 通过 OpenAI 接口用不同 `model=` 请求可得到响应
4) 下线:
- 删除某模型后 `/v1/models` 不再出现
5) 模型路径:
- 支持 `/private/hf/...`common`/private/users/<user>/...`user两类本地路径
6) 资源不足可解释:
- 当 GPU 不足时,模型进入 `QUEUED` 并在 UI/详情中提示“资源不足”
## 9. 待确认点(请你评审时确认)
已确认(来自评审):
1) 推理端口固定使用 `8000`Ray Serve 默认端口)。
2) 对外暴露的 OpenAI 接口 **不与现有 token 体系绑定**v3.8 不做推理侧鉴权)。
3) `model_id` 命名规则:平台统一加 `user_id + 日期小时分钟` 前缀,用户在 UI 里只填写后缀部分。
> 说明:这样可以避免跨用户 model_id 冲突,同时在 OpenAI API 的 `model=` 字段上自然可读。