v3.8 model serving 部署成功
This commit is contained in:
File diff suppressed because it is too large
Load Diff
314
specs/mvp/v3.8/ray_serve.md
Normal file
314
specs/mvp/v3.8/ray_serve.md
Normal 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.
|
||||
87
specs/mvp/v3.8/ray_serve_llm.md
Normal file
87
specs/mvp/v3.8/ray_serve_llm.md
Normal 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)共同抬起这台沉重的机器,每个人只负责自己那一部分的力气,从而让机器能够平稳地运转。
|
||||
8
specs/mvp/v3.8/requirements.md
Normal file
8
specs/mvp/v3.8/requirements.md
Normal 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
224
specs/mvp/v3.8/v3.8_api.md
Normal 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`)
|
||||
371
specs/mvp/v3.8/v3.8_design.md
Normal file
371
specs/mvp/v3.8/v3.8_design.md
Normal 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=` 字段上自然可读。
|
||||
266
specs/mvp/v3.8/v3.8_dev_plan.md
Normal file
266
specs/mvp/v3.8/v3.8_dev_plan.md
Normal 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 主链路可操作
|
||||
48
specs/mvp/v3.8/v3.8_progress.md
Normal file
48
specs/mvp/v3.8/v3.8_progress.md
Normal 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 ...` 可用。
|
||||
Reference in New Issue
Block a user