14 KiB
MVP v3.8 详细设计方案:Ray Serve(vLLM)模型动态部署与管理
基线:当前已具备 v3.7 能力(训练平台 + W&B + SFTPGo + WebUI/API + Ray stateless pool,训练侧默认 rollout=vllm)。
v3.8 目标:在同一套 Ray 集群上,引入 Ray Serve LLM(后端 vLLM) 的模型推理服务能力,并通过 WebUI/API 动态管理模型生命周期。
0. 需求范围(来自 requirements.md)
- 通过 Ray Serve(后端 vLLM)动态拉起 LLM,支持多模型 application 部署
- 默认一个模型 1 个 replica,用户可配置多个
- 用户可删除(下线)模型
- 用户可指定模型使用几张 GPU
- WebUI 可配置、查看模型列表、查看详情
- 模型路径可用 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 Serve(controller + 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 配置,至少包含:
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_configmodel_id: 对外展示/请求时用的模型名(唯一 key)model_source: HF repo id / S3 / local path
accelerator_type- 从
dev.yaml的serving.llm.accelerator_type读取(dev/h1:H20)
- 从
deployment_confignum_replicas或autoscaling_config(v3.8 先用固定num_replicas)ray_actor_options(CPU/资源约束)
engine_kwargs- vLLM 相关参数(
max_model_len、gpu_memory_utilization等)
- vLLM 相关参数(
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 支持两类模型来源:
- common
- 典型为
/private/hf/...(共享 HF cache / snapshot)
- 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/hfHUGGINGFACE_HUB_CACHE=/private/hf/hubTRANSFORMERS_CACHE=/private/hf/transformersHF_HUB_OFFLINE=1HF_ENDPOINT=https://hf-mirror.com(可保留,但离线模式下不应触发网络)
并建议用户在 ServingSpec 中尽量填写 local path 作为 model_source,而不是直接 repo id。
4. 平台数据模型(SQLite)
新增两张主表:
4.1 serve_models
每一行代表一个“声明式模型部署”:
model_key(平台内部唯一 ID,便于重命名/去重)user_idmodel_id(对外 OpenAI model 名称,要求 per-app 唯一)model_source(本地路径或 repo id,存 resolved 后的结果)num_replicasgpus_per_replicaengine_kwargs_json(可选)state:QUEUED | DEPLOYING | RUNNING | FAILED | DELETING | DELETEDserve_app_name(默认argus_llm_app)created_at / updated_aterror_summary
4.2 serve_events
记录关键事件与排障信息(类似 task_events):
idmodel_keyevent_type(DEPLOY_REQUESTED/DEPLOY_APPLIED/STATUS_SYNC/DELETE_REQUESTED/...)payload_jsoncreated_at
5. API 设计(新增)
在现有 Authorization: Bearer <user_token> 的认证体系下,新增 Serving API(路径仅示意,具体在实现时与现有 api/v2 对齐)。
5.1 用户接口
POST /api/v2/serve/models- body: YAML 或 JSON(v3.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:
- 拉取 DB 中
QUEUED/DEPLOYING/RUNNING/DELETING的模型 - 调用
serve.status()读取当前 app 及 deployments 状态
- 拉取 DB 中
- 若存在
QUEUED或需要变更的模型:构建新的 multi-model app(包含全部RUNNING/DEPLOYING/QUEUED的模型配置)并serve.run(...) - 若存在
DELETING:从 app 配置中移除对应模型,并serve.run(...)应用变更 - 更新每个模型的 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)(仅对“新增/扩容的差量”更精确)
- 需要 GPU 总量 =
- 若不足:
- 模型保持
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
- state(RUNNING/DEPLOYING/QUEUED/FAILED)
- 操作:Scale(修改 replicas)、Delete
7.2 Serving 创建/编辑页
两种模式(与 New Task 类似,先做 YAML 模式即可):
示例 YAML(v3.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 replica、1 GPU)成功,状态变为 RUNNING
/v1/models可列出该模型
- 扩缩容:
- 修改
num_replicas生效(Serve status 看到副本数变化)
- 多模型:
- 同一个 app 内能同时部署 2 个模型(不同 model_id)
- 通过 OpenAI 接口用不同
model=请求可得到响应
- 下线:
- 删除某模型后
/v1/models不再出现
- 模型路径:
- 支持
/private/hf/...(common)与/private/users/<user>/...(user)两类本地路径
- 资源不足可解释:
- 当 GPU 不足时,模型进入
QUEUED并在 UI/详情中提示“资源不足”
9. 待确认点(请你评审时确认)
已确认(来自评审):
- 推理端口固定使用
8000(Ray Serve 默认端口)。 - 对外暴露的 OpenAI 接口 不与现有 token 体系绑定(v3.8 不做推理侧鉴权)。
model_id命名规则:平台统一加user_id + 日期小时分钟前缀,用户在 UI 里只填写后缀部分。
说明:这样可以避免跨用户 model_id 冲突,同时在 OpenAI API 的
model=字段上自然可读。