已合并
【docs】设计文档优化:扩缩容文档合一为 scaling.md、主备倒换设计文档归位 #679
jason lyu创建于 8月6日
【docs】设计文档优化:扩缩容文档合一为 scaling.md、主备倒换设计文档归位 #679
已合并
共 5 个文件变更+150-237
| @@ -28,7 +28,7 @@ Controller 和 Coordinator 使用独立的 ETCD 锁键(自动加上 `/controll | |||
| 28 | **选举流程:** | 28 | **选举流程:** |
| 29 | 29 | ||
| 30 | <p align="center"> | 30 | <p align="center"> |
| 31 | - <img alt="主备倒换架构" src="../imgs/standby.png" /> | 31 | + <img alt="主备倒换架构" src="../../imgs/standby.png" /> |
| 32 | </p> | 32 | </p> |
| 33 | 33 | ||
| 34 | <p align="center"><b>图1. 主备倒换架构图</b></p> | 34 | <p align="center"><b>图1. 主备倒换架构图</b></p> |
| @@ -108,7 +108,7 @@ Coordinator 的 `/readiness` 不只是"我是 master 吗",它综合判断以 | |||
| 108 | ## 3. 主备倒换时序 | 108 | ## 3. 主备倒换时序 |
| 109 | 109 | ||
| 110 | <p align="center"> | 110 | <p align="center"> |
| 111 | - <img alt="主备倒换时序图" src="../imgs/standby-seq.jpg" /> | 111 | + <img alt="主备倒换时序图" src="../../imgs/standby-seq.jpg" /> |
| 112 | </p> | 112 | </p> |
| 113 | 113 | ||
| 114 | <p align="center"><b>图2. 主备倒换时序图</b></p> | 114 | <p align="center"><b>图2. 主备倒换时序图</b></p> |
| @@ -1,100 +0,0 @@ | |||
| 1 | -# 实例级手动扩缩容设计说明(MindIE Motor) | ||
| 2 | - | ||
| 3 | -本文档描述 **`examples/deployer/deploy.py` 在传入 `--update_instance_num` 时的行为**,以及与集群 ConfigMap、YAML 产物的对应关系。表述均来自仓库内上述脚本及其依赖模块的实现,不包含脚本未实现的保证。 | ||
| 4 | - | ||
| 5 | -## 1. 入口与前置校验 | ||
| 6 | - | ||
| 7 | -- **入口**:`deploy.py` 的 `main()` 在解析到 `--update_instance_num` 时调用 `handle_update_instance_num(user_config)`,随后 `return`,不会走全量 `deploy_services()`。 | ||
| 8 | -- **通用前置**:在分支判断之前,`main()` 会执行 `validate_instance_nums(user_config)`(`lib/generator/engine.py`)。`motor_deploy_config` 中的 `p_instances_num`、`d_instances_num`(PD 分离)或 `hybrid_instances_num`(PD 混部)校验链条为: | ||
| 9 | - - 字段缺失:内部调用的 `obtain_engine_instance_total`(`lib/utils.py`)抛 `KeyError`,文案 `p_instances_num is required in motor_deploy_config` / `d_instances_num is required ...`。 | ||
| 10 | - - 非整数:`obtain_engine_instance_total` 在 `int(...)` 失败时抛 `ValueError`,文案 `p_instances_num and d_instances_num must be integers`。 | ||
| 11 | - - 越界:`validate_instance_nums` 自身抛 `ValueError`,要求 `> INSTANCE_NUM_ZERO`(0)且 `<= INSTANCE_NUM_MAX`(16),常量定义在 `lib/constant.py`,文案形如 `p_instances_num must be greater than 0` / `must not exceed 16`。 | ||
| 12 | - | ||
| 13 | -## 2. 基线:集群 ConfigMap `motor-config` | ||
| 14 | - | ||
| 15 | -- **读取**:`handle_update_instance_num` 调用 `get_baseline_config_from_configmap(deploy_config["job_id"])`(`lib/generator/k8s_utils.py`)。 | ||
| 16 | -- **命令**:`kubectl get configmap motor-config -n <job_id> -o json`(`MOTOR_CONFIG_CONFIGMAP_NAME` 为 `motor-config`,`job_id` 来自当前输入 `user_config` 的 `motor_deploy_config.job_id`,用作 namespace)。 | ||
| 17 | -- **解析**:从返回 JSON 的 `data["user_config.json"]` 取出字符串,再 `json.loads` 为 dict;若命令失败、JSON 非法、缺 `data` 或缺 `user_config.json` 键,函数返回 `None`。 | ||
| 18 | -- **缺失时**:若基线为 `None`,抛出 `FileNotFoundError`:`ConfigMap motor-config not found. Please deploy once before scaling.` | ||
| 19 | - | ||
| 20 | -## 3. 仅允许修改实例数:`validate_only_instance_changed` | ||
| 21 | - | ||
| 22 | -- **实现**:`lib/config_validator.py` 的 `validate_only_instance_changed(current_config, baseline_config)`。 | ||
| 23 | -- **逻辑**:对两份配置各做一次深拷贝,并从 `motor_deploy_config` 中移除 `p_instances_num` 与 `d_instances_num` 后比较整份 dict;若不等则抛出 `ValueError`:`user_config changes detected beyond instance numbers. Only p_instances_num/d_instances_num can be modified for scaling.` | ||
| 24 | - | ||
| 25 | -## 4. 部署模式以集群基线为准 | ||
| 26 | - | ||
| 27 | -- **取值**:`deploy_mode_arg = baseline_deploy.get("deploy_mode", DEPLOY_MODE_INFER_SERVICE_SET)`(`lib/constant.py`:`DEPLOY_MODE_INFER_SERVICE_SET` 为 `"infer_service_set"`)。 | ||
| 28 | -- **校验**:`validate_deploy_mode_value(deploy_mode_arg)` 要求该值属于 `VALID_DEPLOY_MODES`:`infer_service_set`、`multi_deployment`、`single_container`;非法则 `ValueError`,文案含 `Baseline config has invalid deploy_mode`。 | ||
| 29 | - | ||
| 30 | -后续 YAML 生成与 `kubectl` 行为按 `deploy_mode_arg` 分支(见下节)。**注意**:扩缩容分支里读取的是基线里的 `deploy_mode`,不是仅凭当前本地 `user_config` 决定。 | ||
| 31 | - | ||
| 32 | -## 5. 刷新 ConfigMap 与 `kubectl` 总入口 | ||
| 33 | - | ||
| 34 | -- **统一行为**:`handle_update_instance_num` 末尾调用 `exec_all_kubectl_multi(deploy_config, baseline_config, deploy_mode_arg)`(`lib/generator/k8s_utils.py`)。 | ||
| 35 | -- **ConfigMap**:`exec_all_kubectl_multi` **首先**调用 `create_motor_config_configmap(job_id)`:用当前进程内已设置的 `g_user_config_path`(由 `main()` 中 `set_user_config_path(user_config_path)` 设置,对应本次命令行解析出的 `user_config.json` 路径)与 `startup/`、`probe/` 等文件组装 `kubectl create configmap motor-config ... --from-file=user_config.json=<路径> -n <job_id>`,经 `apply_configmap` 以 client dry-run 管道到 `kubectl apply`。 | ||
| 36 | -- **因此**:每次成功的扩缩容执行都会用**本次输入的** `user_config.json` 文件内容更新集群中的 `motor-config`(与是否走 engine 逐文件扩缩无关)。 | ||
| 37 | - | ||
| 38 | -## 6. 两种部署模式下的扩缩容行为 | ||
| 39 | - | ||
| 40 | -### 6.1 `infer_service_set` | ||
| 41 | - | ||
| 42 | -- **YAML 路径**:`get_deploy_paths()` 将 InferServiceSet 输出定为 `os.path.join(OUTPUT_ROOT_PATH, "infer_service.yaml")`,其中 `OUTPUT_ROOT_PATH` 为 `./output_yamls`(`lib/constant.py`),路径相对于在 `examples/deployer` 下执行脚本时的当前工作目录。 | ||
| 43 | -- **若 `./output_yamls/infer_service.yaml` 已存在**:调用 `update_infer_service_replicas_only(infer_output, deploy_config)`(`lib/generator/infer_service.py`):加载该 YAML,定位 `kind: InferServiceSet` 文档,将角色名为 `prefill` / `decode` 的 role 的顶层 `replicas` 分别设为当前 `deploy_config` 的 `p_instances_num`、`d_instances_num`(由 `obtain_engine_instance_total` 读出),写回同一文件,并把该路径追加到 `g_generate_yaml_list`。 | ||
| 44 | -- **若不存在**:先 `init_service_domain_name(paths, deploy_config)` 初始化服务域名;再校验模板文件 `infer_service_input_yaml`(即 `./yaml_template/infer_service_template.yaml`)是否存在,**不存在则抛 `FileNotFoundError`:`InferServiceSet template yaml not found: <path>.`**;通过后调用 `init_infer_service_domain_name(infer_input, deploy_config)` 与 `generate_yaml_infer_service_set(infer_input, infer_output, user_config)` 全量生成该文件并加入 `g_generate_yaml_list`(与首次生成 InferServiceSet 流程一致)。 | ||
| 45 | -- **`kubectl`**:`exec_all_kubectl_multi` 在 `baseline_config is not None` 且 `deploy_mode_arg == infer_service_set` 时,对 `g_generate_yaml_list` 中**每一个**文件执行 `kubectl apply -f <file> -n <job_id>`。扩缩容场景下列表通常仅含 `infer_service.yaml` 一项。 | ||
| 46 | - | ||
| 47 | -### 6.2 `multi_deployment`(及非 `infer_service_set` 时进入的 else 分支) | ||
| 48 | - | ||
| 49 | -- **YAML 生成**:调用 `generate_yaml_engine(engine_input_yaml, engine_output_yaml, user_config)`(`lib/generator/engine.py`)。`engine_output_yaml` 为 `os.path.join(OUTPUT_ROOT_PATH, g_engine_base_name)`,对每个 `p_index in range(p_total)`、`d_index in range(d_total)` 写出 `{engine_output_yaml}_p{index}.yaml` / `_d{index}.yaml`,并全部追加到 `g_generate_yaml_list`。`g_engine_base_name` 由 `update_engine_base_name` 按引擎类型设置(如 vLLM 为 `vllm`,见 `SERVER_BASE_NAME_MAP` 等)。 | ||
| 50 | -- **`kubectl`**:`exec_all_kubectl_multi` 在存在 `baseline_config` 且模式非 `infer_service_set` 时,调用 `elastic_distributed_engine_deploy(deploy_config, baseline_deploy_config, OUTPUT_ROOT_PATH)`(实现位于 **`lib/generator/k8s_utils.py`**,与 `engine.py` 中同名函数逻辑一致,实际执行以 `k8s_utils` 为准)。 | ||
| 51 | -- **缩容**:对 P 或 D,若目标实例数 `<` 基线,从 `index = base-1` 递减到 `total`,对文件 `{OUTPUT_ROOT_PATH}/{g_engine_base_name}_{p|d}{index}.yaml` 执行 `kubectl delete -f`;若文件仍存在则 `os.remove`。 | ||
| 52 | -- **扩容**:若目标 `>` 基线,对 `index in range(base, total)` 的上述路径执行 `kubectl apply -f`。 | ||
| 53 | -- **顺序**:先 `scale_engine_by_type(..., NODE_TYPE_P)`,再 `scale_engine_by_type(..., NODE_TYPE_D)`。 | ||
| 54 | - | ||
| 55 | -**说明**:`handle_update_instance_num` 在 `deploy_mode` 非 `infer_service_set` 时**不会**对 controller/coordinator 等调用 `generate_yaml_*`;仅生成 engine 多文件并由 `elastic_distributed_engine_deploy` 做增量 apply/delete。与全量部署路径不同。 | ||
| 56 | - | ||
| 57 | -## 7. 与 `--update_config` 的差异(便于对照) | ||
| 58 | - | ||
| 59 | -- **`handle_update_config`**(`--update_config`):同样读取 `motor-config` 基线;若当前 `p_instances_num`/`d_instances_num` 与基线不一致,抛出 `ValueError`,提示使用 `--update_instance_num` 做实例扩缩;否则校验 `deploy_mode` 一致及白名单字段,再 `create_motor_config_configmap`,**不**执行 `elastic_distributed_engine_deploy` 或 InferServiceSet 的 apply 列表扩缩。 | ||
| 60 | -- **基线缺失文案**:`--update_config` 下为 `ConfigMap motor-config not found or has no user_config in cluster. Please deploy once before updating configmap.` | ||
| 61 | - | ||
| 62 | -## 8. 关键符号与辅助函数(源码位置) | ||
| 63 | - | ||
| 64 | -| 名称 | 文件 | | ||
| 65 | -|------|------| | ||
| 66 | -| `handle_update_instance_num` | `examples/deployer/deploy.py` | | ||
| 67 | -| `get_baseline_config_from_configmap`、`run_cmd_get_output`、`exec_all_kubectl_multi`、`create_motor_config_configmap`、`elastic_distributed_engine_deploy`、`scale_engine_by_type` | `examples/deployer/lib/generator/k8s_utils.py` | | ||
| 68 | -| `validate_only_instance_changed`、`strip_instance_nums`、`validate_deploy_mode_value` | `examples/deployer/lib/config_validator.py` | | ||
| 69 | -| `generate_yaml_engine`、`validate_instance_nums`、`update_engine_base_name` | `examples/deployer/lib/generator/engine.py` | | ||
| 70 | -| `generate_yaml_infer_service_set`、`update_infer_service_replicas_only` | `examples/deployer/lib/generator/infer_service.py` | | ||
| 71 | -| `obtain_engine_instance_total` | `examples/deployer/lib/utils.py` | | ||
| 72 | -| `MOTOR_CONFIG_CONFIGMAP_NAME`、`OUTPUT_ROOT_PATH`、`INSTANCE_NUM_MAX` 等 | `examples/deployer/lib/constant.py` | | ||
| 73 | - | ||
| 74 | -## 9. 流程图(与代码分支一致) | ||
| 75 | - | ||
| 76 | -### 9.1 `--update_instance_num` 主流程 | ||
| 77 | - | ||
| 78 | -```mermaid | ||
| 79 | -flowchart TD | ||
| 80 | - A[main: validate_instance_nums] --> B[handle_update_instance_num] | ||
| 81 | - B --> C[get_baseline_config_from_configmap] | ||
| 82 | - C -->|None| E[FileNotFoundError: motor-config not found] | ||
| 83 | - C -->|dict| D[validate_only_instance_changed] | ||
| 84 | - D -->|失败| F[ValueError: beyond instance numbers] | ||
| 85 | - D -->|通过| G[deploy_mode 来自基线 + validate_deploy_mode_value] | ||
| 86 | - G --> H{deploy_mode == infer_service_set?} | ||
| 87 | - H -->|是| I[更新或生成 infer_service.yaml] | ||
| 88 | - H -->|否| J[generate_yaml_engine] | ||
| 89 | - I --> K[exec_all_kubectl_multi] | ||
| 90 | - J --> K | ||
| 91 | - K --> L[create_motor_config_configmap 后 apply 或 elastic_distributed_engine_deploy] | ||
| 92 | -``` | ||
| 93 | - | ||
| 94 | -## 10. 约束小结(均可在上述函数中逐项核对) | ||
| 95 | - | ||
| 96 | -1. 扩缩容前集群中需已有含 `user_config.json` 的 `motor-config`(否则 `get_baseline_config_from_configmap` 返回 `None` 并报错)。 | ||
| 97 | -2. 除 `motor_deploy_config.p_instances_num` / `d_instances_num` 外,整份 `user_config` 与基线须一致(`validate_only_instance_changed`)。 | ||
| 98 | -3. 实例数合法范围由 `validate_instance_nums` 与常量 `INSTANCE_NUM_ZERO`/`INSTANCE_NUM_MAX` 定义。 | ||
| 99 | -4. 每次 `exec_all_kubectl_multi` 都会刷新 `motor-config` 为当前命令使用的 `user_config.json` 文件内容。 | ||
| 100 | -5. 多 Deployment 模式下 engine 产物与扩缩容操作文件位于 **`./output_yamls/`** 下,文件名形如 `{engine_base_name}_p0.yaml`;InferServiceSet 模式下主要产物为 **`./output_yamls/infer_service.yaml`**。 | ||
| @@ -1,12 +1,23 @@ | |||
| 1 | -# 自动弹性扩缩容 | 1 | +# 实例扩缩容设计文档 |
| 2 | 2 | ||
| 3 | -## 特性介绍 | 3 | +MindIE Motor 提供两种实例扩缩容方式,满足不同运维场景的需求: |
| 4 | + | ||
| 5 | +- **自动扩缩容**:依据推理实例的实时负载自动调整 Prefill / Decode 实例数量,适用于负载波动大、追求资源利用率的场景。 | ||
| 6 | +- **手动扩缩容**:人工修改实例数并执行增量调整,适用于需要确定性变更行为、或集群不具备自动扩缩容条件的场景。 | ||
| 7 | + | ||
| 8 | +两种方式互补,分别对应"负载自适应"与"人工决策"两种扩缩容语义。本文档分别介绍两者的设计思路。 | ||
| 9 | + | ||
| 10 | +## 1. 自动扩缩容 | ||
| 11 | + | ||
| 12 | +### 1.1 特性概述 | ||
| 4 | 13 | ||
| 5 | 自动弹性扩缩容功能支持根据推理实例的实时负载自动调整 Prefill 和 Decode 实例数量。当请求量上升时自动扩容,当负载回落时自动缩容,在保障服务 SLA 的同时提升资源利用率。 | 14 | 自动弹性扩缩容功能支持根据推理实例的实时负载自动调整 Prefill 和 Decode 实例数量。当请求量上升时自动扩容,当负载回落时自动缩容,在保障服务 SLA 的同时提升资源利用率。 |
| 6 | 15 | ||
| 7 | 核心机制:Infer Operator 为推理实例创建 HPA(Horizontal Pod Autoscaler)资源,HPA 通过 External Metrics Adaptor 获取 MindIE Motor 汇聚的引擎级负载指标(如排队请求数、TPS、KV Cache 使用率等),按用户配置的扩缩容阈值自动调整实例副本数。 | 16 | 核心机制:Infer Operator 为推理实例创建 HPA(Horizontal Pod Autoscaler)资源,HPA 通过 External Metrics Adaptor 获取 MindIE Motor 汇聚的引擎级负载指标(如排队请求数、TPS、KV Cache 使用率等),按用户配置的扩缩容阈值自动调整实例副本数。 |
| 8 | 17 | ||
| 9 | -## 原理说明 | 18 | +支持 Atlas 800I A2 / A3 系列推理服务器,前置依赖 Infer Operator 部署与 External Metrics Adaptor(用于将 Motor 指标转换为 Kubernetes External Metrics)。 |
| 19 | + | ||
| 20 | +### 1.2 工作原理 | ||
| 10 | 21 | ||
| 11 | ```text | 22 | ```text |
| 12 | ┌──────────────────────────────────────────────────────────────────┐ | 23 | ┌──────────────────────────────────────────────────────────────────┐ |
| @@ -32,22 +43,13 @@ | |||
| 32 | 3. HPA 从 External Metrics API 获取负载数据,与用户配置的目标阈值对比。 | 43 | 3. HPA 从 External Metrics API 获取负载数据,与用户配置的目标阈值对比。 |
| 33 | 4. 当指标持续超出阈值时,HPA 通知 Infer Operator 增加副本;低于阈值时减少副本。 | 44 | 4. 当指标持续超出阈值时,HPA 通知 Infer Operator 增加副本;低于阈值时减少副本。 |
| 34 | 45 | ||
| 35 | -## 支持的产品型号 | 46 | +设计要点:**指标聚合与扩缩容决策解耦**。Motor 只负责指标的采集、聚合与暴露,扩缩容决策完全交由 HPA 标准机制完成,Motor 不感知扩缩容过程。这使得自动扩缩容能力可以复用 K8s 生态成熟的 HPA 语义(稳定窗口、冷却、多指标最保守决策等),无需自研控制器。 |
| 36 | 47 | ||
| 37 | -- Atlas 800I A2 推理服务器 | 48 | +### 1.3 指标设计 |
| 38 | -- Atlas 800I A3 超节点服务器 | ||
| 39 | 49 | ||
| 40 | -## 前置条件 | 50 | +#### 1.3.1 指标聚合视图 |
| 41 | 51 | ||
| 42 | -- 已完成 Infer Operator 的安装部署。 | 52 | +Coordinator `/metrics` 端点提供多种聚合视图,通过 `type` 参数切换,满足不同扩缩容粒度: |
| 43 | -- 已完成 MindIE Motor 推理服务的部署(PD 分离或 PD 混部模式)。 | ||
| 44 | -- 已部署 External Metrics Adaptor,用于将 MindIE Motor 指标转换为 Kubernetes External Metrics。可直接使用 [mindcluster-deploy 提供的 Metrics Adaptor 示例](https://gitcode.com/Ascend/mindcluster-deploy/tree/master/infer-operator-metrics-adaptor)进行部署。 | ||
| 45 | - | ||
| 46 | -## 配置 MindIE Motor 暴露 Metrics | ||
| 47 | - | ||
| 48 | -MindIE Motor Coordinator 默认通过 `/metrics` 端点暴露聚合后的引擎指标,无需额外配置即可使用。 | ||
| 49 | - | ||
| 50 | -Coordinator `/metrics` 端点提供多种聚合视图,通过 `type` 参数切换: | ||
| 51 | 53 | ||
| 52 | | type 值 | 说明 | 适用场景 | | 54 | | type 值 | 说明 | 适用场景 | |
| 53 | |---------|------|---------| | 55 | |---------|------|---------| |
| @@ -64,23 +66,66 @@ curl http://{coordinator-ip}:1027/metrics?type=role&role=prefill | |||
| 64 | curl http://{coordinator-ip}:1027/metrics?type=role&role=decode | 66 | curl http://{coordinator-ip}:1027/metrics?type=role&role=decode |
| 65 | ``` | 67 | ``` |
| 66 | 68 | ||
| 67 | -## 部署 External Metrics Adaptor | 69 | +#### 1.3.2 推荐的扩缩容指标 |
| 68 | 70 | ||
| 69 | -External Metrics Adaptor 负责将 Coordinator 的 Prometheus 格式指标转换为 Kubernetes External Metrics API,供 HPA 消费。 | 71 | +Prefill 为计算密集型、Decode 为访存密集型,建议分别配置不同的扩缩容指标以匹配各自的计算特征。 |
| 70 | 72 | ||
| 71 | -可使用 [mindcluster-deploy 提供的适配器示例](https://gitcode.com/Ascend/mindcluster-deploy/tree/master/infer-operator-metrics-adaptor) 直接部署,也可按需自行实现。 | 73 | +**Prefill 扩缩容推荐指标:** |
| 72 | 74 | ||
| 73 | -部署前需确认 Adaptor 配置了正确的 Coordinator metrics 端点地址和抓取间隔。部署完成后,执行以下命令验证指标可用: | 75 | +| 指标名 | 类型 | 说明 | 推荐阈值建议 | |
| 76 | +|--------|------|------|-------------| | ||
| 77 | +| `vllm:num_requests_waiting` | Gauge | 等待调度的请求数 | > 5 触发扩容,< 2 触发缩容 | | ||
| 78 | +| `vllm:num_requests_running` | Gauge | 当前运行中的请求数 | 视 NPU 规格和模型而定 | | ||
| 79 | +| `vllm:kv_cache_usage_perc` | Gauge | KV Cache 使用率(0-1) | > 0.8 触发扩容 | | ||
| 80 | +| `motor:prompt_tokens_per_second` | Gauge | Prompt token 处理速率(Motor 计算) | 按 SLA 目标设定 | | ||
| 81 | +| `vllm:time_to_first_token_seconds` | Histogram | 首 token 延迟(TTFT) | 按 SLA 目标(如 p95 < 500ms) | | ||
| 74 | 82 | ||
| 75 | -```bash | 83 | +**Decode 扩缩容推荐指标:** |
| 76 | -kubectl get --raw /apis/external.metrics.k8s.io/v1beta1 | grep -E "num_requests_waiting|motor:generation_tokens_per_second" | 84 | + |
| 85 | +| 指标名 | 类型 | 说明 | 推荐阈值建议 | | ||
| 86 | +|--------|------|------|-------------| | ||
| 87 | +| `vllm:num_requests_waiting` | Gauge | 等待调度的请求数 | > 5 触发扩容 | | ||
| 88 | +| `vllm:num_requests_running` | Gauge | 当前运行中的请求数 | 视 NPU 规格和模型而定 | | ||
| 89 | +| `motor:generation_tokens_per_second` | Gauge | 生成 token 速率(Motor 计算) | 按 SLA 目标设定 | | ||
| 90 | +| `vllm:e2e_request_latency_seconds` | Histogram | 端到端请求延迟 | 按 SLA 目标(如 p95 < 2s) | | ||
| 91 | +| `vllm:time_per_output_token_seconds` | Histogram | 跨 token 延迟(TPOT) | 按 SLA 目标(如 p95 < 50ms) | | ||
| 92 | + | ||
| 93 | +> [!NOTE]说明 | ||
| 94 | +> | ||
| 95 | +> - `motor:prompt_tokens_per_second` 和 `motor:generation_tokens_per_second` 是 MindIE Motor Coordinator 计算的服务级指标,基于 vLLM 原始 counter 计算 delta rate 得到,更准确反映实时吞吐。 | ||
| 96 | +> - Histogram 类型指标(如 `vllm:e2e_request_latency_seconds`)需要在 Adaptor 侧计算分位数(p50/p95/p99)后作为独立指标暴露。 | ||
| 97 | +> - Gauge 指标直接反映当前负载水平,优先推荐;Counter 类型指标(如 token 总数)不会因 `/metrics` 请求而重置,直接比较数值无法反映速率变化,不建议作为扩缩容依据。 | ||
| 98 | + | ||
| 99 | +#### 1.3.3 多指标组合策略 | ||
| 100 | + | ||
| 101 | +HPA 支持在同一策略中配置多个指标,并选择**最保守的扩缩容决策**(即当前副本数最接近触发扩容或缩容的指标)——这保证了任一指标触及阈值时都不会被其他指标"掩盖": | ||
| 102 | + | ||
| 103 | +```yaml | ||
| 104 | +scalingPolicy: | ||
| 105 | + type: HPA | ||
| 106 | + spec: | ||
| 107 | + minReplicas: 1 | ||
| 108 | + maxReplicas: 4 | ||
| 109 | + metrics: | ||
| 110 | + - type: External | ||
| 111 | + external: | ||
| 112 | + metric: | ||
| 113 | + name: num_requests_waiting | ||
| 114 | + target: | ||
| 115 | + type: AverageValue | ||
| 116 | + averageValue: "5" | ||
| 117 | + - type: External | ||
| 118 | + external: | ||
| 119 | + metric: | ||
| 120 | + name: kv_cache_usage_perc | ||
| 121 | + target: | ||
| 122 | + type: AverageValue | ||
| 123 | + averageValue: "0.8" | ||
| 77 | ``` | 124 | ``` |
| 78 | 125 | ||
| 79 | -## 配置弹性扩缩容策略 | 126 | +### 1.4 扩缩容策略配置 |
| 80 | 127 | ||
| 81 | -在 `examples/deployer/yaml_template/infer_service_template.yaml` 中,为 Prefill 和 Decode 角色的配置块下添加 `scalingPolicy`。 | 128 | +在 `examples/deployer/yaml_template/infer_service_template.yaml` 中,为 Prefill 和 Decode 角色的配置块下添加 `scalingPolicy`: |
| 82 | - | ||
| 83 | -以下示例为 Prefill 按排队请求数扩缩容,Decode 按生成 token 速率扩缩容: | ||
| 84 | 129 | ||
| 85 | ```yaml | 130 | ```yaml |
| 86 | roles: | 131 | roles: |
| @@ -135,7 +180,7 @@ roles: | |||
| 135 | # ... 其余配置保持不变 ... | 180 | # ... 其余配置保持不变 ... |
| 136 | ``` | 181 | ``` |
| 137 | 182 | ||
| 138 | -### scalingPolicy 参数说明 | 183 | +**scalingPolicy 参数说明:** |
| 139 | 184 | ||
| 140 | | 参数 | 说明 | 取值 | | 185 | | 参数 | 说明 | 取值 | |
| 141 | |------|------|------| | 186 | |------|------|------| |
| @@ -158,122 +203,91 @@ roles: | |||
| 158 | > | 203 | > |
| 159 | > 本特性依赖 MindCluster Infer Operator 版本 ≥ 26.1.0。用户需按上文示例在 `infer_service_template.yaml` 中手动添加 `scalingPolicy` 配置。 | 204 | > 本特性依赖 MindCluster Infer Operator 版本 ≥ 26.1.0。用户需按上文示例在 `infer_service_template.yaml` 中手动添加 `scalingPolicy` 配置。 |
| 160 | 205 | ||
| 161 | -## 推荐的扩缩容指标 | 206 | +### 1.5 关键设计考虑 |
原 auto_scaling.md 用户手册删掉后,部署 External Metrics Adaptor 和验证扩缩容效果(查 HPA、压测触发扩容、验证缩容)整段没了;scaling.md 只保留了设计向内容。如果 nav 还保留自动弹性扩缩容入口,用户会找不到操作步骤。建议要么保留 user_guide 操作篇,要么 nav 改指 design/scaling.md 并补回验证章节。 ![]() ![]() | |||
| 162 | 207 | ||
| 163 | -MindIE Motor `/metrics` 端点提供了丰富的引擎级指标,下表列出推荐用于自动扩缩容的关键指标: | 208 | +- **扩缩容范围**:自动扩缩容仅作用于 Prefill 和 Decode 实例;Router 不参与扩缩容。 |
| 209 | +- **缩容稳定窗口**:缩容存在稳定窗口(HPA 默认 5 分钟),避免负载短暂波动导致频繁扩缩。 | ||
| 210 | +- **新实例性能预热**:扩容的新实例没有 KV Cache 缓存,Prefix Cache 特性会逐步重建缓存,因此新实例的推理性能可能出现小幅度劣化并在一段时间后恢复。 | ||
| 211 | +- **依赖链路**:External Metrics Adaptor 需持续运行并正确配置 Coordinator 地址。若 Adaptor 异常,HPA 将无法获取指标,可能导致扩缩容失效。 | ||
| 212 | +- **缩容下限**:建议 `minReplicas` 至少设为 1,避免缩容到 0 导致服务完全不可用。 | ||
| 213 | +- **角色独立扩缩**:若使用不带 `type` 参数的 `/metrics` 端点(默认 `full`),HPA 获取到的是全局聚合值。如需按 Prefill/Decode 角色独立扩缩容,Adaptor 需分别请求 `/metrics?type=role&role=prefill` 和 `/metrics?type=role&role=decode`。 | ||
| 164 | 214 | ||
| 165 | -### Prefill 扩缩容推荐指标 | 215 | +## 2. 手动扩缩容设计 |
| 166 | 216 | ||
| 167 | -| 指标名 | 类型 | 说明 | 推荐阈值建议 | | 217 | +### 2.1 设计目标与适用场景 |
| 168 | -|--------|------|------|-------------| | ||
| 169 | -| `vllm:num_requests_waiting` | Gauge | 等待调度的请求数 | > 5 触发扩容,< 2 触发缩容 | | ||
| 170 | -| `vllm:num_requests_running` | Gauge | 当前运行中的请求数 | 视 NPU 规格和模型而定 | | ||
| 171 | -| `vllm:kv_cache_usage_perc` | Gauge | KV Cache 使用率(0-1) | > 0.8 触发扩容 | | ||
| 172 | -| `motor:prompt_tokens_per_second` | Gauge | Prompt token 处理速率(Motor 计算) | 按 SLA 目标设定 | | ||
| 173 | -| `vllm:time_to_first_token_seconds` | Histogram | 首 token 延迟(TTFT) | 按 SLA 目标(如 p95 < 500ms) | | ||
| 174 | 218 | ||
| 175 | -### Decode 扩缩容推荐指标 | 219 | +手动扩缩容提供**确定性、人工可控**的实例数调整手段:用户修改实例数后执行一次扩缩容操作,集群实例数精确收敛到目标值,变更行为可预期。 |
| 176 | 220 | ||
| 177 | -| 指标名 | 类型 | 说明 | 推荐阈值建议 | | 221 | +适用场景: |
| 178 | -|--------|------|------|-------------| | ||
| 179 | -| `vllm:num_requests_waiting` | Gauge | 等待调度的请求数 | > 5 触发扩容 | | ||
| 180 | -| `vllm:num_requests_running` | Gauge | 当前运行中的请求数 | 视 NPU 规格和模型而定 | | ||
| 181 | -| `motor:generation_tokens_per_second` | Gauge | 生成 token 速率(Motor 计算) | 按 SLA 目标设定 | | ||
| 182 | -| `vllm:e2e_request_latency_seconds` | Histogram | 端到端请求延迟 | 按 SLA 目标(如 p95 < 2s) | | ||
| 183 | -| `vllm:time_per_output_token_seconds` | Histogram | 跨 token 延迟(TPOT) | 按 SLA 目标(如 p95 < 50ms) | | ||
| 184 | 222 | ||
| 185 | -> [!NOTE]说明 | 223 | +- 集群未部署 HPA / External Metrics Adaptor,不具备自动扩缩容条件; |
| 186 | -> | 224 | +- 需要精确预期变更行为(如按计划扩容、临时缩容、演练); |
| 187 | ->- `motor:prompt_tokens_per_second` 和 `motor:generation_tokens_per_second` 是 MindIE Motor Coordinator 计算的服务级指标,基于 vLLM 原始 counter 计算 delta rate 得到,更准确反映实时吞吐。 | 225 | +- 作为自动扩缩容不可用时的兜底手段。 |
| 188 | ->- Histogram 类型指标(如 `vllm:e2e_request_latency_seconds`)需要在 Adaptor 侧计算分位数(p50/p95/p99)后作为独立指标暴露。 | ||
| 189 | ->- 建议为 Prefill 和 Decode 分别配置不同的扩缩容指标,以匹配各自的计算特征(Prefill 为计算密集型,Decode 为访存密集型)。 | ||
| 190 | 226 | ||
| 191 | -### 多指标组合策略 | 227 | +与自动扩缩容的边界:手动扩缩容只调整 **Prefill / Decode 实例数**,Controller、Coordinator 等控制面组件不在扩缩容路径中。 |
| 192 | 228 | ||
| 193 | -可在 HPA 中配置多个指标,HPA 会选择**最保守的扩缩容决策**(即当前副本数最接近触发扩容或缩容的指标): | 229 | +### 2.2 核心设计思路 |
| 194 | 230 | ||
| 195 | -```yaml | 231 | +手动扩缩容的设计围绕"**以集群内基线为锚、增量调整、成功即收敛**"展开,核心要点如下: |
| 196 | -scalingPolicy: | 232 | + |
| 197 | - type: HPA | 233 | +**1. 集群 ConfigMap 作为唯一基线(motor-config)** |
| 198 | - spec: | 234 | + |
| 199 | - minReplicas: 1 | 235 | +每次部署会把 user_config 持久化到集群中的 ConfigMap `motor-config`。扩缩容以集群内基线与当前输入做对比,而非以本地文件为基准——集群状态即真相,避免多客户端/多机器本地配置漂移导致误扩缩。集群中不存在基线(未部署过)时拒绝扩缩容。 |
| 200 | - maxReplicas: 4 | 236 | + |
| 201 | - metrics: | 237 | +**2. 单一变更维度约束** |
| 202 | - - type: External | 238 | + |
| 203 | - external: | 239 | +扩缩容仅允许修改实例数字段(`p_instances_num`、`d_instances_num` 或 `hybrid_instances_num`),其余配置必须与基线完全一致。该约束保证了"扩缩容 = 只改实例数"这一语义清晰、可校验,防止配置变更与实例变更混入同一次操作。其他配置的修改走全量重新部署路径,职责分离。 |
| 204 | - metric: | 240 | + |
| 205 | - name: num_requests_waiting | 241 | +**3. 增量式调整,不动存量实例** |
| 206 | - target: | 242 | + |
| 207 | - type: AverageValue | 243 | +- **扩容**:仅对新增的实例(更高 index)执行创建,已运行实例不重拉、不滚动重启,存量流量不受影响; |
| 208 | - averageValue: "5" | 244 | +- **缩容**:从最大 index 开始逆序删除,并同步清理对应部署产物。实例对等(无状态),逆序删除不会产生 index 空洞。 |
| 209 | - - type: External | 245 | + |
| 210 | - external: | 246 | +增量语义保证操作耗时与变更量成正比,大规模集群下避免全量重建。 |
| 211 | - metric: | 247 | + |
| 212 | - name: kv_cache_usage_perc | 248 | +**4. 部署模式自适应** |
| 213 | - target: | 249 | + |
| 214 | - type: AverageValue | 250 | +扩缩容路径的部署模式取自集群基线(而非本地输入),保证与集群实际拓扑一致: |
| 215 | - averageValue: "0.8" | 251 | + |
| 252 | +- **InferServiceSet 模式**:直接更新角色 replicas,与自动扩缩容共享同一份 YAML 形态; | ||
| 253 | +- **multi_deployment 模式**:按引擎类型逐个增量 apply / delete 对应实例的 YAML。 | ||
| 254 | + | ||
| 255 | +**5. 成功即刷新基线** | ||
| 256 | + | ||
| 257 | +每次成功的扩缩容都会把本次 user_config 写回 ConfigMap,成为下一次操作的基线。集群状态始终收敛于最后一次成功操作,不会出现"执行了但集群配置未记录"的漂移。 | ||
| 258 | + | ||
| 259 | +**6. 前置校验先行** | ||
| 260 | + | ||
| 261 | +实例数的整数性、范围(大于 0 且不超过 16)在触碰集群之前完成校验,快速失败,避免无效操作。 | ||
| 262 | + | ||
| 263 | +```mermaid | ||
| 264 | +flowchart TD | ||
| 265 | + A[输入 user_config + 扩缩容命令] --> B[校验实例数合法] | ||
| 266 | + B -->|非法| X[快速失败] | ||
| 267 | + B -->|合法| C[读取集群基线 motor-config] | ||
| 268 | + C -->|无基线| X | ||
| 269 | + C -->|有基线| D[校验仅实例数变更] | ||
| 270 | + D -->|有其他变更| X | ||
| 271 | + D -->|通过| E[按部署模式增量调整实例] | ||
| 272 | + E --> F[写回 ConfigMap 刷新基线] | ||
| 216 | ``` | 273 | ``` |
| 217 | 274 | ||
| 218 | -## 验证扩缩容效果 | 275 | +### 2.3 与自动扩缩容的关系 |
| 219 | 276 | ||
| 220 | -### 查看 HPA 状态 | 277 | +两种方式互补而非互斥: |
| 221 | 278 | ||
| 222 | -```bash | 279 | +| 维度 | 自动扩缩容 | 手动扩缩容 | |
| 223 | -kubectl get hpa -n {namespace} | 280 | +|------|-----------|-----------| |
| 224 | -``` | 281 | +| 决策主体 | HPA(负载自适应) | 人工 | |
| 282 | +| 变更粒度 | min/max 范围内连续调整 | 一次性精确设定实例数 | | ||
| 283 | +| 适用场景 | 负载波动大、追求资源利用率 | 无自动扩缩容环境、需确定性变更 | | ||
| 284 | +| 依赖 | Infer Operator + HPA + External Metrics Adaptor | 仅需集群 ConfigMap 基线 | | ||
| 225 | 285 | ||
| 226 | -回显示例如下: | 286 | +两者可组合使用:手动扩缩容设定部署层实例数基数,自动扩缩容在 HPA 的 min/max 范围内进一步动态调整副本数。 |
| 227 | 287 | ||
| 228 | -```text | 288 | +## 3. 参考文档 |
| 229 | -NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE | ||
| 230 | -prefill-my-test StatefulSet/prefill-my-test 3/5 1 4 2 10m | ||
| 231 | -decode-my-test StatefulSet/decode-my-test 8/10 1 4 2 10m | ||
| 232 | -``` | ||
| 233 | - | ||
| 234 | -- `TARGETS` 列显示 `当前值/目标值`,当前值超过目标值时触发扩容。 | ||
| 235 | -- `REPLICAS` 列显示当前实际副本数。 | ||
| 236 | - | ||
| 237 | -### 模拟负载触发扩容 | ||
| 238 | - | ||
| 239 | -发送大量并发推理请求,观察 HPA 是否自动扩容: | ||
| 240 | - | ||
| 241 | -```bash | ||
| 242 | -# 并发发送请求 | ||
| 243 | -for i in {1..100}; do | ||
| 244 | - curl -X POST "http://{service-ip}:31015/v1/chat/completions" \ | ||
| 245 | - -H "Content-Type: application/json" \ | ||
| 246 | - -d '{"model": "your-model", "max_tokens": 100, "messages": [{"role": "user", "content": "Hello"}]}' & | ||
| 247 | -done | ||
| 248 | -``` | ||
| 249 | - | ||
| 250 | -随后查看 HPA 状态,确认 `REPLICAS` 是否增加,以及新增引擎 Pod 是否正常运行: | ||
| 251 | - | ||
| 252 | -```bash | ||
| 253 | -kubectl get hpa -n {namespace} --watch | ||
| 254 | -kubectl get pod -n {namespace} | grep -E "prefill|decode" | ||
| 255 | -``` | ||
| 256 | - | ||
| 257 | -### 验证缩容 | ||
| 258 | - | ||
| 259 | -停止负载后,观察数分钟(由 HPA `--horizontal-pod-autoscaler-downscale-stabilization` 默认 5 分钟),确认实例数回落至 `minReplicas`: | ||
| 260 | - | ||
| 261 | -```bash | ||
| 262 | -kubectl get hpa -n {namespace} --watch | ||
| 263 | -``` | ||
| 264 | - | ||
| 265 | -## 注意事项 | ||
| 266 | - | ||
| 267 | -- HPA 弹性扩缩容当前仅支持 Prefill 和 Decode 实例;Router 不参与扩缩容。 | ||
| 268 | -- 缩容存在稳定窗口(默认 5 分钟),避免负载短暂波动导致频繁扩缩。 | ||
| 269 | -- 扩容的新实例没有 KV Cache 缓存,Prefix Cache 特性会逐步重建缓存,因此新实例的推理性能可能出现小幅度劣化并在一段时间后恢复。 | ||
| 270 | -- External Metrics Adaptor 需持续运行并正确配置 Coordinator 地址。若 Adaptor 异常,HPA 将无法获取指标,可能导致扩缩容失效。 | ||
| 271 | -- 建议 `minReplicas` 至少设为 1,避免缩容到 0 导致服务完全不可用。 | ||
| 272 | -- Counter 类型指标(如 token 总数)不会因 `/metrics` 请求而重置,建议优先使用 Gauge 类型指标或 MindIE Motor 计算的 TPS 指标作为扩缩容依据。 | ||
| 273 | -- 若使用不带 `type` 参数的 `/metrics` 端点(默认 `full`),HPA 获取到的是全局聚合值。如需按 Prefill/Decode 角色独立扩缩容,Adaptor 需分别请求 `/metrics?type=role&role=prefill` 和 `/metrics?type=role&role=decode`。 | ||
| 274 | - | ||
| 275 | -## 参考文档 | ||
| 276 | 289 | ||
| 277 | - [配置基于负载的弹性扩缩容](https://gitcode.com/Ascend/mind-cluster/blob/master/docs/zh/scheduling/04_usage/09_infer_operator_best_practice/05_configuring_elastic_scaling.md) — Infer Operator 弹性扩缩容策略配置指南 | 290 | - [配置基于负载的弹性扩缩容](https://gitcode.com/Ascend/mind-cluster/blob/master/docs/zh/scheduling/04_usage/09_infer_operator_best_practice/05_configuring_elastic_scaling.md) — Infer Operator 弹性扩缩容策略配置指南 |
| 278 | -- [Metrics 可观测性指标设计文档](../../design/metrics.md) — MindIE Motor Metrics 子系统架构与指标说明 | 291 | +- [Metrics 可观测性指标设计文档](./metrics.md) — MindIE Motor Metrics 子系统架构与指标说明 |
| 279 | -- [监控接口](../api/monitoring_interfaces.md) — MindIE Motor `/metrics` 端点使用说明 | 292 | +- [监控接口](../user_guide/api/monitoring_interfaces.md) — MindIE Motor `/metrics` 端点使用说明 |
| 293 | +- [手动扩缩容用户手册](../user_guide/features/manual_scaling.md) — 手动扩缩容操作步骤与常见问题 | ||
| @@ -8,7 +8,7 @@ | |||
| 8 | 8 | ||
| 9 | 本特性通过ETCD分布式锁机制实现Kubernetes集群中Controller的主备倒换功能,确保系统高可用性。开启Controller主备倒换特性开关后,系统会在初始化阶段拉起两个Controller实例,通过ETCD分布式锁竞争来实现主备身份选举,当主Controller发生故障时,备用Controller能在设定时间间隔后自动接管工作。 | 9 | 本特性通过ETCD分布式锁机制实现Kubernetes集群中Controller的主备倒换功能,确保系统高可用性。开启Controller主备倒换特性开关后,系统会在初始化阶段拉起两个Controller实例,通过ETCD分布式锁竞争来实现主备身份选举,当主Controller发生故障时,备用Controller能在设定时间间隔后自动接管工作。 |
| 10 | 10 | ||
| 11 | -了解主备详细设计请参考[主备倒换特性设计文档](../../../design/standby.md)。 | 11 | +了解主备详细设计请参考[主备倒换特性设计文档](../../../design/fault_tolerance/standby.md)。 |
| 12 | 12 | ||
| 13 | **限制与约束** | 13 | **限制与约束** |
| 14 | 14 | ||


删/移了文档但 docs/zh/.nav.yaml 没同步,侧栏会 404:第 37 行还指着已删的 user_guide/features/auto_scaling.md,第 64 行还是 design/standby.md(已移到 fault_tolerance/),第 67 行还是 design/manual_scaling.md(已合并为 scaling.md)。PR 正文写清理与链接同步,nav 漏改的话读者从文档站进不去。