推理服务
HeteroFlow 是本站的异构算力与大模型调度产品。推理服务。推理服务 推理服务是 HeteroFlow 任务组(TaskGroup)体系面向大模型 serving 场景的特化形态:用户指定模型、引擎镜像与卡数,平台完成引擎选择、容器编排、健康检查、端口分配、API Key 发放与 OpenAI 兼容网关接入,把一份模型文件变成一个可被 O…
推理服务
推理服务是 HeteroFlow 任务组(TaskGroup)体系面向大模型 serving 场景的特化形态:用户指定模型、引擎镜像与卡数,平台完成引擎选择、容器编排、健康检查、端口分配、API Key 发放与 OpenAI 兼容网关接入,把一份模型文件变成一个可被 OpenAI SDK 直接调用的推理端点。服务的整个运行期——休眠唤醒、自动伸缩、滚动更新、故障重建、闲时调度窗口——均由平台托管,推理任务长期运行、不受运行超时约束。
推理服务与训练服务、普通容器任务共享同一节点池、同一调度器与同一套租户隔离和用量核算基础设施;训练任务可借用休眠中的推理卡,推理服务也可与同租户服务叠加在同一张卡上按显存承诺共存。网关层的负载均衡、灰度路由、会话粘性与限流见《推理网关》篇,本篇聚焦服务本身的创建、配置与生命周期。
功能总览
| 能力 | 一句话说明 | 状态 |
|---|---|---|
| 服务创建配置面 | 镜像 / 引擎 / 模型路径 / 卡数 / 并行策略 / 端口 / 健康检查 / 显存占用比逐项可配,非法值建组即 400 | 真机验证(vLLM / llama.cpp 多节点) |
| 部署形态 | 单卡、单机多卡张量并行(TP)、多副本独立(Split)、数据并行、流水线并行五种 parallelism | 已实现(TP/单卡真机验证) |
| 推理引擎体系 | 五大引擎 + 平台特化变体,按 GPU 类型自动路由,支持显式指定与镜像名反推 | 真机验证(vLLM;矩阵见下文) |
| 健康检查与启动宽限期 | HTTP/TCP 探测,宽限期内失败不计数,判失败后有限重建 | 已实现 |
| 休眠 / 唤醒 | 空闲超时自动休眠释放显存,请求到达自动唤醒;借卡排队、节点离线换节点重建 | 真机验证 |
| 一卡多服务 | 显存占用比按卡记账准入 + 单节点推理容器数上限,跨租户共卡受共享租户组红线约束 | 已实现 |
| CPU 内存 KV Cache 卸载 | vLLM kv_offload_gb,按镜像内引擎版本选择参数形态,不支持即拒绝启动 | 实验性(真机端到端未核实) |
| 自动伸缩 | 六类指标按比例一次到位,rank-0 保护,冷却防抖 | 已实现 |
| 滚动更新 | surge 式分批替换、批次重试与整体回滚、执行租约互斥与崩溃恢复 | 已实现 |
| 调度窗口 | 闲时暂停 / 缩容 / 停止,回窗自动恢复 | 已实现 |
| 推理服务去重与跨租户复用 | 创建期命中同模型同镜像的活跃组直接复用或唤醒,共享租户组内可跨租户 | 已实现 |
| 模型扫描与一键建服务 | Agent 扫描本地模型上报,一键注册并按节点引擎能力自动建服务、生成 Key | 真机验证 |
| OpenAI 兼容调用 | /v1/chat/completions 等四个端点,改 URL 与 Key 即可迁移 | 真机验证(450 并发压测) |
核心概念:四类资源与一条调用链
一条推理服务链路由四类资源构成,创建时一次成型:
TaskGroup(服务管理单元,状态机与编排参数)
├── Task × N(执行单元 = 一个 Docker 容器,rank 0 为 master)
├── InferenceEndpoint(对外 API 入口,签发 API Key,明文只返回一次)
└── ModelRoute(模型名 → 该组的路由规则,priority / weight / version)- TaskGroup 推理服务的管理单元。关键字段:
task_type=inference(权威类型字段,控制面 8 条创建路径统一写入)、parallelism并行策略、world_sizeGPU 总数、current/min/max_replicas副本与伸缩范围、endpoint关联端点 ID(删端点时同步清空,避免悬空引用)、inference_templates引擎模板链(恢复循环据此跨引擎重建)、current_generation当前代际(滚动更新递增,用于追踪副本属于哪一代)、rolling_update_state与rolling_update_message(更新结论与失败原因)、idle_timeout_minutes空闲休眠阈值、health_check/scale_policy/schedule_window三组 JSON 配置。 - Task 实际执行单元,一个容器。
rank组内序号、gpu_required卡数、gpu_id/node_id绑定的卡与节点、model_path模型路径(所有推理创建路径均落库)、ports端口映射、estimated_gpu_memory_mb预估显存。 - InferenceEndpoint 对外 API 入口。端点名称同一租户内唯一(重名 409);API Key 格式
sk-{uuid},明文只在创建响应中返回一次,库中仅存 sha256 哈希与前缀,丢失只能轮换不能找回;端点port对非平台运营者校验必须落在本组任务声明/映射的端口集合内;external_port校验 0-65535 但当前无运行时消费(网关不据此转发)。 - ModelRoute 模型名到任务组的路由。
model_name是调用时model参数要匹配的标识(建组时取请求的model_name,未传回退服务名称);priority0 最高(非运营者写负数 400);weight0-100,0 表示仅作降级备选;version版本标签(v1 / canary)。建组自动注册的那条固定priority=0, weight=100, active。
服务名称与对外模型名是两个不同的字段:name 是管理展示名(列表卡片、子任务命名、端点命名),model_name 是调用方在请求体 model 里要填的值。前端从下拉选中模型时会自动记录真实模型名作为 model_name;手填模型路径时不传 model_name,对外模型名等于服务名称。网关侧还有一层模型名重映射:确认请求的 model 命中某条 ModelRoute 后,网关向后端查 /v1/models(30 秒缓存)拿到引擎实际注册的模型 ID 替换后再转发,调用方无需关心后端注册名。
生命周期全状态
pending → scheduling → running ⇄ sleeping
↓ ↓ (唤醒中间态 waking)
failed completed
running 副本在滚动更新中另经 Draining 排空连接后退出| 状态 | 行为 |
|---|---|
| pending | 已建组待调度;失败重建的退避等待也在这一态(30s×2^(n-1),上限 5 分钟) |
| scheduling | 调度器逐层过滤选节点选卡,CAS 置 scheduled 后交 Agent 认领 |
| running | 容器健康检查通过后进入;推理任务不受运行/排队超时约束(task_type=inference 豁免) |
| sleeping | 空闲超时或手动休眠;仍计入持卡显存承诺与推理容器名额,便于快速唤醒 |
| waking | 唤醒中间态,Agent 按容器实际状态恢复(start / unpause / 调 /wake_up) |
| failed | 健康检查达阈值、容器非零退出或引擎拒绝启动;恢复循环自动重建(预算 5 次) |
| completed | 用户主动停止的终态(经 stopping);除此之外推理容器的任何退出都按服务下线判 failed |
创建流程:提交 POST /api/v1/task-groups 后,控制面先做参数与租户/配额校验,通过后写入 task_type(显式传入优先,未传按推理意图信号——model_path 非空 / enable_api / 引擎镜像关键字——判为 inference),创建 TaskGroup 与 N 个 Task,自动注册 ModelRoute,enable_api 且有端口映射时创建 InferenceEndpoint 并生成 API Key(明文仅此一次返回);带 model_path 时 enable_api 强制置真,完全没有端口映射会自动补 container_port=8000。ModelRoute / Endpoint 创建失败不阻断建组,记告警日志。若推理去重开关开启且命中同模型同镜像的活跃组,则不再创建新资源(见「推理服务去重」)。
调度流程(调度器每秒一轮):GPU 类型过滤 → VRAM 感知过滤 → 模型位置感知(优先有该模型的节点)→ 推理引擎优先(任务需要哪个引擎,装了该引擎的节点加分)→ 负载阈值检查 → 插件打分(spread / binpack / topology / rdma 等)→ 推理专属两层过滤(单节点推理容器数上限 + 按卡显存承诺准入)→ CAS 状态更新。全部装不下时返回 InsufficientResources 排队,不硬塞。调度器侧另有两道通用机制对推理生效:显存保留(固定保留默认 500MB + 比例保留默认 10%,可用显存 = 总显存 - 已用 - 两项保留)与抢占(高优先级任务资源不足时可抢占低优先级任务,CAS 保证并发安全;Worker Pool 多协程并行调度,优先级队列先来先服务)。排不完队时常见原因:节点离线或 GPU 类型不匹配、剩余显存不足、单节点推理容器数达上限(休眠中的服务仍占名额)、同卡显存承诺装不下(调小本服务 gpu_memory_utilization 或等空闲)、跨租户共卡红线(与卡上任何租户不同组即不可共存)。
拉起与健康检查:Agent 认领任务后构建 docker run 命令、注入 GPU 运行时参数、挂载模型、设置端口映射并启动容器;健康检查从容器真正启动这一刻开始计,启动宽限期(默认 60 秒,零值兜底 30 秒)内的探测失败只记日志不计数,避免模型冷启动未完成就被误杀;休眠期间暂停探测并清零失败计数。连续失败达阈值(默认 3 次)任务判 failed,恢复循环自动重建,上限 5 次;计数清零有门槛——上一代必须稳定运行满 60 秒,防止"能起来但很快退出"的服务无限重建。每次真实重建都落一条组级容错事件,推理服务详情页的容错事件时间线可见(容器丢失重排、节点失联迁移、调度窗口暂停恢复、GPU 卡丢失同在一条时间线)。
休眠与唤醒:休眠的落地是调用 vLLM /sleep?level=2(模型权重卸载到 CPU,引擎不支持时跳过)加 docker stop,显存彻底释放;代价是唤醒需重启容器重新加载模型(等待 vLLM 就绪最长 120 秒)。唤醒有三种非常规分支,都如实反馈而不是静默失败:
| 分支 | 处理 | 对外表现 |
|---|---|---|
| 物理卡被训练任务借用 | 不做状态迁移,记 wake_requested 排队;训练结束后由调度器巡检循环自动完成唤醒 | 手动唤醒返回 202;借用期间无需人工干预 |
| 节点离线 | 唤醒需求排队,调度器确认不可承接后把任务迁回 pending,换在线节点重建 | 手动 202(rescheduling);网关懒唤醒 503 + wake_failed_node_offline + Retry-After 60 |
| 正常唤醒 | sleeping → waking → running,Agent 按容器 exited/paused/running 三态选恢复动作 | 任务对象回到 running |
创建配置详解
基本配置
| 配置项 | 必填 | 类型 / 取值 | 默认 | 说明 |
|---|---|---|---|---|
服务名称 name | 是 | 字符串 | — | 管理展示名,可随时改,不影响调用方式 |
对外模型名 model_name | 否 | 字符串 | 回退用 name | 调用时 model 要填的值;建议显式传真实模型名 |
Docker 镜像 task.image | 是 | 镜像引用,支持 digest 固定形态 name[:tag]@sha256:... | — | 正则校验格式,不合法 400;前端提供已验证镜像预设一键填入 |
推理引擎 task.engine | 否 | vllm / vllm-mtt / sglang / llamacpp / mindie / trtllm | 自动识别(按镜像名) | 显式指定为最高优先级 override,不检查探测可用性、失败在 Docker 层可见 |
启动命令 task.command | 否 | 字符串 | 引擎路由自动生成 | 自带命令则引擎路由不介入(此时配 KV 卸载等依赖引擎路由的能力会建组即 400) |
GPU 数量 task.gpu_required | 是 | 1-64 | — | Split 模式下语义为副本数量,每副本固定 1 卡 |
GPU 类型 task.gpu_type | 否 | NVIDIA / 海光DCU / 摩尔线程MUSA / 昇腾 / AMD 等 | 自动 | 不选时调度器自动匹配;流水线并行组严格按声明类型选节点 |
端口映射 task.ports | 建议 | 8000 或 8080:8000 | 8000 | 宿主机端口由端口池(默认 8000-8999)自动分配避冲突;推理服务务必声明端口映射,否则网关无法回连后端 |
运行超时 timeout | 否 | 秒,0 = 不限 | 0 | 推理任务豁免运行超时,此项对推理实际不生效 |
空闲超时 idle_timeout_minutes | 否 | 分钟,0 = 不休眠 | 0 | 请求队列连续空闲超过该时长自动休眠;建组后可单独修改,不触发滚动更新 |
模型与 API 配置
| 配置项 | 默认 | 说明 |
|---|---|---|
模型路径 task.model_path | — | 节点上的真实路径。接口层不强制,但所有引擎的命令构建都要求非空——留空会建组成功、容器起不来,前端已做提交前必填拦截 |
容器内路径 containerPath | /model | 模型挂载点,自动生成只读卷挂载 |
启用缓存 enableCache | true | 注入 HETEROFLOW_MODEL_CACHE=true,Agent 按缓存目录扫描本地模型 |
启用 API enable_api | true | 自动创建端点与 Key;带 model_path 时强制开启 |
模型路径还有两个副作用:调度器优先把任务调到已有该模型的节点;建组时按路径估算显存做上限预检(估不出则跳过)。模型路径必须通过节点卷白名单校验才会被挂载,白名单外的路径不探查不挂载,容器内找不到模型会显式失败。
健康检查
| 字段 | 默认 | 说明 |
|---|---|---|
type | http | http / tcp 两种 |
path | /health | HTTP GET 路径 |
interval_seconds | 10 | 检查间隔 |
timeout_seconds | 3 | 单次超时 |
unhealthy_threshold | 3 | 连续失败达阈值判失败并杀容器 |
start_period_seconds | 60(零值兜底 30) | 启动宽限期:从容器真正启动起算,拉镜像/建容器/端口重试不占宽限;期内失败不计数。大模型建议 300 秒起;建组后可单独修改救活误杀循环中的服务,不触发滚动更新 |
这是任务组容器级健康检查(Agent 执行、判失败杀容器),与网关端点级探测(只摘路由不杀容器)是两套机制。
资源限额:一卡多服务
| 机制 | 取值 | 说明 |
|---|---|---|
GPU 显存占用比 gpu_memory_utilization | 1-100,缺省回退全局配置再缺省 90 | 写进组内每个任务,Agent 据此拼 vLLM --gpu-memory-utilization;前端按模型体积给建议档位(<2GB 建议 20%-30%、<15GB 30%-50%、<40GB 50%-70%、更大 85%-95%,只提示不锁定) |
| 按卡显存记账与准入 | 卡上所有持卡推理任务的百分比之和 ≤ 100 | 新任务要求已承诺 + 自己 ≤ 100,超了排队不硬塞;候选卡优先选剩余最多的 |
单节点推理容器数上限 max_inference_per_node | 4,可调 1-4 | 同节点持卡推理容器总数(不分租户不分模型),超限整节点出局;与显存上限两层分别把关 |
| 跨租户共卡红线 | 必须同租户或共享租户组内 | 与卡上每一个已有推理任务都满足才行;查不出来按不允许处理(fail-closed);排队等空闲不看租户,同时共存才看 |
注意 tensor_parallel 绕开显存共享记账:TP 组走整卡独占计数(节点剩余整卡数 ≥ TP 度),不参与百分比协议。推理与非推理任务不共卡,唯一例外是训练借用 sleeping 推理卡。共卡后节点 GPUUsed 按数量口径可能超过物理卡数,这是预期行为。
CPU 内存 KV Cache 卸载(vLLM,实验性)
可选开关 kv_offload_gb(GiB/实例,1-4096),默认关闭。开启后 Agent 探测镜像内 vLLM 版本选择参数形态,不支持就拒绝启动而不是静默忽略:
| 镜像内 vLLM 版本 | 平台行为 |
|---|---|
| ≥ 0.11.1 | --kv-offloading-backend native --kv-offloading-size <GiB>;≥ 0.27.0 起为私有 IPC 容器配足 --shm-size |
| 0.6.0 ~ 0.7.x | 语义退化:--swap-space <每卡GiB> --preemption-mode swap(被抢占序列的 KV 块换出到 CPU) |
| 0.8.0 ~ 0.11.0、< 0.6.0、探测不到版本 | 拒绝启动,任务 failed 并给出明确原因 |
配套约束:张量并行多卡实例的 GiB 为全部 TP rank 合计,多副本每副本各占;训练作业、流水线并行、显式非 vLLM 引擎、自带 command 建组即 400;修改必须随 image 一起提交(参数在启动命令里,只有滚动更新重建容器才生效);主机内存 70% 红线——本节点全部卸载实例声明量合计 ≤ MemTotal 的 70%,且本实例 ≤ 当前 MemAvailable 的 70%,超出拒绝启动。真机端到端验证状态:未核实。
QoS 级别
QoS 不由用户选择(创建表单里的下拉是曾不生效的假控件,已摘除),由服务端按分片落点节点的硬件能力推导:MIG 落 gold(硬件切分,独占硬件分区),MPS 落 silver(驱动级配额),其余落 bronze(软件记账)。推导表中的 vNPU / vMLU / vGPU 判据是将来预留的死输入——当前 agent 只为 NVIDIA 卡上报分片能力位(mig_capable / mps_capable),所有国产卡节点一律落 bronze;容器化节点无论什么卡一律 bronze。详见《GPU 显存分片》。
部署形态:单卡 / 张量并行 / 多副本
| parallelism | 语义 | 节点数 | 任务数 | 每任务 GPU |
|---|---|---|---|---|
| single(单卡) | 单容器单卡推理 | 1 | 1 | gpuRequired |
| tensor_parallel(多卡 TP) | 单容器多卡张量并行,所有 GPU 同一节点 | 强制 1 | world_size | gpuRequired |
| split(多副本 Split) | 多副本独立,每副本 1 卡 | 自动计算 | world_size | 1(固定) |
| data_parallel | 数据并行(与 split 同为副本型并行的历史拼法,下游逻辑两者兼容) | ⌈world_size/8⌉ | world_size | gpuRequired |
| pipeline_parallel | 流水线并行,host 网络模式 | world_size | world_size | gpuRequired |
多卡张量并行的共享内存细节:vLLM 等引擎的多进程执行器用共享内存做卡间 IPC,Docker 默认 64MB 的 /dev/shm 会让 NCCL/多进程初始化静默卡死(真机 A10×2 TP=2 实测 GPU 停在空载)。Agent 对单容器多卡推理任务自动注入 --ipc=host --shm-size=16g(TP 单机 bridge 网络即可,不加 host 网络);流水线并行容器则注入 --ipc=host --shm-size=16g --network=host。
流水线并行的收口:会用 MindIE 的 PP 组建组即 400(MindIE 没有 PP/跨节点 rendezvous 形态,只允许 tensor_parallel 或 split,避免每个 rank 各自加载全量模型的静默假成功);PP 组各 stage 一致声明 gpu_type 时严格按该类型选节点——存在但离线则等待不回落别家卡,租户可见范围内没有该类型节点则明确拒绝(文案区分"存在但不可用"与"没有节点上报该类型")。
推理引擎体系
引擎支持矩阵
| 引擎 | 支持卡型 | 优先级链位置 | 状态口径 |
|---|---|---|---|
| vLLM | nvidia(CC≥7.5)/amd/hygon | nvidia/amd/hygon 链首 | 真机验证,多节点反复验证 |
| SGLang | nvidia(CC≥sm_80)/amd/hygon | nvidia 链第二 | 已实现(T4 sm_75 实测不兼容 KeyError sm_75;L20+驱动 580 环境 lmsysorg 官方镜像缺 nvcc 起不来——2026-09-12 因此把链首调回 vLLM) |
| llama.cpp | nvidia(含 P100 CC 6.0 老卡)/amd/hygon | 各链兜底 | 真机验证,P100 兼容推理镜像自建验证 |
| MindIE | ascend 专用 | ascend 链唯一 | 适配(探测逻辑就绪,无昇腾真机) |
| vLLM-MTT | musa 摩尔线程专用 | musa 链唯一 | 适配(无摩尔线程真机) |
| TensorRT-LLM | nvidia(仅显式指定) | 不进自动链(ExplicitOnly) | 适配(客户自带已编译引擎,平台只运行不编译;单测钉住、无真机) |
| Transformers | — | 已退役 | K50 起各入口 400(从未提供真推理,桩服务) |
GPU 架构兼容速查
| 架构档 | vLLM | SGLang | llama.cpp | 结论 |
|---|---|---|---|---|
| sm_75 及更早(T4 / P4) | 可用 | 不兼容(CUTLASS/FlashInfer 不支持 sm_75,启动即 KeyError) | 可用 | 老卡选 vLLM 或 llama.cpp |
| sm_80+(A100 / A10 / A40 / H100) | 可用 | 可用 | 可用 | 三引擎全可选 |
| P100(CC 6.0) | vLLM latest 不兼容 | 不兼容 | 可用 | 平台自动落 llama.cpp 兜底 |
引擎选择链
选择按三级优先裁决:
1. 显式指定:task.engine 字段(或组内 heteroflow.engine 标签)最高优先,绕过可用性探测,失败在 Docker 层可见报错。TensorRT-LLM 只有这条路径(或镜像名含 trtllm 关键字)才可能被选中。
2. 镜像名反推:未显式指定时按镜像名关键字判断(含 vllm → vllm,含 sglang → sglang,含 llamacpp/llama.cpp → llamacpp,含 trtllm/tensorrt-llm → trtllm),效果等同显式指定。这是全新节点首次运行某引擎镜像时唯一能自愈的路径——首次提交时镜像必然未被探测为可用,没有这条反推任务会必然报 no available engine。
3. GPU 类型优先级链:nvidia: vllm → sglang → llamacpp;amd/hygon: vllm → llamacpp;musa: vllm-mtt;ascend: mindie。沿链取第一个"已探测可用且支持该卡型"的引擎;Agent 配置 engine_priorities 可覆盖扩展。
引擎可用性探测(Agent 侧持续探测):启动时全量探测 Docker 镜像与 pip 包(GPU 感知——vLLM 镜像仅在存在 CC≥7.5 的 NVIDIA 卡时才认可用),此后每 20 次心跳(默认心跳 5 秒,约 100 秒)重探一次并无条件回灌路由器。镜像判定是精确匹配裸 tag:本地必须存在与模板完全同名同 tag 的引用才算可用,只有私仓前缀不同的副本判不可用(2026-09-11 真机事故后收紧——此前按后缀匹配曾导致探测误报可用、运行时向公网拉镜像连续超时)。私仓用户照 WARN 日志提示 docker tag 一次即可被识别。
控制面一键建服务的引擎选择(GPU 感知):按节点心跳上报的引擎可用性 + GPU 算力从模板链取项——vllm(要求 NVIDIA CC≥7.0)→ sglang → llamacpp,P100 等旧卡被算力门槛拦下自动落 llama.cpp 兜底;节点没上报任何引擎或全部不匹配时在创建任何资源之前明确失败,绝不编造默认镜像。配套查询接口 GET /api/v1/nodes/{id}/inference-template 返回该节点当前全部可用模板(按链排序,第一项即推荐项),查不到返回空数组。模板链同时写入 TaskGroup 的 inference_templates,恢复循环据此跨引擎 fallback 重建。
TensorRT-LLM:客户自带已编译引擎
平台不负责编译 TensorRT-LLM 引擎,只负责运行客户已编译好的引擎——这不是"全自动支持 TensorRT-LLM"。接入形态:客户在与目标 GPU 同架构的机器上离线编译引擎(编译产物不可跨架构复用),把引擎目录打进自定义镜像(私有镜像走拉取凭证机制);创建服务时显式选择 trtllm 引擎,"模型路径"填容器内引擎目录,平台拼 trtllm-serve <目录> --host 0.0.0.0 --port <port> 启动(多卡 TP 加 --tp_size N,引擎须按相同 TP 度编译),不挂载宿主机模型目录。硬边界:仅 NVIDIA;绝不参与自动选择(优先级链不含、模板 ExplicitOnly、探测不上报,三道闸各有测试钉住);仅支持单卡/单机多卡 TP/多副本 Split,跨节点 PP 不支持;架构或 TP 度不匹配由 trtllm-serve 启动时自行报错(可见失败),平台读不到引擎元信息无法提前校验。
自动伸缩
| 配置 | 取值 | 说明 |
|---|---|---|
auto_scale | 默认 false | 开关 |
min_replicas | ≥1,默认 1 | 低负载保底 |
max_replicas | ≥0,默认 0(不限) | 上限 |
metric | 六选一 | gpu_utilization / cpu / request_queue / concurrent_requests / vllm_pending_requests / vllm_running_requests |
targetValue | 百分比或计数 | 与指标类型匹配 |
cooldownSeconds | ≥30,默认 60 | 两次伸缩最小间隔 |
AutoScaler 由多实例中的 leader 执行,30 秒周期。伸缩公式按比例一次到位,不是逐次 ±1:
desired = ceil(current_replicas × 当前指标值 / targetValue)
指标为 0(空闲)→ desired = min_replicas
clamp 到 [min_replicas, max_replicas],diff 一次性扩足/缩足
缩容时 rank-0/master 副本受保护,创建不足 60 秒的新副本不缩若该服务启用了调度窗口且当前处于窗口外时段,AutoScaler 跳过整轮评估,避免与调度窗口的缩容动作互相拉扯。
滚动更新
PUT /api/v1/task-groups/{id}/update,两种策略:
- rolling(滚动):按批次 surge 式替换——先创建新一代 task(继承同 rank 上一代的全部任务意图字段 + 新镜像),调度并等新代 running 且健康,之后旧 task 才置 Draining、等网关连接排空再停。批次失败同批重试最多 3 次,用尽则整体回滚(停新代、恢复旧代、重建已淘汰旧副本),
rolling_update_state=failed。 - recreate(重建):停全部、一次性建全部,适合允许停机的场景。
代际追踪:TaskGroup 的 current_generation 在取得执行租约的同一 CAS 语句里 +1,副本按代际归属,回滚按代际恢复;字段继承覆盖 env / volumes / 显存与抢占字段等全部任务意图(放置结果剥掉、host_port 清零重分配)。
互斥与崩溃恢复:同一服务的滚动更新由任务组行上的执行租约互斥(TTL 60 秒、执行协程每 15 秒续期、一次最长持有 16 分钟)。持有者崩溃或卡死后租约过期,恢复扫描(所有 server 实例启动时 + 每 30 秒)把该组置 failed 并把原因写入 rolling_update_message——接口返回该字段,详情页在 failed 时展示失败原因,之后可重新提交更新。健康检查配置与空闲休眠超时可单独提交修改(不 bump 代际、不重建容器、不触发滚动更新),正陷在误杀循环里的服务不必删了重建。
调度窗口
面向成本优化场景,在非工作时间自动减少资源占用:
| 参数 | 说明 |
|---|---|
enabled / start_time / end_time / timezone | 开关与窗口时段(HH:MM,支持跨午夜如 22:00-06:00;start == end 视为全天在窗内,不会误触发) |
action | pause 暂停调度 / scale 缩容到 scale_to / stop 停止所有任务 |
scale_to | scale 动作的目标副本数 |
执行器 leader-only 每 60 秒检查:窗口外执行 action(触发前把当前副本数存进标签,避免中途重启丢失恢复目标,动作幂等不重复触发);回到窗口内 pause/scale 自动恢复到保存的副本数,stop 是单向动作不自动恢复;窗口被关闭但动作已生效时下一轮自动恢复。窗口外期间副本数由窗口执行器接管,AutoScaler 跳过该组。窗口恢复与手动扩容扩出的新副本归属组创建者(而非系统操作者),保证只看得见自己任务的成员也能管理自己组里的副本。
Agent 执行层
容器拉起:Agent CAS 抢占任务(scheduled → running)后构建 docker run 命令——注入 GPU 运行时参数、挂载模型路径、设置端口映射、注入分布式环境变量、启动容器、执行健康检查、上报 running。容器已存在时(唤醒场景)走恢复分支,带容器监视:容器自行退出时按对账同一口径推导结局上报,不再出现容器已死、任务挂 running 最长 24 小时的悬空态。
GPU 运行时参数:
| GPU 类型 | Docker 参数 | 环境变量 |
|---|---|---|
| NVIDIA | --gpus all | NVIDIA_VISIBLE_DEVICES=all |
| AMD / 海光 DCU | --device=/dev/kfd --device=/dev/dri --group-add=video --group-add=render | HIP_VISIBLE_DEVICES / ROCR_VISIBLE_DEVICES |
| MPS 分片 | 按分配结果注入可见设备与 CUDA_MPS 限额 | NVIDIA_VISIBLE_DEVICES / CUDA_VISIBLE_DEVICES |
环境变量注入有严格规则:用户 env 里的裸名项(不带 =)丢弃;GPU 可见性变量与任务标识变量由 Agent 独占,用户同名项被丢弃;VLLM_SERVER_DEV_MODE=1(开启 vLLM /sleep、/wake_up 端点,休眠唤醒依赖)只注入 vLLM 系引擎容器。
模型路径自动发现:model_path 非绝对路径时检查 /data/models/{name}/config.json,存在则自动挂载本地模型。绝对路径挂载必须先过节点卷白名单(allowed_volume_prefixes / denied_volume_prefixes)——白名单外的路径连存在性都不探,防止任意宿主目录被只读挂进租户容器。
日志:Agent 以 docker logs 增量拉取容器输出(单轮尾随 2000 行)并追加式上传服务端,前端任务详情实时查看。
文件分发:POST /api/v1/file-requests(request_type: list | download,target_path)对任务所在节点发起文件请求,Agent 异步执行、分块上传(默认 64MB/块),调用方轮询状态后流式取回完整文件。约束:路径必须是绝对路径、≤4096 字节、且必须落在该任务自己挂载的宿主机目录之内(按路径分段边界比对,挡 .. 逃逸)。
僵尸容器清理:每次心跳后比对服务端任务列表与本机 task-* 容器,不在列表中的容器强制删除(容器名格式校验防注入)。
服务操作
| 操作 | API | 适用状态 | 效果 |
|---|---|---|---|
| 休眠 | POST /api/v1/tasks/{id}/sleep | running | 卸载权重 + 停容器,释放显存(仍占名额与显存承诺记账) |
| 唤醒 | POST /api/v1/tasks/{id}/wake | sleeping | 恢复运行;借卡/节点离线返回 202 排队或换节点重建 |
| 停止 | POST /api/v1/tasks/{id}/stop | running | 完全终止容器,进 stopping → completed |
| 重试 | POST /api/v1/tasks/{id}?action=retry | failed | 重新调度失败任务 |
| 删除 | DELETE /api/v1/task-groups/{id} | completed/failed | 删除组与子任务,自动清理关联 ModelRoute 与端点 |
| 扩缩容 | PUT /api/v1/task-groups/{id}/scale | running | 手动调整副本数 |
| 伸缩策略 | PUT /api/v1/task-groups/{id}/scale-policy | — | 设置自动伸缩参数 |
| 滚动更新 | PUT /api/v1/task-groups/{id}/update | — | 换镜像/改配置;单独提交 health_check / idle_timeout 不触发滚动 |
| 调度窗口 | PUT /api/v1/task-groups/{id}/schedule-window | — | 配置闲时策略 |
列表卡片展示服务名、状态、GPU 卡数、并行策略、副本数区间、自动伸缩开关、API 访问开关、模型路由(引擎 + 版本)与运行时长;推理引擎从镜像名自动识别并在卡片标注。API Key 明文只在创建响应中出现一次,之后界面仅显示脱敏前缀,丢失通过端点更新接口轮换。
推理服务去重与跨租户复用(创建期)
创建推理服务时可以不真拉一个新容器,而是复用/唤醒一个已在跑同一模型的任务组。两个平台级开关(推理控制面板,默认都关):
| 开关 | 默认 | 语义 |
|---|---|---|
dedup_enabled(推理服务去重) | 关 | 开:命中时复用/唤醒已有组;关:每次新建独立容器 |
cross_tenant_dedup_enabled(跨租户共享) | 关 | 仅在去重开启时被问到;开:允许跨租户命中但仍要过共享租户组判定 |
匹配键是模型身份 + 镜像(model_path 相同且 image 相同),不是服务名称;只在 running/sleeping 状态的组里找。命中 running 直接复用、命中 sleeping 唤醒——但"命中 sleeping"会先查节点存活与借卡,不可承接时不做状态迁移、记 wake_requested 排队(换节点重建或等训练结束),响应带明确标记与文案,前端创建弹窗会给出对应提示(信息/成功/警告三档)。复用或唤醒都会为本次请求补建 ModelRoute:同租户按模型名判重,跨租户按(租户,模型名)为调用方单独建一条指向同一组的路由——否则"复用了却调不通"。
跨租户的两道闸:总闸 cross_tenant_dedup_enabled 为真,且两个租户同属至少一个共享租户组(平台管理员配置)。默认无组 = 完全隔离;授权发生在配置时刻而非任务提交时刻;查询出错一律按不同组处理(fail-closed)。这里的"共享"指同一引擎实例被多租户调用(复用容器),与"一卡多服务"的共卡(多个独立容器叠同一张卡)是两种不同机制,但受同一条共享租户组红线约束。
模型扫描与自动创建
扫描(Agent 侧):扫描目录来源为 Agent 配置 model_scan_dirs > 环境变量 HETEROFLOW_MODEL_SCAN_DIRS > 默认模型缓存目录(替换关系不是叠加),/data/models 无条件始终追加;目录下若存在 hub / models/hub / models 子目录自动追加(覆盖 HuggingFace hub 缓存布局)。支持两种格式:HuggingFace(目录含 config.json + tokenizer_config.json + 权重文件)与 GGUF(含 .gguf 文件,分片 GGUF 自动合并为一组)。全量扫描在 Agent 启动时执行,增量扫描每 12 次心跳(约 1 分钟)只查 mtime 变化的目录。结果随心跳上报,前端模型管理器可见并可一键注册(详见《模型管理》篇)。
一键建服务:模型管理"一键添加"路径按节点心跳上报的引擎可用性 + GPU 算力从模板链(vllm → sglang → llamacpp)选引擎与镜像,自动创建 TaskGroup(parallelism=split)、注册 ModelRoute、创建端点并生成 API Key,一步从模型文件到可调用服务。引擎模板链写入组,后续故障恢复可跨引擎 fallback 重建(如 vLLM 镜像损坏时降级 llama.cpp)。该路径已真机验证:vLLM 模板在多节点反复验证可用,P100 旧卡自动落 llama.cpp 兜底与真机"P100 与 vLLM latest 不兼容"的结论一致。
OpenAI 兼容调用
推理服务通过网关以 OpenAI 兼容格式对外服务,只改 Base URL 和 API Key 即可迁移既有客户端:
curl http://<server>:3333/v1/chat/completions \
-H "Authorization: Bearer sk-xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"model": "Qwen2.5-0.5B-Instruct", "messages": [{"role": "user", "content": "你好"}]}'- 端点:
/v1/chat/completions、/v1/completions、/v1/embeddings、/v1/models(列出当前 Key 可用的模型名——model_not_found排查的唯一权威清单)。 model填对外模型名(建服务时的model_name,未指定时为服务名称)。- Python SDK:
OpenAI(base_url="http://<server>:3333/v1", api_key="sk-..."),流式/非流式均可。 - 网关侧能力摘要:多副本负载均衡(round_robin / random / least_connections / engine_aware 四算法)、灰度路由(priority 分组 + weight 加权)、会话粘性、温度过滤、后端健康摘除、Token 用量按成员拆分核算、Key 级限流与额度熔断、幂等键、智能路由(model=auto)。完整说明见《推理网关》与《OpenAI 兼容 API》篇。
API 参考
创建推理服务——容器相关字段(镜像、GPU、模型路径、端口)都在 task 子对象里,任务组级别只放编排参数;把 image / gpu_required 写在顶层会被直接 400 并点名字段:
curl -X POST http://localhost:3333/api/v1/task-groups \
-H "Authorization: Bearer <jwt>" \
-H "Content-Type: application/json" \
-d '{
"name": "my-qwen-service", # 服务名称(管理展示名)
"model_name": "Qwen2.5-0.5B-Instruct", # 对外模型名:调用时 model 填这个;不传回退 name
"parallelism": "single", # 部署形态:single/tensor_parallel/split
"world_size": 1, # GPU 总数(split 下为副本数)
"enable_api": true, # 创建端点与 API Key(带 model_path 时强制开启)
"idle_timeout_minutes": 0, # 空闲自动休眠阈值,0=不休眠
"gpu_memory_utilization": 35, # 单卡显存承诺百分比 1-100(调度准入依据)
"task": {
"name": "my-qwen-service-worker",
"image": "vllm/vllm-openai:latest", # 引擎镜像;也支持 name@sha256:... digest 固定形态
"gpu_required": 1, # 卡数 1-64
"gpu_type": "", # 空=调度器自动匹配
"engine": "", # 可选显式引擎:vllm/sglang/llamacpp/mindie/trtllm/vllm-mtt
"model_path": "/data/models/qwen-0.5b", # 节点上的真实模型路径(推理必需)
"ports": [{ "container_port": 8000, "host_port": 8000 }] # host_port 可留 0 由端口池分配
},
"health_check": {
"type": "http", "path": "/health", "port": 8000,
"interval_seconds": 10, "timeout_seconds": 3,
"unhealthy_threshold": 3,
"start_period_seconds": 60 # 启动宽限期,零值兜底 30s;大模型建议 300 起
}
}'成功响应返回 id(tg- 前缀)、status: pending 与 endpoint(ep- 前缀);若创建了 API Key,明文只出现在这一次响应里。权重 / 优先级 / 负载均衡算法不在建组请求里,建组后走 PUT /api/v1/model-routes/{id} 调整;QoS 不可指定(由节点硬件能力推导)。
核心接口:GET/POST /api/v1/task-groups(列表/创建)、GET/DELETE /api/v1/task-groups/{id}(详情/删除)、PUT .../scale|scale-policy|update|schedule-window(扩缩/策略/更新/窗口)、POST /api/v1/tasks/{id}/sleep|wake|stop(生命周期操作)、GET /api/v1/gateway/endpoints(端点列表)、GET /api/v1/nodes/{id}/inference-template(节点可用引擎模板)、GET /v1/models(OpenAI 兼容模型清单)。
已知边界
1. SGLang 在两类环境不可用且平台如实处理:sm_75 及更早架构(CUTLASS/FlashInfer 不支持,启动即 KeyError);L20 + 驱动 580 环境官方镜像缺 nvcc,flashinfer JIT 编译必然失败。模板链已把链首调回 vLLM,SGLang 保留在链中供显式指定或仅装 SGLang 的节点。
2. TensorRT-LLM 只运行不编译:引擎须由客户离线编译并打进镜像,平台不提供编译链路;命令拼接与"不被自动选择"约束有单测钉住,无真机端到端验证;架构/TP 度不匹配要到 trtllm-serve 启动才报错。
3. MindIE(昇腾)与 vLLM-MTT(摩尔线程)探测与路由逻辑就绪,无对应真机端到端验证;MindIE 无流水线并行形态,相关组合建组即 400。
4. CPU 内存 KV Cache 卸载为实验性:版本边界按 vLLM 上游源码核对,真机端到端未核实;0.8.0-0.11.0 区间的镜像配卸载会被拒绝启动(上游该区间参数被默认引擎静默忽略)。
5. 2026-09-12 修复的 llama.cpp 三处命令拼接问题(ENTRYPOINT 重复拼接、参数连字符拼写、多卡 tensor_split 格式——修复前平台建的 llama.cpp 服务从未真正启动成功)只做过 argparse 层复现,修复后尚缺一次真机 GPU 端到端复验。
6. model_path 留空时接口层不拦截:建组成功但所有引擎的命令构建都会失败,容器反复"启动-失败-重建"(预算 5 次);直接调 API 的客户端需自行保证传入。
7. 删除服务后立即用同端口重建存在约 20 秒量级的竞态窗口(旧容器物理退出晚于端口账本释放);Agent 对端口绑定冲突做总计约 119 秒的退避重试兜底,非端口冲突的失败仍立即如实报错。
8. co_schedule 字段在 HTTP 层不开放(传了 400),一卡多服务实际由显存占用比 + 容器数上限两层机制承担;休眠中的服务仍占推理容器名额与显存承诺。
9. 端点 external_port 字段仅做范围校验,当前无运行时消费(网关不据此转发);网关连后端使用任务自己的 host_port,任务不声明端口映射该后端会被跳过。
10. 推理任务的容量/显存记账判据是"任务有端口定义",与超时豁免用的 task_type 权威字段不是同一口径——建了 inference 类型却完全不配端口的组不进入显存记账。
<!-- 状态标注说明:本文能力状态口径——"真机验证"指在真实 GPU 节点端到端运行并确认(vLLM 多节点反复验证、llama.cpp P100 兼容镜像自建验证、450 并发网关压测);"已实现"指代码合入且有单元/集成测试覆盖但未做真机端到端;"适配"指探测与路由逻辑就绪、等待对应卡型真机;"实验性"指版本边界经上游源码核对、真机行为待验证。引擎矩阵与 GPU 架构兼容速查依据平台《推理引擎兼容性》文档(实测节点含 NVIDIA T4/P100/A10/A800/L20 等)。 -->