V2 独立版愿景

推理服务

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_size GPU 总数、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,未传回退服务名称);priority 0 最高(非运营者写负数 400);weight 0-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:80008000宿主机端口由端口池(默认 8000-8999)自动分配避冲突;推理服务务必声明端口映射,否则网关无法回连后端
运行超时 timeout否秒,0 = 不限0推理任务豁免运行超时,此项对推理实际不生效
空闲超时 idle_timeout_minutes否分钟,0 = 不休眠0请求队列连续空闲超过该时长自动休眠;建组后可单独修改,不触发滚动更新

模型与 API 配置

配置项默认说明
模型路径 task.model_path—节点上的真实路径。接口层不强制,但所有引擎的命令构建都要求非空——留空会建组成功、容器起不来,前端已做提交前必填拦截
容器内路径 containerPath/model模型挂载点,自动生成只读卷挂载
启用缓存 enableCachetrue注入 HETEROFLOW_MODEL_CACHE=true,Agent 按缓存目录扫描本地模型
启用 API enable_apitrue自动创建端点与 Key;带 model_path 时强制开启

模型路径还有两个副作用:调度器优先把任务调到已有该模型的节点;建组时按路径估算显存做上限预检(估不出则跳过)。模型路径必须通过节点卷白名单校验才会被挂载,白名单外的路径不探查不挂载,容器内找不到模型会显式失败。

健康检查

字段默认说明
typehttphttp / tcp 两种
path/healthHTTP GET 路径
interval_seconds10检查间隔
timeout_seconds3单次超时
unhealthy_threshold3连续失败达阈值判失败并杀容器
start_period_seconds60(零值兜底 30)启动宽限期:从容器真正启动起算,拉镜像/建容器/端口重试不占宽限;期内失败不计数。大模型建议 300 秒起;建组后可单独修改救活误杀循环中的服务,不触发滚动更新

这是任务组容器级健康检查(Agent 执行、判失败杀容器),与网关端点级探测(只摘路由不杀容器)是两套机制。

资源限额:一卡多服务

机制取值说明
GPU 显存占用比 gpu_memory_utilization1-100,缺省回退全局配置再缺省 90写进组内每个任务,Agent 据此拼 vLLM --gpu-memory-utilization;前端按模型体积给建议档位(<2GB 建议 20%-30%、<15GB 30%-50%、<40GB 50%-70%、更大 85%-95%,只提示不锁定)
按卡显存记账与准入卡上所有持卡推理任务的百分比之和 ≤ 100新任务要求已承诺 + 自己 ≤ 100,超了排队不硬塞;候选卡优先选剩余最多的
单节点推理容器数上限 max_inference_per_node4,可调 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(单卡)单容器单卡推理11gpuRequired
tensor_parallel(多卡 TP)单容器多卡张量并行,所有 GPU 同一节点强制 1world_sizegpuRequired
split(多副本 Split)多副本独立,每副本 1 卡自动计算world_size1(固定)
data_parallel数据并行(与 split 同为副本型并行的历史拼法,下游逻辑两者兼容)⌈world_size/8⌉world_sizegpuRequired
pipeline_parallel流水线并行,host 网络模式world_sizeworld_sizegpuRequired

多卡张量并行的共享内存细节: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 时严格按该类型选节点——存在但离线则等待不回落别家卡,租户可见范围内没有该类型节点则明确拒绝(文案区分"存在但不可用"与"没有节点上报该类型")。

推理引擎体系

引擎支持矩阵

引擎支持卡型优先级链位置状态口径
vLLMnvidia(CC≥7.5)/amd/hygonnvidia/amd/hygon 链首真机验证,多节点反复验证
SGLangnvidia(CC≥sm_80)/amd/hygonnvidia 链第二已实现(T4 sm_75 实测不兼容 KeyError sm_75;L20+驱动 580 环境 lmsysorg 官方镜像缺 nvcc 起不来——2026-09-12 因此把链首调回 vLLM)
llama.cppnvidia(含 P100 CC 6.0 老卡)/amd/hygon各链兜底真机验证,P100 兼容推理镜像自建验证
MindIEascend 专用ascend 链唯一适配(探测逻辑就绪,无昇腾真机)
vLLM-MTTmusa 摩尔线程专用musa 链唯一适配(无摩尔线程真机)
TensorRT-LLMnvidia(仅显式指定)不进自动链(ExplicitOnly)适配(客户自带已编译引擎,平台只运行不编译;单测钉住、无真机)
Transformers—已退役K50 起各入口 400(从未提供真推理,桩服务)

GPU 架构兼容速查

架构档vLLMSGLangllama.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 视为全天在窗内,不会误触发)
actionpause 暂停调度 / scale 缩容到 scale_to / stop 停止所有任务
scale_toscale 动作的目标副本数

执行器 leader-only 每 60 秒检查:窗口外执行 action(触发前把当前副本数存进标签,避免中途重启丢失恢复目标,动作幂等不重复触发);回到窗口内 pause/scale 自动恢复到保存的副本数,stop 是单向动作不自动恢复;窗口被关闭但动作已生效时下一轮自动恢复。窗口外期间副本数由窗口执行器接管,AutoScaler 跳过该组。窗口恢复与手动扩容扩出的新副本归属组创建者(而非系统操作者),保证只看得见自己任务的成员也能管理自己组里的副本。

Agent 执行层

容器拉起:Agent CAS 抢占任务(scheduled → running)后构建 docker run 命令——注入 GPU 运行时参数、挂载模型路径、设置端口映射、注入分布式环境变量、启动容器、执行健康检查、上报 running。容器已存在时(唤醒场景)走恢复分支,带容器监视:容器自行退出时按对账同一口径推导结局上报,不再出现容器已死、任务挂 running 最长 24 小时的悬空态。

GPU 运行时参数:

GPU 类型Docker 参数环境变量
NVIDIA--gpus allNVIDIA_VISIBLE_DEVICES=all
AMD / 海光 DCU--device=/dev/kfd --device=/dev/dri --group-add=video --group-add=renderHIP_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}/sleeprunning卸载权重 + 停容器,释放显存(仍占名额与显存承诺记账)
唤醒POST /api/v1/tasks/{id}/wakesleeping恢复运行;借卡/节点离线返回 202 排队或换节点重建
停止POST /api/v1/tasks/{id}/stoprunning完全终止容器,进 stopping → completed
重试POST /api/v1/tasks/{id}?action=retryfailed重新调度失败任务
删除DELETE /api/v1/task-groups/{id}completed/failed删除组与子任务,自动清理关联 ModelRoute 与端点
扩缩容PUT /api/v1/task-groups/{id}/scalerunning手动调整副本数
伸缩策略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 等)。 -->