v3.8 model serving 部署成功

This commit is contained in:
2026-01-06 22:43:29 +08:00
parent 63963eba29
commit 686739fea2
39 changed files with 6772 additions and 1830 deletions

File diff suppressed because it is too large Load Diff

314
specs/mvp/v3.8/ray_serve.md Normal file
View File

@@ -0,0 +1,314 @@
API参考资料
https://docs.ray.io/en/latest/serve/api/doc/ray.serve.llm.LLMConfig.html
ray.serve.llm.LLMConfig
pydantic model ray.serve.llm.LLMConfig[source]
The configuration for starting an LLM deployment.
PublicAPI (alpha): This API is in alpha and may change before becoming stable.
field accelerator_type: str | None = None
The type of accelerator runs the model on. Only the following values are supported: [‘V100’, ‘P100’, ‘T4’, ‘P4’, ‘K80’, ‘A10G’, ‘L4’, ‘L40S’, ‘A100’, ‘H100’, ‘H200’, ‘H20’, ‘B200’, ‘Intel-GPU-Max-1550’, ‘Intel-GPU-Max-1100’, ‘Intel-GAUDI’, ‘AMD-Instinct-MI100’, ‘AMD-Instinct-MI250X’, ‘AMD-Instinct-MI250X-MI250’, ‘AMD-Instinct-MI210’, ‘AMD-Instinct-MI300A’, ‘AMD-Instinct-MI300X-OAM’, ‘AMD-Instinct-MI300X-HF’, ‘AMD-Instinct-MI308X’, ‘AMD-Instinct-MI325X-OAM’, ‘AMD-Instinct-MI350X-OAM’, ‘AMD-Instinct-MI355X-OAM’, ‘AMD-Radeon-R9-200-HD-7900’, ‘AMD-Radeon-HD-7900’, ‘aws-neuron-core’, ‘TPU-V2’, ‘TPU-V3’, ‘TPU-V4’, ‘TPU-V5P’, ‘TPU-V5LITEPOD’, ‘TPU-V6E’, ‘Ascend910B’, ‘Ascend910B4’, ‘MXC500’, ‘MXC550’, ‘A100-40G’, ‘A100-80G’]
field callback_config: CallbackConfig [Optional]
Callback configuration to use for model initialization. Can be a string path to a class or a Callback subclass.
field deployment_config: Dict[str, Any] [Optional]
The Ray @server.deployment options. Supported fields are: name, num_replicas, ray_actor_options, max_ongoing_requests, autoscaling_config, max_queued_requests, user_config, health_check_period_s, health_check_timeout_s, graceful_shutdown_wait_loop_s, graceful_shutdown_timeout_s, logging_config, request_router_config. For more details, see the Ray Serve Documentation.
field engine_kwargs: Dict[str, Any] = {}
Additional keyword arguments for the engine. In case of vLLM, this will include all the configuration knobs they provide out of the box, except for tensor-parallelism which is set automatically from Ray Serve configs.
field experimental_configs: Dict[str, Any] [Optional]
Experimental configurations for Ray Serve LLM. This is a dictionary of key-value pairs. Current supported keys are: - stream_batching_interval_ms: Ray Serve LLM batches streaming requests together. This config decides how long to wait for the batch before processing the requests. Defaults to 50.0. - num_ingress_replicas: The number of replicas for the router. Ray Serve will take the max amount all the replicas. Default would be 2 router replicas per model replica.
field llm_engine: str = 'vLLM'
The LLMEngine that should be used to run the model. Only the following values are supported: [‘vLLM’]
field log_engine_metrics: bool | None = True
Enable additional engine metrics via Ray Prometheus port.
field lora_config: Dict[str, Any] | LoraConfig | None = None
Settings for LoRA adapter. Validated against LoraConfig.
field model_loading_config: Dict[str, Any] | ModelLoadingConfig [Required]
The settings for how to download and expose the model. Validated against ModelLoadingConfig.
field placement_group_config: Dict[str, Any] | None = None
Ray placement group configuration for scheduling vLLM engine workers. Defines resource bundles and placement strategy for multi-node deployments. Should contain ‘bundles’ (list of resource dicts) and optionally ‘strategy’ (defaults to ‘PACK’). Example: {‘bundles’: [{‘GPU’: 1, ‘CPU’: 2}], ‘strategy’: ‘PACK’}
field runtime_env: Dict[str, Any] | None = None
The runtime_env to use for the model deployment replica and the engine workers.
apply_checkpoint_info(model_id_or_path: str, trust_remote_code: bool = False) → None[source]
Apply the checkpoint info to the model config.
classmethod from_file(path: str, **kwargs) → ModelT
Load a model from a YAML file path.
get_engine_config() → None | VLLMEngineConfig[source]
Returns the engine config for the given LLM config.
LLMConfig not only has engine config but also deployment config, etc.
get_or_create_callback() → CallbackBase | None[source]
Get or create the callback instance for this process.
This ensures one callback instance per process (singleton pattern). The instance is cached so the same object is used across all hooks.
Returns
:
Instance of class that implements Callback
multiplex_config() → ServeMultiplexConfig[source]
classmethod parse_yaml(file, **kwargs) → ModelT
setup_engine_backend()[source]
update_engine_kwargs(**kwargs: Any) → None[source]
Update the engine_kwargs and the engine_config engine_kwargs.
This is typically called during engine starts, when certain engine_kwargs (e.g., data_parallel_rank) become available.
validator validate_accelerator_type » accelerator_type[source]
validator validate_deployment_config » deployment_config[source]
Validates the deployment config dictionary.
validator validate_experimental_configs » experimental_configs[source]
Validates the experimental configs dictionary.
validator validate_llm_engine » llm_engine[source]
Validates the llm_engine string value.
validator validate_lora_config » lora_config[source]
Validates the lora config dictionary.
validator validate_model_loading_config » model_loading_config[source]
Validates the model loading config dictionary.
property input_modality: str
Returns the input modality of the model. There could be more types in the future. Right now assumes if the model doesn’t support version, it’ll be text.
property max_request_context_length: int | None
property model_architecture: str
property model_id: str
property supports_vision: bool
# Python API
ray serve api
https://docs.ray.io/en/latest/serve/api/index.html#serve-api
Python API
Writing Applications
serve.Deployment
Class (or function) decorated with the @serve.deployment decorator.
serve.Application
One or more deployments bound with arguments that can be deployed together.
Deployment Decorators
serve.deployment
Decorator that converts a Python class to a Deployment.
serve.ingress
Wrap a deployment class with an ASGI application for HTTP request parsing.
serve.batch
Converts a function to asynchronously handle batches.
serve.multiplexed
Wrap a callable or method used to load multiplexed models in a replica.
Deployment Handles
Note
The deprecated RayServeHandle and RayServeSyncHandle APIs have been fully removed as of Ray 2.10. See the model composition guide for how to update code to use the DeploymentHandle API instead.
serve.handle.DeploymentHandle
A handle used to make requests to a deployment at runtime.
serve.handle.DeploymentResponse
A future-like object wrapping the result of a unary deployment handle call.
serve.handle.DeploymentResponseGenerator
A future-like object wrapping the result of a streaming deployment handle call.
Running Applications
serve.start
Start Serve on the cluster.
serve.run
Run an application and return a handle to its ingress deployment.
serve.delete
Delete an application by its name.
serve.status
Get the status of Serve on the cluster.
serve.shutdown
Completely shut down Serve on the cluster.
serve.shutdown_async
Completely shut down Serve on the cluster asynchronously.
Configurations
serve.config.ProxyLocation
Config for where to run proxies to receive ingress traffic to the cluster.
serve.config.gRPCOptions
gRPC options for the proxies.
serve.config.HTTPOptions
HTTP options for the proxies.
serve.config.AutoscalingConfig
Config for the Serve Autoscaler.
serve.config.AutoscalingPolicy
PublicAPI (alpha): This API is in alpha and may change before becoming stable.
serve.config.AutoscalingContext
Rich context provided to custom autoscaling policies.
serve.config.AggregationFunction
An enumeration.
serve.config.RequestRouterConfig
Config for the Serve request router.
Schemas
serve.schema.ServeActorDetails
Detailed info about a Ray Serve actor.
serve.schema.ProxyDetails
Detailed info about a Ray Serve ProxyActor.
serve.schema.ApplicationStatusOverview
Describes the status of an application and all its deployments.
serve.schema.ServeStatus
Describes the status of Serve.
serve.schema.DeploymentStatusOverview
Describes the status of a deployment.
serve.schema.EncodingType
Encoding type for the serve logs.
serve.schema.AutoscalingMetricsHealth
An enumeration.
serve.schema.AutoscalingStatus
An enumeration.
serve.schema.ScalingDecision
One autoscaling decision with minimal provenance.
serve.schema.DeploymentAutoscalingDetail
Deployment-level autoscaler observability.
serve.schema.ReplicaRank
Replica rank model.
Request Router
serve.request_router.ReplicaID
A unique identifier for a replica.
serve.request_router.PendingRequest
A request that is pending execution by a replica.
serve.request_router.RunningReplica
Contains info on a running replica.
serve.request_router.FIFOMixin
Mixin for FIFO routing.
serve.request_router.LocalityMixin
Mixin for locality routing.
serve.request_router.MultiplexMixin
Mixin for multiplex routing.
serve.request_router.RequestRouter
Abstract interface for a request router (how the router calls it).
Advanced APIs
serve.get_replica_context
Returns the deployment and replica tag from within a replica at runtime.
serve.context.ReplicaContext
Stores runtime context info for replicas.
serve.get_multiplexed_model_id
Get the multiplexed model ID for the current request.
serve.get_app_handle
Get a handle to the application's ingress deployment by name.
serve.get_deployment_handle
Get a handle to a deployment by name.
serve.grpc_util.RayServegRPCContext
Context manager to set and get gRPC context.
serve.exceptions.BackPressureError
Raised when max_queued_requests is exceeded on a DeploymentHandle.
serve.exceptions.RayServeException
serve.exceptions.RequestCancelledError
Raise when a Serve request is cancelled.
serve.exceptions.DeploymentUnavailableError
Raised when a Serve deployment is unavailable to receive requests.

View File

@@ -0,0 +1,87 @@
基于提供的来源,以下是使用 **Builder Pattern(构建器模式)** 结合 Ray Serve 和 vllm 动态部署**中型大语言模型(Medium-sized LLM)**的原理与操作方案。
### 一、 核心原理
1. **中型 LLM 定义**:中型模型(如 Llama-3.1-70B)通常具有约 70B 参数。它们通常运行在**单个节点**上,利用 **4 到 8 个 GPU**。
2. **Builder Pattern 机制**:该模式通过 `build_openai_app` 函数提供高度抽象。开发者只需定义一个 `LLMConfig` 对象,即可自动构建并链接底层的 `LLMServer` 和 `OpenAiIngress` 组件。
3. **高性能后端 (vLLM)**:Ray Serve LLM 使用 vLLM 作为推理引擎,支持高性能推理和显存管理。
4. **动态扩缩容与资源调度**:
* **张量并行 (Tensor Parallelism)**:通过 `tensor_parallel_size` 将模型权重均匀分布在单节点的所有 GPU 上。
* **副本缩放 (Autoscaling)**:通过 `autoscaling_config` 动态调整 `min_replicas` 和 `max_replicas`,使服务能根据实时流量增减推理副本。
---
### 二、 操作方案
#### 1. 环境准备
确保已安装必要的依赖包并配置 Hugging Face 访问令牌(针对 Llama-3.1 等受限模型)。
```bash
pip install "ray[serve,llm]"
export HF_TOKEN=<YOUR_HUGGINGFACE_TOKEN>
```
#### 2. 编写部署脚本 (`serve_medium_llm.py`)
使用 **Builder Pattern** 定义配置并构建应用。以下示例配置了一个典型的 70B 模型部署:
```python
# serve_medium_llm.py
from ray.serve.llm import LLMConfig, build_openai_app
import os
llm_config = LLMConfig(
model_loading_config=dict(
model_id="my-llama-3.1-70b",
model_source="meta-llama/Llama-3.1-70B-Instruct",
),
accelerator_type="A100-40G", # 或 L40S
deployment_config=dict(
autoscaling_config=dict(
min_replicas=1, # 最小副本数
max_replicas=4, # 最大副本数,实现动态扩展
)
),
runtime_env=dict(env_vars={"HF_TOKEN": os.environ.get("HF_TOKEN")}),
engine_kwargs=dict(
max_model_len=32768, # 上下文长度
tensor_parallel_size=8, # 在单节点的 8 个 GPU 间拆分权重
),
)
# 使用 Builder Pattern 构建应用
app = build_openai_app({"llm_configs": [llm_config]})
```
#### 3. 启动部署
在终端运行以下命令启动服务:
```bash
serve run serve_medium_llm:app
```
部署过程通常需要几分钟,包括配置集群、启动 vLLM 服务器以及下载模型权重。
#### 4. 发送请求测试
服务启动后,可以通过符合 OpenAI 标准的接口进行访问。
```python
from openai import OpenAI
client = OpenAI(base_url="http://localhost:8000/v1", api_key="FAKE_KEY")
response = client.chat.completions.create(
model="my-llama-3.1-70b",
messages=[{"role": "user", "content": "解释一下什么是量子纠缠?"}],
stream=True
)
for chunk in response:
if chunk.choices.delta.content:
print(chunk.choices.delta.content, end="", flush=True)
```
---
### 三、 性能与并发优化建议
* **提高并发量**:可以通过降低 `max_model_len` 来减少 KV 缓存所需的显存,从而显著提升每个副本支持的最大并发请求数。
* **监控指标**:通过 Ray Serve LLM 仪表盘监控 **TTFT(首字延迟)**、**TPOT(单字延迟)** 和 **Token 吞吐量** 来评估服务性能。
* **精度折衷**:对于资源受限的场景,可以使用**量化模型**(如 FP8)来减少模型内存占用,为 KV 缓存留出更多空间,进而提高并发能力。
**比喻理解**:
部署**中型 LLM** 就像是在一个大型车间里组装一台复杂的精密机器(模型权重)。**Builder Pattern** 是你的“全自动组装线”,你只需设定好机器的参数(Config),生产线就会自动帮你把零件固定好并接通电源。而 **vLLM 和张量并行** 就像是让 8 个熟练工人(GPU)共同抬起这台沉重的机器,每个人只负责自己那一部分的力气,从而让机器能够平稳地运转。

View File

@@ -0,0 +1,8 @@
1. 通过ray serve(后端vllm)来动态拉起llm,支持多模型application部署,
2. 默认一个模型只有一个replica,用户配置可以多个
3. 用户可以删除(下线)模型
4. 可以指定模型用几张卡
5. 通过WebUI来进行配置,查看当前部署的模型列表,以及可以查看详情
6. 模型路径可以使用common,也可以用户自己指定user路径
7.

224
specs/mvp/v3.8/v3.8_api.md Normal file
View File

@@ -0,0 +1,224 @@
# MVP v3.8 API Reference(Serving)
> 说明:本节为 v3.8 新增的 **Model Serving** API(Ray Serve LLM / vLLM)。
> 认证:Serving 管理 API 复用现有 MVP API 的认证方式(`Authorization: Bearer <user_token>`)。
> 推理:对外 OpenAI endpoint **不做鉴权**(v3.8 约定)。
## 0. 基本信息
### 0.1 Base URLs
- MVP API server:`http://<host>:8080`
- Ray Serve OpenAI ingress(固定端口 8000):`http://<host>:8000/v1`
### 0.2 认证
所有 `/api/v2/serve/*` 接口要求:
```
Authorization: Bearer <user_token>
```
其中 `user_token` 由管理员通过 `/api/v2/users/<user_id>/tokens` 颁发(沿用现有机制)。
### 0.3 命名规则:`model_id = user_id-YYYYMMDDHHMM-<suffix>`
- 用户提交时填写 `model_id`(语义为 suffix,例如 `qwen-0.5b`)
- 平台生成前缀:
- `prefix = "<user_id>-<YYYYMMDDHHMM>"`
- 平台实际对外暴露的 OpenAI model 名称为:
- `model_id = "<prefix>-<suffix>"`
- 示例:`alice-202601061235-qwen-0.5b`
## 1. 数据结构
### 1.1 ServingSpec(YAML)
请求体建议使用 YAML(与 TaskSpec 一致),示例:
```yaml
model_id: qwen-0.5b # 必填:suffix(平台自动加 user_id- 前缀)
model_source: $HOME/common/hf/.../<sha> # 必填:本地路径或 repo id;平台做 $HOME 宏替换与路径校验
num_replicas: 1 # 可选,默认 1
gpus_per_replica: 1 # 可选,默认 1
# engine_kwargs: # 可选:vLLM 参数透传(白名单/黑名单由实现决定)
# max_model_len: 8192
# gpu_memory_utilization: 0.9
```
说明:
- `accelerator_type` 不在 ServingSpec 中暴露;由平台配置(`dev.yaml` 的 `serving.llm.accelerator_type`)统一注入到 Ray Serve LLM 的 `LLMConfig.accelerator_type`(dev/h1: `H20`)。
#### 宏替换
- `$HOME` → `/private/users/<user_id>`
- `$HOME/common/hf` → `/private/hf`
- `$HOME/common/datasets` → `/private/datasets`(serving 不强依赖,但保留一致语义)
#### 路径校验(v3.8 约定)
`model_source` 允许:
- `/private/hf/...`(common)
- `/private/users/<user_id>/...`(user)
拒绝:
- 其它用户目录
- 非 `/private` 下路径
- 空路径或包含 `..` 的可疑路径
### 1.2 ServingModel(响应体,JSON)
```json
{
"model_key": "svc-alice-20260106-123000-abcd",
"user_id": "alice",
"model_id": "alice-202601061235-qwen-0.5b",
"model_id_suffix": "qwen-0.5b",
"model_id_prefix": "alice-202601061235",
"model_source": "/private/hf/hub/models--.../snapshots/<sha>",
"num_replicas": 1,
"gpus_per_replica": 1,
"total_gpus": 1,
"state": "RUNNING",
"endpoint": {
"openai_base_url": "http://<host>:8000/v1",
"model": "alice-202601061235-qwen-0.5b"
},
"error_summary": null,
"created_at": "2026-01-06T12:30:00Z",
"updated_at": "2026-01-06T12:31:02Z"
}
```
## 2. 管理 API(MVP API server)
### 2.1 Create / Upsert model
`POST /api/v2/serve/models`
#### Request
- Header: `Content-Type: application/yaml`
- Body: ServingSpec(YAML)
#### Response (202)
```json
{
"model_key": "svc-alice-20260106-123000-abcd",
"state": "QUEUED"
}
```
语义:
- 创建新模型(若 suffix 不存在)
- 或更新已有模型(若同一用户同一 suffix 已存在):更新 replicas/gpu 等配置,进入 `QUEUED` 等待 reconciler apply
### 2.2 List models (current user)
`GET /api/v2/serve/models`
#### Response (200)
```json
{
"items": [ ... ServingModel ... ],
"openai_base_url": "http://<host>:8000/v1"
}
```
### 2.3 Get model detail
`GET /api/v2/serve/models/{model_key}`
#### Response (200)
```json
{
"model": { ... ServingModel ... },
"resolved_spec_yaml": "model_id: ...\nmodel_source: ...\n",
"events": [
{ "event_type": "DEPLOY_REQUESTED", "created_at": "...", "payload": {...} }
],
"serve_status": {
"app_name": "argus_llm_app",
"app_status": "RUNNING"
}
}
```
### 2.4 Scale replicas (PATCH)
`PATCH /api/v2/serve/models/{model_key}`
#### Request (JSON)
```json
{ "num_replicas": 2 }
```
#### Response (200)
```json
{ "model_key": "...", "state": "QUEUED" }
```
> v3.8 只支持修改 `num_replicas`(以及可选 engine_kwargs);`gpus_per_replica` 若修改,可能触发重新部署。
### 2.5 Delete / Undeploy model
`DELETE /api/v2/serve/models/{model_key}`
#### Response (200)
```json
{ "model_key": "...", "state": "DELETING" }
```
语义:从“声明式配置”中删除该模型,reconciler 会在下一轮 tick 触发 `serve.run(...)` 更新 app 配置并最终使其不可见。
### 2.6 Admin: Serve cluster status(可选)
`GET /api/v2/serve/status`
#### Response (200)
返回 `serve.status()` 摘要(集群级 + app 级)。
> 仅 admin token 可访问(沿用 v3.x admin gate)。
## 3. 推理 API(Ray Serve OpenAI ingress)
> v3.8 不做鉴权:无需 `Authorization`。
### 3.1 List models
`GET http://<host>:8000/v1/models`
返回可用 model 列表(包含 `alice-qwen-0.5b` 这类带前缀名称)。
### 3.2 Chat completions
`POST http://<host>:8000/v1/chat/completions`
```json
{
"model": "alice-202601061235-qwen-0.5b",
"messages": [{"role":"user","content":"Hello"}],
"stream": false
}
```
### 3.3 Completions / Embeddings
按 Ray Serve LLM OpenAI ingress 支持范围提供(v3.8 验收至少覆盖 chat)。
## 4. 错误码约定(MVP API server)
- `400 invalid yaml/spec`:YAML 解析失败、字段缺失、值不合法
- `403 forbidden`:路径越权(model_source 访问其他用户目录)
- `409 conflict`:model_id_suffix 冲突(同一用户重复创建且不允许覆盖时;若选择 upsert 则不返回该错误)
- `422 unprocessable`:资源参数非法(replica/gpu <=0)
- `500 internal`:reconciler/serve 调用异常(详情记录到 `serve_events`,并写入 `error_summary`)

View File

@@ -0,0 +1,371 @@
# 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)
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 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 配置,至少包含:
```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 或 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:
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
- state(RUNNING/DEPLOYING/QUEUED/FAILED)
- 操作:Scale(修改 replicas)、Delete
### 7.2 Serving 创建/编辑页
两种模式(与 New Task 类似,先做 YAML 模式即可):
示例 YAML(v3.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=` 字段上自然可读。

View File

@@ -0,0 +1,266 @@
# MVP v3.8 开发计划(TDD,细化版)
> 目标:在 v3.7 基础上引入 Ray Serve(vLLM)模型动态部署与管理(多模型单 app),并提供 WebUI + API 管理闭环。
> 约束(已确认):
> - 推理端口固定 `8000`(Serve HTTP)。
> - 推理侧不接入现有 token 鉴权(对外 OpenAI endpoint 无鉴权)。
> - 对外 `model_id` 统一加前缀:`<user_id>-<YYYYMMDDHHMM>-<suffix>`(用户只填 suffix)。
> - `LLMConfig.accelerator_type` 从 `dev.yaml` 读取(dev/h1: `H20`)。
本计划按“测试先行 → 实现 → 回归”的节奏拆分到可验证粒度;每个 milestone 都能单独验收。
---
## M0 - 基线与依赖探测(不改行为)
**目的**:确认 v3.7 baseline 稳定,并明确 Ray Serve LLM 依赖是否已具备(否则后续会卡在镜像/依赖)。
### M0.1 本地回归
- [ ] `.venv/bin/python -m pytest` 通过(coverage ≥ 90%)
### M0.2 远端回归(h1)
- [ ] `src/mvp/scripts/run_all_v30_api.sh` 可跑通(确认训练闭环未回退)
### M0.3 head 容器内依赖探测(记录结论)
- [ ] `python3 -c "import ray; import ray.serve; print(ray.__version__)"`
- [ ] `python3 -c "from ray.serve.llm import LLMConfig, build_openai_app; print('serve_llm_ok')"`
- [ ] 若失败(例如缺 `gymnasium`):记录缺失项,并在 M6 通过补齐 `ray[llm]` 解决
### M0.4 配置探测
- [ ] `configs/dev.yaml` 中存在:
- `serving.llm.accelerator_type: H20`
- `serving.serve.http_port: 8000`
- `serving.serve.proxy_location: HeadOnly`
**验收**:
- baseline 无回退;依赖探测结论明确(可用/不可用)
---
## M1 - ServingSpec(解析/校验/宏替换/路径校验)(单测驱动)
**目的**:先把“输入”这层彻底固化(API/UI 复用),避免后期反复改 schema。
### M1.1 新增/扩展数据模型
- [ ] `ServingSpec`(输入)
- `model_id`(suffix)
- `model_source`(支持 `$HOME` 宏)
- `num_replicas`(default=1)
- `gpus_per_replica`(default=1)
- `engine_kwargs`(可选 dict,先原样存 DB;实现阶段再做白名单/黑名单)
- [ ] `ResolvedServingSpec`(内部)
- `model_id_suffix`
- `model_id_prefix`(由平台生成:`user_id-YYYYMMDDHHMM`)
- `model_id`(对外:`<prefix>-<suffix>`)
- `model_source`(resolved path)
### M1.2 规则(写成纯函数,便于测)
- [ ] `validate_model_id_suffix(suffix)`:长度/字符集限制(建议:`[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}`)
- [ ] `$HOME` 宏替换:`$HOME`、`$HOME/common/hf`、`$HOME/common/datasets`
- [ ] 路径校验(强制本地路径):
- 允许:`/private/hf/...`、`/private/users/<user_id>/...`
- 拒绝:`..`、空、其它用户路径、非 `/private` 路径
- [ ] `make_model_id_prefix(user_id, now_utc)`:`YYYYMMDDHHMM`(UTC)+ user_id
### M1.3 单测(先写失败用例,再补实现)
- [ ] `test_serving_spec_validation.py`
- suffix 合法/非法
- replicas/gpus 边界:0、负数、小数、超大值(按实现决定是否限制上限)
- [ ] `test_serving_spec_paths.py`
- `$HOME` 替换正确
- 越权路径返回 403/ValueError(按接口层映射)
- `/private/hf` 与 `/private/users/<user>` 均可
- [ ] `test_serving_model_id_prefix.py`
- 固定时间输入 → prefix 输出一致(避免时区/格式问题)
**验收**:
- 输入 spec 规则稳定;核心校验/替换均有单测覆盖
---
## M2 - SQLite 表结构与 Db 接口(单测驱动)
**目的**:Serving 的声明式状态必须持久化,可审计、可恢复。
### M2.1 DB schema
- [ ] `serve_models`
- 主键:`model_key`(平台生成)
- unique:`(user_id, model_id_suffix)`(实现 upsert)
- 存储:resolved spec(包含 prefix/full model_id、resolved model_source)
- 状态:`QUEUED/DEPLOYING/RUNNING/FAILED/DELETING/DELETED`
- `error_summary`
- [ ] `serve_events`(append-only)
### M2.2 Db 方法
- [ ] `upsert_serve_model(user_id, spec_yaml, now)` → (model_key, state)
- [ ] `list_serve_models(user_id, include_deleted=False, limit/offset?)`
- [ ] `get_serve_model(model_key)`
- [ ] `set_serve_model_state(model_key, state, error_summary=None)`
- [ ] `append_serve_event(model_key, event_type, payload_json=None)`
- [ ] `pick_next_runnable_serve_change()`(给 reconciler 用)
### M2.3 单测
- [ ] `test_db_serving.py`
- upsert 行为(同 suffix 更新不产生新 model_key 或产生新版本——此处需在实现前明确策略)
- state 流转 + 事件记录
- list 的过滤与排序(按 updated_at)
**验收**:
- DB 行为可预测;upsert/unique 语义确定并测试覆盖
---
## M3 - Serving 管理 API(FastAPI)(单测驱动)
**目的**:先把管理 API 跑通,Ray Serve 先不接真实(reconciler 之后再接)。
### M3.1 API 路由(用户)
- [ ] `POST /api/v2/serve/models`(Content-Type: application/yaml)
- 入参:ServingSpec YAML
- 出参:`{model_key,state}`(202)
- [ ] `GET /api/v2/serve/models`
- 返回 items + `openai_base_url=http://<host>:8000/v1`
- [ ] `GET /api/v2/serve/models/{model_key}`
- 返回 model + resolved_spec_yaml + events(分页可后置)+ serve_status(先空/占位)
- [ ] `PATCH /api/v2/serve/models/{model_key}`(JSON)
- 支持 `num_replicas`(最小闭环)
- [ ] `DELETE /api/v2/serve/models/{model_key}`
### M3.2 API 路由(admin,可选)
- [ ] `GET /api/v2/serve/status`(仅 admin token)
### M3.3 错误映射(必须测试)
- [ ] YAML 解析失败:400
- [ ] spec 校验失败:422
- [ ] 越权路径:403
- [ ] 不存在 model_key:404
### M3.4 单测
- [ ] `test_app_serving_api.py`
- happy path:create → list → get → patch → delete
- 多用户隔离:用户只能看到自己的 model
- 错误码覆盖:400/403/404/422
**验收**:
- API reference (`v3.8_api.md`) 中所有管理接口可返回预期结构(Serve 未接入也能工作)
---
## M4 - ServeClient 抽象 + LLMConfig builder(单测驱动)
**目的**:将“如何从 ResolvedServingSpec 构造 LLMConfig”固化,并把 Ray Serve 的依赖隔离到 client 里,便于 mock。
### M4.1 `ServeClient` 接口(可 mock)
- [ ] `ensure_started(http_port=8000, proxy_location="HeadOnly")`
- [ ] `apply_app(app_name, llm_configs)`(multi-model)
- [ ] `get_status()`(serve.status 摘要)
### M4.2 `build_llm_config(resolved_spec, accelerator_type, runtime_env_defaults)` 纯函数
- [ ] 写入 `LLMConfig.accelerator_type`(来自 dev.yaml:H20)
- [ ] `deployment_config.num_replicas`
- [ ] `engine_kwargs.tensor_parallel_size = gpus_per_replica`
- [ ] `placement_group_config` bundles 按 GPU 张数生成
- [ ] `runtime_env.env_vars` 注入(至少包含 HF cache + `HF_HUB_OFFLINE=1`)
### M4.3 单测
- [ ] `test_llm_config_builder.py`
- gpus_per_replica=1/2/4 → tensor_parallel_size 与 bundles 数量正确
- accelerator_type 注入正确
- runtime_env 含 HF_HUB_OFFLINE 等关键 env
**验收**:
- 从平台 spec 到 Ray Serve LLMConfig 的映射规则稳定,有单测锁定
---
## M5 - Serving Reconciler(状态机 + 资源预检查)(单测驱动)
**目的**:实现声明式对齐:DB → Serve;同时提供可解释的 QUEUED/FAILED 状态。
### M5.1 状态机(最小闭环)
- [ ] `QUEUED`:等待 apply
- [ ] `DEPLOYING`:已触发 apply,等待 Serve running/healthy
- [ ] `RUNNING`:Serve status running
- [ ] `FAILED`:apply 或 status 失败(写 error_summary + event)
- [ ] `DELETING`:等待从 app 中移除
- [ ] `DELETED`:完成删除(可选保留记录)
### M5.2 资源预检查
- [ ] `needed_total_gpus = sum(num_replicas*gpus_per_replica)`(最小可用预检查)
- [ ] `ray.available_resources()["GPU"]`(或更稳健的 per-node 统计)不足时:
- 保持 `QUEUED`
- 记录 `PENDING_RESOURCES` event
### M5.3 reconcile 策略(multi-model app)
- [ ] tick 读取 active models,构建全量 `llm_configs`
- [ ] 处理 deleting:从 configs 中移除对应 model,再 apply
### M5.4 单测(mock ServeClient + mock ray resources)
- [ ] `test_serving_reconciler.py`
- 新增模型:apply_app 被调用;state 进入 DEPLOYING
- 删除模型:apply_app configs 不包含该模型
- GPU 不足:不 apply;state 仍 QUEUED;event 写入
- apply 抛异常:state FAILED;error_summary 写入
**验收**:
- reconciler 行为在纯单测环境可验证;失败可解释
---
## M6 - 真实集成(h1):Ray Serve 启动 + 推理闭环(E2E)
**目的**:在 dev/h1 环境真正跑通:部署模型 → `/v1/models` 可见 → `chat/completions` 成功 → 删除后消失。
### M6.1 compose/端口
- [ ] `src/mvp/docker-compose.yaml`:`ray_head` 增加 `8000:8000`
### M6.2 镜像依赖(若 M0 发现缺失)
- [ ] 在 `argus-ray-node` 镜像中补齐 `ray[serve,llm]`(版本与现有 Ray 对齐,避免升级 Ray 导致不兼容)
- 推荐优先补齐 `ray[llm]`(包含 `ray.serve.llm` 依赖闭包,如 `gymnasium`),再按需补 `ray[serve]`
- 验证点:`python3 -c "from ray.serve.llm import LLMConfig, build_openai_app; print('serve_llm_ok')"`
### M6.3 E2E 脚本(幂等)
- [ ] 新增 `scripts/run_all_v38_serving.sh`:
- 起 compose(确保 Serve 端口可用)
- 起 API
- 创建 user + token
- `POST /api/v2/serve/models` 创建 1GPU 模型
- 轮询模型 state 到 RUNNING
- `curl http://127.0.0.1:8000/v1/models` 验证包含 `<prefix>-<suffix>`
- `curl http://127.0.0.1:8000/v1/chat/completions` 进行最小推理
- `DELETE /api/v2/serve/models/{model_key}` 下线
- 再轮询 `/v1/models` 不包含
**验收**:
- E2E 可重复跑通(至少两次连续跑不需要人工清理)
---
## M7 - WebUI(Serving 页面)(单测驱动)
**目的**:给用户可视化的模型管理页面(最小必要功能)。
### M7.1 页面
- [ ] Sidebar 增加 Serving
- [ ] `/ui/serving`:列表 + 状态 + 操作(delete/scale)
- [ ] `/ui/serving/new`:YAML 输入 + submit
- [ ] `/ui/serving/{model_key}`:详情(resolved spec、events、OpenAI 调用示例)
### M7.2 单测
- [ ] `test_ui_serving.py`:路由 200、关键链接存在、包含 openai_base_url=8000
**验收**:
- WebUI 覆盖 create/list/detail/scale/delete 的主链路
---
## M8 - 文档与验收用例(交付)
**目的**:给用户/运维一套可复用的运行方式与排障路径。
- [ ] 更新 `specs/mvp/v3.8/v3.8_progress.md`(按 milestone 记录)
- [ ] 补充 README(可选):端口说明、推理 API 无鉴权警示、模型路径约定
- [ ] 验收清单(checklist):
- 单测通过
- h1 E2E 通过
- UI 主链路可操作

View File

@@ -0,0 +1,48 @@
# MVP v3.8 进展记录
## 2026-01-06
- 完成 v3.8 设计文档:`specs/mvp/v3.8/v3.8_design.md`
- 完成 v3.8 Serving API reference:`specs/mvp/v3.8/v3.8_api.md`
- 完成 v3.8 TDD 开发计划:`specs/mvp/v3.8/v3.8_dev_plan.md`
- 完成 M0:`configs/dev.yaml` 增加 `serving` 配置(http_port=8000, proxy_location=HeadOnly, accelerator_type=H20)
- 完成 M1:ServingSpec 解析/宏替换/路径校验 + 单测(`src/mvp/py/argus/service/serving_spec.py`)
- 完成 M2:SQLite 新增 `serve_models`/`serve_events` + Db API + 单测(`src/mvp/py/argus/service/db.py`)
- 完成 M3:FastAPI Serving 管理 API + 单测(`src/mvp/py/argus/service/app.py`)
- 完成 M4:ServeClient 抽象 + LLMConfig builder(dict 形态)+ 单测(`src/mvp/py/argus/service/serve_client.py`、`src/mvp/py/argus/service/serve_llm_config.py`)
- 完成 M5:Serving reconciler(状态机 + 资源预检查 + mock 单测)(`src/mvp/py/argus/service/serving_reconciler.py`)
### M6(h1 真实集成)
- `argus-ray-node` 镜像补齐依赖:`ray[serve,llm]` + `gymnasium` + `dm-tree`(避免 `ray.serve.llm` 导入失败)
- 修复 Ray 2.49.2 兼容性问题:
- `LLMConfig` 不支持 `placement_group_config`,改为使用 `resources_per_bundle`(`src/mvp/py/argus/service/serve_llm_config.py`)
- 远端 E2E:
- `scripts/run_all_v38_serving.sh` 可跑通:create → RUNNING → `/v1/models` → `chat/completions` → delete → DELETED
- 修复脚本中 `/v1/models` 解析的 bash heredoc 引号错误(`src/mvp/scripts/run_all_v38_serving.sh`)
### M7(WebUI - Serving)
- WebUI 增加 Serving 页面:
- 列表:`/ui/serving`
- 创建:`/ui/serving/new`
- 详情/事件/缩放/删除:`/ui/serving/{model_key}`
- 单测覆盖:
- `src/mvp/py/tests/test_ui_serving.py`
### M8(文档/验收)
- `src/mvp/README.md` 补充 v3.8 serving 端口与 E2E 脚本说明
### 环境探测(h1 / head 容器)
> 目的:确认 Ray Serve LLM 依赖是否开箱即用,避免后续集成阶段才暴雷。
- `ray`:可用,版本 `2.49.2`
- `ray.serve`:可 import(Serve 基础可用)
- `ray.serve.llm`:当前不可 import
- 报错:`ModuleNotFoundError: No module named 'gymnasium'`
- 原因:`ray.serve.llm` 的导入链路会触发 `ray.rllib`,而 rllib 依赖 `gymnasium`
结论:
- v3.8 在实现阶段需要在 `argus-ray-node` 镜像中补齐 `ray[llm]`(推荐)或至少补齐 `gymnasium` 等必要依赖,确保 `from ray.serve.llm import ...` 可用。